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 "Map.h"
11 
12 #include "../Cheats.h"
13 #include "../Context.h"
14 #include "../Game.h"
15 #include "../Input.h"
16 #include "../OpenRCT2.h"
17 #include "../actions/BannerRemoveAction.h"
18 #include "../actions/FootpathRemoveAction.h"
19 #include "../actions/LandLowerAction.h"
20 #include "../actions/LandRaiseAction.h"
21 #include "../actions/LandSetHeightAction.h"
22 #include "../actions/LandSetRightsAction.h"
23 #include "../actions/LargeSceneryRemoveAction.h"
24 #include "../actions/ParkEntranceRemoveAction.h"
25 #include "../actions/SmallSceneryRemoveAction.h"
26 #include "../actions/WallRemoveAction.h"
27 #include "../actions/WaterSetHeightAction.h"
28 #include "../audio/audio.h"
29 #include "../config/Config.h"
30 #include "../core/Guard.hpp"
31 #include "../interface/Cursors.h"
32 #include "../interface/Window.h"
33 #include "../localisation/Date.h"
34 #include "../localisation/Localisation.h"
35 #include "../management/Finance.h"
36 #include "../network/network.h"
37 #include "../object/ObjectManager.h"
38 #include "../object/TerrainSurfaceObject.h"
39 #include "../ride/RideData.h"
40 #include "../ride/Track.h"
41 #include "../ride/TrackData.h"
42 #include "../ride/TrackDesign.h"
43 #include "../scenario/Scenario.h"
44 #include "../util/Util.h"
45 #include "../windows/Intent.h"
46 #include "Banner.h"
47 #include "Climate.h"
48 #include "Footpath.h"
49 #include "LargeScenery.h"
50 #include "MapAnimation.h"
51 #include "Park.h"
52 #include "Scenery.h"
53 #include "SmallScenery.h"
54 #include "Surface.h"
55 #include "TileElementsView.h"
56 #include "TileInspector.h"
57 #include "Wall.h"
58 
59 #include <algorithm>
60 #include <iterator>
61 #include <memory>
62 
63 using namespace OpenRCT2;
64 
65 /**
66  * Replaces 0x00993CCC, 0x00993CCE
67  */
68 // clang-format off
69 const std::array<CoordsXY, 8> CoordsDirectionDelta = {
70     CoordsXY{ -COORDS_XY_STEP, 0 },
71     CoordsXY{               0, +COORDS_XY_STEP },
72     CoordsXY{ +COORDS_XY_STEP, 0 },
73     CoordsXY{               0, -COORDS_XY_STEP },
74     CoordsXY{ -COORDS_XY_STEP, +COORDS_XY_STEP },
75     CoordsXY{ +COORDS_XY_STEP, +COORDS_XY_STEP },
76     CoordsXY{ +COORDS_XY_STEP, -COORDS_XY_STEP },
77     CoordsXY{ -COORDS_XY_STEP, -COORDS_XY_STEP }
78 };
79 // clang-format on
80 
81 const TileCoordsXY TileDirectionDelta[] = {
82     { -1, 0 }, { 0, +1 }, { +1, 0 }, { 0, -1 }, { -1, +1 }, { +1, +1 }, { +1, -1 }, { -1, -1 },
83 };
84 
85 constexpr size_t MIN_TILE_ELEMENTS = 1024;
86 
87 uint16_t gMapSelectFlags;
88 uint16_t gMapSelectType;
89 CoordsXY gMapSelectPositionA;
90 CoordsXY gMapSelectPositionB;
91 CoordsXYZ gMapSelectArrowPosition;
92 uint8_t gMapSelectArrowDirection;
93 
94 TileCoordsXY gWidePathTileLoopPosition;
95 uint16_t gGrassSceneryTileLoopPosition;
96 
97 int32_t gMapSize;
98 int32_t gMapBaseZ;
99 
100 std::vector<CoordsXY> gMapSelectionTiles;
101 std::vector<PeepSpawn> gPeepSpawns;
102 
103 bool gLandMountainMode;
104 bool gLandPaintMode;
105 bool gClearSmallScenery;
106 bool gClearLargeScenery;
107 bool gClearFootpath;
108 
109 uint32_t gLandRemainingOwnershipSales;
110 uint32_t gLandRemainingConstructionSales;
111 
112 bool gMapLandRightsUpdateSuccess;
113 
114 static TilePointerIndex<TileElement> _tileIndex;
115 static std::vector<TileElement> _tileElements;
116 static TilePointerIndex<TileElement> _tileIndexStash;
117 static std::vector<TileElement> _tileElementsStash;
118 static size_t _tileElementsInUse;
119 static size_t _tileElementsInUseStash;
120 static int32_t _mapSizeStash;
121 static int32_t _currentRotationStash;
122 
StashMap()123 void StashMap()
124 {
125     _tileIndexStash = std::move(_tileIndex);
126     _tileElementsStash = std::move(_tileElements);
127     _mapSizeStash = gMapSize;
128     _currentRotationStash = gCurrentRotation;
129     _tileElementsInUseStash = _tileElementsInUse;
130 }
131 
UnstashMap()132 void UnstashMap()
133 {
134     _tileIndex = std::move(_tileIndexStash);
135     _tileElements = std::move(_tileElementsStash);
136     gMapSize = _mapSizeStash;
137     gCurrentRotation = _currentRotationStash;
138     _tileElementsInUse = _tileElementsInUseStash;
139 }
140 
GetTileElements()141 const std::vector<TileElement>& GetTileElements()
142 {
143     return _tileElements;
144 }
145 
SetTileElements(std::vector<TileElement> && tileElements)146 void SetTileElements(std::vector<TileElement>&& tileElements)
147 {
148     _tileElements = std::move(tileElements);
149     _tileIndex = TilePointerIndex<TileElement>(MAXIMUM_MAP_SIZE_TECHNICAL, _tileElements.data());
150     _tileElementsInUse = _tileElements.size();
151 }
152 
GetDefaultSurfaceElement()153 static TileElement GetDefaultSurfaceElement()
154 {
155     TileElement el;
156     el.ClearAs(TILE_ELEMENT_TYPE_SURFACE);
157     el.SetLastForTile(true);
158     el.base_height = 14;
159     el.clearance_height = 14;
160     el.AsSurface()->SetWaterHeight(0);
161     el.AsSurface()->SetSlope(TILE_ELEMENT_SLOPE_FLAT);
162     el.AsSurface()->SetGrassLength(GRASS_LENGTH_CLEAR_0);
163     el.AsSurface()->SetOwnership(OWNERSHIP_UNOWNED);
164     el.AsSurface()->SetParkFences(0);
165     el.AsSurface()->SetSurfaceStyle(0);
166     el.AsSurface()->SetEdgeStyle(0);
167     return el;
168 }
169 
GetReorganisedTileElementsWithoutGhosts()170 std::vector<TileElement> GetReorganisedTileElementsWithoutGhosts()
171 {
172     std::vector<TileElement> newElements;
173     newElements.reserve(std::max(MIN_TILE_ELEMENTS, _tileElements.size()));
174     for (int32_t y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
175     {
176         for (int32_t x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
177         {
178             auto oldSize = newElements.size();
179 
180             // Add all non-ghost elements
181             const auto* element = map_get_first_element_at(TileCoordsXY{ x, y }.ToCoordsXY());
182             if (element != nullptr)
183             {
184                 do
185                 {
186                     if (!element->IsGhost())
187                     {
188                         newElements.push_back(*element);
189                     }
190                 } while (!(element++)->IsLastForTile());
191             }
192 
193             // Insert default surface element if no elements were added
194             auto newSize = newElements.size();
195             if (oldSize == newSize)
196             {
197                 newElements.push_back(GetDefaultSurfaceElement());
198             }
199 
200             // Ensure last element of tile has last flag set
201             auto& lastEl = newElements.back();
202             lastEl.SetLastForTile(true);
203         }
204     }
205     return newElements;
206 }
207 
ReorganiseTileElements(size_t capacity)208 static void ReorganiseTileElements(size_t capacity)
209 {
210     context_setcurrentcursor(CursorID::ZZZ);
211 
212     std::vector<TileElement> newElements;
213     newElements.reserve(std::max(MIN_TILE_ELEMENTS, capacity));
214     for (int32_t y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
215     {
216         for (int32_t x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
217         {
218             const auto* element = map_get_first_element_at(TileCoordsXY{ x, y });
219             if (element == nullptr)
220             {
221                 newElements.push_back(GetDefaultSurfaceElement());
222             }
223             else
224             {
225                 do
226                 {
227                     newElements.push_back(*element);
228                 } while (!(element++)->IsLastForTile());
229             }
230         }
231     }
232 
233     SetTileElements(std::move(newElements));
234 }
235 
ReorganiseTileElements()236 void ReorganiseTileElements()
237 {
238     ReorganiseTileElements(_tileElements.size());
239 }
240 
map_check_free_elements_and_reorganise(size_t numElementsOnTile,size_t numNewElements)241 static bool map_check_free_elements_and_reorganise(size_t numElementsOnTile, size_t numNewElements)
242 {
243     // Check hard cap on num in use tiles (this would be the size of _tileElements immediately after a reorg)
244     if (_tileElementsInUse + numNewElements > MAX_TILE_ELEMENTS)
245     {
246         return false;
247     }
248 
249     auto totalElementsRequired = numElementsOnTile + numNewElements;
250     auto freeElements = _tileElements.capacity() - _tileElements.size();
251     if (freeElements >= totalElementsRequired)
252     {
253         return true;
254     }
255 
256     // if space issue is due to fragmentation then Reorg Tiles without increasing capacity
257     if (_tileElements.size() > totalElementsRequired + _tileElementsInUse)
258     {
259         ReorganiseTileElements();
260         // This check is not expected to fail
261         freeElements = _tileElements.capacity() - _tileElements.size();
262         if (freeElements >= totalElementsRequired)
263         {
264             return true;
265         }
266     }
267 
268     // Capacity must increase to handle the space (Note capacity can go above MAX_TILE_ELEMENTS)
269     auto newCapacity = _tileElements.capacity() * 2;
270     ReorganiseTileElements(newCapacity);
271     return true;
272 }
273 
274 static size_t CountElementsOnTile(const CoordsXY& loc);
275 
MapCheckCapacityAndReorganise(const CoordsXY & loc,size_t numElements)276 bool MapCheckCapacityAndReorganise(const CoordsXY& loc, size_t numElements)
277 {
278     auto numElementsOnTile = CountElementsOnTile(loc);
279     return map_check_free_elements_and_reorganise(numElementsOnTile, numElements);
280 }
281 
282 static void clear_elements_at(const CoordsXY& loc);
283 static ScreenCoordsXY translate_3d_to_2d(int32_t rotation, const CoordsXY& pos);
284 
tile_element_iterator_begin(tile_element_iterator * it)285 void tile_element_iterator_begin(tile_element_iterator* it)
286 {
287     it->x = 0;
288     it->y = 0;
289     it->element = map_get_first_element_at(TileCoordsXY{ 0, 0 });
290 }
291 
tile_element_iterator_next(tile_element_iterator * it)292 int32_t tile_element_iterator_next(tile_element_iterator* it)
293 {
294     if (it->element == nullptr)
295     {
296         it->element = map_get_first_element_at(TileCoordsXY{ it->x, it->y });
297         return 1;
298     }
299 
300     if (!it->element->IsLastForTile())
301     {
302         it->element++;
303         return 1;
304     }
305 
306     if (it->x < (MAXIMUM_MAP_SIZE_TECHNICAL - 1))
307     {
308         it->x++;
309         it->element = map_get_first_element_at(TileCoordsXY{ it->x, it->y });
310         return 1;
311     }
312 
313     if (it->y < (MAXIMUM_MAP_SIZE_TECHNICAL - 1))
314     {
315         it->x = 0;
316         it->y++;
317         it->element = map_get_first_element_at(TileCoordsXY{ it->x, it->y });
318         return 1;
319     }
320 
321     return 0;
322 }
323 
tile_element_iterator_restart_for_tile(tile_element_iterator * it)324 void tile_element_iterator_restart_for_tile(tile_element_iterator* it)
325 {
326     it->element = nullptr;
327 }
328 
IsTileLocationValid(const TileCoordsXY & coords)329 static bool IsTileLocationValid(const TileCoordsXY& coords)
330 {
331     const bool is_x_valid = coords.x < MAXIMUM_MAP_SIZE_TECHNICAL && coords.x >= 0;
332     const bool is_y_valid = coords.y < MAXIMUM_MAP_SIZE_TECHNICAL && coords.y >= 0;
333     return is_x_valid && is_y_valid;
334 }
335 
map_get_first_element_at(const TileCoordsXY & tilePos)336 TileElement* map_get_first_element_at(const TileCoordsXY& tilePos)
337 {
338     if (!IsTileLocationValid(tilePos))
339     {
340         log_verbose("Trying to access element outside of range");
341         return nullptr;
342     }
343     return _tileIndex.GetFirstElementAt(tilePos);
344 }
345 
map_get_first_element_at(const CoordsXY & elementPos)346 TileElement* map_get_first_element_at(const CoordsXY& elementPos)
347 {
348     return map_get_first_element_at(TileCoordsXY{ elementPos });
349 }
350 
map_get_nth_element_at(const CoordsXY & coords,int32_t n)351 TileElement* map_get_nth_element_at(const CoordsXY& coords, int32_t n)
352 {
353     TileElement* tileElement = map_get_first_element_at(coords);
354     if (tileElement == nullptr)
355     {
356         return nullptr;
357     }
358     // Iterate through elements on this tile. This has to be walked, rather than
359     // jumped directly to, because n may exceed element count for given tile,
360     // and the order of tiles (unlike elements) is not synced over multiplayer.
361     while (n >= 0)
362     {
363         if (n == 0)
364         {
365             return tileElement;
366         }
367         if (tileElement->IsLastForTile())
368         {
369             break;
370         }
371         tileElement++;
372         n--;
373     }
374     // The element sought for is not within given tile.
375     return nullptr;
376 }
377 
map_set_tile_element(const TileCoordsXY & tilePos,TileElement * elements)378 void map_set_tile_element(const TileCoordsXY& tilePos, TileElement* elements)
379 {
380     if (!map_is_location_valid(tilePos.ToCoordsXY()))
381     {
382         log_error("Trying to access element outside of range");
383         return;
384     }
385     _tileIndex.SetTile(tilePos, elements);
386 }
387 
map_get_surface_element_at(const CoordsXY & coords)388 SurfaceElement* map_get_surface_element_at(const CoordsXY& coords)
389 {
390     auto view = TileElementsView<SurfaceElement>(coords);
391 
392     return *view.begin();
393 }
394 
map_get_path_element_at(const TileCoordsXYZ & loc)395 PathElement* map_get_path_element_at(const TileCoordsXYZ& loc)
396 {
397     for (auto* element : TileElementsView<PathElement>(loc.ToCoordsXY()))
398     {
399         if (element->IsGhost())
400             continue;
401         if (element->base_height != loc.z)
402             continue;
403         return element;
404     }
405     return nullptr;
406 }
407 
map_get_banner_element_at(const CoordsXYZ & bannerPos,uint8_t position)408 BannerElement* map_get_banner_element_at(const CoordsXYZ& bannerPos, uint8_t position)
409 {
410     const auto bannerTilePos = TileCoordsXYZ{ bannerPos };
411     for (auto* element : TileElementsView<BannerElement>(bannerPos))
412     {
413         if (element->base_height != bannerTilePos.z)
414             continue;
415         if (element->GetPosition() != position)
416             continue;
417         return element;
418     }
419     return nullptr;
420 }
421 
422 /**
423  *
424  *  rct2: 0x0068AB4C
425  */
map_init(int32_t size)426 void map_init(int32_t size)
427 {
428     auto numTiles = MAXIMUM_MAP_SIZE_TECHNICAL * MAXIMUM_MAP_SIZE_TECHNICAL;
429 
430     std::vector<TileElement> tileElements;
431     tileElements.resize(numTiles);
432     for (int32_t i = 0; i < numTiles; i++)
433     {
434         auto* element = &tileElements[i];
435         element->ClearAs(TILE_ELEMENT_TYPE_SURFACE);
436         element->SetLastForTile(true);
437         element->base_height = 14;
438         element->clearance_height = 14;
439         element->AsSurface()->SetWaterHeight(0);
440         element->AsSurface()->SetSlope(TILE_ELEMENT_SLOPE_FLAT);
441         element->AsSurface()->SetGrassLength(GRASS_LENGTH_CLEAR_0);
442         element->AsSurface()->SetOwnership(OWNERSHIP_UNOWNED);
443         element->AsSurface()->SetParkFences(0);
444         element->AsSurface()->SetSurfaceStyle(0);
445         element->AsSurface()->SetEdgeStyle(0);
446     }
447     SetTileElements(std::move(tileElements));
448 
449     gGrassSceneryTileLoopPosition = 0;
450     gWidePathTileLoopPosition = {};
451     gMapSize = size;
452     gMapBaseZ = 7;
453     map_remove_out_of_range_elements();
454     AutoCreateMapAnimations();
455 
456     auto intent = Intent(INTENT_ACTION_MAP);
457     context_broadcast_intent(&intent);
458 }
459 
460 /**
461  * Counts the number of surface tiles that offer land ownership rights for sale,
462  * but haven't been bought yet. It updates gLandRemainingOwnershipSales and
463  * gLandRemainingConstructionSales.
464  */
map_count_remaining_land_rights()465 void map_count_remaining_land_rights()
466 {
467     gLandRemainingOwnershipSales = 0;
468     gLandRemainingConstructionSales = 0;
469 
470     for (int32_t x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
471     {
472         for (int32_t y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
473         {
474             auto* surfaceElement = map_get_surface_element_at(TileCoordsXY{ x, y }.ToCoordsXY());
475             // Surface elements are sometimes hacked out to save some space for other map elements
476             if (surfaceElement == nullptr)
477             {
478                 continue;
479             }
480 
481             uint8_t flags = surfaceElement->GetOwnership();
482 
483             // Do not combine this condition with (flags & OWNERSHIP_AVAILABLE)
484             // As some RCT1 parks have owned tiles with the 'construction rights available' flag also set
485             if (!(flags & OWNERSHIP_OWNED))
486             {
487                 if (flags & OWNERSHIP_AVAILABLE)
488                 {
489                     gLandRemainingOwnershipSales++;
490                 }
491                 else if (
492                     (flags & OWNERSHIP_CONSTRUCTION_RIGHTS_AVAILABLE) && (flags & OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED) == 0)
493                 {
494                     gLandRemainingConstructionSales++;
495                 }
496             }
497         }
498     }
499 }
500 
501 /**
502  * This is meant to strip TILE_ELEMENT_FLAG_GHOST flag from all elements when
503  * importing a park.
504  *
505  * This can only exist in hacked parks, as we remove ghost elements while saving.
506  *
507  * This is less invasive than removing ghost elements themselves, as they can
508  * contain valid data.
509  */
map_strip_ghost_flag_from_elements()510 void map_strip_ghost_flag_from_elements()
511 {
512     for (auto& element : _tileElements)
513     {
514         element.SetGhost(false);
515     }
516 }
517 
518 /**
519  * Return the absolute height of an element, given its (x,y) coordinates
520  *
521  * ax: x
522  * cx: y
523  * dx: return remember to & with 0xFFFF if you don't want water affecting results
524  *  rct2: 0x00662783
525  */
tile_element_height(const CoordsXY & loc)526 int16_t tile_element_height(const CoordsXY& loc)
527 {
528     // Off the map
529     if (!map_is_location_valid(loc))
530         return MINIMUM_LAND_HEIGHT_BIG;
531 
532     // Get the surface element for the tile
533     auto surfaceElement = map_get_surface_element_at(loc);
534 
535     if (surfaceElement == nullptr)
536     {
537         return MINIMUM_LAND_HEIGHT_BIG;
538     }
539 
540     uint16_t height = surfaceElement->GetBaseZ();
541 
542     uint32_t slope = surfaceElement->GetSlope();
543     uint8_t extra_height = (slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) >> 4; // 0x10 is the 5th bit - sets slope to double height
544     // Remove the extra height bit
545     slope &= TILE_ELEMENT_SLOPE_ALL_CORNERS_UP;
546 
547     int8_t quad = 0, quad_extra = 0; // which quadrant the element is in?
548                                      // quad_extra is for extra height tiles
549 
550     uint8_t xl, yl; // coordinates across this tile
551 
552     uint8_t TILE_SIZE = 31;
553 
554     xl = loc.x & 0x1f;
555     yl = loc.y & 0x1f;
556 
557     // Slope logic:
558     // Each of the four bits in slope represents that corner being raised
559     // slope == 15 (all four bits) is not used and slope == 0 is flat
560     // If the extra_height bit is set, then the slope goes up two z-levels
561 
562     // We arbitrarily take the SW corner to be closest to the viewer
563 
564     // One corner up
565     if (slope == TILE_ELEMENT_SLOPE_N_CORNER_UP || slope == TILE_ELEMENT_SLOPE_E_CORNER_UP
566         || slope == TILE_ELEMENT_SLOPE_S_CORNER_UP || slope == TILE_ELEMENT_SLOPE_W_CORNER_UP)
567     {
568         switch (slope)
569         {
570             case TILE_ELEMENT_SLOPE_N_CORNER_UP:
571                 quad = xl + yl - TILE_SIZE;
572                 break;
573             case TILE_ELEMENT_SLOPE_E_CORNER_UP:
574                 quad = xl - yl;
575                 break;
576             case TILE_ELEMENT_SLOPE_S_CORNER_UP:
577                 quad = TILE_SIZE - yl - xl;
578                 break;
579             case TILE_ELEMENT_SLOPE_W_CORNER_UP:
580                 quad = yl - xl;
581                 break;
582         }
583         // If the element is in the quadrant with the slope, raise its height
584         if (quad > 0)
585         {
586             height += quad / 2;
587         }
588     }
589 
590     // One side up
591     switch (slope)
592     {
593         case TILE_ELEMENT_SLOPE_NE_SIDE_UP:
594             height += xl / 2 + 1;
595             break;
596         case TILE_ELEMENT_SLOPE_SE_SIDE_UP:
597             height += (TILE_SIZE - yl) / 2;
598             break;
599         case TILE_ELEMENT_SLOPE_NW_SIDE_UP:
600             height += yl / 2;
601             height++;
602             break;
603         case TILE_ELEMENT_SLOPE_SW_SIDE_UP:
604             height += (TILE_SIZE - xl) / 2;
605             break;
606     }
607 
608     // One corner down
609     if ((slope == TILE_ELEMENT_SLOPE_W_CORNER_DN) || (slope == TILE_ELEMENT_SLOPE_S_CORNER_DN)
610         || (slope == TILE_ELEMENT_SLOPE_E_CORNER_DN) || (slope == TILE_ELEMENT_SLOPE_N_CORNER_DN))
611     {
612         switch (slope)
613         {
614             case TILE_ELEMENT_SLOPE_W_CORNER_DN:
615                 quad_extra = xl + TILE_SIZE - yl;
616                 quad = xl - yl;
617                 break;
618             case TILE_ELEMENT_SLOPE_S_CORNER_DN:
619                 quad_extra = xl + yl;
620                 quad = xl + yl - TILE_SIZE - 1;
621                 break;
622             case TILE_ELEMENT_SLOPE_E_CORNER_DN:
623                 quad_extra = TILE_SIZE - xl + yl;
624                 quad = yl - xl;
625                 break;
626             case TILE_ELEMENT_SLOPE_N_CORNER_DN:
627                 quad_extra = (TILE_SIZE - xl) + (TILE_SIZE - yl);
628                 quad = TILE_SIZE - yl - xl - 1;
629                 break;
630         }
631 
632         if (extra_height)
633         {
634             height += quad_extra / 2;
635             height++;
636             return height;
637         }
638         // This tile is essentially at the next height level
639         height += LAND_HEIGHT_STEP;
640         // so we move *down* the slope
641         if (quad < 0)
642         {
643             height += quad / 2;
644         }
645     }
646 
647     // Valleys
648     if ((slope == TILE_ELEMENT_SLOPE_W_E_VALLEY) || (slope == TILE_ELEMENT_SLOPE_N_S_VALLEY))
649     {
650         switch (slope)
651         {
652             case TILE_ELEMENT_SLOPE_W_E_VALLEY:
653                 if (xl + yl <= TILE_SIZE + 1)
654                 {
655                     return height;
656                 }
657                 quad = TILE_SIZE - xl - yl;
658                 break;
659             case TILE_ELEMENT_SLOPE_N_S_VALLEY:
660                 quad = xl - yl;
661                 break;
662         }
663         if (quad > 0)
664         {
665             height += quad / 2;
666         }
667     }
668 
669     return height;
670 }
671 
tile_element_water_height(const CoordsXY & loc)672 int16_t tile_element_water_height(const CoordsXY& loc)
673 {
674     // Off the map
675     if (!map_is_location_valid(loc))
676         return 0;
677 
678     // Get the surface element for the tile
679     auto surfaceElement = map_get_surface_element_at(loc);
680 
681     if (surfaceElement == nullptr)
682     {
683         return 0;
684     }
685 
686     return surfaceElement->GetWaterHeight();
687 }
688 
689 /**
690  * Checks if the tile at coordinate at height counts as connected.
691  * @return 1 if connected, 0 otherwise
692  */
map_coord_is_connected(const TileCoordsXYZ & loc,uint8_t faceDirection)693 bool map_coord_is_connected(const TileCoordsXYZ& loc, uint8_t faceDirection)
694 {
695     TileElement* tileElement = map_get_first_element_at(loc);
696 
697     if (tileElement == nullptr)
698         return false;
699 
700     do
701     {
702         if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
703             continue;
704 
705         uint8_t slopeDirection = tileElement->AsPath()->GetSlopeDirection();
706 
707         if (tileElement->AsPath()->IsSloped())
708         {
709             if (slopeDirection == faceDirection)
710             {
711                 if (loc.z == tileElement->base_height + 2)
712                     return true;
713             }
714             else if (direction_reverse(slopeDirection) == faceDirection && loc.z == tileElement->base_height)
715             {
716                 return true;
717             }
718         }
719         else
720         {
721             if (loc.z == tileElement->base_height)
722                 return true;
723         }
724     } while (!(tileElement++)->IsLastForTile());
725 
726     return false;
727 }
728 
729 /**
730  *
731  *  rct2: 0x006A876D
732  */
map_update_path_wide_flags()733 void map_update_path_wide_flags()
734 {
735     if (gScreenFlags & (SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER))
736     {
737         return;
738     }
739 
740     // Presumably update_path_wide_flags is too computationally expensive to call for every
741     // tile every update, so gWidePathTileLoopX and gWidePathTileLoopY store the x and y
742     // progress. A maximum of 128 calls is done per update.
743     auto x = gWidePathTileLoopPosition.x;
744     auto y = gWidePathTileLoopPosition.y;
745     for (int32_t i = 0; i < 128; i++)
746     {
747         footpath_update_path_wide_flags({ x, y });
748 
749         // Next x, y tile
750         x += COORDS_XY_STEP;
751         if (x >= MAXIMUM_MAP_SIZE_BIG)
752         {
753             x = 0;
754             y += COORDS_XY_STEP;
755             if (y >= MAXIMUM_MAP_SIZE_BIG)
756             {
757                 y = 0;
758             }
759         }
760     }
761     gWidePathTileLoopPosition.x = x;
762     gWidePathTileLoopPosition.y = y;
763 }
764 
765 /**
766  *
767  *  rct2: 0x006A7B84
768  */
map_height_from_slope(const CoordsXY & coords,int32_t slopeDirection,bool isSloped)769 int32_t map_height_from_slope(const CoordsXY& coords, int32_t slopeDirection, bool isSloped)
770 {
771     if (!isSloped)
772         return 0;
773 
774     switch (slopeDirection % NumOrthogonalDirections)
775     {
776         case TILE_ELEMENT_DIRECTION_WEST:
777             return (31 - (coords.x & 31)) / 2;
778         case TILE_ELEMENT_DIRECTION_NORTH:
779             return (coords.y & 31) / 2;
780         case TILE_ELEMENT_DIRECTION_EAST:
781             return (coords.x & 31) / 2;
782         case TILE_ELEMENT_DIRECTION_SOUTH:
783             return (31 - (coords.y & 31)) / 2;
784     }
785     return 0;
786 }
787 
map_is_location_valid(const CoordsXY & coords)788 bool map_is_location_valid(const CoordsXY& coords)
789 {
790     const bool is_x_valid = coords.x < MAXIMUM_MAP_SIZE_BIG && coords.x >= 0;
791     const bool is_y_valid = coords.y < MAXIMUM_MAP_SIZE_BIG && coords.y >= 0;
792     return is_x_valid && is_y_valid;
793 }
794 
map_is_edge(const CoordsXY & coords)795 bool map_is_edge(const CoordsXY& coords)
796 {
797     return (coords.x < 32 || coords.y < 32 || coords.x >= GetMapSizeUnits() || coords.y >= GetMapSizeUnits());
798 }
799 
map_can_build_at(const CoordsXYZ & loc)800 bool map_can_build_at(const CoordsXYZ& loc)
801 {
802     if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
803         return true;
804     if (gCheatsSandboxMode)
805         return true;
806     if (map_is_location_owned(loc))
807         return true;
808     return false;
809 }
810 
811 /**
812  *
813  *  rct2: 0x00664F72
814  */
map_is_location_owned(const CoordsXYZ & loc)815 bool map_is_location_owned(const CoordsXYZ& loc)
816 {
817     // This check is to avoid throwing lots of messages in logs.
818     if (map_is_location_valid(loc))
819     {
820         auto* surfaceElement = map_get_surface_element_at(loc);
821         if (surfaceElement != nullptr)
822         {
823             if (surfaceElement->GetOwnership() & OWNERSHIP_OWNED)
824                 return true;
825 
826             if (surfaceElement->GetOwnership() & OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED)
827             {
828                 if (loc.z < surfaceElement->GetBaseZ() || loc.z - LAND_HEIGHT_STEP > surfaceElement->GetBaseZ())
829                     return true;
830             }
831         }
832     }
833     return false;
834 }
835 
836 /**
837  *
838  *  rct2: 0x00664F2C
839  */
map_is_location_in_park(const CoordsXY & coords)840 bool map_is_location_in_park(const CoordsXY& coords)
841 {
842     if (map_is_location_valid(coords))
843     {
844         auto surfaceElement = map_get_surface_element_at(coords);
845         if (surfaceElement == nullptr)
846             return false;
847         if (surfaceElement->GetOwnership() & OWNERSHIP_OWNED)
848             return true;
849     }
850     return false;
851 }
852 
map_is_location_owned_or_has_rights(const CoordsXY & loc)853 bool map_is_location_owned_or_has_rights(const CoordsXY& loc)
854 {
855     if (map_is_location_valid(loc))
856     {
857         auto surfaceElement = map_get_surface_element_at(loc);
858         if (surfaceElement == nullptr)
859         {
860             return false;
861         }
862         if (surfaceElement->GetOwnership() & OWNERSHIP_OWNED)
863             return true;
864         if (surfaceElement->GetOwnership() & OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED)
865             return true;
866     }
867     return false;
868 }
869 
870 // 0x00981A1E
871 // Table of pre-calculated surface slopes (32) when raising the land tile for a given selection (5)
872 // 0x1F = new slope
873 // 0x20 = base height increases
874 const uint8_t tile_element_raise_styles[9][32] = {
875     {
876         0x01, 0x1B, 0x03, 0x1B, 0x05, 0x21, 0x07, 0x21, 0x09, 0x1B, 0x0B, 0x1B, 0x0D, 0x21, 0x20, 0x0F,
877         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x23, 0x18, 0x19, 0x1A, 0x3B, 0x1C, 0x29, 0x24, 0x1F,
878     }, // MAP_SELECT_TYPE_CORNER_0
879        // (absolute rotation)
880     {
881         0x02, 0x03, 0x17, 0x17, 0x06, 0x07, 0x17, 0x17, 0x0A, 0x0B, 0x22, 0x22, 0x0E, 0x20, 0x22, 0x0F,
882         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x37, 0x18, 0x19, 0x1A, 0x23, 0x1C, 0x28, 0x26, 0x1F,
883     }, // MAP_SELECT_TYPE_CORNER_1
884     {
885         0x04, 0x05, 0x06, 0x07, 0x1E, 0x24, 0x1E, 0x24, 0x0C, 0x0D, 0x0E, 0x20, 0x1E, 0x24, 0x1E, 0x0F,
886         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x26, 0x18, 0x19, 0x1A, 0x21, 0x1C, 0x2C, 0x3E, 0x1F,
887     }, // MAP_SELECT_TYPE_CORNER_2
888     {
889         0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x20, 0x1D, 0x1D, 0x28, 0x28, 0x1D, 0x1D, 0x28, 0x0F,
890         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x22, 0x18, 0x19, 0x1A, 0x29, 0x1C, 0x3D, 0x2C, 0x1F,
891     }, // MAP_SELECT_TYPE_CORNER_3
892     {
893         0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
894         0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x20, 0x21, 0x20, 0x28, 0x24, 0x20,
895     }, // MAP_SELECT_TYPE_FULL
896     {
897         0x0C, 0x0D, 0x0E, 0x20, 0x0C, 0x0D, 0x0E, 0x20, 0x0C, 0x0D, 0x0E, 0x20, 0x2C, 0x2C, 0x2C, 0x2C,
898         0x0C, 0x0D, 0x0E, 0x20, 0x0C, 0x0C, 0x0E, 0x22, 0x0C, 0x0D, 0x0E, 0x21, 0x2C, 0x2C, 0x2C, 0x2C,
899     }, // MAP_SELECT_TYPE_EDGE_0
900     {
901         0x09, 0x09, 0x0B, 0x0B, 0x0D, 0x0D, 0x20, 0x20, 0x09, 0x29, 0x0B, 0x29, 0x0D, 0x29, 0x20, 0x29,
902         0x09, 0x09, 0x0B, 0x0B, 0x0D, 0x0D, 0x24, 0x22, 0x09, 0x29, 0x0B, 0x29, 0x0D, 0x29, 0x24, 0x29,
903     }, // MAP_SELECT_TYPE_EDGE_1
904     {
905         0x03, 0x03, 0x03, 0x23, 0x07, 0x07, 0x07, 0x23, 0x0B, 0x0B, 0x0B, 0x23, 0x20, 0x20, 0x20, 0x23,
906         0x03, 0x03, 0x03, 0x23, 0x07, 0x07, 0x07, 0x23, 0x0B, 0x0B, 0x0B, 0x23, 0x20, 0x28, 0x24, 0x23,
907     }, // MAP_SELECT_TYPE_EDGE_2
908     {
909         0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x26, 0x26, 0x0E, 0x20, 0x0E, 0x20, 0x0E, 0x20, 0x26, 0x26,
910         0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x26, 0x26, 0x0E, 0x20, 0x0E, 0x21, 0x0E, 0x28, 0x26, 0x26,
911     }, // MAP_SELECT_TYPE_EDGE_3
912 };
913 
914 // 0x00981ABE
915 // Basically the inverse of the table above.
916 // 0x1F = new slope
917 // 0x20 = base height increases
918 const uint8_t tile_element_lower_styles[9][32] = {
919     {
920         0x2E, 0x00, 0x2E, 0x02, 0x3E, 0x04, 0x3E, 0x06, 0x2E, 0x08, 0x2E, 0x0A, 0x3E, 0x0C, 0x3E, 0x0F,
921         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x06, 0x18, 0x19, 0x1A, 0x0B, 0x1C, 0x0C, 0x3E, 0x1F,
922     }, // MAP_SELECT_TYPE_CORNER_0
923     {
924         0x2D, 0x2D, 0x00, 0x01, 0x2D, 0x2D, 0x04, 0x05, 0x3D, 0x3D, 0x08, 0x09, 0x3D, 0x3D, 0x0C, 0x0F,
925         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x07, 0x18, 0x19, 0x1A, 0x09, 0x1C, 0x3D, 0x0C, 0x1F,
926     }, // MAP_SELECT_TYPE_CORNER_1
927     {
928         0x2B, 0x3B, 0x2B, 0x3B, 0x00, 0x01, 0x02, 0x03, 0x2B, 0x3B, 0x2B, 0x3B, 0x08, 0x09, 0x0A, 0x0F,
929         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x03, 0x18, 0x19, 0x1A, 0x3B, 0x1C, 0x09, 0x0E, 0x1F,
930     }, // MAP_SELECT_TYPE_CORNER_2
931     {
932         0x27, 0x27, 0x37, 0x37, 0x27, 0x27, 0x37, 0x37, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x0F,
933         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x37, 0x18, 0x19, 0x1A, 0x03, 0x1C, 0x0D, 0x06, 0x1F,
934     }, // MAP_SELECT_TYPE_CORNER_3
935     {
936         0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
937         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x0D, 0x0E, 0x00,
938     }, // MAP_SELECT_TYPE_FULL
939     {
940         0x23, 0x23, 0x23, 0x23, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03,
941         0x23, 0x23, 0x23, 0x23, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, 0x00, 0x0D, 0x0E, 0x03,
942     }, // MAP_SELECT_TYPE_EDGE_0
943     {
944         0x26, 0x00, 0x26, 0x02, 0x26, 0x04, 0x26, 0x06, 0x00, 0x00, 0x02, 0x02, 0x04, 0x04, 0x06, 0x06,
945         0x26, 0x00, 0x26, 0x02, 0x26, 0x04, 0x26, 0x06, 0x00, 0x00, 0x02, 0x0B, 0x04, 0x0D, 0x06, 0x06,
946     }, // MAP_SELECT_TYPE_EDGE_1
947     {
948         0x2C, 0x00, 0x00, 0x00, 0x2C, 0x04, 0x04, 0x04, 0x2C, 0x08, 0x08, 0x08, 0x2C, 0x0C, 0x0C, 0x0C,
949         0x2C, 0x00, 0x00, 0x00, 0x2C, 0x04, 0x04, 0x07, 0x2C, 0x08, 0x08, 0x0B, 0x2C, 0x0C, 0x0C, 0x0C,
950     }, // MAP_SELECT_TYPE_EDGE_2
951     {
952         0x29, 0x29, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x29, 0x29, 0x08, 0x09, 0x08, 0x09, 0x08, 0x09,
953         0x29, 0x29, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07, 0x29, 0x29, 0x08, 0x09, 0x08, 0x09, 0x0E, 0x09,
954     }, // MAP_SELECT_TYPE_EDGE_3
955 };
956 
map_get_corner_height(int32_t z,int32_t slope,int32_t direction)957 int32_t map_get_corner_height(int32_t z, int32_t slope, int32_t direction)
958 {
959     switch (direction)
960     {
961         case 0:
962             if (slope & TILE_ELEMENT_SLOPE_N_CORNER_UP)
963             {
964                 z += 2;
965                 if (slope == (TILE_ELEMENT_SLOPE_S_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT))
966                 {
967                     z += 2;
968                 }
969             }
970             break;
971         case 1:
972             if (slope & TILE_ELEMENT_SLOPE_E_CORNER_UP)
973             {
974                 z += 2;
975                 if (slope == (TILE_ELEMENT_SLOPE_W_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT))
976                 {
977                     z += 2;
978                 }
979             }
980             break;
981         case 2:
982             if (slope & TILE_ELEMENT_SLOPE_S_CORNER_UP)
983             {
984                 z += 2;
985                 if (slope == (TILE_ELEMENT_SLOPE_N_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT))
986                 {
987                     z += 2;
988                 }
989             }
990             break;
991         case 3:
992             if (slope & TILE_ELEMENT_SLOPE_W_CORNER_UP)
993             {
994                 z += 2;
995                 if (slope == (TILE_ELEMENT_SLOPE_E_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT))
996                 {
997                     z += 2;
998                 }
999             }
1000             break;
1001     }
1002     return z;
1003 }
1004 
tile_element_get_corner_height(const SurfaceElement * surfaceElement,int32_t direction)1005 int32_t tile_element_get_corner_height(const SurfaceElement* surfaceElement, int32_t direction)
1006 {
1007     int32_t z = surfaceElement->base_height;
1008     int32_t slope = surfaceElement->GetSlope();
1009     return map_get_corner_height(z, slope, direction);
1010 }
1011 
map_get_lowest_land_height(const MapRange & range)1012 uint8_t map_get_lowest_land_height(const MapRange& range)
1013 {
1014     MapRange validRange = { std::max(range.GetLeft(), 32), std::max(range.GetTop(), 32),
1015                             std::min(range.GetRight(), GetMapSizeMaxXY()), std::min(range.GetBottom(), GetMapSizeMaxXY()) };
1016 
1017     uint8_t min_height = 0xFF;
1018     for (int32_t yi = validRange.GetTop(); yi <= validRange.GetBottom(); yi += COORDS_XY_STEP)
1019     {
1020         for (int32_t xi = validRange.GetLeft(); xi <= validRange.GetRight(); xi += COORDS_XY_STEP)
1021         {
1022             auto* surfaceElement = map_get_surface_element_at(CoordsXY{ xi, yi });
1023 
1024             if (surfaceElement != nullptr && min_height > surfaceElement->base_height)
1025             {
1026                 if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gCheatsSandboxMode)
1027                 {
1028                     if (!map_is_location_in_park(CoordsXY{ xi, yi }))
1029                     {
1030                         continue;
1031                     }
1032                 }
1033 
1034                 min_height = surfaceElement->base_height;
1035             }
1036         }
1037     }
1038     return min_height;
1039 }
1040 
map_get_highest_land_height(const MapRange & range)1041 uint8_t map_get_highest_land_height(const MapRange& range)
1042 {
1043     MapRange validRange = { std::max(range.GetLeft(), 32), std::max(range.GetTop(), 32),
1044                             std::min(range.GetRight(), GetMapSizeMaxXY()), std::min(range.GetBottom(), GetMapSizeMaxXY()) };
1045 
1046     uint8_t max_height = 0;
1047     for (int32_t yi = validRange.GetTop(); yi <= validRange.GetBottom(); yi += COORDS_XY_STEP)
1048     {
1049         for (int32_t xi = validRange.GetLeft(); xi <= validRange.GetRight(); xi += COORDS_XY_STEP)
1050         {
1051             auto* surfaceElement = map_get_surface_element_at(CoordsXY{ xi, yi });
1052             if (surfaceElement != nullptr)
1053             {
1054                 if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gCheatsSandboxMode)
1055                 {
1056                     if (!map_is_location_in_park(CoordsXY{ xi, yi }))
1057                     {
1058                         continue;
1059                     }
1060                 }
1061 
1062                 uint8_t base_height = surfaceElement->base_height;
1063                 if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP)
1064                     base_height += 2;
1065                 if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
1066                     base_height += 2;
1067                 if (max_height < base_height)
1068                     max_height = base_height;
1069             }
1070         }
1071     }
1072     return max_height;
1073 }
1074 
map_is_location_at_edge(const CoordsXY & loc)1075 bool map_is_location_at_edge(const CoordsXY& loc)
1076 {
1077     return loc.x < 32 || loc.y < 32 || loc.x >= (MAXIMUM_TILE_START_XY) || loc.y >= (MAXIMUM_TILE_START_XY);
1078 }
1079 
1080 /**
1081  *
1082  *  rct2: 0x0068B280
1083  */
tile_element_remove(TileElement * tileElement)1084 void tile_element_remove(TileElement* tileElement)
1085 {
1086     // Replace Nth element by (N+1)th element.
1087     // This loop will make tileElement point to the old last element position,
1088     // after copy it to it's new position
1089     if (!tileElement->IsLastForTile())
1090     {
1091         do
1092         {
1093             *tileElement = *(tileElement + 1);
1094         } while (!(++tileElement)->IsLastForTile());
1095     }
1096 
1097     // Mark the latest element with the last element flag.
1098     (tileElement - 1)->SetLastForTile(true);
1099     tileElement->base_height = MAX_ELEMENT_HEIGHT;
1100     _tileElementsInUse--;
1101     if (tileElement == &_tileElements.back())
1102     {
1103         _tileElements.pop_back();
1104     }
1105 }
1106 
1107 /**
1108  *
1109  *  rct2: 0x00675A8E
1110  */
map_remove_all_rides()1111 void map_remove_all_rides()
1112 {
1113     tile_element_iterator it;
1114 
1115     tile_element_iterator_begin(&it);
1116     do
1117     {
1118         switch (it.element->GetType())
1119         {
1120             case TILE_ELEMENT_TYPE_PATH:
1121                 if (it.element->AsPath()->IsQueue())
1122                 {
1123                     it.element->AsPath()->SetHasQueueBanner(false);
1124                     it.element->AsPath()->SetRideIndex(RIDE_ID_NULL);
1125                 }
1126                 break;
1127             case TILE_ELEMENT_TYPE_ENTRANCE:
1128                 if (it.element->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_PARK_ENTRANCE)
1129                     break;
1130                 [[fallthrough]];
1131             case TILE_ELEMENT_TYPE_TRACK:
1132                 footpath_queue_chain_reset();
1133                 footpath_remove_edges_at(TileCoordsXY{ it.x, it.y }.ToCoordsXY(), it.element);
1134                 tile_element_remove(it.element);
1135                 tile_element_iterator_restart_for_tile(&it);
1136                 break;
1137         }
1138     } while (tile_element_iterator_next(&it));
1139 }
1140 
1141 /**
1142  *
1143  *  rct2: 0x0068AB1B
1144  */
map_invalidate_map_selection_tiles()1145 void map_invalidate_map_selection_tiles()
1146 {
1147     if (!(gMapSelectFlags & MAP_SELECT_FLAG_ENABLE_CONSTRUCT))
1148         return;
1149 
1150     for (const auto& position : gMapSelectionTiles)
1151         map_invalidate_tile_full(position);
1152 }
1153 
map_get_bounding_box(const MapRange & _range,int32_t * left,int32_t * top,int32_t * right,int32_t * bottom)1154 static void map_get_bounding_box(const MapRange& _range, int32_t* left, int32_t* top, int32_t* right, int32_t* bottom)
1155 {
1156     uint32_t rotation = get_current_rotation();
1157     const std::array corners{
1158         CoordsXY{ _range.GetLeft(), _range.GetTop() },
1159         CoordsXY{ _range.GetRight(), _range.GetTop() },
1160         CoordsXY{ _range.GetRight(), _range.GetBottom() },
1161         CoordsXY{ _range.GetLeft(), _range.GetBottom() },
1162     };
1163 
1164     *left = std::numeric_limits<int32_t>::max();
1165     *top = std::numeric_limits<int32_t>::max();
1166     *right = std::numeric_limits<int32_t>::min();
1167     *bottom = std::numeric_limits<int32_t>::min();
1168 
1169     for (const auto& corner : corners)
1170     {
1171         auto screenCoord = translate_3d_to_2d(rotation, corner);
1172         if (screenCoord.x < *left)
1173             *left = screenCoord.x;
1174         if (screenCoord.x > *right)
1175             *right = screenCoord.x;
1176         if (screenCoord.y > *bottom)
1177             *bottom = screenCoord.y;
1178         if (screenCoord.y < *top)
1179             *top = screenCoord.y;
1180     }
1181 }
1182 
1183 /**
1184  *
1185  *  rct2: 0x0068AAE1
1186  */
map_invalidate_selection_rect()1187 void map_invalidate_selection_rect()
1188 {
1189     int32_t x0, y0, x1, y1, left, right, top, bottom;
1190 
1191     if (!(gMapSelectFlags & MAP_SELECT_FLAG_ENABLE))
1192         return;
1193 
1194     x0 = gMapSelectPositionA.x + 16;
1195     y0 = gMapSelectPositionA.y + 16;
1196     x1 = gMapSelectPositionB.x + 16;
1197     y1 = gMapSelectPositionB.y + 16;
1198     map_get_bounding_box({ x0, y0, x1, y1 }, &left, &top, &right, &bottom);
1199     left -= 32;
1200     right += 32;
1201     bottom += 32;
1202     top -= 32 + 2080;
1203 
1204     viewports_invalidate({ { left, top }, { right, bottom } });
1205 }
1206 
CountElementsOnTile(const CoordsXY & loc)1207 static size_t CountElementsOnTile(const CoordsXY& loc)
1208 {
1209     size_t count = 0;
1210     auto* element = _tileIndex.GetFirstElementAt(TileCoordsXY(loc));
1211     do
1212     {
1213         count++;
1214     } while (!(element++)->IsLastForTile());
1215     return count;
1216 }
1217 
AllocateTileElements(size_t numElementsOnTile,size_t numNewElements)1218 static TileElement* AllocateTileElements(size_t numElementsOnTile, size_t numNewElements)
1219 {
1220     if (!map_check_free_elements_and_reorganise(numElementsOnTile, numNewElements))
1221     {
1222         log_error("Cannot insert new element");
1223         return nullptr;
1224     }
1225 
1226     auto oldSize = _tileElements.size();
1227     _tileElements.resize(_tileElements.size() + numElementsOnTile + numNewElements);
1228     _tileElementsInUse += numNewElements;
1229     return &_tileElements[oldSize];
1230 }
1231 
1232 /**
1233  *
1234  *  rct2: 0x0068B1F6
1235  */
tile_element_insert(const CoordsXYZ & loc,int32_t occupiedQuadrants,TileElementType type)1236 TileElement* tile_element_insert(const CoordsXYZ& loc, int32_t occupiedQuadrants, TileElementType type)
1237 {
1238     const auto& tileLoc = TileCoordsXYZ(loc);
1239 
1240     auto numElementsOnTileOld = CountElementsOnTile(loc);
1241     auto* newTileElement = AllocateTileElements(numElementsOnTileOld, 1);
1242     auto* originalTileElement = _tileIndex.GetFirstElementAt(tileLoc);
1243     if (newTileElement == nullptr)
1244     {
1245         return nullptr;
1246     }
1247 
1248     // Set tile index pointer to point to new element block
1249     _tileIndex.SetTile(tileLoc, newTileElement);
1250 
1251     bool isLastForTile = false;
1252     if (originalTileElement == nullptr)
1253     {
1254         isLastForTile = true;
1255     }
1256     else
1257     {
1258         // Copy all elements that are below the insert height
1259         while (loc.z >= originalTileElement->GetBaseZ())
1260         {
1261             // Copy over map element
1262             *newTileElement = *originalTileElement;
1263             originalTileElement->base_height = MAX_ELEMENT_HEIGHT;
1264             originalTileElement++;
1265             newTileElement++;
1266 
1267             if ((newTileElement - 1)->IsLastForTile())
1268             {
1269                 // No more elements above the insert element
1270                 (newTileElement - 1)->SetLastForTile(false);
1271                 isLastForTile = true;
1272                 break;
1273             }
1274         }
1275     }
1276 
1277     // Insert new map element
1278     auto* insertedElement = newTileElement;
1279     newTileElement->type = 0;
1280     newTileElement->SetType(static_cast<uint8_t>(type));
1281     newTileElement->SetBaseZ(loc.z);
1282     newTileElement->Flags = 0;
1283     newTileElement->SetLastForTile(isLastForTile);
1284     newTileElement->SetOccupiedQuadrants(occupiedQuadrants);
1285     newTileElement->SetClearanceZ(loc.z);
1286     newTileElement->owner = 0;
1287     std::memset(&newTileElement->pad_05, 0, sizeof(newTileElement->pad_05));
1288     std::memset(&newTileElement->pad_08, 0, sizeof(newTileElement->pad_08));
1289     newTileElement++;
1290 
1291     // Insert rest of map elements above insert height
1292     if (!isLastForTile)
1293     {
1294         do
1295         {
1296             // Copy over map element
1297             *newTileElement = *originalTileElement;
1298             originalTileElement->base_height = MAX_ELEMENT_HEIGHT;
1299             originalTileElement++;
1300             newTileElement++;
1301         } while (!((newTileElement - 1)->IsLastForTile()));
1302     }
1303 
1304     return insertedElement;
1305 }
1306 
1307 /**
1308  * Updates grass length, scenery age and jumping fountains.
1309  *
1310  *  rct2: 0x006646E1
1311  */
map_update_tiles()1312 void map_update_tiles()
1313 {
1314     int32_t ignoreScreenFlags = SCREEN_FLAGS_SCENARIO_EDITOR | SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER;
1315     if (gScreenFlags & ignoreScreenFlags)
1316         return;
1317 
1318     // Update 43 more tiles (for each 256x256 block)
1319     for (int32_t j = 0; j < 43; j++)
1320     {
1321         int32_t x = 0;
1322         int32_t y = 0;
1323 
1324         uint16_t interleaved_xy = gGrassSceneryTileLoopPosition;
1325         for (int32_t i = 0; i < 8; i++)
1326         {
1327             x = (x << 1) | (interleaved_xy & 1);
1328             interleaved_xy >>= 1;
1329             y = (y << 1) | (interleaved_xy & 1);
1330             interleaved_xy >>= 1;
1331         }
1332 
1333         // Repeat for each 256x256 block on the map
1334         for (int32_t blockY = 0; blockY < gMapSize; blockY += 256)
1335         {
1336             for (int32_t blockX = 0; blockX < gMapSize; blockX += 256)
1337             {
1338                 auto mapPos = TileCoordsXY{ blockX + x, blockY + y }.ToCoordsXY();
1339                 auto* surfaceElement = map_get_surface_element_at(mapPos);
1340                 if (surfaceElement != nullptr)
1341                 {
1342                     surfaceElement->UpdateGrassLength(mapPos);
1343                     scenery_update_tile(mapPos);
1344                 }
1345             }
1346         }
1347 
1348         gGrassSceneryTileLoopPosition++;
1349         gGrassSceneryTileLoopPosition &= 0xFFFF;
1350     }
1351 }
1352 
map_remove_provisional_elements()1353 void map_remove_provisional_elements()
1354 {
1355     if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1)
1356     {
1357         footpath_provisional_remove();
1358         gProvisionalFootpath.Flags |= PROVISIONAL_PATH_FLAG_1;
1359     }
1360     if (window_find_by_class(WC_RIDE_CONSTRUCTION) != nullptr)
1361     {
1362         ride_remove_provisional_track_piece();
1363         ride_entrance_exit_remove_ghost();
1364     }
1365     // This is in non performant so only make network games suffer for it
1366     // non networked games do not need this as its to prevent desyncs.
1367     if ((network_get_mode() != NETWORK_MODE_NONE) && window_find_by_class(WC_TRACK_DESIGN_PLACE) != nullptr)
1368     {
1369         auto intent = Intent(INTENT_ACTION_TRACK_DESIGN_REMOVE_PROVISIONAL);
1370         context_broadcast_intent(&intent);
1371     }
1372 }
1373 
map_restore_provisional_elements()1374 void map_restore_provisional_elements()
1375 {
1376     if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1)
1377     {
1378         gProvisionalFootpath.Flags &= ~PROVISIONAL_PATH_FLAG_1;
1379         footpath_provisional_set(
1380             gProvisionalFootpath.SurfaceIndex, gProvisionalFootpath.RailingsIndex, gProvisionalFootpath.Position,
1381             gProvisionalFootpath.Slope, gProvisionalFootpath.ConstructFlags);
1382     }
1383     if (window_find_by_class(WC_RIDE_CONSTRUCTION) != nullptr)
1384     {
1385         ride_restore_provisional_track_piece();
1386         ride_entrance_exit_place_provisional_ghost();
1387     }
1388     // This is in non performant so only make network games suffer for it
1389     // non networked games do not need this as its to prevent desyncs.
1390     if ((network_get_mode() != NETWORK_MODE_NONE) && window_find_by_class(WC_TRACK_DESIGN_PLACE) != nullptr)
1391     {
1392         auto intent = Intent(INTENT_ACTION_TRACK_DESIGN_RESTORE_PROVISIONAL);
1393         context_broadcast_intent(&intent);
1394     }
1395 }
1396 
1397 /**
1398  * Removes elements that are out of the map size range and crops the park perimeter.
1399  *  rct2: 0x0068ADBC
1400  */
map_remove_out_of_range_elements()1401 void map_remove_out_of_range_elements()
1402 {
1403     int32_t mapMaxXY = GetMapSizeMaxXY();
1404 
1405     // Ensure that we can remove elements
1406     //
1407     // NOTE: This is only a workaround for non-networked games.
1408     // Map resize has to become its own Game Action to properly solve this issue.
1409     //
1410     bool buildState = gCheatsBuildInPauseMode;
1411     gCheatsBuildInPauseMode = true;
1412 
1413     for (int32_t y = 0; y < MAXIMUM_MAP_SIZE_BIG; y += COORDS_XY_STEP)
1414     {
1415         for (int32_t x = 0; x < MAXIMUM_MAP_SIZE_BIG; x += COORDS_XY_STEP)
1416         {
1417             if (x == 0 || y == 0 || x >= mapMaxXY || y >= mapMaxXY)
1418             {
1419                 // Note this purposely does not use LandSetRightsAction as X Y coordinates are outside of normal range.
1420                 auto surfaceElement = map_get_surface_element_at(CoordsXY{ x, y });
1421                 if (surfaceElement != nullptr)
1422                 {
1423                     surfaceElement->SetOwnership(OWNERSHIP_UNOWNED);
1424                     update_park_fences_around_tile({ x, y });
1425                 }
1426                 clear_elements_at({ x, y });
1427             }
1428         }
1429     }
1430 
1431     // Reset cheat state
1432     gCheatsBuildInPauseMode = buildState;
1433 }
1434 
map_extend_boundary_surface_extend_tile(const SurfaceElement & sourceTile,SurfaceElement & destTile)1435 static void map_extend_boundary_surface_extend_tile(const SurfaceElement& sourceTile, SurfaceElement& destTile)
1436 {
1437     destTile.SetSurfaceStyle(sourceTile.GetSurfaceStyle());
1438     destTile.SetEdgeStyle(sourceTile.GetEdgeStyle());
1439     destTile.SetGrassLength(sourceTile.GetGrassLength());
1440     destTile.SetOwnership(OWNERSHIP_UNOWNED);
1441     destTile.SetWaterHeight(sourceTile.GetWaterHeight());
1442 
1443     auto z = sourceTile.base_height;
1444     auto slope = sourceTile.GetSlope() & TILE_ELEMENT_SLOPE_NW_SIDE_UP;
1445     if (slope == TILE_ELEMENT_SLOPE_NW_SIDE_UP)
1446     {
1447         z += 2;
1448         slope = TILE_ELEMENT_SLOPE_FLAT;
1449         if (sourceTile.GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
1450         {
1451             slope = TILE_ELEMENT_SLOPE_N_CORNER_UP;
1452             if (sourceTile.GetSlope() & TILE_ELEMENT_SLOPE_S_CORNER_UP)
1453             {
1454                 slope = TILE_ELEMENT_SLOPE_W_CORNER_UP;
1455                 if (sourceTile.GetSlope() & TILE_ELEMENT_SLOPE_E_CORNER_UP)
1456                 {
1457                     slope = TILE_ELEMENT_SLOPE_FLAT;
1458                 }
1459             }
1460         }
1461     }
1462     if (slope & TILE_ELEMENT_SLOPE_N_CORNER_UP)
1463         slope |= TILE_ELEMENT_SLOPE_E_CORNER_UP;
1464     if (slope & TILE_ELEMENT_SLOPE_W_CORNER_UP)
1465         slope |= TILE_ELEMENT_SLOPE_S_CORNER_UP;
1466 
1467     destTile.SetSlope(slope);
1468     destTile.base_height = z;
1469     destTile.clearance_height = z;
1470 }
1471 
1472 /**
1473  * Copies the terrain and slope from the edge of the map to the new tiles. Used when increasing the size of the map.
1474  *  rct2: 0x0068AC15
1475  */
map_extend_boundary_surface()1476 void map_extend_boundary_surface()
1477 {
1478     SurfaceElement *existingTileElement, *newTileElement;
1479     int32_t x, y;
1480 
1481     y = gMapSize - 2;
1482     for (x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
1483     {
1484         existingTileElement = map_get_surface_element_at(TileCoordsXY{ x, y - 1 }.ToCoordsXY());
1485         newTileElement = map_get_surface_element_at(TileCoordsXY{ x, y }.ToCoordsXY());
1486 
1487         if (existingTileElement != nullptr && newTileElement != nullptr)
1488         {
1489             map_extend_boundary_surface_extend_tile(*existingTileElement, *newTileElement);
1490         }
1491 
1492         update_park_fences({ x << 5, y << 5 });
1493     }
1494 
1495     x = gMapSize - 2;
1496     for (y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
1497     {
1498         existingTileElement = map_get_surface_element_at(TileCoordsXY{ x - 1, y }.ToCoordsXY());
1499         newTileElement = map_get_surface_element_at(TileCoordsXY{ x, y }.ToCoordsXY());
1500 
1501         if (existingTileElement != nullptr && newTileElement != nullptr)
1502         {
1503             map_extend_boundary_surface_extend_tile(*existingTileElement, *newTileElement);
1504         }
1505 
1506         update_park_fences({ x << 5, y << 5 });
1507     }
1508 }
1509 
1510 /**
1511  * Clears the provided element properly from a certain tile, and updates
1512  * the pointer (when needed) passed to this function to point to the next element.
1513  */
clear_element_at(const CoordsXY & loc,TileElement ** elementPtr)1514 static void clear_element_at(const CoordsXY& loc, TileElement** elementPtr)
1515 {
1516     TileElement* element = *elementPtr;
1517     switch (element->GetType())
1518     {
1519         case TILE_ELEMENT_TYPE_SURFACE:
1520             element->base_height = MINIMUM_LAND_HEIGHT;
1521             element->clearance_height = MINIMUM_LAND_HEIGHT;
1522             element->owner = 0;
1523             element->AsSurface()->SetSlope(TILE_ELEMENT_SLOPE_FLAT);
1524             element->AsSurface()->SetSurfaceStyle(0);
1525             element->AsSurface()->SetEdgeStyle(0);
1526             element->AsSurface()->SetGrassLength(GRASS_LENGTH_CLEAR_0);
1527             element->AsSurface()->SetOwnership(OWNERSHIP_UNOWNED);
1528             element->AsSurface()->SetParkFences(0);
1529             element->AsSurface()->SetWaterHeight(0);
1530             // Because this element is not completely removed, the pointer must be updated manually
1531             // The rest of the elements are removed from the array, so the pointer doesn't need to be updated.
1532             (*elementPtr)++;
1533             break;
1534         case TILE_ELEMENT_TYPE_ENTRANCE:
1535         {
1536             int32_t rotation = element->GetDirectionWithOffset(1);
1537             auto seqLoc = loc;
1538             switch (element->AsEntrance()->GetSequenceIndex())
1539             {
1540                 case 1:
1541                     seqLoc += CoordsDirectionDelta[rotation];
1542                     break;
1543                 case 2:
1544                     seqLoc -= CoordsDirectionDelta[rotation];
1545                     break;
1546             }
1547             auto parkEntranceRemoveAction = ParkEntranceRemoveAction(CoordsXYZ{ seqLoc, element->GetBaseZ() });
1548             auto result = GameActions::ExecuteNested(&parkEntranceRemoveAction);
1549             // If asking nicely did not work, forcibly remove this to avoid an infinite loop.
1550             if (result->Error != GameActions::Status::Ok)
1551             {
1552                 tile_element_remove(element);
1553             }
1554             break;
1555         }
1556         case TILE_ELEMENT_TYPE_WALL:
1557         {
1558             CoordsXYZD wallLocation = { loc.x, loc.y, element->GetBaseZ(), element->GetDirection() };
1559             auto wallRemoveAction = WallRemoveAction(wallLocation);
1560             auto result = GameActions::ExecuteNested(&wallRemoveAction);
1561             // If asking nicely did not work, forcibly remove this to avoid an infinite loop.
1562             if (result->Error != GameActions::Status::Ok)
1563             {
1564                 tile_element_remove(element);
1565             }
1566         }
1567         break;
1568         case TILE_ELEMENT_TYPE_LARGE_SCENERY:
1569         {
1570             auto removeSceneryAction = LargeSceneryRemoveAction(
1571                 { loc.x, loc.y, element->GetBaseZ(), element->GetDirection() }, element->AsLargeScenery()->GetSequenceIndex());
1572             auto result = GameActions::ExecuteNested(&removeSceneryAction);
1573             // If asking nicely did not work, forcibly remove this to avoid an infinite loop.
1574             if (result->Error != GameActions::Status::Ok)
1575             {
1576                 tile_element_remove(element);
1577             }
1578         }
1579         break;
1580         case TILE_ELEMENT_TYPE_BANNER:
1581         {
1582             auto bannerRemoveAction = BannerRemoveAction(
1583                 { loc.x, loc.y, element->GetBaseZ(), element->AsBanner()->GetPosition() });
1584             auto result = GameActions::ExecuteNested(&bannerRemoveAction);
1585             // If asking nicely did not work, forcibly remove this to avoid an infinite loop.
1586             if (result->Error != GameActions::Status::Ok)
1587             {
1588                 tile_element_remove(element);
1589             }
1590             break;
1591         }
1592         default:
1593             tile_element_remove(element);
1594             break;
1595     }
1596 }
1597 
1598 /**
1599  * Clears all elements properly from a certain tile.
1600  *  rct2: 0x0068AE2A
1601  */
clear_elements_at(const CoordsXY & loc)1602 static void clear_elements_at(const CoordsXY& loc)
1603 {
1604     // Remove the spawn point (if there is one in the current tile)
1605     gPeepSpawns.erase(
1606         std::remove_if(
1607             gPeepSpawns.begin(), gPeepSpawns.end(),
1608             [loc](const CoordsXY& spawn) { return spawn.ToTileStart() == loc.ToTileStart(); }),
1609         gPeepSpawns.end());
1610 
1611     TileElement* tileElement = map_get_first_element_at(loc);
1612     if (tileElement == nullptr)
1613         return;
1614 
1615     // Remove all elements except the last one
1616     while (!tileElement->IsLastForTile())
1617         clear_element_at(loc, &tileElement);
1618 
1619     // Remove the last element
1620     clear_element_at(loc, &tileElement);
1621 }
1622 
map_get_highest_z(const CoordsXY & loc)1623 int32_t map_get_highest_z(const CoordsXY& loc)
1624 {
1625     auto surfaceElement = map_get_surface_element_at(loc);
1626     if (surfaceElement == nullptr)
1627         return -1;
1628 
1629     auto z = surfaceElement->GetBaseZ();
1630 
1631     // Raise z so that is above highest point of land and water on tile
1632     if ((surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) != TILE_ELEMENT_SLOPE_FLAT)
1633         z += LAND_HEIGHT_STEP;
1634     if ((surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) != 0)
1635         z += LAND_HEIGHT_STEP;
1636 
1637     z = std::max(z, surfaceElement->GetWaterHeight());
1638     return z;
1639 }
1640 
map_get_large_scenery_segment(const CoordsXYZD & sceneryPos,int32_t sequence)1641 LargeSceneryElement* map_get_large_scenery_segment(const CoordsXYZD& sceneryPos, int32_t sequence)
1642 {
1643     TileElement* tileElement = map_get_first_element_at(sceneryPos);
1644     if (tileElement == nullptr)
1645     {
1646         return nullptr;
1647     }
1648     auto sceneryTilePos = TileCoordsXYZ{ sceneryPos };
1649     do
1650     {
1651         if (tileElement->GetType() != TILE_ELEMENT_TYPE_LARGE_SCENERY)
1652             continue;
1653         if (tileElement->base_height != sceneryTilePos.z)
1654             continue;
1655         if (tileElement->AsLargeScenery()->GetSequenceIndex() != sequence)
1656             continue;
1657         if ((tileElement->GetDirection()) != sceneryPos.direction)
1658             continue;
1659 
1660         return tileElement->AsLargeScenery();
1661     } while (!(tileElement++)->IsLastForTile());
1662     return nullptr;
1663 }
1664 
map_get_park_entrance_element_at(const CoordsXYZ & entranceCoords,bool ghost)1665 EntranceElement* map_get_park_entrance_element_at(const CoordsXYZ& entranceCoords, bool ghost)
1666 {
1667     auto entranceTileCoords = TileCoordsXYZ(entranceCoords);
1668     TileElement* tileElement = map_get_first_element_at(entranceCoords);
1669     if (tileElement != nullptr)
1670     {
1671         do
1672         {
1673             if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
1674                 continue;
1675 
1676             if (tileElement->base_height != entranceTileCoords.z)
1677                 continue;
1678 
1679             if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_PARK_ENTRANCE)
1680                 continue;
1681 
1682             if (!ghost && tileElement->IsGhost())
1683                 continue;
1684 
1685             return tileElement->AsEntrance();
1686         } while (!(tileElement++)->IsLastForTile());
1687     }
1688     return nullptr;
1689 }
1690 
map_get_ride_entrance_element_at(const CoordsXYZ & entranceCoords,bool ghost)1691 EntranceElement* map_get_ride_entrance_element_at(const CoordsXYZ& entranceCoords, bool ghost)
1692 {
1693     auto entranceTileCoords = TileCoordsXYZ{ entranceCoords };
1694     TileElement* tileElement = map_get_first_element_at(entranceCoords);
1695     if (tileElement != nullptr)
1696     {
1697         do
1698         {
1699             if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
1700                 continue;
1701 
1702             if (tileElement->base_height != entranceTileCoords.z)
1703                 continue;
1704 
1705             if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_RIDE_ENTRANCE)
1706                 continue;
1707 
1708             if (!ghost && tileElement->IsGhost())
1709                 continue;
1710 
1711             return tileElement->AsEntrance();
1712         } while (!(tileElement++)->IsLastForTile());
1713     }
1714     return nullptr;
1715 }
1716 
map_get_ride_exit_element_at(const CoordsXYZ & exitCoords,bool ghost)1717 EntranceElement* map_get_ride_exit_element_at(const CoordsXYZ& exitCoords, bool ghost)
1718 {
1719     auto exitTileCoords = TileCoordsXYZ{ exitCoords };
1720     TileElement* tileElement = map_get_first_element_at(exitCoords);
1721     if (tileElement != nullptr)
1722     {
1723         do
1724         {
1725             if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE)
1726                 continue;
1727 
1728             if (tileElement->base_height != exitTileCoords.z)
1729                 continue;
1730 
1731             if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_RIDE_EXIT)
1732                 continue;
1733 
1734             if (!ghost && tileElement->IsGhost())
1735                 continue;
1736 
1737             return tileElement->AsEntrance();
1738         } while (!(tileElement++)->IsLastForTile());
1739     }
1740     return nullptr;
1741 }
1742 
map_get_small_scenery_element_at(const CoordsXYZ & sceneryCoords,int32_t type,uint8_t quadrant)1743 SmallSceneryElement* map_get_small_scenery_element_at(const CoordsXYZ& sceneryCoords, int32_t type, uint8_t quadrant)
1744 {
1745     auto sceneryTileCoords = TileCoordsXYZ{ sceneryCoords };
1746     TileElement* tileElement = map_get_first_element_at(sceneryCoords);
1747     if (tileElement != nullptr)
1748     {
1749         do
1750         {
1751             if (tileElement->GetType() != TILE_ELEMENT_TYPE_SMALL_SCENERY)
1752                 continue;
1753             if (tileElement->AsSmallScenery()->GetSceneryQuadrant() != quadrant)
1754                 continue;
1755             if (tileElement->base_height != sceneryTileCoords.z)
1756                 continue;
1757             if (tileElement->AsSmallScenery()->GetEntryIndex() != type)
1758                 continue;
1759 
1760             return tileElement->AsSmallScenery();
1761         } while (!(tileElement++)->IsLastForTile());
1762     }
1763     return nullptr;
1764 }
1765 
map_large_scenery_get_origin(const CoordsXYZD & sceneryPos,int32_t sequence,LargeSceneryElement ** outElement)1766 std::optional<CoordsXYZ> map_large_scenery_get_origin(
1767     const CoordsXYZD& sceneryPos, int32_t sequence, LargeSceneryElement** outElement)
1768 {
1769     rct_large_scenery_tile* tile;
1770 
1771     auto tileElement = map_get_large_scenery_segment(sceneryPos, sequence);
1772     if (tileElement == nullptr)
1773         return std::nullopt;
1774 
1775     auto* sceneryEntry = tileElement->GetEntry();
1776     tile = &sceneryEntry->tiles[sequence];
1777 
1778     CoordsXY offsetPos{ tile->x_offset, tile->y_offset };
1779     auto rotatedOffsetPos = offsetPos.Rotate(sceneryPos.direction);
1780 
1781     auto origin = CoordsXYZ{ sceneryPos.x - rotatedOffsetPos.x, sceneryPos.y - rotatedOffsetPos.y,
1782                              sceneryPos.z - tile->z_offset };
1783     if (outElement != nullptr)
1784         *outElement = tileElement;
1785     return origin;
1786 }
1787 
1788 /**
1789  *
1790  *  rct2: 0x006B9B05
1791  */
map_large_scenery_sign_set_colour(const CoordsXYZD & signPos,int32_t sequence,uint8_t mainColour,uint8_t textColour)1792 bool map_large_scenery_sign_set_colour(const CoordsXYZD& signPos, int32_t sequence, uint8_t mainColour, uint8_t textColour)
1793 {
1794     LargeSceneryElement* tileElement;
1795     rct_large_scenery_tile *sceneryTiles, *tile;
1796 
1797     auto sceneryOrigin = map_large_scenery_get_origin(signPos, sequence, &tileElement);
1798     if (!sceneryOrigin)
1799     {
1800         return false;
1801     }
1802 
1803     auto* sceneryEntry = tileElement->GetEntry();
1804     sceneryTiles = sceneryEntry->tiles;
1805 
1806     // Iterate through each tile of the large scenery element
1807     sequence = 0;
1808     for (tile = sceneryTiles; tile->x_offset != -1; tile++, sequence++)
1809     {
1810         CoordsXY offsetPos{ tile->x_offset, tile->y_offset };
1811         auto rotatedOffsetPos = offsetPos.Rotate(signPos.direction);
1812 
1813         auto tmpSignPos = CoordsXYZD{ sceneryOrigin->x + rotatedOffsetPos.x, sceneryOrigin->y + rotatedOffsetPos.y,
1814                                       sceneryOrigin->z + tile->z_offset, signPos.direction };
1815         tileElement = map_get_large_scenery_segment(tmpSignPos, sequence);
1816         if (tileElement != nullptr)
1817         {
1818             tileElement->SetPrimaryColour(mainColour);
1819             tileElement->SetSecondaryColour(textColour);
1820 
1821             map_invalidate_tile({ tmpSignPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() });
1822         }
1823     }
1824 
1825     return true;
1826 }
1827 
translate_3d_to_2d(int32_t rotation,const CoordsXY & pos)1828 static ScreenCoordsXY translate_3d_to_2d(int32_t rotation, const CoordsXY& pos)
1829 {
1830     return translate_3d_to_2d_with_z(rotation, CoordsXYZ{ pos, 0 });
1831 }
1832 
translate_3d_to_2d_with_z(int32_t rotation,const CoordsXYZ & pos)1833 ScreenCoordsXY translate_3d_to_2d_with_z(int32_t rotation, const CoordsXYZ& pos)
1834 {
1835     auto rotated = pos.Rotate(rotation);
1836     // Use right shift to avoid issues like #9301
1837     return ScreenCoordsXY{ rotated.y - rotated.x, ((rotated.x + rotated.y) >> 1) - pos.z };
1838 }
1839 
map_invalidate_tile_under_zoom(int32_t x,int32_t y,int32_t z0,int32_t z1,int32_t maxZoom)1840 static void map_invalidate_tile_under_zoom(int32_t x, int32_t y, int32_t z0, int32_t z1, int32_t maxZoom)
1841 {
1842     if (gOpenRCT2Headless)
1843         return;
1844 
1845     int32_t x1, y1, x2, y2;
1846 
1847     x += 16;
1848     y += 16;
1849     auto screenCoord = translate_3d_to_2d(get_current_rotation(), { x, y });
1850 
1851     x1 = screenCoord.x - 32;
1852     y1 = screenCoord.y - 32 - z1;
1853     x2 = screenCoord.x + 32;
1854     y2 = screenCoord.y + 32 - z0;
1855 
1856     viewports_invalidate({ { x1, y1 }, { x2, y2 } }, maxZoom);
1857 }
1858 
1859 /**
1860  *
1861  *  rct2: 0x006EC847
1862  */
map_invalidate_tile(const CoordsXYRangedZ & tilePos)1863 void map_invalidate_tile(const CoordsXYRangedZ& tilePos)
1864 {
1865     map_invalidate_tile_under_zoom(tilePos.x, tilePos.y, tilePos.baseZ, tilePos.clearanceZ, -1);
1866 }
1867 
1868 /**
1869  *
1870  *  rct2: 0x006ECB60
1871  */
map_invalidate_tile_zoom1(const CoordsXYRangedZ & tilePos)1872 void map_invalidate_tile_zoom1(const CoordsXYRangedZ& tilePos)
1873 {
1874     map_invalidate_tile_under_zoom(tilePos.x, tilePos.y, tilePos.baseZ, tilePos.clearanceZ, 1);
1875 }
1876 
1877 /**
1878  *
1879  *  rct2: 0x006EC9CE
1880  */
map_invalidate_tile_zoom0(const CoordsXYRangedZ & tilePos)1881 void map_invalidate_tile_zoom0(const CoordsXYRangedZ& tilePos)
1882 {
1883     map_invalidate_tile_under_zoom(tilePos.x, tilePos.y, tilePos.baseZ, tilePos.clearanceZ, 0);
1884 }
1885 
1886 /**
1887  *
1888  *  rct2: 0x006EC6D7
1889  */
map_invalidate_tile_full(const CoordsXY & tilePos)1890 void map_invalidate_tile_full(const CoordsXY& tilePos)
1891 {
1892     map_invalidate_tile({ tilePos, 0, 2080 });
1893 }
1894 
map_invalidate_element(const CoordsXY & elementPos,TileElement * tileElement)1895 void map_invalidate_element(const CoordsXY& elementPos, TileElement* tileElement)
1896 {
1897     map_invalidate_tile({ elementPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() });
1898 }
1899 
map_invalidate_region(const CoordsXY & mins,const CoordsXY & maxs)1900 void map_invalidate_region(const CoordsXY& mins, const CoordsXY& maxs)
1901 {
1902     int32_t x0, y0, x1, y1, left, right, top, bottom;
1903 
1904     x0 = mins.x + 16;
1905     y0 = mins.y + 16;
1906 
1907     x1 = maxs.x + 16;
1908     y1 = maxs.y + 16;
1909 
1910     map_get_bounding_box({ x0, y0, x1, y1 }, &left, &top, &right, &bottom);
1911 
1912     left -= 32;
1913     right += 32;
1914     bottom += 32;
1915     top -= 32 + 2080;
1916 
1917     viewports_invalidate({ { left, top }, { right, bottom } });
1918 }
1919 
map_get_tile_side(const CoordsXY & mapPos)1920 int32_t map_get_tile_side(const CoordsXY& mapPos)
1921 {
1922     int32_t subMapX = mapPos.x & (32 - 1);
1923     int32_t subMapY = mapPos.y & (32 - 1);
1924     return (subMapX < subMapY) ? ((subMapX + subMapY) < 32 ? 0 : 1) : ((subMapX + subMapY) < 32 ? 3 : 2);
1925 }
1926 
map_get_tile_quadrant(const CoordsXY & mapPos)1927 int32_t map_get_tile_quadrant(const CoordsXY& mapPos)
1928 {
1929     int32_t subMapX = mapPos.x & (32 - 1);
1930     int32_t subMapY = mapPos.y & (32 - 1);
1931     return (subMapX > 16) ? (subMapY < 16 ? 1 : 0) : (subMapY < 16 ? 2 : 3);
1932 }
1933 
1934 /**
1935  *
1936  *  rct2: 0x00693BFF
1937  */
map_surface_is_blocked(const CoordsXY & mapCoords)1938 bool map_surface_is_blocked(const CoordsXY& mapCoords)
1939 {
1940     if (!map_is_location_valid(mapCoords))
1941         return true;
1942 
1943     auto surfaceElement = map_get_surface_element_at(mapCoords);
1944 
1945     if (surfaceElement == nullptr)
1946     {
1947         return true;
1948     }
1949 
1950     if (surfaceElement->GetWaterHeight() > surfaceElement->GetBaseZ())
1951         return true;
1952 
1953     int16_t base_z = surfaceElement->base_height;
1954     int16_t clear_z = surfaceElement->base_height + 2;
1955     if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
1956         clear_z += 2;
1957 
1958     auto tileElement = reinterpret_cast<TileElement*>(surfaceElement);
1959     while (!(tileElement++)->IsLastForTile())
1960     {
1961         if (clear_z >= tileElement->clearance_height)
1962             continue;
1963 
1964         if (base_z < tileElement->base_height)
1965             continue;
1966 
1967         if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH || tileElement->GetType() == TILE_ELEMENT_TYPE_WALL)
1968             continue;
1969 
1970         if (tileElement->GetType() != TILE_ELEMENT_TYPE_SMALL_SCENERY)
1971             return true;
1972 
1973         auto* sceneryEntry = tileElement->AsSmallScenery()->GetEntry();
1974         if (sceneryEntry == nullptr)
1975         {
1976             return false;
1977         }
1978         if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE))
1979             return true;
1980     }
1981     return false;
1982 }
1983 
1984 /* Clears all map elements, to be used before generating a new map */
map_clear_all_elements()1985 void map_clear_all_elements()
1986 {
1987     for (int32_t y = 0; y < MAXIMUM_MAP_SIZE_BIG; y += COORDS_XY_STEP)
1988     {
1989         for (int32_t x = 0; x < MAXIMUM_MAP_SIZE_BIG; x += COORDS_XY_STEP)
1990         {
1991             clear_elements_at({ x, y });
1992         }
1993     }
1994 }
1995 
1996 /**
1997  * Gets the track element at x, y, z.
1998  * @param x x units, not tiles.
1999  * @param y y units, not tiles.
2000  * @param z Base height.
2001  */
map_get_track_element_at(const CoordsXYZ & trackPos)2002 TrackElement* map_get_track_element_at(const CoordsXYZ& trackPos)
2003 {
2004     TileElement* tileElement = map_get_first_element_at(trackPos);
2005     if (tileElement == nullptr)
2006         return nullptr;
2007     do
2008     {
2009         if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
2010             continue;
2011         if (tileElement->GetBaseZ() != trackPos.z)
2012             continue;
2013 
2014         return tileElement->AsTrack();
2015     } while (!(tileElement++)->IsLastForTile());
2016 
2017     return nullptr;
2018 }
2019 
2020 /**
2021  * Gets the track element at x, y, z that is the given track type.
2022  * @param x x units, not tiles.
2023  * @param y y units, not tiles.
2024  * @param z Base height.
2025  */
map_get_track_element_at_of_type(const CoordsXYZ & trackPos,track_type_t trackType)2026 TileElement* map_get_track_element_at_of_type(const CoordsXYZ& trackPos, track_type_t trackType)
2027 {
2028     TileElement* tileElement = map_get_first_element_at(trackPos);
2029     if (tileElement == nullptr)
2030         return nullptr;
2031     auto trackTilePos = TileCoordsXYZ{ trackPos };
2032     do
2033     {
2034         if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
2035             continue;
2036         if (tileElement->base_height != trackTilePos.z)
2037             continue;
2038         if (tileElement->AsTrack()->GetTrackType() != trackType)
2039             continue;
2040 
2041         return tileElement;
2042     } while (!(tileElement++)->IsLastForTile());
2043 
2044     return nullptr;
2045 }
2046 
2047 /**
2048  * Gets the track element at x, y, z that is the given track type and sequence.
2049  * @param x x units, not tiles.
2050  * @param y y units, not tiles.
2051  * @param z Base height.
2052  */
map_get_track_element_at_of_type_seq(const CoordsXYZ & trackPos,track_type_t trackType,int32_t sequence)2053 TileElement* map_get_track_element_at_of_type_seq(const CoordsXYZ& trackPos, track_type_t trackType, int32_t sequence)
2054 {
2055     TileElement* tileElement = map_get_first_element_at(trackPos);
2056     auto trackTilePos = TileCoordsXYZ{ trackPos };
2057     do
2058     {
2059         if (tileElement == nullptr)
2060             break;
2061         if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
2062             continue;
2063         if (tileElement->base_height != trackTilePos.z)
2064             continue;
2065         if (tileElement->AsTrack()->GetTrackType() != trackType)
2066             continue;
2067         if (tileElement->AsTrack()->GetSequenceIndex() != sequence)
2068             continue;
2069 
2070         return tileElement;
2071     } while (!(tileElement++)->IsLastForTile());
2072 
2073     return nullptr;
2074 }
2075 
map_get_track_element_at_of_type(const CoordsXYZD & location,track_type_t trackType)2076 TrackElement* map_get_track_element_at_of_type(const CoordsXYZD& location, track_type_t trackType)
2077 {
2078     auto tileElement = map_get_first_element_at(location);
2079     if (tileElement != nullptr)
2080     {
2081         do
2082         {
2083             auto trackElement = tileElement->AsTrack();
2084             if (trackElement != nullptr)
2085             {
2086                 if (trackElement->GetBaseZ() != location.z)
2087                     continue;
2088                 if (trackElement->GetDirection() != location.direction)
2089                     continue;
2090                 if (trackElement->GetTrackType() != trackType)
2091                     continue;
2092                 return trackElement;
2093             }
2094         } while (!(tileElement++)->IsLastForTile());
2095     }
2096     return nullptr;
2097 }
2098 
map_get_track_element_at_of_type_seq(const CoordsXYZD & location,track_type_t trackType,int32_t sequence)2099 TrackElement* map_get_track_element_at_of_type_seq(const CoordsXYZD& location, track_type_t trackType, int32_t sequence)
2100 {
2101     auto tileElement = map_get_first_element_at(location);
2102     if (tileElement != nullptr)
2103     {
2104         do
2105         {
2106             auto trackElement = tileElement->AsTrack();
2107             if (trackElement != nullptr)
2108             {
2109                 if (trackElement->GetBaseZ() != location.z)
2110                     continue;
2111                 if (trackElement->GetDirection() != location.direction)
2112                     continue;
2113                 if (trackElement->GetTrackType() != trackType)
2114                     continue;
2115                 if (trackElement->GetSequenceIndex() != sequence)
2116                     continue;
2117                 return trackElement;
2118             }
2119         } while (!(tileElement++)->IsLastForTile());
2120     }
2121     return nullptr;
2122 }
2123 
2124 /**
2125  * Gets the track element at x, y, z that is the given track type and sequence.
2126  * @param x x units, not tiles.
2127  * @param y y units, not tiles.
2128  * @param z Base height.
2129  */
map_get_track_element_at_of_type_from_ride(const CoordsXYZ & trackPos,track_type_t trackType,ride_id_t rideIndex)2130 TileElement* map_get_track_element_at_of_type_from_ride(const CoordsXYZ& trackPos, track_type_t trackType, ride_id_t rideIndex)
2131 {
2132     TileElement* tileElement = map_get_first_element_at(trackPos);
2133     if (tileElement == nullptr)
2134         return nullptr;
2135     auto trackTilePos = TileCoordsXYZ{ trackPos };
2136     do
2137     {
2138         if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
2139             continue;
2140         if (tileElement->base_height != trackTilePos.z)
2141             continue;
2142         if (tileElement->AsTrack()->GetRideIndex() != rideIndex)
2143             continue;
2144         if (tileElement->AsTrack()->GetTrackType() != trackType)
2145             continue;
2146 
2147         return tileElement;
2148     } while (!(tileElement++)->IsLastForTile());
2149 
2150     return nullptr;
2151 };
2152 
2153 /**
2154  * Gets the track element at x, y, z that is the given track type and sequence.
2155  * @param x x units, not tiles.
2156  * @param y y units, not tiles.
2157  * @param z Base height.
2158  */
map_get_track_element_at_from_ride(const CoordsXYZ & trackPos,ride_id_t rideIndex)2159 TileElement* map_get_track_element_at_from_ride(const CoordsXYZ& trackPos, ride_id_t rideIndex)
2160 {
2161     TileElement* tileElement = map_get_first_element_at(trackPos);
2162     if (tileElement == nullptr)
2163         return nullptr;
2164     auto trackTilePos = TileCoordsXYZ{ trackPos };
2165     do
2166     {
2167         if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
2168             continue;
2169         if (tileElement->base_height != trackTilePos.z)
2170             continue;
2171         if (tileElement->AsTrack()->GetRideIndex() != rideIndex)
2172             continue;
2173 
2174         return tileElement;
2175     } while (!(tileElement++)->IsLastForTile());
2176 
2177     return nullptr;
2178 };
2179 
2180 /**
2181  * Gets the track element at x, y, z that is the given track type and sequence.
2182  * @param x x units, not tiles.
2183  * @param y y units, not tiles.
2184  * @param z Base height.
2185  * @param direction The direction (0 - 3).
2186  */
map_get_track_element_at_with_direction_from_ride(const CoordsXYZD & trackPos,ride_id_t rideIndex)2187 TileElement* map_get_track_element_at_with_direction_from_ride(const CoordsXYZD& trackPos, ride_id_t rideIndex)
2188 {
2189     TileElement* tileElement = map_get_first_element_at(trackPos);
2190     if (tileElement == nullptr)
2191         return nullptr;
2192     auto trackTilePos = TileCoordsXYZ{ trackPos };
2193     do
2194     {
2195         if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
2196             continue;
2197         if (tileElement->base_height != trackTilePos.z)
2198             continue;
2199         if (tileElement->AsTrack()->GetRideIndex() != rideIndex)
2200             continue;
2201         if (tileElement->GetDirection() != trackPos.direction)
2202             continue;
2203 
2204         return tileElement;
2205     } while (!(tileElement++)->IsLastForTile());
2206 
2207     return nullptr;
2208 };
2209 
map_get_wall_element_at(const CoordsXYRangedZ & coords)2210 WallElement* map_get_wall_element_at(const CoordsXYRangedZ& coords)
2211 {
2212     auto tileElement = map_get_first_element_at(coords);
2213 
2214     if (tileElement != nullptr)
2215     {
2216         do
2217         {
2218             if (tileElement->GetType() == TILE_ELEMENT_TYPE_WALL && coords.baseZ < tileElement->GetClearanceZ()
2219                 && coords.clearanceZ > tileElement->GetBaseZ())
2220             {
2221                 return tileElement->AsWall();
2222             }
2223         } while (!(tileElement++)->IsLastForTile());
2224     }
2225 
2226     return nullptr;
2227 }
2228 
map_get_wall_element_at(const CoordsXYZD & wallCoords)2229 WallElement* map_get_wall_element_at(const CoordsXYZD& wallCoords)
2230 {
2231     auto tileWallCoords = TileCoordsXYZ(wallCoords);
2232     TileElement* tileElement = map_get_first_element_at(wallCoords);
2233     if (tileElement == nullptr)
2234         return nullptr;
2235     do
2236     {
2237         if (tileElement->GetType() != TILE_ELEMENT_TYPE_WALL)
2238             continue;
2239         if (tileElement->base_height != tileWallCoords.z)
2240             continue;
2241         if (tileElement->GetDirection() != wallCoords.direction)
2242             continue;
2243 
2244         return tileElement->AsWall();
2245     } while (!(tileElement++)->IsLastForTile());
2246     return nullptr;
2247 }
2248 
check_max_allowable_land_rights_for_tile(const CoordsXYZ & tileMapPos)2249 uint16_t check_max_allowable_land_rights_for_tile(const CoordsXYZ& tileMapPos)
2250 {
2251     TileElement* tileElement = map_get_first_element_at(tileMapPos);
2252     uint16_t destOwnership = OWNERSHIP_OWNED;
2253 
2254     // Sometimes done deliberately.
2255     if (tileElement == nullptr)
2256     {
2257         return OWNERSHIP_OWNED;
2258     }
2259 
2260     auto tilePos = TileCoordsXYZ{ tileMapPos };
2261     do
2262     {
2263         int32_t type = tileElement->GetType();
2264         if (type == TILE_ELEMENT_TYPE_PATH
2265             || (type == TILE_ELEMENT_TYPE_ENTRANCE
2266                 && tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_PARK_ENTRANCE))
2267         {
2268             destOwnership = OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED;
2269             // Do not own construction rights if too high/below surface
2270             if (tileElement->base_height - 3 > tilePos.z || tileElement->base_height < tilePos.z)
2271             {
2272                 destOwnership = OWNERSHIP_UNOWNED;
2273                 break;
2274             }
2275         }
2276     } while (!(tileElement++)->IsLastForTile());
2277 
2278     return destOwnership;
2279 }
2280 
FixLandOwnershipTiles(std::initializer_list<TileCoordsXY> tiles)2281 void FixLandOwnershipTiles(std::initializer_list<TileCoordsXY> tiles)
2282 {
2283     FixLandOwnershipTilesWithOwnership(tiles, OWNERSHIP_AVAILABLE);
2284 }
2285 
FixLandOwnershipTilesWithOwnership(std::initializer_list<TileCoordsXY> tiles,uint8_t ownership,bool doNotDowngrade)2286 void FixLandOwnershipTilesWithOwnership(std::initializer_list<TileCoordsXY> tiles, uint8_t ownership, bool doNotDowngrade)
2287 {
2288     for (const TileCoordsXY* tile = tiles.begin(); tile != tiles.end(); ++tile)
2289     {
2290         auto surfaceElement = map_get_surface_element_at(tile->ToCoordsXY());
2291         if (surfaceElement != nullptr)
2292         {
2293             if (doNotDowngrade && surfaceElement->GetOwnership() == OWNERSHIP_OWNED)
2294                 continue;
2295 
2296             surfaceElement->SetOwnership(ownership);
2297             update_park_fences_around_tile({ (*tile).x * 32, (*tile).y * 32 });
2298         }
2299     }
2300 }
2301