diff --git a/distribution/changelog.txt b/distribution/changelog.txt index ece0b0786b..3284c44000 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -11,6 +11,7 @@ - Change: [#20790] Default ride price set to free if park charges for entry. - Change: [#20880] Restore removed default coaster colours. - Change: [#21102] The money effect will now update even when the game is paused. +- Fix: [#5677] Balloons pass through the ground and objects. - Fix: [#12299] Placing ride entrances/exits ignores the Disable Clearance Checks cheat. - Fix: [#13473] Guests complain that the default Circus price is too high. - Fix: [#15293] TTF fonts don’t format correctly with OpenGL. diff --git a/src/openrct2/entity/Balloon.cpp b/src/openrct2/entity/Balloon.cpp index c4d6b3941c..eed498cd6d 100644 --- a/src/openrct2/entity/Balloon.cpp +++ b/src/openrct2/entity/Balloon.cpp @@ -47,12 +47,19 @@ void Balloon::Update() { frame = 0; } + + if (Collides()) + { + Pop(false); + return; + } + MoveTo({ x, y, z + 1 }); int32_t maxZ = 1967 - ((x ^ y) & 31); if (z >= maxZ) { - Pop(); + Pop(true); } } } @@ -67,7 +74,7 @@ void Balloon::Press() uint32_t random = ScenarioRand(); if ((Id.ToUnderlying() & 7) || (random & 0xFFFF) < 0x2000) { - Pop(); + Pop(true); } else { @@ -77,11 +84,14 @@ void Balloon::Press() } } -void Balloon::Pop() +void Balloon::Pop(bool playSound) { popped = 1; frame = 0; - OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::BalloonPop, { x, y, z }); + if (playSound) + { + OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::BalloonPop, { x, y, z }); + } } void Balloon::Create(const CoordsXYZ& balloonPos, int32_t colour, bool isPopped) @@ -122,3 +132,49 @@ void Balloon::Paint(PaintSession& session, int32_t imageDirection) const auto image = ImageId(imageId, colour); PaintAddImageAsParent(session, image, { 0, 0, z }, { 1, 1, 0 }); } + +bool Balloon::Collides() const +{ + const TileElement* tileElement = MapGetFirstElementAt(CoordsXY({ x, y })); + if (tileElement == nullptr) + return false; + do + { + // the balloon has height so we add some padding to prevent it clipping through things. + int32_t balloon_top = z + COORDS_Z_STEP * 2; + if (balloon_top == tileElement->GetBaseZ()) + { + return true; + } + + // check for situations where guests can drop a balloon inside a covered building + bool check_ceiling = tileElement->GetType() == TileElementType::Entrance; + if (tileElement->GetType() == TileElementType::Track) + { + const TrackElement* trackElement = tileElement->AsTrack(); + if (trackElement->GetRideType() == RIDE_TYPE_DODGEMS) + { + check_ceiling = true; + } + else + { + // all station platforms besides the plain and invisible ones are covered + auto style = GetRide(trackElement->GetRideIndex())->GetEntranceStyle(); + if (style != RCT12_STATION_STYLE_PLAIN && style != RCT12_STATION_STYLE_INVISIBLE) + { + check_ceiling = true; + } + } + } + + if (check_ceiling) + { + if (balloon_top > tileElement->GetBaseZ() && z < tileElement->GetClearanceZ()) + { + return true; + } + } + + } while (!(tileElement++)->IsLastForTile()); + return false; +} diff --git a/src/openrct2/entity/Balloon.h b/src/openrct2/entity/Balloon.h index 5d9c193f7c..f71896bcc2 100644 --- a/src/openrct2/entity/Balloon.h +++ b/src/openrct2/entity/Balloon.h @@ -24,8 +24,9 @@ struct Balloon : EntityBase uint8_t colour; static void Create(const CoordsXYZ& balloonPos, int32_t colour, bool isPopped); void Update(); - void Pop(); + void Pop(bool playSound); void Press(); void Serialise(DataSerialiser& stream); void Paint(PaintSession& session, int32_t imageDirection) const; + bool Collides() const; }; diff --git a/src/openrct2/interface/InteractiveConsole.cpp b/src/openrct2/interface/InteractiveConsole.cpp index b61ab6f5b9..a7d02b2129 100644 --- a/src/openrct2/interface/InteractiveConsole.cpp +++ b/src/openrct2/interface/InteractiveConsole.cpp @@ -34,6 +34,7 @@ #include "../drawing/Drawing.h" #include "../drawing/Font.h" #include "../drawing/Image.h" +#include "../entity/Balloon.h" #include "../entity/EntityList.h" #include "../entity/EntityRegistry.h" #include "../entity/Staff.h" @@ -1914,6 +1915,23 @@ static int32_t ConsoleCommandProfilerStop( return 0; } +static int32_t ConsoleSpawnBalloon(InteractiveConsole& console, const arguments_t& argv) +{ + if (argv.size() < 3) + { + console.WriteLineError("Need arguments: "); + return 1; + } + int32_t x = COORDS_XY_STEP * atof(argv[0].c_str()); + int32_t y = COORDS_XY_STEP * atof(argv[1].c_str()); + int32_t z = COORDS_Z_STEP * atof(argv[2].c_str()); + int32_t col = 28; + if (argv.size() > 3) + col = atoi(argv[3].c_str()); + Balloon::Create({ x, y, z }, col, false); + return 0; +} + using console_command_func = int32_t (*)(InteractiveConsole& console, const arguments_t& argv); struct ConsoleCommand { @@ -2006,6 +2024,7 @@ static constexpr ConsoleCommand console_command_table[] = { { "say", ConsoleCommandSay, "Say to other players.", "say " }, { "set", ConsoleCommandSet, "Sets the variable to the specified value.", "set " }, { "show_limits", ConsoleCommandShowLimits, "Shows the map data counts and limits.", "show_limits" }, + { "spawn_balloon", ConsoleSpawnBalloon, "Spawns a balloon.", "spawn_balloon " }, { "staff", ConsoleCommandStaff, "Staff management.", "staff " }, { "terminate", ConsoleCommandTerminate, "Calls std::terminate(), for testing purposes only.", "terminate" }, { "variables", ConsoleCommandVariables, "Lists all the variables that can be used with get and sometimes set.", diff --git a/src/openrct2/network/NetworkBase.cpp b/src/openrct2/network/NetworkBase.cpp index e7f2c39d26..0dd62758fb 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 "12" +#define NETWORK_STREAM_VERSION "13" #define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION diff --git a/src/openrct2/ride/Ride.cpp b/src/openrct2/ride/Ride.cpp index 2ba5d04b70..aaeaac51e8 100644 --- a/src/openrct2/ride/Ride.cpp +++ b/src/openrct2/ride/Ride.cpp @@ -5983,3 +5983,15 @@ ResultWithMessage Ride::ChangeStatusCreateVehicles(bool isApplying, const Coords return { true }; } + +uint8_t Ride::GetEntranceStyle() const +{ + if (const auto* stationObject = GetStationObject(); stationObject != nullptr) + { + return GetStationStyleFromIdentifier(stationObject->GetIdentifier()); + } + else + { + return RCT12_STATION_STYLE_PLAIN; + } +} diff --git a/src/openrct2/ride/Ride.h b/src/openrct2/ride/Ride.h index 3aba187622..87d56f5810 100644 --- a/src/openrct2/ride/Ride.h +++ b/src/openrct2/ride/Ride.h @@ -411,6 +411,8 @@ public: bool HasStation() const; bool FindTrackGap(const CoordsXYE& input, CoordsXYE* output) const; + + uint8_t GetEntranceStyle() const; }; void UpdateSpiralSlide(Ride& ride); void UpdateChairlift(Ride& ride); diff --git a/src/openrct2/ride/TrackDesign.cpp b/src/openrct2/ride/TrackDesign.cpp index 8e1749ccfe..a0c2566183 100644 --- a/src/openrct2/ride/TrackDesign.cpp +++ b/src/openrct2/ride/TrackDesign.cpp @@ -83,17 +83,6 @@ static bool _trackDesignPlaceStateEntranceExitPlaced{}; static void TrackDesignPreviewClearMap(); -static uint8_t TrackDesignGetEntranceStyle(const Ride& ride) -{ - const auto* stationObject = ride.GetStationObject(); - if (stationObject == nullptr) - return RCT12_STATION_STYLE_PLAIN; - - const auto objectName = stationObject->GetIdentifier(); - - return GetStationStyleFromIdentifier(objectName); -} - ResultWithMessage TrackDesign::CreateTrackDesign(TrackDesignState& tds, const Ride& ride) { type = ride.type; @@ -134,7 +123,7 @@ ResultWithMessage TrackDesign::CreateTrackDesign(TrackDesignState& tds, const Ri lift_hill_speed = ride.lift_hill_speed; num_circuits = ride.num_circuits; - entrance_style = TrackDesignGetEntranceStyle(ride); + entrance_style = ride.GetEntranceStyle(); max_speed = static_cast(ride.max_speed / 65536); average_speed = static_cast(ride.average_speed / 65536); ride_length = ride.GetTotalLength() / 65536;