1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-16 04:22:43 +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_7002 :{STRINGID} {STRINGID}
STR_7003 :Audio file {STRING} is truncated. Expected sample {INT32}, but only {INT32} are available. Consider reinstalling RCT2. STR_7003 :Audio file {STRING} is truncated. Expected sample {INT32}, but only {INT32} are available. Consider reinstalling RCT2.
STR_7004 :Force Redraw STR_7004 :Force Redraw
STR_7005 :Drag an area of footpath

View File

@@ -1,5 +1,6 @@
0.4.28 (in development) 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: [#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. - 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. - 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": "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_CONSTRUCT_THE_SELECTED_FOOTPATH_SECTION_TIP = 1189,
STR_DIRECTION = 1183, STR_DIRECTION = 1183,
STR_DIRECTION_TIP = 1185, STR_DIRECTION_TIP = 1185,
STR_DRAG_AREA_OF_FOOTPATH_TIP = 7005,
STR_FOOTPATHS = 1181, STR_FOOTPATHS = 1181,
STR_FOOTPATH_TIP = 1424, STR_FOOTPATH_TIP = 1424,
STR_LEVEL_TIP = 1187, STR_LEVEL_TIP = 1187,

View File

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

View File

@@ -225,7 +225,7 @@ namespace OpenRCT2::GameActions
if (GetFlags() & GAME_COMMAND_FLAG_GHOST && !pathElement->IsGhost()) 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; return res;
} }

View File

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

View File

@@ -57,3 +57,20 @@ void MapInvalidateSelectionRect()
OpenRCT2::ViewportsInvalidate({ { left, top }, { right, bottom } }); 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 MapInvalidateMapSelectionTiles();
void MapInvalidateSelectionRect(); void MapInvalidateSelectionRect();
MapRange getMapSelectRange();
void setMapSelectRange(const MapRange& range);
void setMapSelectRange(const CoordsXY coords);