diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 81e2ebcdf4..7b00e66426 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -12,6 +12,8 @@ - Feature: [#22090] [Plugin] Allow writing of paused state in non-networked settings. - Feature: [#22140] Add option to automatically close dropdown menus if Enlarged UI is enabled. - Feature: [#22150] [Plugin] Expose monthly expenditure history to the plugin API. +- Feature: [#22210] [Plugin] Peeps can now be made stationary or completely frozen. +- Feature: [#22210] [Plugin] The direction in which a peep is facing can now be manipulated. - Improved: [#19870] Allow using new colours in UI themes. - Improved: [#21774] The Alpine Coaster now supports using the alternative colour schemes. - Improved: [#21853] Dropdowns now automatically use multiple columns if they are too tall for the screen. diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index f335ab7716..a1a9cfb76e 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -2694,6 +2694,11 @@ declare global { */ destination: CoordsXY; + /** + * The peep's orthogonal direction, from 0 to 3. + */ + direction: Direction; + /** * How tired the guest is between 32 and 128 where lower is more tired. */ @@ -2743,7 +2748,9 @@ declare global { "joy" | "angry" | "iceCream" | - "hereWeAre"; + "hereWeAre" | + "positionFrozen" | + "animationFrozen"; /** * @deprecated since version 34, use EntityType instead. diff --git a/src/openrct2/entity/Guest.cpp b/src/openrct2/entity/Guest.cpp index 85b1b7e48a..5e4b89230e 100644 --- a/src/openrct2/entity/Guest.cpp +++ b/src/openrct2/entity/Guest.cpp @@ -979,7 +979,10 @@ void Guest::Tick128UpdateGuest(uint32_t index) } } - UpdateSpriteType(); + if (!(PeepFlags & PEEP_FLAGS_ANIMATION_FROZEN)) + { + UpdateSpriteType(); + } if (State == PeepState::OnRide || State == PeepState::EnteringRide) { @@ -1009,6 +1012,11 @@ void Guest::Tick128UpdateGuest(uint32_t index) } } + if (PeepFlags & PEEP_FLAGS_POSITION_FROZEN) + { + return; + } + if (State == PeepState::Walking && !OutsideOfPark && !(PeepFlags & PEEP_FLAGS_LEAVING_PARK) && GuestNumRides == 0 && GuestHeadingToRideId.IsNull()) { diff --git a/src/openrct2/entity/Peep.cpp b/src/openrct2/entity/Peep.cpp index d1a2a7cb33..c928b29bfd 100644 --- a/src/openrct2/entity/Peep.cpp +++ b/src/openrct2/entity/Peep.cpp @@ -428,18 +428,13 @@ std::optional Peep::UpdateAction(int16_t& xy_distance) return UpdateWalkingAction(differenceLoc, xy_distance); } - const PeepAnimation& peepAnimation = GetPeepAnimation(SpriteType, ActionSpriteType); - ActionFrame++; - - // If last frame of action - if (ActionFrame >= peepAnimation.frame_offsets.size()) + if (!UpdateActionAnimation()) { ActionSpriteImageOffset = 0; Action = PeepActionType::Walking; UpdateCurrentActionSpriteType(); return { { x, y } }; } - ActionSpriteImageOffset = peepAnimation.frame_offsets[ActionFrame]; // Should we throw up, and are we at the frame where sick appears? auto* guest = As(); @@ -451,6 +446,21 @@ std::optional Peep::UpdateAction(int16_t& xy_distance) return { { x, y } }; } +bool Peep::UpdateActionAnimation() +{ + const PeepAnimation& peepAnimation = GetPeepAnimation(SpriteType, ActionSpriteType); + ActionFrame++; + + // If last frame of action + if (ActionFrame >= peepAnimation.frame_offsets.size()) + { + return false; + } + + ActionSpriteImageOffset = peepAnimation.frame_offsets[ActionFrame]; + return true; +} + std::optional Peep::UpdateWalkingAction(const CoordsXY& differenceLoc, int16_t& xy_distance) { if (!IsActionWalking()) @@ -489,6 +499,13 @@ std::optional Peep::UpdateWalkingAction(const CoordsXY& differenceLoc, CoordsXY loc = { x, y }; loc += walkingOffsetByDirection[nextDirection]; + UpdateWalkingAnimation(); + + return loc; +} + +void Peep::UpdateWalkingAnimation() +{ WalkingFrameNum++; const PeepAnimation& peepAnimation = GetPeepAnimation(SpriteType, ActionSpriteType); if (WalkingFrameNum >= peepAnimation.frame_offsets.size()) @@ -496,8 +513,6 @@ std::optional Peep::UpdateWalkingAction(const CoordsXY& differenceLoc, WalkingFrameNum = 0; } ActionSpriteImageOffset = peepAnimation.frame_offsets[WalkingFrameNum]; - - return loc; } void Peep::ThrowUp() @@ -956,6 +971,29 @@ static void GuestUpdateThoughts(Guest* peep) */ void Peep::Update() { + if (PeepFlags & PEEP_FLAGS_POSITION_FROZEN) + { + if (!(PeepFlags & PEEP_FLAGS_ANIMATION_FROZEN)) + { + // This is circumventing other logic, so only update every few ticks + if ((GetGameState().CurrentTicks & 3) == 0) + { + if (IsActionWalking()) + UpdateWalkingAnimation(); + else + UpdateActionAnimation(); + } + } + return; + } + else if (PeepFlags & PEEP_FLAGS_ANIMATION_FROZEN) + { + // Animation is frozen while position is not. This allows a peep to walk + // around without its sprite being updated, which looks very glitchy. + // We'll just remove the flag and continue as normal, in this case. + PeepFlags &= ~PEEP_FLAGS_ANIMATION_FROZEN; + } + auto* guest = As(); if (guest != nullptr) { diff --git a/src/openrct2/entity/Peep.h b/src/openrct2/entity/Peep.h index ae934c8522..7aaa2ba79d 100644 --- a/src/openrct2/entity/Peep.h +++ b/src/openrct2/entity/Peep.h @@ -227,6 +227,8 @@ enum PeepFlags : uint32_t PEEP_FLAGS_INTAMIN_DEPRECATED = (1 << 27), // Used to make the peep think "I'm so excited - It's an Intamin ride!" while // riding on a Intamin ride. PEEP_FLAGS_HERE_WE_ARE = (1 << 28), // Makes the peep think "...and here we are on X!" while riding a ride + PEEP_FLAGS_POSITION_FROZEN = (1 << 29), // Prevents the peep from moving around, thus keeping them in place + PEEP_FLAGS_ANIMATION_FROZEN = (1 << 30), // Prevents the peep sprite from updating PEEP_FLAGS_TWITCH_DEPRECATED = (1u << 31), // Formerly used for twitch integration }; @@ -375,7 +377,9 @@ public: // Peep void Update(); std::optional UpdateAction(int16_t& xy_distance); std::optional UpdateAction(); + bool UpdateActionAnimation(); std::optional UpdateWalkingAction(const CoordsXY& differenceLoc, int16_t& xy_distance); + void UpdateWalkingAnimation(); void ThrowUp(); void SetState(PeepState new_state); void Remove(); diff --git a/src/openrct2/network/NetworkBase.cpp b/src/openrct2/network/NetworkBase.cpp index d937276c54..07cd6958d8 100644 --- a/src/openrct2/network/NetworkBase.cpp +++ b/src/openrct2/network/NetworkBase.cpp @@ -47,7 +47,7 @@ using namespace OpenRCT2; // It is used for making sure only compatible builds get connected, even within // single OpenRCT2 version. -constexpr uint8_t kNetworkStreamVersion = 2; +constexpr uint8_t kNetworkStreamVersion = 3; const std::string kNetworkStreamID = std::string(OPENRCT2_VERSION) + "-" + std::to_string(kNetworkStreamVersion); diff --git a/src/openrct2/park/ParkFile.h b/src/openrct2/park/ParkFile.h index 3aef7d103b..da6bf79e7c 100644 --- a/src/openrct2/park/ParkFile.h +++ b/src/openrct2/park/ParkFile.h @@ -11,7 +11,7 @@ namespace OpenRCT2 struct GameState_t; // Current version that is saved. - constexpr uint32_t PARK_FILE_CURRENT_VERSION = 33; + constexpr uint32_t PARK_FILE_CURRENT_VERSION = 34; // The minimum version that is forwards compatible with the current version. constexpr uint32_t PARK_FILE_MIN_VERSION = 33; diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h index f54e969390..0ccdce54a5 100644 --- a/src/openrct2/scripting/ScriptEngine.h +++ b/src/openrct2/scripting/ScriptEngine.h @@ -47,7 +47,7 @@ namespace OpenRCT2 namespace OpenRCT2::Scripting { - static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 93; + static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 94; // Versions marking breaking changes. static constexpr int32_t API_VERSION_33_PEEP_DEPRECATION = 33; diff --git a/src/openrct2/scripting/bindings/entity/ScPeep.hpp b/src/openrct2/scripting/bindings/entity/ScPeep.hpp index 2ccbb5f4d7..8cc4d4287a 100644 --- a/src/openrct2/scripting/bindings/entity/ScPeep.hpp +++ b/src/openrct2/scripting/bindings/entity/ScPeep.hpp @@ -41,6 +41,8 @@ namespace OpenRCT2::Scripting { "angry", PEEP_FLAGS_ANGRY }, { "iceCream", PEEP_FLAGS_ICE_CREAM }, { "hereWeAre", PEEP_FLAGS_HERE_WE_ARE }, + { "positionFrozen", PEEP_FLAGS_POSITION_FROZEN }, + { "animationFrozen", PEEP_FLAGS_ANIMATION_FROZEN }, }); class ScPeep : public ScEntity @@ -57,6 +59,7 @@ namespace OpenRCT2::Scripting dukglue_register_property(ctx, &ScPeep::peepType_get, nullptr, "peepType"); dukglue_register_property(ctx, &ScPeep::name_get, &ScPeep::name_set, "name"); dukglue_register_property(ctx, &ScPeep::destination_get, &ScPeep::destination_set, "destination"); + dukglue_register_property(ctx, &ScPeep::direction_get, &ScPeep::direction_set, "direction"); dukglue_register_property(ctx, &ScPeep::energy_get, &ScPeep::energy_set, "energy"); dukglue_register_property(ctx, &ScPeep::energyTarget_get, &ScPeep::energyTarget_set, "energyTarget"); dukglue_register_method(ctx, &ScPeep::getFlag, "getFlag"); @@ -138,6 +141,23 @@ namespace OpenRCT2::Scripting } } + uint8_t direction_get() const + { + auto peep = GetPeep(); + return peep != nullptr ? peep->PeepDirection : 0; + } + + void direction_set(const uint8_t value) + { + ThrowIfGameStateNotMutable(); + auto peep = GetPeep(); + if (peep != nullptr && value < kNumOrthogonalDirections) + { + peep->PeepDirection = value; + peep->Orientation = value << 3; + } + } + uint8_t energy_get() const { auto peep = GetPeep(); @@ -149,6 +169,7 @@ namespace OpenRCT2::Scripting auto peep = GetPeep(); if (peep != nullptr) { + value = std::clamp(value, kPeepMinEnergy, kPeepMaxEnergy); peep->Energy = value; } } @@ -164,6 +185,7 @@ namespace OpenRCT2::Scripting auto peep = GetPeep(); if (peep != nullptr) { + value = std::clamp(value, kPeepMinEnergy, kPeepMaxEnergyTarget); peep->EnergyTarget = value; } }