diff --git a/src/openrct2-ui/windows/TrackDesignPlace.cpp b/src/openrct2-ui/windows/TrackDesignPlace.cpp index ec699e56f3..902fdc7a63 100644 --- a/src/openrct2-ui/windows/TrackDesignPlace.cpp +++ b/src/openrct2-ui/windows/TrackDesignPlace.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -446,23 +447,19 @@ static int32_t window_track_place_get_base_z(int32_t x, int32_t y) static void window_track_place_attempt_placement( TrackDesign* td6, int32_t x, int32_t y, int32_t z, int32_t bl, money32* cost, ride_id_t* rideIndex) { - int32_t eax, ebx, ecx, edx, esi, edi, ebp; - money32 result; + auto tdAction = TrackDesignAction({ x, y, z }, *_trackDesign); + tdAction.SetFlags(bl); + auto res = (bl & GAME_COMMAND_FLAG_APPLY) ? GameActions::Execute(&tdAction) : GameActions::Query(&tdAction); - edx = esi = ebp = 0; - eax = x; - ebx = bl; - ecx = y; - edi = z; - - gActiveTrackDesign = _trackDesign.get(); - result = game_do_command_p(GAME_COMMAND_PLACE_TRACK_DESIGN, &eax, &ebx, &ecx, &edx, &esi, &edi, &ebp); - gActiveTrackDesign = nullptr; - - if (cost != nullptr) - *cost = result; - if (rideIndex != nullptr) - *rideIndex = edi & 0xFF; + if (res->Error != GA_ERROR::OK) + { + *cost = MONEY32_UNDEFINED; + } + else + { + *cost = res->Cost; + } + *rideIndex = dynamic_cast(res.get())->rideIndex; } /** diff --git a/src/openrct2/actions/GameActionRegistration.cpp b/src/openrct2/actions/GameActionRegistration.cpp index 3de9dad348..c693d892c6 100644 --- a/src/openrct2/actions/GameActionRegistration.cpp +++ b/src/openrct2/actions/GameActionRegistration.cpp @@ -77,6 +77,7 @@ #include "StaffSetPatrolAreaAction.hpp" #include "SurfaceSetStyleAction.hpp" #include "TileModifyAction.hpp" +#include "TrackDesignAction.h" #include "TrackPlaceAction.hpp" #include "TrackRemoveAction.hpp" #include "TrackSetBrakeSpeedAction.hpp" @@ -157,6 +158,7 @@ namespace GameActions Register(); Register(); Register(); + Register(); Register(); Register(); Register(); diff --git a/src/openrct2/actions/TrackDesignAction.cpp b/src/openrct2/actions/TrackDesignAction.cpp new file mode 100644 index 0000000000..9b7c5a04af --- /dev/null +++ b/src/openrct2/actions/TrackDesignAction.cpp @@ -0,0 +1,275 @@ +/***************************************************************************** + * Copyright (c) 2014-2019 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. + *****************************************************************************/ + +#include "TrackDesignAction.h" + +#include "../management/Finance.h" +#include "../management/Research.h" +#include "../object/ObjectRepository.h" +#include "../ride/RideGroupManager.h" +#include "../ride/TrackDesign.h" +#include "RideSetSetting.hpp" +#include "RideSetVehiclesAction.hpp" + +static int32_t place_virtual_track( + const TrackDesign& td6, uint8_t ptdOperation, bool placeScenery, Ride* ride, const CoordsXYZ& loc) +{ + return place_virtual_track(const_cast(&td6), ptdOperation, placeScenery, ride, loc.x, loc.y, loc.z); +} + +GameActionResult::Ptr TrackDesignAction::Query() const +{ + auto res = MakeResult(); + res->Position.x = _loc.x + 16; + res->Position.y = _loc.y + 16; + res->Position.z = _loc.z; + res->ExpenditureType = RCT_EXPENDITURE_TYPE_RIDE_CONSTRUCTION; + + if (!(GetFlags() & GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED)) + { + if (game_is_paused() && !gCheatsBuildInPauseMode) + { + return MakeResult(GA_ERROR::GAME_PAUSED, STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED); + } + } + + const rct_object_entry* rideEntryObject = &_td.vehicle_object; + + uint8_t entryType, entryIndex; + if (!find_object_in_entry_group(rideEntryObject, &entryType, &entryIndex)) + { + entryIndex = 0xFF; + } + // Force a fallback if the entry is not invented yet a td6 of it is selected, which can happen in select-by-track-type mode. + else if (!ride_entry_is_invented(entryIndex) && !gCheatsIgnoreResearchStatus) + { + entryIndex = 0xFF; + } + + // The rest of the cases are handled by the code in ride_create() + if (RideGroupManager::RideTypeHasRideGroups(_td.type) && entryIndex == 0xFF) + { + const ObjectRepositoryItem* ori = object_repository_find_object_by_name(rideEntryObject->name); + if (ori != nullptr) + { + uint8_t rideGroupIndex = ori->RideInfo.RideGroupIndex; + const RideGroup* td6RideGroup = RideGroupManager::RideGroupFind(_td.type, rideGroupIndex); + + uint8_t* availableRideEntries = get_ride_entry_indices_for_ride_type(_td.type); + for (uint8_t* rei = availableRideEntries; *rei != RIDE_ENTRY_INDEX_NULL; rei++) + { + rct_ride_entry* ire = get_ride_entry(*rei); + + if (!ride_entry_is_invented(*rei) && !gCheatsIgnoreResearchStatus) + { + continue; + } + + const RideGroup* irg = RideGroupManager::GetRideGroup(_td.type, ire); + if (td6RideGroup->Equals(irg)) + { + entryIndex = *rei; + break; + } + } + } + } + + ride_id_t rideIndex; + uint8_t rideColour; + money32 createRideResult = ride_create_command(_td.type, entryIndex, GetFlags(), &rideIndex, &rideColour); + if (createRideResult == MONEY32_UNDEFINED) + { + return MakeResult(GA_ERROR::NO_FREE_ELEMENTS, STR_CANT_CREATE_NEW_RIDE_ATTRACTION, STR_NONE); + } + + auto ride = get_ride(rideIndex); + if (ride == nullptr) + { + log_warning("Invalid game command for track placement, ride id = %d", rideIndex); + return MakeResult(GA_ERROR::UNKNOWN); + } + + money32 cost = 0; + + bool placeScenery = true; + cost = place_virtual_track(_td, PTD_OPERATION_PLACE_QUERY, placeScenery, ride, _loc); + if (_trackDesignPlaceStateSceneryUnavailable) + { + placeScenery = false; + cost = place_virtual_track(_td, PTD_OPERATION_PLACE_QUERY, placeScenery, ride, _loc); + } + + rct_string_id error_reason = gGameCommandErrorText; + ride_action_modify(ride, RIDE_MODIFY_DEMOLISH, GetFlags()); + if (cost == MONEY32_UNDEFINED) + { + return MakeResult(GA_ERROR::DISALLOWED, error_reason); + } + res->Cost = cost; + return res; +} + +GameActionResult::Ptr TrackDesignAction::Execute() const +{ + auto res = MakeResult(); + res->Position.x = _loc.x + 16; + res->Position.y = _loc.y + 16; + res->Position.z = _loc.z; + res->ExpenditureType = RCT_EXPENDITURE_TYPE_RIDE_CONSTRUCTION; + + if (!(GetFlags() & GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED)) + { + if (game_is_paused() && !gCheatsBuildInPauseMode) + { + return MakeResult(GA_ERROR::GAME_PAUSED, STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED); + } + } + + const rct_object_entry* rideEntryObject = &_td.vehicle_object; + + uint8_t entryType, entryIndex; + if (!find_object_in_entry_group(rideEntryObject, &entryType, &entryIndex)) + { + entryIndex = 0xFF; + } + // Force a fallback if the entry is not invented yet a td6 of it is selected, which can happen in select-by-track-type mode. + else if (!ride_entry_is_invented(entryIndex) && !gCheatsIgnoreResearchStatus) + { + entryIndex = 0xFF; + } + + // The rest of the cases are handled by the code in ride_create() + if (RideGroupManager::RideTypeHasRideGroups(_td.type) && entryIndex == 0xFF) + { + const ObjectRepositoryItem* ori = object_repository_find_object_by_name(rideEntryObject->name); + if (ori != nullptr) + { + uint8_t rideGroupIndex = ori->RideInfo.RideGroupIndex; + const RideGroup* td6RideGroup = RideGroupManager::RideGroupFind(_td.type, rideGroupIndex); + + uint8_t* availableRideEntries = get_ride_entry_indices_for_ride_type(_td.type); + for (uint8_t* rei = availableRideEntries; *rei != RIDE_ENTRY_INDEX_NULL; rei++) + { + rct_ride_entry* ire = get_ride_entry(*rei); + + if (!ride_entry_is_invented(*rei) && !gCheatsIgnoreResearchStatus) + { + continue; + } + + const RideGroup* irg = RideGroupManager::GetRideGroup(_td.type, ire); + if (td6RideGroup->Equals(irg)) + { + entryIndex = *rei; + break; + } + } + } + } + + ride_id_t rideIndex; + uint8_t rideColour; + money32 createRideResult = ride_create_command(_td.type, entryIndex, GetFlags(), &rideIndex, &rideColour); + if (createRideResult == MONEY32_UNDEFINED) + { + return MakeResult(GA_ERROR::NO_FREE_ELEMENTS, STR_CANT_CREATE_NEW_RIDE_ATTRACTION, STR_NONE); + } + + auto ride = get_ride(rideIndex); + if (ride == nullptr) + { + log_warning("Invalid game command for track placement, ride id = %d", rideIndex); + return MakeResult(GA_ERROR::UNKNOWN); + } + + money32 cost = 0; + + bool placeScenery = true; + cost = place_virtual_track(_td, PTD_OPERATION_PLACE_QUERY, placeScenery, ride, _loc); + if (_trackDesignPlaceStateSceneryUnavailable) + { + placeScenery = false; + cost = place_virtual_track(_td, PTD_OPERATION_PLACE_QUERY, placeScenery, ride, _loc); + } + + if (cost != MONEY32_UNDEFINED) + { + uint8_t operation; + if (GetFlags() & GAME_COMMAND_FLAG_GHOST) + { + operation = PTD_OPERATION_PLACE_GHOST; + } + else + { + operation = PTD_OPERATION_PLACE; + } + + cost = place_virtual_track(_td, operation, placeScenery, ride, _loc); + } + + if (cost == MONEY32_UNDEFINED) + { + rct_string_id error_reason = gGameCommandErrorText; + ride_action_modify(ride, RIDE_MODIFY_DEMOLISH, GetFlags()); + return MakeResult(GA_ERROR::DISALLOWED, error_reason); + } + + if (entryIndex != 0xFF) + { + auto colour = ride_get_unused_preset_vehicle_colour(entryIndex); + auto rideSetVehicleAction = RideSetVehicleAction(ride->id, RideSetVehicleType::RideEntry, entryIndex, colour); + GameActions::ExecuteNested(&rideSetVehicleAction); + } + + set_operating_setting_nested(ride->id, RideSetSetting::Mode, _td.ride_mode, GAME_COMMAND_FLAG_APPLY); + auto rideSetVehicleAction2 = RideSetVehicleAction(ride->id, RideSetVehicleType::NumTrains, _td.number_of_trains); + GameActions::ExecuteNested(&rideSetVehicleAction2); + + auto rideSetVehicleAction3 = RideSetVehicleAction( + ride->id, RideSetVehicleType::NumCarsPerTrain, _td.number_of_cars_per_train); + GameActions::ExecuteNested(&rideSetVehicleAction3); + + set_operating_setting_nested(ride->id, RideSetSetting::Departure, _td.depart_flags, GAME_COMMAND_FLAG_APPLY); + set_operating_setting_nested(ride->id, RideSetSetting::MinWaitingTime, _td.min_waiting_time, GAME_COMMAND_FLAG_APPLY); + set_operating_setting_nested(ride->id, RideSetSetting::MaxWaitingTime, _td.max_waiting_time, GAME_COMMAND_FLAG_APPLY); + set_operating_setting_nested(ride->id, RideSetSetting::Operation, _td.operation_setting, GAME_COMMAND_FLAG_APPLY); + set_operating_setting_nested(ride->id, RideSetSetting::LiftHillSpeed, _td.lift_hill_speed & 0x1F, GAME_COMMAND_FLAG_APPLY); + + uint8_t num_circuits = _td.num_circuits; + if (num_circuits == 0) + { + num_circuits = 1; + } + set_operating_setting_nested(ride->id, RideSetSetting::NumCircuits, num_circuits, GAME_COMMAND_FLAG_APPLY); + ride->SetToDefaultInspectionInterval(); + ride->lifecycle_flags |= RIDE_LIFECYCLE_NOT_CUSTOM_DESIGN; + ride->colour_scheme_type = _td.colour_scheme; + + ride->entrance_style = _td.entrance_style; + + for (int32_t i = 0; i < RCT12_NUM_COLOUR_SCHEMES; i++) + { + ride->track_colour[i].main = _td.track_spine_colour[i]; + ride->track_colour[i].additional = _td.track_rail_colour[i]; + ride->track_colour[i].supports = _td.track_support_colour[i]; + } + + for (int32_t i = 0; i < MAX_VEHICLES_PER_RIDE; i++) + { + ride->vehicle_colours[i].Body = _td.vehicle_colours[i].body_colour; + ride->vehicle_colours[i].Trim = _td.vehicle_colours[i].trim_colour; + ride->vehicle_colours[i].Ternary = _td.vehicle_additional_colour[i]; + } + + ride_set_name(ride, _td.name.c_str(), GAME_COMMAND_FLAG_APPLY); + res->Cost = cost; + res->rideIndex = ride->id; + return res; +} diff --git a/src/openrct2/actions/TrackDesignAction.h b/src/openrct2/actions/TrackDesignAction.h new file mode 100644 index 0000000000..1fd606856f --- /dev/null +++ b/src/openrct2/actions/TrackDesignAction.h @@ -0,0 +1,68 @@ +/***************************************************************************** + * Copyright (c) 2014-2019 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 "GameAction.h" +#include "../ride/TrackDesign.h" + +class TrackDesignActionResult final : public GameActionResult +{ +public: + TrackDesignActionResult() + : GameActionResult(GA_ERROR::OK, STR_NONE) + { + } + TrackDesignActionResult(GA_ERROR error) + : GameActionResult(error, STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE, STR_NONE) + { + } + TrackDesignActionResult(GA_ERROR error, rct_string_id title, rct_string_id message) + : GameActionResult(error, title, message) + { + } + TrackDesignActionResult(GA_ERROR error, rct_string_id message) + : GameActionResult(error, STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE, message) + { + } + + ride_id_t rideIndex = RIDE_ID_NULL; +}; + +DEFINE_GAME_ACTION(TrackDesignAction, GAME_COMMAND_PLACE_TRACK_DESIGN, TrackDesignActionResult) +{ +private: + CoordsXYZ _loc; + TrackDesign _td; + +public: + TrackDesignAction() + { + } + TrackDesignAction(CoordsXYZ location, const TrackDesign &td) + : _loc(location) + , _td(td) + { + } + + uint16_t GetActionFlags() const override + { + return GameActionBase::GetActionFlags() | GA_FLAGS::CLIENT_ONLY; + } + + void Serialise(DataSerialiser & stream) override + { + GameAction::Serialise(stream); + + stream << DS_TAG(_loc); + } + + GameActionResult::Ptr Query() const override; + GameActionResult::Ptr Execute() const override; +}; diff --git a/src/openrct2/ride/TrackDesign.cpp b/src/openrct2/ride/TrackDesign.cpp index 418dcc3a1e..da87c4dfb0 100644 --- a/src/openrct2/ride/TrackDesign.cpp +++ b/src/openrct2/ride/TrackDesign.cpp @@ -86,7 +86,7 @@ static int16_t _trackDesignPlaceSceneryZ; // Previously all flags in byte_F4414E static bool _trackDesignPlaceStateEntranceExitPlaced = false; -static bool _trackDesignPlaceStateSceneryUnavailable = false; +bool _trackDesignPlaceStateSceneryUnavailable = false; static bool _trackDesignPlaceStateHasScenery = false; static bool _trackDesignPlaceStatePlaceScenery = true; diff --git a/src/openrct2/ride/TrackDesign.h b/src/openrct2/ride/TrackDesign.h index f14e3172d9..68dd6ca65a 100644 --- a/src/openrct2/ride/TrackDesign.h +++ b/src/openrct2/ride/TrackDesign.h @@ -195,6 +195,7 @@ extern LocationXYZ16 gTrackPreviewOrigin; extern bool byte_9D8150; +extern bool _trackDesignPlaceStateSceneryUnavailable; extern bool gTrackDesignSaveMode; extern ride_id_t gTrackDesignSaveRideIndex;