diff --git a/CMakeLists.txt b/CMakeLists.txt
index 729764dfc3..3e83b17257 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -80,9 +80,9 @@ set(OPENMSX_VERSION "1.6")
set(OPENMSX_URL "https://github.com/OpenRCT2/OpenMusic/releases/download/v${OPENMSX_VERSION}/openmusic.zip")
set(OPENMSX_SHA1 "ba170fa6d777b309c15420f4b6eb3fa25082a9d1")
-set(REPLAYS_VERSION "0.0.81")
+set(REPLAYS_VERSION "0.0.83")
set(REPLAYS_URL "https://github.com/OpenRCT2/replays/releases/download/v${REPLAYS_VERSION}/replays.zip")
-set(REPLAYS_SHA1 "F698526D1D2DABEF80F350A6363223D67DBC96B1")
+set(REPLAYS_SHA1 "FFC98C36AFEC68DC6A48E863413D4E2364A202B3")
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 46c0fbb70d..6d9bf56efd 100644
--- a/openrct2.proj
+++ b/openrct2.proj
@@ -51,8 +51,8 @@
b1b1f1b241d2cbff63a1889c4dc5a09bdf769bfb
https://github.com/OpenRCT2/OpenMusic/releases/download/v1.6/openmusic.zip
ba170fa6d777b309c15420f4b6eb3fa25082a9d1
- https://github.com/OpenRCT2/replays/releases/download/v0.0.81/replays.zip
- F698526D1D2DABEF80F350A6363223D67DBC96B1
+ https://github.com/OpenRCT2/replays/releases/download/v0.0.83/replays.zip
+ FFC98C36AFEC68DC6A48E863413D4E2364A202B3
diff --git a/src/openrct2/GameState.cpp b/src/openrct2/GameState.cpp
index 9fe9a21a36..7196278d68 100644
--- a/src/openrct2/GameState.cpp
+++ b/src/openrct2/GameState.cpp
@@ -332,6 +332,8 @@ namespace OpenRCT2
// Update windows
// WindowDispatchUpdateAll();
+ UpdateEntitiesSpatialIndex();
+
// Start autosave timer after update
if (gLastAutoSaveUpdate == kAutosavePause)
{
diff --git a/src/openrct2/entity/EntityBase.cpp b/src/openrct2/entity/EntityBase.cpp
index 7b61eb001c..862211a732 100644
--- a/src/openrct2/entity/EntityBase.cpp
+++ b/src/openrct2/entity/EntityBase.cpp
@@ -20,18 +20,6 @@ template<> bool EntityBase::Is() const
return true;
}
-CoordsXYZ EntityBase::GetLocation() const
-{
- return { x, y, z };
-}
-
-void EntityBase::SetLocation(const CoordsXYZ& newLocation)
-{
- x = newLocation.x;
- y = newLocation.y;
- z = newLocation.z;
-}
-
void EntityBase::Invalidate()
{
if (x == kLocationNull)
diff --git a/src/openrct2/entity/EntityBase.h b/src/openrct2/entity/EntityBase.h
index 9c43108192..bee06c1021 100644
--- a/src/openrct2/entity/EntityBase.h
+++ b/src/openrct2/entity/EntityBase.h
@@ -44,6 +44,7 @@ struct EntityBase
EntitySpriteData SpriteData;
// Used as direction or rotation depending on the entity.
uint8_t Orientation;
+ uint32_t SpatialIndex;
/**
* Moves a sprite to a new location, invalidates the current position if valid
diff --git a/src/openrct2/entity/EntityRegistry.cpp b/src/openrct2/entity/EntityRegistry.cpp
index 18ffc5b881..3a0104a815 100644
--- a/src/openrct2/entity/EntityRegistry.cpp
+++ b/src/openrct2/entity/EntityRegistry.cpp
@@ -46,28 +46,36 @@ static std::vector _freeIdList;
static bool _entityFlashingList[MAX_ENTITIES];
-constexpr const uint32_t SPATIAL_INDEX_SIZE = (kMaximumMapSizeTechnical * kMaximumMapSizeTechnical) + 1;
-constexpr uint32_t SPATIAL_INDEX_LOCATION_NULL = SPATIAL_INDEX_SIZE - 1;
+static constexpr const uint32_t kSpatialIndexSize = (kMaximumMapSizeTechnical * kMaximumMapSizeTechnical) + 1;
+static constexpr uint32_t kSpatialIndexNullBucket = kSpatialIndexSize - 1;
-static std::array, SPATIAL_INDEX_SIZE> gEntitySpatialIndex;
+static constexpr uint32_t kInvalidSpatialIndex = 0xFFFFFFFFu;
+static constexpr uint32_t kSpatialIndexDirtyMask = 1u << 31;
+
+static std::array, kSpatialIndexSize> gEntitySpatialIndex;
static void FreeEntity(EntityBase& entity);
-static constexpr size_t GetSpatialIndexOffset(const CoordsXY& loc)
+static constexpr uint32_t ComputeSpatialIndex(const CoordsXY& loc)
{
if (loc.IsNull())
- return SPATIAL_INDEX_LOCATION_NULL;
+ return kSpatialIndexNullBucket;
// NOTE: The input coordinate is rotated and can have negative components.
const auto tileX = std::abs(loc.x) / kCoordsXYStep;
const auto tileY = std::abs(loc.y) / kCoordsXYStep;
if (tileX >= kMaximumMapSizeTechnical || tileY >= kMaximumMapSizeTechnical)
- return SPATIAL_INDEX_LOCATION_NULL;
+ return kSpatialIndexNullBucket;
return tileX * kMaximumMapSizeTechnical + tileY;
}
+static constexpr uint32_t GetSpatialIndex(EntityBase* entity)
+{
+ return entity->SpatialIndex & ~kSpatialIndexDirtyMask;
+}
+
constexpr bool EntityTypeIsMiscEntity(const EntityType type)
{
switch (type)
@@ -121,7 +129,7 @@ EntityBase* GetEntity(EntityId entityIndex)
const std::vector& GetEntityTileList(const CoordsXY& spritePos)
{
- return gEntitySpatialIndex[GetSpatialIndexOffset(spritePos)];
+ return gEntitySpatialIndex[ComputeSpatialIndex(spritePos)];
}
static void ResetEntityLists()
@@ -204,10 +212,10 @@ void ResetEntitySpatialIndices()
}
for (EntityId::UnderlyingType i = 0; i < MAX_ENTITIES; i++)
{
- auto* spr = GetEntity(EntityId::FromUnderlying(i));
- if (spr != nullptr && spr->Type != EntityType::Null)
+ auto* entity = GetEntity(EntityId::FromUnderlying(i));
+ if (entity != nullptr && entity->Type != EntityType::Null)
{
- EntitySpatialInsert(spr, { spr->x, spr->y });
+ EntitySpatialInsert(entity, { entity->x, entity->y });
}
}
}
@@ -312,6 +320,7 @@ static void PrepareNewEntity(EntityBase* base, const EntityType type)
base->SpriteData.HeightMin = 0x14;
base->SpriteData.HeightMax = 0x8;
base->SpriteData.SpriteRect = {};
+ base->SpatialIndex = kInvalidSpatialIndex;
EntitySpatialInsert(base, { kLocationNull, 0 });
}
@@ -405,15 +414,19 @@ void UpdateMoneyEffect()
// Performs a search to ensure that insert keeps next_in_quadrant in sprite_index order
static void EntitySpatialInsert(EntityBase* entity, const CoordsXY& newLoc)
{
- size_t newIndex = GetSpatialIndexOffset(newLoc);
+ const auto newIndex = ComputeSpatialIndex(newLoc);
+
auto& spatialVector = gEntitySpatialIndex[newIndex];
auto index = std::lower_bound(std::begin(spatialVector), std::end(spatialVector), entity->Id);
spatialVector.insert(index, entity->Id);
+
+ entity->SpatialIndex = newIndex;
}
static void EntitySpatialRemove(EntityBase* entity)
{
- size_t currentIndex = GetSpatialIndexOffset({ entity->x, entity->y });
+ const auto currentIndex = GetSpatialIndex(entity);
+
auto& spatialVector = gEntitySpatialIndex[currentIndex];
auto index = BinaryFind(std::begin(spatialVector), std::end(spatialVector), entity->Id);
if (index != std::end(spatialVector))
@@ -425,17 +438,43 @@ static void EntitySpatialRemove(EntityBase* entity)
LOG_WARNING("Bad sprite spatial index. Rebuilding the spatial index...");
ResetEntitySpatialIndices();
}
+
+ entity->SpatialIndex = kInvalidSpatialIndex;
}
-static void EntitySpatialMove(EntityBase* entity, const CoordsXY& newLoc)
+void UpdateEntitiesSpatialIndex()
{
- size_t newIndex = GetSpatialIndexOffset(newLoc);
- size_t currentIndex = GetSpatialIndexOffset({ entity->x, entity->y });
- if (newIndex == currentIndex)
- return;
+ for (auto& entityList : gEntityLists)
+ {
+ for (auto& entityId : entityList)
+ {
+ auto* entity = GetEntity(entityId);
+ if (entity == nullptr || entity->Type == EntityType::Null)
+ continue;
- EntitySpatialRemove(entity);
- EntitySpatialInsert(entity, newLoc);
+ if (entity->SpatialIndex & kSpatialIndexDirtyMask)
+ {
+ if (entity->SpatialIndex != kInvalidSpatialIndex)
+ {
+ EntitySpatialRemove(entity);
+ }
+ EntitySpatialInsert(entity, { entity->x, entity->y });
+ }
+ }
+ }
+}
+
+CoordsXYZ EntityBase::GetLocation() const
+{
+ return { x, y, z };
+}
+
+void EntityBase::SetLocation(const CoordsXYZ& newLocation)
+{
+ x = newLocation.x;
+ y = newLocation.y;
+ z = newLocation.z;
+ SpatialIndex |= kSpatialIndexDirtyMask;
}
void EntityBase::MoveTo(const CoordsXYZ& newLocation)
@@ -452,13 +491,9 @@ void EntityBase::MoveTo(const CoordsXYZ& newLocation)
loc.x = kLocationNull;
}
- EntitySpatialMove(this, loc);
-
if (loc.x == kLocationNull)
{
- x = loc.x;
- y = loc.y;
- z = loc.z;
+ SetLocation(loc);
}
else
{
diff --git a/src/openrct2/entity/EntityRegistry.h b/src/openrct2/entity/EntityRegistry.h
index 68affac7e7..7fea52cd94 100644
--- a/src/openrct2/entity/EntityRegistry.h
+++ b/src/openrct2/entity/EntityRegistry.h
@@ -66,6 +66,7 @@ void UpdateMoneyEffect();
void EntitySetCoordinates(const CoordsXYZ& entityPos, EntityBase* entity);
void EntityRemove(EntityBase* entity);
uint16_t RemoveFloatingEntities();
+void UpdateEntitiesSpatialIndex();
#pragma pack(push, 1)
struct EntitiesChecksum
diff --git a/src/openrct2/entity/EntityTweener.cpp b/src/openrct2/entity/EntityTweener.cpp
index bfd40100a6..52bc453237 100644
--- a/src/openrct2/entity/EntityTweener.cpp
+++ b/src/openrct2/entity/EntityTweener.cpp
@@ -97,14 +97,12 @@ void EntityTweener::Tween(float alpha)
if (posA == posB)
continue;
- ent->Invalidate();
- EntitySetCoordinates(
- { static_cast(std::round(posB.x * alpha + posA.x * inv)),
- static_cast(std::round(posB.y * alpha + posA.y * inv)),
- static_cast(std::round(posB.z * alpha + posA.z * inv)) },
- ent);
- ent->Invalidate();
+ ent->MoveTo({ static_cast(std::round(posB.x * alpha + posA.x * inv)),
+ static_cast(std::round(posB.y * alpha + posA.y * inv)),
+ static_cast(std::round(posB.z * alpha + posA.z * inv)) });
}
+
+ UpdateEntitiesSpatialIndex();
}
void EntityTweener::Restore()
diff --git a/src/openrct2/network/NetworkBase.cpp b/src/openrct2/network/NetworkBase.cpp
index 8cc33fafcb..a1c1edf24d 100644
--- a/src/openrct2/network/NetworkBase.cpp
+++ b/src/openrct2/network/NetworkBase.cpp
@@ -49,7 +49,7 @@ using namespace OpenRCT2;
// It is used for making sure only compatible builds get connected, even within
// single OpenRCT2 version.
-constexpr uint8_t kNetworkStreamVersion = 6;
+constexpr uint8_t kNetworkStreamVersion = 7;
const std::string kNetworkStreamID = std::string(OPENRCT2_VERSION) + "-" + std::to_string(kNetworkStreamVersion);