1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-16 11:33:03 +01:00
Files
OpenRCT2/src/openrct2/actions/TrackDesignAction.cpp
2025-03-31 21:22:22 +00:00

278 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*****************************************************************************
* Copyright (c) 2014-2025 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 "../Context.h"
#include "../Diagnostic.h"
#include "../GameState.h"
#include "../config/Config.h"
#include "../management/Finance.h"
#include "../management/Research.h"
#include "../object/ObjectManager.h"
#include "../object/ObjectRepository.h"
#include "../ride/RideConstruction.h"
#include "../ride/TrackDesign.h"
#include "RideCreateAction.h"
#include "RideDemolishAction.h"
#include "RideSetNameAction.h"
#include "RideSetSettingAction.h"
#include "RideSetVehicleAction.h"
using namespace OpenRCT2;
TrackDesignAction::TrackDesignAction(const CoordsXYZD& location, const TrackDesign& td)
: _loc(location)
, _td(td)
{
}
void TrackDesignAction::AcceptParameters(GameActionParameterVisitor& visitor)
{
visitor.Visit(_loc);
// TODO visit the track design (it has a lot of sub fields)
}
uint16_t TrackDesignAction::GetActionFlags() const
{
return GameActionBase::GetActionFlags();
}
void TrackDesignAction::Serialise(DataSerialiser& stream)
{
GameAction::Serialise(stream);
stream << DS_TAG(_loc);
_td.Serialise(stream);
}
GameActions::Result TrackDesignAction::Query() const
{
auto res = GameActions::Result();
res.Position.x = _loc.x + 16;
res.Position.y = _loc.y + 16;
res.Position.z = _loc.z;
res.Expenditure = ExpenditureType::RideConstruction;
if (!LocationValid(_loc))
{
return GameActions::Result(
GameActions::Status::InvalidParameters, STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE, STR_OFF_EDGE_OF_MAP);
}
auto& gameState = getGameState();
auto& objManager = GetContext()->GetObjectManager();
auto entryIndex = objManager.GetLoadedObjectEntryIndex(_td.trackAndVehicle.vehicleObject);
if (entryIndex == kObjectEntryIndexNull)
{
// Force a fallback if the entry is not invented yet a track design of it is selected,
// which can happen in select-by-track-type mode
if (!RideEntryIsInvented(entryIndex) && !gameState.cheats.ignoreResearchStatus)
{
entryIndex = kObjectEntryIndexNull;
}
}
// Colours do not matter as will be overwritten
auto rideCreateAction = RideCreateAction(_td.trackAndVehicle.rtdIndex, entryIndex, 0, 0, gameState.lastEntranceStyle);
rideCreateAction.SetFlags(GetFlags());
auto r = GameActions::ExecuteNested(&rideCreateAction);
if (r.Error != GameActions::Status::Ok)
{
return GameActions::Result(GameActions::Status::NoFreeElements, STR_CANT_CREATE_NEW_RIDE_ATTRACTION, kStringIdNone);
}
const auto rideIndex = r.GetData<RideId>();
auto ride = GetRide(rideIndex);
if (ride == nullptr)
{
LOG_ERROR("Ride not found for rideIndex %d", rideIndex);
return GameActions::Result(
GameActions::Status::Unknown, STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE, STR_ERR_RIDE_NOT_FOUND);
}
bool placeScenery = true;
uint32_t flags = 0;
if (GetFlags() & GAME_COMMAND_FLAG_GHOST)
flags |= GAME_COMMAND_FLAG_GHOST;
if (GetFlags() & GAME_COMMAND_FLAG_REPLAY)
flags |= GAME_COMMAND_FLAG_REPLAY;
auto queryRes = TrackDesignPlace(_td, flags, placeScenery, *ride, _loc);
if (_trackDesignPlaceStateSceneryUnavailable)
{
placeScenery = false;
queryRes = TrackDesignPlace(_td, flags, placeScenery, *ride, _loc);
}
auto gameAction = RideDemolishAction(ride->id, RideModifyType::demolish);
gameAction.SetFlags(GetFlags());
GameActions::ExecuteNested(&gameAction);
if (queryRes.Error != GameActions::Status::Ok)
{
res.Error = queryRes.Error;
res.ErrorTitle = STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE;
res.ErrorMessage = queryRes.ErrorMessage;
res.ErrorMessageArgs = queryRes.ErrorMessageArgs;
return res;
}
res.Cost = queryRes.Cost;
res.SetData(RideId{ RideId::GetNull() });
return res;
}
GameActions::Result TrackDesignAction::Execute() const
{
auto res = GameActions::Result();
res.Position.x = _loc.x + 16;
res.Position.y = _loc.y + 16;
res.Position.z = _loc.z;
res.Expenditure = ExpenditureType::RideConstruction;
auto& gameState = getGameState();
auto& objManager = GetContext()->GetObjectManager();
auto entryIndex = objManager.GetLoadedObjectEntryIndex(_td.trackAndVehicle.vehicleObject);
if (entryIndex != kObjectEntryIndexNull)
{
// Force a fallback if the entry is not invented yet a track design using it is selected.
// This can happen on rides with multiple vehicles where some have been invented and some havent.
if (!RideEntryIsInvented(entryIndex) && !gameState.cheats.ignoreResearchStatus)
{
entryIndex = kObjectEntryIndexNull;
}
}
// Colours do not matter as will be overwritten
auto rideCreateAction = RideCreateAction(_td.trackAndVehicle.rtdIndex, entryIndex, 0, 0, gameState.lastEntranceStyle);
rideCreateAction.SetFlags(GetFlags());
auto r = GameActions::ExecuteNested(&rideCreateAction);
if (r.Error != GameActions::Status::Ok)
{
return GameActions::Result(GameActions::Status::NoFreeElements, STR_CANT_CREATE_NEW_RIDE_ATTRACTION, kStringIdNone);
}
const auto rideIndex = r.GetData<RideId>();
auto ride = GetRide(rideIndex);
if (ride == nullptr)
{
LOG_ERROR("Ride not found for rideIndex %d", rideIndex);
return GameActions::Result(
GameActions::Status::Unknown, STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE, STR_ERR_RIDE_NOT_FOUND);
}
// Query first, this is required again to determine if scenery is available.
uint32_t flags = GetFlags() & ~GAME_COMMAND_FLAG_APPLY;
bool placeScenery = true;
auto queryRes = TrackDesignPlace(_td, flags, placeScenery, *ride, _loc);
if (_trackDesignPlaceStateSceneryUnavailable)
{
placeScenery = false;
queryRes = TrackDesignPlace(_td, flags, placeScenery, *ride, _loc);
}
if (queryRes.Error != GameActions::Status::Ok)
{
auto gameAction = RideDemolishAction(ride->id, RideModifyType::demolish);
gameAction.SetFlags(GetFlags());
GameActions::ExecuteNested(&gameAction);
res.Error = queryRes.Error;
res.ErrorTitle = STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE;
res.ErrorMessage = queryRes.ErrorMessage;
res.ErrorMessageArgs = queryRes.ErrorMessageArgs;
return res;
}
// Execute.
flags |= GAME_COMMAND_FLAG_APPLY;
auto execRes = TrackDesignPlace(_td, flags, placeScenery, *ride, _loc);
if (execRes.Error != GameActions::Status::Ok)
{
auto gameAction = RideDemolishAction(ride->id, RideModifyType::demolish);
gameAction.SetFlags(GetFlags());
GameActions::ExecuteNested(&gameAction);
res.Error = execRes.Error;
res.ErrorTitle = STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE;
res.ErrorMessage = execRes.ErrorMessage;
res.ErrorMessageArgs = execRes.ErrorMessageArgs;
return res;
}
if (entryIndex != kObjectEntryIndexNull)
{
auto colour = RideGetUnusedPresetVehicleColour(entryIndex);
auto rideSetVehicleAction = RideSetVehicleAction(ride->id, RideSetVehicleType::RideEntry, entryIndex, colour);
GameActions::ExecuteNested(&rideSetVehicleAction);
}
SetOperatingSettingNested(ride->id, RideSetSetting::Mode, static_cast<uint8_t>(_td.operation.rideMode), flags);
auto rideSetVehicleAction2 = RideSetVehicleAction(
ride->id, RideSetVehicleType::NumTrains, _td.trackAndVehicle.numberOfTrains);
GameActions::ExecuteNested(&rideSetVehicleAction2);
auto rideSetVehicleAction3 = RideSetVehicleAction(
ride->id, RideSetVehicleType::NumCarsPerTrain, _td.trackAndVehicle.numberOfCarsPerTrain);
GameActions::ExecuteNested(&rideSetVehicleAction3);
SetOperatingSettingNested(ride->id, RideSetSetting::Departure, _td.operation.departFlags, flags);
SetOperatingSettingNested(ride->id, RideSetSetting::MinWaitingTime, _td.operation.minWaitingTime, flags);
SetOperatingSettingNested(ride->id, RideSetSetting::MaxWaitingTime, _td.operation.maxWaitingTime, flags);
SetOperatingSettingNested(ride->id, RideSetSetting::Operation, _td.operation.operationSetting, flags);
SetOperatingSettingNested(ride->id, RideSetSetting::LiftHillSpeed, _td.operation.liftHillSpeed, flags);
auto numCircuits = std::max<uint8_t>(1, _td.operation.numCircuits);
SetOperatingSettingNested(ride->id, RideSetSetting::NumCircuits, numCircuits, flags);
uint8_t defaultInspectionInterval = Config::Get().general.DefaultInspectionInterval;
if (defaultInspectionInterval <= RIDE_INSPECTION_NEVER)
SetOperatingSettingNested(ride->id, RideSetSetting::InspectionInterval, defaultInspectionInterval, flags);
ride->lifecycleFlags |= RIDE_LIFECYCLE_NOT_CUSTOM_DESIGN;
ride->vehicleColourSettings = _td.appearance.vehicleColourSettings;
ride->entranceStyle = objManager.GetLoadedObjectEntryIndex(_td.appearance.stationObjectIdentifier);
if (ride->entranceStyle == kObjectEntryIndexNull)
{
ride->entranceStyle = gameState.lastEntranceStyle;
}
for (size_t i = 0; i < std::min(std::size(ride->trackColours), std::size(_td.appearance.trackColours)); i++)
{
ride->trackColours[i] = _td.appearance.trackColours[i];
}
for (size_t i = 0; i < Limits::kMaxVehicleColours; i++)
{
ride->vehicleColours[i] = _td.appearance.vehicleColours[i];
}
for (int32_t count = 1; count == 1 || r.Error != GameActions::Status::Ok; ++count)
{
auto name = count == 1 ? _td.gameStateData.name : (_td.gameStateData.name + " " + std::to_string(count));
auto gameAction = RideSetNameAction(ride->id, name);
gameAction.SetFlags(GetFlags());
r = GameActions::ExecuteNested(&gameAction);
}
res.Cost = execRes.Cost;
res.SetData(RideId{ ride->id });
return res;
}