OpenRCT2/src/openrct2/entity/Duck.cpp

388 lines
9.2 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2024 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include "Duck.h"
#include "../Game.h"
#include "../GameState.h"
#include "../audio/audio.h"
#include "../core/DataSerialiser.h"
#include "../localisation/Date.h"
#include "../paint/Paint.h"
#include "../profiling/Profiling.h"
#include "../scenario/Scenario.h"
#include "../sprites.h"
#include "../world/Surface.h"
#include "EntityRegistry.h"
#include <iterator>
#include <limits>
using namespace OpenRCT2;
constexpr int32_t DUCK_MAX_STATES = 5;
// clang-format off
static constexpr CoordsXY DuckMoveOffset[] =
{
{ -1, 0 },
{ 0, 1 },
{ 1, 0 },
{ 0, -1 },
};
static constexpr uint8_t DuckAnimationFlyToWater[] =
{
8, 9, 10, 11, 12, 13
};
static constexpr uint8_t DuckAnimationSwim[] =
{
0
};
static constexpr uint8_t 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 constexpr uint8_t 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 constexpr uint8_t DuckAnimationFlyAway[] =
{
8, 9, 10, 11, 12, 13
};
static constexpr const uint8_t * DuckAnimations[] =
{
DuckAnimationFlyToWater, // FLY_TO_WATER
DuckAnimationSwim, // SWIM
DuckAnimationDrink, // DRINK
DuckAnimationDoubleDrink, // DOUBLE_DRINK
DuckAnimationFlyAway, // FLY_AWAY
};
// clang-format on
template<> bool EntityBase::Is<Duck>() const
{
return Type == EntityType::Duck;
}
bool Duck::IsFlying()
{
return this->state == DuckState::FlyAway || this->state == DuckState::FlyToWater;
}
void Duck::Remove()
{
Invalidate();
EntityRemove(this);
}
void Duck::UpdateFlyToWater()
{
const auto currentTicks = GetGameState().CurrentTicks;
if ((currentTicks & 3) != 0)
return;
frame++;
if (frame >= std::size(DuckAnimationFlyToWater))
{
frame = 0;
}
Invalidate();
int32_t manhattanDistance = abs(target_x - x) + abs(target_y - y);
int32_t direction = Orientation >> 3;
auto destination = CoordsXYZ{ CoordsXY{ x, y } + DuckMoveOffset[direction], 0 };
int32_t manhattanDistanceN = abs(target_x - destination.x) + abs(target_y - destination.y);
auto surfaceElement = MapGetSurfaceElementAt(CoordsXY{ target_x, target_y });
int32_t waterHeight = surfaceElement != nullptr ? surfaceElement->GetWaterHeight() : 0;
if (waterHeight == 0)
{
state = DuckState::FlyAway;
UpdateFlyAway();
}
else
{
destination.z = abs(z - waterHeight);
if (manhattanDistanceN <= manhattanDistance)
{
if (destination.z > manhattanDistanceN)
{
destination.z = z - 2;
if (waterHeight >= z)
{
destination.z += 4;
}
frame = 1;
}
else
{
destination.z = z;
}
MoveTo(destination);
}
else
{
if (destination.z > 4)
{
state = DuckState::FlyAway;
UpdateFlyAway();
}
else
{
state = DuckState::Swim;
frame = 0;
UpdateSwim();
}
}
}
}
void Duck::UpdateSwim()
{
const auto currentTicks = GetGameState().CurrentTicks;
if (((currentTicks + Id.ToUnderlying()) & 3) != 0)
return;
uint32_t randomNumber = ScenarioRand();
if ((randomNumber & 0xFFFF) < 0x666)
{
if (randomNumber & 0x80000000)
{
state = DuckState::DoubleDrink;
frame = std::numeric_limits<uint16_t>::max();
UpdateDoubleDrink();
}
else
{
state = DuckState::Drink;
frame = std::numeric_limits<uint16_t>::max();
UpdateDrink();
}
}
else
{
int32_t currentMonth = GetDate().GetMonth();
if (currentMonth >= MONTH_SEPTEMBER && (randomNumber >> 16) < 218)
{
state = DuckState::FlyAway;
UpdateFlyAway();
}
else
{
Invalidate();
int16_t landZ = TileElementHeight({ x, y });
int16_t waterZ = TileElementWaterHeight({ x, y });
if (z < landZ || waterZ == 0)
{
state = DuckState::FlyAway;
UpdateFlyAway();
}
else
{
z = waterZ;
randomNumber = ScenarioRand();
if ((randomNumber & 0xFFFF) <= 0xAAA)
{
randomNumber >>= 16;
Orientation = randomNumber & 0x18;
}
int32_t direction = Orientation >> 3;
auto destination = CoordsXYZ{ CoordsXY{ x, y } + DuckMoveOffset[direction], 0 };
landZ = TileElementHeight(destination);
waterZ = TileElementWaterHeight(destination);
if (z > landZ && z == waterZ)
{
destination.z = waterZ;
MoveTo(destination);
Invalidate();
}
}
}
}
}
void Duck::UpdateDrink()
{
frame++;
if (DuckAnimationDrink[frame] == 0xFF)
{
state = DuckState::Swim;
frame = 0;
UpdateSwim();
}
else
{
Invalidate();
}
}
void Duck::UpdateDoubleDrink()
{
frame++;
if (DuckAnimationDoubleDrink[frame] == 0xFF)
{
state = DuckState::Swim;
frame = 0;
UpdateSwim();
}
else
{
Invalidate();
}
}
void Duck::UpdateFlyAway()
{
if ((GetGameState().CurrentTicks & 3) == 0)
{
frame++;
if (frame >= std::size(DuckAnimationFlyAway))
{
frame = 0;
}
Invalidate();
int32_t direction = Orientation >> 3;
auto destination = CoordsXYZ{ x + (DuckMoveOffset[direction].x * 2), y + (DuckMoveOffset[direction].y * 2),
std::min<int32_t>(z + 2, 496) };
if (MapIsLocationValid(destination))
{
MoveTo(destination);
}
else
{
Remove();
}
}
}
uint32_t Duck::GetFrameImage(int32_t direction) const
{
uint32_t imageId = 0;
if (EnumValue(state) < DUCK_MAX_STATES)
{
// TODO: Check frame is in range
uint8_t imageOffset = DuckAnimations[EnumValue(state)][frame];
imageId = SPR_DUCK + (imageOffset * 4) + (direction / 8);
}
return imageId;
}
void Duck::Create(const CoordsXY& pos)
{
auto* duck = CreateEntity<Duck>();
if (duck == nullptr)
return;
CoordsXY targetPos = pos;
int32_t offsetXY = ScenarioRand() & 0x1E;
targetPos.x += offsetXY;
targetPos.y += offsetXY;
duck->SpriteData.Width = 9;
duck->SpriteData.HeightMin = 12;
duck->SpriteData.HeightMax = 9;
duck->target_x = targetPos.x;
duck->target_y = targetPos.y;
uint8_t direction = ScenarioRand() & 3;
switch (direction)
{
case 0:
targetPos.x = GetMapSizeMaxXY().x - (ScenarioRand() & 0x3F);
break;
case 1:
targetPos.y = ScenarioRand() & 0x3F;
break;
case 2:
targetPos.x = ScenarioRand() & 0x3F;
break;
case 3:
targetPos.y = GetMapSizeMaxXY().y - (ScenarioRand() & 0x3F);
break;
}
duck->Orientation = direction << 3;
duck->MoveTo({ targetPos.x, targetPos.y, 496 });
duck->state = Duck::DuckState::FlyToWater;
duck->frame = 0;
}
void Duck::Update()
{
switch (state)
{
case DuckState::FlyToWater:
UpdateFlyToWater();
break;
case DuckState::Swim:
UpdateSwim();
break;
case DuckState::Drink:
UpdateDrink();
break;
case DuckState::DoubleDrink:
UpdateDoubleDrink();
break;
case DuckState::FlyAway:
UpdateFlyAway();
break;
}
}
void Duck::Press()
{
OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::Quack, { x, y, z });
}
void Duck::RemoveAll()
{
for (auto duck : EntityList<Duck>())
{
duck->Remove();
}
}
void Duck::Serialise(DataSerialiser& stream)
{
EntityBase::Serialise(stream);
stream << frame;
stream << target_x;
stream << target_y;
stream << state;
}
void Duck::Paint(PaintSession& session, int32_t imageDirection) const
{
PROFILED_FUNCTION();
DrawPixelInfo& dpi = session.DPI;
if (dpi.zoom_level > ZoomLevel{ 1 })
return;
uint32_t imageId = GetFrameImage(imageDirection);
if (imageId != 0)
{
PaintAddImageAsParent(session, ImageId(imageId), { 0, 0, z }, { 1, 1, 0 });
}
}