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:
committed by
GitHub
parent
a2cc3c20c6
commit
ff580112c8
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -838,5 +838,10 @@
|
||||
},
|
||||
{
|
||||
"path": "icons/rotate_view_anti-clockwise.png"
|
||||
},
|
||||
{
|
||||
"path": "tool/path_drag_tool.png",
|
||||
"x": 1,
|
||||
"y": 2
|
||||
}
|
||||
]
|
||||
|
||||
BIN
resources/g2/tool/path_drag_tool.png
Normal file
BIN
resources/g2/tool/path_drag_tool.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 481 B |
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user