mirror of https://github.com/OpenRCT2/OpenRCT2.git
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:
parent
2ac5e070c1
commit
fdbc3d29bb
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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>
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 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<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())
|
||||
{
|
||||
|
|
|
@ -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 = {};
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -274,7 +274,7 @@ namespace OpenRCT2::Scripting
|
|||
auto entry = GetEntry();
|
||||
if (entry != nullptr)
|
||||
{
|
||||
return entry->animation;
|
||||
return EnumValue(entry->animation);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue