mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-21 05:53:02 +01:00
399 lines
9.9 KiB
C++
399 lines
9.9 KiB
C++
#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 "../core/Math.hpp"
|
|
#include "../core/Util.hpp"
|
|
#include "../sprites.h"
|
|
|
|
extern "C"
|
|
{
|
|
#include "../audio/audio.h"
|
|
#include "../game.h"
|
|
#include "../localisation/date.h"
|
|
#include "../scenario/scenario.h"
|
|
#include "sprite.h"
|
|
}
|
|
|
|
enum DUCK_STATE
|
|
{
|
|
FLY_TO_WATER,
|
|
SWIM,
|
|
DRINK,
|
|
DOUBLE_DRINK,
|
|
FLY_AWAY,
|
|
};
|
|
constexpr sint32 DUCK_MAX_STATES = 5;
|
|
|
|
static const rct_xy16 DuckMoveOffset[] =
|
|
{
|
|
{ -1, 0 },
|
|
{ 0, 1 },
|
|
{ 1, 0 },
|
|
{ 0, -1 },
|
|
};
|
|
|
|
static const uint8 DuckAnimationFlyToWater[] =
|
|
{
|
|
8, 9, 10, 11, 12, 13
|
|
};
|
|
|
|
static const uint8 DuckAnimationSwim[] =
|
|
{
|
|
0
|
|
};
|
|
|
|
static const uint8 DuckAnimationDrink[] =
|
|
{
|
|
1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0xFF
|
|
};
|
|
|
|
static const uint8 DuckAnimationDoubleDrink[] =
|
|
{
|
|
4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6,
|
|
6, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 0, 0, 0, 0, 0xFF
|
|
};
|
|
|
|
static const uint8 DuckAnimationFlyAway[] =
|
|
{
|
|
8, 9, 10, 11, 12, 13
|
|
};
|
|
|
|
static const uint8 * DuckAnimations[] =
|
|
{
|
|
DuckAnimationFlyToWater, // FLY_TO_WATER
|
|
DuckAnimationSwim, // SWIM
|
|
DuckAnimationDrink, // DRINK
|
|
DuckAnimationDoubleDrink, // DOUBLE_DRINK
|
|
DuckAnimationFlyAway, // FLY_AWAY
|
|
};
|
|
|
|
bool rct_sprite::IsDuck()
|
|
{
|
|
return this->duck.sprite_identifier == SPRITE_IDENTIFIER_MISC &&
|
|
this->duck.misc_identifier == SPRITE_MISC_DUCK;
|
|
}
|
|
|
|
rct_duck * rct_sprite::AsDuck()
|
|
{
|
|
rct_duck * result = nullptr;
|
|
if (IsDuck())
|
|
{
|
|
return (rct_duck *)result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void rct_duck::Invalidate()
|
|
{
|
|
invalidate_sprite_0((rct_sprite *)this);
|
|
}
|
|
|
|
void rct_duck::Remove()
|
|
{
|
|
sprite_remove((rct_sprite *)this);
|
|
}
|
|
|
|
void rct_duck::MoveTo(sint16 destX, sint16 destY, sint16 destZ)
|
|
{
|
|
sprite_move(destX, destY, destZ, (rct_sprite *)this);
|
|
}
|
|
|
|
void rct_duck::UpdateFlyToWater()
|
|
{
|
|
if ((gCurrentTicks & 3) != 0) return;
|
|
|
|
frame++;
|
|
if (frame >= Util::CountOf(DuckAnimationFlyToWater))
|
|
{
|
|
frame = 0;
|
|
}
|
|
|
|
Invalidate();
|
|
sint32 manhattanDistance = abs(target_x - x) + abs(target_y - y);
|
|
sint32 direction = sprite_direction >> 3;
|
|
sint32 newX = x + DuckMoveOffset[direction].x;
|
|
sint32 newY = y + DuckMoveOffset[direction].y;
|
|
sint32 manhattanDistanceN = abs(target_x - newX) + abs(target_y - newY);
|
|
|
|
rct_map_element * mapElement = map_get_surface_element_at(target_x >> 5, target_y >> 5);
|
|
sint32 waterHeight = map_get_water_height(mapElement);
|
|
if (waterHeight == 0)
|
|
{
|
|
state = DUCK_STATE::FLY_AWAY;
|
|
UpdateFlyAway();
|
|
}
|
|
else
|
|
{
|
|
waterHeight <<= 4;
|
|
sint32 newZ = abs(z - waterHeight);
|
|
|
|
if (manhattanDistanceN <= manhattanDistance)
|
|
{
|
|
if (newZ > manhattanDistanceN)
|
|
{
|
|
newZ = z - 2;
|
|
if (waterHeight >= z)
|
|
{
|
|
newZ += 4;
|
|
}
|
|
frame = 1;
|
|
}
|
|
else
|
|
{
|
|
newZ = z;
|
|
}
|
|
MoveTo(newX, newY, newZ);
|
|
Invalidate();
|
|
}
|
|
else
|
|
{
|
|
if (newZ > 4)
|
|
{
|
|
state = DUCK_STATE::FLY_AWAY;
|
|
UpdateFlyAway();
|
|
}
|
|
else
|
|
{
|
|
state = DUCK_STATE::SWIM;
|
|
frame = 0;
|
|
UpdateSwim();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void rct_duck::UpdateSwim()
|
|
{
|
|
if (((gCurrentTicks + sprite_index) & 3) != 0) return;
|
|
|
|
uint32 randomNumber = scenario_rand();
|
|
if ((randomNumber & 0xFFFF) < 0x666)
|
|
{
|
|
if (randomNumber & 0x80000000)
|
|
{
|
|
state = DUCK_STATE::DOUBLE_DRINK;
|
|
frame = -1;
|
|
UpdateDoubleDrink();
|
|
}
|
|
else
|
|
{
|
|
state = DUCK_STATE::DRINK;
|
|
frame = -1;
|
|
UpdateDrink();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sint32 currentMonth = date_get_month(gDateMonthsElapsed);
|
|
if (currentMonth >= MONTH_SEPTEMBER && (randomNumber >> 16) < 218)
|
|
{
|
|
state = DUCK_STATE::FLY_AWAY;
|
|
UpdateFlyAway();
|
|
}
|
|
else
|
|
{
|
|
Invalidate();
|
|
sint32 landZ = map_element_height(x, y);
|
|
sint32 waterZ = (landZ >> 16) & 0xFFFF;
|
|
landZ &= 0xFFFF;
|
|
|
|
if (z < landZ || waterZ == 0)
|
|
{
|
|
state = DUCK_STATE::FLY_AWAY;
|
|
UpdateFlyAway();
|
|
}
|
|
else
|
|
{
|
|
z = waterZ;
|
|
randomNumber = scenario_rand();
|
|
if ((randomNumber & 0xFFFF) <= 0xAAA)
|
|
{
|
|
randomNumber >>= 16;
|
|
sprite_direction = randomNumber & 0x18;
|
|
}
|
|
|
|
sint32 direction = sprite_direction >> 3;
|
|
sint32 newX = x + DuckMoveOffset[direction].x;
|
|
sint32 newY = y + DuckMoveOffset[direction].y;
|
|
landZ = map_element_height(newX, newY);
|
|
waterZ = (landZ >> 16) & 0xFFFF;
|
|
landZ &= 0xFFFF;
|
|
|
|
if (z >= landZ && z == waterZ)
|
|
{
|
|
MoveTo(newX, newY, waterZ);
|
|
Invalidate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void rct_duck::UpdateDrink()
|
|
{
|
|
frame++;
|
|
if (DuckAnimationDrink[frame] == 0xFF)
|
|
{
|
|
state = DUCK_STATE::SWIM;
|
|
frame = 0;
|
|
UpdateSwim();
|
|
}
|
|
else
|
|
{
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
void rct_duck::UpdateDoubleDrink()
|
|
{
|
|
frame++;
|
|
if (DuckAnimationDoubleDrink[frame] == 0xFF)
|
|
{
|
|
state = DUCK_STATE::SWIM;
|
|
frame = 0;
|
|
UpdateSwim();
|
|
}
|
|
else
|
|
{
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
void rct_duck::UpdateFlyAway()
|
|
{
|
|
if ((gCurrentTicks & 3) == 0)
|
|
{
|
|
frame++;
|
|
if (frame >= Util::CountOf(DuckAnimationFlyAway))
|
|
{
|
|
frame = 0;
|
|
}
|
|
|
|
Invalidate();
|
|
|
|
sint32 direction = sprite_direction >> 3;
|
|
sint32 newX = x + (DuckMoveOffset[direction].x * 2);
|
|
sint32 newY = y + (DuckMoveOffset[direction].y * 2);
|
|
sint32 newZ = Math::Min(z + 2, 496);
|
|
if (map_is_location_valid(newX, newY))
|
|
{
|
|
MoveTo(newX, newY, newZ);
|
|
Invalidate();
|
|
}
|
|
else
|
|
{
|
|
Remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 rct_duck::GetFrameImage(sint32 direction) const
|
|
{
|
|
uint32 imageId = 0;
|
|
if (state < DUCK_MAX_STATES)
|
|
{
|
|
// TODO: Check frame is in range
|
|
uint8 imageOffset = DuckAnimations[state][frame];
|
|
imageId = SPR_DUCK + (imageOffset * 4) + (direction / 8);
|
|
}
|
|
return imageId;
|
|
}
|
|
|
|
extern "C"
|
|
{
|
|
void create_duck(sint32 targetX, sint32 targetY)
|
|
{
|
|
rct_sprite * sprite = create_sprite(2);
|
|
if (sprite != nullptr)
|
|
{
|
|
sprite->duck.sprite_identifier = SPRITE_IDENTIFIER_MISC;
|
|
sprite->duck.misc_identifier = SPRITE_MISC_DUCK;
|
|
sprite->duck.var_14 = 9;
|
|
sprite->duck.var_09 = 0xC;
|
|
sprite->duck.var_15 = 9;
|
|
sint32 offsetXY = scenario_rand() & 0x1E;
|
|
targetX += offsetXY;
|
|
targetY += offsetXY;
|
|
sprite->duck.target_x = targetX;
|
|
sprite->duck.target_y = targetY;
|
|
uint8 direction = scenario_rand() & 3;
|
|
switch (direction) {
|
|
case 0:
|
|
targetX = 8191 - (scenario_rand() & 0x3F);
|
|
break;
|
|
case 1:
|
|
targetY = scenario_rand() & 0x3F;
|
|
break;
|
|
case 2:
|
|
targetX = scenario_rand() & 0x3F;
|
|
break;
|
|
case 3:
|
|
targetY = 8191 - (scenario_rand() & 0x3F);
|
|
break;
|
|
}
|
|
sprite->duck.sprite_direction = direction << 3;
|
|
sprite_move(targetX, targetY, 496, sprite);
|
|
sprite->duck.state = DUCK_STATE::FLY_TO_WATER;
|
|
sprite->duck.frame = 0;
|
|
}
|
|
}
|
|
|
|
void duck_update(rct_duck * duck)
|
|
{
|
|
switch ((DUCK_STATE)duck->state) {
|
|
case DUCK_STATE::FLY_TO_WATER:
|
|
duck->UpdateFlyToWater();
|
|
break;
|
|
case DUCK_STATE::SWIM:
|
|
duck->UpdateSwim();
|
|
break;
|
|
case DUCK_STATE::DRINK:
|
|
duck->UpdateDrink();
|
|
break;
|
|
case DUCK_STATE::DOUBLE_DRINK:
|
|
duck->UpdateDoubleDrink();
|
|
break;
|
|
case DUCK_STATE::FLY_AWAY:
|
|
duck->UpdateFlyAway();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void duck_press(rct_duck * duck)
|
|
{
|
|
audio_play_sound_at_location(SOUND_QUACK, duck->x, duck->y, duck->z);
|
|
}
|
|
|
|
void duck_remove_all()
|
|
{
|
|
uint16 nextSpriteIndex;
|
|
for (uint16 spriteIndex = gSpriteListHead[SPRITE_LIST_MISC]; spriteIndex != SPRITE_INDEX_NULL; spriteIndex = nextSpriteIndex)
|
|
{
|
|
rct_unk_sprite * sprite = &(get_sprite(spriteIndex)->unknown);
|
|
nextSpriteIndex = sprite->next;
|
|
if (sprite->misc_identifier == SPRITE_MISC_DUCK)
|
|
{
|
|
sprite_remove((rct_sprite *)sprite);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 duck_get_frame_image(const rct_duck * duck, sint32 direction)
|
|
{
|
|
return duck->GetFrameImage(direction);
|
|
}
|
|
}
|