diff --git a/src/openrct2/actions/GameActionRegistration.cpp b/src/openrct2/actions/GameActionRegistration.cpp index b0c2400b95..ccd395562e 100644 --- a/src/openrct2/actions/GameActionRegistration.cpp +++ b/src/openrct2/actions/GameActionRegistration.cpp @@ -39,6 +39,7 @@ #include "StaffSetNameAction.hpp" #include "StaffSetOrdersAction.hpp" #include "TrackPlaceAction.hpp" +#include "TrackRemoveAction.hpp" #include "WallRemoveAction.hpp" namespace GameActions @@ -75,6 +76,7 @@ namespace GameActions Register(); Register(); Register(); + Register(); Register(); Register(); } diff --git a/src/openrct2/actions/TrackRemoveAction.hpp b/src/openrct2/actions/TrackRemoveAction.hpp new file mode 100644 index 0000000000..e8e5cd95c1 --- /dev/null +++ b/src/openrct2/actions/TrackRemoveAction.hpp @@ -0,0 +1,532 @@ +/***************************************************************************** + * Copyright (c) 2014-2018 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#pragma once + +#include "../management/Finance.h" +#include "../ride/RideGroupManager.h" +#include "../ride/Track.h" +#include "../ride/TrackData.h" +#include "../ride/TrackDesign.h" +#include "../util/Util.h" +#include "../world/MapAnimation.h" +#include "../world/Surface.h" +#include "GameAction.h" + +DEFINE_GAME_ACTION(TrackRemoveAction, GAME_COMMAND_REMOVE_TRACK, GameActionResult) +{ +private: + int32_t _trackType{ 0 }; + int32_t _sequence{ 0 }; + CoordsXYZD _origin; + +public: + TrackRemoveAction() + { + } + + TrackRemoveAction(int32_t trackType, int32_t sequence, CoordsXYZD origin) + : _trackType(trackType) + , _sequence(sequence) + , _origin(origin) + { + _origin.direction &= 3; + } + + uint16_t GetActionFlags() const override + { + return GameAction::GetActionFlags(); + } + + void Serialise(DataSerialiser & stream) override + { + GameAction::Serialise(stream); + + stream << DS_TAG(_trackType) << DS_TAG(_origin); + } + + GameActionResult::Ptr Query() const override + { + auto res = MakeResult(); + res->Position.x = _origin.x + 16; + res->Position.y = _origin.y + 16; + res->Position.z = _origin.z; + res->ExpenditureType = RCT_EXPENDITURE_TYPE_RIDE_CONSTRUCTION; + + int16_t trackpieceZ = _origin.z; + + // Stations require some massaging of the track type for comparing + auto comparableTrackType = _trackType; + switch (_trackType) + { + case TRACK_ELEM_BEGIN_STATION: + case TRACK_ELEM_MIDDLE_STATION: + comparableTrackType = TRACK_ELEM_END_STATION; + break; + } + + if (!(GetFlags() & GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED) && game_is_paused() && !gCheatsBuildInPauseMode) + { + return MakeResult(GA_ERROR::GAME_PAUSED, STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED); + } + + bool found = false; + bool isGhost = GetFlags() & GAME_COMMAND_FLAG_GHOST; + TileElement* tileElement = map_get_first_element_at(_origin.x / 32, _origin.y / 32); + if (tileElement == nullptr) + { + log_warning("Invalid coordinates for track removal. x = %d, y = %d", _origin.x, _origin.y); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_NONE); + } + + do + { + if (tileElement->base_height * 8 != _origin.z) + continue; + + if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK) + continue; + + if ((tileElement->GetDirection()) != _origin.direction) + continue; + + if (tileElement->AsTrack()->GetSequenceIndex() != _sequence) + continue; + + if (tileElement->IsGhost() != isGhost) + continue; + + uint8_t tileTrackType = tileElement->AsTrack()->GetTrackType(); + switch (tileTrackType) + { + case TRACK_ELEM_BEGIN_STATION: + case TRACK_ELEM_MIDDLE_STATION: + tileTrackType = TRACK_ELEM_END_STATION; + break; + } + + if (tileTrackType != comparableTrackType) + continue; + + found = true; + break; + } while (!(tileElement++)->IsLastForTile()); + + if (!found) + { + log_warning( + "Track Element not found. x = %d, y = %d, z = %d, d = %d, seq = %d.", _origin.x, _origin.y, _origin.z, + _origin.direction, _sequence); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_NONE); + } + + if (tileElement->flags & TILE_ELEMENT_FLAG_INDESTRUCTIBLE_TRACK_PIECE) + { + return MakeResult(GA_ERROR::DISALLOWED, STR_YOU_ARE_NOT_ALLOWED_TO_REMOVE_THIS_SECTION); + } + + ride_id_t rideIndex = tileElement->AsTrack()->GetRideIndex(); + auto trackType = tileElement->AsTrack()->GetTrackType(); + bool isLiftHill = tileElement->AsTrack()->HasChain(); + + Ride* ride = get_ride(rideIndex); + const rct_preview_track* trackBlock = get_track_def_from_ride(ride, trackType); + trackBlock += tileElement->AsTrack()->GetSequenceIndex(); + + auto startLoc = _origin; + startLoc.direction = tileElement->GetDirection(); + + LocationXY16 trackLoc = { trackBlock->x, trackBlock->y }; + rotate_map_coordinates(&trackLoc.x, &trackLoc.y, startLoc.direction); + startLoc.x -= trackLoc.x; + startLoc.y -= trackLoc.y; + startLoc.z -= trackBlock->z; + + money32 cost = 0; + + trackBlock = get_track_def_from_ride(ride, trackType); + for (; trackBlock->index != 255; trackBlock++) + { + CoordsXYZ mapLoc{ startLoc.x, startLoc.y, startLoc.z }; + LocationXY16 trackLoc = { trackBlock->x, trackBlock->y }; + rotate_map_coordinates(&trackLoc.x, &trackLoc.y, startLoc.direction); + mapLoc.x += trackLoc.x; + mapLoc.y += trackLoc.y; + mapLoc.z += trackBlock->z; + + map_invalidate_tile_full(mapLoc.x, mapLoc.y); + + trackpieceZ = mapLoc.z; + + found = false; + tileElement = map_get_first_element_at(mapLoc.x / 32, mapLoc.y / 32); + do + { + if (tileElement == nullptr) + break; + + if (tileElement->base_height != mapLoc.z / 8) + continue; + + if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK) + continue; + + if (tileElement->GetDirection() != _origin.direction) + continue; + + if (tileElement->AsTrack()->GetSequenceIndex() != trackBlock->index) + continue; + + if (tileElement->AsTrack()->GetTrackType() != trackType) + continue; + + if (tileElement->IsGhost() != isGhost) + continue; + + found = true; + break; + } while (!(tileElement++)->IsLastForTile()); + + if (!found) + { + log_warning( + "Track Element not found. x = %d, y = %d, z = %d, d = %d, seq = %d.", mapLoc.x, mapLoc.y, mapLoc.z, + _origin.direction, trackBlock->index); + return MakeResult(GA_ERROR::UNKNOWN, STR_NONE); + } + + int32_t entranceDirections; + if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_FLAT_RIDE)) + { + entranceDirections = FlatRideTrackSequenceProperties[trackType][0]; + } + else + { + entranceDirections = TrackSequenceProperties[trackType][0]; + } + + if (entranceDirections & TRACK_SEQUENCE_FLAG_ORIGIN && (tileElement->AsTrack()->GetSequenceIndex() == 0)) + { + if (!track_remove_station_element(mapLoc.x, mapLoc.y, mapLoc.z / 8, _origin.direction, rideIndex, 0)) + { + return MakeResult(GA_ERROR::UNKNOWN, gGameCommandErrorText); + } + } + + TileElement* surfaceElement = map_get_surface_element_at({ mapLoc.x, mapLoc.y }); + if (surfaceElement == nullptr) + { + log_warning("Surface Element not found. x = %d, y = %d", mapLoc.x, mapLoc.y); + return MakeResult(GA_ERROR::UNKNOWN, STR_NONE); + } + + int8_t _support_height = tileElement->base_height - surfaceElement->base_height; + if (_support_height < 0) + { + _support_height = 10; + } + + cost += (_support_height / 2) * RideTrackCosts[ride->type].support_price; + } + + money32 price = RideTrackCosts[ride->type].track_price; + if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_FLAT_RIDE)) + { + price *= FlatRideTrackPricing[trackType]; + } + else + { + price *= TrackPricing[trackType]; + } + price >>= 16; + price = (price + cost) / 2; + if (ride->lifecycle_flags & RIDE_LIFECYCLE_EVER_BEEN_OPENED) + price *= -7; + else + price *= -10; + + if (gGameCommandNestLevel == 1) + { + LocationXYZ16 coord; + coord.x = startLoc.x + 16; + coord.y = startLoc.y + 16; + coord.z = trackpieceZ; + network_set_player_last_action_coord(network_get_player_index(game_command_playerid), coord); + } + + res->Cost = price; + return res; + } + + GameActionResult::Ptr Execute() const override + { + auto res = MakeResult(); + res->Position.x = _origin.x + 16; + res->Position.y = _origin.y + 16; + res->Position.z = _origin.z; + res->ExpenditureType = RCT_EXPENDITURE_TYPE_RIDE_CONSTRUCTION; + + int16_t trackpieceZ = _origin.z; + + // Stations require some massaging of the track type for comparing + auto comparableTrackType = _trackType; + switch (_trackType) + { + case TRACK_ELEM_BEGIN_STATION: + case TRACK_ELEM_MIDDLE_STATION: + comparableTrackType = TRACK_ELEM_END_STATION; + break; + } + + bool found = false; + bool isGhost = GetFlags() & GAME_COMMAND_FLAG_GHOST; + TileElement* tileElement = map_get_first_element_at(_origin.x / 32, _origin.y / 32); + if (tileElement == nullptr) + { + log_warning("Invalid coordinates for track removal. x = %d, y = %d", _origin.x, _origin.y); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_NONE); + } + + do + { + if (tileElement->base_height * 8 != _origin.z) + continue; + + if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK) + continue; + + if ((tileElement->GetDirection()) != _origin.direction) + continue; + + if (tileElement->AsTrack()->GetSequenceIndex() != _sequence) + continue; + + if (tileElement->IsGhost() != isGhost) + continue; + + uint8_t tileTrackType = tileElement->AsTrack()->GetTrackType(); + switch (tileTrackType) + { + case TRACK_ELEM_BEGIN_STATION: + case TRACK_ELEM_MIDDLE_STATION: + tileTrackType = TRACK_ELEM_END_STATION; + break; + } + + if (tileTrackType != comparableTrackType) + continue; + + found = true; + break; + } while (!(tileElement++)->IsLastForTile()); + + if (!found) + { + log_warning( + "Track Element not found. x = %d, y = %d, z = %d, d = %d, seq = %d.", _origin.x, _origin.y, _origin.z, + _origin.direction, _sequence); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_NONE); + } + + ride_id_t rideIndex = tileElement->AsTrack()->GetRideIndex(); + auto trackType = tileElement->AsTrack()->GetTrackType(); + bool isLiftHill = tileElement->AsTrack()->HasChain(); + + Ride* ride = get_ride(rideIndex); + const rct_preview_track* trackBlock = get_track_def_from_ride(ride, trackType); + trackBlock += tileElement->AsTrack()->GetSequenceIndex(); + + auto startLoc = _origin; + startLoc.direction = tileElement->GetDirection(); + + LocationXY16 trackLoc = { trackBlock->x, trackBlock->y }; + rotate_map_coordinates(&trackLoc.x, &trackLoc.y, startLoc.direction); + startLoc.x -= trackLoc.x; + startLoc.y -= trackLoc.y; + startLoc.z -= trackBlock->z; + + money32 cost = 0; + + trackBlock = get_track_def_from_ride(ride, trackType); + for (; trackBlock->index != 255; trackBlock++) + { + CoordsXYZ mapLoc{ startLoc.x, startLoc.y, startLoc.z }; + LocationXY16 trackLoc = { trackBlock->x, trackBlock->y }; + rotate_map_coordinates(&trackLoc.x, &trackLoc.y, startLoc.direction); + mapLoc.x += trackLoc.x; + mapLoc.y += trackLoc.y; + mapLoc.z += trackBlock->z; + + map_invalidate_tile_full(mapLoc.x, mapLoc.y); + + trackpieceZ = mapLoc.z; + + found = false; + tileElement = map_get_first_element_at(mapLoc.x / 32, mapLoc.y / 32); + do + { + if (tileElement == nullptr) + break; + + if (tileElement->base_height != mapLoc.z / 8) + continue; + + if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK) + continue; + + if (tileElement->GetDirection() != _origin.direction) + continue; + + if (tileElement->AsTrack()->GetSequenceIndex() != trackBlock->index) + continue; + + if (tileElement->AsTrack()->GetTrackType() != trackType) + continue; + + if (tileElement->IsGhost() != isGhost) + continue; + + found = true; + break; + } while (!(tileElement++)->IsLastForTile()); + + if (!found) + { + log_warning( + "Track Element not found. x = %d, y = %d, z = %d, d = %d, seq = %d.", mapLoc.x, mapLoc.y, mapLoc.z, + _origin.direction, trackBlock->index); + return MakeResult(GA_ERROR::UNKNOWN, STR_NONE); + } + + int32_t entranceDirections; + if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_FLAT_RIDE)) + { + entranceDirections = FlatRideTrackSequenceProperties[trackType][0]; + } + else + { + entranceDirections = TrackSequenceProperties[trackType][0]; + } + + if (entranceDirections & TRACK_SEQUENCE_FLAG_ORIGIN && (tileElement->AsTrack()->GetSequenceIndex() == 0)) + { + if (!track_remove_station_element(mapLoc.x, mapLoc.y, mapLoc.z / 8, _origin.direction, rideIndex, 0)) + { + return MakeResult(GA_ERROR::UNKNOWN, gGameCommandErrorText); + } + } + + TileElement* surfaceElement = map_get_surface_element_at({ mapLoc.x, mapLoc.y }); + if (surfaceElement == nullptr) + { + log_warning("Surface Element not found. x = %d, y = %d", mapLoc.x, mapLoc.y); + return MakeResult(GA_ERROR::UNKNOWN, STR_NONE); + } + + int8_t _support_height = tileElement->base_height - surfaceElement->base_height; + if (_support_height < 0) + { + _support_height = 10; + } + + cost += (_support_height / 2) * RideTrackCosts[ride->type].support_price; + + if (entranceDirections & TRACK_SEQUENCE_FLAG_ORIGIN && (tileElement->AsTrack()->GetSequenceIndex() == 0)) + { + if (!track_remove_station_element( + mapLoc.x, mapLoc.y, mapLoc.z / 8, _origin.direction, rideIndex, GAME_COMMAND_FLAG_APPLY)) + { + return MakeResult(GA_ERROR::UNKNOWN, gGameCommandErrorText); + } + } + + if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_TRACK_MUST_BE_ON_WATER)) + { + surfaceElement->AsSurface()->SetHasTrackThatNeedsWater(false); + } + + invalidate_test_results(ride); + footpath_queue_chain_reset(); + if (!gCheatsDisableClearanceChecks || !(tileElement->flags & TILE_ELEMENT_FLAG_GHOST)) + { + footpath_remove_edges_at(mapLoc.x, mapLoc.y, tileElement); + } + tile_element_remove(tileElement); + sub_6CB945(ride); + if (!(GetFlags() & GAME_COMMAND_FLAG_GHOST)) + { + ride_update_max_vehicles(ride); + } + } + + switch (trackType) + { + case TRACK_ELEM_ON_RIDE_PHOTO: + ride->lifecycle_flags &= ~RIDE_LIFECYCLE_ON_RIDE_PHOTO; + break; + case TRACK_ELEM_CABLE_LIFT_HILL: + ride->lifecycle_flags &= ~RIDE_LIFECYCLE_CABLE_LIFT_HILL_COMPONENT_USED; + break; + case TRACK_ELEM_BLOCK_BRAKES: + ride->num_block_brakes--; + if (ride->num_block_brakes == 0) + { + ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_OPERATING; + ride->mode = RIDE_MODE_CONTINUOUS_CIRCUIT; + if (ride->type == RIDE_TYPE_LIM_LAUNCHED_ROLLER_COASTER) + { + ride->mode = RIDE_MODE_POWERED_LAUNCH; + } + } + break; + } + + switch (trackType) + { + case TRACK_ELEM_25_DEG_UP_TO_FLAT: + case TRACK_ELEM_60_DEG_UP_TO_FLAT: + case TRACK_ELEM_DIAG_25_DEG_UP_TO_FLAT: + case TRACK_ELEM_DIAG_60_DEG_UP_TO_FLAT: + if (!isLiftHill) + break; + [[fallthrough]]; + case TRACK_ELEM_CABLE_LIFT_HILL: + ride->num_block_brakes--; + break; + } + + money32 price = RideTrackCosts[ride->type].track_price; + if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_FLAT_RIDE)) + { + price *= FlatRideTrackPricing[trackType]; + } + else + { + price *= TrackPricing[trackType]; + } + price >>= 16; + price = (price + cost) / 2; + if (ride->lifecycle_flags & RIDE_LIFECYCLE_EVER_BEEN_OPENED) + price *= -7; + else + price *= -10; + + if (gGameCommandNestLevel == 1) + { + LocationXYZ16 coord; + coord.x = startLoc.x + 16; + coord.y = startLoc.y + 16; + coord.z = trackpieceZ; + network_set_player_last_action_coord(network_get_player_index(game_command_playerid), coord); + } + + res->Cost = price; + return res; + } +}; diff --git a/src/openrct2/ride/Track.cpp b/src/openrct2/ride/Track.cpp index 3e6acd5cba..a38528da70 100644 --- a/src/openrct2/ride/Track.cpp +++ b/src/openrct2/ride/Track.cpp @@ -787,7 +787,7 @@ bool track_add_station_element(int32_t x, int32_t y, int32_t z, int32_t directio * * rct2: 0x006C494B */ -static bool track_remove_station_element(int32_t x, int32_t y, int32_t z, int32_t direction, ride_id_t rideIndex, int32_t flags) +bool track_remove_station_element(int32_t x, int32_t y, int32_t z, int32_t direction, ride_id_t rideIndex, int32_t flags) { int32_t removeX = x; int32_t removeY = y; diff --git a/src/openrct2/ride/Track.h b/src/openrct2/ride/Track.h index 5a3454c2cc..e2ad71af9f 100644 --- a/src/openrct2/ride/Track.h +++ b/src/openrct2/ride/Track.h @@ -546,6 +546,7 @@ int32_t track_get_actual_bank_2(int32_t rideType, bool isInverted, int32_t bank) int32_t track_get_actual_bank_3(rct_vehicle* vehicle, TileElement* tileElement); bool track_add_station_element(int32_t x, int32_t y, int32_t z, int32_t direction, ride_id_t rideIndex, int32_t flags); +bool track_remove_station_element(int32_t x, int32_t y, int32_t z, int32_t direction, ride_id_t rideIndex, int32_t flags); void game_command_remove_track( int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp);