mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-15 11:03:00 +01:00
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.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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<PatrolArea*>(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<TileCoordsXY>& other)
|
||||
{
|
||||
for (const auto& pos : other)
|
||||
{
|
||||
Set(pos, true);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TileCoordsXY> PatrolArea::ToVector() const
|
||||
{
|
||||
std::vector<TileCoordsXY> 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<Staff>())
|
||||
{
|
||||
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<int32_t, int32_t> 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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<TileCoordsXY> 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<Cell, NumCells> 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<TileCoordsXY>& other);
|
||||
std::vector<TileCoordsXY> ToVector() const;
|
||||
};
|
||||
|
||||
struct Staff : Peep
|
||||
@@ -73,6 +102,8 @@ public:
|
||||
void ClearPatrolArea();
|
||||
void SetPatrolArea(const CoordsXY& coords, bool value);
|
||||
bool HasPatrolArea() const;
|
||||
std::vector<TileCoordsXY> GetPatrolArea();
|
||||
void SetPatrolArea(const std::vector<TileCoordsXY>& area);
|
||||
|
||||
private:
|
||||
void UpdatePatrolling();
|
||||
|
||||
@@ -2015,64 +2015,23 @@ namespace OpenRCT2
|
||||
cs.ReadWrite(guest.ItemFlags);
|
||||
}
|
||||
|
||||
static std::vector<TileCoordsXY> GetPatrolArea(Staff& staff)
|
||||
{
|
||||
std::vector<TileCoordsXY> 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<int32_t>(sx + x), static_cast<int32_t>(sy + y) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return area;
|
||||
}
|
||||
|
||||
static void SetPatrolArea(Staff& staff, const std::vector<TileCoordsXY>& 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<TileCoordsXY> 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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user