diff --git a/distribution/changelog.txt b/distribution/changelog.txt index d2f454e624..7dda3462c3 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -1,5 +1,6 @@ 0.4.4 (in development) ------------------------------------------------------------------------ +- Feature: [#11269] Add properties for speed and length of vehicle animations. - Feature: [#15849] Objectives can now be set for up to 50000 guests. - Feature: [#18537] Add shift/control modifiers to window close buttons, closing all but the given window or all windows of the same type, respectively. - Feature: [#18732] [Plugin] API to get the guests thoughts. diff --git a/src/openrct2/entity/Yaw.hpp b/src/openrct2/entity/Yaw.hpp index 0e2ec6d707..aac3bf5537 100644 --- a/src/openrct2/entity/Yaw.hpp +++ b/src/openrct2/entity/Yaw.hpp @@ -56,6 +56,10 @@ namespace OpenRCT2::Entity::Yaw { return yaw; } + [[nodiscard]] constexpr int32_t YawTo64(int32_t yaw) + { + return yaw << 1; + } [[nodiscard]] constexpr int32_t YawToPrecision(int32_t yaw, SpritePrecision precision) { diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index b86b45e203..478e94b52b 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -276,6 +276,7 @@ + @@ -1028,4 +1029,4 @@ - + \ No newline at end of file diff --git a/src/openrct2/math/Trigonometry.hpp b/src/openrct2/math/Trigonometry.hpp new file mode 100644 index 0000000000..98e73f5c8c --- /dev/null +++ b/src/openrct2/math/Trigonometry.hpp @@ -0,0 +1,121 @@ +/***************************************************************************** + * Copyright (c) 2014-2023 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. + *****************************************************************************/ + +#pragma once +#include "../entity/Yaw.hpp" +#include "../ride/Vehicle.h" +#include "../world/Location.hpp" + +namespace OpenRCT2::Math::Trigonometry +{ + /** + * The cos and sin of sprite direction + * ROUND(COS((32/64+(L1/64))*(2*PI()))*256,0), ROUND(SIN(((L1/64))*(2*PI())) * 256,0) + * Where L1 represents an incrementing column 0 - 63 + * Note: Must be at least 32bit to ensure all users do not overflow + */ + static constexpr CoordsXY YawToDirectionVector[64] = { + { -256, 0 }, { -255, 25 }, { -251, 50 }, { -245, 74 }, { -237, 98 }, { -226, 121 }, { -213, 142 }, + { -198, 162 }, { -181, 181 }, { -162, 198 }, { -142, 213 }, { -121, 226 }, { -98, 237 }, { -74, 245 }, + { -50, 251 }, { -25, 255 }, { 0, 256 }, { 25, 255 }, { 50, 251 }, { 74, 245 }, { 98, 237 }, + { 121, 226 }, { 142, 213 }, { 162, 198 }, { 181, 181 }, { 198, 162 }, { 213, 142 }, { 226, 121 }, + { 237, 98 }, { 245, 74 }, { 251, 50 }, { 255, 25 }, { 256, 0 }, { 255, -25 }, { 251, -50 }, + { 245, -74 }, { 237, -98 }, { 226, -121 }, { 213, -142 }, { 198, -162 }, { 181, -181 }, { 162, -198 }, + { 142, -213 }, { 121, -226 }, { 98, -237 }, { 74, -245 }, { 50, -251 }, { 25, -255 }, { 0, -256 }, + { -25, -255 }, { -50, -251 }, { -74, -245 }, { -98, -237 }, { -121, -226 }, { -142, -213 }, { -162, -198 }, + { -181, -181 }, { -198, -162 }, { -213, -142 }, { -226, -121 }, { -237, -98 }, { -245, -74 }, { -251, -50 }, + { -255, -25 }, + }; + // Currently OpenRCT2::Entity::Yaw::BaseSpritePrecision is 32, but one day it will be 64. + static_assert(std::size(YawToDirectionVector) == 64); + + /** + * The cos and sin of vehicle pitch based on vehicle sprite angles + * COS((Y1/360)*2*PI())*256,-SIN((Y1/360)*2*PI())*256 + * Where Y1 represents the angle of pitch in degrees + */ + constexpr CoordsXY PitchToDirectionVectorFromGeometry[] = { + { 256, 0 }, // flat + { 251, 49 }, // slopes up + { 236, 97 }, // slopes up + { 195, 165 }, // slopes up + { 134, 217 }, // slopes up + { 251, -49 }, // slopes down + { 236, -97 }, // slopes down + { 195, -165 }, // slopes down + { 135, -217 }, // slopes down + { 70, 246 }, // slopes vertical up + { 0, 256 }, // slopes vertical up + { -66, 247 }, // slopes looping up + { -128, 221 }, // slopes looping up + { -181, 181 }, // slopes looping up + { -221, 128 }, // slopes looping up + { -247, 66 }, // slopes looping up + { -256, 0 }, // inverted + { 70, -246 }, // slopes vertical down + { 0, -256 }, // slopes vertical down + { -66, -247 }, // slopes looping down + { -128, -221 }, // slopes looping down + { -181, -181 }, // slopes looping down + { -221, -128 }, // slopes looping down + { -247, -66 }, // slopes looping down + { 221, 128 }, // corkscrew up left + { 128, 221 }, // corkscrew up left + { 0, 256 }, // corkscrew up left + { -128, 221 }, // corkscrew up left + { -221, 128 }, // corkscrew up left + { -221, -128 }, // corkscrew down left + { -128, -221 }, // corkscrew down left + { 0, -256 }, // corkscrew down left + { 128, -221 }, // corkscrew down left + { 221, -128 }, // corkscrew down left + { 221, 128 }, // corkscrew up right + { 128, 221 }, // corkscrew up right + { 0, 256 }, // corkscrew up right + { -128, 221 }, // corkscrew up right + { -221, 128 }, // corkscrew up right + { -221, -128 }, // corkscrew down right + { -128, -221 }, // corkscrew down right + { 0, -256 }, // corkscrew down right + { 128, -221 }, // corkscrew down right + { 221, 128 }, // corkscrew down right + { 256, 0 }, // half helixes + { 256, 0 }, // half helixes + { 256, 0 }, // half helixes + { 256, 0 }, // half helixes + { 256, 0 }, // quarter helixes + { 256, 0 }, // quarter helixes + { 252, 42 }, // diagonal slopes up + { 241, 83 }, // diagonal slopes up + { 168, 193 }, // diagonal slopes up + { 252, -42 }, // diagonal slopes down + { 241, -83 }, // diagonal slopes down + { 168, -193 }, // diagonal slopes down + { 236, -97 }, // inverting transition slopes down + { 195, -165 }, // inverting transition slopes down + { 134, -217 }, // inverting transition slopes down + { 252, 44 }, // spiral lift hill up + }; + static_assert(std::size(PitchToDirectionVectorFromGeometry) == NumVehiclePitches); + + constexpr int32_t ComputeHorizontalMagnitude(int32_t length, uint8_t pitch) + { + return (-PitchToDirectionVectorFromGeometry[static_cast(pitch)].y * length) / 256; + } + + constexpr CoordsXY ComputeXYVector(int32_t magnitude, uint8_t yaw) + { + return (static_cast(YawToDirectionVector[yaw]) * magnitude) / 256; + } + + constexpr CoordsXY ComputeXYVector(int32_t length, uint8_t pitch, uint8_t yaw) + { + return ComputeXYVector(ComputeHorizontalMagnitude(length, pitch), yaw); + } +} // namespace OpenRCT2::Math::Trigonometry diff --git a/src/openrct2/network/NetworkBase.cpp b/src/openrct2/network/NetworkBase.cpp index 4a685f42f2..35a13838b1 100644 --- a/src/openrct2/network/NetworkBase.cpp +++ b/src/openrct2/network/NetworkBase.cpp @@ -43,7 +43,7 @@ // It is used for making sure only compatible builds get connected, even within // single OpenRCT2 version. -#define NETWORK_STREAM_VERSION "11" +#define NETWORK_STREAM_VERSION "12" #define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION diff --git a/src/openrct2/object/RideObject.cpp b/src/openrct2/object/RideObject.cpp index 0f45555a73..305e64fa5b 100644 --- a/src/openrct2/object/RideObject.cpp +++ b/src/openrct2/object/RideObject.cpp @@ -43,6 +43,56 @@ static const uint8_t SpriteGroupMultiplier[EnumValue(SpriteGroupType::Count)] = 1, 2, 2, 2, 2, 2, 2, 10, 1, 2, 2, 2, 2, 2, 2, 2, 6, 4, 4, 4, 4, 4, 4, 4, 12, 4, 4, 4, 4, 4, 20, 3, 1, }; +constexpr const uint8_t DefaultSteamSpawnPosition[] = { 11, 22 }; + +static const EnumMap AnimationNameLookup{ + { "none", CarEntryAnimation::None }, + { "simpleVehicle", CarEntryAnimation::SimpleVehicle }, + { "steamLocomotive", CarEntryAnimation::SteamLocomotive }, + { "swanBoat", CarEntryAnimation::SwanBoat }, + { "monorailCycle", CarEntryAnimation::MonorailCycle }, + { "MultiDimension", CarEntryAnimation::MultiDimension }, + { "observationTower", CarEntryAnimation::ObservationTower }, + { "animalFlying", CarEntryAnimation::AnimalFlying }, +}; + +constexpr const auto NumLegacyAnimationTypes = 11; + +struct LegacyAnimationParameters +{ + uint16_t Speed; + uint8_t NumFrames; + CarEntryAnimation Alias; +}; + +constexpr const LegacyAnimationParameters VehicleEntryDefaultAnimation[] = { + { 0, 1, CarEntryAnimation::None }, // None + { 1 << 12, 4, CarEntryAnimation::SteamLocomotive }, // Miniature Railway Locomotive + { 1 << 10, 2, CarEntryAnimation::SwanBoat }, // Swan Boat + { 1 << 11, 6, CarEntryAnimation::SimpleVehicle }, // Canoe + { 1 << 11, 7, CarEntryAnimation::SimpleVehicle }, // Rowboat + { 1 << 10, 2, CarEntryAnimation::SimpleVehicle }, // Water Tricycle + { 0x3333, 8, CarEntryAnimation::ObservationTower }, // Observation Tower + { 1 << 10, 4, CarEntryAnimation::SimpleVehicle }, // Mini Helicopter + { 1 << 11, 4, CarEntryAnimation::MonorailCycle }, // Monorail Cycle + { 0x3333, 8, CarEntryAnimation::MultiDimension }, // Multi Dimension Coaster + { 24, 4, CarEntryAnimation::AnimalFlying }, // Animal Flying +}; +static_assert(std::size(VehicleEntryDefaultAnimation) == NumLegacyAnimationTypes); + +static CarEntryAnimation GetAnimationTypeFromString(const std::string& s) +{ + auto result = AnimationNameLookup.find(s); + return (result != AnimationNameLookup.end()) ? result->second : CarEntryAnimation::None; +} + +static LegacyAnimationParameters GetDefaultAnimationParameters(uint8_t legacyAnimationType) +{ + if (legacyAnimationType >= NumLegacyAnimationTypes) + return VehicleEntryDefaultAnimation[0]; + return VehicleEntryDefaultAnimation[legacyAnimationType]; +} + static constexpr SpritePrecision PrecisionFromNumFrames(uint8_t numRotationFrames) { if (numRotationFrames == 0) @@ -240,7 +290,7 @@ void RideObject::Load() carEntry.NumCarImages = imageIndex - currentCarImagesOffset; - // Move the offset over this car’s images. Including peeps + // Move the offset over this car's images. Including peeps currentCarImagesOffset = imageIndex + carEntry.no_seating_rows * carEntry.NumCarImages; // 0x6DEB0D @@ -350,7 +400,7 @@ void RideObject::ReadLegacyCar([[maybe_unused]] IReadObjectContext* context, ISt car->sprite_width = stream->ReadValue(); car->sprite_height_negative = stream->ReadValue(); car->sprite_height_positive = stream->ReadValue(); - car->animation = stream->ReadValue(); + auto legacyAnimation = stream->ReadValue(); car->flags = stream->ReadValue(); car->base_num_frames = stream->ReadValue(); stream->Seek(15 * 4, STREAM_SEEK_CURRENT); @@ -368,6 +418,14 @@ void RideObject::ReadLegacyCar([[maybe_unused]] IReadObjectContext* context, ISt car->draw_order = stream->ReadValue(); car->num_vertical_frames_override = stream->ReadValue(); stream->Seek(4, STREAM_SEEK_CURRENT); + + // OpenRCT2-specific features below + auto animationProperties = GetDefaultAnimationParameters(legacyAnimation); + car->animation = animationProperties.Alias; + car->AnimationSpeed = animationProperties.Speed; + car->AnimationFrames = animationProperties.NumFrames; + car->SteamEffect.Longitudinal = DefaultSteamSpawnPosition[0]; + car->SteamEffect.Vertical = DefaultSteamSpawnPosition[1]; ReadLegacySpriteGroups(car, spriteGroups); } @@ -383,8 +441,8 @@ uint8_t RideObject::CalculateNumVerticalFrames(const CarEntry& carEntry) { if (!(carEntry.flags & CAR_ENTRY_FLAG_SPINNING_ADDITIONAL_FRAMES)) { - if (carEntry.flags & CAR_ENTRY_FLAG_VEHICLE_ANIMATION - && carEntry.animation != CAR_ENTRY_ANIMATION_OBSERVATION_TOWER) + if ((carEntry.flags & CAR_ENTRY_FLAG_VEHICLE_ANIMATION) + && carEntry.animation != CarEntryAnimation::ObservationTower) { if (!(carEntry.flags & CAR_ENTRY_FLAG_DODGEM_INUSE_LIGHTS)) { @@ -647,7 +705,6 @@ CarEntry RideObject::ReadJsonCar([[maybe_unused]] IReadObjectContext* context, j car.sprite_width = Json::GetNumber(jCar["spriteWidth"]); car.sprite_height_negative = Json::GetNumber(jCar["spriteHeightNegative"]); car.sprite_height_positive = Json::GetNumber(jCar["spriteHeightPositive"]); - car.animation = Json::GetNumber(jCar["animation"]); car.base_num_frames = Json::GetNumber(jCar["baseNumFrames"]); car.NumCarImages = Json::GetNumber(jCar["numImages"]); car.no_seating_rows = Json::GetNumber(jCar["numSeatRows"]); @@ -664,6 +721,38 @@ CarEntry RideObject::ReadJsonCar([[maybe_unused]] IReadObjectContext* context, j car.draw_order = Json::GetNumber(jCar["drawOrder"]); car.num_vertical_frames_override = Json::GetNumber(jCar["numVerticalFramesOverride"]); + auto jAnimation = jCar["animation"]; + if (jAnimation.is_object()) + { + car.animation = GetAnimationTypeFromString(Json::GetString(jAnimation["animationType"])); + car.AnimationSpeed = Json::GetNumber(jAnimation["animationSpeed"]); + car.AnimationFrames = Json::GetNumber(jAnimation["animationFrames"]); + } + else + { + auto animationProperties = GetDefaultAnimationParameters(Json::GetNumber(jAnimation)); + car.animation = animationProperties.Alias; + car.AnimationSpeed = animationProperties.Speed; + car.AnimationFrames = animationProperties.NumFrames; + + if (!jCar["animationSpeed"].is_null()) + car.AnimationSpeed = Json::GetNumber(jCar["animationSpeed"]); + if (!jCar["animationFrames"].is_null()) + car.AnimationFrames = Json::GetNumber(jCar["animationFrames"]); + } + + auto jSteamTranslation = jCar["steamPosition"]; + if (jSteamTranslation.is_object()) + { + car.SteamEffect.Longitudinal = Json::GetNumber(jSteamTranslation["longitudinal"], DefaultSteamSpawnPosition[0]); + car.SteamEffect.Vertical = Json::GetNumber(jSteamTranslation["vertical"], DefaultSteamSpawnPosition[1]); + } + else + { + car.SteamEffect.Longitudinal = DefaultSteamSpawnPosition[0]; + car.SteamEffect.Vertical = DefaultSteamSpawnPosition[1]; + } + auto jLoadingPositions = jCar["loadingPositions"]; if (jLoadingPositions.is_array()) { diff --git a/src/openrct2/ride/CarEntry.h b/src/openrct2/ride/CarEntry.h index c8b31a31bc..eda82c121f 100644 --- a/src/openrct2/ride/CarEntry.h +++ b/src/openrct2/ride/CarEntry.h @@ -23,6 +23,19 @@ namespace OpenRCT2::Audio enum class SoundId : uint8_t; } +enum class CarEntryAnimation : uint8_t +{ + None = 0, + SimpleVehicle, + SteamLocomotive, + SwanBoat, + MonorailCycle, + MultiDimension, + ObservationTower, + AnimalFlying, + Count, +}; + enum : uint32_t { CAR_ENTRY_FLAG_POWERED_RIDE_UNRESTRICTED_GRAVITY = 1 @@ -166,7 +179,7 @@ struct CarEntry uint8_t sprite_width; uint8_t sprite_height_negative; uint8_t sprite_height_positive; - uint8_t animation; + CarEntryAnimation animation; uint32_t flags; uint16_t base_num_frames; // The number of sprites of animation or swinging per rotation frame uint32_t base_image_id; @@ -187,6 +200,13 @@ struct CarEntry uint8_t num_vertical_frames_override; // A custom number that can be used rather than letting RCT2 determine it. // Needs the CAR_ENTRY_FLAG_OVERRIDE_NUM_VERTICAL_FRAMES flag to be set. uint8_t peep_loading_waypoint_segments; + uint16_t AnimationSpeed; + uint8_t AnimationFrames; + struct + { + int8_t Longitudinal; + int8_t Vertical; + } SteamEffect; std::vector> peep_loading_waypoints = {}; std::vector peep_loading_positions = {}; diff --git a/src/openrct2/ride/RideData.cpp b/src/openrct2/ride/RideData.cpp index 266ec56172..1a330e901e 100644 --- a/src/openrct2/ride/RideData.cpp +++ b/src/openrct2/ride/RideData.cpp @@ -133,7 +133,7 @@ const CarEntry CableLiftVehicle = { /* .sprite_width = */ 0, /* .sprite_height_negative = */ 0, /* .sprite_height_positive = */ 0, - /* .animation = */ 0, + /* .animation = */ CarEntryAnimation::None, /* .flags = */ 0, /* .base_num_frames = */ 1, /* .base_image_id = */ 29110, @@ -184,7 +184,11 @@ const CarEntry CableLiftVehicle = { /* .effect_visual = */ 1, /* .draw_order = */ 14, /* .num_vertical_frames_override = */ 0, - /* .peep_loading_positions = */ 0 + /* .peep_loading_positions = */ 0, + /* .AnimationExponent = */ 0, + /* .AnimationFrames = */ 0, + /* .SteamEffectType.longitudinal = */ 0, + /* .SteamEffectType.vertical = */ 0 }; /* rct2: 0x009A0AA0 */ diff --git a/src/openrct2/ride/Vehicle.cpp b/src/openrct2/ride/Vehicle.cpp index 54d408539d..1dd7cf3606 100644 --- a/src/openrct2/ride/Vehicle.cpp +++ b/src/openrct2/ride/Vehicle.cpp @@ -26,6 +26,7 @@ #include "../localisation/Formatter.h" #include "../localisation/Localisation.h" #include "../management/NewsItem.h" +#include "../math/Trigonometry.hpp" #include "../object/SmallSceneryEntry.h" #include "../platform/Platform.h" #include "../profiling/Profiling.h" @@ -56,6 +57,7 @@ using namespace OpenRCT2::Audio; using namespace OpenRCT2::TrackMetaData; +using namespace OpenRCT2::Math::Trigonometry; static bool vehicle_boat_is_location_accessible(const CoordsXYZ& location); constexpr int16_t VEHICLE_MAX_SPIN_SPEED = 1536; @@ -435,66 +437,6 @@ static constexpr const OpenRCT2::Audio::SoundId DoorCloseSoundIds[] = { OpenRCT2::Audio::SoundId::Portcullis, }; -static const struct -{ - int8_t x, y, z; -} SteamParticleOffsets[][16] = { - { - { -11, 0, 22 }, - { -10, 4, 22 }, - { -8, 8, 22 }, - { -4, 10, 22 }, - { 0, 11, 22 }, - { 4, 10, 22 }, - { 8, 8, 22 }, - { 10, 4, 22 }, - { 11, 0, 22 }, - { 10, -4, 22 }, - { 8, -8, 22 }, - { 4, -10, 22 }, - { 0, -11, 22 }, - { -4, -10, 22 }, - { -8, -8, 22 }, - { -10, -4, 22 }, - }, - { - { -9, 0, 27 }, - { -8, 4, 27 }, - { -6, 6, 27 }, - { -4, 8, 27 }, - { 0, 9, 27 }, - { 4, 8, 27 }, - { 6, 6, 27 }, - { 8, 4, 27 }, - { 9, 0, 27 }, - { 8, -4, 27 }, - { 6, -6, 27 }, - { 4, -8, 27 }, - { 0, -9, 27 }, - { -4, -8, 27 }, - { -6, -6, 27 }, - { -8, -4, 27 }, - }, - { - { -13, 0, 18 }, - { -12, 4, 17 }, - { -9, 9, 17 }, - { -4, 8, 17 }, - { 0, 13, 18 }, - { 4, 8, 17 }, - { 6, 6, 17 }, - { 8, 4, 17 }, - { 13, 0, 18 }, - { 8, -4, 17 }, - { 6, -6, 17 }, - { 4, -8, 17 }, - { 0, -13, 18 }, - { -4, -8, 17 }, - { -6, -6, 17 }, - { -8, -4, 17 }, - }, -}; - template<> bool EntityBase::Is() const { return Type == EntityType::Vehicle; @@ -1350,23 +1292,23 @@ bool Vehicle::OpenRestraints() continue; } } - if (carEntry.animation == CAR_ENTRY_ANIMATION_OBSERVATION_TOWER && vehicle->animation_frame != 0) + if (carEntry.animation == CarEntryAnimation::ObservationTower && vehicle->animation_frame != 0) { if (vehicle->animationState <= 0xCCCC) { - vehicle->animationState += 0x3333; + vehicle->animationState += carEntry.AnimationSpeed; } else { vehicle->animationState = 0; vehicle->animation_frame++; - vehicle->animation_frame &= 7; + vehicle->animation_frame %= carEntry.AnimationFrames; vehicle->Invalidate(); } restraintsOpen = false; continue; } - if (carEntry.animation == CAR_ENTRY_ANIMATION_ANIMAL_FLYING + if (carEntry.animation == CarEntryAnimation::AnimalFlying && (vehicle->animation_frame != 0 || vehicle->animationState > 0)) { vehicle->UpdateAnimationAnimalFlying(); @@ -6637,164 +6579,211 @@ void Vehicle::UpdateAnimationAnimalFlying() animationState = frameWaitTimes[animation_frame]; } +/** + * Get the frame of animation for the current animationState based on animation speed and animation frames + */ +static uint8_t GetTargetFrame(const CarEntry& carEntry, uint32_t animationState) +{ + if (carEntry.AnimationSpeed == 0) + return 0; + auto targetFrame = animationState / (carEntry.AnimationSpeed << 2); + // mask of 0xFF + targetFrame &= std::numeric_limits::max(); + // multiply by number of frames. After the bitshift 8, the range will be 0 to AnimationFrames - 1 + targetFrame *= carEntry.AnimationFrames; + return targetFrame >> std::numeric_limits::digits; +} + +/** + * Compute the position that steam should be spawned + */ +static constexpr CoordsXYZ ComputeSteamOffset(int32_t height, int32_t length, uint8_t pitch, uint8_t yaw) +{ + uint8_t trueYaw = OpenRCT2::Entity::Yaw::YawTo64(yaw); + auto offsets = PitchToDirectionVectorFromGeometry[pitch]; + int32_t projectedRun = (offsets.x * length - offsets.y * height) / 256; + int32_t projectedHeight = (offsets.x * height + offsets.y * length) / 256; + return { ComputeXYVector(projectedRun, trueYaw), projectedHeight }; +} + +/** + * Decide based on current frame and number of frames if a steam particle should be generated on this frame + */ +static bool ShouldMakeSteam(uint8_t targetFrame, uint8_t animationFrames) +{ + if (animationFrames < 1) + return false; + // steam is produced twice per wheel revolution + return targetFrame == 0 || targetFrame == animationFrames / 2; +} + +/** + * Dummy function + */ +static void AnimateNone(Vehicle& vehicle, const CarEntry& carEntry) +{ + return; +} + +/** + * Animate the vehicle based on its speed + */ +static void AnimateSimpleVehicle(Vehicle& vehicle, const CarEntry& carEntry) +{ + vehicle.animationState += _vehicleVelocityF64E08; + uint8_t targetFrame = GetTargetFrame(carEntry, vehicle.animationState); + if (vehicle.animation_frame != targetFrame) + { + vehicle.animation_frame = targetFrame; + vehicle.Invalidate(); + } +} + +/** + * Animate the vehicle based on its speed plus add steam particles + */ +static void AnimateSteamLocomotive(Vehicle& vehicle, const CarEntry& carEntry) +{ + vehicle.animationState += _vehicleVelocityF64E08; + uint8_t targetFrame = GetTargetFrame(carEntry, vehicle.animationState); + if (vehicle.animation_frame != targetFrame) + { + vehicle.animation_frame = targetFrame; + if (ShouldMakeSteam(targetFrame, carEntry.AnimationFrames)) + { + auto curRide = vehicle.GetRide(); + if (curRide != nullptr) + { + if (!RideHasStationShelter(*curRide) + || (vehicle.status != Vehicle::Status::MovingToEndOfStation && vehicle.status != Vehicle::Status::Arriving)) + { + CoordsXYZ steamOffset = ComputeSteamOffset( + carEntry.SteamEffect.Vertical, carEntry.SteamEffect.Longitudinal, vehicle.Pitch, + vehicle.sprite_direction); + SteamParticle::Create(CoordsXYZ(vehicle.x, vehicle.y, vehicle.z) + steamOffset); + } + } + } + vehicle.Invalidate(); + } +} + +/** + * Animate the vehicle based on its speed. Specialized animation with exactly 2 frames due to how peep animation works. + */ +static void AnimateSwanBoat(Vehicle& vehicle, const CarEntry& carEntry) +{ + // The animation of swan boats places frames at 0 and 2 instead of 0 and 1 like Water Tricycles due to the second + // pair of peeps. The animation technically uses 4 frames, but ignores frames 1 and 3. + vehicle.animationState += _vehicleVelocityF64E08; + uint8_t targetFrame = GetTargetFrame(carEntry, vehicle.animationState) * 2; + if (vehicle.animation_frame != targetFrame) + { + vehicle.animation_frame = targetFrame; + vehicle.Invalidate(); + } +} + +/** + * Monorail Cycle animation only animates when a peep is present + */ +static void AnimateMonorailCycle(Vehicle& vehicle, const CarEntry& carEntry) +{ + if (vehicle.num_peeps != 0) + { + AnimateSimpleVehicle(vehicle, carEntry); + } +} + +/** + * Observation tower animates at a constant speed continuously + */ +static void AnimateObservationTower(Vehicle& vehicle, const CarEntry& carEntry) +{ + if (vehicle.animationState <= 0xCCCC) + { + vehicle.animationState += carEntry.AnimationSpeed; + } + else + { + vehicle.animationState = 0; + vehicle.animation_frame += 1; + vehicle.animation_frame %= carEntry.AnimationFrames; + vehicle.Invalidate(); + } +} +/** + * seatRotation value of 4 translates to animationFrame value of 0. This function makes that true for any number of animation + * frames + */ +static int16_t MultiDimensionTargetAngle(int16_t seatRotation, int16_t animationFrames) +{ + return ((seatRotation - 4) % animationFrames + animationFrames) % animationFrames; +} + +/** + * Multidimension targets a specific animation frame based on track + */ +static void AnimateMultiDimension(Vehicle& vehicle, const CarEntry& carEntry) +{ + if (vehicle.seat_rotation != vehicle.target_seat_rotation) + { + if (vehicle.animationState <= 0xCCCC) + { + vehicle.animationState += carEntry.AnimationSpeed; + } + else + { + vehicle.animationState = 0; + + if (vehicle.seat_rotation >= vehicle.target_seat_rotation) + vehicle.seat_rotation--; + else + vehicle.seat_rotation++; + + int16_t targetSeatRotation = MultiDimensionTargetAngle(vehicle.seat_rotation, carEntry.AnimationFrames); + if (targetSeatRotation != vehicle.animation_frame) + { + vehicle.animation_frame = targetSeatRotation; + vehicle.Invalidate(); + } + } + } +} + +/** + * Animal Flying animates only on chainlift and in an unusual way. Made by Spacek531 + */ +static void AnimateAnimalFlying(Vehicle& vehicle, const CarEntry& carEntry) +{ + vehicle.UpdateAnimationAnimalFlying(); + // makes animation play faster with vehicle speed + uint8_t targetFrame = abs(_vehicleVelocityF64E08) >> carEntry.AnimationSpeed; + vehicle.animationState = std::max(vehicle.animationState - targetFrame, 0u); +} + +using AnimateFunction = void (*)(Vehicle& vehicle, const CarEntry& carEntry); + +constexpr static const AnimateFunction AnimationFunctions[]{ + AnimateNone, AnimateSimpleVehicle, AnimateSteamLocomotive, AnimateSwanBoat, + AnimateMonorailCycle, AnimateMultiDimension, AnimateObservationTower, AnimateAnimalFlying, +}; +static_assert(std::size(AnimationFunctions) == EnumValue(CarEntryAnimation::Count)); + /** * * rct2: 0x006D63D4 */ void Vehicle::UpdateAdditionalAnimation() { - uint8_t targetFrame{}; - uint8_t curFrame{}; - uint32_t eax{}; - auto carEntry = Entry(); if (carEntry == nullptr) { return; } - switch (carEntry->animation) - { - case CAR_ENTRY_ANIMATION_MINITURE_RAILWAY_LOCOMOTIVE: // Loc6D652B - animationState += _vehicleVelocityF64E08; - targetFrame = (animationState >> 20) & 3; - if (animation_frame != targetFrame) - { - curFrame = animation_frame; - animation_frame = targetFrame; - targetFrame &= 0x02; - curFrame &= 0x02; - if (targetFrame != curFrame) - { - auto curRide = GetRide(); - if (curRide != nullptr) - { - if (!RideHasStationShelter(*curRide) - || (status != Vehicle::Status::MovingToEndOfStation && status != Vehicle::Status::Arriving)) - { - int32_t typeIndex = [&] { - switch (Pitch) - { - case 2: - // uphill - return 1; - case 6: - // downhill - return 2; - default: - return 0; - } - }(); - int32_t directionIndex = sprite_direction >> 1; - auto offset = SteamParticleOffsets[typeIndex][directionIndex]; - SteamParticle::Create({ x + offset.x, y + offset.y, z + offset.z }); - } - } - } - Invalidate(); - } - break; - case CAR_ENTRY_ANIMATION_SWAN: // Loc6D6424 - animationState += _vehicleVelocityF64E08; - targetFrame = (animationState >> 18) & 2; - if (animation_frame != targetFrame) - { - animation_frame = targetFrame; - Invalidate(); - } - break; - case CAR_ENTRY_ANIMATION_CANOES: // Loc6D6482 - animationState += _vehicleVelocityF64E08; - eax = ((animationState >> 13) & 0xFF) * 6; - targetFrame = (eax >> 8) & 0xFF; - if (animation_frame != targetFrame) - { - animation_frame = targetFrame; - Invalidate(); - } - break; - case CAR_ENTRY_ANIMATION_ROW_BOATS: // Loc6D64F7 - animationState += _vehicleVelocityF64E08; - eax = ((animationState >> 13) & 0xFF) * 7; - targetFrame = (eax >> 8) & 0xFF; - if (animation_frame != targetFrame) - { - animation_frame = targetFrame; - Invalidate(); - } - break; - case CAR_ENTRY_ANIMATION_WATER_TRICYCLES: // Loc6D6453 - animationState += _vehicleVelocityF64E08; - targetFrame = (animationState >> 19) & 1; - if (animation_frame != targetFrame) - { - animation_frame = targetFrame; - Invalidate(); - } - break; - case CAR_ENTRY_ANIMATION_OBSERVATION_TOWER: // Loc6D65C3 - if (animationState <= 0xCCCC) - { - animationState += 0x3333; - } - else - { - animationState = 0; - animation_frame += 1; - animation_frame &= 7; - Invalidate(); - } - break; - case CAR_ENTRY_ANIMATION_HELICARS: // Loc6D63F5 - animationState += _vehicleVelocityF64E08; - targetFrame = (animationState >> 18) & 3; - if (animation_frame != targetFrame) - { - animation_frame = targetFrame; - Invalidate(); - } - break; - case CAR_ENTRY_ANIMATION_MONORAIL_CYCLES: // Loc6D64B6 - if (num_peeps != 0) - { - animationState += _vehicleVelocityF64E08; - eax = ((animationState >> 13) & 0xFF) << 2; - targetFrame = (eax >> 8) & 0xFF; - if (animation_frame != targetFrame) - { - animation_frame = targetFrame; - Invalidate(); - } - } - break; - case CAR_ENTRY_ANIMATION_MULTI_DIM_COASTER: // Loc6D65E1 - if (seat_rotation != target_seat_rotation) - { - if (animationState <= 0xCCCC) - { - animationState += 0x3333; - } - else - { - animationState = 0; - - if (seat_rotation >= target_seat_rotation) - seat_rotation--; - - else - seat_rotation++; - - animation_frame = (seat_rotation - 4) & 7; - Invalidate(); - } - } - break; - case CAR_ENTRY_ANIMATION_ANIMAL_FLYING: - UpdateAnimationAnimalFlying(); - // makes animation play faster with vehicle speed - targetFrame = abs(_vehicleVelocityF64E08) >> 24; - animationState = std::max(animationState - targetFrame, 0u); - break; - } + if (carEntry->AnimationFrames == 0 || carEntry->animation >= CarEntryAnimation::Count) + return; + AnimationFunctions[EnumValue(carEntry->animation)](*this, *carEntry); } /** diff --git a/src/openrct2/ride/Vehicle.h b/src/openrct2/ride/Vehicle.h index d46a8deb76..55fc833ecf 100644 --- a/src/openrct2/ride/Vehicle.h +++ b/src/openrct2/ride/Vehicle.h @@ -38,6 +38,9 @@ struct GForces int32_t LateralG{}; }; +// How many valid pitch values are currently in the game. Eventually pitch will be enumerated. +constexpr const uint8_t NumVehiclePitches = 60; + // Size: 0x09 struct VehicleInfo { @@ -230,6 +233,7 @@ struct Vehicle : EntityBase Ride* GetRide() const; Vehicle* TrainHead() const; Vehicle* TrainTail() const; + void UpdateAnimationAnimalFlying(); void EnableCollisionsForTrain(); /** * Instantly moves the specific car forward or backwards along the track. @@ -328,7 +332,6 @@ private: void UpdateCrashSetup(); void UpdateCollisionSetup(); int32_t UpdateMotionDodgems(); - void UpdateAnimationAnimalFlying(); void UpdateAdditionalAnimation(); void CheckIfMissing(); bool CurrentTowerElementIsTop(); @@ -422,21 +425,6 @@ enum class MiniGolfAnimation : uint8_t Putt, }; -enum -{ - CAR_ENTRY_ANIMATION_NONE, - CAR_ENTRY_ANIMATION_MINITURE_RAILWAY_LOCOMOTIVE, - CAR_ENTRY_ANIMATION_SWAN, - CAR_ENTRY_ANIMATION_CANOES, - CAR_ENTRY_ANIMATION_ROW_BOATS, - CAR_ENTRY_ANIMATION_WATER_TRICYCLES, - CAR_ENTRY_ANIMATION_OBSERVATION_TOWER, - CAR_ENTRY_ANIMATION_HELICARS, - CAR_ENTRY_ANIMATION_MONORAIL_CYCLES, - CAR_ENTRY_ANIMATION_MULTI_DIM_COASTER, - CAR_ENTRY_ANIMATION_ANIMAL_FLYING // OpenRCT2-specific feature -}; - namespace VehicleFlags { constexpr uint32_t OnLiftHill = (1 << 0); diff --git a/src/openrct2/scripting/bindings/object/ScObject.hpp b/src/openrct2/scripting/bindings/object/ScObject.hpp index 20a50d1250..fdfbb991de 100644 --- a/src/openrct2/scripting/bindings/object/ScObject.hpp +++ b/src/openrct2/scripting/bindings/object/ScObject.hpp @@ -274,7 +274,7 @@ namespace OpenRCT2::Scripting auto entry = GetEntry(); if (entry != nullptr) { - return entry->animation; + return EnumValue(entry->animation); } return 0; }