/***************************************************************************** * Copyright (c) 2014-2023 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 "../Cheats.h" #include "../Context.h" #include "../Game.h" #include "../Identifiers.h" #include "../OpenRCT2.h" #include "../actions/FootpathPlaceAction.h" #include "../actions/FootpathRemoveAction.h" #include "../actions/LandSetRightsAction.h" #include "../core/Guard.hpp" #include "../entity/EntityList.h" #include "../entity/EntityRegistry.h" #include "../interface/Window_internal.h" #include "../localisation/Localisation.h" #include "../management/Finance.h" #include "../network/network.h" #include "../object/FootpathItemEntry.h" #include "../object/FootpathObject.h" #include "../object/FootpathRailingsObject.h" #include "../object/FootpathSurfaceObject.h" #include "../object/ObjectEntryManager.h" #include "../object/ObjectList.h" #include "../object/ObjectManager.h" #include "../paint/VirtualFloor.h" #include "../ride/RideData.h" #include "../ride/Station.h" #include "../ride/Track.h" #include "../ride/TrackData.h" #include "../util/Util.h" #include "Location.hpp" #include "Map.h" #include "MapAnimation.h" #include "Park.h" #include "Scenery.h" #include "Surface.h" #include "TileElement.h" #include #include using namespace OpenRCT2::TrackMetaData; void FootpathUpdateQueueEntranceBanner(const CoordsXY& footpathPos, TileElement* tileElement); FootpathSelection gFootpathSelection; ProvisionalFootpath gProvisionalFootpath; uint16_t gFootpathSelectedId; CoordsXYZ gFootpathConstructFromPosition; uint8_t gFootpathConstructSlope; uint8_t gFootpathGroundFlags; static RideId* _footpathQueueChainNext; static RideId _footpathQueueChain[64]; // This is the coordinates that a user of the bin should move to // rct2: 0x00992A4C const CoordsXY BinUseOffsets[4] = { { 11, 16 }, { 16, 21 }, { 21, 16 }, { 16, 11 }, }; // These are the offsets for bench positions on footpaths, 2 for each edge // rct2: 0x00981F2C, 0x00981F2E const CoordsXY BenchUseOffsets[8] = { { 7, 12 }, { 12, 25 }, { 25, 20 }, { 20, 7 }, { 7, 20 }, { 20, 25 }, { 25, 12 }, { 12, 7 }, }; /** rct2: 0x00981D6C, 0x00981D6E */ const CoordsXY DirectionOffsets[4] = { { -1, 0 }, { 0, 1 }, { 1, 0 }, { 0, -1 }, }; // rct2: 0x0097B974 static constexpr const uint16_t EntranceDirections[] = { (4), 0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_RIDE_ENTRANCE, (4), 0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_RIDE_EXIT, (4 | 1), 0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_PARK_ENTRANCE }; /** rct2: 0x0098D7F0 */ static constexpr const uint8_t connected_path_count[] = { 0, // 0b0000 1, // 0b0001 1, // 0b0010 2, // 0b0011 1, // 0b0100 2, // 0b0101 2, // 0b0110 3, // 0b0111 1, // 0b1000 2, // 0b1001 2, // 0b1010 3, // 0b1011 2, // 0b1100 3, // 0b1101 3, // 0b1110 4, // 0b1111 }; int32_t EntranceElement::GetDirections() const { return EntranceDirections[(GetEntranceType() * 8) + GetSequenceIndex()]; } static bool entrance_has_direction(const EntranceElement& entranceElement, int32_t direction) { return entranceElement.GetDirections() & (1 << (direction & 3)); } TileElement* MapGetFootpathElement(const CoordsXYZ& coords) { TileElement* tileElement = MapGetFirstElementAt(coords); do { if (tileElement == nullptr) break; if (tileElement->GetType() == TileElementType::Path && tileElement->GetBaseZ() == coords.z) return tileElement; } while (!(tileElement++)->IsLastForTile()); return nullptr; } /** * * rct2: 0x006A76FF */ money64 FootpathProvisionalSet( ObjectEntryIndex type, ObjectEntryIndex railingsType, const CoordsXYZ& footpathLoc, int32_t slope, PathConstructFlags constructFlags) { money64 cost; FootpathProvisionalRemove(); auto footpathPlaceAction = FootpathPlaceAction(footpathLoc, slope, type, railingsType, INVALID_DIRECTION, constructFlags); footpathPlaceAction.SetFlags(GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED); auto res = GameActions::Execute(&footpathPlaceAction); cost = res.Error == GameActions::Status::Ok ? res.Cost : MONEY64_UNDEFINED; if (res.Error == GameActions::Status::Ok) { gProvisionalFootpath.SurfaceIndex = type; gProvisionalFootpath.RailingsIndex = railingsType; gProvisionalFootpath.Position = footpathLoc; gProvisionalFootpath.Slope = slope; gProvisionalFootpath.ConstructFlags = constructFlags; gProvisionalFootpath.Flags |= PROVISIONAL_PATH_FLAG_1; if (gFootpathGroundFlags & ELEMENT_IS_UNDERGROUND) { ViewportSetVisibility(1); } else { ViewportSetVisibility(3); } } // Invalidate previous footpath piece. VirtualFloorInvalidate(); if (!SceneryToolIsActive()) { if (res.Error != GameActions::Status::Ok) { // If we can't build this, don't show a virtual floor. VirtualFloorSetHeight(0); } else if ( gFootpathConstructSlope == TILE_ELEMENT_SLOPE_FLAT || gProvisionalFootpath.Position.z < gFootpathConstructFromPosition.z) { // Going either straight on, or down. VirtualFloorSetHeight(gProvisionalFootpath.Position.z); } else { // Going up in the world! VirtualFloorSetHeight(gProvisionalFootpath.Position.z + LAND_HEIGHT_STEP); } } return cost; } /** * * rct2: 0x006A77FF */ void FootpathProvisionalRemove() { if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1) { gProvisionalFootpath.Flags &= ~PROVISIONAL_PATH_FLAG_1; auto action = FootpathRemoveAction(gProvisionalFootpath.Position); action.SetFlags(GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST); GameActions::Execute(&action); } } /** * * rct2: 0x006A7831 */ void FootpathProvisionalUpdate() { if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_SHOW_ARROW) { gProvisionalFootpath.Flags &= ~PROVISIONAL_PATH_FLAG_SHOW_ARROW; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; MapInvalidateTileFull(gFootpathConstructFromPosition); } FootpathProvisionalRemove(); } /** * Determines the location of the footpath at which we point with the cursor. If no footpath is underneath the cursor, * then return the location of the ground tile. Besides the location it also computes the direction of the yellow arrow * when we are going to build a footpath bridge/tunnel. * rct2: 0x00689726 * In: * screenX: eax * screenY: ebx * Out: * x: ax * y: bx * direction: ecx * tileElement: edx */ CoordsXY FootpathGetCoordinatesFromPos(const ScreenCoordsXY& screenCoords, int32_t* direction, TileElement** tileElement) { WindowBase* window = WindowFindFromPoint(screenCoords); if (window == nullptr || window->viewport == nullptr) { CoordsXY position{}; position.SetNull(); return position; } auto viewport = window->viewport; auto info = GetMapCoordinatesFromPosWindow(window, screenCoords, EnumsToFlags(ViewportInteractionItem::Footpath)); if (info.SpriteType != ViewportInteractionItem::Footpath || !(viewport->flags & (VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_HIDE_VERTICAL))) { info = GetMapCoordinatesFromPosWindow( window, screenCoords, EnumsToFlags(ViewportInteractionItem::Terrain, ViewportInteractionItem::Footpath)); if (info.SpriteType == ViewportInteractionItem::None) { auto position = info.Loc; position.SetNull(); return position; } } auto minPosition = info.Loc; auto maxPosition = info.Loc + CoordsXY{ 31, 31 }; auto myTileElement = info.Element; auto position = info.Loc.ToTileCentre(); auto z = 0; if (info.SpriteType == ViewportInteractionItem::Footpath) { z = myTileElement->GetBaseZ(); if (myTileElement->AsPath()->IsSloped()) { z += 8; } } auto start_vp_pos = viewport->ScreenToViewportCoord(screenCoords); for (int32_t i = 0; i < 5; i++) { if (info.SpriteType != ViewportInteractionItem::Footpath) { z = TileElementHeight(position); } position = ViewportPosToMapPos(start_vp_pos, z); position.x = std::clamp(position.x, minPosition.x, maxPosition.x); position.y = std::clamp(position.y, minPosition.y, maxPosition.y); } // Determine to which edge the cursor is closest uint32_t myDirection; int32_t mod_x = position.x & 0x1F, mod_y = position.y & 0x1F; if (mod_x < mod_y) { if (mod_x + mod_y < 32) { myDirection = 0; } else { myDirection = 1; } } else { if (mod_x + mod_y < 32) { myDirection = 3; } else { myDirection = 2; } } position = position.ToTileStart(); if (direction != nullptr) *direction = myDirection; if (tileElement != nullptr) *tileElement = myTileElement; return position; } /** * * rct2: 0x0068A0C9 * screenX: eax * screenY: ebx * x: ax * y: bx * direction: cl * tileElement: edx */ CoordsXY FootpathBridgeGetInfoFromPos(const ScreenCoordsXY& screenCoords, int32_t* direction, TileElement** tileElement) { // First check if we point at an entrance or exit. In that case, we would want the path coming from the entrance/exit. WindowBase* window = WindowFindFromPoint(screenCoords); if (window == nullptr || window->viewport == nullptr) { CoordsXY ret{}; ret.SetNull(); return ret; } auto viewport = window->viewport; auto info = GetMapCoordinatesFromPosWindow(window, screenCoords, EnumsToFlags(ViewportInteractionItem::Ride)); *tileElement = info.Element; if (info.SpriteType == ViewportInteractionItem::Ride && viewport->flags & (VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_HIDE_VERTICAL) && (*tileElement)->GetType() == TileElementType::Entrance) { int32_t directions = (*tileElement)->AsEntrance()->GetDirections(); if (directions & 0x0F) { int32_t bx = UtilBitScanForward(directions); bx += (*tileElement)->AsEntrance()->GetDirection(); bx &= 3; if (direction != nullptr) *direction = bx; return info.Loc; } } info = GetMapCoordinatesFromPosWindow( window, screenCoords, EnumsToFlags(ViewportInteractionItem::Terrain, ViewportInteractionItem::Footpath, ViewportInteractionItem::Ride)); if (info.SpriteType == ViewportInteractionItem::Ride && (*tileElement)->GetType() == TileElementType::Entrance) { int32_t directions = (*tileElement)->AsEntrance()->GetDirections(); if (directions & 0x0F) { int32_t bx = (*tileElement)->GetDirectionWithOffset(UtilBitScanForward(directions)); if (direction != nullptr) *direction = bx; return info.Loc; } } // We point at something else return FootpathGetCoordinatesFromPos(screenCoords, direction, tileElement); } /** * * rct2: 0x00673883 */ void FootpathRemoveLitter(const CoordsXYZ& footpathPos) { std::vector removals; for (auto litter : EntityTileList(footpathPos)) { int32_t distanceZ = abs(litter->z - footpathPos.z); if (distanceZ <= 32) { removals.push_back(litter); } } for (auto* litter : removals) { litter->Invalidate(); EntityRemove(litter); } } /** * * rct2: 0x0069A48B */ void FootpathInterruptPeeps(const CoordsXYZ& footpathPos) { auto quad = EntityTileList(footpathPos); for (auto peep : quad) { if (peep->State == PeepState::Sitting || peep->State == PeepState::Watching) { auto location = peep->GetLocation(); if (location.z == footpathPos.z) { auto destination = location.ToTileCentre(); peep->SetState(PeepState::Walking); peep->SetDestination(destination, 5); peep->UpdateCurrentActionSpriteType(); } } } } /** * Returns true if the edge of tile x, y specified by direction is occupied by a fence * between heights z0 and z1. * * Note that there may still be a fence on the opposing tile. * * rct2: 0x006E59DC */ bool WallInTheWay(const CoordsXYRangedZ& fencePos, int32_t direction) { TileElement* tileElement; tileElement = MapGetFirstElementAt(fencePos); if (tileElement == nullptr) return false; do { if (tileElement->GetType() != TileElementType::Wall) continue; if (tileElement->IsGhost()) continue; if (fencePos.baseZ >= tileElement->GetClearanceZ()) continue; if (fencePos.clearanceZ <= tileElement->GetBaseZ()) continue; if ((tileElement->GetDirection()) != direction) continue; return true; } while (!(tileElement++)->IsLastForTile()); return false; } static PathElement* FootpathConnectCornersGetNeighbour(const CoordsXYZ& footpathPos, int32_t requireEdges) { if (!MapIsLocationValid(footpathPos)) { return nullptr; } TileElement* tileElement = MapGetFirstElementAt(footpathPos); if (tileElement == nullptr) return nullptr; do { if (tileElement->GetType() != TileElementType::Path) continue; auto pathElement = tileElement->AsPath(); if (pathElement->IsQueue()) continue; if (tileElement->GetBaseZ() != footpathPos.z) continue; if (!(pathElement->GetEdgesAndCorners() & requireEdges)) continue; return pathElement; } while (!(tileElement++)->IsLastForTile()); return nullptr; } /** * Sets the corner edges of four path tiles. * The function will search for a path in the direction given, then check clockwise to see if it there is a path and again until * it reaches the initial path. In other words, checks if there are four paths together so that it can set the inner corners of * each one. * * rct2: 0x006A70EB */ static void FootpathConnectCorners(const CoordsXY& footpathPos, PathElement* initialTileElement) { using PathElementCoordsPair = std::pair; std::array tileElements; if (initialTileElement->IsQueue()) return; if (initialTileElement->IsSloped()) return; std::get<0>(tileElements) = { initialTileElement, footpathPos }; int32_t z = initialTileElement->GetBaseZ(); for (int32_t initialDirection = 0; initialDirection < NumOrthogonalDirections; initialDirection++) { int32_t direction = initialDirection; auto currentPos = footpathPos + CoordsDirectionDelta[direction]; std::get<1>(tileElements) = { FootpathConnectCornersGetNeighbour({ currentPos, z }, (1 << DirectionReverse(direction))), currentPos }; if (std::get<1>(tileElements).first == nullptr) continue; direction = DirectionNext(direction); currentPos += CoordsDirectionDelta[direction]; std::get<2>(tileElements) = { FootpathConnectCornersGetNeighbour({ currentPos, z }, (1 << DirectionReverse(direction))), currentPos }; if (std::get<2>(tileElements).first == nullptr) continue; direction = DirectionNext(direction); currentPos += CoordsDirectionDelta[direction]; // First check link to previous tile std::get<3>(tileElements) = { FootpathConnectCornersGetNeighbour({ currentPos, z }, (1 << DirectionReverse(direction))), currentPos }; if (std::get<3>(tileElements).first == nullptr) continue; // Second check link to initial tile std::get<3>(tileElements) = { FootpathConnectCornersGetNeighbour({ currentPos, z }, (1 << ((direction + 1) & 3))), currentPos }; if (std::get<3>(tileElements).first == nullptr) continue; direction = DirectionNext(direction); std::get<3>(tileElements).first->SetCorners(std::get<3>(tileElements).first->GetCorners() | (1 << (direction))); MapInvalidateElement(std::get<3>(tileElements).second, reinterpret_cast(std::get<3>(tileElements).first)); direction = DirectionPrev(direction); std::get<2>(tileElements).first->SetCorners(std::get<2>(tileElements).first->GetCorners() | (1 << (direction))); MapInvalidateElement(std::get<2>(tileElements).second, reinterpret_cast(std::get<2>(tileElements).first)); direction = DirectionPrev(direction); std::get<1>(tileElements).first->SetCorners(std::get<1>(tileElements).first->GetCorners() | (1 << (direction))); MapInvalidateElement(std::get<1>(tileElements).second, reinterpret_cast(std::get<1>(tileElements).first)); direction = initialDirection; std::get<0>(tileElements).first->SetCorners(std::get<0>(tileElements).first->GetCorners() | (1 << (direction))); MapInvalidateElement(std::get<0>(tileElements).second, reinterpret_cast(std::get<0>(tileElements).first)); } } struct FootpathNeighbour { uint8_t order; uint8_t direction; RideId ride_index; StationIndex entrance_index; }; struct FootpathNeighbourList { FootpathNeighbour items[8]; size_t count; }; static int32_t FootpathNeighbourCompare(const void* a, const void* b) { uint8_t va = (static_cast(a))->order; uint8_t vb = (static_cast(b))->order; if (va < vb) return 1; if (va > vb) return -1; uint8_t da = (static_cast(a))->direction; uint8_t db = (static_cast(b))->direction; if (da < db) return -1; if (da > db) return 1; return 0; } static void FootpathNeighbourListInit(FootpathNeighbourList* neighbourList) { neighbourList->count = 0; } static void FootpathNeighbourListPush( FootpathNeighbourList* neighbourList, int32_t order, int32_t direction, RideId rideIndex, ::StationIndex entrance_index) { Guard::Assert(neighbourList->count < std::size(neighbourList->items)); neighbourList->items[neighbourList->count].order = order; neighbourList->items[neighbourList->count].direction = direction; neighbourList->items[neighbourList->count].ride_index = rideIndex; neighbourList->items[neighbourList->count].entrance_index = entrance_index; neighbourList->count++; } static bool FootpathNeighbourListPop(FootpathNeighbourList* neighbourList, FootpathNeighbour* outNeighbour) { if (neighbourList->count == 0) return false; *outNeighbour = neighbourList->items[0]; const size_t bytesToMove = (neighbourList->count - 1) * sizeof(neighbourList->items[0]); memmove(&neighbourList->items[0], &neighbourList->items[1], bytesToMove); neighbourList->count--; return true; } static void FootpathNeighbourListRemove(FootpathNeighbourList* neighbourList, size_t index) { Guard::ArgumentInRange(index, 0, neighbourList->count - 1); int32_t itemsRemaining = static_cast(neighbourList->count - index) - 1; if (itemsRemaining > 0) { memmove(&neighbourList->items[index], &neighbourList->items[index + 1], sizeof(FootpathNeighbour) * itemsRemaining); } neighbourList->count--; } static void FoopathNeighbourListSort(FootpathNeighbourList* neighbourList) { qsort(neighbourList->items, neighbourList->count, sizeof(FootpathNeighbour), FootpathNeighbourCompare); } static TileElement* FootpathGetElement(const CoordsXYRangedZ& footpathPos, int32_t direction) { TileElement* tileElement = MapGetFirstElementAt(footpathPos); if (tileElement == nullptr) return nullptr; do { if (tileElement->GetType() != TileElementType::Path) continue; if (footpathPos.clearanceZ == tileElement->GetBaseZ()) { if (tileElement->AsPath()->IsSloped()) { auto slope = tileElement->AsPath()->GetSlopeDirection(); if (slope != direction) break; } return tileElement; } if (footpathPos.baseZ == tileElement->GetBaseZ()) { if (!tileElement->AsPath()->IsSloped()) break; auto slope = DirectionReverse(tileElement->AsPath()->GetSlopeDirection()); if (slope != direction) break; return tileElement; } } while (!(tileElement++)->IsLastForTile()); return nullptr; } /** * Attempt to connect a newly disconnected queue tile to the specified path tile */ static bool FootpathReconnectQueueToPath( const CoordsXY& footpathPos, TileElement* tileElement, int32_t action, int32_t direction) { if (((tileElement->AsPath()->GetEdges() & (1 << direction)) == 0) ^ (action < 0)) return false; auto targetQueuePos = footpathPos + CoordsDirectionDelta[direction]; if (action < 0) { if (WallInTheWay({ footpathPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() }, direction)) return false; if (WallInTheWay( { targetQueuePos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() }, DirectionReverse(direction))) return false; } int32_t z = tileElement->GetBaseZ(); TileElement* targetFootpathElement = FootpathGetElement({ targetQueuePos, z - LAND_HEIGHT_STEP, z }, direction); if (targetFootpathElement != nullptr && !targetFootpathElement->AsPath()->IsQueue()) { auto targetQueueElement = targetFootpathElement->AsPath(); tileElement->AsPath()->SetSlopeDirection(0); if (action > 0) { tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() & ~(1 << direction)); targetQueueElement->SetEdges(targetQueueElement->GetEdges() & ~(1 << (DirectionReverse(direction) & 3))); if (action >= 2) tileElement->AsPath()->SetSlopeDirection(direction); } else if (action < 0) { tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() | (1 << direction)); targetQueueElement->SetEdges(targetQueueElement->GetEdges() | (1 << (DirectionReverse(direction) & 3))); } if (action != 0) MapInvalidateTileFull(targetQueuePos); return true; } return false; } static bool FootpathDisconnectQueueFromPath(const CoordsXY& footpathPos, TileElement* tileElement, int32_t action) { if (!tileElement->AsPath()->IsQueue()) return false; if (tileElement->AsPath()->IsSloped()) return false; uint8_t c = connected_path_count[tileElement->AsPath()->GetEdges()]; if ((action < 0) ? (c >= 2) : (c < 2)) return false; if (action < 0) { uint8_t direction = tileElement->AsPath()->GetSlopeDirection(); if (FootpathReconnectQueueToPath(footpathPos, tileElement, action, direction)) return true; } for (Direction direction : ALL_DIRECTIONS) { if ((action < 0) && (direction == tileElement->AsPath()->GetSlopeDirection())) continue; if (FootpathReconnectQueueToPath(footpathPos, tileElement, action, direction)) return true; } return false; } /** * * rct2: 0x006A6D7E */ static void Loc6A6FD2(const CoordsXYZ& initialTileElementPos, int32_t direction, TileElement* initialTileElement, bool query) { if ((initialTileElement)->GetType() == TileElementType::Path) { if (!query) { initialTileElement->AsPath()->SetEdges(initialTileElement->AsPath()->GetEdges() | (1 << direction)); MapInvalidateElement(initialTileElementPos, initialTileElement); } } } static void Loc6A6F1F( const CoordsXYZ& initialTileElementPos, int32_t direction, TileElement* tileElement, TileElement* initialTileElement, const CoordsXY& targetPos, int32_t flags, bool query, FootpathNeighbourList* neighbourList) { if (query) { if (WallInTheWay({ targetPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() }, DirectionReverse(direction))) { return; } if (tileElement->AsPath()->IsQueue()) { if (connected_path_count[tileElement->AsPath()->GetEdges()] < 2) { FootpathNeighbourListPush( neighbourList, 4, direction, tileElement->AsPath()->GetRideIndex(), tileElement->AsPath()->GetStationIndex()); } else { if ((initialTileElement)->GetType() == TileElementType::Path && initialTileElement->AsPath()->IsQueue()) { if (FootpathDisconnectQueueFromPath(targetPos, tileElement, 0)) { FootpathNeighbourListPush( neighbourList, 3, direction, tileElement->AsPath()->GetRideIndex(), tileElement->AsPath()->GetStationIndex()); } } } } else { FootpathNeighbourListPush(neighbourList, 2, direction, RideId::GetNull(), StationIndex::GetNull()); } } else { FootpathDisconnectQueueFromPath(targetPos, tileElement, 1 + ((flags >> 6) & 1)); tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() | (1 << DirectionReverse(direction))); if (tileElement->AsPath()->IsQueue()) { FootpathQueueChainPush(tileElement->AsPath()->GetRideIndex()); } } if (!(flags & (GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED))) { FootpathInterruptPeeps({ targetPos, tileElement->GetBaseZ() }); } MapInvalidateElement(targetPos, tileElement); Loc6A6FD2(initialTileElementPos, direction, initialTileElement, query); } static void Loc6A6D7E( const CoordsXYZ& initialTileElementPos, int32_t direction, TileElement* initialTileElement, int32_t flags, bool query, FootpathNeighbourList* neighbourList) { auto targetPos = CoordsXY{ initialTileElementPos } + CoordsDirectionDelta[direction]; if (((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode) && MapIsEdge(targetPos)) { if (query) { FootpathNeighbourListPush(neighbourList, 7, direction, RideId::GetNull(), StationIndex::GetNull()); } Loc6A6FD2(initialTileElementPos, direction, initialTileElement, query); } else { TileElement* tileElement = MapGetFirstElementAt(targetPos); if (tileElement == nullptr) return; do { switch (tileElement->GetType()) { case TileElementType::Path: if (tileElement->GetBaseZ() == initialTileElementPos.z) { if (!tileElement->AsPath()->IsSloped() || tileElement->AsPath()->GetSlopeDirection() == direction) { Loc6A6F1F( initialTileElementPos, direction, tileElement, initialTileElement, targetPos, flags, query, neighbourList); } return; } if (tileElement->GetBaseZ() == initialTileElementPos.z - LAND_HEIGHT_STEP) { if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == DirectionReverse(direction)) { Loc6A6F1F( initialTileElementPos, direction, tileElement, initialTileElement, targetPos, flags, query, neighbourList); } return; } break; case TileElementType::Track: if (initialTileElementPos.z == tileElement->GetBaseZ()) { auto ride = GetRide(tileElement->AsTrack()->GetRideIndex()); if (ride == nullptr) { continue; } if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE)) { continue; } const auto trackType = tileElement->AsTrack()->GetTrackType(); const uint8_t trackSequence = tileElement->AsTrack()->GetSequenceIndex(); const auto& ted = GetTrackElementDescriptor(trackType); if (!(ted.SequenceProperties[trackSequence] & TRACK_SEQUENCE_FLAG_CONNECTS_TO_PATH)) { return; } uint16_t dx = DirectionReverse((direction - tileElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK); if (!(ted.SequenceProperties[trackSequence] & (1 << dx))) { return; } if (query) { FootpathNeighbourListPush( neighbourList, 1, direction, tileElement->AsTrack()->GetRideIndex(), StationIndex::GetNull()); } Loc6A6FD2(initialTileElementPos, direction, initialTileElement, query); return; } break; case TileElementType::Entrance: if (initialTileElementPos.z == tileElement->GetBaseZ()) { if (entrance_has_direction( *(tileElement->AsEntrance()), DirectionReverse(direction - tileElement->GetDirection()))) { if (query) { FootpathNeighbourListPush( neighbourList, 8, direction, tileElement->AsEntrance()->GetRideIndex(), tileElement->AsEntrance()->GetStationIndex()); } else { if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_PARK_ENTRANCE) { FootpathQueueChainPush(tileElement->AsEntrance()->GetRideIndex()); } } Loc6A6FD2(initialTileElementPos, direction, initialTileElement, query); return; } } break; default: break; } } while (!(tileElement++)->IsLastForTile()); } } // TODO: Change this into a simple check that validates if the direction should be fully checked with Loc6A6D7E and move the // calling of Loc6A6D7E into the parent function. static void Loc6A6C85( const CoordsXYE& tileElementPos, int32_t direction, int32_t flags, bool query, FootpathNeighbourList* neighbourList) { if (query && WallInTheWay( { tileElementPos, tileElementPos.element->GetBaseZ(), tileElementPos.element->GetClearanceZ() }, direction)) return; if (tileElementPos.element->GetType() == TileElementType::Entrance) { if (!entrance_has_direction( *(tileElementPos.element->AsEntrance()), direction - tileElementPos.element->GetDirection())) { return; } } if (tileElementPos.element->GetType() == TileElementType::Track) { auto ride = GetRide(tileElementPos.element->AsTrack()->GetRideIndex()); if (ride == nullptr) { return; } if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE)) { return; } const auto trackType = tileElementPos.element->AsTrack()->GetTrackType(); const uint8_t trackSequence = tileElementPos.element->AsTrack()->GetSequenceIndex(); const auto& ted = GetTrackElementDescriptor(trackType); if (!(ted.SequenceProperties[trackSequence] & TRACK_SEQUENCE_FLAG_CONNECTS_TO_PATH)) { return; } uint16_t dx = (direction - tileElementPos.element->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK; if (!(ted.SequenceProperties[trackSequence] & (1 << dx))) { return; } } auto pos = CoordsXYZ{ tileElementPos, tileElementPos.element->GetBaseZ() }; if (tileElementPos.element->GetType() == TileElementType::Path) { if (tileElementPos.element->AsPath()->IsSloped()) { if ((tileElementPos.element->AsPath()->GetSlopeDirection() - direction) & 1) { return; } if (tileElementPos.element->AsPath()->GetSlopeDirection() == direction) { pos.z += LAND_HEIGHT_STEP; } } } Loc6A6D7E(pos, direction, tileElementPos.element, flags, query, neighbourList); } /** * * rct2: 0x006A6C66 */ void FootpathConnectEdges(const CoordsXY& footpathPos, TileElement* tileElement, int32_t flags) { FootpathNeighbourList neighbourList; FootpathNeighbour neighbour; FootpathUpdateQueueChains(); FootpathNeighbourListInit(&neighbourList); FootpathUpdateQueueEntranceBanner(footpathPos, tileElement); for (Direction direction : ALL_DIRECTIONS) { Loc6A6C85({ footpathPos, tileElement }, direction, flags, true, &neighbourList); } FoopathNeighbourListSort(&neighbourList); if (tileElement->GetType() == TileElementType::Path && tileElement->AsPath()->IsQueue()) { RideId rideIndex = RideId::GetNull(); StationIndex entranceIndex = StationIndex::GetNull(); for (size_t i = 0; i < neighbourList.count; i++) { if (!neighbourList.items[i].ride_index.IsNull()) { if (rideIndex.IsNull()) { rideIndex = neighbourList.items[i].ride_index; entranceIndex = neighbourList.items[i].entrance_index; } else if (rideIndex != neighbourList.items[i].ride_index) { FootpathNeighbourListRemove(&neighbourList, i); } else if ( rideIndex == neighbourList.items[i].ride_index && entranceIndex != neighbourList.items[i].entrance_index && !neighbourList.items[i].entrance_index.IsNull()) { FootpathNeighbourListRemove(&neighbourList, i); } } } neighbourList.count = std::min(neighbourList.count, 2); } while (FootpathNeighbourListPop(&neighbourList, &neighbour)) { Loc6A6C85({ footpathPos, tileElement }, neighbour.direction, flags, false, nullptr); } if (tileElement->GetType() == TileElementType::Path) { FootpathConnectCorners(footpathPos, tileElement->AsPath()); } } /** * * rct2: 0x006A742F */ void FootpathChainRideQueue( RideId rideIndex, StationIndex entranceIndex, const CoordsXY& initialFootpathPos, TileElement* const initialTileElement, int32_t direction) { TileElement *lastPathElement, *lastQueuePathElement; auto tileElement = initialTileElement; auto curQueuePos = initialFootpathPos; auto lastPath = curQueuePos; int32_t baseZ = tileElement->GetBaseZ(); int32_t lastPathDirection = direction; lastPathElement = nullptr; lastQueuePathElement = nullptr; for (;;) { if (tileElement->GetType() == TileElementType::Path) { lastPathElement = tileElement; lastPath = curQueuePos; lastPathDirection = direction; if (tileElement->AsPath()->IsSloped()) { if (tileElement->AsPath()->GetSlopeDirection() == direction) { baseZ += LAND_HEIGHT_STEP; } } } auto targetQueuePos = curQueuePos + CoordsDirectionDelta[direction]; tileElement = MapGetFirstElementAt(targetQueuePos); bool foundQueue = false; if (tileElement != nullptr) { do { if (lastQueuePathElement == tileElement) continue; if (tileElement->GetType() != TileElementType::Path) continue; if (tileElement->GetBaseZ() == baseZ) { if (tileElement->AsPath()->IsSloped()) { if (tileElement->AsPath()->GetSlopeDirection() != direction) break; } foundQueue = true; break; } if (tileElement->GetBaseZ() == baseZ - LAND_HEIGHT_STEP) { if (!tileElement->AsPath()->IsSloped()) break; if (DirectionReverse(tileElement->AsPath()->GetSlopeDirection()) != direction) break; baseZ -= LAND_HEIGHT_STEP; foundQueue = true; break; } } while (!(tileElement++)->IsLastForTile()); } if (!foundQueue) break; if (tileElement->AsPath()->IsQueue()) { // Fix #2051: Stop queue paths that are already connected to two other tiles // from connecting to the tile we are coming from. int32_t edges = tileElement->AsPath()->GetEdges(); int32_t numEdges = BitCount(edges); if (numEdges >= 2) { int32_t requiredEdgeMask = 1 << DirectionReverse(direction); if (!(edges & requiredEdgeMask)) { break; } } tileElement->AsPath()->SetHasQueueBanner(false); tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() | (1 << DirectionReverse(direction))); tileElement->AsPath()->SetRideIndex(rideIndex); tileElement->AsPath()->SetStationIndex(entranceIndex); curQueuePos = targetQueuePos; MapInvalidateElement(targetQueuePos, tileElement); if (lastQueuePathElement == nullptr) { lastQueuePathElement = tileElement; } if (tileElement->AsPath()->GetEdges() & (1 << direction)) continue; direction = (direction + 1) & 3; if (tileElement->AsPath()->GetEdges() & (1 << direction)) continue; direction = DirectionReverse(direction); if (tileElement->AsPath()->GetEdges() & (1 << direction)) continue; } break; } if (!rideIndex.IsNull() && lastPathElement != nullptr) { if (lastPathElement->AsPath()->IsQueue()) { lastPathElement->AsPath()->SetHasQueueBanner(true); lastPathElement->AsPath()->SetQueueBannerDirection(lastPathDirection); // set the ride sign direction MapAnimationCreate(MAP_ANIMATION_TYPE_QUEUE_BANNER, { lastPath, lastPathElement->GetBaseZ() }); } } } void FootpathQueueChainReset() { _footpathQueueChainNext = _footpathQueueChain; } /** * * rct2: 0x006A76E9 */ void FootpathQueueChainPush(RideId rideIndex) { if (!rideIndex.IsNull()) { auto* lastSlot = _footpathQueueChain + std::size(_footpathQueueChain) - 1; if (_footpathQueueChainNext <= lastSlot) { *_footpathQueueChainNext++ = rideIndex; } } } /** * * rct2: 0x006A759F */ void FootpathUpdateQueueChains() { for (auto* queueChainPtr = _footpathQueueChain; queueChainPtr < _footpathQueueChainNext; queueChainPtr++) { RideId rideIndex = *queueChainPtr; auto ride = GetRide(rideIndex); if (ride == nullptr) continue; for (const auto& station : ride->GetStations()) { if (station.Entrance.IsNull()) continue; TileElement* tileElement = MapGetFirstElementAt(station.Entrance); if (tileElement != nullptr) { do { if (tileElement->GetType() != TileElementType::Entrance) continue; if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_RIDE_ENTRANCE) continue; if (tileElement->AsEntrance()->GetRideIndex() != rideIndex) continue; Direction direction = DirectionReverse(tileElement->GetDirection()); FootpathChainRideQueue( rideIndex, ride->GetStationIndex(&station), station.Entrance.ToCoordsXY(), tileElement, direction); } while (!(tileElement++)->IsLastForTile()); } } } } /** * * rct2: 0x0069ADBD */ static void FootpathFixOwnership(const CoordsXY& mapPos) { const auto* surfaceElement = MapGetSurfaceElementAt(mapPos); uint16_t ownership; // Unlikely to be NULL unless deliberate. if (surfaceElement != nullptr) { // If the tile is not safe to own construction rights of, erase them. if (CheckMaxAllowableLandRightsForTile({ mapPos, surfaceElement->BaseHeight << 3 }) == OWNERSHIP_UNOWNED) { ownership = OWNERSHIP_UNOWNED; } // If the tile is safe to own construction rights of, do not erase construction rights. else { ownership = surfaceElement->GetOwnership(); // You can't own the entrance path. if (ownership == OWNERSHIP_OWNED || ownership == OWNERSHIP_AVAILABLE) { ownership = OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED; } } } else { ownership = OWNERSHIP_UNOWNED; } auto landSetRightsAction = LandSetRightsAction(mapPos, LandSetRightSetting::SetOwnershipWithChecks, ownership); landSetRightsAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND); GameActions::Execute(&landSetRightsAction); } static bool GetNextDirection(int32_t edges, int32_t* direction) { int32_t index = UtilBitScanForward(edges); if (index == -1) return false; *direction = index; return true; } /** * * rct2: 0x0069AC1A * @param flags (1 << 0): Ignore queues * (1 << 5): Unown * (1 << 7): Ignore no entry signs */ static int32_t FootpathIsConnectedToMapEdgeHelper(CoordsXYZ footpathPos, int32_t direction, int32_t flags) { int32_t returnVal = FOOTPATH_SEARCH_INCOMPLETE; struct TileState { bool processed = false; CoordsXYZ footpathPos; int32_t direction; int32_t level; int32_t distanceFromJunction; int32_t junctionTolerance; }; // Vector of all of the child tile elements for us to explore std::vector tiles; TileElement* tileElement = nullptr; int numPendingTiles = 0; TileState currentTile = { false, footpathPos, direction, 0, 0, 16 }; // Captures the current state of the variables and stores them in tiles vector for iteration later auto CaptureCurrentTileState = [&tiles, &numPendingTiles](TileState t_currentTile) -> void { // Search for an entry of this tile in our list already for (const TileState& tile : tiles) { if (tile.footpathPos == t_currentTile.footpathPos && tile.direction == t_currentTile.direction) return; } // If we get here we did not find it, so insert the tile into our list tiles.push_back(t_currentTile); ++numPendingTiles; }; // Loads the next tile to visit into our variables auto LoadNextTileElement = [&tiles, &numPendingTiles](TileState& t_currentTile) -> void { // Do not continue if there are no tiles in the list if (tiles.size() == 0) return; // Find the next unprocessed tile for (size_t tileIndex = tiles.size() - 1; tileIndex > 0; --tileIndex) { if (tiles[tileIndex].processed) continue; --numPendingTiles; t_currentTile = tiles[tileIndex]; tiles[tileIndex].processed = true; return; } // Default to tile 0 --numPendingTiles; t_currentTile = tiles[0]; tiles[0].processed = true; }; // Encapsulate the tile skipping logic to make do-while more readable auto SkipTileElement = [](int32_t ste_flags, TileElement* ste_tileElement, int32_t& ste_slopeDirection, int32_t ste_direction, const CoordsXYZ& ste_targetPos) { if (ste_tileElement->GetType() != TileElementType::Path) return true; if (ste_tileElement->AsPath()->IsSloped() && (ste_slopeDirection = ste_tileElement->AsPath()->GetSlopeDirection()) != ste_direction) { if (DirectionReverse(ste_slopeDirection) != ste_direction) return true; if (ste_tileElement->GetBaseZ() + PATH_HEIGHT_STEP != ste_targetPos.z) return true; } else if (ste_tileElement->GetBaseZ() != ste_targetPos.z) return true; if (!(ste_flags & FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_QUEUES)) if (ste_tileElement->AsPath()->IsQueue()) return true; return false; }; int32_t edges, slopeDirection; // Capture the current tile state to begin the loop CaptureCurrentTileState(currentTile); // Loop on this until all tiles are processed or we return while (numPendingTiles > 0) { LoadNextTileElement(currentTile); CoordsXYZ targetPos = CoordsXYZ{ CoordsXY{ currentTile.footpathPos } + CoordsDirectionDelta[currentTile.direction], currentTile.footpathPos.z }; if (++currentTile.level > 250) return FOOTPATH_SEARCH_TOO_COMPLEX; // Return immediately if we are at the edge of the map and not unowning // Or if we are unowning and have no tiles left if ((MapIsEdge(targetPos) && !(flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN))) { return FOOTPATH_SEARCH_SUCCESS; } tileElement = MapGetFirstElementAt(targetPos); if (tileElement == nullptr) return currentTile.level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : FOOTPATH_SEARCH_INCOMPLETE; // Loop while there are unvisited TileElements at targetPos do { if (SkipTileElement(flags, tileElement, slopeDirection, currentTile.direction, targetPos)) continue; // Unown the footpath if needed if (flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN) FootpathFixOwnership(targetPos); edges = tileElement->AsPath()->GetEdges(); currentTile.direction = DirectionReverse(currentTile.direction); if (!tileElement->IsLastForTile() && !(flags & FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_NO_ENTRY)) { int elementIndex = 1; // Loop over all elements and cull appropriate edges do { if (tileElement[elementIndex].GetType() == TileElementType::Path) break; if (tileElement[elementIndex].GetType() != TileElementType::Banner) { continue; } edges &= tileElement[elementIndex].AsBanner()->GetAllowedEdges(); } while (!tileElement[elementIndex++].IsLastForTile()); } // Exclude the direction we came from targetPos.z = tileElement->GetBaseZ(); edges &= ~(1 << currentTile.direction); if (!GetNextDirection(edges, ¤tTile.direction)) break; edges &= ~(1 << currentTile.direction); if (edges == 0) { // Only possible direction to go if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == currentTile.direction) targetPos.z += PATH_HEIGHT_STEP; // Prepare the next iteration currentTile.footpathPos = targetPos; ++currentTile.distanceFromJunction; CaptureCurrentTileState(currentTile); } else { // We have reached a junction --currentTile.junctionTolerance; if (currentTile.distanceFromJunction != 0) { --currentTile.junctionTolerance; } if (currentTile.junctionTolerance < 0 && !(flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN)) { returnVal = FOOTPATH_SEARCH_TOO_COMPLEX; break; } // Loop until there are no more directions we can go do { edges &= ~(1 << currentTile.direction); if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == currentTile.direction) { targetPos.z += PATH_HEIGHT_STEP; } // Add each possible path to the list of pending tiles currentTile.footpathPos = targetPos; currentTile.distanceFromJunction = 0; CaptureCurrentTileState(currentTile); } while (GetNextDirection(edges, ¤tTile.direction)); } break; } while (!(tileElement++)->IsLastForTile()); // Return success if we have unowned all tiles in our pending list if ((flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN) && numPendingTiles <= 0) { return FOOTPATH_SEARCH_SUCCESS; } } return currentTile.level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : returnVal; } // TODO: Use GAME_COMMAND_FLAGS int32_t FootpathIsConnectedToMapEdge(const CoordsXYZ& footpathPos, int32_t direction, int32_t flags) { flags |= FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_QUEUES; return FootpathIsConnectedToMapEdgeHelper(footpathPos, direction, flags); } bool PathElement::IsSloped() const { return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_IS_SLOPED) != 0; } void PathElement::SetSloped(bool isSloped) { Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_IS_SLOPED; if (isSloped) Flags2 |= FOOTPATH_ELEMENT_FLAGS2_IS_SLOPED; } bool PathElement::HasJunctionRailings() const { return Flags2 & FOOTPATH_ELEMENT_FLAGS2_HAS_JUNCTION_RAILINGS; } void PathElement::SetJunctionRailings(bool hasJunctionRailings) { Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_HAS_JUNCTION_RAILINGS; if (hasJunctionRailings) Flags2 |= FOOTPATH_ELEMENT_FLAGS2_HAS_JUNCTION_RAILINGS; } Direction PathElement::GetSlopeDirection() const { return SlopeDirection; } void PathElement::SetSlopeDirection(Direction newSlope) { SlopeDirection = newSlope; } bool PathElement::IsQueue() const { return (Type & FOOTPATH_ELEMENT_TYPE_FLAG_IS_QUEUE) != 0; } void PathElement::SetIsQueue(bool isQueue) { Type &= ~FOOTPATH_ELEMENT_TYPE_FLAG_IS_QUEUE; if (isQueue) Type |= FOOTPATH_ELEMENT_TYPE_FLAG_IS_QUEUE; } bool PathElement::HasQueueBanner() const { return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_HAS_QUEUE_BANNER) != 0; } void PathElement::SetHasQueueBanner(bool hasQueueBanner) { Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_HAS_QUEUE_BANNER; if (hasQueueBanner) Flags2 |= FOOTPATH_ELEMENT_FLAGS2_HAS_QUEUE_BANNER; } bool PathElement::IsBroken() const { return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_BROKEN) != 0; } void PathElement::SetIsBroken(bool isBroken) { if (isBroken) { Flags2 |= FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_BROKEN; } else { Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_BROKEN; } } bool PathElement::IsBlockedByVehicle() const { return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_BLOCKED_BY_VEHICLE) != 0; } void PathElement::SetIsBlockedByVehicle(bool isBlocked) { if (isBlocked) { Flags2 |= FOOTPATH_ELEMENT_FLAGS2_BLOCKED_BY_VEHICLE; } else { Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_BLOCKED_BY_VEHICLE; } } ::StationIndex PathElement::GetStationIndex() const { return StationIndex; } void PathElement::SetStationIndex(::StationIndex newStationIndex) { StationIndex = newStationIndex; } bool PathElement::IsWide() const { return (Type & FOOTPATH_ELEMENT_TYPE_FLAG_IS_WIDE) != 0; } void PathElement::SetWide(bool isWide) { Type &= ~FOOTPATH_ELEMENT_TYPE_FLAG_IS_WIDE; if (isWide) Type |= FOOTPATH_ELEMENT_TYPE_FLAG_IS_WIDE; } bool PathElement::HasAddition() const { return Additions != 0; } uint8_t PathElement::GetAddition() const { return Additions; } ObjectEntryIndex PathElement::GetAdditionEntryIndex() const { return GetAddition() - 1; } const PathBitEntry* PathElement::GetAdditionEntry() const { if (!HasAddition()) return nullptr; return OpenRCT2::ObjectManager::GetObjectEntry(GetAdditionEntryIndex()); } void PathElement::SetAddition(uint8_t newAddition) { Additions = newAddition; } bool PathElement::AdditionIsGhost() const { return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_GHOST) != 0; } void PathElement::SetAdditionIsGhost(bool isGhost) { Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_GHOST; if (isGhost) Flags2 |= FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_GHOST; } ObjectEntryIndex PathElement::GetLegacyPathEntryIndex() const { if (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY) return SurfaceIndex; return OBJECT_ENTRY_INDEX_NULL; } const FootpathObject* PathElement::GetLegacyPathEntry() const { return GetLegacyFootpathEntry(GetLegacyPathEntryIndex()); } void PathElement::SetLegacyPathEntryIndex(ObjectEntryIndex newIndex) { SurfaceIndex = newIndex; RailingsIndex = OBJECT_ENTRY_INDEX_NULL; Flags2 |= FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY; } bool PathElement::HasLegacyPathEntry() const { return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY) != 0; } const PathSurfaceDescriptor* PathElement::GetSurfaceDescriptor() const { if (HasLegacyPathEntry()) { const auto* legacyPathEntry = GetLegacyPathEntry(); if (legacyPathEntry == nullptr) return nullptr; if (IsQueue()) return &legacyPathEntry->GetQueueSurfaceDescriptor(); return &legacyPathEntry->GetPathSurfaceDescriptor(); } const auto* surfaceEntry = GetSurfaceEntry(); if (surfaceEntry == nullptr) return nullptr; return &surfaceEntry->GetDescriptor(); } const PathRailingsDescriptor* PathElement::GetRailingsDescriptor() const { if (HasLegacyPathEntry()) { const auto* legacyPathEntry = GetLegacyPathEntry(); if (legacyPathEntry == nullptr) return nullptr; return &legacyPathEntry->GetPathRailingsDescriptor(); } const auto* railingsEntry = GetRailingsEntry(); if (railingsEntry == nullptr) return nullptr; return &railingsEntry->GetDescriptor(); } ObjectEntryIndex PathElement::GetSurfaceEntryIndex() const { if (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY) return OBJECT_ENTRY_INDEX_NULL; return SurfaceIndex; } const FootpathSurfaceObject* PathElement::GetSurfaceEntry() const { auto& objMgr = OpenRCT2::GetContext()->GetObjectManager(); return static_cast(objMgr.GetLoadedObject(ObjectType::FootpathSurface, GetSurfaceEntryIndex())); } void PathElement::SetSurfaceEntryIndex(ObjectEntryIndex newIndex) { SurfaceIndex = newIndex; Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY; } ObjectEntryIndex PathElement::GetRailingsEntryIndex() const { if (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY) return OBJECT_ENTRY_INDEX_NULL; return RailingsIndex; } const FootpathRailingsObject* PathElement::GetRailingsEntry() const { auto& objMgr = OpenRCT2::GetContext()->GetObjectManager(); return static_cast(objMgr.GetLoadedObject(ObjectType::FootpathRailings, GetRailingsEntryIndex())); } void PathElement::SetRailingsEntryIndex(ObjectEntryIndex newEntryIndex) { RailingsIndex = newEntryIndex; Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY; } uint8_t PathElement::GetQueueBannerDirection() const { return ((Type & FOOTPATH_ELEMENT_TYPE_DIRECTION_MASK) >> 6); } void PathElement::SetQueueBannerDirection(uint8_t direction) { Type &= ~FOOTPATH_ELEMENT_TYPE_DIRECTION_MASK; Type |= (direction << 6); } bool PathElement::ShouldDrawPathOverSupports() const { // TODO: make this an actual decision of the tile element. return (GetRailingsDescriptor()->Flags & RAILING_ENTRY_FLAG_DRAW_PATH_OVER_SUPPORTS); } void PathElement::SetShouldDrawPathOverSupports(bool on) { LOG_VERBOSE("Setting 'draw path over supports' to %d", static_cast(on)); } /** * * rct2: 0x006A8B12 * clears the wide footpath flag for all footpaths * at location */ static void FootpathClearWide(const CoordsXY& footpathPos) { TileElement* tileElement = MapGetFirstElementAt(footpathPos); if (tileElement == nullptr) return; do { if (tileElement->GetType() != TileElementType::Path) continue; tileElement->AsPath()->SetWide(false); } while (!(tileElement++)->IsLastForTile()); } /** * * rct2: 0x006A8ACF * returns footpath element if it can be made wide * returns NULL if it can not be made wide */ static TileElement* FootpathCanBeWide(const CoordsXYZ& footpathPos) { TileElement* tileElement = MapGetFirstElementAt(footpathPos); if (tileElement == nullptr) return nullptr; do { if (tileElement->GetType() != TileElementType::Path) continue; if (footpathPos.z != tileElement->GetBaseZ()) continue; if (tileElement->AsPath()->IsQueue()) continue; if (tileElement->AsPath()->IsSloped()) continue; return tileElement; } while (!(tileElement++)->IsLastForTile()); return nullptr; } /** * * rct2: 0x006A87BB */ void FootpathUpdatePathWideFlags(const CoordsXY& footpathPos) { if (MapIsLocationAtEdge(footpathPos)) return; FootpathClearWide(footpathPos); /* Rather than clearing the wide flag of the following tiles and * checking the state of them later, leave them intact and assume * they were cleared. Consequently only the wide flag for this single * tile is modified by this update. * This is important for avoiding glitches in pathfinding that occurs * between the batches of updates to the path wide flags. * Corresponding pathList[] indexes for the following tiles * are: 2, 3, 4, 5. * Note: indexes 3, 4, 5 are reset in the current call; * index 2 is reset in the previous call. */ // x += 0x20; // FootpathClearWide(x, y); // y += 0x20; // FootpathClearWide(x, y); // x -= 0x20; // FootpathClearWide(x, y); // y -= 0x20; TileElement* tileElement = MapGetFirstElementAt(footpathPos); if (tileElement == nullptr) return; do { if (tileElement->GetType() != TileElementType::Path) continue; if (tileElement->AsPath()->IsQueue()) continue; if (tileElement->AsPath()->IsSloped()) continue; if (tileElement->AsPath()->GetEdges() == 0) continue; auto height = tileElement->GetBaseZ(); // pathList is a list of elements, set by Sub6A8ACF adjacent to x,y // Spanned from 0x00F3EFA8 to 0x00F3EFC7 (8 elements) in the original std::array pathList; for (std::size_t direction = 0; direction < pathList.size(); ++direction) { auto footpathLoc = CoordsXYZ(footpathPos + CoordsDirectionDelta[direction], height); pathList[direction] = FootpathCanBeWide(footpathLoc); } uint8_t pathConnections = 0; if (tileElement->AsPath()->GetEdges() & EDGE_NW) { pathConnections |= FOOTPATH_CONNECTION_NW; const auto* pathElement = std::get<3>(pathList); if (pathElement != nullptr && pathElement->AsPath()->IsWide()) { pathConnections &= ~FOOTPATH_CONNECTION_NW; } } if (tileElement->AsPath()->GetEdges() & EDGE_NE) { pathConnections |= FOOTPATH_CONNECTION_NE; const auto* pathElement = std::get<0>(pathList); if (pathElement != nullptr && pathElement->AsPath()->IsWide()) { pathConnections &= ~FOOTPATH_CONNECTION_NE; } } if (tileElement->AsPath()->GetEdges() & EDGE_SE) { pathConnections |= FOOTPATH_CONNECTION_SE; /* In the following: * footpath_element_is_wide(pathList[1]) * is always false due to the tile update order * in combination with reset tiles. * Commented out since it will never occur. */ // if (pathList[1] != nullptr) { // if (footpath_element_is_wide(pathList[1])) { // pathConnections &= ~FOOTPATH_CONNECTION_SE; // } //} } if (tileElement->AsPath()->GetEdges() & EDGE_SW) { pathConnections |= FOOTPATH_CONNECTION_SW; /* In the following: * footpath_element_is_wide(pathList[2]) * is always false due to the tile update order * in combination with reset tiles. * Commented out since it will never occur. */ // if (pathList[2] != nullptr) { // if (footpath_element_is_wide(pathList[2])) { // pathConnections &= ~FOOTPATH_CONNECTION_SW; // } //} } if ((pathConnections & FOOTPATH_CONNECTION_NW) && std::get<3>(pathList) != nullptr && !std::get<3>(pathList)->AsPath()->IsWide()) { constexpr uint8_t edgeMask1 = EDGE_SE | EDGE_SW; const auto* pathElement0 = std::get<0>(pathList); const auto* pathElement7 = std::get<7>(pathList); if ((pathConnections & FOOTPATH_CONNECTION_NE) && pathElement7 != nullptr && !pathElement7->AsPath()->IsWide() && (pathElement7->AsPath()->GetEdges() & edgeMask1) == edgeMask1 && pathElement0 != nullptr && !pathElement0->AsPath()->IsWide()) { pathConnections |= FOOTPATH_CONNECTION_S; } /* In the following: * footpath_element_is_wide(pathList[2]) * is always false due to the tile update order * in combination with reset tiles. * Short circuit the logic appropriately. */ constexpr uint8_t edgeMask2 = EDGE_NE | EDGE_SE; const auto* pathElement2 = std::get<2>(pathList); const auto* pathElement6 = std::get<6>(pathList); if ((pathConnections & FOOTPATH_CONNECTION_SW) && pathElement6 != nullptr && !(pathElement6)->AsPath()->IsWide() && (pathElement6->AsPath()->GetEdges() & edgeMask2) == edgeMask2 && pathElement2 != nullptr) { pathConnections |= FOOTPATH_CONNECTION_E; } } /* In the following: * footpath_element_is_wide(pathList[4]) * footpath_element_is_wide(pathList[1]) * are always false due to the tile update order * in combination with reset tiles. * Short circuit the logic appropriately. */ if ((pathConnections & FOOTPATH_CONNECTION_SE) && std::get<1>(pathList) != nullptr) { constexpr uint8_t edgeMask1 = EDGE_SW | EDGE_NW; const auto* pathElement0 = std::get<0>(pathList); const auto* pathElement4 = std::get<4>(pathList); if ((pathConnections & FOOTPATH_CONNECTION_NE) && (pathElement4 != nullptr) && (pathElement4->AsPath()->GetEdges() & edgeMask1) == edgeMask1 && pathElement0 != nullptr && !pathElement0->AsPath()->IsWide()) { pathConnections |= FOOTPATH_CONNECTION_W; } /* In the following: * footpath_element_is_wide(pathList[5]) * footpath_element_is_wide(pathList[2]) * are always false due to the tile update order * in combination with reset tiles. * Short circuit the logic appropriately. */ constexpr uint8_t edgeMask2 = EDGE_NE | EDGE_NW; const auto* pathElement2 = std::get<2>(pathList); const auto* pathElement5 = std::get<5>(pathList); if ((pathConnections & FOOTPATH_CONNECTION_SW) && pathElement5 != nullptr && (pathElement5->AsPath()->GetEdges() & edgeMask2) == edgeMask2 && pathElement2 != nullptr) { pathConnections |= FOOTPATH_CONNECTION_N; } } if ((pathConnections & FOOTPATH_CONNECTION_NW) && (pathConnections & (FOOTPATH_CONNECTION_E | FOOTPATH_CONNECTION_S))) { pathConnections &= ~FOOTPATH_CONNECTION_NW; } if ((pathConnections & FOOTPATH_CONNECTION_NE) && (pathConnections & (FOOTPATH_CONNECTION_W | FOOTPATH_CONNECTION_S))) { pathConnections &= ~FOOTPATH_CONNECTION_NE; } if ((pathConnections & FOOTPATH_CONNECTION_SE) && (pathConnections & (FOOTPATH_CONNECTION_N | FOOTPATH_CONNECTION_W))) { pathConnections &= ~FOOTPATH_CONNECTION_SE; } if ((pathConnections & FOOTPATH_CONNECTION_SW) && (pathConnections & (FOOTPATH_CONNECTION_E | FOOTPATH_CONNECTION_N))) { pathConnections &= ~FOOTPATH_CONNECTION_SW; } if (!(pathConnections & (FOOTPATH_CONNECTION_NE | FOOTPATH_CONNECTION_SE | FOOTPATH_CONNECTION_SW | FOOTPATH_CONNECTION_NW))) { uint8_t e = tileElement->AsPath()->GetEdgesAndCorners(); if ((e != 0b10101111) && (e != 0b01011111) && (e != 0b11101111)) tileElement->AsPath()->SetWide(true); } } while (!(tileElement++)->IsLastForTile()); } bool FootpathIsBlockedByVehicle(const TileCoordsXYZ& position) { auto pathElement = MapGetFirstTileElementWithBaseHeightBetween({ position, position.z + PATH_HEIGHT_STEP }); return pathElement != nullptr && pathElement->IsBlockedByVehicle(); } /** * * rct2: 0x006A7642 */ void FootpathUpdateQueueEntranceBanner(const CoordsXY& footpathPos, TileElement* tileElement) { const auto elementType = tileElement->GetType(); if (elementType == TileElementType::Path) { if (tileElement->AsPath()->IsQueue()) { FootpathQueueChainPush(tileElement->AsPath()->GetRideIndex()); for (int32_t direction = 0; direction < NumOrthogonalDirections; direction++) { if (tileElement->AsPath()->GetEdges() & (1 << direction)) { FootpathChainRideQueue( RideId::GetNull(), StationIndex::FromUnderlying(0), footpathPos, tileElement, direction); } } tileElement->AsPath()->SetRideIndex(RideId::GetNull()); } } else if (elementType == TileElementType::Entrance) { if (tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_RIDE_ENTRANCE) { FootpathQueueChainPush(tileElement->AsEntrance()->GetRideIndex()); FootpathChainRideQueue( RideId::GetNull(), StationIndex::FromUnderlying(0), footpathPos, tileElement, DirectionReverse(tileElement->GetDirection())); } } } /** * * rct2: 0x006A6B7F */ static void FootpathRemoveEdgesTowardsHere( const CoordsXYZ& footpathPos, int32_t direction, TileElement* tileElement, bool isQueue) { if (tileElement->AsPath()->IsQueue()) { FootpathQueueChainPush(tileElement->AsPath()->GetRideIndex()); } auto d = DirectionReverse(direction); tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() & ~(1 << d)); int32_t cd = ((d - 1) & 3); tileElement->AsPath()->SetCorners(tileElement->AsPath()->GetCorners() & ~(1 << cd)); cd = ((cd + 1) & 3); tileElement->AsPath()->SetCorners(tileElement->AsPath()->GetCorners() & ~(1 << cd)); MapInvalidateTile({ footpathPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() }); if (isQueue) FootpathDisconnectQueueFromPath(footpathPos, tileElement, -1); Direction shiftedDirection = (direction + 1) & 3; auto targetFootPathPos = CoordsXYZ{ CoordsXY{ footpathPos } + CoordsDirectionDelta[shiftedDirection], footpathPos.z }; tileElement = MapGetFirstElementAt(targetFootPathPos); if (tileElement == nullptr) return; do { if (tileElement->GetType() != TileElementType::Path) continue; if (tileElement->GetBaseZ() != targetFootPathPos.z) continue; if (tileElement->AsPath()->IsSloped()) break; cd = ((shiftedDirection + 1) & 3); tileElement->AsPath()->SetCorners(tileElement->AsPath()->GetCorners() & ~(1 << cd)); MapInvalidateTile({ targetFootPathPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() }); break; } while (!(tileElement++)->IsLastForTile()); } /** * * rct2: 0x006A6B14 */ static void FootpathRemoveEdgesTowards(const CoordsXYRangedZ& footPathPos, int32_t direction, bool isQueue) { if (!MapIsLocationValid(footPathPos)) { return; } TileElement* tileElement = MapGetFirstElementAt(footPathPos); if (tileElement == nullptr) return; do { if (tileElement->GetType() != TileElementType::Path) continue; if (footPathPos.clearanceZ == tileElement->GetBaseZ()) { if (tileElement->AsPath()->IsSloped()) { uint8_t slope = tileElement->AsPath()->GetSlopeDirection(); if (slope != direction) break; } FootpathRemoveEdgesTowardsHere({ footPathPos, footPathPos.clearanceZ }, direction, tileElement, isQueue); break; } if (footPathPos.baseZ == tileElement->GetBaseZ()) { if (!tileElement->AsPath()->IsSloped()) break; uint8_t slope = DirectionReverse(tileElement->AsPath()->GetSlopeDirection()); if (slope != direction) break; FootpathRemoveEdgesTowardsHere({ footPathPos, footPathPos.clearanceZ }, direction, tileElement, isQueue); break; } } while (!(tileElement++)->IsLastForTile()); } // Returns true when there is an element at the given coordinates that want to connect to a path with the given direction (ride // entrances and exits, shops, paths). bool TileElementWantsPathConnectionTowards(const TileCoordsXYZD& coords, const TileElement* const elementToBeRemoved) { TileElement* tileElement = MapGetFirstElementAt(coords); if (tileElement == nullptr) return false; do { // Don't check the element that gets removed if (tileElement == elementToBeRemoved) continue; switch (tileElement->GetType()) { case TileElementType::Path: if (tileElement->BaseHeight == coords.z) { if (!tileElement->AsPath()->IsSloped()) // The footpath is flat, it can be connected to from any direction return true; if (tileElement->AsPath()->GetSlopeDirection() == DirectionReverse(coords.direction)) // The footpath is sloped and its lowest point matches the edge connection return true; } else if (tileElement->BaseHeight + 2 == coords.z) { if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == coords.direction) // The footpath is sloped and its higher point matches the edge connection return true; } break; case TileElementType::Track: if (tileElement->BaseHeight == coords.z) { auto ride = GetRide(tileElement->AsTrack()->GetRideIndex()); if (ride == nullptr) continue; if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE)) break; const auto trackType = tileElement->AsTrack()->GetTrackType(); const uint8_t trackSequence = tileElement->AsTrack()->GetSequenceIndex(); const auto& ted = GetTrackElementDescriptor(trackType); if (ted.SequenceProperties[trackSequence] & TRACK_SEQUENCE_FLAG_CONNECTS_TO_PATH) { uint16_t dx = ((coords.direction - tileElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK); if (ted.SequenceProperties[trackSequence] & (1 << dx)) { // Track element has the flags required for the given direction return true; } } } break; case TileElementType::Entrance: if (tileElement->BaseHeight == coords.z) { if (entrance_has_direction(*(tileElement->AsEntrance()), coords.direction - tileElement->GetDirection())) { // Entrance wants to be connected towards the given direction return true; } } break; default: break; } } while (!(tileElement++)->IsLastForTile()); return false; } // fix up the corners around the given path element that gets removed static void FootpathFixCornersAround(const TileCoordsXY& footpathPos, TileElement* pathElement) { // A mask for the paths' corners of each possible neighbour static constexpr uint8_t cornersTouchingTile[3][3] = { { 0b0010, 0b0011, 0b0001 }, { 0b0110, 0b0000, 0b1001 }, { 0b0100, 0b1100, 0b1000 }, }; // Sloped paths don't create filled corners, so no need to remove any if (pathElement->GetType() == TileElementType::Path && pathElement->AsPath()->IsSloped()) return; for (int32_t xOffset = -1; xOffset <= 1; xOffset++) { for (int32_t yOffset = -1; yOffset <= 1; yOffset++) { // Skip self if (xOffset == 0 && yOffset == 0) continue; TileElement* tileElement = MapGetFirstElementAt( TileCoordsXY{ footpathPos.x + xOffset, footpathPos.y + yOffset }.ToCoordsXY()); if (tileElement == nullptr) continue; do { if (tileElement->GetType() != TileElementType::Path) continue; if (tileElement->AsPath()->IsSloped()) continue; if (tileElement->BaseHeight != pathElement->BaseHeight) continue; const int32_t ix = xOffset + 1; const int32_t iy = yOffset + 1; tileElement->AsPath()->SetCorners(tileElement->AsPath()->GetCorners() & ~(cornersTouchingTile[iy][ix])); } while (!(tileElement++)->IsLastForTile()); } } } /** * * rct2: 0x006A6AA7 * @param x x-coordinate in units (not tiles) * @param y y-coordinate in units (not tiles) */ void FootpathRemoveEdgesAt(const CoordsXY& footpathPos, TileElement* tileElement) { if (tileElement->GetType() == TileElementType::Track) { auto rideIndex = tileElement->AsTrack()->GetRideIndex(); auto ride = GetRide(rideIndex); if (ride == nullptr) return; if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE)) return; } FootpathUpdateQueueEntranceBanner(footpathPos, tileElement); bool fixCorners = false; for (uint8_t direction = 0; direction < NumOrthogonalDirections; direction++) { int32_t z1 = tileElement->BaseHeight; if (tileElement->GetType() == TileElementType::Path) { if (tileElement->AsPath()->IsSloped()) { int32_t slope = tileElement->AsPath()->GetSlopeDirection(); // Sloped footpaths don't connect sideways if ((slope - direction) & 1) continue; // When a path is sloped, the higher point of the path is 2 units higher z1 += (slope == direction) ? 2 : 0; } } // When clearance checks were disabled a neighbouring path can be connected to both the path-ghost and to something // else, so before removing edges from neighbouring paths we have to make sure there is nothing else they are connected // to. if (!TileElementWantsPathConnectionTowards({ TileCoordsXY{ footpathPos }, z1, direction }, tileElement)) { bool isQueue = tileElement->GetType() == TileElementType::Path ? tileElement->AsPath()->IsQueue() : false; int32_t z0 = z1 - 2; FootpathRemoveEdgesTowards( { footpathPos + CoordsDirectionDelta[direction], z0 * COORDS_Z_STEP, z1 * COORDS_Z_STEP }, direction, isQueue); } else { // A footpath may stay connected, but its edges must be fixed later on when another edge does get removed. fixCorners = true; } } // Only fix corners when needed, to avoid changing corners that have been set for its looks. if (fixCorners && tileElement->IsGhost()) { auto tileFootpathPos = TileCoordsXY{ footpathPos }; FootpathFixCornersAround(tileFootpathPos, tileElement); } if (tileElement->GetType() == TileElementType::Path) tileElement->AsPath()->SetEdgesAndCorners(0); } static ObjectEntryIndex FootpathGetDefaultSurface(bool queue) { bool showEditorPaths = ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode); for (ObjectEntryIndex i = 0; i < MAX_FOOTPATH_SURFACE_OBJECTS; i++) { auto pathEntry = GetPathSurfaceEntry(i); if (pathEntry != nullptr) { if (!showEditorPaths && (pathEntry->Flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR)) { continue; } if (queue == ((pathEntry->Flags & FOOTPATH_ENTRY_FLAG_IS_QUEUE) != 0)) { return i; } } } return OBJECT_ENTRY_INDEX_NULL; } static bool FootpathIsSurfaceEntryOkay(ObjectEntryIndex index, bool queue) { auto pathEntry = GetPathSurfaceEntry(index); if (pathEntry != nullptr) { bool showEditorPaths = ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode); if (!showEditorPaths && (pathEntry->Flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR)) { return false; } if (queue == ((pathEntry->Flags & FOOTPATH_ENTRY_FLAG_IS_QUEUE) != 0)) { return true; } } return false; } static ObjectEntryIndex FootpathGetDefaultRailings() { for (ObjectEntryIndex i = 0; i < MAX_FOOTPATH_RAILINGS_OBJECTS; i++) { const auto* railingEntry = GetPathRailingsEntry(i); if (railingEntry != nullptr) { return i; } } return OBJECT_ENTRY_INDEX_NULL; } static bool FootpathIsLegacyPathEntryOkay(ObjectEntryIndex index) { bool showEditorPaths = ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode); auto& objManager = OpenRCT2::GetContext()->GetObjectManager(); auto footpathObj = static_cast(objManager.GetLoadedObject(ObjectType::Paths, index)); if (footpathObj != nullptr) { auto pathEntry = reinterpret_cast(footpathObj->GetLegacyData()); return showEditorPaths || !(pathEntry->flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR); } return false; } static ObjectEntryIndex FootpathGetDefaultLegacyPath() { for (ObjectEntryIndex i = 0; i < MAX_PATH_OBJECTS; i++) { if (FootpathIsLegacyPathEntryOkay(i)) { return i; } } return OBJECT_ENTRY_INDEX_NULL; } bool FootpathSelectDefault() { // Select default footpath auto surfaceIndex = FootpathGetDefaultSurface(false); if (FootpathIsSurfaceEntryOkay(gFootpathSelection.NormalSurface, false)) { surfaceIndex = gFootpathSelection.NormalSurface; } // Select default queue auto queueIndex = FootpathGetDefaultSurface(true); if (FootpathIsSurfaceEntryOkay(gFootpathSelection.QueueSurface, true)) { queueIndex = gFootpathSelection.QueueSurface; } // Select default railing auto railingIndex = FootpathGetDefaultRailings(); const auto* railingEntry = GetPathRailingsEntry(gFootpathSelection.Railings); if (railingEntry != nullptr) { railingIndex = gFootpathSelection.Railings; } // Select default legacy path auto legacyPathIndex = FootpathGetDefaultLegacyPath(); if (gFootpathSelection.LegacyPath != OBJECT_ENTRY_INDEX_NULL) { if (FootpathIsLegacyPathEntryOkay(gFootpathSelection.LegacyPath)) { // Keep legacy path selected legacyPathIndex = gFootpathSelection.LegacyPath; } else { // Reset legacy path, we default to a surface (if there are any) gFootpathSelection.LegacyPath = OBJECT_ENTRY_INDEX_NULL; } } if (surfaceIndex == OBJECT_ENTRY_INDEX_NULL) { if (legacyPathIndex == OBJECT_ENTRY_INDEX_NULL) { // No surfaces or legacy paths available return false; } // No surfaces available, so default to legacy path gFootpathSelection.LegacyPath = legacyPathIndex; } gFootpathSelection.NormalSurface = surfaceIndex; gFootpathSelection.QueueSurface = queueIndex; gFootpathSelection.Railings = railingIndex; return true; } const FootpathObject* GetLegacyFootpathEntry(ObjectEntryIndex entryIndex) { auto& objMgr = OpenRCT2::GetContext()->GetObjectManager(); auto obj = objMgr.GetLoadedObject(ObjectType::Paths, entryIndex); if (obj == nullptr) return nullptr; const FootpathObject* footpathObject = (static_cast(obj)); return footpathObject; } const FootpathSurfaceObject* GetPathSurfaceEntry(ObjectEntryIndex entryIndex) { auto& objMgr = OpenRCT2::GetContext()->GetObjectManager(); auto obj = objMgr.GetLoadedObject(ObjectType::FootpathSurface, entryIndex); if (obj == nullptr) return nullptr; return static_cast(obj); } const FootpathRailingsObject* GetPathRailingsEntry(ObjectEntryIndex entryIndex) { auto& objMgr = OpenRCT2::GetContext()->GetObjectManager(); auto obj = objMgr.GetLoadedObject(ObjectType::FootpathRailings, entryIndex); if (obj == nullptr) return nullptr; return static_cast(obj); } RideId PathElement::GetRideIndex() const { return rideIndex; } void PathElement::SetRideIndex(RideId newRideIndex) { rideIndex = newRideIndex; } uint8_t PathElement::GetAdditionStatus() const { return AdditionStatus; } void PathElement::SetAdditionStatus(uint8_t newStatus) { AdditionStatus = newStatus; } uint8_t PathElement::GetEdges() const { return EdgesAndCorners & FOOTPATH_PROPERTIES_EDGES_EDGES_MASK; } void PathElement::SetEdges(uint8_t newEdges) { EdgesAndCorners &= ~FOOTPATH_PROPERTIES_EDGES_EDGES_MASK; EdgesAndCorners |= (newEdges & FOOTPATH_PROPERTIES_EDGES_EDGES_MASK); } uint8_t PathElement::GetCorners() const { return EdgesAndCorners >> 4; } void PathElement::SetCorners(uint8_t newCorners) { EdgesAndCorners &= ~FOOTPATH_PROPERTIES_EDGES_CORNERS_MASK; EdgesAndCorners |= (newCorners << 4); } uint8_t PathElement::GetEdgesAndCorners() const { return EdgesAndCorners; } void PathElement::SetEdgesAndCorners(uint8_t newEdgesAndCorners) { EdgesAndCorners = newEdgesAndCorners; } bool PathElement::IsLevelCrossing(const CoordsXY& coords) const { auto trackElement = MapGetTrackElementAt({ coords, GetBaseZ() }); if (trackElement == nullptr) { return false; } if (trackElement->GetTrackType() != TrackElemType::Flat) { return false; } auto ride = GetRide(trackElement->GetRideIndex()); if (ride == nullptr) { return false; } return ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_SUPPORTS_LEVEL_CROSSINGS); }