1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-18 04:23:20 +01:00
Files
OpenRCT2/src/openrct2/world/MapAnimation.cpp
2025-06-13 21:13:32 +01:00

538 lines
16 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2025 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 "MapAnimation.h"
#include "../Context.h"
#include "../Diagnostic.h"
#include "../Game.h"
#include "../GameState.h"
#include "../entity/EntityList.h"
#include "../entity/Peep.h"
#include "../interface/Viewport.h"
#include "../object/LargeSceneryEntry.h"
#include "../object/SmallSceneryEntry.h"
#include "../object/StationObject.h"
#include "../object/WallSceneryEntry.h"
#include "../profiling/Profiling.h"
#include "../ride/Ride.h"
#include "../ride/RideData.h"
#include "../ride/Track.h"
#include "Banner.h"
#include "Footpath.h"
#include "Scenery.h"
#include "tile_element/EntranceElement.h"
#include "tile_element/LargeSceneryElement.h"
#include "tile_element/PathElement.h"
#include "tile_element/SmallSceneryElement.h"
#include "tile_element/TileElement.h"
#include "tile_element/TrackElement.h"
#include "tile_element/WallElement.h"
#include <algorithm>
#include <set>
using namespace OpenRCT2;
struct TemporaryMapAnimation
{
CoordsXYZ location{};
MapAnimation::TemporaryType type{};
bool operator<(const TemporaryMapAnimation& rhs) const
{
return std::tie(location.y, location.x, type, location.z)
< std::tie(rhs.location.y, rhs.location.x, rhs.type, rhs.location.z);
}
};
struct TileCoordsXYCmp
{
constexpr bool operator()(const TileCoordsXY& lhs, const TileCoordsXY& rhs) const noexcept
{
// NOTE: Ordering should match GetFirstElementAt which is x + (y * size)
return std::tie(lhs.y, lhs.x) < std::tie(rhs.y, rhs.x);
}
};
// A list of tile coordinates which contains animated tile elements.
static std::set<TileCoordsXY, TileCoordsXYCmp> _mapAnimations;
static std::set<TemporaryMapAnimation> _temporaryMapAnimations;
static bool UpdateEntranceAnimation(const EntranceElement& entrance, const CoordsXYZ& loc, const int32_t baseZ)
{
if (entrance.GetEntranceType() == ENTRANCE_TYPE_RIDE_ENTRANCE)
{
const auto ride = GetRide(entrance.GetRideIndex());
if (ride != nullptr)
{
const auto stationObj = ride->getStationObject();
if (stationObj != nullptr)
{
MapInvalidateTileZoom1({ loc, baseZ + stationObj->Height + 8, baseZ + stationObj->Height + 24 });
return true;
}
}
}
else if (entrance.GetEntranceType() == ENTRANCE_TYPE_PARK_ENTRANCE && entrance.GetSequenceIndex() == 0)
{
const int32_t direction = (entrance.GetDirection() + GetCurrentRotation()) & 3;
if (direction == TILE_ELEMENT_DIRECTION_SOUTH || direction == TILE_ELEMENT_DIRECTION_WEST)
{
MapInvalidateTileZoom1({ loc, baseZ + 32, baseZ + 64 });
}
return true;
}
return false;
}
static bool UpdatePathAnimation(const PathElement& path, const CoordsXYZ& loc, const int32_t baseZ)
{
if (path.IsQueue() && path.HasQueueBanner())
{
const int32_t direction = (path.GetQueueBannerDirection() + GetCurrentRotation()) & 3;
if (direction == TILE_ELEMENT_DIRECTION_NORTH || direction == TILE_ELEMENT_DIRECTION_EAST)
{
MapInvalidateTileZoom1({ loc, baseZ + 16, baseZ + 30 });
}
return true;
}
return false;
}
static bool UpdateSmallSceneryAnimation(const SmallSceneryElement& scenery, const CoordsXYZ& loc, const int32_t baseZ)
{
const auto entry = scenery.GetEntry();
if (entry == nullptr)
{
return false;
}
if (entry->HasFlag(
SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_1 | SMALL_SCENERY_FLAG_FOUNTAIN_SPRAY_4 | SMALL_SCENERY_FLAG_SWAMP_GOO
| SMALL_SCENERY_FLAG_HAS_FRAME_OFFSETS | SMALL_SCENERY_FLAG_IS_CLOCK))
{
// Don't apply anything to peeps when the scenery is a ghost.
if (!scenery.IsGhost())
{
if (entry->HasFlag(SMALL_SCENERY_FLAG_IS_CLOCK) && !(getGameState().currentTicks & 0x3FF))
{
const int32_t direction = scenery.GetDirection();
auto quad = EntityTileList<Peep>(CoordsXY{ loc.x, loc.y } - CoordsDirectionDelta[direction]);
for (auto peep : quad)
{
if (peep->State != PeepState::Walking || peep->z != baseZ || peep->Action < PeepActionType::Idle)
continue;
peep->Action = PeepActionType::CheckTime;
peep->AnimationFrameNum = 0;
peep->AnimationImageIdOffset = 0;
peep->UpdateCurrentAnimationType();
peep->Invalidate();
break;
}
}
}
const auto clearZ = scenery.GetClearanceZ();
MapInvalidateTileZoom1({ loc, baseZ, clearZ });
return true;
}
return false;
}
static bool UpdateTrackAnimation(TrackElement& track, const CoordsXYZ& loc, const int32_t baseZ)
{
switch (track.GetTrackType())
{
case TrackElemType::Waterfall:
MapInvalidateTileZoom1({ loc, baseZ + 14, baseZ + 46 });
return true;
case TrackElemType::Rapids:
case TrackElemType::Whirlpool:
MapInvalidateTileZoom1({ loc, baseZ + 14, baseZ + 18 });
return true;
case TrackElemType::SpinningTunnel:
MapInvalidateTileZoom1({ loc, baseZ + 14, baseZ + 32 });
return true;
default:
break;
}
return false;
}
static bool UpdateLargeSceneryAnimation(const LargeSceneryElement& scenery, const CoordsXYZ& loc, const int32_t baseZ)
{
const auto entry = scenery.GetEntry();
if (entry != nullptr && (entry->flags & LARGE_SCENERY_FLAG_ANIMATED))
{
MapInvalidateTileZoom1({ loc, baseZ, baseZ + 16 });
return true;
}
return false;
}
static bool UpdateWallAnimation(WallElement& wall, const CoordsXYZ& loc, const int32_t baseZ)
{
const auto entry = wall.GetEntry();
if (entry == nullptr)
{
return false;
}
if (entry->flags & WALL_SCENERY_IS_DOOR && wall.IsAnimating())
{
if (getGameState().currentTicks & 1)
{
return true;
}
bool removeAnim = true;
const auto currentFrame = wall.GetAnimationFrame();
if (currentFrame != 0)
{
auto newFrame = currentFrame;
if (currentFrame == 15)
{
newFrame = 0;
wall.SetIsAnimating(false);
}
else
{
removeAnim = false;
if (currentFrame != 5)
{
newFrame++;
if (newFrame == 13 && !(entry->flags & WALL_SCENERY_LONG_DOOR_ANIMATION))
newFrame = 15;
}
}
if (currentFrame != newFrame)
{
wall.SetAnimationFrame(newFrame);
MapInvalidateTileZoom1({ loc, baseZ, baseZ + 32 });
}
}
return !removeAnim;
}
else if ((entry->flags2 & WALL_SCENERY_2_ANIMATED) || entry->scrolling_mode != kScrollingModeNone)
{
MapInvalidateTileZoom1({ loc, baseZ, baseZ + 16 });
return true;
}
return false;
}
static bool UpdateBannerAnimation([[maybe_unused]] const BannerElement& banner, const CoordsXYZ& loc, const int32_t baseZ)
{
MapInvalidateTileZoom1({ loc, baseZ, baseZ + 16 });
return true;
}
static bool UpdateTileAnimations(const TileCoordsXY& coords)
{
auto tileElement = MapGetFirstElementAt(coords);
if (tileElement == nullptr)
{
return false;
}
bool hasAnimations = false;
do
{
const auto baseZ = tileElement->GetBaseZ();
const CoordsXYZ loc{ coords.ToCoordsXY(), baseZ };
switch (tileElement->GetType())
{
case TileElementType::Entrance:
hasAnimations |= UpdateEntranceAnimation(*tileElement->AsEntrance(), loc, baseZ);
break;
case TileElementType::Path:
hasAnimations |= UpdatePathAnimation(*tileElement->AsPath(), loc, baseZ);
break;
case TileElementType::SmallScenery:
hasAnimations |= UpdateSmallSceneryAnimation(*tileElement->AsSmallScenery(), loc, baseZ);
break;
case TileElementType::Track:
hasAnimations |= UpdateTrackAnimation(*tileElement->AsTrack(), loc, baseZ);
break;
case TileElementType::Banner:
hasAnimations |= UpdateBannerAnimation(*tileElement->AsBanner(), loc, baseZ);
break;
case TileElementType::LargeScenery:
hasAnimations |= UpdateLargeSceneryAnimation(*tileElement->AsLargeScenery(), loc, baseZ);
break;
case TileElementType::Wall:
hasAnimations |= UpdateWallAnimation(*tileElement->AsWall(), loc, baseZ);
break;
default:
break;
}
} while (!(tileElement++)->IsLastForTile());
return hasAnimations;
}
static bool UpdateOnRidePhotoAnimation(TrackElement& track, const CoordsXYZ& coords)
{
if (track.IsTakingPhoto())
{
track.DecrementPhotoTimeout();
MapInvalidateTileZoom1({ coords, track.GetClearanceZ() });
return true;
}
return false;
}
static bool UpdateLandEdgeDoorsAnimation(TrackElement& track, const CoordsXYZ& coords)
{
if (getGameState().currentTicks & 3)
{
return true;
}
bool isAnimating = false;
const auto doorAState = track.GetDoorAState();
if (doorAState >= kLandEdgeDoorFrameEnd)
{
track.SetDoorAState(kLandEdgeDoorFrameClosed);
MapInvalidateTileZoom1({ coords, coords.z + 32 });
}
else if (doorAState != kLandEdgeDoorFrameClosed && doorAState != kLandEdgeDoorFrameOpen)
{
track.SetDoorAState(doorAState + 1);
MapInvalidateTileZoom1({ coords, coords.z + 32 });
isAnimating = true;
}
const auto doorBState = track.GetDoorBState();
if (doorBState >= kLandEdgeDoorFrameEnd)
{
track.SetDoorBState(kLandEdgeDoorFrameClosed);
MapInvalidateTileZoom1({ coords, coords.z + 32 });
}
else if (doorBState != kLandEdgeDoorFrameClosed && doorBState != kLandEdgeDoorFrameOpen)
{
track.SetDoorBState(doorBState + 1);
MapInvalidateTileZoom1({ coords, coords.z + 32 });
isAnimating = true;
}
return isAnimating;
}
static bool UpdateTemporaryAnimation(const TemporaryMapAnimation& animation)
{
const TileCoordsXYZ tileCoords{ animation.location };
TileElement* tileElement = MapGetFirstElementAt(tileCoords);
if (tileElement == nullptr)
{
return true;
}
bool isAnimating = false;
do
{
switch (animation.type)
{
case MapAnimation::TemporaryType::onRidePhoto:
{
if (tileElement->GetType() == TileElementType::Track && tileElement->BaseHeight == tileCoords.z
&& tileElement->AsTrack()->GetTrackType() == TrackElemType::OnRidePhoto)
{
isAnimating |= UpdateOnRidePhotoAnimation(*tileElement->AsTrack(), animation.location);
}
break;
}
case MapAnimation::TemporaryType::landEdgeDoor:
{
if (tileElement->GetType() == TileElementType::Track && tileElement->BaseHeight == tileCoords.z)
{
isAnimating |= UpdateLandEdgeDoorsAnimation(*tileElement->AsTrack(), animation.location);
}
}
}
} while (!(tileElement++)->IsLastForTile());
return isAnimating;
}
static bool IsElementAnimated(const TileElementBase& element)
{
switch (element.GetType())
{
case TileElementType::Banner:
return true;
case TileElementType::Wall:
{
const auto wall = element.AsWall();
const auto entry = wall->GetEntry();
if (entry != nullptr)
{
if ((entry->flags2 & WALL_SCENERY_2_ANIMATED) || entry->scrolling_mode != kScrollingModeNone)
{
return true;
}
if (entry->flags & WALL_SCENERY_IS_DOOR && wall->IsAnimating())
{
return true;
}
}
break;
}
case TileElementType::SmallScenery:
{
const auto scenery = element.AsSmallScenery();
const auto entry = scenery->GetEntry();
if (entry != nullptr && entry->HasFlag(SMALL_SCENERY_FLAG_ANIMATED))
{
return true;
}
break;
}
case TileElementType::LargeScenery:
{
const auto scenery = element.AsLargeScenery();
const auto entry = scenery->GetEntry();
if (entry != nullptr && (entry->flags & LARGE_SCENERY_FLAG_ANIMATED))
{
return true;
}
break;
}
case TileElementType::Path:
{
const auto path = element.AsPath();
if (path->HasQueueBanner())
{
return true;
}
break;
}
case TileElementType::Entrance:
{
const auto entrance = element.AsEntrance();
if (entrance->GetEntranceType() == ENTRANCE_TYPE_PARK_ENTRANCE && entrance->GetSequenceIndex() == 0)
{
return true;
}
else if (entrance->GetEntranceType() == ENTRANCE_TYPE_RIDE_ENTRANCE)
{
return true;
}
break;
}
case TileElementType::Track:
{
const auto track = element.AsTrack();
switch (track->GetTrackType())
{
case TrackElemType::Waterfall:
case TrackElemType::Rapids:
case TrackElemType::Whirlpool:
case TrackElemType::SpinningTunnel:
return true;
default:
break;
}
break;
}
default:
break;
}
return false;
}
void MapAnimation::Create(const CoordsXY coords)
{
_mapAnimations.insert(TileCoordsXY(coords));
}
void MapAnimation::CreateTemporary(const CoordsXYZ& coords, const TemporaryType type)
{
_temporaryMapAnimations.insert(TemporaryMapAnimation{ coords, type });
}
void MapAnimation::CreateAll()
{
ClearAll();
TileElementIterator it;
TileElementIteratorBegin(&it);
while (TileElementIteratorNext(&it))
{
if (IsElementAnimated(*it.element))
{
_mapAnimations.insert(TileCoordsXY(it.x, it.y));
}
}
}
template<auto TUpdateFunction>
static void UpdateAll(auto& mapAnimations)
{
auto it = mapAnimations.begin();
while (it != mapAnimations.end())
{
if (TUpdateFunction(*it))
{
++it;
}
else
{
it = mapAnimations.erase(it);
}
}
}
void MapAnimation::UpdateAll()
{
PROFILED_FUNCTION();
UpdateAll<UpdateTileAnimations>(_mapAnimations);
UpdateAll<UpdateTemporaryAnimation>(_temporaryMapAnimations);
}
void MapAnimation::ClearAll()
{
_mapAnimations.clear();
_temporaryMapAnimations.clear();
}
void MapAnimation::ShiftAll(const CoordsXY amount)
{
if (amount.x == 0 && amount.y == 0)
return;
std::set<TileCoordsXY, TileCoordsXYCmp> newMapAnimations;
for (const auto& a : _mapAnimations)
{
newMapAnimations.insert(a + TileCoordsXY(amount));
}
_mapAnimations = std::move(newMapAnimations);
std::set<TemporaryMapAnimation> newTemporaryMapAnimations;
for (const auto& a : _temporaryMapAnimations)
{
newTemporaryMapAnimations.insert(TemporaryMapAnimation{ a.location + CoordsXYZ(amount, 0), a.type });
}
_temporaryMapAnimations = std::move(newTemporaryMapAnimations);
}