2021-07-27 17:21:03 +02:00
|
|
|
/*****************************************************************************
|
|
|
|
* Copyright (c) 2014-2021 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.
|
|
|
|
*****************************************************************************/
|
|
|
|
|
2021-12-18 19:50:29 +01:00
|
|
|
#include "RideConstruction.h"
|
|
|
|
|
2021-07-27 17:21:03 +02:00
|
|
|
#include "../Context.h"
|
|
|
|
#include "../Input.h"
|
|
|
|
#include "../actions/RideEntranceExitRemoveAction.h"
|
|
|
|
#include "../actions/RideSetSettingAction.h"
|
|
|
|
#include "../actions/RideSetStatusAction.h"
|
|
|
|
#include "../actions/RideSetVehicleAction.h"
|
|
|
|
#include "../actions/TrackRemoveAction.h"
|
|
|
|
#include "../common.h"
|
2021-11-24 15:48:33 +01:00
|
|
|
#include "../entity/EntityList.h"
|
2021-11-24 15:58:01 +01:00
|
|
|
#include "../entity/EntityRegistry.h"
|
2021-11-25 22:47:24 +01:00
|
|
|
#include "../entity/Staff.h"
|
2021-07-27 17:21:03 +02:00
|
|
|
#include "../interface/Window.h"
|
|
|
|
#include "../localisation/Date.h"
|
2021-12-12 00:06:06 +01:00
|
|
|
#include "../localisation/Formatter.h"
|
2021-07-27 17:21:03 +02:00
|
|
|
#include "../localisation/Localisation.h"
|
|
|
|
#include "../network/network.h"
|
|
|
|
#include "../paint/VirtualFloor.h"
|
|
|
|
#include "../ui/UiContext.h"
|
|
|
|
#include "../ui/WindowManager.h"
|
|
|
|
#include "../util/Util.h"
|
|
|
|
#include "../windows/Intent.h"
|
|
|
|
#include "../world/Banner.h"
|
|
|
|
#include "../world/Climate.h"
|
|
|
|
#include "../world/Entrance.h"
|
|
|
|
#include "../world/Footpath.h"
|
|
|
|
#include "../world/Location.hpp"
|
|
|
|
#include "../world/Map.h"
|
|
|
|
#include "../world/MapAnimation.h"
|
|
|
|
#include "../world/Park.h"
|
|
|
|
#include "../world/Scenery.h"
|
2021-11-29 12:09:58 +01:00
|
|
|
#include "../world/TileElementsView.h"
|
2021-07-27 17:21:03 +02:00
|
|
|
#include "Ride.h"
|
|
|
|
#include "RideData.h"
|
|
|
|
#include "Track.h"
|
|
|
|
#include "TrackData.h"
|
2021-08-02 22:02:06 +02:00
|
|
|
#include "TrainManager.h"
|
2021-07-27 17:21:03 +02:00
|
|
|
#include "Vehicle.h"
|
|
|
|
|
2021-08-22 17:18:38 +02:00
|
|
|
using namespace OpenRCT2::TrackMetaData;
|
2021-07-27 17:21:03 +02:00
|
|
|
bool gGotoStartPlacementMode = false;
|
|
|
|
|
|
|
|
money16 gTotalRideValueForMoney;
|
|
|
|
|
|
|
|
money32 _currentTrackPrice;
|
|
|
|
|
|
|
|
uint32_t _currentTrackCurve;
|
2021-08-11 17:51:59 +02:00
|
|
|
RideConstructionState _rideConstructionState;
|
2022-01-19 14:17:11 +01:00
|
|
|
RideId _currentRideIndex;
|
2021-07-27 17:21:03 +02:00
|
|
|
|
|
|
|
CoordsXYZ _currentTrackBegin;
|
|
|
|
|
|
|
|
uint8_t _currentTrackPieceDirection;
|
|
|
|
track_type_t _currentTrackPieceType;
|
|
|
|
uint8_t _currentTrackSelectionFlags;
|
|
|
|
uint32_t _rideConstructionNextArrowPulse = 0;
|
|
|
|
uint8_t _currentTrackSlopeEnd;
|
|
|
|
uint8_t _currentTrackBankEnd;
|
|
|
|
uint8_t _currentTrackLiftHill;
|
|
|
|
uint8_t _currentTrackAlternative;
|
|
|
|
track_type_t _selectedTrackType;
|
|
|
|
|
|
|
|
uint8_t _previousTrackBankEnd;
|
|
|
|
uint8_t _previousTrackSlopeEnd;
|
|
|
|
|
|
|
|
CoordsXYZ _previousTrackPiece;
|
|
|
|
|
|
|
|
uint8_t _currentBrakeSpeed2;
|
|
|
|
uint8_t _currentSeatRotationAngle;
|
|
|
|
|
|
|
|
CoordsXYZD _unkF440C5;
|
|
|
|
|
2021-08-15 22:54:43 +02:00
|
|
|
ObjectEntryIndex gLastEntranceStyle;
|
2021-07-27 17:21:03 +02:00
|
|
|
|
|
|
|
uint8_t gRideEntranceExitPlaceType;
|
2022-01-19 14:17:11 +01:00
|
|
|
RideId gRideEntranceExitPlaceRideIndex;
|
2021-07-27 17:21:03 +02:00
|
|
|
StationIndex gRideEntranceExitPlaceStationIndex;
|
2021-08-11 17:51:59 +02:00
|
|
|
RideConstructionState gRideEntranceExitPlacePreviousRideConstructionState;
|
2021-07-27 17:21:03 +02:00
|
|
|
Direction gRideEntranceExitPlaceDirection;
|
|
|
|
|
|
|
|
using namespace OpenRCT2;
|
2021-08-22 15:50:44 +02:00
|
|
|
using namespace OpenRCT2::TrackMetaData;
|
2021-07-27 17:21:03 +02:00
|
|
|
|
|
|
|
static int32_t ride_check_if_construction_allowed(Ride* ride)
|
|
|
|
{
|
|
|
|
Formatter ft;
|
|
|
|
rct_ride_entry* rideEntry = ride->GetRideEntry();
|
|
|
|
if (rideEntry == nullptr)
|
|
|
|
{
|
|
|
|
context_show_error(STR_INVALID_RIDE_TYPE, STR_CANT_EDIT_INVALID_RIDE_TYPE, ft);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
|
|
|
|
{
|
|
|
|
ft.Increment(6);
|
|
|
|
ride->FormatNameTo(ft);
|
|
|
|
context_show_error(STR_CANT_START_CONSTRUCTION_ON, STR_HAS_BROKEN_DOWN_AND_REQUIRES_FIXING, ft);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ride->status != RideStatus::Closed && ride->status != RideStatus::Simulating)
|
|
|
|
{
|
|
|
|
ft.Increment(6);
|
|
|
|
ride->FormatNameTo(ft);
|
|
|
|
context_show_error(STR_CANT_START_CONSTRUCTION_ON, STR_MUST_BE_CLOSED_FIRST, ft);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2022-01-19 14:17:11 +01:00
|
|
|
static rct_window* ride_create_or_find_construction_window(RideId rideIndex)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
auto windowManager = GetContext()->GetUiContext()->GetWindowManager();
|
|
|
|
auto intent = Intent(INTENT_ACTION_RIDE_CONSTRUCTION_FOCUS);
|
2022-01-19 15:54:57 +01:00
|
|
|
intent.putExtra(INTENT_EXTRA_RIDE_ID, rideIndex.ToUnderlying());
|
2021-07-27 17:21:03 +02:00
|
|
|
windowManager->BroadcastIntent(intent);
|
|
|
|
return window_find_by_class(WC_RIDE_CONSTRUCTION);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006B4857
|
|
|
|
*/
|
|
|
|
void ride_construct(Ride* ride)
|
|
|
|
{
|
|
|
|
CoordsXYE trackElement;
|
|
|
|
if (ride_try_get_origin_element(ride, &trackElement))
|
|
|
|
{
|
|
|
|
ride_find_track_gap(ride, &trackElement, &trackElement);
|
|
|
|
|
|
|
|
rct_window* w = window_get_main();
|
|
|
|
if (w != nullptr && ride_modify(&trackElement))
|
|
|
|
window_scroll_to_location(w, { trackElement, trackElement.element->GetBaseZ() });
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ride_initialise_construction_window(ride);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006DD4D5
|
|
|
|
*/
|
|
|
|
static void ride_remove_cable_lift(Ride* ride)
|
|
|
|
{
|
|
|
|
if (ride->lifecycle_flags & RIDE_LIFECYCLE_CABLE_LIFT)
|
|
|
|
{
|
|
|
|
ride->lifecycle_flags &= ~RIDE_LIFECYCLE_CABLE_LIFT;
|
2022-02-12 22:31:06 +01:00
|
|
|
auto spriteIndex = ride->cable_lift;
|
2021-07-27 17:21:03 +02:00
|
|
|
do
|
|
|
|
{
|
|
|
|
Vehicle* vehicle = GetEntity<Vehicle>(spriteIndex);
|
|
|
|
if (vehicle == nullptr)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
vehicle->Invalidate();
|
|
|
|
spriteIndex = vehicle->next_vehicle_on_train;
|
2021-11-24 14:37:47 +01:00
|
|
|
EntityRemove(vehicle);
|
2022-02-12 22:31:06 +01:00
|
|
|
} while (!spriteIndex.IsNull());
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006DD506
|
|
|
|
*/
|
2021-08-02 22:02:06 +02:00
|
|
|
void Ride::RemoveVehicles()
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-08-02 22:02:06 +02:00
|
|
|
if (lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-08-02 22:02:06 +02:00
|
|
|
lifecycle_flags &= ~RIDE_LIFECYCLE_ON_TRACK;
|
|
|
|
lifecycle_flags &= ~(RIDE_LIFECYCLE_TEST_IN_PROGRESS | RIDE_LIFECYCLE_HAS_STALLED_VEHICLE);
|
2021-07-27 17:21:03 +02:00
|
|
|
|
2022-02-01 21:59:48 +01:00
|
|
|
for (size_t i = 0; i <= OpenRCT2::Limits::MaxTrainsPerRide; i++)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2022-02-12 22:31:06 +01:00
|
|
|
auto spriteIndex = vehicles[i];
|
|
|
|
while (!spriteIndex.IsNull())
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
Vehicle* vehicle = GetEntity<Vehicle>(spriteIndex);
|
|
|
|
if (vehicle == nullptr)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
vehicle->Invalidate();
|
|
|
|
spriteIndex = vehicle->next_vehicle_on_train;
|
2021-11-24 14:37:47 +01:00
|
|
|
EntityRemove(vehicle);
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
|
2022-02-12 22:31:06 +01:00
|
|
|
vehicles[i] = EntityId::GetNull();
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
|
2022-02-01 21:59:48 +01:00
|
|
|
for (size_t i = 0; i < OpenRCT2::Limits::MaxStationsPerRide; i++)
|
2021-08-02 22:02:06 +02:00
|
|
|
stations[i].TrainAtStation = RideStation::NO_TRAIN;
|
|
|
|
|
|
|
|
// Also clean up orphaned vehicles for good measure.
|
|
|
|
for (auto* vehicle : TrainManager::View())
|
|
|
|
{
|
|
|
|
if (vehicle->ride == id)
|
|
|
|
{
|
|
|
|
vehicle->Invalidate();
|
2021-11-24 14:37:47 +01:00
|
|
|
EntityRemove(vehicle);
|
2021-08-02 22:02:06 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006DD4AC
|
|
|
|
*/
|
|
|
|
void ride_clear_for_construction(Ride* ride)
|
|
|
|
{
|
|
|
|
ride->measurement = {};
|
|
|
|
|
|
|
|
ride->lifecycle_flags &= ~(RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN);
|
|
|
|
ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST;
|
|
|
|
|
|
|
|
// Open circuit rides will go directly into building mode (creating ghosts) where it would normally clear the stats,
|
|
|
|
// however this causes desyncs since it's directly run from the window and other clients would not get it.
|
|
|
|
// To prevent these problems, unconditionally invalidate the test results on all clients in multiplayer games.
|
|
|
|
if (network_get_mode() != NETWORK_MODE_NONE)
|
|
|
|
{
|
|
|
|
invalidate_test_results(ride);
|
|
|
|
}
|
|
|
|
|
|
|
|
ride_remove_cable_lift(ride);
|
2021-08-02 22:02:06 +02:00
|
|
|
ride->RemoveVehicles();
|
2021-07-27 17:21:03 +02:00
|
|
|
ride_clear_blocked_tiles(ride);
|
|
|
|
|
2022-01-19 15:54:57 +01:00
|
|
|
auto w = window_find_by_number(WC_RIDE, ride->id.ToUnderlying());
|
2021-07-27 17:21:03 +02:00
|
|
|
if (w != nullptr)
|
|
|
|
window_event_resize_call(w);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006664DF
|
|
|
|
*/
|
2021-12-12 12:18:12 +01:00
|
|
|
void Ride::RemovePeeps()
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
// Find first station
|
2021-12-12 12:18:12 +01:00
|
|
|
auto stationIndex = ride_get_first_valid_station_start(this);
|
2021-07-27 17:21:03 +02:00
|
|
|
|
|
|
|
// Get exit position and direction
|
|
|
|
auto exitPosition = CoordsXYZD{ 0, 0, 0, INVALID_DIRECTION };
|
2022-01-26 13:28:19 +01:00
|
|
|
if (!stationIndex.IsNull())
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2022-01-28 15:57:28 +01:00
|
|
|
auto location = GetStation(stationIndex).Exit.ToCoordsXYZD();
|
2021-09-09 01:48:53 +02:00
|
|
|
if (!location.IsNull())
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
auto direction = direction_reverse(location.direction);
|
|
|
|
exitPosition = location;
|
|
|
|
exitPosition.x += (DirectionOffsets[direction].x * 20) + COORDS_XY_HALF_TILE;
|
|
|
|
exitPosition.y += (DirectionOffsets[direction].y * 20) + COORDS_XY_HALF_TILE;
|
|
|
|
exitPosition.z += 2;
|
|
|
|
|
|
|
|
// Reverse direction
|
|
|
|
exitPosition.direction = direction_reverse(exitPosition.direction);
|
|
|
|
|
|
|
|
exitPosition.direction *= 8;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Place all the guests at exit
|
|
|
|
for (auto peep : EntityList<Guest>())
|
|
|
|
{
|
|
|
|
if (peep->State == PeepState::QueuingFront || peep->State == PeepState::EnteringRide
|
|
|
|
|| peep->State == PeepState::LeavingRide || peep->State == PeepState::OnRide)
|
|
|
|
{
|
2021-12-12 12:18:12 +01:00
|
|
|
if (peep->CurrentRide != id)
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
peep_decrement_num_riders(peep);
|
|
|
|
if (peep->State == PeepState::QueuingFront && peep->RideSubState == PeepRideSubState::AtEntrance)
|
|
|
|
peep->RemoveFromQueue();
|
|
|
|
|
|
|
|
if (exitPosition.direction == INVALID_DIRECTION)
|
|
|
|
{
|
|
|
|
CoordsXYZ newLoc = { peep->NextLoc.ToTileCentre(), peep->NextLoc.z };
|
|
|
|
if (peep->GetNextIsSloped())
|
|
|
|
newLoc.z += COORDS_Z_STEP;
|
|
|
|
newLoc.z++;
|
|
|
|
peep->MoveTo(newLoc);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
peep->MoveTo(exitPosition);
|
|
|
|
peep->sprite_direction = exitPosition.direction;
|
|
|
|
}
|
|
|
|
|
|
|
|
peep->State = PeepState::Falling;
|
|
|
|
peep->SwitchToSpecialSprite(0);
|
|
|
|
|
|
|
|
peep->Happiness = std::min(peep->Happiness, peep->HappinessTarget) / 2;
|
|
|
|
peep->HappinessTarget = peep->Happiness;
|
|
|
|
peep->WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_STATS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Place all the staff at exit
|
|
|
|
for (auto peep : EntityList<Staff>())
|
|
|
|
{
|
|
|
|
if (peep->State == PeepState::Fixing || peep->State == PeepState::Inspecting)
|
|
|
|
{
|
2021-12-12 12:18:12 +01:00
|
|
|
if (peep->CurrentRide != id)
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if (exitPosition.direction == INVALID_DIRECTION)
|
|
|
|
{
|
|
|
|
CoordsXYZ newLoc = { peep->NextLoc.ToTileCentre(), peep->NextLoc.z };
|
|
|
|
if (peep->GetNextIsSloped())
|
|
|
|
newLoc.z += COORDS_Z_STEP;
|
|
|
|
newLoc.z++;
|
|
|
|
peep->MoveTo(newLoc);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
peep->MoveTo(exitPosition);
|
|
|
|
peep->sprite_direction = exitPosition.direction;
|
|
|
|
}
|
|
|
|
|
|
|
|
peep->State = PeepState::Falling;
|
|
|
|
peep->SwitchToSpecialSprite(0);
|
|
|
|
|
|
|
|
peep->WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_STATS;
|
|
|
|
}
|
|
|
|
}
|
2021-12-12 12:18:12 +01:00
|
|
|
num_riders = 0;
|
|
|
|
slide_in_use = 0;
|
|
|
|
window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN;
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void ride_clear_blocked_tiles(Ride* ride)
|
|
|
|
{
|
2021-12-17 19:25:46 +01:00
|
|
|
for (TileCoordsXY tilePos = {}; tilePos.x < gMapSize.x; ++tilePos.x)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-12-17 19:25:46 +01:00
|
|
|
for (tilePos.y = 0; tilePos.y < gMapSize.y; ++tilePos.y)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-11-29 12:09:58 +01:00
|
|
|
for (auto* trackElement : TileElementsView<TrackElement>(tilePos.ToCoordsXY()))
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2022-01-31 23:05:26 +01:00
|
|
|
if (trackElement->GetRideIndex() != ride->id)
|
|
|
|
continue;
|
|
|
|
|
2021-11-29 12:09:58 +01:00
|
|
|
// Unblock footpath element that is at same position
|
|
|
|
auto* footpathElement = map_get_footpath_element(
|
|
|
|
TileCoordsXYZ{ tilePos, trackElement->base_height }.ToCoordsXYZ());
|
|
|
|
|
|
|
|
if (footpathElement == nullptr)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
footpathElement->AsPath()->SetIsBlockedByVehicle(false);
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the origin track element (sequence 0). Seems to do more than that though and even invalidates track.
|
|
|
|
* rct2: 0x006C683D
|
|
|
|
* ax : x
|
|
|
|
* bx : direction << 8, type
|
|
|
|
* cx : y
|
|
|
|
* dx : z
|
|
|
|
* si : extra_params
|
|
|
|
* di : output_element
|
|
|
|
* bp : flags
|
|
|
|
*/
|
2021-09-02 04:00:18 +02:00
|
|
|
std::optional<CoordsXYZ> GetTrackElementOriginAndApplyChanges(
|
2021-07-27 17:21:03 +02:00
|
|
|
const CoordsXYZD& location, track_type_t type, uint16_t extra_params, TileElement** output_element, uint16_t flags)
|
|
|
|
{
|
|
|
|
// Find the relevant track piece, prefer sequence 0 (this ensures correct behaviour for diagonal track pieces)
|
|
|
|
auto trackElement = map_get_track_element_at_of_type_seq(location, type, 0);
|
|
|
|
if (trackElement == nullptr)
|
|
|
|
{
|
|
|
|
trackElement = map_get_track_element_at_of_type(location, type);
|
|
|
|
if (trackElement == nullptr)
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Possibly z should be & 0xF8
|
2021-08-27 23:49:32 +02:00
|
|
|
const auto& ted = GetTrackElementDescriptor(type);
|
|
|
|
const auto* trackBlock = ted.Block;
|
2021-07-27 17:21:03 +02:00
|
|
|
if (trackBlock == nullptr)
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
// Now find all the elements that belong to this track piece
|
|
|
|
int32_t sequence = trackElement->GetSequenceIndex();
|
|
|
|
uint8_t mapDirection = trackElement->GetDirection();
|
|
|
|
|
|
|
|
CoordsXY offsets = { trackBlock[sequence].x, trackBlock[sequence].y };
|
|
|
|
CoordsXY newCoords = location;
|
|
|
|
newCoords += offsets.Rotate(direction_reverse(mapDirection));
|
|
|
|
|
|
|
|
auto retCoordsXYZ = CoordsXYZ{ newCoords.x, newCoords.y, location.z - trackBlock[sequence].z };
|
|
|
|
|
|
|
|
int32_t start_z = retCoordsXYZ.z;
|
|
|
|
retCoordsXYZ.z += trackBlock[0].z;
|
|
|
|
for (int32_t i = 0; trackBlock[i].index != 0xFF; ++i)
|
|
|
|
{
|
|
|
|
CoordsXY cur = { retCoordsXYZ };
|
|
|
|
offsets = { trackBlock[i].x, trackBlock[i].y };
|
|
|
|
cur += offsets.Rotate(mapDirection);
|
|
|
|
int32_t cur_z = start_z + trackBlock[i].z;
|
|
|
|
|
|
|
|
map_invalidate_tile_full(cur);
|
|
|
|
|
|
|
|
trackElement = map_get_track_element_at_of_type_seq(
|
|
|
|
{ cur, cur_z, static_cast<Direction>(location.direction) }, type, trackBlock[i].index);
|
|
|
|
if (trackElement == nullptr)
|
|
|
|
{
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
if (i == 0 && output_element != nullptr)
|
|
|
|
{
|
|
|
|
*output_element = reinterpret_cast<TileElement*>(trackElement);
|
|
|
|
}
|
|
|
|
if (flags & TRACK_ELEMENT_SET_HIGHLIGHT_FALSE)
|
|
|
|
{
|
|
|
|
trackElement->SetHighlight(false);
|
|
|
|
}
|
|
|
|
if (flags & TRACK_ELEMENT_SET_HIGHLIGHT_TRUE)
|
|
|
|
{
|
|
|
|
trackElement->SetHighlight(true);
|
|
|
|
}
|
|
|
|
if (flags & TRACK_ELEMENT_SET_COLOUR_SCHEME)
|
|
|
|
{
|
|
|
|
trackElement->SetColourScheme(static_cast<uint8_t>(extra_params & 0xFF));
|
|
|
|
}
|
|
|
|
if (flags & TRACK_ELEMENT_SET_SEAT_ROTATION)
|
|
|
|
{
|
|
|
|
trackElement->SetSeatRotation(static_cast<uint8_t>(extra_params & 0xFF));
|
|
|
|
}
|
|
|
|
if (flags & TRACK_ELEMENT_SET_HAS_CABLE_LIFT_TRUE)
|
|
|
|
{
|
|
|
|
trackElement->SetHasCableLift(true);
|
|
|
|
}
|
|
|
|
if (flags & TRACK_ELEMENT_SET_HAS_CABLE_LIFT_FALSE)
|
|
|
|
{
|
|
|
|
trackElement->SetHasCableLift(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return retCoordsXYZ;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ride_restore_provisional_track_piece()
|
|
|
|
{
|
|
|
|
if (_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_TRACK)
|
|
|
|
{
|
2022-01-19 14:17:11 +01:00
|
|
|
RideId rideIndex;
|
2021-07-27 17:21:03 +02:00
|
|
|
int32_t direction, type, liftHillAndAlternativeState;
|
|
|
|
CoordsXYZ trackPos;
|
|
|
|
if (window_ride_construction_update_state(
|
|
|
|
&type, &direction, &rideIndex, &liftHillAndAlternativeState, &trackPos, nullptr))
|
|
|
|
{
|
|
|
|
ride_construction_remove_ghosts();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_currentTrackPrice = place_provisional_track_piece(
|
|
|
|
rideIndex, type, direction, liftHillAndAlternativeState, trackPos);
|
|
|
|
window_ride_construction_update_active_elements();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ride_remove_provisional_track_piece()
|
|
|
|
{
|
|
|
|
auto rideIndex = _currentRideIndex;
|
|
|
|
auto ride = get_ride(rideIndex);
|
|
|
|
if (ride == nullptr || !(_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_TRACK))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t x = _unkF440C5.x;
|
|
|
|
int32_t y = _unkF440C5.y;
|
|
|
|
int32_t z = _unkF440C5.z;
|
|
|
|
if (ride->type == RIDE_TYPE_MAZE)
|
|
|
|
{
|
|
|
|
int32_t flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND
|
|
|
|
| GAME_COMMAND_FLAG_GHOST;
|
2021-12-21 23:17:14 +01:00
|
|
|
maze_set_track(CoordsXYZD{ x, y, z, 0 }, flags, false, rideIndex, GC_SET_MAZE_TRACK_FILL);
|
|
|
|
maze_set_track(CoordsXYZD{ x, y + 16, z, 1 }, flags, false, rideIndex, GC_SET_MAZE_TRACK_FILL);
|
|
|
|
maze_set_track(CoordsXYZD{ x + 16, y + 16, z, 2 }, flags, false, rideIndex, GC_SET_MAZE_TRACK_FILL);
|
|
|
|
maze_set_track(CoordsXYZD{ x + 16, y, z, 3 }, flags, false, rideIndex, GC_SET_MAZE_TRACK_FILL);
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int32_t direction = _unkF440C5.direction;
|
|
|
|
if (!(direction & 4))
|
|
|
|
{
|
|
|
|
x -= CoordsDirectionDelta[direction].x;
|
|
|
|
y -= CoordsDirectionDelta[direction].y;
|
|
|
|
}
|
|
|
|
CoordsXYE next_track;
|
|
|
|
if (track_block_get_next_from_zero({ x, y, z }, ride, direction, &next_track, &z, &direction, true))
|
|
|
|
{
|
|
|
|
auto trackType = next_track.element->AsTrack()->GetTrackType();
|
|
|
|
int32_t trackSequence = next_track.element->AsTrack()->GetSequenceIndex();
|
|
|
|
auto trackRemoveAction = TrackRemoveAction{ trackType,
|
|
|
|
trackSequence,
|
|
|
|
{ next_track.x, next_track.y, z, static_cast<Direction>(direction) } };
|
|
|
|
trackRemoveAction.SetFlags(
|
|
|
|
GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
|
|
|
|
GameActions::Execute(&trackRemoveAction);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006C96C0
|
|
|
|
*/
|
|
|
|
void ride_construction_remove_ghosts()
|
|
|
|
{
|
|
|
|
if (_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_ENTRANCE_OR_EXIT)
|
|
|
|
{
|
|
|
|
ride_entrance_exit_remove_ghost();
|
|
|
|
_currentTrackSelectionFlags &= ~TRACK_SELECTION_FLAG_ENTRANCE_OR_EXIT;
|
|
|
|
}
|
|
|
|
if (_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_TRACK)
|
|
|
|
{
|
|
|
|
ride_remove_provisional_track_piece();
|
|
|
|
_currentTrackSelectionFlags &= ~TRACK_SELECTION_FLAG_TRACK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* rct2: 0x006C9627
|
|
|
|
*/
|
|
|
|
void ride_construction_invalidate_current_track()
|
|
|
|
{
|
|
|
|
switch (_rideConstructionState)
|
|
|
|
{
|
2021-08-11 17:51:59 +02:00
|
|
|
case RideConstructionState::Selected:
|
2021-09-02 04:00:18 +02:00
|
|
|
GetTrackElementOriginAndApplyChanges(
|
2021-07-27 17:21:03 +02:00
|
|
|
{ _currentTrackBegin, static_cast<Direction>(_currentTrackPieceDirection & 3) }, _currentTrackPieceType, 0,
|
|
|
|
nullptr, TRACK_ELEMENT_SET_HIGHLIGHT_FALSE);
|
|
|
|
break;
|
2021-08-12 17:43:34 +02:00
|
|
|
case RideConstructionState::MazeBuild:
|
2021-08-11 17:51:59 +02:00
|
|
|
case RideConstructionState::MazeMove:
|
|
|
|
case RideConstructionState::MazeFill:
|
|
|
|
case RideConstructionState::Front:
|
|
|
|
case RideConstructionState::Back:
|
2021-07-27 17:21:03 +02:00
|
|
|
if (_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_ARROW)
|
|
|
|
{
|
|
|
|
map_invalidate_tile_full(_currentTrackBegin.ToTileStart());
|
|
|
|
}
|
|
|
|
ride_construction_remove_ghosts();
|
|
|
|
break;
|
2021-08-11 17:51:59 +02:00
|
|
|
case RideConstructionState::Place:
|
|
|
|
case RideConstructionState::EntranceExit:
|
2021-07-27 17:21:03 +02:00
|
|
|
default:
|
|
|
|
if (_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_ARROW)
|
|
|
|
{
|
|
|
|
_currentTrackSelectionFlags &= ~TRACK_SELECTION_FLAG_ARROW;
|
|
|
|
gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
|
|
|
|
map_invalidate_tile_full(_currentTrackBegin);
|
|
|
|
}
|
|
|
|
ride_construction_remove_ghosts();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006C9B19
|
|
|
|
*/
|
|
|
|
static void ride_construction_reset_current_piece()
|
|
|
|
{
|
|
|
|
auto ride = get_ride(_currentRideIndex);
|
|
|
|
if (ride == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
const auto& rtd = ride->GetRideTypeDescriptor();
|
|
|
|
|
|
|
|
if (!rtd.HasFlag(RIDE_TYPE_FLAG_HAS_NO_TRACK) || ride->num_stations == 0)
|
|
|
|
{
|
|
|
|
_currentTrackCurve = rtd.StartTrackPiece | RideConstructionSpecialPieceSelected;
|
|
|
|
_currentTrackSlopeEnd = 0;
|
|
|
|
_currentTrackBankEnd = 0;
|
|
|
|
_currentTrackLiftHill = 0;
|
|
|
|
_currentTrackAlternative = RIDE_TYPE_NO_ALTERNATIVES;
|
|
|
|
if (rtd.HasFlag(RIDE_TYPE_FLAG_START_CONSTRUCTION_INVERTED))
|
|
|
|
{
|
|
|
|
_currentTrackAlternative |= RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
|
|
|
|
}
|
|
|
|
_previousTrackSlopeEnd = 0;
|
|
|
|
_previousTrackBankEnd = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_currentTrackCurve = TrackElemType::None;
|
2021-08-11 17:51:59 +02:00
|
|
|
_rideConstructionState = RideConstructionState::State0;
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006C9800
|
|
|
|
*/
|
|
|
|
void ride_construction_set_default_next_piece()
|
|
|
|
{
|
|
|
|
auto rideIndex = _currentRideIndex;
|
|
|
|
auto ride = get_ride(rideIndex);
|
|
|
|
if (ride == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
const auto& rtd = ride->GetRideTypeDescriptor();
|
|
|
|
|
|
|
|
int32_t z, direction, trackType, curve, bank, slope;
|
|
|
|
track_begin_end trackBeginEnd;
|
|
|
|
CoordsXYE xyElement;
|
|
|
|
TileElement* tileElement;
|
|
|
|
_currentTrackPrice = MONEY32_UNDEFINED;
|
2021-08-22 15:50:44 +02:00
|
|
|
|
2021-08-29 00:57:46 +02:00
|
|
|
const TrackElementDescriptor* ted;
|
2021-07-27 17:21:03 +02:00
|
|
|
switch (_rideConstructionState)
|
|
|
|
{
|
2021-08-11 17:51:59 +02:00
|
|
|
case RideConstructionState::Front:
|
2021-07-27 17:21:03 +02:00
|
|
|
direction = _currentTrackPieceDirection;
|
|
|
|
if (!track_block_get_previous_from_zero(_currentTrackBegin, ride, direction, &trackBeginEnd))
|
|
|
|
{
|
|
|
|
ride_construction_reset_current_piece();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
tileElement = trackBeginEnd.begin_element;
|
|
|
|
trackType = tileElement->AsTrack()->GetTrackType();
|
|
|
|
|
|
|
|
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_NO_TRACK))
|
|
|
|
{
|
|
|
|
ride_construction_reset_current_piece();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set whether track is covered
|
|
|
|
_currentTrackAlternative &= ~RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
|
|
|
|
if (rtd.HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE))
|
|
|
|
{
|
|
|
|
if (tileElement->AsTrack()->IsInverted())
|
|
|
|
{
|
|
|
|
_currentTrackAlternative |= RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-29 00:57:46 +02:00
|
|
|
ted = &GetTrackElementDescriptor(trackType);
|
|
|
|
curve = ted->CurveChain.next;
|
2021-08-30 02:15:44 +02:00
|
|
|
bank = ted->Definition.bank_end;
|
|
|
|
slope = ted->Definition.vangle_end;
|
2021-07-27 17:21:03 +02:00
|
|
|
|
|
|
|
// Set track curve
|
|
|
|
_currentTrackCurve = curve;
|
|
|
|
|
|
|
|
// Set track banking
|
|
|
|
if (rtd.HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE))
|
|
|
|
{
|
|
|
|
if (bank == TRACK_BANK_UPSIDE_DOWN)
|
|
|
|
{
|
|
|
|
bank = TRACK_BANK_NONE;
|
|
|
|
_currentTrackAlternative ^= RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_currentTrackBankEnd = bank;
|
|
|
|
_previousTrackBankEnd = bank;
|
|
|
|
|
|
|
|
// Set track slope and lift hill
|
|
|
|
_currentTrackSlopeEnd = slope;
|
|
|
|
_previousTrackSlopeEnd = slope;
|
2022-03-28 22:34:37 +02:00
|
|
|
_currentTrackLiftHill = tileElement->AsTrack()->HasChain()
|
|
|
|
&& ((slope != TRACK_SLOPE_DOWN_25 && slope != TRACK_SLOPE_DOWN_60) || gCheatsEnableChainLiftOnAllTrack);
|
2021-07-27 17:21:03 +02:00
|
|
|
break;
|
2021-08-11 17:51:59 +02:00
|
|
|
case RideConstructionState::Back:
|
2021-07-27 17:21:03 +02:00
|
|
|
direction = direction_reverse(_currentTrackPieceDirection);
|
|
|
|
if (!track_block_get_next_from_zero(_currentTrackBegin, ride, direction, &xyElement, &z, &direction, false))
|
|
|
|
{
|
|
|
|
ride_construction_reset_current_piece();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
tileElement = xyElement.element;
|
|
|
|
trackType = tileElement->AsTrack()->GetTrackType();
|
|
|
|
|
|
|
|
// Set whether track is covered
|
|
|
|
_currentTrackAlternative &= ~RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
|
|
|
|
if (rtd.HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE))
|
|
|
|
{
|
|
|
|
if (tileElement->AsTrack()->IsInverted())
|
|
|
|
{
|
|
|
|
_currentTrackAlternative |= RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-29 00:57:46 +02:00
|
|
|
ted = &GetTrackElementDescriptor(trackType);
|
|
|
|
curve = ted->CurveChain.previous;
|
2021-08-30 02:15:44 +02:00
|
|
|
bank = ted->Definition.bank_start;
|
|
|
|
slope = ted->Definition.vangle_start;
|
2021-07-27 17:21:03 +02:00
|
|
|
|
|
|
|
// Set track curve
|
|
|
|
_currentTrackCurve = curve;
|
|
|
|
|
|
|
|
// Set track banking
|
|
|
|
if (rtd.HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE))
|
|
|
|
{
|
|
|
|
if (bank == TRACK_BANK_UPSIDE_DOWN)
|
|
|
|
{
|
|
|
|
bank = TRACK_BANK_NONE;
|
|
|
|
_currentTrackAlternative ^= RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_currentTrackBankEnd = bank;
|
|
|
|
_previousTrackBankEnd = bank;
|
|
|
|
|
|
|
|
// Set track slope and lift hill
|
|
|
|
_currentTrackSlopeEnd = slope;
|
|
|
|
_previousTrackSlopeEnd = slope;
|
|
|
|
if (!gCheatsEnableChainLiftOnAllTrack)
|
|
|
|
{
|
|
|
|
_currentTrackLiftHill = tileElement->AsTrack()->HasChain();
|
|
|
|
}
|
|
|
|
break;
|
2021-08-11 17:51:59 +02:00
|
|
|
default:
|
|
|
|
break;
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006C9296
|
|
|
|
*/
|
|
|
|
void ride_select_next_section()
|
|
|
|
{
|
2021-08-11 17:51:59 +02:00
|
|
|
if (_rideConstructionState == RideConstructionState::Selected)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
ride_construction_invalidate_current_track();
|
|
|
|
int32_t direction = _currentTrackPieceDirection;
|
|
|
|
int32_t type = _currentTrackPieceType;
|
|
|
|
TileElement* tileElement;
|
2021-09-02 04:00:18 +02:00
|
|
|
auto newCoords = GetTrackElementOriginAndApplyChanges(
|
|
|
|
{ _currentTrackBegin, static_cast<Direction>(direction & 3) }, type, 0, &tileElement, 0);
|
2021-09-13 18:47:13 +02:00
|
|
|
if (!newCoords.has_value())
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-08-11 17:51:59 +02:00
|
|
|
_rideConstructionState = RideConstructionState::State0;
|
2021-07-27 17:21:03 +02:00
|
|
|
window_ride_construction_update_active_elements();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Invalidate previous track piece (we may not be changing height!)
|
|
|
|
virtual_floor_invalidate();
|
|
|
|
|
|
|
|
CoordsXYE inputElement, outputElement;
|
|
|
|
inputElement.x = newCoords->x;
|
|
|
|
inputElement.y = newCoords->y;
|
|
|
|
inputElement.element = tileElement;
|
|
|
|
if (track_block_get_next(&inputElement, &outputElement, &newCoords->z, &direction))
|
|
|
|
{
|
|
|
|
newCoords->x = outputElement.x;
|
|
|
|
newCoords->y = outputElement.y;
|
|
|
|
tileElement = outputElement.element;
|
|
|
|
if (!scenery_tool_is_active())
|
|
|
|
{
|
|
|
|
// Set next element's height.
|
|
|
|
virtual_floor_set_height(tileElement->GetBaseZ());
|
|
|
|
}
|
|
|
|
|
|
|
|
_currentTrackBegin = *newCoords;
|
|
|
|
_currentTrackPieceDirection = tileElement->GetDirection();
|
|
|
|
_currentTrackPieceType = tileElement->AsTrack()->GetTrackType();
|
|
|
|
_currentTrackSelectionFlags = 0;
|
|
|
|
window_ride_construction_update_active_elements();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-08-11 17:51:59 +02:00
|
|
|
_rideConstructionState = RideConstructionState::Front;
|
2021-07-27 17:21:03 +02:00
|
|
|
_currentTrackBegin = { outputElement, newCoords->z };
|
|
|
|
_currentTrackPieceDirection = direction;
|
|
|
|
_currentTrackPieceType = tileElement->AsTrack()->GetTrackType();
|
|
|
|
_currentTrackSelectionFlags = 0;
|
|
|
|
ride_construction_set_default_next_piece();
|
|
|
|
window_ride_construction_update_active_elements();
|
|
|
|
}
|
|
|
|
}
|
2021-08-11 17:51:59 +02:00
|
|
|
else if (_rideConstructionState == RideConstructionState::Back)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
|
|
|
|
|
|
|
|
if (ride_select_forwards_from_back())
|
|
|
|
{
|
|
|
|
window_ride_construction_update_active_elements();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006C93B8
|
|
|
|
*/
|
|
|
|
void ride_select_previous_section()
|
|
|
|
{
|
2021-08-11 17:51:59 +02:00
|
|
|
if (_rideConstructionState == RideConstructionState::Selected)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
ride_construction_invalidate_current_track();
|
|
|
|
int32_t direction = _currentTrackPieceDirection;
|
|
|
|
int32_t type = _currentTrackPieceType;
|
|
|
|
TileElement* tileElement;
|
2021-09-02 04:00:18 +02:00
|
|
|
auto newCoords = GetTrackElementOriginAndApplyChanges(
|
|
|
|
{ _currentTrackBegin, static_cast<Direction>(direction & 3) }, type, 0, &tileElement, 0);
|
2021-07-27 17:21:03 +02:00
|
|
|
if (newCoords == std::nullopt)
|
|
|
|
{
|
2021-08-11 17:51:59 +02:00
|
|
|
_rideConstructionState = RideConstructionState::State0;
|
2021-07-27 17:21:03 +02:00
|
|
|
window_ride_construction_update_active_elements();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Invalidate previous track piece (we may not be changing height!)
|
|
|
|
virtual_floor_invalidate();
|
|
|
|
|
|
|
|
track_begin_end trackBeginEnd;
|
|
|
|
if (track_block_get_previous({ *newCoords, tileElement }, &trackBeginEnd))
|
|
|
|
{
|
|
|
|
_currentTrackBegin.x = trackBeginEnd.begin_x;
|
|
|
|
_currentTrackBegin.y = trackBeginEnd.begin_y;
|
|
|
|
_currentTrackBegin.z = trackBeginEnd.begin_z;
|
|
|
|
_currentTrackPieceDirection = trackBeginEnd.begin_direction;
|
|
|
|
_currentTrackPieceType = trackBeginEnd.begin_element->AsTrack()->GetTrackType();
|
|
|
|
_currentTrackSelectionFlags = 0;
|
|
|
|
if (!scenery_tool_is_active())
|
|
|
|
{
|
|
|
|
// Set previous element's height.
|
|
|
|
virtual_floor_set_height(trackBeginEnd.begin_element->GetBaseZ());
|
|
|
|
}
|
|
|
|
window_ride_construction_update_active_elements();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-08-11 17:51:59 +02:00
|
|
|
_rideConstructionState = RideConstructionState::Back;
|
2021-07-27 17:21:03 +02:00
|
|
|
_currentTrackBegin.x = trackBeginEnd.end_x;
|
|
|
|
_currentTrackBegin.y = trackBeginEnd.end_y;
|
|
|
|
_currentTrackBegin.z = trackBeginEnd.begin_z;
|
|
|
|
_currentTrackPieceDirection = trackBeginEnd.end_direction;
|
|
|
|
_currentTrackPieceType = tileElement->AsTrack()->GetTrackType();
|
|
|
|
_currentTrackSelectionFlags = 0;
|
|
|
|
ride_construction_set_default_next_piece();
|
|
|
|
window_ride_construction_update_active_elements();
|
|
|
|
}
|
|
|
|
}
|
2021-08-11 17:51:59 +02:00
|
|
|
else if (_rideConstructionState == RideConstructionState::Front)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
|
|
|
|
|
|
|
|
if (ride_select_backwards_from_front())
|
|
|
|
{
|
|
|
|
window_ride_construction_update_active_elements();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006CC2CA
|
|
|
|
*/
|
|
|
|
static bool ride_modify_entrance_or_exit(const CoordsXYE& tileElement)
|
|
|
|
{
|
|
|
|
if (tileElement.element == nullptr)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
auto entranceElement = tileElement.element->AsEntrance();
|
|
|
|
if (entranceElement == nullptr)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
auto rideIndex = entranceElement->GetRideIndex();
|
|
|
|
auto ride = get_ride(rideIndex);
|
|
|
|
if (ride == nullptr)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
auto entranceType = entranceElement->GetEntranceType();
|
|
|
|
if (entranceType != ENTRANCE_TYPE_RIDE_ENTRANCE && entranceType != ENTRANCE_TYPE_RIDE_EXIT)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
auto stationIndex = entranceElement->GetStationIndex();
|
|
|
|
|
|
|
|
// Get or create construction window for ride
|
|
|
|
auto constructionWindow = window_find_by_class(WC_RIDE_CONSTRUCTION);
|
|
|
|
if (constructionWindow == nullptr)
|
|
|
|
{
|
|
|
|
if (!ride_initialise_construction_window(ride))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
constructionWindow = window_find_by_class(WC_RIDE_CONSTRUCTION);
|
|
|
|
if (constructionWindow == nullptr)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ride_construction_invalidate_current_track();
|
2021-08-11 17:51:59 +02:00
|
|
|
if (_rideConstructionState != RideConstructionState::EntranceExit || !(input_test_flag(INPUT_FLAG_TOOL_ACTIVE))
|
2021-07-27 17:21:03 +02:00
|
|
|
|| gCurrentToolWidget.window_classification != WC_RIDE_CONSTRUCTION)
|
|
|
|
{
|
|
|
|
// Replace entrance / exit
|
|
|
|
tool_set(
|
|
|
|
constructionWindow,
|
|
|
|
entranceType == ENTRANCE_TYPE_RIDE_ENTRANCE ? WC_RIDE_CONSTRUCTION__WIDX_ENTRANCE : WC_RIDE_CONSTRUCTION__WIDX_EXIT,
|
|
|
|
Tool::Crosshair);
|
|
|
|
gRideEntranceExitPlaceType = entranceType;
|
|
|
|
gRideEntranceExitPlaceRideIndex = rideIndex;
|
|
|
|
gRideEntranceExitPlaceStationIndex = stationIndex;
|
|
|
|
input_set_flag(INPUT_FLAG_6, true);
|
2021-08-11 17:51:59 +02:00
|
|
|
if (_rideConstructionState != RideConstructionState::EntranceExit)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
gRideEntranceExitPlacePreviousRideConstructionState = _rideConstructionState;
|
2021-08-11 17:51:59 +02:00
|
|
|
_rideConstructionState = RideConstructionState::EntranceExit;
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
window_ride_construction_update_active_elements();
|
|
|
|
gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Remove entrance / exit
|
|
|
|
auto rideEntranceExitRemove = RideEntranceExitRemoveAction(
|
|
|
|
{ tileElement.x, tileElement.y }, rideIndex, stationIndex, entranceType == ENTRANCE_TYPE_RIDE_EXIT);
|
|
|
|
|
|
|
|
rideEntranceExitRemove.SetCallback([=](const GameAction* ga, const GameActions::Result* result) {
|
|
|
|
gCurrentToolWidget.widget_index = entranceType == ENTRANCE_TYPE_RIDE_ENTRANCE ? WC_RIDE_CONSTRUCTION__WIDX_ENTRANCE
|
|
|
|
: WC_RIDE_CONSTRUCTION__WIDX_EXIT;
|
|
|
|
gRideEntranceExitPlaceType = entranceType;
|
|
|
|
window_invalidate_by_class(WC_RIDE_CONSTRUCTION);
|
|
|
|
});
|
|
|
|
|
|
|
|
GameActions::Execute(&rideEntranceExitRemove);
|
|
|
|
}
|
|
|
|
|
|
|
|
window_invalidate_by_class(WC_RIDE_CONSTRUCTION);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006CC287
|
|
|
|
*/
|
|
|
|
static bool ride_modify_maze(const CoordsXYE& tileElement)
|
|
|
|
{
|
|
|
|
if (tileElement.element != nullptr)
|
|
|
|
{
|
|
|
|
auto trackElement = tileElement.element->AsTrack();
|
|
|
|
if (trackElement != nullptr)
|
|
|
|
{
|
|
|
|
_currentRideIndex = trackElement->GetRideIndex();
|
2021-08-12 17:43:34 +02:00
|
|
|
_rideConstructionState = RideConstructionState::MazeBuild;
|
2021-07-27 17:21:03 +02:00
|
|
|
_currentTrackBegin.x = tileElement.x;
|
|
|
|
_currentTrackBegin.y = tileElement.y;
|
|
|
|
_currentTrackBegin.z = trackElement->GetBaseZ();
|
|
|
|
_currentTrackSelectionFlags = 0;
|
|
|
|
_rideConstructionNextArrowPulse = 0;
|
|
|
|
gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
|
|
|
|
|
|
|
|
auto intent = Intent(INTENT_ACTION_UPDATE_MAZE_CONSTRUCTION);
|
|
|
|
context_broadcast_intent(&intent);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006CC056
|
|
|
|
*/
|
|
|
|
bool ride_modify(CoordsXYE* input)
|
|
|
|
{
|
|
|
|
auto tileElement = *input;
|
|
|
|
if (tileElement.element == nullptr)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
auto rideIndex = tileElement.element->GetRideIndex();
|
|
|
|
auto ride = get_ride(rideIndex);
|
|
|
|
if (ride == nullptr)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto rideEntry = ride->GetRideEntry();
|
|
|
|
if (rideEntry == nullptr || !ride_check_if_construction_allowed(ride))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (ride->lifecycle_flags & RIDE_LIFECYCLE_INDESTRUCTIBLE)
|
|
|
|
{
|
|
|
|
Formatter ft;
|
|
|
|
ft.Increment(6);
|
|
|
|
ride->FormatNameTo(ft);
|
|
|
|
context_show_error(
|
|
|
|
STR_CANT_START_CONSTRUCTION_ON, STR_LOCAL_AUTHORITY_FORBIDS_DEMOLITION_OR_MODIFICATIONS_TO_THIS_RIDE, ft);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop the ride again to clear all vehicles and peeps (compatible with network games)
|
|
|
|
if (ride->status != RideStatus::Simulating)
|
|
|
|
{
|
|
|
|
ride_set_status(ride, RideStatus::Closed);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if element is a station entrance or exit
|
2021-12-11 00:39:39 +01:00
|
|
|
if (tileElement.element->GetType() == TileElementType::Entrance)
|
2021-07-27 17:21:03 +02:00
|
|
|
return ride_modify_entrance_or_exit(tileElement);
|
|
|
|
|
|
|
|
ride_create_or_find_construction_window(rideIndex);
|
|
|
|
|
|
|
|
if (ride->type == RIDE_TYPE_MAZE)
|
|
|
|
{
|
|
|
|
return ride_modify_maze(tileElement);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_CANNOT_HAVE_GAPS))
|
|
|
|
{
|
|
|
|
CoordsXYE endOfTrackElement{};
|
|
|
|
if (ride_find_track_gap(ride, &tileElement, &endOfTrackElement))
|
|
|
|
tileElement = endOfTrackElement;
|
|
|
|
}
|
|
|
|
|
2021-12-11 00:39:39 +01:00
|
|
|
if (tileElement.element == nullptr || tileElement.element->GetType() != TileElementType::Track)
|
2021-07-27 17:21:03 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
auto tileCoords = CoordsXYZ{ tileElement, tileElement.element->GetBaseZ() };
|
|
|
|
auto direction = tileElement.element->GetDirection();
|
|
|
|
auto type = tileElement.element->AsTrack()->GetTrackType();
|
2021-09-02 04:00:18 +02:00
|
|
|
auto newCoords = GetTrackElementOriginAndApplyChanges({ tileCoords, direction }, type, 0, nullptr, 0);
|
2021-09-13 18:47:13 +02:00
|
|
|
if (!newCoords.has_value())
|
2021-07-27 17:21:03 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
_currentRideIndex = rideIndex;
|
2021-08-11 17:51:59 +02:00
|
|
|
_rideConstructionState = RideConstructionState::Selected;
|
2021-09-13 18:47:13 +02:00
|
|
|
_currentTrackBegin = newCoords.value();
|
2021-07-27 17:21:03 +02:00
|
|
|
_currentTrackPieceDirection = direction;
|
|
|
|
_currentTrackPieceType = type;
|
|
|
|
_currentTrackSelectionFlags = 0;
|
|
|
|
_rideConstructionNextArrowPulse = 0;
|
|
|
|
gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
|
|
|
|
|
|
|
|
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_NO_TRACK))
|
|
|
|
{
|
|
|
|
window_ride_construction_update_active_elements();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ride_select_next_section();
|
2021-08-11 17:51:59 +02:00
|
|
|
if (_rideConstructionState == RideConstructionState::Front)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
window_ride_construction_update_active_elements();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-08-11 17:51:59 +02:00
|
|
|
_rideConstructionState = RideConstructionState::Selected;
|
2021-07-27 17:21:03 +02:00
|
|
|
_currentTrackBegin = *newCoords;
|
|
|
|
_currentTrackPieceDirection = direction;
|
|
|
|
_currentTrackPieceType = type;
|
|
|
|
_currentTrackSelectionFlags = 0;
|
|
|
|
|
|
|
|
ride_select_previous_section();
|
|
|
|
|
2021-08-11 17:51:59 +02:00
|
|
|
if (_rideConstructionState != RideConstructionState::Back)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-08-11 17:51:59 +02:00
|
|
|
_rideConstructionState = RideConstructionState::Selected;
|
2021-07-27 17:21:03 +02:00
|
|
|
_currentTrackBegin = *newCoords;
|
|
|
|
_currentTrackPieceDirection = direction;
|
|
|
|
_currentTrackPieceType = type;
|
|
|
|
_currentTrackSelectionFlags = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
window_ride_construction_update_active_elements();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006CC3FB
|
|
|
|
*/
|
|
|
|
int32_t ride_initialise_construction_window(Ride* ride)
|
|
|
|
{
|
|
|
|
rct_window* w;
|
|
|
|
|
|
|
|
tool_cancel();
|
|
|
|
|
|
|
|
if (!ride_check_if_construction_allowed(ride))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ride_clear_for_construction(ride);
|
2021-12-12 12:18:12 +01:00
|
|
|
ride->RemovePeeps();
|
2021-07-27 17:21:03 +02:00
|
|
|
|
|
|
|
w = ride_create_or_find_construction_window(ride->id);
|
|
|
|
|
|
|
|
tool_set(w, WC_RIDE_CONSTRUCTION__WIDX_CONSTRUCT, Tool::Crosshair);
|
|
|
|
input_set_flag(INPUT_FLAG_6, true);
|
|
|
|
|
|
|
|
ride = get_ride(_currentRideIndex);
|
|
|
|
if (ride == nullptr)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
_currentTrackCurve = ride->GetRideTypeDescriptor().StartTrackPiece | RideConstructionSpecialPieceSelected;
|
|
|
|
_currentTrackSlopeEnd = 0;
|
|
|
|
_currentTrackBankEnd = 0;
|
|
|
|
_currentTrackLiftHill = 0;
|
|
|
|
_currentTrackAlternative = RIDE_TYPE_NO_ALTERNATIVES;
|
|
|
|
|
|
|
|
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_START_CONSTRUCTION_INVERTED))
|
|
|
|
_currentTrackAlternative |= RIDE_TYPE_ALTERNATIVE_TRACK_TYPE;
|
|
|
|
|
|
|
|
_previousTrackBankEnd = 0;
|
|
|
|
_previousTrackSlopeEnd = 0;
|
|
|
|
|
|
|
|
_currentTrackPieceDirection = 0;
|
2021-08-11 17:51:59 +02:00
|
|
|
_rideConstructionState = RideConstructionState::Place;
|
2021-07-27 17:21:03 +02:00
|
|
|
_currentTrackSelectionFlags = 0;
|
|
|
|
|
|
|
|
window_ride_construction_update_active_elements();
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006CB7FB
|
|
|
|
*/
|
|
|
|
int32_t ride_get_refund_price(const Ride* ride)
|
|
|
|
{
|
|
|
|
CoordsXYE trackElement;
|
|
|
|
money32 cost = 0;
|
|
|
|
|
|
|
|
if (!ride_try_get_origin_element(ride, &trackElement))
|
|
|
|
{
|
|
|
|
return 0; // Ride has no track to refund
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the start in case it is not a complete circuit
|
|
|
|
ride_get_start_of_track(&trackElement);
|
|
|
|
|
|
|
|
uint8_t direction = trackElement.element->GetDirection();
|
|
|
|
|
|
|
|
// Used in the following loop to know when we have
|
|
|
|
// completed all of the elements and are back at the
|
|
|
|
// start.
|
|
|
|
TileElement* initial_map = trackElement.element;
|
|
|
|
CoordsXYE slowIt = trackElement;
|
|
|
|
bool moveSlowIt = true;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
auto trackRemoveAction = TrackRemoveAction(
|
|
|
|
trackElement.element->AsTrack()->GetTrackType(), trackElement.element->AsTrack()->GetSequenceIndex(),
|
|
|
|
{ trackElement.x, trackElement.y, trackElement.element->GetBaseZ(), direction });
|
|
|
|
trackRemoveAction.SetFlags(GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED);
|
|
|
|
|
|
|
|
auto res = GameActions::Query(&trackRemoveAction);
|
|
|
|
|
2021-11-24 08:35:08 +01:00
|
|
|
cost += res.Cost;
|
2021-07-27 17:21:03 +02:00
|
|
|
|
|
|
|
if (!track_block_get_next(&trackElement, &trackElement, nullptr, nullptr))
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
moveSlowIt = !moveSlowIt;
|
|
|
|
if (moveSlowIt)
|
|
|
|
{
|
|
|
|
if (!track_block_get_next(&slowIt, &slowIt, nullptr, nullptr) || slowIt.element == trackElement.element)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
direction = trackElement.element->GetDirection();
|
|
|
|
|
|
|
|
} while (trackElement.element != initial_map);
|
|
|
|
|
|
|
|
return cost;
|
|
|
|
}
|
|
|
|
|
2022-01-19 14:17:11 +01:00
|
|
|
money32 set_operating_setting(RideId rideId, RideSetSetting setting, uint8_t value)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
auto rideSetSetting = RideSetSettingAction(rideId, setting, value);
|
|
|
|
auto res = GameActions::Execute(&rideSetSetting);
|
2021-11-24 08:35:08 +01:00
|
|
|
return res.Error == GameActions::Status::Ok ? 0 : MONEY32_UNDEFINED;
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
|
2022-01-19 14:17:11 +01:00
|
|
|
money32 set_operating_setting_nested(RideId rideId, RideSetSetting setting, uint8_t value, uint8_t flags)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
auto rideSetSetting = RideSetSettingAction(rideId, setting, value);
|
|
|
|
rideSetSetting.SetFlags(flags);
|
|
|
|
auto res = flags & GAME_COMMAND_FLAG_APPLY ? GameActions::ExecuteNested(&rideSetSetting)
|
|
|
|
: GameActions::QueryNested(&rideSetSetting);
|
2021-11-24 08:35:08 +01:00
|
|
|
return res.Error == GameActions::Status::Ok ? 0 : MONEY32_UNDEFINED;
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006CCF70
|
|
|
|
*/
|
|
|
|
CoordsXYZD ride_get_entrance_or_exit_position_from_screen_position(const ScreenCoordsXY& screenCoords)
|
|
|
|
{
|
|
|
|
CoordsXYZD entranceExitCoords{};
|
|
|
|
gRideEntranceExitPlaceDirection = INVALID_DIRECTION;
|
2021-09-02 04:00:18 +02:00
|
|
|
// determine if the mouse is hovering over a station - that's the station to add the entrance to
|
2021-07-27 17:21:03 +02:00
|
|
|
auto info = get_map_coordinates_from_pos(screenCoords, EnumsToFlags(ViewportInteractionItem::Ride));
|
|
|
|
if (info.SpriteType != ViewportInteractionItem::None)
|
|
|
|
{
|
2021-12-11 00:39:39 +01:00
|
|
|
if (info.Element->GetType() == TileElementType::Track)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-10-14 16:01:47 +02:00
|
|
|
const auto* trackElement = info.Element->AsTrack();
|
|
|
|
if (trackElement->GetRideIndex() == gRideEntranceExitPlaceRideIndex)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-10-14 16:01:47 +02:00
|
|
|
const auto& ted = GetTrackElementDescriptor(trackElement->GetTrackType());
|
2021-11-25 10:13:20 +01:00
|
|
|
if (std::get<0>(ted.SequenceProperties) & TRACK_SEQUENCE_FLAG_ORIGIN)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-10-14 16:01:47 +02:00
|
|
|
if (trackElement->GetTrackType() == TrackElemType::Maze)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2022-01-26 13:28:19 +01:00
|
|
|
gRideEntranceExitPlaceStationIndex = StationIndex::FromUnderlying(0);
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-10-14 16:01:47 +02:00
|
|
|
gRideEntranceExitPlaceStationIndex = trackElement->GetStationIndex();
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto ride = get_ride(gRideEntranceExitPlaceRideIndex);
|
|
|
|
if (ride == nullptr)
|
|
|
|
{
|
2021-09-09 01:48:53 +02:00
|
|
|
entranceExitCoords.SetNull();
|
2021-07-27 17:21:03 +02:00
|
|
|
return entranceExitCoords;
|
|
|
|
}
|
|
|
|
|
2022-01-28 00:34:04 +01:00
|
|
|
auto stationBaseZ = ride->GetStation(gRideEntranceExitPlaceStationIndex).GetBaseZ();
|
2021-07-27 17:21:03 +02:00
|
|
|
|
2021-09-02 04:00:18 +02:00
|
|
|
auto coordsAtHeight = screen_get_map_xy_with_z(screenCoords, stationBaseZ);
|
2021-09-13 18:47:13 +02:00
|
|
|
if (!coordsAtHeight.has_value())
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-09-09 01:48:53 +02:00
|
|
|
entranceExitCoords.SetNull();
|
2021-07-27 17:21:03 +02:00
|
|
|
return entranceExitCoords;
|
|
|
|
}
|
|
|
|
|
2021-09-02 04:00:18 +02:00
|
|
|
entranceExitCoords = { coordsAtHeight->ToTileStart(), stationBaseZ, INVALID_DIRECTION };
|
2021-07-27 17:21:03 +02:00
|
|
|
|
|
|
|
if (ride->type == RIDE_TYPE_NULL)
|
|
|
|
{
|
2021-09-09 01:48:53 +02:00
|
|
|
entranceExitCoords.SetNull();
|
2021-07-27 17:21:03 +02:00
|
|
|
return entranceExitCoords;
|
|
|
|
}
|
|
|
|
|
2022-01-28 00:34:04 +01:00
|
|
|
auto stationStart = ride->GetStation(gRideEntranceExitPlaceStationIndex).Start;
|
2021-09-09 01:48:53 +02:00
|
|
|
if (stationStart.IsNull())
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-09-09 01:48:53 +02:00
|
|
|
entranceExitCoords.SetNull();
|
2021-07-27 17:21:03 +02:00
|
|
|
return entranceExitCoords;
|
|
|
|
}
|
|
|
|
|
2021-10-14 16:01:47 +02:00
|
|
|
// find the quadrant the mouse is hovering over - that's the direction to start searching for a station TileElement
|
|
|
|
Direction startDirection = 0;
|
|
|
|
auto mapX = (coordsAtHeight->x & 0x1F) - 16;
|
|
|
|
auto mapY = (coordsAtHeight->y & 0x1F) - 16;
|
|
|
|
if (std::abs(mapX) < std::abs(mapY))
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-10-14 16:01:47 +02:00
|
|
|
startDirection = mapY < 0 ? 3 : 1;
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-10-14 16:01:47 +02:00
|
|
|
startDirection = mapX < 0 ? 0 : 2;
|
|
|
|
}
|
|
|
|
// check all 4 directions, starting with the mouse's quadrant
|
|
|
|
for (uint8_t directionIncrement = 0; directionIncrement < 4; directionIncrement++)
|
|
|
|
{
|
|
|
|
entranceExitCoords.direction = (startDirection + directionIncrement) & 3;
|
|
|
|
// search for TrackElement one tile over, shifted in the search direction
|
|
|
|
auto nextLocation = entranceExitCoords;
|
|
|
|
nextLocation += CoordsDirectionDelta[entranceExitCoords.direction];
|
|
|
|
if (map_is_location_valid(nextLocation))
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-10-14 16:01:47 +02:00
|
|
|
// iterate over every element in the tile until we find what we want
|
|
|
|
auto* tileElement = map_get_first_element_at(nextLocation);
|
2021-07-27 17:21:03 +02:00
|
|
|
if (tileElement == nullptr)
|
2021-10-14 16:01:47 +02:00
|
|
|
continue;
|
2021-07-27 17:21:03 +02:00
|
|
|
do
|
|
|
|
{
|
2021-12-11 00:39:39 +01:00
|
|
|
if (tileElement->GetType() != TileElementType::Track)
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
2021-10-14 16:01:47 +02:00
|
|
|
if (tileElement->GetBaseZ() != stationBaseZ)
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
2021-10-14 16:01:47 +02:00
|
|
|
auto* trackElement = tileElement->AsTrack();
|
|
|
|
if (trackElement->GetRideIndex() != gRideEntranceExitPlaceRideIndex)
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
2021-10-14 16:01:47 +02:00
|
|
|
if (trackElement->GetTrackType() == TrackElemType::Maze)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-10-14 16:01:47 +02:00
|
|
|
// if it's a maze, it can place the entrance and exit immediately
|
|
|
|
entranceExitCoords.direction = direction_reverse(entranceExitCoords.direction);
|
|
|
|
gRideEntranceExitPlaceDirection = entranceExitCoords.direction;
|
|
|
|
return entranceExitCoords;
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
2021-10-14 16:01:47 +02:00
|
|
|
// if it's not a maze, the sequence properties for the TrackElement must be found to determine if an
|
|
|
|
// entrance can be placed on that side
|
2021-07-27 17:21:03 +02:00
|
|
|
|
2021-10-14 16:01:47 +02:00
|
|
|
gRideEntranceExitPlaceStationIndex = trackElement->GetStationIndex();
|
2021-07-27 17:21:03 +02:00
|
|
|
|
2021-10-14 16:01:47 +02:00
|
|
|
// get the ride entrance's side relative to the TrackElement
|
|
|
|
Direction direction = (direction_reverse(entranceExitCoords.direction) - tileElement->GetDirection()) & 3;
|
|
|
|
const auto& ted = GetTrackElementDescriptor(trackElement->GetTrackType());
|
|
|
|
if (ted.SequenceProperties[trackElement->GetSequenceIndex()] & (1 << direction))
|
|
|
|
{
|
|
|
|
// if that side of the TrackElement supports stations, the ride entrance is valid and faces away from
|
|
|
|
// the station
|
|
|
|
entranceExitCoords.direction = direction_reverse(entranceExitCoords.direction);
|
|
|
|
gRideEntranceExitPlaceDirection = entranceExitCoords.direction;
|
|
|
|
return entranceExitCoords;
|
|
|
|
}
|
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
}
|
2021-10-14 16:01:47 +02:00
|
|
|
gRideEntranceExitPlaceDirection = INVALID_DIRECTION;
|
2021-07-27 17:21:03 +02:00
|
|
|
return entranceExitCoords;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006CB945
|
|
|
|
*/
|
2021-09-02 04:00:18 +02:00
|
|
|
void Ride::ValidateStations()
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-08-29 00:57:46 +02:00
|
|
|
const TrackElementDescriptor* ted;
|
2021-09-02 04:00:18 +02:00
|
|
|
if (type != RIDE_TYPE_MAZE)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-09-02 04:00:18 +02:00
|
|
|
// find the stations of the ride to begin stepping over track elements from
|
2022-01-28 10:04:16 +01:00
|
|
|
for (const auto& station : stations)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2022-01-28 10:04:16 +01:00
|
|
|
if (station.Start.IsNull())
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
|
|
|
|
2022-01-28 10:04:16 +01:00
|
|
|
CoordsXYZ location = station.GetStart();
|
2021-07-27 17:21:03 +02:00
|
|
|
uint8_t direction = INVALID_DIRECTION;
|
|
|
|
|
|
|
|
bool specialTrack = false;
|
|
|
|
TileElement* tileElement = nullptr;
|
|
|
|
while (true)
|
|
|
|
{
|
2021-09-02 04:00:18 +02:00
|
|
|
// search backwards for the previous station TrackElement (only if the first station TrackElement is found)
|
2021-07-27 17:21:03 +02:00
|
|
|
if (direction != INVALID_DIRECTION)
|
|
|
|
{
|
|
|
|
location.x -= CoordsDirectionDelta[direction].x;
|
|
|
|
location.y -= CoordsDirectionDelta[direction].y;
|
|
|
|
}
|
|
|
|
tileElement = map_get_first_element_at(location);
|
|
|
|
if (tileElement == nullptr)
|
|
|
|
break;
|
|
|
|
|
2021-09-02 04:00:18 +02:00
|
|
|
// find the target TrackElement on the tile it's supposed to appear on
|
2021-07-27 17:21:03 +02:00
|
|
|
bool trackFound = false;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
if (tileElement->GetBaseZ() != location.z)
|
|
|
|
continue;
|
2021-12-11 00:39:39 +01:00
|
|
|
if (tileElement->GetType() != TileElementType::Track)
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
2021-09-02 04:00:18 +02:00
|
|
|
if (tileElement->AsTrack()->GetRideIndex() != id)
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
|
|
|
if (tileElement->AsTrack()->GetSequenceIndex() != 0)
|
|
|
|
continue;
|
2021-08-22 17:18:38 +02:00
|
|
|
|
2021-08-29 00:57:46 +02:00
|
|
|
ted = &GetTrackElementDescriptor(tileElement->AsTrack()->GetTrackType());
|
2021-09-02 04:00:18 +02:00
|
|
|
// keep searching for a station piece (coaster station, tower ride base, shops, and flat ride base)
|
2021-11-25 10:13:20 +01:00
|
|
|
if (!(std::get<0>(ted->SequenceProperties) & TRACK_SEQUENCE_FLAG_ORIGIN))
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
trackFound = true;
|
|
|
|
break;
|
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
|
|
|
|
|
|
if (!trackFound)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2021-09-02 04:00:18 +02:00
|
|
|
// update the StationIndex, get the TrackElement's rotation
|
2022-01-28 10:04:16 +01:00
|
|
|
tileElement->AsTrack()->SetStationIndex(GetStationIndex(&station));
|
2021-07-27 17:21:03 +02:00
|
|
|
direction = tileElement->GetDirection();
|
|
|
|
|
2021-09-03 19:08:01 +02:00
|
|
|
// In the future this could look at the TED and see if the station has a sequence longer than 1
|
2021-09-02 04:00:18 +02:00
|
|
|
// tower ride, flat ride, shop
|
|
|
|
if (GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_SINGLE_PIECE_STATION))
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2021-09-02 04:00:18 +02:00
|
|
|
// if the track has multiple sequences, stop looking for the next one.
|
2021-07-27 17:21:03 +02:00
|
|
|
specialTrack = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-02 04:00:18 +02:00
|
|
|
// if the track piece is not a tower ride, flat ride, or shop, continue to the next StationIndex
|
2021-07-27 17:21:03 +02:00
|
|
|
if (!specialTrack)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2021-09-02 04:00:18 +02:00
|
|
|
// update all the blocks with StationIndex
|
2021-08-29 00:57:46 +02:00
|
|
|
ted = &GetTrackElementDescriptor(tileElement->AsTrack()->GetTrackType());
|
|
|
|
const rct_preview_track* trackBlock = ted->Block;
|
2021-07-27 17:21:03 +02:00
|
|
|
while ((++trackBlock)->index != 0xFF)
|
|
|
|
{
|
|
|
|
CoordsXYZ blockLocation = location + CoordsXYZ{ CoordsXY{ trackBlock->x, trackBlock->y }.Rotate(direction), 0 };
|
|
|
|
|
|
|
|
bool trackFound = false;
|
|
|
|
tileElement = map_get_first_element_at(blockLocation);
|
|
|
|
if (tileElement == nullptr)
|
|
|
|
break;
|
2021-09-02 04:00:18 +02:00
|
|
|
// find the target TrackElement on the tile it's supposed to appear on
|
2021-07-27 17:21:03 +02:00
|
|
|
do
|
|
|
|
{
|
|
|
|
if (blockLocation.z != tileElement->GetBaseZ())
|
|
|
|
continue;
|
2021-12-11 00:39:39 +01:00
|
|
|
if (tileElement->GetType() != TileElementType::Track)
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
2021-08-22 17:18:38 +02:00
|
|
|
|
2021-08-29 00:57:46 +02:00
|
|
|
ted = &GetTrackElementDescriptor(tileElement->AsTrack()->GetTrackType());
|
2021-11-25 10:13:20 +01:00
|
|
|
if (!(std::get<0>(ted->SequenceProperties) & TRACK_SEQUENCE_FLAG_ORIGIN))
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
trackFound = true;
|
|
|
|
break;
|
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
|
|
|
|
|
|
if (!trackFound)
|
|
|
|
{
|
2021-09-02 04:00:18 +02:00
|
|
|
// Critical error! Stop trying to find the next sequence to set StationIndex.
|
2021-07-27 17:21:03 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-01-28 10:04:16 +01:00
|
|
|
tileElement->AsTrack()->SetStationIndex(GetStationIndex(&station));
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-02 04:00:18 +02:00
|
|
|
// determine what entrances and exits exist
|
2021-07-27 17:21:03 +02:00
|
|
|
FixedVector<TileCoordsXYZD, MAX_STATION_LOCATIONS> locations;
|
2022-01-28 10:04:16 +01:00
|
|
|
for (auto& station : stations)
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2022-01-28 10:04:16 +01:00
|
|
|
if (!station.Entrance.IsNull())
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2022-01-28 10:04:16 +01:00
|
|
|
locations.push_back(station.Entrance);
|
|
|
|
station.Entrance.SetNull();
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
|
2022-01-28 10:04:16 +01:00
|
|
|
if (!station.Exit.IsNull())
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2022-01-28 10:04:16 +01:00
|
|
|
locations.push_back(station.Exit);
|
|
|
|
station.Exit.SetNull();
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto locationListIter = locations.cbegin();
|
|
|
|
for (const TileCoordsXYZD& locationCoords : locations)
|
|
|
|
{
|
|
|
|
auto locationList = ++locationListIter;
|
2021-09-04 22:47:30 +02:00
|
|
|
// determine if there's another ride entrance at this location later in the array
|
|
|
|
// if there is, skip it. The last ride entrance in the array at the location is not skipped
|
2021-07-27 17:21:03 +02:00
|
|
|
bool duplicateLocation = false;
|
|
|
|
while (locationList != locations.cend())
|
|
|
|
{
|
|
|
|
const TileCoordsXYZD& locationCoords2 = *locationList++;
|
|
|
|
if (locationCoords.x == locationCoords2.x && locationCoords.y == locationCoords2.y
|
|
|
|
&& locationCoords.z == locationCoords2.z)
|
|
|
|
{
|
|
|
|
duplicateLocation = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (duplicateLocation)
|
|
|
|
{
|
2021-09-04 22:47:30 +02:00
|
|
|
// if it's a duplicate continue to the next ride entrance
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
|
|
|
}
|
2021-09-02 04:00:18 +02:00
|
|
|
// if it's not a duplicate location
|
2021-07-27 17:21:03 +02:00
|
|
|
CoordsXY location = locationCoords.ToCoordsXY();
|
|
|
|
|
|
|
|
TileElement* tileElement = map_get_first_element_at(location);
|
|
|
|
if (tileElement == nullptr)
|
|
|
|
continue;
|
|
|
|
do
|
|
|
|
{
|
2021-12-11 00:39:39 +01:00
|
|
|
if (tileElement->GetType() != TileElementType::Entrance)
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
|
|
|
if (tileElement->base_height != locationCoords.z)
|
|
|
|
continue;
|
2021-09-02 04:00:18 +02:00
|
|
|
if (tileElement->AsEntrance()->GetRideIndex() != id)
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
2021-09-02 04:00:18 +02:00
|
|
|
// if it's a park entrance continue to the next tile element
|
2021-07-27 17:21:03 +02:00
|
|
|
if (tileElement->AsEntrance()->GetEntranceType() > ENTRANCE_TYPE_RIDE_EXIT)
|
|
|
|
continue;
|
|
|
|
|
2021-09-04 22:47:30 +02:00
|
|
|
// find the station that's connected to this ride entrance
|
2021-07-27 17:21:03 +02:00
|
|
|
CoordsXY nextLocation = location;
|
|
|
|
nextLocation.x += CoordsDirectionDelta[tileElement->GetDirection()].x;
|
|
|
|
nextLocation.y += CoordsDirectionDelta[tileElement->GetDirection()].y;
|
|
|
|
|
2021-09-04 22:47:30 +02:00
|
|
|
// if there's no connected station, remove the ride entrance (see below)
|
2021-07-27 17:21:03 +02:00
|
|
|
bool shouldRemove = true;
|
|
|
|
TileElement* trackElement = map_get_first_element_at(nextLocation);
|
|
|
|
if (trackElement == nullptr)
|
|
|
|
continue;
|
|
|
|
do
|
|
|
|
{
|
2021-12-11 00:39:39 +01:00
|
|
|
if (trackElement->GetType() != TileElementType::Track)
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
2021-09-02 04:00:18 +02:00
|
|
|
if (trackElement->AsTrack()->GetRideIndex() != id)
|
2021-07-27 17:21:03 +02:00
|
|
|
continue;
|
|
|
|
if (trackElement->base_height != tileElement->base_height)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
auto trackType = trackElement->AsTrack()->GetTrackType();
|
|
|
|
|
2021-09-02 04:00:18 +02:00
|
|
|
// get the StationIndex for the station
|
2022-01-26 13:28:19 +01:00
|
|
|
StationIndex stationId = StationIndex::FromUnderlying(0);
|
2021-07-27 17:21:03 +02:00
|
|
|
if (trackType != TrackElemType::Maze)
|
|
|
|
{
|
2021-11-02 09:31:21 +01:00
|
|
|
uint8_t trackSequence = trackElement->AsTrack()->GetSequenceIndex();
|
|
|
|
|
|
|
|
// determine where the ride entrance is relative to the station track
|
|
|
|
Direction direction = (tileElement->GetDirection() - direction_reverse(trackElement->GetDirection())) & 3;
|
|
|
|
|
|
|
|
// if the ride entrance is not on a valid side, remove it
|
|
|
|
ted = &GetTrackElementDescriptor(trackType);
|
|
|
|
if (!(ted->SequenceProperties[trackSequence] & (1 << direction)))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-07-27 17:21:03 +02:00
|
|
|
stationId = trackElement->AsTrack()->GetStationIndex();
|
|
|
|
}
|
2022-01-28 15:57:28 +01:00
|
|
|
|
|
|
|
auto& station = GetStation(stationId);
|
2021-07-27 17:21:03 +02:00
|
|
|
if (tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_RIDE_EXIT)
|
|
|
|
{
|
2021-09-02 04:00:18 +02:00
|
|
|
// if the location is already set for this station, big problem!
|
2022-01-28 15:57:28 +01:00
|
|
|
if (!station.Exit.IsNull())
|
2021-07-27 17:21:03 +02:00
|
|
|
break;
|
2021-09-04 22:47:30 +02:00
|
|
|
// set the station's exit location to this one
|
2022-01-28 15:57:28 +01:00
|
|
|
CoordsXYZD loc = { location, station.GetBaseZ(), tileElement->GetDirection() };
|
|
|
|
station.Exit = TileCoordsXYZD{ loc };
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-09-02 04:00:18 +02:00
|
|
|
// if the location is already set for this station, big problem!
|
2022-01-28 15:57:28 +01:00
|
|
|
if (!station.Entrance.IsNull())
|
2021-07-27 17:21:03 +02:00
|
|
|
break;
|
2021-09-04 22:47:30 +02:00
|
|
|
// set the station's entrance location to this one
|
2022-01-28 15:57:28 +01:00
|
|
|
CoordsXYZD loc = { location, station.GetBaseZ(), tileElement->GetDirection() };
|
|
|
|
station.Entrance = TileCoordsXYZD{ loc };
|
2021-07-27 17:21:03 +02:00
|
|
|
}
|
2021-09-04 22:47:30 +02:00
|
|
|
// set the entrance's StationIndex as this station
|
2021-07-27 17:21:03 +02:00
|
|
|
tileElement->AsEntrance()->SetStationIndex(stationId);
|
|
|
|
shouldRemove = false;
|
|
|
|
} while (!(trackElement++)->IsLastForTile());
|
|
|
|
|
2021-09-04 22:47:30 +02:00
|
|
|
// remove the ride entrance and clean up if necessary
|
2021-07-27 17:21:03 +02:00
|
|
|
if (shouldRemove)
|
|
|
|
{
|
|
|
|
footpath_queue_chain_reset();
|
|
|
|
maze_entrance_hedge_replacement({ location, tileElement });
|
|
|
|
footpath_remove_edges_at(location, tileElement);
|
|
|
|
footpath_update_queue_chains();
|
|
|
|
map_invalidate_tile_full(location);
|
|
|
|
tile_element_remove(tileElement);
|
|
|
|
tileElement--;
|
|
|
|
}
|
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ride_select_backwards_from_front()
|
|
|
|
{
|
|
|
|
auto ride = get_ride(_currentRideIndex);
|
|
|
|
if (ride != nullptr)
|
|
|
|
{
|
|
|
|
ride_construction_invalidate_current_track();
|
|
|
|
track_begin_end trackBeginEnd;
|
|
|
|
if (track_block_get_previous_from_zero(_currentTrackBegin, ride, _currentTrackPieceDirection, &trackBeginEnd))
|
|
|
|
{
|
2021-08-11 17:51:59 +02:00
|
|
|
_rideConstructionState = RideConstructionState::Selected;
|
2021-07-27 17:21:03 +02:00
|
|
|
_currentTrackBegin.x = trackBeginEnd.begin_x;
|
|
|
|
_currentTrackBegin.y = trackBeginEnd.begin_y;
|
|
|
|
_currentTrackBegin.z = trackBeginEnd.begin_z;
|
|
|
|
_currentTrackPieceDirection = trackBeginEnd.begin_direction;
|
|
|
|
_currentTrackPieceType = trackBeginEnd.begin_element->AsTrack()->GetTrackType();
|
|
|
|
_currentTrackSelectionFlags = 0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ride_select_forwards_from_back()
|
|
|
|
{
|
|
|
|
auto ride = get_ride(_currentRideIndex);
|
|
|
|
if (ride != nullptr)
|
|
|
|
{
|
|
|
|
ride_construction_invalidate_current_track();
|
|
|
|
|
|
|
|
int32_t z = _currentTrackBegin.z;
|
|
|
|
int32_t direction = direction_reverse(_currentTrackPieceDirection);
|
|
|
|
CoordsXYE next_track;
|
|
|
|
if (track_block_get_next_from_zero(_currentTrackBegin, ride, direction, &next_track, &z, &direction, false))
|
|
|
|
{
|
2021-08-11 17:51:59 +02:00
|
|
|
_rideConstructionState = RideConstructionState::Selected;
|
2021-07-27 17:21:03 +02:00
|
|
|
_currentTrackBegin.x = next_track.x;
|
|
|
|
_currentTrackBegin.y = next_track.y;
|
|
|
|
_currentTrackBegin.z = z;
|
|
|
|
_currentTrackPieceDirection = next_track.element->GetDirection();
|
|
|
|
_currentTrackPieceType = next_track.element->AsTrack()->GetTrackType();
|
|
|
|
_currentTrackSelectionFlags = 0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006B58EF
|
|
|
|
*/
|
|
|
|
bool ride_are_all_possible_entrances_and_exits_built(Ride* ride)
|
|
|
|
{
|
|
|
|
if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP))
|
|
|
|
return true;
|
|
|
|
|
2022-01-28 00:34:04 +01:00
|
|
|
for (auto& station : ride->GetStations())
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
2022-01-28 00:34:04 +01:00
|
|
|
if (station.Start.IsNull())
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2022-01-28 00:34:04 +01:00
|
|
|
if (station.Entrance.IsNull())
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
gGameCommandErrorText = STR_ENTRANCE_NOT_YET_BUILT;
|
|
|
|
return false;
|
|
|
|
}
|
2022-01-28 00:34:04 +01:00
|
|
|
if (station.Exit.IsNull())
|
2021-07-27 17:21:03 +02:00
|
|
|
{
|
|
|
|
gGameCommandErrorText = STR_EXIT_NOT_YET_BUILT;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|