From 566bc4d3115c770b4cc50dbdd4e5ba81d85a293d Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 2 Mar 2022 00:44:11 +0000 Subject: [PATCH 1/8] Allow patrol areas to be single tiles Change the patrol areas to be stored as a sorted list of points grouped in fixed cells for each staff entity. The complexity will be a mixed O(1) and O(log n) search. --- distribution/changelog.txt | 1 + src/openrct2/entity/Staff.cpp | 164 +++++++++++++++++++++++-------- src/openrct2/entity/Staff.h | 33 ++++++- src/openrct2/park/ParkFile.cpp | 55 ++--------- src/openrct2/rct1/S4Importer.cpp | 8 +- src/openrct2/rct2/S6Importer.cpp | 8 +- 6 files changed, 175 insertions(+), 94 deletions(-) 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/src/openrct2/entity/Staff.cpp b/src/openrct2/entity/Staff.cpp index 141cf2d9d5..599b2dd662 100644 --- a/src/openrct2/entity/Staff.cpp +++ b/src/openrct2/entity/Staff.cpp @@ -68,6 +68,116 @@ colour_t gStaffSecurityColour; static PatrolArea _mergedPatrolAreas[EnumValue(StaffType::Count)]; +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(TileCoordsXY pos) const +{ + return const_cast(this)->GetCell(pos); +} + +PatrolArea::Cell* PatrolArea::GetCell(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(TileCoordsXY pos) const +{ + auto* area = GetCell(pos); + if (area == nullptr) + return false; + + auto it = std::lower_bound(area->SortedTiles.begin(), area->SortedTiles.end(), pos, CompareTileCoordsXY); + auto found = it != area->SortedTiles.end() && *it == pos; + return found; +} + +bool PatrolArea::Get(CoordsXY pos) const +{ + return Get(TileCoordsXY(pos)); +} + +void PatrolArea::Set(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(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 _mergedPatrolAreas[EnumValue(type)]; @@ -99,25 +209,18 @@ 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); + auto& mergedArea = _mergedPatrolAreas[staffType]; + mergedArea.Clear(); 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]; - } + if (staff->PatrolInfo == nullptr) + return; + + mergedArea.Union(*staff->PatrolInfo); } } } @@ -321,31 +424,18 @@ 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); + return _mergedPatrolAreas[EnumValue(type)].Get(coords); } void Staff::SetPatrolArea(const CoordsXY& coords, bool value) @@ -361,16 +451,8 @@ void Staff::SetPatrolArea(const CoordsXY& coords, bool value) return; } } - auto [offset, bitIndex] = getPatrolAreaOffsetIndex(coords); - auto* addr = &PatrolInfo->Data[offset]; - if (value) - { - *addr |= (1 << bitIndex); - } - else - { - *addr &= ~(1 << bitIndex); - } + + PatrolInfo->Set(coords, value); } void Staff::ClearPatrolArea() @@ -381,11 +463,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..f9d69e656e 100644 --- a/src/openrct2/entity/Staff.h +++ b/src/openrct2/entity/Staff.h @@ -22,7 +22,36 @@ constexpr size_t STAFF_PATROL_AREA_SIZE = (STAFF_PATROL_AREA_BLOCKS_PER_LINE * S struct PatrolArea { - uint32_t Data[STAFF_PATROL_AREA_SIZE]; +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(TileCoordsXY pos) const; + Cell* GetCell(TileCoordsXY pos); + +public: + bool IsEmpty() const; + void Clear(); + bool Get(TileCoordsXY pos) const; + bool Get(CoordsXY pos) const; + void Set(TileCoordsXY pos, bool value); + void Set(CoordsXY pos, bool value); + void Union(const PatrolArea& other); + void Union(const std::vector& other); + std::vector ToVector() const; }; struct Staff : Peep @@ -73,6 +102,8 @@ public: void ClearPatrolArea(); void SetPatrolArea(const CoordsXY& coords, bool value); bool HasPatrolArea() const; + std::vector GetPatrolArea(); + void SetPatrolArea(const std::vector& area); private: void UpdatePatrolling(); diff --git a/src/openrct2/park/ParkFile.cpp b/src/openrct2/park/ParkFile.cpp index 395da8836a..e59be8344a 100644 --- a/src/openrct2/park/ParkFile.cpp +++ b/src/openrct2/park/ParkFile.cpp @@ -2015,64 +2015,23 @@ 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 (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..646fdf5d01 100644 --- a/src/openrct2/rct1/S4Importer.cpp +++ b/src/openrct2/rct1/S4Importer.cpp @@ -1360,7 +1360,13 @@ namespace RCT1 x <<= 7; int32_t y = val & 0x3E0; y <<= 2; - staffmember->SetPatrolArea({ x, y }, true); + for (int32_t offsetY = 0; offsetY < 4 * COORDS_XY_STEP; offsetY += COORDS_XY_STEP) + { + for (int32_t offsetX = 0; offsetX < 4 * COORDS_XY_STEP; offsetX += COORDS_XY_STEP) + { + staffmember->SetPatrolArea({ x + offsetX, y + offsetY }, true); + } + } } } } diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp index 93db84f9aa..f7c61ce4b7 100644 --- a/src/openrct2/rct2/S6Importer.cpp +++ b/src/openrct2/rct2/S6Importer.cpp @@ -1515,7 +1515,13 @@ namespace RCT2 x <<= 7; int32_t y = val & 0xFC0; y <<= 1; - staffmember->SetPatrolArea({ x, y }, true); + for (int32_t offsetY = 0; offsetY < 4 * COORDS_XY_STEP; offsetY += COORDS_XY_STEP) + { + for (int32_t offsetX = 0; offsetX < 4 * COORDS_XY_STEP; offsetX += COORDS_XY_STEP) + { + staffmember->SetPatrolArea({ x + offsetX, y + offsetY }, true); + } + } } } } From 90718ca81cb750fcc0025d54e9e7480fd84404c3 Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 8 Mar 2022 00:14:52 +0000 Subject: [PATCH 2/8] Refactor patrol area class to new file --- src/openrct2-ui/windows/Staff.cpp | 5 +- src/openrct2-ui/windows/StaffList.cpp | 7 +- src/openrct2/Editor.cpp | 3 +- src/openrct2/Game.cpp | 3 +- src/openrct2/GameState.cpp | 3 +- .../actions/StaffSetPatrolAreaAction.cpp | 3 +- src/openrct2/entity/PatrolArea.cpp | 177 ++++++++++++++++++ src/openrct2/entity/PatrolArea.h | 61 ++++++ src/openrct2/entity/Peep.cpp | 3 +- src/openrct2/entity/Staff.cpp | 158 +--------------- src/openrct2/entity/Staff.h | 44 +---- src/openrct2/interface/Viewport.cpp | 3 +- src/openrct2/libopenrct2.vcxproj | 4 +- .../paint/tile_element/Paint.Path.cpp | 45 +---- .../paint/tile_element/Paint.Surface.cpp | 90 ++++----- .../paint/tile_element/Paint.Surface.h | 5 + src/openrct2/park/ParkFile.cpp | 16 +- src/openrct2/rct1/S4Importer.cpp | 3 +- src/openrct2/rct2/S6Importer.cpp | 3 +- 19 files changed, 340 insertions(+), 296 deletions(-) create mode 100644 src/openrct2/entity/PatrolArea.cpp create mode 100644 src/openrct2/entity/PatrolArea.h diff --git a/src/openrct2-ui/windows/Staff.cpp b/src/openrct2-ui/windows/Staff.cpp index 73d7166c38..ecf681b5e9 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 @@ -550,7 +551,7 @@ void WindowStaffOverviewDropdown(rct_window* w, rct_widgetindex widgetIndex, int if (!tool_set(w, widgetIndex, Tool::WalkDown)) { show_gridlines(); - gStaffDrawPatrolAreas = w->number; + SetPatrolAreaToRender(EntityId::FromUnderlying(w->number)); gfx_invalidate_screen(); } } @@ -1282,7 +1283,7 @@ void WindowStaffOverviewToolAbort(rct_window* w, rct_widgetindex widgetIndex) else if (widgetIndex == WIDX_PATROL) { hide_gridlines(); - gStaffDrawPatrolAreas = 0xFFFF; + ClearPatrolAreaToRender(); gfx_invalidate_screen(); } } 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/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/StaffSetPatrolAreaAction.cpp b/src/openrct2/actions/StaffSetPatrolAreaAction.cpp index ad2aa456cd..2c528ed40e 100644 --- a/src/openrct2/actions/StaffSetPatrolAreaAction.cpp +++ b/src/openrct2/actions/StaffSetPatrolAreaAction.cpp @@ -10,6 +10,7 @@ #include "StaffSetPatrolAreaAction.h" #include "../entity/EntityRegistry.h" +#include "../entity/PatrolArea.h" #include "../entity/Peep.h" #include "../entity/Staff.h" #include "../interface/Window.h" @@ -97,7 +98,7 @@ GameActions::Result StaffSetPatrolAreaAction::Execute() const break; } - staff_update_greyed_patrol_areas(); + UpdateConsolidatedPatrolAreas(); return GameActions::Result(); } diff --git a/src/openrct2/entity/PatrolArea.cpp b/src/openrct2/entity/PatrolArea.cpp new file mode 100644 index 0000000000..92ca2739bf --- /dev/null +++ b/src/openrct2/entity/PatrolArea.cpp @@ -0,0 +1,177 @@ +/***************************************************************************** + * 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 "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 = std::lower_bound(area->SortedTiles.begin(), area->SortedTiles.end(), pos, CompareTileCoordsXY); + auto found = it != area->SortedTiles.end() && *it == pos; + 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 599b2dd662..195056413c 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,128 +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)]; - -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(TileCoordsXY pos) const -{ - return const_cast(this)->GetCell(pos); -} - -PatrolArea::Cell* PatrolArea::GetCell(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(TileCoordsXY pos) const -{ - auto* area = GetCell(pos); - if (area == nullptr) - return false; - - auto it = std::lower_bound(area->SortedTiles.begin(), area->SortedTiles.end(), pos, CompareTileCoordsXY); - auto found = it != area->SortedTiles.end() && *it == pos; - return found; -} - -bool PatrolArea::Get(CoordsXY pos) const -{ - return Get(TileCoordsXY(pos)); -} - -void PatrolArea::Set(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(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 _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; @@ -191,40 +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& mergedArea = _mergedPatrolAreas[staffType]; - mergedArea.Clear(); - - for (auto staff : EntityList()) - { - if (EnumValue(staff->AssignedStaffType) != staffType) - continue; - - if (staff->PatrolInfo == nullptr) - return; - - mergedArea.Union(*staff->PatrolInfo); - } - } -} - /** * * rct2: 0x006C0905 @@ -433,11 +282,6 @@ bool Staff::IsPatrolAreaSet(const CoordsXY& coords) const return false; } -bool staff_is_patrol_area_set_for_type(StaffType type, const CoordsXY& coords) -{ - return _mergedPatrolAreas[EnumValue(type)].Get(coords); -} - void Staff::SetPatrolArea(const CoordsXY& coords, bool value) { if (PatrolInfo == nullptr) diff --git a/src/openrct2/entity/Staff.h b/src/openrct2/entity/Staff.h index f9d69e656e..472665dc0b 100644 --- a/src/openrct2/entity/Staff.h +++ b/src/openrct2/entity/Staff.h @@ -14,45 +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 -{ -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(TileCoordsXY pos) const; - Cell* GetCell(TileCoordsXY pos); - -public: - bool IsEmpty() const; - void Clear(); - bool Get(TileCoordsXY pos) const; - bool Get(CoordsXY pos) const; - void Set(TileCoordsXY pos, bool value); - void Set(CoordsXY pos, bool value); - void Union(const PatrolArea& other); - void Union(const std::vector& other); - std::vector ToVector() const; -}; +class PatrolArea; struct Staff : Peep { @@ -181,14 +143,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/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index a5d8a1181e..94e062dd66 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -218,6 +218,7 @@ + @@ -687,6 +688,7 @@ + @@ -952,4 +954,4 @@ - + \ No newline at end of file diff --git a/src/openrct2/paint/tile_element/Paint.Path.cpp b/src/openrct2/paint/tile_element/Paint.Path.cpp index ff5881c007..d27adf01b3 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 e59be8344a..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" @@ -2027,11 +2028,18 @@ namespace OpenRCT2 cs.ReadWriteVector(patrolArea, [&cs](TileCoordsXY& value) { cs.ReadWrite(value); }); if (cs.GetMode() == OrcaStream::Mode::READING) { - if (entity.PatrolInfo == nullptr) - entity.PatrolInfo = new PatrolArea(); + if (patrolArea.empty()) + { + entity.ClearPatrolArea(); + } else - entity.PatrolInfo->Clear(); - entity.PatrolInfo->Union(patrolArea); + { + 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 646fdf5d01..7530c8f64e 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) diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp index f7c61ce4b7..a4d667e48c 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(); From 0aa6ceb2aa0be1986fe24e9e4d3b39bcd3531e3e Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 8 Mar 2022 00:24:37 +0000 Subject: [PATCH 3/8] Refactor similar loop in s4 and s6 import --- src/openrct2/entity/Staff.cpp | 11 +++++++++++ src/openrct2/entity/Staff.h | 1 + src/openrct2/rct1/S4Importer.cpp | 9 ++------- src/openrct2/rct2/S6Importer.cpp | 9 ++------- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/openrct2/entity/Staff.cpp b/src/openrct2/entity/Staff.cpp index 195056413c..da4dd878df 100644 --- a/src/openrct2/entity/Staff.cpp +++ b/src/openrct2/entity/Staff.cpp @@ -299,6 +299,17 @@ void Staff::SetPatrolArea(const CoordsXY& coords, bool 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) + { + for (int32_t xx = range.GetLeft(); xx <= range.GetRight(); xx += COORDS_XY_STEP) + { + SetPatrolArea({ xx, yy }, value); + } + } +} + void Staff::ClearPatrolArea() { delete PatrolInfo; diff --git a/src/openrct2/entity/Staff.h b/src/openrct2/entity/Staff.h index 472665dc0b..d373e99be7 100644 --- a/src/openrct2/entity/Staff.h +++ b/src/openrct2/entity/Staff.h @@ -63,6 +63,7 @@ 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); diff --git a/src/openrct2/rct1/S4Importer.cpp b/src/openrct2/rct1/S4Importer.cpp index 7530c8f64e..4bd196d03f 100644 --- a/src/openrct2/rct1/S4Importer.cpp +++ b/src/openrct2/rct1/S4Importer.cpp @@ -1361,13 +1361,8 @@ namespace RCT1 x <<= 7; int32_t y = val & 0x3E0; y <<= 2; - for (int32_t offsetY = 0; offsetY < 4 * COORDS_XY_STEP; offsetY += COORDS_XY_STEP) - { - for (int32_t offsetX = 0; offsetX < 4 * COORDS_XY_STEP; offsetX += COORDS_XY_STEP) - { - staffmember->SetPatrolArea({ x + offsetX, y + offsetY }, 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 a4d667e48c..2d1261b32b 100644 --- a/src/openrct2/rct2/S6Importer.cpp +++ b/src/openrct2/rct2/S6Importer.cpp @@ -1516,13 +1516,8 @@ namespace RCT2 x <<= 7; int32_t y = val & 0xFC0; y <<= 1; - for (int32_t offsetY = 0; offsetY < 4 * COORDS_XY_STEP; offsetY += COORDS_XY_STEP) - { - for (int32_t offsetX = 0; offsetX < 4 * COORDS_XY_STEP; offsetX += COORDS_XY_STEP) - { - staffmember->SetPatrolArea({ x + offsetX, y + offsetY }, true); - } - } + staffmember->SetPatrolArea( + MapRange(x, y, x + (4 * COORDS_XY_STEP) - 1, y + (4 * COORDS_XY_STEP) - 1), true); } } } From 97dfe3cb736bdd58ed9a6d9b6017915830619dc6 Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 8 Mar 2022 19:49:43 +0000 Subject: [PATCH 4/8] Refactor std::lower_bound to binary_find --- src/openrct2/core/Algorithm.hpp | 20 ++++++++++++++++++++ src/openrct2/entity/EntityRegistry.cpp | 13 +++++++------ src/openrct2/entity/PatrolArea.cpp | 5 +++-- src/openrct2/libopenrct2.vcxproj | 1 + 4 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 src/openrct2/core/Algorithm.hpp diff --git a/src/openrct2/core/Algorithm.hpp b/src/openrct2/core/Algorithm.hpp new file mode 100644 index 0000000000..1dbeb32cda --- /dev/null +++ b/src/openrct2/core/Algorithm.hpp @@ -0,0 +1,20 @@ +/***************************************************************************** + * 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 +#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 index 92ca2739bf..95405958c5 100644 --- a/src/openrct2/entity/PatrolArea.cpp +++ b/src/openrct2/entity/PatrolArea.cpp @@ -9,6 +9,7 @@ #include "PatrolArea.h" +#include "../core/Algorithm.hpp" #include "EntityList.h" #include "Staff.h" @@ -56,8 +57,8 @@ bool PatrolArea::Get(const TileCoordsXY& pos) const if (area == nullptr) return false; - auto it = std::lower_bound(area->SortedTiles.begin(), area->SortedTiles.end(), pos, CompareTileCoordsXY); - auto found = it != area->SortedTiles.end() && *it == pos; + auto it = binary_find(area->SortedTiles.begin(), area->SortedTiles.end(), pos, CompareTileCoordsXY); + auto found = it != area->SortedTiles.end(); return found; } diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index 94e062dd66..de3aa49e00 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -149,6 +149,7 @@ + From 38d36acef3b02aab0dd11945c208d19f95cd70f3 Mon Sep 17 00:00:00 2001 From: Ted John Date: Fri, 11 Mar 2022 20:26:29 +0000 Subject: [PATCH 5/8] Add patrol area tool window --- data/language/en-GB.txt | 2 + src/openrct2-ui/interface/Theme.cpp | 1 + src/openrct2-ui/libopenrct2ui.vcxproj | 1 + src/openrct2-ui/windows/PatrolArea.cpp | 305 ++++++++++++++++++ src/openrct2-ui/windows/Staff.cpp | 149 ++------- src/openrct2-ui/windows/Themes.cpp | 1 + src/openrct2-ui/windows/Window.h | 3 + src/openrct2/actions/GameAction.h | 8 + .../actions/StaffSetPatrolAreaAction.cpp | 38 +-- .../actions/StaffSetPatrolAreaAction.h | 5 +- src/openrct2/interface/Window.h | 1 + src/openrct2/localisation/StringIds.h | 3 + 12 files changed, 377 insertions(+), 140 deletions(-) create mode 100644 src/openrct2-ui/windows/PatrolArea.cpp 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/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..fe89e273ad --- /dev/null +++ b/src/openrct2-ui/windows/PatrolArea.cpp @@ -0,0 +1,305 @@ +/***************************************************************************** + * 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() + { + 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 current = reinterpret_cast(window_find_by_class(WC_PATROL_AREA)); + if (current != nullptr) + { + if (current->GetStaffId() == staffId) + { + return window_bring_to_front(current); + } + current->Close(); + } + + 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 ecf681b5e9..5172d72cc9 100644 --- a/src/openrct2-ui/windows/Staff.cpp +++ b/src/openrct2-ui/windows/Staff.cpp @@ -135,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); @@ -170,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; @@ -209,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)); @@ -542,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(); - SetPatrolAreaToRender(EntityId::FromUnderlying(w->number)); - gfx_invalidate_screen(); + window_close_by_class(WC_PATROL_AREA); + } + else + { + WindowPatrolAreaOpen(staffId); } } break; @@ -895,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; @@ -1174,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); } /** @@ -1272,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(); - ClearPatrolAreaToRender(); - 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/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/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 2c528ed40e..601292adb2 100644 --- a/src/openrct2/actions/StaffSetPatrolAreaAction.cpp +++ b/src/openrct2/actions/StaffSetPatrolAreaAction.cpp @@ -15,13 +15,20 @@ #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; @@ -30,7 +37,7 @@ 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 @@ -41,11 +48,6 @@ GameActions::Result StaffSetPatrolAreaAction::Query() const 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) { @@ -56,17 +58,9 @@ GameActions::Result StaffSetPatrolAreaAction::Query() const return GameActions::Result(); } -static void InvalidatePatrolTile(const CoordsXY& loc) +static void InvalidatePatrolTiles(const MapRange& range) { - // 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 }); - } - } + map_invalidate_region(range.Point1, range.Point2); } GameActions::Result StaffSetPatrolAreaAction::Execute() const @@ -81,16 +75,16 @@ GameActions::Result StaffSetPatrolAreaAction::Execute() const switch (_mode) { case StaffSetPatrolAreaMode::Set: - staff->SetPatrolArea(_loc, true); - InvalidatePatrolTile(_loc); + staff->SetPatrolArea(_range, true); + InvalidatePatrolTiles(_range); break; case StaffSetPatrolAreaMode::Unset: - staff->SetPatrolArea(_loc, false); + staff->SetPatrolArea(_range, false); if (!staff->HasPatrolArea()) { staff->ClearPatrolArea(); } - InvalidatePatrolTile(_loc); + InvalidatePatrolTiles(_range); break; case StaffSetPatrolAreaMode::ClearAll: staff->ClearPatrolArea(); diff --git a/src/openrct2/actions/StaffSetPatrolAreaAction.h b/src/openrct2/actions/StaffSetPatrolAreaAction.h index 939f608a73..cdfc10b533 100644 --- a/src/openrct2/actions/StaffSetPatrolAreaAction.h +++ b/src/openrct2/actions/StaffSetPatrolAreaAction.h @@ -22,15 +22,16 @@ class StaffSetPatrolAreaAction final : public GameActionBase Date: Fri, 11 Mar 2022 22:39:50 +0000 Subject: [PATCH 6/8] Update replays --- CMakeLists.txt | 4 ++-- openrct2.proj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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/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 From f8224150d5066231e55ebccb2a6117290c2968b1 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 12 Mar 2022 10:26:57 +0000 Subject: [PATCH 7/8] Validate patrol area location --- .../actions/StaffSetPatrolAreaAction.cpp | 69 ++++++++++--------- .../actions/StaffSetPatrolAreaAction.h | 2 + 2 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/openrct2/actions/StaffSetPatrolAreaAction.cpp b/src/openrct2/actions/StaffSetPatrolAreaAction.cpp index 601292adb2..d3ddc9f464 100644 --- a/src/openrct2/actions/StaffSetPatrolAreaAction.cpp +++ b/src/openrct2/actions/StaffSetPatrolAreaAction.cpp @@ -42,20 +42,12 @@ void StaffSetPatrolAreaAction::Serialise(DataSerialiser& stream) 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); - } + return QueryExecute(false); +} - 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(); +GameActions::Result StaffSetPatrolAreaAction::Execute() const +{ + return QueryExecute(true); } static void InvalidatePatrolTiles(const MapRange& range) @@ -63,36 +55,49 @@ static void InvalidatePatrolTiles(const MapRange& range) map_invalidate_region(range.Point1, range.Point2); } -GameActions::Result StaffSetPatrolAreaAction::Execute() const +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(_range, true); - InvalidatePatrolTiles(_range); - break; - case StaffSetPatrolAreaMode::Unset: - staff->SetPatrolArea(_range, 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); } - InvalidatePatrolTiles(_range); - break; - case StaffSetPatrolAreaMode::ClearAll: - staff->ClearPatrolArea(); - gfx_invalidate_screen(); - break; + } } - UpdateConsolidatedPatrolAreas(); - + 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 cdfc10b533..87a1a99f5f 100644 --- a/src/openrct2/actions/StaffSetPatrolAreaAction.h +++ b/src/openrct2/actions/StaffSetPatrolAreaAction.h @@ -25,6 +25,8 @@ private: MapRange _range; StaffSetPatrolAreaMode _mode; + GameActions::Result QueryExecute(bool executing) const; + public: StaffSetPatrolAreaAction() = default; StaffSetPatrolAreaAction(EntityId spriteId, const MapRange& range, const StaffSetPatrolAreaMode mode); From 5334b8f5c8e77af5d6c65b118801d135ce4375f6 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 13 Mar 2022 14:26:17 +0000 Subject: [PATCH 8/8] Do not re-create window when switching staff --- src/openrct2-ui/windows/PatrolArea.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/openrct2-ui/windows/PatrolArea.cpp b/src/openrct2-ui/windows/PatrolArea.cpp index fe89e273ad..cd8ade51fc 100644 --- a/src/openrct2-ui/windows/PatrolArea.cpp +++ b/src/openrct2-ui/windows/PatrolArea.cpp @@ -237,14 +237,22 @@ private: void EnableTool() { - show_gridlines(); - if (!tool_set(this, 0, Tool::WalkDown)) + if (PatrolAreaToolIsActive()) { - input_set_flag(INPUT_FLAG_6, true); - show_gridlines(); 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() @@ -280,16 +288,6 @@ private: rct_window* WindowPatrolAreaOpen(EntityId staffId) { - auto current = reinterpret_cast(window_find_by_class(WC_PATROL_AREA)); - if (current != nullptr) - { - if (current->GetStaffId() == staffId) - { - return window_bring_to_front(current); - } - current->Close(); - } - auto w = WindowFocusOrCreate(WC_PATROL_AREA, ScreenCoordsXY(context_get_width() - WW, 29), WW, WH, 0); if (w != nullptr) {