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