OpenRCT2/src/world/sprite.c

696 lines
18 KiB
C
Raw Normal View History

#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
2014-05-25 20:47:11 +02:00
/*****************************************************************************
* 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
2014-05-25 20:47:11 +02:00
*
* 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
2014-05-25 20:47:11 +02:00
*****************************************************************************/
#pragma endregion
2014-05-25 20:47:11 +02:00
2014-10-06 18:36:58 +02:00
#include "../addresses.h"
#include "../audio/audio.h"
#include "../cheats.h"
#include "../game.h"
#include "../interface/viewport.h"
2015-03-26 00:57:09 +01:00
#include "../localisation/date.h"
2015-03-31 04:23:40 +02:00
#include "../localisation/localisation.h"
#include "../scenario.h"
2015-03-24 17:48:38 +01:00
#include "fountain.h"
#include "sprite.h"
2016-07-17 23:54:31 +02:00
#include "../openrct2.h"
2014-05-25 20:47:11 +02:00
2016-05-09 01:08:03 +02:00
uint16 *gSpriteListHead = RCT2_ADDRESS(RCT2_ADDRESS_SPRITE_LISTS_HEAD, uint16);
uint16 *gSpriteListCount = RCT2_ADDRESS(RCT2_ADDRESS_SPRITE_LISTS_COUNT, uint16);
uint16 *gSpriteSpatialIndex = (uint16*)0xF1EF60;
rct_sprite* sprite_list = RCT2_ADDRESS(RCT2_ADDRESS_SPRITE_LIST, rct_sprite);
rct_sprite *get_sprite(size_t sprite_idx)
{
2016-07-17 23:54:31 +02:00
openrct2_assert(sprite_idx < MAX_SPRITES, "Tried getting sprite %u", sprite_idx);
return &sprite_list[sprite_idx];
}
uint16 sprite_get_first_in_quadrant(int x, int y)
2015-09-05 14:59:12 +02:00
{
int offset = ((x & 0x1FE0) << 3) | (y >> 5);
return gSpriteSpatialIndex[offset];
2015-09-05 14:59:12 +02:00
}
static void invalidate_sprite_max_zoom(rct_sprite *sprite, int maxZoom)
2015-08-22 12:56:32 +02:00
{
2015-10-25 16:34:01 +01:00
if (sprite->unknown.sprite_left == SPRITE_LOCATION_NULL) return;
for (int i = 0; i < MAX_VIEWPORT_COUNT; i++) {
rct_viewport *viewport = &g_viewport_list[i];
if (viewport->width != 0 && viewport->zoom <= maxZoom) {
2015-08-22 12:56:32 +02:00
viewport_invalidate(
viewport,
sprite->unknown.sprite_left,
sprite->unknown.sprite_top,
sprite->unknown.sprite_right,
sprite->unknown.sprite_bottom
);
}
}
}
2015-03-26 00:57:09 +01:00
/**
2015-08-22 12:56:32 +02:00
* 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
2015-03-26 00:57:09 +01:00
*/
2015-08-22 12:56:32 +02:00
void invalidate_sprite_1(rct_sprite *sprite)
2015-03-26 00:57:09 +01:00
{
2015-08-22 12:56:32 +02:00
invalidate_sprite_max_zoom(sprite, 1);
}
/**
2016-02-03 21:31:30 +01:00
* Invalidate sprite if not at furthest zoom.
* rct2: 0x006EC473
*
* @param sprite (esi)
*/
2015-08-22 12:56:32 +02:00
void invalidate_sprite_2(rct_sprite *sprite)
{
invalidate_sprite_max_zoom(sprite, 2);
2015-03-26 00:57:09 +01:00
}
/**
*
* rct2: 0x0069EB13
*/
2016-05-09 01:08:03 +02:00
void reset_sprite_list()
{
2016-01-11 01:50:03 +01:00
RCT2_GLOBAL(RCT2_ADDRESS_SAVED_AGE, uint16) = 0;
memset(sprite_list, 0, sizeof(rct_sprite) * MAX_SPRITES);
2016-05-09 01:08:03 +02:00
for (int i = 0; i < NUM_SPRITE_LISTS; i++) {
gSpriteListHead[i] = SPRITE_INDEX_NULL;
gSpriteListCount[i] = 0;
}
rct_sprite* previous_spr = (rct_sprite*)SPRITE_INDEX_NULL;
2015-10-25 16:34:01 +01:00
for (int i = 0; i < MAX_SPRITES; ++i){
rct_sprite *spr = get_sprite(i);
2015-10-25 16:34:01 +01:00
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;
2016-05-09 01:08:03 +02:00
gSpriteListHead[SPRITE_LIST_NULL] = i;
}
previous_spr = spr;
spr++;
}
2016-05-09 01:08:03 +02:00
gSpriteListCount[SPRITE_LIST_NULL] = MAX_SPRITES;
game_do_command(0, GAME_COMMAND_FLAG_APPLY, 0, 0, GAME_COMMAND_RESET_SPRITES, 0, 0);
2014-06-21 14:31:28 +02:00
}
/**
*
* rct2: 0x0069EBE4
2014-06-21 14:31:28 +02:00
* This function looks as though it sets some sort of order for sprites.
* Sprites can share thier position if this is the case.
*/
void reset_sprite_spatial_index()
2016-05-09 01:08:03 +02:00
{
memset(gSpriteSpatialIndex, -1, 0x10001 * sizeof(uint16));
2016-05-09 01:08:03 +02:00
for (size_t i = 0; i < MAX_SPRITES; i++) {
rct_sprite *spr = get_sprite(i);
2016-05-09 01:08:03 +02:00
if (spr->unknown.sprite_identifier != SPRITE_IDENTIFIER_NULL) {
uint32 index;
2016-05-09 01:08:03 +02:00
if (spr->unknown.x == SPRITE_LOCATION_NULL) {
index = 0x10000;
2015-07-23 19:38:43 +02:00
} else {
sint16 x = floor2(spr->unknown.x, 32);
uint8 tileY = spr->unknown.y >> 5;
index = (x << 3) | tileY;
2014-06-21 14:31:28 +02:00
}
uint16 nextSpriteId = gSpriteSpatialIndex[index];
gSpriteSpatialIndex[index] = spr->unknown.sprite_index;
spr->unknown.next_in_quadrant = nextSpriteId;
2014-06-21 14:31:28 +02:00
}
}
}
void game_command_reset_sprites(int* eax, int* ebx, int* ecx, int* edx, int* esi, int* edi, int* ebp)
{
if (*ebx & GAME_COMMAND_FLAG_APPLY) {
reset_all_sprite_quadrant_placements();
reset_sprite_spatial_index();
}
*ebx = 0;
}
#ifndef DISABLE_NETWORK
static unsigned char _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)
{
if (EVP_DigestUpdate(gHashCTX, sprite, sizeof(rct_sprite)) <= 0)
{
openrct2_assert(false, "Failed to update digest");
}
}
}
unsigned char localhash[EVP_MAX_MD_SIZE + 1];
unsigned int size = sizeof(localhash);
EVP_DigestFinal(gHashCTX, localhash, &size);
assert(size <= sizeof(localhash));
localhash[sizeof(localhash) - 1] = '\0';
char *x = (char *)_spriteChecksum;
for (unsigned int i = 0; i < size; i++)
{
sprintf(x, "%02x", localhash[i]);
x += 2;
}
*x = '\0';
return (char *)_spriteChecksum;
}
#else
char * sprite_checksum()
{
return NULL;
}
#endif // DISABLE_NETWORK
/**
* 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;
2015-06-09 02:24:02 +02:00
uint16 spriteIndex, nextSpriteIndex, previousSpriteIndex;
2015-10-20 20:16:30 +02:00
2016-05-09 01:08:03 +02:00
spriteIndex = gSpriteListHead[SPRITE_LIST_NULL];
while (spriteIndex != SPRITE_INDEX_NULL) {
sprite = &get_sprite(spriteIndex)->unknown;
nextSpriteIndex = sprite->next;
2015-06-09 02:24:02 +02:00
previousSpriteIndex = sprite->previous;
memset(sprite, 0, sizeof(rct_sprite));
sprite->sprite_identifier = SPRITE_IDENTIFIER_NULL;
sprite->next = nextSpriteIndex;
2015-06-09 02:24:02 +02:00
sprite->previous = previousSpriteIndex;
2016-05-09 01:08:03 +02:00
sprite->linked_list_type_offset = SPRITE_LIST_NULL * 2;
sprite->sprite_index = spriteIndex;
spriteIndex = nextSpriteIndex;
}
}
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 prev = sprite->previous;
uint16 sprite_index = sprite->sprite_index;
memset(sprite, 0, sizeof(rct_sprite));
sprite->linked_list_type_offset = llto;
sprite->next = next;
sprite->previous = prev;
sprite->sprite_index = sprite_index;
}
/*
* rct2: 0x0069EC6B
* bl: if bl & 2 > 0, the sprite ends up in the MISC linked list.
*/
rct_sprite *create_sprite(uint8 bl)
{
2016-05-09 01:08:03 +02:00
size_t linkedListTypeOffset = SPRITE_LIST_UNKNOWN * 2;
if ((bl & 2) != 0) {
// 69EC96;
2016-05-09 01:08:03 +02:00
sint16 cx = 0x12C - gSpriteListCount[SPRITE_LIST_MISC];
if (cx >= gSpriteListCount[SPRITE_LIST_NULL]) {
return NULL;
}
2016-05-09 01:08:03 +02:00
linkedListTypeOffset = SPRITE_LIST_MISC * 2;
} else if (gSpriteListCount[SPRITE_LIST_NULL] == 0) {
return NULL;
}
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 = SPRITE_LOCATION_NULL;
sprite->y = SPRITE_LOCATION_NULL;
sprite->z = 0;
sprite->name_string_idx = 0;
2015-02-18 19:27:36 +01:00
sprite->sprite_width = 0x10;
sprite->sprite_height_negative = 0x14;
sprite->sprite_height_positive = 0x8;
sprite->flags = 0;
sprite->sprite_left = SPRITE_LOCATION_NULL;
sprite->next_in_quadrant = gSpriteSpatialIndex[0x10000];
gSpriteSpatialIndex[0x10000] = sprite->sprite_index;
2014-09-05 00:27:16 +02:00
return (rct_sprite*)sprite;
}
/*
2016-05-09 01:08:03 +02:00
* 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;
2016-05-09 01:08:03 +02:00
uint8 oldListOffset = unkSprite->linked_list_type_offset;
int oldList = oldListOffset >> 1;
int newList = newListOffset >> 1;
// No need to move if the sprite is already in the desired list
2016-05-09 01:08:03 +02:00
if (oldListOffset == newListOffset) {
return;
2016-05-09 01:08:03 +02:00
}
2015-10-20 20:16:30 +02:00
// If the sprite is currently the head of the list, the
// sprite following this one becomes the new head of the list.
2016-05-09 01:08:03 +02:00
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
2016-05-09 01:08:03 +02:00
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
2016-05-09 01:08:03 +02:00
unkSprite->linked_list_type_offset = newListOffset;
2016-05-09 01:08:03 +02:00
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.
2016-05-09 01:08:03 +02:00
gSpriteListCount[oldList]--;
gSpriteListCount[newList]++;
2014-10-09 21:31:58 +02:00
}
2015-06-29 18:22:01 +02:00
/**
*
* rct2: 0x00673200
2015-06-29 18:22:01 +02:00
*/
2015-12-20 13:50:33 +01:00
static void sprite_steam_particle_update(rct_steam_particle *steam)
2015-06-29 18:22:01 +02:00
{
2015-12-20 13:50:33 +01:00
invalidate_sprite_2((rct_sprite*)steam);
steam->var_24 += 0x5555;
if (steam->var_24 < 0x5555) {
sprite_move(
steam->x,
steam->y,
steam->z + 1,
(rct_sprite*)steam
);
2015-08-22 17:39:24 +02:00
}
2016-04-28 13:28:25 +02:00
steam->frame += 64;
if (steam->frame >= (56 * 64)) {
2015-12-20 13:50:33 +01:00
sprite_remove((rct_sprite*)steam);
2015-08-22 17:39:24 +02:00
}
2015-06-29 18:22:01 +02:00
}
/**
*
2015-08-22 17:39:24 +02:00
* rct2: 0x0067363D
2015-06-29 18:22:01 +02:00
*/
2016-04-28 13:28:25 +02:00
void sprite_misc_explosion_cloud_create(int x, int y, int z)
2015-06-29 18:22:01 +02:00
{
2015-08-22 17:39:24 +02:00
rct_unk_sprite *sprite = (rct_unk_sprite*)create_sprite(2);
if (sprite != NULL) {
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);
2016-04-28 13:28:25 +02:00
sprite->misc_identifier = SPRITE_MISC_EXPLOSION_CLOUD;
sprite->frame = 0;
2015-08-22 17:39:24 +02:00
}
2015-06-29 18:22:01 +02:00
}
/**
*
* rct2: 0x00673385
2015-06-29 18:22:01 +02:00
*/
2016-04-28 13:28:25 +02:00
static void sprite_misc_explosion_cloud_update(rct_sprite * sprite)
2015-06-29 18:22:01 +02:00
{
2015-08-22 17:39:24 +02:00
invalidate_sprite_2(sprite);
2016-04-28 13:28:25 +02:00
sprite->unknown.frame += 128;
if (sprite->unknown.frame >= (36 * 128)) {
2015-08-22 17:39:24 +02:00
sprite_remove(sprite);
}
2015-06-29 18:22:01 +02:00
}
/**
*
2015-08-22 17:39:24 +02:00
* rct2: 0x0067366B
2015-06-29 18:22:01 +02:00
*/
2016-04-28 13:28:25 +02:00
void sprite_misc_explosion_flare_create(int x, int y, int z)
2015-06-29 18:22:01 +02:00
{
2015-08-22 17:39:24 +02:00
rct_unk_sprite *sprite = (rct_unk_sprite*)create_sprite(2);
if (sprite != NULL) {
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);
2016-04-28 13:28:25 +02:00
sprite->misc_identifier = SPRITE_MISC_EXPLOSION_FLARE;
sprite->frame = 0;
2015-08-22 17:39:24 +02:00
}
2015-06-29 18:22:01 +02:00
}
/**
*
* rct2: 0x006733B4
2015-06-29 18:22:01 +02:00
*/
2016-04-28 13:28:25 +02:00
static void sprite_misc_explosion_flare_update(rct_sprite * sprite)
2015-06-29 18:22:01 +02:00
{
2015-08-22 17:39:24 +02:00
invalidate_sprite_2(sprite);
2016-04-28 13:28:25 +02:00
sprite->unknown.frame += 64;
if (sprite->unknown.frame >= (124 * 64)) {
2015-08-22 17:39:24 +02:00
sprite_remove(sprite);
}
2015-06-29 18:22:01 +02:00
}
/**
*
* rct2: 0x006731CD
*/
static void sprite_misc_update(rct_sprite *sprite)
{
2015-03-24 17:48:38 +01:00
switch (sprite->unknown.misc_identifier) {
2015-12-20 13:50:33 +01:00
case SPRITE_MISC_STEAM_PARTICLE:
sprite_steam_particle_update((rct_steam_particle*)sprite);
2015-03-24 17:48:38 +01:00
break;
case SPRITE_MISC_MONEY_EFFECT:
money_effect_update(&sprite->money_effect);
2015-03-24 17:48:38 +01:00
break;
2015-08-22 17:39:24 +02:00
case SPRITE_MISC_CRASHED_VEHICLE_PARTICLE:
crashed_vehicle_particle_update((rct_crashed_vehicle_particle*)sprite);
2015-03-24 17:48:38 +01:00
break;
2016-04-28 13:28:25 +02:00
case SPRITE_MISC_EXPLOSION_CLOUD:
sprite_misc_explosion_cloud_update(sprite);
2015-03-24 17:48:38 +01:00
break;
2015-08-22 17:39:24 +02:00
case SPRITE_MISC_CRASH_SPLASH:
crash_splash_update((rct_crash_splash*)sprite);
2015-03-24 17:48:38 +01:00
break;
2016-04-28 13:28:25 +02:00
case SPRITE_MISC_EXPLOSION_FLARE:
sprite_misc_explosion_flare_update(sprite);
2015-03-24 17:48:38 +01:00
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;
}
}
2014-10-09 21:31:58 +02:00
/**
*
* rct2: 0x00672AA4
2014-10-09 21:31:58 +02:00
*/
void sprite_misc_update_all()
2014-10-09 21:31:58 +02:00
{
rct_sprite *sprite;
uint16 spriteIndex;
2016-05-09 01:08:03 +02:00
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
2016-02-04 10:14:27 +01:00
*
* @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)
2015-10-25 16:34:01 +01:00
x = SPRITE_LOCATION_NULL;
int new_position = x;
2015-10-25 16:34:01 +01:00
if (x == SPRITE_LOCATION_NULL)new_position = 0x10000;
else{
new_position &= 0x1FE0;
new_position = (y >> 5) | (new_position << 3);
}
int current_position = sprite->unknown.x;
2015-10-25 16:34:01 +01:00
if (sprite->unknown.x == SPRITE_LOCATION_NULL)current_position = 0x10000;
else{
current_position &= 0x1FE0;
current_position = (sprite->unknown.y >> 5) | (current_position << 3);
}
if (new_position != current_position){
uint16* sprite_idx = &gSpriteSpatialIndex[current_position];
rct_sprite* sprite2 = get_sprite(*sprite_idx);
while (sprite != sprite2){
2015-03-09 23:06:08 +01:00
sprite_idx = &sprite2->unknown.next_in_quadrant;
sprite2 = get_sprite(*sprite_idx);
}
2015-03-09 23:06:08 +01:00
*sprite_idx = sprite->unknown.next_in_quadrant;
int temp_sprite_idx = gSpriteSpatialIndex[new_position];
gSpriteSpatialIndex[new_position] = sprite->unknown.sprite_index;
2015-03-09 23:06:08 +01:00
sprite->unknown.next_in_quadrant = temp_sprite_idx;
}
2015-10-25 16:34:01 +01:00
if (x == SPRITE_LOCATION_NULL){
sprite->unknown.sprite_left = SPRITE_LOCATION_NULL;
sprite->unknown.x = x;
sprite->unknown.y = y;
sprite->unknown.z = z;
return;
}
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;
}
2015-02-18 19:27:36 +01:00
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;
}
2016-07-25 20:18:35 +02:00
void sprite_set_coordinates(sint16 x, sint16 y, sint16 z, rct_sprite *sprite){
sprite->unknown.x = x;
sprite->unknown.y = y;
sprite->unknown.z = z;
}
/**
*
* rct2: 0x0069EDB6
*/
void sprite_remove(rct_sprite *sprite)
{
2016-05-09 01:08:03 +02:00
move_sprite_to_list(sprite, SPRITE_LIST_NULL * 2);
2015-08-31 17:20:56 +02:00
user_string_free(sprite->unknown.name_string_idx);
sprite->unknown.sprite_identifier = SPRITE_IDENTIFIER_NULL;
uint32 quadrantIndex = sprite->unknown.x;
if (sprite->unknown.x == SPRITE_LOCATION_NULL) {
quadrantIndex = 0x10000;
} else {
quadrantIndex = (floor2(sprite->unknown.x, 32) << 3) | (sprite->unknown.y >> 5);
}
uint16 *spriteIndex = &gSpriteSpatialIndex[quadrantIndex];
2015-08-31 17:20:56 +02:00
rct_sprite *quadrantSprite;
while ((quadrantSprite = get_sprite(*spriteIndex)) != sprite) {
2015-08-31 17:20:56 +02:00
spriteIndex = &quadrantSprite->unknown.next_in_quadrant;
}
*spriteIndex = sprite->unknown.next_in_quadrant;
}
static bool litter_can_be_at(int x, int y, int z)
{
rct_map_element *mapElement;
if (!map_is_location_owned(x & 0xFFE0, y & 0xFFE0, z))
return false;
mapElement = map_get_first_element_at(x >> 5, y >> 5);
do {
if (map_element_get_type(mapElement) != MAP_ELEMENT_TYPE_PATH)
continue;
int pathZ = mapElement->base_height * 8;
if (pathZ < z || pathZ >= z + 32)
continue;
if (map_element_is_underground(mapElement))
return false;
return true;
} while (!map_element_is_last_for_tile(mapElement++));
return false;
}
/**
*
* rct2: 0x0067375D
*/
void litter_create(int x, int y, int z, int direction, int 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;
rct_litter *litter, *newestLitter;
uint16 spriteIndex, nextSpriteIndex;
uint32 newestLitterCreationTick;
2016-05-09 01:08:03 +02:00
if (gSpriteListCount[SPRITE_LIST_LITTER] >= 500) {
newestLitter = NULL;
newestLitterCreationTick = 0;
2016-05-09 01:08:03 +02:00
for (spriteIndex = gSpriteListHead[SPRITE_LIST_LITTER]; spriteIndex != SPRITE_INDEX_NULL; spriteIndex = nextSpriteIndex) {
litter = &get_sprite(spriteIndex)->litter;
nextSpriteIndex = litter->next;
if (newestLitterCreationTick <= litter->creationTick) {
newestLitterCreationTick = litter->creationTick;
newestLitter = litter;
}
}
if (newestLitter != NULL) {
2015-08-22 12:56:32 +02:00
invalidate_sprite_0((rct_sprite*)newestLitter);
sprite_remove((rct_sprite*)newestLitter);
}
}
litter = (rct_litter*)create_sprite(1);
if (litter == NULL)
return;
2016-05-09 01:08:03 +02:00
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);
2015-08-22 12:56:32 +02:00
invalidate_sprite_0((rct_sprite*)litter);
2016-05-09 00:20:42 +02:00
litter->creationTick = gScenarioTicks;
2015-06-29 18:22:01 +02:00
}
2015-07-02 19:22:38 +02:00
/**
*
* rct2: 0x006738E1
*/
2015-09-05 14:59:12 +02:00
void litter_remove_at(int x, int y, int z)
2015-07-02 19:22:38 +02:00
{
2015-09-05 14:59:12 +02:00
uint16 spriteIndex = sprite_get_first_in_quadrant(x, y);
while (spriteIndex != SPRITE_INDEX_NULL) {
rct_sprite *sprite = get_sprite(spriteIndex);
2015-09-05 14:59:12 +02:00
uint16 nextSpriteIndex = sprite->unknown.next_in_quadrant;
2016-05-09 01:08:03 +02:00
if (sprite->unknown.linked_list_type_offset == SPRITE_LIST_LITTER * 2) {
2015-09-05 14:59:12 +02:00
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;
}
2015-07-02 19:22:38 +02:00
}