diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0701552155..eb08bc606e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -68,9 +68,9 @@ set(OBJECTS_VERSION "1.2.6")
set(OBJECTS_URL "https://github.com/OpenRCT2/objects/releases/download/v${OBJECTS_VERSION}/objects.zip")
set(OBJECTS_SHA1 "cd86dd2e42edb513b18293ef7ae52a93a7cdfc57")
-set(REPLAYS_VERSION "0.0.64")
+set(REPLAYS_VERSION "0.0.65")
set(REPLAYS_URL "https://github.com/OpenRCT2/replays/releases/download/v${REPLAYS_VERSION}/replays.zip")
-set(REPLAYS_SHA1 "E8DA520B3462090D894F0E7B844C5AB646BFB12E")
+set(REPLAYS_SHA1 "CE3796062BF3FDDC3FA3C8FC2F0DDD8EE9314174")
option(FORCE32 "Force 32-bit build. It will add `-m32` to compiler flags.")
option(WITH_TESTS "Build tests")
diff --git a/data/language/en-GB.txt b/data/language/en-GB.txt
index ed9b41a46a..ca7d4cbfa5 100644
--- a/data/language/en-GB.txt
+++ b/data/language/en-GB.txt
@@ -3657,6 +3657,8 @@ STR_6465 :Intensity: {COMMA2DP32}
STR_6466 :Nausea
STR_6467 :Nausea: {COMMA2DP32}
STR_6468 :Not Yet Known
+STR_6469 :Adjust smaller area of patrol area
+STR_6470 :Adjust larger area of patrol area
#############
# Scenarios #
diff --git a/distribution/changelog.txt b/distribution/changelog.txt
index 91403b5ba0..28c8dcecb7 100644
--- a/distribution/changelog.txt
+++ b/distribution/changelog.txt
@@ -25,6 +25,7 @@
- Improved: [#16251] openrct2.d.ts: removed unused LabelWidget.onChange property.
- Improved: [#16258] Increased image limit in the engine.
- Improved: [#16408] Improve --version cli option to report more compatibility information.
+- Improved: [#16740] Allow staff patrol areas to be defined with individual tiles rather than groups of 4x4.
- Improved: [#16764] [Plugin] Add hook 'map.save', called before the map is about is saved.
- Change: [#14484] Make the Heartline Twister coaster ratings a little bit less hateful.
- Change: [#16077] When importing SV6 files, the RCT1 land types are only added when they were actually used.
diff --git a/openrct2.proj b/openrct2.proj
index 13b86618b9..23a46ecce1 100644
--- a/openrct2.proj
+++ b/openrct2.proj
@@ -48,8 +48,8 @@
304d13a126c15bf2c86ff13b81a2f2cc1856ac8d
https://github.com/OpenRCT2/objects/releases/download/v1.2.6/objects.zip
cd86dd2e42edb513b18293ef7ae52a93a7cdfc57
- https://github.com/OpenRCT2/replays/releases/download/v0.0.64/replays.zip
- E8DA520B3462090D894F0E7B844C5AB646BFB12E
+ https://github.com/OpenRCT2/replays/releases/download/v0.0.65/replays.zip
+ CE3796062BF3FDDC3FA3C8FC2F0DDD8EE9314174
diff --git a/src/openrct2-ui/interface/Theme.cpp b/src/openrct2-ui/interface/Theme.cpp
index f0a9905b86..65ef37be3b 100644
--- a/src/openrct2-ui/interface/Theme.cpp
+++ b/src/openrct2-ui/interface/Theme.cpp
@@ -174,6 +174,7 @@ static constexpr const WindowThemeDesc WindowThemeDescriptors[] =
{ THEME_WC(WC_TITLE_EDITOR), STR_TITLE_EDITOR_TITLE, COLOURS_3(COLOUR_GREY, COLOUR_OLIVE_GREEN, COLOUR_OLIVE_GREEN ) },
{ THEME_WC(WC_TILE_INSPECTOR), STR_TILE_INSPECTOR_TITLE, COLOURS_2(COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE ) },
{ THEME_WC(WC_VIEW_CLIPPING), STR_VIEW_CLIPPING_TITLE, COLOURS_1(COLOUR_DARK_GREEN ) },
+ { THEME_WC(WC_PATROL_AREA), STR_SET_PATROL_AREA, COLOURS_3(COLOUR_LIGHT_PURPLE, COLOUR_LIGHT_PURPLE, COLOUR_LIGHT_PURPLE ) },
{ THEME_WC(WC_ABOUT), STR_ABOUT, COLOURS_2(COLOUR_GREY, COLOUR_LIGHT_BLUE ) },
{ THEME_WC(WC_CHANGELOG), STR_CHANGELOG_TITLE, COLOURS_2(COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE ) },
{ THEME_WC(WC_MULTIPLAYER), STR_MULTIPLAYER, COLOURS_3(COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE ) },
diff --git a/src/openrct2-ui/libopenrct2ui.vcxproj b/src/openrct2-ui/libopenrct2ui.vcxproj
index b8a3649b3c..1103d25e87 100644
--- a/src/openrct2-ui/libopenrct2ui.vcxproj
+++ b/src/openrct2-ui/libopenrct2ui.vcxproj
@@ -156,6 +156,7 @@
+
diff --git a/src/openrct2-ui/windows/PatrolArea.cpp b/src/openrct2-ui/windows/PatrolArea.cpp
new file mode 100644
index 0000000000..cd8ade51fc
--- /dev/null
+++ b/src/openrct2-ui/windows/PatrolArea.cpp
@@ -0,0 +1,303 @@
+/*****************************************************************************
+ * Copyright (c) 2014-2020 OpenRCT2 developers
+ *
+ * For a complete list of all authors, please refer to contributors.md
+ * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
+ *
+ * OpenRCT2 is licensed under the GNU General Public License version 3.
+ *****************************************************************************/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static constexpr const rct_string_id WINDOW_TITLE = STR_SET_PATROL_AREA;
+static constexpr const int32_t WH = 54;
+static constexpr const int32_t WW = 104;
+
+enum WindowPatrolAreaWidgetIdx
+{
+ WIDX_BACKGROUND,
+ WIDX_TITLE,
+ WIDX_CLOSE,
+ WIDX_PREVIEW,
+ WIDX_DECREMENT,
+ WIDX_INCREMENT,
+};
+
+// clang-format off
+static rct_widget PatrolAreaWidgets[] = {
+ WINDOW_SHIM(WINDOW_TITLE, WW, WH),
+ MakeWidget ({27, 17}, {44, 32}, WindowWidgetType::ImgBtn, WindowColour::Primary , SPR_LAND_TOOL_SIZE_0 ), // preview box
+ MakeRemapWidget({28, 18}, {16, 16}, WindowWidgetType::TrnBtn, WindowColour::Tertiary, SPR_LAND_TOOL_DECREASE, STR_ADJUST_SMALLER_PATROL_AREA_TIP), // decrement size
+ MakeRemapWidget({54, 32}, {16, 16}, WindowWidgetType::TrnBtn, WindowColour::Tertiary, SPR_LAND_TOOL_INCREASE, STR_ADJUST_LARGER_PATROL_AREA_TIP ), // increment size
+ WIDGETS_END,
+};
+// clang-format on
+
+class PatrolAreaWindow final : public Window
+{
+public:
+ void OnOpen() override
+ {
+ widgets = PatrolAreaWidgets;
+ hold_down_widgets = (1ULL << WIDX_INCREMENT) | (1ULL << WIDX_DECREMENT);
+ WindowInitScrollWidgets(this);
+ window_push_others_below(this);
+ gLandToolSize = 4;
+ }
+
+ void OnClose() override
+ {
+ // If the tool wasn't changed, turn tool off
+ if (PatrolAreaToolIsActive())
+ tool_cancel();
+ }
+
+ void OnMouseUp(rct_widgetindex widgetIndex) override
+ {
+ switch (widgetIndex)
+ {
+ case WIDX_CLOSE:
+ Close();
+ break;
+ case WIDX_PREVIEW:
+ InputSize();
+ break;
+ }
+ }
+
+ void OnMouseDown(rct_widgetindex widgetIndex) override
+ {
+ switch (widgetIndex)
+ {
+ case WIDX_DECREMENT:
+ gLandToolSize = std::max(MINIMUM_TOOL_SIZE, gLandToolSize - 1);
+ Invalidate();
+ break;
+ case WIDX_INCREMENT:
+ gLandToolSize = std::min(MAXIMUM_TOOL_SIZE, gLandToolSize + 1);
+ Invalidate();
+ break;
+ }
+ }
+
+ void OnTextInput(rct_widgetindex widgetIndex, std::string_view text) override
+ {
+ if (text.empty())
+ return;
+
+ if (widgetIndex != WIDX_PREVIEW)
+ return;
+
+ const auto res = String::Parse(text);
+ if (res.has_value())
+ {
+ int32_t size;
+ size = res.value();
+ size = std::max(MINIMUM_TOOL_SIZE, size);
+ size = std::min(MAXIMUM_TOOL_SIZE, size);
+ gLandToolSize = size;
+ Invalidate();
+ }
+ }
+
+ void OnUpdate() override
+ {
+ // Close window if another tool is open or staff window gets closed
+ if (!PatrolAreaToolIsActive() || !IsStaffWindowOpen())
+ {
+ Close();
+ }
+ }
+
+ void OnPrepareDraw() override
+ {
+ SetWidgetPressed(WIDX_PREVIEW, true);
+ PatrolAreaWidgets[WIDX_PREVIEW].image = LandTool::SizeToSpriteIndex(gLandToolSize);
+ }
+
+ void OnDraw(rct_drawpixelinfo& dpi) override
+ {
+ DrawWidgets(dpi);
+
+ // Draw number for tool sizes bigger than 7
+ if (gLandToolSize > MAX_TOOL_SIZE_WITH_SPRITE)
+ {
+ auto screenCoords = ScreenCoordsXY{ windowPos.x + PatrolAreaWidgets[WIDX_PREVIEW].midX(),
+ windowPos.y + PatrolAreaWidgets[WIDX_PREVIEW].midY() };
+ auto ft = Formatter();
+ ft.Add(gLandToolSize);
+ DrawTextBasic(&dpi, screenCoords - ScreenCoordsXY{ 0, 2 }, STR_LAND_TOOL_SIZE_VALUE, ft, { TextAlignment::CENTRE });
+ }
+ }
+
+ void OnToolUpdate(rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords) override
+ {
+ auto mapTile = GetBestCoordsFromPos(screenCoords);
+ if (!mapTile)
+ return;
+
+ auto stateChanged = false;
+ if (!(gMapSelectFlags & MAP_SELECT_FLAG_ENABLE))
+ stateChanged = true;
+
+ if (gMapSelectType != MAP_SELECT_TYPE_FULL)
+ stateChanged = true;
+
+ auto toolSize = std::max(1, gLandToolSize);
+ auto toolLength = (toolSize - 1) * 32;
+
+ // Move to tool bottom left
+ mapTile->x -= (toolSize - 1) * 16;
+ mapTile->y -= (toolSize - 1) * 16;
+ mapTile = mapTile->ToTileStart();
+ auto posA = *mapTile;
+ mapTile->x += toolLength;
+ mapTile->y += toolLength;
+ auto posB = *mapTile;
+ if (gMapSelectPositionA != posA || gMapSelectPositionB != posB)
+ stateChanged = true;
+
+ if (stateChanged)
+ {
+ // Invalidate previous area
+ map_invalidate_selection_rect();
+
+ // Update and invalidate new area
+ gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE;
+ gMapSelectType = MAP_SELECT_TYPE_FULL;
+ gMapSelectPositionA = posA;
+ gMapSelectPositionB = posB;
+ map_invalidate_selection_rect();
+ }
+ }
+
+ void OnToolAbort(rct_widgetindex widgetIndex) override
+ {
+ hide_gridlines();
+ ClearPatrolAreaToRender();
+ gfx_invalidate_screen();
+ }
+
+ void OnToolDown(rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords) override
+ {
+ auto mapTile = GetBestCoordsFromPos(screenCoords);
+ if (mapTile)
+ {
+ auto staff = GetEntity(_staffId);
+ if (staff != nullptr)
+ {
+ _mode = staff->IsPatrolAreaSet(*mapTile) ? StaffSetPatrolAreaMode::Unset : StaffSetPatrolAreaMode::Set;
+ }
+ }
+
+ OnToolDrag(widgetIndex, screenCoords);
+ }
+
+ void OnToolDrag(rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords) override
+ {
+ auto staff = GetEntity(_staffId);
+ if (staff != nullptr)
+ {
+ MapRange range(gMapSelectPositionA, gMapSelectPositionB);
+ auto staffSetPatrolAreaAction = StaffSetPatrolAreaAction(_staffId, range, _mode);
+ GameActions::Execute(&staffSetPatrolAreaAction);
+ }
+ }
+
+ EntityId GetStaffId() const
+ {
+ return _staffId;
+ }
+
+ void SetStaffId(EntityId staffId)
+ {
+ _staffId = staffId;
+ EnableTool();
+ }
+
+private:
+ EntityId _staffId;
+ StaffSetPatrolAreaMode _mode;
+
+ void EnableTool()
+ {
+ if (PatrolAreaToolIsActive())
+ {
+ SetPatrolAreaToRender(_staffId);
+ gfx_invalidate_screen();
+ }
+ else
+ {
+ show_gridlines();
+ if (!tool_set(this, 0, Tool::WalkDown))
+ {
+ input_set_flag(INPUT_FLAG_6, true);
+ show_gridlines();
+ SetPatrolAreaToRender(_staffId);
+ gfx_invalidate_screen();
+ }
+ }
+ }
+
+ void InputSize()
+ {
+ Formatter ft;
+ ft.Add(MINIMUM_TOOL_SIZE);
+ ft.Add(MAXIMUM_TOOL_SIZE);
+ WindowTextInputOpen(this, WIDX_PREVIEW, STR_SELECTION_SIZE, STR_ENTER_SELECTION_SIZE, ft, STR_NONE, STR_NONE, 3);
+ }
+
+ bool PatrolAreaToolIsActive()
+ {
+ if (!(input_test_flag(INPUT_FLAG_TOOL_ACTIVE)))
+ return false;
+ if (gCurrentToolWidget.window_classification != WC_PATROL_AREA)
+ return false;
+ return true;
+ }
+
+ bool IsStaffWindowOpen()
+ {
+ // If staff window for this patrol area was closed, tool is no longer active
+ auto staffWindow = window_find_by_number(WC_PEEP, _staffId);
+ return staffWindow != nullptr;
+ }
+
+ std::optional GetBestCoordsFromPos(const ScreenCoordsXY& pos)
+ {
+ auto coords = footpath_get_coordinates_from_pos(pos, nullptr, nullptr);
+ return coords.IsNull() ? std::nullopt : std::make_optional(coords);
+ }
+};
+
+rct_window* WindowPatrolAreaOpen(EntityId staffId)
+{
+ auto w = WindowFocusOrCreate(WC_PATROL_AREA, ScreenCoordsXY(context_get_width() - WW, 29), WW, WH, 0);
+ if (w != nullptr)
+ {
+ w->SetStaffId(staffId);
+ }
+ return w;
+}
+
+EntityId WindowPatrolAreaGetCurrentStaffId()
+{
+ auto current = reinterpret_cast(window_find_by_class(WC_PATROL_AREA));
+ return current != nullptr ? current->GetStaffId() : EntityId();
+}
diff --git a/src/openrct2-ui/windows/Staff.cpp b/src/openrct2-ui/windows/Staff.cpp
index 73d7166c38..5172d72cc9 100644
--- a/src/openrct2-ui/windows/Staff.cpp
+++ b/src/openrct2-ui/windows/Staff.cpp
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -134,8 +135,6 @@ static void WindowStaffOverviewPaint(rct_window* w, rct_drawpixelinfo* dpi);
static void WindowStaffOverviewTabPaint(rct_window* w, rct_drawpixelinfo* dpi);
static void WindowStaffOverviewToolUpdate(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
static void WindowStaffOverviewToolDown(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
-static void WindowStaffOverviewToolDrag(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
-static void WindowStaffOverviewToolUp(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
static void WindowStaffOverviewToolAbort(rct_window* w, rct_widgetindex widgetIndex);
static void WindowStaffOverviewTextInput(rct_window* w, rct_widgetindex widgetIndex, char* text);
static void WindowStaffOverviewViewportRotate(rct_window* w);
@@ -169,8 +168,6 @@ static rct_window_event_list window_staff_overview_events([](auto& events) {
events.update = &WindowStaffOverviewUpdate;
events.tool_update = &WindowStaffOverviewToolUpdate;
events.tool_down = &WindowStaffOverviewToolDown;
- events.tool_drag = &WindowStaffOverviewToolDrag;
- events.tool_up = &WindowStaffOverviewToolUp;
events.tool_abort = &WindowStaffOverviewToolAbort;
events.text_input = &WindowStaffOverviewTextInput;
events.viewport_rotate = &WindowStaffOverviewViewportRotate;
@@ -208,15 +205,6 @@ static rct_window_event_list* window_staff_page_events[] = {
static EntertainerCostume _availableCostumes[static_cast(EntertainerCostume::Count)];
-enum class PatrolAreaValue
-{
- UNSET = 0,
- SET = 1,
- NONE = -1,
-};
-
-static PatrolAreaValue _staffPatrolAreaPaintValue = PatrolAreaValue::NONE;
-
static Staff* GetStaff(rct_window* w)
{
auto staff = GetEntity(EntityId::FromUnderlying(w->number));
@@ -541,17 +529,22 @@ void WindowStaffOverviewDropdown(rct_window* w, rct_widgetindex widgetIndex, int
return;
}
+ window_close_by_class(WC_PATROL_AREA);
+
auto staffSetPatrolAreaAction = StaffSetPatrolAreaAction(
peep->sprite_index, {}, StaffSetPatrolAreaMode::ClearAll);
GameActions::Execute(&staffSetPatrolAreaAction);
}
else
{
- if (!tool_set(w, widgetIndex, Tool::WalkDown))
+ auto staffId = EntityId::FromUnderlying(w->number);
+ if (WindowPatrolAreaGetCurrentStaffId() == staffId)
{
- show_gridlines();
- gStaffDrawPatrolAreas = w->number;
- gfx_invalidate_screen();
+ window_close_by_class(WC_PATROL_AREA);
+ }
+ else
+ {
+ WindowPatrolAreaOpen(staffId);
}
}
break;
@@ -894,6 +887,7 @@ void WindowStaffOverviewInvalidate(rct_window* w)
window_staff_overview_widgets[WIDX_PICKUP].left = w->width - 25;
window_staff_overview_widgets[WIDX_PICKUP].right = w->width - 2;
+ WidgetSetPressed(w, WIDX_PATROL, WindowPatrolAreaGetCurrentStaffId() == peep->sprite_index);
window_staff_overview_widgets[WIDX_PATROL].left = w->width - 25;
window_staff_overview_widgets[WIDX_PATROL].right = w->width - 2;
@@ -1173,96 +1167,26 @@ void WindowStaffOverviewToolUpdate(rct_window* w, rct_widgetindex widgetIndex, c
*/
void WindowStaffOverviewToolDown(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
{
+ if (widgetIndex != WIDX_PICKUP)
+ return;
+
const auto staffEntityId = EntityId::FromUnderlying(w->number);
-
- if (widgetIndex == WIDX_PICKUP)
- {
- TileElement* tileElement;
- auto destCoords = footpath_get_coordinates_from_pos({ screenCoords.x, screenCoords.y + 16 }, nullptr, &tileElement);
-
- if (destCoords.IsNull())
- return;
-
- PeepPickupAction pickupAction{
- PeepPickupType::Place, staffEntityId, { destCoords, tileElement->GetBaseZ() }, network_get_current_player_id()
- };
- pickupAction.SetCallback([](const GameAction* ga, const GameActions::Result* result) {
- if (result->Error != GameActions::Status::Ok)
- return;
- tool_cancel();
- gPickupPeepImage = ImageId();
- });
- GameActions::Execute(&pickupAction);
- }
- else if (widgetIndex == WIDX_PATROL)
- {
- auto destCoords = footpath_get_coordinates_from_pos(screenCoords, nullptr, nullptr);
-
- if (destCoords.IsNull())
- return;
-
- auto staff = TryGetEntity(staffEntityId);
- if (staff == nullptr)
- return;
-
- if (staff->IsPatrolAreaSet(destCoords))
- {
- _staffPatrolAreaPaintValue = PatrolAreaValue::UNSET;
- }
- else
- {
- _staffPatrolAreaPaintValue = PatrolAreaValue::SET;
- }
- auto staffSetPatrolAreaAction = StaffSetPatrolAreaAction(
- staffEntityId, destCoords,
- _staffPatrolAreaPaintValue == PatrolAreaValue::SET ? StaffSetPatrolAreaMode::Set : StaffSetPatrolAreaMode::Unset);
- GameActions::Execute(&staffSetPatrolAreaAction);
- }
-}
-
-void WindowStaffOverviewToolDrag(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
-{
- if (widgetIndex != WIDX_PATROL)
- return;
-
- if (network_get_mode() != NETWORK_MODE_NONE)
- return;
-
- // This works only for singleplayer if the game_do_command can not be prevented
- // to send packets more often than patrol area is updated.
-
- if (_staffPatrolAreaPaintValue == PatrolAreaValue::NONE)
- return; // Do nothing if we do not have a paintvalue(this should never happen)
-
- auto destCoords = footpath_get_coordinates_from_pos(screenCoords, nullptr, nullptr);
+ TileElement* tileElement;
+ auto destCoords = footpath_get_coordinates_from_pos({ screenCoords.x, screenCoords.y + 16 }, nullptr, &tileElement);
if (destCoords.IsNull())
return;
- const auto staffEntityId = EntityId::FromUnderlying(w->number);
-
- auto* staff = TryGetEntity(staffEntityId);
- if (staff == nullptr)
- return;
-
- bool patrolAreaValue = staff->IsPatrolAreaSet(destCoords);
- if (_staffPatrolAreaPaintValue == PatrolAreaValue::SET && patrolAreaValue)
- return; // Since area is already the value we want, skip...
- if (_staffPatrolAreaPaintValue == PatrolAreaValue::UNSET && !patrolAreaValue)
- return; // Since area is already the value we want, skip...
-
- auto staffSetPatrolAreaAction = StaffSetPatrolAreaAction(
- staffEntityId, destCoords,
- _staffPatrolAreaPaintValue == PatrolAreaValue::SET ? StaffSetPatrolAreaMode::Set : StaffSetPatrolAreaMode::Unset);
- GameActions::Execute(&staffSetPatrolAreaAction);
-}
-
-void WindowStaffOverviewToolUp(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
-{
- if (widgetIndex != WIDX_PATROL)
- return;
-
- _staffPatrolAreaPaintValue = PatrolAreaValue::NONE;
+ PeepPickupAction pickupAction{
+ PeepPickupType::Place, staffEntityId, { destCoords, tileElement->GetBaseZ() }, network_get_current_player_id()
+ };
+ pickupAction.SetCallback([](const GameAction* ga, const GameActions::Result* result) {
+ if (result->Error != GameActions::Status::Ok)
+ return;
+ tool_cancel();
+ gPickupPeepImage = ImageId();
+ });
+ GameActions::Execute(&pickupAction);
}
/**
@@ -1271,20 +1195,14 @@ void WindowStaffOverviewToolUp(rct_window* w, rct_widgetindex widgetIndex, const
*/
void WindowStaffOverviewToolAbort(rct_window* w, rct_widgetindex widgetIndex)
{
- if (widgetIndex == WIDX_PICKUP)
- {
- PeepPickupAction pickupAction{ PeepPickupType::Cancel,
- EntityId::FromUnderlying(w->number),
- { w->picked_peep_old_x, 0, 0 },
- network_get_current_player_id() };
- GameActions::Execute(&pickupAction);
- }
- else if (widgetIndex == WIDX_PATROL)
- {
- hide_gridlines();
- gStaffDrawPatrolAreas = 0xFFFF;
- gfx_invalidate_screen();
- }
+ if (widgetIndex != WIDX_PICKUP)
+ return;
+
+ PeepPickupAction pickupAction{ PeepPickupType::Cancel,
+ EntityId::FromUnderlying(w->number),
+ { w->picked_peep_old_x, 0, 0 },
+ network_get_current_player_id() };
+ GameActions::Execute(&pickupAction);
}
/* rct2: 0x6BDFED */
diff --git a/src/openrct2-ui/windows/StaffList.cpp b/src/openrct2-ui/windows/StaffList.cpp
index a0e9801d58..a42101ea47 100644
--- a/src/openrct2-ui/windows/StaffList.cpp
+++ b/src/openrct2-ui/windows/StaffList.cpp
@@ -22,6 +22,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -141,7 +142,7 @@ public:
if (!tool_set(this, WIDX_STAFF_LIST_SHOW_PATROL_AREA_BUTTON, Tool::Crosshair))
{
show_gridlines();
- gStaffDrawPatrolAreas = _selectedTab | 0x8000;
+ SetPatrolAreaToRender(GetSelectedStaffType());
gfx_invalidate_screen();
}
break;
@@ -471,7 +472,7 @@ public:
{
hide_gridlines();
tool_cancel();
- gStaffDrawPatrolAreas = 0xFFFF;
+ ClearPatrolAreaToRender();
gfx_invalidate_screen();
}
}
@@ -593,7 +594,7 @@ private:
if (footpathCoords.IsNull())
return nullptr;
- auto isPatrolAreaSet = staff_is_patrol_area_set_for_type(GetSelectedStaffType(), footpathCoords);
+ auto isPatrolAreaSet = IsPatrolAreaSetForStaffType(GetSelectedStaffType(), footpathCoords);
Peep* closestPeep = nullptr;
auto closestPeepDistance = std::numeric_limits::max();
diff --git a/src/openrct2-ui/windows/Themes.cpp b/src/openrct2-ui/windows/Themes.cpp
index 6ef1cb0069..5af77f7cbd 100644
--- a/src/openrct2-ui/windows/Themes.cpp
+++ b/src/openrct2-ui/windows/Themes.cpp
@@ -195,6 +195,7 @@ static rct_windowclass window_themes_tab_3_classes[] = {
WC_TRACK_DESIGN_PLACE,
WC_CONSTRUCT_RIDE,
WC_TRACK_DESIGN_LIST,
+ WC_PATROL_AREA,
};
static rct_windowclass window_themes_tab_4_classes[] = {
diff --git a/src/openrct2-ui/windows/Window.h b/src/openrct2-ui/windows/Window.h
index 362e914a10..ae814db5d4 100644
--- a/src/openrct2-ui/windows/Window.h
+++ b/src/openrct2-ui/windows/Window.h
@@ -10,6 +10,7 @@
#pragma once
#include
+#include
#include
#include
#include
@@ -200,6 +201,8 @@ void WindowTooltipOpen(rct_window* widgetWindow, rct_widgetindex widgetIndex, co
void WindowTooltipClose();
rct_window* WindowSceneryScatterOpen();
+rct_window* WindowPatrolAreaOpen(EntityId staffId);
+EntityId WindowPatrolAreaGetCurrentStaffId();
// clang-format off
#define WINDOW_SHIM_RAW(TITLE, WIDTH, HEIGHT, CLOSE_STR) \
diff --git a/src/openrct2/Editor.cpp b/src/openrct2/Editor.cpp
index b2b6d8af7d..e4a38f5d7b 100644
--- a/src/openrct2/Editor.cpp
+++ b/src/openrct2/Editor.cpp
@@ -23,6 +23,7 @@
#include "entity/EntityList.h"
#include "entity/EntityRegistry.h"
#include "entity/Guest.h"
+#include "entity/PatrolArea.h"
#include "entity/Staff.h"
#include "interface/Viewport.h"
#include "interface/Window_internal.h"
@@ -350,7 +351,7 @@ namespace Editor
}
ResetAllEntities();
- staff_reset_modes();
+ UpdateConsolidatedPatrolAreas();
gNumGuestsInPark = 0;
gNumGuestsHeadingForPark = 0;
gNumGuestsInParkLastWeek = 0;
diff --git a/src/openrct2/Game.cpp b/src/openrct2/Game.cpp
index 9baa6162ea..b0f0739be9 100644
--- a/src/openrct2/Game.cpp
+++ b/src/openrct2/Game.cpp
@@ -27,6 +27,7 @@
#include "core/FileScanner.h"
#include "core/Path.hpp"
#include "entity/EntityRegistry.h"
+#include "entity/PatrolArea.h"
#include "entity/Peep.h"
#include "entity/Staff.h"
#include "interface/Colour.h"
@@ -448,7 +449,7 @@ void game_fix_save_vars()
// Fix gParkEntrance locations for which the tile_element no longer exists
fix_park_entrance_locations();
- staff_update_greyed_patrol_areas();
+ UpdateConsolidatedPatrolAreas();
}
void game_load_init()
diff --git a/src/openrct2/GameState.cpp b/src/openrct2/GameState.cpp
index 3a9d96af52..08a196cefa 100644
--- a/src/openrct2/GameState.cpp
+++ b/src/openrct2/GameState.cpp
@@ -20,6 +20,7 @@
#include "actions/GameAction.h"
#include "config/Config.h"
#include "entity/EntityRegistry.h"
+#include "entity/PatrolArea.h"
#include "entity/Staff.h"
#include "interface/Screenshot.h"
#include "localisation/Date.h"
@@ -67,7 +68,7 @@ void GameState::InitAll(const TileCoordsXY& mapSize)
banner_init();
ride_init_all();
ResetAllEntities();
- staff_reset_modes();
+ UpdateConsolidatedPatrolAreas();
date_reset();
climate_reset(ClimateType::CoolAndWet);
News::InitQueue();
diff --git a/src/openrct2/actions/GameAction.h b/src/openrct2/actions/GameAction.h
index 7e5e89eb0a..c0a6fe2d12 100644
--- a/src/openrct2/actions/GameAction.h
+++ b/src/openrct2/actions/GameAction.h
@@ -79,6 +79,14 @@ public:
Visit("direction", param.direction);
}
+ void Visit(MapRange& param)
+ {
+ Visit("x1", param.Point1.x);
+ Visit("y1", param.Point1.y);
+ Visit("x2", param.Point2.x);
+ Visit("y2", param.Point2.y);
+ }
+
template void Visit(std::string_view name, T& param)
{
static_assert(std::is_arithmetic_v || std::is_enum_v, "Not an arithmetic type");
diff --git a/src/openrct2/actions/StaffSetPatrolAreaAction.cpp b/src/openrct2/actions/StaffSetPatrolAreaAction.cpp
index ad2aa456cd..d3ddc9f464 100644
--- a/src/openrct2/actions/StaffSetPatrolAreaAction.cpp
+++ b/src/openrct2/actions/StaffSetPatrolAreaAction.cpp
@@ -10,17 +10,25 @@
#include "StaffSetPatrolAreaAction.h"
#include "../entity/EntityRegistry.h"
+#include "../entity/PatrolArea.h"
#include "../entity/Peep.h"
#include "../entity/Staff.h"
#include "../interface/Window.h"
-StaffSetPatrolAreaAction::StaffSetPatrolAreaAction(EntityId spriteId, const CoordsXY& loc, const StaffSetPatrolAreaMode mode)
+StaffSetPatrolAreaAction::StaffSetPatrolAreaAction(EntityId spriteId, const MapRange& range, const StaffSetPatrolAreaMode mode)
: _spriteId(spriteId)
- , _loc(loc)
+ , _range(range)
, _mode(mode)
{
}
+void StaffSetPatrolAreaAction::AcceptParameters(GameActionParameterVisitor& visitor)
+{
+ visitor.Visit("id", _spriteId);
+ visitor.Visit(_range);
+ visitor.Visit("mode", _mode);
+}
+
uint16_t StaffSetPatrolAreaAction::GetActionFlags() const
{
return GameAction::GetActionFlags() | GameActions::Flags::AllowWhilePaused;
@@ -29,75 +37,67 @@ uint16_t StaffSetPatrolAreaAction::GetActionFlags() const
void StaffSetPatrolAreaAction::Serialise(DataSerialiser& stream)
{
GameAction::Serialise(stream);
- stream << DS_TAG(_spriteId) << DS_TAG(_loc) << DS_TAG(_mode);
+ stream << DS_TAG(_spriteId) << DS_TAG(_range) << DS_TAG(_mode);
}
GameActions::Result StaffSetPatrolAreaAction::Query() const
{
- if (_spriteId.ToUnderlying() >= MAX_ENTITIES || _spriteId.IsNull())
- {
- log_error("Invalid spriteId. spriteId = %u", _spriteId);
- return GameActions::Result(GameActions::Status::InvalidParameters, STR_NONE, STR_NONE);
- }
-
- if (!LocationValid(_loc))
- {
- return GameActions::Result(GameActions::Status::InvalidParameters, STR_NONE, STR_NONE);
- }
-
- auto staff = TryGetEntity(_spriteId);
- if (staff == nullptr)
- {
- log_error("Invalid spriteId. spriteId = %u", _spriteId);
- return GameActions::Result(GameActions::Status::InvalidParameters, STR_NONE, STR_NONE);
- }
-
- return GameActions::Result();
-}
-
-static void InvalidatePatrolTile(const CoordsXY& loc)
-{
- // Align the location to the top left of the patrol square
- const auto alignedLoc = CoordsXY{ loc.x & 0x1F80, loc.y & 0x1F80 };
- for (int32_t y = 0; y < 4 * COORDS_XY_STEP; y += COORDS_XY_STEP)
- {
- for (int32_t x = 0; x < 4 * COORDS_XY_STEP; x += COORDS_XY_STEP)
- {
- map_invalidate_tile_full(alignedLoc + CoordsXY{ x, y });
- }
- }
+ return QueryExecute(false);
}
GameActions::Result StaffSetPatrolAreaAction::Execute() const
+{
+ return QueryExecute(true);
+}
+
+static void InvalidatePatrolTiles(const MapRange& range)
+{
+ map_invalidate_region(range.Point1, range.Point2);
+}
+
+GameActions::Result StaffSetPatrolAreaAction::QueryExecute(bool executing) const
{
auto staff = TryGetEntity(_spriteId);
if (staff == nullptr)
{
- log_error("Invalid spriteId. spriteId = %u", _spriteId);
+ log_error("Invalid entity ID: %u", _spriteId.ToUnderlying());
return GameActions::Result(GameActions::Status::InvalidParameters, STR_NONE, STR_NONE);
}
- switch (_mode)
+ auto validRange = ClampRangeWithinMap(_range);
+ for (int32_t y = validRange.GetTop(); y <= validRange.GetBottom(); y += COORDS_XY_STEP)
{
- case StaffSetPatrolAreaMode::Set:
- staff->SetPatrolArea(_loc, true);
- InvalidatePatrolTile(_loc);
- break;
- case StaffSetPatrolAreaMode::Unset:
- staff->SetPatrolArea(_loc, false);
- if (!staff->HasPatrolArea())
+ for (int32_t x = validRange.GetLeft(); x <= validRange.GetRight(); x += COORDS_XY_STEP)
+ {
+ if (!LocationValid({ x, y }))
{
- staff->ClearPatrolArea();
+ return GameActions::Result(GameActions::Status::NotOwned, STR_SET_PATROL_AREA, STR_LAND_NOT_OWNED_BY_PARK);
}
- InvalidatePatrolTile(_loc);
- break;
- case StaffSetPatrolAreaMode::ClearAll:
- staff->ClearPatrolArea();
- gfx_invalidate_screen();
- break;
+ }
}
- staff_update_greyed_patrol_areas();
-
+ if (executing)
+ {
+ switch (_mode)
+ {
+ case StaffSetPatrolAreaMode::Set:
+ staff->SetPatrolArea(_range, true);
+ InvalidatePatrolTiles(_range);
+ break;
+ case StaffSetPatrolAreaMode::Unset:
+ staff->SetPatrolArea(_range, false);
+ if (!staff->HasPatrolArea())
+ {
+ staff->ClearPatrolArea();
+ }
+ InvalidatePatrolTiles(_range);
+ break;
+ case StaffSetPatrolAreaMode::ClearAll:
+ staff->ClearPatrolArea();
+ gfx_invalidate_screen();
+ break;
+ }
+ UpdateConsolidatedPatrolAreas();
+ }
return GameActions::Result();
}
diff --git a/src/openrct2/actions/StaffSetPatrolAreaAction.h b/src/openrct2/actions/StaffSetPatrolAreaAction.h
index 939f608a73..87a1a99f5f 100644
--- a/src/openrct2/actions/StaffSetPatrolAreaAction.h
+++ b/src/openrct2/actions/StaffSetPatrolAreaAction.h
@@ -22,15 +22,18 @@ class StaffSetPatrolAreaAction final : public GameActionBase
+#include
+
+template>
+ForwardIt binary_find(ForwardIt first, ForwardIt last, const T& value, Compare comp = {})
+{
+ first = std::lower_bound(first, last, value, comp);
+ return first != last && !comp(value, *first) ? first : last;
+}
diff --git a/src/openrct2/entity/EntityRegistry.cpp b/src/openrct2/entity/EntityRegistry.cpp
index c2d30c6713..0744889ba6 100644
--- a/src/openrct2/entity/EntityRegistry.cpp
+++ b/src/openrct2/entity/EntityRegistry.cpp
@@ -10,6 +10,7 @@
#include "EntityRegistry.h"
#include "../Game.h"
+#include "../core/Algorithm.hpp"
#include "../core/ChecksumStream.h"
#include "../core/Crypt.h"
#include "../core/DataSerialiser.h"
@@ -291,8 +292,8 @@ static void AddToFreeList(EntityId index)
static void RemoveFromEntityList(EntityBase* entity)
{
auto& list = gEntityLists[EnumValue(entity->Type)];
- auto ptr = std::lower_bound(std::begin(list), std::end(list), entity->sprite_index);
- if (ptr != std::end(list) && *ptr == entity->sprite_index)
+ auto ptr = binary_find(std::begin(list), std::end(list), entity->sprite_index);
+ if (ptr != std::end(list))
{
list.erase(ptr);
}
@@ -364,8 +365,8 @@ EntityBase* CreateEntity(EntityType type)
EntityBase* CreateEntityAt(const EntityId index, const EntityType type)
{
- auto id = std::lower_bound(std::rbegin(_freeIdList), std::rend(_freeIdList), index);
- if (id == std::rend(_freeIdList) || *id != index)
+ auto id = binary_find(std::rbegin(_freeIdList), std::rend(_freeIdList), index);
+ if (id == std::rend(_freeIdList))
{
return nullptr;
}
@@ -421,8 +422,8 @@ static void EntitySpatialRemove(EntityBase* entity)
{
size_t currentIndex = GetSpatialIndexOffset({ entity->x, entity->y });
auto& spatialVector = gEntitySpatialIndex[currentIndex];
- auto index = std::lower_bound(std::begin(spatialVector), std::end(spatialVector), entity->sprite_index);
- if (index != std::end(spatialVector) && *index == entity->sprite_index)
+ auto index = binary_find(std::begin(spatialVector), std::end(spatialVector), entity->sprite_index);
+ if (index != std::end(spatialVector))
{
spatialVector.erase(index, index + 1);
}
diff --git a/src/openrct2/entity/PatrolArea.cpp b/src/openrct2/entity/PatrolArea.cpp
new file mode 100644
index 0000000000..95405958c5
--- /dev/null
+++ b/src/openrct2/entity/PatrolArea.cpp
@@ -0,0 +1,178 @@
+/*****************************************************************************
+ * Copyright (c) 2014-2022 OpenRCT2 developers
+ *
+ * For a complete list of all authors, please refer to contributors.md
+ * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
+ *
+ * OpenRCT2 is licensed under the GNU General Public License version 3.
+ *****************************************************************************/
+
+#include "PatrolArea.h"
+
+#include "../core/Algorithm.hpp"
+#include "EntityList.h"
+#include "Staff.h"
+
+static PatrolArea _consolidatedPatrolArea[EnumValue(StaffType::Count)];
+static std::variant _patrolAreaToRender;
+
+static bool CompareTileCoordsXY(const TileCoordsXY& lhs, const TileCoordsXY& rhs)
+{
+ if (lhs.y == rhs.y)
+ return lhs.x < rhs.x;
+ return lhs.y < rhs.y;
+}
+
+const PatrolArea::Cell* PatrolArea::GetCell(const TileCoordsXY& pos) const
+{
+ return const_cast(this)->GetCell(pos);
+}
+
+PatrolArea::Cell* PatrolArea::GetCell(const TileCoordsXY& pos)
+{
+ auto areaPos = TileCoordsXY(pos.x / Cell::Width, pos.y / Cell::Height);
+ if (areaPos.x < 0 || areaPos.x >= CellColumns || areaPos.y < 0 || areaPos.y >= CellRows)
+ return nullptr;
+
+ auto& area = Areas[(areaPos.y * CellColumns) + areaPos.x];
+ return &area;
+}
+
+bool PatrolArea::IsEmpty() const
+{
+ return TileCount == 0;
+}
+
+void PatrolArea::Clear()
+{
+ for (auto& area : Areas)
+ {
+ area.SortedTiles.clear();
+ }
+}
+
+bool PatrolArea::Get(const TileCoordsXY& pos) const
+{
+ auto* area = GetCell(pos);
+ if (area == nullptr)
+ return false;
+
+ auto it = binary_find(area->SortedTiles.begin(), area->SortedTiles.end(), pos, CompareTileCoordsXY);
+ auto found = it != area->SortedTiles.end();
+ return found;
+}
+
+bool PatrolArea::Get(const CoordsXY& pos) const
+{
+ return Get(TileCoordsXY(pos));
+}
+
+void PatrolArea::Set(const TileCoordsXY& pos, bool value)
+{
+ auto* area = GetCell(pos);
+ if (area == nullptr)
+ return;
+
+ auto it = std::lower_bound(area->SortedTiles.begin(), area->SortedTiles.end(), pos, CompareTileCoordsXY);
+ auto found = it != area->SortedTiles.end() && *it == pos;
+
+ if (!found && value)
+ {
+ area->SortedTiles.insert(it, pos);
+ TileCount++;
+ }
+ else if (found && !value)
+ {
+ area->SortedTiles.erase(it);
+ assert(TileCount != 0);
+ TileCount--;
+ }
+}
+
+void PatrolArea::Set(const CoordsXY& pos, bool value)
+{
+ Set(TileCoordsXY(pos), value);
+}
+
+void PatrolArea::Union(const PatrolArea& other)
+{
+ for (size_t i = 0; i < Areas.size(); i++)
+ {
+ for (const auto& pos : other.Areas[i].SortedTiles)
+ {
+ Set(pos, true);
+ }
+ }
+}
+
+void PatrolArea::Union(const std::vector& other)
+{
+ for (const auto& pos : other)
+ {
+ Set(pos, true);
+ }
+}
+
+std::vector PatrolArea::ToVector() const
+{
+ std::vector result;
+ for (const auto& area : Areas)
+ {
+ for (const auto& pos : area.SortedTiles)
+ {
+ result.push_back(pos);
+ }
+ }
+ return result;
+}
+
+const PatrolArea& GetMergedPatrolArea(const StaffType type)
+{
+ return _consolidatedPatrolArea[EnumValue(type)];
+}
+
+void UpdateConsolidatedPatrolAreas()
+{
+ for (int32_t staffType = 0; staffType < EnumValue(StaffType::Count); ++staffType)
+ {
+ // Reset all of the merged data for the type.
+ auto& mergedArea = _consolidatedPatrolArea[staffType];
+ mergedArea.Clear();
+
+ for (auto staff : EntityList())
+ {
+ if (EnumValue(staff->AssignedStaffType) != staffType)
+ continue;
+
+ if (staff->PatrolInfo == nullptr)
+ continue;
+
+ mergedArea.Union(*staff->PatrolInfo);
+ }
+ }
+}
+
+bool IsPatrolAreaSetForStaffType(StaffType type, const CoordsXY& coords)
+{
+ return _consolidatedPatrolArea[EnumValue(type)].Get(coords);
+}
+
+std::variant GetPatrolAreaToRender()
+{
+ return _patrolAreaToRender;
+}
+
+void ClearPatrolAreaToRender()
+{
+ SetPatrolAreaToRender(EntityId::GetNull());
+}
+
+void SetPatrolAreaToRender(EntityId staffId)
+{
+ _patrolAreaToRender = staffId;
+}
+
+void SetPatrolAreaToRender(StaffType staffType)
+{
+ _patrolAreaToRender = staffType;
+}
diff --git a/src/openrct2/entity/PatrolArea.h b/src/openrct2/entity/PatrolArea.h
new file mode 100644
index 0000000000..222249a061
--- /dev/null
+++ b/src/openrct2/entity/PatrolArea.h
@@ -0,0 +1,61 @@
+/*****************************************************************************
+ * Copyright (c) 2014-2022 OpenRCT2 developers
+ *
+ * For a complete list of all authors, please refer to contributors.md
+ * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
+ *
+ * OpenRCT2 is licensed under the GNU General Public License version 3.
+ *****************************************************************************/
+
+#pragma once
+
+#include "../world/Map.h"
+#include "Peep.h"
+
+#include
+
+// The number of elements in the gStaffPatrolAreas array per staff member. Every bit in the array represents a 4x4 square.
+// Right now, it's a 32-bit array like in RCT2. 32 * 128 = 4096 bits, which is also the number of 4x4 squares on a 256x256 map.
+constexpr size_t STAFF_PATROL_AREA_BLOCKS_PER_LINE = MAXIMUM_MAP_SIZE_TECHNICAL / 4;
+constexpr size_t STAFF_PATROL_AREA_SIZE = (STAFF_PATROL_AREA_BLOCKS_PER_LINE * STAFF_PATROL_AREA_BLOCKS_PER_LINE) / 32;
+
+class PatrolArea
+{
+private:
+ struct Cell
+ {
+ static constexpr auto Width = 64;
+ static constexpr auto Height = 64;
+ static constexpr auto NumTiles = Width * Height;
+
+ std::vector SortedTiles;
+ };
+
+ static constexpr auto CellColumns = (MAXIMUM_MAP_SIZE_TECHNICAL + (Cell::Width - 1)) / Cell::Width;
+ static constexpr auto CellRows = (MAXIMUM_MAP_SIZE_TECHNICAL + (Cell::Height - 1)) / Cell::Height;
+ static constexpr auto NumCells = CellColumns * CellRows;
+
+ std::array| Areas;
+ size_t TileCount{};
+
+ const Cell* GetCell(const TileCoordsXY& pos) const;
+ Cell* GetCell(const TileCoordsXY& pos);
+
+public:
+ bool IsEmpty() const;
+ void Clear();
+ bool Get(const TileCoordsXY& pos) const;
+ bool Get(const CoordsXY& pos) const;
+ void Set(const TileCoordsXY& pos, bool value);
+ void Set(const CoordsXY& pos, bool value);
+ void Union(const PatrolArea& other);
+ void Union(const std::vector& other);
+ std::vector ToVector() const;
+};
+
+void UpdateConsolidatedPatrolAreas();
+bool IsPatrolAreaSetForStaffType(StaffType type, const CoordsXY& coords);
+std::variant GetPatrolAreaToRender();
+void ClearPatrolAreaToRender();
+void SetPatrolAreaToRender(EntityId staffId);
+void SetPatrolAreaToRender(StaffType staffType);
diff --git a/src/openrct2/entity/Peep.cpp b/src/openrct2/entity/Peep.cpp
index 3963262188..946906d13e 100644
--- a/src/openrct2/entity/Peep.cpp
+++ b/src/openrct2/entity/Peep.cpp
@@ -52,6 +52,7 @@
#include "../world/Scenery.h"
#include "../world/SmallScenery.h"
#include "../world/Surface.h"
+#include "PatrolArea.h"
#include "Staff.h"
#include
@@ -667,7 +668,7 @@ void peep_sprite_remove(Peep* peep)
else
{
staff->ClearPatrolArea();
- staff_update_greyed_patrol_areas();
+ UpdateConsolidatedPatrolAreas();
News::DisableNewsItems(News::ItemType::Peep, staff->sprite_index.ToUnderlying());
}
diff --git a/src/openrct2/entity/Staff.cpp b/src/openrct2/entity/Staff.cpp
index 141cf2d9d5..da4dd878df 100644
--- a/src/openrct2/entity/Staff.cpp
+++ b/src/openrct2/entity/Staff.cpp
@@ -40,6 +40,7 @@
#include "../world/Scenery.h"
#include "../world/SmallScenery.h"
#include "../world/Surface.h"
+#include "PatrolArea.h"
#include "Peep.h"
#include
@@ -61,18 +62,10 @@ const rct_string_id StaffCostumeNames[] = {
};
// clang-format on
-uint16_t gStaffDrawPatrolAreas;
colour_t gStaffHandymanColour;
colour_t gStaffMechanicColour;
colour_t gStaffSecurityColour;
-static PatrolArea _mergedPatrolAreas[EnumValue(StaffType::Count)];
-
-const PatrolArea& GetMergedPatrolArea(const StaffType type)
-{
- return _mergedPatrolAreas[EnumValue(type)];
-}
-
// Maximum manhattan distance that litter can be for a handyman to seek to it
const uint16_t MAX_LITTER_DISTANCE = 3 * COORDS_XY_STEP;
@@ -81,47 +74,6 @@ template<> bool EntityBase::Is() const
return Type == EntityType::Staff;
}
-/**
- *
- * rct2: 0x006BD3A4
- */
-void staff_reset_modes()
-{
- staff_update_greyed_patrol_areas();
-}
-
-/**
- *
- * rct2: 0x006C0C3F
- */
-void staff_update_greyed_patrol_areas()
-{
- for (int32_t staffType = 0; staffType < EnumValue(StaffType::Count); ++staffType)
- {
- // Reset all of the merged data for the type.
- auto& mergedData = _mergedPatrolAreas[staffType].Data;
- std::fill(std::begin(mergedData), std::end(mergedData), 0);
-
- for (auto staff : EntityList())
- {
- if (EnumValue(staff->AssignedStaffType) != staffType)
- {
- continue;
- }
- if (!staff->HasPatrolArea())
- {
- continue;
- }
-
- auto staffData = staff->PatrolInfo->Data;
- for (size_t i = 0; i < STAFF_PATROL_AREA_SIZE; i++)
- {
- mergedData[i] |= staffData[i];
- }
- }
- }
-}
-
/**
*
* rct2: 0x006C0905
@@ -321,33 +273,15 @@ void Staff::ResetStats()
}
}
-static std::pair getPatrolAreaOffsetIndex(const CoordsXY& coords)
-{
- auto tilePos = TileCoordsXY(coords);
- auto x = tilePos.x / 4;
- auto y = tilePos.y / 4;
- auto bitIndex = (y * STAFF_PATROL_AREA_BLOCKS_PER_LINE) + x;
- auto byteIndex = int32_t(bitIndex / 32);
- auto byteBitIndex = int32_t(bitIndex % 32);
- return { byteIndex, byteBitIndex };
-}
-
bool Staff::IsPatrolAreaSet(const CoordsXY& coords) const
{
if (PatrolInfo != nullptr)
{
- auto [offset, bitIndex] = getPatrolAreaOffsetIndex(coords);
- return PatrolInfo->Data[offset] & (1UL << bitIndex);
+ return PatrolInfo->Get(coords);
}
return false;
}
-bool staff_is_patrol_area_set_for_type(StaffType type, const CoordsXY& coords)
-{
- auto [offset, bitIndex] = getPatrolAreaOffsetIndex(coords);
- return _mergedPatrolAreas[EnumValue(type)].Data[offset] & (1UL << bitIndex);
-}
-
void Staff::SetPatrolArea(const CoordsXY& coords, bool value)
{
if (PatrolInfo == nullptr)
@@ -361,15 +295,18 @@ void Staff::SetPatrolArea(const CoordsXY& coords, bool value)
return;
}
}
- auto [offset, bitIndex] = getPatrolAreaOffsetIndex(coords);
- auto* addr = &PatrolInfo->Data[offset];
- if (value)
+
+ PatrolInfo->Set(coords, value);
+}
+
+void Staff::SetPatrolArea(const MapRange& range, bool value)
+{
+ for (int32_t yy = range.GetTop(); yy <= range.GetBottom(); yy += COORDS_XY_STEP)
{
- *addr |= (1 << bitIndex);
- }
- else
- {
- *addr &= ~(1 << bitIndex);
+ for (int32_t xx = range.GetLeft(); xx <= range.GetRight(); xx += COORDS_XY_STEP)
+ {
+ SetPatrolArea({ xx, yy }, value);
+ }
}
}
@@ -381,11 +318,7 @@ void Staff::ClearPatrolArea()
bool Staff::HasPatrolArea() const
{
- if (PatrolInfo == nullptr)
- return false;
-
- constexpr auto hasData = [](const auto& datapoint) { return datapoint != 0; };
- return std::any_of(std::begin(PatrolInfo->Data), std::end(PatrolInfo->Data), hasData);
+ return PatrolInfo == nullptr ? false : !PatrolInfo->IsEmpty();
}
/**
diff --git a/src/openrct2/entity/Staff.h b/src/openrct2/entity/Staff.h
index e35939a29e..d373e99be7 100644
--- a/src/openrct2/entity/Staff.h
+++ b/src/openrct2/entity/Staff.h
@@ -14,16 +14,7 @@
#include "Peep.h"
class DataSerialiser;
-
-// The number of elements in the gStaffPatrolAreas array per staff member. Every bit in the array represents a 4x4 square.
-// Right now, it's a 32-bit array like in RCT2. 32 * 128 = 4096 bits, which is also the number of 4x4 squares on a 256x256 map.
-constexpr size_t STAFF_PATROL_AREA_BLOCKS_PER_LINE = MAXIMUM_MAP_SIZE_TECHNICAL / 4;
-constexpr size_t STAFF_PATROL_AREA_SIZE = (STAFF_PATROL_AREA_BLOCKS_PER_LINE * STAFF_PATROL_AREA_BLOCKS_PER_LINE) / 32;
-
-struct PatrolArea
-{
- uint32_t Data[STAFF_PATROL_AREA_SIZE];
-};
+class PatrolArea;
struct Staff : Peep
{
@@ -72,7 +63,10 @@ public:
void ClearPatrolArea();
void SetPatrolArea(const CoordsXY& coords, bool value);
+ void SetPatrolArea(const MapRange& range, bool value);
bool HasPatrolArea() const;
+ std::vector GetPatrolArea();
+ void SetPatrolArea(const std::vector& area);
private:
void UpdatePatrolling();
@@ -150,14 +144,10 @@ enum class EntertainerCostume : uint8_t
extern const rct_string_id StaffCostumeNames[static_cast(EntertainerCostume::Count)];
-extern uint16_t gStaffDrawPatrolAreas;
extern colour_t gStaffHandymanColour;
extern colour_t gStaffMechanicColour;
extern colour_t gStaffSecurityColour;
-void staff_reset_modes();
-void staff_update_greyed_patrol_areas();
-bool staff_is_patrol_area_set_for_type(StaffType type, const CoordsXY& coords);
colour_t staff_get_colour(StaffType staffType);
bool staff_set_colour(StaffType staffType, colour_t value);
uint32_t staff_get_available_entertainer_costumes();
diff --git a/src/openrct2/interface/Viewport.cpp b/src/openrct2/interface/Viewport.cpp
index bc9a0b9be4..bbda3980fb 100644
--- a/src/openrct2/interface/Viewport.cpp
+++ b/src/openrct2/interface/Viewport.cpp
@@ -20,6 +20,7 @@
#include "../drawing/IDrawingEngine.h"
#include "../entity/EntityList.h"
#include "../entity/Guest.h"
+#include "../entity/PatrolArea.h"
#include "../entity/Staff.h"
#include "../paint/Paint.h"
#include "../profiling/Profiling.h"
@@ -90,7 +91,7 @@ void viewport_init_all()
gPickupPeepImage = ImageId();
reset_tooltip_not_shown();
gMapSelectFlags = 0;
- gStaffDrawPatrolAreas = 0xFFFF;
+ ClearPatrolAreaToRender();
textinput_cancel();
}
diff --git a/src/openrct2/interface/Window.h b/src/openrct2/interface/Window.h
index 404c28f5e0..eb10c816f9 100644
--- a/src/openrct2/interface/Window.h
+++ b/src/openrct2/interface/Window.h
@@ -462,6 +462,7 @@ enum
WC_DEBUG_PAINT = 130,
WC_VIEW_CLIPPING = 131,
WC_OBJECT_LOAD_ERROR = 132,
+ WC_PATROL_AREA = 133,
// Only used for colour schemes
WC_STAFF = 220,
diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj
index a5d8a1181e..de3aa49e00 100644
--- a/src/openrct2/libopenrct2.vcxproj
+++ b/src/openrct2/libopenrct2.vcxproj
@@ -149,6 +149,7 @@
+
@@ -218,6 +219,7 @@
+
@@ -687,6 +689,7 @@
+
@@ -952,4 +955,4 @@
|
-
+
\ No newline at end of file
diff --git a/src/openrct2/localisation/StringIds.h b/src/openrct2/localisation/StringIds.h
index 93d4f54105..5d34226047 100644
--- a/src/openrct2/localisation/StringIds.h
+++ b/src/openrct2/localisation/StringIds.h
@@ -3925,6 +3925,9 @@ enum : uint16_t
STR_NAUSEA_LABEL = 6467,
STR_RATING_UKNOWN_LABEL = 6468,
+ STR_ADJUST_SMALLER_PATROL_AREA_TIP = 6469,
+ STR_ADJUST_LARGER_PATROL_AREA_TIP = 6470,
+
// Have to include resource strings (from scenarios and objects) for the time being now that language is partially working
/* MAX_STR_COUNT = 32768 */ // MAX_STR_COUNT - upper limit for number of strings, not the current count strings
};
diff --git a/src/openrct2/paint/tile_element/Paint.Path.cpp b/src/openrct2/paint/tile_element/Paint.Path.cpp
index 4c5beef9f3..a965765e44 100644
--- a/src/openrct2/paint/tile_element/Paint.Path.cpp
+++ b/src/openrct2/paint/tile_element/Paint.Path.cpp
@@ -15,6 +15,7 @@
#include "../../core/Numerics.hpp"
#include "../../drawing/LightFX.h"
#include "../../entity/EntityRegistry.h"
+#include "../../entity/PatrolArea.h"
#include "../../entity/Peep.h"
#include "../../entity/Staff.h"
#include "../../interface/Viewport.h"
@@ -898,45 +899,19 @@ static bool ShouldDrawSupports(paint_session& session, const PathElement& pathEl
static void PaintPatrolAreas(paint_session& session, const PathElement& pathEl)
{
- if (gStaffDrawPatrolAreas != 0xFFFF)
+ auto colour = GetPatrolAreaTileColour(session.MapPosition);
+ if (colour)
{
- // TODO: Split this into two.
- auto staffIndex = gStaffDrawPatrolAreas;
- auto staffType = static_cast(staffIndex & 0x7FFF);
- auto is_staff_list = staffIndex & 0x8000;
-
- auto patrolColour = COLOUR_LIGHT_BLUE;
-
- if (!is_staff_list)
+ uint32_t baseImageIndex = SPR_TERRAIN_STAFF;
+ auto patrolAreaBaseZ = pathEl.GetBaseZ();
+ if (pathEl.IsSloped())
{
- Staff* staff = GetEntity(EntityId::FromUnderlying(staffIndex));
- if (staff == nullptr)
- {
- log_error("Invalid staff index for draw patrol areas!");
- }
- else
- {
- if (!staff->IsPatrolAreaSet(session.MapPosition))
- {
- patrolColour = COLOUR_GREY;
- }
- staffType = staff->AssignedStaffType;
- }
+ baseImageIndex = SPR_TERRAIN_STAFF_SLOPED + ((pathEl.GetSlopeDirection() + session.CurrentRotation) & 3);
+ patrolAreaBaseZ += 16;
}
- if (staff_is_patrol_area_set_for_type(staffType, session.MapPosition))
- {
- uint32_t baseImageIndex = SPR_TERRAIN_STAFF;
- auto patrolAreaBaseZ = pathEl.GetBaseZ();
- if (pathEl.IsSloped())
- {
- baseImageIndex = SPR_TERRAIN_STAFF_SLOPED + ((pathEl.GetSlopeDirection() + session.CurrentRotation) & 3);
- patrolAreaBaseZ += 16;
- }
-
- auto imageId = ImageId(baseImageIndex, patrolColour);
- PaintAddImageAsParent(session, imageId, { 16, 16, patrolAreaBaseZ + 2 }, { 1, 1, 0 });
- }
+ auto imageId = ImageId(baseImageIndex, *colour);
+ PaintAddImageAsParent(session, imageId, { 16, 16, patrolAreaBaseZ + 2 }, { 1, 1, 0 });
}
}
diff --git a/src/openrct2/paint/tile_element/Paint.Surface.cpp b/src/openrct2/paint/tile_element/Paint.Surface.cpp
index fe4fe52d9b..0205e83af5 100644
--- a/src/openrct2/paint/tile_element/Paint.Surface.cpp
+++ b/src/openrct2/paint/tile_element/Paint.Surface.cpp
@@ -17,6 +17,7 @@
#include "../../core/Numerics.hpp"
#include "../../drawing/Drawing.h"
#include "../../entity/EntityRegistry.h"
+#include "../../entity/PatrolArea.h"
#include "../../entity/Peep.h"
#include "../../entity/Staff.h"
#include "../../interface/Colour.h"
@@ -969,6 +970,51 @@ static std::pair surface_get_height_above_water(
return { localHeight, localSurfaceShape };
}
+std::optional GetPatrolAreaTileColour(const CoordsXY& pos)
+{
+ auto patrolAreaToRender = GetPatrolAreaToRender();
+ if (const auto* staffType = std::get_if(&patrolAreaToRender))
+ {
+ if (IsPatrolAreaSetForStaffType(*staffType, pos))
+ {
+ return COLOUR_GREY;
+ }
+ }
+ else
+ {
+ auto& staffId = std::get(patrolAreaToRender);
+ auto* staff = GetEntity(staffId);
+ if (staff != nullptr)
+ {
+ if (staff->IsPatrolAreaSet(pos))
+ {
+ return COLOUR_LIGHT_BLUE;
+ }
+ else if (IsPatrolAreaSetForStaffType(staff->AssignedStaffType, pos))
+ {
+ return COLOUR_GREY;
+ }
+ }
+ }
+ return {};
+}
+
+static void PaintPatrolArea(paint_session& session, const SurfaceElement& element, int32_t height, uint8_t surfaceShape)
+{
+ auto colour = GetPatrolAreaTileColour(session.MapPosition);
+ if (colour)
+ {
+ assert(surfaceShape < std::size(byte_97B444));
+
+ auto [localZ, localSurfaceShape] = surface_get_height_above_water(element, height, surfaceShape);
+ auto imageId = ImageId(SPR_TERRAIN_SELECTION_PATROL_AREA + byte_97B444[localSurfaceShape], *colour);
+
+ auto* backup = session.LastPS;
+ PaintAddImageAsParent(session, imageId, { 0, 0, localZ }, { 32, 32, 1 });
+ session.LastPS = backup;
+ }
+}
+
/**
* rct2: 0x0066062C
*/
@@ -1101,49 +1147,7 @@ void PaintSurface(paint_session& session, uint8_t direction, uint16_t height, co
has_surface = true;
}
- // Draw Staff Patrol Areas
- // loc_660D02
- if (gStaffDrawPatrolAreas != EntityId::GetNull().ToUnderlying())
- {
- // TODO: Split is_staff_list into a new variable.
- const int32_t staffIndex = gStaffDrawPatrolAreas;
- const bool is_staff_list = staffIndex & 0x8000;
- const int16_t x = session.MapPosition.x, y = session.MapPosition.y;
-
- uint8_t staffType = staffIndex & 0x7FFF;
- uint32_t image_id = IMAGE_TYPE_REMAP;
- uint8_t patrolColour = COLOUR_LIGHT_BLUE;
-
- if (!is_staff_list)
- {
- Staff* staff = GetEntity(EntityId::FromUnderlying(staffIndex));
- if (staff == nullptr)
- {
- log_error("Invalid staff index for draw patrol areas!");
- }
- else
- {
- if (!staff->IsPatrolAreaSet({ x, y }))
- {
- patrolColour = COLOUR_GREY;
- }
- staffType = static_cast(staff->AssignedStaffType);
- }
- }
-
- if (staff_is_patrol_area_set_for_type(static_cast(staffType), session.MapPosition))
- {
- assert(surfaceShape < std::size(byte_97B444));
-
- auto [local_height, local_surfaceShape] = surface_get_height_above_water(tileElement, height, surfaceShape);
- image_id |= SPR_TERRAIN_SELECTION_PATROL_AREA + byte_97B444[local_surfaceShape];
- image_id |= patrolColour << 19;
-
- paint_struct* backup = session.LastPS;
- PaintAddImageAsParent(session, image_id, { 0, 0, local_height }, { 32, 32, 1 });
- session.LastPS = backup;
- }
- }
+ PaintPatrolArea(session, tileElement, height, surfaceShape);
// Draw Peep Spawns
if (((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode)
diff --git a/src/openrct2/paint/tile_element/Paint.Surface.h b/src/openrct2/paint/tile_element/Paint.Surface.h
index 10c95091e2..403ac32dc2 100644
--- a/src/openrct2/paint/tile_element/Paint.Surface.h
+++ b/src/openrct2/paint/tile_element/Paint.Surface.h
@@ -11,6 +11,9 @@
#include "../../common.h"
#include "../../sprites.h"
+#include "../../world/Location.hpp"
+
+#include
enum
{
@@ -102,3 +105,5 @@ enum
SPR_RCT1_WATER_MASK = SPR_CSG_BEGIN + 46787,
SPR_RCT1_WATER_OVERLAY = SPR_CSG_BEGIN + 46792,
};
+
+std::optional GetPatrolAreaTileColour(const CoordsXY& pos);
diff --git a/src/openrct2/park/ParkFile.cpp b/src/openrct2/park/ParkFile.cpp
index 395da8836a..9d41ae66d1 100644
--- a/src/openrct2/park/ParkFile.cpp
+++ b/src/openrct2/park/ParkFile.cpp
@@ -31,6 +31,7 @@
#include "../entity/Litter.h"
#include "../entity/MoneyEffect.h"
#include "../entity/Particle.h"
+#include "../entity/PatrolArea.h"
#include "../entity/Staff.h"
#include "../interface/Viewport.h"
#include "../interface/Window.h"
@@ -2015,64 +2016,30 @@ namespace OpenRCT2
cs.ReadWrite(guest.ItemFlags);
}
- static std::vector GetPatrolArea(Staff& staff)
- {
- std::vector area;
- if (staff.PatrolInfo != nullptr)
- {
- for (size_t i = 0; i < STAFF_PATROL_AREA_SIZE; i++)
- {
- // 32 blocks per array item (32 bits)
- auto arrayItem = staff.PatrolInfo->Data[i];
- for (size_t j = 0; j < 32; j++)
- {
- auto blockIndex = (i * 32) + j;
- if (arrayItem & (1 << j))
- {
- auto sx = (blockIndex % STAFF_PATROL_AREA_BLOCKS_PER_LINE) * 4;
- auto sy = (blockIndex / STAFF_PATROL_AREA_BLOCKS_PER_LINE) * 4;
- for (size_t y = 0; y < 4; y++)
- {
- for (size_t x = 0; x < 4; x++)
- {
- area.push_back({ static_cast(sx + x), static_cast(sy + y) });
- }
- }
- }
- }
- }
- }
- return area;
- }
-
- static void SetPatrolArea(Staff& staff, const std::vector& area)
- {
- if (area.empty())
- {
- staff.ClearPatrolArea();
- }
- else
- {
- for (const auto& coord : area)
- {
- staff.SetPatrolArea(coord.ToCoordsXY(), true);
- }
- }
- }
-
template<> void ParkFile::ReadWriteEntity(OrcaStream& os, OrcaStream::ChunkStream& cs, Staff& entity)
{
ReadWritePeep(os, cs, entity);
std::vector patrolArea;
- if (cs.GetMode() == OrcaStream::Mode::WRITING)
+ if (cs.GetMode() == OrcaStream::Mode::WRITING && entity.PatrolInfo != nullptr)
{
- patrolArea = GetPatrolArea(entity);
+ patrolArea = entity.PatrolInfo->ToVector();
}
cs.ReadWriteVector(patrolArea, [&cs](TileCoordsXY& value) { cs.ReadWrite(value); });
if (cs.GetMode() == OrcaStream::Mode::READING)
{
- SetPatrolArea(entity, patrolArea);
+ if (patrolArea.empty())
+ {
+ entity.ClearPatrolArea();
+ }
+ else
+ {
+ if (entity.PatrolInfo == nullptr)
+ entity.PatrolInfo = new PatrolArea();
+ else
+ entity.PatrolInfo->Clear();
+ entity.PatrolInfo->Union(patrolArea);
+ }
}
if (os.GetHeader().TargetVersion <= 1)
diff --git a/src/openrct2/rct1/S4Importer.cpp b/src/openrct2/rct1/S4Importer.cpp
index 24543e0fd1..4bd196d03f 100644
--- a/src/openrct2/rct1/S4Importer.cpp
+++ b/src/openrct2/rct1/S4Importer.cpp
@@ -32,6 +32,7 @@
#include "../entity/Litter.h"
#include "../entity/MoneyEffect.h"
#include "../entity/Particle.h"
+#include "../entity/PatrolArea.h"
#include "../entity/Peep.h"
#include "../entity/Staff.h"
#include "../interface/Window.h"
@@ -1260,7 +1261,7 @@ namespace RCT1
void FixImportStaff()
{
// Only the individual patrol areas have been converted, so generate the combined patrol areas of each staff type
- staff_update_greyed_patrol_areas();
+ UpdateConsolidatedPatrolAreas();
}
void ImportPeep(::Peep* dst, const RCT1::Peep* src)
@@ -1360,7 +1361,8 @@ namespace RCT1
x <<= 7;
int32_t y = val & 0x3E0;
y <<= 2;
- staffmember->SetPatrolArea({ x, y }, true);
+ staffmember->SetPatrolArea(
+ MapRange(x, y, x + (4 * COORDS_XY_STEP) - 1, y + (4 * COORDS_XY_STEP) - 1), true);
}
}
}
diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp
index 93db84f9aa..2d1261b32b 100644
--- a/src/openrct2/rct2/S6Importer.cpp
+++ b/src/openrct2/rct2/S6Importer.cpp
@@ -32,6 +32,7 @@
#include "../entity/Litter.h"
#include "../entity/MoneyEffect.h"
#include "../entity/Particle.h"
+#include "../entity/PatrolArea.h"
#include "../entity/Staff.h"
#include "../interface/Viewport.h"
#include "../localisation/Date.h"
@@ -490,7 +491,7 @@ namespace RCT2
FixLandOwnership();
research_determine_first_of_type();
- staff_update_greyed_patrol_areas();
+ UpdateConsolidatedPatrolAreas();
CheatsReset();
ClearRestrictedScenery();
@@ -1515,7 +1516,8 @@ namespace RCT2
x <<= 7;
int32_t y = val & 0xFC0;
y <<= 1;
- staffmember->SetPatrolArea({ x, y }, true);
+ staffmember->SetPatrolArea(
+ MapRange(x, y, x + (4 * COORDS_XY_STEP) - 1, y + (4 * COORDS_XY_STEP) - 1), true);
}
}
}