2021-02-04 17:21:17 +01:00
|
|
|
/*****************************************************************************
|
2024-01-01 12:52:28 +01:00
|
|
|
* Copyright (c) 2014-2024 OpenRCT2 developers
|
2014-04-09 04:09:30 +02:00
|
|
|
*
|
2018-06-15 14:07:34 +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
|
|
|
*
|
2018-06-15 14:07:34 +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"
|
2017-03-25 15:57:02 +01:00
|
|
|
#include "../Context.h"
|
2018-06-22 23:17:03 +02:00
|
|
|
#include "../Game.h"
|
2024-02-11 22:40:32 +01:00
|
|
|
#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"
|
2016-10-11 19:23:49 +02:00
|
|
|
#include "../interface/Cursors.h"
|
2024-04-21 01:18:11 +02:00
|
|
|
#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"
|
2016-01-25 05:00:31 +01:00
|
|
|
#include "../network/network.h"
|
2023-01-26 19:44:42 +01:00
|
|
|
#include "../object/LargeSceneryEntry.h"
|
2018-12-05 21:33:45 +01:00
|
|
|
#include "../object/ObjectManager.h"
|
2023-01-25 20:25:33 +01:00
|
|
|
#include "../object/SmallSceneryEntry.h"
|
2018-12-05 21:33:45 +01:00
|
|
|
#include "../object/TerrainSurfaceObject.h"
|
2022-01-18 19:21:20 +01:00
|
|
|
#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"
|
2017-10-19 10:01:05 +02:00
|
|
|
#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"
|
2021-02-04 17:58:45 +01:00
|
|
|
#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
|
|
|
|
2018-08-12 13:50:40 +02:00
|
|
|
#include <algorithm>
|
2018-11-21 23:16:04 +01:00
|
|
|
#include <iterator>
|
2020-06-15 20:47:07 +02:00
|
|
|
#include <memory>
|
2018-08-12 13:50:40 +02:00
|
|
|
|
2018-12-05 21:33:45 +01:00
|
|
|
using namespace OpenRCT2;
|
|
|
|
|
2015-12-12 21:08:09 +01:00
|
|
|
/**
|
|
|
|
* Replaces 0x00993CCC, 0x00993CCE
|
|
|
|
*/
|
2020-01-14 02:22:04 +01:00
|
|
|
// 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
|
2014-12-17 21:45:27 +01:00
|
|
|
|
2021-10-17 15:31:56 +02:00
|
|
|
const TileCoordsXY TileDirectionDelta[] = {
|
|
|
|
{ -1, 0 }, { 0, +1 }, { +1, 0 }, { 0, -1 }, { -1, +1 }, { +1, +1 }, { +1, -1 }, { -1, -1 },
|
|
|
|
};
|
2018-04-18 20:34:34 +02:00
|
|
|
|
2021-04-09 03:09:08 +02:00
|
|
|
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;
|
2019-12-30 17:51:35 +01:00
|
|
|
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;
|
2014-12-17 21:45:27 +01:00
|
|
|
|
2015-10-11 13:26:33 +02:00
|
|
|
bool gLandMountainMode;
|
|
|
|
bool gLandPaintMode;
|
2015-06-05 18:38:52 +02:00
|
|
|
bool gClearSmallScenery;
|
|
|
|
bool gClearLargeScenery;
|
|
|
|
bool gClearFootpath;
|
2015-05-12 17:37:16 +02:00
|
|
|
|
2021-08-16 21:51:16 +02:00
|
|
|
uint32_t gLandRemainingOwnershipSales;
|
|
|
|
uint32_t gLandRemainingConstructionSales;
|
2016-10-31 20:21:10 +01:00
|
|
|
|
2017-06-08 22:28:39 +02:00
|
|
|
bool gMapLandRightsUpdateSuccess;
|
2016-09-10 18:46:34 +02:00
|
|
|
|
2021-04-09 03:09:08 +02:00
|
|
|
static TilePointerIndex<TileElement> _tileIndex;
|
|
|
|
static TilePointerIndex<TileElement> _tileIndexStash;
|
|
|
|
static std::vector<TileElement> _tileElementsStash;
|
2021-06-10 13:22:31 +02:00
|
|
|
static size_t _tileElementsInUse;
|
|
|
|
static size_t _tileElementsInUseStash;
|
2021-12-17 19:25:46 +01:00
|
|
|
static TileCoordsXY _mapSizeStash;
|
2021-04-09 03:09:08 +02:00
|
|
|
|
|
|
|
void StashMap()
|
|
|
|
{
|
2024-02-25 16:53:41 +01:00
|
|
|
auto& gameState = GetGameState();
|
2021-04-09 03:09:08 +02:00
|
|
|
_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;
|
2021-06-10 13:22:31 +02:00
|
|
|
_tileElementsInUseStash = _tileElementsInUse;
|
2021-04-09 03:09:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void UnstashMap()
|
|
|
|
{
|
2024-02-25 16:53:41 +01:00
|
|
|
auto& gameState = GetGameState();
|
2021-04-09 03:09:08 +02:00
|
|
|
_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;
|
2021-06-10 13:22:31 +02:00
|
|
|
_tileElementsInUse = _tileElementsInUseStash;
|
2021-04-09 03:09:08 +02:00
|
|
|
}
|
|
|
|
|
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 };
|
|
|
|
}
|
|
|
|
|
2021-04-09 03:09:08 +02:00
|
|
|
const std::vector<TileElement>& GetTileElements()
|
|
|
|
{
|
2024-02-25 16:53:41 +01:00
|
|
|
return GetGameState().TileElements;
|
2021-04-09 03:09:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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>(
|
2024-03-17 14:41:36 +01:00
|
|
|
kMaximumMapSizeTechnical, gameState.TileElements.data(), gameState.TileElements.size());
|
2024-02-25 16:53:41 +01:00
|
|
|
_tileElementsInUse = gameState.TileElements.size();
|
2021-04-09 03:09:08 +02:00
|
|
|
}
|
|
|
|
|
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);
|
2023-01-19 20:36:30 +01:00
|
|
|
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);
|
2023-08-26 19:50:06 +02:00
|
|
|
el.AsSurface()->SetSurfaceObjectIndex(0);
|
2023-08-26 20:01:27 +02:00
|
|
|
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()));
|
2024-03-17 14:41:36 +01:00
|
|
|
for (int32_t y = 0; y < kMaximumMapSizeTechnical; y++)
|
2021-11-14 21:58:33 +01:00
|
|
|
{
|
2024-03-17 14:41:36 +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;
|
|
|
|
}
|
|
|
|
|
2021-04-09 03:09:08 +02:00
|
|
|
static void ReorganiseTileElements(size_t capacity)
|
|
|
|
{
|
2022-11-06 21:49:07 +01:00
|
|
|
ContextSetCurrentCursor(CursorID::ZZZ);
|
2021-04-09 03:09:08 +02:00
|
|
|
|
|
|
|
std::vector<TileElement> newElements;
|
|
|
|
newElements.reserve(std::max(MIN_TILE_ELEMENTS, capacity));
|
2024-03-17 14:41:36 +01:00
|
|
|
for (int32_t y = 0; y < kMaximumMapSizeTechnical; y++)
|
2021-04-09 03:09:08 +02:00
|
|
|
{
|
2024-03-17 14:41:36 +01:00
|
|
|
for (int32_t x = 0; x < kMaximumMapSizeTechnical; x++)
|
2021-04-09 03:09:08 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
const auto* element = MapGetFirstElementAt(TileCoordsXY{ x, y });
|
2021-04-09 03:09:08 +02:00
|
|
|
if (element == nullptr)
|
|
|
|
{
|
2021-11-14 21:58:33 +01:00
|
|
|
newElements.push_back(GetDefaultSurfaceElement());
|
2021-04-09 03:09:08 +02:00
|
|
|
}
|
|
|
|
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());
|
2021-04-09 03:09:08 +02:00
|
|
|
}
|
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
static bool MapCheckFreeElementsAndReorganise(size_t numElementsOnTile, size_t numNewElements)
|
2021-04-09 03:09:08 +02:00
|
|
|
{
|
2021-06-10 13:22:31 +02:00
|
|
|
// 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();
|
2021-06-10 13:22:31 +02:00
|
|
|
auto totalElementsRequired = numElementsOnTile + numNewElements;
|
2024-02-25 16:53:41 +01:00
|
|
|
auto freeElements = gameState.TileElements.capacity() - gameState.TileElements.size();
|
2021-06-10 13:22:31 +02:00
|
|
|
if (freeElements >= totalElementsRequired)
|
2021-04-09 03:09:08 +02:00
|
|
|
{
|
|
|
|
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-04-09 03:09:08 +02:00
|
|
|
{
|
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-04-09 03:09:08 +02:00
|
|
|
{
|
2021-09-15 22:22:15 +02:00
|
|
|
return true;
|
2021-04-09 03:09:08 +02:00
|
|
|
}
|
|
|
|
}
|
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;
|
2021-04-09 03:09:08 +02:00
|
|
|
}
|
|
|
|
|
2021-06-06 09:28:07 +02:00
|
|
|
static size_t CountElementsOnTile(const CoordsXY& loc);
|
|
|
|
|
|
|
|
bool MapCheckCapacityAndReorganise(const CoordsXY& loc, size_t numElements)
|
|
|
|
{
|
|
|
|
auto numElementsOnTile = CountElementsOnTile(loc);
|
2022-10-12 14:14:45 +02:00
|
|
|
return MapCheckFreeElementsAndReorganise(numElementsOnTile, numElements);
|
2021-06-06 09:28:07 +02:00
|
|
|
}
|
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
static void ClearElementsAt(const CoordsXY& loc);
|
2014-04-09 04:09:30 +02:00
|
|
|
|
2023-01-19 16:13:23 +01:00
|
|
|
void TileElementIteratorBegin(TileElementIterator* it)
|
2015-01-22 01:19:05 +01:00
|
|
|
{
|
2024-03-02 01:33:45 +01:00
|
|
|
it->x = 1;
|
|
|
|
it->y = 1;
|
|
|
|
it->element = MapGetFirstElementAt(TileCoordsXY{ 1, 1 });
|
2015-01-22 01:19:05 +01:00
|
|
|
}
|
|
|
|
|
2023-01-19 16:13:23 +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)
|
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
it->element = MapGetFirstElementAt(TileCoordsXY{ it->x, it->y });
|
2017-06-06 23:24:18 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2018-06-22 23:17:03 +02:00
|
|
|
if (!it->element->IsLastForTile())
|
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
it->element++;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2024-03-02 01:33:45 +01:00
|
|
|
auto& gameState = GetGameState();
|
|
|
|
if (it->y < (gameState.MapSize.y - 2))
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2021-12-30 23:03:32 +01:00
|
|
|
it->y++;
|
2022-10-11 20:39:24 +02:00
|
|
|
it->element = MapGetFirstElementAt(TileCoordsXY{ it->x, it->y });
|
2017-06-06 23:24:18 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2024-03-02 01:33:45 +01:00
|
|
|
if (it->x < (gameState.MapSize.x - 2))
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2024-03-02 01:33:45 +01:00
|
|
|
it->y = 1;
|
2021-12-30 23:03:32 +01:00
|
|
|
it->x++;
|
2022-10-11 20:39:24 +02:00
|
|
|
it->element = MapGetFirstElementAt(TileCoordsXY{ it->x, it->y });
|
2017-06-06 23:24:18 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2015-01-22 01:19:05 +01:00
|
|
|
}
|
|
|
|
|
2023-01-19 16:13:23 +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
|
|
|
}
|
|
|
|
|
2021-09-07 21:19:14 +02:00
|
|
|
static bool IsTileLocationValid(const TileCoordsXY& coords)
|
|
|
|
{
|
2024-03-17 14:41:36 +01:00
|
|
|
const bool is_x_valid = coords.x < kMaximumMapSizeTechnical && coords.x >= 0;
|
|
|
|
const bool is_y_valid = coords.y < kMaximumMapSizeTechnical && coords.y >= 0;
|
2021-09-07 21:19:14 +02:00
|
|
|
return is_x_valid && is_y_valid;
|
|
|
|
}
|
|
|
|
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* MapGetFirstElementAt(const TileCoordsXY& tilePos)
|
2015-01-22 01:19:05 +01:00
|
|
|
{
|
2021-09-07 21:19:14 +02:00
|
|
|
if (!IsTileLocationValid(tilePos))
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2023-01-17 13:24:51 +01:00
|
|
|
LOG_VERBOSE("Trying to access element outside of range");
|
2018-01-04 06:58:44 +01:00
|
|
|
return nullptr;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2021-09-07 21:19:14 +02:00
|
|
|
return _tileIndex.GetFirstElementAt(tilePos);
|
|
|
|
}
|
|
|
|
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* MapGetFirstElementAt(const CoordsXY& elementPos)
|
2021-09-07 21:19:14 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
return MapGetFirstElementAt(TileCoordsXY{ elementPos });
|
2015-01-22 01:19:05 +01:00
|
|
|
}
|
|
|
|
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* MapGetNthElementAt(const CoordsXY& coords, int32_t n)
|
2017-02-14 00:15:08 +01:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
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;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
// 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;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2018-06-22 23:17:03 +02:00
|
|
|
if (tileElement->IsLastForTile())
|
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
break;
|
|
|
|
}
|
2017-10-31 14:03:45 +01:00
|
|
|
tileElement++;
|
2017-06-06 23:24:18 +02:00
|
|
|
n--;
|
|
|
|
}
|
|
|
|
// The element sought for is not within given tile.
|
2018-01-04 06:58:44 +01:00
|
|
|
return nullptr;
|
2017-02-14 00:15:08 +01:00
|
|
|
}
|
|
|
|
|
2022-09-30 20:00:59 +02:00
|
|
|
TileElement* MapGetFirstTileElementWithBaseHeightBetween(const TileCoordsXYRangedZ& loc, TileElementType type)
|
|
|
|
{
|
2023-04-05 22:18:12 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(loc);
|
2022-09-30 20:00:59 +02:00
|
|
|
if (tileElement == nullptr)
|
|
|
|
return nullptr;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
if (tileElement->GetType() != type)
|
|
|
|
continue;
|
2023-01-19 20:36:30 +01:00
|
|
|
if (tileElement->BaseHeight >= loc.baseZ && tileElement->BaseHeight <= loc.clearanceZ)
|
2022-09-30 20:00:59 +02:00
|
|
|
return tileElement;
|
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2022-10-11 20:39:24 +02:00
|
|
|
void MapSetTileElement(const TileCoordsXY& tilePos, TileElement* elements)
|
2015-06-27 16:17:54 +02:00
|
|
|
{
|
2022-10-12 07:35:20 +02:00
|
|
|
if (!MapIsLocationValid(tilePos.ToCoordsXY()))
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2023-01-17 13:24:51 +01:00
|
|
|
LOG_ERROR("Trying to access element outside of range");
|
2017-06-06 23:24:18 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-04-09 03:09:08 +02:00
|
|
|
_tileIndex.SetTile(tilePos, elements);
|
2015-06-27 16:17:54 +02:00
|
|
|
}
|
|
|
|
|
2023-04-05 22:13:47 +02:00
|
|
|
SurfaceElement* MapGetSurfaceElementAt(const TileCoordsXY& coords)
|
2014-05-03 22:34:22 +02:00
|
|
|
{
|
2021-02-04 17:58:45 +01:00
|
|
|
auto view = TileElementsView<SurfaceElement>(coords);
|
2014-05-03 22:34:22 +02:00
|
|
|
|
2021-02-04 17:58:45 +01:00
|
|
|
return *view.begin();
|
2014-05-03 22:34:22 +02:00
|
|
|
}
|
|
|
|
|
2023-04-05 22:13:47 +02:00
|
|
|
SurfaceElement* MapGetSurfaceElementAt(const CoordsXY& coords)
|
|
|
|
{
|
|
|
|
return MapGetSurfaceElementAt(TileCoordsXY{ coords });
|
|
|
|
}
|
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
PathElement* MapGetPathElementAt(const TileCoordsXYZ& loc)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2021-02-04 17:58:45 +01:00
|
|
|
for (auto* element : TileElementsView<PathElement>(loc.ToCoordsXY()))
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2021-02-04 17:58:45 +01:00
|
|
|
if (element->IsGhost())
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
2023-01-19 20:36:30 +01:00
|
|
|
if (element->BaseHeight != loc.z)
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
2021-02-04 17:58:45 +01:00
|
|
|
return element;
|
|
|
|
}
|
2018-01-04 06:58:44 +01:00
|
|
|
return nullptr;
|
2015-05-04 19:08:22 +02:00
|
|
|
}
|
2015-11-13 19:30:14 +01:00
|
|
|
|
2022-10-11 20:39:24 +02:00
|
|
|
BannerElement* MapGetBannerElementAt(const CoordsXYZ& bannerPos, uint8_t position)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2021-02-04 17:58:45 +01:00
|
|
|
const auto bannerTilePos = TileCoordsXYZ{ bannerPos };
|
|
|
|
for (auto* element : TileElementsView<BannerElement>(bannerPos))
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2023-01-19 20:36:30 +01:00
|
|
|
if (element->BaseHeight != bannerTilePos.z)
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
2021-02-04 17:58:45 +01:00
|
|
|
if (element->GetPosition() != position)
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
2021-02-04 17:58:45 +01:00
|
|
|
return element;
|
|
|
|
}
|
2018-01-04 06:58:44 +01:00
|
|
|
return nullptr;
|
2015-11-13 19:30:14 +01:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
*/
|
2022-10-11 20:39:24 +02:00
|
|
|
void MapInit(const TileCoordsXY& size)
|
2014-04-09 04:09:30 +02:00
|
|
|
{
|
2024-03-17 14:41:36 +01:00
|
|
|
auto numTiles = kMaximumMapSizeTechnical * kMaximumMapSizeTechnical;
|
2022-07-04 21:59:09 +02:00
|
|
|
SetTileElements(std::vector<TileElement>(numTiles, GetDefaultSurfaceElement()));
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2024-02-11 22:40:32 +01:00
|
|
|
auto& gameState = GetGameState();
|
|
|
|
|
2024-03-21 22:50:18 +01:00
|
|
|
gameState.GrassSceneryTileLoopPosition = 0;
|
|
|
|
gameState.WidePathTileLoopPosition = {};
|
2024-02-12 22:32:08 +01:00
|
|
|
gameState.MapSize = size;
|
2022-10-12 14:14:45 +02:00
|
|
|
MapRemoveOutOfRangeElements();
|
2022-10-04 21:24:38 +02:00
|
|
|
MapAnimationAutoCreate();
|
2017-06-06 23:24:18 +02:00
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2016-10-31 20:21:10 +01: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
|
|
|
*/
|
2022-10-11 20:39:24 +02:00
|
|
|
void MapCountRemainingLandRights()
|
2016-10-31 20:21:10 +01:00
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
gLandRemainingOwnershipSales = 0;
|
|
|
|
gLandRemainingConstructionSales = 0;
|
2024-02-12 22:32:08 +01:00
|
|
|
auto& gameState = GetGameState();
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2024-02-12 22:32:08 +01:00
|
|
|
for (int32_t y = 0; y < gameState.MapSize.y; y++)
|
2017-12-27 18:51:55 +01:00
|
|
|
{
|
2024-02-12 22:32:08 +01:00
|
|
|
for (int32_t x = 0; x < gameState.MapSize.x; x++)
|
2017-12-27 18:51:55 +01:00
|
|
|
{
|
2023-04-05 22:13:47 +02:00
|
|
|
auto* surfaceElement = MapGetSurfaceElementAt(TileCoordsXY{ x, y });
|
2017-06-06 23:24:18 +02:00
|
|
|
// Surface elements are sometimes hacked out to save some space for other map elements
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement == nullptr)
|
2017-12-27 18:51:55 +01:00
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-08-11 10:06:04 +02:00
|
|
|
uint8_t flags = surfaceElement->GetOwnership();
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2017-12-27 18:51:55 +01:00
|
|
|
// 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)
|
2017-12-27 18:51:55 +01:00
|
|
|
{
|
|
|
|
gLandRemainingConstructionSales++;
|
|
|
|
}
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-31 20:21:10 +01:00
|
|
|
}
|
|
|
|
|
2017-01-21 01:21:10 +01:00
|
|
|
/**
|
2017-10-31 12:57:40 +01:00
|
|
|
* This is meant to strip TILE_ELEMENT_FLAG_GHOST flag from all elements when
|
2017-01-21 01:21:10 +01:00
|
|
|
* 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.
|
|
|
|
*/
|
2022-10-11 20:39:24 +02:00
|
|
|
void MapStripGhostFlagFromElements()
|
2017-01-21 01:21:10 +01:00
|
|
|
{
|
2024-02-25 16:53:41 +01:00
|
|
|
auto& gameState = GetGameState();
|
|
|
|
for (auto& element : gameState.TileElements)
|
2018-06-20 22:33:51 +02:00
|
|
|
{
|
2019-02-25 21:44:12 +01:00
|
|
|
element.SetGhost(false);
|
2018-06-20 22:33:51 +02:00
|
|
|
}
|
2017-01-21 01:21:10 +01:00
|
|
|
}
|
|
|
|
|
2014-04-10 16:14:47 +02:00
|
|
|
/**
|
2014-05-02 21:25:42 +02:00
|
|
|
* Return the absolute height of an element, given its (x,y) coordinates
|
2014-09-18 18:18:39 +02:00
|
|
|
*
|
2015-12-11 16:38:37 +01:00
|
|
|
* 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
|
|
|
|
*/
|
2022-10-12 07:35:20 +02:00
|
|
|
int16_t TileElementHeight(const CoordsXY& loc)
|
2014-04-10 16:14:47 +02:00
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
// Off the map
|
2022-10-12 07:35:20 +02:00
|
|
|
if (!MapIsLocationValid(loc))
|
2024-03-01 21:23:29 +01:00
|
|
|
return kMinimumLandZ;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
|
|
|
// Get the surface element for the tile
|
2022-10-11 20:39:24 +02:00
|
|
|
auto surfaceElement = MapGetSurfaceElementAt(loc);
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement == nullptr)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2024-03-01 21:23:29 +01:00
|
|
|
return kMinimumLandZ;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
|
2024-01-12 08:14:11 +01:00
|
|
|
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))
|
2024-03-01 21:23:29 +01:00
|
|
|
return kMinimumLandZ;
|
2024-01-12 08:14:11 +01:00
|
|
|
|
|
|
|
auto height = loc.z;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2018-06-20 17:28:51 +02:00
|
|
|
uint8_t extra_height = (slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) >> 4; // 0x10 is the 5th bit - sets slope to double height
|
2017-06-06 23:24:18 +02:00
|
|
|
// Remove the extra height bit
|
2017-11-18 12:58:27 +01:00
|
|
|
slope &= TILE_ELEMENT_SLOPE_ALL_CORNERS_UP;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2018-06-20 17:28:51 +02:00
|
|
|
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
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2018-06-22 23:17:03 +02:00
|
|
|
uint8_t xl, yl; // coordinates across this tile
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2022-07-20 21:33:33 +02:00
|
|
|
uint8_t TILE_SIZE = 32;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2019-08-13 19:21:50 +02:00
|
|
|
xl = loc.x & 0x1f;
|
|
|
|
yl = loc.y & 0x1f;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
|
|
|
// 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)
|
2017-11-18 12:58:27 +01:00
|
|
|
{
|
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;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
// If the element is in the quadrant with the slope, raise its height
|
2018-06-22 23:17:03 +02:00
|
|
|
if (quad > 0)
|
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
height += quad / 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// One side up
|
2018-06-22 23:17:03 +02:00
|
|
|
switch (slope)
|
|
|
|
{
|
|
|
|
case TILE_ELEMENT_SLOPE_NE_SIDE_UP:
|
2022-07-20 21:33:33 +02:00
|
|
|
height += xl / 2;
|
2017-06-06 23:24:18 +02:00
|
|
|
break;
|
2018-06-22 23:17:03 +02:00
|
|
|
case TILE_ELEMENT_SLOPE_SE_SIDE_UP:
|
|
|
|
height += (TILE_SIZE - yl) / 2;
|
2017-06-06 23:24:18 +02:00
|
|
|
break;
|
2018-06-22 23:17:03 +02:00
|
|
|
case TILE_ELEMENT_SLOPE_NW_SIDE_UP:
|
|
|
|
height += yl / 2;
|
2017-06-06 23:24:18 +02:00
|
|
|
break;
|
2018-06-22 23:17:03 +02:00
|
|
|
case TILE_ELEMENT_SLOPE_SW_SIDE_UP:
|
|
|
|
height += (TILE_SIZE - xl) / 2;
|
2017-06-06 23:24:18 +02:00
|
|
|
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;
|
2022-07-20 21:33:33 +02:00
|
|
|
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);
|
2022-07-20 21:33:33 +02:00
|
|
|
quad = TILE_SIZE - yl - xl;
|
2018-06-22 23:17:03 +02:00
|
|
|
break;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
|
2018-06-22 23:17:03 +02:00
|
|
|
if (extra_height)
|
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
height += quad_extra / 2;
|
|
|
|
return height;
|
|
|
|
}
|
|
|
|
// This tile is essentially at the next height level
|
2020-02-08 17:19:59 +01:00
|
|
|
height += LAND_HEIGHT_STEP;
|
2017-06-06 23:24:18 +02:00
|
|
|
// so we move *down* the slope
|
2018-06-22 23:17:03 +02:00
|
|
|
if (quad < 0)
|
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
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:
|
2022-07-20 21:33:33 +02:00
|
|
|
quad = std::abs(xl - yl);
|
2018-06-22 23:17:03 +02:00
|
|
|
break;
|
|
|
|
case TILE_ELEMENT_SLOPE_N_S_VALLEY:
|
2022-07-20 21:33:33 +02:00
|
|
|
quad = std::abs(xl + yl - TILE_SIZE);
|
2018-06-22 23:17:03 +02:00
|
|
|
break;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2022-07-20 21:33:33 +02:00
|
|
|
height += quad / 2;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return height;
|
2014-04-21 11:27:48 +02:00
|
|
|
}
|
2014-05-04 19:51:36 +02:00
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
int16_t TileElementWaterHeight(const CoordsXY& loc)
|
2019-04-23 19:26:48 +02:00
|
|
|
{
|
|
|
|
// Off the map
|
2022-10-12 07:35:20 +02:00
|
|
|
if (!MapIsLocationValid(loc))
|
2019-04-23 19:26:48 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
// Get the surface element for the tile
|
2022-10-11 20:39:24 +02:00
|
|
|
auto surfaceElement = MapGetSurfaceElementAt(loc);
|
2019-04-23 19:26:48 +02:00
|
|
|
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement == nullptr)
|
2019-04-23 19:26:48 +02:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-01-19 16:12:48 +01:00
|
|
|
return surfaceElement->GetWaterHeight();
|
2019-04-23 19:26:48 +02:00
|
|
|
}
|
|
|
|
|
2014-05-26 15:22:52 +02:00
|
|
|
/**
|
|
|
|
* Checks if the tile at coordinate at height counts as connected.
|
2014-06-08 06:14:55 +02:00
|
|
|
* @return 1 if connected, 0 otherwise
|
2014-05-26 15:22:52 +02:00
|
|
|
*/
|
2022-10-12 07:35:20 +02:00
|
|
|
bool MapCoordIsConnected(const TileCoordsXYZ& loc, uint8_t faceDirection)
|
2014-05-26 15:22:52 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(loc);
|
2017-06-06 23:24:18 +02:00
|
|
|
|
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)
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
|
|
|
|
2018-10-04 15:40:55 +02:00
|
|
|
uint8_t slopeDirection = tileElement->AsPath()->GetSlopeDirection();
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2018-09-16 16:17:35 +02:00
|
|
|
if (tileElement->AsPath()->IsSloped())
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2018-10-04 15:40:55 +02:00
|
|
|
if (slopeDirection == faceDirection)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2023-01-19 20:36:30 +01: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
|
|
|
}
|
2023-01-19 20:36:30 +01: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;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2018-06-22 23:17:03 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-04-01 08:44:22 +02:00
|
|
|
if (loc.z == tileElement->BaseHeight && (tileElement->AsPath()->GetEdges() & (1 << faceDirection)))
|
2018-01-04 13:36:08 +01:00
|
|
|
return true;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2018-05-24 11:44:53 +02:00
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2018-01-04 13:36:08 +01:00
|
|
|
return false;
|
2014-05-26 15:22:52 +02:00
|
|
|
}
|
|
|
|
|
2014-10-09 21:31:58 +02:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006A876D
|
|
|
|
*/
|
2022-10-12 07:35:20 +02:00
|
|
|
void MapUpdatePathWideFlags()
|
2014-10-09 21:31:58 +02:00
|
|
|
{
|
2022-01-18 19:21:20 +01:00
|
|
|
PROFILED_FUNCTION();
|
|
|
|
|
2018-06-22 23:17:03 +02:00
|
|
|
if (gScreenFlags & (SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER))
|
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
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.
|
2024-03-21 22:50:18 +01:00
|
|
|
CoordsXY& loopPosition = GetGameState().WidePathTileLoopPosition;
|
2018-06-22 23:17:03 +02:00
|
|
|
for (int32_t i = 0; i < 128; i++)
|
|
|
|
{
|
2024-03-21 22:50:18 +01:00
|
|
|
FootpathUpdatePathWideFlags(loopPosition);
|
2017-06-06 23:24:18 +02:00
|
|
|
|
|
|
|
// Next x, y tile
|
2024-03-21 22:50:18 +01:00
|
|
|
loopPosition.x += COORDS_XY_STEP;
|
|
|
|
if (loopPosition.x >= MAXIMUM_MAP_SIZE_BIG)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2024-03-21 22:50:18 +01:00
|
|
|
loopPosition.x = 0;
|
|
|
|
loopPosition.y += COORDS_XY_STEP;
|
|
|
|
if (loopPosition.y >= MAXIMUM_MAP_SIZE_BIG)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2024-03-21 22:50:18 +01:00
|
|
|
loopPosition.y = 0;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-10-09 21:31:58 +02:00
|
|
|
}
|
|
|
|
|
2014-12-10 02:44:30 +01:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006A7B84
|
|
|
|
*/
|
2022-10-11 20:39:24 +02:00
|
|
|
int32_t MapHeightFromSlope(const CoordsXY& coords, int32_t slopeDirection, bool isSloped)
|
2014-12-10 02:44:30 +01:00
|
|
|
{
|
2018-09-16 16:17:35 +02:00
|
|
|
if (!isSloped)
|
2017-06-06 23:24:18 +02:00
|
|
|
return 0;
|
|
|
|
|
2020-03-04 18:43:09 +01:00
|
|
|
switch (slopeDirection % NumOrthogonalDirections)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2018-09-16 13:07:54 +02:00
|
|
|
case TILE_ELEMENT_DIRECTION_WEST:
|
2018-09-16 16:17:35 +02:00
|
|
|
return (31 - (coords.x & 31)) / 2;
|
2018-09-16 13:07:54 +02:00
|
|
|
case TILE_ELEMENT_DIRECTION_NORTH:
|
2018-09-16 16:17:35 +02:00
|
|
|
return (coords.y & 31) / 2;
|
2018-09-16 13:07:54 +02:00
|
|
|
case TILE_ELEMENT_DIRECTION_EAST:
|
2018-09-16 16:17:35 +02:00
|
|
|
return (coords.x & 31) / 2;
|
2018-09-16 13:07:54 +02:00
|
|
|
case TILE_ELEMENT_DIRECTION_SOUTH:
|
2018-09-16 16:17:35 +02:00
|
|
|
return (31 - (coords.y & 31)) / 2;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
return 0;
|
2014-09-20 15:06:42 +02:00
|
|
|
}
|
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
bool MapIsLocationValid(const CoordsXY& coords)
|
2016-05-09 16:52:03 +02:00
|
|
|
{
|
2019-12-30 16:03:51 +01:00
|
|
|
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;
|
2016-05-09 16:52:03 +02:00
|
|
|
}
|
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
bool MapIsEdge(const CoordsXY& coords)
|
2018-06-01 11:30:41 +02:00
|
|
|
{
|
2021-12-17 19:25:46 +01:00
|
|
|
auto mapSizeUnits = GetMapSizeUnits();
|
|
|
|
return (coords.x < 32 || coords.y < 32 || coords.x >= mapSizeUnits.x || coords.y >= mapSizeUnits.y);
|
2018-06-01 11:30:41 +02:00
|
|
|
}
|
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
bool MapCanBuildAt(const CoordsXYZ& loc)
|
2017-03-13 22:47:43 +01:00
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
|
|
|
|
return true;
|
2024-03-03 22:44:15 +01:00
|
|
|
if (GetGameState().Cheats.SandboxMode)
|
2017-06-06 23:24:18 +02:00
|
|
|
return true;
|
2022-10-12 07:35:20 +02:00
|
|
|
if (MapIsLocationOwned(loc))
|
2017-06-06 23:24:18 +02:00
|
|
|
return true;
|
|
|
|
return false;
|
2017-03-13 22:47:43 +01:00
|
|
|
}
|
|
|
|
|
2014-11-02 02:41:00 +01:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x00664F72
|
|
|
|
*/
|
2022-10-12 07:35:20 +02:00
|
|
|
bool MapIsLocationOwned(const CoordsXYZ& loc)
|
2014-11-02 02:41:00 +01:00
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
// This check is to avoid throwing lots of messages in logs.
|
2022-10-12 07:35:20 +02:00
|
|
|
if (MapIsLocationValid(loc))
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
auto* surfaceElement = MapGetSurfaceElementAt(loc);
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement != nullptr)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement->GetOwnership() & OWNERSHIP_OWNED)
|
2017-06-06 23:24:18 +02:00
|
|
|
return true;
|
|
|
|
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement->GetOwnership() & OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2022-08-31 23:49:45 +02:00
|
|
|
if (loc.z < surfaceElement->GetBaseZ() || loc.z >= surfaceElement->GetBaseZ() + ConstructionRightsClearanceBig)
|
2017-06-06 23:24:18 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2014-11-02 02:41:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x00664F2C
|
|
|
|
*/
|
2022-10-12 07:35:20 +02:00
|
|
|
bool MapIsLocationInPark(const CoordsXY& coords)
|
2014-11-02 02:41:00 +01:00
|
|
|
{
|
2022-10-12 07:35:20 +02:00
|
|
|
if (MapIsLocationValid(coords))
|
2018-06-04 19:18:52 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
auto surfaceElement = MapGetSurfaceElementAt(coords);
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement == nullptr)
|
2017-06-06 23:24:18 +02:00
|
|
|
return false;
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement->GetOwnership() & OWNERSHIP_OWNED)
|
2017-06-06 23:24:18 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2014-11-02 04:37:56 +01:00
|
|
|
}
|
2014-10-11 20:21:37 +02:00
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
bool MapIsLocationOwnedOrHasRights(const CoordsXY& loc)
|
2015-08-04 17:01:31 +02:00
|
|
|
{
|
2022-10-12 07:35:20 +02:00
|
|
|
if (MapIsLocationValid(loc))
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
auto surfaceElement = MapGetSurfaceElementAt(loc);
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement == nullptr)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
return false;
|
|
|
|
}
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement->GetOwnership() & OWNERSHIP_OWNED)
|
2018-06-22 23:17:03 +02:00
|
|
|
return true;
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement->GetOwnership() & OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED)
|
2018-06-22 23:17:03 +02:00
|
|
|
return true;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
return false;
|
2015-08-04 17:01:31 +02:00
|
|
|
}
|
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
int32_t MapGetCornerHeight(int32_t z, int32_t slope, int32_t direction)
|
2017-06-27 14:43:08 +02:00
|
|
|
{
|
2018-06-22 23:17:03 +02:00
|
|
|
switch (direction)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
if (slope & TILE_ELEMENT_SLOPE_N_CORNER_UP)
|
|
|
|
{
|
2017-06-27 14:43:08 +02:00
|
|
|
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;
|
|
|
|
}
|
2017-06-27 14:43:08 +02:00
|
|
|
}
|
2018-06-22 23:17:03 +02:00
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
if (slope & TILE_ELEMENT_SLOPE_E_CORNER_UP)
|
|
|
|
{
|
2017-06-27 14:43:08 +02:00
|
|
|
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;
|
|
|
|
}
|
2017-06-27 14:43:08 +02:00
|
|
|
}
|
2018-06-22 23:17:03 +02:00
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
if (slope & TILE_ELEMENT_SLOPE_S_CORNER_UP)
|
|
|
|
{
|
2017-06-27 14:43:08 +02:00
|
|
|
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;
|
|
|
|
}
|
2017-06-27 14:43:08 +02:00
|
|
|
}
|
2018-06-22 23:17:03 +02:00
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
if (slope & TILE_ELEMENT_SLOPE_W_CORNER_UP)
|
|
|
|
{
|
2017-06-27 14:43:08 +02:00
|
|
|
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;
|
|
|
|
}
|
2017-06-27 14:43:08 +02:00
|
|
|
}
|
2018-06-22 23:17:03 +02:00
|
|
|
break;
|
2017-06-27 14:43:08 +02:00
|
|
|
}
|
|
|
|
return z;
|
|
|
|
}
|
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
int32_t TileElementGetCornerHeight(const SurfaceElement* surfaceElement, int32_t direction)
|
2017-06-27 14:43:08 +02:00
|
|
|
{
|
2023-01-19 20:36:30 +01:00
|
|
|
int32_t z = surfaceElement->BaseHeight;
|
2019-08-11 10:06:04 +02:00
|
|
|
int32_t slope = surfaceElement->GetSlope();
|
2022-10-12 14:14:45 +02:00
|
|
|
return MapGetCornerHeight(z, slope, direction);
|
2017-06-27 14:43:08 +02:00
|
|
|
}
|
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
uint8_t MapGetLowestLandHeight(const MapRange& range)
|
2017-07-21 15:23:38 +02:00
|
|
|
{
|
2021-12-17 19:25:46 +01:00
|
|
|
auto mapSizeMax = GetMapSizeMaxXY();
|
2019-08-13 21:51:37 +02:00
|
|
|
MapRange validRange = { std::max(range.GetLeft(), 32), std::max(range.GetTop(), 32),
|
2021-12-17 19:25:46 +01:00
|
|
|
std::min(range.GetRight(), mapSizeMax.x), std::min(range.GetBottom(), mapSizeMax.y) };
|
2017-07-21 15:23:38 +02:00
|
|
|
|
2018-06-20 17:28:51 +02:00
|
|
|
uint8_t min_height = 0xFF;
|
2019-12-30 16:03:51 +01:00
|
|
|
for (int32_t yi = validRange.GetTop(); yi <= validRange.GetBottom(); yi += COORDS_XY_STEP)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2019-12-30 16:03:51 +01:00
|
|
|
for (int32_t xi = validRange.GetLeft(); xi <= validRange.GetRight(); xi += COORDS_XY_STEP)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
auto* surfaceElement = MapGetSurfaceElementAt(CoordsXY{ xi, yi });
|
2020-04-18 13:19:03 +02:00
|
|
|
|
2023-01-19 20:36:30 +01:00
|
|
|
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)
|
2020-04-18 13:19:03 +02:00
|
|
|
{
|
2022-10-12 07:35:20 +02:00
|
|
|
if (!MapIsLocationInPark(CoordsXY{ xi, yi }))
|
2020-04-18 13:19:03 +02:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-19 20:36:30 +01:00
|
|
|
min_height = surfaceElement->BaseHeight;
|
2017-07-21 15:23:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return min_height;
|
|
|
|
}
|
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
uint8_t MapGetHighestLandHeight(const MapRange& range)
|
2017-07-21 15:23:38 +02:00
|
|
|
{
|
2021-12-17 19:25:46 +01:00
|
|
|
auto mapSizeMax = GetMapSizeMaxXY();
|
2019-08-13 21:51:37 +02:00
|
|
|
MapRange validRange = { std::max(range.GetLeft(), 32), std::max(range.GetTop(), 32),
|
2021-12-17 19:25:46 +01:00
|
|
|
std::min(range.GetRight(), mapSizeMax.x), std::min(range.GetBottom(), mapSizeMax.y) };
|
2017-07-21 15:23:38 +02:00
|
|
|
|
2018-06-20 17:28:51 +02:00
|
|
|
uint8_t max_height = 0;
|
2019-12-30 16:03:51 +01:00
|
|
|
for (int32_t yi = validRange.GetTop(); yi <= validRange.GetBottom(); yi += COORDS_XY_STEP)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2019-12-30 16:03:51 +01:00
|
|
|
for (int32_t xi = validRange.GetLeft(); xi <= validRange.GetRight(); xi += COORDS_XY_STEP)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
auto* surfaceElement = MapGetSurfaceElementAt(CoordsXY{ xi, yi });
|
2019-08-11 10:06:04 +02:00
|
|
|
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)
|
2020-04-18 13:19:03 +02:00
|
|
|
{
|
2022-10-12 07:35:20 +02:00
|
|
|
if (!MapIsLocationInPark(CoordsXY{ xi, yi }))
|
2020-04-18 13:19:03 +02:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-19 20:36:30 +01:00
|
|
|
uint8_t BaseHeight = surfaceElement->BaseHeight;
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP)
|
2023-01-19 20:36:30 +01:00
|
|
|
BaseHeight += 2;
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
|
2023-01-19 20:36:30 +01:00
|
|
|
BaseHeight += 2;
|
|
|
|
if (max_height < BaseHeight)
|
|
|
|
max_height = BaseHeight;
|
2017-07-21 15:23:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return max_height;
|
|
|
|
}
|
2015-10-04 01:07:22 +02:00
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
bool MapIsLocationAtEdge(const CoordsXY& loc)
|
2015-09-06 18:36:55 +02:00
|
|
|
{
|
2019-12-30 16:03:51 +01:00
|
|
|
return loc.x < 32 || loc.y < 32 || loc.x >= (MAXIMUM_TILE_START_XY) || loc.y >= (MAXIMUM_TILE_START_XY);
|
2015-09-06 18:36:55 +02:00
|
|
|
}
|
|
|
|
|
2014-11-24 03:58:48 +01:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x0068B280
|
|
|
|
*/
|
2022-10-12 07:35:20 +02:00
|
|
|
void TileElementRemove(TileElement* tileElement)
|
2014-11-24 03:58:48 +01:00
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
// 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,
|
2017-06-06 23:24:18 +02:00
|
|
|
// 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);
|
2018-05-24 11:44:53 +02:00
|
|
|
} while (!(++tileElement)->IsLastForTile());
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Mark the latest element with the last element flag.
|
2019-08-25 16:13:02 +02:00
|
|
|
(tileElement - 1)->SetLastForTile(true);
|
2023-01-19 20:36:30 +01:00
|
|
|
tileElement->BaseHeight = MAX_ELEMENT_HEIGHT;
|
2021-06-10 13:22:31 +02:00
|
|
|
_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();
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2014-11-24 03:58:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x00675A8E
|
|
|
|
*/
|
2022-10-12 07:35:20 +02:00
|
|
|
void MapRemoveAllRides()
|
2014-11-24 03:58:48 +01:00
|
|
|
{
|
2023-01-19 16:13:23 +01:00
|
|
|
TileElementIterator it;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
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
|
|
|
{
|
2018-10-04 15:40:55 +02:00
|
|
|
it.element->AsPath()->SetHasQueueBanner(false);
|
2022-01-19 17:24:23 +01:00
|
|
|
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;
|
2019-01-04 22:26:56 +01:00
|
|
|
[[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);
|
2022-10-12 07:35:20 +02:00
|
|
|
TileElementRemove(it.element);
|
2022-10-12 14:14:45 +02:00
|
|
|
TileElementIteratorRestartForTile(&it);
|
2017-06-06 23:24:18 +02:00
|
|
|
break;
|
2021-12-11 00:30:59 +01:00
|
|
|
default:
|
|
|
|
break;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2022-10-12 14:14:45 +02:00
|
|
|
} while (TileElementIteratorNext(&it));
|
2014-12-17 21:45:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x0068AB1B
|
|
|
|
*/
|
2022-10-12 07:35:20 +02:00
|
|
|
void MapInvalidateMapSelectionTiles()
|
2014-12-17 21:45:27 +01:00
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
if (!(gMapSelectFlags & MAP_SELECT_FLAG_ENABLE_CONSTRUCT))
|
|
|
|
return;
|
2014-12-17 21:45:27 +01:00
|
|
|
|
2019-03-28 19:29:51 +01:00
|
|
|
for (const auto& position : gMapSelectionTiles)
|
2022-10-12 14:14:45 +02:00
|
|
|
MapInvalidateTileFull(position);
|
2014-12-17 21:45:27 +01:00
|
|
|
}
|
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
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();
|
2021-10-14 23:27:28 +02:00
|
|
|
const std::array corners{
|
|
|
|
CoordsXY{ _range.GetLeft(), _range.GetTop() },
|
|
|
|
CoordsXY{ _range.GetRight(), _range.GetTop() },
|
|
|
|
CoordsXY{ _range.GetRight(), _range.GetBottom() },
|
|
|
|
CoordsXY{ _range.GetLeft(), _range.GetBottom() },
|
|
|
|
};
|
2019-08-17 12:57:32 +02:00
|
|
|
|
|
|
|
*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)
|
|
|
|
{
|
2024-04-21 01:18:11 +02:00
|
|
|
auto screenCoord = Translate3DTo2DWithZ(rotation, CoordsXYZ{ corner, 0 });
|
2019-08-17 12:57:32 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2014-12-17 21:45:27 +01:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x0068AAE1
|
|
|
|
*/
|
2022-10-12 07:35:20 +02:00
|
|
|
void MapInvalidateSelectionRect()
|
2014-12-17 21:45:27 +01:00
|
|
|
{
|
2018-06-20 17:28:51 +02:00
|
|
|
int32_t x0, y0, x1, y1, left, right, top, bottom;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
|
|
|
if (!(gMapSelectFlags & MAP_SELECT_FLAG_ENABLE))
|
|
|
|
return;
|
|
|
|
|
|
|
|
x0 = gMapSelectPositionA.x + 16;
|
|
|
|
y0 = gMapSelectPositionA.y + 16;
|
|
|
|
x1 = gMapSelectPositionB.x + 16;
|
|
|
|
y1 = gMapSelectPositionB.y + 16;
|
2022-10-12 14:14:45 +02:00
|
|
|
MapGetBoundingBox({ x0, y0, x1, y1 }, &left, &top, &right, &bottom);
|
2017-06-06 23:24:18 +02:00
|
|
|
left -= 32;
|
|
|
|
right += 32;
|
|
|
|
bottom += 32;
|
|
|
|
top -= 32 + 2080;
|
|
|
|
|
2023-01-16 21:14:50 +01:00
|
|
|
ViewportsInvalidate({ { left, top }, { right, bottom } });
|
2015-01-15 00:38:48 +01:00
|
|
|
}
|
|
|
|
|
2021-04-09 03:09:08 +02:00
|
|
|
static size_t CountElementsOnTile(const CoordsXY& loc)
|
2015-01-15 00:38:48 +01:00
|
|
|
{
|
2021-04-09 03:09:08 +02:00
|
|
|
size_t count = 0;
|
|
|
|
auto* element = _tileIndex.GetFirstElementAt(TileCoordsXY(loc));
|
|
|
|
do
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2021-04-09 03:09:08 +02:00
|
|
|
count++;
|
|
|
|
} while (!(element++)->IsLastForTile());
|
|
|
|
return count;
|
2015-01-15 00:38:48 +01:00
|
|
|
}
|
|
|
|
|
2021-06-10 13:22:31 +02:00
|
|
|
static TileElement* AllocateTileElements(size_t numElementsOnTile, size_t numNewElements)
|
2015-01-15 00:38:48 +01:00
|
|
|
{
|
2022-10-12 14:14:45 +02:00
|
|
|
if (!MapCheckFreeElementsAndReorganise(numElementsOnTile, numNewElements))
|
2018-06-20 22:33:51 +02:00
|
|
|
{
|
2023-01-17 13:24:51 +01:00
|
|
|
LOG_ERROR("Cannot insert new element");
|
2021-04-09 03:09:08 +02:00
|
|
|
return nullptr;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2021-04-09 03:09:08 +02:00
|
|
|
|
2024-02-25 16:53:41 +01:00
|
|
|
auto& gameState = GetGameState();
|
|
|
|
auto oldSize = gameState.TileElements.size();
|
|
|
|
gameState.TileElements.resize(gameState.TileElements.size() + numElementsOnTile + numNewElements);
|
2021-06-10 13:22:31 +02:00
|
|
|
_tileElementsInUse += numNewElements;
|
2024-02-25 16:53:41 +01:00
|
|
|
return &gameState.TileElements[oldSize];
|
2015-01-15 00:38:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x0068B1F6
|
|
|
|
*/
|
2022-10-12 07:35:20 +02:00
|
|
|
TileElement* TileElementInsert(const CoordsXYZ& loc, int32_t occupiedQuadrants, TileElementType type)
|
2015-01-15 00:38:48 +01:00
|
|
|
{
|
2020-03-07 21:28:36 +01:00
|
|
|
const auto& tileLoc = TileCoordsXYZ(loc);
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2021-04-09 03:09:08 +02:00
|
|
|
auto numElementsOnTileOld = CountElementsOnTile(loc);
|
2021-06-10 13:22:31 +02:00
|
|
|
auto* newTileElement = AllocateTileElements(numElementsOnTileOld, 1);
|
2021-04-09 03:09:08 +02:00
|
|
|
auto* originalTileElement = _tileIndex.GetFirstElementAt(tileLoc);
|
2021-06-06 09:28:07 +02:00
|
|
|
if (newTileElement == nullptr)
|
|
|
|
{
|
|
|
|
return nullptr;
|
|
|
|
}
|
2017-06-06 23:24:18 +02:00
|
|
|
|
|
|
|
// Set tile index pointer to point to new element block
|
2021-04-09 03:09:08 +02:00
|
|
|
_tileIndex.SetTile(tileLoc, newTileElement);
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2021-04-09 03:09:08 +02:00
|
|
|
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;
|
2023-01-19 20:36:30 +01:00
|
|
|
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;
|
|
|
|
}
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert new map element
|
2021-04-09 03:09:08 +02:00
|
|
|
auto* insertedElement = newTileElement;
|
2023-01-21 16:37:11 +01:00
|
|
|
newTileElement->Type = 0;
|
2021-12-11 00:39:39 +01:00
|
|
|
newTileElement->SetType(type);
|
2020-03-07 21:28:36 +01:00
|
|
|
newTileElement->SetBaseZ(loc.z);
|
2020-03-04 21:47:34 +01:00
|
|
|
newTileElement->Flags = 0;
|
2019-08-27 21:12:48 +02:00
|
|
|
newTileElement->SetLastForTile(isLastForTile);
|
|
|
|
newTileElement->SetOccupiedQuadrants(occupiedQuadrants);
|
2020-03-07 21:28:36 +01:00
|
|
|
newTileElement->SetClearanceZ(loc.z);
|
2023-01-19 20:36:30 +01:00
|
|
|
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++;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
|
|
|
// Insert rest of map elements above insert height
|
2019-08-27 21:12:48 +02:00
|
|
|
if (!isLastForTile)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
// Copy over map element
|
2017-10-31 14:03:45 +01:00
|
|
|
*newTileElement = *originalTileElement;
|
2023-01-19 20:36:30 +01:00
|
|
|
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()));
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return insertedElement;
|
2015-01-15 00:38:48 +01:00
|
|
|
}
|
|
|
|
|
2015-03-22 18:04:30 +01:00
|
|
|
/**
|
|
|
|
* Updates grass length, scenery age and jumping fountains.
|
|
|
|
*
|
|
|
|
* rct2: 0x006646E1
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
void MapUpdateTiles()
|
2015-03-22 18:04:30 +01:00
|
|
|
{
|
2022-01-18 19:21:20 +01:00
|
|
|
PROFILED_FUNCTION();
|
|
|
|
|
2018-06-20 17:28:51 +02:00
|
|
|
int32_t ignoreScreenFlags = SCREEN_FLAGS_SCENARIO_EDITOR | SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER;
|
2017-06-06 23:24:18 +02:00
|
|
|
if (gScreenFlags & ignoreScreenFlags)
|
|
|
|
return;
|
|
|
|
|
2024-02-12 22:32:08 +01:00
|
|
|
auto& gameState = GetGameState();
|
|
|
|
|
2021-04-08 22:05:34 +02:00
|
|
|
// Update 43 more tiles (for each 256x256 block)
|
2018-06-22 23:17:03 +02:00
|
|
|
for (int32_t j = 0; j < 43; j++)
|
|
|
|
{
|
2018-06-20 17:28:51 +02:00
|
|
|
int32_t x = 0;
|
|
|
|
int32_t y = 0;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2024-03-21 22:50:18 +01:00
|
|
|
uint16_t interleaved_xy = gameState.GrassSceneryTileLoopPosition;
|
2018-06-22 23:17:03 +02:00
|
|
|
for (int32_t i = 0; i < 8; i++)
|
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
x = (x << 1) | (interleaved_xy & 1);
|
|
|
|
interleaved_xy >>= 1;
|
|
|
|
y = (y << 1) | (interleaved_xy & 1);
|
|
|
|
interleaved_xy >>= 1;
|
|
|
|
}
|
|
|
|
|
2021-04-08 22:05:34 +02:00
|
|
|
// 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)
|
2021-04-08 22:05:34 +02:00
|
|
|
{
|
|
|
|
auto mapPos = TileCoordsXY{ blockX + x, blockY + y }.ToCoordsXY();
|
2023-05-06 01:31:46 +02:00
|
|
|
if (MapIsEdge(mapPos))
|
|
|
|
continue;
|
|
|
|
|
2022-10-11 20:39:24 +02:00
|
|
|
auto* surfaceElement = MapGetSurfaceElementAt(mapPos);
|
2021-04-08 22:05:34 +02:00
|
|
|
if (surfaceElement != nullptr)
|
|
|
|
{
|
|
|
|
surfaceElement->UpdateGrassLength(mapPos);
|
2022-10-08 11:56:17 +02:00
|
|
|
SceneryUpdateTile(mapPos);
|
2021-04-08 22:05:34 +02:00
|
|
|
}
|
|
|
|
}
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
|
2024-03-21 22:50:18 +01:00
|
|
|
gameState.GrassSceneryTileLoopPosition++;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2015-03-22 18:04:30 +01:00
|
|
|
}
|
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
void MapRemoveProvisionalElements()
|
2016-09-20 22:22:55 +02:00
|
|
|
{
|
2022-01-18 19:21:20 +01:00
|
|
|
PROFILED_FUNCTION();
|
|
|
|
|
2021-04-14 14:56:28 +02:00
|
|
|
if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1)
|
2017-06-06 23:24:18 +02:00
|
|
|
{
|
2022-10-04 08:51:27 +02:00
|
|
|
FootpathProvisionalRemove();
|
2021-04-14 14:56:28 +02:00
|
|
|
gProvisionalFootpath.Flags |= PROVISIONAL_PATH_FLAG_1;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2023-01-16 21:13:42 +01:00
|
|
|
if (WindowFindByClass(WindowClass::RideConstruction) != nullptr)
|
2017-06-06 23:24:18 +02:00
|
|
|
{
|
2023-01-17 01:32:54 +01:00
|
|
|
RideRemoveProvisionalTrackPiece();
|
2022-10-04 08:38:00 +02:00
|
|
|
RideEntranceExitRemoveGhost();
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2019-12-11 17:13:40 +01:00
|
|
|
// 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.
|
2023-01-18 07:05:53 +01:00
|
|
|
if ((NetworkGetMode() != NETWORK_MODE_NONE) && WindowFindByClass(WindowClass::TrackDesignPlace) != nullptr)
|
2019-12-11 17:13:40 +01:00
|
|
|
{
|
|
|
|
auto intent = Intent(INTENT_ACTION_TRACK_DESIGN_REMOVE_PROVISIONAL);
|
2022-11-06 21:49:07 +01:00
|
|
|
ContextBroadcastIntent(&intent);
|
2019-12-11 17:13:40 +01:00
|
|
|
}
|
2016-09-20 22:22:55 +02:00
|
|
|
}
|
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
void MapRestoreProvisionalElements()
|
2016-09-20 22:22:55 +02:00
|
|
|
{
|
2022-01-18 19:21:20 +01:00
|
|
|
PROFILED_FUNCTION();
|
|
|
|
|
2021-04-14 14:56:28 +02:00
|
|
|
if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1)
|
2017-06-06 23:24:18 +02:00
|
|
|
{
|
2021-04-14 14:56:28 +02:00
|
|
|
gProvisionalFootpath.Flags &= ~PROVISIONAL_PATH_FLAG_1;
|
2022-10-04 08:51:27 +02:00
|
|
|
FootpathProvisionalSet(
|
2021-09-03 19:19:42 +02:00
|
|
|
gProvisionalFootpath.SurfaceIndex, gProvisionalFootpath.RailingsIndex, gProvisionalFootpath.Position,
|
|
|
|
gProvisionalFootpath.Slope, gProvisionalFootpath.ConstructFlags);
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2023-01-16 21:13:42 +01:00
|
|
|
if (WindowFindByClass(WindowClass::RideConstruction) != nullptr)
|
2017-06-06 23:24:18 +02:00
|
|
|
{
|
2023-01-17 01:32:54 +01:00
|
|
|
RideRestoreProvisionalTrackPiece();
|
2022-10-04 08:38:00 +02:00
|
|
|
RideEntranceExitPlaceProvisionalGhost();
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2019-12-11 17:13:40 +01:00
|
|
|
// 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.
|
2023-01-18 07:05:53 +01:00
|
|
|
if ((NetworkGetMode() != NETWORK_MODE_NONE) && WindowFindByClass(WindowClass::TrackDesignPlace) != nullptr)
|
2019-12-11 17:13:40 +01:00
|
|
|
{
|
|
|
|
auto intent = Intent(INTENT_ACTION_TRACK_DESIGN_RESTORE_PROVISIONAL);
|
2022-11-06 21:49:07 +01:00
|
|
|
ContextBroadcastIntent(&intent);
|
2019-12-11 17:13:40 +01:00
|
|
|
}
|
2016-09-20 22:22:55 +02:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
void MapRemoveOutOfRangeElements()
|
2015-06-20 17:24:38 +02:00
|
|
|
{
|
2021-12-17 19:25:46 +01: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;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2021-12-17 19:25:46 +01:00
|
|
|
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
|
|
|
{
|
2021-12-17 19:25:46 +01: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
|
|
|
{
|
2021-12-17 19:25:46 +01: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.
|
2022-10-11 20:39:24 +02:00
|
|
|
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);
|
2024-03-26 21:35:55 +01:00
|
|
|
Park::UpdateFencesAroundTile({ x, y });
|
2019-03-17 09:25:51 +01:00
|
|
|
}
|
2022-10-12 14:14:45 +02:00
|
|
|
ClearElementsAt({ x, y });
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
static void MapExtendBoundarySurfaceExtendTile(const SurfaceElement& sourceTile, SurfaceElement& destTile)
|
2019-08-17 09:00:03 +02:00
|
|
|
{
|
2023-08-26 19:50:06 +02:00
|
|
|
destTile.SetSurfaceObjectIndex(sourceTile.GetSurfaceObjectIndex());
|
2023-08-26 20:01:27 +02:00
|
|
|
destTile.SetEdgeObjectIndex(sourceTile.GetEdgeObjectIndex());
|
2019-08-17 09:00:03 +02:00
|
|
|
destTile.SetGrassLength(sourceTile.GetGrassLength());
|
|
|
|
destTile.SetOwnership(OWNERSHIP_UNOWNED);
|
|
|
|
destTile.SetWaterHeight(sourceTile.GetWaterHeight());
|
|
|
|
|
2023-01-19 20:36:30 +01:00
|
|
|
auto z = sourceTile.BaseHeight;
|
2019-08-17 09:00:03 +02:00
|
|
|
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);
|
2023-01-19 20:36:30 +01:00
|
|
|
destTile.BaseHeight = z;
|
|
|
|
destTile.ClearanceHeight = z;
|
2019-08-17 09:00:03 +02:00
|
|
|
}
|
|
|
|
|
2015-07-02 01:37:55 +02:00
|
|
|
/**
|
2021-12-17 19:25:46 +01: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
|
|
|
*/
|
2022-10-12 14:14:45 +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;
|
2024-03-17 14:41:36 +01:00
|
|
|
for (auto x = 0; x < kMaximumMapSizeTechnical; x++)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2023-04-05 22:13:47 +02:00
|
|
|
auto existingTileElement = MapGetSurfaceElementAt(TileCoordsXY{ x, y - 1 });
|
|
|
|
auto newTileElement = MapGetSurfaceElementAt(TileCoordsXY{ x, y });
|
2019-08-17 09:00:03 +02:00
|
|
|
|
2021-09-24 20:05:50 +02:00
|
|
|
if (existingTileElement != nullptr && newTileElement != nullptr)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2022-10-12 14:14:45 +02:00
|
|
|
MapExtendBoundarySurfaceExtendTile(*existingTileElement, *newTileElement);
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
|
2024-03-26 21:35:55 +01:00
|
|
|
Park::UpdateFences({ x << 5, y << 5 });
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2021-12-17 19:25:46 +01:00
|
|
|
}
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2021-12-17 19:25:46 +01:00
|
|
|
/**
|
|
|
|
* Copies the terrain and slope from the X edge of the map to the new tiles. Used when increasing the size of the map.
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
void MapExtendBoundarySurfaceX()
|
2021-12-17 19:25:46 +01:00
|
|
|
{
|
2024-02-12 22:32:08 +01:00
|
|
|
auto x = GetGameState().MapSize.x - 2;
|
2024-03-17 14:41:36 +01:00
|
|
|
for (auto y = 0; y < kMaximumMapSizeTechnical; y++)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2023-04-05 22:13:47 +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
|
|
|
{
|
2022-10-12 14:14:45 +02:00
|
|
|
MapExtendBoundarySurfaceExtendTile(*existingTileElement, *newTileElement);
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2024-03-26 21:35:55 +01:00
|
|
|
Park::UpdateFences({ x << 5, y << 5 });
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
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
|
|
|
*/
|
2022-10-12 14:14:45 +02: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())
|
2018-06-02 15:34:01 +02:00
|
|
|
{
|
2021-12-11 00:39:39 +01:00
|
|
|
case TileElementType::Surface:
|
2024-03-01 21:23:29 +01:00
|
|
|
element->BaseHeight = kMinimumLandHeight;
|
|
|
|
element->ClearanceHeight = kMinimumLandHeight;
|
2023-01-19 20:36:30 +01:00
|
|
|
element->Owner = 0;
|
2018-09-14 14:54:12 +02:00
|
|
|
element->AsSurface()->SetSlope(TILE_ELEMENT_SLOPE_FLAT);
|
2023-08-26 19:50:06 +02:00
|
|
|
element->AsSurface()->SetSurfaceObjectIndex(0);
|
2023-08-26 20:01:27 +02:00
|
|
|
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)++;
|
2018-06-02 15:34:01 +02:00
|
|
|
break;
|
2021-12-11 00:39:39 +01:00
|
|
|
case TileElementType::Entrance:
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2018-09-14 11:14:19 +02:00
|
|
|
int32_t rotation = element->GetDirectionWithOffset(1);
|
2019-08-18 12:18:39 +02:00
|
|
|
auto seqLoc = loc;
|
2018-09-26 12:54:44 +02:00
|
|
|
switch (element->AsEntrance()->GetSequenceIndex())
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
|
|
|
case 1:
|
2019-08-18 12:18:39 +02:00
|
|
|
seqLoc += CoordsDirectionDelta[rotation];
|
2018-06-22 23:17:03 +02:00
|
|
|
break;
|
|
|
|
case 2:
|
2019-08-18 12:18:39 +02:00
|
|
|
seqLoc -= CoordsDirectionDelta[rotation];
|
2018-06-22 23:17:03 +02:00
|
|
|
break;
|
|
|
|
}
|
2019-12-22 22:14:02 +01:00
|
|
|
auto parkEntranceRemoveAction = ParkEntranceRemoveAction(CoordsXYZ{ seqLoc, element->GetBaseZ() });
|
2021-09-29 17:46:03 +02:00
|
|
|
auto result = GameActions::ExecuteNested(&parkEntranceRemoveAction);
|
2021-01-12 22:04:22 +01:00
|
|
|
// If asking nicely did not work, forcibly remove this to avoid an infinite loop.
|
2021-11-24 08:35:08 +01:00
|
|
|
if (result.Error != GameActions::Status::Ok)
|
2021-01-12 22:04:22 +01:00
|
|
|
{
|
2022-10-12 07:35:20 +02:00
|
|
|
TileElementRemove(element);
|
2021-01-12 22:04:22 +01:00
|
|
|
}
|
2018-06-02 15:34:01 +02:00
|
|
|
break;
|
|
|
|
}
|
2021-12-11 00:39:39 +01:00
|
|
|
case TileElementType::Wall:
|
2018-04-22 19:11:06 +02:00
|
|
|
{
|
2019-12-22 22:14:02 +01:00
|
|
|
CoordsXYZD wallLocation = { loc.x, loc.y, element->GetBaseZ(), element->GetDirection() };
|
2018-05-14 12:36:45 +02:00
|
|
|
auto wallRemoveAction = WallRemoveAction(wallLocation);
|
2021-09-29 17:46:03 +02:00
|
|
|
auto result = GameActions::ExecuteNested(&wallRemoveAction);
|
2021-01-12 22:04:22 +01:00
|
|
|
// If asking nicely did not work, forcibly remove this to avoid an infinite loop.
|
2021-11-24 08:35:08 +01:00
|
|
|
if (result.Error != GameActions::Status::Ok)
|
2021-01-12 22:04:22 +01:00
|
|
|
{
|
2022-10-12 07:35:20 +02:00
|
|
|
TileElementRemove(element);
|
2021-01-12 22:04:22 +01:00
|
|
|
}
|
2018-04-22 19:11:06 +02:00
|
|
|
}
|
2017-06-06 23:24:18 +02:00
|
|
|
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(
|
2019-12-22 22:14:02 +01:00
|
|
|
{ loc.x, loc.y, element->GetBaseZ(), element->GetDirection() }, element->AsLargeScenery()->GetSequenceIndex());
|
2021-09-29 17:46:03 +02:00
|
|
|
auto result = GameActions::ExecuteNested(&removeSceneryAction);
|
2021-01-12 22:04:22 +01:00
|
|
|
// If asking nicely did not work, forcibly remove this to avoid an infinite loop.
|
2021-11-24 08:35:08 +01:00
|
|
|
if (result.Error != GameActions::Status::Ok)
|
2021-01-12 22:04:22 +01:00
|
|
|
{
|
2022-10-12 07:35:20 +02:00
|
|
|
TileElementRemove(element);
|
2021-01-12 22:04:22 +01:00
|
|
|
}
|
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(
|
2019-12-22 22:14:02 +01:00
|
|
|
{ loc.x, loc.y, element->GetBaseZ(), element->AsBanner()->GetPosition() });
|
2021-09-29 17:46:03 +02:00
|
|
|
auto result = GameActions::ExecuteNested(&bannerRemoveAction);
|
2021-01-12 22:04:22 +01:00
|
|
|
// If asking nicely did not work, forcibly remove this to avoid an infinite loop.
|
2021-11-24 08:35:08 +01:00
|
|
|
if (result.Error != GameActions::Status::Ok)
|
2021-01-12 22:04:22 +01:00
|
|
|
{
|
2022-10-12 07:35:20 +02:00
|
|
|
TileElementRemove(element);
|
2021-01-12 22:04:22 +01:00
|
|
|
}
|
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:
|
2022-10-12 07:35:20 +02:00
|
|
|
TileElementRemove(element);
|
2018-06-22 23:17:03 +02:00
|
|
|
break;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2016-07-13 01:21:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clears all elements properly from a certain tile.
|
|
|
|
* rct2: 0x0068AE2A
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
static void ClearElementsAt(const CoordsXY& loc)
|
2016-07-13 01:21:03 +02:00
|
|
|
{
|
2024-03-10 19:16:28 +01:00
|
|
|
auto& gameState = GetGameState();
|
2017-06-06 23:24:18 +02:00
|
|
|
// Remove the spawn point (if there is one in the current tile)
|
2024-03-10 19:16:28 +01:00
|
|
|
gameState.PeepSpawns.erase(
|
2018-12-15 19:23:40 +01:00
|
|
|
std::remove_if(
|
2024-03-10 19:16:28 +01:00
|
|
|
gameState.PeepSpawns.begin(), gameState.PeepSpawns.end(),
|
2020-05-09 16:17:08 +02:00
|
|
|
[loc](const CoordsXY& spawn) { return spawn.ToTileStart() == loc.ToTileStart(); }),
|
2024-03-10 19:16:28 +01:00
|
|
|
gameState.PeepSpawns.end());
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(loc);
|
2019-10-09 16:02:21 +02:00
|
|
|
if (tileElement == nullptr)
|
|
|
|
return;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
|
|
|
// Remove all elements except the last one
|
2018-06-22 23:17:03 +02:00
|
|
|
while (!tileElement->IsLastForTile())
|
2022-10-12 14:14:45 +02:00
|
|
|
ClearElementAt(loc, &tileElement);
|
2017-06-06 23:24:18 +02:00
|
|
|
|
|
|
|
// Remove the last element
|
2022-10-12 14:14:45 +02:00
|
|
|
ClearElementAt(loc, &tileElement);
|
2015-06-21 13:40:44 +02:00
|
|
|
}
|
2015-06-27 16:17:54 +02:00
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
int32_t MapGetHighestZ(const CoordsXY& loc)
|
2015-06-27 16:17:54 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
auto surfaceElement = MapGetSurfaceElementAt(loc);
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement == nullptr)
|
2017-06-06 23:24:18 +02:00
|
|
|
return -1;
|
2015-06-27 16:17:54 +02:00
|
|
|
|
2020-01-19 16:12:48 +01:00
|
|
|
auto z = surfaceElement->GetBaseZ();
|
2015-06-27 16:17:54 +02:00
|
|
|
|
2017-06-06 23:24:18 +02:00
|
|
|
// Raise z so that is above highest point of land and water on tile
|
2019-08-11 10:06:04 +02:00
|
|
|
if ((surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) != TILE_ELEMENT_SLOPE_FLAT)
|
2020-01-19 16:12:48 +01:00
|
|
|
z += LAND_HEIGHT_STEP;
|
2019-08-11 10:06:04 +02:00
|
|
|
if ((surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) != 0)
|
2020-01-19 16:12:48 +01:00
|
|
|
z += LAND_HEIGHT_STEP;
|
2015-06-27 16:17:54 +02:00
|
|
|
|
2020-01-19 16:12:48 +01:00
|
|
|
z = std::max(z, surfaceElement->GetWaterHeight());
|
2017-06-06 23:24:18 +02:00
|
|
|
return z;
|
2015-06-27 16:17:54 +02:00
|
|
|
}
|
2015-06-28 18:45:19 +02:00
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
LargeSceneryElement* MapGetLargeScenerySegment(const CoordsXYZD& sceneryPos, int32_t sequence)
|
2015-07-06 18:59:37 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(sceneryPos);
|
2018-01-04 06:58:44 +01:00
|
|
|
if (tileElement == nullptr)
|
2017-06-06 23:24:18 +02:00
|
|
|
{
|
2018-01-04 06:58:44 +01:00
|
|
|
return nullptr;
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
2019-12-23 13:53:21 +01:00
|
|
|
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)
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
2023-01-19 20:36:30 +01:00
|
|
|
if (tileElement->BaseHeight != sceneryTilePos.z)
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
2018-09-14 12:12:22 +02:00
|
|
|
if (tileElement->AsLargeScenery()->GetSequenceIndex() != sequence)
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
2019-12-23 13:53:21 +01:00
|
|
|
if ((tileElement->GetDirection()) != sceneryPos.direction)
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
|
|
|
|
2019-03-16 17:23:26 +01:00
|
|
|
return tileElement->AsLargeScenery();
|
2018-05-24 11:44:53 +02:00
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
2018-01-04 06:58:44 +01:00
|
|
|
return nullptr;
|
2015-07-06 18:59:37 +02:00
|
|
|
}
|
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
EntranceElement* MapGetParkEntranceElementAt(const CoordsXYZ& entranceCoords, bool ghost)
|
2016-09-26 20:35:56 +02:00
|
|
|
{
|
2019-12-14 16:12:43 +01:00
|
|
|
auto entranceTileCoords = TileCoordsXYZ(entranceCoords);
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(entranceCoords);
|
2018-01-04 06:58:44 +01:00
|
|
|
if (tileElement != nullptr)
|
2017-11-04 19:04:16 +01:00
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
2021-12-11 00:39:39 +01:00
|
|
|
if (tileElement->GetType() != TileElementType::Entrance)
|
2017-11-04 19:04:16 +01:00
|
|
|
continue;
|
2016-09-26 20:35:56 +02:00
|
|
|
|
2023-01-19 20:36:30 +01:00
|
|
|
if (tileElement->BaseHeight != entranceTileCoords.z)
|
2017-11-04 19:04:16 +01:00
|
|
|
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)
|
2017-11-04 19:04:16 +01:00
|
|
|
continue;
|
2016-09-26 20:35:56 +02:00
|
|
|
|
2019-05-10 22:00:38 +02:00
|
|
|
if (!ghost && tileElement->IsGhost())
|
2017-11-04 19:04:16 +01:00
|
|
|
continue;
|
2016-11-13 20:17:49 +01:00
|
|
|
|
2018-09-26 13:20:47 +02:00
|
|
|
return tileElement->AsEntrance();
|
2018-06-22 23:17:03 +02:00
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
2017-11-04 19:04:16 +01:00
|
|
|
}
|
2018-01-04 06:58:44 +01:00
|
|
|
return nullptr;
|
2016-09-26 20:35:56 +02:00
|
|
|
}
|
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
EntranceElement* MapGetRideEntranceElementAt(const CoordsXYZ& entranceCoords, bool ghost)
|
2017-10-25 10:25:55 +02:00
|
|
|
{
|
2019-12-18 03:55:46 +01:00
|
|
|
auto entranceTileCoords = TileCoordsXYZ{ entranceCoords };
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(entranceCoords);
|
2018-01-04 06:58:44 +01:00
|
|
|
if (tileElement != nullptr)
|
2017-10-25 10:25:55 +02:00
|
|
|
{
|
2017-11-04 19:06:59 +01:00
|
|
|
do
|
|
|
|
{
|
2021-12-11 00:39:39 +01:00
|
|
|
if (tileElement->GetType() != TileElementType::Entrance)
|
2017-11-04 19:06:59 +01:00
|
|
|
continue;
|
2017-10-25 10:25:55 +02:00
|
|
|
|
2023-01-19 20:36:30 +01:00
|
|
|
if (tileElement->BaseHeight != entranceTileCoords.z)
|
2017-11-04 19:06:59 +01:00
|
|
|
continue;
|
2017-10-25 10:25:55 +02:00
|
|
|
|
2018-09-26 12:13:44 +02:00
|
|
|
if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_RIDE_ENTRANCE)
|
2017-11-04 19:06:59 +01:00
|
|
|
continue;
|
2017-10-25 10:25:55 +02:00
|
|
|
|
2019-05-10 22:00:38 +02:00
|
|
|
if (!ghost && tileElement->IsGhost())
|
2017-11-04 19:06:59 +01:00
|
|
|
continue;
|
2017-10-25 10:25:55 +02:00
|
|
|
|
2018-09-26 13:20:47 +02:00
|
|
|
return tileElement->AsEntrance();
|
2018-06-22 23:17:03 +02:00
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
2017-10-25 10:25:55 +02:00
|
|
|
}
|
2018-01-04 06:58:44 +01:00
|
|
|
return nullptr;
|
2017-10-25 10:25:55 +02:00
|
|
|
}
|
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
EntranceElement* MapGetRideExitElementAt(const CoordsXYZ& exitCoords, bool ghost)
|
2017-10-25 15:05:10 +02:00
|
|
|
{
|
2019-12-18 03:55:46 +01:00
|
|
|
auto exitTileCoords = TileCoordsXYZ{ exitCoords };
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(exitCoords);
|
2018-01-04 06:58:44 +01:00
|
|
|
if (tileElement != nullptr)
|
2017-10-25 15:05:10 +02:00
|
|
|
{
|
2017-11-04 19:06:59 +01:00
|
|
|
do
|
|
|
|
{
|
2021-12-11 00:39:39 +01:00
|
|
|
if (tileElement->GetType() != TileElementType::Entrance)
|
2017-11-04 19:06:59 +01:00
|
|
|
continue;
|
2017-10-25 15:05:10 +02:00
|
|
|
|
2023-01-19 20:36:30 +01:00
|
|
|
if (tileElement->BaseHeight != exitTileCoords.z)
|
2017-11-04 19:06:59 +01:00
|
|
|
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)
|
2017-11-04 19:06:59 +01:00
|
|
|
continue;
|
2017-10-25 15:05:10 +02:00
|
|
|
|
2019-05-10 22:00:38 +02:00
|
|
|
if (!ghost && tileElement->IsGhost())
|
2017-11-04 19:06:59 +01:00
|
|
|
continue;
|
2017-10-25 15:05:10 +02:00
|
|
|
|
2018-09-26 13:20:47 +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
|
|
|
}
|
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
SmallSceneryElement* MapGetSmallSceneryElementAt(const CoordsXYZ& sceneryCoords, int32_t type, uint8_t quadrant)
|
2015-11-12 20:22:08 +01:00
|
|
|
{
|
2019-12-21 14:08:18 +01:00
|
|
|
auto sceneryTileCoords = TileCoordsXYZ{ sceneryCoords };
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(sceneryCoords);
|
2018-01-04 06:58:44 +01:00
|
|
|
if (tileElement != nullptr)
|
2017-11-04 19:06:59 +01:00
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
2021-12-11 00:39:39 +01:00
|
|
|
if (tileElement->GetType() != TileElementType::SmallScenery)
|
2017-11-04 19:06:59 +01:00
|
|
|
continue;
|
2018-10-03 11:39:33 +02:00
|
|
|
if (tileElement->AsSmallScenery()->GetSceneryQuadrant() != quadrant)
|
2017-11-04 19:06:59 +01:00
|
|
|
continue;
|
2023-01-19 20:36:30 +01:00
|
|
|
if (tileElement->BaseHeight != sceneryTileCoords.z)
|
2017-11-04 19:06:59 +01:00
|
|
|
continue;
|
2018-09-13 17:02:38 +02:00
|
|
|
if (tileElement->AsSmallScenery()->GetEntryIndex() != type)
|
2017-11-04 19:06:59 +01:00
|
|
|
continue;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2019-03-16 17:29:02 +01:00
|
|
|
return tileElement->AsSmallScenery();
|
2018-06-22 23:17:03 +02:00
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
2017-11-04 19:06:59 +01:00
|
|
|
}
|
2018-01-04 06:58:44 +01:00
|
|
|
return nullptr;
|
2015-11-12 20:22:08 +01:00
|
|
|
}
|
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
std::optional<CoordsXYZ> MapLargeSceneryGetOrigin(
|
2019-12-23 14:07:20 +01:00
|
|
|
const CoordsXYZD& sceneryPos, int32_t sequence, LargeSceneryElement** outElement)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2023-01-20 21:58:55 +01:00
|
|
|
LargeSceneryTile* tile;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
auto tileElement = MapGetLargeScenerySegment(sceneryPos, sequence);
|
2018-01-04 06:58:44 +01:00
|
|
|
if (tileElement == nullptr)
|
2019-12-23 14:07:20 +01:00
|
|
|
return std::nullopt;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2021-06-04 13:41:23 +02:00
|
|
|
auto* sceneryEntry = tileElement->GetEntry();
|
|
|
|
tile = &sceneryEntry->tiles[sequence];
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2019-12-05 20:38:14 +01:00
|
|
|
CoordsXY offsetPos{ tile->x_offset, tile->y_offset };
|
2019-12-23 14:07:20 +01:00
|
|
|
auto rotatedOffsetPos = offsetPos.Rotate(sceneryPos.direction);
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2019-12-23 14:07:20 +01:00
|
|
|
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;
|
2019-12-23 14:07:20 +01:00
|
|
|
return origin;
|
2015-07-06 18:59:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-10-20 20:16:30 +02:00
|
|
|
*
|
2015-07-06 18:59:37 +02:00
|
|
|
* rct2: 0x006B9B05
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
bool MapLargeScenerySignSetColour(const CoordsXYZD& signPos, int32_t sequence, uint8_t mainColour, uint8_t textColour)
|
2015-07-06 18:59:37 +02:00
|
|
|
{
|
2019-03-16 17:23:26 +01:00
|
|
|
LargeSceneryElement* tileElement;
|
2023-01-20 21:58:55 +01:00
|
|
|
LargeSceneryTile *sceneryTiles, *tile;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
auto sceneryOrigin = MapLargeSceneryGetOrigin(signPos, sequence, &tileElement);
|
2019-12-23 14:07:20 +01:00
|
|
|
if (!sceneryOrigin)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-06-04 13:41:23 +02:00
|
|
|
auto* sceneryEntry = tileElement->GetEntry();
|
|
|
|
sceneryTiles = sceneryEntry->tiles;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
|
|
|
// 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++)
|
|
|
|
{
|
2019-12-05 20:38:14 +01:00
|
|
|
CoordsXY offsetPos{ tile->x_offset, tile->y_offset };
|
2019-12-26 16:18:51 +01:00
|
|
|
auto rotatedOffsetPos = offsetPos.Rotate(signPos.direction);
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2019-12-26 16:18:51 +01:00
|
|
|
auto tmpSignPos = CoordsXYZD{ sceneryOrigin->x + rotatedOffsetPos.x, sceneryOrigin->y + rotatedOffsetPos.y,
|
|
|
|
sceneryOrigin->z + tile->z_offset, signPos.direction };
|
2022-10-12 14:14:45 +02:00
|
|
|
tileElement = MapGetLargeScenerySegment(tmpSignPos, sequence);
|
2018-01-04 06:58:44 +01:00
|
|
|
if (tileElement != nullptr)
|
2017-11-17 17:15:55 +01:00
|
|
|
{
|
2019-03-16 17:23:26 +01:00
|
|
|
tileElement->SetPrimaryColour(mainColour);
|
|
|
|
tileElement->SetSecondaryColour(textColour);
|
2017-10-31 14:03:45 +01:00
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
MapInvalidateTile({ tmpSignPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() });
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2015-07-06 18:59:37 +02:00
|
|
|
}
|
2015-07-06 19:57:36 +02:00
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
static void MapInvalidateTileUnderZoom(int32_t x, int32_t y, int32_t z0, int32_t z1, ZoomLevel maxZoom)
|
2015-07-06 19:57:36 +02:00
|
|
|
{
|
2018-06-22 23:17:03 +02:00
|
|
|
if (gOpenRCT2Headless)
|
|
|
|
return;
|
2016-04-14 23:53:00 +02:00
|
|
|
|
2024-02-18 20:11:31 +01:00
|
|
|
ViewportsInvalidate(x, y, z0, z1, maxZoom);
|
2015-07-06 19:57:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006EC847
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
void MapInvalidateTile(const CoordsXYRangedZ& tilePos)
|
2015-07-06 19:57:36 +02:00
|
|
|
{
|
2022-10-12 14:14:45 +02:00
|
|
|
MapInvalidateTileUnderZoom(tilePos.x, tilePos.y, tilePos.baseZ, tilePos.clearanceZ, ZoomLevel{ -1 });
|
2015-07-06 19:57:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006ECB60
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
void MapInvalidateTileZoom1(const CoordsXYRangedZ& tilePos)
|
2015-07-06 19:57:36 +02:00
|
|
|
{
|
2022-10-12 14:14:45 +02:00
|
|
|
MapInvalidateTileUnderZoom(tilePos.x, tilePos.y, tilePos.baseZ, tilePos.clearanceZ, ZoomLevel{ 1 });
|
2015-07-06 19:57:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006EC9CE
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
void MapInvalidateTileZoom0(const CoordsXYRangedZ& tilePos)
|
2015-07-06 19:57:36 +02:00
|
|
|
{
|
2022-10-12 14:14:45 +02:00
|
|
|
MapInvalidateTileUnderZoom(tilePos.x, tilePos.y, tilePos.baseZ, tilePos.clearanceZ, ZoomLevel{ 0 });
|
2015-07-06 19:57:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x006EC6D7
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
void MapInvalidateTileFull(const CoordsXY& tilePos)
|
2015-07-06 19:57:36 +02:00
|
|
|
{
|
2022-10-12 14:14:45 +02:00
|
|
|
MapInvalidateTile({ tilePos, 0, 2080 });
|
2015-07-06 19:57:36 +02:00
|
|
|
}
|
2015-07-07 20:09:12 +02:00
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
void MapInvalidateElement(const CoordsXY& elementPos, TileElement* tileElement)
|
2015-07-07 20:09:12 +02:00
|
|
|
{
|
2022-10-12 14:14:45 +02:00
|
|
|
MapInvalidateTile({ elementPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() });
|
2015-07-07 20:09:12 +02:00
|
|
|
}
|
2015-07-11 19:23:59 +02:00
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
void MapInvalidateRegion(const CoordsXY& mins, const CoordsXY& maxs)
|
2018-03-04 19:02:07 +01:00
|
|
|
{
|
2018-06-20 17:28:51 +02:00
|
|
|
int32_t x0, y0, x1, y1, left, right, top, bottom;
|
2018-03-04 19:02:07 +01:00
|
|
|
|
|
|
|
x0 = mins.x + 16;
|
|
|
|
y0 = mins.y + 16;
|
|
|
|
|
|
|
|
x1 = maxs.x + 16;
|
|
|
|
y1 = maxs.y + 16;
|
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
MapGetBoundingBox({ x0, y0, x1, y1 }, &left, &top, &right, &bottom);
|
2018-03-04 19:02:07 +01:00
|
|
|
|
|
|
|
left -= 32;
|
|
|
|
right += 32;
|
|
|
|
bottom += 32;
|
|
|
|
top -= 32 + 2080;
|
|
|
|
|
2023-01-16 21:14:50 +01:00
|
|
|
ViewportsInvalidate({ { left, top }, { right, bottom } });
|
2018-03-04 19:02:07 +01:00
|
|
|
}
|
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
int32_t MapGetTileSide(const CoordsXY& mapPos)
|
2015-07-11 19:23:59 +02:00
|
|
|
{
|
2019-12-27 12:36:47 +01:00
|
|
|
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);
|
2015-07-11 19:23:59 +02:00
|
|
|
}
|
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
int32_t MapGetTileQuadrant(const CoordsXY& mapPos)
|
2015-07-11 19:23:59 +02:00
|
|
|
{
|
2019-12-27 12:36:47 +01:00
|
|
|
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);
|
2015-07-11 19:23:59 +02:00
|
|
|
}
|
|
|
|
|
2015-12-11 16:38:37 +01:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x00693BFF
|
|
|
|
*/
|
2022-10-12 07:35:20 +02:00
|
|
|
bool MapSurfaceIsBlocked(const CoordsXY& mapCoords)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2022-10-12 07:35:20 +02:00
|
|
|
if (!MapIsLocationValid(mapCoords))
|
2017-06-06 23:24:18 +02:00
|
|
|
return true;
|
2015-07-25 12:16:59 +02:00
|
|
|
|
2022-10-11 20:39:24 +02:00
|
|
|
auto surfaceElement = MapGetSurfaceElementAt(mapCoords);
|
2015-07-25 12:16:59 +02:00
|
|
|
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement == nullptr)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2017-06-06 23:24:18 +02:00
|
|
|
return true;
|
|
|
|
}
|
2016-12-26 15:53:11 +01:00
|
|
|
|
2020-01-19 16:12:48 +01:00
|
|
|
if (surfaceElement->GetWaterHeight() > surfaceElement->GetBaseZ())
|
2017-06-06 23:24:18 +02:00
|
|
|
return true;
|
2015-07-25 12:16:59 +02:00
|
|
|
|
2023-01-19 20:36:30 +01:00
|
|
|
int16_t base_z = surfaceElement->BaseHeight;
|
|
|
|
int16_t clear_z = surfaceElement->BaseHeight + 2;
|
2019-08-11 10:06:04 +02:00
|
|
|
if (surfaceElement->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
|
2017-06-06 23:24:18 +02:00
|
|
|
clear_z += 2;
|
2015-07-25 12:16:59 +02:00
|
|
|
|
2019-08-11 10:06:04 +02:00
|
|
|
auto tileElement = reinterpret_cast<TileElement*>(surfaceElement);
|
2018-06-22 23:17:03 +02:00
|
|
|
while (!(tileElement++)->IsLastForTile())
|
|
|
|
{
|
2023-01-19 20:36:30 +01:00
|
|
|
if (clear_z >= tileElement->ClearanceHeight)
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
2015-07-25 12:16:59 +02:00
|
|
|
|
2023-01-19 20:36:30 +01:00
|
|
|
if (base_z < tileElement->BaseHeight)
|
2017-06-06 23:24:18 +02:00
|
|
|
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)
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
2015-07-25 12:16:59 +02:00
|
|
|
|
2021-12-11 00:39:39 +01:00
|
|
|
if (tileElement->GetType() != TileElementType::SmallScenery)
|
2017-06-06 23:24:18 +02:00
|
|
|
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)
|
2017-08-22 10:20:59 +02:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2021-10-01 21:50:05 +02:00
|
|
|
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE))
|
2017-06-06 23:24:18 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2015-07-25 12:16:59 +02:00
|
|
|
}
|
|
|
|
|
2015-08-31 12:42:46 +02:00
|
|
|
/* Clears all map elements, to be used before generating a new map */
|
2022-10-12 14:14:45 +02:00
|
|
|
void MapClearAllElements()
|
2015-08-31 12:42:46 +02:00
|
|
|
{
|
2019-12-30 16:03:51 +01:00
|
|
|
for (int32_t y = 0; y < MAXIMUM_MAP_SIZE_BIG; y += COORDS_XY_STEP)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2019-12-30 16:03:51 +01:00
|
|
|
for (int32_t x = 0; x < MAXIMUM_MAP_SIZE_BIG; x += COORDS_XY_STEP)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2022-10-12 14:14:45 +02:00
|
|
|
ClearElementsAt({ x, y });
|
2017-06-06 23:24:18 +02:00
|
|
|
}
|
|
|
|
}
|
2015-08-31 12:42:46 +02:00
|
|
|
}
|
2015-10-01 22:24:24 +02:00
|
|
|
|
2015-10-25 17:00:21 +01:00
|
|
|
/**
|
2015-11-01 15:58:46 +01:00
|
|
|
* Gets the track element at x, y, z.
|
2015-10-25 17:00:21 +01:00
|
|
|
* @param x x units, not tiles.
|
|
|
|
* @param y y units, not tiles.
|
|
|
|
* @param z Base height.
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
TrackElement* MapGetTrackElementAt(const CoordsXYZ& trackPos)
|
2015-10-25 17:00:21 +01:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(trackPos);
|
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;
|
2020-01-05 17:59:02 +01:00
|
|
|
if (tileElement->GetBaseZ() != trackPos.z)
|
2018-06-22 23:17:03 +02:00
|
|
|
continue;
|
2015-10-25 17:00:21 +01:00
|
|
|
|
2019-03-16 12:21:36 +01:00
|
|
|
return tileElement->AsTrack();
|
2018-05-24 11:44:53 +02:00
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
2015-10-25 17:00:21 +01:00
|
|
|
|
2018-01-04 06:58:44 +01:00
|
|
|
return nullptr;
|
2015-10-25 17:00:21 +01:00
|
|
|
}
|
2015-10-25 22:25:54 +01:00
|
|
|
|
|
|
|
/**
|
2015-11-01 15:58:46 +01:00
|
|
|
* Gets the track element at x, y, z that is the given track type.
|
2015-10-25 22:25:54 +01:00
|
|
|
* @param x x units, not tiles.
|
|
|
|
* @param y y units, not tiles.
|
|
|
|
* @param z Base height.
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
TileElement* MapGetTrackElementAtOfType(const CoordsXYZ& trackPos, track_type_t trackType)
|
2015-10-25 22:25:54 +01:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(trackPos);
|
2019-10-09 16:02:21 +02:00
|
|
|
if (tileElement == nullptr)
|
|
|
|
return nullptr;
|
2019-12-23 13:12:44 +01:00
|
|
|
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;
|
2023-01-19 20:36:30 +01:00
|
|
|
if (tileElement->BaseHeight != trackTilePos.z)
|
2018-06-22 23:17:03 +02:00
|
|
|
continue;
|
2018-09-17 21:54:36 +02:00
|
|
|
if (tileElement->AsTrack()->GetTrackType() != trackType)
|
2018-06-22 23:17:03 +02:00
|
|
|
continue;
|
2015-10-25 22:25:54 +01:00
|
|
|
|
2017-10-31 14:03:45 +01:00
|
|
|
return tileElement;
|
2018-05-24 11:44:53 +02:00
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
2015-10-25 22:25:54 +01:00
|
|
|
|
2018-01-04 06:58:44 +01:00
|
|
|
return nullptr;
|
2015-10-25 22:25:54 +01:00
|
|
|
}
|
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.
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
TileElement* MapGetTrackElementAtOfTypeSeq(const CoordsXYZ& trackPos, track_type_t trackType, int32_t sequence)
|
2015-11-01 15:58:46 +01:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(trackPos);
|
2019-12-23 13:18:20 +01:00
|
|
|
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;
|
2023-01-19 20:36:30 +01:00
|
|
|
if (tileElement->BaseHeight != trackTilePos.z)
|
2018-06-22 23:17:03 +02:00
|
|
|
continue;
|
2018-09-17 21:54:36 +02:00
|
|
|
if (tileElement->AsTrack()->GetTrackType() != trackType)
|
2018-06-22 23:17:03 +02:00
|
|
|
continue;
|
2018-09-17 22:10:15 +02:00
|
|
|
if (tileElement->AsTrack()->GetSequenceIndex() != sequence)
|
2018-06-22 23:17:03 +02:00
|
|
|
continue;
|
2017-06-06 23:24:18 +02:00
|
|
|
|
2017-10-31 14:03:45 +01:00
|
|
|
return tileElement;
|
2018-05-24 11:44:53 +02:00
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
2017-06-06 23:24:18 +02:00
|
|
|
|
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
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
TrackElement* MapGetTrackElementAtOfType(const CoordsXYZD& location, track_type_t trackType)
|
2019-08-08 13:59:06 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
auto tileElement = MapGetFirstElementAt(location);
|
2019-08-08 13:59:06 +02:00
|
|
|
if (tileElement != nullptr)
|
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
|
|
|
auto trackElement = tileElement->AsTrack();
|
|
|
|
if (trackElement != nullptr)
|
|
|
|
{
|
2019-12-27 15:26:40 +01:00
|
|
|
if (trackElement->GetBaseZ() != location.z)
|
2019-08-08 13:59:06 +02:00
|
|
|
continue;
|
|
|
|
if (trackElement->GetDirection() != location.direction)
|
|
|
|
continue;
|
|
|
|
if (trackElement->GetTrackType() != trackType)
|
|
|
|
continue;
|
|
|
|
return trackElement;
|
|
|
|
}
|
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
TrackElement* MapGetTrackElementAtOfTypeSeq(const CoordsXYZD& location, track_type_t trackType, int32_t sequence)
|
2019-08-08 13:59:06 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
auto tileElement = MapGetFirstElementAt(location);
|
2019-08-08 13:59:06 +02:00
|
|
|
if (tileElement != nullptr)
|
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
|
|
|
auto trackElement = tileElement->AsTrack();
|
|
|
|
if (trackElement != nullptr)
|
|
|
|
{
|
2019-12-27 15:26:40 +01:00
|
|
|
if (trackElement->GetBaseZ() != location.z)
|
2019-08-08 13:59:06 +02:00
|
|
|
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.
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
TileElement* MapGetTrackElementAtOfTypeFromRide(const CoordsXYZ& trackPos, track_type_t trackType, RideId rideIndex)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(trackPos);
|
2019-10-09 16:02:21 +02:00
|
|
|
if (tileElement == nullptr)
|
|
|
|
return nullptr;
|
2019-12-23 13:26:08 +01:00
|
|
|
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;
|
2023-01-19 20:36:30 +01:00
|
|
|
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;
|
2018-09-17 21:54:36 +02:00
|
|
|
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;
|
2018-05-24 11:44:53 +02:00
|
|
|
} 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.
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
TileElement* MapGetTrackElementAtFromRide(const CoordsXYZ& trackPos, RideId rideIndex)
|
2018-06-22 23:17:03 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(trackPos);
|
2019-10-09 16:02:21 +02:00
|
|
|
if (tileElement == nullptr)
|
|
|
|
return nullptr;
|
2019-12-23 13:30:48 +01:00
|
|
|
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;
|
2023-01-19 20:36:30 +01:00
|
|
|
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;
|
2018-05-24 11:44:53 +02:00
|
|
|
} 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).
|
|
|
|
*/
|
2022-10-12 14:14:45 +02:00
|
|
|
TileElement* MapGetTrackElementAtWithDirectionFromRide(const CoordsXYZD& trackPos, RideId rideIndex)
|
2016-02-14 18:37:28 +01:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(trackPos);
|
2019-10-09 16:02:21 +02:00
|
|
|
if (tileElement == nullptr)
|
|
|
|
return nullptr;
|
2019-12-23 13:39:14 +01:00
|
|
|
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;
|
2023-01-19 20:36:30 +01:00
|
|
|
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;
|
2019-12-23 13:39:14 +01:00
|
|
|
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;
|
2018-05-24 11:44:53 +02:00
|
|
|
} 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
|
|
|
};
|
2016-04-28 20:43:20 +02:00
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
WallElement* MapGetWallElementAt(const CoordsXYRangedZ& coords)
|
2020-07-16 22:52:45 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
auto tileElement = MapGetFirstElementAt(coords);
|
2020-07-16 22:52:45 +02:00
|
|
|
|
|
|
|
if (tileElement != nullptr)
|
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
2021-12-11 00:39:39 +01:00
|
|
|
if (tileElement->GetType() == TileElementType::Wall && coords.baseZ < tileElement->GetClearanceZ()
|
2020-07-16 22:52:45 +02:00
|
|
|
&& coords.clearanceZ > tileElement->GetBaseZ())
|
|
|
|
{
|
|
|
|
return tileElement->AsWall();
|
|
|
|
}
|
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2022-10-12 07:35:20 +02:00
|
|
|
WallElement* MapGetWallElementAt(const CoordsXYZD& wallCoords)
|
2017-02-20 22:02:19 +01:00
|
|
|
{
|
2019-12-18 11:23:54 +01:00
|
|
|
auto tileWallCoords = TileCoordsXYZ(wallCoords);
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(wallCoords);
|
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)
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
2023-01-19 20:36:30 +01:00
|
|
|
if (tileElement->BaseHeight != tileWallCoords.z)
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
2019-12-18 11:23:54 +01:00
|
|
|
if (tileElement->GetDirection() != wallCoords.direction)
|
2017-06-06 23:24:18 +02:00
|
|
|
continue;
|
|
|
|
|
2019-03-16 17:32:13 +01:00
|
|
|
return tileElement->AsWall();
|
2018-05-24 11:44:53 +02:00
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
2018-01-04 06:58:44 +01:00
|
|
|
return nullptr;
|
2017-02-20 22:02:19 +01:00
|
|
|
}
|
2017-03-02 23:02:54 +01:00
|
|
|
|
2022-10-12 14:14:45 +02:00
|
|
|
uint16_t CheckMaxAllowableLandRightsForTile(const CoordsXYZ& tileMapPos)
|
2017-06-29 11:30:18 +02:00
|
|
|
{
|
2022-10-11 20:39:24 +02:00
|
|
|
TileElement* tileElement = MapGetFirstElementAt(tileMapPos);
|
2018-06-20 17:28:51 +02:00
|
|
|
uint16_t destOwnership = OWNERSHIP_OWNED;
|
2017-06-29 11:30:18 +02:00
|
|
|
|
|
|
|
// Sometimes done deliberately.
|
2018-01-04 06:58:44 +01:00
|
|
|
if (tileElement == nullptr)
|
2017-06-29 11:30:18 +02:00
|
|
|
{
|
|
|
|
return OWNERSHIP_OWNED;
|
|
|
|
}
|
|
|
|
|
2019-12-23 17:24:41 +01:00
|
|
|
auto tilePos = TileCoordsXYZ{ tileMapPos };
|
2017-06-29 11:30:18 +02:00
|
|
|
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))
|
2017-06-29 11:30:18 +02:00
|
|
|
{
|
|
|
|
destOwnership = OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED;
|
|
|
|
// Do not own construction rights if too high/below surface
|
2023-01-19 20:36:30 +01:00
|
|
|
if (tileElement->BaseHeight - ConstructionRightsClearanceSmall > tilePos.z || tileElement->BaseHeight < tilePos.z)
|
2017-06-29 11:30:18 +02:00
|
|
|
{
|
|
|
|
destOwnership = OWNERSHIP_UNOWNED;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-06-22 23:17:03 +02:00
|
|
|
} while (!(tileElement++)->IsLastForTile());
|
2017-06-29 11:30:18 +02:00
|
|
|
|
|
|
|
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)
|
2018-02-01 10:24:20 +01:00
|
|
|
{
|
|
|
|
FixLandOwnershipTilesWithOwnership(tiles, OWNERSHIP_AVAILABLE);
|
|
|
|
}
|
|
|
|
|
2021-08-01 17:26:43 +02:00
|
|
|
void FixLandOwnershipTilesWithOwnership(std::initializer_list<TileCoordsXY> tiles, uint8_t ownership, bool doNotDowngrade)
|
2018-02-01 10:24:20 +01:00
|
|
|
{
|
2018-06-22 23:17:03 +02:00
|
|
|
for (const TileCoordsXY* tile = tiles.begin(); tile != tiles.end(); ++tile)
|
2018-02-01 10:24:20 +01:00
|
|
|
{
|
2023-04-05 22:13:47 +02:00
|
|
|
auto surfaceElement = MapGetSurfaceElementAt(*tile);
|
2019-10-08 13:45:30 +02:00
|
|
|
if (surfaceElement != nullptr)
|
|
|
|
{
|
2021-08-01 17:26:43 +02:00
|
|
|
if (doNotDowngrade && surfaceElement->GetOwnership() == OWNERSHIP_OWNED)
|
|
|
|
continue;
|
|
|
|
|
2019-10-08 13:45:30 +02:00
|
|
|
surfaceElement->SetOwnership(ownership);
|
2024-03-26 21:35:55 +01:00
|
|
|
Park::UpdateFencesAroundTile({ (*tile).x * 32, (*tile).y * 32 });
|
2019-10-08 13:45:30 +02:00
|
|
|
}
|
2018-02-01 10:24:20 +01:00
|
|
|
}
|
|
|
|
}
|
2021-12-17 19:25:46 +01:00
|
|
|
|
|
|
|
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());
|
2021-12-17 19:25:46 +01:00
|
|
|
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());
|
2021-12-17 19:25:46 +01:00
|
|
|
auto bY = std::min<decltype(range.GetBottom())>(mapSizeMax.y, range.GetBottom());
|
|
|
|
MapRange validRange = MapRange{ aX, aY, bX, bY };
|
|
|
|
return validRange;
|
|
|
|
}
|