#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * * OpenRCT2 is the work of many authors, a full list can be found in contributors.md * For more information, visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * A full copy of the GNU General Public License can be found in licence.txt *****************************************************************************/ #pragma endregion #include "../audio/audio.h" #include "../Cheats.h" #include "../core/Math.hpp" #include "../Game.h" #include "../interface/Viewport.h" #include "../localisation/Date.h" #include "../localisation/Localisation.h" #include "../OpenRCT2.h" #include "../scenario/Scenario.h" #include "Fountain.h" #include "Sprite.h" uint16 gSpriteListHead[6]; uint16 gSpriteListCount[6]; static rct_sprite _spriteList[MAX_SPRITES]; static bool _spriteFlashingList[MAX_SPRITES]; #define SPATIAL_INDEX_LOCATION_NULL 0x10000 uint16 gSpriteSpatialIndex[0x10001]; const rct_string_id litterNames[12] = { STR_LITTER_VOMIT, STR_LITTER_VOMIT, STR_SHOP_ITEM_SINGULAR_EMPTY_CAN, STR_SHOP_ITEM_SINGULAR_RUBBISH, STR_SHOP_ITEM_SINGULAR_EMPTY_BURGER_BOX, STR_SHOP_ITEM_SINGULAR_EMPTY_CUP, STR_SHOP_ITEM_SINGULAR_EMPTY_BOX, STR_SHOP_ITEM_SINGULAR_EMPTY_BOTTLE, STR_SHOP_ITEM_SINGULAR_EMPTY_BOWL_RED, STR_SHOP_ITEM_SINGULAR_EMPTY_DRINK_CARTON, STR_SHOP_ITEM_SINGULAR_EMPTY_JUICE_CUP, STR_SHOP_ITEM_SINGULAR_EMPTY_BOWL_BLUE }; static LocationXYZ16 _spritelocations1[MAX_SPRITES]; static LocationXYZ16 _spritelocations2[MAX_SPRITES]; static size_t GetSpatialIndexOffset(sint32 x, sint32 y); rct_sprite *try_get_sprite(size_t spriteIndex) { rct_sprite * sprite = nullptr; if (spriteIndex < MAX_SPRITES) { sprite = &_spriteList[spriteIndex]; } return sprite; } rct_sprite *get_sprite(size_t sprite_idx) { openrct2_assert(sprite_idx < MAX_SPRITES, "Tried getting sprite %u", sprite_idx); return &_spriteList[sprite_idx]; } uint16 sprite_get_first_in_quadrant(sint32 x, sint32 y) { sint32 offset = ((x & 0x1FE0) << 3) | (y >> 5); return gSpriteSpatialIndex[offset]; } static void invalidate_sprite_max_zoom(rct_sprite *sprite, sint32 maxZoom) { if (sprite->unknown.sprite_left == LOCATION_NULL) return; for (sint32 i = 0; i < MAX_VIEWPORT_COUNT; i++) { rct_viewport *viewport = &g_viewport_list[i]; if (viewport->width != 0 && viewport->zoom <= maxZoom) { viewport_invalidate( viewport, sprite->unknown.sprite_left, sprite->unknown.sprite_top, sprite->unknown.sprite_right, sprite->unknown.sprite_bottom ); } } } /** * Invalidate the sprite if at closest zoom. * rct2: 0x006EC60B */ void invalidate_sprite_0(rct_sprite* sprite) { invalidate_sprite_max_zoom(sprite, 0); } /** * Invalidate sprite if at closest zoom or next zoom up from closest. * rct2: 0x006EC53F */ void invalidate_sprite_1(rct_sprite *sprite) { invalidate_sprite_max_zoom(sprite, 1); } /** * Invalidate sprite if not at furthest zoom. * rct2: 0x006EC473 * * @param sprite (esi) */ void invalidate_sprite_2(rct_sprite *sprite) { invalidate_sprite_max_zoom(sprite, 2); } /** * * rct2: 0x0069EB13 */ void reset_sprite_list() { gSavedAge = 0; memset(_spriteList, 0, sizeof(rct_sprite) * MAX_SPRITES); for (sint32 i = 0; i < NUM_SPRITE_LISTS; i++) { gSpriteListHead[i] = SPRITE_INDEX_NULL; gSpriteListCount[i] = 0; _spriteFlashingList[i] = false; } rct_sprite* previous_spr = (rct_sprite*)SPRITE_INDEX_NULL; for (sint32 i = 0; i < MAX_SPRITES; ++i){ rct_sprite *spr = get_sprite(i); spr->unknown.sprite_identifier = SPRITE_IDENTIFIER_NULL; spr->unknown.sprite_index = i; spr->unknown.next = SPRITE_INDEX_NULL; spr->unknown.linked_list_type_offset = 0; if (previous_spr != (rct_sprite*)SPRITE_INDEX_NULL){ spr->unknown.previous = previous_spr->unknown.sprite_index; previous_spr->unknown.next = i; } else{ spr->unknown.previous = SPRITE_INDEX_NULL; gSpriteListHead[SPRITE_LIST_NULL] = i; } _spriteFlashingList[i] = false; previous_spr = spr; } gSpriteListCount[SPRITE_LIST_NULL] = MAX_SPRITES; reset_sprite_spatial_index(); } /** * * 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 reset_sprite_spatial_index() { memset(gSpriteSpatialIndex, SPRITE_INDEX_NULL, sizeof(gSpriteSpatialIndex)); for (size_t i = 0; i < MAX_SPRITES; i++) { rct_sprite *spr = get_sprite(i); if (spr->unknown.sprite_identifier != SPRITE_IDENTIFIER_NULL) { size_t index = GetSpatialIndexOffset(spr->unknown.x, spr->unknown.y); uint16 nextSpriteId = gSpriteSpatialIndex[index]; gSpriteSpatialIndex[index] = spr->unknown.sprite_index; spr->unknown.next_in_quadrant = nextSpriteId; } } } static size_t GetSpatialIndexOffset(sint32 x, sint32 y) { size_t index = SPATIAL_INDEX_LOCATION_NULL; if (x != LOCATION_NULL) { x = Math::Clamp(0, x, 0xFFFF); y = Math::Clamp(0, y, 0xFFFF); sint16 flooredX = floor2(x, 32); uint8 tileY = y >> 5; index = (flooredX << 3) | tileY; } openrct2_assert(index < sizeof(gSpriteSpatialIndex), "GetSpatialIndexOffset out of range"); return index; } #ifndef DISABLE_NETWORK static uint8 _spriteChecksum[EVP_MAX_MD_SIZE + 1]; const char * sprite_checksum() { if (EVP_DigestInit_ex(gHashCTX, EVP_sha1(), NULL) <= 0) { openrct2_assert(false, "Failed to initialise SHA1 engine"); } for (size_t i = 0; i < MAX_SPRITES; i++) { rct_sprite *sprite = get_sprite(i); if (sprite->unknown.sprite_identifier != SPRITE_IDENTIFIER_NULL && sprite->unknown.sprite_identifier != SPRITE_IDENTIFIER_MISC) { rct_sprite copy = *sprite; copy.unknown.sprite_left = copy.unknown.sprite_right = copy.unknown.sprite_top = copy.unknown.sprite_bottom = 0; if (copy.unknown.sprite_identifier == SPRITE_IDENTIFIER_PEEP) { // We set this to 0 because as soon the client selects a guest the window will remove the // invalidation flags causing the sprite checksum to be different than on server, the flag does not affect game state. copy.peep.window_invalidate_flags = 0; } if (EVP_DigestUpdate(gHashCTX, ©, sizeof(rct_sprite)) <= 0) { openrct2_assert(false, "Failed to update digest"); } } } uint8 localhash[EVP_MAX_MD_SIZE + 1]; uint32 size = sizeof(localhash); EVP_DigestFinal(gHashCTX, localhash, &size); assert(size <= sizeof(localhash)); localhash[sizeof(localhash) - 1] = '\0'; char *x = (char *)_spriteChecksum; for (uint32 i = 0; i < size; i++) { snprintf(x, EVP_MAX_MD_SIZE + 1, "%02x", localhash[i]); x += 2; } *x = '\0'; return (char *)_spriteChecksum; } #else const char * sprite_checksum() { return nullptr; } #endif // DISABLE_NETWORK static void sprite_reset(rct_unk_sprite *sprite) { // Need to retain how the sprite is linked in lists uint8 llto = sprite->linked_list_type_offset; uint16 next = sprite->next; uint16 next_in_quadrant = sprite->next_in_quadrant; uint16 prev = sprite->previous; uint16 sprite_index = sprite->sprite_index; _spriteFlashingList[sprite_index] = false; memset(sprite, 0, sizeof(rct_sprite)); sprite->linked_list_type_offset = llto; sprite->next = next; sprite->next_in_quadrant = next_in_quadrant; sprite->previous = prev; sprite->sprite_index = sprite_index; sprite->sprite_identifier = SPRITE_IDENTIFIER_NULL; } /** * Clears all the unused sprite memory to zero. Probably so that it can be compressed better when saving. * rct2: 0x0069EBA4 */ void sprite_clear_all_unused() { rct_unk_sprite *sprite; uint16 spriteIndex, nextSpriteIndex; spriteIndex = gSpriteListHead[SPRITE_LIST_NULL]; while (spriteIndex != SPRITE_INDEX_NULL) { sprite = &get_sprite(spriteIndex)->unknown; nextSpriteIndex = sprite->next; sprite_reset(sprite); sprite->linked_list_type_offset = SPRITE_LIST_NULL * 2; // This shouldn't be necessary, as sprite_reset() preserves the index // but it has been left in as a safety net in case the index isn't set correctly sprite->sprite_index = spriteIndex; // sprite->next_in_quadrant will only end up as zero owing to corruption // most likely due to previous builds not preserving it when resetting sprites // We reset it to SPRITE_INDEX_NULL to prevent cycles in the sprite lists if (sprite->next_in_quadrant == 0) { sprite->next_in_quadrant = SPRITE_INDEX_NULL; } _spriteFlashingList[spriteIndex] = false; spriteIndex = nextSpriteIndex; } } /* * rct2: 0x0069EC6B * bl: if bl & 2 > 0, the sprite ends up in the MISC linked list. */ rct_sprite *create_sprite(uint8 bl) { size_t linkedListTypeOffset = SPRITE_LIST_UNKNOWN * 2; if ((bl & 2) != 0) { // 69EC96; uint16 cx = 0x12C - gSpriteListCount[SPRITE_LIST_MISC]; if (cx >= gSpriteListCount[SPRITE_LIST_NULL]) { return nullptr; } linkedListTypeOffset = SPRITE_LIST_MISC * 2; } else if (gSpriteListCount[SPRITE_LIST_NULL] == 0) { return nullptr; } rct_unk_sprite *sprite = &(get_sprite(gSpriteListHead[SPRITE_LIST_NULL]))->unknown; move_sprite_to_list((rct_sprite *)sprite, (uint8)linkedListTypeOffset); // Need to reset all sprite data, as the uninitialised values // may contain garbage and cause a desync later on. sprite_reset(sprite); sprite->x = LOCATION_NULL; sprite->y = LOCATION_NULL; sprite->z = 0; sprite->name_string_idx = 0; sprite->sprite_width = 0x10; sprite->sprite_height_negative = 0x14; sprite->sprite_height_positive = 0x8; sprite->flags = 0; sprite->sprite_left = LOCATION_NULL; sprite->next_in_quadrant = gSpriteSpatialIndex[SPATIAL_INDEX_LOCATION_NULL]; gSpriteSpatialIndex[SPATIAL_INDEX_LOCATION_NULL] = sprite->sprite_index; return (rct_sprite*)sprite; } /* * rct2: 0x0069ED0B * This function moves a sprite to the specified sprite linked list. * There are 5/6 of those, and cl specifies a pointer offset * of the desired linked list in a uint16 array. Known valid values are * 2, 4, 6, 8 or 10 (SPRITE_LIST_... * 2) */ void move_sprite_to_list(rct_sprite *sprite, uint8 newListOffset) { rct_unk_sprite *unkSprite = &sprite->unknown; uint8 oldListOffset = unkSprite->linked_list_type_offset; sint32 oldList = oldListOffset >> 1; sint32 newList = newListOffset >> 1; // No need to move if the sprite is already in the desired list if (oldListOffset == newListOffset) { return; } // If the sprite is currently the head of the list, the // sprite following this one becomes the new head of the list. if (unkSprite->previous == SPRITE_INDEX_NULL) { gSpriteListHead[oldList] = unkSprite->next; } else { // Hook up sprite->previous->next to sprite->next, removing the sprite from its old list get_sprite(unkSprite->previous)->unknown.next = unkSprite->next; } // Similarly, hook up sprite->next->previous to sprite->previous if (unkSprite->next != SPRITE_INDEX_NULL) { get_sprite(unkSprite->next)->unknown.previous = unkSprite->previous; } unkSprite->previous = SPRITE_INDEX_NULL; // We become the new head of the target list, so there's no previous sprite unkSprite->linked_list_type_offset = newListOffset; unkSprite->next = gSpriteListHead[newList]; // This sprite's next sprite is the old head, since we're the new head gSpriteListHead[newList] = unkSprite->sprite_index; // Store this sprite's index as head of its new list if (unkSprite->next != SPRITE_INDEX_NULL) { // Fix the chain by settings sprite->next->previous to sprite_index get_sprite(unkSprite->next)->unknown.previous = unkSprite->sprite_index; } // These globals are probably counters for each sprite list? // Decrement old list counter, increment new list counter. gSpriteListCount[oldList]--; gSpriteListCount[newList]++; } /** * * rct2: 0x00673200 */ static void sprite_steam_particle_update(rct_steam_particle *steam) { invalidate_sprite_2((rct_sprite*)steam); // Move up 1 z every 3 ticks (Starts after 4 ticks) steam->time_to_move++; if (steam->time_to_move >= 4) { steam->time_to_move = 1; sprite_move( steam->x, steam->y, steam->z + 1, (rct_sprite*)steam ); } steam->frame += 64; if (steam->frame >= (56 * 64)) { sprite_remove((rct_sprite*)steam); } } /** * * rct2: 0x0067363D */ void sprite_misc_explosion_cloud_create(sint32 x, sint32 y, sint32 z) { rct_unk_sprite *sprite = (rct_unk_sprite*)create_sprite(2); if (sprite != nullptr) { sprite->sprite_width = 44; sprite->sprite_height_negative = 32; sprite->sprite_height_positive = 34; sprite->sprite_identifier = SPRITE_IDENTIFIER_MISC; sprite_move(x, y, z + 4, (rct_sprite*)sprite); sprite->misc_identifier = SPRITE_MISC_EXPLOSION_CLOUD; sprite->frame = 0; } } /** * * rct2: 0x00673385 */ static void sprite_misc_explosion_cloud_update(rct_sprite * sprite) { invalidate_sprite_2(sprite); sprite->unknown.frame += 128; if (sprite->unknown.frame >= (36 * 128)) { sprite_remove(sprite); } } /** * * rct2: 0x0067366B */ void sprite_misc_explosion_flare_create(sint32 x, sint32 y, sint32 z) { rct_unk_sprite *sprite = (rct_unk_sprite*)create_sprite(2); if (sprite != nullptr) { sprite->sprite_width = 25; sprite->sprite_height_negative = 85; sprite->sprite_height_positive = 8; sprite->sprite_identifier = SPRITE_IDENTIFIER_MISC; sprite_move(x, y, z + 4, (rct_sprite*)sprite); sprite->misc_identifier = SPRITE_MISC_EXPLOSION_FLARE; sprite->frame = 0; } } /** * * rct2: 0x006733B4 */ static void sprite_misc_explosion_flare_update(rct_sprite * sprite) { invalidate_sprite_2(sprite); sprite->unknown.frame += 64; if (sprite->unknown.frame >= (124 * 64)) { sprite_remove(sprite); } } /** * * rct2: 0x006731CD */ static void sprite_misc_update(rct_sprite *sprite) { switch (sprite->unknown.misc_identifier) { case SPRITE_MISC_STEAM_PARTICLE: sprite_steam_particle_update((rct_steam_particle*)sprite); break; case SPRITE_MISC_MONEY_EFFECT: money_effect_update(&sprite->money_effect); break; case SPRITE_MISC_CRASHED_VEHICLE_PARTICLE: crashed_vehicle_particle_update((rct_crashed_vehicle_particle*)sprite); break; case SPRITE_MISC_EXPLOSION_CLOUD: sprite_misc_explosion_cloud_update(sprite); break; case SPRITE_MISC_CRASH_SPLASH: crash_splash_update((rct_crash_splash*)sprite); break; case SPRITE_MISC_EXPLOSION_FLARE: sprite_misc_explosion_flare_update(sprite); break; case SPRITE_MISC_JUMPING_FOUNTAIN_WATER: case SPRITE_MISC_JUMPING_FOUNTAIN_SNOW: jumping_fountain_update(&sprite->jumping_fountain); break; case SPRITE_MISC_BALLOON: balloon_update(&sprite->balloon); break; case SPRITE_MISC_DUCK: duck_update(&sprite->duck); break; } } /** * * rct2: 0x00672AA4 */ void sprite_misc_update_all() { rct_sprite *sprite; uint16 spriteIndex; spriteIndex = gSpriteListHead[SPRITE_LIST_MISC]; while (spriteIndex != SPRITE_INDEX_NULL) { sprite = get_sprite(spriteIndex); spriteIndex = sprite->unknown.next; sprite_misc_update(sprite); } } /** * Moves a sprite to a new location. * rct2: 0x0069E9D3 * * @param x (ax) * @param y (cx) * @param z (dx) * @param sprite (esi) */ void sprite_move(sint16 x, sint16 y, sint16 z, rct_sprite *sprite) { if (x < 0 || y < 0 || x > 0x1FFF || y > 0x1FFF) { x = LOCATION_NULL; } size_t newIndex = GetSpatialIndexOffset(x, y); size_t currentIndex = GetSpatialIndexOffset(sprite->unknown.x, sprite->unknown.y); if (newIndex != currentIndex) { uint16 *spriteIndex = &gSpriteSpatialIndex[currentIndex]; if (*spriteIndex != SPRITE_INDEX_NULL) { rct_sprite *sprite2 = get_sprite(*spriteIndex); while (sprite != sprite2) { spriteIndex = &sprite2->unknown.next_in_quadrant; if (*spriteIndex == SPRITE_INDEX_NULL) { break; } sprite2 = get_sprite(*spriteIndex); } } *spriteIndex = sprite->unknown.next_in_quadrant; sint32 tempSpriteIndex = gSpriteSpatialIndex[newIndex]; gSpriteSpatialIndex[newIndex] = sprite->unknown.sprite_index; sprite->unknown.next_in_quadrant = tempSpriteIndex; } if (x == LOCATION_NULL) { sprite->unknown.sprite_left = LOCATION_NULL; sprite->unknown.x = x; sprite->unknown.y = y; sprite->unknown.z = z; } else { sprite_set_coordinates(x, y, z, sprite); } } void sprite_set_coordinates(sint16 x, sint16 y, sint16 z, rct_sprite *sprite){ sint16 new_x = x, new_y = y, start_x = x; switch (get_current_rotation()){ case 0: new_x = new_y - new_x; new_y = (new_y + start_x) / 2 - z; break; case 1: new_x = -new_y - new_x; new_y = (new_y - start_x) / 2 - z; break; case 2: new_x = -new_y + new_x; new_y = (-new_y - start_x) / 2 - z; break; case 3: new_x = new_y + new_x; new_y = (-new_y + start_x) / 2 - z; break; } sprite->unknown.sprite_left = new_x - sprite->unknown.sprite_width; sprite->unknown.sprite_right = new_x + sprite->unknown.sprite_width; sprite->unknown.sprite_top = new_y - sprite->unknown.sprite_height_negative; sprite->unknown.sprite_bottom = new_y + sprite->unknown.sprite_height_positive; sprite->unknown.x = x; sprite->unknown.y = y; sprite->unknown.z = z; } /** * * rct2: 0x0069EDB6 */ void sprite_remove(rct_sprite *sprite) { move_sprite_to_list(sprite, SPRITE_LIST_NULL * 2); user_string_free(sprite->unknown.name_string_idx); sprite->unknown.sprite_identifier = SPRITE_IDENTIFIER_NULL; _spriteFlashingList[sprite->unknown.sprite_index] = false; size_t quadrantIndex = GetSpatialIndexOffset(sprite->unknown.x, sprite->unknown.y); uint16 *spriteIndex = &gSpriteSpatialIndex[quadrantIndex]; rct_sprite *quadrantSprite; while ((quadrantSprite = get_sprite(*spriteIndex)) != sprite) { spriteIndex = &quadrantSprite->unknown.next_in_quadrant; } *spriteIndex = sprite->unknown.next_in_quadrant; } static bool litter_can_be_at(sint32 x, sint32 y, sint32 z) { rct_tile_element *tileElement; if (!map_is_location_owned(x & 0xFFE0, y & 0xFFE0, z)) return false; tileElement = map_get_first_element_at(x >> 5, y >> 5); do { if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_PATH) continue; sint32 pathZ = tileElement->base_height * 8; if (pathZ < z || pathZ >= z + 32) continue; if (tile_element_is_underground(tileElement)) return false; return true; } while (!tile_element_is_last_for_tile(tileElement++)); return false; } /** * * rct2: 0x0067375D */ void litter_create(sint32 x, sint32 y, sint32 z, sint32 direction, sint32 type) { if (gCheatsDisableLittering) return; x += TileDirectionDelta[direction >> 3].x / 8; y += TileDirectionDelta[direction >> 3].y / 8; if (!litter_can_be_at(x, y, z)) return; if (gSpriteListCount[SPRITE_LIST_LITTER] >= 500) { rct_litter *newestLitter = nullptr; uint32 newestLitterCreationTick = 0; for (uint16 nextSpriteIndex, spriteIndex = gSpriteListHead[SPRITE_LIST_LITTER]; spriteIndex != SPRITE_INDEX_NULL; spriteIndex = nextSpriteIndex) { rct_litter *litter = &get_sprite(spriteIndex)->litter; nextSpriteIndex = litter->next; if (newestLitterCreationTick <= litter->creationTick) { newestLitterCreationTick = litter->creationTick; newestLitter = litter; } } if (newestLitter != nullptr) { invalidate_sprite_0((rct_sprite*)newestLitter); sprite_remove((rct_sprite*)newestLitter); } } rct_litter *litter = (rct_litter*)create_sprite(1); if (litter == nullptr) return; move_sprite_to_list((rct_sprite*)litter, SPRITE_LIST_LITTER * 2); litter->sprite_direction = direction; litter->sprite_width = 6; litter->sprite_height_negative = 6; litter->sprite_height_positive = 3; litter->sprite_identifier = SPRITE_IDENTIFIER_LITTER; litter->type = type; sprite_move(x, y, z, (rct_sprite*)litter); invalidate_sprite_0((rct_sprite*)litter); litter->creationTick = gScenarioTicks; } /** * * rct2: 0x006738E1 */ void litter_remove_at(sint32 x, sint32 y, sint32 z) { uint16 spriteIndex = sprite_get_first_in_quadrant(x, y); while (spriteIndex != SPRITE_INDEX_NULL) { rct_sprite *sprite = get_sprite(spriteIndex); uint16 nextSpriteIndex = sprite->unknown.next_in_quadrant; if (sprite->unknown.linked_list_type_offset == SPRITE_LIST_LITTER * 2) { rct_litter *litter = &sprite->litter; if (abs(litter->z - z) <= 16) { if (abs(litter->x - x) <= 8 && abs(litter->y - y) <= 8) { invalidate_sprite_0(sprite); sprite_remove(sprite); } } } spriteIndex = nextSpriteIndex; } } /** * Determines whether it's worth tweening a sprite or not when frame smoothing is on. */ static bool sprite_should_tween(rct_sprite *sprite) { switch (sprite->unknown.linked_list_type_offset >> 1) { case SPRITE_LIST_TRAIN: case SPRITE_LIST_PEEP: case SPRITE_LIST_UNKNOWN: return true; } return false; } static void store_sprite_locations(LocationXYZ16 * sprite_locations) { for (uint16 i = 0; i < MAX_SPRITES; i++) { // skip going through `get_sprite` to not get stalled on assert, // this can get very expensive for busy parks with uncap FPS option on const rct_sprite *sprite = &_spriteList[i]; sprite_locations[i].x = sprite->unknown.x; sprite_locations[i].y = sprite->unknown.y; sprite_locations[i].z = sprite->unknown.z; } } void sprite_position_tween_store_a() { store_sprite_locations(_spritelocations1); } void sprite_position_tween_store_b() { store_sprite_locations(_spritelocations2); } void sprite_position_tween_all(float alpha) { const float inv = (1.0f - alpha); for (uint16 i = 0; i < MAX_SPRITES; i++) { rct_sprite * sprite = get_sprite(i); if (sprite_should_tween(sprite)) { LocationXYZ16 posA = _spritelocations1[i]; LocationXYZ16 posB = _spritelocations2[i]; if (posA.x == posB.x && posA.y == posB.y && posA.z == posB.z) { continue; } sprite_set_coordinates( posB.x * alpha + posA.x * inv, posB.y * alpha + posA.y * inv, posB.z * alpha + posA.z * inv, sprite ); invalidate_sprite_2(sprite); } } } /** * Restore the real positions of the sprites so they aren't left at the mid-tween positions */ void sprite_position_tween_restore() { for (uint16 i = 0; i < MAX_SPRITES; i++) { rct_sprite * sprite = get_sprite(i); if (sprite_should_tween(sprite)) { invalidate_sprite_2(sprite); LocationXYZ16 pos = _spritelocations2[i]; sprite_set_coordinates(pos.x, pos.y, pos.z, sprite); } } } void sprite_position_tween_reset() { for (uint16 i = 0; i < MAX_SPRITES; i++) { rct_sprite * sprite = get_sprite(i); _spritelocations1[i].x = _spritelocations2[i].x = sprite->unknown.x; _spritelocations1[i].y = _spritelocations2[i].y = sprite->unknown.y; _spritelocations1[i].z = _spritelocations2[i].z = sprite->unknown.z; } } void sprite_set_flashing(rct_sprite *sprite, bool flashing) { assert(sprite->unknown.sprite_index < MAX_SPRITES); _spriteFlashingList[sprite->unknown.sprite_index] = flashing; } bool sprite_get_flashing(rct_sprite *sprite) { assert(sprite->unknown.sprite_index < MAX_SPRITES); return _spriteFlashingList[sprite->unknown.sprite_index]; } static rct_sprite * find_sprite_list_cycle(uint16 sprite_idx) { if (sprite_idx == SPRITE_INDEX_NULL) { return nullptr; } const rct_sprite * fast = get_sprite(sprite_idx); const rct_sprite * slow = fast; bool increment_slow = false; rct_sprite * cycle_start = nullptr; while (fast->unknown.sprite_index != SPRITE_INDEX_NULL) { // increment fast every time, unless reached the end if (fast->unknown.next == SPRITE_INDEX_NULL) { break; } else { fast = get_sprite(fast->unknown.next); } // increment slow only every second iteration if (increment_slow) { slow = get_sprite(slow->unknown.next); } increment_slow = !increment_slow; if (fast == slow) { cycle_start = get_sprite(slow->unknown.sprite_index); break; } } return cycle_start; } static rct_sprite * find_sprite_quadrant_cycle(uint16 sprite_idx) { if (sprite_idx == SPRITE_INDEX_NULL) { return nullptr; } const rct_sprite * fast = get_sprite(sprite_idx); const rct_sprite * slow = fast; bool increment_slow = false; rct_sprite * cycle_start = nullptr; while (fast->unknown.sprite_index != SPRITE_INDEX_NULL) { // increment fast every time, unless reached the end if (fast->unknown.next_in_quadrant == SPRITE_INDEX_NULL) { break; } else { fast = get_sprite(fast->unknown.next_in_quadrant); } // increment slow only every second iteration if (increment_slow) { slow = get_sprite(slow->unknown.next_in_quadrant); } increment_slow = !increment_slow; if (fast == slow) { cycle_start = get_sprite(slow->unknown.sprite_index); break; } } return cycle_start; } static bool index_is_in_list(uint16 index, enum SPRITE_LIST sl) { uint16 sprite_index = gSpriteListHead[sl]; while (sprite_index != SPRITE_INDEX_NULL) { if (sprite_index == index) { return true; } sprite_index = get_sprite(sprite_index)->unknown.next; } return false; } sint32 check_for_sprite_list_cycles(bool fix) { for (sint32 i = 0; i < NUM_SPRITE_LISTS; i++) { rct_sprite * 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 get_sprite(gSpriteListHead[i])->unknown.previous = SPRITE_INDEX_NULL; // Store the leftover part of cycle to be fixed uint16 cycle_next = cycle_start->unknown.next; // Break the cycle cycle_start->unknown.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, (SPRITE_LIST)i)) { rct_sprite * spr = get_sprite(cycle_next); cycle_start->unknown.next = cycle_next; spr->unknown.previous = cycle_start->unknown.sprite_index; cycle_next = spr->unknown.next; spr->unknown.next = SPRITE_INDEX_NULL; cycle_start = spr; } } return i; } } return -1; } /** * Finds and fixes null sprites that are not reachable via SPRITE_LIST_NULL list. * * @return count of disjoint sprites found */ sint32 fix_disjoint_sprites() { // Find reachable sprites bool reachable[MAX_SPRITES] = { false }; uint16 sprite_idx = gSpriteListHead[SPRITE_LIST_NULL]; rct_sprite * null_list_tail = nullptr; while (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 = get_sprite(sprite_idx); sprite_idx = null_list_tail->unknown.next; } sint32 count = 0; // Find all null sprites for (sprite_idx = 0; sprite_idx < MAX_SPRITES; sprite_idx++) { rct_sprite * spr = get_sprite(sprite_idx); if (spr->unknown.sprite_identifier == SPRITE_IDENTIFIER_NULL) { openrct2_assert(null_list_tail != nullptr, "Null list is empty, yet found null sprites"); spr->unknown.sprite_index = sprite_idx; if (!reachable[sprite_idx]) { // Add the sprite directly to the list null_list_tail->unknown.next = sprite_idx; spr->unknown.next = SPRITE_INDEX_NULL; spr->unknown.previous = null_list_tail->unknown.sprite_index; null_list_tail = spr; count++; reachable[sprite_idx] = true; } } } return count; } sint32 check_for_spatial_index_cycles(bool fix) { for (sint32 i = 0; i < SPATIAL_INDEX_LOCATION_NULL; i++) { rct_sprite * cycle_start = find_sprite_quadrant_cycle(gSpriteSpatialIndex[i]); if (cycle_start != nullptr) { if (fix) { // Store the leftover part of cycle to be fixed uint16 cycle_next = cycle_start->unknown.next_in_quadrant; // Break the cycle cycle_start->unknown.next_in_quadrant = 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, (SPRITE_LIST)i)) { rct_sprite * spr = get_sprite(cycle_next); cycle_start->unknown.next_in_quadrant = cycle_next; cycle_next = spr->unknown.next_in_quadrant; spr->unknown.next_in_quadrant = SPRITE_INDEX_NULL; cycle_start = spr; } } return i; } } return -1; }