mirror of https://github.com/OpenRCT2/OpenRCT2.git
270 lines
9.8 KiB
C++
270 lines
9.8 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2024 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 "RideEntranceExitPlaceAction.h"
|
|
|
|
#include "../GameState.h"
|
|
#include "../actions/RideEntranceExitRemoveAction.h"
|
|
#include "../management/Finance.h"
|
|
#include "../ride/Ride.h"
|
|
#include "../ride/Station.h"
|
|
#include "../world/ConstructionClearance.h"
|
|
#include "../world/MapAnimation.h"
|
|
|
|
using namespace OpenRCT2;
|
|
|
|
RideEntranceExitPlaceAction::RideEntranceExitPlaceAction(
|
|
const CoordsXY& loc, Direction direction, RideId rideIndex, StationIndex stationNum, bool isExit)
|
|
: _loc(loc)
|
|
, _direction(direction)
|
|
, _rideIndex(rideIndex)
|
|
, _stationNum(stationNum)
|
|
, _isExit(isExit)
|
|
{
|
|
}
|
|
|
|
void RideEntranceExitPlaceAction::AcceptParameters(GameActionParameterVisitor& visitor)
|
|
{
|
|
visitor.Visit(_loc);
|
|
visitor.Visit("direction", _direction);
|
|
visitor.Visit("ride", _rideIndex);
|
|
visitor.Visit("station", _stationNum);
|
|
visitor.Visit("isExit", _isExit);
|
|
}
|
|
|
|
uint16_t RideEntranceExitPlaceAction::GetActionFlags() const
|
|
{
|
|
return GameAction::GetActionFlags();
|
|
}
|
|
|
|
void RideEntranceExitPlaceAction::Serialise(DataSerialiser& stream)
|
|
{
|
|
GameAction::Serialise(stream);
|
|
|
|
stream << DS_TAG(_loc) << DS_TAG(_direction) << DS_TAG(_rideIndex) << DS_TAG(_stationNum) << DS_TAG(_isExit);
|
|
}
|
|
|
|
GameActions::Result RideEntranceExitPlaceAction::Query() const
|
|
{
|
|
const auto errorTitle = _isExit ? STR_CANT_BUILD_MOVE_EXIT_FOR_THIS_RIDE_ATTRACTION
|
|
: STR_CANT_BUILD_MOVE_ENTRANCE_FOR_THIS_RIDE_ATTRACTION;
|
|
|
|
auto ride = GetRide(_rideIndex);
|
|
if (ride == nullptr)
|
|
{
|
|
LOG_ERROR("Ride not found for rideIndex %u", _rideIndex.ToUnderlying());
|
|
return GameActions::Result(GameActions::Status::InvalidParameters, errorTitle, STR_ERR_RIDE_NOT_FOUND);
|
|
}
|
|
|
|
if (_stationNum.ToUnderlying() >= Limits::MaxStationsPerRide)
|
|
{
|
|
LOG_ERROR("Invalid station number for ride. stationNum: %u", _stationNum.ToUnderlying());
|
|
return GameActions::Result(GameActions::Status::InvalidParameters, errorTitle, STR_NONE);
|
|
}
|
|
|
|
if (ride->status != RideStatus::Closed && ride->status != RideStatus::Simulating)
|
|
{
|
|
return GameActions::Result(GameActions::Status::NotClosed, errorTitle, STR_MUST_BE_CLOSED_FIRST);
|
|
}
|
|
|
|
if (ride->lifecycle_flags & RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK)
|
|
{
|
|
return GameActions::Result(GameActions::Status::Disallowed, errorTitle, STR_NOT_ALLOWED_TO_MODIFY_STATION);
|
|
}
|
|
|
|
const auto& station = ride->GetStation(_stationNum);
|
|
const auto location = _isExit ? station.Exit : station.Entrance;
|
|
|
|
if (!location.IsNull())
|
|
{
|
|
auto rideEntranceExitRemove = RideEntranceExitRemoveAction(location.ToCoordsXY(), _rideIndex, _stationNum, _isExit);
|
|
rideEntranceExitRemove.SetFlags(GetFlags());
|
|
|
|
auto result = GameActions::QueryNested(&rideEntranceExitRemove);
|
|
if (result.Error != GameActions::Status::Ok)
|
|
{
|
|
result.ErrorTitle = errorTitle;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
auto z = ride->GetStation(_stationNum).GetBaseZ();
|
|
if (!LocationValid(_loc))
|
|
{
|
|
return GameActions::Result(GameActions::Status::InvalidParameters, errorTitle, STR_OFF_EDGE_OF_MAP);
|
|
}
|
|
if (!GetGameState().Cheats.SandboxMode && !MapIsLocationOwned({ _loc, z }))
|
|
{
|
|
return GameActions::Result(GameActions::Status::NotOwned, errorTitle, STR_LAND_NOT_OWNED_BY_PARK);
|
|
}
|
|
|
|
if (!MapCheckCapacityAndReorganise(_loc))
|
|
{
|
|
return GameActions::Result(GameActions::Status::NoFreeElements, errorTitle, STR_TILE_ELEMENT_LIMIT_REACHED);
|
|
}
|
|
auto clear_z = z + (_isExit ? RideExitHeight : RideEntranceHeight);
|
|
auto canBuild = MapCanConstructWithClearAt({ _loc, z, clear_z }, &MapPlaceNonSceneryClearFunc, { 0b1111, 0 }, GetFlags());
|
|
if (canBuild.Error != GameActions::Status::Ok)
|
|
{
|
|
canBuild.ErrorTitle = errorTitle;
|
|
return canBuild;
|
|
}
|
|
|
|
const auto clearanceData = canBuild.GetData<ConstructClearResult>();
|
|
if (clearanceData.GroundFlags & ELEMENT_IS_UNDERWATER)
|
|
{
|
|
return GameActions::Result(GameActions::Status::Disallowed, errorTitle, STR_RIDE_CANT_BUILD_THIS_UNDERWATER);
|
|
}
|
|
|
|
if (z > MaxRideEntranceOrExitHeight)
|
|
{
|
|
return GameActions::Result(GameActions::Status::Disallowed, errorTitle, STR_TOO_HIGH);
|
|
}
|
|
|
|
auto res = GameActions::Result();
|
|
res.Position = { _loc.ToTileCentre(), z };
|
|
res.Expenditure = ExpenditureType::RideConstruction;
|
|
res.Cost += canBuild.Cost;
|
|
return res;
|
|
}
|
|
|
|
GameActions::Result RideEntranceExitPlaceAction::Execute() const
|
|
{
|
|
// Remember when in unknown station num mode rideIndex is unknown and z is set
|
|
// When in known station num mode rideIndex is known and z is unknown
|
|
const auto errorTitle = _isExit ? STR_CANT_BUILD_MOVE_EXIT_FOR_THIS_RIDE_ATTRACTION
|
|
: STR_CANT_BUILD_MOVE_ENTRANCE_FOR_THIS_RIDE_ATTRACTION;
|
|
auto ride = GetRide(_rideIndex);
|
|
if (ride == nullptr)
|
|
{
|
|
LOG_ERROR("Ride not found for rideIndex %u", _rideIndex.ToUnderlying());
|
|
return GameActions::Result(GameActions::Status::InvalidParameters, errorTitle, STR_ERR_RIDE_NOT_FOUND);
|
|
}
|
|
|
|
if (!(GetFlags() & GAME_COMMAND_FLAG_GHOST))
|
|
{
|
|
RideClearForConstruction(*ride);
|
|
ride->RemovePeeps();
|
|
}
|
|
|
|
auto& station = ride->GetStation(_stationNum);
|
|
const auto location = _isExit ? station.Exit : station.Entrance;
|
|
if (!location.IsNull())
|
|
{
|
|
auto rideEntranceExitRemove = RideEntranceExitRemoveAction(location.ToCoordsXY(), _rideIndex, _stationNum, _isExit);
|
|
rideEntranceExitRemove.SetFlags(GetFlags());
|
|
|
|
auto result = GameActions::ExecuteNested(&rideEntranceExitRemove);
|
|
if (result.Error != GameActions::Status::Ok)
|
|
{
|
|
result.ErrorTitle = errorTitle;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
auto z = station.GetBaseZ();
|
|
if (!(GetFlags() & GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED) && !(GetFlags() & GAME_COMMAND_FLAG_GHOST)
|
|
&& !GetGameState().Cheats.DisableClearanceChecks)
|
|
{
|
|
FootpathRemoveLitter({ _loc, z });
|
|
WallRemoveAtZ({ _loc, z });
|
|
}
|
|
|
|
auto clear_z = z + (_isExit ? RideExitHeight : RideEntranceHeight);
|
|
auto canBuild = MapCanConstructWithClearAt(
|
|
{ _loc, z, clear_z }, &MapPlaceNonSceneryClearFunc, { 0b1111, 0 }, GetFlags() | GAME_COMMAND_FLAG_APPLY);
|
|
if (canBuild.Error != GameActions::Status::Ok)
|
|
{
|
|
canBuild.ErrorTitle = errorTitle;
|
|
return canBuild;
|
|
}
|
|
|
|
auto res = GameActions::Result();
|
|
res.Position = { _loc.ToTileCentre(), z };
|
|
res.Expenditure = ExpenditureType::RideConstruction;
|
|
res.Cost += canBuild.Cost;
|
|
|
|
auto* entranceElement = TileElementInsert<EntranceElement>(CoordsXYZ{ _loc, z }, 0b1111);
|
|
Guard::Assert(entranceElement != nullptr);
|
|
|
|
entranceElement->SetDirection(_direction);
|
|
entranceElement->SetClearanceZ(clear_z);
|
|
entranceElement->SetEntranceType(_isExit ? ENTRANCE_TYPE_RIDE_EXIT : ENTRANCE_TYPE_RIDE_ENTRANCE);
|
|
entranceElement->SetStationIndex(_stationNum);
|
|
entranceElement->SetRideIndex(_rideIndex);
|
|
entranceElement->SetGhost(GetFlags() & GAME_COMMAND_FLAG_GHOST);
|
|
|
|
if (_isExit)
|
|
{
|
|
station.Exit = TileCoordsXYZD(CoordsXYZD{ _loc, z, entranceElement->GetDirection() });
|
|
}
|
|
else
|
|
{
|
|
station.Entrance = TileCoordsXYZD(CoordsXYZD{ _loc, z, entranceElement->GetDirection() });
|
|
station.LastPeepInQueue = EntityId::GetNull();
|
|
station.QueueLength = 0;
|
|
|
|
MapAnimationCreate(MAP_ANIMATION_TYPE_RIDE_ENTRANCE, { _loc, z });
|
|
}
|
|
|
|
FootpathQueueChainReset();
|
|
|
|
if (!(GetFlags() & GAME_COMMAND_FLAG_GHOST))
|
|
{
|
|
MazeEntranceHedgeRemoval({ _loc, entranceElement->as<TileElement>() });
|
|
}
|
|
|
|
FootpathConnectEdges(_loc, entranceElement->as<TileElement>(), GetFlags());
|
|
FootpathUpdateQueueChains();
|
|
|
|
MapInvalidateTileFull(_loc);
|
|
|
|
return res;
|
|
}
|
|
|
|
GameActions::Result RideEntranceExitPlaceAction::TrackPlaceQuery(const CoordsXYZ& loc, const bool isExit)
|
|
{
|
|
const auto errorTitle = isExit ? STR_CANT_BUILD_MOVE_EXIT_FOR_THIS_RIDE_ATTRACTION
|
|
: STR_CANT_BUILD_MOVE_ENTRANCE_FOR_THIS_RIDE_ATTRACTION;
|
|
|
|
if (!GetGameState().Cheats.SandboxMode && !MapIsLocationOwned(loc))
|
|
{
|
|
return GameActions::Result(GameActions::Status::NotOwned, errorTitle, STR_LAND_NOT_OWNED_BY_PARK);
|
|
}
|
|
|
|
if (!MapCheckCapacityAndReorganise(loc))
|
|
{
|
|
return GameActions::Result(GameActions::Status::NoFreeElements, errorTitle, STR_TILE_ELEMENT_LIMIT_REACHED);
|
|
}
|
|
int16_t baseZ = loc.z;
|
|
int16_t clearZ = baseZ + (isExit ? RideExitHeight : RideEntranceHeight);
|
|
auto canBuild = MapCanConstructWithClearAt({ loc, baseZ, clearZ }, &MapPlaceNonSceneryClearFunc, { 0b1111, 0 }, 0);
|
|
if (canBuild.Error != GameActions::Status::Ok)
|
|
{
|
|
canBuild.ErrorTitle = errorTitle;
|
|
return canBuild;
|
|
}
|
|
|
|
const auto clearanceData = canBuild.GetData<ConstructClearResult>();
|
|
if (clearanceData.GroundFlags & ELEMENT_IS_UNDERWATER)
|
|
{
|
|
return GameActions::Result(GameActions::Status::Disallowed, errorTitle, STR_RIDE_CANT_BUILD_THIS_UNDERWATER);
|
|
}
|
|
|
|
if (baseZ > MaxRideEntranceOrExitHeight)
|
|
{
|
|
return GameActions::Result(GameActions::Status::Disallowed, errorTitle, STR_TOO_HIGH);
|
|
}
|
|
auto res = GameActions::Result();
|
|
res.Position = { loc.ToTileCentre(), TileElementHeight(loc) };
|
|
res.Expenditure = ExpenditureType::RideConstruction;
|
|
res.Cost += canBuild.Cost;
|
|
return res;
|
|
}
|