1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-10 09:32:29 +01:00

Add footpath dragging tool

This commit is contained in:
Michael Steenbeek
2025-10-17 21:37:19 +02:00
committed by GitHub
parent a2cc3c20c6
commit ff580112c8
11 changed files with 399 additions and 76 deletions

View File

@@ -3843,3 +3843,4 @@ STR_7001 :Ride name
STR_7002 :{STRINGID} {STRINGID}
STR_7003 :Audio file {STRING} is truncated. Expected sample {INT32}, but only {INT32} are available. Consider reinstalling RCT2.
STR_7004 :Force Redraw
STR_7005 :Drag an area of footpath

View File

@@ -1,5 +1,6 @@
0.4.28 (in development)
------------------------------------------------------------------------
- Feature: [#25286] Footpath area dragging tool.
- Change: [#25089] Peep actions and animations that cause them to stop moving no longer trigger when they are on a level crossing.
- Change: [#25337] Placing track designs with scenery that is obstructed no longer disables all of the scenery.
- Fix: [#20486] Multiplayer desync when placing track designs without any scenery.

View File

@@ -838,5 +838,10 @@
},
{
"path": "icons/rotate_view_anti-clockwise.png"
},
{
"path": "tool/path_drag_tool.png",
"x": 1,
"y": 2
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

View File

@@ -703,6 +703,7 @@ namespace OpenRCT2
STR_CONSTRUCT_THE_SELECTED_FOOTPATH_SECTION_TIP = 1189,
STR_DIRECTION = 1183,
STR_DIRECTION_TIP = 1185,
STR_DRAG_AREA_OF_FOOTPATH_TIP = 7005,
STR_FOOTPATHS = 1181,
STR_FOOTPATH_TIP = 1424,
STR_LEVEL_TIP = 1187,

View File

@@ -50,13 +50,25 @@ 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 CoordsXYZ& footpathLoc, FootpathSlope slope,
PathConstructFlags constructFlags);
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
{
land,
onLand,
dragArea,
/**
* When picking a location to start the bridge or tunnel
*/
@@ -85,15 +97,21 @@ namespace OpenRCT2::Ui::Windows
};
using ProvisionalPathFlags = FlagHolder<uint8_t, ProvisionalPathFlag>;
struct ProvisionalFootpath
{
ObjectEntryIndex type;
CoordsXYZ position;
FootpathSlope slope;
ProvisionalPathFlags flags;
ObjectEntryIndex surfaceIndex;
ObjectEntryIndex railingsIndex;
PathConstructFlags constructFlags;
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;
@@ -103,7 +121,7 @@ namespace OpenRCT2::Ui::Windows
#pragma region Measurements
static constexpr StringId kWindowTitle = STR_FOOTPATHS;
static constexpr ScreenSize kWindowSize = { 106, 421 };
static constexpr ScreenSize kWindowSize = { 106, 461 };
#pragma endregion
@@ -136,10 +154,11 @@ namespace OpenRCT2::Ui::Windows
WIDX_MODE_GROUP,
WIDX_CONSTRUCT_ON_LAND,
WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL,
WIDX_CONSTRUCT_DRAG_AREA,
};
// clang-format off
static constexpr auto window_footpath_widgets = makeWidgets(
static constexpr auto kWindowFootpathWidgets = makeWidgets(
makeWindowShim(kWindowTitle, kWindowSize),
// Type group
@@ -164,9 +183,10 @@ namespace OpenRCT2::Ui::Windows
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, 54}, WidgetType::groupbox, WindowColour::primary ),
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({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
@@ -204,19 +224,21 @@ namespace OpenRCT2::Ui::Windows
int32_t _footpathPlaceZ;
CoordsXY _dragStartPos;
public:
#pragma region Window Override Events
void onOpen() override
{
setWidgets(window_footpath_widgets);
setWidgets(kWindowFootpathWidgets);
WindowInitScrollWidgets(*this);
WindowPushOthersRight(*this);
ShowGridlines();
ToolCancel();
_footpathConstructionMode = PathConstructionMode::land;
_footpathConstructionMode = PathConstructionMode::onLand;
ToolSet(*this, WIDX_CONSTRUCT_ON_LAND, Tool::pathDown);
gInputFlags.set(InputFlag::unk6);
_footpathErrorOccured = false;
@@ -253,16 +275,25 @@ namespace OpenRCT2::Ui::Windows
}
// Check tool
if (_footpathConstructionMode == PathConstructionMode::land)
switch (_footpathConstructionMode)
{
if (!isToolActive(WindowClass::footpath, WIDX_CONSTRUCT_ON_LAND))
close();
}
else if (_footpathConstructionMode == PathConstructionMode::bridgeOrTunnelPick)
{
if (!isToolActive(WindowClass::footpath, WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL))
close();
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
@@ -308,6 +339,20 @@ namespace OpenRCT2::Ui::Windows
}
}
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)
@@ -316,7 +361,7 @@ namespace OpenRCT2::Ui::Windows
close();
break;
case WIDX_CONSTRUCT_ON_LAND:
if (_footpathConstructionMode == PathConstructionMode::land)
if (_footpathConstructionMode == PathConstructionMode::onLand)
{
break;
}
@@ -326,12 +371,20 @@ namespace OpenRCT2::Ui::Windows
FootpathUpdateProvisional();
MapInvalidateMapSelectionTiles();
gMapSelectFlags.unset(MapSelectFlag::enableConstruct);
_footpathConstructionMode = PathConstructionMode::land;
_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)
{
@@ -404,6 +457,10 @@ namespace OpenRCT2::Ui::Windows
{
WindowFootpathSetProvisionalPathAtPoint(screenCoords);
}
else if (widgetIndex == WIDX_CONSTRUCT_DRAG_AREA)
{
WindowFootpathPlaceDragAreaHover(screenCoords);
}
else if (widgetIndex == WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL)
{
WindowFootpathSetSelectionStartBridgeAtPoint(screenCoords);
@@ -416,6 +473,11 @@ namespace OpenRCT2::Ui::Windows
{
_footpathErrorOccured = false;
}
else if (widgetIndex == WIDX_CONSTRUCT_DRAG_AREA)
{
WindowFootpathPlacePath();
_footpathErrorOccured = false;
}
}
void onToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override
@@ -424,6 +486,10 @@ namespace OpenRCT2::Ui::Windows
{
WindowFootpathPlacePathAtPoint(screenCoords);
}
else if (widgetIndex == WIDX_CONSTRUCT_DRAG_AREA)
{
WindowFootpathPlaceDragAreaSetStart(screenCoords);
}
else if (widgetIndex == WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL)
{
WindowFootpathStartBridgeAtPoint(screenCoords);
@@ -436,6 +502,24 @@ namespace OpenRCT2::Ui::Windows
{
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
@@ -529,17 +613,6 @@ namespace OpenRCT2::Ui::Windows
#pragma endregion
private:
FootpathPlacementResult FootpathGetPlacementFromScreenCoords(const ScreenCoordsXY& screenCoords)
{
if (_footpathPlaceZ > 0)
return { _footpathPlaceZ, {} };
auto info = GetMapCoordinatesFromPos(
screenCoords, EnumsToFlags(ViewportInteractionItem::terrain, ViewportInteractionItem::footpath));
return FootpathGetPlacementFromInfo(info);
}
/**
*
* rct2: 0x006A7760
@@ -568,8 +641,10 @@ namespace OpenRCT2::Ui::Windows
FootpathSlope slope;
FootpathGetNextPathInfo(&type, footpathLoc, &slope);
auto pathConstructFlags = FootpathCreateConstructFlags(type);
auto tiles = std::array<ProvisionalTile, 1>({ footpathLoc, slope });
_windowFootpathCost = FootpathProvisionalSet(type, railings, footpathLoc, slope, pathConstructFlags);
_windowFootpathCost = FootpathProvisionalSet(
type, railings, footpathLoc, footpathLoc, footpathLoc.z, tiles, pathConstructFlags);
invalidateWidget(WIDX_CONSTRUCT);
}
@@ -884,12 +959,8 @@ namespace OpenRCT2::Ui::Windows
if (_footpathPlaceShiftState)
{
auto surfaceElement = MapGetSurfaceElementAt(mapCoords);
if (surfaceElement == nullptr)
return std::nullopt;
auto mapZ = floor2(surfaceElement->GetBaseZ(), 16);
mapZ += _footpathPlaceShiftZ;
auto res = FootpathGetPlacementFromInfo(info);
auto mapZ = res.baseZ + _footpathPlaceShiftZ;
mapZ = std::max<int16_t>(mapZ, 16);
_footpathPlaceZ = mapZ;
}
@@ -979,7 +1050,7 @@ namespace OpenRCT2::Ui::Windows
// Check for change
auto provisionalPos = CoordsXYZ(*mapPos, _footpathPlaceZ);
if ((_provisionalFootpath.flags.has(ProvisionalPathFlag::placed))
&& _provisionalFootpath.position == provisionalPos)
&& _provisionalFootpath.positionA == provisionalPos)
{
return;
}
@@ -987,13 +1058,12 @@ namespace OpenRCT2::Ui::Windows
// Set map selection
gMapSelectFlags.set(MapSelectFlag::enable);
gMapSelectType = MapSelectType::full;
gMapSelectPositionA = *mapPos;
gMapSelectPositionB = *mapPos;
setMapSelectRange(*mapPos);
FootpathUpdateProvisional();
// Figure out what slope and height to use
auto placement = FootpathGetPlacementFromScreenCoords(screenCoords);
auto placement = WindowFootpathGetPlacementFromScreenCoords(screenCoords);
if (!placement.isValid())
{
gMapSelectFlags.unset(MapSelectFlag::enable);
@@ -1004,8 +1074,61 @@ namespace OpenRCT2::Ui::Windows
// 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, placement.baseZ }, placement.slope, constructFlags);
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)
{
@@ -1034,8 +1157,7 @@ namespace OpenRCT2::Ui::Windows
gMapSelectFlags.set(MapSelectFlag::enable, MapSelectFlag::enableArrow);
gMapSelectType = MapSelectType::full;
gMapSelectPositionA = mapCoords;
gMapSelectPositionB = mapCoords;
setMapSelectRange(mapCoords);
int32_t z = tileElement->GetBaseZ();
@@ -1056,6 +1178,155 @@ namespace OpenRCT2::Ui::Windows
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);
footpathPlaceAction.SetCallback(
[&anySuccess, &cost](const GameActions::GameAction* ga, const GameActions::Result* result) {
if (result->Error == GameActions::Status::Ok)
{
anySuccess = true;
cost += result->Cost;
}
});
GameActions::Execute(&footpathPlaceAction, getGameState());
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
@@ -1073,7 +1344,7 @@ namespace OpenRCT2::Ui::Windows
if (!mapPos)
return;
auto placement = FootpathGetPlacementFromScreenCoords(screenCoords);
auto placement = WindowFootpathGetPlacementFromScreenCoords(screenCoords);
// Try and place path
auto selectedType = gFootpathSelection.GetSelectedSurface();
@@ -1685,24 +1956,38 @@ namespace OpenRCT2::Ui::Windows
* rct2: 0x006A76FF
*/
static money64 FootpathProvisionalSet(
ObjectEntryIndex type, ObjectEntryIndex railingsType, const CoordsXYZ& footpathLoc, FootpathSlope slope,
PathConstructFlags constructFlags)
ObjectEntryIndex type, ObjectEntryIndex railingsType, const CoordsXY& footpathLocA, const CoordsXY& footpathLocB,
int32_t startZ, const std::span<const ProvisionalTile> tiles, PathConstructFlags constructFlags)
{
FootpathRemoveProvisional();
auto footpathPlaceAction = GameActions::FootpathPlaceAction(
footpathLoc, slope, type, railingsType, kInvalidDirection, constructFlags);
footpathPlaceAction.SetFlags(GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED);
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;
}
}
auto res = GameActions::Execute(&footpathPlaceAction, getGameState());
money64 cost = res.Error == GameActions::Status::Ok ? res.Cost : kMoney64Undefined;
if (res.Error == GameActions::Status::Ok)
bool anySuccesful = succesfulTiles.size() > 0;
if (anySuccesful)
{
_provisionalFootpath.surfaceIndex = type;
_provisionalFootpath.railingsIndex = railingsType;
_provisionalFootpath.position = footpathLoc;
_provisionalFootpath.slope = slope;
_provisionalFootpath.positionA = footpathLocA;
_provisionalFootpath.positionB = footpathLocB;
_provisionalFootpath.startZ = startZ;
_provisionalFootpath.tiles = succesfulTiles;
_provisionalFootpath.constructFlags = constructFlags;
_provisionalFootpath.flags.set(ProvisionalPathFlag::placed);
@@ -1718,26 +2003,26 @@ namespace OpenRCT2::Ui::Windows
if (!isToolActive(WindowClass::scenery))
{
if (res.Error != GameActions::Status::Ok)
if (succesfulTiles.size() == 0)
{
// If we can't build this, don't show a virtual floor.
VirtualFloorSetHeight(0);
}
else if (
_provisionalFootpath.slope.type == FootpathSlopeType::flat
|| _provisionalFootpath.position.z < _footpathConstructFromPosition.z)
succesfulTiles[0].slope.type == FootpathSlopeType::flat
|| succesfulTiles[0].position.z < _footpathConstructFromPosition.z)
{
// Going either straight on, or down.
VirtualFloorSetHeight(_provisionalFootpath.position.z);
VirtualFloorSetHeight(succesfulTiles[0].position.z);
}
else
{
// Going up in the world!
VirtualFloorSetHeight(_provisionalFootpath.position.z + kLandHeightStep);
VirtualFloorSetHeight(succesfulTiles[0].position.z + kLandHeightStep);
}
}
return cost;
return anySuccesful ? cost : kMoney64Undefined;
}
/**
@@ -1746,11 +2031,14 @@ namespace OpenRCT2::Ui::Windows
*/
void FootpathRemoveProvisional()
{
if (_provisionalFootpath.flags.has(ProvisionalPathFlag::placed))
{
_provisionalFootpath.flags.unset(ProvisionalPathFlag::placed);
if (!_provisionalFootpath.flags.has(ProvisionalPathFlag::placed))
return;
auto action = GameActions::FootpathRemoveAction(_provisionalFootpath.position);
_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());
}
@@ -1787,8 +2075,9 @@ namespace OpenRCT2::Ui::Windows
{
_provisionalFootpath.flags.unset(ProvisionalPathFlag::placed);
FootpathProvisionalSet(
_provisionalFootpath.surfaceIndex, _provisionalFootpath.railingsIndex, _provisionalFootpath.position,
_provisionalFootpath.slope, _provisionalFootpath.constructFlags);
_provisionalFootpath.surfaceIndex, _provisionalFootpath.railingsIndex, _provisionalFootpath.positionA,
_provisionalFootpath.positionB, _provisionalFootpath.startZ, _provisionalFootpath.tiles,
_provisionalFootpath.constructFlags);
}
}

View File

@@ -1084,6 +1084,7 @@ enum : ImageIndex
SPR_G2_ICON_MEDIUM_CURVE_LEFT = SPR_G2_OPAQUE_WATER_OVERLAY + 5,
SPR_G2_ICON_MEDIUM_CURVE_RIGHT,
SPR_G2_ICON_ROTATE_ANTI_CLOCKWISE,
SPR_G2_ICON_PATH_DRAG_TOOL,
SPR_G2_END,

View File

@@ -225,7 +225,7 @@ namespace OpenRCT2::GameActions
if (GetFlags() & GAME_COMMAND_FLAG_GHOST && !pathElement->IsGhost())
{
return Result(Status::Unknown, STR_CANT_BUILD_FOOTPATH_HERE, kStringIdNone);
return Result(Status::ItemAlreadyPlaced, STR_CANT_BUILD_FOOTPATH_HERE, kStringIdNone);
}
return res;
}

View File

@@ -95,6 +95,11 @@ struct FootpathSlope
{
FootpathSlopeType type{};
Direction direction{};
constexpr bool operator==(const FootpathSlope& rhs) const
{
return type == rhs.type && direction == rhs.direction;
}
};
struct FootpathPlacementResult

View File

@@ -57,3 +57,20 @@ void MapInvalidateSelectionRect()
OpenRCT2::ViewportsInvalidate({ { left, top }, { right, bottom } });
}
MapRange getMapSelectRange()
{
return MapRange(gMapSelectPositionA, gMapSelectPositionB);
}
void setMapSelectRange(const MapRange& range)
{
const auto normalised = range.Normalise();
gMapSelectPositionA = normalised.Point1;
gMapSelectPositionB = normalised.Point2;
}
void setMapSelectRange(const CoordsXY coords)
{
setMapSelectRange({ coords, coords });
}

View File

@@ -65,3 +65,6 @@ extern std::vector<CoordsXY> gMapSelectionTiles;
void MapInvalidateMapSelectionTiles();
void MapInvalidateSelectionRect();
MapRange getMapSelectRange();
void setMapSelectRange(const MapRange& range);
void setMapSelectRange(const CoordsXY coords);