steam position modifier and animation speed properties (#11269)

* Add speed and frame properties

* add steam position modifier

* copy code from OpenLoco

* update sin and cos

* add computation function

* finalize locomotion code

* fix formatting

* refine code

* refactor things slightly

* manually fix formatting

* use PascalCase and rename

* fix copyright notice

* fix name again

* rename function, move outt  of namespace

* fix rebase issues

* remove pitch table derived from physics

* rename some stuff

* flip vertical component sign to make sense

* change json structure

* create steam particles based on number of animation frames

* fix formatting

* add slope for spiral lift hill down

* fix formatting again

* parens around bitwise and

* make animations separate functions

* rename MultiDimCoaster to MultiDimension

* use EnumValue method

* rework multidim frame count

* bump network to be safe

* fix formatting

* move array out of function

* make table const

* move struct into RideObject.cpp

* try new method to fix numbers in multidim

* implement ZehMatt modulo

* add documentation to new function

* include Yaw.hpp

* rename src/ride/SteamPosition.hpp to src/math/Trigonometry.hpp

* actually add src/math/Trigonometry.hpp

* move ComputeSteamOffset to Vehicle.cpp

* use static asserts on arrays

* fix changelog grammar

* add more static asserts
This commit is contained in:
spacek531 2023-03-09 05:45:45 -08:00 committed by GitHub
parent 2ac5e070c1
commit fdbc3d29bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 455 additions and 238 deletions

View File

@ -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.

View File

@ -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)
{

View File

@ -276,6 +276,7 @@
<ClInclude Include="management\Marketing.h" />
<ClInclude Include="management\NewsItem.h" />
<ClInclude Include="management\Research.h" />
<ClInclude Include="math\Trigonometry.hpp" />
<ClInclude Include="network\DiscordService.h" />
<ClInclude Include="network\network.h" />
<ClInclude Include="network\NetworkAction.h" />
@ -1028,4 +1029,4 @@
</ClCompile>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
</Project>

View File

@ -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<uint8_t>(pitch)].y * length) / 256;
}
constexpr CoordsXY ComputeXYVector(int32_t magnitude, uint8_t yaw)
{
return (static_cast<CoordsXY>(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

View File

@ -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

View File

@ -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<CarEntryAnimation> 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 cars 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<uint8_t>();
car->sprite_height_negative = stream->ReadValue<uint8_t>();
car->sprite_height_positive = stream->ReadValue<uint8_t>();
car->animation = stream->ReadValue<uint8_t>();
auto legacyAnimation = stream->ReadValue<uint8_t>();
car->flags = stream->ReadValue<uint32_t>();
car->base_num_frames = stream->ReadValue<uint16_t>();
stream->Seek(15 * 4, STREAM_SEEK_CURRENT);
@ -368,6 +418,14 @@ void RideObject::ReadLegacyCar([[maybe_unused]] IReadObjectContext* context, ISt
car->draw_order = stream->ReadValue<uint8_t>();
car->num_vertical_frames_override = stream->ReadValue<uint8_t>();
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<uint8_t>(jCar["spriteWidth"]);
car.sprite_height_negative = Json::GetNumber<uint8_t>(jCar["spriteHeightNegative"]);
car.sprite_height_positive = Json::GetNumber<uint8_t>(jCar["spriteHeightPositive"]);
car.animation = Json::GetNumber<uint8_t>(jCar["animation"]);
car.base_num_frames = Json::GetNumber<uint16_t>(jCar["baseNumFrames"]);
car.NumCarImages = Json::GetNumber<uint32_t>(jCar["numImages"]);
car.no_seating_rows = Json::GetNumber<uint8_t>(jCar["numSeatRows"]);
@ -664,6 +721,38 @@ CarEntry RideObject::ReadJsonCar([[maybe_unused]] IReadObjectContext* context, j
car.draw_order = Json::GetNumber<uint8_t>(jCar["drawOrder"]);
car.num_vertical_frames_override = Json::GetNumber<uint8_t>(jCar["numVerticalFramesOverride"]);
auto jAnimation = jCar["animation"];
if (jAnimation.is_object())
{
car.animation = GetAnimationTypeFromString(Json::GetString(jAnimation["animationType"]));
car.AnimationSpeed = Json::GetNumber<uint16_t>(jAnimation["animationSpeed"]);
car.AnimationFrames = Json::GetNumber<uint16_t>(jAnimation["animationFrames"]);
}
else
{
auto animationProperties = GetDefaultAnimationParameters(Json::GetNumber<uint8_t>(jAnimation));
car.animation = animationProperties.Alias;
car.AnimationSpeed = animationProperties.Speed;
car.AnimationFrames = animationProperties.NumFrames;
if (!jCar["animationSpeed"].is_null())
car.AnimationSpeed = Json::GetNumber<uint16_t>(jCar["animationSpeed"]);
if (!jCar["animationFrames"].is_null())
car.AnimationFrames = Json::GetNumber<uint16_t>(jCar["animationFrames"]);
}
auto jSteamTranslation = jCar["steamPosition"];
if (jSteamTranslation.is_object())
{
car.SteamEffect.Longitudinal = Json::GetNumber<int8_t>(jSteamTranslation["longitudinal"], DefaultSteamSpawnPosition[0]);
car.SteamEffect.Vertical = Json::GetNumber<int8_t>(jSteamTranslation["vertical"], DefaultSteamSpawnPosition[1]);
}
else
{
car.SteamEffect.Longitudinal = DefaultSteamSpawnPosition[0];
car.SteamEffect.Vertical = DefaultSteamSpawnPosition[1];
}
auto jLoadingPositions = jCar["loadingPositions"];
if (jLoadingPositions.is_array())
{

View File

@ -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<std::array<CoordsXY, 3>> peep_loading_waypoints = {};
std::vector<int8_t> peep_loading_positions = {};

View File

@ -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 */

View File

@ -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<Vehicle>() 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<uint8_t>::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<uint8_t>::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);
}
/**

View File

@ -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);

View File

@ -274,7 +274,7 @@ namespace OpenRCT2::Scripting
auto entry = GetEntry();
if (entry != nullptr)
{
return entry->animation;
return EnumValue(entry->animation);
}
return 0;
}