OpenRCT2/src/openrct2/world/Map.cpp

2235 lines
67 KiB
C++
Raw Normal View History

/*****************************************************************************
* Copyright (c) 2014-2024 OpenRCT2 developers
2014-04-09 04:09:30 +02:00
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
2014-04-09 04:09:30 +02:00
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
2014-04-09 04:09:30 +02:00
*****************************************************************************/
2018-06-22 23:17:03 +02:00
#include "Map.h"
2017-12-13 13:02:24 +01:00
#include "../Cheats.h"
#include "../Context.h"
2018-06-22 23:17:03 +02:00
#include "../Game.h"
#include "../GameState.h"
2018-06-22 23:17:03 +02:00
#include "../Input.h"
#include "../OpenRCT2.h"
Split actions hpp files into separate h and cpp files (#13548) * Split up SmallSceneryPlace/Remove Added undo function for Remove Scenery * Refactor: Balloon and Banner actions hpp=>h/cpp * Refactor: rename all action *.hpp files to *.cpp This is preparation for separation in later commits. Note that without the complete set of commits in this branch, the code will not build. * Refactor Clear, Climate, Custom, and Footpath actions hpp=>h/cpp * VSCode: add src subdirectories to includePath * Refactor Guest actions hpp=>h/cpp * Refactor Land actions hpp=>h/cpp * Refactor LargeScenery actions hpp=>h/cpp * Refactor Load, Maze, Network actions hpp=>h/cpp * Refactor Park actions hpp=>h/cpp * Refactor/style: move private function declarations in actions *.h Previous action .h files included private function declarations with private member variables, before public function declarations. This commit re-orders the header files to the following order: - public member variables - private member variables - public functions - private functions * Refactor Pause action hpp=>h/cpp * Refactor Peep, Place, Player actions hpp=>h/cpp * Refactor Ride actions hpp=>h/cpp * Refactor Scenario, Set*, Sign* actions hpp=>h/cpp * Refactor SmallScenerySetColourAction hpp=>h/cpp * Refactor Staff actions hpp=>h/cpp * Refactor Surface, Tile, Track* actions hpp=>h/cpp * Refactor Wall and Water actions hpp=>h/cpp * Fix various includes and other compile errors Update includes for tests. Move static function declarations to .h files Add explicit includes to various files that were previously implicit (the required header was a nested include in an action hpp file, and the action .h file does not include that header) Move RideSetStatus string enum to the cpp file to avoid unused imports * Xcode: modify project file for actions refactor * Cleanup whitespace and end-of-file newlines Co-authored-by: duncanspumpkin <duncans_pumpkin@hotmail.co.uk>
2020-12-10 07:39:10 +01:00
#include "../actions/BannerRemoveAction.h"
#include "../actions/LargeSceneryRemoveAction.h"
#include "../actions/ParkEntranceRemoveAction.h"
#include "../actions/WallRemoveAction.h"
2018-06-22 23:17:03 +02:00
#include "../audio/audio.h"
#include "../config/Config.h"
2018-03-13 21:07:15 +01:00
#include "../core/Guard.hpp"
#include "../interface/Cursors.h"
#include "../interface/Viewport.h"
2018-01-06 01:05:16 +01:00
#include "../interface/Window.h"
2018-01-06 18:32:25 +01:00
#include "../localisation/Date.h"
#include "../localisation/Localisation.h"
2017-10-06 22:37:06 +02:00
#include "../management/Finance.h"
#include "../network/network.h"
2023-01-26 19:44:42 +01:00
#include "../object/LargeSceneryEntry.h"
#include "../object/ObjectManager.h"
#include "../object/SmallSceneryEntry.h"
#include "../object/TerrainSurfaceObject.h"
#include "../profiling/Profiling.h"
2021-12-18 19:50:29 +01:00
#include "../ride/RideConstruction.h"
2018-01-10 00:00:09 +01:00
#include "../ride/RideData.h"
2017-10-17 13:51:47 +02:00
#include "../ride/Track.h"
2017-10-16 12:02:23 +02:00
#include "../ride/TrackData.h"
2018-03-19 23:28:40 +01:00
#include "../ride/TrackDesign.h"
2018-01-02 18:58:43 +01:00
#include "../scenario/Scenario.h"
2017-12-13 13:02:24 +01:00
#include "../util/Util.h"
#include "../windows/Intent.h"
2021-11-21 19:25:27 +01:00
#include "../world/TilePointerIndex.hpp"
2017-12-14 10:34:12 +01:00
#include "Banner.h"
2017-03-11 12:24:18 +01:00
#include "Climate.h"
2018-01-11 10:59:26 +01:00
#include "Footpath.h"
#include "MapAnimation.h"
2017-12-31 21:40:00 +01:00
#include "Park.h"
2018-01-11 10:59:26 +01:00
#include "Scenery.h"
2018-06-22 23:17:03 +02:00
#include "Surface.h"
#include "TileElementsView.h"
2017-11-20 11:31:36 +01:00
#include "TileInspector.h"
2017-11-20 11:13:55 +01:00
#include "Wall.h"
2015-07-12 02:46:52 +02:00
#include <algorithm>
2018-11-21 23:16:04 +01:00
#include <iterator>
#include <memory>
using namespace OpenRCT2;
/**
* Replaces 0x00993CCC, 0x00993CCE
*/
// clang-format off
const std::array<CoordsXY, 8> CoordsDirectionDelta = {
CoordsXY{ -COORDS_XY_STEP, 0 },
CoordsXY{ 0, +COORDS_XY_STEP },
CoordsXY{ +COORDS_XY_STEP, 0 },
CoordsXY{ 0, -COORDS_XY_STEP },
CoordsXY{ -COORDS_XY_STEP, +COORDS_XY_STEP },
CoordsXY{ +COORDS_XY_STEP, +COORDS_XY_STEP },
CoordsXY{ +COORDS_XY_STEP, -COORDS_XY_STEP },
CoordsXY{ -COORDS_XY_STEP, -COORDS_XY_STEP }
};
// clang-format on
const TileCoordsXY TileDirectionDelta[] = {
{ -1, 0 }, { 0, +1 }, { +1, 0 }, { 0, -1 }, { -1, +1 }, { +1, +1 }, { +1, -1 }, { -1, -1 },
};
constexpr size_t MIN_TILE_ELEMENTS = 1024;
2018-06-22 23:17:03 +02:00
uint16_t gMapSelectFlags;
uint16_t gMapSelectType;
2019-12-12 12:04:40 +01:00
CoordsXY gMapSelectPositionA;
CoordsXY gMapSelectPositionB;
CoordsXYZ gMapSelectArrowPosition;
2018-06-22 23:17:03 +02:00
uint8_t gMapSelectArrowDirection;
2016-05-14 01:54:13 +02:00
2019-03-28 19:29:51 +01:00
std::vector<CoordsXY> gMapSelectionTiles;
2015-10-11 13:26:33 +02:00
bool gLandMountainMode;
bool gLandPaintMode;
bool gClearSmallScenery;
bool gClearLargeScenery;
bool gClearFootpath;
uint32_t gLandRemainingOwnershipSales;
uint32_t gLandRemainingConstructionSales;
bool gMapLandRightsUpdateSuccess;
2016-09-10 18:46:34 +02:00
static TilePointerIndex<TileElement> _tileIndex;
static TilePointerIndex<TileElement> _tileIndexStash;
static std::vector<TileElement> _tileElementsStash;
static size_t _tileElementsInUse;
static size_t _tileElementsInUseStash;
static TileCoordsXY _mapSizeStash;
void StashMap()
{
2024-02-25 16:53:41 +01:00
auto& gameState = GetGameState();
_tileIndexStash = std::move(_tileIndex);
2024-02-25 16:53:41 +01:00
_tileElementsStash = std::move(gameState.TileElements);
2024-02-12 22:32:08 +01:00
_mapSizeStash = GetGameState().MapSize;
_tileElementsInUseStash = _tileElementsInUse;
}
void UnstashMap()
{
2024-02-25 16:53:41 +01:00
auto& gameState = GetGameState();
_tileIndex = std::move(_tileIndexStash);
2024-02-25 16:53:41 +01:00
gameState.TileElements = std::move(_tileElementsStash);
2024-02-12 22:32:08 +01:00
GetGameState().MapSize = _mapSizeStash;
_tileElementsInUse = _tileElementsInUseStash;
}
2024-02-12 22:32:08 +01:00
CoordsXY GetMapSizeUnits()
{
auto& gameState = OpenRCT2::GetGameState();
return { (gameState.MapSize.x - 1) * COORDS_XY_STEP, (gameState.MapSize.y - 1) * COORDS_XY_STEP };
}
CoordsXY GetMapSizeMinus2()
{
auto& gameState = OpenRCT2::GetGameState();
return { (gameState.MapSize.x * COORDS_XY_STEP) + (8 * COORDS_XY_STEP - 2),
(gameState.MapSize.y * COORDS_XY_STEP) + (8 * COORDS_XY_STEP - 2) };
}
CoordsXY GetMapSizeMaxXY()
{
return GetMapSizeUnits() - CoordsXY{ 1, 1 };
}
const std::vector<TileElement>& GetTileElements()
{
2024-02-25 16:53:41 +01:00
return GetGameState().TileElements;
}
void SetTileElements(std::vector<TileElement>&& tileElements)
{
2024-02-25 16:53:41 +01:00
auto& gameState = GetGameState();
gameState.TileElements = std::move(tileElements);
_tileIndex = TilePointerIndex<TileElement>(
kMaximumMapSizeTechnical, gameState.TileElements.data(), gameState.TileElements.size());
2024-02-25 16:53:41 +01:00
_tileElementsInUse = gameState.TileElements.size();
}
2021-11-14 21:58:33 +01:00
static TileElement GetDefaultSurfaceElement()
{
TileElement el;
2021-12-11 00:39:39 +01:00
el.ClearAs(TileElementType::Surface);
2021-11-14 21:58:33 +01:00
el.SetLastForTile(true);
el.BaseHeight = 14;
el.ClearanceHeight = 14;
2021-11-14 21:58:33 +01:00
el.AsSurface()->SetWaterHeight(0);
el.AsSurface()->SetSlope(TILE_ELEMENT_SLOPE_FLAT);
el.AsSurface()->SetGrassLength(GRASS_LENGTH_CLEAR_0);
el.AsSurface()->SetOwnership(OWNERSHIP_UNOWNED);
el.AsSurface()->SetParkFences(0);
el.AsSurface()->SetSurfaceObjectIndex(0);
el.AsSurface()->SetEdgeObjectIndex(0);
2021-11-14 21:58:33 +01:00
return el;
}
std::vector<TileElement> GetReorganisedTileElementsWithoutGhosts()
{
std::vector<TileElement> newElements;
2024-02-25 16:53:41 +01:00
newElements.reserve(std::max(MIN_TILE_ELEMENTS, GetGameState().TileElements.size()));
for (int32_t y = 0; y < kMaximumMapSizeTechnical; y++)
2021-11-14 21:58:33 +01:00
{
for (int32_t x = 0; x < kMaximumMapSizeTechnical; x++)
2021-11-14 21:58:33 +01:00
{
auto oldSize = newElements.size();
// Add all non-ghost elements
2023-04-05 22:18:12 +02:00
const auto* element = MapGetFirstElementAt(TileCoordsXY{ x, y });
2021-11-14 21:58:33 +01:00
if (element != nullptr)
{
do
{
if (!element->IsGhost())
{
newElements.push_back(*element);
}
} while (!(element++)->IsLastForTile());
}
// Insert default surface element if no elements were added
auto newSize = newElements.size();
if (oldSize == newSize)
{
newElements.push_back(GetDefaultSurfaceElement());
}
// Ensure last element of tile has last flag set
auto& lastEl = newElements.back();
lastEl.SetLastForTile(true);
}
}
return newElements;
}
static void ReorganiseTileElements(size_t capacity)
{
2022-11-06 21:49:07 +01:00
ContextSetCurrentCursor(CursorID::ZZZ);
std::vector<TileElement> newElements;
newElements.reserve(std::max(MIN_TILE_ELEMENTS, capacity));
for (int32_t y = 0; y < kMaximumMapSizeTechnical; y++)
{
for (int32_t x = 0; x < kMaximumMapSizeTechnical; x++)
{
const auto* element = MapGetFirstElementAt(TileCoordsXY{ x, y });
if (element == nullptr)
{
2021-11-14 21:58:33 +01:00
newElements.push_back(GetDefaultSurfaceElement());
}
else
{
do
{
newElements.push_back(*element);
} while (!(element++)->IsLastForTile());
}
}
}
SetTileElements(std::move(newElements));
}
void ReorganiseTileElements()
{
2024-02-25 16:53:41 +01:00
ReorganiseTileElements(GetGameState().TileElements.size());
}
static bool MapCheckFreeElementsAndReorganise(size_t numElementsOnTile, size_t numNewElements)
{
// Check hard cap on num in use tiles (this would be the size of _tileElements immediately after a reorg)
if (_tileElementsInUse + numNewElements > MAX_TILE_ELEMENTS)
{
return false;
}
2024-02-25 16:53:41 +01:00
auto& gameState = GetGameState();
auto totalElementsRequired = numElementsOnTile + numNewElements;
2024-02-25 16:53:41 +01:00
auto freeElements = gameState.TileElements.capacity() - gameState.TileElements.size();
if (freeElements >= totalElementsRequired)
{
return true;
}
2021-09-15 22:22:15 +02:00
// if space issue is due to fragmentation then Reorg Tiles without increasing capacity
2024-02-25 16:53:41 +01:00
if (gameState.TileElements.size() > totalElementsRequired + _tileElementsInUse)
{
2021-09-15 22:22:15 +02:00
ReorganiseTileElements();
// This check is not expected to fail
2024-02-25 16:53:41 +01:00
freeElements = gameState.TileElements.capacity() - gameState.TileElements.size();
2021-09-15 22:22:15 +02:00
if (freeElements >= totalElementsRequired)
{
2021-09-15 22:22:15 +02:00
return true;
}
}
2021-09-15 22:22:15 +02:00
// Capacity must increase to handle the space (Note capacity can go above MAX_TILE_ELEMENTS)
2024-02-25 16:53:41 +01:00
auto newCapacity = gameState.TileElements.capacity() * 2;
2021-09-15 22:22:15 +02:00
ReorganiseTileElements(newCapacity);
return true;
}
static size_t CountElementsOnTile(const CoordsXY& loc);
bool MapCheckCapacityAndReorganise(const CoordsXY& loc, size_t numElements)
{
auto numElementsOnTile = CountElementsOnTile(loc);
return MapCheckFreeElementsAndReorganise(numElementsOnTile, numElements);
}
static void ClearElementsAt(const CoordsXY& loc);
2014-04-09 04:09:30 +02:00
void TileElementIteratorBegin(TileElementIterator* it)
2015-01-22 01:19:05 +01:00
{
it->x = 1;
it->y = 1;
it->element = MapGetFirstElementAt(TileCoordsXY{ 1, 1 });
2015-01-22 01:19:05 +01:00
}
int32_t TileElementIteratorNext(TileElementIterator* it)
2015-01-22 01:19:05 +01:00
{
2018-06-22 23:17:03 +02:00
if (it->element == nullptr)
{
it->element = MapGetFirstElementAt(TileCoordsXY{ it->x, it->y });
return 1;
}
2018-06-22 23:17:03 +02:00
if (!it->element->IsLastForTile())
{
it->element++;
return 1;
}
auto& gameState = GetGameState();
if (it->y < (gameState.MapSize.y - 2))
2018-06-22 23:17:03 +02:00
{
it->y++;
it->element = MapGetFirstElementAt(TileCoordsXY{ it->x, it->y });
return 1;
}
if (it->x < (gameState.MapSize.x - 2))
2018-06-22 23:17:03 +02:00
{
it->y = 1;
it->x++;
it->element = MapGetFirstElementAt(TileCoordsXY{ it->x, it->y });
return 1;
}
return 0;
2015-01-22 01:19:05 +01:00
}
void TileElementIteratorRestartForTile(TileElementIterator* it)
2015-01-22 01:19:05 +01:00
{
2018-01-04 06:58:44 +01:00
it->element = nullptr;
2015-01-22 01:19:05 +01:00
}
static bool IsTileLocationValid(const TileCoordsXY& coords)
{
const bool is_x_valid = coords.x < kMaximumMapSizeTechnical && coords.x >= 0;
const bool is_y_valid = coords.y < kMaximumMapSizeTechnical && coords.y >= 0;
return is_x_valid && is_y_valid;
}
TileElement* MapGetFirstElementAt(const TileCoordsXY& tilePos)
2015-01-22 01:19:05 +01:00
{
if (!IsTileLocationValid(tilePos))
2018-06-22 23:17:03 +02:00
{
LOG_VERBOSE("Trying to access element outside of range");
2018-01-04 06:58:44 +01:00
return nullptr;
}
return _tileIndex.GetFirstElementAt(tilePos);
}
TileElement* MapGetFirstElementAt(const CoordsXY& elementPos)
{
return MapGetFirstElementAt(TileCoordsXY{ elementPos });
2015-01-22 01:19:05 +01:00
}
TileElement* MapGetNthElementAt(const CoordsXY& coords, int32_t n)
{
TileElement* tileElement = MapGetFirstElementAt(coords);
2018-06-22 23:17:03 +02:00
if (tileElement == nullptr)
{
2018-01-04 06:58:44 +01:00
return nullptr;
}
// Iterate through elements on this tile. This has to be walked, rather than
// jumped directly to, because n may exceed element count for given tile,
// and the order of tiles (unlike elements) is not synced over multiplayer.
2018-06-22 23:17:03 +02:00
while (n >= 0)
{
if (n == 0)
{
2017-10-31 14:03:45 +01:00
return tileElement;
}
2018-06-22 23:17:03 +02:00
if (tileElement->IsLastForTile())
{
break;
}
2017-10-31 14:03:45 +01:00
tileElement++;
n--;
}
// The element sought for is not within given tile.
2018-01-04 06:58:44 +01:00
return nullptr;
}
TileElement* MapGetFirstTileElementWithBaseHeightBetween(const TileCoordsXYRangedZ& loc, TileElementType type)
{
2023-04-05 22:18:12 +02:00
TileElement* tileElement = MapGetFirstElementAt(loc);
if (tileElement == nullptr)
return nullptr;
do
{
if (tileElement->GetType() != type)
continue;
if (tileElement->BaseHeight >= loc.baseZ && tileElement->BaseHeight <= loc.clearanceZ)
return tileElement;
} while (!(tileElement++)->IsLastForTile());
return nullptr;
}
void MapSetTileElement(const TileCoordsXY& tilePos, TileElement* elements)
2015-06-27 16:17:54 +02:00
{
if (!MapIsLocationValid(tilePos.ToCoordsXY()))
2018-06-22 23:17:03 +02:00
{
LOG_ERROR("Trying to access element outside of range");
return;
}
_tileIndex.SetTile(tilePos, elements);
2015-06-27 16:17:54 +02:00
}
SurfaceElement* MapGetSurfaceElementAt(const TileCoordsXY& coords)
{
auto view = TileElementsView<SurfaceElement>(coords);
return *view.begin();
}
SurfaceElement* MapGetSurfaceElementAt(const CoordsXY& coords)
{
return MapGetSurfaceElementAt(TileCoordsXY{ coords });
}
PathElement* MapGetPathElementAt(const TileCoordsXYZ& loc)
2018-06-22 23:17:03 +02:00
{
for (auto* element : TileElementsView<PathElement>(loc.ToCoordsXY()))
2018-06-22 23:17:03 +02:00
{
if (element->IsGhost())
continue;
if (element->BaseHeight != loc.z)
continue;
return element;
}
2018-01-04 06:58:44 +01:00
return nullptr;
}
BannerElement* MapGetBannerElementAt(const CoordsXYZ& bannerPos, uint8_t position)
2018-06-22 23:17:03 +02:00
{
const auto bannerTilePos = TileCoordsXYZ{ bannerPos };
for (auto* element : TileElementsView<BannerElement>(bannerPos))
2018-06-22 23:17:03 +02:00
{
if (element->BaseHeight != bannerTilePos.z)
continue;
if (element->GetPosition() != position)
continue;
return element;
}
2018-01-04 06:58:44 +01:00
return nullptr;
}
2014-04-09 04:09:30 +02:00
/**
2015-10-20 20:16:30 +02:00
*
2014-04-09 04:09:30 +02:00
* rct2: 0x0068AB4C
*/
void MapInit(const TileCoordsXY& size)
2014-04-09 04:09:30 +02:00
{
auto numTiles = kMaximumMapSizeTechnical * kMaximumMapSizeTechnical;
2022-07-04 21:59:09 +02:00
SetTileElements(std::vector<TileElement>(numTiles, GetDefaultSurfaceElement()));
auto& gameState = GetGameState();
gameState.GrassSceneryTileLoopPosition = 0;
gameState.WidePathTileLoopPosition = {};
2024-02-12 22:32:08 +01:00
gameState.MapSize = size;
MapRemoveOutOfRangeElements();
MapAnimationAutoCreate();
2018-02-05 22:59:44 +01:00
auto intent = Intent(INTENT_ACTION_MAP);
2022-11-06 21:49:07 +01:00
ContextBroadcastIntent(&intent);
2014-04-09 18:06:47 +02:00
}
/**
* Counts the number of surface tiles that offer land ownership rights for sale,
* but haven't been bought yet. It updates gLandRemainingOwnershipSales and
* gLandRemainingConstructionSales.
2018-06-22 23:17:03 +02:00
*/
void MapCountRemainingLandRights()
{
gLandRemainingOwnershipSales = 0;
gLandRemainingConstructionSales = 0;
2024-02-12 22:32:08 +01:00
auto& gameState = GetGameState();
2024-02-12 22:32:08 +01:00
for (int32_t y = 0; y < gameState.MapSize.y; y++)
{
2024-02-12 22:32:08 +01:00
for (int32_t x = 0; x < gameState.MapSize.x; x++)
{
auto* surfaceElement = MapGetSurfaceElementAt(TileCoordsXY{ x, y });
// Surface elements are sometimes hacked out to save some space for other map elements
if (surfaceElement == nullptr)
{
continue;
}
uint8_t flags = surfaceElement->GetOwnership();
// Do not combine this condition with (flags & OWNERSHIP_AVAILABLE)
// As some RCT1 parks have owned tiles with the 'construction rights available' flag also set
if (!(flags & OWNERSHIP_OWNED))
{
if (flags & OWNERSHIP_AVAILABLE)
{
gLandRemainingOwnershipSales++;
}
2018-06-22 23:17:03 +02:00
else if (
(flags & OWNERSHIP_CONSTRUCTION_RIGHTS_AVAILABLE) && (flags & OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED) == 0)
{
gLandRemainingConstructionSales++;
}
}
}
}
}
/**
2017-10-31 12:57:40 +01:00
* This is meant to strip TILE_ELEMENT_FLAG_GHOST flag from all elements when
* importing a park.
*
* This can only exist in hacked parks, as we remove ghost elements while saving.
*
* This is less invasive than removing ghost elements themselves, as they can
* contain valid data.
*/
void MapStripGhostFlagFromElements()
{
2024-02-25 16:53:41 +01:00
auto& gameState = GetGameState();
for (auto& element : gameState.TileElements)
{
element.SetGhost(false);
}
}
2014-04-10 16:14:47 +02:00
/**
* Return the absolute height of an element, given its (x,y) coordinates
*
* ax: x
* cx: y
* dx: return remember to & with 0xFFFF if you don't want water affecting results
2014-04-10 16:14:47 +02:00
* rct2: 0x00662783
*/
int16_t TileElementHeight(const CoordsXY& loc)
2014-04-10 16:14:47 +02:00
{
// Off the map
if (!MapIsLocationValid(loc))
return kMinimumLandZ;
// Get the surface element for the tile
auto surfaceElement = MapGetSurfaceElementAt(loc);
if (surfaceElement == nullptr)
2018-06-22 23:17:03 +02:00
{
return kMinimumLandZ;
}
auto height = surfaceElement->GetBaseZ();
auto slope = surfaceElement->GetSlope();
return TileElementHeight(CoordsXYZ{ loc, height }, slope);
}
int16_t TileElementHeight(const CoordsXYZ& loc, uint8_t slope)
{
// Off the map
if (!MapIsLocationValid(loc))
return kMinimumLandZ;
auto height = loc.z;
uint8_t extra_height = (slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) >> 4; // 0x10 is the 5th bit - sets slope to double height
// Remove the extra height bit
slope &= TILE_ELEMENT_SLOPE_ALL_CORNERS_UP;
int8_t quad = 0, quad_extra = 0; // which quadrant the element is in?
2018-06-22 23:17:03 +02:00
// quad_extra is for extra height tiles
2018-06-22 23:17:03 +02:00
uint8_t xl, yl; // coordinates across this tile
uint8_t TILE_SIZE = 32;
xl = loc.x & 0x1f;
yl = loc.y & 0x1f;
// Slope logic:
// Each of the four bits in slope represents that corner being raised
// slope == 15 (all four bits) is not used and slope == 0 is flat
// If the extra_height bit is set, then the slope goes up two z-levels
// We arbitrarily take the SW corner to be closest to the viewer
// One corner up
2018-06-22 23:17:03 +02:00
if (slope == TILE_ELEMENT_SLOPE_N_CORNER_UP || slope == TILE_ELEMENT_SLOPE_E_CORNER_UP
|| slope == TILE_ELEMENT_SLOPE_S_CORNER_UP || slope == TILE_ELEMENT_SLOPE_W_CORNER_UP)
{
2018-06-22 23:17:03 +02:00
switch (slope)
{
case TILE_ELEMENT_SLOPE_N_CORNER_UP:
quad = xl + yl - TILE_SIZE;
break;
case TILE_ELEMENT_SLOPE_E_CORNER_UP:
quad = xl - yl;
break;
case TILE_ELEMENT_SLOPE_S_CORNER_UP:
quad = TILE_SIZE - yl - xl;
break;
case TILE_ELEMENT_SLOPE_W_CORNER_UP:
quad = yl - xl;
break;
}
// If the element is in the quadrant with the slope, raise its height
2018-06-22 23:17:03 +02:00
if (quad > 0)
{
height += quad / 2;
}
}
// One side up
2018-06-22 23:17:03 +02:00
switch (slope)
{
case TILE_ELEMENT_SLOPE_NE_SIDE_UP:
height += xl / 2;
break;
2018-06-22 23:17:03 +02:00
case TILE_ELEMENT_SLOPE_SE_SIDE_UP:
height += (TILE_SIZE - yl) / 2;
break;
2018-06-22 23:17:03 +02:00
case TILE_ELEMENT_SLOPE_NW_SIDE_UP:
height += yl / 2;
break;
2018-06-22 23:17:03 +02:00
case TILE_ELEMENT_SLOPE_SW_SIDE_UP:
height += (TILE_SIZE - xl) / 2;
break;
2018-06-22 23:17:03 +02:00
}
// One corner down
if ((slope == TILE_ELEMENT_SLOPE_W_CORNER_DN) || (slope == TILE_ELEMENT_SLOPE_S_CORNER_DN)
|| (slope == TILE_ELEMENT_SLOPE_E_CORNER_DN) || (slope == TILE_ELEMENT_SLOPE_N_CORNER_DN))
{
switch (slope)
{
case TILE_ELEMENT_SLOPE_W_CORNER_DN:
quad_extra = xl + TILE_SIZE - yl;
quad = xl - yl;
break;
case TILE_ELEMENT_SLOPE_S_CORNER_DN:
quad_extra = xl + yl;
quad = xl + yl - TILE_SIZE;
2018-06-22 23:17:03 +02:00
break;
case TILE_ELEMENT_SLOPE_E_CORNER_DN:
quad_extra = TILE_SIZE - xl + yl;
quad = yl - xl;
break;
case TILE_ELEMENT_SLOPE_N_CORNER_DN:
quad_extra = (TILE_SIZE - xl) + (TILE_SIZE - yl);
quad = TILE_SIZE - yl - xl;
2018-06-22 23:17:03 +02:00
break;
}
2018-06-22 23:17:03 +02:00
if (extra_height)
{
height += quad_extra / 2;
return height;
}
// This tile is essentially at the next height level
height += LAND_HEIGHT_STEP;
// so we move *down* the slope
2018-06-22 23:17:03 +02:00
if (quad < 0)
{
height += quad / 2;
}
}
// Valleys
2018-06-22 23:17:03 +02:00
if ((slope == TILE_ELEMENT_SLOPE_W_E_VALLEY) || (slope == TILE_ELEMENT_SLOPE_N_S_VALLEY))
{
switch (slope)
{
case TILE_ELEMENT_SLOPE_W_E_VALLEY:
quad = std::abs(xl - yl);
2018-06-22 23:17:03 +02:00
break;
case TILE_ELEMENT_SLOPE_N_S_VALLEY:
quad = std::abs(xl + yl - TILE_SIZE);
2018-06-22 23:17:03 +02:00
break;
}
height += quad / 2;
}
return height;
}
2014-05-04 19:51:36 +02:00
int16_t TileElementWaterHeight(const CoordsXY& loc)
{
// Off the map
if (!MapIsLocationValid(loc))
return 0;
// Get the surface element for the tile
auto surfaceElement = MapGetSurfaceElementAt(loc);
if (surfaceElement == nullptr)
{
return 0;
}
return surfaceElement->GetWaterHeight();
}
/**
* Checks if the tile at coordinate at height counts as connected.
* @return 1 if connected, 0 otherwise
*/
bool MapCoordIsConnected(const TileCoordsXYZ& loc, uint8_t faceDirection)
{
TileElement* tileElement = MapGetFirstElementAt(loc);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return false;
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Path)
continue;
uint8_t slopeDirection = tileElement->AsPath()->GetSlopeDirection();
2018-09-16 16:17:35 +02:00
if (tileElement->AsPath()->IsSloped())
2018-06-22 23:17:03 +02:00
{
if (slopeDirection == faceDirection)
2018-06-22 23:17:03 +02:00
{
if (loc.z == tileElement->BaseHeight + 2)
2018-01-04 13:36:08 +01:00
return true;
2018-06-22 23:17:03 +02:00
}
else if (DirectionReverse(slopeDirection) == faceDirection && loc.z == tileElement->BaseHeight)
2018-06-22 23:17:03 +02:00
{
2018-01-04 13:36:08 +01:00
return true;
}
2018-06-22 23:17:03 +02:00
}
else
{
if (loc.z == tileElement->BaseHeight && (tileElement->AsPath()->GetEdges() & (1 << faceDirection)))
2018-01-04 13:36:08 +01:00
return true;
}
} while (!(tileElement++)->IsLastForTile());
2018-01-04 13:36:08 +01:00
return false;
}
2014-10-09 21:31:58 +02:00
/**
*
* rct2: 0x006A876D
*/
void MapUpdatePathWideFlags()
2014-10-09 21:31:58 +02:00
{
PROFILED_FUNCTION();
2018-06-22 23:17:03 +02:00
if (gScreenFlags & (SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER))
{
return;
}
// Presumably update_path_wide_flags is too computationally expensive to call for every
// tile every update, so gWidePathTileLoopX and gWidePathTileLoopY store the x and y
// progress. A maximum of 128 calls is done per update.
CoordsXY& loopPosition = GetGameState().WidePathTileLoopPosition;
2018-06-22 23:17:03 +02:00
for (int32_t i = 0; i < 128; i++)
{
FootpathUpdatePathWideFlags(loopPosition);
// Next x, y tile
loopPosition.x += COORDS_XY_STEP;
if (loopPosition.x >= MAXIMUM_MAP_SIZE_BIG)
2018-06-22 23:17:03 +02:00
{
loopPosition.x = 0;
loopPosition.y += COORDS_XY_STEP;
if (loopPosition.y >= MAXIMUM_MAP_SIZE_BIG)
2018-06-22 23:17:03 +02:00
{
loopPosition.y = 0;
}
}
}
2014-10-09 21:31:58 +02:00
}
/**
*
* rct2: 0x006A7B84
*/
int32_t MapHeightFromSlope(const CoordsXY& coords, int32_t slopeDirection, bool isSloped)
{
2018-09-16 16:17:35 +02:00
if (!isSloped)
return 0;
2020-03-04 18:43:09 +01:00
switch (slopeDirection % NumOrthogonalDirections)
2018-06-22 23:17:03 +02:00
{
case TILE_ELEMENT_DIRECTION_WEST:
2018-09-16 16:17:35 +02:00
return (31 - (coords.x & 31)) / 2;
case TILE_ELEMENT_DIRECTION_NORTH:
2018-09-16 16:17:35 +02:00
return (coords.y & 31) / 2;
case TILE_ELEMENT_DIRECTION_EAST:
2018-09-16 16:17:35 +02:00
return (coords.x & 31) / 2;
case TILE_ELEMENT_DIRECTION_SOUTH:
2018-09-16 16:17:35 +02:00
return (31 - (coords.y & 31)) / 2;
}
return 0;
2014-09-20 15:06:42 +02:00
}
bool MapIsLocationValid(const CoordsXY& coords)
{
const bool is_x_valid = coords.x < MAXIMUM_MAP_SIZE_BIG && coords.x >= 0;
const bool is_y_valid = coords.y < MAXIMUM_MAP_SIZE_BIG && coords.y >= 0;
2019-05-10 22:00:38 +02:00
return is_x_valid && is_y_valid;
}
bool MapIsEdge(const CoordsXY& coords)
{
auto mapSizeUnits = GetMapSizeUnits();
return (coords.x < 32 || coords.y < 32 || coords.x >= mapSizeUnits.x || coords.y >= mapSizeUnits.y);
}
bool MapCanBuildAt(const CoordsXYZ& loc)
2017-03-13 22:47:43 +01:00
{
if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
return true;
2024-03-03 22:44:15 +01:00
if (GetGameState().Cheats.SandboxMode)
return true;
if (MapIsLocationOwned(loc))
return true;
return false;
2017-03-13 22:47:43 +01:00
}
2014-11-02 02:41:00 +01:00
/**
*
* rct2: 0x00664F72
*/
bool MapIsLocationOwned(const CoordsXYZ& loc)
2014-11-02 02:41:00 +01:00
{
// This check is to avoid throwing lots of messages in logs.
if (MapIsLocationValid(loc))
2018-06-22 23:17:03 +02:00
{
auto* surfaceElement = MapGetSurfaceElementAt(loc);
if (surfaceElement != nullptr)
2018-06-22 23:17:03 +02:00
{
if (surfaceElement->GetOwnership() & OWNERSHIP_OWNED)
return true;
if (surfaceElement->GetOwnership() & OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED)
2018-06-22 23:17:03 +02:00
{
if (loc.z < surfaceElement->GetBaseZ() || loc.z >= surfaceElement->GetBaseZ() + ConstructionRightsClearanceBig)
return true;
}
}
}
return false;
2014-11-02 02:41:00 +01:00
}
/**
*
* rct2: 0x00664F2C
*/
bool MapIsLocationInPark(const CoordsXY& coords)
2014-11-02 02:41:00 +01:00
{
if (MapIsLocationValid(coords))
2018-06-04 19:18:52 +02:00
{
auto surfaceElement = MapGetSurfaceElementAt(coords);
if (surfaceElement == nullptr)
return false;
if (surfaceElement->GetOwnership() & OWNERSHIP_OWNED)
return true;
}
return false;
2014-11-02 04:37:56 +01:00
}
bool MapIsLocationOwnedOrHasRights(const CoordsXY& loc)
2015-08-04 17:01:31 +02:00
{
if (MapIsLocationValid(loc))
2018-06-22 23:17:03 +02:00
{
auto surfaceElement = MapGetSurfaceElementAt(loc);
if (surfaceElement == nullptr)
2018-06-22 23:17:03 +02:00
{
return false;
}
if (surfaceElement->GetOwnership() & OWNERSHIP_OWNED)
2018-06-22 23:17:03 +02:00
return true;
if (surfaceElement->GetOwnership() & OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED)
2018-06-22 23:17:03 +02:00
return true;
}
return false;
2015-08-04 17:01:31 +02:00
}
int32_t MapGetCornerHeight(int32_t z, int32_t slope, int32_t direction)
{
2018-06-22 23:17:03 +02:00
switch (direction)
{
case 0:
if (slope & TILE_ELEMENT_SLOPE_N_CORNER_UP)
{
z += 2;
2018-06-22 23:17:03 +02:00
if (slope == (TILE_ELEMENT_SLOPE_S_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT))
{
z += 2;
}
}
2018-06-22 23:17:03 +02:00
break;
case 1:
if (slope & TILE_ELEMENT_SLOPE_E_CORNER_UP)
{
z += 2;
2018-06-22 23:17:03 +02:00
if (slope == (TILE_ELEMENT_SLOPE_W_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT))
{
z += 2;
}
}
2018-06-22 23:17:03 +02:00
break;
case 2:
if (slope & TILE_ELEMENT_SLOPE_S_CORNER_UP)
{
z += 2;
2018-06-22 23:17:03 +02:00
if (slope == (TILE_ELEMENT_SLOPE_N_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT))
{
z += 2;
}
}
2018-06-22 23:17:03 +02:00
break;
case 3:
if (slope & TILE_ELEMENT_SLOPE_W_CORNER_UP)
{
z += 2;
2018-06-22 23:17:03 +02:00
if (slope == (TILE_ELEMENT_SLOPE_E_CORNER_DN | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT))
{
z += 2;
}
}
2018-06-22 23:17:03 +02:00
break;
}
return z;
}
int32_t TileElementGetCornerHeight(const SurfaceElement* surfaceElement, int32_t direction)
{
int32_t z = surfaceElement->BaseHeight;
int32_t slope = surfaceElement->GetSlope();
return MapGetCornerHeight(z, slope, direction);
}
uint8_t MapGetLowestLandHeight(const MapRange& range)
{
auto mapSizeMax = GetMapSizeMaxXY();
MapRange validRange = { std::max(range.GetLeft(), 32), std::max(range.GetTop(), 32),
std::min(range.GetRight(), mapSizeMax.x), std::min(range.GetBottom(), mapSizeMax.y) };
uint8_t min_height = 0xFF;
for (int32_t yi = validRange.GetTop(); yi <= validRange.GetBottom(); yi += COORDS_XY_STEP)
2018-06-22 23:17:03 +02:00
{
for (int32_t xi = validRange.GetLeft(); xi <= validRange.GetRight(); xi += COORDS_XY_STEP)
2018-06-22 23:17:03 +02:00
{
auto* surfaceElement = MapGetSurfaceElementAt(CoordsXY{ xi, yi });
if (surfaceElement != nullptr && min_height > surfaceElement->BaseHeight)
2018-06-22 23:17:03 +02:00
{
2024-03-03 22:44:15 +01:00
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !GetGameState().Cheats.SandboxMode)
{
if (!MapIsLocationInPark(CoordsXY{ xi, yi }))
{
continue;
}
}
min_height = surfaceElement->BaseHeight;
}
}
}
return min_height;
}
uint8_t MapGetHighestLandHeight(const MapRange& range)
{
auto mapSizeMax = GetMapSizeMaxXY();
MapRange validRange = { std::max(range.GetLeft(), 32), std::max(range.GetTop(), 32),
std::min(range.GetRight(), mapSizeMax.x), std::min(range.GetBottom(), mapSizeMax.y) };
uint8_t max_height = 0;
for (int32_t yi = validRange.GetTop(); yi <= validRange.GetBottom(); yi += COORDS_XY_STEP)
2018-06-22 23:17:03 +02:00
{
for (int32_t xi = validRange.GetLeft(); xi <= validRange.GetRight(); xi += COORDS_XY_STEP)
2018-06-22 23:17:03 +02:00
{
auto* surfaceElement = MapGetSurfaceElementAt(CoordsXY{ xi, yi });
if (surfaceElement != nullptr)
2018-06-22 23:17:03 +02:00
{
2024-03-03 22:44:15 +01:00
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !GetGameState().Cheats.SandboxMode)
{
if (!MapIsLocationInPark(CoordsXY{ xi, yi }))
{
continue;
}
}
uint8_t BaseHeight = surfaceElement->BaseHeight;
if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP)
BaseHeight += 2;
if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
BaseHeight += 2;
if (max_height < BaseHeight)
max_height = BaseHeight;
}
}
}
return max_height;
}
bool MapIsLocationAtEdge(const CoordsXY& loc)
{
return loc.x < 32 || loc.y < 32 || loc.x >= (MAXIMUM_TILE_START_XY) || loc.y >= (MAXIMUM_TILE_START_XY);
}
/**
*
* rct2: 0x0068B280
*/
void TileElementRemove(TileElement* tileElement)
{
// Replace Nth element by (N+1)th element.
2017-10-31 14:03:45 +01:00
// This loop will make tileElement point to the old last element position,
// after copy it to it's new position
2018-06-22 23:17:03 +02:00
if (!tileElement->IsLastForTile())
{
do
{
2017-10-31 14:03:45 +01:00
*tileElement = *(tileElement + 1);
} while (!(++tileElement)->IsLastForTile());
}
// Mark the latest element with the last element flag.
(tileElement - 1)->SetLastForTile(true);
tileElement->BaseHeight = MAX_ELEMENT_HEIGHT;
_tileElementsInUse--;
2024-02-25 16:53:41 +01:00
auto& gameState = GetGameState();
if (tileElement == &gameState.TileElements.back())
2018-06-22 23:17:03 +02:00
{
2024-02-25 16:53:41 +01:00
gameState.TileElements.pop_back();
}
}
/**
*
* rct2: 0x00675A8E
*/
void MapRemoveAllRides()
{
TileElementIterator it;
TileElementIteratorBegin(&it);
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
switch (it.element->GetType())
2018-06-22 23:17:03 +02:00
{
2021-12-11 00:39:39 +01:00
case TileElementType::Path:
2018-10-03 10:57:29 +02:00
if (it.element->AsPath()->IsQueue())
2018-06-22 23:17:03 +02:00
{
it.element->AsPath()->SetHasQueueBanner(false);
it.element->AsPath()->SetRideIndex(RideId::GetNull());
2018-06-22 23:17:03 +02:00
}
break;
2021-12-11 00:39:39 +01:00
case TileElementType::Entrance:
2018-09-26 12:13:44 +02:00
if (it.element->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_PARK_ENTRANCE)
2018-06-22 23:17:03 +02:00
break;
[[fallthrough]];
2021-12-11 00:39:39 +01:00
case TileElementType::Track:
2022-10-04 08:51:27 +02:00
FootpathQueueChainReset();
FootpathRemoveEdgesAt(TileCoordsXY{ it.x, it.y }.ToCoordsXY(), it.element);
TileElementRemove(it.element);
TileElementIteratorRestartForTile(&it);
break;
default:
break;
}
} while (TileElementIteratorNext(&it));
}
/**
*
* rct2: 0x0068AB1B
*/
void MapInvalidateMapSelectionTiles()
{
if (!(gMapSelectFlags & MAP_SELECT_FLAG_ENABLE_CONSTRUCT))
return;
2019-03-28 19:29:51 +01:00
for (const auto& position : gMapSelectionTiles)
MapInvalidateTileFull(position);
}
static void MapGetBoundingBox(const MapRange& _range, int32_t* left, int32_t* top, int32_t* right, int32_t* bottom)
2015-07-09 17:04:24 +02:00
{
2023-01-16 21:14:50 +01:00
uint32_t rotation = GetCurrentRotation();
const std::array corners{
CoordsXY{ _range.GetLeft(), _range.GetTop() },
CoordsXY{ _range.GetRight(), _range.GetTop() },
CoordsXY{ _range.GetRight(), _range.GetBottom() },
CoordsXY{ _range.GetLeft(), _range.GetBottom() },
};
*left = std::numeric_limits<int32_t>::max();
*top = std::numeric_limits<int32_t>::max();
*right = std::numeric_limits<int32_t>::min();
*bottom = std::numeric_limits<int32_t>::min();
for (const auto& corner : corners)
{
auto screenCoord = Translate3DTo2DWithZ(rotation, CoordsXYZ{ corner, 0 });
if (screenCoord.x < *left)
*left = screenCoord.x;
if (screenCoord.x > *right)
*right = screenCoord.x;
if (screenCoord.y > *bottom)
*bottom = screenCoord.y;
if (screenCoord.y < *top)
*top = screenCoord.y;
}
2015-07-09 17:04:24 +02:00
}
/**
*
* rct2: 0x0068AAE1
*/
void MapInvalidateSelectionRect()
{
int32_t x0, y0, x1, y1, left, right, top, bottom;
if (!(gMapSelectFlags & MAP_SELECT_FLAG_ENABLE))
return;
x0 = gMapSelectPositionA.x + 16;
y0 = gMapSelectPositionA.y + 16;
x1 = gMapSelectPositionB.x + 16;
y1 = gMapSelectPositionB.y + 16;
MapGetBoundingBox({ x0, y0, x1, y1 }, &left, &top, &right, &bottom);
left -= 32;
right += 32;
bottom += 32;
top -= 32 + 2080;
2023-01-16 21:14:50 +01:00
ViewportsInvalidate({ { left, top }, { right, bottom } });
}
static size_t CountElementsOnTile(const CoordsXY& loc)
{
size_t count = 0;
auto* element = _tileIndex.GetFirstElementAt(TileCoordsXY(loc));
do
2018-06-22 23:17:03 +02:00
{
count++;
} while (!(element++)->IsLastForTile());
return count;
}
static TileElement* AllocateTileElements(size_t numElementsOnTile, size_t numNewElements)
{
if (!MapCheckFreeElementsAndReorganise(numElementsOnTile, numNewElements))
{
LOG_ERROR("Cannot insert new element");
return nullptr;
}
2024-02-25 16:53:41 +01:00
auto& gameState = GetGameState();
auto oldSize = gameState.TileElements.size();
gameState.TileElements.resize(gameState.TileElements.size() + numElementsOnTile + numNewElements);
_tileElementsInUse += numNewElements;
2024-02-25 16:53:41 +01:00
return &gameState.TileElements[oldSize];
}
/**
*
* rct2: 0x0068B1F6
*/
TileElement* TileElementInsert(const CoordsXYZ& loc, int32_t occupiedQuadrants, TileElementType type)
{
const auto& tileLoc = TileCoordsXYZ(loc);
auto numElementsOnTileOld = CountElementsOnTile(loc);
auto* newTileElement = AllocateTileElements(numElementsOnTileOld, 1);
auto* originalTileElement = _tileIndex.GetFirstElementAt(tileLoc);
if (newTileElement == nullptr)
{
return nullptr;
}
// Set tile index pointer to point to new element block
_tileIndex.SetTile(tileLoc, newTileElement);
bool isLastForTile = false;
2020-02-28 21:56:04 +01:00
if (originalTileElement == nullptr)
2018-06-22 23:17:03 +02:00
{
2020-02-28 21:56:04 +01:00
isLastForTile = true;
}
else
{
// Copy all elements that are below the insert height
while (loc.z >= originalTileElement->GetBaseZ())
2018-06-22 23:17:03 +02:00
{
2020-02-28 21:56:04 +01:00
// Copy over map element
*newTileElement = *originalTileElement;
originalTileElement->BaseHeight = MAX_ELEMENT_HEIGHT;
2020-02-28 21:56:04 +01:00
originalTileElement++;
newTileElement++;
if ((newTileElement - 1)->IsLastForTile())
{
// No more elements above the insert element
(newTileElement - 1)->SetLastForTile(false);
isLastForTile = true;
break;
}
}
}
// Insert new map element
auto* insertedElement = newTileElement;
newTileElement->Type = 0;
2021-12-11 00:39:39 +01:00
newTileElement->SetType(type);
newTileElement->SetBaseZ(loc.z);
2020-03-04 21:47:34 +01:00
newTileElement->Flags = 0;
newTileElement->SetLastForTile(isLastForTile);
newTileElement->SetOccupiedQuadrants(occupiedQuadrants);
newTileElement->SetClearanceZ(loc.z);
newTileElement->Owner = 0;
2023-01-21 16:39:35 +01:00
std::memset(&newTileElement->Pad05, 0, sizeof(newTileElement->Pad05));
std::memset(&newTileElement->Pad08, 0, sizeof(newTileElement->Pad08));
2017-10-31 14:03:45 +01:00
newTileElement++;
// Insert rest of map elements above insert height
if (!isLastForTile)
2018-06-22 23:17:03 +02:00
{
do
{
// Copy over map element
2017-10-31 14:03:45 +01:00
*newTileElement = *originalTileElement;
originalTileElement->BaseHeight = MAX_ELEMENT_HEIGHT;
2017-10-31 14:03:45 +01:00
originalTileElement++;
newTileElement++;
2019-02-26 11:09:58 +01:00
} while (!((newTileElement - 1)->IsLastForTile()));
}
return insertedElement;
}
/**
* Updates grass length, scenery age and jumping fountains.
*
* rct2: 0x006646E1
*/
void MapUpdateTiles()
{
PROFILED_FUNCTION();
int32_t ignoreScreenFlags = SCREEN_FLAGS_SCENARIO_EDITOR | SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER;
if (gScreenFlags & ignoreScreenFlags)
return;
2024-02-12 22:32:08 +01:00
auto& gameState = GetGameState();
// Update 43 more tiles (for each 256x256 block)
2018-06-22 23:17:03 +02:00
for (int32_t j = 0; j < 43; j++)
{
int32_t x = 0;
int32_t y = 0;
uint16_t interleaved_xy = gameState.GrassSceneryTileLoopPosition;
2018-06-22 23:17:03 +02:00
for (int32_t i = 0; i < 8; i++)
{
x = (x << 1) | (interleaved_xy & 1);
interleaved_xy >>= 1;
y = (y << 1) | (interleaved_xy & 1);
interleaved_xy >>= 1;
}
// Repeat for each 256x256 block on the map
2024-02-12 22:32:08 +01:00
for (int32_t blockY = 0; blockY < gameState.MapSize.y; blockY += 256)
2018-06-22 23:17:03 +02:00
{
2024-02-12 22:32:08 +01:00
for (int32_t blockX = 0; blockX < gameState.MapSize.x; blockX += 256)
{
auto mapPos = TileCoordsXY{ blockX + x, blockY + y }.ToCoordsXY();
if (MapIsEdge(mapPos))
continue;
auto* surfaceElement = MapGetSurfaceElementAt(mapPos);
if (surfaceElement != nullptr)
{
surfaceElement->UpdateGrassLength(mapPos);
SceneryUpdateTile(mapPos);
}
}
}
gameState.GrassSceneryTileLoopPosition++;
}
}
void MapRemoveProvisionalElements()
{
PROFILED_FUNCTION();
2021-04-14 14:56:28 +02:00
if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1)
{
2022-10-04 08:51:27 +02:00
FootpathProvisionalRemove();
2021-04-14 14:56:28 +02:00
gProvisionalFootpath.Flags |= PROVISIONAL_PATH_FLAG_1;
}
if (WindowFindByClass(WindowClass::RideConstruction) != nullptr)
{
RideRemoveProvisionalTrackPiece();
2022-10-04 08:38:00 +02:00
RideEntranceExitRemoveGhost();
}
// This is in non performant so only make network games suffer for it
// non networked games do not need this as its to prevent desyncs.
if ((NetworkGetMode() != NETWORK_MODE_NONE) && WindowFindByClass(WindowClass::TrackDesignPlace) != nullptr)
{
auto intent = Intent(INTENT_ACTION_TRACK_DESIGN_REMOVE_PROVISIONAL);
2022-11-06 21:49:07 +01:00
ContextBroadcastIntent(&intent);
}
}
void MapRestoreProvisionalElements()
{
PROFILED_FUNCTION();
2021-04-14 14:56:28 +02:00
if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1)
{
2021-04-14 14:56:28 +02:00
gProvisionalFootpath.Flags &= ~PROVISIONAL_PATH_FLAG_1;
2022-10-04 08:51:27 +02:00
FootpathProvisionalSet(
gProvisionalFootpath.SurfaceIndex, gProvisionalFootpath.RailingsIndex, gProvisionalFootpath.Position,
gProvisionalFootpath.Slope, gProvisionalFootpath.ConstructFlags);
}
if (WindowFindByClass(WindowClass::RideConstruction) != nullptr)
{
RideRestoreProvisionalTrackPiece();
2022-10-04 08:38:00 +02:00
RideEntranceExitPlaceProvisionalGhost();
}
// This is in non performant so only make network games suffer for it
// non networked games do not need this as its to prevent desyncs.
if ((NetworkGetMode() != NETWORK_MODE_NONE) && WindowFindByClass(WindowClass::TrackDesignPlace) != nullptr)
{
auto intent = Intent(INTENT_ACTION_TRACK_DESIGN_RESTORE_PROVISIONAL);
2022-11-06 21:49:07 +01:00
ContextBroadcastIntent(&intent);
}
}
2015-07-02 01:37:55 +02:00
/**
* Removes elements that are out of the map size range and crops the park perimeter.
* rct2: 0x0068ADBC
*/
void MapRemoveOutOfRangeElements()
2015-06-20 17:24:38 +02:00
{
auto mapSizeMax = GetMapSizeMaxXY();
2019-10-16 13:21:21 +02:00
2019-10-03 22:22:24 +02:00
// Ensure that we can remove elements
2019-10-16 13:21:21 +02:00
//
// NOTE: This is only a workaround for non-networked games.
// Map resize has to become its own Game Action to properly solve this issue.
//
2024-03-03 22:44:15 +01:00
bool buildState = GetGameState().Cheats.BuildInPauseMode;
GetGameState().Cheats.BuildInPauseMode = true;
for (int32_t y = MAXIMUM_MAP_SIZE_BIG - COORDS_XY_STEP; y >= 0; y -= COORDS_XY_STEP)
2018-06-22 23:17:03 +02:00
{
for (int32_t x = MAXIMUM_MAP_SIZE_BIG - COORDS_XY_STEP; x >= 0; x -= COORDS_XY_STEP)
2018-06-22 23:17:03 +02:00
{
if (x == 0 || y == 0 || x >= mapSizeMax.x || y >= mapSizeMax.y)
2018-06-22 23:17:03 +02:00
{
2019-10-16 13:21:21 +02:00
// Note this purposely does not use LandSetRightsAction as X Y coordinates are outside of normal range.
auto surfaceElement = MapGetSurfaceElementAt(CoordsXY{ x, y });
2019-10-16 13:21:21 +02:00
if (surfaceElement != nullptr)
2019-03-17 09:25:51 +01:00
{
2019-10-16 13:21:21 +02:00
surfaceElement->SetOwnership(OWNERSHIP_UNOWNED);
Park::UpdateFencesAroundTile({ x, y });
2019-03-17 09:25:51 +01:00
}
ClearElementsAt({ x, y });
}
}
}
2019-10-03 22:22:24 +02:00
2019-10-16 13:21:21 +02:00
// Reset cheat state
2024-03-03 22:44:15 +01:00
GetGameState().Cheats.BuildInPauseMode = buildState;
2015-06-20 17:24:38 +02:00
}
static void MapExtendBoundarySurfaceExtendTile(const SurfaceElement& sourceTile, SurfaceElement& destTile)
{
destTile.SetSurfaceObjectIndex(sourceTile.GetSurfaceObjectIndex());
destTile.SetEdgeObjectIndex(sourceTile.GetEdgeObjectIndex());
destTile.SetGrassLength(sourceTile.GetGrassLength());
destTile.SetOwnership(OWNERSHIP_UNOWNED);
destTile.SetWaterHeight(sourceTile.GetWaterHeight());
auto z = sourceTile.BaseHeight;
auto slope = sourceTile.GetSlope() & TILE_ELEMENT_SLOPE_NW_SIDE_UP;
if (slope == TILE_ELEMENT_SLOPE_NW_SIDE_UP)
{
z += 2;
slope = TILE_ELEMENT_SLOPE_FLAT;
if (sourceTile.GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
{
slope = TILE_ELEMENT_SLOPE_N_CORNER_UP;
if (sourceTile.GetSlope() & TILE_ELEMENT_SLOPE_S_CORNER_UP)
{
slope = TILE_ELEMENT_SLOPE_W_CORNER_UP;
if (sourceTile.GetSlope() & TILE_ELEMENT_SLOPE_E_CORNER_UP)
{
slope = TILE_ELEMENT_SLOPE_FLAT;
}
}
}
}
if (slope & TILE_ELEMENT_SLOPE_N_CORNER_UP)
slope |= TILE_ELEMENT_SLOPE_E_CORNER_UP;
if (slope & TILE_ELEMENT_SLOPE_W_CORNER_UP)
slope |= TILE_ELEMENT_SLOPE_S_CORNER_UP;
destTile.SetSlope(slope);
destTile.BaseHeight = z;
destTile.ClearanceHeight = z;
}
2015-07-02 01:37:55 +02:00
/**
* Copies the terrain and slope from the Y edge of the map to the new tiles. Used when increasing the size of the map.
2015-07-02 01:37:55 +02:00
*/
void MapExtendBoundarySurfaceY()
2015-07-02 01:37:55 +02:00
{
2024-02-12 22:32:08 +01:00
auto y = GetGameState().MapSize.y - 2;
for (auto x = 0; x < kMaximumMapSizeTechnical; x++)
2018-06-22 23:17:03 +02:00
{
auto existingTileElement = MapGetSurfaceElementAt(TileCoordsXY{ x, y - 1 });
auto newTileElement = MapGetSurfaceElementAt(TileCoordsXY{ x, y });
2021-09-24 20:05:50 +02:00
if (existingTileElement != nullptr && newTileElement != nullptr)
2018-06-22 23:17:03 +02:00
{
MapExtendBoundarySurfaceExtendTile(*existingTileElement, *newTileElement);
}
Park::UpdateFences({ x << 5, y << 5 });
}
}
/**
* Copies the terrain and slope from the X edge of the map to the new tiles. Used when increasing the size of the map.
*/
void MapExtendBoundarySurfaceX()
{
2024-02-12 22:32:08 +01:00
auto x = GetGameState().MapSize.x - 2;
for (auto y = 0; y < kMaximumMapSizeTechnical; y++)
2018-06-22 23:17:03 +02:00
{
auto existingTileElement = MapGetSurfaceElementAt(TileCoordsXY{ x - 1, y });
auto newTileElement = MapGetSurfaceElementAt(TileCoordsXY{ x, y });
2021-09-24 20:05:50 +02:00
if (existingTileElement != nullptr && newTileElement != nullptr)
2018-06-22 23:17:03 +02:00
{
MapExtendBoundarySurfaceExtendTile(*existingTileElement, *newTileElement);
}
Park::UpdateFences({ x << 5, y << 5 });
}
2015-07-02 01:37:55 +02:00
}
2016-01-17 17:23:56 +01:00
/**
2016-07-13 18:57:55 +02:00
* Clears the provided element properly from a certain tile, and updates
* the pointer (when needed) passed to this function to point to the next element.
2016-01-17 17:23:56 +01:00
*/
static void ClearElementAt(const CoordsXY& loc, TileElement** elementPtr)
2015-06-20 17:24:38 +02:00
{
2018-11-01 13:53:50 +01:00
TileElement* element = *elementPtr;
2021-12-11 00:39:39 +01:00
switch (element->GetType())
{
2021-12-11 00:39:39 +01:00
case TileElementType::Surface:
element->BaseHeight = kMinimumLandHeight;
element->ClearanceHeight = kMinimumLandHeight;
element->Owner = 0;
2018-09-14 14:54:12 +02:00
element->AsSurface()->SetSlope(TILE_ELEMENT_SLOPE_FLAT);
element->AsSurface()->SetSurfaceObjectIndex(0);
element->AsSurface()->SetEdgeObjectIndex(0);
2018-09-14 14:54:12 +02:00
element->AsSurface()->SetGrassLength(GRASS_LENGTH_CLEAR_0);
element->AsSurface()->SetOwnership(OWNERSHIP_UNOWNED);
2018-09-17 13:07:25 +02:00
element->AsSurface()->SetParkFences(0);
element->AsSurface()->SetWaterHeight(0);
2018-06-22 23:17:03 +02:00
// Because this element is not completely removed, the pointer must be updated manually
// The rest of the elements are removed from the array, so the pointer doesn't need to be updated.
(*elementPtr)++;
break;
2021-12-11 00:39:39 +01:00
case TileElementType::Entrance:
2018-06-22 23:17:03 +02:00
{
int32_t rotation = element->GetDirectionWithOffset(1);
auto seqLoc = loc;
switch (element->AsEntrance()->GetSequenceIndex())
2018-06-22 23:17:03 +02:00
{
case 1:
seqLoc += CoordsDirectionDelta[rotation];
2018-06-22 23:17:03 +02:00
break;
case 2:
seqLoc -= CoordsDirectionDelta[rotation];
2018-06-22 23:17:03 +02:00
break;
}
auto parkEntranceRemoveAction = ParkEntranceRemoveAction(CoordsXYZ{ seqLoc, element->GetBaseZ() });
auto result = GameActions::ExecuteNested(&parkEntranceRemoveAction);
// If asking nicely did not work, forcibly remove this to avoid an infinite loop.
if (result.Error != GameActions::Status::Ok)
{
TileElementRemove(element);
}
break;
}
2021-12-11 00:39:39 +01:00
case TileElementType::Wall:
{
CoordsXYZD wallLocation = { loc.x, loc.y, element->GetBaseZ(), element->GetDirection() };
2018-05-14 12:36:45 +02:00
auto wallRemoveAction = WallRemoveAction(wallLocation);
auto result = GameActions::ExecuteNested(&wallRemoveAction);
// If asking nicely did not work, forcibly remove this to avoid an infinite loop.
if (result.Error != GameActions::Status::Ok)
{
TileElementRemove(element);
}
}
break;
2021-12-11 00:39:39 +01:00
case TileElementType::LargeScenery:
2018-12-22 19:46:59 +01:00
{
2019-01-03 02:20:35 +01:00
auto removeSceneryAction = LargeSceneryRemoveAction(
{ loc.x, loc.y, element->GetBaseZ(), element->GetDirection() }, element->AsLargeScenery()->GetSequenceIndex());
auto result = GameActions::ExecuteNested(&removeSceneryAction);
// If asking nicely did not work, forcibly remove this to avoid an infinite loop.
if (result.Error != GameActions::Status::Ok)
{
TileElementRemove(element);
}
2018-12-22 19:46:59 +01:00
}
break;
2021-12-11 00:39:39 +01:00
case TileElementType::Banner:
2019-04-06 10:44:07 +02:00
{
auto bannerRemoveAction = BannerRemoveAction(
{ loc.x, loc.y, element->GetBaseZ(), element->AsBanner()->GetPosition() });
auto result = GameActions::ExecuteNested(&bannerRemoveAction);
// If asking nicely did not work, forcibly remove this to avoid an infinite loop.
if (result.Error != GameActions::Status::Ok)
{
TileElementRemove(element);
}
2018-06-22 23:17:03 +02:00
break;
2019-04-06 10:44:07 +02:00
}
2018-06-22 23:17:03 +02:00
default:
TileElementRemove(element);
2018-06-22 23:17:03 +02:00
break;
}
}
/**
* Clears all elements properly from a certain tile.
* rct2: 0x0068AE2A
*/
static void ClearElementsAt(const CoordsXY& loc)
{
auto& gameState = GetGameState();
// Remove the spawn point (if there is one in the current tile)
gameState.PeepSpawns.erase(
std::remove_if(
gameState.PeepSpawns.begin(), gameState.PeepSpawns.end(),
[loc](const CoordsXY& spawn) { return spawn.ToTileStart() == loc.ToTileStart(); }),
gameState.PeepSpawns.end());
TileElement* tileElement = MapGetFirstElementAt(loc);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return;
// Remove all elements except the last one
2018-06-22 23:17:03 +02:00
while (!tileElement->IsLastForTile())
ClearElementAt(loc, &tileElement);
// Remove the last element
ClearElementAt(loc, &tileElement);
}
2015-06-27 16:17:54 +02:00
int32_t MapGetHighestZ(const CoordsXY& loc)
2015-06-27 16:17:54 +02:00
{
auto surfaceElement = MapGetSurfaceElementAt(loc);
if (surfaceElement == nullptr)
return -1;
2015-06-27 16:17:54 +02:00
auto z = surfaceElement->GetBaseZ();
2015-06-27 16:17:54 +02:00
// Raise z so that is above highest point of land and water on tile
if ((surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) != TILE_ELEMENT_SLOPE_FLAT)
z += LAND_HEIGHT_STEP;
if ((surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) != 0)
z += LAND_HEIGHT_STEP;
2015-06-27 16:17:54 +02:00
z = std::max(z, surfaceElement->GetWaterHeight());
return z;
2015-06-27 16:17:54 +02:00
}
LargeSceneryElement* MapGetLargeScenerySegment(const CoordsXYZD& sceneryPos, int32_t sequence)
{
TileElement* tileElement = MapGetFirstElementAt(sceneryPos);
2018-01-04 06:58:44 +01:00
if (tileElement == nullptr)
{
2018-01-04 06:58:44 +01:00
return nullptr;
}
auto sceneryTilePos = TileCoordsXYZ{ sceneryPos };
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::LargeScenery)
continue;
if (tileElement->BaseHeight != sceneryTilePos.z)
continue;
2018-09-14 12:12:22 +02:00
if (tileElement->AsLargeScenery()->GetSequenceIndex() != sequence)
continue;
if ((tileElement->GetDirection()) != sceneryPos.direction)
continue;
return tileElement->AsLargeScenery();
} while (!(tileElement++)->IsLastForTile());
2018-01-04 06:58:44 +01:00
return nullptr;
}
EntranceElement* MapGetParkEntranceElementAt(const CoordsXYZ& entranceCoords, bool ghost)
2016-09-26 20:35:56 +02:00
{
auto entranceTileCoords = TileCoordsXYZ(entranceCoords);
TileElement* tileElement = MapGetFirstElementAt(entranceCoords);
2018-01-04 06:58:44 +01:00
if (tileElement != nullptr)
{
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Entrance)
continue;
2016-09-26 20:35:56 +02:00
if (tileElement->BaseHeight != entranceTileCoords.z)
continue;
2016-09-26 20:35:56 +02:00
2018-09-26 12:13:44 +02:00
if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_PARK_ENTRANCE)
continue;
2016-09-26 20:35:56 +02:00
2019-05-10 22:00:38 +02:00
if (!ghost && tileElement->IsGhost())
continue;
2016-11-13 20:17:49 +01:00
return tileElement->AsEntrance();
2018-06-22 23:17:03 +02:00
} while (!(tileElement++)->IsLastForTile());
}
2018-01-04 06:58:44 +01:00
return nullptr;
2016-09-26 20:35:56 +02:00
}
EntranceElement* MapGetRideEntranceElementAt(const CoordsXYZ& entranceCoords, bool ghost)
{
auto entranceTileCoords = TileCoordsXYZ{ entranceCoords };
TileElement* tileElement = MapGetFirstElementAt(entranceCoords);
2018-01-04 06:58:44 +01:00
if (tileElement != nullptr)
{
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Entrance)
continue;
if (tileElement->BaseHeight != entranceTileCoords.z)
continue;
2018-09-26 12:13:44 +02:00
if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_RIDE_ENTRANCE)
continue;
2019-05-10 22:00:38 +02:00
if (!ghost && tileElement->IsGhost())
continue;
return tileElement->AsEntrance();
2018-06-22 23:17:03 +02:00
} while (!(tileElement++)->IsLastForTile());
}
2018-01-04 06:58:44 +01:00
return nullptr;
}
EntranceElement* MapGetRideExitElementAt(const CoordsXYZ& exitCoords, bool ghost)
2017-10-25 15:05:10 +02:00
{
auto exitTileCoords = TileCoordsXYZ{ exitCoords };
TileElement* tileElement = MapGetFirstElementAt(exitCoords);
2018-01-04 06:58:44 +01:00
if (tileElement != nullptr)
2017-10-25 15:05:10 +02:00
{
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Entrance)
continue;
2017-10-25 15:05:10 +02:00
if (tileElement->BaseHeight != exitTileCoords.z)
continue;
2017-10-25 15:05:10 +02:00
2018-09-26 12:13:44 +02:00
if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_RIDE_EXIT)
continue;
2017-10-25 15:05:10 +02:00
2019-05-10 22:00:38 +02:00
if (!ghost && tileElement->IsGhost())
continue;
2017-10-25 15:05:10 +02:00
return tileElement->AsEntrance();
2018-06-22 23:17:03 +02:00
} while (!(tileElement++)->IsLastForTile());
2017-10-25 15:05:10 +02:00
}
2018-01-04 06:58:44 +01:00
return nullptr;
2017-10-25 15:05:10 +02:00
}
SmallSceneryElement* MapGetSmallSceneryElementAt(const CoordsXYZ& sceneryCoords, int32_t type, uint8_t quadrant)
2015-11-12 20:22:08 +01:00
{
auto sceneryTileCoords = TileCoordsXYZ{ sceneryCoords };
TileElement* tileElement = MapGetFirstElementAt(sceneryCoords);
2018-01-04 06:58:44 +01:00
if (tileElement != nullptr)
{
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::SmallScenery)
continue;
if (tileElement->AsSmallScenery()->GetSceneryQuadrant() != quadrant)
continue;
if (tileElement->BaseHeight != sceneryTileCoords.z)
continue;
if (tileElement->AsSmallScenery()->GetEntryIndex() != type)
continue;
2019-03-16 17:29:02 +01:00
return tileElement->AsSmallScenery();
2018-06-22 23:17:03 +02:00
} while (!(tileElement++)->IsLastForTile());
}
2018-01-04 06:58:44 +01:00
return nullptr;
2015-11-12 20:22:08 +01:00
}
std::optional<CoordsXYZ> MapLargeSceneryGetOrigin(
const CoordsXYZD& sceneryPos, int32_t sequence, LargeSceneryElement** outElement)
2018-06-22 23:17:03 +02:00
{
LargeSceneryTile* tile;
auto tileElement = MapGetLargeScenerySegment(sceneryPos, sequence);
2018-01-04 06:58:44 +01:00
if (tileElement == nullptr)
return std::nullopt;
auto* sceneryEntry = tileElement->GetEntry();
tile = &sceneryEntry->tiles[sequence];
CoordsXY offsetPos{ tile->x_offset, tile->y_offset };
auto rotatedOffsetPos = offsetPos.Rotate(sceneryPos.direction);
auto origin = CoordsXYZ{ sceneryPos.x - rotatedOffsetPos.x, sceneryPos.y - rotatedOffsetPos.y,
sceneryPos.z - tile->z_offset };
2018-01-04 06:58:44 +01:00
if (outElement != nullptr)
2017-10-31 14:03:45 +01:00
*outElement = tileElement;
return origin;
}
/**
2015-10-20 20:16:30 +02:00
*
* rct2: 0x006B9B05
*/
bool MapLargeScenerySignSetColour(const CoordsXYZD& signPos, int32_t sequence, uint8_t mainColour, uint8_t textColour)
{
LargeSceneryElement* tileElement;
LargeSceneryTile *sceneryTiles, *tile;
auto sceneryOrigin = MapLargeSceneryGetOrigin(signPos, sequence, &tileElement);
if (!sceneryOrigin)
2018-06-22 23:17:03 +02:00
{
return false;
}
auto* sceneryEntry = tileElement->GetEntry();
sceneryTiles = sceneryEntry->tiles;
// Iterate through each tile of the large scenery element
sequence = 0;
2018-06-22 23:17:03 +02:00
for (tile = sceneryTiles; tile->x_offset != -1; tile++, sequence++)
{
CoordsXY offsetPos{ tile->x_offset, tile->y_offset };
auto rotatedOffsetPos = offsetPos.Rotate(signPos.direction);
auto tmpSignPos = CoordsXYZD{ sceneryOrigin->x + rotatedOffsetPos.x, sceneryOrigin->y + rotatedOffsetPos.y,
sceneryOrigin->z + tile->z_offset, signPos.direction };
tileElement = MapGetLargeScenerySegment(tmpSignPos, sequence);
2018-01-04 06:58:44 +01:00
if (tileElement != nullptr)
{
tileElement->SetPrimaryColour(mainColour);
tileElement->SetSecondaryColour(textColour);
2017-10-31 14:03:45 +01:00
MapInvalidateTile({ tmpSignPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() });
}
}
return true;
}
static void MapInvalidateTileUnderZoom(int32_t x, int32_t y, int32_t z0, int32_t z1, ZoomLevel maxZoom)
{
2018-06-22 23:17:03 +02:00
if (gOpenRCT2Headless)
return;
ViewportsInvalidate(x, y, z0, z1, maxZoom);
}
/**
*
* rct2: 0x006EC847
*/
void MapInvalidateTile(const CoordsXYRangedZ& tilePos)
{
MapInvalidateTileUnderZoom(tilePos.x, tilePos.y, tilePos.baseZ, tilePos.clearanceZ, ZoomLevel{ -1 });
}
/**
*
* rct2: 0x006ECB60
*/
void MapInvalidateTileZoom1(const CoordsXYRangedZ& tilePos)
{
MapInvalidateTileUnderZoom(tilePos.x, tilePos.y, tilePos.baseZ, tilePos.clearanceZ, ZoomLevel{ 1 });
}
/**
*
* rct2: 0x006EC9CE
*/
void MapInvalidateTileZoom0(const CoordsXYRangedZ& tilePos)
{
MapInvalidateTileUnderZoom(tilePos.x, tilePos.y, tilePos.baseZ, tilePos.clearanceZ, ZoomLevel{ 0 });
}
/**
*
* rct2: 0x006EC6D7
*/
void MapInvalidateTileFull(const CoordsXY& tilePos)
{
MapInvalidateTile({ tilePos, 0, 2080 });
}
2015-07-07 20:09:12 +02:00
void MapInvalidateElement(const CoordsXY& elementPos, TileElement* tileElement)
2015-07-07 20:09:12 +02:00
{
MapInvalidateTile({ elementPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() });
2015-07-07 20:09:12 +02:00
}
void MapInvalidateRegion(const CoordsXY& mins, const CoordsXY& maxs)
{
int32_t x0, y0, x1, y1, left, right, top, bottom;
x0 = mins.x + 16;
y0 = mins.y + 16;
x1 = maxs.x + 16;
y1 = maxs.y + 16;
MapGetBoundingBox({ x0, y0, x1, y1 }, &left, &top, &right, &bottom);
left -= 32;
right += 32;
bottom += 32;
top -= 32 + 2080;
2023-01-16 21:14:50 +01:00
ViewportsInvalidate({ { left, top }, { right, bottom } });
}
int32_t MapGetTileSide(const CoordsXY& mapPos)
{
int32_t subMapX = mapPos.x & (32 - 1);
int32_t subMapY = mapPos.y & (32 - 1);
2018-06-22 23:17:03 +02:00
return (subMapX < subMapY) ? ((subMapX + subMapY) < 32 ? 0 : 1) : ((subMapX + subMapY) < 32 ? 3 : 2);
}
int32_t MapGetTileQuadrant(const CoordsXY& mapPos)
{
int32_t subMapX = mapPos.x & (32 - 1);
int32_t subMapY = mapPos.y & (32 - 1);
2018-06-22 23:17:03 +02:00
return (subMapX > 16) ? (subMapY < 16 ? 1 : 0) : (subMapY < 16 ? 2 : 3);
}
/**
*
* rct2: 0x00693BFF
*/
bool MapSurfaceIsBlocked(const CoordsXY& mapCoords)
2018-06-22 23:17:03 +02:00
{
if (!MapIsLocationValid(mapCoords))
return true;
2015-07-25 12:16:59 +02:00
auto surfaceElement = MapGetSurfaceElementAt(mapCoords);
2015-07-25 12:16:59 +02:00
if (surfaceElement == nullptr)
2018-06-22 23:17:03 +02:00
{
return true;
}
2016-12-26 15:53:11 +01:00
if (surfaceElement->GetWaterHeight() > surfaceElement->GetBaseZ())
return true;
2015-07-25 12:16:59 +02:00
int16_t base_z = surfaceElement->BaseHeight;
int16_t clear_z = surfaceElement->BaseHeight + 2;
if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
clear_z += 2;
2015-07-25 12:16:59 +02:00
auto tileElement = reinterpret_cast<TileElement*>(surfaceElement);
2018-06-22 23:17:03 +02:00
while (!(tileElement++)->IsLastForTile())
{
if (clear_z >= tileElement->ClearanceHeight)
continue;
2015-07-25 12:16:59 +02:00
if (base_z < tileElement->BaseHeight)
continue;
2015-07-25 12:16:59 +02:00
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() == TileElementType::Path || tileElement->GetType() == TileElementType::Wall)
continue;
2015-07-25 12:16:59 +02:00
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::SmallScenery)
return true;
2015-07-25 12:16:59 +02:00
2021-06-04 03:14:41 +02:00
auto* sceneryEntry = tileElement->AsSmallScenery()->GetEntry();
if (sceneryEntry == nullptr)
{
return false;
}
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE))
return true;
}
return false;
2015-07-25 12:16:59 +02:00
}
/* Clears all map elements, to be used before generating a new map */
void MapClearAllElements()
{
for (int32_t y = 0; y < MAXIMUM_MAP_SIZE_BIG; y += COORDS_XY_STEP)
2018-06-22 23:17:03 +02:00
{
for (int32_t x = 0; x < MAXIMUM_MAP_SIZE_BIG; x += COORDS_XY_STEP)
2018-06-22 23:17:03 +02:00
{
ClearElementsAt({ x, y });
}
}
}
/**
2015-11-01 15:58:46 +01:00
* Gets the track element at x, y, z.
* @param x x units, not tiles.
* @param y y units, not tiles.
* @param z Base height.
*/
TrackElement* MapGetTrackElementAt(const CoordsXYZ& trackPos)
{
TileElement* tileElement = MapGetFirstElementAt(trackPos);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return nullptr;
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Track)
2018-06-22 23:17:03 +02:00
continue;
if (tileElement->GetBaseZ() != trackPos.z)
2018-06-22 23:17:03 +02:00
continue;
return tileElement->AsTrack();
} while (!(tileElement++)->IsLastForTile());
2018-01-04 06:58:44 +01:00
return nullptr;
}
/**
2015-11-01 15:58:46 +01:00
* Gets the track element at x, y, z that is the given track type.
* @param x x units, not tiles.
* @param y y units, not tiles.
* @param z Base height.
*/
TileElement* MapGetTrackElementAtOfType(const CoordsXYZ& trackPos, track_type_t trackType)
{
TileElement* tileElement = MapGetFirstElementAt(trackPos);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return nullptr;
auto trackTilePos = TileCoordsXYZ{ trackPos };
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Track)
2018-06-22 23:17:03 +02:00
continue;
if (tileElement->BaseHeight != trackTilePos.z)
2018-06-22 23:17:03 +02:00
continue;
if (tileElement->AsTrack()->GetTrackType() != trackType)
2018-06-22 23:17:03 +02:00
continue;
2017-10-31 14:03:45 +01:00
return tileElement;
} while (!(tileElement++)->IsLastForTile());
2018-01-04 06:58:44 +01:00
return nullptr;
}
2015-11-01 15:58:46 +01:00
/**
* Gets the track element at x, y, z that is the given track type and sequence.
* @param x x units, not tiles.
* @param y y units, not tiles.
* @param z Base height.
*/
TileElement* MapGetTrackElementAtOfTypeSeq(const CoordsXYZ& trackPos, track_type_t trackType, int32_t sequence)
2015-11-01 15:58:46 +01:00
{
TileElement* tileElement = MapGetFirstElementAt(trackPos);
auto trackTilePos = TileCoordsXYZ{ trackPos };
2018-06-22 23:17:03 +02:00
do
{
if (tileElement == nullptr)
break;
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Track)
2018-06-22 23:17:03 +02:00
continue;
if (tileElement->BaseHeight != trackTilePos.z)
2018-06-22 23:17:03 +02:00
continue;
if (tileElement->AsTrack()->GetTrackType() != trackType)
2018-06-22 23:17:03 +02:00
continue;
if (tileElement->AsTrack()->GetSequenceIndex() != sequence)
2018-06-22 23:17:03 +02:00
continue;
2017-10-31 14:03:45 +01:00
return tileElement;
} while (!(tileElement++)->IsLastForTile());
2018-01-04 06:58:44 +01:00
return nullptr;
2015-11-01 15:58:46 +01:00
}
2016-01-21 02:43:33 +01:00
TrackElement* MapGetTrackElementAtOfType(const CoordsXYZD& location, track_type_t trackType)
{
auto tileElement = MapGetFirstElementAt(location);
if (tileElement != nullptr)
{
do
{
auto trackElement = tileElement->AsTrack();
if (trackElement != nullptr)
{
if (trackElement->GetBaseZ() != location.z)
continue;
if (trackElement->GetDirection() != location.direction)
continue;
if (trackElement->GetTrackType() != trackType)
continue;
return trackElement;
}
} while (!(tileElement++)->IsLastForTile());
}
return nullptr;
}
TrackElement* MapGetTrackElementAtOfTypeSeq(const CoordsXYZD& location, track_type_t trackType, int32_t sequence)
{
auto tileElement = MapGetFirstElementAt(location);
if (tileElement != nullptr)
{
do
{
auto trackElement = tileElement->AsTrack();
if (trackElement != nullptr)
{
if (trackElement->GetBaseZ() != location.z)
continue;
if (trackElement->GetDirection() != location.direction)
continue;
if (trackElement->GetTrackType() != trackType)
continue;
if (trackElement->GetSequenceIndex() != sequence)
continue;
return trackElement;
}
} while (!(tileElement++)->IsLastForTile());
}
return nullptr;
}
2016-01-21 02:43:33 +01:00
/**
* Gets the track element at x, y, z that is the given track type and sequence.
* @param x x units, not tiles.
* @param y y units, not tiles.
* @param z Base height.
*/
TileElement* MapGetTrackElementAtOfTypeFromRide(const CoordsXYZ& trackPos, track_type_t trackType, RideId rideIndex)
2018-06-22 23:17:03 +02:00
{
TileElement* tileElement = MapGetFirstElementAt(trackPos);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return nullptr;
auto trackTilePos = TileCoordsXYZ{ trackPos };
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Track)
2018-06-22 23:17:03 +02:00
continue;
if (tileElement->BaseHeight != trackTilePos.z)
2018-06-22 23:17:03 +02:00
continue;
2018-09-18 13:10:29 +02:00
if (tileElement->AsTrack()->GetRideIndex() != rideIndex)
2018-06-22 23:17:03 +02:00
continue;
if (tileElement->AsTrack()->GetTrackType() != trackType)
2018-06-22 23:17:03 +02:00
continue;
2016-01-21 02:43:33 +01:00
2017-10-31 14:03:45 +01:00
return tileElement;
} while (!(tileElement++)->IsLastForTile());
2016-01-21 02:43:33 +01:00
2018-01-04 06:58:44 +01:00
return nullptr;
2016-01-21 02:43:33 +01:00
};
2016-02-12 16:40:17 +01:00
/**
* Gets the track element at x, y, z that is the given track type and sequence.
* @param x x units, not tiles.
* @param y y units, not tiles.
* @param z Base height.
*/
TileElement* MapGetTrackElementAtFromRide(const CoordsXYZ& trackPos, RideId rideIndex)
2018-06-22 23:17:03 +02:00
{
TileElement* tileElement = MapGetFirstElementAt(trackPos);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return nullptr;
auto trackTilePos = TileCoordsXYZ{ trackPos };
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Track)
2018-06-22 23:17:03 +02:00
continue;
if (tileElement->BaseHeight != trackTilePos.z)
2018-06-22 23:17:03 +02:00
continue;
2018-09-18 13:10:29 +02:00
if (tileElement->AsTrack()->GetRideIndex() != rideIndex)
2018-06-22 23:17:03 +02:00
continue;
2016-02-12 16:40:17 +01:00
2017-10-31 14:03:45 +01:00
return tileElement;
} while (!(tileElement++)->IsLastForTile());
2016-02-12 16:40:17 +01:00
2018-01-04 06:58:44 +01:00
return nullptr;
2016-02-12 16:40:17 +01:00
};
2016-02-14 18:37:28 +01:00
/**
* Gets the track element at x, y, z that is the given track type and sequence.
* @param x x units, not tiles.
* @param y y units, not tiles.
* @param z Base height.
* @param direction The direction (0 - 3).
*/
TileElement* MapGetTrackElementAtWithDirectionFromRide(const CoordsXYZD& trackPos, RideId rideIndex)
2016-02-14 18:37:28 +01:00
{
TileElement* tileElement = MapGetFirstElementAt(trackPos);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return nullptr;
auto trackTilePos = TileCoordsXYZ{ trackPos };
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Track)
2018-06-22 23:17:03 +02:00
continue;
if (tileElement->BaseHeight != trackTilePos.z)
2018-06-22 23:17:03 +02:00
continue;
2018-09-18 13:10:29 +02:00
if (tileElement->AsTrack()->GetRideIndex() != rideIndex)
2018-06-22 23:17:03 +02:00
continue;
if (tileElement->GetDirection() != trackPos.direction)
2018-06-22 23:17:03 +02:00
continue;
2016-02-14 18:37:28 +01:00
2017-10-31 14:03:45 +01:00
return tileElement;
} while (!(tileElement++)->IsLastForTile());
2016-02-14 18:37:28 +01:00
2018-01-04 06:58:44 +01:00
return nullptr;
2016-02-14 18:37:28 +01:00
};
WallElement* MapGetWallElementAt(const CoordsXYRangedZ& coords)
{
auto tileElement = MapGetFirstElementAt(coords);
if (tileElement != nullptr)
{
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() == TileElementType::Wall && coords.baseZ < tileElement->GetClearanceZ()
&& coords.clearanceZ > tileElement->GetBaseZ())
{
return tileElement->AsWall();
}
} while (!(tileElement++)->IsLastForTile());
}
return nullptr;
}
WallElement* MapGetWallElementAt(const CoordsXYZD& wallCoords)
{
auto tileWallCoords = TileCoordsXYZ(wallCoords);
TileElement* tileElement = MapGetFirstElementAt(wallCoords);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return nullptr;
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Wall)
continue;
if (tileElement->BaseHeight != tileWallCoords.z)
continue;
if (tileElement->GetDirection() != wallCoords.direction)
continue;
2019-03-16 17:32:13 +01:00
return tileElement->AsWall();
} while (!(tileElement++)->IsLastForTile());
2018-01-04 06:58:44 +01:00
return nullptr;
}
uint16_t CheckMaxAllowableLandRightsForTile(const CoordsXYZ& tileMapPos)
{
TileElement* tileElement = MapGetFirstElementAt(tileMapPos);
uint16_t destOwnership = OWNERSHIP_OWNED;
// Sometimes done deliberately.
2018-01-04 06:58:44 +01:00
if (tileElement == nullptr)
{
return OWNERSHIP_OWNED;
}
auto tilePos = TileCoordsXYZ{ tileMapPos };
do
{
2021-12-11 00:39:39 +01:00
auto type = tileElement->GetType();
if (type == TileElementType::Path
|| (type == TileElementType::Entrance
2018-09-26 15:44:09 +02:00
&& tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_PARK_ENTRANCE))
{
destOwnership = OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED;
// Do not own construction rights if too high/below surface
if (tileElement->BaseHeight - ConstructionRightsClearanceSmall > tilePos.z || tileElement->BaseHeight < tilePos.z)
{
destOwnership = OWNERSHIP_UNOWNED;
break;
}
}
2018-06-22 23:17:03 +02:00
} while (!(tileElement++)->IsLastForTile());
return destOwnership;
}
2017-11-16 10:14:57 +01:00
2018-02-15 17:42:53 +01:00
void FixLandOwnershipTiles(std::initializer_list<TileCoordsXY> tiles)
{
FixLandOwnershipTilesWithOwnership(tiles, OWNERSHIP_AVAILABLE);
}
void FixLandOwnershipTilesWithOwnership(std::initializer_list<TileCoordsXY> tiles, uint8_t ownership, bool doNotDowngrade)
{
2018-06-22 23:17:03 +02:00
for (const TileCoordsXY* tile = tiles.begin(); tile != tiles.end(); ++tile)
{
auto surfaceElement = MapGetSurfaceElementAt(*tile);
if (surfaceElement != nullptr)
{
if (doNotDowngrade && surfaceElement->GetOwnership() == OWNERSHIP_OWNED)
continue;
surfaceElement->SetOwnership(ownership);
Park::UpdateFencesAroundTile({ (*tile).x * 32, (*tile).y * 32 });
}
}
}
MapRange ClampRangeWithinMap(const MapRange& range)
{
auto mapSizeMax = GetMapSizeMaxXY();
2022-02-09 21:48:22 +01:00
auto aX = std::max<decltype(range.GetLeft())>(COORDS_XY_STEP, range.GetLeft());
auto bX = std::min<decltype(range.GetRight())>(mapSizeMax.x, range.GetRight());
2022-02-09 21:48:22 +01:00
auto aY = std::max<decltype(range.GetTop())>(COORDS_XY_STEP, range.GetTop());
auto bY = std::min<decltype(range.GetBottom())>(mapSizeMax.y, range.GetBottom());
MapRange validRange = MapRange{ aX, aY, bX, bY };
return validRange;
}