From 566bc4d3115c770b4cc50dbdd4e5ba81d85a293d Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 2 Mar 2022 00:44:11 +0000 Subject: [PATCH] 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); + } + } } } }