/***************************************************************************** * Copyright (c) 2014-2019 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ #include "../Cheats.h" #include "../Context.h" #include "../Game.h" #include "../OpenRCT2.h" #include "../actions/FootpathPlaceAction.hpp" #include "../actions/FootpathRemoveAction.hpp" #include "../actions/LandSetRightsAction.hpp" #include "../core/Guard.hpp" #include "../localisation/Localisation.h" #include "../management/Finance.h" #include "../network/network.h" #include "../object/FootpathObject.h" #include "../object/ObjectList.h" #include "../object/ObjectManager.h" #include "../paint/VirtualFloor.h" #include "../ride/Station.h" #include "../ride/Track.h" #include "../ride/TrackData.h" #include "../util/Util.h" #include "Map.h" #include "MapAnimation.h" #include "Park.h" #include "Sprite.h" #include "Surface.h" #include #include void footpath_update_queue_entrance_banner(int32_t x, int32_t y, TileElement* tileElement); uint8_t gFootpathProvisionalFlags; LocationXYZ16 gFootpathProvisionalPosition; uint8_t gFootpathProvisionalType; uint8_t gFootpathProvisionalSlope; uint8_t gFootpathConstructionMode; uint16_t gFootpathSelectedId; uint8_t gFootpathSelectedType; LocationXYZ16 gFootpathConstructFromPosition; uint8_t gFootpathConstructDirection; uint8_t gFootpathConstructSlope; uint8_t gFootpathConstructValidDirections; money32 gFootpathPrice; uint8_t gFootpathGroundFlags; static uint8_t* _footpathQueueChainNext; static uint8_t _footpathQueueChain[64]; // This is the coordinates that a user of the bin should move to // rct2: 0x00992A4C const LocationXY16 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 LocationXY16 BenchUseOffsets[8] = { { 7, 12 }, { 12, 25 }, { 25, 20 }, { 20, 7 }, { 7, 20 }, { 20, 25 }, { 25, 12 }, { 12, 7 }, }; /** rct2: 0x00981D6C, 0x00981D6E */ const LocationXY16 word_981D6C[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 entrance_get_directions(const TileElement* tileElement) { uint8_t entranceType = tileElement->AsEntrance()->GetEntranceType(); uint8_t sequence = tileElement->AsEntrance()->GetSequenceIndex(); return EntranceDirections[(entranceType * 8) + sequence]; } static bool entrance_has_direction(TileElement* tileElement, int32_t direction) { return entrance_get_directions(tileElement) & (1 << (direction & 3)); } TileElement* map_get_footpath_element(int32_t x, int32_t y, int32_t z) { TileElement* tileElement = map_get_first_element_at(x, y); do { if (tileElement == nullptr) break; if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH && tileElement->base_height == z) return tileElement; } while (!(tileElement++)->IsLastForTile()); return nullptr; } money32 footpath_remove(int32_t x, int32_t y, int32_t z, int32_t flags) { auto action = FootpathRemoveAction({ x, y, z * 8 }); action.SetFlags(flags); if (flags & GAME_COMMAND_FLAG_APPLY) { auto res = GameActions::Execute(&action); return res->Cost; } auto res = GameActions::Query(&action); return res->Cost; } /** * * rct2: 0x006A76FF */ money32 footpath_provisional_set(int32_t type, int32_t x, int32_t y, int32_t z, int32_t slope) { money32 cost; footpath_provisional_remove(); auto footpathPlaceAction = FootpathPlaceAction({ x, y, z * 8 }, slope, type); footpathPlaceAction.SetFlags(GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED); auto res = GameActions::Execute(&footpathPlaceAction); cost = res->Error == GA_ERROR::OK ? res->Cost : MONEY32_UNDEFINED; if (res->Error == GA_ERROR::OK) { gFootpathProvisionalType = type; gFootpathProvisionalPosition.x = x; gFootpathProvisionalPosition.y = y; gFootpathProvisionalPosition.z = z & 0xFF; gFootpathProvisionalSlope = slope; gFootpathProvisionalFlags |= PROVISIONAL_PATH_FLAG_1; if (gFootpathGroundFlags & ELEMENT_IS_UNDERGROUND) { viewport_set_visibility(1); } else { viewport_set_visibility(3); } } // Invalidate previous footpath piece. virtual_floor_invalidate(); if (!scenery_tool_is_active()) { if (res->Error != GA_ERROR::OK) { // If we can't build this, don't show a virtual floor. virtual_floor_set_height(0); } else if ( gFootpathConstructSlope == TILE_ELEMENT_SLOPE_FLAT || gFootpathProvisionalPosition.z * 8 < gFootpathConstructFromPosition.z) { // Going either straight on, or down. virtual_floor_set_height(gFootpathProvisionalPosition.z * 8); } else { // Going up in the world! virtual_floor_set_height((gFootpathProvisionalPosition.z + 2) * 8); } } return cost; } /** * * rct2: 0x006A77FF */ void footpath_provisional_remove() { if (gFootpathProvisionalFlags & PROVISIONAL_PATH_FLAG_1) { gFootpathProvisionalFlags &= ~PROVISIONAL_PATH_FLAG_1; footpath_remove( gFootpathProvisionalPosition.x, gFootpathProvisionalPosition.y, gFootpathProvisionalPosition.z, GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST); } } /** * * rct2: 0x006A7831 */ void footpath_provisional_update() { if (gFootpathProvisionalFlags & PROVISIONAL_PATH_FLAG_SHOW_ARROW) { gFootpathProvisionalFlags &= ~PROVISIONAL_PATH_FLAG_SHOW_ARROW; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; map_invalidate_tile_full(gFootpathConstructFromPosition.x, gFootpathConstructFromPosition.y); } footpath_provisional_remove(); } /** * 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 */ void footpath_get_coordinates_from_pos( ScreenCoordsXY screenCoords, int32_t* x, int32_t* y, int32_t* direction, TileElement** tileElement) { int32_t z = 0, interactionType; TileElement* myTileElement; rct_viewport* viewport; LocationXY16 position16 = {}; get_map_coordinates_from_pos( screenCoords.x, screenCoords.y, VIEWPORT_INTERACTION_MASK_FOOTPATH, &position16.x, &position16.y, &interactionType, &myTileElement, &viewport); if (interactionType != VIEWPORT_INTERACTION_ITEM_FOOTPATH || !(viewport->flags & (VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_HIDE_VERTICAL))) { get_map_coordinates_from_pos( screenCoords.x, screenCoords.y, VIEWPORT_INTERACTION_MASK_FOOTPATH & VIEWPORT_INTERACTION_MASK_TERRAIN, &position16.x, &position16.y, &interactionType, &myTileElement, &viewport); if (interactionType == VIEWPORT_INTERACTION_ITEM_NONE) { if (x != nullptr) *x = LOCATION_NULL; return; } } CoordsXY position = { position16.x, position16.y }; auto minPosition = position; auto maxPosition = position + CoordsXY{ 31, 31 }; position += CoordsXY{ 16, 16 }; if (interactionType == VIEWPORT_INTERACTION_ITEM_FOOTPATH) { z = myTileElement->base_height * 8; if (myTileElement->AsPath()->IsSloped()) { z += 8; } } LocationXY16 start_vp_pos = screen_coord_to_viewport_coord(viewport, screenCoords); for (int32_t i = 0; i < 5; i++) { if (interactionType != VIEWPORT_INTERACTION_ITEM_FOOTPATH) { z = tile_element_height(position); } position = viewport_coord_to_map_coord(start_vp_pos.x, start_vp_pos.y, 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; } } if (x != nullptr) *x = position.x & ~0x1F; if (y != nullptr) *y = position.y & ~0x1F; if (direction != nullptr) *direction = myDirection; if (tileElement != nullptr) *tileElement = myTileElement; } /** * * rct2: 0x0068A0C9 * screenX: eax * screenY: ebx * x: ax * y: bx * direction: cl * tileElement: edx */ void footpath_bridge_get_info_from_pos( ScreenCoordsXY screenCoords, int32_t* x, int32_t* y, 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. int32_t interactionType; rct_viewport* viewport; LocationXY16 map_pos = {}; get_map_coordinates_from_pos( screenCoords.x, screenCoords.y, VIEWPORT_INTERACTION_MASK_RIDE, &map_pos.x, &map_pos.y, &interactionType, tileElement, &viewport); *x = map_pos.x; *y = map_pos.y; if (interactionType == VIEWPORT_INTERACTION_ITEM_RIDE && viewport->flags & (VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_HIDE_VERTICAL) && (*tileElement)->GetType() == TILE_ELEMENT_TYPE_ENTRANCE) { int32_t directions = entrance_get_directions(*tileElement); if (directions & 0x0F) { int32_t bx = bitscanforward(directions); bx += (*tileElement)->AsEntrance()->GetDirection(); bx &= 3; if (direction != nullptr) *direction = bx; return; } } get_map_coordinates_from_pos( screenCoords.x, screenCoords.y, VIEWPORT_INTERACTION_MASK_RIDE & VIEWPORT_INTERACTION_MASK_FOOTPATH & VIEWPORT_INTERACTION_MASK_TERRAIN, &map_pos.x, &map_pos.y, &interactionType, tileElement, &viewport); *x = map_pos.x; *y = map_pos.y; if (interactionType == VIEWPORT_INTERACTION_ITEM_RIDE && (*tileElement)->GetType() == TILE_ELEMENT_TYPE_ENTRANCE) { int32_t directions = entrance_get_directions(*tileElement); if (directions & 0x0F) { int32_t bx = (*tileElement)->GetDirectionWithOffset(bitscanforward(directions)); if (direction != nullptr) *direction = bx; return; } } // We point at something else footpath_get_coordinates_from_pos(screenCoords, x, y, direction, tileElement); } /** * * rct2: 0x00673883 */ void footpath_remove_litter(int32_t x, int32_t y, int32_t z) { uint16_t spriteIndex = sprite_get_first_in_quadrant(x, y); while (spriteIndex != SPRITE_INDEX_NULL) { rct_litter* sprite = &get_sprite(spriteIndex)->litter; uint16_t nextSpriteIndex = sprite->next_in_quadrant; if (sprite->linked_list_index == SPRITE_LIST_LITTER) { int32_t distanceZ = abs(sprite->z - z); if (distanceZ <= 32) { invalidate_sprite_0((rct_sprite*)sprite); sprite_remove((rct_sprite*)sprite); } } spriteIndex = nextSpriteIndex; } } /** * * rct2: 0x0069A48B */ void footpath_interrupt_peeps(int32_t x, int32_t y, int32_t z) { uint16_t spriteIndex = sprite_get_first_in_quadrant(x, y); while (spriteIndex != SPRITE_INDEX_NULL) { Peep* peep = &get_sprite(spriteIndex)->peep; uint16_t nextSpriteIndex = peep->next_in_quadrant; if (peep->linked_list_index == SPRITE_LIST_PEEP) { if (peep->state == PEEP_STATE_SITTING || peep->state == PEEP_STATE_WATCHING) { if (peep->z == z) { peep->SetState(PEEP_STATE_WALKING); peep->destination_x = (peep->x & 0xFFE0) + 16; peep->destination_y = (peep->y & 0xFFE0) + 16; peep->destination_tolerance = 5; peep->UpdateCurrentActionSpriteType(); } } } spriteIndex = nextSpriteIndex; } } /** * 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 fence_in_the_way(int32_t x, int32_t y, int32_t z0, int32_t z1, int32_t direction) { TileElement* tileElement; tileElement = map_get_first_element_at(x >> 5, y >> 5); if (tileElement == nullptr) return false; do { if (tileElement->GetType() != TILE_ELEMENT_TYPE_WALL) continue; if (tileElement->IsGhost()) continue; if (z0 >= tileElement->clearance_height) continue; if (z1 <= tileElement->base_height) continue; if ((tileElement->GetDirection()) != direction) continue; return true; } while (!(tileElement++)->IsLastForTile()); return false; } static TileElement* footpath_connect_corners_get_neighbour(int32_t x, int32_t y, int32_t z, int32_t requireEdges) { if (!map_is_location_valid({ x, y })) { return nullptr; } TileElement* tileElement = map_get_first_element_at(x >> 5, y >> 5); if (tileElement == nullptr) return nullptr; do { if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH) continue; if (tileElement->AsPath()->IsQueue()) continue; if (tileElement->base_height != z) continue; if (!(tileElement->AsPath()->GetEdgesAndCorners() & requireEdges)) continue; return tileElement; } 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 footpath_connect_corners(int32_t initialX, int32_t initialY, TileElement* initialTileElement) { TileElement* tileElement[4]; if (initialTileElement->AsPath()->IsQueue()) return; if (initialTileElement->AsPath()->IsSloped()) return; tileElement[0] = initialTileElement; int32_t z = initialTileElement->base_height; for (int32_t initialDirection = 0; initialDirection < 4; initialDirection++) { int32_t x = initialX; int32_t y = initialY; int32_t direction = initialDirection; x += CoordsDirectionDelta[direction].x; y += CoordsDirectionDelta[direction].y; tileElement[1] = footpath_connect_corners_get_neighbour(x, y, z, (1 << direction_reverse(direction))); if (tileElement[1] == nullptr) continue; direction = (direction + 1) & 3; x += CoordsDirectionDelta[direction].x; y += CoordsDirectionDelta[direction].y; tileElement[2] = footpath_connect_corners_get_neighbour(x, y, z, (1 << direction_reverse(direction))); if (tileElement[2] == nullptr) continue; direction = (direction + 1) & 3; x += CoordsDirectionDelta[direction].x; y += CoordsDirectionDelta[direction].y; // First check link to previous tile tileElement[3] = footpath_connect_corners_get_neighbour(x, y, z, (1 << direction_reverse(direction))); if (tileElement[3] == nullptr) continue; // Second check link to initial tile tileElement[3] = footpath_connect_corners_get_neighbour(x, y, z, (1 << ((direction + 1) & 3))); if (tileElement[3] == nullptr) continue; direction = (direction + 1) & 3; tileElement[3]->AsPath()->SetCorners(tileElement[3]->AsPath()->GetCorners() | (1 << (direction))); map_invalidate_element(x, y, tileElement[3]); direction = (direction - 1) & 3; tileElement[2]->AsPath()->SetCorners(tileElement[2]->AsPath()->GetCorners() | (1 << (direction))); map_invalidate_element(x, y, tileElement[2]); direction = (direction - 1) & 3; tileElement[1]->AsPath()->SetCorners(tileElement[1]->AsPath()->GetCorners() | (1 << (direction))); map_invalidate_element(x, y, tileElement[1]); direction = initialDirection; tileElement[0]->AsPath()->SetCorners(tileElement[0]->AsPath()->GetCorners() | (1 << (direction))); map_invalidate_element(x, y, tileElement[0]); } } struct rct_neighbour { uint8_t order; uint8_t direction; uint8_t ride_index; uint8_t entrance_index; }; struct rct_neighbour_list { rct_neighbour items[8]; size_t count; }; static int32_t rct_neighbour_compare(const void* a, const void* b) { uint8_t va = ((rct_neighbour*)a)->order; uint8_t vb = ((rct_neighbour*)b)->order; if (va < vb) return 1; else if (va > vb) return -1; else { uint8_t da = ((rct_neighbour*)a)->direction; uint8_t db = ((rct_neighbour*)b)->direction; if (da < db) return -1; else if (da > db) return 1; else return 0; } } static void neighbour_list_init(rct_neighbour_list* neighbourList) { neighbourList->count = 0; } static void neighbour_list_push( rct_neighbour_list* neighbourList, int32_t order, int32_t direction, ride_id_t rideIndex, uint8_t 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 neighbour_list_pop(rct_neighbour_list* neighbourList, rct_neighbour* 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 neighbour_list_remove(rct_neighbour_list* neighbourList, size_t index) { Guard::ArgumentInRange(index, 0, neighbourList->count - 1); int32_t itemsRemaining = (int32_t)(neighbourList->count - index) - 1; if (itemsRemaining > 0) { memmove(&neighbourList->items[index], &neighbourList->items[index + 1], sizeof(rct_neighbour) * itemsRemaining); } neighbourList->count--; } static void neighbour_list_sort(rct_neighbour_list* neighbourList) { qsort(neighbourList->items, neighbourList->count, sizeof(rct_neighbour), rct_neighbour_compare); } static TileElement* footpath_get_element(int32_t x, int32_t y, int32_t z0, int32_t z1, int32_t direction) { TileElement* tileElement; int32_t slope; tileElement = map_get_first_element_at(x >> 5, y >> 5); if (tileElement == nullptr) return nullptr; do { if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH) continue; if (z1 == tileElement->base_height) { if (tileElement->AsPath()->IsSloped()) { slope = tileElement->AsPath()->GetSlopeDirection(); if (slope != direction) break; } return tileElement; } if (z0 == tileElement->base_height) { if (!tileElement->AsPath()->IsSloped()) break; slope = direction_reverse(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 footpath_reconnect_queue_to_path(int32_t x, int32_t y, TileElement* tileElement, int32_t action, int32_t direction) { if (((tileElement->AsPath()->GetEdges() & (1 << direction)) == 0) ^ (action < 0)) return false; int32_t x1 = x + CoordsDirectionDelta[direction].x; int32_t y1 = y + CoordsDirectionDelta[direction].y; if (action < 0) { if (fence_in_the_way(x, y, tileElement->base_height, tileElement->clearance_height, direction)) return false; if (fence_in_the_way(x1, y1, tileElement->base_height, tileElement->clearance_height, direction_reverse(direction))) return false; } int32_t z = tileElement->base_height; TileElement* otherTileElement = footpath_get_element(x1, y1, z - 2, z, direction); if (otherTileElement != nullptr && !otherTileElement->AsPath()->IsQueue()) { tileElement->AsPath()->SetSlopeDirection(0); if (action > 0) { tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() & ~(1 << direction)); otherTileElement->AsPath()->SetEdges(otherTileElement->AsPath()->GetEdges() & ~(1 << ((direction + 2) & 3))); if (action >= 2) tileElement->AsPath()->SetSlopeDirection(direction); } else if (action < 0) { tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() | (1 << direction)); otherTileElement->AsPath()->SetEdges(otherTileElement->AsPath()->GetEdges() | (1 << ((direction + 2) & 3))); } if (action != 0) map_invalidate_tile_full(x1, y1); return true; } return false; } static bool footpath_disconnect_queue_from_path(int32_t x, int32_t y, 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 (footpath_reconnect_queue_to_path(x, y, tileElement, action, direction)) return true; } for (Direction direction : ALL_DIRECTIONS) { if ((action < 0) && (direction == tileElement->AsPath()->GetSlopeDirection())) continue; if (footpath_reconnect_queue_to_path(x, y, tileElement, action, direction)) return true; } return false; } /** * * rct2: 0x006A6D7E */ static void loc_6A6D7E( int32_t initialX, int32_t initialY, int32_t z, int32_t direction, TileElement* initialTileElement, int32_t flags, bool query, rct_neighbour_list* neighbourList) { int32_t x = initialX + CoordsDirectionDelta[direction].x; int32_t y = initialY + CoordsDirectionDelta[direction].y; if (((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode) && map_is_edge({ x, y })) { if (query) { neighbour_list_push(neighbourList, 7, direction, 255, 255); } } else { TileElement* tileElement = map_get_first_element_at(x >> 5, y >> 5); if (tileElement == nullptr) return; do { switch (tileElement->GetType()) { case TILE_ELEMENT_TYPE_PATH: if (z == tileElement->base_height) { if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() != direction) { return; } else { goto loc_6A6F1F; } } if (z - 2 == tileElement->base_height) { if (!tileElement->AsPath()->IsSloped() || tileElement->AsPath()->GetSlopeDirection() != direction_reverse(direction)) { return; } goto loc_6A6F1F; } break; case TILE_ELEMENT_TYPE_TRACK: if (z == tileElement->base_height) { auto ride = get_ride(tileElement->AsTrack()->GetRideIndex()); if (ride == nullptr || !ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_FLAT_RIDE)) { continue; } const uint8_t trackType = tileElement->AsTrack()->GetTrackType(); const uint8_t trackSequence = tileElement->AsTrack()->GetSequenceIndex(); if (!(FlatRideTrackSequenceProperties[trackType][trackSequence] & TRACK_SEQUENCE_FLAG_CONNECTS_TO_PATH)) { return; } uint16_t dx = direction_reverse( (direction - tileElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK); if (!(FlatRideTrackSequenceProperties[trackType][trackSequence] & (1 << dx))) { return; } if (query) { neighbour_list_push(neighbourList, 1, direction, tileElement->AsTrack()->GetRideIndex(), 255); } goto loc_6A6FD2; } break; case TILE_ELEMENT_TYPE_ENTRANCE: if (z == tileElement->base_height) { if (entrance_has_direction(tileElement, direction_reverse(direction - tileElement->GetDirection()))) { if (query) { neighbour_list_push( neighbourList, 8, direction, tileElement->AsEntrance()->GetRideIndex(), tileElement->AsEntrance()->GetStationIndex()); } else { if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_PARK_ENTRANCE) { footpath_queue_chain_push(tileElement->AsEntrance()->GetRideIndex()); } } goto loc_6A6FD2; } } break; } } while (!(tileElement++)->IsLastForTile()); return; loc_6A6F1F: if (query) { if (fence_in_the_way(x, y, tileElement->base_height, tileElement->clearance_height, direction_reverse(direction))) { return; } if (tileElement->AsPath()->IsQueue()) { if (connected_path_count[tileElement->AsPath()->GetEdges()] < 2) { neighbour_list_push( neighbourList, 4, direction, tileElement->AsPath()->GetRideIndex(), tileElement->AsPath()->GetStationIndex()); } else { if ((initialTileElement)->GetType() == TILE_ELEMENT_TYPE_PATH && initialTileElement->AsPath()->IsQueue()) { if (footpath_disconnect_queue_from_path(x, y, tileElement, 0)) { neighbour_list_push( neighbourList, 3, direction, tileElement->AsPath()->GetRideIndex(), tileElement->AsPath()->GetStationIndex()); } } } } else { neighbour_list_push(neighbourList, 2, direction, 255, 255); } } else { footpath_disconnect_queue_from_path(x, y, tileElement, 1 + ((flags >> 6) & 1)); tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() | (1 << direction_reverse(direction))); if (tileElement->AsPath()->IsQueue()) { footpath_queue_chain_push(tileElement->AsPath()->GetRideIndex()); } } if (!(flags & (GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED))) { footpath_interrupt_peeps(x, y, tileElement->base_height * 8); } map_invalidate_element(x, y, tileElement); } loc_6A6FD2: if ((initialTileElement)->GetType() == TILE_ELEMENT_TYPE_PATH) { if (!query) { initialTileElement->AsPath()->SetEdges(initialTileElement->AsPath()->GetEdges() | (1 << direction)); map_invalidate_element(initialX, initialY, initialTileElement); } } } static void loc_6A6C85( int32_t x, int32_t y, int32_t direction, TileElement* tileElement, int32_t flags, bool query, rct_neighbour_list* neighbourList) { if (query && fence_in_the_way(x, y, tileElement->base_height, tileElement->clearance_height, direction)) return; if (tileElement->GetType() == TILE_ELEMENT_TYPE_ENTRANCE) { if (!entrance_has_direction(tileElement, direction - tileElement->GetDirection())) { return; } } if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK) { auto ride = get_ride(tileElement->AsTrack()->GetRideIndex()); if (ride == nullptr || !ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_FLAT_RIDE)) { return; } const uint8_t trackType = tileElement->AsTrack()->GetTrackType(); const uint8_t trackSequence = tileElement->AsTrack()->GetSequenceIndex(); if (!(FlatRideTrackSequenceProperties[trackType][trackSequence] & TRACK_SEQUENCE_FLAG_CONNECTS_TO_PATH)) { return; } uint16_t dx = (direction - tileElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK; if (!(FlatRideTrackSequenceProperties[trackType][trackSequence] & (1 << dx))) { return; } } int32_t z = tileElement->base_height; if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH) { if (tileElement->AsPath()->IsSloped()) { if ((tileElement->AsPath()->GetSlopeDirection() - direction) & 1) { return; } if (tileElement->AsPath()->GetSlopeDirection() == direction) { z += 2; } } } loc_6A6D7E(x, y, z, direction, tileElement, flags, query, neighbourList); } /** * * rct2: 0x006A6C66 */ void footpath_connect_edges(int32_t x, int32_t y, TileElement* tileElement, int32_t flags) { rct_neighbour_list neighbourList; rct_neighbour neighbour; footpath_update_queue_chains(); neighbour_list_init(&neighbourList); footpath_update_queue_entrance_banner(x, y, tileElement); for (Direction direction : ALL_DIRECTIONS) { loc_6A6C85(x, y, direction, tileElement, flags, true, &neighbourList); } neighbour_list_sort(&neighbourList); if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH && tileElement->AsPath()->IsQueue()) { ride_id_t rideIndex = RIDE_ID_NULL; uint8_t entranceIndex = 255; for (size_t i = 0; i < neighbourList.count; i++) { if (neighbourList.items[i].ride_index != RIDE_ID_NULL) { if (rideIndex == RIDE_ID_NULL) { rideIndex = neighbourList.items[i].ride_index; entranceIndex = neighbourList.items[i].entrance_index; } else if (rideIndex != neighbourList.items[i].ride_index) { neighbour_list_remove(&neighbourList, i); } else if ( rideIndex == neighbourList.items[i].ride_index && entranceIndex != neighbourList.items[i].entrance_index && neighbourList.items[i].entrance_index != 255) { neighbour_list_remove(&neighbourList, i); } } } neighbourList.count = std::min(neighbourList.count, 2); } while (neighbour_list_pop(&neighbourList, &neighbour)) { loc_6A6C85(x, y, neighbour.direction, tileElement, flags, false, nullptr); } if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH) { footpath_connect_corners(x, y, tileElement); } } /** * * rct2: 0x006A742F */ void footpath_chain_ride_queue( ride_id_t rideIndex, int32_t entranceIndex, int32_t x, int32_t y, TileElement* tileElement, int32_t direction) { TileElement *lastPathElement, *lastQueuePathElement; int32_t lastPathX = x, lastPathY = y, lastPathDirection = direction; lastPathElement = nullptr; lastQueuePathElement = nullptr; int32_t z = tileElement->base_height; for (;;) { if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH) { lastPathElement = tileElement; lastPathX = x; lastPathY = y; lastPathDirection = direction; if (tileElement->AsPath()->IsSloped()) { if (tileElement->AsPath()->GetSlopeDirection() == direction) { z += 2; } } } x += CoordsDirectionDelta[direction].x; y += CoordsDirectionDelta[direction].y; tileElement = map_get_first_element_at(x >> 5, y >> 5); if (tileElement != nullptr) { do { if (lastQueuePathElement == tileElement) continue; if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH) continue; if (tileElement->base_height == z) { if (tileElement->AsPath()->IsSloped()) { if (tileElement->AsPath()->GetSlopeDirection() != direction) break; } goto foundNextPath; } if (tileElement->base_height == z - 2) { if (!tileElement->AsPath()->IsSloped()) break; if (direction_reverse(tileElement->AsPath()->GetSlopeDirection()) != direction) break; z -= 2; goto foundNextPath; } } while (!(tileElement++)->IsLastForTile()); } break; foundNextPath: 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 << direction_reverse(direction); if (!(edges & requiredEdgeMask)) { break; } } tileElement->AsPath()->SetHasQueueBanner(false); tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() | (1 << direction_reverse(direction))); tileElement->AsPath()->SetRideIndex(rideIndex); tileElement->AsPath()->SetStationIndex(entranceIndex); map_invalidate_element(x, y, 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 = direction_reverse(direction); if (tileElement->AsPath()->GetEdges() & (1 << direction)) continue; } break; } if (rideIndex != RIDE_ID_NULL && lastPathElement != nullptr) { if (lastPathElement->AsPath()->IsQueue()) { lastPathElement->AsPath()->SetHasQueueBanner(true); lastPathElement->AsPath()->SetQueueBannerDirection(lastPathDirection); // set the ride sign direction map_animation_create(MAP_ANIMATION_TYPE_QUEUE_BANNER, lastPathX, lastPathY, lastPathElement->base_height); } } } void footpath_queue_chain_reset() { _footpathQueueChainNext = _footpathQueueChain; } /** * * rct2: 0x006A76E9 */ void footpath_queue_chain_push(ride_id_t rideIndex) { if (rideIndex != RIDE_ID_NULL) { uint8_t* lastSlot = _footpathQueueChain + std::size(_footpathQueueChain) - 1; if (_footpathQueueChainNext <= lastSlot) { *_footpathQueueChainNext++ = rideIndex; } } } /** * * rct2: 0x006A759F */ void footpath_update_queue_chains() { for (uint8_t* queueChainPtr = _footpathQueueChain; queueChainPtr < _footpathQueueChainNext; queueChainPtr++) { ride_id_t rideIndex = *queueChainPtr; auto ride = get_ride(rideIndex); if (ride == nullptr) continue; for (int32_t i = 0; i < MAX_STATIONS; i++) { TileCoordsXYZD location = ride_get_entrance_location(ride, i); if (location.isNull()) continue; TileElement* tileElement = map_get_first_element_at(location.x, location.y); if (tileElement != nullptr) { do { if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE) continue; if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_RIDE_ENTRANCE) continue; if (tileElement->AsEntrance()->GetRideIndex() != rideIndex) continue; Direction direction = direction_reverse(tileElement->GetDirection()); footpath_chain_ride_queue(rideIndex, i, location.x << 5, location.y << 5, tileElement, direction); } while (!(tileElement++)->IsLastForTile()); } } } } /** * * rct2: 0x0069ADBD */ static void footpath_fix_ownership(int32_t x, int32_t y) { const auto* surfaceElement = map_get_surface_element_at({ x, y }); 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 (check_max_allowable_land_rights_for_tile(x >> 5, y >> 5, surfaceElement->base_height) == OWNERSHIP_UNOWNED) { ownership = OWNERSHIP_UNOWNED; } // If the tile is safe to own construction rights of, do not erase contruction 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({ x, y }, LandSetRightSetting::SetOwnershipWithChecks, ownership); landSetRightsAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND); GameActions::Execute(&landSetRightsAction); } static bool get_next_direction(int32_t edges, int32_t* direction) { int32_t index = bitscanforward(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 footpath_is_connected_to_map_edge_recurse( int32_t x, int32_t y, int32_t z, int32_t direction, int32_t flags, int32_t level, int32_t distanceFromJunction, int32_t junctionTolerance) { TileElement* tileElement; int32_t edges, slopeDirection; x += CoordsDirectionDelta[direction].x; y += CoordsDirectionDelta[direction].y; if (++level > 250) return FOOTPATH_SEARCH_TOO_COMPLEX; // Check if we are at edge of map if (x < 32 || y < 32) return FOOTPATH_SEARCH_SUCCESS; if (x >= gMapSizeUnits || y >= gMapSizeUnits) return FOOTPATH_SEARCH_SUCCESS; tileElement = map_get_first_element_at(x >> 5, y >> 5); if (tileElement == nullptr) return level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : FOOTPATH_SEARCH_INCOMPLETE; do { if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH) continue; if (tileElement->AsPath()->IsSloped() && (slopeDirection = tileElement->AsPath()->GetSlopeDirection()) != direction) { if (direction_reverse(slopeDirection) != direction) continue; if (tileElement->base_height + 2 != z) continue; } else if (tileElement->base_height != z) { continue; } if (!(flags & (1 << 0))) { if (tileElement->AsPath()->IsQueue()) { continue; } } if (flags & (1 << 5)) { footpath_fix_ownership(x, y); } edges = tileElement->AsPath()->GetEdges(); direction = direction_reverse(direction); if (!(flags & (1 << 7))) { if (tileElement[1].GetType() == TILE_ELEMENT_TYPE_BANNER) { for (int32_t i = 1; i < 4; i++) { if ((&tileElement[i - 1])->IsLastForTile()) break; if (tileElement[i].GetType() != TILE_ELEMENT_TYPE_BANNER) break; edges &= tileElement[i].AsBanner()->GetAllowedEdges(); } } if (tileElement[2].GetType() == TILE_ELEMENT_TYPE_BANNER && tileElement[1].GetType() != TILE_ELEMENT_TYPE_PATH) { for (int32_t i = 1; i < 6; i++) { if ((&tileElement[i - 1])->IsLastForTile()) break; if (tileElement[i].GetType() != TILE_ELEMENT_TYPE_BANNER) break; edges &= tileElement[i].AsBanner()->GetAllowedEdges(); } } } goto searchFromFootpath; } while (!(tileElement++)->IsLastForTile()); return level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : FOOTPATH_SEARCH_INCOMPLETE; searchFromFootpath: // Exclude direction we came from z = tileElement->base_height; edges &= ~(1 << direction); // Find next direction to go if (!get_next_direction(edges, &direction)) { return FOOTPATH_SEARCH_INCOMPLETE; } edges &= ~(1 << direction); if (edges == 0) { // Only possible direction to go if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == direction) { z += 2; } return footpath_is_connected_to_map_edge_recurse( x, y, z, direction, flags, level, distanceFromJunction + 1, junctionTolerance); } else { // We have reached a junction if (distanceFromJunction != 0) { junctionTolerance--; } junctionTolerance--; if (junctionTolerance < 0) { return FOOTPATH_SEARCH_TOO_COMPLEX; } do { edges &= ~(1 << direction); if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == direction) { z += 2; } int32_t result = footpath_is_connected_to_map_edge_recurse(x, y, z, direction, flags, level, 0, junctionTolerance); if (result == FOOTPATH_SEARCH_SUCCESS) { return result; } } while (get_next_direction(edges, &direction)); return FOOTPATH_SEARCH_INCOMPLETE; } } int32_t footpath_is_connected_to_map_edge(int32_t x, int32_t y, int32_t z, int32_t direction, int32_t flags) { flags |= (1 << 0); return footpath_is_connected_to_map_edge_recurse(x, y, z, direction, flags, 0, 0, 16); } bool PathElement::IsSloped() const { return (entryIndex & FOOTPATH_PROPERTIES_FLAG_IS_SLOPED) != 0; } void PathElement::SetSloped(bool isSloped) { entryIndex &= ~FOOTPATH_PROPERTIES_FLAG_IS_SLOPED; if (isSloped) entryIndex |= FOOTPATH_PROPERTIES_FLAG_IS_SLOPED; } Direction PathElement::GetSlopeDirection() const { return static_cast(entryIndex & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK); } void PathElement::SetSlopeDirection(Direction newSlope) { entryIndex &= ~FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK; entryIndex |= static_cast(newSlope) & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK; } 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 (entryIndex & FOOTPATH_PROPERTIES_FLAG_HAS_QUEUE_BANNER) != 0; } void PathElement::SetHasQueueBanner(bool hasQueueBanner) { entryIndex &= ~FOOTPATH_PROPERTIES_FLAG_HAS_QUEUE_BANNER; if (hasQueueBanner) entryIndex |= FOOTPATH_PROPERTIES_FLAG_HAS_QUEUE_BANNER; } bool PathElement::IsBroken() const { return (flags & TILE_ELEMENT_FLAG_BROKEN) != 0; } void PathElement::SetIsBroken(bool isBroken) { if (isBroken) { flags |= TILE_ELEMENT_FLAG_BROKEN; } else { flags &= ~TILE_ELEMENT_FLAG_BROKEN; } } bool PathElement::IsBlockedByVehicle() const { return (flags & TILE_ELEMENT_FLAG_BLOCKED_BY_VEHICLE) != 0; } void PathElement::SetIsBlockedByVehicle(bool isBlocked) { if (isBlocked) { flags |= TILE_ELEMENT_FLAG_BLOCKED_BY_VEHICLE; } else { flags &= ~TILE_ELEMENT_FLAG_BLOCKED_BY_VEHICLE; } } uint8_t PathElement::GetStationIndex() const { return (additions & FOOTPATH_PROPERTIES_ADDITIONS_STATION_INDEX_MASK) >> 4; } void PathElement::SetStationIndex(uint8_t newStationIndex) { additions &= ~FOOTPATH_PROPERTIES_ADDITIONS_STATION_INDEX_MASK; additions |= ((newStationIndex << 4) & FOOTPATH_PROPERTIES_ADDITIONS_STATION_INDEX_MASK); } 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 & FOOTPATH_PROPERTIES_ADDITIONS_TYPE_MASK) != 0; } uint8_t PathElement::GetAddition() const { return additions & FOOTPATH_PROPERTIES_ADDITIONS_TYPE_MASK; } uint8_t PathElement::GetAdditionEntryIndex() const { return GetAddition() - 1; } rct_scenery_entry* PathElement::GetAdditionEntry() const { return get_footpath_item_entry(GetAdditionEntryIndex()); } void PathElement::SetAddition(uint8_t newAddition) { additions &= ~FOOTPATH_PROPERTIES_ADDITIONS_TYPE_MASK; additions |= newAddition; } bool PathElement::AdditionIsGhost() const { return (additions & FOOTPATH_ADDITION_FLAG_IS_GHOST) != 0; } void PathElement::SetAdditionIsGhost(bool isGhost) { additions &= ~FOOTPATH_ADDITION_FLAG_IS_GHOST; if (isGhost) additions |= FOOTPATH_ADDITION_FLAG_IS_GHOST; } uint8_t PathElement::GetPathEntryIndex() const { return (entryIndex & FOOTPATH_PROPERTIES_TYPE_MASK) >> 4; } uint8_t PathElement::GetRailingEntryIndex() const { return GetPathEntryIndex(); } PathSurfaceEntry* PathElement::GetPathEntry() const { if (!IsQueue()) return get_path_surface_entry(GetPathEntryIndex()); else return get_path_surface_entry(GetPathEntryIndex() + MAX_PATH_OBJECTS); } PathRailingsEntry* PathElement::GetRailingEntry() const { return get_path_railings_entry(GetRailingEntryIndex()); } void PathElement::SetPathEntryIndex(uint8_t newEntryIndex) { entryIndex &= ~FOOTPATH_PROPERTIES_TYPE_MASK; entryIndex |= (newEntryIndex << 4); } void PathElement::SetRailingEntryIndex(uint8_t newEntryIndex) { log_verbose("Setting railing entry index to %d", newEntryIndex); } 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() { return (GetRailingEntry()->flags & RAILING_ENTRY_FLAG_DRAW_PATH_OVER_SUPPORTS); } void PathElement::SetShouldDrawPathOverSupports(bool on) { log_verbose("Setting 'draw path over supports' to %d", (size_t)on); } /** * * rct2: 0x006A8B12 * clears the wide footpath flag for all footpaths * at location */ static void footpath_clear_wide(int32_t x, int32_t y) { TileElement* tileElement = map_get_first_element_at(x / 32, y / 32); if (tileElement == nullptr) return; do { if (tileElement->GetType() != TILE_ELEMENT_TYPE_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* footpath_can_be_wide(int32_t x, int32_t y, uint8_t height) { TileElement* tileElement = map_get_first_element_at(x / 32, y / 32); if (tileElement == nullptr) return nullptr; do { if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH) continue; if (height != tileElement->base_height) continue; if (tileElement->AsPath()->IsQueue()) continue; if (tileElement->AsPath()->IsSloped()) continue; return tileElement; } while (!(tileElement++)->IsLastForTile()); return nullptr; } /** * * rct2: 0x006A87BB */ void footpath_update_path_wide_flags(int32_t x, int32_t y) { if (x < 0x20) return; if (y < 0x20) return; if (x > 0x1FDF) return; if (y > 0x1FDF) return; footpath_clear_wide(x, y); /* 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; // footpath_clear_wide(x, y); // y += 0x20; // footpath_clear_wide(x, y); // x -= 0x20; // footpath_clear_wide(x, y); // y -= 0x20; TileElement* tileElement = map_get_first_element_at(x / 32, y / 32); if (tileElement == nullptr) return; do { if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH) continue; if (tileElement->AsPath()->IsQueue()) continue; if (tileElement->AsPath()->IsSloped()) continue; if (tileElement->AsPath()->GetEdges() == 0) continue; uint8_t height = tileElement->base_height; // pathList is a list of elements, set by sub_6A8ACF adjacent to x,y // Spanned from 0x00F3EFA8 to 0x00F3EFC7 (8 elements) in the original TileElement* pathList[8]; x -= 0x20; y -= 0x20; pathList[0] = footpath_can_be_wide(x, y, height); y += 0x20; pathList[1] = footpath_can_be_wide(x, y, height); y += 0x20; pathList[2] = footpath_can_be_wide(x, y, height); x += 0x20; pathList[3] = footpath_can_be_wide(x, y, height); x += 0x20; pathList[4] = footpath_can_be_wide(x, y, height); y -= 0x20; pathList[5] = footpath_can_be_wide(x, y, height); y -= 0x20; pathList[6] = footpath_can_be_wide(x, y, height); x -= 0x20; pathList[7] = footpath_can_be_wide(x, y, height); y += 0x20; uint8_t pathConnections = 0; if (tileElement->AsPath()->GetEdges() & EDGE_NW) { pathConnections |= FOOTPATH_CONNECTION_NW; if (pathList[7] != nullptr && pathList[7]->AsPath()->IsWide()) { pathConnections &= ~FOOTPATH_CONNECTION_NW; } } if (tileElement->AsPath()->GetEdges() & EDGE_NE) { pathConnections |= FOOTPATH_CONNECTION_NE; if (pathList[1] != nullptr && pathList[1]->AsPath()->IsWide()) { pathConnections &= ~FOOTPATH_CONNECTION_NE; } } if (tileElement->AsPath()->GetEdges() & EDGE_SE) { pathConnections |= FOOTPATH_CONNECTION_SE; /* In the following: * footpath_element_is_wide(pathList[3]) * is always false due to the tile update order * in combination with reset tiles. * Commented out since it will never occur. */ // if (pathList[3] != nullptr) { // if (footpath_element_is_wide(pathList[3])) { // pathConnections &= ~FOOTPATH_CONNECTION_SE; // } //} } if (tileElement->AsPath()->GetEdges() & EDGE_SW) { pathConnections |= FOOTPATH_CONNECTION_SW; /* In the following: * footpath_element_is_wide(pathList[5]) * is always false due to the tile update order * in combination with reset tiles. * Commented out since it will never occur. */ // if (pathList[5] != nullptr) { // if (footpath_element_is_wide(pathList[5])) { // pathConnections &= ~FOOTPATH_CONNECTION_SW; // } //} } if ((pathConnections & FOOTPATH_CONNECTION_NW) && pathList[7] != nullptr && !pathList[7]->AsPath()->IsWide()) { constexpr uint8_t edgeMask1 = EDGE_SE | EDGE_SW; if ((pathConnections & FOOTPATH_CONNECTION_NE) && pathList[0] != nullptr && !pathList[0]->AsPath()->IsWide() && (pathList[0]->AsPath()->GetEdges() & edgeMask1) == edgeMask1 && pathList[1] != nullptr && !pathList[1]->AsPath()->IsWide()) { pathConnections |= FOOTPATH_CONNECTION_S; } /* In the following: * footpath_element_is_wide(pathList[5]) * 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; if ((pathConnections & FOOTPATH_CONNECTION_SW) && pathList[6] != nullptr && !(pathList[6])->AsPath()->IsWide() && (pathList[6]->AsPath()->GetEdges() & edgeMask2) == edgeMask2 && pathList[5] != nullptr) { pathConnections |= FOOTPATH_CONNECTION_E; } } /* In the following: * footpath_element_is_wide(pathList[2]) * footpath_element_is_wide(pathList[3]) * are always false due to the tile update order * in combination with reset tiles. * Short circuit the logic appropriately. */ if ((pathConnections & FOOTPATH_CONNECTION_SE) && pathList[3] != nullptr) { constexpr uint8_t edgeMask1 = EDGE_SW | EDGE_NW; if ((pathConnections & FOOTPATH_CONNECTION_NE) && (pathList[2] != nullptr) && (pathList[2]->AsPath()->GetEdges() & edgeMask1) == edgeMask1 && pathList[1] != nullptr && !pathList[1]->AsPath()->IsWide()) { pathConnections |= FOOTPATH_CONNECTION_W; } /* In the following: * footpath_element_is_wide(pathList[4]) * footpath_element_is_wide(pathList[5]) * 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; if ((pathConnections & FOOTPATH_CONNECTION_SW) && pathList[4] != nullptr && (pathList[4]->AsPath()->GetEdges() & edgeMask2) == edgeMask2 && pathList[5] != 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 footpath_is_blocked_by_vehicle(const TileCoordsXYZ& position) { auto pathElement = map_get_path_element_at(position); return pathElement != nullptr && pathElement->IsBlockedByVehicle(); } /** * * rct2: 0x006A7642 */ void footpath_update_queue_entrance_banner(int32_t x, int32_t y, TileElement* tileElement) { int32_t elementType = tileElement->GetType(); switch (elementType) { case TILE_ELEMENT_TYPE_PATH: if (tileElement->AsPath()->IsQueue()) { footpath_queue_chain_push(tileElement->AsPath()->GetRideIndex()); for (int32_t direction = 0; direction < 4; direction++) { if (tileElement->AsPath()->GetEdges() & (1 << direction)) { footpath_chain_ride_queue(255, 0, x, y, tileElement, direction); } } tileElement->AsPath()->SetRideIndex(RIDE_ID_NULL); } break; case TILE_ELEMENT_TYPE_ENTRANCE: if (tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_RIDE_ENTRANCE) { footpath_queue_chain_push(tileElement->AsEntrance()->GetRideIndex()); footpath_chain_ride_queue(255, 0, x, y, tileElement, direction_reverse(tileElement->GetDirection())); } break; } } /** * * rct2: 0x006A6B7F */ static void footpath_remove_edges_towards_here( int32_t x, int32_t y, int32_t z, int32_t direction, TileElement* tileElement, bool isQueue) { if (tileElement->AsPath()->IsQueue()) { footpath_queue_chain_push(tileElement->AsPath()->GetRideIndex()); } int32_t d = direction_reverse(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)); map_invalidate_tile(x, y, tileElement->base_height * 8, tileElement->clearance_height * 8); if (isQueue) footpath_disconnect_queue_from_path(x, y, tileElement, -1); direction = (direction + 1) & 3; x += CoordsDirectionDelta[direction].x; y += CoordsDirectionDelta[direction].y; tileElement = map_get_first_element_at(x >> 5, y >> 5); if (tileElement == nullptr) return; do { if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH) continue; if (tileElement->base_height != z) continue; if (tileElement->AsPath()->IsSloped()) break; cd = ((direction + 1) & 3); tileElement->AsPath()->SetCorners(tileElement->AsPath()->GetCorners() & ~(1 << cd)); map_invalidate_tile(x, y, tileElement->base_height * 8, tileElement->clearance_height * 8); break; } while (!(tileElement++)->IsLastForTile()); } /** * * rct2: 0x006A6B14 */ static void footpath_remove_edges_towards(int32_t x, int32_t y, int32_t z0, int32_t z1, int32_t direction, bool isQueue) { if (!map_is_location_valid({ x, y })) { return; } TileElement* tileElement = map_get_first_element_at(x >> 5, y >> 5); if (tileElement == nullptr) return; do { if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH) continue; if (z1 == tileElement->base_height) { if (tileElement->AsPath()->IsSloped()) { uint8_t slope = tileElement->AsPath()->GetSlopeDirection(); if (slope != direction) break; } footpath_remove_edges_towards_here(x, y, z1, direction, tileElement, isQueue); break; } if (z0 == tileElement->base_height) { if (!tileElement->AsPath()->IsSloped()) break; uint8_t slope = direction_reverse(tileElement->AsPath()->GetSlopeDirection()); if (slope != direction) break; footpath_remove_edges_towards_here(x, y, z1, 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 tile_element_wants_path_connection_towards(TileCoordsXYZD coords, const TileElement* const elementToBeRemoved) { TileElement* tileElement = map_get_first_element_at(coords.x, coords.y); if (tileElement == nullptr) return false; do { // Don't check the element that gets removed if (tileElement == elementToBeRemoved) continue; switch (tileElement->GetType()) { case TILE_ELEMENT_TYPE_PATH: if (tileElement->base_height == coords.z) { if (!tileElement->AsPath()->IsSloped()) // The footpath is flat, it can be connected to from any direction return true; else if (tileElement->AsPath()->GetSlopeDirection() == direction_reverse(coords.direction)) // The footpath is sloped and its lowest point matches the edge connection return true; } else if (tileElement->base_height + 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 TILE_ELEMENT_TYPE_TRACK: if (tileElement->base_height == coords.z) { auto ride = get_ride(tileElement->AsTrack()->GetRideIndex()); if (ride == nullptr) continue; if (!ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_FLAT_RIDE)) break; const uint8_t trackType = tileElement->AsTrack()->GetTrackType(); const uint8_t trackSequence = tileElement->AsTrack()->GetSequenceIndex(); if (FlatRideTrackSequenceProperties[trackType][trackSequence] & TRACK_SEQUENCE_FLAG_CONNECTS_TO_PATH) { uint16_t dx = ((coords.direction - tileElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK); if (FlatRideTrackSequenceProperties[trackType][trackSequence] & (1 << dx)) { // Track element has the flags required for the given direction return true; } } } break; case TILE_ELEMENT_TYPE_ENTRANCE: if (tileElement->base_height == coords.z) { if (entrance_has_direction(tileElement, 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 footpath_fix_corners_around(int32_t x, int32_t y, 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() == TILE_ELEMENT_TYPE_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 = map_get_first_element_at(x + xOffset, y + yOffset); if (tileElement == nullptr) continue; do { if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH) continue; if (tileElement->AsPath()->IsSloped()) continue; if (tileElement->base_height != pathElement->base_height) 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 footpath_remove_edges_at(int32_t x, int32_t y, TileElement* tileElement) { if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK) { auto rideIndex = tileElement->AsTrack()->GetRideIndex(); auto ride = get_ride(rideIndex); if (ride == nullptr || !ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_FLAT_RIDE)) return; } footpath_update_queue_entrance_banner(x, y, tileElement); bool fixCorners = false; for (uint8_t direction = 0; direction < 4; direction++) { int32_t z1 = tileElement->base_height; if (tileElement->GetType() == TILE_ELEMENT_TYPE_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 (!tile_element_wants_path_connection_towards({ x / 32, y / 32, z1, direction }, tileElement)) { bool isQueue = tileElement->GetType() == TILE_ELEMENT_TYPE_PATH ? tileElement->AsPath()->IsQueue() : false; int32_t z0 = z1 - 2; footpath_remove_edges_towards( x + CoordsDirectionDelta[direction].x, y + CoordsDirectionDelta[direction].y, z0, z1, 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()) { footpath_fix_corners_around(x / 32, y / 32, tileElement); } if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH) tileElement->AsPath()->SetEdgesAndCorners(0); } PathSurfaceEntry* get_path_surface_entry(int32_t entryIndex) { PathSurfaceEntry* result = nullptr; auto& objMgr = OpenRCT2::GetContext()->GetObjectManager(); // TODO: Change when moving to the new save format. auto obj = objMgr.GetLoadedObject(OBJECT_TYPE_PATHS, entryIndex % MAX_PATH_OBJECTS); if (obj != nullptr) { if (entryIndex < MAX_PATH_OBJECTS) result = ((FootpathObject*)obj)->GetPathSurfaceEntry(); else result = ((FootpathObject*)obj)->GetQueueEntry(); } return result; } PathRailingsEntry* get_path_railings_entry(int32_t entryIndex) { PathRailingsEntry* result = nullptr; auto& objMgr = OpenRCT2::GetContext()->GetObjectManager(); auto obj = objMgr.GetLoadedObject(OBJECT_TYPE_PATHS, entryIndex); if (obj != nullptr) { result = ((FootpathObject*)obj)->GetPathRailingsEntry(); } return result; } ride_id_t PathElement::GetRideIndex() const { return rideIndex; } void PathElement::SetRideIndex(ride_id_t newRideIndex) { rideIndex = newRideIndex; } uint8_t PathElement::GetAdditionStatus() const { return additionStatus; } void PathElement::SetAdditionStatus(uint8_t newStatus) { additionStatus = newStatus; } uint8_t PathElement::GetEdges() const { return edges & FOOTPATH_PROPERTIES_EDGES_EDGES_MASK; } void PathElement::SetEdges(uint8_t newEdges) { edges &= ~FOOTPATH_PROPERTIES_EDGES_EDGES_MASK; edges |= (newEdges & FOOTPATH_PROPERTIES_EDGES_EDGES_MASK); } uint8_t PathElement::GetCorners() const { return edges >> 4; } void PathElement::SetCorners(uint8_t newCorners) { edges &= ~FOOTPATH_PROPERTIES_EDGES_CORNERS_MASK; edges |= (newCorners << 4); } uint8_t PathElement::GetEdgesAndCorners() const { return edges; } void PathElement::SetEdgesAndCorners(uint8_t newEdgesAndCorners) { edges = newEdgesAndCorners; }