OpenRCT2/src/openrct2/world/Footpath.cpp

2585 lines
86 KiB
C++
Raw Normal View History

/*****************************************************************************
* Copyright (c) 2014-2023 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
2017-12-13 13:02:24 +01:00
#include "../Cheats.h"
#include "../Context.h"
2018-06-22 23:17:03 +02:00
#include "../Game.h"
2022-05-01 18:21:53 +02:00
#include "../Identifiers.h"
2018-06-22 23:17:03 +02:00
#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/FootpathPlaceAction.h"
#include "../actions/FootpathRemoveAction.h"
#include "../actions/LandSetRightsAction.h"
#include "../core/Guard.hpp"
2021-11-24 15:48:33 +01:00
#include "../entity/EntityList.h"
2021-11-24 15:58:01 +01:00
#include "../entity/EntityRegistry.h"
#include "../interface/Window_internal.h"
2018-01-06 18:32:25 +01:00
#include "../localisation/Localisation.h"
2018-03-19 23:28:40 +01:00
#include "../management/Finance.h"
#include "../network/network.h"
#include "../object/FootpathItemEntry.h"
#include "../object/FootpathObject.h"
#include "../object/FootpathRailingsObject.h"
#include "../object/FootpathSurfaceObject.h"
#include "../object/ObjectEntryManager.h"
2018-01-02 20:36:42 +01:00
#include "../object/ObjectList.h"
2018-02-11 23:09:38 +01:00
#include "../object/ObjectManager.h"
#include "../paint/VirtualFloor.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 "../ride/RideData.h"
2017-10-23 14:47:12 +02:00
#include "../ride/Station.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"
2017-12-13 13:02:24 +01:00
#include "../util/Util.h"
#include "Location.hpp"
2018-01-03 14:56:08 +01:00
#include "Map.h"
2018-06-22 23:17:03 +02:00
#include "MapAnimation.h"
2018-03-19 23:28:40 +01:00
#include "Park.h"
#include "Scenery.h"
2018-05-01 16:33:16 +02:00
#include "Surface.h"
2022-04-07 21:42:08 +02:00
#include "TileElement.h"
#include <algorithm>
2018-11-21 23:16:04 +01:00
#include <iterator>
using namespace OpenRCT2::TrackMetaData;
2022-10-04 08:51:27 +02:00
void FootpathUpdateQueueEntranceBanner(const CoordsXY& footpathPos, TileElement* tileElement);
FootpathSelection gFootpathSelection;
2021-04-14 14:56:28 +02:00
ProvisionalFootpath gProvisionalFootpath;
uint16_t gFootpathSelectedId;
CoordsXYZ gFootpathConstructFromPosition;
uint8_t gFootpathConstructSlope;
uint8_t gFootpathGroundFlags;
2022-01-19 14:17:11 +01:00
static RideId* _footpathQueueChainNext;
static RideId _footpathQueueChain[64];
2016-05-17 22:29:05 +02:00
2018-04-02 19:00:36 +02:00
// This is the coordinates that a user of the bin should move to
// rct2: 0x00992A4C
const std::array<CoordsXY, NumOrthogonalDirections> BinUseOffsets = {
CoordsXY{ 11, 16 },
2018-04-02 19:00:36 +02:00
{ 16, 21 },
{ 21, 16 },
{ 16, 11 },
};
// These are the offsets for bench positions on footpaths, 2 for each edge
// rct2: 0x00981F2C, 0x00981F2E
const std::array<CoordsXY, NumOrthogonalDirections* 2> BenchUseOffsets = {
CoordsXY{ 7, 12 }, { 12, 25 }, { 25, 20 }, { 20, 7 }, { 7, 20 }, { 20, 25 }, { 25, 12 }, { 12, 7 },
};
2018-04-02 19:00:36 +02:00
2016-08-14 14:12:07 +02:00
/** rct2: 0x00981D6C, 0x00981D6E */
const std::array<CoordsXY, NumOrthogonalDirections> DirectionOffsets = {
CoordsXY{ -1, 0 },
{ 0, 1 },
{ 1, 0 },
{ 0, -1 },
};
2015-07-10 21:58:09 +02:00
// rct2: 0x0097B974
static constexpr const uint16_t EntranceDirections[] = {
2018-06-22 23:17:03 +02:00
(4), 0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_RIDE_ENTRANCE,
(4), 0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_RIDE_EXIT,
(4 | 1), 0, 0, 0, 0, 0, 0, 0, // ENTRANCE_TYPE_PARK_ENTRANCE
2015-07-10 21:58:09 +02:00
};
2016-08-11 21:38:27 +02:00
/** rct2: 0x0098D7F0 */
static constexpr const uint8_t connected_path_count[] = {
0, // 0b0000
1, // 0b0001
1, // 0b0010
2, // 0b0011
1, // 0b0100
2, // 0b0101
2, // 0b0110
3, // 0b0111
1, // 0b1000
2, // 0b1001
2, // 0b1010
3, // 0b1011
2, // 0b1100
3, // 0b1101
3, // 0b1110
4, // 0b1111
2016-08-11 21:38:27 +02:00
};
int32_t EntranceElement::GetDirections() const
2015-07-10 21:58:09 +02:00
{
return EntranceDirections[(GetEntranceType() * 8) + GetSequenceIndex()];
2015-07-10 21:58:09 +02:00
}
static bool entrance_has_direction(const EntranceElement& entranceElement, int32_t direction)
2015-07-10 21:58:09 +02:00
{
return entranceElement.GetDirections() & (1 << (direction & 3));
2015-07-10 21:58:09 +02:00
}
2022-10-04 08:51:27 +02:00
TileElement* MapGetFootpathElement(const CoordsXYZ& coords)
{
TileElement* tileElement = MapGetFirstElementAt(coords);
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::Path && tileElement->GetBaseZ() == coords.z)
2017-10-31 14:03:45 +01:00
return tileElement;
} while (!(tileElement++)->IsLastForTile());
2018-01-29 17:06:01 +01:00
return nullptr;
}
/**
2015-10-20 20:16:30 +02:00
*
* rct2: 0x006A76FF
*/
money64 FootpathProvisionalSet(
ObjectEntryIndex type, ObjectEntryIndex railingsType, const CoordsXYZ& footpathLoc, int32_t slope,
PathConstructFlags constructFlags)
{
money64 cost;
2022-10-04 08:51:27 +02:00
FootpathProvisionalRemove();
auto footpathPlaceAction = FootpathPlaceAction(footpathLoc, slope, type, railingsType, INVALID_DIRECTION, constructFlags);
2019-03-01 18:17:46 +01:00
footpathPlaceAction.SetFlags(GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED);
auto res = GameActions::Execute(&footpathPlaceAction);
cost = res.Error == GameActions::Status::Ok ? res.Cost : MONEY64_UNDEFINED;
if (res.Error == GameActions::Status::Ok)
2018-06-22 23:17:03 +02:00
{
gProvisionalFootpath.SurfaceIndex = type;
gProvisionalFootpath.RailingsIndex = railingsType;
2021-04-14 14:56:28 +02:00
gProvisionalFootpath.Position = footpathLoc;
gProvisionalFootpath.Slope = slope;
gProvisionalFootpath.ConstructFlags = constructFlags;
2021-04-14 14:56:28 +02:00
gProvisionalFootpath.Flags |= PROVISIONAL_PATH_FLAG_1;
2018-06-22 23:17:03 +02:00
if (gFootpathGroundFlags & ELEMENT_IS_UNDERGROUND)
{
2023-01-16 21:14:50 +01:00
ViewportSetVisibility(1);
2018-06-22 23:17:03 +02:00
}
else
{
2023-01-16 21:14:50 +01:00
ViewportSetVisibility(3);
}
}
// Invalidate previous footpath piece.
VirtualFloorInvalidate();
if (!SceneryToolIsActive())
2018-01-07 00:09:26 +01:00
{
if (res.Error != GameActions::Status::Ok)
{
// If we can't build this, don't show a virtual floor.
VirtualFloorSetHeight(0);
}
2018-06-22 23:17:03 +02:00
else if (
gFootpathConstructSlope == TILE_ELEMENT_SLOPE_FLAT
2021-04-14 14:56:28 +02:00
|| gProvisionalFootpath.Position.z < gFootpathConstructFromPosition.z)
{
// Going either straight on, or down.
VirtualFloorSetHeight(gProvisionalFootpath.Position.z);
}
else
{
// Going up in the world!
VirtualFloorSetHeight(gProvisionalFootpath.Position.z + LAND_HEIGHT_STEP);
}
2018-01-07 00:09:26 +01:00
}
return cost;
}
/**
2015-10-20 20:16:30 +02:00
*
* rct2: 0x006A77FF
*/
2022-10-04 08:51:27 +02:00
void FootpathProvisionalRemove()
{
2021-04-14 14:56:28 +02:00
if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1)
{
2021-04-14 14:56:28 +02:00
gProvisionalFootpath.Flags &= ~PROVISIONAL_PATH_FLAG_1;
2022-08-11 00:00:58 +02:00
auto action = FootpathRemoveAction(gProvisionalFootpath.Position);
action.SetFlags(GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
GameActions::Execute(&action);
}
}
/**
2015-10-20 20:16:30 +02:00
*
* rct2: 0x006A7831
*/
2022-10-04 08:51:27 +02:00
void FootpathProvisionalUpdate()
{
2021-04-14 14:56:28 +02:00
if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_SHOW_ARROW)
2018-06-22 23:17:03 +02:00
{
2021-04-14 14:56:28 +02:00
gProvisionalFootpath.Flags &= ~PROVISIONAL_PATH_FLAG_SHOW_ARROW;
gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW;
MapInvalidateTileFull(gFootpathConstructFromPosition);
}
2022-10-04 08:51:27 +02:00
FootpathProvisionalRemove();
}
/**
* Determines the location of the footpath at which we point with the cursor. If no footpath is underneath the cursor,
* then return the location of the ground tile. Besides the location it also computes the direction of the yellow arrow
* when we are going to build a footpath bridge/tunnel.
* rct2: 0x00689726
* In:
* screenX: eax
* screenY: ebx
* Out:
* x: ax
* y: bx
* direction: ecx
2017-10-31 14:03:45 +01:00
* tileElement: edx
*/
2022-10-04 08:51:27 +02:00
CoordsXY FootpathGetCoordinatesFromPos(const ScreenCoordsXY& screenCoords, int32_t* direction, TileElement** tileElement)
{
WindowBase* window = WindowFindFromPoint(screenCoords);
if (window == nullptr || window->viewport == nullptr)
{
CoordsXY position{};
position.SetNull();
return position;
}
auto viewport = window->viewport;
2023-01-16 21:14:50 +01:00
auto info = GetMapCoordinatesFromPosWindow(window, screenCoords, EnumsToFlags(ViewportInteractionItem::Footpath));
if (info.SpriteType != ViewportInteractionItem::Footpath
2018-06-22 23:17:03 +02:00
|| !(viewport->flags & (VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_HIDE_VERTICAL)))
{
2023-01-16 21:14:50 +01:00
info = GetMapCoordinatesFromPosWindow(
window, screenCoords, EnumsToFlags(ViewportInteractionItem::Terrain, ViewportInteractionItem::Footpath));
if (info.SpriteType == ViewportInteractionItem::None)
2018-06-22 23:17:03 +02:00
{
auto position = info.Loc;
position.SetNull();
return position;
}
}
auto minPosition = info.Loc;
auto maxPosition = info.Loc + CoordsXY{ 31, 31 };
auto myTileElement = info.Element;
auto position = info.Loc.ToTileCentre();
auto z = 0;
if (info.SpriteType == ViewportInteractionItem::Footpath)
2018-06-22 23:17:03 +02:00
{
z = myTileElement->GetBaseZ();
2018-09-16 16:17:35 +02:00
if (myTileElement->AsPath()->IsSloped())
2018-06-22 23:17:03 +02:00
{
z += 8;
}
}
auto start_vp_pos = viewport->ScreenToViewportCoord(screenCoords);
2018-06-22 23:17:03 +02:00
for (int32_t i = 0; i < 5; i++)
{
if (info.SpriteType != ViewportInteractionItem::Footpath)
2018-06-22 23:17:03 +02:00
{
z = TileElementHeight(position);
}
2023-01-16 21:14:50 +01:00
position = ViewportPosToMapPos(start_vp_pos, z);
position.x = std::clamp(position.x, minPosition.x, maxPosition.x);
position.y = std::clamp(position.y, minPosition.y, maxPosition.y);
}
// Determine to which edge the cursor is closest
uint32_t myDirection;
int32_t mod_x = position.x & 0x1F, mod_y = position.y & 0x1F;
2018-06-22 23:17:03 +02:00
if (mod_x < mod_y)
{
if (mod_x + mod_y < 32)
{
myDirection = 0;
2018-06-22 23:17:03 +02:00
}
else
{
myDirection = 1;
}
2018-06-22 23:17:03 +02:00
}
else
{
if (mod_x + mod_y < 32)
{
myDirection = 3;
2018-06-22 23:17:03 +02:00
}
else
{
myDirection = 2;
}
}
position = position.ToTileStart();
2018-06-22 23:17:03 +02:00
if (direction != nullptr)
*direction = myDirection;
if (tileElement != nullptr)
*tileElement = myTileElement;
return position;
}
/**
2015-10-20 20:16:30 +02:00
*
* rct2: 0x0068A0C9
* screenX: eax
* screenY: ebx
* x: ax
* y: bx
* direction: cl
2017-10-31 14:03:45 +01:00
* tileElement: edx
*/
2022-10-04 08:51:27 +02:00
CoordsXY FootpathBridgeGetInfoFromPos(const ScreenCoordsXY& screenCoords, int32_t* direction, TileElement** tileElement)
{
// First check if we point at an entrance or exit. In that case, we would want the path coming from the entrance/exit.
WindowBase* window = WindowFindFromPoint(screenCoords);
if (window == nullptr || window->viewport == nullptr)
{
CoordsXY ret{};
ret.SetNull();
return ret;
}
auto viewport = window->viewport;
2023-01-16 21:14:50 +01:00
auto info = GetMapCoordinatesFromPosWindow(window, screenCoords, EnumsToFlags(ViewportInteractionItem::Ride));
*tileElement = info.Element;
if (info.SpriteType == ViewportInteractionItem::Ride
&& viewport->flags & (VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_HIDE_VERTICAL)
2021-12-11 00:39:39 +01:00
&& (*tileElement)->GetType() == TileElementType::Entrance)
2018-06-22 23:17:03 +02:00
{
int32_t directions = (*tileElement)->AsEntrance()->GetDirections();
2018-06-22 23:17:03 +02:00
if (directions & 0x0F)
{
int32_t bx = UtilBitScanForward(directions);
2018-10-05 14:02:55 +02:00
bx += (*tileElement)->AsEntrance()->GetDirection();
bx &= 3;
2018-06-22 23:17:03 +02:00
if (direction != nullptr)
*direction = bx;
return info.Loc;
}
}
2023-01-16 21:14:50 +01:00
info = GetMapCoordinatesFromPosWindow(
window, screenCoords,
EnumsToFlags(ViewportInteractionItem::Terrain, ViewportInteractionItem::Footpath, ViewportInteractionItem::Ride));
2021-12-11 00:39:39 +01:00
if (info.SpriteType == ViewportInteractionItem::Ride && (*tileElement)->GetType() == TileElementType::Entrance)
2018-06-22 23:17:03 +02:00
{
int32_t directions = (*tileElement)->AsEntrance()->GetDirections();
2018-06-22 23:17:03 +02:00
if (directions & 0x0F)
{
int32_t bx = (*tileElement)->GetDirectionWithOffset(UtilBitScanForward(directions));
2018-06-22 23:17:03 +02:00
if (direction != nullptr)
*direction = bx;
return info.Loc;
}
}
// We point at something else
2022-10-04 08:51:27 +02:00
return FootpathGetCoordinatesFromPos(screenCoords, direction, tileElement);
}
/**
2015-10-20 20:16:30 +02:00
*
* rct2: 0x00673883
*/
2022-10-04 08:51:27 +02:00
void FootpathRemoveLitter(const CoordsXYZ& footpathPos)
{
std::vector<Litter*> removals;
for (auto litter : EntityTileList<Litter>(footpathPos))
2018-06-22 23:17:03 +02:00
{
int32_t distanceZ = abs(litter->z - footpathPos.z);
if (distanceZ <= 32)
2018-06-22 23:17:03 +02:00
{
removals.push_back(litter);
}
}
for (auto* litter : removals)
{
litter->Invalidate();
2021-11-24 14:37:47 +01:00
EntityRemove(litter);
}
}
/**
2015-10-20 20:16:30 +02:00
*
* rct2: 0x0069A48B
*/
2022-10-04 08:51:27 +02:00
void FootpathInterruptPeeps(const CoordsXYZ& footpathPos)
{
auto quad = EntityTileList<Peep>(footpathPos);
for (auto peep : quad)
2018-06-22 23:17:03 +02:00
{
if (peep->State == PeepState::Sitting || peep->State == PeepState::Watching)
2018-06-22 23:17:03 +02:00
{
auto location = peep->GetLocation();
if (location.z == footpathPos.z)
2018-06-22 23:17:03 +02:00
{
auto destination = location.ToTileCentre();
peep->SetState(PeepState::Walking);
peep->SetDestination(destination, 5);
peep->UpdateCurrentActionSpriteType();
}
}
}
}
/**
* Returns true if the edge of tile x, y specified by direction is occupied by a fence
* between heights z0 and z1.
*
* Note that there may still be a fence on the opposing tile.
*
* rct2: 0x006E59DC
*/
2022-10-04 08:51:27 +02:00
bool WallInTheWay(const CoordsXYRangedZ& fencePos, int32_t direction)
2015-07-07 20:09:12 +02:00
{
2018-11-01 13:53:50 +01:00
TileElement* tileElement;
tileElement = MapGetFirstElementAt(fencePos);
2018-01-29 17:06:01 +01: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::Wall)
continue;
if (tileElement->IsGhost())
continue;
if (fencePos.baseZ >= tileElement->GetClearanceZ())
continue;
if (fencePos.clearanceZ <= tileElement->GetBaseZ())
continue;
if ((tileElement->GetDirection()) != direction)
continue;
return true;
} while (!(tileElement++)->IsLastForTile());
return false;
2015-07-07 20:09:12 +02:00
}
2022-10-04 08:51:27 +02:00
static PathElement* FootpathConnectCornersGetNeighbour(const CoordsXYZ& footpathPos, int32_t requireEdges)
2015-07-07 20:09:12 +02:00
{
if (!MapIsLocationValid(footpathPos))
{
return nullptr;
}
TileElement* tileElement = MapGetFirstElementAt(footpathPos);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return nullptr;
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Path)
continue;
auto pathElement = tileElement->AsPath();
if (pathElement->IsQueue())
continue;
if (tileElement->GetBaseZ() != footpathPos.z)
continue;
if (!(pathElement->GetEdgesAndCorners() & requireEdges))
continue;
return pathElement;
} while (!(tileElement++)->IsLastForTile());
2018-01-29 17:06:01 +01:00
return nullptr;
2015-07-07 20:09:12 +02:00
}
/**
* Sets the corner edges of four path tiles.
* The function will search for a path in the direction given, then check clockwise to see if it there is a path and again until
* it reaches the initial path. In other words, checks if there are four paths together so that it can set the inner corners of
* each one.
*
* rct2: 0x006A70EB
*/
2022-10-04 08:51:27 +02:00
static void FootpathConnectCorners(const CoordsXY& footpathPos, PathElement* initialTileElement)
2015-07-07 20:09:12 +02:00
{
using PathElementCoordsPair = std::pair<PathElement*, CoordsXY>;
std::array<PathElementCoordsPair, 4> tileElements;
if (initialTileElement->IsQueue())
return;
if (initialTileElement->IsSloped())
return;
std::get<0>(tileElements) = { initialTileElement, footpathPos };
int32_t z = initialTileElement->GetBaseZ();
for (int32_t initialDirection = 0; initialDirection < NumOrthogonalDirections; initialDirection++)
2018-06-22 23:17:03 +02:00
{
int32_t direction = initialDirection;
auto currentPos = footpathPos + CoordsDirectionDelta[direction];
std::get<1>(tileElements) = { FootpathConnectCornersGetNeighbour({ currentPos, z }, (1 << DirectionReverse(direction))),
currentPos };
if (std::get<1>(tileElements).first == nullptr)
continue;
direction = DirectionNext(direction);
currentPos += CoordsDirectionDelta[direction];
std::get<2>(tileElements) = { FootpathConnectCornersGetNeighbour({ currentPos, z }, (1 << DirectionReverse(direction))),
currentPos };
if (std::get<2>(tileElements).first == nullptr)
continue;
direction = DirectionNext(direction);
currentPos += CoordsDirectionDelta[direction];
// First check link to previous tile
std::get<3>(tileElements) = { FootpathConnectCornersGetNeighbour({ currentPos, z }, (1 << DirectionReverse(direction))),
currentPos };
if (std::get<3>(tileElements).first == nullptr)
continue;
// Second check link to initial tile
2022-10-04 08:51:27 +02:00
std::get<3>(tileElements) = { FootpathConnectCornersGetNeighbour({ currentPos, z }, (1 << ((direction + 1) & 3))),
currentPos };
if (std::get<3>(tileElements).first == nullptr)
continue;
direction = DirectionNext(direction);
std::get<3>(tileElements).first->SetCorners(std::get<3>(tileElements).first->GetCorners() | (1 << (direction)));
MapInvalidateElement(std::get<3>(tileElements).second, reinterpret_cast<TileElement*>(std::get<3>(tileElements).first));
direction = DirectionPrev(direction);
std::get<2>(tileElements).first->SetCorners(std::get<2>(tileElements).first->GetCorners() | (1 << (direction)));
MapInvalidateElement(std::get<2>(tileElements).second, reinterpret_cast<TileElement*>(std::get<2>(tileElements).first));
direction = DirectionPrev(direction);
std::get<1>(tileElements).first->SetCorners(std::get<1>(tileElements).first->GetCorners() | (1 << (direction)));
MapInvalidateElement(std::get<1>(tileElements).second, reinterpret_cast<TileElement*>(std::get<1>(tileElements).first));
direction = initialDirection;
std::get<0>(tileElements).first->SetCorners(std::get<0>(tileElements).first->GetCorners() | (1 << (direction)));
MapInvalidateElement(std::get<0>(tileElements).second, reinterpret_cast<TileElement*>(std::get<0>(tileElements).first));
}
2015-07-07 20:09:12 +02:00
}
struct FootpathNeighbour
2018-06-22 23:17:03 +02:00
{
uint8_t order;
uint8_t direction;
2022-01-19 14:17:11 +01:00
RideId ride_index;
StationIndex entrance_index;
};
2015-07-07 20:09:12 +02:00
struct FootpathNeighbourList
2018-06-22 23:17:03 +02:00
{
FootpathNeighbour items[8];
size_t count;
};
2015-07-07 20:09:12 +02:00
2022-10-04 08:51:27 +02:00
static int32_t FootpathNeighbourCompare(const void* a, const void* b)
2015-07-07 20:09:12 +02:00
{
uint8_t va = (static_cast<const FootpathNeighbour*>(a))->order;
uint8_t vb = (static_cast<const FootpathNeighbour*>(b))->order;
2018-06-22 23:17:03 +02:00
if (va < vb)
return 1;
2021-09-15 22:22:15 +02:00
if (va > vb)
2018-06-22 23:17:03 +02:00
return -1;
2021-09-15 22:22:15 +02:00
uint8_t da = (static_cast<const FootpathNeighbour*>(a))->direction;
uint8_t db = (static_cast<const FootpathNeighbour*>(b))->direction;
2021-09-15 22:22:15 +02:00
if (da < db)
return -1;
if (da > db)
return 1;
return 0;
2015-07-07 20:09:12 +02:00
}
static void FootpathNeighbourListInit(FootpathNeighbourList* neighbourList)
2015-07-07 20:09:12 +02:00
{
neighbourList->count = 0;
2015-07-07 20:09:12 +02:00
}
2022-10-04 08:51:27 +02:00
static void FootpathNeighbourListPush(
FootpathNeighbourList* neighbourList, int32_t order, int32_t direction, RideId rideIndex, ::StationIndex entrance_index)
2015-07-07 20:09:12 +02:00
{
2018-11-21 23:16:04 +01:00
Guard::Assert(neighbourList->count < std::size(neighbourList->items));
neighbourList->items[neighbourList->count].order = order;
neighbourList->items[neighbourList->count].direction = direction;
neighbourList->items[neighbourList->count].ride_index = rideIndex;
neighbourList->items[neighbourList->count].entrance_index = entrance_index;
neighbourList->count++;
2015-07-07 20:09:12 +02:00
}
static bool FootpathNeighbourListPop(FootpathNeighbourList* neighbourList, FootpathNeighbour* outNeighbour)
2015-07-07 20:09:12 +02:00
{
if (neighbourList->count == 0)
return false;
2015-07-07 20:09:12 +02:00
*outNeighbour = neighbourList->items[0];
2018-03-16 20:57:22 +01:00
const size_t bytesToMove = (neighbourList->count - 1) * sizeof(neighbourList->items[0]);
memmove(&neighbourList->items[0], &neighbourList->items[1], bytesToMove);
neighbourList->count--;
return true;
2015-07-07 20:09:12 +02:00
}
static void FootpathNeighbourListRemove(FootpathNeighbourList* neighbourList, size_t index)
2015-08-04 18:40:02 +02:00
{
Guard::ArgumentInRange<size_t>(index, 0, neighbourList->count - 1);
int32_t itemsRemaining = static_cast<int32_t>(neighbourList->count - index) - 1;
2018-06-22 23:17:03 +02:00
if (itemsRemaining > 0)
{
memmove(&neighbourList->items[index], &neighbourList->items[index + 1], sizeof(FootpathNeighbour) * itemsRemaining);
}
neighbourList->count--;
2015-08-04 18:40:02 +02:00
}
static void FoopathNeighbourListSort(FootpathNeighbourList* neighbourList)
2015-07-07 20:09:12 +02:00
{
qsort(neighbourList->items, neighbourList->count, sizeof(FootpathNeighbour), FootpathNeighbourCompare);
2015-07-07 20:09:12 +02:00
}
2022-10-04 08:51:27 +02:00
static TileElement* FootpathGetElement(const CoordsXYRangedZ& footpathPos, int32_t direction)
2015-07-08 07:58:48 +02:00
{
TileElement* tileElement = MapGetFirstElementAt(footpathPos);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return nullptr;
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Path)
continue;
if (footpathPos.clearanceZ == tileElement->GetBaseZ())
2018-06-22 23:17:03 +02:00
{
2018-09-16 16:17:35 +02:00
if (tileElement->AsPath()->IsSloped())
2018-06-22 23:17:03 +02:00
{
auto slope = tileElement->AsPath()->GetSlopeDirection();
if (slope != direction)
break;
}
2017-10-31 14:03:45 +01:00
return tileElement;
}
if (footpathPos.baseZ == tileElement->GetBaseZ())
2018-06-22 23:17:03 +02:00
{
2018-09-16 16:17:35 +02:00
if (!tileElement->AsPath()->IsSloped())
break;
auto slope = DirectionReverse(tileElement->AsPath()->GetSlopeDirection());
if (slope != direction)
break;
2017-10-31 14:03:45 +01:00
return tileElement;
}
} while (!(tileElement++)->IsLastForTile());
2018-01-29 17:06:01 +01:00
return nullptr;
2015-07-08 07:58:48 +02:00
}
/**
* Attempt to connect a newly disconnected queue tile to the specified path tile
*/
2022-10-04 08:51:27 +02:00
static bool FootpathReconnectQueueToPath(
const CoordsXY& footpathPos, TileElement* tileElement, int32_t action, int32_t direction)
2018-06-22 23:17:03 +02:00
{
if (((tileElement->AsPath()->GetEdges() & (1 << direction)) == 0) ^ (action < 0))
return false;
auto targetQueuePos = footpathPos + CoordsDirectionDelta[direction];
if (action < 0)
{
2022-10-04 08:51:27 +02:00
if (WallInTheWay({ footpathPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() }, direction))
return false;
2022-10-04 08:51:27 +02:00
if (WallInTheWay(
{ targetQueuePos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() }, DirectionReverse(direction)))
return false;
}
int32_t z = tileElement->GetBaseZ();
2022-10-04 08:51:27 +02:00
TileElement* targetFootpathElement = FootpathGetElement({ targetQueuePos, z - LAND_HEIGHT_STEP, z }, direction);
if (targetFootpathElement != nullptr && !targetFootpathElement->AsPath()->IsQueue())
2018-06-22 23:17:03 +02:00
{
auto targetQueueElement = targetFootpathElement->AsPath();
tileElement->AsPath()->SetSlopeDirection(0);
2018-06-22 23:17:03 +02:00
if (action > 0)
{
tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() & ~(1 << direction));
targetQueueElement->SetEdges(targetQueueElement->GetEdges() & ~(1 << (DirectionReverse(direction) & 3)));
2018-06-22 23:17:03 +02:00
if (action >= 2)
tileElement->AsPath()->SetSlopeDirection(direction);
}
2018-06-22 23:17:03 +02:00
else if (action < 0)
{
tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() | (1 << direction));
targetQueueElement->SetEdges(targetQueueElement->GetEdges() | (1 << (DirectionReverse(direction) & 3)));
}
2018-06-22 23:17:03 +02:00
if (action != 0)
MapInvalidateTileFull(targetQueuePos);
return true;
}
return false;
2015-07-08 07:58:48 +02:00
}
2022-10-04 08:51:27 +02:00
static bool FootpathDisconnectQueueFromPath(const CoordsXY& footpathPos, TileElement* tileElement, int32_t action)
2018-06-22 23:17:03 +02:00
{
2018-10-03 10:57:29 +02:00
if (!tileElement->AsPath()->IsQueue())
2018-06-22 23:17:03 +02:00
return false;
2015-07-08 07:58:48 +02:00
2018-09-16 16:17:35 +02:00
if (tileElement->AsPath()->IsSloped())
2018-06-22 23:17:03 +02:00
return false;
2015-07-08 07:58:48 +02:00
uint8_t c = connected_path_count[tileElement->AsPath()->GetEdges()];
2018-06-22 23:17:03 +02:00
if ((action < 0) ? (c >= 2) : (c < 2))
return false;
2015-07-08 07:58:48 +02:00
2018-06-22 23:17:03 +02:00
if (action < 0)
{
uint8_t direction = tileElement->AsPath()->GetSlopeDirection();
2022-10-04 08:51:27 +02:00
if (FootpathReconnectQueueToPath(footpathPos, tileElement, action, direction))
return true;
}
2015-07-08 07:58:48 +02:00
for (Direction direction : ALL_DIRECTIONS)
2018-06-22 23:17:03 +02:00
{
if ((action < 0) && (direction == tileElement->AsPath()->GetSlopeDirection()))
continue;
2022-10-04 08:51:27 +02:00
if (FootpathReconnectQueueToPath(footpathPos, tileElement, action, direction))
return true;
}
2015-07-08 07:58:48 +02:00
return false;
2015-07-08 07:58:48 +02:00
}
2015-07-07 20:09:12 +02:00
/**
2015-10-20 20:16:30 +02:00
*
2015-07-07 20:09:12 +02:00
* rct2: 0x006A6D7E
*/
2023-01-17 19:05:14 +01:00
static void Loc6A6FD2(const CoordsXYZ& initialTileElementPos, int32_t direction, TileElement* initialTileElement, bool query)
{
2021-12-11 00:39:39 +01:00
if ((initialTileElement)->GetType() == TileElementType::Path)
{
if (!query)
{
initialTileElement->AsPath()->SetEdges(initialTileElement->AsPath()->GetEdges() | (1 << direction));
MapInvalidateElement(initialTileElementPos, initialTileElement);
}
}
}
2023-01-17 19:05:14 +01:00
static void Loc6A6F1F(
const CoordsXYZ& initialTileElementPos, int32_t direction, TileElement* tileElement, TileElement* initialTileElement,
const CoordsXY& targetPos, int32_t flags, bool query, FootpathNeighbourList* neighbourList)
{
if (query)
{
if (WallInTheWay({ targetPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() }, DirectionReverse(direction)))
{
return;
}
if (tileElement->AsPath()->IsQueue())
{
if (connected_path_count[tileElement->AsPath()->GetEdges()] < 2)
{
2022-10-04 08:51:27 +02:00
FootpathNeighbourListPush(
neighbourList, 4, direction, tileElement->AsPath()->GetRideIndex(),
tileElement->AsPath()->GetStationIndex());
}
else
{
2021-12-11 00:39:39 +01:00
if ((initialTileElement)->GetType() == TileElementType::Path && initialTileElement->AsPath()->IsQueue())
{
2022-10-04 08:51:27 +02:00
if (FootpathDisconnectQueueFromPath(targetPos, tileElement, 0))
{
2022-10-04 08:51:27 +02:00
FootpathNeighbourListPush(
neighbourList, 3, direction, tileElement->AsPath()->GetRideIndex(),
tileElement->AsPath()->GetStationIndex());
}
}
}
}
else
{
2022-10-04 08:51:27 +02:00
FootpathNeighbourListPush(neighbourList, 2, direction, RideId::GetNull(), StationIndex::GetNull());
}
}
else
{
2022-10-04 08:51:27 +02:00
FootpathDisconnectQueueFromPath(targetPos, tileElement, 1 + ((flags >> 6) & 1));
tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() | (1 << DirectionReverse(direction)));
if (tileElement->AsPath()->IsQueue())
{
2022-10-04 08:51:27 +02:00
FootpathQueueChainPush(tileElement->AsPath()->GetRideIndex());
}
}
if (!(flags & (GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED)))
{
2022-10-04 08:51:27 +02:00
FootpathInterruptPeeps({ targetPos, tileElement->GetBaseZ() });
}
MapInvalidateElement(targetPos, tileElement);
2023-01-17 19:05:14 +01:00
Loc6A6FD2(initialTileElementPos, direction, initialTileElement, query);
}
2023-01-17 19:05:14 +01:00
static void Loc6A6D7E(
const CoordsXYZ& initialTileElementPos, int32_t direction, TileElement* initialTileElement, int32_t flags, bool query,
FootpathNeighbourList* neighbourList)
2018-06-22 23:17:03 +02:00
{
auto targetPos = CoordsXY{ initialTileElementPos } + CoordsDirectionDelta[direction];
if (((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode) && MapIsEdge(targetPos))
2018-06-22 23:17:03 +02:00
{
if (query)
{
2022-10-04 08:51:27 +02:00
FootpathNeighbourListPush(neighbourList, 7, direction, RideId::GetNull(), StationIndex::GetNull());
}
2023-01-17 19:05:14 +01:00
Loc6A6FD2(initialTileElementPos, direction, initialTileElement, query);
2018-06-22 23:17:03 +02:00
}
else
{
TileElement* tileElement = MapGetFirstElementAt(targetPos);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return;
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
switch (tileElement->GetType())
2018-06-22 23:17:03 +02:00
{
2021-12-11 00:39:39 +01:00
case TileElementType::Path:
if (tileElement->GetBaseZ() == initialTileElementPos.z)
2018-06-22 23:17:03 +02:00
{
if (!tileElement->AsPath()->IsSloped() || tileElement->AsPath()->GetSlopeDirection() == direction)
2018-06-22 23:17:03 +02:00
{
2023-01-17 19:05:14 +01:00
Loc6A6F1F(
initialTileElementPos, direction, tileElement, initialTileElement, targetPos, flags, query,
neighbourList);
2018-06-22 23:17:03 +02:00
}
return;
}
2021-09-15 22:22:15 +02:00
if (tileElement->GetBaseZ() == initialTileElementPos.z - LAND_HEIGHT_STEP)
2018-06-22 23:17:03 +02:00
{
if (tileElement->AsPath()->IsSloped()
&& tileElement->AsPath()->GetSlopeDirection() == DirectionReverse(direction))
2018-06-22 23:17:03 +02:00
{
2023-01-17 19:05:14 +01:00
Loc6A6F1F(
initialTileElementPos, direction, tileElement, initialTileElement, targetPos, flags, query,
neighbourList);
2018-06-22 23:17:03 +02:00
}
return;
}
2018-06-22 23:17:03 +02:00
break;
2021-12-11 00:39:39 +01:00
case TileElementType::Track:
if (initialTileElementPos.z == tileElement->GetBaseZ())
2018-06-22 23:17:03 +02:00
{
auto ride = GetRide(tileElement->AsTrack()->GetRideIndex());
if (ride == nullptr)
{
continue;
}
if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE))
2018-06-22 23:17:03 +02:00
{
continue;
}
const auto trackType = tileElement->AsTrack()->GetTrackType();
const uint8_t trackSequence = tileElement->AsTrack()->GetSequenceIndex();
2021-08-27 23:49:32 +02:00
const auto& ted = GetTrackElementDescriptor(trackType);
if (!(ted.SequenceProperties[trackSequence] & TRACK_SEQUENCE_FLAG_CONNECTS_TO_PATH))
2018-06-22 23:17:03 +02:00
{
return;
}
uint16_t dx = DirectionReverse((direction - tileElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK);
if (!(ted.SequenceProperties[trackSequence] & (1 << dx)))
2018-06-22 23:17:03 +02:00
{
return;
}
if (query)
{
2022-10-04 08:51:27 +02:00
FootpathNeighbourListPush(
neighbourList, 1, direction, tileElement->AsTrack()->GetRideIndex(), StationIndex::GetNull());
2018-06-22 23:17:03 +02:00
}
2023-01-17 19:05:14 +01:00
Loc6A6FD2(initialTileElementPos, direction, initialTileElement, query);
return;
}
2018-06-22 23:17:03 +02:00
break;
2021-12-11 00:39:39 +01:00
case TileElementType::Entrance:
if (initialTileElementPos.z == tileElement->GetBaseZ())
2018-06-22 23:17:03 +02:00
{
if (entrance_has_direction(
*(tileElement->AsEntrance()), DirectionReverse(direction - tileElement->GetDirection())))
2018-06-22 23:17:03 +02:00
{
if (query)
{
2022-10-04 08:51:27 +02:00
FootpathNeighbourListPush(
2018-09-26 12:30:27 +02:00
neighbourList, 8, direction, tileElement->AsEntrance()->GetRideIndex(),
tileElement->AsEntrance()->GetStationIndex());
2018-06-22 23:17:03 +02:00
}
else
{
2018-09-26 12:13:44 +02:00
if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_PARK_ENTRANCE)
2018-06-22 23:17:03 +02:00
{
2022-10-04 08:51:27 +02:00
FootpathQueueChainPush(tileElement->AsEntrance()->GetRideIndex());
2018-06-22 23:17:03 +02:00
}
}
2023-01-17 19:05:14 +01:00
Loc6A6FD2(initialTileElementPos, direction, initialTileElement, query);
return;
}
}
2018-06-22 23:17:03 +02:00
break;
default:
break;
}
2015-07-07 20:09:12 +02:00
} while (!(tileElement++)->IsLastForTile());
}
2015-07-07 20:09:12 +02:00
}
2023-01-17 19:05:14 +01:00
// TODO: Change this into a simple check that validates if the direction should be fully checked with Loc6A6D7E and move the
// calling of Loc6A6D7E into the parent function.
static void Loc6A6C85(
const CoordsXYE& tileElementPos, int32_t direction, int32_t flags, bool query, FootpathNeighbourList* neighbourList)
2018-06-22 23:17:03 +02:00
{
if (query
2022-10-04 08:51:27 +02:00
&& WallInTheWay(
{ tileElementPos, tileElementPos.element->GetBaseZ(), tileElementPos.element->GetClearanceZ() }, direction))
return;
2021-12-11 00:39:39 +01:00
if (tileElementPos.element->GetType() == TileElementType::Entrance)
2018-06-22 23:17:03 +02:00
{
if (!entrance_has_direction(
*(tileElementPos.element->AsEntrance()), direction - tileElementPos.element->GetDirection()))
2018-06-22 23:17:03 +02:00
{
return;
}
}
2021-12-11 00:39:39 +01:00
if (tileElementPos.element->GetType() == TileElementType::Track)
2018-06-22 23:17:03 +02:00
{
auto ride = GetRide(tileElementPos.element->AsTrack()->GetRideIndex());
if (ride == nullptr)
2018-06-22 23:17:03 +02:00
{
return;
}
if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE))
{
return;
}
const auto trackType = tileElementPos.element->AsTrack()->GetTrackType();
const uint8_t trackSequence = tileElementPos.element->AsTrack()->GetSequenceIndex();
2021-08-27 23:49:32 +02:00
const auto& ted = GetTrackElementDescriptor(trackType);
if (!(ted.SequenceProperties[trackSequence] & TRACK_SEQUENCE_FLAG_CONNECTS_TO_PATH))
2018-06-22 23:17:03 +02:00
{
return;
}
uint16_t dx = (direction - tileElementPos.element->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK;
if (!(ted.SequenceProperties[trackSequence] & (1 << dx)))
2018-06-22 23:17:03 +02:00
{
return;
}
}
auto pos = CoordsXYZ{ tileElementPos, tileElementPos.element->GetBaseZ() };
2021-12-11 00:39:39 +01:00
if (tileElementPos.element->GetType() == TileElementType::Path)
2018-06-22 23:17:03 +02:00
{
if (tileElementPos.element->AsPath()->IsSloped())
2018-06-22 23:17:03 +02:00
{
if ((tileElementPos.element->AsPath()->GetSlopeDirection() - direction) & 1)
2018-06-22 23:17:03 +02:00
{
return;
}
if (tileElementPos.element->AsPath()->GetSlopeDirection() == direction)
2018-06-22 23:17:03 +02:00
{
pos.z += LAND_HEIGHT_STEP;
}
}
}
2023-01-17 19:05:14 +01:00
Loc6A6D7E(pos, direction, tileElementPos.element, flags, query, neighbourList);
2015-07-07 20:09:12 +02:00
}
/**
2015-10-20 20:16:30 +02:00
*
* rct2: 0x006A6C66
*/
2022-10-04 08:51:27 +02:00
void FootpathConnectEdges(const CoordsXY& footpathPos, TileElement* tileElement, int32_t flags)
{
FootpathNeighbourList neighbourList;
FootpathNeighbour neighbour;
2022-10-04 08:51:27 +02:00
FootpathUpdateQueueChains();
2022-10-04 08:51:27 +02:00
FootpathNeighbourListInit(&neighbourList);
2022-10-04 08:51:27 +02:00
FootpathUpdateQueueEntranceBanner(footpathPos, tileElement);
for (Direction direction : ALL_DIRECTIONS)
2018-06-22 23:17:03 +02:00
{
2023-01-17 19:05:14 +01:00
Loc6A6C85({ footpathPos, tileElement }, direction, flags, true, &neighbourList);
}
2022-10-04 08:51:27 +02:00
FoopathNeighbourListSort(&neighbourList);
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() == TileElementType::Path && tileElement->AsPath()->IsQueue())
2018-06-22 23:17:03 +02:00
{
RideId rideIndex = RideId::GetNull();
StationIndex entranceIndex = StationIndex::GetNull();
2018-06-22 23:17:03 +02:00
for (size_t i = 0; i < neighbourList.count; i++)
{
if (!neighbourList.items[i].ride_index.IsNull())
2018-06-22 23:17:03 +02:00
{
if (rideIndex.IsNull())
2018-06-22 23:17:03 +02:00
{
rideIndex = neighbourList.items[i].ride_index;
entranceIndex = neighbourList.items[i].entrance_index;
2018-06-22 23:17:03 +02:00
}
else if (rideIndex != neighbourList.items[i].ride_index)
{
2022-10-04 08:51:27 +02:00
FootpathNeighbourListRemove(&neighbourList, i);
2018-06-22 23:17:03 +02:00
}
else if (
rideIndex == neighbourList.items[i].ride_index && entranceIndex != neighbourList.items[i].entrance_index
&& !neighbourList.items[i].entrance_index.IsNull())
2018-06-22 23:17:03 +02:00
{
2022-10-04 08:51:27 +02:00
FootpathNeighbourListRemove(&neighbourList, i);
}
}
}
neighbourList.count = std::min<size_t>(neighbourList.count, 2);
}
2022-10-04 08:51:27 +02:00
while (FootpathNeighbourListPop(&neighbourList, &neighbour))
2018-06-22 23:17:03 +02:00
{
2023-01-17 19:05:14 +01:00
Loc6A6C85({ footpathPos, tileElement }, neighbour.direction, flags, false, nullptr);
}
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() == TileElementType::Path)
2018-06-22 23:17:03 +02:00
{
2022-10-04 08:51:27 +02:00
FootpathConnectCorners(footpathPos, tileElement->AsPath());
}
}
/**
2015-10-20 20:16:30 +02:00
*
* rct2: 0x006A742F
*/
2022-10-04 08:51:27 +02:00
void FootpathChainRideQueue(
RideId rideIndex, StationIndex entranceIndex, const CoordsXY& initialFootpathPos, TileElement* const initialTileElement,
int32_t direction)
2015-02-16 16:16:28 +01:00
{
2018-11-01 13:53:50 +01:00
TileElement *lastPathElement, *lastQueuePathElement;
auto tileElement = initialTileElement;
auto curQueuePos = initialFootpathPos;
auto lastPath = curQueuePos;
int32_t baseZ = tileElement->GetBaseZ();
int32_t lastPathDirection = direction;
2018-01-29 17:06:01 +01:00
lastPathElement = nullptr;
lastQueuePathElement = nullptr;
2018-06-22 23:17:03 +02:00
for (;;)
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() == TileElementType::Path)
2018-06-22 23:17:03 +02:00
{
2017-10-31 14:03:45 +01:00
lastPathElement = tileElement;
lastPath = curQueuePos;
lastPathDirection = direction;
2018-09-16 16:17:35 +02:00
if (tileElement->AsPath()->IsSloped())
2018-06-22 23:17:03 +02:00
{
if (tileElement->AsPath()->GetSlopeDirection() == direction)
2018-06-22 23:17:03 +02:00
{
baseZ += LAND_HEIGHT_STEP;
}
}
}
auto targetQueuePos = curQueuePos + CoordsDirectionDelta[direction];
tileElement = MapGetFirstElementAt(targetQueuePos);
2020-09-12 13:41:10 +02:00
bool foundQueue = false;
if (tileElement != nullptr)
2018-06-22 23:17:03 +02:00
{
do
2018-06-22 23:17:03 +02:00
{
if (lastQueuePathElement == tileElement)
continue;
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Path)
continue;
if (tileElement->GetBaseZ() == baseZ)
2018-06-22 23:17:03 +02:00
{
if (tileElement->AsPath()->IsSloped())
{
if (tileElement->AsPath()->GetSlopeDirection() != direction)
break;
}
2020-09-12 13:41:10 +02:00
foundQueue = true;
2020-07-21 17:52:47 +02:00
break;
}
if (tileElement->GetBaseZ() == baseZ - LAND_HEIGHT_STEP)
{
if (!tileElement->AsPath()->IsSloped())
break;
if (DirectionReverse(tileElement->AsPath()->GetSlopeDirection()) != direction)
break;
baseZ -= LAND_HEIGHT_STEP;
2020-09-12 13:41:10 +02:00
foundQueue = true;
2020-07-21 17:52:47 +02:00
break;
}
} while (!(tileElement++)->IsLastForTile());
}
2020-09-12 13:41:10 +02:00
if (!foundQueue)
break;
2018-10-03 10:57:29 +02:00
if (tileElement->AsPath()->IsQueue())
2018-06-22 23:17:03 +02:00
{
// Fix #2051: Stop queue paths that are already connected to two other tiles
// from connecting to the tile we are coming from.
int32_t edges = tileElement->AsPath()->GetEdges();
int32_t numEdges = BitCount(edges);
2018-06-22 23:17:03 +02:00
if (numEdges >= 2)
{
int32_t requiredEdgeMask = 1 << DirectionReverse(direction);
2018-06-22 23:17:03 +02:00
if (!(edges & requiredEdgeMask))
{
break;
}
}
tileElement->AsPath()->SetHasQueueBanner(false);
tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() | (1 << DirectionReverse(direction)));
tileElement->AsPath()->SetRideIndex(rideIndex);
tileElement->AsPath()->SetStationIndex(entranceIndex);
curQueuePos = targetQueuePos;
MapInvalidateElement(targetQueuePos, tileElement);
2018-06-22 23:17:03 +02:00
if (lastQueuePathElement == nullptr)
{
2017-10-31 14:03:45 +01:00
lastQueuePathElement = tileElement;
}
if (tileElement->AsPath()->GetEdges() & (1 << direction))
continue;
direction = (direction + 1) & 3;
if (tileElement->AsPath()->GetEdges() & (1 << direction))
continue;
direction = DirectionReverse(direction);
if (tileElement->AsPath()->GetEdges() & (1 << direction))
continue;
}
break;
}
if (!rideIndex.IsNull() && lastPathElement != nullptr)
2018-06-22 23:17:03 +02:00
{
2018-10-03 10:57:29 +02:00
if (lastPathElement->AsPath()->IsQueue())
2018-06-22 23:17:03 +02:00
{
lastPathElement->AsPath()->SetHasQueueBanner(true);
lastPathElement->AsPath()->SetQueueBannerDirection(lastPathDirection); // set the ride sign direction
MapAnimationCreate(MAP_ANIMATION_TYPE_QUEUE_BANNER, { lastPath, lastPathElement->GetBaseZ() });
}
}
2016-05-17 22:29:05 +02:00
}
2015-07-07 03:35:53 +02:00
2022-10-04 08:51:27 +02:00
void FootpathQueueChainReset()
2016-05-17 22:29:05 +02:00
{
_footpathQueueChainNext = _footpathQueueChain;
2016-05-17 22:29:05 +02:00
}
/**
*
* rct2: 0x006A76E9
*/
2022-10-04 08:51:27 +02:00
void FootpathQueueChainPush(RideId rideIndex)
2016-05-17 22:29:05 +02:00
{
if (!rideIndex.IsNull())
2018-06-22 23:17:03 +02:00
{
auto* lastSlot = _footpathQueueChain + std::size(_footpathQueueChain) - 1;
2018-06-22 23:17:03 +02:00
if (_footpathQueueChainNext <= lastSlot)
{
*_footpathQueueChainNext++ = rideIndex;
}
}
2015-02-16 16:16:28 +01:00
}
/**
2015-10-20 20:16:30 +02:00
*
* rct2: 0x006A759F
*/
2022-10-04 08:51:27 +02:00
void FootpathUpdateQueueChains()
{
for (auto* queueChainPtr = _footpathQueueChain; queueChainPtr < _footpathQueueChainNext; queueChainPtr++)
{
2022-01-19 14:17:11 +01:00
RideId rideIndex = *queueChainPtr;
auto ride = GetRide(rideIndex);
if (ride == nullptr)
continue;
2022-01-28 10:04:16 +01:00
for (const auto& station : ride->GetStations())
{
2022-01-28 10:04:16 +01:00
if (station.Entrance.IsNull())
continue;
TileElement* tileElement = MapGetFirstElementAt(station.Entrance);
if (tileElement != nullptr)
{
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Entrance)
continue;
if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_RIDE_ENTRANCE)
continue;
if (tileElement->AsEntrance()->GetRideIndex() != rideIndex)
continue;
Direction direction = DirectionReverse(tileElement->GetDirection());
2022-10-04 08:51:27 +02:00
FootpathChainRideQueue(
2022-01-28 10:04:16 +01:00
rideIndex, ride->GetStationIndex(&station), station.Entrance.ToCoordsXY(), tileElement, direction);
} while (!(tileElement++)->IsLastForTile());
}
}
}
}
2015-07-05 17:47:22 +02:00
2015-07-06 15:40:37 +02:00
/**
*
* rct2: 0x0069ADBD
*/
2022-10-04 08:51:27 +02:00
static void FootpathFixOwnership(const CoordsXY& mapPos)
2015-07-05 17:47:22 +02:00
{
const auto* surfaceElement = MapGetSurfaceElementAt(mapPos);
uint16_t ownership;
// Unlikely to be NULL unless deliberate.
2018-01-29 17:06:01 +01:00
if (surfaceElement != nullptr)
{
// If the tile is not safe to own construction rights of, erase them.
if (CheckMaxAllowableLandRightsForTile({ mapPos, surfaceElement->BaseHeight << 3 }) == OWNERSHIP_UNOWNED)
{
ownership = OWNERSHIP_UNOWNED;
}
// If the tile is safe to own construction rights of, do not erase construction rights.
else
{
ownership = surfaceElement->GetOwnership();
// You can't own the entrance path.
if (ownership == OWNERSHIP_OWNED || ownership == OWNERSHIP_AVAILABLE)
{
ownership = OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED;
}
}
}
else
{
ownership = OWNERSHIP_UNOWNED;
}
auto landSetRightsAction = LandSetRightsAction(mapPos, LandSetRightSetting::SetOwnershipWithChecks, ownership);
2019-03-17 09:25:51 +01:00
landSetRightsAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND);
GameActions::Execute(&landSetRightsAction);
2015-07-05 17:47:22 +02:00
}
2022-10-04 08:51:27 +02:00
static bool GetNextDirection(int32_t edges, int32_t* direction)
2015-07-05 17:47:22 +02:00
{
int32_t index = UtilBitScanForward(edges);
if (index == -1)
return false;
2015-07-05 17:47:22 +02:00
*direction = index;
return true;
2015-07-05 17:47:22 +02:00
}
/**
*
* rct2: 0x0069AC1A
* @param flags (1 << 0): Ignore queues
* (1 << 5): Unown
* (1 << 7): Ignore no entry signs
2015-07-05 17:47:22 +02:00
*/
2022-10-04 08:51:27 +02:00
static int32_t FootpathIsConnectedToMapEdgeHelper(CoordsXYZ footpathPos, int32_t direction, int32_t flags)
2018-06-22 23:17:03 +02:00
{
int32_t returnVal = FOOTPATH_SEARCH_INCOMPLETE;
struct TileState
{
bool processed = false;
CoordsXYZ footpathPos;
int32_t direction;
int32_t level;
int32_t distanceFromJunction;
int32_t junctionTolerance;
};
// Vector of all of the child tile elements for us to explore
std::vector<TileState> tiles;
TileElement* tileElement = nullptr;
int numPendingTiles = 0;
TileState currentTile = { false, footpathPos, direction, 0, 0, 16 };
// Captures the current state of the variables and stores them in tiles vector for iteration later
auto CaptureCurrentTileState = [&tiles, &numPendingTiles](TileState t_currentTile) -> void {
// Search for an entry of this tile in our list already
for (const TileState& tile : tiles)
{
if (tile.footpathPos == t_currentTile.footpathPos && tile.direction == t_currentTile.direction)
return;
}
// If we get here we did not find it, so insert the tile into our list
tiles.push_back(t_currentTile);
++numPendingTiles;
};
// Loads the next tile to visit into our variables
auto LoadNextTileElement = [&tiles, &numPendingTiles](TileState& t_currentTile) -> void {
// Do not continue if there are no tiles in the list
if (tiles.size() == 0)
return;
// Find the next unprocessed tile
for (size_t tileIndex = tiles.size() - 1; tileIndex > 0; --tileIndex)
2018-06-22 23:17:03 +02:00
{
if (tiles[tileIndex].processed)
2018-06-22 23:17:03 +02:00
continue;
--numPendingTiles;
t_currentTile = tiles[tileIndex];
tiles[tileIndex].processed = true;
return;
2018-06-22 23:17:03 +02:00
}
// Default to tile 0
--numPendingTiles;
t_currentTile = tiles[0];
tiles[0].processed = true;
};
// Encapsulate the tile skipping logic to make do-while more readable
auto SkipTileElement = [](int32_t ste_flags, TileElement* ste_tileElement, int32_t& ste_slopeDirection,
int32_t ste_direction, const CoordsXYZ& ste_targetPos) {
2021-12-11 00:39:39 +01:00
if (ste_tileElement->GetType() != TileElementType::Path)
return true;
if (ste_tileElement->AsPath()->IsSloped()
&& (ste_slopeDirection = ste_tileElement->AsPath()->GetSlopeDirection()) != ste_direction)
2018-06-22 23:17:03 +02:00
{
if (DirectionReverse(ste_slopeDirection) != ste_direction)
return true;
if (ste_tileElement->GetBaseZ() + PATH_HEIGHT_STEP != ste_targetPos.z)
return true;
}
else if (ste_tileElement->GetBaseZ() != ste_targetPos.z)
return true;
if (!(ste_flags & FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_QUEUES))
if (ste_tileElement->AsPath()->IsQueue())
return true;
return false;
};
int32_t edges, slopeDirection;
// Capture the current tile state to begin the loop
CaptureCurrentTileState(currentTile);
// Loop on this until all tiles are processed or we return
while (numPendingTiles > 0)
2018-06-22 23:17:03 +02:00
{
LoadNextTileElement(currentTile);
CoordsXYZ targetPos = CoordsXYZ{ CoordsXY{ currentTile.footpathPos } + CoordsDirectionDelta[currentTile.direction],
currentTile.footpathPos.z };
if (++currentTile.level > 250)
return FOOTPATH_SEARCH_TOO_COMPLEX;
// Return immediately if we are at the edge of the map and not unowning
// Or if we are unowning and have no tiles left
if ((MapIsEdge(targetPos) && !(flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN)))
2018-06-22 23:17:03 +02:00
{
return FOOTPATH_SEARCH_SUCCESS;
}
tileElement = MapGetFirstElementAt(targetPos);
if (tileElement == nullptr)
return currentTile.level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : FOOTPATH_SEARCH_INCOMPLETE;
// Loop while there are unvisited TileElements at targetPos
do
2018-06-22 23:17:03 +02:00
{
if (SkipTileElement(flags, tileElement, slopeDirection, currentTile.direction, targetPos))
continue;
// Unown the footpath if needed
if (flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN)
2022-10-04 08:51:27 +02:00
FootpathFixOwnership(targetPos);
edges = tileElement->AsPath()->GetEdges();
currentTile.direction = DirectionReverse(currentTile.direction);
if (!tileElement->IsLastForTile() && !(flags & FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_NO_ENTRY))
2018-06-22 23:17:03 +02:00
{
int elementIndex = 1;
// Loop over all elements and cull appropriate edges
do
2018-06-22 23:17:03 +02:00
{
2021-12-11 00:39:39 +01:00
if (tileElement[elementIndex].GetType() == TileElementType::Path)
2018-06-22 23:17:03 +02:00
break;
2021-12-11 00:39:39 +01:00
if (tileElement[elementIndex].GetType() != TileElementType::Banner)
{
continue;
}
edges &= tileElement[elementIndex].AsBanner()->GetAllowedEdges();
} while (!tileElement[elementIndex++].IsLastForTile());
}
2015-07-05 17:47:22 +02:00
// Exclude the direction we came from
targetPos.z = tileElement->GetBaseZ();
edges &= ~(1 << currentTile.direction);
2022-10-04 08:51:27 +02:00
if (!GetNextDirection(edges, &currentTile.direction))
break;
edges &= ~(1 << currentTile.direction);
if (edges == 0)
2018-06-22 23:17:03 +02:00
{
// Only possible direction to go
if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == currentTile.direction)
targetPos.z += PATH_HEIGHT_STEP;
// Prepare the next iteration
currentTile.footpathPos = targetPos;
++currentTile.distanceFromJunction;
CaptureCurrentTileState(currentTile);
}
else
2018-06-22 23:17:03 +02:00
{
// We have reached a junction
--currentTile.junctionTolerance;
if (currentTile.distanceFromJunction != 0)
{
--currentTile.junctionTolerance;
}
if (currentTile.junctionTolerance < 0 && !(flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN))
{
returnVal = FOOTPATH_SEARCH_TOO_COMPLEX;
break;
}
// Loop until there are no more directions we can go
do
{
edges &= ~(1 << currentTile.direction);
if (tileElement->AsPath()->IsSloped()
&& tileElement->AsPath()->GetSlopeDirection() == currentTile.direction)
{
targetPos.z += PATH_HEIGHT_STEP;
}
// Add each possible path to the list of pending tiles
currentTile.footpathPos = targetPos;
currentTile.distanceFromJunction = 0;
CaptureCurrentTileState(currentTile);
2022-10-04 08:51:27 +02:00
} while (GetNextDirection(edges, &currentTile.direction));
}
break;
} while (!(tileElement++)->IsLastForTile());
// Return success if we have unowned all tiles in our pending list
if ((flags & FOOTPATH_CONNECTED_MAP_EDGE_UNOWN) && numPendingTiles <= 0)
{
return FOOTPATH_SEARCH_SUCCESS;
}
}
return currentTile.level == 1 ? FOOTPATH_SEARCH_NOT_FOUND : returnVal;
2015-07-05 17:47:22 +02:00
}
// TODO: Use GAME_COMMAND_FLAGS
2022-10-04 08:51:27 +02:00
int32_t FootpathIsConnectedToMapEdge(const CoordsXYZ& footpathPos, int32_t direction, int32_t flags)
2015-07-05 17:47:22 +02:00
{
2020-04-20 10:39:26 +02:00
flags |= FOOTPATH_CONNECTED_MAP_EDGE_IGNORE_QUEUES;
2022-10-04 08:51:27 +02:00
return FootpathIsConnectedToMapEdgeHelper(footpathPos, direction, flags);
2015-07-05 17:47:22 +02:00
}
2018-09-16 16:17:35 +02:00
bool PathElement::IsSloped() const
2017-12-08 16:47:54 +01:00
{
2020-03-04 18:43:09 +01:00
return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_IS_SLOPED) != 0;
2017-12-08 16:47:54 +01:00
}
2018-09-16 16:17:35 +02:00
void PathElement::SetSloped(bool isSloped)
2017-12-08 16:47:54 +01:00
{
2020-03-04 18:43:09 +01:00
Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_IS_SLOPED;
2017-12-08 16:47:54 +01:00
if (isSloped)
2020-03-04 18:43:09 +01:00
Flags2 |= FOOTPATH_ELEMENT_FLAGS2_IS_SLOPED;
2017-12-08 16:47:54 +01:00
}
bool PathElement::HasJunctionRailings() const
{
return Flags2 & FOOTPATH_ELEMENT_FLAGS2_HAS_JUNCTION_RAILINGS;
}
void PathElement::SetJunctionRailings(bool hasJunctionRailings)
{
Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_HAS_JUNCTION_RAILINGS;
if (hasJunctionRailings)
Flags2 |= FOOTPATH_ELEMENT_FLAGS2_HAS_JUNCTION_RAILINGS;
}
Direction PathElement::GetSlopeDirection() const
2015-07-05 17:47:22 +02:00
{
2020-03-04 18:43:09 +01:00
return SlopeDirection;
}
void PathElement::SetSlopeDirection(Direction newSlope)
{
2020-03-04 18:43:09 +01:00
SlopeDirection = newSlope;
2015-07-05 17:47:22 +02:00
}
2018-10-03 10:57:29 +02:00
bool PathElement::IsQueue() const
2015-07-05 17:47:22 +02:00
{
return (Type & FOOTPATH_ELEMENT_TYPE_FLAG_IS_QUEUE) != 0;
}
2018-10-03 10:57:29 +02:00
void PathElement::SetIsQueue(bool isQueue)
{
Type &= ~FOOTPATH_ELEMENT_TYPE_FLAG_IS_QUEUE;
2018-10-03 10:57:29 +02:00
if (isQueue)
Type |= FOOTPATH_ELEMENT_TYPE_FLAG_IS_QUEUE;
}
bool PathElement::HasQueueBanner() const
{
2020-03-04 18:43:09 +01:00
return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_HAS_QUEUE_BANNER) != 0;
2015-07-05 17:47:22 +02:00
}
2015-07-06 23:12:45 +02:00
void PathElement::SetHasQueueBanner(bool hasQueueBanner)
{
2020-03-04 18:43:09 +01:00
Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_HAS_QUEUE_BANNER;
if (hasQueueBanner)
2020-03-04 18:43:09 +01:00
Flags2 |= FOOTPATH_ELEMENT_FLAGS2_HAS_QUEUE_BANNER;
}
bool PathElement::IsBroken() const
{
2020-03-04 21:47:34 +01:00
return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_BROKEN) != 0;
}
void PathElement::SetIsBroken(bool isBroken)
{
if (isBroken)
{
2020-03-04 21:47:34 +01:00
Flags2 |= FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_BROKEN;
}
else
{
2020-03-04 21:47:34 +01:00
Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_BROKEN;
}
}
bool PathElement::IsBlockedByVehicle() const
{
2020-03-04 21:47:34 +01:00
return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_BLOCKED_BY_VEHICLE) != 0;
}
void PathElement::SetIsBlockedByVehicle(bool isBlocked)
{
if (isBlocked)
{
2020-03-04 21:47:34 +01:00
Flags2 |= FOOTPATH_ELEMENT_FLAGS2_BLOCKED_BY_VEHICLE;
}
else
{
2020-03-04 21:47:34 +01:00
Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_BLOCKED_BY_VEHICLE;
}
}
::StationIndex PathElement::GetStationIndex() const
{
2020-03-04 18:43:09 +01:00
return StationIndex;
}
2020-03-04 18:43:09 +01:00
void PathElement::SetStationIndex(::StationIndex newStationIndex)
{
2020-03-04 18:43:09 +01:00
StationIndex = newStationIndex;
}
bool PathElement::IsWide() const
2015-07-06 23:12:45 +02:00
{
return (Type & FOOTPATH_ELEMENT_TYPE_FLAG_IS_WIDE) != 0;
2015-07-06 23:12:45 +02:00
}
void PathElement::SetWide(bool isWide)
{
Type &= ~FOOTPATH_ELEMENT_TYPE_FLAG_IS_WIDE;
2017-12-08 16:47:54 +01:00
if (isWide)
Type |= FOOTPATH_ELEMENT_TYPE_FLAG_IS_WIDE;
}
bool PathElement::HasAddition() const
{
2020-03-04 18:43:09 +01:00
return Additions != 0;
}
uint8_t PathElement::GetAddition() const
{
2020-03-04 18:43:09 +01:00
return Additions;
}
ObjectEntryIndex PathElement::GetAdditionEntryIndex() const
{
return GetAddition() - 1;
}
const PathBitEntry* PathElement::GetAdditionEntry() const
{
if (!HasAddition())
return nullptr;
return OpenRCT2::ObjectManager::GetObjectEntry<PathBitEntry>(GetAdditionEntryIndex());
}
void PathElement::SetAddition(uint8_t newAddition)
{
2020-03-04 18:43:09 +01:00
Additions = newAddition;
}
bool PathElement::AdditionIsGhost() const
{
2020-03-04 18:43:09 +01:00
return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_GHOST) != 0;
}
void PathElement::SetAdditionIsGhost(bool isGhost)
{
2020-03-04 18:43:09 +01:00
Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_GHOST;
if (isGhost)
2020-03-04 18:43:09 +01:00
Flags2 |= FOOTPATH_ELEMENT_FLAGS2_ADDITION_IS_GHOST;
}
ObjectEntryIndex PathElement::GetLegacyPathEntryIndex() const
{
if (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY)
return SurfaceIndex;
2021-09-15 22:22:15 +02:00
return OBJECT_ENTRY_INDEX_NULL;
2017-12-08 16:47:54 +01:00
}
const FootpathObject* PathElement::GetLegacyPathEntry() const
2021-09-10 09:13:20 +02:00
{
return GetLegacyFootpathEntry(GetLegacyPathEntryIndex());
2021-09-10 09:13:20 +02:00
}
void PathElement::SetLegacyPathEntryIndex(ObjectEntryIndex newIndex)
{
SurfaceIndex = newIndex;
RailingsIndex = OBJECT_ENTRY_INDEX_NULL;
Flags2 |= FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY;
}
bool PathElement::HasLegacyPathEntry() const
{
return (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY) != 0;
}
const PathSurfaceDescriptor* PathElement::GetSurfaceDescriptor() const
2019-01-02 19:52:53 +01:00
{
if (HasLegacyPathEntry())
{
const auto* legacyPathEntry = GetLegacyPathEntry();
if (legacyPathEntry == nullptr)
return nullptr;
if (IsQueue())
return &legacyPathEntry->GetQueueSurfaceDescriptor();
2019-01-02 19:52:53 +01:00
return &legacyPathEntry->GetPathSurfaceDescriptor();
}
const auto* surfaceEntry = GetSurfaceEntry();
if (surfaceEntry == nullptr)
return nullptr;
return &surfaceEntry->GetDescriptor();
2019-01-02 19:52:53 +01:00
}
const PathRailingsDescriptor* PathElement::GetRailingsDescriptor() const
{
if (HasLegacyPathEntry())
{
const auto* legacyPathEntry = GetLegacyPathEntry();
if (legacyPathEntry == nullptr)
return nullptr;
return &legacyPathEntry->GetPathRailingsDescriptor();
}
const auto* railingsEntry = GetRailingsEntry();
if (railingsEntry == nullptr)
return nullptr;
return &railingsEntry->GetDescriptor();
2017-12-08 16:47:54 +01:00
}
ObjectEntryIndex PathElement::GetSurfaceEntryIndex() const
2019-01-02 19:52:53 +01:00
{
if (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY)
return OBJECT_ENTRY_INDEX_NULL;
2021-09-15 22:22:15 +02:00
return SurfaceIndex;
}
const FootpathSurfaceObject* PathElement::GetSurfaceEntry() const
{
auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
return static_cast<FootpathSurfaceObject*>(objMgr.GetLoadedObject(ObjectType::FootpathSurface, GetSurfaceEntryIndex()));
}
void PathElement::SetSurfaceEntryIndex(ObjectEntryIndex newIndex)
{
SurfaceIndex = newIndex;
Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY;
}
ObjectEntryIndex PathElement::GetRailingsEntryIndex() const
{
if (Flags2 & FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY)
return OBJECT_ENTRY_INDEX_NULL;
2021-09-15 22:22:15 +02:00
return RailingsIndex;
}
const FootpathRailingsObject* PathElement::GetRailingsEntry() const
{
auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
return static_cast<FootpathRailingsObject*>(objMgr.GetLoadedObject(ObjectType::FootpathRailings, GetRailingsEntryIndex()));
}
void PathElement::SetRailingsEntryIndex(ObjectEntryIndex newEntryIndex)
{
RailingsIndex = newEntryIndex;
Flags2 &= ~FOOTPATH_ELEMENT_FLAGS2_LEGACY_PATH_ENTRY;
2019-01-02 19:52:53 +01:00
}
uint8_t PathElement::GetQueueBannerDirection() const
2017-12-08 16:47:54 +01:00
{
return ((Type & FOOTPATH_ELEMENT_TYPE_DIRECTION_MASK) >> 6);
2017-12-08 16:47:54 +01:00
}
void PathElement::SetQueueBannerDirection(uint8_t direction)
2017-12-08 16:47:54 +01:00
{
Type &= ~FOOTPATH_ELEMENT_TYPE_DIRECTION_MASK;
Type |= (direction << 6);
}
bool PathElement::ShouldDrawPathOverSupports() const
{
// TODO: make this an actual decision of the tile element.
return (GetRailingsDescriptor()->Flags & RAILING_ENTRY_FLAG_DRAW_PATH_OVER_SUPPORTS);
}
void PathElement::SetShouldDrawPathOverSupports(bool on)
{
LOG_VERBOSE("Setting 'draw path over supports' to %d", static_cast<size_t>(on));
}
2015-11-14 01:08:31 +01:00
/**
2018-06-22 23:17:03 +02:00
*
* rct2: 0x006A8B12
* clears the wide footpath flag for all footpaths
* at location
*/
2022-10-04 08:51:27 +02:00
static void FootpathClearWide(const CoordsXY& footpathPos)
2015-11-14 01:08:31 +01:00
{
TileElement* tileElement = MapGetFirstElementAt(footpathPos);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return;
2017-12-08 16:47:54 +01:00
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Path)
continue;
tileElement->AsPath()->SetWide(false);
2018-06-22 23:17:03 +02:00
} while (!(tileElement++)->IsLastForTile());
2015-11-14 01:08:31 +01:00
}
/**
2018-06-22 23:17:03 +02:00
*
* rct2: 0x006A8ACF
* returns footpath element if it can be made wide
* returns NULL if it can not be made wide
*/
2022-10-04 08:51:27 +02:00
static TileElement* FootpathCanBeWide(const CoordsXYZ& footpathPos)
2015-11-14 01:08:31 +01:00
{
TileElement* tileElement = MapGetFirstElementAt(footpathPos);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return nullptr;
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Path)
continue;
2020-03-13 12:03:43 +01:00
if (footpathPos.z != tileElement->GetBaseZ())
continue;
2018-10-03 10:57:29 +02:00
if (tileElement->AsPath()->IsQueue())
continue;
2018-09-16 16:17:35 +02:00
if (tileElement->AsPath()->IsSloped())
continue;
2017-10-31 14:03:45 +01:00
return tileElement;
} while (!(tileElement++)->IsLastForTile());
2015-11-14 01:08:31 +01:00
2018-01-29 17:06:01 +01:00
return nullptr;
2015-11-14 01:08:31 +01:00
}
/**
2018-06-22 23:17:03 +02:00
*
* rct2: 0x006A87BB
*/
2022-10-04 08:51:27 +02:00
void FootpathUpdatePathWideFlags(const CoordsXY& footpathPos)
2015-11-14 01:08:31 +01:00
{
if (MapIsLocationAtEdge(footpathPos))
return;
2022-10-04 08:51:27 +02:00
FootpathClearWide(footpathPos);
/* Rather than clearing the wide flag of the following tiles and
* checking the state of them later, leave them intact and assume
* they were cleared. Consequently only the wide flag for this single
* tile is modified by this update.
* This is important for avoiding glitches in pathfinding that occurs
* between the batches of updates to the path wide flags.
* Corresponding pathList[] indexes for the following tiles
* are: 2, 3, 4, 5.
* Note: indexes 3, 4, 5 are reset in the current call;
* index 2 is reset in the previous call. */
2018-06-22 23:17:03 +02:00
// x += 0x20;
2022-10-04 08:51:27 +02:00
// FootpathClearWide(x, y);
2018-06-22 23:17:03 +02:00
// y += 0x20;
2022-10-04 08:51:27 +02:00
// FootpathClearWide(x, y);
2018-06-22 23:17:03 +02:00
// x -= 0x20;
2022-10-04 08:51:27 +02:00
// FootpathClearWide(x, y);
2018-06-22 23:17:03 +02:00
// y -= 0x20;
TileElement* tileElement = MapGetFirstElementAt(footpathPos);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return;
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Path)
continue;
2018-10-03 10:57:29 +02:00
if (tileElement->AsPath()->IsQueue())
continue;
2018-09-16 16:17:35 +02:00
if (tileElement->AsPath()->IsSloped())
continue;
if (tileElement->AsPath()->GetEdges() == 0)
continue;
2020-03-13 12:03:43 +01:00
auto height = tileElement->GetBaseZ();
2023-01-17 19:05:14 +01:00
// pathList is a list of elements, set by Sub6A8ACF adjacent to x,y
// Spanned from 0x00F3EFA8 to 0x00F3EFC7 (8 elements) in the original
std::array<TileElement*, 8> pathList;
for (std::size_t direction = 0; direction < pathList.size(); ++direction)
{
2020-03-13 12:03:43 +01:00
auto footpathLoc = CoordsXYZ(footpathPos + CoordsDirectionDelta[direction], height);
2022-10-04 08:51:27 +02:00
pathList[direction] = FootpathCanBeWide(footpathLoc);
}
uint8_t pathConnections = 0;
if (tileElement->AsPath()->GetEdges() & EDGE_NW)
2018-06-22 23:17:03 +02:00
{
pathConnections |= FOOTPATH_CONNECTION_NW;
const auto* pathElement = std::get<3>(pathList);
if (pathElement != nullptr && pathElement->AsPath()->IsWide())
2018-06-22 23:17:03 +02:00
{
pathConnections &= ~FOOTPATH_CONNECTION_NW;
}
}
if (tileElement->AsPath()->GetEdges() & EDGE_NE)
2018-06-22 23:17:03 +02:00
{
pathConnections |= FOOTPATH_CONNECTION_NE;
const auto* pathElement = std::get<0>(pathList);
if (pathElement != nullptr && pathElement->AsPath()->IsWide())
2018-06-22 23:17:03 +02:00
{
pathConnections &= ~FOOTPATH_CONNECTION_NE;
}
}
if (tileElement->AsPath()->GetEdges() & EDGE_SE)
2018-06-22 23:17:03 +02:00
{
pathConnections |= FOOTPATH_CONNECTION_SE;
/* In the following:
* footpath_element_is_wide(pathList[1])
* is always false due to the tile update order
* in combination with reset tiles.
* Commented out since it will never occur. */
// if (pathList[1] != nullptr) {
// if (footpath_element_is_wide(pathList[1])) {
// pathConnections &= ~FOOTPATH_CONNECTION_SE;
// }
//}
}
if (tileElement->AsPath()->GetEdges() & EDGE_SW)
2018-06-22 23:17:03 +02:00
{
pathConnections |= FOOTPATH_CONNECTION_SW;
/* In the following:
* footpath_element_is_wide(pathList[2])
* is always false due to the tile update order
* in combination with reset tiles.
* Commented out since it will never occur. */
// if (pathList[2] != nullptr) {
// if (footpath_element_is_wide(pathList[2])) {
// pathConnections &= ~FOOTPATH_CONNECTION_SW;
// }
//}
}
if ((pathConnections & FOOTPATH_CONNECTION_NW) && std::get<3>(pathList) != nullptr
&& !std::get<3>(pathList)->AsPath()->IsWide())
2018-06-22 23:17:03 +02:00
{
constexpr uint8_t edgeMask1 = EDGE_SE | EDGE_SW;
const auto* pathElement0 = std::get<0>(pathList);
const auto* pathElement7 = std::get<7>(pathList);
if ((pathConnections & FOOTPATH_CONNECTION_NE) && pathElement7 != nullptr && !pathElement7->AsPath()->IsWide()
&& (pathElement7->AsPath()->GetEdges() & edgeMask1) == edgeMask1 && pathElement0 != nullptr
&& !pathElement0->AsPath()->IsWide())
2018-06-22 23:17:03 +02:00
{
pathConnections |= FOOTPATH_CONNECTION_S;
}
/* In the following:
* footpath_element_is_wide(pathList[2])
* is always false due to the tile update order
* in combination with reset tiles.
* Short circuit the logic appropriately. */
constexpr uint8_t edgeMask2 = EDGE_NE | EDGE_SE;
const auto* pathElement2 = std::get<2>(pathList);
const auto* pathElement6 = std::get<6>(pathList);
if ((pathConnections & FOOTPATH_CONNECTION_SW) && pathElement6 != nullptr && !(pathElement6)->AsPath()->IsWide()
&& (pathElement6->AsPath()->GetEdges() & edgeMask2) == edgeMask2 && pathElement2 != nullptr)
2018-06-22 23:17:03 +02:00
{
pathConnections |= FOOTPATH_CONNECTION_E;
}
}
/* In the following:
* footpath_element_is_wide(pathList[4])
* footpath_element_is_wide(pathList[1])
* are always false due to the tile update order
* in combination with reset tiles.
* Short circuit the logic appropriately. */
if ((pathConnections & FOOTPATH_CONNECTION_SE) && std::get<1>(pathList) != nullptr)
2018-06-22 23:17:03 +02:00
{
constexpr uint8_t edgeMask1 = EDGE_SW | EDGE_NW;
const auto* pathElement0 = std::get<0>(pathList);
const auto* pathElement4 = std::get<4>(pathList);
if ((pathConnections & FOOTPATH_CONNECTION_NE) && (pathElement4 != nullptr)
&& (pathElement4->AsPath()->GetEdges() & edgeMask1) == edgeMask1 && pathElement0 != nullptr
&& !pathElement0->AsPath()->IsWide())
2018-06-22 23:17:03 +02:00
{
pathConnections |= FOOTPATH_CONNECTION_W;
}
/* In the following:
* footpath_element_is_wide(pathList[5])
* footpath_element_is_wide(pathList[2])
* are always false due to the tile update order
* in combination with reset tiles.
* Short circuit the logic appropriately. */
constexpr uint8_t edgeMask2 = EDGE_NE | EDGE_NW;
const auto* pathElement2 = std::get<2>(pathList);
const auto* pathElement5 = std::get<5>(pathList);
if ((pathConnections & FOOTPATH_CONNECTION_SW) && pathElement5 != nullptr
&& (pathElement5->AsPath()->GetEdges() & edgeMask2) == edgeMask2 && pathElement2 != nullptr)
2018-06-22 23:17:03 +02:00
{
pathConnections |= FOOTPATH_CONNECTION_N;
}
}
if ((pathConnections & FOOTPATH_CONNECTION_NW) && (pathConnections & (FOOTPATH_CONNECTION_E | FOOTPATH_CONNECTION_S)))
{
pathConnections &= ~FOOTPATH_CONNECTION_NW;
}
if ((pathConnections & FOOTPATH_CONNECTION_NE) && (pathConnections & (FOOTPATH_CONNECTION_W | FOOTPATH_CONNECTION_S)))
{
pathConnections &= ~FOOTPATH_CONNECTION_NE;
}
if ((pathConnections & FOOTPATH_CONNECTION_SE) && (pathConnections & (FOOTPATH_CONNECTION_N | FOOTPATH_CONNECTION_W)))
{
pathConnections &= ~FOOTPATH_CONNECTION_SE;
}
if ((pathConnections & FOOTPATH_CONNECTION_SW) && (pathConnections & (FOOTPATH_CONNECTION_E | FOOTPATH_CONNECTION_N)))
{
pathConnections &= ~FOOTPATH_CONNECTION_SW;
}
if (!(pathConnections
& (FOOTPATH_CONNECTION_NE | FOOTPATH_CONNECTION_SE | FOOTPATH_CONNECTION_SW | FOOTPATH_CONNECTION_NW)))
2018-06-22 23:17:03 +02:00
{
uint8_t e = tileElement->AsPath()->GetEdgesAndCorners();
2017-12-08 16:47:54 +01:00
if ((e != 0b10101111) && (e != 0b01011111) && (e != 0b11101111))
tileElement->AsPath()->SetWide(true);
}
} while (!(tileElement++)->IsLastForTile());
2015-11-14 01:08:31 +01:00
}
2022-10-04 08:51:27 +02:00
bool FootpathIsBlockedByVehicle(const TileCoordsXYZ& position)
2018-06-07 19:42:56 +02:00
{
auto pathElement = MapGetFirstTileElementWithBaseHeightBetween<PathElement>({ position, position.z + PATH_HEIGHT_STEP });
return pathElement != nullptr && pathElement->IsBlockedByVehicle();
2018-06-07 19:42:56 +02:00
}
2015-07-07 03:35:53 +02:00
/**
*
* rct2: 0x006A7642
*/
2022-10-04 08:51:27 +02:00
void FootpathUpdateQueueEntranceBanner(const CoordsXY& footpathPos, TileElement* tileElement)
2015-07-07 03:35:53 +02:00
{
2021-12-11 00:39:39 +01:00
const auto elementType = tileElement->GetType();
if (elementType == TileElementType::Path)
2018-06-22 23:17:03 +02:00
{
if (tileElement->AsPath()->IsQueue())
{
2022-10-04 08:51:27 +02:00
FootpathQueueChainPush(tileElement->AsPath()->GetRideIndex());
for (int32_t direction = 0; direction < NumOrthogonalDirections; direction++)
2018-06-22 23:17:03 +02:00
{
if (tileElement->AsPath()->GetEdges() & (1 << direction))
2018-06-22 23:17:03 +02:00
{
2022-10-04 08:51:27 +02:00
FootpathChainRideQueue(
RideId::GetNull(), StationIndex::FromUnderlying(0), footpathPos, tileElement, direction);
}
2018-06-22 23:17:03 +02:00
}
tileElement->AsPath()->SetRideIndex(RideId::GetNull());
}
}
2021-12-11 00:39:39 +01:00
else if (elementType == TileElementType::Entrance)
{
if (tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_RIDE_ENTRANCE)
{
2022-10-04 08:51:27 +02:00
FootpathQueueChainPush(tileElement->AsEntrance()->GetRideIndex());
FootpathChainRideQueue(
RideId::GetNull(), StationIndex::FromUnderlying(0), footpathPos, tileElement,
DirectionReverse(tileElement->GetDirection()));
}
}
2015-07-07 03:35:53 +02:00
}
2015-07-06 23:12:45 +02:00
/**
*
* rct2: 0x006A6B7F
*/
2022-10-04 08:51:27 +02:00
static void FootpathRemoveEdgesTowardsHere(
const CoordsXYZ& footpathPos, int32_t direction, TileElement* tileElement, bool isQueue)
2015-07-06 23:12:45 +02:00
{
2018-10-03 10:57:29 +02:00
if (tileElement->AsPath()->IsQueue())
2018-06-22 23:17:03 +02:00
{
2022-10-04 08:51:27 +02:00
FootpathQueueChainPush(tileElement->AsPath()->GetRideIndex());
}
2015-07-06 23:12:45 +02:00
auto d = DirectionReverse(direction);
tileElement->AsPath()->SetEdges(tileElement->AsPath()->GetEdges() & ~(1 << d));
int32_t cd = ((d - 1) & 3);
tileElement->AsPath()->SetCorners(tileElement->AsPath()->GetCorners() & ~(1 << cd));
cd = ((cd + 1) & 3);
tileElement->AsPath()->SetCorners(tileElement->AsPath()->GetCorners() & ~(1 << cd));
MapInvalidateTile({ footpathPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() });
2015-07-06 23:12:45 +02:00
2018-06-22 23:17:03 +02:00
if (isQueue)
2022-10-04 08:51:27 +02:00
FootpathDisconnectQueueFromPath(footpathPos, tileElement, -1);
2015-07-08 07:58:48 +02:00
Direction shiftedDirection = (direction + 1) & 3;
auto targetFootPathPos = CoordsXYZ{ CoordsXY{ footpathPos } + CoordsDirectionDelta[shiftedDirection], footpathPos.z };
2015-07-06 23:12:45 +02:00
tileElement = MapGetFirstElementAt(targetFootPathPos);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return;
2018-06-22 23:17:03 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Path)
continue;
if (tileElement->GetBaseZ() != targetFootPathPos.z)
continue;
2015-07-06 23:12:45 +02:00
2018-09-16 16:17:35 +02:00
if (tileElement->AsPath()->IsSloped())
break;
2015-07-06 23:12:45 +02:00
cd = ((shiftedDirection + 1) & 3);
tileElement->AsPath()->SetCorners(tileElement->AsPath()->GetCorners() & ~(1 << cd));
MapInvalidateTile({ targetFootPathPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() });
break;
} while (!(tileElement++)->IsLastForTile());
2015-07-06 23:12:45 +02:00
}
/**
*
* rct2: 0x006A6B14
*/
2022-10-04 08:51:27 +02:00
static void FootpathRemoveEdgesTowards(const CoordsXYRangedZ& footPathPos, int32_t direction, bool isQueue)
2015-07-06 23:12:45 +02:00
{
if (!MapIsLocationValid(footPathPos))
{
return;
}
TileElement* tileElement = MapGetFirstElementAt(footPathPos);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return;
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Path)
continue;
if (footPathPos.clearanceZ == tileElement->GetBaseZ())
{
2018-09-16 16:17:35 +02:00
if (tileElement->AsPath()->IsSloped())
{
uint8_t slope = tileElement->AsPath()->GetSlopeDirection();
if (slope != direction)
break;
}
2022-10-04 08:51:27 +02:00
FootpathRemoveEdgesTowardsHere({ footPathPos, footPathPos.clearanceZ }, direction, tileElement, isQueue);
break;
}
if (footPathPos.baseZ == tileElement->GetBaseZ())
{
2018-09-16 16:17:35 +02:00
if (!tileElement->AsPath()->IsSloped())
break;
uint8_t slope = DirectionReverse(tileElement->AsPath()->GetSlopeDirection());
if (slope != direction)
break;
2022-10-04 08:51:27 +02:00
FootpathRemoveEdgesTowardsHere({ footPathPos, footPathPos.clearanceZ }, direction, tileElement, isQueue);
break;
}
} while (!(tileElement++)->IsLastForTile());
2015-07-06 23:12:45 +02:00
}
// Returns true when there is an element at the given coordinates that want to connect to a path with the given direction (ride
// entrances and exits, shops, paths).
2022-10-04 08:51:27 +02:00
bool TileElementWantsPathConnectionTowards(const TileCoordsXYZD& coords, const TileElement* const elementToBeRemoved)
{
TileElement* tileElement = MapGetFirstElementAt(coords);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
return false;
do
{
// Don't check the element that gets removed
if (tileElement == elementToBeRemoved)
continue;
2021-12-11 00:39:39 +01:00
switch (tileElement->GetType())
{
2021-12-11 00:39:39 +01:00
case TileElementType::Path:
if (tileElement->BaseHeight == coords.z)
{
2018-09-16 16:17:35 +02:00
if (!tileElement->AsPath()->IsSloped())
2018-06-22 23:17:03 +02:00
// The footpath is flat, it can be connected to from any direction
return true;
if (tileElement->AsPath()->GetSlopeDirection() == DirectionReverse(coords.direction))
2018-06-22 23:17:03 +02:00
// The footpath is sloped and its lowest point matches the edge connection
return true;
}
else if (tileElement->BaseHeight + 2 == coords.z)
2018-06-22 23:17:03 +02:00
{
if (tileElement->AsPath()->IsSloped() && tileElement->AsPath()->GetSlopeDirection() == coords.direction)
2018-06-22 23:17:03 +02:00
// The footpath is sloped and its higher point matches the edge connection
return true;
}
break;
2021-12-11 00:39:39 +01:00
case TileElementType::Track:
if (tileElement->BaseHeight == coords.z)
2018-06-22 23:17:03 +02:00
{
auto ride = GetRide(tileElement->AsTrack()->GetRideIndex());
if (ride == nullptr)
continue;
if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE))
2018-06-22 23:17:03 +02:00
break;
const auto trackType = tileElement->AsTrack()->GetTrackType();
const uint8_t trackSequence = tileElement->AsTrack()->GetSequenceIndex();
2021-08-27 23:49:32 +02:00
const auto& ted = GetTrackElementDescriptor(trackType);
if (ted.SequenceProperties[trackSequence] & TRACK_SEQUENCE_FLAG_CONNECTS_TO_PATH)
2018-06-22 23:17:03 +02:00
{
2018-09-15 10:56:35 +02:00
uint16_t dx = ((coords.direction - tileElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK);
if (ted.SequenceProperties[trackSequence] & (1 << dx))
2018-06-22 23:17:03 +02:00
{
// Track element has the flags required for the given direction
return true;
}
}
}
2018-06-22 23:17:03 +02:00
break;
2021-12-11 00:39:39 +01:00
case TileElementType::Entrance:
if (tileElement->BaseHeight == coords.z)
{
if (entrance_has_direction(*(tileElement->AsEntrance()), coords.direction - tileElement->GetDirection()))
2018-06-22 23:17:03 +02:00
{
// Entrance wants to be connected towards the given direction
return true;
}
}
2018-06-22 23:17:03 +02:00
break;
default:
break;
}
} while (!(tileElement++)->IsLastForTile());
return false;
}
// fix up the corners around the given path element that gets removed
2022-10-04 08:51:27 +02:00
static void FootpathFixCornersAround(const TileCoordsXY& footpathPos, TileElement* pathElement)
{
// A mask for the paths' corners of each possible neighbour
static constexpr uint8_t cornersTouchingTile[3][3] = {
{ 0b0010, 0b0011, 0b0001 },
{ 0b0110, 0b0000, 0b1001 },
{ 0b0100, 0b1100, 0b1000 },
};
// Sloped paths don't create filled corners, so no need to remove any
2021-12-11 00:39:39 +01:00
if (pathElement->GetType() == TileElementType::Path && pathElement->AsPath()->IsSloped())
return;
for (int32_t xOffset = -1; xOffset <= 1; xOffset++)
{
for (int32_t yOffset = -1; yOffset <= 1; yOffset++)
{
// Skip self
if (xOffset == 0 && yOffset == 0)
continue;
TileElement* tileElement = MapGetFirstElementAt(
TileCoordsXY{ footpathPos.x + xOffset, footpathPos.y + yOffset }.ToCoordsXY());
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
continue;
do
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() != TileElementType::Path)
continue;
2018-09-16 16:17:35 +02:00
if (tileElement->AsPath()->IsSloped())
continue;
if (tileElement->BaseHeight != pathElement->BaseHeight)
continue;
const int32_t ix = xOffset + 1;
const int32_t iy = yOffset + 1;
tileElement->AsPath()->SetCorners(tileElement->AsPath()->GetCorners() & ~(cornersTouchingTile[iy][ix]));
} while (!(tileElement++)->IsLastForTile());
}
}
}
2015-07-06 23:12:45 +02:00
/**
*
* rct2: 0x006A6AA7
* @param x x-coordinate in units (not tiles)
* @param y y-coordinate in units (not tiles)
*/
2022-10-04 08:51:27 +02:00
void FootpathRemoveEdgesAt(const CoordsXY& footpathPos, TileElement* tileElement)
2015-07-06 23:12:45 +02:00
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() == TileElementType::Track)
2018-06-22 23:17:03 +02:00
{
auto rideIndex = tileElement->AsTrack()->GetRideIndex();
auto ride = GetRide(rideIndex);
if (ride == nullptr)
return;
if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE))
return;
}
2022-10-04 08:51:27 +02:00
FootpathUpdateQueueEntranceBanner(footpathPos, tileElement);
bool fixCorners = false;
for (uint8_t direction = 0; direction < NumOrthogonalDirections; direction++)
2018-06-22 23:17:03 +02:00
{
int32_t z1 = tileElement->BaseHeight;
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() == TileElementType::Path)
2018-06-22 23:17:03 +02:00
{
2018-09-16 16:17:35 +02:00
if (tileElement->AsPath()->IsSloped())
2018-06-22 23:17:03 +02:00
{
int32_t slope = tileElement->AsPath()->GetSlopeDirection();
// Sloped footpaths don't connect sideways
if ((slope - direction) & 1)
continue;
// When a path is sloped, the higher point of the path is 2 units higher
z1 += (slope == direction) ? 2 : 0;
}
}
// When clearance checks were disabled a neighbouring path can be connected to both the path-ghost and to something
// else, so before removing edges from neighbouring paths we have to make sure there is nothing else they are connected
// to.
2022-10-04 08:51:27 +02:00
if (!TileElementWantsPathConnectionTowards({ TileCoordsXY{ footpathPos }, z1, direction }, tileElement))
{
2021-12-11 00:39:39 +01:00
bool isQueue = tileElement->GetType() == TileElementType::Path ? tileElement->AsPath()->IsQueue() : false;
int32_t z0 = z1 - 2;
2022-10-04 08:51:27 +02:00
FootpathRemoveEdgesTowards(
{ footpathPos + CoordsDirectionDelta[direction], z0 * COORDS_Z_STEP, z1 * COORDS_Z_STEP }, direction, isQueue);
}
else
{
// A footpath may stay connected, but its edges must be fixed later on when another edge does get removed.
fixCorners = true;
}
}
// Only fix corners when needed, to avoid changing corners that have been set for its looks.
if (fixCorners && tileElement->IsGhost())
{
auto tileFootpathPos = TileCoordsXY{ footpathPos };
2022-10-04 08:51:27 +02:00
FootpathFixCornersAround(tileFootpathPos, tileElement);
}
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() == TileElementType::Path)
tileElement->AsPath()->SetEdgesAndCorners(0);
2015-07-06 23:12:45 +02:00
}
static ObjectEntryIndex FootpathGetDefaultSurface(bool queue)
{
bool showEditorPaths = ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode);
for (ObjectEntryIndex i = 0; i < MAX_FOOTPATH_SURFACE_OBJECTS; i++)
{
auto pathEntry = GetPathSurfaceEntry(i);
if (pathEntry != nullptr)
{
if (!showEditorPaths && (pathEntry->Flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR))
{
continue;
}
if (queue == ((pathEntry->Flags & FOOTPATH_ENTRY_FLAG_IS_QUEUE) != 0))
{
return i;
}
}
}
return OBJECT_ENTRY_INDEX_NULL;
}
static bool FootpathIsSurfaceEntryOkay(ObjectEntryIndex index, bool queue)
{
auto pathEntry = GetPathSurfaceEntry(index);
if (pathEntry != nullptr)
{
bool showEditorPaths = ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode);
if (!showEditorPaths && (pathEntry->Flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR))
{
return false;
}
if (queue == ((pathEntry->Flags & FOOTPATH_ENTRY_FLAG_IS_QUEUE) != 0))
{
return true;
}
}
return false;
}
static ObjectEntryIndex FootpathGetDefaultRailings()
{
for (ObjectEntryIndex i = 0; i < MAX_FOOTPATH_RAILINGS_OBJECTS; i++)
{
const auto* railingEntry = GetPathRailingsEntry(i);
if (railingEntry != nullptr)
{
return i;
}
}
return OBJECT_ENTRY_INDEX_NULL;
}
static bool FootpathIsLegacyPathEntryOkay(ObjectEntryIndex index)
{
bool showEditorPaths = ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode);
auto& objManager = OpenRCT2::GetContext()->GetObjectManager();
auto footpathObj = static_cast<FootpathObject*>(objManager.GetLoadedObject(ObjectType::Paths, index));
if (footpathObj != nullptr)
{
auto pathEntry = reinterpret_cast<FootpathEntry*>(footpathObj->GetLegacyData());
return showEditorPaths || !(pathEntry->flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR);
}
return false;
}
static ObjectEntryIndex FootpathGetDefaultLegacyPath()
{
for (ObjectEntryIndex i = 0; i < MAX_PATH_OBJECTS; i++)
{
if (FootpathIsLegacyPathEntryOkay(i))
{
return i;
}
}
return OBJECT_ENTRY_INDEX_NULL;
}
bool FootpathSelectDefault()
{
// Select default footpath
auto surfaceIndex = FootpathGetDefaultSurface(false);
if (FootpathIsSurfaceEntryOkay(gFootpathSelection.NormalSurface, false))
{
surfaceIndex = gFootpathSelection.NormalSurface;
}
// Select default queue
auto queueIndex = FootpathGetDefaultSurface(true);
if (FootpathIsSurfaceEntryOkay(gFootpathSelection.QueueSurface, true))
{
queueIndex = gFootpathSelection.QueueSurface;
}
// Select default railing
auto railingIndex = FootpathGetDefaultRailings();
const auto* railingEntry = GetPathRailingsEntry(gFootpathSelection.Railings);
if (railingEntry != nullptr)
{
railingIndex = gFootpathSelection.Railings;
}
// Select default legacy path
auto legacyPathIndex = FootpathGetDefaultLegacyPath();
if (gFootpathSelection.LegacyPath != OBJECT_ENTRY_INDEX_NULL)
{
if (FootpathIsLegacyPathEntryOkay(gFootpathSelection.LegacyPath))
{
// Keep legacy path selected
legacyPathIndex = gFootpathSelection.LegacyPath;
}
else
{
// Reset legacy path, we default to a surface (if there are any)
gFootpathSelection.LegacyPath = OBJECT_ENTRY_INDEX_NULL;
}
}
if (surfaceIndex == OBJECT_ENTRY_INDEX_NULL)
{
if (legacyPathIndex == OBJECT_ENTRY_INDEX_NULL)
{
// No surfaces or legacy paths available
return false;
}
// No surfaces available, so default to legacy path
gFootpathSelection.LegacyPath = legacyPathIndex;
}
gFootpathSelection.NormalSurface = surfaceIndex;
gFootpathSelection.QueueSurface = queueIndex;
gFootpathSelection.Railings = railingIndex;
return true;
}
const FootpathObject* GetLegacyFootpathEntry(ObjectEntryIndex entryIndex)
{
auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
auto obj = objMgr.GetLoadedObject(ObjectType::Paths, entryIndex);
if (obj == nullptr)
return nullptr;
const FootpathObject* footpathObject = (static_cast<FootpathObject*>(obj));
return footpathObject;
}
2018-02-06 10:05:48 +01:00
const FootpathSurfaceObject* GetPathSurfaceEntry(ObjectEntryIndex entryIndex)
{
auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
auto obj = objMgr.GetLoadedObject(ObjectType::FootpathSurface, entryIndex);
if (obj == nullptr)
return nullptr;
2021-09-10 09:13:20 +02:00
return static_cast<FootpathSurfaceObject*>(obj);
}
const FootpathRailingsObject* GetPathRailingsEntry(ObjectEntryIndex entryIndex)
{
auto& objMgr = OpenRCT2::GetContext()->GetObjectManager();
auto obj = objMgr.GetLoadedObject(ObjectType::FootpathRailings, entryIndex);
if (obj == nullptr)
return nullptr;
return static_cast<FootpathRailingsObject*>(obj);
}
2022-01-19 14:17:11 +01:00
RideId PathElement::GetRideIndex() const
{
return rideIndex;
}
2022-01-19 14:17:11 +01:00
void PathElement::SetRideIndex(RideId newRideIndex)
{
rideIndex = newRideIndex;
}
uint8_t PathElement::GetAdditionStatus() const
{
2020-03-04 18:43:09 +01:00
return AdditionStatus;
}
void PathElement::SetAdditionStatus(uint8_t newStatus)
{
2020-03-04 18:43:09 +01:00
AdditionStatus = newStatus;
}
uint8_t PathElement::GetEdges() const
{
return EdgesAndCorners & FOOTPATH_PROPERTIES_EDGES_EDGES_MASK;
}
void PathElement::SetEdges(uint8_t newEdges)
{
EdgesAndCorners &= ~FOOTPATH_PROPERTIES_EDGES_EDGES_MASK;
EdgesAndCorners |= (newEdges & FOOTPATH_PROPERTIES_EDGES_EDGES_MASK);
}
uint8_t PathElement::GetCorners() const
{
return EdgesAndCorners >> 4;
}
void PathElement::SetCorners(uint8_t newCorners)
{
EdgesAndCorners &= ~FOOTPATH_PROPERTIES_EDGES_CORNERS_MASK;
EdgesAndCorners |= (newCorners << 4);
}
uint8_t PathElement::GetEdgesAndCorners() const
{
return EdgesAndCorners;
}
void PathElement::SetEdgesAndCorners(uint8_t newEdgesAndCorners)
{
EdgesAndCorners = newEdgesAndCorners;
}
bool PathElement::IsLevelCrossing(const CoordsXY& coords) const
{
auto trackElement = MapGetTrackElementAt({ coords, GetBaseZ() });
if (trackElement == nullptr)
{
return false;
}
if (trackElement->GetTrackType() != TrackElemType::Flat)
{
return false;
}
auto ride = GetRide(trackElement->GetRideIndex());
if (ride == nullptr)
{
return false;
}
return ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_SUPPORTS_LEVEL_CROSSINGS);
}