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, ¤tTile.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, ¤tTile.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