diff --git a/src/openrct2/GameStateSnapshots.cpp b/src/openrct2/GameStateSnapshots.cpp index 25c28f3356..c104cfbad5 100644 --- a/src/openrct2/GameStateSnapshots.cpp +++ b/src/openrct2/GameStateSnapshots.cpp @@ -26,6 +26,8 @@ static constexpr size_t kMaximumGameStateSnapshots = 32; static constexpr uint32_t kInvalidTick = 0xFFFFFFFF; +using namespace OpenRCT2; + #pragma pack(push, 1) union EntitySnapshot { diff --git a/src/openrct2/entity/EntityList.h b/src/openrct2/entity/EntityList.h index 4fa21bbe10..93a943d00d 100644 --- a/src/openrct2/entity/EntityList.h +++ b/src/openrct2/entity/EntityList.h @@ -17,158 +17,161 @@ #include #include -const std::list& GetEntityList(const EntityType id); - -uint16_t GetEntityListCount(EntityType list); -uint16_t GetMiscEntityCount(); -uint16_t GetNumFreeEntities(); -const std::vector& GetEntityTileList(const CoordsXY& spritePos); - -template -class EntityTileIterator +namespace OpenRCT2 { -private: - std::vector::const_iterator iter; - std::vector::const_iterator end; - T* Entity = nullptr; + const std::list& GetEntityList(const EntityType id); -public: - EntityTileIterator(std::vector::const_iterator _iter, std::vector::const_iterator _end) - : iter(_iter) - , end(_end) - { - ++(*this); - } - EntityTileIterator& operator++() - { - Entity = nullptr; + uint16_t GetEntityListCount(EntityType list); + uint16_t GetMiscEntityCount(); + uint16_t GetNumFreeEntities(); + const std::vector& GetEntityTileList(const CoordsXY& spritePos); - while (iter != end && Entity == nullptr) + template + class EntityTileIterator + { + private: + std::vector::const_iterator iter; + std::vector::const_iterator end; + T* Entity = nullptr; + + public: + EntityTileIterator(std::vector::const_iterator _iter, std::vector::const_iterator _end) + : iter(_iter) + , end(_end) { - Entity = TryGetEntity(*iter++); + ++(*this); } - return *this; - } - - EntityTileIterator operator++(int) - { - EntityTileIterator retval = *this; - ++(*this); - return *iter; - } - bool operator==(EntityTileIterator other) const - { - return Entity == other.Entity; - } - bool operator!=(EntityTileIterator other) const - { - return !(*this == other); - } - T* operator*() - { - return Entity; - } - // iterator traits - using difference_type = std::ptrdiff_t; - using value_type = T; - using pointer = const T*; - using reference = const T&; - using iterator_category = std::forward_iterator_tag; -}; - -template -class EntityTileList -{ -private: - const std::vector& vec; - -public: - EntityTileList(const CoordsXY& loc) - : vec(GetEntityTileList(loc)) - { - } - - EntityTileIterator begin() - { - return EntityTileIterator(std::begin(vec), std::end(vec)); - } - EntityTileIterator end() - { - return EntityTileIterator(std::end(vec), std::end(vec)); - } -}; - -template -class EntityListIterator -{ -private: - std::list::const_iterator iter; - std::list::const_iterator end; - T* Entity = nullptr; - -public: - EntityListIterator(std::list::const_iterator _iter, std::list::const_iterator _end) - : iter(_iter) - , end(_end) - { - ++(*this); - } - EntityListIterator& operator++() - { - Entity = nullptr; - - while (iter != end && Entity == nullptr) + EntityTileIterator& operator++() { - Entity = TryGetEntity(*iter++); + Entity = nullptr; + + while (iter != end && Entity == nullptr) + { + Entity = TryGetEntity(*iter++); + } + return *this; } - return *this; - } - EntityListIterator operator++(int) - { - EntityListIterator retval = *this; - ++(*this); - return *iter; - } - bool operator==(EntityListIterator other) const - { - return Entity == other.Entity; - } - bool operator!=(EntityListIterator other) const - { - return !(*this == other); - } - T* operator*() - { - return Entity; - } - // iterator traits - using difference_type = std::ptrdiff_t; - using value_type = T; - using pointer = const T*; - using reference = const T&; - using iterator_category = std::forward_iterator_tag; -}; + EntityTileIterator operator++(int) + { + EntityTileIterator retval = *this; + ++(*this); + return *iter; + } + bool operator==(EntityTileIterator other) const + { + return Entity == other.Entity; + } + bool operator!=(EntityTileIterator other) const + { + return !(*this == other); + } + T* operator*() + { + return Entity; + } + // iterator traits + using difference_type = std::ptrdiff_t; + using value_type = T; + using pointer = const T*; + using reference = const T&; + using iterator_category = std::forward_iterator_tag; + }; -template -class EntityList -{ -private: - using EntityListIterator_t = EntityListIterator; - const std::list& vec; + template + class EntityTileList + { + private: + const std::vector& vec; -public: - EntityList() - : vec(GetEntityList(T::cEntityType)) - { - } + public: + EntityTileList(const CoordsXY& loc) + : vec(GetEntityTileList(loc)) + { + } - EntityListIterator_t begin() const + EntityTileIterator begin() + { + return EntityTileIterator(std::begin(vec), std::end(vec)); + } + EntityTileIterator end() + { + return EntityTileIterator(std::end(vec), std::end(vec)); + } + }; + + template + class EntityListIterator { - return EntityListIterator_t(std::cbegin(vec), std::cend(vec)); - } - EntityListIterator_t end() const + private: + std::list::const_iterator iter; + std::list::const_iterator end; + T* Entity = nullptr; + + public: + EntityListIterator(std::list::const_iterator _iter, std::list::const_iterator _end) + : iter(_iter) + , end(_end) + { + ++(*this); + } + EntityListIterator& operator++() + { + Entity = nullptr; + + while (iter != end && Entity == nullptr) + { + Entity = TryGetEntity(*iter++); + } + return *this; + } + + EntityListIterator operator++(int) + { + EntityListIterator retval = *this; + ++(*this); + return *iter; + } + bool operator==(EntityListIterator other) const + { + return Entity == other.Entity; + } + bool operator!=(EntityListIterator other) const + { + return !(*this == other); + } + T* operator*() + { + return Entity; + } + // iterator traits + using difference_type = std::ptrdiff_t; + using value_type = T; + using pointer = const T*; + using reference = const T&; + using iterator_category = std::forward_iterator_tag; + }; + + template + class EntityList { - return EntityListIterator_t(std::cend(vec), std::cend(vec)); - } -}; + private: + using EntityListIterator_t = EntityListIterator; + const std::list& vec; + + public: + EntityList() + : vec(GetEntityList(T::cEntityType)) + { + } + + EntityListIterator_t begin() const + { + return EntityListIterator_t(std::cbegin(vec), std::cend(vec)); + } + EntityListIterator_t end() const + { + return EntityListIterator_t(std::cend(vec), std::cend(vec)); + } + }; +} // namespace OpenRCT2 diff --git a/src/openrct2/entity/EntityRegistry.cpp b/src/openrct2/entity/EntityRegistry.cpp index e18a45b93c..128ef5b26e 100644 --- a/src/openrct2/entity/EntityRegistry.cpp +++ b/src/openrct2/entity/EntityRegistry.cpp @@ -39,441 +39,448 @@ #include #include -using namespace OpenRCT2; -using namespace OpenRCT2::Core; - -static std::array, EnumValue(EntityType::Count)> gEntityLists; -static std::vector _freeIdList; - -static bool _entityFlashingList[kMaxEntities]; - -static constexpr const uint32_t kSpatialIndexSize = (kMaximumMapSizeTechnical * kMaximumMapSizeTechnical) + 1; -static constexpr uint32_t kSpatialIndexNullBucket = kSpatialIndexSize - 1; - -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 uint32_t ComputeSpatialIndex(const CoordsXY& loc) +namespace OpenRCT2 { - if (loc.IsNull()) - return kSpatialIndexNullBucket; + using namespace OpenRCT2::Core; - // 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; + static constexpr const uint32_t kSpatialIndexSize = (kMaximumMapSizeTechnical * kMaximumMapSizeTechnical) + 1; + static constexpr uint32_t kSpatialIndexNullBucket = kSpatialIndexSize - 1; - if (tileX >= kMaximumMapSizeTechnical || tileY >= kMaximumMapSizeTechnical) - return kSpatialIndexNullBucket; + static constexpr uint32_t kInvalidSpatialIndex = 0xFFFFFFFFu; + static constexpr uint32_t kSpatialIndexDirtyMask = 1u << 31; - return tileX * kMaximumMapSizeTechnical + tileY; -} + // TODO: move into GameState_t + static std::array, EnumValue(EntityType::Count)> gEntityLists; + static std::vector _freeIdList; -static constexpr uint32_t GetSpatialIndex(EntityBase& entity) -{ - return entity.SpatialIndex & ~kSpatialIndexDirtyMask; -} + // TODO: move into MapWindow or GameState_t? + static bool _entityFlashingList[kMaxEntities]; -constexpr bool EntityTypeIsMiscEntity(const EntityType type) -{ - switch (type) + // TODO: move into GameState_t + static std::array, kSpatialIndexSize> gEntitySpatialIndex; + + static void FreeEntity(EntityBase& entity); + + static constexpr uint32_t ComputeSpatialIndex(const CoordsXY& loc) { - case EntityType::SteamParticle: - case EntityType::MoneyEffect: - case EntityType::CrashedVehicleParticle: - case EntityType::ExplosionCloud: - case EntityType::CrashSplash: - case EntityType::ExplosionFlare: - case EntityType::JumpingFountain: - case EntityType::Balloon: - case EntityType::Duck: - return true; - default: - return false; + if (loc.IsNull()) + 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 kSpatialIndexNullBucket; + + return tileX * kMaximumMapSizeTechnical + tileY; } -} -uint16_t GetEntityListCount(EntityType type) -{ - return static_cast(gEntityLists[EnumValue(type)].size()); -} - -uint16_t GetNumFreeEntities() -{ - return static_cast(_freeIdList.size()); -} - -std::string EntitiesChecksum::ToString() const -{ - return String::StringFromHex(raw); -} - -EntityBase* TryGetEntity(EntityId entityIndex) -{ - auto& gameState = getGameState(); - const auto idx = entityIndex.ToUnderlying(); - return idx >= kMaxEntities ? nullptr : &gameState.entities[idx].base; -} - -EntityBase* GetEntity(EntityId entityIndex) -{ - if (entityIndex.IsNull()) + static constexpr uint32_t GetSpatialIndex(EntityBase& entity) { - return nullptr; + return entity.SpatialIndex & ~kSpatialIndexDirtyMask; } - Guard::Assert(entityIndex.ToUnderlying() < kMaxEntities, "Tried getting entity %u", entityIndex.ToUnderlying()); - return TryGetEntity(entityIndex); -} -const std::vector& GetEntityTileList(const CoordsXY& spritePos) -{ - return gEntitySpatialIndex[ComputeSpatialIndex(spritePos)]; -} - -static void ResetEntityLists() -{ - for (auto& list : gEntityLists) + constexpr bool EntityTypeIsMiscEntity(const EntityType type) { - list.clear(); - } -} - -static void ResetFreeIds() -{ - _freeIdList.clear(); - _freeIdList.resize(kMaxEntities); - - // List needs to be back to front to simplify removing - auto nextId = 0; - std::for_each(std::rbegin(_freeIdList), std::rend(_freeIdList), [&](auto& elem) { - elem = EntityId::FromUnderlying(nextId); - nextId++; - }); -} - -const std::list& GetEntityList(const EntityType id) -{ - return gEntityLists[EnumValue(id)]; -} - -/** - * - * rct2: 0x0069EB13 - */ -void ResetAllEntities() -{ - // Free all associated Entity pointers prior to zeroing memory - for (int32_t i = 0; i < kMaxEntities; ++i) - { - auto* spr = GetEntity(EntityId::FromUnderlying(i)); - if (spr == nullptr) + switch (type) { - continue; - } - FreeEntity(*spr); - } - - auto& gameState = getGameState(); - std::fill(std::begin(gameState.entities), std::end(gameState.entities), Entity_t()); - OpenRCT2::RideUse::GetHistory().Clear(); - OpenRCT2::RideUse::GetTypeHistory().Clear(); - for (int32_t i = 0; i < kMaxEntities; ++i) - { - auto* spr = GetEntity(EntityId::FromUnderlying(i)); - if (spr == nullptr) - { - continue; - } - spr->Type = EntityType::Null; - spr->Id = EntityId::FromUnderlying(i); - - _entityFlashingList[i] = false; - } - ResetEntityLists(); - ResetFreeIds(); - ResetEntitySpatialIndices(); -} - -static void EntitySpatialInsert(EntityBase& entity, const CoordsXY& newLoc); - -/** - * - * rct2: 0x0069EBE4 - * This function looks as though it sets some sort of order for sprites. - * Sprites can share their position if this is the case. - */ -void ResetEntitySpatialIndices() -{ - for (auto& vec : gEntitySpatialIndex) - { - vec.clear(); - } - for (EntityId::UnderlyingType i = 0; i < kMaxEntities; i++) - { - auto* entity = GetEntity(EntityId::FromUnderlying(i)); - if (entity != nullptr && entity->Type != EntityType::Null) - { - EntitySpatialInsert(*entity, { entity->x, entity->y }); + case EntityType::SteamParticle: + case EntityType::MoneyEffect: + case EntityType::CrashedVehicleParticle: + case EntityType::ExplosionCloud: + case EntityType::CrashSplash: + case EntityType::ExplosionFlare: + case EntityType::JumpingFountain: + case EntityType::Balloon: + case EntityType::Duck: + return true; + default: + return false; } } -} -#ifndef DISABLE_NETWORK - -template -void NetworkSerialseEntityType(DataSerialiser& ds) -{ - for (auto* ent : EntityList()) + uint16_t GetEntityListCount(EntityType type) { - ent->Serialise(ds); - } -} - -template -void NetworkSerialiseEntityTypes(DataSerialiser& ds) -{ - (NetworkSerialseEntityType(ds), ...); -} - -EntitiesChecksum GetAllEntitiesChecksum() -{ - EntitiesChecksum checksum{}; - - OpenRCT2::ChecksumStream ms(checksum.raw); - DataSerialiser ds(true, ms); - NetworkSerialiseEntityTypes(ds); - - return checksum; -} -#else - -EntitiesChecksum GetAllEntitiesChecksum() -{ - return EntitiesChecksum{}; -} - -#endif // DISABLE_NETWORK - -static void EntityReset(EntityBase& entity) -{ - // Need to retain how the sprite is linked in lists - auto entityIndex = entity.Id; - _entityFlashingList[entityIndex.ToUnderlying()] = false; - - Entity_t* tempEntity = reinterpret_cast(&entity); - *tempEntity = Entity_t(); - - entity.Id = entityIndex; - entity.Type = EntityType::Null; -} - -static constexpr uint16_t kMaxMiscEntities = 3200; - -static void AddToEntityList(EntityBase& entity) -{ - auto& list = gEntityLists[EnumValue(entity.Type)]; - - // Entity list is sorted by Id to prevent desyncs. - Algorithm::sortedInsert(list, entity.Id); -} - -static void AddToFreeList(EntityId index) -{ - // Free list must be in reverse sprite_index order to prevent desync issues - _freeIdList.insert(std::upper_bound(std::rbegin(_freeIdList), std::rend(_freeIdList), index).base(), index); -} - -static void RemoveFromEntityList(EntityBase& entity) -{ - auto& list = gEntityLists[EnumValue(entity.Type)]; - auto ptr = Algorithm::binaryFind(std::begin(list), std::end(list), entity.Id); - if (ptr != std::end(list)) - { - list.erase(ptr); - } -} - -uint16_t GetMiscEntityCount() -{ - uint16_t count = 0; - for (auto id : { EntityType::SteamParticle, EntityType::MoneyEffect, EntityType::CrashedVehicleParticle, - EntityType::ExplosionCloud, EntityType::CrashSplash, EntityType::ExplosionFlare, - EntityType::JumpingFountain, EntityType::Balloon, EntityType::Duck }) - { - count += GetEntityListCount(id); - } - return count; -} - -static void PrepareNewEntity(EntityBase& base, const EntityType type) -{ - // Need to reset all sprite data, as the uninitialised values - // may contain garbage and cause a desync later on. - EntityReset(base); - - base.Type = type; - AddToEntityList(base); - - base.x = kLocationNull; - base.y = kLocationNull; - base.z = 0; - base.SpriteData.Width = 0x10; - base.SpriteData.HeightMin = 0x14; - base.SpriteData.HeightMax = 0x8; - base.SpriteData.SpriteRect = {}; - base.SpatialIndex = kInvalidSpatialIndex; - - EntitySpatialInsert(base, { kLocationNull, 0 }); -} - -EntityBase* CreateEntity(EntityType type) -{ - if (_freeIdList.size() == 0) - { - // No free sprites. - return nullptr; + return static_cast(gEntityLists[EnumValue(type)].size()); } - if (EntityTypeIsMiscEntity(type)) + uint16_t GetNumFreeEntities() { - // Misc sprites are commonly used for effects, give other entity types higher priority. - if (GetMiscEntityCount() >= kMaxMiscEntities) + return static_cast(_freeIdList.size()); + } + + std::string EntitiesChecksum::ToString() const + { + return String::StringFromHex(raw); + } + + EntityBase* TryGetEntity(EntityId entityIndex) + { + auto& gameState = getGameState(); + const auto idx = entityIndex.ToUnderlying(); + return idx >= kMaxEntities ? nullptr : &gameState.entities[idx].base; + } + + EntityBase* GetEntity(EntityId entityIndex) + { + if (entityIndex.IsNull()) { return nullptr; } + Guard::Assert(entityIndex.ToUnderlying() < kMaxEntities, "Tried getting entity %u", entityIndex.ToUnderlying()); + return TryGetEntity(entityIndex); + } - // If there are less than kMaxMiscEntities free slots, ensure other entities can be created. - if (_freeIdList.size() < kMaxMiscEntities) + const std::vector& GetEntityTileList(const CoordsXY& spritePos) + { + return gEntitySpatialIndex[ComputeSpatialIndex(spritePos)]; + } + + static void ResetEntityLists() + { + for (auto& list : gEntityLists) { - return nullptr; + list.clear(); } } - auto* entity = GetEntity(_freeIdList.back()); - if (entity == nullptr) + static void ResetFreeIds() { - return nullptr; - } - _freeIdList.pop_back(); + _freeIdList.clear(); + _freeIdList.resize(kMaxEntities); - PrepareNewEntity(*entity, type); - - return entity; -} - -EntityBase* CreateEntityAt(const EntityId index, const EntityType type) -{ - auto id = Algorithm::binaryFind(std::rbegin(_freeIdList), std::rend(_freeIdList), index); - if (id == std::rend(_freeIdList)) - { - return nullptr; + // List needs to be back to front to simplify removing + auto nextId = 0; + std::for_each(std::rbegin(_freeIdList), std::rend(_freeIdList), [&](auto& elem) { + elem = EntityId::FromUnderlying(nextId); + nextId++; + }); } - auto* entity = GetEntity(index); - if (entity == nullptr) + const std::list& GetEntityList(const EntityType id) { - return nullptr; + return gEntityLists[EnumValue(id)]; } - _freeIdList.erase(std::next(id).base()); - - PrepareNewEntity(*entity, type); - return entity; -} - -template -void MiscUpdateAllType() -{ - for (auto misc : EntityList()) + /** + * + * rct2: 0x0069EB13 + */ + void ResetAllEntities() { - misc->Update(); - } -} + // Free all associated Entity pointers prior to zeroing memory + for (int32_t i = 0; i < kMaxEntities; ++i) + { + auto* spr = GetEntity(EntityId::FromUnderlying(i)); + if (spr == nullptr) + { + continue; + } + FreeEntity(*spr); + } -template -void MiscUpdateAllTypes() -{ - (MiscUpdateAllType(), ...); -} + auto& gameState = getGameState(); + std::fill(std::begin(gameState.entities), std::end(gameState.entities), Entity_t()); + OpenRCT2::RideUse::GetHistory().Clear(); + OpenRCT2::RideUse::GetTypeHistory().Clear(); + for (int32_t i = 0; i < kMaxEntities; ++i) + { + auto* spr = GetEntity(EntityId::FromUnderlying(i)); + if (spr == nullptr) + { + continue; + } + spr->Type = EntityType::Null; + spr->Id = EntityId::FromUnderlying(i); -/** - * - * rct2: 0x00672AA4 - */ -void UpdateAllMiscEntities() -{ - PROFILED_FUNCTION(); - - MiscUpdateAllTypes< - SteamParticle, MoneyEffect, VehicleCrashParticle, ExplosionCloud, CrashSplashParticle, ExplosionFlare, JumpingFountain, - Balloon, Duck>(); -} - -void UpdateMoneyEffect() -{ - MiscUpdateAllTypes(); -} - -// Performs a search to ensure that insert keeps next_in_quadrant in sprite_index order -static void EntitySpatialInsert(EntityBase& entity, const CoordsXY& newLoc) -{ - const auto newIndex = ComputeSpatialIndex(newLoc); - - auto& spatialVector = gEntitySpatialIndex[newIndex]; - - Algorithm::sortedInsert(spatialVector, entity.Id); - - entity.SpatialIndex = newIndex; -} - -static void EntitySpatialRemove(EntityBase& entity) -{ - const auto currentIndex = GetSpatialIndex(entity); - - auto& spatialVector = gEntitySpatialIndex[currentIndex]; - auto index = Algorithm::binaryFind(std::begin(spatialVector), std::end(spatialVector), entity.Id); - if (index != std::end(spatialVector)) - { - spatialVector.erase(index, index + 1); - } - else - { - LOG_WARNING("Bad sprite spatial index. Rebuilding the spatial index..."); + _entityFlashingList[i] = false; + } + ResetEntityLists(); + ResetFreeIds(); ResetEntitySpatialIndices(); } - entity.SpatialIndex = kInvalidSpatialIndex; -} + static void EntitySpatialInsert(EntityBase& entity, const CoordsXY& newLoc); -static void UpdateEntitySpatialIndex(EntityBase& entity) -{ - if (entity.SpatialIndex & kSpatialIndexDirtyMask) + /** + * + * rct2: 0x0069EBE4 + * This function looks as though it sets some sort of order for sprites. + * Sprites can share their position if this is the case. + */ + void ResetEntitySpatialIndices() { - if (entity.SpatialIndex != kInvalidSpatialIndex) + for (auto& vec : gEntitySpatialIndex) { - EntitySpatialRemove(entity); + vec.clear(); } - EntitySpatialInsert(entity, { entity.x, entity.y }); - } -} - -void UpdateEntitiesSpatialIndex() -{ - for (auto& entityList : gEntityLists) - { - for (auto& entityId : entityList) + for (EntityId::UnderlyingType i = 0; i < kMaxEntities; i++) { - auto* entity = TryGetEntity(entityId); + auto* entity = GetEntity(EntityId::FromUnderlying(i)); if (entity != nullptr && entity->Type != EntityType::Null) { - UpdateEntitySpatialIndex(*entity); + EntitySpatialInsert(*entity, { entity->x, entity->y }); } } } -} + +#ifndef DISABLE_NETWORK + + template + void NetworkSerialseEntityType(DataSerialiser& ds) + { + for (auto* ent : EntityList()) + { + ent->Serialise(ds); + } + } + + template + void NetworkSerialiseEntityTypes(DataSerialiser& ds) + { + (NetworkSerialseEntityType(ds), ...); + } + + EntitiesChecksum GetAllEntitiesChecksum() + { + EntitiesChecksum checksum{}; + + OpenRCT2::ChecksumStream ms(checksum.raw); + DataSerialiser ds(true, ms); + NetworkSerialiseEntityTypes(ds); + + return checksum; + } +#else + + EntitiesChecksum GetAllEntitiesChecksum() + { + return EntitiesChecksum{}; + } + +#endif // DISABLE_NETWORK + + static void EntityReset(EntityBase& entity) + { + // Need to retain how the sprite is linked in lists + auto entityIndex = entity.Id; + _entityFlashingList[entityIndex.ToUnderlying()] = false; + + Entity_t* tempEntity = reinterpret_cast(&entity); + *tempEntity = Entity_t(); + + entity.Id = entityIndex; + entity.Type = EntityType::Null; + } + + static constexpr uint16_t kMaxMiscEntities = 3200; + + static void AddToEntityList(EntityBase& entity) + { + auto& list = gEntityLists[EnumValue(entity.Type)]; + + // Entity list is sorted by Id to prevent desyncs. + Algorithm::sortedInsert(list, entity.Id); + } + + static void AddToFreeList(EntityId index) + { + // Free list must be in reverse sprite_index order to prevent desync issues + _freeIdList.insert(std::upper_bound(std::rbegin(_freeIdList), std::rend(_freeIdList), index).base(), index); + } + + static void RemoveFromEntityList(EntityBase& entity) + { + auto& list = gEntityLists[EnumValue(entity.Type)]; + auto ptr = Algorithm::binaryFind(std::begin(list), std::end(list), entity.Id); + if (ptr != std::end(list)) + { + list.erase(ptr); + } + } + + uint16_t GetMiscEntityCount() + { + uint16_t count = 0; + for (auto id : { EntityType::SteamParticle, EntityType::MoneyEffect, EntityType::CrashedVehicleParticle, + EntityType::ExplosionCloud, EntityType::CrashSplash, EntityType::ExplosionFlare, + EntityType::JumpingFountain, EntityType::Balloon, EntityType::Duck }) + { + count += GetEntityListCount(id); + } + return count; + } + + static void PrepareNewEntity(EntityBase& base, const EntityType type) + { + // Need to reset all sprite data, as the uninitialised values + // may contain garbage and cause a desync later on. + EntityReset(base); + + base.Type = type; + AddToEntityList(base); + + base.x = kLocationNull; + base.y = kLocationNull; + base.z = 0; + base.SpriteData.Width = 0x10; + base.SpriteData.HeightMin = 0x14; + base.SpriteData.HeightMax = 0x8; + base.SpriteData.SpriteRect = {}; + base.SpatialIndex = kInvalidSpatialIndex; + + EntitySpatialInsert(base, { kLocationNull, 0 }); + } + + EntityBase* CreateEntity(EntityType type) + { + if (_freeIdList.size() == 0) + { + // No free sprites. + return nullptr; + } + + if (EntityTypeIsMiscEntity(type)) + { + // Misc sprites are commonly used for effects, give other entity types higher priority. + if (GetMiscEntityCount() >= kMaxMiscEntities) + { + return nullptr; + } + + // If there are less than kMaxMiscEntities free slots, ensure other entities can be created. + if (_freeIdList.size() < kMaxMiscEntities) + { + return nullptr; + } + } + + auto* entity = GetEntity(_freeIdList.back()); + if (entity == nullptr) + { + return nullptr; + } + _freeIdList.pop_back(); + + PrepareNewEntity(*entity, type); + + return entity; + } + + EntityBase* CreateEntityAt(const EntityId index, const EntityType type) + { + auto id = Algorithm::binaryFind(std::rbegin(_freeIdList), std::rend(_freeIdList), index); + if (id == std::rend(_freeIdList)) + { + return nullptr; + } + + auto* entity = GetEntity(index); + if (entity == nullptr) + { + return nullptr; + } + + _freeIdList.erase(std::next(id).base()); + + PrepareNewEntity(*entity, type); + return entity; + } + + template + void MiscUpdateAllType() + { + for (auto misc : EntityList()) + { + misc->Update(); + } + } + + template + void MiscUpdateAllTypes() + { + (MiscUpdateAllType(), ...); + } + + /** + * + * rct2: 0x00672AA4 + */ + void UpdateAllMiscEntities() + { + PROFILED_FUNCTION(); + + MiscUpdateAllTypes< + SteamParticle, MoneyEffect, VehicleCrashParticle, ExplosionCloud, CrashSplashParticle, ExplosionFlare, + JumpingFountain, Balloon, Duck>(); + } + + void UpdateMoneyEffect() + { + MiscUpdateAllTypes(); + } + + // Performs a search to ensure that insert keeps next_in_quadrant in sprite_index order + static void EntitySpatialInsert(EntityBase& entity, const CoordsXY& newLoc) + { + const auto newIndex = ComputeSpatialIndex(newLoc); + + auto& spatialVector = gEntitySpatialIndex[newIndex]; + + Algorithm::sortedInsert(spatialVector, entity.Id); + + entity.SpatialIndex = newIndex; + } + + static void EntitySpatialRemove(EntityBase& entity) + { + const auto currentIndex = GetSpatialIndex(entity); + + auto& spatialVector = gEntitySpatialIndex[currentIndex]; + auto index = Algorithm::binaryFind(std::begin(spatialVector), std::end(spatialVector), entity.Id); + if (index != std::end(spatialVector)) + { + spatialVector.erase(index, index + 1); + } + else + { + LOG_WARNING("Bad sprite spatial index. Rebuilding the spatial index..."); + ResetEntitySpatialIndices(); + } + + entity.SpatialIndex = kInvalidSpatialIndex; + } + + static void UpdateEntitySpatialIndex(EntityBase& entity) + { + if (entity.SpatialIndex & kSpatialIndexDirtyMask) + { + if (entity.SpatialIndex != kInvalidSpatialIndex) + { + EntitySpatialRemove(entity); + } + EntitySpatialInsert(entity, { entity.x, entity.y }); + } + } + + void UpdateEntitiesSpatialIndex() + { + for (auto& entityList : gEntityLists) + { + for (auto& entityId : entityList) + { + auto* entity = TryGetEntity(entityId); + if (entity != nullptr && entity->Type != EntityType::Null) + { + UpdateEntitySpatialIndex(*entity); + } + } + } + } +} // namespace OpenRCT2 + +using namespace OpenRCT2; CoordsXYZ EntityBase::GetLocation() const { @@ -549,80 +556,83 @@ void EntityBase::MoveToAndUpdateSpatialIndex(const CoordsXYZ& newLocation) UpdateEntitySpatialIndex(*this); } -/** - * Frees any dynamically attached memory to the entity, such as peep name. - */ -static void FreeEntity(EntityBase& entity) +namespace OpenRCT2 { - auto* guest = entity.As(); - auto* staff = entity.As(); - if (staff != nullptr) + /** + * Frees any dynamically attached memory to the entity, such as peep name. + */ + static void FreeEntity(EntityBase& entity) { - staff->SetName({}); - staff->ClearPatrolArea(); - } - else if (guest != nullptr) - { - guest->SetName({}); - guest->GuestNextInQueue = EntityId::GetNull(); - - OpenRCT2::RideUse::GetHistory().RemoveHandle(guest->Id); - OpenRCT2::RideUse::GetTypeHistory().RemoveHandle(guest->Id); - } -} - -/** - * - * rct2: 0x0069EDB6 - */ -void EntityRemove(EntityBase* entity) -{ - FreeEntity(*entity); - - EntityTweener::Get().RemoveEntity(entity); - RemoveFromEntityList(*entity); // remove from existing list - AddToFreeList(entity->Id); - - EntitySpatialRemove(*entity); - EntityReset(*entity); -} - -/** - * Loops through all floating entities and removes them. - * Returns the amount of removed objects as feedback. - */ -uint16_t RemoveFloatingEntities() -{ - uint16_t removed = 0; - for (auto* balloon : EntityList()) - { - EntityRemove(balloon); - removed++; - } - for (auto* duck : EntityList()) - { - if (duck->IsFlying()) + auto* guest = entity.As(); + auto* staff = entity.As(); + if (staff != nullptr) { - EntityRemove(duck); - removed++; + staff->SetName({}); + staff->ClearPatrolArea(); + } + else if (guest != nullptr) + { + guest->SetName({}); + guest->GuestNextInQueue = EntityId::GetNull(); + + OpenRCT2::RideUse::GetHistory().RemoveHandle(guest->Id); + OpenRCT2::RideUse::GetTypeHistory().RemoveHandle(guest->Id); } } - for (auto* money : EntityList()) + + /** + * + * rct2: 0x0069EDB6 + */ + void EntityRemove(EntityBase* entity) { - EntityRemove(money); - removed++; + FreeEntity(*entity); + + EntityTweener::Get().RemoveEntity(entity); + RemoveFromEntityList(*entity); // remove from existing list + AddToFreeList(entity->Id); + + EntitySpatialRemove(*entity); + EntityReset(*entity); } - return removed; -} -void EntitySetFlashing(EntityBase* entity, bool flashing) -{ - assert(entity->Id.ToUnderlying() < kMaxEntities); - _entityFlashingList[entity->Id.ToUnderlying()] = flashing; -} + /** + * Loops through all floating entities and removes them. + * Returns the amount of removed objects as feedback. + */ + uint16_t RemoveFloatingEntities() + { + uint16_t removed = 0; + for (auto* balloon : EntityList()) + { + EntityRemove(balloon); + removed++; + } + for (auto* duck : EntityList()) + { + if (duck->IsFlying()) + { + EntityRemove(duck); + removed++; + } + } + for (auto* money : EntityList()) + { + EntityRemove(money); + removed++; + } + return removed; + } -bool EntityGetFlashing(EntityBase* entity) -{ - assert(entity->Id.ToUnderlying() < kMaxEntities); - return _entityFlashingList[entity->Id.ToUnderlying()]; -} + void EntitySetFlashing(EntityBase* entity, bool flashing) + { + assert(entity->Id.ToUnderlying() < kMaxEntities); + _entityFlashingList[entity->Id.ToUnderlying()] = flashing; + } + + bool EntityGetFlashing(EntityBase* entity) + { + assert(entity->Id.ToUnderlying() < kMaxEntities); + return _entityFlashingList[entity->Id.ToUnderlying()]; + } +} // namespace OpenRCT2 diff --git a/src/openrct2/entity/EntityRegistry.h b/src/openrct2/entity/EntityRegistry.h index 69d6aaeeed..6d9cb5c06f 100644 --- a/src/openrct2/entity/EntityRegistry.h +++ b/src/openrct2/entity/EntityRegistry.h @@ -25,84 +25,84 @@ namespace OpenRCT2 { } }; -} // namespace OpenRCT2 -constexpr uint16_t kMaxEntities = 65535; + constexpr uint16_t kMaxEntities = 65535; -EntityBase* GetEntity(EntityId entityId); + EntityBase* GetEntity(EntityId entityId); -template -T* GetEntity(EntityId entityId) -{ - auto* ent = GetEntity(entityId); - if (ent == nullptr) + template + T* GetEntity(EntityId entityId) { - return nullptr; + auto* ent = GetEntity(entityId); + if (ent == nullptr) + { + return nullptr; + } + if constexpr (std::is_same_v) + { + return ent; + } + else + { + return ent->As(); + } } - if constexpr (std::is_same_v) + + EntityBase* TryGetEntity(EntityId spriteIndex); + + template + T* TryGetEntity(EntityId entityId) { - return ent; + auto* ent = TryGetEntity(entityId); + if (ent == nullptr) + { + return nullptr; + } + if constexpr (std::is_same_v) + { + return ent; + } + else + { + return ent->As(); + } } - else + + EntityBase* CreateEntity(EntityType type); + + template + T* CreateEntity() { - return ent->As(); + return static_cast(CreateEntity(T::cEntityType)); } -} -EntityBase* TryGetEntity(EntityId spriteIndex); - -template -T* TryGetEntity(EntityId entityId) -{ - auto* ent = TryGetEntity(entityId); - if (ent == nullptr) + // Use only with imports that must happen at a specified index + EntityBase* CreateEntityAt(const EntityId index, const EntityType type); + // Use only with imports that must happen at a specified index + template + T* CreateEntityAt(const EntityId index) { - return nullptr; + return static_cast(CreateEntityAt(index, T::cEntityType)); } - if constexpr (std::is_same_v) - { - return ent; - } - else - { - return ent->As(); - } -} -EntityBase* CreateEntity(EntityType type); - -template -T* CreateEntity() -{ - return static_cast(CreateEntity(T::cEntityType)); -} - -// Use only with imports that must happen at a specified index -EntityBase* CreateEntityAt(const EntityId index, const EntityType type); -// Use only with imports that must happen at a specified index -template -T* CreateEntityAt(const EntityId index) -{ - return static_cast(CreateEntityAt(index, T::cEntityType)); -} - -void ResetAllEntities(); -void ResetEntitySpatialIndices(); -void UpdateAllMiscEntities(); -void UpdateMoneyEffect(); -void EntityRemove(EntityBase* entity); -uint16_t RemoveFloatingEntities(); -void UpdateEntitiesSpatialIndex(); + void ResetAllEntities(); + void ResetEntitySpatialIndices(); + void UpdateAllMiscEntities(); + void UpdateMoneyEffect(); + void EntityRemove(EntityBase* entity); + uint16_t RemoveFloatingEntities(); + void UpdateEntitiesSpatialIndex(); #pragma pack(push, 1) -struct EntitiesChecksum -{ - std::array raw; + struct EntitiesChecksum + { + std::array raw; - std::string ToString() const; -}; + std::string ToString() const; + }; #pragma pack(pop) -EntitiesChecksum GetAllEntitiesChecksum(); + EntitiesChecksum GetAllEntitiesChecksum(); -void EntitySetFlashing(EntityBase* entity, bool flashing); -bool EntityGetFlashing(EntityBase* entity); + void EntitySetFlashing(EntityBase* entity, bool flashing); + bool EntityGetFlashing(EntityBase* entity); +} // namespace OpenRCT2 diff --git a/src/openrct2/entity/Particle.cpp b/src/openrct2/entity/Particle.cpp index f6150a12dc..7fd3c17565 100644 --- a/src/openrct2/entity/Particle.cpp +++ b/src/openrct2/entity/Particle.cpp @@ -20,6 +20,8 @@ #include +using namespace OpenRCT2; + static constexpr uint32_t kVehicleCrashParticleSprites[kCrashedVehicleParticleNumberTypes] = { SPR_VEHICLE_CRASH_PARTICLE_1, SPR_VEHICLE_CRASH_PARTICLE_2, SPR_VEHICLE_CRASH_PARTICLE_3, SPR_VEHICLE_CRASH_PARTICLE_4, SPR_VEHICLE_CRASH_PARTICLE_5, diff --git a/src/openrct2/scripting/bindings/entity/ScBalloon.cpp b/src/openrct2/scripting/bindings/entity/ScBalloon.cpp index c00ac6f592..ad573a6642 100644 --- a/src/openrct2/scripting/bindings/entity/ScBalloon.cpp +++ b/src/openrct2/scripting/bindings/entity/ScBalloon.cpp @@ -28,7 +28,7 @@ namespace OpenRCT2::Scripting Balloon* ScBalloon::GetBalloon() const { - return ::GetEntity(_id); + return OpenRCT2::GetEntity(_id); } uint8_t ScBalloon::colour_get() const diff --git a/src/openrct2/scripting/bindings/entity/ScEntity.hpp b/src/openrct2/scripting/bindings/entity/ScEntity.hpp index df0e5eda03..da17875eb5 100644 --- a/src/openrct2/scripting/bindings/entity/ScEntity.hpp +++ b/src/openrct2/scripting/bindings/entity/ScEntity.hpp @@ -192,7 +192,7 @@ namespace OpenRCT2::Scripting EntityBase* GetEntity() const { - return ::GetEntity(_id); + return OpenRCT2::GetEntity(_id); } public: diff --git a/src/openrct2/scripting/bindings/entity/ScGuest.cpp b/src/openrct2/scripting/bindings/entity/ScGuest.cpp index cfd1aeea15..0f304ef717 100644 --- a/src/openrct2/scripting/bindings/entity/ScGuest.cpp +++ b/src/openrct2/scripting/bindings/entity/ScGuest.cpp @@ -194,7 +194,7 @@ namespace OpenRCT2::Scripting Guest* ScGuest::GetGuest() const { - return ::GetEntity(_id); + return OpenRCT2::GetEntity(_id); } uint8_t ScGuest::tshirtColour_get() const diff --git a/src/openrct2/scripting/bindings/entity/ScLitter.cpp b/src/openrct2/scripting/bindings/entity/ScLitter.cpp index 86dc36d6c6..0da245b12d 100644 --- a/src/openrct2/scripting/bindings/entity/ScLitter.cpp +++ b/src/openrct2/scripting/bindings/entity/ScLitter.cpp @@ -45,7 +45,7 @@ namespace OpenRCT2::Scripting Litter* ScLitter::GetLitter() const { - return ::GetEntity(_id); + return OpenRCT2::GetEntity(_id); } std::string ScLitter::litterType_get() const diff --git a/src/openrct2/scripting/bindings/entity/ScMoneyEffect.cpp b/src/openrct2/scripting/bindings/entity/ScMoneyEffect.cpp index 92b4ef92d5..35a27c85dd 100644 --- a/src/openrct2/scripting/bindings/entity/ScMoneyEffect.cpp +++ b/src/openrct2/scripting/bindings/entity/ScMoneyEffect.cpp @@ -28,7 +28,7 @@ namespace OpenRCT2::Scripting MoneyEffect* ScMoneyEffect::GetMoneyEffect() const { - return ::GetEntity(_id); + return OpenRCT2::GetEntity(_id); } money64 ScMoneyEffect::value_get() const diff --git a/src/openrct2/scripting/bindings/entity/ScParticle.cpp b/src/openrct2/scripting/bindings/entity/ScParticle.cpp index f4250c6e0e..e5f6a187d8 100644 --- a/src/openrct2/scripting/bindings/entity/ScParticle.cpp +++ b/src/openrct2/scripting/bindings/entity/ScParticle.cpp @@ -49,7 +49,7 @@ namespace OpenRCT2::Scripting VehicleCrashParticle* ScCrashedVehicleParticle::GetCrashedVehicleParticle() const { - return ::GetEntity(_id); + return OpenRCT2::GetEntity(_id); } void ScCrashedVehicleParticle::frame_set(uint8_t value) diff --git a/src/openrct2/scripting/bindings/entity/ScPeep.hpp b/src/openrct2/scripting/bindings/entity/ScPeep.hpp index 3ee1bbd344..ed46421120 100644 --- a/src/openrct2/scripting/bindings/entity/ScPeep.hpp +++ b/src/openrct2/scripting/bindings/entity/ScPeep.hpp @@ -196,7 +196,7 @@ namespace OpenRCT2::Scripting protected: Peep* GetPeep() const { - return ::GetEntity(_id); + return OpenRCT2::GetEntity(_id); } }; diff --git a/src/openrct2/scripting/bindings/entity/ScStaff.cpp b/src/openrct2/scripting/bindings/entity/ScStaff.cpp index a24ca3e7ec..88eea14914 100644 --- a/src/openrct2/scripting/bindings/entity/ScStaff.cpp +++ b/src/openrct2/scripting/bindings/entity/ScStaff.cpp @@ -43,7 +43,7 @@ namespace OpenRCT2::Scripting Staff* ScStaff::GetStaff() const { - return ::GetEntity(_id); + return OpenRCT2::GetEntity(_id); } std::string ScStaff::staffType_get() const @@ -420,7 +420,7 @@ namespace OpenRCT2::Scripting Staff* ScHandyman::GetHandyman() const { - return ::GetEntity(_id); + return OpenRCT2::GetEntity(_id); } DukValue ScHandyman::lawnsMown_get() const @@ -501,7 +501,7 @@ namespace OpenRCT2::Scripting Staff* ScMechanic::GetMechanic() const { - return ::GetEntity(_id); + return OpenRCT2::GetEntity(_id); } DukValue ScMechanic::ridesFixed_get() const @@ -549,7 +549,7 @@ namespace OpenRCT2::Scripting Staff* ScSecurity::GetSecurity() const { - return ::GetEntity(_id); + return OpenRCT2::GetEntity(_id); } DukValue ScSecurity::vandalsStopped_get() const