1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-24 08:12:53 +01:00

Merge pull request #8807 from duncanspumpkin/footpath_ga

Footpath GameAction
This commit is contained in:
Duncan
2019-03-04 17:46:36 +00:00
committed by GitHub
17 changed files with 947 additions and 515 deletions

View File

@@ -34,6 +34,9 @@
2A5C1368221E9F9000F8C245 /* TrackRemoveAction.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2A5C1367221E9F9000F8C245 /* TrackRemoveAction.hpp */; };
2A61CAFB2229E5C50095AD67 /* RideEntranceExitPlaceAction.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2A61CAFA2229E5C50095AD67 /* RideEntranceExitPlaceAction.hpp */; };
2A61CAF92229E59F0095AD67 /* WaterSetHeightAction.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2A61CAF82229E59F0095AD67 /* WaterSetHeightAction.hpp */; };
2A61CAF52229E5720095AD67 /* FootpathSceneryPlaceAction.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2A61CAF22229E5720095AD67 /* FootpathSceneryPlaceAction.hpp */; };
2A61CAF62229E5720095AD67 /* FootpathSceneryRemoveAction.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2A61CAF32229E5720095AD67 /* FootpathSceneryRemoveAction.hpp */; };
2A61CAF72229E5720095AD67 /* FootpathPlaceAction.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2A61CAF42229E5720095AD67 /* FootpathPlaceAction.hpp */; };
2AA050322209A8E300D3A922 /* StaffSetCostumeAction.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2AA050302209A8E300D3A922 /* StaffSetCostumeAction.hpp */; };
2AA050332209A8E300D3A922 /* StaffSetOrdersAction.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2AA050312209A8E300D3A922 /* StaffSetOrdersAction.hpp */; };
2AAFD7FA220DD2DC002461A4 /* TrackPlaceAction.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2AAFD7F9220DD2DC002461A4 /* TrackPlaceAction.hpp */; };
@@ -642,6 +645,9 @@
2A5C1367221E9F9000F8C245 /* TrackRemoveAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TrackRemoveAction.hpp; sourceTree = "<group>"; };
2A61CAFA2229E5C50095AD67 /* RideEntranceExitPlaceAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RideEntranceExitPlaceAction.hpp; sourceTree = "<group>"; };
2A61CAF82229E59F0095AD67 /* WaterSetHeightAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = WaterSetHeightAction.hpp; sourceTree = "<group>"; };
2A61CAF22229E5720095AD67 /* FootpathSceneryPlaceAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FootpathSceneryPlaceAction.hpp; sourceTree = "<group>"; };
2A61CAF32229E5720095AD67 /* FootpathSceneryRemoveAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FootpathSceneryRemoveAction.hpp; sourceTree = "<group>"; };
2A61CAF42229E5720095AD67 /* FootpathPlaceAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FootpathPlaceAction.hpp; sourceTree = "<group>"; };
2AA050302209A8E300D3A922 /* StaffSetCostumeAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaffSetCostumeAction.hpp; sourceTree = "<group>"; };
2AA050312209A8E300D3A922 /* StaffSetOrdersAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaffSetOrdersAction.hpp; sourceTree = "<group>"; };
2AAFD7F9220DD2DC002461A4 /* TrackPlaceAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TrackPlaceAction.hpp; sourceTree = "<group>"; };
@@ -2030,6 +2036,9 @@
children = (
2A61CAFA2229E5C50095AD67 /* RideEntranceExitPlaceAction.hpp */,
2A61CAF82229E59F0095AD67 /* WaterSetHeightAction.hpp */,
2A61CAF42229E5720095AD67 /* FootpathPlaceAction.hpp */,
2A61CAF22229E5720095AD67 /* FootpathSceneryPlaceAction.hpp */,
2A61CAF32229E5720095AD67 /* FootpathSceneryRemoveAction.hpp */,
2ACBAB162226850A0034FB91 /* RideSetSetting.hpp */,
2A43D2B92225B8D900E8F73B /* LoadOrQuitAction.hpp */,
2A43D2B72225B8D900E8F73B /* RideSetVehiclesAction.hpp */,
@@ -3346,6 +3355,7 @@
C6352B951F477032006CCEE3 /* RideCreateAction.hpp in Headers */,
C6352B851F477022006CCEE3 /* DataSerialiserTraits.h in Headers */,
939A359F20C12FDE00630B3F /* Paint.Surface.h in Headers */,
2A61CAF62229E5720095AD67 /* FootpathSceneryRemoveAction.hpp in Headers */,
C67B28192002D7F200109C93 /* Window_internal.h in Headers */,
C6352B971F477032006CCEE3 /* SetParkEntranceFeeAction.hpp in Headers */,
2AA050332209A8E300D3A922 /* StaffSetOrdersAction.hpp in Headers */,
@@ -3362,6 +3372,7 @@
2A43D2BA2225B8D900E8F73B /* RideSetVehiclesAction.hpp in Headers */,
2A43D2C02225B91A00E8F73B /* RideSetVehiclesAction.hpp in Headers */,
2AA050322209A8E300D3A922 /* StaffSetCostumeAction.hpp in Headers */,
2A61CAF72229E5720095AD67 /* FootpathPlaceAction.hpp in Headers */,
2A43D2C22225B91A00E8F73B /* LoadOrQuitAction.hpp in Headers */,
C62D838B1FD36D6F008C04F1 /* EditorObjectSelectionSession.h in Headers */,
2A43D2BC2225B8D900E8F73B /* LoadOrQuitAction.hpp in Headers */,
@@ -3369,6 +3380,7 @@
939A35A220C12FFD00630B3F /* InteractiveConsole.h in Headers */,
2ACBAB172226850A0034FB91 /* RideSetSetting.hpp in Headers */,
93CBA4C320A7502E00867D56 /* Imaging.h in Headers */,
2A61CAF52229E5720095AD67 /* FootpathSceneryPlaceAction.hpp in Headers */,
9308DA05209908090079EE96 /* Surface.h in Headers */,
93DE9753209C3C1000FB1CC8 /* GameState.h in Headers */,
C6352B841F477022006CCEE3 /* DataSerialiser.h in Headers */,

View File

@@ -16,6 +16,7 @@
#include <openrct2/Game.h>
#include <openrct2/Input.h>
#include <openrct2/OpenRCT2.h>
#include <openrct2/actions/FootpathSceneryRemoveAction.hpp>
#include <openrct2/actions/LargeSceneryRemoveAction.hpp>
#include <openrct2/actions/SmallSceneryRemoveAction.hpp>
#include <openrct2/actions/WallRemoveAction.hpp>
@@ -507,16 +508,8 @@ static void viewport_interaction_remove_footpath(TileElement* tileElement, int32
*/
static void viewport_interaction_remove_footpath_item(TileElement* tileElement, int32_t x, int32_t y)
{
int32_t type = tileElement->AsPath()->GetPathEntryIndex();
if (tileElement->AsPath()->IsQueue())
type |= 0x80;
int32_t slopeData = tileElement->AsPath()->GetSlopeDirection();
if (tileElement->AsPath()->IsSloped())
slopeData |= FOOTPATH_PROPERTIES_FLAG_IS_SLOPED;
gGameCommandErrorTitle = STR_CANT_REMOVE_THIS;
game_do_command(x, (slopeData << 8) | 1, y, (type << 8) | tileElement->base_height, GAME_COMMAND_PLACE_PATH, 0, 0);
auto footpathSceneryRemoveAction = FootpathSceneryRemoveAction({ x, y, tileElement->base_height * 8 });
GameActions::Execute(&footpathSceneryRemoveAction);
}
/**

View File

@@ -15,6 +15,7 @@
#include <openrct2/Game.h>
#include <openrct2/Input.h>
#include <openrct2/OpenRCT2.h>
#include <openrct2/actions/FootpathPlaceAction.hpp>
#include <openrct2/audio/audio.h>
#include <openrct2/localisation/Localisation.h>
#include <openrct2/object/ObjectLimits.h>
@@ -847,7 +848,7 @@ static void window_footpath_set_selection_start_bridge_at_point(int32_t screenX,
*/
static void window_footpath_place_path_at_point(int32_t x, int32_t y)
{
int32_t interactionType, currentType, selectedType, z, cost;
int32_t interactionType, currentType, selectedType, z;
TileElement* tileElement;
if (_footpathErrorOccured)
@@ -894,19 +895,22 @@ static void window_footpath_place_path_at_point(int32_t x, int32_t y)
// Try and place path
gGameCommandErrorTitle = STR_CANT_BUILD_FOOTPATH_HERE;
cost = footpath_place(selectedType, x, y, z, currentType, GAME_COMMAND_FLAG_APPLY);
if (cost == MONEY32_UNDEFINED)
{
_footpathErrorOccured = true;
}
else if (gFootpathPrice != 0)
{
// bp = RCT2_ADDRESS_COMMAND_MAP_Z
// dx = RCT2_ADDRESS_COMMAND_MAP_Y
// cx = RCT2_ADDRESS_COMMAND_MAP_X
audio_play_sound_at_location(SOUND_PLACE_ITEM, gCommandPosition.x, gCommandPosition.y, gCommandPosition.z);
}
auto footpathPlaceAction = FootpathPlaceAction({ x, y, z * 8 }, currentType, selectedType);
footpathPlaceAction.SetCallback([](const GameAction* ga, const GameActionResult* result) {
if (result->Error == GA_ERROR::OK)
{
// Don't play sound if it is no cost to prevent multiple sounds. TODO: make this work in no money scenarios
if (result->Cost != 0)
{
audio_play_sound_at_location(SOUND_PLACE_ITEM, result->Position.x, result->Position.y, result->Position.z);
}
}
else
{
_footpathErrorOccured = true;
}
});
GameActions::Execute(&footpathPlaceAction);
}
/**
@@ -982,43 +986,44 @@ static void window_footpath_construct()
footpath_get_next_path_info(&type, &x, &y, &z, &slope);
gGameCommandErrorTitle = STR_CANT_BUILD_FOOTPATH_HERE;
money32 cost = footpath_place_remove_intersecting(
type, x, y, z, slope, GAME_COMMAND_FLAG_APPLY, gFootpathConstructDirection);
if (cost != MONEY32_UNDEFINED)
{
audio_play_sound_at_location(
SOUND_PLACE_ITEM, gFootpathConstructFromPosition.x, gFootpathConstructFromPosition.y,
gFootpathConstructFromPosition.z);
if (gFootpathConstructSlope == 0)
auto footpathPlaceAction = FootpathPlaceAction({ x, y, z * 8 }, slope, type, gFootpathConstructDirection);
footpathPlaceAction.SetCallback([=](const GameAction* ga, const GameActionResult* result) {
if (result->Error == GA_ERROR::OK)
{
gFootpathConstructValidDirections = 0xFF;
}
else
{
gFootpathConstructValidDirections = gFootpathConstructDirection;
}
audio_play_sound_at_location(SOUND_PLACE_ITEM, result->Position.x, result->Position.y, result->Position.z);
if (gFootpathGroundFlags & ELEMENT_IS_UNDERGROUND)
{
viewport_set_visibility(1);
if (gFootpathConstructSlope == 0)
{
gFootpathConstructValidDirections = 0xFF;
}
else
{
gFootpathConstructValidDirections = gFootpathConstructDirection;
}
if (gFootpathGroundFlags & ELEMENT_IS_UNDERGROUND)
{
viewport_set_visibility(1);
}
// If we have just built an upwards slope, the next path to construct is
// a bit higher. Note that the z returned by footpath_get_next_path_info
// already is lowered if we are building a downwards slope.
if (gFootpathConstructSlope == 2)
{
gFootpathConstructFromPosition.z = (z + 2) * 8;
}
else
{
gFootpathConstructFromPosition.z = z * 8;
}
gFootpathConstructFromPosition.x = x;
gFootpathConstructFromPosition.y = y;
}
// If we have just built an upwards slope, the next path to construct is
// a bit higher. Note that the z returned by footpath_get_next_path_info
// already is lowered if we are building a downwards slope.
if (gFootpathConstructSlope == 2)
{
z += 2;
}
gFootpathConstructFromPosition.x = x;
gFootpathConstructFromPosition.y = y;
gFootpathConstructFromPosition.z = z << 3;
}
window_footpath_set_enabled_and_pressed_widgets();
window_footpath_set_enabled_and_pressed_widgets();
});
GameActions::Execute(&footpathPlaceAction);
}
/**

View File

@@ -26,6 +26,7 @@
#include <openrct2/OpenRCT2.h>
#include <openrct2/ParkImporter.h>
#include <openrct2/actions/ClearAction.hpp>
#include <openrct2/actions/FootpathSceneryPlaceAction.hpp>
#include <openrct2/actions/LoadOrQuitAction.hpp>
#include <openrct2/actions/PauseToggleAction.hpp>
#include <openrct2/actions/SmallSceneryPlaceAction.hpp>
@@ -1816,14 +1817,18 @@ static void window_top_toolbar_scenery_tool_down(int16_t x, int16_t y, rct_windo
}
case SCENERY_TYPE_PATH_ITEM:
{
int32_t flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_PATH_SCENERY | (parameter_1 & 0xFF00);
auto pathItemType = parameter_3 & 0xFF;
int32_t z = (parameter_2 & 0xFF) * 8;
auto footpathSceneryPlaceAction = FootpathSceneryPlaceAction({ gridX, gridY, z }, pathItemType);
gGameCommandErrorTitle = STR_CANT_POSITION_THIS_HERE;
int32_t cost = game_do_command(gridX, flags, gridY, parameter_2, GAME_COMMAND_PLACE_PATH, parameter_3, 0);
if (cost != MONEY32_UNDEFINED)
{
audio_play_sound_at_location(SOUND_PLACE_ITEM, gCommandPosition.x, gCommandPosition.y, gCommandPosition.z);
}
footpathSceneryPlaceAction.SetCallback([](const GameAction* ga, const GameActionResult* result) {
if (result->Error != GA_ERROR::OK)
{
return;
}
audio_play_sound_at_location(SOUND_PLACE_ITEM, result->Position.x, result->Position.y, result->Position.z);
});
auto res = GameActions::Execute(&footpathSceneryPlaceAction);
break;
}
case SCENERY_TYPE_WALL:
@@ -2489,27 +2494,32 @@ static money32 try_place_ghost_scenery(
break;
}
case 1:
{
// Path Bits
// 6e265b
cost = game_do_command(
map_tile.x,
(parameter_1 & 0xFF00)
| (GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_5
| GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_PATH_SCENERY),
map_tile.y, parameter_2, GAME_COMMAND_PLACE_PATH, parameter_3, 0);
auto pathItemType = parameter_3 & 0xFF;
int32_t z = (parameter_2 & 0xFF) * 8;
auto footpathSceneryPlaceAction = FootpathSceneryPlaceAction({ map_tile.x, map_tile.y, z }, pathItemType);
footpathSceneryPlaceAction.SetFlags(GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED);
footpathSceneryPlaceAction.SetCallback([=](const GameAction* ga, const GameActionResult* result) {
if (result->Error != GA_ERROR::OK)
{
return;
}
gSceneryGhostPosition.x = map_tile.x;
gSceneryGhostPosition.y = map_tile.y;
gSceneryGhostPosition.z = (parameter_2 & 0xFF);
gSceneryPlacePathSlope = ((parameter_1 >> 8) & 0xFF);
gSceneryPlacePathType = ((parameter_2 >> 8) & 0xFF);
gSceneryGhostPathObjectType = parameter_3;
if (cost == MONEY32_UNDEFINED)
return cost;
gSceneryGhostPosition.x = map_tile.x;
gSceneryGhostPosition.y = map_tile.y;
gSceneryGhostPosition.z = (parameter_2 & 0xFF);
gSceneryPlacePathSlope = ((parameter_1 >> 8) & 0xFF);
gSceneryPlacePathType = ((parameter_2 >> 8) & 0xFF);
gSceneryGhostPathObjectType = parameter_3;
gSceneryGhostType |= SCENERY_GHOST_FLAG_1;
gSceneryGhostType |= SCENERY_GHOST_FLAG_1;
});
auto res = GameActions::Execute(&footpathSceneryPlaceAction);
if (res->Error != GA_ERROR::OK)
return MONEY32_UNDEFINED;
break;
}
case 2:
// Walls
// 6e26b0

View File

@@ -1277,7 +1277,7 @@ GAME_COMMAND_POINTER* new_game_command_table[GAME_COMMAND_COUNT] = {
nullptr,
nullptr,
nullptr,
game_command_place_footpath,
nullptr,
game_command_place_footpath_from_track,
nullptr,
game_command_change_surface_style,

View File

@@ -35,9 +35,9 @@ enum GAME_COMMAND
GAME_COMMAND_REMOVE_SCENERY, // GA
GAME_COMMAND_PLACE_SCENERY, // GA
GAME_COMMAND_SET_WATER_HEIGHT, // GA
GAME_COMMAND_PLACE_PATH,
GAME_COMMAND_PLACE_PATH, // GA
GAME_COMMAND_PLACE_PATH_FROM_TRACK,
GAME_COMMAND_REMOVE_PATH,
GAME_COMMAND_REMOVE_PATH, // GA
GAME_COMMAND_CHANGE_SURFACE_STYLE,
GAME_COMMAND_SET_RIDE_PRICE, // GA
GAME_COMMAND_SET_GUEST_NAME, // GA
@@ -90,10 +90,12 @@ enum GAME_COMMAND
GAME_COMMAND_BALLOON_PRESS,
GAME_COMMAND_MODIFY_TILE,
GAME_COMMAND_EDIT_SCENARIO_OPTIONS,
GAME_COMMAND_PLACE_PEEP_SPAWN, // GA, TODO: refactor to separate array for just game actions
GAME_COMMAND_SET_CLIMATE, // GA
GAME_COMMAND_SET_COLOUR_SCHEME, // GA
GAME_COMMAND_SET_STAFF_COSTUME, // GA
GAME_COMMAND_PLACE_PEEP_SPAWN, // GA, TODO: refactor to separate array for just game actions
GAME_COMMAND_SET_CLIMATE, // GA
GAME_COMMAND_SET_COLOUR_SCHEME, // GA
GAME_COMMAND_SET_STAFF_COSTUME, // GA
GAME_COMMAND_PLACE_FOOTPATH_SCENERY, // GA
GAME_COMMAND_REMOVE_FOOTPATH_SCENERY, // GA
GAME_COMMAND_COUNT,
};

View File

@@ -14,6 +14,7 @@
#include "OpenRCT2.h"
#include "ParkImporter.h"
#include "PlatformEnvironment.h"
#include "actions/FootpathPlaceAction.hpp"
#include "actions/GameAction.h"
#include "actions/RideEntranceExitPlaceAction.hpp"
#include "actions/RideSetSetting.hpp"
@@ -515,6 +516,16 @@ namespace OpenRCT2
result.action->SetFlags(command.ebx & 0xFF);
break;
}
case GAME_COMMAND_PLACE_PATH:
{
CoordsXYZ loc = { (int32_t)(command.eax & 0xFFFF), (int32_t)(command.ecx & 0xFFFF),
(int32_t)(command.edx & 0xFF) * 8 };
uint8_t slope = (command.ebx >> 8) & 0xFF;
uint8_t type = (command.edx >> 8) & 0xFF;
result.action = std::make_unique<FootpathPlaceAction>(loc, slope, type);
result.action->SetFlags(command.ebx & 0xFF);
break;
}
default:
throw std::runtime_error("Deprecated game command requires replay translation.");
}

View File

@@ -0,0 +1,455 @@
/*****************************************************************************
* Copyright (c) 2014-2019 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#pragma once
#include "../Cheats.h"
#include "../OpenRCT2.h"
#include "../core/MemoryStream.h"
#include "../interface/Window.h"
#include "../localisation/StringIds.h"
#include "../management/Finance.h"
#include "../world/Footpath.h"
#include "../world/Location.hpp"
#include "../world/Park.h"
#include "../world/Surface.h"
#include "../world/Wall.h"
#include "GameAction.h"
DEFINE_GAME_ACTION(FootpathPlaceAction, GAME_COMMAND_PLACE_PATH, GameActionResult)
{
private:
CoordsXYZ _loc;
uint8_t _slope;
uint8_t _type;
uint8_t _direction = 0xFF;
public:
FootpathPlaceAction() = default;
FootpathPlaceAction(CoordsXYZ loc, uint8_t slope, uint8_t type, uint8_t direction = 0xFF)
: _loc(loc)
, _slope(slope)
, _type(type)
, _direction(direction)
{
}
uint16_t GetActionFlags() const override
{
return GameAction::GetActionFlags();
}
void Serialise(DataSerialiser & stream) override
{
GameAction::Serialise(stream);
stream << DS_TAG(_loc) << DS_TAG(_slope) << DS_TAG(_type) << DS_TAG(_direction);
}
GameActionResult::Ptr Query() const override
{
GameActionResult::Ptr res = std::make_unique<GameActionResult>();
res->Cost = 0;
res->ExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
res->Position = _loc;
res->Position.x += 16;
res->Position.y += 16;
gFootpathGroundFlags = 0;
if (map_is_edge({ _loc.x, _loc.y }))
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_FOOTPATH_HERE, STR_OFF_EDGE_OF_MAP);
}
if (!((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode)
&& !map_is_location_owned(_loc.x, _loc.y, _loc.z))
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_BUILD_FOOTPATH_HERE, STR_LAND_NOT_OWNED_BY_PARK);
}
if (_slope & SLOPE_IS_IRREGULAR_FLAG)
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_BUILD_FOOTPATH_HERE, STR_LAND_SLOPE_UNSUITABLE);
}
if (_loc.z / 8 < 2)
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_BUILD_FOOTPATH_HERE, STR_TOO_LOW);
}
if (_loc.z / 8 > 248)
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_BUILD_FOOTPATH_HERE, STR_TOO_HIGH);
}
if (_direction != 0xFF && _direction > 15)
{
log_error("Direction invalid. direction = %u", _direction);
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_FOOTPATH_HERE);
}
footpath_provisional_remove();
auto tileElement = map_get_footpath_element_slope((_loc.x / 32), (_loc.y / 32), _loc.z / 8, _slope);
if (tileElement == nullptr)
{
return ElementInsertQuery(std::move(res));
}
else
{
return ElementUpdateQuery(tileElement, std::move(res));
}
}
GameActionResult::Ptr Execute() const override
{
GameActionResult::Ptr res = std::make_unique<GameActionResult>();
res->Cost = 0;
res->ExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
res->Position = _loc;
res->Position.x += 16;
res->Position.y += 16;
if (!(GetFlags() & GAME_COMMAND_FLAG_GHOST))
{
footpath_interrupt_peeps(_loc.x, _loc.y, _loc.z);
}
gFootpathGroundFlags = 0;
// Force ride construction to recheck area
_currentTrackSelectionFlags |= TRACK_SELECTION_FLAG_RECHECK;
if (!(GetFlags() & GAME_COMMAND_FLAG_GHOST))
{
if (_direction != 0xFF && !gCheatsDisableClearanceChecks)
{
// It is possible, let's remove walls between the old and new piece of path
auto zLow = _loc.z / 8;
auto zHigh = zLow + 4;
wall_remove_intersecting_walls(
_loc.x, _loc.y, zLow, zHigh + ((_slope & TILE_ELEMENT_SURFACE_RAISED_CORNERS_MASK) ? 2 : 0),
direction_reverse(_direction));
wall_remove_intersecting_walls(
_loc.x - CoordsDirectionDelta[_direction].x, _loc.y - CoordsDirectionDelta[_direction].y, zLow, zHigh,
_direction);
}
}
auto tileElement = map_get_footpath_element_slope((_loc.x / 32), (_loc.y / 32), _loc.z / 8, _slope);
if (tileElement == nullptr)
{
return ElementInsertExecute(std::move(res));
}
else
{
return ElementUpdateExecute(tileElement, std::move(res));
}
}
private:
GameActionResult::Ptr ElementUpdateQuery(PathElement * pathElement, GameActionResult::Ptr res) const
{
const int32_t newFootpathType = (_type & (FOOTPATH_PROPERTIES_TYPE_MASK >> 4));
const bool newPathIsQueue = ((_type >> 7) == 1);
if (pathElement->GetPathEntryIndex() != newFootpathType || pathElement->IsQueue() != newPathIsQueue)
{
res->Cost += MONEY(6, 00);
}
if (GetFlags() & GAME_COMMAND_FLAG_GHOST && !pathElement->IsGhost())
{
return MakeResult(GA_ERROR::UNKNOWN, STR_CANT_BUILD_FOOTPATH_HERE);
}
return res;
}
GameActionResult::Ptr ElementUpdateExecute(PathElement * pathElement, GameActionResult::Ptr res) const
{
const int32_t newFootpathType = (_type & (FOOTPATH_PROPERTIES_TYPE_MASK >> 4));
const bool newPathIsQueue = ((_type >> 7) == 1);
if (pathElement->GetPathEntryIndex() != newFootpathType || pathElement->IsQueue() != newPathIsQueue)
{
res->Cost += MONEY(6, 00);
}
footpath_queue_chain_reset();
if (!(GetFlags() & GAME_COMMAND_FLAG_PATH_SCENERY))
{
footpath_remove_edges_at(_loc.x, _loc.y, (TileElement*)pathElement);
}
pathElement->SetPathEntryIndex(_type);
if (_type & (1 << 7))
{
pathElement->SetIsQueue(true);
}
else
{
pathElement->SetIsQueue(false);
}
pathElement->SetAddition(0);
pathElement->SetIsBroken(false);
RemoveIntersectingWalls(pathElement);
return res;
}
GameActionResult::Ptr ElementInsertQuery(GameActionResult::Ptr res) const
{
bool entrancePath = false, entranceIsSamePath = false;
if (!map_check_free_elements_and_reorganise(1))
{
return MakeResult(GA_ERROR::NO_FREE_ELEMENTS, STR_CANT_BUILD_FOOTPATH_HERE);
}
res->Cost = MONEY(12, 00);
QuarterTile quarterTile{ 0b1111, 0 };
auto zLow = _loc.z / 8;
auto zHigh = zLow + 4;
if (_slope & FOOTPATH_PROPERTIES_FLAG_IS_SLOPED)
{
quarterTile = QuarterTile{ 0b1111, 0b1100 }.Rotate(_slope & TILE_ELEMENT_DIRECTION_MASK);
zHigh += 2;
}
auto entranceElement = map_get_park_entrance_element_at(_loc.x, _loc.y, zLow, false);
// Make sure the entrance part is the middle
if (entranceElement != nullptr && (entranceElement->GetSequenceIndex()) == 0)
{
entrancePath = true;
// Make the price the same as replacing a path
if (entranceElement->GetPathType() == (_type & 0xF))
entranceIsSamePath = true;
else
res->Cost -= MONEY(6, 00);
}
// Do not attempt to build a crossing with a queue or a sloped.
uint8_t crossingMode = (_type & FOOTPATH_ELEMENT_INSERT_QUEUE) || (_slope != TILE_ELEMENT_SLOPE_FLAT)
? CREATE_CROSSING_MODE_NONE
: CREATE_CROSSING_MODE_PATH_OVER_TRACK;
if (!entrancePath
&& !map_can_construct_with_clear_at(
_loc.x, _loc.y, zLow, zHigh, &map_place_non_scenery_clear_func, quarterTile, GetFlags(), &res->Cost,
crossingMode))
{
return MakeResult(GA_ERROR::NO_CLEARANCE, STR_CANT_BUILD_FOOTPATH_HERE, gGameCommandErrorText, gCommonFormatArgs);
}
gFootpathGroundFlags = gMapGroundFlags;
if (!gCheatsDisableClearanceChecks && (gMapGroundFlags & ELEMENT_IS_UNDERWATER))
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_BUILD_FOOTPATH_HERE, STR_CANT_BUILD_THIS_UNDERWATER);
}
auto tileElement = map_get_surface_element_at({ _loc.x, _loc.y });
if (tileElement == nullptr)
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_FOOTPATH_HERE);
}
auto surfaceElement = tileElement->AsSurface();
int32_t supportHeight = zLow - surfaceElement->base_height;
res->Cost += supportHeight < 0 ? MONEY(20, 00) : (supportHeight / 2) * MONEY(5, 00);
// Prevent the place sound from being spammed
if (entranceIsSamePath)
res->Cost = 0;
return res;
}
GameActionResult::Ptr ElementInsertExecute(GameActionResult::Ptr res) const
{
bool entrancePath = false, entranceIsSamePath = false;
if (!(GetFlags() & (GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_GHOST)))
{
footpath_remove_litter(_loc.x, _loc.y, _loc.z);
}
res->Cost = MONEY(12, 00);
QuarterTile quarterTile{ 0b1111, 0 };
auto zLow = _loc.z / 8;
auto zHigh = zLow + 4;
if (_slope & FOOTPATH_PROPERTIES_FLAG_IS_SLOPED)
{
quarterTile = QuarterTile{ 0b1111, 0b1100 }.Rotate(_slope & TILE_ELEMENT_DIRECTION_MASK);
zHigh += 2;
}
auto entranceElement = map_get_park_entrance_element_at(_loc.x, _loc.y, zLow, false);
// Make sure the entrance part is the middle
if (entranceElement != nullptr && (entranceElement->GetSequenceIndex()) == 0)
{
entrancePath = true;
// Make the price the same as replacing a path
if (entranceElement->GetPathType() == (_type & 0xF))
entranceIsSamePath = true;
else
res->Cost -= MONEY(6, 00);
}
// Do not attempt to build a crossing with a queue or a sloped.
uint8_t crossingMode = (_type & FOOTPATH_ELEMENT_INSERT_QUEUE) || (_slope != TILE_ELEMENT_SLOPE_FLAT)
? CREATE_CROSSING_MODE_NONE
: CREATE_CROSSING_MODE_PATH_OVER_TRACK;
if (!entrancePath
&& !map_can_construct_with_clear_at(
_loc.x, _loc.y, zLow, zHigh, &map_place_non_scenery_clear_func, quarterTile,
GAME_COMMAND_FLAG_APPLY | GetFlags(), &res->Cost, crossingMode))
{
return MakeResult(GA_ERROR::NO_CLEARANCE, STR_CANT_BUILD_FOOTPATH_HERE, gGameCommandErrorText, gCommonFormatArgs);
}
gFootpathGroundFlags = gMapGroundFlags;
auto tileElement = map_get_surface_element_at({ _loc.x, _loc.y });
if (tileElement == nullptr)
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_FOOTPATH_HERE);
}
auto surfaceElement = tileElement->AsSurface();
int32_t supportHeight = zLow - surfaceElement->base_height;
res->Cost += supportHeight < 0 ? MONEY(20, 00) : (supportHeight / 2) * MONEY(5, 00);
if (entrancePath)
{
if (!(GetFlags() & GAME_COMMAND_FLAG_GHOST) && !entranceIsSamePath)
{
// Set the path type but make sure it's not a queue as that will not show up
entranceElement->SetPathType(_type & 0x7F);
map_invalidate_tile_full(_loc.x, _loc.y);
}
}
else
{
tileElement = tile_element_insert(_loc.x / 32, _loc.y / 32, zLow, 0b1111);
assert(tileElement != nullptr);
tileElement->SetType(TILE_ELEMENT_TYPE_PATH);
PathElement* pathElement = tileElement->AsPath();
pathElement->clearance_height = zHigh;
pathElement->SetPathEntryIndex(_type);
pathElement->SetSlopeDirection(_slope & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK);
if (_slope & FOOTPATH_PROPERTIES_FLAG_IS_SLOPED)
{
pathElement->SetSloped(true);
}
if (_type & FOOTPATH_ELEMENT_INSERT_QUEUE)
{
pathElement->SetIsQueue(true);
}
pathElement->SetAddition(0);
pathElement->SetRideIndex(RIDE_ID_NULL);
pathElement->SetAdditionStatus(255);
pathElement->SetIsBroken(false);
if (GetFlags() & GAME_COMMAND_FLAG_GHOST)
{
pathElement->SetGhost(true);
}
footpath_queue_chain_reset();
if (!(GetFlags() & GAME_COMMAND_FLAG_PATH_SCENERY))
{
footpath_remove_edges_at(_loc.x, _loc.y, tileElement);
}
if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !(GetFlags() & GAME_COMMAND_FLAG_GHOST))
{
AutomaticallySetPeepSpawn();
}
RemoveIntersectingWalls(pathElement);
}
// Prevent the place sound from being spammed
if (entranceIsSamePath)
res->Cost = 0;
return res;
}
/**
*
* rct2: 0x006A65AD
*/
void AutomaticallySetPeepSpawn() const
{
uint8_t direction = 0;
if (_loc.x != 32)
{
direction++;
if (_loc.y != gMapSizeUnits - 32)
{
direction++;
if (_loc.x != gMapSizeUnits - 32)
{
direction++;
if (_loc.y != 32)
return;
}
}
}
if (gPeepSpawns.size() == 0)
{
gPeepSpawns.emplace_back();
}
PeepSpawn* peepSpawn = &gPeepSpawns[0];
peepSpawn->x = _loc.x + (word_981D6C[direction].x * 15) + 16;
peepSpawn->y = _loc.y + (word_981D6C[direction].y * 15) + 16;
peepSpawn->direction = direction;
peepSpawn->z = _loc.z;
}
void RemoveIntersectingWalls(PathElement * pathElement) const
{
if (pathElement->IsSloped() && !(GetFlags() & GAME_COMMAND_FLAG_GHOST))
{
int32_t direction = pathElement->GetSlopeDirection();
int32_t z = pathElement->base_height;
wall_remove_intersecting_walls(_loc.x, _loc.y, z, z + 6, direction_reverse(direction));
wall_remove_intersecting_walls(_loc.x, _loc.y, z, z + 6, direction);
// Removing walls may have made the pointer invalid, so find it again
auto tileElement = map_get_footpath_element(_loc.x / 32, _loc.y / 32, z);
if (tileElement == nullptr)
{
log_error("Something went wrong. Could not refind footpath.");
return;
}
pathElement = tileElement->AsPath();
}
if (!(GetFlags() & GAME_COMMAND_FLAG_PATH_SCENERY))
footpath_connect_edges(_loc.x, _loc.y, (TileElement*)pathElement, GetFlags());
footpath_update_queue_chains();
map_invalidate_tile_full(_loc.x, _loc.y);
}
PathElement* map_get_footpath_element_slope(int32_t x, int32_t y, int32_t z, int32_t slope) const
{
TileElement* tileElement;
bool isSloped = slope & FOOTPATH_PROPERTIES_FLAG_IS_SLOPED;
tileElement = map_get_first_element_at(x, y);
do
{
if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH && tileElement->base_height == z
&& (tileElement->AsPath()->IsSloped() == isSloped)
&& (tileElement->AsPath()->GetSlopeDirection() == (slope & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK)))
{
return tileElement->AsPath();
}
} while (!(tileElement++)->IsLastForTile());
return nullptr;
}
};

View File

@@ -0,0 +1,208 @@
/*****************************************************************************
* Copyright (c) 2014-2019 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#pragma once
#include "../Cheats.h"
#include "../OpenRCT2.h"
#include "../core/MemoryStream.h"
#include "../interface/Window.h"
#include "../localisation/StringIds.h"
#include "../management/Finance.h"
#include "../world/Footpath.h"
#include "../world/Location.hpp"
#include "../world/Park.h"
#include "../world/Scenery.h"
#include "../world/Wall.h"
#include "GameAction.h"
DEFINE_GAME_ACTION(FootpathSceneryPlaceAction, GAME_COMMAND_PLACE_FOOTPATH_SCENERY, GameActionResult)
{
private:
CoordsXYZ _loc;
uint8_t _pathItemType;
public:
FootpathSceneryPlaceAction() = default;
FootpathSceneryPlaceAction(CoordsXYZ loc, uint8_t pathItemType)
: _loc(loc)
, _pathItemType(pathItemType)
{
}
uint16_t GetActionFlags() const override
{
return GameAction::GetActionFlags();
}
void Serialise(DataSerialiser & stream) override
{
GameAction::Serialise(stream);
stream << DS_TAG(_loc) << DS_TAG(_pathItemType);
}
GameActionResult::Ptr Query() const override
{
auto res = MakeResult();
res->ExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
res->Position = _loc;
if (!map_is_location_valid({ _loc.x, _loc.y }))
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_POSITION_THIS_HERE, STR_OFF_EDGE_OF_MAP);
}
if (!((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode)
&& !map_is_location_owned(_loc.x, _loc.y, _loc.z / 8))
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_POSITION_THIS_HERE, STR_LAND_NOT_OWNED_BY_PARK);
}
if (_loc.z / 8 < 2)
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_POSITION_THIS_HERE, STR_TOO_LOW);
}
if (_loc.z / 8 > 248)
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_POSITION_THIS_HERE, STR_TOO_HIGH);
}
auto tileElement = map_get_footpath_element(_loc.x / 32, _loc.y / 32, _loc.z / 8);
auto pathElement = tileElement->AsPath();
if (pathElement == nullptr)
{
log_error("Could not find path element.");
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_POSITION_THIS_HERE);
}
// No change
if (!(GetFlags() & GAME_COMMAND_FLAG_GHOST) && pathElement->GetAddition() == _pathItemType
&& !(pathElement->IsBroken()))
{
if (GetFlags() & GAME_COMMAND_FLAG_4)
return MakeResult(GA_ERROR::UNKNOWN, STR_CANT_POSITION_THIS_HERE);
return res;
}
if (_pathItemType != 0)
{
rct_scenery_entry* sceneryEntry = get_footpath_item_entry(_pathItemType - 1);
if (sceneryEntry == nullptr)
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_POSITION_THIS_HERE);
}
uint16_t sceneryFlags = sceneryEntry->path_bit.flags;
if ((sceneryFlags & PATH_BIT_FLAG_DONT_ALLOW_ON_SLOPE) && pathElement->IsSloped())
{
return MakeResult(
GA_ERROR::INVALID_PARAMETERS, STR_CANT_POSITION_THIS_HERE, STR_CANT_BUILD_THIS_ON_SLOPED_FOOTPATH);
}
if ((sceneryFlags & PATH_BIT_FLAG_DONT_ALLOW_ON_QUEUE) && pathElement->IsQueue())
{
return MakeResult(
GA_ERROR::INVALID_PARAMETERS, STR_CANT_POSITION_THIS_HERE, STR_CANNOT_PLACE_THESE_ON_QUEUE_LINE_AREA);
}
if (!(sceneryFlags & (PATH_BIT_FLAG_JUMPING_FOUNTAIN_WATER | PATH_BIT_FLAG_JUMPING_FOUNTAIN_SNOW))
&& (pathElement->GetEdges()) == 0x0F)
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_POSITION_THIS_HERE);
}
if ((sceneryFlags & PATH_BIT_FLAG_IS_QUEUE_SCREEN) && !pathElement->IsQueue())
{
return MakeResult(
GA_ERROR::INVALID_PARAMETERS, STR_CANT_POSITION_THIS_HERE, STR_CAN_ONLY_PLACE_THESE_ON_QUEUE_AREA);
}
res->Cost = sceneryEntry->path_bit.price;
}
if (GetFlags() & GAME_COMMAND_FLAG_4)
return MakeResult(GA_ERROR::UNKNOWN, STR_CANT_POSITION_THIS_HERE);
// Should place a ghost?
if (GetFlags() & GAME_COMMAND_FLAG_GHOST)
{
// Check if there is something on the path already
if (pathElement->HasAddition())
{
return MakeResult(GA_ERROR::ITEM_ALREADY_PLACED, STR_CANT_POSITION_THIS_HERE);
}
}
return res;
}
GameActionResult::Ptr Execute() const override
{
auto res = MakeResult();
res->Position = _loc;
res->ExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
auto tileElement = map_get_footpath_element(_loc.x / 32, _loc.y / 32, _loc.z / 8);
auto pathElement = tileElement->AsPath();
if (pathElement == nullptr)
{
log_error("Could not find path element.");
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_POSITION_THIS_HERE);
}
// No change
if (!(GetFlags() & GAME_COMMAND_FLAG_GHOST) && pathElement->GetAddition() == _pathItemType && !(pathElement->IsBroken())
&& !pathElement->AdditionIsGhost())
{
return res;
}
if (_pathItemType != 0)
{
rct_scenery_entry* sceneryEntry = get_footpath_item_entry(_pathItemType - 1);
if (sceneryEntry == nullptr)
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_POSITION_THIS_HERE);
}
res->Cost = sceneryEntry->path_bit.price;
}
if (GetFlags() & GAME_COMMAND_FLAG_GHOST)
{
pathElement->SetAdditionIsGhost(true);
}
else
{
footpath_interrupt_peeps(_loc.x, _loc.y, _loc.z);
}
if ((_pathItemType != 0 && !(GetFlags() & GAME_COMMAND_FLAG_GHOST))
|| (_pathItemType == 0 && pathElement->AdditionIsGhost()))
{
pathElement->SetAdditionIsGhost(false);
}
pathElement->SetAddition(_pathItemType);
pathElement->SetIsBroken(false);
if (_pathItemType != 0)
{
rct_scenery_entry* scenery_entry = get_footpath_item_entry(_pathItemType - 1);
if (scenery_entry != nullptr && scenery_entry->path_bit.flags & PATH_BIT_FLAG_IS_BIN)
{
pathElement->SetAdditionStatus(255);
}
}
map_invalidate_tile_full(_loc.x, _loc.y);
return res;
}
};

View File

@@ -0,0 +1,115 @@
/*****************************************************************************
* Copyright (c) 2014-2019 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#pragma once
#include "../Cheats.h"
#include "../OpenRCT2.h"
#include "../core/MemoryStream.h"
#include "../interface/Window.h"
#include "../localisation/StringIds.h"
#include "../management/Finance.h"
#include "../world/Footpath.h"
#include "../world/Location.hpp"
#include "../world/Park.h"
#include "../world/Wall.h"
#include "GameAction.h"
DEFINE_GAME_ACTION(FootpathSceneryRemoveAction, GAME_COMMAND_REMOVE_FOOTPATH_SCENERY, GameActionResult)
{
private:
CoordsXYZ _loc;
public:
FootpathSceneryRemoveAction() = default;
FootpathSceneryRemoveAction(CoordsXYZ loc)
: _loc(loc)
{
}
uint16_t GetActionFlags() const override
{
return GameAction::GetActionFlags();
}
void Serialise(DataSerialiser & stream) override
{
GameAction::Serialise(stream);
stream << DS_TAG(_loc);
}
GameActionResult::Ptr Query() const override
{
if (!map_is_location_valid({ _loc.x, _loc.y }))
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_REMOVE_THIS, STR_OFF_EDGE_OF_MAP);
}
if (!((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode)
&& !map_is_location_owned(_loc.x, _loc.y, _loc.z / 8))
{
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_REMOVE_THIS, STR_LAND_NOT_OWNED_BY_PARK);
}
if (_loc.z / 8 < 2)
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_REMOVE_THIS, STR_TOO_LOW);
}
if (_loc.z / 8 > 248)
{
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_REMOVE_THIS, STR_TOO_HIGH);
}
auto tileElement = map_get_footpath_element(_loc.x / 32, _loc.y / 32, _loc.z / 8);
auto pathElement = tileElement->AsPath();
if (pathElement == nullptr)
{
log_error("Could not find path element.");
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_REMOVE_THIS);
}
if (!pathElement->AdditionIsGhost() && (GetFlags() & GAME_COMMAND_FLAG_GHOST))
{
log_error("Tried to remove non ghost during ghost removal.");
return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_REMOVE_THIS);
}
auto res = MakeResult();
res->Position = _loc;
res->Cost = MONEY(0, 0);
return res;
}
GameActionResult::Ptr Execute() const override
{
auto tileElement = map_get_footpath_element(_loc.x / 32, _loc.y / 32, _loc.z / 8);
auto pathElement = tileElement->AsPath();
if (!(GetFlags() & GAME_COMMAND_FLAG_GHOST))
{
footpath_interrupt_peeps(_loc.x, _loc.y, _loc.z);
}
if (pathElement == nullptr)
{
log_error("Could not find path element.");
return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_REMOVE_THIS);
}
pathElement->SetAddition(0);
map_invalidate_tile_full(_loc.x, _loc.y);
auto res = MakeResult();
res->Position = _loc;
res->Cost = MONEY(0, 0);
return res;
}
};

View File

@@ -10,7 +10,10 @@
#include "BannerSetNameAction.hpp"
#include "ClearAction.hpp"
#include "ClimateSetAction.hpp"
#include "FootpathPlaceAction.hpp"
#include "FootpathRemoveAction.hpp"
#include "FootpathSceneryPlaceAction.hpp"
#include "FootpathSceneryRemoveAction.hpp"
#include "GameAction.h"
#include "GuestSetNameAction.hpp"
#include "LandSetHeightAction.hpp"
@@ -55,7 +58,10 @@ namespace GameActions
{
Register<BannerSetNameAction>();
Register<ClimateSetAction>();
Register<FootpathPlaceAction>();
Register<FootpathRemoveAction>();
Register<FootpathSceneryPlaceAction>();
Register<FootpathSceneryRemoveAction>();
Register<GuestSetNameAction>();
Register<MazeSetTrackAction>();
Register<ParkMarketingAction>();

View File

@@ -343,6 +343,31 @@ template<> struct DataSerializerTraits<CoordsXY>
}
};
template<> struct DataSerializerTraits<CoordsXYZ>
{
static void encode(IStream* stream, const CoordsXYZ& coord)
{
stream->WriteValue(ByteSwapBE(coord.x));
stream->WriteValue(ByteSwapBE(coord.y));
stream->WriteValue(ByteSwapBE(coord.z));
}
static void decode(IStream* stream, CoordsXYZ& coord)
{
auto x = ByteSwapBE(stream->ReadValue<int32_t>());
auto y = ByteSwapBE(stream->ReadValue<int32_t>());
auto z = ByteSwapBE(stream->ReadValue<int32_t>());
coord = CoordsXYZ{ x, y, z };
}
static void log(IStream* stream, const CoordsXYZ& coord)
{
char msg[128] = {};
snprintf(msg, sizeof(msg), "CoordsXYZ(x = %d, y = %d, z = %d)", coord.x, coord.y, coord.z);
stream->Write(msg, strlen(msg));
}
};
template<> struct DataSerializerTraits<CoordsXYZD>
{
static void encode(IStream* stream, const CoordsXYZD& coord)

View File

@@ -31,7 +31,7 @@
// This string specifies which version of network stream current build uses.
// It is used for making sure only compatible builds get connected, even within
// single OpenRCT2 version.
#define NETWORK_STREAM_VERSION "53"
#define NETWORK_STREAM_VERSION "54"
#define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION
static Peep* _pickup_peep = nullptr;

View File

@@ -153,6 +153,8 @@ const std::array<NetworkAction, NETWORK_PERMISSION_COUNT> NetworkActions::Action
GAME_COMMAND_PLACE_PATH,
GAME_COMMAND_PLACE_PATH_FROM_TRACK,
GAME_COMMAND_REMOVE_PATH,
GAME_COMMAND_PLACE_FOOTPATH_SCENERY,
GAME_COMMAND_REMOVE_FOOTPATH_SCENERY,
},
},
NetworkAction{

View File

@@ -11,6 +11,7 @@
#include "../Context.h"
#include "../Game.h"
#include "../OpenRCT2.h"
#include "../actions/FootpathPlaceAction.hpp"
#include "../actions/FootpathRemoveAction.hpp"
#include "../core/Guard.hpp"
#include "../localisation/Localisation.h"
@@ -109,39 +110,6 @@ static bool entrance_has_direction(TileElement* tileElement, int32_t direction)
return entrance_get_directions(tileElement) & (1 << (direction & 3));
}
/**
*
* rct2: 0x006A65AD
*/
static void automatically_set_peep_spawn(CoordsXYZ location)
{
uint8_t direction = 0;
if (location.x != 32)
{
direction++;
if (location.y != gMapSizeUnits - 32)
{
direction++;
if (location.x != gMapSizeUnits - 32)
{
direction++;
if (location.y != 32)
return;
}
}
}
if (gPeepSpawns.size() == 0)
{
gPeepSpawns.emplace_back();
}
PeepSpawn* peepSpawn = &gPeepSpawns[0];
peepSpawn->x = location.x + (word_981D6C[direction].x * 15) + 16;
peepSpawn->y = location.y + (word_981D6C[direction].y * 15) + 16;
peepSpawn->direction = direction;
peepSpawn->z = location.z;
}
TileElement* map_get_footpath_element(int32_t x, int32_t y, int32_t z)
{
TileElement* tileElement;
@@ -156,365 +124,11 @@ TileElement* map_get_footpath_element(int32_t x, int32_t y, int32_t z)
return nullptr;
}
static TileElement* map_get_footpath_element_slope(int32_t x, int32_t y, int32_t z, int32_t slope)
{
TileElement* tileElement;
bool isSloped = slope & FOOTPATH_PROPERTIES_FLAG_IS_SLOPED;
tileElement = map_get_first_element_at(x, y);
do
{
if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH && tileElement->base_height == z
&& (tileElement->AsPath()->IsSloped() == isSloped)
&& (tileElement->AsPath()->GetSlopeDirection() == (slope & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK)))
{
return tileElement;
}
} while (!(tileElement++)->IsLastForTile());
return nullptr;
}
static void loc_6A6620(int32_t flags, int32_t x, int32_t y, TileElement* tileElement)
{
if (tileElement->AsPath()->IsSloped() && !(flags & GAME_COMMAND_FLAG_GHOST))
{
int32_t direction = tileElement->AsPath()->GetSlopeDirection();
int32_t z = tileElement->base_height;
wall_remove_intersecting_walls(x, y, z, z + 6, direction_reverse(direction));
wall_remove_intersecting_walls(x, y, z, z + 6, direction);
// Removing walls may have made the pointer invalid, so find it again
tileElement = map_get_footpath_element(x / 32, y / 32, z);
}
if (!(flags & GAME_COMMAND_FLAG_PATH_SCENERY))
footpath_connect_edges(x, y, tileElement, flags);
footpath_update_queue_chains();
map_invalidate_tile_full(x, y);
}
/** rct2: 0x0098D7EC */
static constexpr const QuarterTile SlopedFootpathQuarterTiles[] = {
{ 0b1111, 0b1100 }, { 0b1111, 0b1001 }, { 0b1111, 0b0011 }, { 0b1111, 0b0110 }
};
static money32 footpath_element_insert(
int32_t type, int32_t x, int32_t y, int32_t z, int32_t slope, int32_t flags, uint8_t pathItemType)
{
TileElement* tileElement;
EntranceElement* entranceElement;
int32_t zHigh;
bool entrancePath = false, entranceIsSamePath = false;
if (!map_check_free_elements_and_reorganise(1))
return MONEY32_UNDEFINED;
if ((flags & GAME_COMMAND_FLAG_APPLY) && !(flags & (GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_GHOST)))
footpath_remove_litter(x, y, gCommandPosition.z);
// loc_6A649D:
gFootpathPrice += MONEY(12, 00);
QuarterTile quarterTile{ 0b1111, 0 };
zHigh = z + 4;
if (slope & FOOTPATH_PROPERTIES_FLAG_IS_SLOPED)
{
quarterTile = SlopedFootpathQuarterTiles[slope & TILE_ELEMENT_DIRECTION_MASK];
zHigh += 2;
}
entranceElement = map_get_park_entrance_element_at(x, y, z, false);
// Make sure the entrance part is the middle
if (entranceElement != nullptr && (entranceElement->GetSequenceIndex()) == 0)
{
entrancePath = true;
// Make the price the same as replacing a path
if (entranceElement->GetPathType() == (type & 0xF))
entranceIsSamePath = true;
else
gFootpathPrice -= MONEY(6, 00);
}
// Do not attempt to build a crossing with a queue or a sloped.
uint8_t crossingMode = (type & FOOTPATH_ELEMENT_INSERT_QUEUE) || (slope != TILE_ELEMENT_SLOPE_FLAT)
? CREATE_CROSSING_MODE_NONE
: CREATE_CROSSING_MODE_PATH_OVER_TRACK;
if (!entrancePath
&& !map_can_construct_with_clear_at(
x, y, z, zHigh, &map_place_non_scenery_clear_func, quarterTile, flags, &gFootpathPrice, crossingMode))
return MONEY32_UNDEFINED;
gFootpathGroundFlags = gMapGroundFlags;
if (!gCheatsDisableClearanceChecks && (gMapGroundFlags & ELEMENT_IS_UNDERWATER))
{
gGameCommandErrorText = STR_CANT_BUILD_THIS_UNDERWATER;
return MONEY32_UNDEFINED;
}
tileElement = map_get_surface_element_at({ x, y });
if (tileElement == nullptr)
{
return MONEY32_UNDEFINED;
}
int32_t supportHeight = z - tileElement->base_height;
gFootpathPrice += supportHeight < 0 ? MONEY(20, 00) : (supportHeight / 2) * MONEY(5, 00);
if (flags & GAME_COMMAND_FLAG_APPLY)
{
if (entrancePath)
{
if (!(flags & GAME_COMMAND_FLAG_GHOST) && !entranceIsSamePath)
{
// Set the path type but make sure it's not a queue as that will not show up
entranceElement->SetPathType(type & 0x7F);
map_invalidate_tile_full(x, y);
}
}
else
{
tileElement = tile_element_insert(x / 32, y / 32, z, 0x0F);
assert(tileElement != nullptr);
tileElement->SetType(TILE_ELEMENT_TYPE_PATH);
PathElement* pathElement = tileElement->AsPath();
pathElement->clearance_height = z + 4 + ((slope & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK) ? 2 : 0);
pathElement->SetPathEntryIndex(type);
pathElement->SetSlopeDirection(slope & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK);
if (slope & FOOTPATH_PROPERTIES_FLAG_IS_SLOPED)
pathElement->SetSloped(true);
if (type & FOOTPATH_ELEMENT_INSERT_QUEUE)
pathElement->SetIsQueue(true);
pathElement->SetAddition(pathItemType);
pathElement->SetRideIndex(RIDE_ID_NULL);
pathElement->SetAdditionStatus(255);
pathElement->SetIsBroken(false);
if (flags & GAME_COMMAND_FLAG_GHOST)
pathElement->SetGhost(true);
footpath_queue_chain_reset();
if (!(flags & GAME_COMMAND_FLAG_PATH_SCENERY))
footpath_remove_edges_at(x, y, tileElement);
if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !(flags & GAME_COMMAND_FLAG_GHOST))
automatically_set_peep_spawn({ x, y, tileElement->base_height * 8 });
loc_6A6620(flags, x, y, tileElement);
}
}
// Prevent the place sound from being spammed
if (entranceIsSamePath)
gFootpathPrice = 0;
return gParkFlags & PARK_FLAGS_NO_MONEY ? 0 : gFootpathPrice;
}
static money32 footpath_element_update(
int32_t x, int32_t y, TileElement* tileElement, int32_t type, int32_t flags, uint8_t pathItemType)
{
const int32_t newFootpathType = (type & (FOOTPATH_PROPERTIES_TYPE_MASK >> 4));
const bool newPathIsQueue = ((type >> 7) == 1);
if (tileElement->AsPath()->GetPathEntryIndex() != newFootpathType || tileElement->AsPath()->IsQueue() != newPathIsQueue)
{
gFootpathPrice += MONEY(6, 00);
}
else if (pathItemType != 0)
{
if (!(flags & GAME_COMMAND_FLAG_GHOST) && tileElement->AsPath()->GetAddition() == pathItemType
&& !(tileElement->AsPath()->IsBroken()))
{
if (flags & GAME_COMMAND_FLAG_4)
return MONEY32_UNDEFINED;
return gParkFlags & PARK_FLAGS_NO_MONEY ? 0 : gFootpathPrice;
}
if (pathItemType != 0)
{
rct_scenery_entry* scenery_entry = get_footpath_item_entry(pathItemType - 1);
uint16_t unk6 = scenery_entry->path_bit.flags;
if ((unk6 & PATH_BIT_FLAG_DONT_ALLOW_ON_SLOPE) && tileElement->AsPath()->IsSloped())
{
gGameCommandErrorText = STR_CANT_BUILD_THIS_ON_SLOPED_FOOTPATH;
return MONEY32_UNDEFINED;
}
if ((unk6 & PATH_BIT_FLAG_DONT_ALLOW_ON_QUEUE) && tileElement->AsPath()->IsQueue())
{
gGameCommandErrorText = STR_CANNOT_PLACE_THESE_ON_QUEUE_LINE_AREA;
return MONEY32_UNDEFINED;
}
if (!(unk6 & (PATH_BIT_FLAG_JUMPING_FOUNTAIN_WATER | PATH_BIT_FLAG_JUMPING_FOUNTAIN_SNOW))
&& (tileElement->AsPath()->GetEdges()) == 0x0F)
{
gGameCommandErrorText = STR_NONE;
return MONEY32_UNDEFINED;
}
if ((unk6 & PATH_BIT_FLAG_IS_QUEUE_SCREEN) && !tileElement->AsPath()->IsQueue())
{
gGameCommandErrorText = STR_CAN_ONLY_PLACE_THESE_ON_QUEUE_AREA;
return MONEY32_UNDEFINED;
}
gFootpathPrice += scenery_entry->path_bit.price;
}
if (flags & GAME_COMMAND_FLAG_4)
return MONEY32_UNDEFINED;
// Should place a ghost?
if (flags & GAME_COMMAND_FLAG_GHOST)
{
// Check if there is something on the path already
if (tileElement->AsPath()->HasAddition())
{
gGameCommandErrorText = STR_NONE;
return MONEY32_UNDEFINED;
}
// There is nothing yet - check if we should place a ghost
if (flags & GAME_COMMAND_FLAG_APPLY)
tileElement->AsPath()->SetAdditionIsGhost(true);
}
if (!(flags & GAME_COMMAND_FLAG_APPLY))
return gParkFlags & PARK_FLAGS_NO_MONEY ? 0 : gFootpathPrice;
if ((pathItemType != 0 && !(flags & GAME_COMMAND_FLAG_GHOST))
|| (pathItemType == 0 && tileElement->AsPath()->AdditionIsGhost()))
{
tileElement->AsPath()->SetAdditionIsGhost(false);
}
tileElement->AsPath()->SetAddition(pathItemType);
tileElement->AsPath()->SetIsBroken(false);
if (pathItemType != 0)
{
rct_scenery_entry* scenery_entry = get_footpath_item_entry(pathItemType - 1);
if (scenery_entry != nullptr && scenery_entry->path_bit.flags & PATH_BIT_FLAG_IS_BIN)
{
tileElement->AsPath()->SetAdditionStatus(255);
}
}
map_invalidate_tile_full(x, y);
return gParkFlags & PARK_FLAGS_NO_MONEY ? 0 : gFootpathPrice;
}
if (flags & GAME_COMMAND_FLAG_4)
return MONEY32_UNDEFINED;
if (flags & GAME_COMMAND_FLAG_APPLY)
{
footpath_queue_chain_reset();
if (!(flags & GAME_COMMAND_FLAG_PATH_SCENERY))
footpath_remove_edges_at(x, y, tileElement);
tileElement->AsPath()->SetPathEntryIndex(type);
if (type & (1 << 7))
tileElement->AsPath()->SetIsQueue(true);
else
tileElement->AsPath()->SetIsQueue(false);
tileElement->AsPath()->SetAddition(pathItemType);
tileElement->AsPath()->SetIsBroken(false);
loc_6A6620(flags, x, y, tileElement);
}
return gParkFlags & PARK_FLAGS_NO_MONEY ? 0 : gFootpathPrice;
}
static money32 footpath_place_real(
int32_t type, int32_t x, int32_t y, int32_t z, int32_t slope, int32_t flags, uint8_t pathItemType, bool clearDirection,
int32_t direction)
{
TileElement* tileElement;
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
gCommandPosition.x = x + 16;
gCommandPosition.y = y + 16;
gCommandPosition.z = z * 8;
if (!(flags & GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED) && game_is_paused() && !gCheatsBuildInPauseMode)
{
gGameCommandErrorText = STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED;
return MONEY32_UNDEFINED;
}
if ((flags & GAME_COMMAND_FLAG_APPLY) && !(flags & GAME_COMMAND_FLAG_GHOST))
footpath_interrupt_peeps(x, y, z * 8);
gFootpathPrice = 0;
gFootpathGroundFlags = 0;
if (map_is_edge({ x, y }))
{
gGameCommandErrorText = STR_OFF_EDGE_OF_MAP;
return MONEY32_UNDEFINED;
}
if (!((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode) && !map_is_location_owned(x, y, z * 8))
return MONEY32_UNDEFINED;
if (slope & SLOPE_IS_IRREGULAR_FLAG)
{
gGameCommandErrorText = STR_LAND_SLOPE_UNSUITABLE;
return MONEY32_UNDEFINED;
}
if (z < 2)
{
gGameCommandErrorText = STR_TOO_LOW;
return MONEY32_UNDEFINED;
}
if (z > 248)
{
gGameCommandErrorText = STR_TOO_HIGH;
return MONEY32_UNDEFINED;
}
// Force ride construction to recheck area
_currentTrackSelectionFlags |= TRACK_SELECTION_FLAG_RECHECK;
if (gGameCommandNestLevel == 1 && !(flags & GAME_COMMAND_FLAG_GHOST))
{
LocationXYZ16 coord;
coord.x = x + 16;
coord.y = y + 16;
coord.z = tile_element_height(coord.x, coord.y);
network_set_player_last_action_coord(network_get_player_index(game_command_playerid), coord);
if (clearDirection && !gCheatsDisableClearanceChecks)
{
direction = direction & 0xF;
// It is possible, let's remove walls between the old and new piece of path
wall_remove_intersecting_walls(
x, y, z, z + 4 + ((slope & TILE_ELEMENT_SURFACE_RAISED_CORNERS_MASK) ? 2 : 0), direction_reverse(direction));
wall_remove_intersecting_walls(
x - CoordsDirectionDelta[direction].x, y - CoordsDirectionDelta[direction].y, z, z + 4, direction);
}
}
footpath_provisional_remove();
tileElement = map_get_footpath_element_slope((x / 32), (y / 32), z, slope);
if (tileElement == nullptr)
{
return footpath_element_insert(type, x, y, z, slope, flags, pathItemType);
}
else
{
return footpath_element_update(x, y, tileElement, type, flags, pathItemType);
}
}
/**
*
* rct2: 0x006BA23E
@@ -534,18 +148,6 @@ void remove_banners_at_element(int32_t x, int32_t y, TileElement* tileElement)
}
}
/**
*
* rct2: 0x006A61DE
*/
void game_command_place_footpath(
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, [[maybe_unused]] int32_t* esi, int32_t* edi, int32_t* ebp)
{
*ebx = footpath_place_real(
(*edx >> 8) & 0xFF, *eax & 0xFFFF, *ecx & 0xFFFF, *edx & 0xFF, (*ebx >> 8) & 0xFF, *ebx & 0xFF, *edi & 0xFF,
(*ebp & FOOTPATH_CLEAR_DIRECTIONAL) >> 8, *ebp & 0xFF);
}
static money32 footpath_place_from_track(
int32_t type, int32_t x, int32_t y, int32_t z, int32_t slope, int32_t edges, int32_t flags)
{
@@ -711,18 +313,6 @@ void game_command_place_footpath_from_track(
(*ebx >> 8) & 0xF, *ebx & 0xFF);
}
money32 footpath_place(int32_t type, int32_t x, int32_t y, int32_t z, int32_t slope, int32_t flags)
{
return game_do_command(x, (slope << 8) | flags, y, (type << 8) | z, GAME_COMMAND_PLACE_PATH, 0, 0);
}
money32 footpath_place_remove_intersecting(
int32_t type, int32_t x, int32_t y, int32_t z, int32_t slope, int32_t flags, int32_t direction)
{
return game_do_command(
x, (slope << 8) | flags, y, (type << 8) | z, GAME_COMMAND_PLACE_PATH, 0, FOOTPATH_CLEAR_DIRECTIONAL | direction);
}
money32 footpath_remove(int32_t x, int32_t y, int32_t z, int32_t flags)
{
auto action = FootpathRemoveAction(x, y, z);
@@ -747,11 +337,11 @@ money32 footpath_provisional_set(int32_t type, int32_t x, int32_t y, int32_t z,
footpath_provisional_remove();
cost = footpath_place(
type, x, y, z, slope,
GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_5 | GAME_COMMAND_FLAG_4 | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED
| GAME_COMMAND_FLAG_APPLY);
if (cost != MONEY32_UNDEFINED)
auto footpathPlaceAction = FootpathPlaceAction({ x, y, z * 8 }, slope, type);
footpathPlaceAction.SetFlags(GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED);
auto res = GameActions::Execute(&footpathPlaceAction);
cost = res->Error == GA_ERROR::OK ? res->Cost : MONEY32_UNDEFINED;
if (res->Error == GA_ERROR::OK)
{
gFootpathProvisionalType = type;
gFootpathProvisionalPosition.x = x;
@@ -775,7 +365,7 @@ money32 footpath_provisional_set(int32_t type, int32_t x, int32_t y, int32_t z,
if (!scenery_tool_is_active())
{
if (cost == MONEY32_UNDEFINED)
if (res->Error != GA_ERROR::OK)
{
// If we can't build this, don't show a virtual floor.
virtual_floor_set_height(0);

View File

@@ -170,16 +170,13 @@ extern const LocationXY16 BinUseOffsets[4];
extern const LocationXY16 BenchUseOffsets[8];
TileElement* map_get_footpath_element(int32_t x, int32_t y, int32_t z);
struct PathElement;
PathElement* map_get_footpath_element_slope(int32_t x, int32_t y, int32_t z, int32_t slope);
void footpath_interrupt_peeps(int32_t x, int32_t y, int32_t z);
void game_command_place_footpath(
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp);
void game_command_place_footpath_from_track(
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp);
void game_command_remove_footpath(
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp);
money32 footpath_place(int32_t type, int32_t x, int32_t y, int32_t z, int32_t slope, int32_t flags);
money32 footpath_place_remove_intersecting(
int32_t type, int32_t x, int32_t y, int32_t z, int32_t slope, int32_t flags, int32_t direction);
money32 footpath_remove(int32_t x, int32_t y, int32_t z, int32_t flags);
money32 footpath_provisional_set(int32_t type, int32_t x, int32_t y, int32_t z, int32_t slope);
void footpath_provisional_remove();

View File

@@ -12,6 +12,7 @@
#include "../Cheats.h"
#include "../Context.h"
#include "../Game.h"
#include "../actions/FootpathSceneryRemoveAction.hpp"
#include "../actions/LargeSceneryRemoveAction.hpp"
#include "../actions/SmallSceneryRemoveAction.hpp"
#include "../actions/WallRemoveAction.hpp"
@@ -205,9 +206,9 @@ void scenery_remove_ghost_tool_placement()
if (tileElement->base_height != z)
continue;
game_do_command(
x, 233 | (gSceneryPlacePathSlope << 8), y, z | (gSceneryPlacePathType << 8), GAME_COMMAND_PLACE_PATH,
gSceneryGhostPathObjectType & 0xFFFF0000, 0);
auto footpathSceneryRemoveAction = FootpathSceneryRemoveAction({ x, y, z * 8 });
footpathSceneryRemoveAction.SetFlags(GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_GHOST);
GameActions::Execute(&footpathSceneryRemoveAction);
break;
} while (!(tileElement++)->IsLastForTile());
}