1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-26 00:04:43 +01:00

Allow dragging lines of walls

This commit is contained in:
Michael Steenbeek
2025-11-16 17:14:20 +01:00
committed by GitHub
parent 5c8867098d
commit 6a5dd316a1
4 changed files with 221 additions and 16 deletions

View File

@@ -1,5 +1,6 @@
0.4.29 (in development)
------------------------------------------------------------------------
- Feature: [#25459] Wall line dragging tool.
- Improved: [#25028] Stalls now support colour presets, just like regular rides.
- Improved: [#25426] Building the track designs index is now quicker.
- Improved: [#25490] The New Ride window can now be resized.

View File

@@ -29,6 +29,7 @@
#include <openrct2/actions/SmallSceneryPlaceAction.h>
#include <openrct2/actions/SmallScenerySetColourAction.h>
#include <openrct2/actions/WallPlaceAction.h>
#include <openrct2/actions/WallRemoveAction.h>
#include <openrct2/actions/WallSetColourAction.h>
#include <openrct2/audio/Audio.h>
#include <openrct2/config/Config.h>
@@ -206,6 +207,12 @@ namespace OpenRCT2::Ui::Windows
}
};
struct ProvisionalWallTile
{
CoordsXYZD location;
int32_t calculatedZ;
};
std::vector<SceneryTabInfo> _tabEntries;
int32_t _requiredWidth;
int32_t _actualMinHeight;
@@ -216,6 +223,18 @@ namespace OpenRCT2::Ui::Windows
uint8_t _unkF64F0E{ 0 };
int16_t _unkF64F0A{ 0 };
CoordsXY _dragStartPos{};
/**
* When placing fences in the air using Shift, the user may shift their mouse to the left or right.
* We save this position to avoid detecting a wall drag where there isnt one.
*/
CoordsXY _dragStartHoverPos{};
CoordsXY _dragEndPos{};
uint8_t _startEdge{};
GameActions::Result _lastProvisionalError{};
bool _inDragMode = false;
std::vector<ProvisionalWallTile> _provisionalTiles{};
public:
void onOpen() override
{
@@ -255,6 +274,7 @@ namespace OpenRCT2::Ui::Windows
void onClose() override
{
SceneryRemoveGhostToolPlacement();
removeProvisionalTilesFromMap();
HideGridlines();
ViewportSetVisibility(ViewportVisibility::standard);
@@ -1814,6 +1834,13 @@ namespace OpenRCT2::Ui::Windows
void onToolUpdateWall(WidgetIndex widgetIndex, const ScreenCoordsXY& screenPos, ScenerySelection selection)
{
bool isLeftMousePressed = gInputFlags.has(InputFlag::leftMousePressed);
if (isLeftMousePressed)
{
gMapSelectFlags.set(MapSelectFlag::enable);
return;
}
CoordsXY mapTile = {};
uint8_t edge;
@@ -1826,10 +1853,7 @@ namespace OpenRCT2::Ui::Windows
}
gMapSelectFlags.set(MapSelectFlag::enable);
gMapSelectPositionA.x = mapTile.x;
gMapSelectPositionA.y = mapTile.y;
gMapSelectPositionB.x = mapTile.x;
gMapSelectPositionB.y = mapTile.y;
setMapSelectRange(mapTile);
gMapSelectType = getMapSelectEdge(edge);
MapInvalidateSelectionRect();
@@ -2717,6 +2741,8 @@ namespace OpenRCT2::Ui::Windows
if (gridPos.IsNull())
return;
gridPos = gridPos.ToTileStart();
if (Config::Get().general.virtualFloorStyle != VirtualFloorStyles::Off)
{
VirtualFloorSetHeight(gSceneryPlaceZ);
@@ -3027,6 +3053,31 @@ namespace OpenRCT2::Ui::Windows
auto res = GameActions::Execute(&footpathAdditionPlaceAction, gameState);
}
std::optional<CoordsXY> getMapPosFromScreenPos(const ScreenCoordsXY& screenCoords)
{
CoordsXY coords;
if (gSceneryPlaceZ > 0)
{
auto candidate = ScreenGetMapXYWithZ(screenCoords, gSceneryPlaceZ);
if (!candidate.has_value())
return std::nullopt;
coords = *candidate;
}
else
{
auto info = GetMapCoordinatesFromPos(screenCoords, EnumsToFlags(ViewportInteractionItem::terrain));
if (info.interactionType == ViewportInteractionItem::none)
return std::nullopt;
coords = info.Loc;
}
coords = coords.ToTileStart();
return coords;
}
void onToolDownWall(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords, uint16_t selectedScenery)
{
CoordsXY gridPos;
@@ -3068,19 +3119,165 @@ namespace OpenRCT2::Ui::Windows
}
}
auto wallPlaceAction = GameActions::WallPlaceAction(
selectedScenery, { gridPos, gSceneryPlaceZ }, edges, _sceneryPrimaryColour, _scenerySecondaryColour,
_sceneryTertiaryColour);
_inDragMode = true;
_dragStartPos = gridPos;
auto hoverPos = getMapPosFromScreenPos(screenCoords);
_dragStartHoverPos = hoverPos.has_value() ? *hoverPos : gridPos;
_dragEndPos = {};
_startEdge = edges;
gMapSelectFlags.set(MapSelectFlag::enable);
gMapSelectType = getMapSelectEdge(_startEdge);
setMapSelectRange(gridPos);
}
wallPlaceAction.SetCallback([](const GameActions::GameAction* ga, const GameActions::Result* result) {
if (result->Error == GameActions::Status::Ok)
{
Audio::Play3D(Audio::SoundId::placeItem, result->Position);
}
});
void dragWallSetEndPos(const ScreenCoordsXY& screenCoords)
{
auto endCoords = getMapPosFromScreenPos(screenCoords);
if (!endCoords.has_value())
return;
if (endCoords == _dragStartHoverPos)
{
endCoords = _dragStartPos;
}
const bool onXAxis = (_startEdge == 1 || _startEdge == 3);
if (onXAxis)
{
endCoords->y = _dragStartPos.y;
}
else
{
endCoords->x = _dragStartPos.x;
}
if (*endCoords != _dragEndPos)
{
setMapSelectRange({ _dragStartPos, *endCoords });
updateProvisionalTiles();
}
_dragEndPos = *endCoords;
}
void removeProvisionalTilesFromMap()
{
auto& gameState = getGameState();
auto res = GameActions::Execute(&wallPlaceAction, gameState);
for (const auto& tile : _provisionalTiles)
{
auto location = tile.location;
location.z = tile.calculatedZ;
auto wallRemoveAction = GameActions::WallRemoveAction(location);
wallRemoveAction.SetFlags(
GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_NO_SPEND | GAME_COMMAND_FLAG_GHOST);
wallRemoveAction.Execute(gameState);
}
}
void updateProvisionalTiles()
{
auto& gameState = getGameState();
auto tabSelection = WindowSceneryGetTabSelection();
removeProvisionalTilesFromMap();
_provisionalTiles.clear();
_lastProvisionalError = {};
auto mapRange = getMapSelectRange();
for (auto y = mapRange.GetY1(); y <= mapRange.GetY2(); y += kCoordsXYStep)
{
for (auto x = mapRange.GetX1(); x <= mapRange.GetX2(); x += kCoordsXYStep)
{
auto wallPlaceAction = GameActions::WallPlaceAction(
tabSelection.EntryIndex, { x, y, gSceneryPlaceZ }, _startEdge, _sceneryPrimaryColour,
_scenerySecondaryColour, _sceneryTertiaryColour);
wallPlaceAction.SetFlags(GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED);
auto result = GameActions::Execute(&wallPlaceAction, gameState);
if (result.Error == GameActions::Status::Ok)
{
const auto placementData = result.GetData<GameActions::WallPlaceActionResult>();
_provisionalTiles.push_back({ CoordsXYZD(x, y, gSceneryPlaceZ, _startEdge), placementData.BaseHeight });
}
else
{
_lastProvisionalError = result;
}
}
}
}
void onToolDragWall(const ScreenCoordsXY& screenCoords)
{
if (!_inDragMode)
return;
dragWallSetEndPos(screenCoords);
}
void onToolUp(WidgetIndex, const ScreenCoordsXY&) override
{
if (_sceneryPaintEnabled || gWindowSceneryEyedropperEnabled)
return;
auto tabSelection = WindowSceneryGetTabSelection();
auto sceneryType = tabSelection.SceneryType;
if (sceneryType == SCENERY_TYPE_WALL)
{
onToolUpWall(tabSelection.EntryIndex);
}
}
void onToolUpWall(uint16_t selectedScenery)
{
removeProvisionalTilesFromMap();
if (!_inDragMode)
{
_provisionalTiles.clear();
return;
}
if (_provisionalTiles.empty())
{
auto z = gSceneryPlaceZ > 0 ? gSceneryPlaceZ : TileElementHeight(_dragStartPos);
Audio::Play3D(Audio::SoundId::error, { _dragStartPos, z });
if (_lastProvisionalError.Error != GameActions::Status::Ok)
{
auto windowManager = Ui::GetWindowManager();
windowManager->ShowError(
_lastProvisionalError.GetErrorTitle(), _lastProvisionalError.GetErrorMessage(), true);
}
_inDragMode = false;
return;
}
auto mapRange = getMapSelectRange();
bool anySuccessful = false;
CoordsXYZ lastLocation = { mapRange.Point2, gSceneryPlaceZ };
for (const auto& tile : _provisionalTiles)
{
auto wallPlaceAction = GameActions::WallPlaceAction(
selectedScenery, tile.location, tile.location.direction, _sceneryPrimaryColour, _scenerySecondaryColour,
_sceneryTertiaryColour);
auto& gameState = getGameState();
auto result = GameActions::Execute(&wallPlaceAction, gameState);
if (result.Error == GameActions::Status::Ok)
{
anySuccessful = true;
lastLocation = result.Position;
}
}
_provisionalTiles.clear();
_inDragMode = false;
if (anySuccessful)
{
Audio::Play3D(Audio::SoundId::placeItem, lastLocation);
}
}
void onToolDownLargeScenery(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords, uint16_t selectedScenery)
@@ -3223,6 +3420,13 @@ namespace OpenRCT2::Ui::Windows
{
if (_sceneryPaintEnabled || gWindowSceneryEyedropperEnabled)
onToolDown(widgetIndex, screenCoords);
auto tabSelection = WindowSceneryGetTabSelection();
auto sceneryType = tabSelection.SceneryType;
if (sceneryType == SCENERY_TYPE_WALL)
{
onToolDragWall(screenCoords);
}
}
};

View File

@@ -268,7 +268,7 @@ namespace OpenRCT2::GameActions
res.Cost = wallEntry->price;
res.SetData(WallPlaceActionResult{});
res.SetData(WallPlaceActionResult{ targetHeight });
return res;
}

View File

@@ -47,7 +47,7 @@
// It is used for making sure only compatible builds get connected, even within
// single OpenRCT2 version.
constexpr uint8_t kStreamVersion = 3;
constexpr uint8_t kStreamVersion = 4;
const std::string kStreamID = std::string(kOpenRCT2Version) + "-" + std::to_string(kStreamVersion);