/***************************************************************************** * Copyright (c) 2014-2020 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 "MazeSetTrackAction.h" #include "../Cheats.h" #include "../core/MemoryStream.h" #include "../interface/Window.h" #include "../localisation/Localisation.h" #include "../localisation/StringIds.h" #include "../management/Finance.h" #include "../ride/RideData.h" #include "../ride/Track.h" #include "../ride/TrackData.h" #include "../world/ConstructionClearance.h" #include "../world/Footpath.h" #include "../world/Park.h" using namespace OpenRCT2::TrackMetaData; MazeSetTrackAction::MazeSetTrackAction( const CoordsXYZD& location, bool initialPlacement, NetworkRideId_t rideIndex, uint8_t mode) : _loc(location) , _initialPlacement(initialPlacement) , _rideIndex(rideIndex) , _mode(mode) { } void MazeSetTrackAction::AcceptParameters(GameActionParameterVisitor& visitor) { visitor.Visit(_loc); visitor.Visit("ride", _rideIndex); visitor.Visit("mode", _mode); visitor.Visit("isInitialPlacement", _initialPlacement); } void MazeSetTrackAction::Serialise(DataSerialiser& stream) { GameAction::Serialise(stream); stream << DS_TAG(_loc) << DS_TAG(_loc.direction) << DS_TAG(_initialPlacement) << DS_TAG(_rideIndex) << DS_TAG(_mode); } GameActions::Result::Ptr MazeSetTrackAction::Query() const { auto res = std::make_unique(); res->Position = _loc + CoordsXYZ{ 8, 8, 0 }; res->Expenditure = ExpenditureType::RideConstruction; res->ErrorTitle = STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE; if ((_loc.z & 0xF) != 0 && _mode == GC_SET_MAZE_TRACK_BUILD) { res->Error = GameActions::Status::Unknown; res->ErrorMessage = STR_CONSTRUCTION_ERR_UNKNOWN; return res; } if (!LocationValid(_loc) || (!map_is_location_owned(_loc) && !gCheatsSandboxMode)) { res->Error = GameActions::Status::NotOwned; res->ErrorMessage = STR_LAND_NOT_OWNED_BY_PARK; return res; } if (!MapCheckCapacityAndReorganise(_loc)) { res->Error = GameActions::Status::NoFreeElements; res->ErrorMessage = STR_TILE_ELEMENT_LIMIT_REACHED; return res; } auto surfaceElement = map_get_surface_element_at(_loc); if (surfaceElement == nullptr) { res->Error = GameActions::Status::Unknown; res->ErrorMessage = STR_INVALID_SELECTION_OF_OBJECTS; return res; } auto baseHeight = _loc.z; auto clearanceHeight = _loc.z + 32; auto heightDifference = baseHeight - surfaceElement->GetBaseZ(); if (heightDifference >= 0 && !gCheatsDisableSupportLimits) { heightDifference /= COORDS_Z_PER_TINY_Z; if (heightDifference > GetRideTypeDescriptor(RIDE_TYPE_MAZE).Heights.MaxHeight) { res->Error = GameActions::Status::TooHigh; res->ErrorMessage = STR_TOO_HIGH_FOR_SUPPORTS; return res; } } TileElement* tileElement = map_get_track_element_at_of_type_from_ride(_loc, TrackElemType::Maze, _rideIndex); if (tileElement == nullptr) { if (_mode != GC_SET_MAZE_TRACK_BUILD) { res->Error = GameActions::Status::Unknown; res->ErrorMessage = STR_INVALID_SELECTION_OF_OBJECTS; return res; } auto constructResult = MapCanConstructAt({ _loc.ToTileStart(), baseHeight, clearanceHeight }, { 0b1111, 0 }); if (constructResult->Error != GameActions::Status::Ok) { constructResult->ErrorTitle = STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE; return constructResult; } const auto clearanceData = constructResult->GetData(); if (clearanceData.GroundFlags & ELEMENT_IS_UNDERWATER) { res->Error = GameActions::Status::NoClearance; res->ErrorMessage = STR_RIDE_CANT_BUILD_THIS_UNDERWATER; return res; } if (clearanceData.GroundFlags & ELEMENT_IS_UNDERGROUND) { res->Error = GameActions::Status::NoClearance; res->ErrorMessage = STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND; return res; } auto ride = get_ride(_rideIndex); if (ride == nullptr || ride->type == RIDE_CRASH_TYPE_NONE) { res->Error = GameActions::Status::NoClearance; res->ErrorMessage = STR_INVALID_SELECTION_OF_OBJECTS; return res; } const auto& ted = GetTrackElementDescriptor(TrackElemType::Maze); money32 price = (((ride->GetRideTypeDescriptor().BuildCosts.TrackPrice * ted.Price) >> 16)); res->Cost = price / 2 * 10; return res; } return std::make_unique(); } GameActions::Result::Ptr MazeSetTrackAction::Execute() const { auto res = std::make_unique(); res->Position = _loc + CoordsXYZ{ 8, 8, 0 }; res->Expenditure = ExpenditureType::RideConstruction; res->ErrorTitle = STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE; auto ride = get_ride(_rideIndex); if (ride == nullptr) { res->Error = GameActions::Status::InvalidParameters; res->ErrorMessage = STR_NONE; return res; } uint32_t flags = GetFlags(); if (!(flags & GAME_COMMAND_FLAG_GHOST)) { footpath_remove_litter(_loc); wall_remove_at({ _loc.ToTileStart(), _loc.z, _loc.z + 32 }); } auto tileElement = map_get_track_element_at_of_type_from_ride(_loc, TrackElemType::Maze, _rideIndex); if (tileElement == nullptr) { const auto& ted = GetTrackElementDescriptor(TrackElemType::Maze); money32 price = (((ride->GetRideTypeDescriptor().BuildCosts.TrackPrice * ted.Price) >> 16)); res->Cost = price / 2 * 10; auto startLoc = _loc.ToTileStart(); auto* trackElement = TileElementInsert(_loc, 0b1111); Guard::Assert(trackElement != nullptr); trackElement->SetClearanceZ(_loc.z + MAZE_CLEARANCE_HEIGHT); trackElement->SetTrackType(TrackElemType::Maze); trackElement->SetRideType(ride->type); trackElement->SetRideIndex(_rideIndex); trackElement->SetMazeEntry(0xFFFF); trackElement->SetGhost(flags & GAME_COMMAND_FLAG_GHOST); tileElement = trackElement->as(); map_invalidate_tile_full(startLoc); ride->maze_tiles++; ride->stations[0].SetBaseZ(tileElement->GetBaseZ()); ride->stations[0].Start = { 0, 0 }; if (_initialPlacement && !(flags & GAME_COMMAND_FLAG_GHOST)) { ride->overall_view = startLoc; } } switch (_mode) { case GC_SET_MAZE_TRACK_BUILD: { uint8_t segmentOffset = MazeGetSegmentBit(_loc.x, _loc.y); tileElement->AsTrack()->MazeEntrySubtract(1 << segmentOffset); if (!_initialPlacement) { segmentOffset = byte_993CE9[(_loc.direction + segmentOffset)]; tileElement->AsTrack()->MazeEntrySubtract(1 << segmentOffset); uint8_t temp_edx = byte_993CFC[segmentOffset]; if (temp_edx != 0xFF) { auto previousElementLoc = CoordsXY{ _loc }.ToTileStart() - CoordsDirectionDelta[_loc.direction]; TileElement* previousTileElement = map_get_track_element_at_of_type_from_ride( { previousElementLoc, _loc.z }, TrackElemType::Maze, _rideIndex); if (previousTileElement != nullptr) { previousTileElement->AsTrack()->MazeEntrySubtract(1 << temp_edx); } else { tileElement->AsTrack()->MazeEntryAdd(1 << segmentOffset); } } } break; } case GC_SET_MAZE_TRACK_MOVE: break; case GC_SET_MAZE_TRACK_FILL: if (!_initialPlacement) { auto previousSegment = CoordsXY{ _loc.x - CoordsDirectionDelta[_loc.direction].x / 2, _loc.y - CoordsDirectionDelta[_loc.direction].y / 2 }; tileElement = map_get_track_element_at_of_type_from_ride( { previousSegment, _loc.z }, TrackElemType::Maze, _rideIndex); map_invalidate_tile_full(previousSegment.ToTileStart()); if (tileElement == nullptr) { log_error("No surface found"); res->Error = GameActions::Status::Unknown; res->ErrorMessage = STR_NONE; return res; } uint32_t segmentBit = MazeGetSegmentBit(previousSegment.x, previousSegment.y); tileElement->AsTrack()->MazeEntryAdd(1 << segmentBit); segmentBit--; tileElement->AsTrack()->MazeEntryAdd(1 << segmentBit); segmentBit = (segmentBit - 4) & 0x0F; tileElement->AsTrack()->MazeEntryAdd(1 << segmentBit); segmentBit = (segmentBit + 3) & 0x0F; do { tileElement->AsTrack()->MazeEntryAdd(1 << segmentBit); uint32_t direction1 = byte_993D0C[segmentBit]; auto nextElementLoc = previousSegment.ToTileStart() + CoordsDirectionDelta[direction1]; TileElement* tmp_tileElement = map_get_track_element_at_of_type_from_ride( { nextElementLoc, _loc.z }, TrackElemType::Maze, _rideIndex); if (tmp_tileElement != nullptr) { uint8_t edx11 = byte_993CFC[segmentBit]; tmp_tileElement->AsTrack()->MazeEntryAdd(1 << (edx11)); } segmentBit--; } while ((segmentBit & 0x3) != 0x3); } break; } map_invalidate_tile({ _loc.ToTileStart(), tileElement->GetBaseZ(), tileElement->GetClearanceZ() }); if ((tileElement->AsTrack()->GetMazeEntry() & 0x8888) == 0x8888) { tile_element_remove(tileElement); ride->ValidateStations(); ride->maze_tiles--; } return res; } uint8_t MazeSetTrackAction::MazeGetSegmentBit(uint16_t x, uint16_t y) const { uint8_t minorX = x & 0x1F; // 0 or 16 uint8_t minorY = y & 0x1F; // 0 or 16 if (minorX == 0 && minorY == 0) { return 3; } if (minorY == 16 && minorX == 16) { return 11; } if (minorY == 0) { return 15; } return 7; }