diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4aa6576937..094c7bdcc1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -76,9 +76,9 @@ set(OPENMSX_VERSION "1.2.0")
set(OPENMSX_URL "https://github.com/OpenRCT2/OpenMusic/releases/download/v${OPENMSX_VERSION}/openmusic.zip")
set(OPENMSX_SHA1 "c1c9b030c83c12ff628e8ec72fcfe748291a5344")
-set(REPLAYS_VERSION "0.0.77")
+set(REPLAYS_VERSION "0.0.78")
set(REPLAYS_URL "https://github.com/OpenRCT2/replays/releases/download/v${REPLAYS_VERSION}/replays.zip")
-set(REPLAYS_SHA1 "65CA6D830789C074F575F15E3F9E2C28691C24F8")
+set(REPLAYS_SHA1 "31C5D07EED8481D5C6D57F9E4FE9443AAEDE7739")
option(FORCE32 "Force 32-bit build. It will add `-m32` to compiler flags.")
option(WITH_TESTS "Build tests")
diff --git a/data/language/en-GB.txt b/data/language/en-GB.txt
index 2f8fc0d2f1..30aa68feb1 100644
--- a/data/language/en-GB.txt
+++ b/data/language/en-GB.txt
@@ -3679,6 +3679,9 @@ STR_6573 :Invisible
STR_6574 :Void
STR_6575 :Allow special colour schemes
STR_6576 :Adds special colours to colour dropdown
+STR_6577 :Block brake speed
+STR_6578 :Set speed limit for block brakes. In block section mode, adjacent brakes with a slower speed are linked to the block brake.
+STR_6579 :Block brakes will be set to default speed when saved as track design
#############
# Scenarios #
diff --git a/distribution/changelog.txt b/distribution/changelog.txt
index cf96997341..9ab4299b52 100644
--- a/distribution/changelog.txt
+++ b/distribution/changelog.txt
@@ -1,5 +1,6 @@
0.4.5 (in development)
------------------------------------------------------------------------
+- Feature: [#18713] Block brakes have speed control and brakes slower than adjacent block brakes copy block brake speed when block brake open.
- Feature: [#19276] Add Powered Lifthill to Giga Coaster.
- Feature: [#19446] Add new color options to color dropdown.
- Feature: [#19547] Add large sloped turns to hybrid coaster and single rail coaster.
diff --git a/openrct2.proj b/openrct2.proj
index 9c4c3c6838..39a6dbe0c4 100644
--- a/openrct2.proj
+++ b/openrct2.proj
@@ -51,8 +51,8 @@
64EF7E0B7785602C91AEC66F005C035B05A2133B
https://github.com/OpenRCT2/OpenMusic/releases/download/v1.2.0/openmusic.zip
c1c9b030c83c12ff628e8ec72fcfe748291a5344
- https://github.com/OpenRCT2/replays/releases/download/v0.0.77/replays.zip
- 65CA6D830789C074F575F15E3F9E2C28691C24F8
+ https://github.com/OpenRCT2/replays/releases/download/v0.0.78/replays.zip
+ 31C5D07EED8481D5C6D57F9E4FE9443AAEDE7739
diff --git a/src/openrct2-ui/windows/RideConstruction.cpp b/src/openrct2-ui/windows/RideConstruction.cpp
index 14838a87cd..3c7a873438 100644
--- a/src/openrct2-ui/windows/RideConstruction.cpp
+++ b/src/openrct2-ui/windows/RideConstruction.cpp
@@ -1405,6 +1405,8 @@ public:
_currentTrackBankEnd = TRACK_BANK_NONE;
_currentTrackLiftHill &= ~CONSTRUCTION_LIFT_HILL_SELECTED;
break;
+ case TrackElemType::BlockBrakes:
+ _currentBrakeSpeed2 = kRCT2DefaultBlockBrakeSpeed;
}
_currentTrackCurve = trackPiece | RideConstructionSpecialPieceSelected;
WindowRideConstructionUpdateActiveElements();
diff --git a/src/openrct2/GameStateSnapshots.cpp b/src/openrct2/GameStateSnapshots.cpp
index 6f0bbcad1f..2bfb86510f 100644
--- a/src/openrct2/GameStateSnapshots.cpp
+++ b/src/openrct2/GameStateSnapshots.cpp
@@ -455,6 +455,7 @@ struct GameStateSnapshots final : public IGameStateSnapshots
COMPARE_FIELD(Vehicle, target_seat_rotation);
COMPARE_FIELD(Vehicle, BoatLocation.x);
COMPARE_FIELD(Vehicle, BoatLocation.y);
+ COMPARE_FIELD(Vehicle, BlockBrakeSpeed);
}
void CompareSpriteDataLitter(const Litter& spriteBase, const Litter& spriteCmp, GameStateSpriteChange& changeData) const
diff --git a/src/openrct2/actions/TrackPlaceAction.cpp b/src/openrct2/actions/TrackPlaceAction.cpp
index df2715671c..fbfaf0e20f 100644
--- a/src/openrct2/actions/TrackPlaceAction.cpp
+++ b/src/openrct2/actions/TrackPlaceAction.cpp
@@ -573,12 +573,16 @@ GameActions::Result TrackPlaceAction::Execute() const
case TrackElemType::SpinningTunnel:
MapAnimationCreate(MAP_ANIMATION_TYPE_TRACK_SPINNINGTUNNEL, CoordsXYZ{ mapLoc, trackElement->GetBaseZ() });
break;
+ case TrackElemType::Brakes:
+ trackElement->SetBrakeClosed(true);
+ break;
}
if (TrackTypeHasSpeedSetting(_trackType))
{
trackElement->SetBrakeBoosterSpeed(_brakeSpeed);
}
- else if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_LANDSCAPE_DOORS))
+
+ if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_LANDSCAPE_DOORS))
{
trackElement->SetDoorAState(LANDSCAPE_DOOR_CLOSED);
trackElement->SetDoorBState(LANDSCAPE_DOOR_CLOSED);
diff --git a/src/openrct2/localisation/StringIds.h b/src/openrct2/localisation/StringIds.h
index fd5335493c..950d14d41b 100644
--- a/src/openrct2/localisation/StringIds.h
+++ b/src/openrct2/localisation/StringIds.h
@@ -3977,6 +3977,10 @@ enum : uint16_t
STR_CHEAT_ALLOW_SPECIAL_COLOUR_SCHEMES = 6575,
STR_CHEAT_ALLOW_SPECIAL_COLOUR_SCHEMES_TIP = 6576,
+ STR_RIDE_CONSTRUCTION_BLOCK_BRAKE_SPEED = 6577,
+ STR_RIDE_CONSTRUCTION_BLOCK_BRAKE_SPEED_LIMIT_TIP = 6578,
+ STR_TRACK_DESIGN_BLOCK_BRAKE_SPEED_RESET = 6579,
+
// Have to include resource strings (from scenarios and objects) for the time being now that language is partially working
/* MAX_STR_COUNT = 32768 */ // MAX_STR_COUNT - upper limit for number of strings, not the current count strings
};
diff --git a/src/openrct2/network/NetworkBase.cpp b/src/openrct2/network/NetworkBase.cpp
index d9cd570809..e293da6707 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 "9"
+#define NETWORK_STREAM_VERSION "10"
#define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION
diff --git a/src/openrct2/park/ParkFile.cpp b/src/openrct2/park/ParkFile.cpp
index f2f904d566..b4746f148b 100644
--- a/src/openrct2/park/ParkFile.cpp
+++ b/src/openrct2/park/ParkFile.cpp
@@ -64,6 +64,8 @@
#include
#include
+constexpr const uint32_t BlockBrakeImprovementsVersion = 27;
+
using namespace OpenRCT2;
namespace OpenRCT2
@@ -1092,12 +1094,19 @@ namespace OpenRCT2
else if (it.element->GetType() == TileElementType::Track)
{
auto* trackElement = it.element->AsTrack();
+ auto trackType = trackElement->GetTrackType();
if (TrackTypeMustBeMadeInvisible(
- trackElement->GetRideType(), trackElement->GetTrackType(),
- os.GetHeader().TargetVersion))
+ trackElement->GetRideType(), trackType, os.GetHeader().TargetVersion))
{
it.element->SetInvisible(true);
}
+ if (os.GetHeader().TargetVersion < BlockBrakeImprovementsVersion)
+ {
+ if (trackType == TrackElemType::Brakes)
+ trackElement->SetBrakeClosed(true);
+ if (trackType == TrackElemType::BlockBrakes)
+ trackElement->SetBrakeBoosterSpeed(kRCT2DefaultBlockBrakeSpeed);
+ }
}
else if (
it.element->GetType() == TileElementType::SmallScenery && os.GetHeader().TargetVersion < 23)
@@ -2088,7 +2097,18 @@ namespace OpenRCT2
cs.ReadWrite(entity.scream_sound_id);
cs.ReadWrite(entity.TrackSubposition);
cs.ReadWrite(entity.NumLaps);
- cs.ReadWrite(entity.brake_speed);
+ if (cs.GetMode() == OrcaStream::Mode::READING && os.GetHeader().TargetVersion < BlockBrakeImprovementsVersion)
+ {
+ uint8_t brakeSpeed;
+ cs.ReadWrite(brakeSpeed);
+ if (entity.GetTrackType() == TrackElemType::BlockBrakes)
+ brakeSpeed = kRCT2DefaultBlockBrakeSpeed;
+ entity.brake_speed = brakeSpeed;
+ }
+ else
+ {
+ cs.ReadWrite(entity.brake_speed);
+ }
cs.ReadWrite(entity.lost_time_out);
cs.ReadWrite(entity.vertical_drop_countdown);
cs.ReadWrite(entity.var_D3);
@@ -2107,6 +2127,14 @@ namespace OpenRCT2
entity.SetFlag(VehicleFlags::Crashed);
}
}
+ if (cs.GetMode() == OrcaStream::Mode::READING && os.GetHeader().TargetVersion < BlockBrakeImprovementsVersion)
+ {
+ entity.BlockBrakeSpeed = kRCT2DefaultBlockBrakeSpeed;
+ }
+ else
+ {
+ cs.ReadWrite(entity.BlockBrakeSpeed);
+ }
}
template<> void ParkFile::ReadWriteEntity(OrcaStream& os, OrcaStream::ChunkStream& cs, Guest& guest)
diff --git a/src/openrct2/park/ParkFile.h b/src/openrct2/park/ParkFile.h
index 735f5b688c..977a10de69 100644
--- a/src/openrct2/park/ParkFile.h
+++ b/src/openrct2/park/ParkFile.h
@@ -9,10 +9,10 @@ struct ObjectRepositoryItem;
namespace OpenRCT2
{
// Current version that is saved.
- constexpr uint32_t PARK_FILE_CURRENT_VERSION = 26;
+ constexpr uint32_t PARK_FILE_CURRENT_VERSION = 27;
// The minimum version that is forwards compatible with the current version.
- constexpr uint32_t PARK_FILE_MIN_VERSION = 26;
+ constexpr uint32_t PARK_FILE_MIN_VERSION = 27;
// The minimum version that is backwards compatible with the current version.
// If this is increased beyond 0, uncomment the checks in ParkFile.cpp and Context.cpp!
diff --git a/src/openrct2/rct1/S4Importer.cpp b/src/openrct2/rct1/S4Importer.cpp
index b55902d8cc..0d46740fd2 100644
--- a/src/openrct2/rct1/S4Importer.cpp
+++ b/src/openrct2/rct1/S4Importer.cpp
@@ -1674,6 +1674,8 @@ namespace RCT1
// Skipping IsHighlighted()
auto trackType = dst2->GetTrackType();
+ // Brakes import as closed to preserve legacy behaviour
+ dst2->SetBrakeClosed(trackType == TrackElemType::Brakes);
if (TrackTypeHasSpeedSetting(trackType))
{
dst2->SetBrakeBoosterSpeed(src2->GetBrakeBoosterSpeed());
@@ -2817,6 +2819,7 @@ namespace RCT1
{
dst->SetFlag(VehicleFlags::Crashed);
}
+ dst->BlockBrakeSpeed = kRCT2DefaultBlockBrakeSpeed;
}
template<> void S4Importer::ImportEntity(const RCT12EntityBase& srcBase)
diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp
index 0088bce477..9bbbb40242 100644
--- a/src/openrct2/rct2/S6Importer.cpp
+++ b/src/openrct2/rct2/S6Importer.cpp
@@ -1440,11 +1440,17 @@ namespace RCT2
dst2->SetInverted(src2->IsInverted());
dst2->SetStationIndex(StationIndex::FromUnderlying(src2->GetStationIndex()));
dst2->SetHasGreenLight(src2->HasGreenLight());
- dst2->SetBrakeClosed(src2->BlockBrakeClosed());
+ // Brakes import as closed to preserve legacy behaviour
+ dst2->SetBrakeClosed(src2->BlockBrakeClosed() || (trackType == TrackElemType::Brakes));
dst2->SetIsIndestructible(src2->IsIndestructible());
// Skipping IsHighlighted()
- if (TrackTypeHasSpeedSetting(trackType))
+ // Import block brakes to keep legacy behaviour
+ if (trackType == TrackElemType::BlockBrakes)
+ {
+ dst2->SetBrakeBoosterSpeed(kRCT2DefaultBlockBrakeSpeed);
+ }
+ else if (TrackTypeHasSpeedSetting(trackType))
{
dst2->SetBrakeBoosterSpeed(src2->GetBrakeBoosterSpeed());
}
@@ -2009,6 +2015,10 @@ namespace RCT2
if (tileElement2 != nullptr)
dst->SetTrackType(TrackElemType::RotationControlToggle);
}
+ else if (src->GetTrackType() == TrackElemType::BlockBrakes)
+ {
+ dst->brake_speed = kRCT2DefaultBlockBrakeSpeed;
+ }
}
else
{
@@ -2075,6 +2085,7 @@ namespace RCT2
{
dst->SetFlag(VehicleFlags::Crashed);
}
+ dst->BlockBrakeSpeed = kRCT2DefaultBlockBrakeSpeed;
}
static uint32_t AdjustScenarioToCurrentTicks(const S6Data& s6, uint32_t tick)
diff --git a/src/openrct2/ride/Ride.cpp b/src/openrct2/ride/Ride.cpp
index 43dd98109d..ec4a0217c5 100644
--- a/src/openrct2/ride/Ride.cpp
+++ b/src/openrct2/ride/Ride.cpp
@@ -3050,13 +3050,17 @@ static void RideOpenBlockBrakes(const CoordsXYE& startElement)
auto trackType = currentElement.element->AsTrack()->GetTrackType();
switch (trackType)
{
+ case TrackElemType::BlockBrakes:
+ BlockBrakeSetLinkedBrakesClosed(
+ CoordsXYZ(currentElement.x, currentElement.y, currentElement.element->GetBaseZ()),
+ *currentElement.element->AsTrack(), false);
+ [[fallthrough]];
case TrackElemType::EndStation:
case TrackElemType::CableLiftHill:
case TrackElemType::Up25ToFlat:
case TrackElemType::Up60ToFlat:
case TrackElemType::DiagUp25ToFlat:
case TrackElemType::DiagUp60ToFlat:
- case TrackElemType::BlockBrakes:
currentElement.element->AsTrack()->SetBrakeClosed(false);
break;
}
@@ -3064,6 +3068,60 @@ static void RideOpenBlockBrakes(const CoordsXYE& startElement)
&& currentElement.element != startElement.element);
}
+/**
+ * Set the open status of brakes adjacent to the block brake
+ */
+void BlockBrakeSetLinkedBrakesClosed(const CoordsXYZ& vehicleTrackLocation, TrackElement& trackElement, bool isClosed)
+{
+ uint8_t brakeSpeed = trackElement.GetBrakeBoosterSpeed();
+
+ auto tileElement = reinterpret_cast(&trackElement);
+ auto location = vehicleTrackLocation;
+ TrackBeginEnd trackBeginEnd, slowTrackBeginEnd;
+ TileElement slowTileElement = *tileElement;
+ bool counter = true;
+ CoordsXY slowLocation = location;
+ do
+ {
+ if (!TrackBlockGetPrevious({ location, tileElement }, &trackBeginEnd))
+ {
+ return;
+ }
+ if (trackBeginEnd.begin_x == vehicleTrackLocation.x && trackBeginEnd.begin_y == vehicleTrackLocation.y
+ && tileElement == trackBeginEnd.begin_element)
+ {
+ return;
+ }
+
+ location.x = trackBeginEnd.end_x;
+ location.y = trackBeginEnd.end_y;
+ location.z = trackBeginEnd.begin_z;
+ tileElement = trackBeginEnd.begin_element;
+
+ if (trackBeginEnd.begin_element->AsTrack()->GetTrackType() == TrackElemType::Brakes)
+ {
+ trackBeginEnd.begin_element->AsTrack()->SetBrakeClosed(
+ (trackBeginEnd.begin_element->AsTrack()->GetBrakeBoosterSpeed() >= brakeSpeed) || isClosed);
+ }
+
+ // prevent infinite loop
+ counter = !counter;
+ if (counter)
+ {
+ TrackBlockGetPrevious({ slowLocation, &slowTileElement }, &slowTrackBeginEnd);
+ slowLocation.x = slowTrackBeginEnd.end_x;
+ slowLocation.y = slowTrackBeginEnd.end_y;
+ slowTileElement = *(slowTrackBeginEnd.begin_element);
+ if (slowLocation == location && slowTileElement.GetBaseZ() == tileElement->GetBaseZ()
+ && slowTileElement.GetType() == tileElement->GetType()
+ && slowTileElement.GetDirection() == tileElement->GetDirection())
+ {
+ return;
+ }
+ }
+ } while (trackBeginEnd.begin_element->AsTrack()->GetTrackType() == TrackElemType::Brakes);
+}
+
/**
*
* rct2: 0x006B4D26
@@ -3534,7 +3592,8 @@ ResultWithMessage Ride::CreateVehicles(const CoordsXYE& element, bool isApplying
{
CoordsXYE firstBlock{};
RideCreateVehiclesFindFirstBlock(*this, &firstBlock);
- MoveTrainsToBlockBrakes(firstBlock.element->AsTrack());
+ MoveTrainsToBlockBrakes(
+ { firstBlock.x, firstBlock.y, firstBlock.element->GetBaseZ() }, *firstBlock.element->AsTrack());
}
else
{
@@ -3567,7 +3626,7 @@ ResultWithMessage Ride::CreateVehicles(const CoordsXYE& element, bool isApplying
* preceding that block.
* rct2: 0x006DDF9C
*/
-void Ride::MoveTrainsToBlockBrakes(TrackElement* firstBlock)
+void Ride::MoveTrainsToBlockBrakes(const CoordsXYZ& firstBlockPosition, TrackElement& firstBlock)
{
for (int32_t i = 0; i < NumTrains; i++)
{
@@ -3578,7 +3637,8 @@ void Ride::MoveTrainsToBlockBrakes(TrackElement* firstBlock)
// At this point, all vehicles have state of MovingToEndOfStation, which slowly moves forward at a constant speed
// regardless of incline. The first vehicle stops at the station immediately, while all other vehicles seek forward
// until they reach a closed block brake. The block brake directly before the station is set to closed every frame
- // because the trains will open the block brake when the tail leaves the station.
+ // because the trains will open the block brake when the tail leaves the station. Brakes have no effect at this time, so
+ // do not set linked brakes when closing the first block.
train->UpdateTrackMotion(nullptr);
if (i == 0)
@@ -3597,7 +3657,7 @@ void Ride::MoveTrainsToBlockBrakes(TrackElement* firstBlock)
break;
}
- firstBlock->SetBrakeClosed(true);
+ firstBlock.SetBrakeClosed(true);
for (Vehicle* car = train; car != nullptr; car = GetEntity(car->next_vehicle_on_train))
{
car->velocity = 0;
@@ -3607,8 +3667,13 @@ void Ride::MoveTrainsToBlockBrakes(TrackElement* firstBlock)
}
} while (!(train->UpdateTrackMotion(nullptr) & VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_BLOCK_BRAKE));
- // All vehicles are in position, set the block brake directly before the station one last time
- firstBlock->SetBrakeClosed(true);
+ // All vehicles are in position, set the block brake directly before the station one last time and make sure the brakes
+ // are set appropriately
+ firstBlock.SetBrakeClosed(true);
+ if (firstBlock.GetTrackType() == TrackElemType::BlockBrakes)
+ {
+ BlockBrakeSetLinkedBrakesClosed(firstBlockPosition, firstBlock, true);
+ }
for (Vehicle* car = train; car != nullptr; car = GetEntity(car->next_vehicle_on_train))
{
car->ClearFlag(VehicleFlags::CollisionDisabled);
diff --git a/src/openrct2/ride/Ride.h b/src/openrct2/ride/Ride.h
index bc61a3c762..8a4c8b0dd7 100644
--- a/src/openrct2/ride/Ride.h
+++ b/src/openrct2/ride/Ride.h
@@ -304,7 +304,7 @@ private:
void Update();
void UpdateQueueLength(StationIndex stationIndex);
ResultWithMessage CreateVehicles(const CoordsXYE& element, bool isApplying);
- void MoveTrainsToBlockBrakes(TrackElement* firstBlock);
+ void MoveTrainsToBlockBrakes(const CoordsXYZ& firstBlockPosition, TrackElement& firstBlock);
money64 CalculateIncomePerHour() const;
void ChainQueues() const;
void ConstructMissingEntranceOrExit() const;
@@ -1067,6 +1067,8 @@ money64 RideEntranceExitPlaceGhost(
ResultWithMessage RideAreAllPossibleEntrancesAndExitsBuilt(const Ride& ride);
void RideFixBreakdown(Ride& ride, int32_t reliabilityIncreaseFactor);
+void BlockBrakeSetLinkedBrakesClosed(const CoordsXYZ& vehicleTrackLocation, TrackElement& tileElement, bool isOpen);
+
uint8_t RideEntryGetVehicleAtPosition(int32_t rideEntryIndex, int32_t numCarsPerTrain, int32_t position);
void RideUpdateVehicleColours(const Ride& ride);
diff --git a/src/openrct2/ride/Track.cpp b/src/openrct2/ride/Track.cpp
index ebcf61b5b7..8343c30dcf 100644
--- a/src/openrct2/ride/Track.cpp
+++ b/src/openrct2/ride/Track.cpp
@@ -655,7 +655,7 @@ bool TrackElementIsCovered(track_type_t trackElementType)
bool TrackTypeHasSpeedSetting(track_type_t trackType)
{
- return trackType == TrackElemType::Brakes || trackType == TrackElemType::Booster;
+ return trackType == TrackElemType::Brakes || trackType == TrackElemType::Booster || trackType == TrackElemType::BlockBrakes;
}
bool TrackTypeIsHelix(track_type_t trackType)
diff --git a/src/openrct2/ride/Track.h b/src/openrct2/ride/Track.h
index 51240c5519..aa77231528 100644
--- a/src/openrct2/ride/Track.h
+++ b/src/openrct2/ride/Track.h
@@ -18,7 +18,9 @@
constexpr const uint32_t RideConstructionSpecialPieceSelected = 0x10000;
-constexpr const int32_t BLOCK_BRAKE_BASE_SPEED = 0x20364;
+constexpr const uint8_t kRCT2DefaultBlockBrakeSpeed = 2;
+constexpr const int32_t kBlockBrakeBaseSpeed = 0x20364;
+constexpr const int32_t kBlockBrakeSpeedOffset = kBlockBrakeBaseSpeed - (kRCT2DefaultBlockBrakeSpeed << 16);
using track_type_t = uint16_t;
using roll_type_t = uint8_t;
diff --git a/src/openrct2/ride/TrackDesign.cpp b/src/openrct2/ride/TrackDesign.cpp
index e13c139b63..6a47357eed 100644
--- a/src/openrct2/ride/TrackDesign.cpp
+++ b/src/openrct2/ride/TrackDesign.cpp
@@ -182,6 +182,8 @@ ResultWithMessage TrackDesign::CreateTrackDesignTrack(TrackDesignState& tds, con
return { false, STR_TRACK_TOO_LARGE_OR_TOO_MUCH_SCENERY };
}
+ StringId warningMessage = STR_NONE;
+
RideGetStartOfTrack(&trackElement);
int32_t z = trackElement.element->GetBaseZ();
@@ -222,7 +224,8 @@ ResultWithMessage TrackDesign::CreateTrackDesignTrack(TrackDesignState& tds, con
track.type = trackElement.element->AsTrack()->GetTrackType();
uint8_t trackFlags;
- if (TrackTypeHasSpeedSetting(track.type))
+ // This if-else block only applies to td6. New track design format will always encode speed and seat rotation.
+ if (TrackTypeHasSpeedSetting(track.type) && track.type != TrackElemType::BlockBrakes)
{
trackFlags = trackElement.element->AsTrack()->GetBrakeBoosterSpeed() >> 1;
}
@@ -231,6 +234,13 @@ ResultWithMessage TrackDesign::CreateTrackDesignTrack(TrackDesignState& tds, con
trackFlags = trackElement.element->AsTrack()->GetSeatRotation();
}
+ // This warning will not apply to new track design format
+ if (track.type == TrackElemType::BlockBrakes
+ && trackElement.element->AsTrack()->GetBrakeBoosterSpeed() != kRCT2DefaultBlockBrakeSpeed)
+ {
+ warningMessage = STR_TRACK_DESIGN_BLOCK_BRAKE_SPEED_RESET;
+ }
+
if (trackElement.element->AsTrack()->HasChain())
trackFlags |= RCT12_TRACK_ELEMENT_TYPE_FLAG_CHAIN_LIFT;
trackFlags |= trackElement.element->AsTrack()->GetColourScheme() << 4;
@@ -349,7 +359,7 @@ ResultWithMessage TrackDesign::CreateTrackDesignTrack(TrackDesignState& tds, con
space_required_x = ((tds.PreviewMax.x - tds.PreviewMin.x) / 32) + 1;
space_required_y = ((tds.PreviewMax.y - tds.PreviewMin.y) / 32) + 1;
- return { true };
+ return { true, warningMessage };
}
ResultWithMessage TrackDesign::CreateTrackDesignMaze(TrackDesignState& tds, const Ride& ride)
@@ -1617,7 +1627,17 @@ static GameActions::Result TrackDesignPlaceRide(TrackDesignState& tds, TrackDesi
// di
int16_t tempZ = newCoords.z - trackCoordinates->z_begin;
uint32_t trackColour = (track.flags >> 4) & 0x3;
- uint32_t brakeSpeed = (track.flags & 0x0F) * 2;
+ uint32_t brakeSpeed;
+ // RCT2-created track designs write brake speed to all tracks; block brake speed must be treated as
+ // garbage data.
+ if (trackType == TrackElemType::BlockBrakes)
+ {
+ brakeSpeed = kRCT2DefaultBlockBrakeSpeed;
+ }
+ else
+ {
+ brakeSpeed = (track.flags & 0x0F) * 2;
+ }
uint32_t seatRotation = track.flags & 0x0F;
int32_t liftHillAndAlternativeState = 0;
diff --git a/src/openrct2/ride/Vehicle.cpp b/src/openrct2/ride/Vehicle.cpp
index 53b79184ff..fde8843af3 100644
--- a/src/openrct2/ride/Vehicle.cpp
+++ b/src/openrct2/ride/Vehicle.cpp
@@ -6004,15 +6004,14 @@ void Vehicle::ApplyNonStopBlockBrake()
if (velocity >= 0)
{
// If the vehicle is below the speed limit
- if (velocity <= BLOCK_BRAKE_BASE_SPEED)
+ if (velocity <= kBlockBrakeBaseSpeed)
{
// Boost it to the fixed block brake speed
- velocity = BLOCK_BRAKE_BASE_SPEED;
+ velocity = kBlockBrakeBaseSpeed;
acceleration = 0;
}
- else
+ else if (velocity > (brake_speed << 16) + kBlockBrakeSpeedOffset)
{
- // Slow it down till the fixed block brake speed
velocity -= velocity >> 4;
acceleration = 0;
}
@@ -6185,12 +6184,14 @@ static void block_brakes_open_previous_section(
MapInvalidateElement(location, reinterpret_cast(trackElement));
auto trackType = trackElement->GetTrackType();
- if (trackType == TrackElemType::BlockBrakes || trackType == TrackElemType::EndStation)
+ if (trackType == TrackElemType::EndStation)
{
- if (ride.IsBlockSectioned())
- {
- OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::BlockBrakeClose, location);
- }
+ OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::BlockBrakeClose, location);
+ }
+ else if (trackType == TrackElemType::BlockBrakes)
+ {
+ OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::BlockBrakeClose, location);
+ BlockBrakeSetLinkedBrakesClosed(location, *trackElement, false);
}
}
@@ -6981,7 +6982,7 @@ void Vehicle::UpdateLandscapeDoorBackwards() const
static void vehicle_update_play_water_splash_sound()
{
- if (_vehicleVelocityF64E08 <= BLOCK_BRAKE_BASE_SPEED)
+ if (_vehicleVelocityF64E08 <= kBlockBrakeBaseSpeed)
{
return;
}
@@ -7377,6 +7378,60 @@ void Vehicle::Sub6DBF3E()
}
}
+/**
+ * Determine whether to use block brake speed or brake speed. If block brake is closed or no block brake present, use the
+ * brake's speed; if block brake is open, use maximum of brake speed or block brake speed.
+ */
+uint8_t Vehicle::ChooseBrakeSpeed() const
+{
+ if (GetTrackType() != TrackElemType::Brakes)
+ return brake_speed;
+ auto trackElement = MapGetTrackElementAtOfTypeSeq(TrackLocation, GetTrackType(), 0);
+ if (trackElement != nullptr)
+ {
+ if (trackElement->AsTrack()->IsBrakeClosed())
+ return brake_speed;
+ else
+ return std::max(brake_speed, BlockBrakeSpeed);
+ }
+ return brake_speed;
+}
+
+/**
+ * Populate the vehicle's brake_speed and BlockBrakeSpeed values.
+ */
+void Vehicle::PopulateBrakeSpeed(const CoordsXYZ& vehicleTrackLocation, TrackElement& brake)
+{
+ auto trackSpeed = brake.GetBrakeBoosterSpeed();
+ brake_speed = trackSpeed;
+ if (brake.GetTrackType() != TrackElemType::Brakes)
+ {
+ BlockBrakeSpeed = trackSpeed;
+ return;
+ }
+ // As soon as feasible, encode block brake speed into track element so the lookforward can be skipped here.
+
+ CoordsXYE output = CoordsXYE(vehicleTrackLocation.x, vehicleTrackLocation.y, reinterpret_cast(&brake));
+ int32_t outputZ = vehicleTrackLocation.z;
+ uint16_t timeoutCount = 256;
+ do
+ {
+ if (output.element->AsTrack()->GetTrackType() == TrackElemType::BlockBrakes)
+ {
+ BlockBrakeSpeed = output.element->AsTrack()->GetBrakeBoosterSpeed();
+ return;
+ }
+ if (output.element->AsTrack()->GetTrackType() != TrackElemType::Brakes)
+ {
+ break;
+ }
+ timeoutCount--;
+ } while (TrackBlockGetNext(&output, &output, &outputZ, nullptr) && timeoutCount);
+
+ // If block brake is not found, use the track's speed
+ BlockBrakeSpeed = trackSpeed;
+}
+
/**
*
* rct2: 0x006DB08C
@@ -7413,6 +7468,10 @@ bool Vehicle::UpdateTrackMotionForwardsGetNewTrack(uint16_t trackType, const Rid
}
MapInvalidateElement(TrackLocation, tileElement);
block_brakes_open_previous_section(curRide, TrackLocation, tileElement);
+ if (trackType == TrackElemType::BlockBrakes)
+ {
+ BlockBrakeSetLinkedBrakesClosed(TrackLocation, *tileElement->AsTrack(), true);
+ }
}
}
@@ -7542,7 +7601,7 @@ bool Vehicle::UpdateTrackMotionForwardsGetNewTrack(uint16_t trackType, const Rid
}
SetTrackDirection(location.direction);
SetTrackType(trackType);
- brake_speed = tileElement->AsTrack()->GetBrakeBoosterSpeed();
+ PopulateBrakeSpeed(TrackLocation, *tileElement->AsTrack());
if (trackType == TrackElemType::OnRidePhoto)
{
trigger_on_ride_photo(TrackLocation, tileElement);
@@ -7589,8 +7648,9 @@ Loc6DAEB9:
&& curRide.breakdown_reason_pending == BREAKDOWN_BRAKES_FAILURE;
if (!hasBrakesFailure || curRide.mechanic_status == RIDE_MECHANIC_STATUS_HAS_FIXED_STATION_BRAKES)
{
- auto brakeSpeed = brake_speed << 16;
- if (brakeSpeed < _vehicleVelocityF64E08)
+ auto brakeSpeed = ChooseBrakeSpeed();
+
+ if ((brakeSpeed << 16) < _vehicleVelocityF64E08)
{
acceleration = -_vehicleVelocityF64E08 * 16;
}
@@ -7947,7 +8007,7 @@ bool Vehicle::UpdateTrackMotionBackwardsGetNewTrack(uint16_t trackType, const Ri
direction &= 3;
SetTrackType(trackType);
SetTrackDirection(direction);
- brake_speed = tileElement->AsTrack()->GetBrakeBoosterSpeed();
+ PopulateBrakeSpeed(TrackLocation, *tileElement->AsTrack());
// There are two bytes before the move info list
uint16_t trackTotalProgress = GetTrackProgress();
@@ -7978,7 +8038,9 @@ bool Vehicle::UpdateTrackMotionBackwards(const CarEntry* carEntry, const Ride& c
if (trackType == TrackElemType::Brakes)
{
- if (-(brake_speed << 16) > _vehicleVelocityF64E08)
+ auto brakeSpeed = ChooseBrakeSpeed();
+
+ if (-(brakeSpeed << 16) > _vehicleVelocityF64E08)
{
acceleration = _vehicleVelocityF64E08 * -16;
}
@@ -9400,4 +9462,5 @@ void Vehicle::Serialise(DataSerialiser& stream)
stream << seat_rotation;
stream << target_seat_rotation;
stream << BoatLocation;
+ stream << BlockBrakeSpeed;
}
diff --git a/src/openrct2/ride/Vehicle.h b/src/openrct2/ride/Vehicle.h
index 55fc833ecf..a3f98f6ac1 100644
--- a/src/openrct2/ride/Vehicle.h
+++ b/src/openrct2/ride/Vehicle.h
@@ -210,6 +210,7 @@ struct Vehicle : EntityBase
uint8_t seat_rotation;
uint8_t target_seat_rotation;
CoordsXY BoatLocation;
+ uint8_t BlockBrakeSpeed;
constexpr bool IsHead() const
{
@@ -377,6 +378,8 @@ private:
void UpdateLandscapeDoor() const;
void UpdateLandscapeDoorBackwards() const;
int32_t CalculateRiderBraking() const;
+ uint8_t ChooseBrakeSpeed() const;
+ void PopulateBrakeSpeed(const CoordsXYZ& vehicleTrackLocation, TrackElement& brake);
void Loc6DCE02(const Ride& curRide);
};