mirror of https://github.com/OpenRCT2/OpenRCT2.git
Implement EntityLists (#13853)
* Implement EntityLists * Remove dead code * Use alternative name for iterator * Add comments * Increment network version * Update replays * Remove further dead code * Update replays again
This commit is contained in:
parent
3321720d4a
commit
f80531070b
|
@ -44,8 +44,8 @@ set(TITLE_SEQUENCE_SHA1 "304d13a126c15bf2c86ff13b81a2f2cc1856ac8d")
|
|||
set(OBJECTS_URL "https://github.com/OpenRCT2/objects/releases/download/v1.0.20/objects.zip")
|
||||
set(OBJECTS_SHA1 "151424d24b1d49a167932b58319bedaa6ec368e9")
|
||||
|
||||
set(REPLAYS_URL "https://github.com/OpenRCT2/replays/releases/download/v0.0.27/replays.zip")
|
||||
set(REPLAYS_SHA1 "CC0BE0C9B9829062B67E1CFE14E36BEA1182882D")
|
||||
set(REPLAYS_URL "https://github.com/OpenRCT2/replays/releases/download/v0.0.29/replays.zip")
|
||||
set(REPLAYS_SHA1 "B64135F28A79758AD9FCB611625ADDB5C89FB40B")
|
||||
|
||||
option(FORCE32 "Force 32-bit build. It will add `-m32` to compiler flags.")
|
||||
option(WITH_TESTS "Build tests")
|
||||
|
|
|
@ -48,8 +48,8 @@
|
|||
<TitleSequencesSha1>304d13a126c15bf2c86ff13b81a2f2cc1856ac8d</TitleSequencesSha1>
|
||||
<ObjectsUrl>https://github.com/OpenRCT2/objects/releases/download/v1.0.20/objects.zip</ObjectsUrl>
|
||||
<ObjectsSha1>151424d24b1d49a167932b58319bedaa6ec368e9</ObjectsSha1>
|
||||
<ReplaysUrl>https://github.com/OpenRCT2/replays/releases/download/v0.0.27/replays.zip</ReplaysUrl>
|
||||
<ReplaysSha1>CC0BE0C9B9829062B67E1CFE14E36BEA1182882D</ReplaysSha1>
|
||||
<ReplaysUrl>https://github.com/OpenRCT2/replays/releases/download/v0.0.29/replays.zip</ReplaysUrl>
|
||||
<ReplaysSha1>B64135F28A79758AD9FCB611625ADDB5C89FB40B</ReplaysSha1>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -201,8 +201,6 @@ struct GameStateSnapshots final : public IGameStateSnapshots
|
|||
{
|
||||
COMPARE_FIELD(SpriteBase, sprite_identifier);
|
||||
COMPARE_FIELD(SpriteBase, next_in_quadrant);
|
||||
COMPARE_FIELD(SpriteBase, next);
|
||||
COMPARE_FIELD(SpriteBase, previous);
|
||||
COMPARE_FIELD(SpriteBase, linked_list_index);
|
||||
COMPARE_FIELD(SpriteBase, sprite_index);
|
||||
COMPARE_FIELD(SpriteBase, flags);
|
||||
|
|
|
@ -1231,7 +1231,7 @@ static int32_t cc_show_limits(InteractiveConsole& console, [[maybe_unused]] cons
|
|||
int32_t spriteCount = 0;
|
||||
for (int32_t i = 1; i < static_cast<uint8_t>(EntityListId::Count); ++i)
|
||||
{
|
||||
spriteCount += gSpriteListCount[i];
|
||||
spriteCount += GetEntityListCount(EntityListId(i));
|
||||
}
|
||||
|
||||
int32_t staffCount = 0;
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
// This string specifies which version of network stream current build uses.
|
||||
// It is used for making sure only compatible builds get connected, even within
|
||||
// single OpenRCT2 version.
|
||||
#define NETWORK_STREAM_VERSION "12"
|
||||
#define NETWORK_STREAM_VERSION "13"
|
||||
#define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION
|
||||
|
||||
static Peep* _pickup_peep = nullptr;
|
||||
|
|
|
@ -675,27 +675,38 @@ struct RCT12EightCarsCorruptElement15 : RCT12TileElementBase
|
|||
};
|
||||
assert_struct_size(RCT12EightCarsCorruptElement15, 8);
|
||||
|
||||
// Offset into sprite_lists_head and sprite_lists_count
|
||||
enum class RCT12EntityLinkListOffset : uint8_t
|
||||
{
|
||||
Free = 0,
|
||||
TrainHead = 1 * sizeof(uint16_t),
|
||||
Peep = 2 * sizeof(uint16_t),
|
||||
Misc = 3 * sizeof(uint16_t),
|
||||
Litter = 4 * sizeof(uint16_t),
|
||||
Vehicle = 5 * sizeof(uint16_t),
|
||||
};
|
||||
|
||||
struct RCT12SpriteBase
|
||||
{
|
||||
SpriteIdentifier sprite_identifier; // 0x00
|
||||
uint8_t type; // 0x01
|
||||
uint16_t next_in_quadrant; // 0x02
|
||||
uint16_t next; // 0x04
|
||||
uint16_t previous; // 0x06
|
||||
uint8_t linked_list_type_offset; // 0x08
|
||||
uint8_t sprite_height_negative; // 0x09
|
||||
uint16_t sprite_index; // 0x0A
|
||||
uint16_t flags; // 0x0C
|
||||
int16_t x; // 0x0E
|
||||
int16_t y; // 0x10
|
||||
int16_t z; // 0x12
|
||||
uint8_t sprite_width; // 0x14
|
||||
uint8_t sprite_height_positive; // 0x15
|
||||
int16_t sprite_left; // 0x16
|
||||
int16_t sprite_top; // 0x18
|
||||
int16_t sprite_right; // 0x1A
|
||||
int16_t sprite_bottom; // 0x1C
|
||||
uint8_t sprite_direction; // 0x1E
|
||||
SpriteIdentifier sprite_identifier; // 0x00
|
||||
uint8_t type; // 0x01
|
||||
uint16_t next_in_quadrant; // 0x02
|
||||
uint16_t next; // 0x04
|
||||
uint16_t previous; // 0x06
|
||||
RCT12EntityLinkListOffset linked_list_type_offset; // 0x08
|
||||
uint8_t sprite_height_negative; // 0x09
|
||||
uint16_t sprite_index; // 0x0A
|
||||
uint16_t flags; // 0x0C
|
||||
int16_t x; // 0x0E
|
||||
int16_t y; // 0x10
|
||||
int16_t z; // 0x12
|
||||
uint8_t sprite_width; // 0x14
|
||||
uint8_t sprite_height_positive; // 0x15
|
||||
int16_t sprite_left; // 0x16
|
||||
int16_t sprite_top; // 0x18
|
||||
int16_t sprite_right; // 0x1A
|
||||
int16_t sprite_bottom; // 0x1C
|
||||
uint8_t sprite_direction; // 0x1E
|
||||
};
|
||||
assert_struct_size(RCT12SpriteBase, 0x1F);
|
||||
|
||||
|
|
|
@ -151,14 +151,6 @@ void S6Exporter::Save(OpenRCT2::IStream* stream, bool isScenario)
|
|||
|
||||
void S6Exporter::Export()
|
||||
{
|
||||
int32_t regular_cycle = check_for_sprite_list_cycles(false);
|
||||
int32_t disjoint_sprites_count = fix_disjoint_sprites();
|
||||
openrct2_assert(regular_cycle == -1, "Sprite cycle exists in regular list %d", regular_cycle);
|
||||
// This one is less harmful, no need to assert for it ~janisozaur
|
||||
if (disjoint_sprites_count > 0)
|
||||
{
|
||||
log_error("Found %d disjoint null sprites", disjoint_sprites_count);
|
||||
}
|
||||
_s6.info = gS6Info;
|
||||
{
|
||||
auto temp = utf8_to_rct2(gS6Info.name);
|
||||
|
@ -957,8 +949,37 @@ void S6Exporter::ExportSprites()
|
|||
|
||||
for (int32_t i = 0; i < static_cast<uint8_t>(EntityListId::Count); i++)
|
||||
{
|
||||
_s6.sprite_lists_head[i] = gSpriteListHead[i];
|
||||
_s6.sprite_lists_count[i] = gSpriteListCount[i];
|
||||
//_s6.sprite_lists_head[i] = gSpriteListHead[i];
|
||||
_s6.sprite_lists_count[i] = GetEntityListCount(EntityListId(i));
|
||||
}
|
||||
RebuildEntityLinks();
|
||||
}
|
||||
|
||||
void S6Exporter::RebuildEntityLinks()
|
||||
{
|
||||
// Rebuild next/previous linked list entity indexs
|
||||
for (auto list :
|
||||
{ RCT12EntityLinkListOffset::Free, RCT12EntityLinkListOffset::Litter, RCT12EntityLinkListOffset::Misc,
|
||||
RCT12EntityLinkListOffset::Peep, RCT12EntityLinkListOffset::TrainHead, RCT12EntityLinkListOffset::Vehicle })
|
||||
{
|
||||
uint16_t previous = SPRITE_INDEX_NULL;
|
||||
for (auto& entity : _s6.sprites)
|
||||
{
|
||||
if (entity.unknown.linked_list_type_offset == list)
|
||||
{
|
||||
_s6.sprites[entity.unknown.sprite_index].unknown.previous = previous;
|
||||
if (previous != SPRITE_INDEX_NULL)
|
||||
{
|
||||
_s6.sprites[previous].unknown.next = entity.unknown.sprite_index;
|
||||
}
|
||||
else
|
||||
{
|
||||
_s6.sprite_lists_head[EnumValue(list)] = entity.unknown.sprite_index;
|
||||
}
|
||||
_s6.sprites[entity.unknown.sprite_index].unknown.next = SPRITE_INDEX_NULL;
|
||||
previous = entity.unknown.sprite_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -989,13 +1010,44 @@ void S6Exporter::ExportSprite(RCT2Sprite* dst, const rct_sprite* src)
|
|||
}
|
||||
}
|
||||
|
||||
constexpr RCT12EntityLinkListOffset GetRCT2LinkListOffset(const SpriteBase* src)
|
||||
{
|
||||
RCT12EntityLinkListOffset output = RCT12EntityLinkListOffset::Free;
|
||||
switch (src->sprite_identifier)
|
||||
{
|
||||
case SpriteIdentifier::Vehicle:
|
||||
{
|
||||
auto veh = src->As<Vehicle>();
|
||||
if (veh && veh->IsHead())
|
||||
{
|
||||
output = RCT12EntityLinkListOffset::TrainHead;
|
||||
}
|
||||
else
|
||||
{
|
||||
output = RCT12EntityLinkListOffset::Vehicle;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SpriteIdentifier::Peep:
|
||||
output = RCT12EntityLinkListOffset::Peep;
|
||||
break;
|
||||
case SpriteIdentifier::Misc:
|
||||
output = RCT12EntityLinkListOffset::Misc;
|
||||
break;
|
||||
case SpriteIdentifier::Litter:
|
||||
output = RCT12EntityLinkListOffset::Litter;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
void S6Exporter::ExportSpriteCommonProperties(RCT12SpriteBase* dst, const SpriteBase* src)
|
||||
{
|
||||
dst->sprite_identifier = src->sprite_identifier;
|
||||
dst->next_in_quadrant = src->next_in_quadrant;
|
||||
dst->next = src->next;
|
||||
dst->previous = src->previous;
|
||||
dst->linked_list_type_offset = static_cast<uint8_t>(src->linked_list_index) * 2;
|
||||
dst->linked_list_type_offset = GetRCT2LinkListOffset(src);
|
||||
dst->sprite_height_negative = src->sprite_height_negative;
|
||||
dst->sprite_index = src->sprite_index;
|
||||
dst->flags = src->flags;
|
||||
|
|
|
@ -78,4 +78,5 @@ private:
|
|||
|
||||
std::optional<uint16_t> AllocateUserString(std::string_view value);
|
||||
void ExportUserStrings();
|
||||
void RebuildEntityLinks();
|
||||
};
|
||||
|
|
|
@ -461,15 +461,6 @@ public:
|
|||
auto& park = OpenRCT2::GetContext()->GetGameState()->GetPark();
|
||||
park.Name = GetUserString(_s6.park_name);
|
||||
|
||||
// We try to fix the cycles on import, hence the 'true' parameter
|
||||
check_for_sprite_list_cycles(true);
|
||||
int32_t disjoint_sprites_count = fix_disjoint_sprites();
|
||||
// This one is less harmful, no need to assert for it ~janisozaur
|
||||
if (disjoint_sprites_count > 0)
|
||||
{
|
||||
log_error("Found %d disjoint null sprites", disjoint_sprites_count);
|
||||
}
|
||||
|
||||
if (String::Equals(_s6.scenario_filename, "Europe - European Cultural Festival.SC6"))
|
||||
{
|
||||
// This scenario breaks pathfinding. Create passages between the worlds. (List is grouped by neighbouring tiles.)
|
||||
|
@ -1332,14 +1323,7 @@ public:
|
|||
auto dst = GetEntity(i);
|
||||
ImportSprite(reinterpret_cast<rct_sprite*>(dst), src);
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < static_cast<uint8_t>(EntityListId::Count); i++)
|
||||
{
|
||||
gSpriteListHead[i] = _s6.sprite_lists_head[i];
|
||||
gSpriteListCount[i] = _s6.sprite_lists_count[i];
|
||||
}
|
||||
// This list contains the number of free slots. Increase it according to our own sprite limit.
|
||||
gSpriteListCount[static_cast<uint8_t>(EntityListId::Free)] += (MAX_SPRITES - RCT2_MAX_SPRITES);
|
||||
RebuildEntityLists();
|
||||
}
|
||||
|
||||
void ImportSprite(rct_sprite* dst, const RCT2Sprite* src)
|
||||
|
@ -1673,9 +1657,7 @@ public:
|
|||
{
|
||||
dst->sprite_identifier = src->sprite_identifier;
|
||||
dst->next_in_quadrant = src->next_in_quadrant;
|
||||
dst->next = src->next;
|
||||
dst->previous = src->previous;
|
||||
dst->linked_list_index = static_cast<EntityListId>(src->linked_list_type_offset >> 1);
|
||||
dst->linked_list_index = static_cast<EntityListId>(EnumValue(src->linked_list_type_offset) >> 1);
|
||||
dst->sprite_height_negative = src->sprite_height_negative;
|
||||
dst->sprite_index = src->sprite_index;
|
||||
dst->flags = src->flags;
|
||||
|
|
|
@ -25,9 +25,8 @@
|
|||
#include <cmath>
|
||||
#include <iterator>
|
||||
|
||||
uint16_t gSpriteListHead[static_cast<uint8_t>(EntityListId::Count)];
|
||||
uint16_t gSpriteListCount[static_cast<uint8_t>(EntityListId::Count)];
|
||||
static rct_sprite _spriteList[MAX_SPRITES];
|
||||
static std::array<std::list<uint16_t>, EnumValue(EntityListId::Count)> gEntityLists;
|
||||
|
||||
static bool _spriteFlashingList[MAX_SPRITES];
|
||||
|
||||
|
@ -47,7 +46,6 @@ const rct_string_id litterNames[12] = { STR_LITTER_VOMIT,
|
|||
STR_SHOP_ITEM_SINGULAR_EMPTY_BOWL_BLUE };
|
||||
|
||||
static size_t GetSpatialIndexOffset(int32_t x, int32_t y);
|
||||
static void move_sprite_to_list(SpriteBase* sprite, EntityListId newListIndex);
|
||||
|
||||
// Required for GetEntity to return a default
|
||||
template<> bool SpriteBase::Is<SpriteBase>() const
|
||||
|
@ -85,7 +83,7 @@ template<> bool SpriteBase::Is<ExplosionCloud>() const
|
|||
|
||||
uint16_t GetEntityListCount(EntityListId list)
|
||||
{
|
||||
return gSpriteListCount[static_cast<uint8_t>(list)];
|
||||
return static_cast<uint16_t>(gEntityLists[static_cast<uint8_t>(list)].size());
|
||||
}
|
||||
|
||||
std::string rct_sprite_checksum::ToString() const
|
||||
|
@ -174,6 +172,47 @@ void SpriteBase::Invalidate()
|
|||
|
||||
viewports_invalidate(sprite_left, sprite_top, sprite_right, sprite_bottom, maxZoom);
|
||||
}
|
||||
constexpr EntityListId EntityIdentifierToListId(const SpriteIdentifier type)
|
||||
{
|
||||
EntityListId linkedListIndex = EntityListId::Free;
|
||||
switch (type)
|
||||
{
|
||||
case SpriteIdentifier::Vehicle:
|
||||
linkedListIndex = EntityListId::Vehicle;
|
||||
break;
|
||||
case SpriteIdentifier::Peep:
|
||||
linkedListIndex = EntityListId::Peep;
|
||||
break;
|
||||
case SpriteIdentifier::Misc:
|
||||
linkedListIndex = EntityListId::Misc;
|
||||
break;
|
||||
case SpriteIdentifier::Litter:
|
||||
linkedListIndex = EntityListId::Litter;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return linkedListIndex;
|
||||
}
|
||||
|
||||
void RebuildEntityLists()
|
||||
{
|
||||
for (auto& list : gEntityLists)
|
||||
{
|
||||
list.clear();
|
||||
}
|
||||
|
||||
for (auto& ent : _spriteList)
|
||||
{
|
||||
// auto listId = EntityIdentifierToListId(ent.misc.sprite_identifier);
|
||||
gEntityLists[EnumValue(ent.misc.linked_list_index)].push_back(ent.misc.sprite_index);
|
||||
}
|
||||
}
|
||||
|
||||
const std::list<uint16_t>& GetEntityList(const EntityListId id)
|
||||
{
|
||||
return gEntityLists[EnumValue(id)];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -184,15 +223,6 @@ void reset_sprite_list()
|
|||
gSavedAge = 0;
|
||||
std::memset(static_cast<void*>(_spriteList), 0, sizeof(_spriteList));
|
||||
|
||||
for (int32_t i = 0; i < static_cast<uint8_t>(EntityListId::Count); i++)
|
||||
{
|
||||
gSpriteListHead[i] = SPRITE_INDEX_NULL;
|
||||
gSpriteListCount[i] = 0;
|
||||
_spriteFlashingList[i] = false;
|
||||
}
|
||||
|
||||
SpriteBase* previous_spr = nullptr;
|
||||
|
||||
for (int32_t i = 0; i < MAX_SPRITES; ++i)
|
||||
{
|
||||
auto* spr = GetEntity(i);
|
||||
|
@ -203,25 +233,12 @@ void reset_sprite_list()
|
|||
|
||||
spr->sprite_identifier = SpriteIdentifier::Null;
|
||||
spr->sprite_index = i;
|
||||
spr->next = SPRITE_INDEX_NULL;
|
||||
spr->linked_list_index = EntityListId::Free;
|
||||
|
||||
if (previous_spr != nullptr)
|
||||
{
|
||||
spr->previous = previous_spr->sprite_index;
|
||||
previous_spr->next = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
spr->previous = SPRITE_INDEX_NULL;
|
||||
gSpriteListHead[static_cast<uint8_t>(EntityListId::Free)] = i;
|
||||
}
|
||||
_spriteFlashingList[i] = false;
|
||||
previous_spr = spr;
|
||||
}
|
||||
|
||||
gSpriteListCount[static_cast<uint8_t>(EntityListId::Free)] = MAX_SPRITES;
|
||||
|
||||
RebuildEntityLists();
|
||||
reset_sprite_spatial_index();
|
||||
}
|
||||
|
||||
|
@ -348,18 +365,14 @@ static void sprite_reset(SpriteBase* sprite)
|
|||
{
|
||||
// Need to retain how the sprite is linked in lists
|
||||
auto llto = sprite->linked_list_index;
|
||||
uint16_t next = sprite->next;
|
||||
uint16_t next_in_quadrant = sprite->next_in_quadrant;
|
||||
uint16_t prev = sprite->previous;
|
||||
uint16_t sprite_index = sprite->sprite_index;
|
||||
_spriteFlashingList[sprite_index] = false;
|
||||
|
||||
std::memset(sprite, 0, sizeof(rct_sprite));
|
||||
|
||||
sprite->linked_list_index = llto;
|
||||
sprite->next = next;
|
||||
sprite->next_in_quadrant = next_in_quadrant;
|
||||
sprite->previous = prev;
|
||||
sprite->sprite_index = sprite_index;
|
||||
sprite->sprite_identifier = SpriteIdentifier::Null;
|
||||
}
|
||||
|
@ -389,6 +402,23 @@ void sprite_clear_all_unused()
|
|||
static void SpriteSpatialInsert(SpriteBase* sprite, const CoordsXY& newLoc);
|
||||
|
||||
static constexpr uint16_t MAX_MISC_SPRITES = 300;
|
||||
static void AddToEntityList(const EntityListId linkedListIndex, SpriteBase* entity)
|
||||
{
|
||||
auto& list = gEntityLists[EnumValue(linkedListIndex)];
|
||||
entity->linked_list_index = linkedListIndex;
|
||||
// Entity list must be in sprite_index order to prevent desync issues
|
||||
list.insert(std::lower_bound(std::begin(list), std::end(list), entity->sprite_index), entity->sprite_index);
|
||||
}
|
||||
|
||||
static void RemoveFromEntityList(SpriteBase* entity)
|
||||
{
|
||||
auto& list = gEntityLists[EnumValue(entity->linked_list_index)];
|
||||
auto ptr = std::lower_bound(std::begin(list), std::end(list), entity->sprite_index);
|
||||
if (ptr != std::end(list) && *ptr == entity->sprite_index)
|
||||
{
|
||||
list.erase(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
rct_sprite* create_sprite(SpriteIdentifier spriteIdentifier, EntityListId linkedListIndex)
|
||||
{
|
||||
|
@ -410,13 +440,14 @@ rct_sprite* create_sprite(SpriteIdentifier spriteIdentifier, EntityListId linked
|
|||
}
|
||||
}
|
||||
|
||||
auto* sprite = GetEntity(gSpriteListHead[static_cast<uint8_t>(EntityListId::Free)]);
|
||||
auto* sprite = GetEntity(gEntityLists[static_cast<uint8_t>(EntityListId::Free)].front());
|
||||
if (sprite == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
move_sprite_to_list(sprite, linkedListIndex);
|
||||
|
||||
gEntityLists[static_cast<uint8_t>(EntityListId::Free)].pop_front();
|
||||
RemoveFromEntityList(sprite); // remove from Free list
|
||||
AddToEntityList(linkedListIndex, sprite);
|
||||
// Need to reset all sprite data, as the uninitialised values
|
||||
// may contain garbage and cause a desync later on.
|
||||
sprite_reset(sprite);
|
||||
|
@ -459,83 +490,6 @@ rct_sprite* create_sprite(SpriteIdentifier spriteIdentifier)
|
|||
return create_sprite(spriteIdentifier, linkedListIndex);
|
||||
}
|
||||
|
||||
/*
|
||||
* rct2: 0x0069ED0B
|
||||
* This function moves a sprite to the specified sprite linked list.
|
||||
* The game uses this list to categorise sprites by type.
|
||||
*/
|
||||
static void move_sprite_to_list(SpriteBase* sprite, EntityListId newListIndex)
|
||||
{
|
||||
auto oldListIndex = sprite->linked_list_index;
|
||||
|
||||
// No need to move if the sprite is already in the desired list
|
||||
if (oldListIndex == newListIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If the sprite is currently the head of the list, the
|
||||
// sprite following this one becomes the new head of the list.
|
||||
if (sprite->previous == SPRITE_INDEX_NULL)
|
||||
{
|
||||
gSpriteListHead[static_cast<uint8_t>(oldListIndex)] = sprite->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hook up sprite->previous->next to sprite->next, removing the sprite from its old list
|
||||
auto previous = GetEntity(sprite->previous);
|
||||
if (previous == nullptr)
|
||||
{
|
||||
log_error("Broken previous entity id. Entity list corrupted!");
|
||||
}
|
||||
else
|
||||
{
|
||||
previous->next = sprite->next;
|
||||
}
|
||||
}
|
||||
|
||||
// Similarly, hook up sprite->next->previous to sprite->previous
|
||||
if (sprite->next != SPRITE_INDEX_NULL)
|
||||
{
|
||||
auto next = GetEntity(sprite->next);
|
||||
if (next == nullptr)
|
||||
{
|
||||
log_error("Broken next entity id. Entity list corrupted!");
|
||||
}
|
||||
else
|
||||
{
|
||||
next->previous = sprite->previous;
|
||||
}
|
||||
}
|
||||
|
||||
sprite->previous = SPRITE_INDEX_NULL; // We become the new head of the target list, so there's no previous sprite
|
||||
sprite->linked_list_index = newListIndex;
|
||||
|
||||
sprite->next = gSpriteListHead[static_cast<uint8_t>(
|
||||
newListIndex)]; // This sprite's next sprite is the old head, since we're the new head
|
||||
gSpriteListHead[static_cast<uint8_t>(newListIndex)] = sprite->sprite_index; // Store this sprite's index as head of its
|
||||
// new list
|
||||
|
||||
if (sprite->next != SPRITE_INDEX_NULL)
|
||||
{
|
||||
// Fix the chain by settings sprite->next->previous to sprite_index
|
||||
auto next = GetEntity(sprite->next);
|
||||
if (next == nullptr)
|
||||
{
|
||||
log_error("Broken next entity id. Entity list corrupted!");
|
||||
}
|
||||
else
|
||||
{
|
||||
next->previous = sprite->sprite_index;
|
||||
}
|
||||
}
|
||||
|
||||
// These globals are probably counters for each sprite list?
|
||||
// Decrement old list counter, increment new list counter.
|
||||
gSpriteListCount[static_cast<uint8_t>(oldListIndex)]--;
|
||||
gSpriteListCount[static_cast<uint8_t>(newListIndex)]++;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* rct2: 0x00673200
|
||||
|
@ -766,8 +720,9 @@ void sprite_remove(SpriteBase* sprite)
|
|||
}
|
||||
|
||||
EntityTweener::Get().RemoveEntity(sprite);
|
||||
RemoveFromEntityList(sprite); // remove from existing list
|
||||
AddToEntityList(EntityListId::Free, sprite);
|
||||
|
||||
move_sprite_to_list(sprite, EntityListId::Free);
|
||||
SpriteSpatialRemove(sprite);
|
||||
sprite_reset(sprite);
|
||||
}
|
||||
|
@ -1007,157 +962,3 @@ bool sprite_get_flashing(SpriteBase* sprite)
|
|||
assert(sprite->sprite_index < MAX_SPRITES);
|
||||
return _spriteFlashingList[sprite->sprite_index];
|
||||
}
|
||||
|
||||
static SpriteBase* find_sprite_list_cycle(uint16_t sprite_idx)
|
||||
{
|
||||
if (sprite_idx == SPRITE_INDEX_NULL)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const SpriteBase* fast = GetEntity(sprite_idx);
|
||||
const SpriteBase* slow = fast;
|
||||
bool increment_slow = false;
|
||||
SpriteBase* cycle_start = nullptr;
|
||||
while (fast->sprite_index != SPRITE_INDEX_NULL)
|
||||
{
|
||||
// increment fast every time, unless reached the end
|
||||
if (fast->next == SPRITE_INDEX_NULL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
fast = GetEntity(fast->next);
|
||||
}
|
||||
// increment slow only every second iteration
|
||||
if (increment_slow)
|
||||
{
|
||||
slow = GetEntity(slow->next);
|
||||
}
|
||||
increment_slow = !increment_slow;
|
||||
if (fast == slow)
|
||||
{
|
||||
cycle_start = GetEntity(slow->sprite_index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return cycle_start;
|
||||
}
|
||||
|
||||
static bool index_is_in_list(uint16_t index, EntityListId sl)
|
||||
{
|
||||
for (auto entity : EntityList(sl))
|
||||
{
|
||||
if (entity->sprite_index == index)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t check_for_sprite_list_cycles(bool fix)
|
||||
{
|
||||
for (int32_t i = 0; i < static_cast<uint8_t>(EntityListId::Count); i++)
|
||||
{
|
||||
auto* cycle_start = find_sprite_list_cycle(gSpriteListHead[i]);
|
||||
if (cycle_start != nullptr)
|
||||
{
|
||||
if (fix)
|
||||
{
|
||||
// Fix head list, but only in reverse order
|
||||
// This is likely not needed, but just in case
|
||||
auto head = GetEntity(gSpriteListHead[i]);
|
||||
if (head == nullptr)
|
||||
{
|
||||
log_error("SpriteListHead is corrupted!");
|
||||
return -1;
|
||||
}
|
||||
head->previous = SPRITE_INDEX_NULL;
|
||||
|
||||
// Store the leftover part of cycle to be fixed
|
||||
uint16_t cycle_next = cycle_start->next;
|
||||
|
||||
// Break the cycle
|
||||
cycle_start->next = SPRITE_INDEX_NULL;
|
||||
|
||||
// Now re-add remainder of the cycle back to list, safely.
|
||||
// Add each sprite to the list until we encounter one that is already part of the list.
|
||||
while (!index_is_in_list(cycle_next, static_cast<EntityListId>(i)))
|
||||
{
|
||||
auto* spr = GetEntity(cycle_next);
|
||||
if (spr == nullptr)
|
||||
{
|
||||
log_error("EntityList is corrupted!");
|
||||
return -1;
|
||||
}
|
||||
cycle_start->next = cycle_next;
|
||||
spr->previous = cycle_start->sprite_index;
|
||||
cycle_next = spr->next;
|
||||
spr->next = SPRITE_INDEX_NULL;
|
||||
cycle_start = spr;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and fixes null sprites that are not reachable via EntityListId::Free list.
|
||||
*
|
||||
* @return count of disjoint sprites found
|
||||
*/
|
||||
int32_t fix_disjoint_sprites()
|
||||
{
|
||||
// Find reachable sprites
|
||||
bool reachable[MAX_SPRITES] = { false };
|
||||
|
||||
SpriteBase* null_list_tail = nullptr;
|
||||
for (uint16_t sprite_idx = gSpriteListHead[static_cast<uint8_t>(EntityListId::Free)]; sprite_idx != SPRITE_INDEX_NULL;)
|
||||
{
|
||||
reachable[sprite_idx] = true;
|
||||
// cache the tail, so we don't have to walk the list twice
|
||||
null_list_tail = GetEntity(sprite_idx);
|
||||
if (null_list_tail == nullptr)
|
||||
{
|
||||
log_error("Broken Entity list");
|
||||
sprite_idx = SPRITE_INDEX_NULL;
|
||||
return 0;
|
||||
}
|
||||
sprite_idx = null_list_tail->next;
|
||||
}
|
||||
|
||||
int32_t count = 0;
|
||||
|
||||
// Find all null sprites
|
||||
for (uint16_t sprite_idx = 0; sprite_idx < MAX_SPRITES; sprite_idx++)
|
||||
{
|
||||
auto* spr = GetEntity(sprite_idx);
|
||||
if (spr != nullptr && spr->sprite_identifier == SpriteIdentifier::Null)
|
||||
{
|
||||
openrct2_assert(null_list_tail != nullptr, "Null list is empty, yet found null sprites");
|
||||
spr->sprite_index = sprite_idx;
|
||||
if (!reachable[sprite_idx])
|
||||
{
|
||||
// Add the sprite directly to the list
|
||||
if (null_list_tail == nullptr)
|
||||
{
|
||||
gSpriteListHead[static_cast<uint8_t>(EntityListId::Free)] = sprite_idx;
|
||||
spr->previous = SPRITE_INDEX_NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
null_list_tail->next = sprite_idx;
|
||||
spr->previous = null_list_tail->sprite_index;
|
||||
}
|
||||
spr->next = SPRITE_INDEX_NULL;
|
||||
null_list_tail = spr;
|
||||
count++;
|
||||
reachable[sprite_idx] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#include "Fountain.h"
|
||||
#include "SpriteBase.h"
|
||||
|
||||
#include <list>
|
||||
|
||||
#define SPRITE_INDEX_NULL 0xFFFF
|
||||
#define MAX_SPRITES 10000
|
||||
|
||||
|
@ -226,8 +228,6 @@ template<typename T = SpriteBase> T* TryGetEntity(size_t sprite_idx)
|
|||
}
|
||||
|
||||
uint16_t GetEntityListCount(EntityListId list);
|
||||
extern uint16_t gSpriteListHead[static_cast<uint8_t>(EntityListId::Count)];
|
||||
extern uint16_t gSpriteListCount[static_cast<uint8_t>(EntityListId::Count)];
|
||||
|
||||
constexpr const uint32_t SPATIAL_INDEX_SIZE = (MAXIMUM_MAP_SIZE_TECHNICAL * MAXIMUM_MAP_SIZE_TECHNICAL) + 1;
|
||||
constexpr const uint32_t SPATIAL_INDEX_LOCATION_NULL = SPATIAL_INDEX_SIZE - 1;
|
||||
|
@ -237,6 +237,7 @@ extern const rct_string_id litterNames[12];
|
|||
|
||||
rct_sprite* create_sprite(SpriteIdentifier spriteIdentifier);
|
||||
rct_sprite* create_sprite(SpriteIdentifier spriteIdentifier, EntityListId linkedListIndex);
|
||||
void RebuildEntityLists();
|
||||
void reset_sprite_list();
|
||||
void reset_sprite_spatial_index();
|
||||
void sprite_clear_all_unused();
|
||||
|
@ -273,8 +274,8 @@ rct_sprite_checksum sprite_checksum();
|
|||
|
||||
void sprite_set_flashing(SpriteBase* sprite, bool flashing);
|
||||
bool sprite_get_flashing(SpriteBase* sprite);
|
||||
int32_t check_for_sprite_list_cycles(bool fix);
|
||||
int32_t fix_disjoint_sprites();
|
||||
|
||||
const std::list<uint16_t>& GetEntityList(const EntityListId id);
|
||||
|
||||
template<typename T, uint16_t SpriteBase::*NextList> class EntityIterator
|
||||
{
|
||||
|
@ -354,25 +355,76 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
template<typename T> class EntityListIterator
|
||||
{
|
||||
private:
|
||||
std::list<uint16_t>::const_iterator iter;
|
||||
std::list<uint16_t>::const_iterator end;
|
||||
T* Entity = nullptr;
|
||||
|
||||
public:
|
||||
EntityListIterator(std::list<uint16_t>::const_iterator _iter, std::list<uint16_t>::const_iterator _end)
|
||||
: iter(_iter)
|
||||
, end(_end)
|
||||
{
|
||||
++(*this);
|
||||
}
|
||||
EntityListIterator& operator++()
|
||||
{
|
||||
Entity = nullptr;
|
||||
|
||||
while (iter != end && Entity == nullptr)
|
||||
{
|
||||
Entity = GetEntity<T>(*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<typename T = SpriteBase> class EntityList
|
||||
{
|
||||
private:
|
||||
uint16_t FirstEntity = SPRITE_INDEX_NULL;
|
||||
using EntityListIterator = EntityIterator<T, &SpriteBase::next>;
|
||||
using EntityListIterator_t = EntityListIterator<T>;
|
||||
const std::list<uint16_t>& vec;
|
||||
|
||||
public:
|
||||
EntityList(EntityListId type)
|
||||
: FirstEntity(gSpriteListHead[static_cast<uint8_t>(type)])
|
||||
: vec(GetEntityList(type))
|
||||
{
|
||||
}
|
||||
|
||||
EntityListIterator begin()
|
||||
EntityListIterator_t begin()
|
||||
{
|
||||
return EntityListIterator(FirstEntity);
|
||||
return EntityListIterator_t(std::cbegin(vec), std::cend(vec));
|
||||
}
|
||||
EntityListIterator end()
|
||||
EntityListIterator_t end()
|
||||
{
|
||||
return EntityListIterator(SPRITE_INDEX_NULL);
|
||||
return EntityListIterator_t(std::cend(vec), std::cend(vec));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -10,8 +10,6 @@ struct SpriteBase
|
|||
{
|
||||
SpriteIdentifier sprite_identifier;
|
||||
uint16_t next_in_quadrant;
|
||||
uint16_t next;
|
||||
uint16_t previous;
|
||||
// Valid values are EntityListId::...
|
||||
EntityListId linked_list_index;
|
||||
// Height from centre of sprite to bottom
|
||||
|
|
|
@ -132,8 +132,6 @@ static void CompareSpriteDataCommon(const SpriteBase& left, const SpriteBase& ri
|
|||
{
|
||||
COMPARE_FIELD(sprite_identifier);
|
||||
COMPARE_FIELD(next_in_quadrant);
|
||||
COMPARE_FIELD(next);
|
||||
COMPARE_FIELD(previous);
|
||||
COMPARE_FIELD(linked_list_index);
|
||||
COMPARE_FIELD(sprite_index);
|
||||
COMPARE_FIELD(flags);
|
||||
|
|
Loading…
Reference in New Issue