1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include "../Cheats.h"
11 #include "../Context.h"
12 #include "../Game.h"
13 #include "../OpenRCT2.h"
14 #include "../actions/FootpathPlaceAction.h"
15 #include "../actions/FootpathRemoveAction.h"
16 #include "../actions/LandSetRightsAction.h"
17 #include "../core/Guard.hpp"
18 #include "../interface/Window_internal.h"
19 #include "../localisation/Localisation.h"
20 #include "../management/Finance.h"
21 #include "../network/network.h"
22 #include "../object/FootpathObject.h"
23 #include "../object/FootpathRailingsObject.h"
24 #include "../object/FootpathSurfaceObject.h"
25 #include "../object/ObjectList.h"
26 #include "../object/ObjectManager.h"
27 #include "../paint/VirtualFloor.h"
28 #include "../ride/RideData.h"
29 #include "../ride/Station.h"
30 #include "../ride/Track.h"
31 #include "../ride/TrackData.h"
32 #include "../util/Util.h"
33 #include "EntityList.h"
34 #include "Map.h"
35 #include "MapAnimation.h"
36 #include "Park.h"
37 #include "Sprite.h"
38 #include "Surface.h"
39 
40 #include <algorithm>
41 #include <iterator>
42 
43 using namespace OpenRCT2::TrackMetaData;
44 void footpath_update_queue_entrance_banner(const CoordsXY& footpathPos, TileElement* tileElement);
45 
46 FootpathSelection gFootpathSelection;
47 ProvisionalFootpath gProvisionalFootpath;
48 uint16_t gFootpathSelectedId;
49 CoordsXYZ gFootpathConstructFromPosition;
50 uint8_t gFootpathConstructSlope;
51 uint8_t gFootpathGroundFlags;
52 
53 static ride_id_t* _footpathQueueChainNext;
54 static ride_id_t _footpathQueueChain[64];
55 
56 // This is the coordinates that a user of the bin should move to
57 // rct2: 0x00992A4C
58 const CoordsXY BinUseOffsets[4] = {
59     { 11, 16 },
60     { 16, 21 },
61     { 21, 16 },
62     { 16, 11 },
63 };
64 
65 // These are the offsets for bench positions on footpaths, 2 for each edge
66 // rct2: 0x00981F2C, 0x00981F2E
67 const CoordsXY BenchUseOffsets[8] = {
68     { 7, 12 }, { 12, 25 }, { 25, 20 }, { 20, 7 }, { 7, 20 }, { 20, 25 }, { 25, 12 }, { 12, 7 },
69 };
70 
71 /** rct2: 0x00981D6C, 0x00981D6E */
72 const CoordsXY DirectionOffsets[4] = {
73     { -1, 0 },
74     { 0, 1 },
75     { 1, 0 },
76     { 0, -1 },
77 };
78 
79 // rct2: 0x0097B974
80 static constexpr const uint16_t EntranceDirections[] = {
81     (4),     0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_RIDE_ENTRANCE,
82     (4),     0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_RIDE_EXIT,
83     (4 | 1), 0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_PARK_ENTRANCE
84 };
85 
86 /** rct2: 0x0098D7F0 */
87 static constexpr const uint8_t connected_path_count[] = {
88     0, // 0b0000
89     1, // 0b0001
90     1, // 0b0010
91     2, // 0b0011
92     1, // 0b0100
93     2, // 0b0101
94     2, // 0b0110
95     3, // 0b0111
96     1, // 0b1000
97     2, // 0b1001
98     2, // 0b1010
99     3, // 0b1011
100     2, // 0b1100
101     3, // 0b1101
102     3, // 0b1110
103     4, // 0b1111
104 };
105 
GetDirections() const106 int32_t EntranceElement::GetDirections() const
107 {
108     return EntranceDirections[(GetEntranceType() * 8) + GetSequenceIndex()];
109 }
110 
entrance_has_direction(const EntranceElement & entranceElement,int32_t direction)111 static bool entrance_has_direction(const EntranceElement& entranceElement, int32_t direction)
112 {
113     return entranceElement.GetDirections() & (1 << (direction & 3));
114 }
115 
map_get_footpath_element(const CoordsXYZ & coords)116 TileElement* map_get_footpath_element(const CoordsXYZ& coords)
117 {
118     TileElement* tileElement = map_get_first_element_at(coords);
119     do
120     {
121         if (tileElement == nullptr)
122             break;
123         if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH && tileElement->GetBaseZ() == coords.z)
124             return tileElement;
125     } while (!(tileElement++)->IsLastForTile());
126 
127     return nullptr;
128 }
129 
footpath_remove(const CoordsXYZ & footpathLoc,int32_t flags)130 money32 footpath_remove(const CoordsXYZ& footpathLoc, int32_t flags)
131 {
132     auto action = FootpathRemoveAction(footpathLoc);
133     action.SetFlags(flags);
134 
135     if (flags & GAME_COMMAND_FLAG_APPLY)
136     {
137         auto res = GameActions::Execute(&action);
138         return res->Cost;
139     }
140     auto res = GameActions::Query(&action);
141     return res->Cost;
142 }
143 
144 /**
145  *
146  *  rct2: 0x006A76FF
147  */
footpath_provisional_set(ObjectEntryIndex type,ObjectEntryIndex railingsType,const CoordsXYZ & footpathLoc,int32_t slope,PathConstructFlags constructFlags)148 money32 footpath_provisional_set(
149     ObjectEntryIndex type, ObjectEntryIndex railingsType, const CoordsXYZ& footpathLoc, int32_t slope,
150     PathConstructFlags constructFlags)
151 {
152     money32 cost;
153 
154     footpath_provisional_remove();
155 
156     auto footpathPlaceAction = FootpathPlaceAction(footpathLoc, slope, type, railingsType, INVALID_DIRECTION, constructFlags);
157     footpathPlaceAction.SetFlags(GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED);
158     auto res = GameActions::Execute(&footpathPlaceAction);
159     cost = res->Error == GameActions::Status::Ok ? res->Cost : MONEY32_UNDEFINED;
160     if (res->Error == GameActions::Status::Ok)
161     {
162         gProvisionalFootpath.SurfaceIndex = type;
163         gProvisionalFootpath.RailingsIndex = railingsType;
164         gProvisionalFootpath.Position = footpathLoc;
165         gProvisionalFootpath.Slope = slope;
166         gProvisionalFootpath.ConstructFlags = constructFlags;
167         gProvisionalFootpath.Flags |= PROVISIONAL_PATH_FLAG_1;
168 
169         if (gFootpathGroundFlags & ELEMENT_IS_UNDERGROUND)
170         {
171             viewport_set_visibility(1);
172         }
173         else
174         {
175             viewport_set_visibility(3);
176         }
177     }
178 
179     // Invalidate previous footpath piece.
180     virtual_floor_invalidate();
181 
182     if (!scenery_tool_is_active())
183     {
184         if (res->Error != GameActions::Status::Ok)
185         {
186             // If we can't build this, don't show a virtual floor.
187             virtual_floor_set_height(0);
188         }
189         else if (
190             gFootpathConstructSlope == TILE_ELEMENT_SLOPE_FLAT
191             || gProvisionalFootpath.Position.z < gFootpathConstructFromPosition.z)
192         {
193             // Going either straight on, or down.
194             virtual_floor_set_height(gProvisionalFootpath.Position.z);
195         }
196         else
197         {
198             // Going up in the world!
199             virtual_floor_set_height(gProvisionalFootpath.Position.z + LAND_HEIGHT_STEP);
200         }
201     }
202 
203     return cost;
204 }
205 
206 /**
207  *
208  *  rct2: 0x006A77FF
209  */
footpath_provisional_remove()210 void footpath_provisional_remove()
211 {
212     if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1)
213     {
214         gProvisionalFootpath.Flags &= ~PROVISIONAL_PATH_FLAG_1;
215 
216         footpath_remove(
217             gProvisionalFootpath.Position,
218             GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND
219                 | GAME_COMMAND_FLAG_GHOST);
220     }
221 }
222 
223 /**
224  *
225  *  rct2: 0x006A7831
226  */
footpath_provisional_update()227 void footpath_provisional_update()
228 {
229     if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_SHOW_ARROW)
230     {
231         gProvisionalFootpath.Flags &= ~PROVISIONAL_PATH_FLAG_SHOW_ARROW;
232 
233         gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
234         map_invalidate_tile_full(gFootpathConstructFromPosition);
235     }
236     footpath_provisional_remove();
237 }
238 
239 /**
240  * Determines the location of the footpath at which we point with the cursor. If no footpath is underneath the cursor,
241  * then return the location of the ground tile. Besides the location it also computes the direction of the yellow arrow
242  * when we are going to build a footpath bridge/tunnel.
243  *  rct2: 0x00689726
244  *  In:
245  *      screenX: eax
246  *      screenY: ebx
247  *  Out:
248  *      x: ax
249  *      y: bx
250  *      direction: ecx
251  *      tileElement: edx
252  */
footpath_get_coordinates_from_pos(const ScreenCoordsXY & screenCoords,int32_t * direction,TileElement ** tileElement)253 CoordsXY footpath_get_coordinates_from_pos(const ScreenCoordsXY& screenCoords, int32_t* direction, TileElement** tileElement)
254 {
255     rct_window* window = window_find_from_point(screenCoords);
256     if (window == nullptr || window->viewport == nullptr)
257     {
258         CoordsXY position{};
259         position.SetNull();
260         return position;
261     }
262     auto viewport = window->viewport;
263     auto info = get_map_coordinates_from_pos_window(window, screenCoords, EnumsToFlags(ViewportInteractionItem::Footpath));
264     if (info.SpriteType != ViewportInteractionItem::Footpath
265         || !(viewport->flags & (VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_HIDE_VERTICAL)))
266     {
267         info = get_map_coordinates_from_pos_window(
268             window, screenCoords, EnumsToFlags(ViewportInteractionItem::Terrain, ViewportInteractionItem::Footpath));
269         if (info.SpriteType == ViewportInteractionItem::None)
270         {
271             auto position = info.Loc;
272             position.SetNull();
273             return position;
274         }
275     }
276 
277     auto minPosition = info.Loc;
278     auto maxPosition = info.Loc + CoordsXY{ 31, 31 };
279     auto myTileElement = info.Element;
280     auto position = info.Loc.ToTileCentre();
281     auto z = 0;
282     if (info.SpriteType == ViewportInteractionItem::Footpath)
283     {
284         z = myTileElement->GetBaseZ();
285         if (myTileElement->AsPath()->IsSloped())
286         {
287             z += 8;
288         }
289     }
290 
291     auto start_vp_pos = viewport->ScreenToViewportCoord(screenCoords);
292 
293     for (int32_t i = 0; i < 5; i++)
294     {
295         if (info.SpriteType != ViewportInteractionItem::Footpath)
296         {
297             z = tile_element_height(position);
298         }
299         position = viewport_coord_to_map_coord(start_vp_pos, z);
300         position.x = std::clamp(position.x, minPosition.x, maxPosition.x);
301         position.y = std::clamp(position.y, minPosition.y, maxPosition.y);
302     }
303 
304     // Determine to which edge the cursor is closest
305     uint32_t myDirection;
306     int32_t mod_x = position.x & 0x1F, mod_y = position.y & 0x1F;
307     if (mod_x < mod_y)
308     {
309         if (mod_x + mod_y < 32)
310         {
311             myDirection = 0;
312         }
313         else
314         {
315             myDirection = 1;
316         }
317     }
318     else
319     {
320         if (mod_x + mod_y < 32)
321         {
322             myDirection = 3;
323         }
324         else
325         {
326             myDirection = 2;
327         }
328     }
329 
330     position = position.ToTileStart();
331 
332     if (direction != nullptr)
333         *direction = myDirection;
334     if (tileElement != nullptr)
335         *tileElement = myTileElement;
336 
337     return position;
338 }
339 
340 /**
341  *
342  *  rct2: 0x0068A0C9
343  * screenX: eax
344  * screenY: ebx
345  * x: ax
346  * y: bx
347  * direction: cl
348  * tileElement: edx
349  */
footpath_bridge_get_info_from_pos(const ScreenCoordsXY & screenCoords,int32_t * direction,TileElement ** tileElement)350 CoordsXY footpath_bridge_get_info_from_pos(const ScreenCoordsXY& screenCoords, int32_t* direction, TileElement** tileElement)
351 {
352     // First check if we point at an entrance or exit. In that case, we would want the path coming from the entrance/exit.
353     rct_window* window = window_find_from_point(screenCoords);
354     if (window == nullptr || window->viewport == nullptr)
355     {
356         CoordsXY ret{};
357         ret.SetNull();
358         return ret;
359     }
360     auto viewport = window->viewport;
361     auto info = get_map_coordinates_from_pos_window(window, screenCoords, EnumsToFlags(ViewportInteractionItem::Ride));
362     *tileElement = info.Element;
363     if (info.SpriteType == ViewportInteractionItem::Ride
364         && viewport->flags & (VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_HIDE_VERTICAL)
365         && (*tileElement)->GetType() == TILE_ELEMENT_TYPE_ENTRANCE)
366     {
367         int32_t directions = (*tileElement)->AsEntrance()->GetDirections();
368         if (directions & 0x0F)
369         {
370             int32_t bx = bitscanforward(directions);
371             bx += (*tileElement)->AsEntrance()->GetDirection();
372             bx &= 3;
373             if (direction != nullptr)
374                 *direction = bx;
375             return info.Loc;
376         }
377     }
378 
379     info = get_map_coordinates_from_pos_window(
380         window, screenCoords,
381         EnumsToFlags(ViewportInteractionItem::Terrain, ViewportInteractionItem::Footpath, ViewportInteractionItem::Ride));
382     if (info.SpriteType == ViewportInteractionItem::Ride && (*tileElement)->GetType() == TILE_ELEMENT_TYPE_ENTRANCE)
383     {
384         int32_t directions = (*tileElement)->AsEntrance()->GetDirections();
385         if (directions & 0x0F)
386         {
387             int32_t bx = (*tileElement)->GetDirectionWithOffset(bitscanforward(directions));
388             if (direction != nullptr)
389                 *direction = bx;
390             return info.Loc;
391         }
392     }
393 
394     // We point at something else
395     return footpath_get_coordinates_from_pos(screenCoords, direction, tileElement);
396 }
397 
398 /**
399  *
400  *  rct2: 0x00673883
401  */
footpath_remove_litter(const CoordsXYZ & footpathPos)402 void footpath_remove_litter(const CoordsXYZ& footpathPos)
403 {
404     std::vector<Litter*> removals;
405     for (auto litter : EntityTileList<Litter>(footpathPos))
406     {
407         int32_t distanceZ = abs(litter->z - footpathPos.z);
408         if (distanceZ <= 32)
409         {
410             removals.push_back(litter);
411         }
412     }
413     for (auto* litter : removals)
414     {
415         litter->Invalidate();
416         sprite_remove(litter);
417     }
418 }
419 
420 /**
421  *
422  *  rct2: 0x0069A48B
423  */
footpath_interrupt_peeps(const CoordsXYZ & footpathPos)424 void footpath_interrupt_peeps(const CoordsXYZ& footpathPos)
425 {
426     auto quad = EntityTileList<Peep>(footpathPos);
427     for (auto peep : quad)
428     {
429         if (peep->State == PeepState::Sitting || peep->State == PeepState::Watching)
430         {
431             auto location = peep->GetLocation();
432             if (location.z == footpathPos.z)
433             {
434                 auto destination = location.ToTileCentre();
435                 peep->SetState(PeepState::Walking);
436                 peep->SetDestination(destination, 5);
437                 peep->UpdateCurrentActionSpriteType();
438             }
439         }
440     }
441 }
442 
443 /**
444  * Returns true if the edge of tile x, y specified by direction is occupied by a fence
445  * between heights z0 and z1.
446  *
447  * Note that there may still be a fence on the opposing tile.
448  *
449  *  rct2: 0x006E59DC
450  */
fence_in_the_way(const CoordsXYRangedZ & fencePos,int32_t direction)451 bool fence_in_the_way(const CoordsXYRangedZ& fencePos, int32_t direction)
452 {
453     TileElement* tileElement;
454 
455     tileElement = map_get_first_element_at(fencePos);
456     if (tileElement == nullptr)
457         return false;
458     do
459     {
460         if (tileElement->GetType() != TILE_ELEMENT_TYPE_WALL)
461             continue;
462         if (tileElement->IsGhost())
463             continue;
464         if (fencePos.baseZ >= tileElement->GetClearanceZ())
465             continue;
466         if (fencePos.clearanceZ <= tileElement->GetBaseZ())
467             continue;
468         if ((tileElement->GetDirection()) != direction)
469             continue;
470 
471         return true;
472     } while (!(tileElement++)->IsLastForTile());
473     return false;
474 }
475 
footpath_connect_corners_get_neighbour(const CoordsXYZ & footpathPos,int32_t requireEdges)476 static PathElement* footpath_connect_corners_get_neighbour(const CoordsXYZ& footpathPos, int32_t requireEdges)
477 {
478     if (!map_is_location_valid(footpathPos))
479     {
480         return nullptr;
481     }
482 
483     TileElement* tileElement = map_get_first_element_at(footpathPos);
484     if (tileElement == nullptr)
485         return nullptr;
486     do
487     {
488         if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
489             continue;
490         auto pathElement = tileElement->AsPath();
491         if (pathElement->IsQueue())
492             continue;
493         if (tileElement->GetBaseZ() != footpathPos.z)
494             continue;
495         if (!(pathElement->GetEdgesAndCorners() & requireEdges))
496             continue;
497 
498         return pathElement;
499     } while (!(tileElement++)->IsLastForTile());
500 
501     return nullptr;
502 }
503 
504 /**
505  * Sets the corner edges of four path tiles.
506  * 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
507  * it reaches the initial path. In other words, checks if there are four paths together so that it can set the inner corners of
508  * each one.
509  *
510  *  rct2: 0x006A70EB
511  */
footpath_connect_corners(const CoordsXY & footpathPos,PathElement * initialTileElement)512 static void footpath_connect_corners(const CoordsXY& footpathPos, PathElement* initialTileElement)
513 {
514     using PathElementCoordsPair = std::pair<PathElement*, CoordsXY>;
515     std::array<PathElementCoordsPair, 4> tileElements;
516 
517     if (initialTileElement->IsQueue())
518         return;
519     if (initialTileElement->IsSloped())
520         return;
521 
522     tileElements[0] = { initialTileElement, footpathPos };
523     int32_t z = initialTileElement->GetBaseZ();
524     for (int32_t initialDirection = 0; initialDirection < 4; initialDirection++)
525     {
526         int32_t direction = initialDirection;
527         auto currentPos = footpathPos + CoordsDirectionDelta[direction];
528 
529         tileElements[1] = { footpath_connect_corners_get_neighbour({ currentPos, z }, (1 << direction_reverse(direction))),
530                             currentPos };
531         if (tileElements[1].first == nullptr)
532             continue;
533 
534         direction = direction_next(direction);
535         currentPos += CoordsDirectionDelta[direction];
536         tileElements[2] = { footpath_connect_corners_get_neighbour({ currentPos, z }, (1 << direction_reverse(direction))),
537                             currentPos };
538         if (tileElements[2].first == nullptr)
539             continue;
540 
541         direction = direction_next(direction);
542         currentPos += CoordsDirectionDelta[direction];
543         // First check link to previous tile
544         tileElements[3] = { footpath_connect_corners_get_neighbour({ currentPos, z }, (1 << direction_reverse(direction))),
545                             currentPos };
546         if (tileElements[3].first == nullptr)
547             continue;
548         // Second check link to initial tile
549         tileElements[3] = { footpath_connect_corners_get_neighbour({ currentPos, z }, (1 << ((direction + 1) & 3))),
550                             currentPos };
551         if (tileElements[3].first == nullptr)
552             continue;
553 
554         direction = direction_next(direction);
555         tileElements[3].first->SetCorners(tileElements[3].first->GetCorners() | (1 << (direction)));
556         map_invalidate_element(tileElements[3].second, reinterpret_cast<TileElement*>(tileElements[3].first));
557 
558         direction = direction_prev(direction);
559         tileElements[2].first->SetCorners(tileElements[2].first->GetCorners() | (1 << (direction)));
560 
561         map_invalidate_element(tileElements[2].second, reinterpret_cast<TileElement*>(tileElements[2].first));
562 
563         direction = direction_prev(direction);
564         tileElements[1].first->SetCorners(tileElements[1].first->GetCorners() | (1 << (direction)));
565 
566         map_invalidate_element(tileElements[1].second, reinterpret_cast<TileElement*>(tileElements[1].first));
567 
568         direction = initialDirection;
569         tileElements[0].first->SetCorners(tileElements[0].first->GetCorners() | (1 << (direction)));
570         map_invalidate_element(tileElements[0].second, reinterpret_cast<TileElement*>(tileElements[0].first));
571     }
572 }
573 
574 struct rct_neighbour
575 {
576     uint8_t order;
577     uint8_t direction;
578     ride_id_t ride_index;
579     uint8_t entrance_index;
580 };
581 
582 struct rct_neighbour_list
583 {
584     rct_neighbour items[8];
585     size_t count;
586 };
587 
rct_neighbour_compare(const void * a,const void * b)588 static int32_t rct_neighbour_compare(const void* a, const void* b)
589 {
590     uint8_t va = (static_cast<const rct_neighbour*>(a))->order;
591     uint8_t vb = (static_cast<const rct_neighbour*>(b))->order;
592     if (va < vb)
593         return 1;
594     if (va > vb)
595         return -1;
596 
597     uint8_t da = (static_cast<const rct_neighbour*>(a))->direction;
598     uint8_t db = (static_cast<const rct_neighbour*>(b))->direction;
599     if (da < db)
600         return -1;
601     if (da > db)
602         return 1;
603     return 0;
604 }
605 
neighbour_list_init(rct_neighbour_list * neighbourList)606 static void neighbour_list_init(rct_neighbour_list* neighbourList)
607 {
608     neighbourList->count = 0;
609 }
610 
neighbour_list_push(rct_neighbour_list * neighbourList,int32_t order,int32_t direction,ride_id_t rideIndex,uint8_t entrance_index)611 static void neighbour_list_push(
612     rct_neighbour_list* neighbourList, int32_t order, int32_t direction, ride_id_t rideIndex, uint8_t entrance_index)
613 {
614     Guard::Assert(neighbourList->count < std::size(neighbourList->items));
615     neighbourList->items[neighbourList->count].order = order;
616     neighbourList->items[neighbourList->count].direction = direction;
617     neighbourList->items[neighbourList->count].ride_index = rideIndex;
618     neighbourList->items[neighbourList->count].entrance_index = entrance_index;
619     neighbourList->count++;
620 }
621 
neighbour_list_pop(rct_neighbour_list * neighbourList,rct_neighbour * outNeighbour)622 static bool neighbour_list_pop(rct_neighbour_list* neighbourList, rct_neighbour* outNeighbour)
623 {
624     if (neighbourList->count == 0)
625         return false;
626 
627     *outNeighbour = neighbourList->items[0];
628     const size_t bytesToMove = (neighbourList->count - 1) * sizeof(neighbourList->items[0]);
629     memmove(&neighbourList->items[0], &neighbourList->items[1], bytesToMove);
630     neighbourList->count--;
631     return true;
632 }
633 
neighbour_list_remove(rct_neighbour_list * neighbourList,size_t index)634 static void neighbour_list_remove(rct_neighbour_list* neighbourList, size_t index)
635 {
636     Guard::ArgumentInRange<size_t>(index, 0, neighbourList->count - 1);
637     int32_t itemsRemaining = static_cast<int32_t>(neighbourList->count - index) - 1;
638     if (itemsRemaining > 0)
639     {
640         memmove(&neighbourList->items[index], &neighbourList->items[index + 1], sizeof(rct_neighbour) * itemsRemaining);
641     }
642     neighbourList->count--;
643 }
644 
neighbour_list_sort(rct_neighbour_list * neighbourList)645 static void neighbour_list_sort(rct_neighbour_list* neighbourList)
646 {
647     qsort(neighbourList->items, neighbourList->count, sizeof(rct_neighbour), rct_neighbour_compare);
648 }
649 
footpath_get_element(const CoordsXYRangedZ & footpathPos,int32_t direction)650 static TileElement* footpath_get_element(const CoordsXYRangedZ& footpathPos, int32_t direction)
651 {
652     TileElement* tileElement = map_get_first_element_at(footpathPos);
653     if (tileElement == nullptr)
654         return nullptr;
655     do
656     {
657         if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
658             continue;
659 
660         if (footpathPos.clearanceZ == tileElement->GetBaseZ())
661         {
662             if (tileElement->AsPath()->IsSloped())
663             {
664                 auto slope = tileElement->AsPath()->GetSlopeDirection();
665                 if (slope != direction)
666                     break;
667             }
668             return tileElement;
669         }
670         if (footpathPos.baseZ == tileElement->GetBaseZ())
671         {
672             if (!tileElement->AsPath()->IsSloped())
673                 break;
674 
675             auto slope = direction_reverse(tileElement->AsPath()->GetSlopeDirection());
676             if (slope != direction)
677                 break;
678 
679             return tileElement;
680         }
681     } while (!(tileElement++)->IsLastForTile());
682     return nullptr;
683 }
684 
685 /**
686  * Attempt to connect a newly disconnected queue tile to the specified path tile
687  */
footpath_reconnect_queue_to_path(const CoordsXY & footpathPos,TileElement * tileElement,int32_t action,int32_t direction)688 static bool footpath_reconnect_queue_to_path(
689     const CoordsXY& footpathPos, TileElement* tileElement, int32_t action, int32_t direction)
690 {
691     if (((tileElement->AsPath()->GetEdges() & (1 << direction)) == 0) ^ (action < 0))
692         return false;
693 
694     auto targetQueuePos = footpathPos + CoordsDirectionDelta[direction];
695 
696     if (action < 0)
697     {
698         if (fence_in_the_way({ footpathPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() }, direction))
699             return false;
700 
701         if (fence_in_the_way(
702                 { targetQueuePos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() }, direction_reverse(direction)))
703             return false;
704     }
705 
706     int32_t z = tileElement->GetBaseZ();
707     TileElement* targetFootpathElement = footpath_get_element({ targetQueuePos, z - LAND_HEIGHT_STEP, z }, direction);
708     if (targetFootpathElement != nullptr && !targetFootpathElement->AsPath()->IsQueue())
709     {
710         auto targetQueueElement = targetFootpathElement->AsPath();
711         tileElement->AsPath()->SetSlopeDirection(0);
712         if (action > 0)
713         {
714             tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() & ~(1 << direction));
715             targetQueueElement->SetEdges(targetQueueElement->GetEdges() & ~(1 << (direction_reverse(direction) & 3)));
716             if (action >= 2)
717                 tileElement->AsPath()->SetSlopeDirection(direction);
718         }
719         else if (action < 0)
720         {
721             tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() | (1 << direction));
722             targetQueueElement->SetEdges(targetQueueElement->GetEdges() | (1 << (direction_reverse(direction) & 3)));
723         }
724         if (action != 0)
725             map_invalidate_tile_full(targetQueuePos);
726         return true;
727     }
728     return false;
729 }
730 
footpath_disconnect_queue_from_path(const CoordsXY & footpathPos,TileElement * tileElement,int32_t action)731 static bool footpath_disconnect_queue_from_path(const CoordsXY& footpathPos, TileElement* tileElement, int32_t action)
732 {
733     if (!tileElement->AsPath()->IsQueue())
734         return false;
735 
736     if (tileElement->AsPath()->IsSloped())
737         return false;
738 
739     uint8_t c = connected_path_count[tileElement->AsPath()->GetEdges()];
740     if ((action < 0) ? (c >= 2) : (c < 2))
741         return false;
742 
743     if (action < 0)
744     {
745         uint8_t direction = tileElement->AsPath()->GetSlopeDirection();
746         if (footpath_reconnect_queue_to_path(footpathPos, tileElement, action, direction))
747             return true;
748     }
749 
750     for (Direction direction : ALL_DIRECTIONS)
751     {
752         if ((action < 0) && (direction == tileElement->AsPath()->GetSlopeDirection()))
753             continue;
754         if (footpath_reconnect_queue_to_path(footpathPos, tileElement, action, direction))
755             return true;
756     }
757 
758     return false;
759 }
760 
761 /**
762  *
763  *  rct2: 0x006A6D7E
764  */
765 
loc_6A6FD2(const CoordsXYZ & initialTileElementPos,int32_t direction,TileElement * initialTileElement,bool query)766 static void loc_6A6FD2(const CoordsXYZ& initialTileElementPos, int32_t direction, TileElement* initialTileElement, bool query)
767 {
768     if ((initialTileElement)->GetType() == TILE_ELEMENT_TYPE_PATH)
769     {
770         if (!query)
771         {
772             initialTileElement->AsPath()->SetEdges(initialTileElement->AsPath()->GetEdges() | (1 << direction));
773             map_invalidate_element(initialTileElementPos, initialTileElement);
774         }
775     }
776 }
777 
loc_6A6F1F(const CoordsXYZ & initialTileElementPos,int32_t direction,TileElement * tileElement,TileElement * initialTileElement,const CoordsXY & targetPos,int32_t flags,bool query,rct_neighbour_list * neighbourList)778 static void loc_6A6F1F(
779     const CoordsXYZ& initialTileElementPos, int32_t direction, TileElement* tileElement, TileElement* initialTileElement,
780     const CoordsXY& targetPos, int32_t flags, bool query, rct_neighbour_list* neighbourList)
781 {
782     if (query)
783     {
784         if (fence_in_the_way(
785                 { targetPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() }, direction_reverse(direction)))
786         {
787             return;
788         }
789         if (tileElement->AsPath()->IsQueue())
790         {
791             if (connected_path_count[tileElement->AsPath()->GetEdges()] < 2)
792             {
793                 neighbour_list_push(
794                     neighbourList, 4, direction, tileElement->AsPath()->GetRideIndex(),
795                     tileElement->AsPath()->GetStationIndex());
796             }
797             else
798             {
799                 if ((initialTileElement)->GetType() == TILE_ELEMENT_TYPE_PATH && initialTileElement->AsPath()->IsQueue())
800                 {
801                     if (footpath_disconnect_queue_from_path(targetPos, tileElement, 0))
802                     {
803                         neighbour_list_push(
804                             neighbourList, 3, direction, tileElement->AsPath()->GetRideIndex(),
805                             tileElement->AsPath()->GetStationIndex());
806                     }
807                 }
808             }
809         }
810         else
811         {
812             neighbour_list_push(neighbourList, 2, direction, RIDE_ID_NULL, 255);
813         }
814     }
815     else
816     {
817         footpath_disconnect_queue_from_path(targetPos, tileElement, 1 + ((flags >> 6) & 1));
818         tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() | (1 << direction_reverse(direction)));
819         if (tileElement->AsPath()->IsQueue())
820         {
821             footpath_queue_chain_push(tileElement->AsPath()->GetRideIndex());
822         }
823     }
824     if (!(flags & (GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED)))
825     {
826         footpath_interrupt_peeps({ targetPos, tileElement->GetBaseZ() });
827     }
828     map_invalidate_element(targetPos, tileElement);
829     loc_6A6FD2(initialTileElementPos, direction, initialTileElement, query);
830 }
831 
loc_6A6D7E(const CoordsXYZ & initialTileElementPos,int32_t direction,TileElement * initialTileElement,int32_t flags,bool query,rct_neighbour_list * neighbourList)832 static void loc_6A6D7E(
833     const CoordsXYZ& initialTileElementPos, int32_t direction, TileElement* initialTileElement, int32_t flags, bool query,
834     rct_neighbour_list* neighbourList)
835 {
836     auto targetPos = CoordsXY{ initialTileElementPos } + CoordsDirectionDelta[direction];
837     if (((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode) && map_is_edge(targetPos))
838     {
839         if (query)
840         {
841             neighbour_list_push(neighbourList, 7, direction, RIDE_ID_NULL, 255);
842         }
843         loc_6A6FD2(initialTileElementPos, direction, initialTileElement, query);
844     }
845     else
846     {
847         TileElement* tileElement = map_get_first_element_at(targetPos);
848         if (tileElement == nullptr)
849             return;
850         do
851         {
852             switch (tileElement->GetType())
853             {
854                 case TILE_ELEMENT_TYPE_PATH:
855                     if (tileElement->GetBaseZ() == initialTileElementPos.z)
856                     {
857                         if (!tileElement->AsPath()->IsSloped() || tileElement->AsPath()->GetSlopeDirection() == direction)
858                         {
859                             loc_6A6F1F(
860                                 initialTileElementPos, direction, tileElement, initialTileElement, targetPos, flags, query,
861                                 neighbourList);
862                         }
863                         return;
864                     }
865                     if (tileElement->GetBaseZ() == initialTileElementPos.z - LAND_HEIGHT_STEP)
866                     {
867                         if (tileElement->AsPath()->IsSloped()
868                             && tileElement->AsPath()->GetSlopeDirection() == direction_reverse(direction))
869                         {
870                             loc_6A6F1F(
871                                 initialTileElementPos, direction, tileElement, initialTileElement, targetPos, flags, query,
872                                 neighbourList);
873                         }
874                         return;
875                     }
876                     break;
877                 case TILE_ELEMENT_TYPE_TRACK:
878                     if (initialTileElementPos.z == tileElement->GetBaseZ())
879                     {
880                         auto ride = get_ride(tileElement->AsTrack()->GetRideIndex());
881                         if (ride == nullptr)
882                         {
883                             continue;
884                         }
885 
886                         if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE))
887                         {
888                             continue;
889                         }
890 
891                         const auto trackType = tileElement->AsTrack()->GetTrackType();
892                         const uint8_t trackSequence = tileElement->AsTrack()->GetSequenceIndex();
893                         const auto& ted = GetTrackElementDescriptor(trackType);
894                         if (!(ted.SequenceProperties[trackSequence] & TRACK_SEQUENCE_FLAG_CONNECTS_TO_PATH))
895                         {
896                             return;
897                         }
898                         uint16_t dx = direction_reverse(
899                             (direction - tileElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK);
900 
901                         if (!(ted.SequenceProperties[trackSequence] & (1 << dx)))
902                         {
903                             return;
904                         }
905                         if (query)
906                         {
907                             neighbour_list_push(neighbourList, 1, direction, tileElement->AsTrack()->GetRideIndex(), 255);
908                         }
909                         loc_6A6FD2(initialTileElementPos, direction, initialTileElement, query);
910                         return;
911                     }
912                     break;
913                 case TILE_ELEMENT_TYPE_ENTRANCE:
914                     if (initialTileElementPos.z == tileElement->GetBaseZ())
915                     {
916                         if (entrance_has_direction(
917                                 *(tileElement->AsEntrance()), direction_reverse(direction - tileElement->GetDirection())))
918                         {
919                             if (query)
920                             {
921                                 neighbour_list_push(
922                                     neighbourList, 8, direction, tileElement->AsEntrance()->GetRideIndex(),
923                                     tileElement->AsEntrance()->GetStationIndex());
924                             }
925                             else
926                             {
927                                 if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_PARK_ENTRANCE)
928                                 {
929                                     footpath_queue_chain_push(tileElement->AsEntrance()->GetRideIndex());
930                                 }
931                             }
932                             loc_6A6FD2(initialTileElementPos, direction, initialTileElement, query);
933                             return;
934                         }
935                     }
936                     break;
937             }
938 
939         } while (!(tileElement++)->IsLastForTile());
940     }
941 }
942 
943 // TODO: Change this into a simple check that validates if the direction should be fully checked with loc_6A6D7E and move the
944 // calling of loc_6A6D7E into the parent function.
loc_6A6C85(const CoordsXYE & tileElementPos,int32_t direction,int32_t flags,bool query,rct_neighbour_list * neighbourList)945 static void loc_6A6C85(
946     const CoordsXYE& tileElementPos, int32_t direction, int32_t flags, bool query, rct_neighbour_list* neighbourList)
947 {
948     if (query
949         && fence_in_the_way(
950             { tileElementPos, tileElementPos.element->GetBaseZ(), tileElementPos.element->GetClearanceZ() }, direction))
951         return;
952 
953     if (tileElementPos.element->GetType() == TILE_ELEMENT_TYPE_ENTRANCE)
954     {
955         if (!entrance_has_direction(
956                 *(tileElementPos.element->AsEntrance()), direction - tileElementPos.element->GetDirection()))
957         {
958             return;
959         }
960     }
961 
962     if (tileElementPos.element->GetType() == TILE_ELEMENT_TYPE_TRACK)
963     {
964         auto ride = get_ride(tileElementPos.element->AsTrack()->GetRideIndex());
965         if (ride == nullptr)
966         {
967             return;
968         }
969 
970         if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE))
971         {
972             return;
973         }
974 
975         const auto trackType = tileElementPos.element->AsTrack()->GetTrackType();
976         const uint8_t trackSequence = tileElementPos.element->AsTrack()->GetSequenceIndex();
977         const auto& ted = GetTrackElementDescriptor(trackType);
978         if (!(ted.SequenceProperties[trackSequence] & TRACK_SEQUENCE_FLAG_CONNECTS_TO_PATH))
979         {
980             return;
981         }
982         uint16_t dx = (direction - tileElementPos.element->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK;
983         if (!(ted.SequenceProperties[trackSequence] & (1 << dx)))
984         {
985             return;
986         }
987     }
988 
989     auto pos = CoordsXYZ{ tileElementPos, tileElementPos.element->GetBaseZ() };
990     if (tileElementPos.element->GetType() == TILE_ELEMENT_TYPE_PATH)
991     {
992         if (tileElementPos.element->AsPath()->IsSloped())
993         {
994             if ((tileElementPos.element->AsPath()->GetSlopeDirection() - direction) & 1)
995             {
996                 return;
997             }
998             if (tileElementPos.element->AsPath()->GetSlopeDirection() == direction)
999             {
1000                 pos.z += LAND_HEIGHT_STEP;
1001             }
1002         }
1003     }
1004 
1005     loc_6A6D7E(pos, direction, tileElementPos.element, flags, query, neighbourList);
1006 }
1007 
1008 /**
1009  *
1010  *  rct2: 0x006A6C66
1011  */
footpath_connect_edges(const CoordsXY & footpathPos,TileElement * tileElement,int32_t flags)1012 void footpath_connect_edges(const CoordsXY& footpathPos, TileElement* tileElement, int32_t flags)
1013 {
1014     rct_neighbour_list neighbourList;
1015     rct_neighbour neighbour;
1016 
1017     footpath_update_queue_chains();
1018 
1019     neighbour_list_init(&neighbourList);
1020 
1021     footpath_update_queue_entrance_banner(footpathPos, tileElement);
1022     for (Direction direction : ALL_DIRECTIONS)
1023     {
1024         loc_6A6C85({ footpathPos, tileElement }, direction, flags, true, &neighbourList);
1025     }
1026 
1027     neighbour_list_sort(&neighbourList);
1028 
1029     if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH && tileElement->AsPath()->IsQueue())
1030     {
1031         ride_id_t rideIndex = RIDE_ID_NULL;
1032         uint8_t entranceIndex = 255;
1033         for (size_t i = 0; i < neighbourList.count; i++)
1034         {
1035             if (neighbourList.items[i].ride_index != RIDE_ID_NULL)
1036             {
1037                 if (rideIndex == RIDE_ID_NULL)
1038                 {
1039                     rideIndex = neighbourList.items[i].ride_index;
1040                     entranceIndex = neighbourList.items[i].entrance_index;
1041                 }
1042                 else if (rideIndex != neighbourList.items[i].ride_index)
1043                 {
1044                     neighbour_list_remove(&neighbourList, i);
1045                 }
1046                 else if (
1047                     rideIndex == neighbourList.items[i].ride_index && entranceIndex != neighbourList.items[i].entrance_index
1048                     && neighbourList.items[i].entrance_index != 255)
1049                 {
1050                     neighbour_list_remove(&neighbourList, i);
1051                 }
1052             }
1053         }
1054 
1055         neighbourList.count = std::min<size_t>(neighbourList.count, 2);
1056     }
1057 
1058     while (neighbour_list_pop(&neighbourList, &neighbour))
1059     {
1060         loc_6A6C85({ footpathPos, tileElement }, neighbour.direction, flags, false, nullptr);
1061     }
1062 
1063     if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
1064     {
1065         footpath_connect_corners(footpathPos, tileElement->AsPath());
1066     }
1067 }
1068 
1069 /**
1070  *
1071  *  rct2: 0x006A742F
1072  */
footpath_chain_ride_queue(ride_id_t rideIndex,int32_t entranceIndex,const CoordsXY & initialFootpathPos,TileElement * const initialTileElement,int32_t direction)1073 void footpath_chain_ride_queue(
1074     ride_id_t rideIndex, int32_t entranceIndex, const CoordsXY& initialFootpathPos, TileElement* const initialTileElement,
1075     int32_t direction)
1076 {
1077     TileElement *lastPathElement, *lastQueuePathElement;
1078     auto tileElement = initialTileElement;
1079     auto curQueuePos = initialFootpathPos;
1080     auto lastPath = curQueuePos;
1081     int32_t baseZ = tileElement->GetBaseZ();
1082     int32_t lastPathDirection = direction;
1083 
1084     lastPathElement = nullptr;
1085     lastQueuePathElement = nullptr;
1086     for (;;)
1087     {
1088         if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
1089         {
1090             lastPathElement = tileElement;
1091             lastPath = curQueuePos;
1092             lastPathDirection = direction;
1093             if (tileElement->AsPath()->IsSloped())
1094             {
1095                 if (tileElement->AsPath()->GetSlopeDirection() == direction)
1096                 {
1097                     baseZ += LAND_HEIGHT_STEP;
1098                 }
1099             }
1100         }
1101 
1102         auto targetQueuePos = curQueuePos + CoordsDirectionDelta[direction];
1103         tileElement = map_get_first_element_at(targetQueuePos);
1104         bool foundQueue = false;
1105         if (tileElement != nullptr)
1106         {
1107             do
1108             {
1109                 if (lastQueuePathElement == tileElement)
1110                     continue;
1111                 if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
1112                     continue;
1113                 if (tileElement->GetBaseZ() == baseZ)
1114                 {
1115                     if (tileElement->AsPath()->IsSloped())
1116                     {
1117                         if (tileElement->AsPath()->GetSlopeDirection() != direction)
1118                             break;
1119                     }
1120                     foundQueue = true;
1121                     break;
1122                 }
1123                 if (tileElement->GetBaseZ() == baseZ - LAND_HEIGHT_STEP)
1124                 {
1125                     if (!tileElement->AsPath()->IsSloped())
1126                         break;
1127 
1128                     if (direction_reverse(tileElement->AsPath()->GetSlopeDirection()) != direction)
1129                         break;
1130 
1131                     baseZ -= LAND_HEIGHT_STEP;
1132                     foundQueue = true;
1133                     break;
1134                 }
1135             } while (!(tileElement++)->IsLastForTile());
1136         }
1137         if (!foundQueue)
1138             break;
1139 
1140         if (tileElement->AsPath()->IsQueue())
1141         {
1142             // Fix #2051: Stop queue paths that are already connected to two other tiles
1143             //            from connecting to the tile we are coming from.
1144             int32_t edges = tileElement->AsPath()->GetEdges();
1145             int32_t numEdges = bitcount(edges);
1146             if (numEdges >= 2)
1147             {
1148                 int32_t requiredEdgeMask = 1 << direction_reverse(direction);
1149                 if (!(edges & requiredEdgeMask))
1150                 {
1151                     break;
1152                 }
1153             }
1154 
1155             tileElement->AsPath()->SetHasQueueBanner(false);
1156             tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() | (1 << direction_reverse(direction)));
1157             tileElement->AsPath()->SetRideIndex(rideIndex);
1158             tileElement->AsPath()->SetStationIndex(entranceIndex);
1159 
1160             curQueuePos = targetQueuePos;
1161             map_invalidate_element(targetQueuePos, tileElement);
1162 
1163             if (lastQueuePathElement == nullptr)
1164             {
1165                 lastQueuePathElement = tileElement;
1166             }
1167 
1168             if (tileElement->AsPath()->GetEdges() & (1 << direction))
1169                 continue;
1170 
1171             direction = (direction + 1) & 3;
1172             if (tileElement->AsPath()->GetEdges() & (1 << direction))
1173                 continue;
1174 
1175             direction = direction_reverse(direction);
1176             if (tileElement->AsPath()->GetEdges() & (1 << direction))
1177                 continue;
1178         }
1179         break;
1180     }
1181 
1182     if (rideIndex != RIDE_ID_NULL && lastPathElement != nullptr)
1183     {
1184         if (lastPathElement->AsPath()->IsQueue())
1185         {
1186             lastPathElement->AsPath()->SetHasQueueBanner(true);
1187             lastPathElement->AsPath()->SetQueueBannerDirection(lastPathDirection); // set the ride sign direction
1188 
1189             map_animation_create(MAP_ANIMATION_TYPE_QUEUE_BANNER, { lastPath, lastPathElement->GetBaseZ() });
1190         }
1191     }
1192 }
1193 
footpath_queue_chain_reset()1194 void footpath_queue_chain_reset()
1195 {
1196     _footpathQueueChainNext = _footpathQueueChain;
1197 }
1198 
1199 /**
1200  *
1201  *  rct2: 0x006A76E9
1202  */
footpath_queue_chain_push(ride_id_t rideIndex)1203 void footpath_queue_chain_push(ride_id_t rideIndex)
1204 {
1205     if (rideIndex != RIDE_ID_NULL)
1206     {
1207         auto* lastSlot = _footpathQueueChain + std::size(_footpathQueueChain) - 1;
1208         if (_footpathQueueChainNext <= lastSlot)
1209         {
1210             *_footpathQueueChainNext++ = rideIndex;
1211         }
1212     }
1213 }
1214 
1215 /**
1216  *
1217  *  rct2: 0x006A759F
1218  */
footpath_update_queue_chains()1219 void footpath_update_queue_chains()
1220 {
1221     for (auto* queueChainPtr = _footpathQueueChain; queueChainPtr < _footpathQueueChainNext; queueChainPtr++)
1222     {
1223         ride_id_t rideIndex = *queueChainPtr;
1224         auto ride = get_ride(rideIndex);
1225         if (ride == nullptr)
1226             continue;
1227 
1228         for (int32_t i = 0; i < MAX_STATIONS; i++)
1229         {
1230             TileCoordsXYZD location = ride_get_entrance_location(ride, i);
1231             if (location.IsNull())
1232                 continue;
1233 
1234             TileElement* tileElement = map_get_first_element_at(location);
1235             if (tileElement != nullptr)
1236             {
1237                 do
1238                 {
1239                     if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
1240                         continue;
1241                     if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_RIDE_ENTRANCE)
1242                         continue;
1243                     if (tileElement->AsEntrance()->GetRideIndex() != rideIndex)
1244                         continue;
1245 
1246                     Direction direction = direction_reverse(tileElement->GetDirection());
1247                     footpath_chain_ride_queue(rideIndex, i, location.ToCoordsXY(), tileElement, direction);
1248                 } while (!(tileElement++)->IsLastForTile());
1249             }
1250         }
1251     }
1252 }
1253 
1254 /**
1255  *
1256  *  rct2: 0x0069ADBD
1257  */
footpath_fix_ownership(const CoordsXY & mapPos)1258 static void footpath_fix_ownership(const CoordsXY& mapPos)
1259 {
1260     const auto* surfaceElement = map_get_surface_element_at(mapPos);
1261     uint16_t ownership;
1262 
1263     // Unlikely to be NULL unless deliberate.
1264     if (surfaceElement != nullptr)
1265     {
1266         // If the tile is not safe to own construction rights of, erase them.
1267         if (check_max_allowable_land_rights_for_tile({ mapPos, surfaceElement->base_height << 3 }) == OWNERSHIP_UNOWNED)
1268         {
1269             ownership = OWNERSHIP_UNOWNED;
1270         }
1271         // If the tile is safe to own construction rights of, do not erase construction rights.
1272         else
1273         {
1274             ownership = surfaceElement->GetOwnership();
1275             // You can't own the entrance path.
1276             if (ownership == OWNERSHIP_OWNED || ownership == OWNERSHIP_AVAILABLE)
1277             {
1278                 ownership = OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED;
1279             }
1280         }
1281     }
1282     else
1283     {
1284         ownership = OWNERSHIP_UNOWNED;
1285     }
1286 
1287     auto landSetRightsAction = LandSetRightsAction(mapPos, LandSetRightSetting::SetOwnershipWithChecks, ownership);
1288     landSetRightsAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND);
1289     GameActions::Execute(&landSetRightsAction);
1290 }
1291 
get_next_direction(int32_t edges,int32_t * direction)1292 static bool get_next_direction(int32_t edges, int32_t* direction)
1293 {
1294     int32_t index = bitscanforward(edges);
1295     if (index == -1)
1296         return false;
1297 
1298     *direction = index;
1299     return true;
1300 }
1301 
1302 /**
1303  *
1304  *  rct2: 0x0069AC1A
1305  * @param flags (1 << 0): Ignore queues
1306  *              (1 << 5): Unown
1307  *              (1 << 7): Ignore no entry signs
1308  */
footpath_is_connected_to_map_edge_helper(CoordsXYZ footpathPos,int32_t direction,int32_t flags)1309 static int32_t footpath_is_connected_to_map_edge_helper(CoordsXYZ footpathPos, int32_t direction, int32_t flags)
1310 {
1311     int32_t returnVal = FOOTPATH_SEARCH_INCOMPLETE;
1312 
1313     struct TileState
1314     {
1315         bool processed = false;
1316         CoordsXYZ footpathPos;
1317         int32_t direction;
1318         int32_t level;
1319         int32_t distanceFromJunction;
1320         int32_t junctionTolerance;
1321     };
1322 
1323     // Vector of all of the child tile elements for us to explore
1324     std::vector<TileState> tiles;
1325     TileElement* tileElement = nullptr;
1326     int numPendingTiles = 0;
1327 
1328     TileState currentTile = { false, footpathPos, direction, 0, 0, 16 };
1329 
1330     // Captures the current state of the variables and stores them in tiles vector for iteration later
1331     auto CaptureCurrentTileState = [&tiles, &numPendingTiles](TileState t_currentTile) -> void {
1332         // Search for an entry of this tile in our list already
1333         for (const TileState& tile : tiles)
1334         {
1335             if (tile.footpathPos == t_currentTile.footpathPos && tile.direction == t_currentTile.direction)
1336                 return;
1337         }
1338 
1339         // If we get here we did not find it, so insert the tile into our list
1340         tiles.push_back(t_currentTile);
1341         ++numPendingTiles;
1342     };
1343 
1344     // Loads the next tile to visit into our variables
1345     auto LoadNextTileElement = [&tiles, &numPendingTiles](TileState& t_currentTile) -> void {
1346         // Do not continue if there are no tiles in the list
1347         if (tiles.size() == 0)
1348             return;
1349 
1350         // Find the next unprocessed tile
1351         for (size_t tileIndex = tiles.size() - 1; tileIndex > 0; --tileIndex)
1352         {
1353             if (tiles[tileIndex].processed)
1354                 continue;
1355             --numPendingTiles;
1356             t_currentTile = tiles[tileIndex];
1357             tiles[tileIndex].processed = true;
1358             return;
1359         }
1360         // Default to tile 0
1361         --numPendingTiles;
1362         t_currentTile = tiles[0];
1363         tiles[0].processed = true;
1364     };
1365 
1366     // Encapsulate the tile skipping logic to make do-while more readable
1367     auto SkipTileElement = [](int32_t ste_flags, TileElement* ste_tileElement, int32_t& ste_slopeDirection,
1368                               int32_t ste_direction, const CoordsXYZ& ste_targetPos) {
1369         if (ste_tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
1370             return true;
1371 
1372         if (ste_tileElement->AsPath()->IsSloped()
1373             && (ste_slopeDirection = ste_tileElement->AsPath()->GetSlopeDirection()) != ste_direction)
1374         {
1375             if (direction_reverse(ste_slopeDirection) != ste_direction)
1376                 return true;
1377             if (ste_tileElement->GetBaseZ() + PATH_HEIGHT_STEP != ste_targetPos.z)
1378                 return true;
1379         }
1380         else if (ste_tileElement->GetBaseZ() != ste_targetPos.z)
1381             return true;
1382 
1383         if (!(ste_flags & FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_QUEUES))
1384             if (ste_tileElement->AsPath()->IsQueue())
1385                 return true;
1386         return false;
1387     };
1388 
1389     int32_t edges, slopeDirection;
1390 
1391     // Capture the current tile state to begin the loop
1392     CaptureCurrentTileState(currentTile);
1393 
1394     // Loop on this until all tiles are processed or we return
1395     while (numPendingTiles > 0)
1396     {
1397         LoadNextTileElement(currentTile);
1398 
1399         CoordsXYZ targetPos = CoordsXYZ{ CoordsXY{ currentTile.footpathPos } + CoordsDirectionDelta[currentTile.direction],
1400                                          currentTile.footpathPos.z };
1401 
1402         if (++currentTile.level > 250)
1403             return FOOTPATH_SEARCH_TOO_COMPLEX;
1404 
1405         // Return immediately if we are at the edge of the map and not unowning
1406         // Or if we are unowning and have no tiles left
1407         if ((map_is_edge(targetPos) && !(flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN)))
1408         {
1409             return FOOTPATH_SEARCH_SUCCESS;
1410         }
1411 
1412         tileElement = map_get_first_element_at(targetPos);
1413         if (tileElement == nullptr)
1414             return currentTile.level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : FOOTPATH_SEARCH_INCOMPLETE;
1415 
1416         // Loop while there are unvisited TileElements at targetPos
1417         do
1418         {
1419             if (SkipTileElement(flags, tileElement, slopeDirection, currentTile.direction, targetPos))
1420                 continue;
1421 
1422             // Unown the footpath if needed
1423             if (flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN)
1424                 footpath_fix_ownership(targetPos);
1425 
1426             edges = tileElement->AsPath()->GetEdges();
1427             currentTile.direction = direction_reverse(currentTile.direction);
1428             if (!tileElement->IsLastForTile() && !(flags & FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_NO_ENTRY))
1429             {
1430                 int elementIndex = 1;
1431                 // Loop over all elements and cull appropriate edges
1432                 do
1433                 {
1434                     if (tileElement[elementIndex].GetType() == TILE_ELEMENT_TYPE_PATH)
1435                         break;
1436                     if (tileElement[elementIndex].GetType() != TILE_ELEMENT_TYPE_BANNER)
1437                     {
1438                         continue;
1439                     }
1440                     edges &= tileElement[elementIndex].AsBanner()->GetAllowedEdges();
1441                 } while (!tileElement[elementIndex++].IsLastForTile());
1442             }
1443 
1444             // Exclude the direction we came from
1445             targetPos.z = tileElement->GetBaseZ();
1446             edges &= ~(1 << currentTile.direction);
1447 
1448             if (!get_next_direction(edges, &currentTile.direction))
1449                 break;
1450 
1451             edges &= ~(1 << currentTile.direction);
1452             if (edges == 0)
1453             {
1454                 // Only possible direction to go
1455                 if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == currentTile.direction)
1456                     targetPos.z += PATH_HEIGHT_STEP;
1457 
1458                 // Prepare the next iteration
1459                 currentTile.footpathPos = targetPos;
1460                 ++currentTile.distanceFromJunction;
1461                 CaptureCurrentTileState(currentTile);
1462             }
1463             else
1464             {
1465                 // We have reached a junction
1466                 --currentTile.junctionTolerance;
1467                 if (currentTile.distanceFromJunction != 0)
1468                 {
1469                     --currentTile.junctionTolerance;
1470                 }
1471 
1472                 if (currentTile.junctionTolerance < 0 && !(flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN))
1473                 {
1474                     returnVal = FOOTPATH_SEARCH_TOO_COMPLEX;
1475                     break;
1476                 }
1477 
1478                 // Loop until there are no more directions we can go
1479                 do
1480                 {
1481                     edges &= ~(1 << currentTile.direction);
1482                     if (tileElement->AsPath()->IsSloped()
1483                         && tileElement->AsPath()->GetSlopeDirection() == currentTile.direction)
1484                     {
1485                         targetPos.z += PATH_HEIGHT_STEP;
1486                     }
1487 
1488                     // Add each possible path to the list of pending tiles
1489                     currentTile.footpathPos = targetPos;
1490                     currentTile.distanceFromJunction = 0;
1491                     CaptureCurrentTileState(currentTile);
1492                 } while (get_next_direction(edges, &currentTile.direction));
1493             }
1494             break;
1495         } while (!(tileElement++)->IsLastForTile());
1496 
1497         // Return success if we have unowned all tiles in our pending list
1498         if ((flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN) && numPendingTiles <= 0)
1499         {
1500             return FOOTPATH_SEARCH_SUCCESS;
1501         }
1502     }
1503     return currentTile.level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : returnVal;
1504 }
1505 
1506 // TODO: Use GAME_COMMAND_FLAGS
footpath_is_connected_to_map_edge(const CoordsXYZ & footpathPos,int32_t direction,int32_t flags)1507 int32_t footpath_is_connected_to_map_edge(const CoordsXYZ& footpathPos, int32_t direction, int32_t flags)
1508 {
1509     flags |= FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_QUEUES;
1510     return footpath_is_connected_to_map_edge_helper(footpathPos, direction, flags);
1511 }
1512 
IsSloped() const1513 bool PathElement::IsSloped() const
1514 {
1515     return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_IS_SLOPED) != 0;
1516 }
1517 
SetSloped(bool isSloped)1518 void PathElement::SetSloped(bool isSloped)
1519 {
1520     Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_IS_SLOPED;
1521     if (isSloped)
1522         Flags2 |= FOOTPATH_ELEMENT_FLAGS2_IS_SLOPED;
1523 }
1524 
GetSlopeDirection() const1525 Direction PathElement::GetSlopeDirection() const
1526 {
1527     return SlopeDirection;
1528 }
1529 
SetSlopeDirection(Direction newSlope)1530 void PathElement::SetSlopeDirection(Direction newSlope)
1531 {
1532     SlopeDirection = newSlope;
1533 }
1534 
IsQueue() const1535 bool PathElement::IsQueue() const
1536 {
1537     return (type & FOOTPATH_ELEMENT_TYPE_FLAG_IS_QUEUE) != 0;
1538 }
1539 
SetIsQueue(bool isQueue)1540 void PathElement::SetIsQueue(bool isQueue)
1541 {
1542     type &= ~FOOTPATH_ELEMENT_TYPE_FLAG_IS_QUEUE;
1543     if (isQueue)
1544         type |= FOOTPATH_ELEMENT_TYPE_FLAG_IS_QUEUE;
1545 }
1546 
HasQueueBanner() const1547 bool PathElement::HasQueueBanner() const
1548 {
1549     return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_HAS_QUEUE_BANNER) != 0;
1550 }
1551 
SetHasQueueBanner(bool hasQueueBanner)1552 void PathElement::SetHasQueueBanner(bool hasQueueBanner)
1553 {
1554     Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_HAS_QUEUE_BANNER;
1555     if (hasQueueBanner)
1556         Flags2 |= FOOTPATH_ELEMENT_FLAGS2_HAS_QUEUE_BANNER;
1557 }
1558 
IsBroken() const1559 bool PathElement::IsBroken() const
1560 {
1561     return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_BROKEN) != 0;
1562 }
1563 
SetIsBroken(bool isBroken)1564 void PathElement::SetIsBroken(bool isBroken)
1565 {
1566     if (isBroken)
1567     {
1568         Flags2 |= FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_BROKEN;
1569     }
1570     else
1571     {
1572         Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_BROKEN;
1573     }
1574 }
1575 
IsBlockedByVehicle() const1576 bool PathElement::IsBlockedByVehicle() const
1577 {
1578     return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_BLOCKED_BY_VEHICLE) != 0;
1579 }
1580 
SetIsBlockedByVehicle(bool isBlocked)1581 void PathElement::SetIsBlockedByVehicle(bool isBlocked)
1582 {
1583     if (isBlocked)
1584     {
1585         Flags2 |= FOOTPATH_ELEMENT_FLAGS2_BLOCKED_BY_VEHICLE;
1586     }
1587     else
1588     {
1589         Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_BLOCKED_BY_VEHICLE;
1590     }
1591 }
1592 
GetStationIndex() const1593 ::StationIndex PathElement::GetStationIndex() const
1594 {
1595     return StationIndex;
1596 }
1597 
SetStationIndex(::StationIndex newStationIndex)1598 void PathElement::SetStationIndex(::StationIndex newStationIndex)
1599 {
1600     StationIndex = newStationIndex;
1601 }
1602 
IsWide() const1603 bool PathElement::IsWide() const
1604 {
1605     return (type & FOOTPATH_ELEMENT_TYPE_FLAG_IS_WIDE) != 0;
1606 }
1607 
SetWide(bool isWide)1608 void PathElement::SetWide(bool isWide)
1609 {
1610     type &= ~FOOTPATH_ELEMENT_TYPE_FLAG_IS_WIDE;
1611     if (isWide)
1612         type |= FOOTPATH_ELEMENT_TYPE_FLAG_IS_WIDE;
1613 }
1614 
HasAddition() const1615 bool PathElement::HasAddition() const
1616 {
1617     return Additions != 0;
1618 }
1619 
GetAddition() const1620 uint8_t PathElement::GetAddition() const
1621 {
1622     return Additions;
1623 }
1624 
GetAdditionEntryIndex() const1625 ObjectEntryIndex PathElement::GetAdditionEntryIndex() const
1626 {
1627     return GetAddition() - 1;
1628 }
1629 
GetAdditionEntry() const1630 PathBitEntry* PathElement::GetAdditionEntry() const
1631 {
1632     if (!HasAddition())
1633         return nullptr;
1634     return get_footpath_item_entry(GetAdditionEntryIndex());
1635 }
1636 
SetAddition(uint8_t newAddition)1637 void PathElement::SetAddition(uint8_t newAddition)
1638 {
1639     Additions = newAddition;
1640 }
1641 
AdditionIsGhost() const1642 bool PathElement::AdditionIsGhost() const
1643 {
1644     return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_GHOST) != 0;
1645 }
1646 
SetAdditionIsGhost(bool isGhost)1647 void PathElement::SetAdditionIsGhost(bool isGhost)
1648 {
1649     Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_GHOST;
1650     if (isGhost)
1651         Flags2 |= FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_GHOST;
1652 }
1653 
GetLegacyPathEntryIndex() const1654 ObjectEntryIndex PathElement::GetLegacyPathEntryIndex() const
1655 {
1656     if (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY)
1657         return SurfaceIndex;
1658 
1659     return OBJECT_ENTRY_INDEX_NULL;
1660 }
1661 
GetLegacyPathEntry() const1662 const FootpathObject* PathElement::GetLegacyPathEntry() const
1663 {
1664     return GetLegacyFootpathEntry(GetLegacyPathEntryIndex());
1665 }
1666 
SetLegacyPathEntryIndex(ObjectEntryIndex newIndex)1667 void PathElement::SetLegacyPathEntryIndex(ObjectEntryIndex newIndex)
1668 {
1669     SurfaceIndex = newIndex;
1670     RailingsIndex = OBJECT_ENTRY_INDEX_NULL;
1671     Flags2 |= FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY;
1672 }
1673 
HasLegacyPathEntry() const1674 bool PathElement::HasLegacyPathEntry() const
1675 {
1676     return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY) != 0;
1677 }
1678 
GetSurfaceDescriptor() const1679 const PathSurfaceDescriptor* PathElement::GetSurfaceDescriptor() const
1680 {
1681     if (HasLegacyPathEntry())
1682     {
1683         const auto* legacyPathEntry = GetLegacyPathEntry();
1684         if (legacyPathEntry == nullptr)
1685             return nullptr;
1686 
1687         if (IsQueue())
1688             return &legacyPathEntry->GetQueueSurfaceDescriptor();
1689 
1690         return &legacyPathEntry->GetPathSurfaceDescriptor();
1691     }
1692 
1693     const auto* surfaceEntry = GetSurfaceEntry();
1694     if (surfaceEntry == nullptr)
1695         return nullptr;
1696 
1697     return &surfaceEntry->GetDescriptor();
1698 }
1699 
GetRailingsDescriptor() const1700 const PathRailingsDescriptor* PathElement::GetRailingsDescriptor() const
1701 {
1702     if (HasLegacyPathEntry())
1703     {
1704         const auto* legacyPathEntry = GetLegacyPathEntry();
1705         if (legacyPathEntry == nullptr)
1706             return nullptr;
1707 
1708         return &legacyPathEntry->GetPathRailingsDescriptor();
1709     }
1710 
1711     const auto* railingsEntry = GetRailingsEntry();
1712     if (railingsEntry == nullptr)
1713         return nullptr;
1714 
1715     return &railingsEntry->GetDescriptor();
1716 }
1717 
GetSurfaceEntryIndex() const1718 ObjectEntryIndex PathElement::GetSurfaceEntryIndex() const
1719 {
1720     if (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY)
1721         return OBJECT_ENTRY_INDEX_NULL;
1722 
1723     return SurfaceIndex;
1724 }
1725 
GetSurfaceEntry() const1726 const FootpathSurfaceObject* PathElement::GetSurfaceEntry() const
1727 {
1728     auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
1729     return static_cast<FootpathSurfaceObject*>(objMgr.GetLoadedObject(ObjectType::FootpathSurface, GetSurfaceEntryIndex()));
1730 }
1731 
SetSurfaceEntryIndex(ObjectEntryIndex newIndex)1732 void PathElement::SetSurfaceEntryIndex(ObjectEntryIndex newIndex)
1733 {
1734     SurfaceIndex = newIndex;
1735     Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY;
1736 }
1737 
GetRailingsEntryIndex() const1738 ObjectEntryIndex PathElement::GetRailingsEntryIndex() const
1739 {
1740     if (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY)
1741         return OBJECT_ENTRY_INDEX_NULL;
1742 
1743     return RailingsIndex;
1744 }
1745 
GetRailingsEntry() const1746 const FootpathRailingsObject* PathElement::GetRailingsEntry() const
1747 {
1748     auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
1749     return static_cast<FootpathRailingsObject*>(objMgr.GetLoadedObject(ObjectType::FootpathRailings, GetRailingsEntryIndex()));
1750 }
1751 
SetRailingsEntryIndex(ObjectEntryIndex newEntryIndex)1752 void PathElement::SetRailingsEntryIndex(ObjectEntryIndex newEntryIndex)
1753 {
1754     RailingsIndex = newEntryIndex;
1755     Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY;
1756 }
1757 
GetQueueBannerDirection() const1758 uint8_t PathElement::GetQueueBannerDirection() const
1759 {
1760     return ((type & FOOTPATH_ELEMENT_TYPE_DIRECTION_MASK) >> 6);
1761 }
1762 
SetQueueBannerDirection(uint8_t direction)1763 void PathElement::SetQueueBannerDirection(uint8_t direction)
1764 {
1765     type &= ~FOOTPATH_ELEMENT_TYPE_DIRECTION_MASK;
1766     type |= (direction << 6);
1767 }
1768 
ShouldDrawPathOverSupports() const1769 bool PathElement::ShouldDrawPathOverSupports() const
1770 {
1771     // TODO: make this an actual decision of the tile element.
1772     return (GetRailingsDescriptor()->Flags & RAILING_ENTRY_FLAG_DRAW_PATH_OVER_SUPPORTS);
1773 }
1774 
SetShouldDrawPathOverSupports(bool on)1775 void PathElement::SetShouldDrawPathOverSupports(bool on)
1776 {
1777     log_verbose("Setting 'draw path over supports' to %d", static_cast<size_t>(on));
1778 }
1779 
1780 /**
1781  *
1782  *  rct2: 0x006A8B12
1783  *  clears the wide footpath flag for all footpaths
1784  *  at location
1785  */
footpath_clear_wide(const CoordsXY & footpathPos)1786 static void footpath_clear_wide(const CoordsXY& footpathPos)
1787 {
1788     TileElement* tileElement = map_get_first_element_at(footpathPos);
1789     if (tileElement == nullptr)
1790         return;
1791     do
1792     {
1793         if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
1794             continue;
1795         tileElement->AsPath()->SetWide(false);
1796     } while (!(tileElement++)->IsLastForTile());
1797 }
1798 
1799 /**
1800  *
1801  *  rct2: 0x006A8ACF
1802  *  returns footpath element if it can be made wide
1803  *  returns NULL if it can not be made wide
1804  */
footpath_can_be_wide(const CoordsXYZ & footpathPos)1805 static TileElement* footpath_can_be_wide(const CoordsXYZ& footpathPos)
1806 {
1807     TileElement* tileElement = map_get_first_element_at(footpathPos);
1808     if (tileElement == nullptr)
1809         return nullptr;
1810     do
1811     {
1812         if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
1813             continue;
1814         if (footpathPos.z != tileElement->GetBaseZ())
1815             continue;
1816         if (tileElement->AsPath()->IsQueue())
1817             continue;
1818         if (tileElement->AsPath()->IsSloped())
1819             continue;
1820         return tileElement;
1821     } while (!(tileElement++)->IsLastForTile());
1822 
1823     return nullptr;
1824 }
1825 
1826 /**
1827  *
1828  *  rct2: 0x006A87BB
1829  */
footpath_update_path_wide_flags(const CoordsXY & footpathPos)1830 void footpath_update_path_wide_flags(const CoordsXY& footpathPos)
1831 {
1832     if (map_is_location_at_edge(footpathPos))
1833         return;
1834 
1835     footpath_clear_wide(footpathPos);
1836     /* Rather than clearing the wide flag of the following tiles and
1837      * checking the state of them later, leave them intact and assume
1838      * they were cleared. Consequently only the wide flag for this single
1839      * tile is modified by this update.
1840      * This is important for avoiding glitches in pathfinding that occurs
1841      * between the batches of updates to the path wide flags.
1842      * Corresponding pathList[] indexes for the following tiles
1843      * are: 2, 3, 4, 5.
1844      * Note: indexes 3, 4, 5 are reset in the current call;
1845      *       index 2 is reset in the previous call. */
1846     // x += 0x20;
1847     // footpath_clear_wide(x, y);
1848     // y += 0x20;
1849     // footpath_clear_wide(x, y);
1850     // x -= 0x20;
1851     // footpath_clear_wide(x, y);
1852     // y -= 0x20;
1853 
1854     TileElement* tileElement = map_get_first_element_at(footpathPos);
1855     if (tileElement == nullptr)
1856         return;
1857     do
1858     {
1859         if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
1860             continue;
1861 
1862         if (tileElement->AsPath()->IsQueue())
1863             continue;
1864 
1865         if (tileElement->AsPath()->IsSloped())
1866             continue;
1867 
1868         if (tileElement->AsPath()->GetEdges() == 0)
1869             continue;
1870 
1871         auto height = tileElement->GetBaseZ();
1872 
1873         // pathList is a list of elements, set by sub_6A8ACF adjacent to x,y
1874         // Spanned from 0x00F3EFA8 to 0x00F3EFC7 (8 elements) in the original
1875         std::array<TileElement*, 8> pathList;
1876 
1877         for (int32_t direction = 0; direction < 8; ++direction)
1878         {
1879             auto footpathLoc = CoordsXYZ(footpathPos + CoordsDirectionDelta[direction], height);
1880             pathList[direction] = footpath_can_be_wide(footpathLoc);
1881         }
1882 
1883         uint8_t pathConnections = 0;
1884         if (tileElement->AsPath()->GetEdges() & EDGE_NW)
1885         {
1886             pathConnections |= FOOTPATH_CONNECTION_NW;
1887             if (pathList[3] != nullptr && pathList[3]->AsPath()->IsWide())
1888             {
1889                 pathConnections &= ~FOOTPATH_CONNECTION_NW;
1890             }
1891         }
1892 
1893         if (tileElement->AsPath()->GetEdges() & EDGE_NE)
1894         {
1895             pathConnections |= FOOTPATH_CONNECTION_NE;
1896             if (pathList[0] != nullptr && pathList[0]->AsPath()->IsWide())
1897             {
1898                 pathConnections &= ~FOOTPATH_CONNECTION_NE;
1899             }
1900         }
1901 
1902         if (tileElement->AsPath()->GetEdges() & EDGE_SE)
1903         {
1904             pathConnections |= FOOTPATH_CONNECTION_SE;
1905             /* In the following:
1906              * footpath_element_is_wide(pathList[1])
1907              * is always false due to the tile update order
1908              * in combination with reset tiles.
1909              * Commented out since it will never occur. */
1910             // if (pathList[1] != nullptr) {
1911             //  if (footpath_element_is_wide(pathList[1])) {
1912             //      pathConnections &= ~FOOTPATH_CONNECTION_SE;
1913             //  }
1914             //}
1915         }
1916 
1917         if (tileElement->AsPath()->GetEdges() & EDGE_SW)
1918         {
1919             pathConnections |= FOOTPATH_CONNECTION_SW;
1920             /* In the following:
1921              * footpath_element_is_wide(pathList[2])
1922              * is always false due to the tile update order
1923              * in combination with reset tiles.
1924              * Commented out since it will never occur. */
1925             // if (pathList[2] != nullptr) {
1926             //  if (footpath_element_is_wide(pathList[2])) {
1927             //      pathConnections &= ~FOOTPATH_CONNECTION_SW;
1928             //  }
1929             //}
1930         }
1931 
1932         if ((pathConnections & FOOTPATH_CONNECTION_NW) && pathList[3] != nullptr && !pathList[3]->AsPath()->IsWide())
1933         {
1934             constexpr uint8_t edgeMask1 = EDGE_SE | EDGE_SW;
1935             if ((pathConnections & FOOTPATH_CONNECTION_NE) && pathList[7] != nullptr && !pathList[7]->AsPath()->IsWide()
1936                 && (pathList[7]->AsPath()->GetEdges() & edgeMask1) == edgeMask1 && pathList[0] != nullptr
1937                 && !pathList[0]->AsPath()->IsWide())
1938             {
1939                 pathConnections |= FOOTPATH_CONNECTION_S;
1940             }
1941 
1942             /* In the following:
1943              * footpath_element_is_wide(pathList[2])
1944              * is always false due to the tile update order
1945              * in combination with reset tiles.
1946              * Short circuit the logic appropriately. */
1947             constexpr uint8_t edgeMask2 = EDGE_NE | EDGE_SE;
1948             if ((pathConnections & FOOTPATH_CONNECTION_SW) && pathList[6] != nullptr && !(pathList[6])->AsPath()->IsWide()
1949                 && (pathList[6]->AsPath()->GetEdges() & edgeMask2) == edgeMask2 && pathList[2] != nullptr)
1950             {
1951                 pathConnections |= FOOTPATH_CONNECTION_E;
1952             }
1953         }
1954 
1955         /* In the following:
1956          * footpath_element_is_wide(pathList[4])
1957          * footpath_element_is_wide(pathList[1])
1958          * are always false due to the tile update order
1959          * in combination with reset tiles.
1960          * Short circuit the logic appropriately. */
1961         if ((pathConnections & FOOTPATH_CONNECTION_SE) && pathList[1] != nullptr)
1962         {
1963             constexpr uint8_t edgeMask1 = EDGE_SW | EDGE_NW;
1964             if ((pathConnections & FOOTPATH_CONNECTION_NE) && (pathList[4] != nullptr)
1965                 && (pathList[4]->AsPath()->GetEdges() & edgeMask1) == edgeMask1 && pathList[0] != nullptr
1966                 && !pathList[0]->AsPath()->IsWide())
1967             {
1968                 pathConnections |= FOOTPATH_CONNECTION_W;
1969             }
1970 
1971             /* In the following:
1972              * footpath_element_is_wide(pathList[5])
1973              * footpath_element_is_wide(pathList[2])
1974              * are always false due to the tile update order
1975              * in combination with reset tiles.
1976              * Short circuit the logic appropriately. */
1977             constexpr uint8_t edgeMask2 = EDGE_NE | EDGE_NW;
1978             if ((pathConnections & FOOTPATH_CONNECTION_SW) && pathList[5] != nullptr
1979                 && (pathList[5]->AsPath()->GetEdges() & edgeMask2) == edgeMask2 && pathList[2] != nullptr)
1980             {
1981                 pathConnections |= FOOTPATH_CONNECTION_N;
1982             }
1983         }
1984 
1985         if ((pathConnections & FOOTPATH_CONNECTION_NW) && (pathConnections & (FOOTPATH_CONNECTION_E | FOOTPATH_CONNECTION_S)))
1986         {
1987             pathConnections &= ~FOOTPATH_CONNECTION_NW;
1988         }
1989 
1990         if ((pathConnections & FOOTPATH_CONNECTION_NE) && (pathConnections & (FOOTPATH_CONNECTION_W | FOOTPATH_CONNECTION_S)))
1991         {
1992             pathConnections &= ~FOOTPATH_CONNECTION_NE;
1993         }
1994 
1995         if ((pathConnections & FOOTPATH_CONNECTION_SE) && (pathConnections & (FOOTPATH_CONNECTION_N | FOOTPATH_CONNECTION_W)))
1996         {
1997             pathConnections &= ~FOOTPATH_CONNECTION_SE;
1998         }
1999 
2000         if ((pathConnections & FOOTPATH_CONNECTION_SW) && (pathConnections & (FOOTPATH_CONNECTION_E | FOOTPATH_CONNECTION_N)))
2001         {
2002             pathConnections &= ~FOOTPATH_CONNECTION_SW;
2003         }
2004 
2005         if (!(pathConnections
2006               & (FOOTPATH_CONNECTION_NE | FOOTPATH_CONNECTION_SE | FOOTPATH_CONNECTION_SW | FOOTPATH_CONNECTION_NW)))
2007         {
2008             uint8_t e = tileElement->AsPath()->GetEdgesAndCorners();
2009             if ((e != 0b10101111) && (e != 0b01011111) && (e != 0b11101111))
2010                 tileElement->AsPath()->SetWide(true);
2011         }
2012     } while (!(tileElement++)->IsLastForTile());
2013 }
2014 
footpath_is_blocked_by_vehicle(const TileCoordsXYZ & position)2015 bool footpath_is_blocked_by_vehicle(const TileCoordsXYZ& position)
2016 {
2017     auto pathElement = map_get_path_element_at(position);
2018     return pathElement != nullptr && pathElement->IsBlockedByVehicle();
2019 }
2020 
2021 /**
2022  *
2023  *  rct2: 0x006A7642
2024  */
footpath_update_queue_entrance_banner(const CoordsXY & footpathPos,TileElement * tileElement)2025 void footpath_update_queue_entrance_banner(const CoordsXY& footpathPos, TileElement* tileElement)
2026 {
2027     int32_t elementType = tileElement->GetType();
2028     switch (elementType)
2029     {
2030         case TILE_ELEMENT_TYPE_PATH:
2031             if (tileElement->AsPath()->IsQueue())
2032             {
2033                 footpath_queue_chain_push(tileElement->AsPath()->GetRideIndex());
2034                 for (int32_t direction = 0; direction < 4; direction++)
2035                 {
2036                     if (tileElement->AsPath()->GetEdges() & (1 << direction))
2037                     {
2038                         footpath_chain_ride_queue(RIDE_ID_NULL, 0, footpathPos, tileElement, direction);
2039                     }
2040                 }
2041                 tileElement->AsPath()->SetRideIndex(RIDE_ID_NULL);
2042             }
2043             break;
2044         case TILE_ELEMENT_TYPE_ENTRANCE:
2045             if (tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_RIDE_ENTRANCE)
2046             {
2047                 footpath_queue_chain_push(tileElement->AsEntrance()->GetRideIndex());
2048                 footpath_chain_ride_queue(
2049                     RIDE_ID_NULL, 0, footpathPos, tileElement, direction_reverse(tileElement->GetDirection()));
2050             }
2051             break;
2052     }
2053 }
2054 
2055 /**
2056  *
2057  *  rct2: 0x006A6B7F
2058  */
footpath_remove_edges_towards_here(const CoordsXYZ & footpathPos,int32_t direction,TileElement * tileElement,bool isQueue)2059 static void footpath_remove_edges_towards_here(
2060     const CoordsXYZ& footpathPos, int32_t direction, TileElement* tileElement, bool isQueue)
2061 {
2062     if (tileElement->AsPath()->IsQueue())
2063     {
2064         footpath_queue_chain_push(tileElement->AsPath()->GetRideIndex());
2065     }
2066 
2067     auto d = direction_reverse(direction);
2068     tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() & ~(1 << d));
2069     int32_t cd = ((d - 1) & 3);
2070     tileElement->AsPath()->SetCorners(tileElement->AsPath()->GetCorners() & ~(1 << cd));
2071     cd = ((cd + 1) & 3);
2072     tileElement->AsPath()->SetCorners(tileElement->AsPath()->GetCorners() & ~(1 << cd));
2073     map_invalidate_tile({ footpathPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() });
2074 
2075     if (isQueue)
2076         footpath_disconnect_queue_from_path(footpathPos, tileElement, -1);
2077 
2078     Direction shiftedDirection = (direction + 1) & 3;
2079     auto targetFootPathPos = CoordsXYZ{ CoordsXY{ footpathPos } + CoordsDirectionDelta[shiftedDirection], footpathPos.z };
2080 
2081     tileElement = map_get_first_element_at(targetFootPathPos);
2082     if (tileElement == nullptr)
2083         return;
2084     do
2085     {
2086         if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
2087             continue;
2088         if (tileElement->GetBaseZ() != targetFootPathPos.z)
2089             continue;
2090 
2091         if (tileElement->AsPath()->IsSloped())
2092             break;
2093 
2094         cd = ((shiftedDirection + 1) & 3);
2095         tileElement->AsPath()->SetCorners(tileElement->AsPath()->GetCorners() & ~(1 << cd));
2096         map_invalidate_tile({ targetFootPathPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() });
2097         break;
2098     } while (!(tileElement++)->IsLastForTile());
2099 }
2100 
2101 /**
2102  *
2103  *  rct2: 0x006A6B14
2104  */
footpath_remove_edges_towards(const CoordsXYRangedZ & footPathPos,int32_t direction,bool isQueue)2105 static void footpath_remove_edges_towards(const CoordsXYRangedZ& footPathPos, int32_t direction, bool isQueue)
2106 {
2107     if (!map_is_location_valid(footPathPos))
2108     {
2109         return;
2110     }
2111 
2112     TileElement* tileElement = map_get_first_element_at(footPathPos);
2113     if (tileElement == nullptr)
2114         return;
2115     do
2116     {
2117         if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
2118             continue;
2119 
2120         if (footPathPos.clearanceZ == tileElement->GetBaseZ())
2121         {
2122             if (tileElement->AsPath()->IsSloped())
2123             {
2124                 uint8_t slope = tileElement->AsPath()->GetSlopeDirection();
2125                 if (slope != direction)
2126                     break;
2127             }
2128             footpath_remove_edges_towards_here({ footPathPos, footPathPos.clearanceZ }, direction, tileElement, isQueue);
2129             break;
2130         }
2131 
2132         if (footPathPos.baseZ == tileElement->GetBaseZ())
2133         {
2134             if (!tileElement->AsPath()->IsSloped())
2135                 break;
2136 
2137             uint8_t slope = direction_reverse(tileElement->AsPath()->GetSlopeDirection());
2138             if (slope != direction)
2139                 break;
2140 
2141             footpath_remove_edges_towards_here({ footPathPos, footPathPos.clearanceZ }, direction, tileElement, isQueue);
2142             break;
2143         }
2144     } while (!(tileElement++)->IsLastForTile());
2145 }
2146 
2147 // Returns true when there is an element at the given coordinates that want to connect to a path with the given direction (ride
2148 // entrances and exits, shops, paths).
tile_element_wants_path_connection_towards(const TileCoordsXYZD & coords,const TileElement * const elementToBeRemoved)2149 bool tile_element_wants_path_connection_towards(const TileCoordsXYZD& coords, const TileElement* const elementToBeRemoved)
2150 {
2151     TileElement* tileElement = map_get_first_element_at(coords);
2152     if (tileElement == nullptr)
2153         return false;
2154     do
2155     {
2156         // Don't check the element that gets removed
2157         if (tileElement == elementToBeRemoved)
2158             continue;
2159 
2160         switch (tileElement->GetType())
2161         {
2162             case TILE_ELEMENT_TYPE_PATH:
2163                 if (tileElement->base_height == coords.z)
2164                 {
2165                     if (!tileElement->AsPath()->IsSloped())
2166                         // The footpath is flat, it can be connected to from any direction
2167                         return true;
2168                     if (tileElement->AsPath()->GetSlopeDirection() == direction_reverse(coords.direction))
2169                         // The footpath is sloped and its lowest point matches the edge connection
2170                         return true;
2171                 }
2172                 else if (tileElement->base_height + 2 == coords.z)
2173                 {
2174                     if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == coords.direction)
2175                         // The footpath is sloped and its higher point matches the edge connection
2176                         return true;
2177                 }
2178                 break;
2179             case TILE_ELEMENT_TYPE_TRACK:
2180                 if (tileElement->base_height == coords.z)
2181                 {
2182                     auto ride = get_ride(tileElement->AsTrack()->GetRideIndex());
2183                     if (ride == nullptr)
2184                         continue;
2185 
2186                     if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE))
2187                         break;
2188 
2189                     const auto trackType = tileElement->AsTrack()->GetTrackType();
2190                     const uint8_t trackSequence = tileElement->AsTrack()->GetSequenceIndex();
2191                     const auto& ted = GetTrackElementDescriptor(trackType);
2192                     if (ted.SequenceProperties[trackSequence] & TRACK_SEQUENCE_FLAG_CONNECTS_TO_PATH)
2193                     {
2194                         uint16_t dx = ((coords.direction - tileElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK);
2195                         if (ted.SequenceProperties[trackSequence] & (1 << dx))
2196                         {
2197                             // Track element has the flags required for the given direction
2198                             return true;
2199                         }
2200                     }
2201                 }
2202                 break;
2203             case TILE_ELEMENT_TYPE_ENTRANCE:
2204                 if (tileElement->base_height == coords.z)
2205                 {
2206                     if (entrance_has_direction(*(tileElement->AsEntrance()), coords.direction - tileElement->GetDirection()))
2207                     {
2208                         // Entrance wants to be connected towards the given direction
2209                         return true;
2210                     }
2211                 }
2212                 break;
2213             default:
2214                 break;
2215         }
2216     } while (!(tileElement++)->IsLastForTile());
2217 
2218     return false;
2219 }
2220 
2221 // fix up the corners around the given path element that gets removed
footpath_fix_corners_around(const TileCoordsXY & footpathPos,TileElement * pathElement)2222 static void footpath_fix_corners_around(const TileCoordsXY& footpathPos, TileElement* pathElement)
2223 {
2224     // A mask for the paths' corners of each possible neighbour
2225     static constexpr uint8_t cornersTouchingTile[3][3] = {
2226         { 0b0010, 0b0011, 0b0001 },
2227         { 0b0110, 0b0000, 0b1001 },
2228         { 0b0100, 0b1100, 0b1000 },
2229     };
2230 
2231     // Sloped paths don't create filled corners, so no need to remove any
2232     if (pathElement->GetType() == TILE_ELEMENT_TYPE_PATH && pathElement->AsPath()->IsSloped())
2233         return;
2234 
2235     for (int32_t xOffset = -1; xOffset <= 1; xOffset++)
2236     {
2237         for (int32_t yOffset = -1; yOffset <= 1; yOffset++)
2238         {
2239             // Skip self
2240             if (xOffset == 0 && yOffset == 0)
2241                 continue;
2242 
2243             TileElement* tileElement = map_get_first_element_at(
2244                 TileCoordsXY{ footpathPos.x + xOffset, footpathPos.y + yOffset }.ToCoordsXY());
2245             if (tileElement == nullptr)
2246                 continue;
2247             do
2248             {
2249                 if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
2250                     continue;
2251                 if (tileElement->AsPath()->IsSloped())
2252                     continue;
2253                 if (tileElement->base_height != pathElement->base_height)
2254                     continue;
2255 
2256                 const int32_t ix = xOffset + 1;
2257                 const int32_t iy = yOffset + 1;
2258                 tileElement->AsPath()->SetCorners(tileElement->AsPath()->GetCorners() & ~(cornersTouchingTile[iy][ix]));
2259             } while (!(tileElement++)->IsLastForTile());
2260         }
2261     }
2262 }
2263 
2264 /**
2265  *
2266  *  rct2: 0x006A6AA7
2267  * @param x x-coordinate in units (not tiles)
2268  * @param y y-coordinate in units (not tiles)
2269  */
footpath_remove_edges_at(const CoordsXY & footpathPos,TileElement * tileElement)2270 void footpath_remove_edges_at(const CoordsXY& footpathPos, TileElement* tileElement)
2271 {
2272     if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
2273     {
2274         auto rideIndex = tileElement->AsTrack()->GetRideIndex();
2275         auto ride = get_ride(rideIndex);
2276         if (ride == nullptr)
2277             return;
2278 
2279         if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE))
2280             return;
2281     }
2282 
2283     footpath_update_queue_entrance_banner(footpathPos, tileElement);
2284 
2285     bool fixCorners = false;
2286     for (uint8_t direction = 0; direction < 4; direction++)
2287     {
2288         int32_t z1 = tileElement->base_height;
2289         if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
2290         {
2291             if (tileElement->AsPath()->IsSloped())
2292             {
2293                 int32_t slope = tileElement->AsPath()->GetSlopeDirection();
2294                 // Sloped footpaths don't connect sideways
2295                 if ((slope - direction) & 1)
2296                     continue;
2297 
2298                 // When a path is sloped, the higher point of the path is 2 units higher
2299                 z1 += (slope == direction) ? 2 : 0;
2300             }
2301         }
2302 
2303         // When clearance checks were disabled a neighbouring path can be connected to both the path-ghost and to something
2304         // else, so before removing edges from neighbouring paths we have to make sure there is nothing else they are connected
2305         // to.
2306         if (!tile_element_wants_path_connection_towards({ TileCoordsXY{ footpathPos }, z1, direction }, tileElement))
2307         {
2308             bool isQueue = tileElement->GetType() == TILE_ELEMENT_TYPE_PATH ? tileElement->AsPath()->IsQueue() : false;
2309             int32_t z0 = z1 - 2;
2310             footpath_remove_edges_towards(
2311                 { footpathPos + CoordsDirectionDelta[direction], z0 * COORDS_Z_STEP, z1 * COORDS_Z_STEP }, direction, isQueue);
2312         }
2313         else
2314         {
2315             // A footpath may stay connected, but its edges must be fixed later on when another edge does get removed.
2316             fixCorners = true;
2317         }
2318     }
2319 
2320     // Only fix corners when needed, to avoid changing corners that have been set for its looks.
2321     if (fixCorners && tileElement->IsGhost())
2322     {
2323         auto tileFootpathPos = TileCoordsXY{ footpathPos };
2324         footpath_fix_corners_around(tileFootpathPos, tileElement);
2325     }
2326 
2327     if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
2328         tileElement->AsPath()->SetEdgesAndCorners(0);
2329 }
2330 
GetLegacyFootpathEntry(ObjectEntryIndex entryIndex)2331 const FootpathObject* GetLegacyFootpathEntry(ObjectEntryIndex entryIndex)
2332 {
2333     auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
2334     auto obj = objMgr.GetLoadedObject(ObjectType::Paths, entryIndex);
2335     if (obj == nullptr)
2336         return nullptr;
2337 
2338     const FootpathObject* footpathObject = (static_cast<FootpathObject*>(obj));
2339     return footpathObject;
2340 }
2341 
GetPathSurfaceEntry(ObjectEntryIndex entryIndex)2342 const FootpathSurfaceObject* GetPathSurfaceEntry(ObjectEntryIndex entryIndex)
2343 {
2344     auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
2345     auto obj = objMgr.GetLoadedObject(ObjectType::FootpathSurface, entryIndex);
2346     if (obj == nullptr)
2347         return nullptr;
2348 
2349     return static_cast<FootpathSurfaceObject*>(obj);
2350 }
2351 
GetPathRailingsEntry(ObjectEntryIndex entryIndex)2352 const FootpathRailingsObject* GetPathRailingsEntry(ObjectEntryIndex entryIndex)
2353 {
2354     auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
2355     auto obj = objMgr.GetLoadedObject(ObjectType::FootpathRailings, entryIndex);
2356     if (obj == nullptr)
2357         return nullptr;
2358 
2359     return static_cast<FootpathRailingsObject*>(obj);
2360 }
2361 
GetRideIndex() const2362 ride_id_t PathElement::GetRideIndex() const
2363 {
2364     return rideIndex;
2365 }
2366 
SetRideIndex(ride_id_t newRideIndex)2367 void PathElement::SetRideIndex(ride_id_t newRideIndex)
2368 {
2369     rideIndex = newRideIndex;
2370 }
2371 
GetAdditionStatus() const2372 uint8_t PathElement::GetAdditionStatus() const
2373 {
2374     return AdditionStatus;
2375 }
2376 
SetAdditionStatus(uint8_t newStatus)2377 void PathElement::SetAdditionStatus(uint8_t newStatus)
2378 {
2379     AdditionStatus = newStatus;
2380 }
2381 
GetEdges() const2382 uint8_t PathElement::GetEdges() const
2383 {
2384     return EdgesAndCorners & FOOTPATH_PROPERTIES_EDGES_EDGES_MASK;
2385 }
2386 
SetEdges(uint8_t newEdges)2387 void PathElement::SetEdges(uint8_t newEdges)
2388 {
2389     EdgesAndCorners &= ~FOOTPATH_PROPERTIES_EDGES_EDGES_MASK;
2390     EdgesAndCorners |= (newEdges & FOOTPATH_PROPERTIES_EDGES_EDGES_MASK);
2391 }
2392 
GetCorners() const2393 uint8_t PathElement::GetCorners() const
2394 {
2395     return EdgesAndCorners >> 4;
2396 }
2397 
SetCorners(uint8_t newCorners)2398 void PathElement::SetCorners(uint8_t newCorners)
2399 {
2400     EdgesAndCorners &= ~FOOTPATH_PROPERTIES_EDGES_CORNERS_MASK;
2401     EdgesAndCorners |= (newCorners << 4);
2402 }
2403 
GetEdgesAndCorners() const2404 uint8_t PathElement::GetEdgesAndCorners() const
2405 {
2406     return EdgesAndCorners;
2407 }
2408 
SetEdgesAndCorners(uint8_t newEdgesAndCorners)2409 void PathElement::SetEdgesAndCorners(uint8_t newEdgesAndCorners)
2410 {
2411     EdgesAndCorners = newEdgesAndCorners;
2412 }
2413 
IsLevelCrossing(const CoordsXY & coords) const2414 bool PathElement::IsLevelCrossing(const CoordsXY& coords) const
2415 {
2416     auto trackElement = map_get_track_element_at({ coords, GetBaseZ() });
2417     if (trackElement == nullptr)
2418     {
2419         return false;
2420     }
2421 
2422     if (trackElement->GetTrackType() != TrackElemType::Flat)
2423     {
2424         return false;
2425     }
2426 
2427     auto ride = get_ride(trackElement->GetRideIndex());
2428     if (ride == nullptr)
2429     {
2430         return false;
2431     }
2432 
2433     return ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_SUPPORTS_LEVEL_CROSSINGS);
2434 }
2435