1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-24 00:03:11 +01:00
Files
OpenRCT2/src/openrct2-ui/windows/Footpath.cpp
2025-10-19 22:35:57 +02:00

2221 lines
84 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*****************************************************************************
* Copyright (c) 2014-2025 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.
*****************************************************************************/
#include <openrct2-ui/ProvisionalElements.h>
#include <openrct2-ui/UiContext.h>
#include <openrct2-ui/input/InputManager.h>
#include <openrct2-ui/input/ShortcutIds.h>
#include <openrct2-ui/interface/Dropdown.h>
#include <openrct2-ui/interface/Viewport.h>
#include <openrct2-ui/interface/ViewportInteraction.h>
#include <openrct2-ui/interface/ViewportQuery.h>
#include <openrct2-ui/interface/Widget.h>
#include <openrct2-ui/windows/Windows.h>
#include <openrct2/Cheats.h>
#include <openrct2/Context.h>
#include <openrct2/Game.h>
#include <openrct2/GameState.h>
#include <openrct2/Input.h>
#include <openrct2/OpenRCT2.h>
#include <openrct2/SpriteIds.h>
#include <openrct2/actions/FootpathPlaceAction.h>
#include <openrct2/actions/FootpathRemoveAction.h>
#include <openrct2/audio/Audio.h>
#include <openrct2/core/FlagHolder.hpp>
#include <openrct2/localisation/Formatter.h>
#include <openrct2/object/FootpathObject.h>
#include <openrct2/object/FootpathRailingsObject.h>
#include <openrct2/object/FootpathSurfaceObject.h>
#include <openrct2/object/ObjectLimits.h>
#include <openrct2/object/ObjectManager.h>
#include <openrct2/paint/VirtualFloor.h>
#include <openrct2/platform/Platform.h>
#include <openrct2/ui/WindowManager.h>
#include <openrct2/world/ConstructionClearance.h>
#include <openrct2/world/Footpath.h>
#include <openrct2/world/Map.h>
#include <openrct2/world/MapSelection.h>
#include <openrct2/world/Park.h>
#include <openrct2/world/tile_element/PathElement.h>
#include <openrct2/world/tile_element/Slope.h>
#include <openrct2/world/tile_element/SurfaceElement.h>
using namespace OpenRCT2::Numerics;
namespace OpenRCT2::Ui::Windows
{
struct ProvisionalTile
{
CoordsXYZ position;
FootpathSlope slope;
constexpr bool operator==(const ProvisionalTile& rhs) const
{
return position == rhs.position && slope == rhs.slope;
}
};
static money64 FootpathProvisionalSet(
ObjectEntryIndex type, ObjectEntryIndex railingsType, const CoordsXY& footpathLocA, const CoordsXY& footpathLocB,
int32_t startZ, const std::span<const ProvisionalTile> tiles, PathConstructFlags constructFlags);
enum class PathConstructionMode : uint8_t
{
onLand,
dragArea,
/**
* When picking a location to start the bridge or tunnel
*/
bridgeOrTunnelPick,
/**
* When actually building a bridge or tunnel (enables the appropriate buttons)
*/
bridgeOrTunnel,
};
enum class ProvisionalPathFlag : uint8_t
{
showArrow = 0,
/**
* Set when any provisional path is present.
*/
placed = 1,
forceRecheck = 2,
};
enum class SlopePitch : uint8_t
{
flat,
upwards,
downwards,
};
using ProvisionalPathFlags = FlagHolder<uint8_t, ProvisionalPathFlag>;
struct ProvisionalFootpath
{
ObjectEntryIndex type{};
CoordsXY positionA{};
CoordsXY positionB{};
/**
* Z of the first tile. Used for checking if the provisional path needs updating when in dragArea mode.
*/
int32_t startZ{};
ProvisionalPathFlags flags{};
ObjectEntryIndex surfaceIndex{};
ObjectEntryIndex railingsIndex{};
PathConstructFlags constructFlags{};
std::vector<ProvisionalTile> tiles{};
};
static ProvisionalFootpath _provisionalFootpath;
static CoordsXYZ _footpathConstructFromPosition;
static SlopePitch _footpathConstructSlope;
#pragma region Measurements
static constexpr StringId kWindowTitle = STR_FOOTPATHS;
static constexpr ScreenSize kWindowSize = { 106, 461 };
#pragma endregion
#pragma region Widgets
enum WindowFootpathWidgetIdx : WidgetIndex
{
WIDX_BACKGROUND,
WIDX_TITLE,
WIDX_CLOSE,
WIDX_TYPE_GROUP,
WIDX_FOOTPATH_TYPE,
WIDX_QUEUELINE_TYPE,
WIDX_RAILINGS_TYPE,
WIDX_DIRECTION_GROUP,
WIDX_DIRECTION_NW,
WIDX_DIRECTION_NE,
WIDX_DIRECTION_SW,
WIDX_DIRECTION_SE,
WIDX_SLOPE_GROUP,
WIDX_SLOPEDOWN,
WIDX_LEVEL,
WIDX_SLOPEUP,
WIDX_CONSTRUCT,
WIDX_REMOVE,
WIDX_MODE_GROUP,
WIDX_CONSTRUCT_ON_LAND,
WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL,
WIDX_CONSTRUCT_DRAG_AREA,
};
// clang-format off
static constexpr auto kWindowFootpathWidgets = makeWidgets(
makeWindowShim(kWindowTitle, kWindowSize),
// Type group
makeWidget({ 3, 17}, {100, 95}, WidgetType::groupbox, WindowColour::primary , STR_TYPE ),
makeWidget({ 6, 30}, { 47, 36}, WidgetType::flatBtn, WindowColour::secondary, 0xFFFFFFFF, STR_FOOTPATH_TIP ),
makeWidget({53, 30}, { 47, 36}, WidgetType::flatBtn, WindowColour::secondary, 0xFFFFFFFF, STR_QUEUE_LINE_PATH_TIP ),
makeWidget({29, 69}, { 47, 36}, WidgetType::flatBtn, WindowColour::secondary, 0xFFFFFFFF, STR_OBJECT_SELECTION_FOOTPATH_RAILINGS ),
// Direction group
makeWidget({ 3, 115}, {100, 77}, WidgetType::groupbox, WindowColour::primary , STR_DIRECTION ),
makeWidget({53, 127}, { 45, 29}, WidgetType::flatBtn, WindowColour::secondary, ImageId(SPR_CONSTRUCTION_DIRECTION_NE), STR_DIRECTION_TIP ),
makeWidget({53, 156}, { 45, 29}, WidgetType::flatBtn, WindowColour::secondary, ImageId(SPR_CONSTRUCTION_DIRECTION_SE), STR_DIRECTION_TIP ),
makeWidget({ 8, 156}, { 45, 29}, WidgetType::flatBtn, WindowColour::secondary, ImageId(SPR_CONSTRUCTION_DIRECTION_SW), STR_DIRECTION_TIP ),
makeWidget({ 8, 127}, { 45, 29}, WidgetType::flatBtn, WindowColour::secondary, ImageId(SPR_CONSTRUCTION_DIRECTION_NW), STR_DIRECTION_TIP ),
// Slope group
makeWidget({ 3, 195}, {100, 41}, WidgetType::groupbox, WindowColour::primary , STR_SLOPE ),
makeWidget({17, 207}, { 24, 24}, WidgetType::flatBtn, WindowColour::secondary, ImageId(SPR_RIDE_CONSTRUCTION_SLOPE_DOWN), STR_SLOPE_DOWN_TIP ),
makeWidget({41, 207}, { 24, 24}, WidgetType::flatBtn, WindowColour::secondary, ImageId(SPR_RIDE_CONSTRUCTION_SLOPE_LEVEL), STR_LEVEL_TIP ),
makeWidget({65, 207}, { 24, 24}, WidgetType::flatBtn, WindowColour::secondary, ImageId(SPR_RIDE_CONSTRUCTION_SLOPE_UP), STR_SLOPE_UP_TIP ),
makeWidget({ 8, 242}, { 90, 90}, WidgetType::flatBtn, WindowColour::secondary, 0xFFFFFFFF, STR_CONSTRUCT_THE_SELECTED_FOOTPATH_SECTION_TIP),
makeWidget({30, 335}, { 46, 24}, WidgetType::flatBtn, WindowColour::secondary, ImageId(SPR_DEMOLISH_CURRENT_SECTION), STR_REMOVE_PREVIOUS_FOOTPATH_SECTION_TIP ),
// Mode group
makeWidget({ 3, 361}, {100, 99}, WidgetType::groupbox, WindowColour::primary ),
makeWidget({13, 372}, { 36, 36}, WidgetType::flatBtn, WindowColour::secondary, ImageId(SPR_CONSTRUCTION_FOOTPATH_LAND), STR_CONSTRUCT_FOOTPATH_ON_LAND_TIP ),
makeWidget({57, 372}, { 36, 36}, WidgetType::flatBtn, WindowColour::secondary, ImageId(SPR_CONSTRUCTION_FOOTPATH_BRIDGE), STR_CONSTRUCT_BRIDGE_OR_TUNNEL_FOOTPATH_TIP ),
makeWidget({35, 416}, { 36, 36}, WidgetType::flatBtn, WindowColour::secondary, ImageId(SPR_G2_ICON_PATH_DRAG_TOOL), STR_DRAG_AREA_OF_FOOTPATH_TIP )
);
#pragma endregion
/** rct2: 0x0098D7E0 */
static constexpr uint8_t kConstructionPreviewImages[][4] = {
{ 5, 10, 5, 10 }, // SlopePitch::flat
{ 16, 17, 18, 19 }, // SlopePitch::upwards
{ 18, 19, 16, 17 }, // SlopePitch::downwards
};
// clang-format on
class FootpathWindow final : public Window
{
private:
const uint16_t ARROW_PULSE_DURATION = 200;
uint8_t _footpathConstructDirection;
uint8_t _footpathConstructValidDirections;
PathConstructionMode _footpathConstructionMode;
std::vector<std::pair<ObjectType, ObjectEntryIndex>> _dropdownEntries;
money64 _windowFootpathCost;
uint32_t _footpathConstructionNextArrowPulse = 0;
uint8_t _lastUpdatedCameraRotation = UINT8_MAX;
bool _footpathErrorOccured = false;
bool _footpathPlaceCtrlState;
int32_t _footpathPlaceCtrlZ;
bool _footpathPlaceShiftState;
ScreenCoordsXY _footpathPlaceShiftStart;
int32_t _footpathPlaceShiftZ;
int32_t _footpathPlaceZ;
CoordsXY _dragStartPos;
public:
#pragma region Window Override Events
void onOpen() override
{
setWidgets(kWindowFootpathWidgets);
WindowInitScrollWidgets(*this);
WindowPushOthersRight(*this);
ShowGridlines();
ToolCancel();
_footpathConstructionMode = PathConstructionMode::onLand;
ToolSet(*this, WIDX_CONSTRUCT_ON_LAND, Tool::pathDown);
gInputFlags.set(InputFlag::unk6);
_footpathErrorOccured = false;
WindowFootpathSetEnabledAndPressedWidgets();
_footpathPlaceCtrlState = false;
_footpathPlaceShiftState = false;
holdDownWidgets = (1u << WIDX_CONSTRUCT) | (1u << WIDX_REMOVE);
}
void onClose() override
{
FootpathUpdateProvisional();
ViewportSetVisibility(ViewportVisibility::standard);
MapInvalidateMapSelectionTiles();
gMapSelectFlags.unset(MapSelectFlag::enableConstruct);
auto* windowMgr = Ui::GetWindowManager();
windowMgr->InvalidateByClass(WindowClass::topToolbar);
HideGridlines();
}
void onUpdate() override
{
WindowFootpathUpdateProvisionalPathForBridgeMode();
// #2502: The camera might have changed rotation, so we need to update which directional buttons are pressed
uint8_t currentRotation = GetCurrentRotation();
if (_lastUpdatedCameraRotation != currentRotation)
{
_lastUpdatedCameraRotation = currentRotation;
WindowFootpathSetEnabledAndPressedWidgets();
}
// Check tool
switch (_footpathConstructionMode)
{
case PathConstructionMode::onLand:
if (!isToolActive(WindowClass::footpath, WIDX_CONSTRUCT_ON_LAND))
close();
break;
case PathConstructionMode::dragArea:
break;
case PathConstructionMode::bridgeOrTunnelPick:
if (!isToolActive(WindowClass::footpath, WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL))
close();
break;
case PathConstructionMode::bridgeOrTunnel:
break;
}
// If another window has enabled a tool, close ours.
if (gInputFlags.has(InputFlag::toolActive) && gCurrentToolWidget.windowClassification != WindowClass::footpath)
close();
}
void onMouseDown(WidgetIndex widgetIndex) override
{
switch (widgetIndex)
{
case WIDX_FOOTPATH_TYPE:
WindowFootpathShowFootpathTypesDialog(&widgets[widgetIndex], false);
break;
case WIDX_QUEUELINE_TYPE:
WindowFootpathShowFootpathTypesDialog(&widgets[widgetIndex], true);
break;
case WIDX_RAILINGS_TYPE:
WindowFootpathShowRailingsTypesDialog(&widgets[widgetIndex]);
break;
case WIDX_DIRECTION_NW:
WindowFootpathMousedownDirection(0);
break;
case WIDX_DIRECTION_NE:
WindowFootpathMousedownDirection(1);
break;
case WIDX_DIRECTION_SW:
WindowFootpathMousedownDirection(2);
break;
case WIDX_DIRECTION_SE:
WindowFootpathMousedownDirection(3);
break;
case WIDX_SLOPEDOWN:
WindowFootpathMousedownSlope(SlopePitch::downwards);
break;
case WIDX_LEVEL:
WindowFootpathMousedownSlope(SlopePitch::flat);
break;
case WIDX_SLOPEUP:
WindowFootpathMousedownSlope(SlopePitch::upwards);
break;
case WIDX_CONSTRUCT:
WindowFootpathConstruct();
break;
case WIDX_REMOVE:
WindowFootpathRemove();
break;
}
}
void enableDragAreaMode()
{
_windowFootpathCost = kMoney64Undefined;
ToolCancel();
FootpathUpdateProvisional();
MapInvalidateMapSelectionTiles();
gMapSelectFlags.unset(MapSelectFlag::enableConstruct);
_footpathConstructionMode = PathConstructionMode::dragArea;
ToolSet(*this, WIDX_CONSTRUCT_DRAG_AREA, Tool::pathDown);
gInputFlags.set(InputFlag::unk6);
_footpathErrorOccured = false;
WindowFootpathSetEnabledAndPressedWidgets();
}
void onMouseUp(WidgetIndex widgetIndex) override
{
switch (widgetIndex)
{
case WIDX_CLOSE:
close();
break;
case WIDX_CONSTRUCT_ON_LAND:
if (_footpathConstructionMode == PathConstructionMode::onLand)
{
break;
}
_windowFootpathCost = kMoney64Undefined;
ToolCancel();
FootpathUpdateProvisional();
MapInvalidateMapSelectionTiles();
gMapSelectFlags.unset(MapSelectFlag::enableConstruct);
_footpathConstructionMode = PathConstructionMode::onLand;
ToolSet(*this, WIDX_CONSTRUCT_ON_LAND, Tool::pathDown);
gInputFlags.set(InputFlag::unk6);
_footpathErrorOccured = false;
WindowFootpathSetEnabledAndPressedWidgets();
break;
case WIDX_CONSTRUCT_DRAG_AREA:
if (_footpathConstructionMode == PathConstructionMode::dragArea)
{
break;
}
enableDragAreaMode();
break;
case WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL:
if (_footpathConstructionMode == PathConstructionMode::bridgeOrTunnelPick)
{
break;
}
_windowFootpathCost = kMoney64Undefined;
ToolCancel();
FootpathUpdateProvisional();
MapInvalidateMapSelectionTiles();
gMapSelectFlags.unset(MapSelectFlag::enableConstruct);
_footpathConstructionMode = PathConstructionMode::bridgeOrTunnelPick;
ToolSet(*this, WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL, Tool::crosshair);
gInputFlags.set(InputFlag::unk6);
_footpathErrorOccured = false;
WindowFootpathSetEnabledAndPressedWidgets();
break;
}
}
void onDropdown(WidgetIndex widgetIndex, int32_t selectedIndex) override
{
if (selectedIndex < 0 || static_cast<size_t>(selectedIndex) >= _dropdownEntries.size())
return;
auto entryIndex = _dropdownEntries[selectedIndex];
if (widgetIndex == WIDX_FOOTPATH_TYPE)
{
gFootpathSelection.IsQueueSelected = false;
if (entryIndex.first == ObjectType::paths)
{
gFootpathSelection.LegacyPath = entryIndex.second;
}
else
{
gFootpathSelection.LegacyPath = kObjectEntryIndexNull;
gFootpathSelection.NormalSurface = entryIndex.second;
}
}
else if (widgetIndex == WIDX_QUEUELINE_TYPE)
{
gFootpathSelection.IsQueueSelected = true;
if (entryIndex.first == ObjectType::paths)
{
gFootpathSelection.LegacyPath = entryIndex.second;
}
else
{
gFootpathSelection.LegacyPath = kObjectEntryIndexNull;
gFootpathSelection.QueueSurface = entryIndex.second;
}
}
else if (widgetIndex == WIDX_RAILINGS_TYPE)
{
gFootpathSelection.Railings = entryIndex.second;
}
else
{
return;
}
FootpathUpdateProvisional();
_windowFootpathCost = kMoney64Undefined;
invalidate();
}
void onToolUpdate(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override
{
if (widgetIndex == WIDX_CONSTRUCT_ON_LAND)
{
WindowFootpathSetProvisionalPathAtPoint(screenCoords);
}
else if (widgetIndex == WIDX_CONSTRUCT_DRAG_AREA)
{
WindowFootpathPlaceDragAreaHover(screenCoords);
}
else if (widgetIndex == WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL)
{
WindowFootpathSetSelectionStartBridgeAtPoint(screenCoords);
}
}
void onToolUp(WidgetIndex widgetIndex, const ScreenCoordsXY&) override
{
if (widgetIndex == WIDX_CONSTRUCT_ON_LAND)
{
_footpathErrorOccured = false;
}
else if (widgetIndex == WIDX_CONSTRUCT_DRAG_AREA)
{
WindowFootpathPlacePath();
_footpathErrorOccured = false;
}
}
void onToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override
{
if (widgetIndex == WIDX_CONSTRUCT_ON_LAND)
{
WindowFootpathPlacePathAtPoint(screenCoords);
}
else if (widgetIndex == WIDX_CONSTRUCT_DRAG_AREA)
{
WindowFootpathPlaceDragAreaSetStart(screenCoords);
}
else if (widgetIndex == WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL)
{
WindowFootpathStartBridgeAtPoint(screenCoords);
}
}
void onToolDrag(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override
{
if (widgetIndex == WIDX_CONSTRUCT_ON_LAND)
{
WindowFootpathPlacePathAtPoint(screenCoords);
}
else if (widgetIndex == WIDX_CONSTRUCT_DRAG_AREA)
{
WindowFootpathPlaceDragAreaSetEnd(screenCoords);
}
}
void onToolAbort(WidgetIndex widgetIndex) override
{
// Needs both checks as this will otherwise be triggered when switching from the dragArea tool
// to another.
if (widgetIndex == WIDX_CONSTRUCT_DRAG_AREA && _footpathConstructionMode == PathConstructionMode::dragArea)
{
FootpathUpdateProvisional();
enableDragAreaMode();
// When this tool is aborted, the mouse button is still down.
// Set this to prevent a stray piece of path from being placed under the mouse pointer.
_footpathErrorOccured = true;
}
}
void onPrepareDraw() override
{
// Press / unpress footpath and queue type buttons
pressedWidgets &= ~(1uLL << WIDX_FOOTPATH_TYPE);
pressedWidgets &= ~(1uLL << WIDX_QUEUELINE_TYPE);
pressedWidgets |= gFootpathSelection.IsQueueSelected ? (1uLL << WIDX_QUEUELINE_TYPE) : (1uLL << WIDX_FOOTPATH_TYPE);
// Enable / disable construct button
widgets[WIDX_CONSTRUCT].type = _footpathConstructionMode == PathConstructionMode::bridgeOrTunnel
? WidgetType::imgBtn
: WidgetType::empty;
if (gFootpathSelection.LegacyPath == kObjectEntryIndexNull)
{
widgets[WIDX_RAILINGS_TYPE].type = WidgetType::flatBtn;
}
else
{
widgets[WIDX_RAILINGS_TYPE].type = WidgetType::empty;
}
}
void onDraw(RenderTarget& rt) override
{
ScreenCoordsXY screenCoords;
WindowDrawWidgets(*this, rt);
WindowFootpathDrawDropdownButtons(rt);
if (!isWidgetDisabled(WIDX_CONSTRUCT))
{
// Get construction image
uint8_t direction = (_footpathConstructDirection + GetCurrentRotation()) % kNumOrthogonalDirections;
auto slopeOffset = EnumValue(_footpathConstructSlope);
std::optional<uint32_t> baseImage;
if (gFootpathSelection.LegacyPath == kObjectEntryIndexNull)
{
auto selectedPath = gFootpathSelection.GetSelectedSurface();
const auto* pathType = GetPathSurfaceEntry(selectedPath);
if (pathType != nullptr)
{
baseImage = pathType->BaseImageId;
}
}
else
{
auto& objManager = OpenRCT2::GetContext()->GetObjectManager();
const auto* pathObj = objManager.GetLoadedObject<FootpathObject>(gFootpathSelection.LegacyPath);
if (pathObj != nullptr)
{
auto pathEntry = reinterpret_cast<const FootpathEntry*>(pathObj->GetLegacyData());
if (gFootpathSelection.IsQueueSelected)
baseImage = pathEntry->GetQueueImage();
else
baseImage = pathEntry->image;
}
}
if (baseImage)
{
auto image = *baseImage + kConstructionPreviewImages[slopeOffset][direction];
// Draw construction image
screenCoords = this->windowPos
+ ScreenCoordsXY{ widgets[WIDX_CONSTRUCT].midX(), widgets[WIDX_CONSTRUCT].bottom - 60 };
GfxDrawSprite(rt, ImageId(image), screenCoords);
}
// Draw build this... label
screenCoords = this->windowPos
+ ScreenCoordsXY{ widgets[WIDX_CONSTRUCT].midX(), widgets[WIDX_CONSTRUCT].bottom - 23 };
DrawTextBasic(rt, screenCoords, STR_BUILD_THIS, {}, { TextAlignment::CENTRE });
}
// Draw cost
screenCoords = this->windowPos
+ ScreenCoordsXY{ widgets[WIDX_CONSTRUCT].midX(), widgets[WIDX_CONSTRUCT].bottom - 12 };
if (_windowFootpathCost != kMoney64Undefined)
{
if (!(getGameState().park.flags & PARK_FLAGS_NO_MONEY))
{
auto ft = Formatter();
ft.Add<money64>(_windowFootpathCost);
DrawTextBasic(rt, screenCoords, STR_COST_LABEL, ft, { TextAlignment::CENTRE });
}
}
}
#pragma endregion
private:
/**
*
* rct2: 0x006A7760
*/
void WindowFootpathUpdateProvisionalPathForBridgeMode()
{
if (_footpathConstructionMode != PathConstructionMode::bridgeOrTunnel)
{
return;
}
// Recheck area for construction. Set by ride_construction window
if (_provisionalFootpath.flags.has(ProvisionalPathFlag::forceRecheck))
{
FootpathRemoveProvisional();
_provisionalFootpath.flags.unset(ProvisionalPathFlag::forceRecheck);
}
// Update provisional bridge mode path
if (!(_provisionalFootpath.flags.has(ProvisionalPathFlag::placed)))
{
ObjectEntryIndex type;
ObjectEntryIndex railings = gFootpathSelection.Railings;
CoordsXYZ footpathLoc;
FootpathSlope slope;
FootpathGetNextPathInfo(&type, footpathLoc, &slope);
auto pathConstructFlags = FootpathCreateConstructFlags(type);
auto tiles = std::array<ProvisionalTile, 1>({ footpathLoc, slope });
_windowFootpathCost = FootpathProvisionalSet(
type, railings, footpathLoc, footpathLoc, footpathLoc.z, tiles, pathConstructFlags);
invalidateWidget(WIDX_CONSTRUCT);
}
auto curTime = Platform::GetTicks();
// Update little directional arrow on provisional bridge mode path
if (_footpathConstructionNextArrowPulse < curTime)
{
_footpathConstructionNextArrowPulse = curTime + ARROW_PULSE_DURATION;
_provisionalFootpath.flags.flip(ProvisionalPathFlag::showArrow);
CoordsXYZ footpathLoc;
FootpathSlope slope;
FootpathGetNextPathInfo(nullptr, footpathLoc, &slope);
gMapSelectArrowPosition = footpathLoc;
gMapSelectArrowDirection = _footpathConstructDirection;
if (_provisionalFootpath.flags.has(ProvisionalPathFlag::showArrow))
{
gMapSelectFlags.set(MapSelectFlag::enableArrow);
}
else
{
gMapSelectFlags.unset(MapSelectFlag::enableArrow);
}
MapInvalidateTileFull(footpathLoc);
}
}
void WindowFootpathDrawDropdownButtons(RenderTarget& rt)
{
if (gFootpathSelection.LegacyPath == kObjectEntryIndexNull)
{
// Set footpath and queue type button images
auto pathImage = kSpriteIdNull;
auto queueImage = kSpriteIdNull;
auto pathEntry = GetPathSurfaceEntry(gFootpathSelection.NormalSurface);
if (pathEntry != nullptr)
{
pathImage = pathEntry->PreviewImageId;
}
pathEntry = GetPathSurfaceEntry(gFootpathSelection.QueueSurface);
if (pathEntry != nullptr)
{
queueImage = pathEntry->PreviewImageId;
}
WindowFootpathDrawDropdownButton(rt, WIDX_FOOTPATH_TYPE, pathImage);
WindowFootpathDrawDropdownButton(rt, WIDX_QUEUELINE_TYPE, queueImage);
// Set railing
auto railingsImage = kSpriteIdNull;
auto railingsEntry = GetPathRailingsEntry(gFootpathSelection.Railings);
if (railingsEntry != nullptr)
{
railingsImage = railingsEntry->PreviewImageId;
}
WindowFootpathDrawDropdownButton(rt, WIDX_RAILINGS_TYPE, railingsImage);
}
else
{
auto& objManager = OpenRCT2::GetContext()->GetObjectManager();
// Set footpath and queue type button images
auto pathImage = kSpriteIdNull;
auto queueImage = kSpriteIdNull;
const auto* pathObj = objManager.GetLoadedObject<FootpathObject>(gFootpathSelection.LegacyPath);
if (pathObj != nullptr)
{
auto pathEntry = reinterpret_cast<const FootpathEntry*>(pathObj->GetLegacyData());
pathImage = pathEntry->GetPreviewImage();
queueImage = pathEntry->GetQueuePreviewImage();
}
WindowFootpathDrawDropdownButton(rt, WIDX_FOOTPATH_TYPE, pathImage);
WindowFootpathDrawDropdownButton(rt, WIDX_QUEUELINE_TYPE, queueImage);
}
}
void WindowFootpathDrawDropdownButton(RenderTarget& rt, WidgetIndex widgetIndex, ImageIndex image)
{
const auto& widget = widgets[widgetIndex];
GfxDrawSprite(rt, ImageId(image), { windowPos.x + widget.left, windowPos.y + widget.top });
}
/**
*
* rct2: 0x006A7F88
*/
void WindowFootpathShowFootpathTypesDialog(Widget* widget, bool showQueues)
{
auto& objManager = OpenRCT2::GetContext()->GetObjectManager();
uint32_t numPathTypes = 0;
// If the game is in sandbox mode, also show paths that are normally restricted to the scenario editor
bool showEditorPaths = (gLegacyScene == LegacyScene::scenarioEditor || getGameState().cheats.sandboxMode);
_dropdownEntries.clear();
std::optional<size_t> defaultIndex;
for (ObjectEntryIndex i = 0; i < kMaxFootpathSurfaceObjects; i++)
{
const auto* pathType = objManager.GetLoadedObject<FootpathSurfaceObject>(i);
if (pathType == nullptr)
{
continue;
}
if ((pathType->Flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR) && !showEditorPaths)
{
continue;
}
// If regular paths can be used as queue, only hide the path if were _not_ looking at a queue,
// but the path surface is one.
if (getGameState().cheats.allowRegularPathAsQueue && !showQueues
&& ((pathType->Flags & FOOTPATH_ENTRY_FLAG_IS_QUEUE) != 0))
{
continue;
}
// If the cheat is disabled, hide queues from the regular path view and vice versa.
else if (
!getGameState().cheats.allowRegularPathAsQueue
&& showQueues != ((pathType->Flags & FOOTPATH_ENTRY_FLAG_IS_QUEUE) != 0))
{
continue;
}
if (gFootpathSelection.LegacyPath == kObjectEntryIndexNull
&& i == (showQueues ? gFootpathSelection.QueueSurface : gFootpathSelection.NormalSurface))
{
defaultIndex = numPathTypes;
}
gDropdown.items[numPathTypes] = Dropdown::ImageItem(ImageId(pathType->PreviewImageId), pathType->NameStringId);
_dropdownEntries.push_back({ ObjectType::footpathSurface, i });
numPathTypes++;
}
for (ObjectEntryIndex i = 0; i < kMaxPathObjects; i++)
{
auto* pathObj = objManager.GetLoadedObject<FootpathObject>(i);
if (pathObj == nullptr)
{
continue;
}
auto pathEntry = reinterpret_cast<const FootpathEntry*>(pathObj->GetLegacyData());
if ((pathEntry->flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR) && !showEditorPaths)
{
continue;
}
if (gFootpathSelection.LegacyPath != kObjectEntryIndexNull && gFootpathSelection.LegacyPath == i)
{
defaultIndex = numPathTypes;
}
auto image = ImageId(showQueues ? pathEntry->GetQueuePreviewImage() : pathEntry->GetPreviewImage());
gDropdown.items[numPathTypes] = Dropdown::ImageItem(image, pathEntry->string_idx);
_dropdownEntries.push_back({ ObjectType::paths, i });
numPathTypes++;
}
auto itemsPerRow = DropdownGetAppropriateImageDropdownItemsPerRow(numPathTypes);
WindowDropdownShowImage(
windowPos.x + widget->left, windowPos.y + widget->top, widget->height() + 1, colours[1], 0, numPathTypes, 47,
36, itemsPerRow);
gDropdown.hasTooltips = true;
if (defaultIndex)
gDropdown.defaultIndex = static_cast<int32_t>(*defaultIndex);
}
void WindowFootpathShowRailingsTypesDialog(Widget* widget)
{
uint32_t numRailingsTypes = 0;
// If the game is in sandbox mode, also show paths that are normally restricted to the scenario editor
_dropdownEntries.clear();
std::optional<size_t> defaultIndex;
for (int32_t i = 0; i < kMaxFootpathRailingsObjects; i++)
{
const auto* railingsEntry = GetPathRailingsEntry(i);
if (railingsEntry == nullptr)
{
continue;
}
if (i == gFootpathSelection.Railings)
{
defaultIndex = numRailingsTypes;
}
gDropdown.items[numRailingsTypes] = Dropdown::ImageItem(
ImageId(railingsEntry->PreviewImageId), railingsEntry->NameStringId);
_dropdownEntries.push_back({ ObjectType::footpathRailings, i });
numRailingsTypes++;
}
auto itemsPerRow = DropdownGetAppropriateImageDropdownItemsPerRow(numRailingsTypes);
WindowDropdownShowImage(
windowPos.x + widget->left, windowPos.y + widget->top, widget->height() + 1, colours[1], 0, numRailingsTypes,
47, 36, itemsPerRow);
gDropdown.hasTooltips = true;
if (defaultIndex)
gDropdown.defaultIndex = static_cast<int32_t>(*defaultIndex);
}
/**
*
* rct2: 0x006A8111 0x006A8135 0x006A815C 0x006A8183
*/
void WindowFootpathMousedownDirection(int32_t direction)
{
FootpathUpdateProvisional();
_footpathConstructDirection = (direction - GetCurrentRotation()) & 3;
_windowFootpathCost = kMoney64Undefined;
WindowFootpathSetEnabledAndPressedWidgets();
}
/**
*
* rct2: 0x006A81AA 0x006A81C5 0x006A81E0
*/
void WindowFootpathMousedownSlope(SlopePitch pitch)
{
FootpathUpdateProvisional();
_footpathConstructSlope = pitch;
_windowFootpathCost = kMoney64Undefined;
WindowFootpathSetEnabledAndPressedWidgets();
}
void WindowFootpathUpdateModifierKeyState(ScreenCoordsXY& screenCoords)
{
auto& im = GetInputManager();
// First, store the initial copy/ctrl state
if (!_footpathPlaceCtrlState && im.isModifierKeyPressed(ModifierKey::ctrl))
{
constexpr auto interactionFlags = EnumsToFlags(
ViewportInteractionItem::terrain, ViewportInteractionItem::ride, ViewportInteractionItem::scenery,
ViewportInteractionItem::footpath, ViewportInteractionItem::wall, ViewportInteractionItem::largeScenery);
auto info = GetMapCoordinatesFromPos(screenCoords, interactionFlags);
if (info.interactionType != ViewportInteractionItem::none)
{
const bool allowInvalidHeights = getGameState().cheats.allowTrackPlaceInvalidHeights;
const auto heightStep = kCoordsZStep * (!allowInvalidHeights ? 2 : 1);
_footpathPlaceCtrlZ = floor2(info.Element->GetBaseZ(), heightStep);
_footpathPlaceCtrlState = true;
}
}
else if (!im.isModifierKeyPressed(ModifierKey::ctrl))
{
_footpathPlaceCtrlState = false;
_footpathPlaceCtrlZ = 0;
}
// In addition, vertical shifting on top of the base (copy) placement?
if (!_footpathPlaceShiftState && im.isModifierKeyPressed(ModifierKey::shift))
{
_footpathPlaceShiftState = true;
_footpathPlaceShiftStart = screenCoords;
_footpathPlaceShiftZ = 0;
}
else if (im.isModifierKeyPressed(ModifierKey::shift))
{
uint16_t maxPathHeight = ZoomLevel::max().ApplyTo(
std::numeric_limits<decltype(TileElement::BaseHeight)>::max() - 32);
_footpathPlaceShiftZ = _footpathPlaceShiftStart.y - screenCoords.y + 4;
// Scale delta by zoom to match mouse position.
auto* mainWnd = WindowGetMain();
if (mainWnd != nullptr && mainWnd->viewport != nullptr)
{
_footpathPlaceShiftZ = mainWnd->viewport->zoom.ApplyTo(_footpathPlaceShiftZ);
}
const bool allowInvalidHeights = getGameState().cheats.allowTrackPlaceInvalidHeights;
const auto heightStep = kCoordsZStep * (!allowInvalidHeights ? 2 : 1);
_footpathPlaceShiftZ = floor2(_footpathPlaceShiftZ, heightStep);
// Clamp to maximum possible value of BaseHeight can offer.
_footpathPlaceShiftZ = std::min<int16_t>(_footpathPlaceShiftZ, maxPathHeight);
screenCoords = _footpathPlaceShiftStart;
}
else if (_footpathPlaceShiftState)
{
_footpathPlaceShiftState = false;
_footpathPlaceShiftZ = 0;
}
}
std::optional<CoordsXY> FootpathGetPlacePositionFromScreenPosition(ScreenCoordsXY screenCoords)
{
WindowFootpathUpdateModifierKeyState(screenCoords);
CoordsXY mapCoords;
if (!_footpathPlaceCtrlState)
{
auto info = GetMapCoordinatesFromPos(
screenCoords, EnumsToFlags(ViewportInteractionItem::terrain, ViewportInteractionItem::footpath));
if (info.interactionType == ViewportInteractionItem::none)
return std::nullopt;
mapCoords = info.Loc;
_footpathPlaceZ = 0;
if (_footpathPlaceShiftState)
{
auto res = FootpathGetPlacementFromInfo(info);
auto mapZ = res.baseZ + _footpathPlaceShiftZ;
mapZ = std::max<int16_t>(mapZ, 16);
_footpathPlaceZ = mapZ;
}
}
else
{
auto mapZ = _footpathPlaceCtrlZ;
auto mapXYCoords = ScreenGetMapXYWithZ(screenCoords, mapZ);
if (mapXYCoords.has_value())
{
mapCoords = mapXYCoords.value();
}
else
{
return std::nullopt;
}
if (_footpathPlaceShiftState != 0)
{
mapZ += _footpathPlaceShiftZ;
}
_footpathPlaceZ = std::max<int32_t>(mapZ, 16);
}
if (mapCoords.x == kLocationNull)
return std::nullopt;
return mapCoords.ToTileStart();
}
FootpathPlacementResult FootpathGetPlacementFromInfo(const InteractionInfo& info)
{
if (info.interactionType == ViewportInteractionItem::none || info.Element == nullptr)
{
gMapSelectFlags.unset(MapSelectFlag::enable);
FootpathUpdateProvisional();
return {};
}
switch (info.interactionType)
{
case ViewportInteractionItem::terrain:
{
auto surfaceElement = info.Element->AsSurface();
if (surfaceElement != nullptr)
{
return FootpathGetOnTerrainPlacement(*surfaceElement);
}
break;
}
case ViewportInteractionItem::footpath:
{
auto pathElement = info.Element->AsPath();
if (pathElement != nullptr)
{
auto slopeDirection = pathElement->GetSlopeDirection();
FootpathSlope slope = { FootpathSlopeType::flat, slopeDirection };
if (pathElement->IsSloped())
{
slope.type = FootpathSlopeType::sloped;
}
return { pathElement->GetBaseZ(), slope };
}
break;
}
default:
break;
}
return {};
}
/**
*
* rct2: 0x006A81FB
*/
void WindowFootpathSetProvisionalPathAtPoint(const ScreenCoordsXY& screenCoords)
{
MapInvalidateSelectionRect();
gMapSelectFlags.unset(MapSelectFlag::enableArrow);
// Get current map pos and handle key modifier state
auto mapPos = FootpathGetPlacePositionFromScreenPosition(screenCoords);
if (!mapPos)
return;
// Check for change
auto provisionalPos = CoordsXYZ(*mapPos, _footpathPlaceZ);
if ((_provisionalFootpath.flags.has(ProvisionalPathFlag::placed))
&& _provisionalFootpath.positionA == provisionalPos)
{
return;
}
// Set map selection
gMapSelectFlags.set(MapSelectFlag::enable);
gMapSelectType = MapSelectType::full;
setMapSelectRange(*mapPos);
FootpathUpdateProvisional();
// Figure out what slope and height to use
auto placement = WindowFootpathGetPlacementFromScreenCoords(screenCoords);
if (!placement.isValid())
{
gMapSelectFlags.unset(MapSelectFlag::enable);
FootpathUpdateProvisional();
return;
}
// Set provisional path
auto pathType = gFootpathSelection.GetSelectedSurface();
auto constructFlags = FootpathCreateConstructFlags(pathType);
auto footpathLoc = CoordsXYZ(*mapPos, placement.baseZ);
auto tiles = std::array<ProvisionalTile, 1>({ footpathLoc, placement.slope });
const auto footpathCost = FootpathProvisionalSet(
pathType, gFootpathSelection.Railings, *mapPos, *mapPos, placement.baseZ, tiles, constructFlags);
if (_windowFootpathCost != footpathCost)
{
_windowFootpathCost = footpathCost;
invalidateWidget(WIDX_CONSTRUCT);
}
}
static std::vector<ProvisionalTile> buildTileVector(MapRange range, int32_t baseZ)
{
std::vector<ProvisionalTile> tiles{};
for (auto y = range.GetY1(); y <= range.GetY2(); y += kCoordsXYStep)
{
for (auto x = range.GetX1(); x <= range.GetX2(); x += kCoordsXYStep)
{
FootpathPlacementResult placement = { baseZ, {} };
if (baseZ == 0)
placement = FootpathGetOnTerrainPlacement(TileCoordsXY(CoordsXY(x, y)));
auto calculatedLocation = CoordsXYZ(x, y, placement.baseZ);
tiles.push_back({ calculatedLocation, placement.slope });
}
}
return tiles;
}
void WindowFootpathSetProvisionalPathDragArea(MapRange range, int32_t baseZ)
{
MapInvalidateSelectionRect();
gMapSelectFlags.unset(MapSelectFlag::enableArrow);
// Check for change
if ((_provisionalFootpath.flags.has(ProvisionalPathFlag::placed)) && range.Point1 == _provisionalFootpath.positionA
&& range.Point2 == _provisionalFootpath.positionB && baseZ == _provisionalFootpath.startZ)
{
return;
}
// Set map selection
gMapSelectFlags.set(MapSelectFlag::enable);
gMapSelectType = MapSelectType::full;
FootpathUpdateProvisional();
// Set provisional path
auto tiles = buildTileVector(range, baseZ);
auto pathType = gFootpathSelection.GetSelectedSurface();
auto constructFlags = FootpathCreateConstructFlags(pathType);
const auto footpathCost = FootpathProvisionalSet(
pathType, gFootpathSelection.Railings, range.Point1, range.Point2, baseZ, tiles, constructFlags);
if (_windowFootpathCost != footpathCost)
{
_windowFootpathCost = footpathCost;
invalidateWidget(WIDX_CONSTRUCT);
}
}
/**
*
* rct2: 0x006A8388
*/
void WindowFootpathSetSelectionStartBridgeAtPoint(const ScreenCoordsXY& screenCoords)
{
int32_t direction;
TileElement* tileElement;
MapInvalidateSelectionRect();
gMapSelectFlags.unset(MapSelectFlag::enable, MapSelectFlag::enableArrow);
auto mapCoords = FootpathBridgeGetInfoFromPos(screenCoords, &direction, &tileElement);
if (mapCoords.IsNull())
{
return;
}
gMapSelectFlags.set(MapSelectFlag::enable, MapSelectFlag::enableArrow);
gMapSelectType = MapSelectType::full;
setMapSelectRange(mapCoords);
int32_t z = tileElement->GetBaseZ();
if (tileElement->GetType() == TileElementType::Surface)
{
uint8_t slope = tileElement->AsSurface()->GetSlope();
if (slope & kTileSlopeRaisedCornersMask)
{
z += kPathHeightStep;
} // Add 2 for a slope
if (slope & kTileSlopeDiagonalFlag)
z += kPathHeightStep; // Add another 2 for a steep slope
}
gMapSelectArrowPosition = CoordsXYZ{ mapCoords, z };
gMapSelectArrowDirection = direction;
MapInvalidateSelectionRect();
}
FootpathPlacementResult WindowFootpathGetPlacementFromScreenCoords(const ScreenCoordsXY& screenCoords)
{
if (_footpathPlaceZ != 0)
return { _footpathPlaceZ, { FootpathSlopeType::flat } };
const auto info = GetMapCoordinatesFromPos(
screenCoords, EnumsToFlags(ViewportInteractionItem::terrain, ViewportInteractionItem::footpath));
return FootpathGetPlacementFromInfo(info);
}
void WindowFootpathPlaceDragAreaSetStart(const ScreenCoordsXY& screenCoords)
{
if (_footpathErrorOccured)
{
return;
}
auto mapPos = FootpathGetPlacePositionFromScreenPosition(screenCoords);
if (!mapPos)
return;
auto placement = WindowFootpathGetPlacementFromScreenCoords(screenCoords);
if (placement.baseZ > 0 && placement.slope.type == FootpathSlopeType::flat)
_footpathPlaceZ = placement.baseZ;
gMapSelectFlags.set(MapSelectFlag::enable);
gMapSelectType = MapSelectType::full;
auto correctedPosition = mapPos->ToTileStart();
setMapSelectRange(correctedPosition);
_dragStartPos = correctedPosition;
WindowFootpathSetProvisionalPathDragArea(getMapSelectRange(), placement.baseZ);
}
void WindowFootpathPlaceDragAreaSetEnd(const ScreenCoordsXY& screenCoords)
{
if (_footpathErrorOccured)
{
return;
}
std::optional<CoordsXY> mapPos;
if (_footpathPlaceShiftState)
mapPos = ScreenGetMapXYWithZ(screenCoords, _footpathPlaceZ);
else
mapPos = FootpathGetPlacePositionFromScreenPosition(screenCoords);
if (!mapPos)
{
return;
}
auto correctedPos = mapPos->ToTileStart();
// For queues, only allow selecting a single line.
if (gFootpathSelection.IsQueueSelected)
{
auto xDiff = correctedPos.x - _dragStartPos.x;
auto yDiff = correctedPos.y - _dragStartPos.y;
if (std::abs(xDiff) > std::abs(yDiff))
{
correctedPos.y = _dragStartPos.y;
}
else
{
correctedPos.x = _dragStartPos.x;
}
}
setMapSelectRange({ _dragStartPos, correctedPos });
WindowFootpathSetProvisionalPathDragArea(getMapSelectRange(), _footpathPlaceZ);
}
void WindowFootpathPlaceDragAreaHover(const ScreenCoordsXY& screenCoords)
{
bool isLeftMousePressed = gInputFlags.has(InputFlag::leftMousePressed);
if (isLeftMousePressed)
return;
MapInvalidateSelectionRect();
gMapSelectFlags.unset(MapSelectFlag::enableArrow);
auto mapPos = FootpathGetPlacePositionFromScreenPosition(screenCoords);
if (!mapPos)
{
gMapSelectFlags.unset(MapSelectFlag::enable);
return;
}
auto placement = WindowFootpathGetPlacementFromScreenCoords(screenCoords);
// Set map selection
gMapSelectFlags.set(MapSelectFlag::enable);
gMapSelectType = MapSelectType::full;
setMapSelectRange(*mapPos);
WindowFootpathSetProvisionalPathDragArea(getMapSelectRange(), placement.baseZ);
}
void WindowFootpathPlacePath()
{
if (_footpathErrorOccured)
{
return;
}
FootpathUpdateProvisional();
if (_provisionalFootpath.tiles.size() == 0)
return;
// Try and place path
auto selectedType = gFootpathSelection.GetSelectedSurface();
PathConstructFlags constructFlags = FootpathCreateConstructFlags(selectedType);
bool anySuccess = false;
money64 cost = 0;
CoordsXYZ lastLocation;
for (const auto& tile : _provisionalFootpath.tiles)
{
auto footpathPlaceAction = GameActions::FootpathPlaceAction(
tile.position, tile.slope, selectedType, gFootpathSelection.Railings, kInvalidDirection, constructFlags);
auto result = GameActions::Execute(&footpathPlaceAction, getGameState());
if (result.Error == GameActions::Status::Ok)
{
anySuccess = true;
cost += result.Cost;
}
lastLocation = tile.position;
}
if (anySuccess)
{
// Don't play sound if it is no cost to prevent multiple sounds. TODO: make this work in no
// money scenarios
if (cost != 0)
{
Audio::Play3D(Audio::SoundId::placeItem, lastLocation);
}
}
else
{
_footpathErrorOccured = true;
}
}
/**
*
* rct2: 0x006A82C5
*/
void WindowFootpathPlacePathAtPoint(const ScreenCoordsXY& screenCoords)
{
if (_footpathErrorOccured)
{
return;
}
FootpathUpdateProvisional();
auto mapPos = FootpathGetPlacePositionFromScreenPosition(screenCoords);
if (!mapPos)
return;
auto placement = WindowFootpathGetPlacementFromScreenCoords(screenCoords);
// Try and place path
auto selectedType = gFootpathSelection.GetSelectedSurface();
PathConstructFlags constructFlags = FootpathCreateConstructFlags(selectedType);
auto footpathPlaceAction = GameActions::FootpathPlaceAction(
{ *mapPos, placement.baseZ }, placement.slope, selectedType, gFootpathSelection.Railings, kInvalidDirection,
constructFlags);
footpathPlaceAction.SetCallback([this](const GameActions::GameAction* ga, const GameActions::Result* result) {
if (result->Error == GameActions::Status::Ok)
{
// Don't play sound if it is no cost to prevent multiple sounds. TODO: make this work in no money scenarios
if (result->Cost != 0)
{
OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::placeItem, result->Position);
}
}
else
{
_footpathErrorOccured = true;
}
});
GameActions::Execute(&footpathPlaceAction, getGameState());
}
/**
*
* rct2: 0x006A840F
*/
void WindowFootpathStartBridgeAtPoint(const ScreenCoordsXY& screenCoords)
{
int32_t z, direction;
TileElement* tileElement;
auto mapCoords = FootpathBridgeGetInfoFromPos(screenCoords, &direction, &tileElement);
if (mapCoords.IsNull())
{
return;
}
if (tileElement->GetType() == TileElementType::Surface)
{
// If we start the path on a slope, the arrow is slightly raised, so we
// expect the path to be slightly raised as well.
uint8_t slope = tileElement->AsSurface()->GetSlope();
z = tileElement->GetBaseZ();
if (slope & kTileSlopeDiagonalFlag)
{
// Steep diagonal slope
z += 2 * kPathHeightStep;
}
else if (slope & kTileSlopeRaisedCornersMask)
{
// Normal slope
z += kPathHeightStep;
}
}
else
{
z = tileElement->GetBaseZ();
if (tileElement->GetType() == TileElementType::Path)
{
if (tileElement->AsPath()->IsSloped())
{
if (direction == (tileElement->AsPath()->GetSlopeDirection()))
{
z += kPathHeightStep;
}
}
}
}
ToolCancel();
_footpathConstructFromPosition = { mapCoords, z };
_footpathConstructDirection = direction;
_provisionalFootpath.flags.clearAll();
_footpathConstructSlope = SlopePitch::flat;
_footpathConstructionMode = PathConstructionMode::bridgeOrTunnel;
_footpathConstructValidDirections = kInvalidDirection;
WindowFootpathSetEnabledAndPressedWidgets();
}
/**
* Construct a piece of footpath while in bridge building mode.
* rct2: 0x006A79B7
*/
void WindowFootpathConstruct()
{
_windowFootpathCost = kMoney64Undefined;
FootpathUpdateProvisional();
ObjectEntryIndex type;
FootpathSlope slope;
CoordsXYZ footpathLoc;
FootpathGetNextPathInfo(&type, footpathLoc, &slope);
PathConstructFlags constructFlags = FootpathCreateConstructFlags(type);
auto footpathPlaceAction = GameActions::FootpathPlaceAction(
footpathLoc, slope, type, gFootpathSelection.Railings, _footpathConstructDirection, constructFlags);
footpathPlaceAction.SetCallback(
[footpathLoc](const GameActions::GameAction* ga, const GameActions::Result* result) {
if (result->Error == GameActions::Status::Ok)
{
Audio::Play3D(OpenRCT2::Audio::SoundId::placeItem, result->Position);
}
auto* windowMgr = GetWindowManager();
auto* self = static_cast<FootpathWindow*>(windowMgr->FindByClass(WindowClass::footpath));
if (self == nullptr)
{
return;
}
if (result->Error == GameActions::Status::Ok)
{
if (_footpathConstructSlope == SlopePitch::flat)
{
self->_footpathConstructValidDirections = kInvalidDirection;
}
else
{
self->_footpathConstructValidDirections = self->_footpathConstructDirection;
}
if (gFootpathGroundFlags & ELEMENT_IS_UNDERGROUND)
{
ViewportSetVisibility(ViewportVisibility::undergroundViewOn);
}
_footpathConstructFromPosition = footpathLoc;
// If we have just built an upwards slope, the next path to construct is
// a bit higher. Note that the z returned by footpath_get_next_path_info
// already is lowered if we are building a downwards slope.
if (_footpathConstructSlope == SlopePitch::upwards)
{
_footpathConstructFromPosition.z += kPathHeightStep;
}
}
self->WindowFootpathSetEnabledAndPressedWidgets();
});
GameActions::Execute(&footpathPlaceAction, getGameState());
}
/**
*
* rct2: 0x006A78EF
*/
void FootpathRemoveTileElement(TileElement* tileElement)
{
auto z = tileElement->GetBaseZ();
if (tileElement->AsPath()->IsSloped())
{
uint8_t slopeDirection = tileElement->AsPath()->GetSlopeDirection();
slopeDirection = DirectionReverse(slopeDirection);
if (slopeDirection == _footpathConstructDirection)
{
z += kPathHeightStep;
}
}
// Find a connected edge
int32_t edge = DirectionReverse(_footpathConstructDirection);
if (!(tileElement->AsPath()->GetEdges() & (1 << edge)))
{
edge = (edge + 1) & 3;
if (!(tileElement->AsPath()->GetEdges() & (1 << edge)))
{
edge = (edge + 2) & 3;
if (!(tileElement->AsPath()->GetEdges() & (1 << edge)))
{
edge = (edge - 1) & 3;
if (!(tileElement->AsPath()->GetEdges() & (1 << edge)))
{
edge = DirectionReverse(edge);
}
}
}
}
_footpathConstructFromPosition.z = tileElement->GetBaseZ();
auto action = GameActions::FootpathRemoveAction(_footpathConstructFromPosition);
GameActions::Execute(&action, getGameState());
// Move selection
edge = DirectionReverse(edge);
_footpathConstructFromPosition.x -= CoordsDirectionDelta[edge].x;
_footpathConstructFromPosition.y -= CoordsDirectionDelta[edge].y;
_footpathConstructFromPosition.z = z;
_footpathConstructDirection = edge;
_footpathConstructValidDirections = kInvalidDirection;
}
/**
*
* rct2: 0x006A7873
*/
TileElement* FootpathGetTileElementToRemove()
{
TileElement* tileElement;
int32_t z, zLow;
if (!MapIsLocationValid(_footpathConstructFromPosition))
{
return nullptr;
}
z = std::min(255 * kCoordsZStep, _footpathConstructFromPosition.z);
zLow = z - kPathHeightStep;
tileElement = MapGetFirstElementAt(_footpathConstructFromPosition);
do
{
if (tileElement == nullptr)
break;
if (tileElement->GetType() == TileElementType::Path)
{
if (tileElement->GetBaseZ() == z)
{
if (tileElement->AsPath()->IsSloped())
{
if (DirectionReverse(tileElement->AsPath()->GetSlopeDirection()) != _footpathConstructDirection)
{
continue;
}
}
return tileElement;
}
if (tileElement->GetBaseZ() == zLow)
{
if (!tileElement->AsPath()->IsSloped())
{
if ((tileElement->AsPath()->GetSlopeDirection()) == _footpathConstructDirection)
{
continue;
}
}
return tileElement;
}
}
} while (!(tileElement++)->IsLastForTile());
return nullptr;
}
/**
*
* rct2: 0x006A7863
*/
void WindowFootpathRemove()
{
TileElement* tileElement;
_windowFootpathCost = kMoney64Undefined;
FootpathUpdateProvisional();
tileElement = FootpathGetTileElementToRemove();
if (tileElement != nullptr)
{
FootpathRemoveTileElement(tileElement);
}
WindowFootpathSetEnabledAndPressedWidgets();
}
/**
*
* rct2: 0x006A855C
*/
void WindowFootpathSetEnabledAndPressedWidgets()
{
if (_footpathConstructionMode == PathConstructionMode::bridgeOrTunnel)
{
MapInvalidateMapSelectionTiles();
gMapSelectFlags.set(MapSelectFlag::enableConstruct, MapSelectFlag::green);
int32_t direction = _footpathConstructDirection;
gMapSelectionTiles.clear();
gMapSelectionTiles.push_back(
{ _footpathConstructFromPosition.x + CoordsDirectionDelta[direction].x,
_footpathConstructFromPosition.y + CoordsDirectionDelta[direction].y });
MapInvalidateMapSelectionTiles();
}
uint64_t newPressedWidgets = pressedWidgets
& ~((1LL << WIDX_DIRECTION_NW) | (1LL << WIDX_DIRECTION_NE) | (1LL << WIDX_DIRECTION_SW)
| (1LL << WIDX_DIRECTION_SE) | (1LL << WIDX_SLOPEDOWN) | (1LL << WIDX_LEVEL) | (1LL << WIDX_SLOPEUP));
uint64_t newDisabledWidgets = 0;
int32_t currentRotation = GetCurrentRotation();
if (_footpathConstructionMode == PathConstructionMode::bridgeOrTunnel)
{
// Set pressed directional widget
int32_t direction = (_footpathConstructDirection + currentRotation) & 3;
newPressedWidgets |= (1LL << (WIDX_DIRECTION_NW + direction));
// Set pressed slope widget
switch (_footpathConstructSlope)
{
case SlopePitch::flat:
newPressedWidgets |= (1uLL << WIDX_LEVEL);
break;
case SlopePitch::upwards:
newPressedWidgets |= (1uLL << WIDX_SLOPEUP);
break;
case SlopePitch::downwards:
newPressedWidgets |= (1uLL << WIDX_SLOPEDOWN);
break;
}
// Enable / disable directional widgets
direction = _footpathConstructValidDirections;
if (direction != kInvalidDirection)
{
newDisabledWidgets |= (1uLL << WIDX_DIRECTION_NW) | (1uLL << WIDX_DIRECTION_NE)
| (1uLL << WIDX_DIRECTION_SW) | (1uLL << WIDX_DIRECTION_SE);
direction = (direction + currentRotation) & 3;
newDisabledWidgets &= ~(1 << (WIDX_DIRECTION_NW + direction));
}
}
else
{
// Disable all bridge mode widgets
newDisabledWidgets |= (1uLL << WIDX_DIRECTION_GROUP) | (1uLL << WIDX_DIRECTION_NW) | (1uLL << WIDX_DIRECTION_NE)
| (1uLL << WIDX_DIRECTION_SW) | (1uLL << WIDX_DIRECTION_SE) | (1uLL << WIDX_SLOPE_GROUP)
| (1uLL << WIDX_SLOPEDOWN) | (1uLL << WIDX_LEVEL) | (1uLL << WIDX_SLOPEUP) | (1uLL << WIDX_CONSTRUCT)
| (1uLL << WIDX_REMOVE);
}
pressedWidgets = newPressedWidgets;
disabledWidgets = newDisabledWidgets;
invalidate();
}
/**
*
* rct2: 0x006A7B20
*/
void FootpathGetNextPathInfo(ObjectEntryIndex* type, CoordsXYZ& footpathLoc, FootpathSlope* slope)
{
auto direction = _footpathConstructDirection;
footpathLoc.x = _footpathConstructFromPosition.x + CoordsDirectionDelta[direction].x;
footpathLoc.y = _footpathConstructFromPosition.y + CoordsDirectionDelta[direction].y;
footpathLoc.z = _footpathConstructFromPosition.z;
if (type != nullptr)
{
*type = gFootpathSelection.GetSelectedSurface();
}
*slope = {};
if (_footpathConstructSlope != SlopePitch::flat)
{
*slope = { FootpathSlopeType::sloped, _footpathConstructDirection };
if (_footpathConstructSlope == SlopePitch::downwards)
{
footpathLoc.z -= kPathHeightStep;
slope->direction = DirectionReverse(slope->direction);
}
}
}
PathConstructFlags FootpathCreateConstructFlags(ObjectEntryIndex& type)
{
PathConstructFlags pathConstructFlags = 0;
if (gFootpathSelection.IsQueueSelected)
pathConstructFlags |= PathConstructFlag::IsQueue;
if (gFootpathSelection.LegacyPath != kObjectEntryIndexNull)
{
pathConstructFlags |= PathConstructFlag::IsLegacyPathObject;
type = gFootpathSelection.LegacyPath;
}
return pathConstructFlags;
}
#pragma region Keyboard Shortcuts Events
public:
void KeyboardShortcutTurnLeft()
{
if (isWidgetDisabled(WIDX_DIRECTION_NW) || isWidgetDisabled(WIDX_DIRECTION_NE)
|| isWidgetDisabled(WIDX_DIRECTION_SW) || isWidgetDisabled(WIDX_DIRECTION_SE)
|| _footpathConstructionMode != PathConstructionMode::bridgeOrTunnel)
{
return;
}
int32_t currentRotation = GetCurrentRotation();
int32_t turnedRotation = _footpathConstructDirection - currentRotation + (currentRotation % 2 == 1 ? 1 : -1);
WindowFootpathMousedownDirection(turnedRotation);
}
void KeyboardShortcutTurnRight()
{
if (isWidgetDisabled(WIDX_DIRECTION_NW) || isWidgetDisabled(WIDX_DIRECTION_NE)
|| isWidgetDisabled(WIDX_DIRECTION_SW) || isWidgetDisabled(WIDX_DIRECTION_SE)
|| _footpathConstructionMode != PathConstructionMode::bridgeOrTunnel)
{
return;
}
int32_t currentRotation = GetCurrentRotation();
int32_t turnedRotation = _footpathConstructDirection - currentRotation + (currentRotation % 2 == 1 ? -1 : 1);
WindowFootpathMousedownDirection(turnedRotation);
}
void KeyboardShortcutShortcutSlopeDown()
{
if (isWidgetDisabled(WIDX_SLOPEDOWN) || isWidgetDisabled(WIDX_LEVEL) || isWidgetDisabled(WIDX_SLOPEUP)
|| widgets[WIDX_LEVEL].type == WidgetType::empty)
{
return;
}
switch (_footpathConstructSlope)
{
case SlopePitch::flat:
onMouseDown(WIDX_SLOPEDOWN);
break;
case SlopePitch::upwards:
onMouseDown(WIDX_LEVEL);
break;
default:
case SlopePitch::downwards:
return;
}
}
void KeyboardShortcutSlopeUp()
{
if (isWidgetDisabled(WIDX_SLOPEDOWN) || isWidgetDisabled(WIDX_LEVEL) || isWidgetDisabled(WIDX_SLOPEUP)
|| widgets[WIDX_LEVEL].type == WidgetType::empty)
{
return;
}
switch (_footpathConstructSlope)
{
case SlopePitch::downwards:
onMouseDown(WIDX_LEVEL);
break;
case SlopePitch::flat:
onMouseDown(WIDX_SLOPEUP);
break;
default:
case SlopePitch::upwards:
return;
}
}
void KeyboardShortcutSlopeLevel()
{
if (isWidgetDisabled(WIDX_SLOPEDOWN) || isWidgetDisabled(WIDX_LEVEL) || isWidgetDisabled(WIDX_SLOPEUP)
|| widgets[WIDX_LEVEL].type == WidgetType::empty || _footpathConstructSlope == SlopePitch::flat)
{
return;
}
onMouseDown(WIDX_LEVEL);
}
void KeyboardShortcutDemolishCurrent()
{
if (isWidgetDisabled(WIDX_REMOVE) || widgets[WIDX_REMOVE].type == WidgetType::empty
|| (!getGameState().cheats.buildInPauseMode && GameIsPaused()))
{
return;
}
WindowFootpathRemove();
}
void KeyboardShortcutBuildCurrent()
{
if (isWidgetDisabled(WIDX_CONSTRUCT) || widgets[WIDX_CONSTRUCT].type == WidgetType::empty)
{
return;
}
onMouseDown(WIDX_CONSTRUCT);
}
#pragma endregion
};
/**
*
* rct2: 0x006A7C43
*/
WindowBase* FootpathOpen()
{
if (!WindowFootpathSelectDefault())
{
// No path objects to select from, don't open window
return nullptr;
}
auto* windowMgr = GetWindowManager();
return windowMgr->FocusOrCreate<FootpathWindow>(WindowClass::footpath, kWindowSize, {});
}
void WindowFootpathResetSelectedPath()
{
gFootpathSelection = {};
}
void WindowFootpathKeyboardShortcutTurnLeft()
{
auto* windowMgr = GetWindowManager();
WindowBase* w = windowMgr->FindByClass(WindowClass::footpath);
if (w != nullptr)
{
auto* footpathWindow = static_cast<FootpathWindow*>(w);
if (footpathWindow != nullptr)
{
footpathWindow->KeyboardShortcutTurnLeft();
}
}
}
void WindowFootpathKeyboardShortcutTurnRight()
{
auto* windowMgr = GetWindowManager();
WindowBase* w = windowMgr->FindByClass(WindowClass::footpath);
if (w != nullptr)
{
auto* footpathWindow = static_cast<FootpathWindow*>(w);
if (footpathWindow != nullptr)
{
footpathWindow->KeyboardShortcutTurnRight();
}
}
}
void WindowFootpathKeyboardShortcutSlopeDown()
{
auto* windowMgr = GetWindowManager();
WindowBase* w = windowMgr->FindByClass(WindowClass::footpath);
if (w != nullptr)
{
auto* footpathWindow = static_cast<FootpathWindow*>(w);
if (footpathWindow != nullptr)
{
footpathWindow->KeyboardShortcutShortcutSlopeDown();
}
}
}
void WindowFootpathKeyboardShortcutSlopeUp()
{
auto* windowMgr = GetWindowManager();
WindowBase* w = windowMgr->FindByClass(WindowClass::footpath);
if (w != nullptr)
{
auto* footpathWindow = static_cast<FootpathWindow*>(w);
if (footpathWindow != nullptr)
{
footpathWindow->KeyboardShortcutSlopeUp();
}
}
}
void WindowFootpathKeyboardShortcutDemolishCurrent()
{
auto* windowMgr = GetWindowManager();
WindowBase* w = windowMgr->FindByClass(WindowClass::footpath);
if (w != nullptr)
{
auto* footpathWindow = static_cast<FootpathWindow*>(w);
if (footpathWindow != nullptr)
{
footpathWindow->KeyboardShortcutDemolishCurrent();
}
}
}
void WindowFootpathKeyboardShortcutBuildCurrent()
{
auto* windowMgr = GetWindowManager();
WindowBase* w = windowMgr->FindByClass(WindowClass::footpath);
if (w != nullptr)
{
auto* footpathWindow = static_cast<FootpathWindow*>(w);
if (footpathWindow != nullptr)
{
footpathWindow->KeyboardShortcutBuildCurrent();
}
}
}
/**
*
* rct2: 0x0066CCE7
*/
void ToggleFootpathWindow()
{
auto* windowMgr = GetWindowManager();
if (windowMgr->FindByClass(WindowClass::footpath) == nullptr)
{
ContextOpenWindow(WindowClass::footpath);
}
else
{
ToolCancel();
windowMgr->CloseByClass(WindowClass::footpath);
}
}
/**
*
* rct2: 0x006A76FF
*/
static money64 FootpathProvisionalSet(
ObjectEntryIndex type, ObjectEntryIndex railingsType, const CoordsXY& footpathLocA, const CoordsXY& footpathLocB,
int32_t startZ, const std::span<const ProvisionalTile> tiles, PathConstructFlags constructFlags)
{
FootpathRemoveProvisional();
GameActions::Result res;
std::vector<ProvisionalTile> succesfulTiles{};
money64 cost = 0;
for (const auto& tile : tiles)
{
auto footpathPlaceAction = GameActions::FootpathPlaceAction(
tile.position, tile.slope, type, railingsType, kInvalidDirection, constructFlags);
footpathPlaceAction.SetFlags(GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED);
res = GameActions::Execute(&footpathPlaceAction, getGameState());
// The latter status will be returned if there is already path at the tile in question, to prevent a ghost
// from glitching through the existing path.
if (res.Error == GameActions::Status::Ok || res.Error == GameActions::Status::ItemAlreadyPlaced)
{
succesfulTiles.emplace_back(tile);
cost += res.Cost;
}
}
bool anySuccesful = succesfulTiles.size() > 0;
if (anySuccesful)
{
_provisionalFootpath.surfaceIndex = type;
_provisionalFootpath.railingsIndex = railingsType;
_provisionalFootpath.positionA = footpathLocA;
_provisionalFootpath.positionB = footpathLocB;
_provisionalFootpath.startZ = startZ;
_provisionalFootpath.tiles = succesfulTiles;
_provisionalFootpath.constructFlags = constructFlags;
_provisionalFootpath.flags.set(ProvisionalPathFlag::placed);
if (gFootpathGroundFlags & ELEMENT_IS_UNDERGROUND)
{
ViewportSetVisibility(ViewportVisibility::undergroundViewOn);
}
else
{
ViewportSetVisibility(ViewportVisibility::undergroundViewOff);
}
}
if (!isToolActive(WindowClass::scenery))
{
if (succesfulTiles.size() == 0)
{
// If we can't build this, don't show a virtual floor.
VirtualFloorSetHeight(0);
}
else if (
succesfulTiles[0].slope.type == FootpathSlopeType::flat
|| succesfulTiles[0].position.z < _footpathConstructFromPosition.z)
{
// Going either straight on, or down.
VirtualFloorSetHeight(succesfulTiles[0].position.z);
}
else
{
// Going up in the world!
VirtualFloorSetHeight(succesfulTiles[0].position.z + kLandHeightStep);
}
}
return anySuccesful ? cost : kMoney64Undefined;
}
/**
*
* rct2: 0x006A77FF
*/
void FootpathRemoveProvisional()
{
if (!_provisionalFootpath.flags.has(ProvisionalPathFlag::placed))
return;
_provisionalFootpath.flags.unset(ProvisionalPathFlag::placed);
for (const auto& tile : _provisionalFootpath.tiles)
{
auto action = GameActions::FootpathRemoveAction(tile.position);
action.SetFlags(GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
GameActions::Execute(&action, getGameState());
}
}
/**
*
* rct2: 0x006A7831
*/
void FootpathUpdateProvisional()
{
if (_provisionalFootpath.flags.has(ProvisionalPathFlag::showArrow))
{
_provisionalFootpath.flags.unset(ProvisionalPathFlag::showArrow);
gMapSelectFlags.unset(MapSelectFlag::enableArrow);
MapInvalidateTileFull(_footpathConstructFromPosition);
}
FootpathRemoveProvisional();
}
void FootpathRemoveProvisionalTemporarily()
{
if (_provisionalFootpath.flags.has(ProvisionalPathFlag::placed))
{
FootpathRemoveProvisional();
_provisionalFootpath.flags.set(ProvisionalPathFlag::placed);
}
}
void FootpathRestoreProvisional()
{
if (_provisionalFootpath.flags.has(ProvisionalPathFlag::placed))
{
_provisionalFootpath.flags.unset(ProvisionalPathFlag::placed);
FootpathProvisionalSet(
_provisionalFootpath.surfaceIndex, _provisionalFootpath.railingsIndex, _provisionalFootpath.positionA,
_provisionalFootpath.positionB, _provisionalFootpath.startZ, _provisionalFootpath.tiles,
_provisionalFootpath.constructFlags);
}
}
void FootpathRecheckProvisional()
{
_provisionalFootpath.flags.set(ProvisionalPathFlag::forceRecheck);
}
static ObjectEntryIndex FootpathGetDefaultSurface(bool queue)
{
bool showEditorPaths = (gLegacyScene == LegacyScene::scenarioEditor || getGameState().cheats.sandboxMode);
for (ObjectEntryIndex i = 0; i < kMaxFootpathSurfaceObjects; 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 kObjectEntryIndexNull;
}
static bool FootpathIsSurfaceEntryOkay(ObjectEntryIndex index, bool queue)
{
auto pathEntry = GetPathSurfaceEntry(index);
if (pathEntry != nullptr)
{
bool showEditorPaths = (gLegacyScene == LegacyScene::scenarioEditor || getGameState().cheats.sandboxMode);
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 < kMaxFootpathRailingsObjects; i++)
{
const auto* railingEntry = GetPathRailingsEntry(i);
if (railingEntry != nullptr)
{
return i;
}
}
return kObjectEntryIndexNull;
}
static bool FootpathIsLegacyPathEntryOkay(ObjectEntryIndex index)
{
bool showEditorPaths = (gLegacyScene == LegacyScene::scenarioEditor || getGameState().cheats.sandboxMode);
auto& objManager = OpenRCT2::GetContext()->GetObjectManager();
auto footpathObj = objManager.GetLoadedObject<FootpathObject>(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 < kMaxPathObjects; i++)
{
if (FootpathIsLegacyPathEntryOkay(i))
{
return i;
}
}
return kObjectEntryIndexNull;
}
bool WindowFootpathSelectDefault()
{
// 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 != kObjectEntryIndexNull)
{
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 = kObjectEntryIndexNull;
}
}
if (surfaceIndex == kObjectEntryIndexNull)
{
if (legacyPathIndex == kObjectEntryIndexNull)
{
// 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;
}
} // namespace OpenRCT2::Ui::Windows