diff --git a/src/openrct2/Game.cpp b/src/openrct2/Game.cpp index 7a46e8b733..fc38e1bb4b 100644 --- a/src/openrct2/Game.cpp +++ b/src/openrct2/Game.cpp @@ -1278,7 +1278,7 @@ GAME_COMMAND_POINTER* new_game_command_table[GAME_COMMAND_COUNT] = { nullptr, nullptr, nullptr, - game_command_place_footpath_from_track, + nullptr, nullptr, game_command_change_surface_style, nullptr, diff --git a/src/openrct2/Game.h b/src/openrct2/Game.h index 99961ed78e..35bdde7acc 100644 --- a/src/openrct2/Game.h +++ b/src/openrct2/Game.h @@ -36,8 +36,8 @@ enum GAME_COMMAND GAME_COMMAND_PLACE_SCENERY, // GA GAME_COMMAND_SET_WATER_HEIGHT, // GA GAME_COMMAND_PLACE_PATH, // GA - GAME_COMMAND_PLACE_PATH_FROM_TRACK, - GAME_COMMAND_REMOVE_PATH, // GA + GAME_COMMAND_PLACE_PATH_FROM_TRACK, // GA + GAME_COMMAND_REMOVE_PATH, // GA GAME_COMMAND_CHANGE_SURFACE_STYLE, GAME_COMMAND_SET_RIDE_PRICE, // GA GAME_COMMAND_SET_GUEST_NAME, // GA diff --git a/src/openrct2/actions/FootpathPlaceFromTrackAction.hpp b/src/openrct2/actions/FootpathPlaceFromTrackAction.hpp new file mode 100644 index 0000000000..41f4e50c19 --- /dev/null +++ b/src/openrct2/actions/FootpathPlaceFromTrackAction.hpp @@ -0,0 +1,285 @@ +/***************************************************************************** + * 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(FootpathPlaceFromTrackAction, GAME_COMMAND_PLACE_PATH_FROM_TRACK, GameActionResult) +{ +private: + CoordsXYZ _loc; + uint8_t _slope; + uint8_t _type; + uint8_t _edges; + +public: + FootpathPlaceFromTrackAction() = default; + FootpathPlaceFromTrackAction(CoordsXYZ loc, uint8_t slope, uint8_t type, uint8_t edges) + : _loc(loc) + , _slope(slope) + , _type(type) + , _edges(edges) + { + } + + 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(_edges); + } + + GameActionResult::Ptr Query() const override + { + GameActionResult::Ptr res = std::make_unique(); + 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_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_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_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE, STR_LAND_NOT_OWNED_BY_PARK); + } + + if (_loc.z / 8 < 2) + { + return MakeResult(GA_ERROR::DISALLOWED, STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE, STR_TOO_LOW); + } + + if (_loc.z / 8 > 248) + { + return MakeResult(GA_ERROR::DISALLOWED, STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE, STR_TOO_HIGH); + } + + return ElementInsertQuery(std::move(res)); + } + + GameActionResult::Ptr Execute() const override + { + GameActionResult::Ptr res = std::make_unique(); + 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; + + return ElementInsertExecute(std::move(res)); + } + +private: + 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_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_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_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE, gGameCommandErrorText, + gCommonFormatArgs); + } + + gFootpathGroundFlags = gMapGroundFlags; + if (!gCheatsDisableClearanceChecks && (gMapGroundFlags & ELEMENT_IS_UNDERWATER)) + { + return MakeResult( + GA_ERROR::DISALLOWED, STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_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_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_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_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_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_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_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); + pathElement->SetEdges(_edges); + pathElement->SetCorners(0); + if (GetFlags() & GAME_COMMAND_FLAG_GHOST) + { + pathElement->SetGhost(true); + } + map_invalidate_tile_full(_loc.x, _loc.y); + } + + // Prevent the place sound from being spammed + if (entranceIsSamePath) + res->Cost = 0; + + return res; + } +}; diff --git a/src/openrct2/actions/GameActionRegistration.cpp b/src/openrct2/actions/GameActionRegistration.cpp index c24a8e0a7f..fa705b1681 100644 --- a/src/openrct2/actions/GameActionRegistration.cpp +++ b/src/openrct2/actions/GameActionRegistration.cpp @@ -11,6 +11,7 @@ #include "ClearAction.hpp" #include "ClimateSetAction.hpp" #include "FootpathPlaceAction.hpp" +#include "FootpathPlaceFromTrackAction.hpp" #include "FootpathRemoveAction.hpp" #include "FootpathSceneryPlaceAction.hpp" #include "FootpathSceneryRemoveAction.hpp" @@ -59,6 +60,7 @@ namespace GameActions Register(); Register(); Register(); + Register(); Register(); Register(); Register(); diff --git a/src/openrct2/ride/TrackDesign.cpp b/src/openrct2/ride/TrackDesign.cpp index 049c03e316..54eff77a42 100644 --- a/src/openrct2/ride/TrackDesign.cpp +++ b/src/openrct2/ride/TrackDesign.cpp @@ -12,6 +12,7 @@ #include "../Cheats.h" #include "../Game.h" #include "../OpenRCT2.h" +#include "../actions/FootpathPlaceFromTrackAction.hpp" #include "../actions/LargeSceneryRemoveAction.hpp" #include "../actions/RideEntranceExitPlaceAction.hpp" #include "../actions/RideSetSetting.hpp" @@ -1098,15 +1099,15 @@ static int32_t track_design_place_scenery( flags = 0; } - gGameCommandErrorTitle = STR_RIDE_CONSTRUCTION_CANT_CONSTRUCT_THIS_HERE; - cost = game_do_command( - mapCoord.x, flags | (bh << 8), mapCoord.y, z | (entry_index << 8), - GAME_COMMAND_PLACE_PATH_FROM_TRACK, 0, 0); - - if (cost == MONEY32_UNDEFINED) - { - cost = 0; - } + uint8_t slope = ((bh >> 5) & 0x3) | ((bh >> 2) & 0x4); + uint8_t edges = bh & 0xF; + auto footpathPlaceAction = FootpathPlaceFromTrackAction( + { mapCoord.x, mapCoord.y, z * 8 }, slope, entry_index, edges); + footpathPlaceAction.SetFlags(flags); + auto res = flags & GAME_COMMAND_FLAG_APPLY ? GameActions::ExecuteNested(&footpathPlaceAction) + : GameActions::QueryNested(&footpathPlaceAction); + // Ignore failures + cost = res->Error == GA_ERROR::OK ? res->Cost : 0; } else { diff --git a/src/openrct2/world/Footpath.cpp b/src/openrct2/world/Footpath.cpp index 7222e6bf9e..801e679203 100644 --- a/src/openrct2/world/Footpath.cpp +++ b/src/openrct2/world/Footpath.cpp @@ -124,11 +124,6 @@ TileElement* map_get_footpath_element(int32_t x, int32_t y, int32_t z) return nullptr; } -/** rct2: 0x0098D7EC */ -static constexpr const QuarterTile SlopedFootpathQuarterTiles[] = { - { 0b1111, 0b1100 }, { 0b1111, 0b1001 }, { 0b1111, 0b0011 }, { 0b1111, 0b0110 } -}; - /** * * rct2: 0x006BA23E @@ -148,171 +143,6 @@ void remove_banners_at_element(int32_t x, int32_t y, TileElement* tileElement) } } -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) -{ - TileElement* tileElement; - EntranceElement* entranceElement; - bool entrancePath = false, entranceIsSamePath = false; - - 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_location_owned(x, y, z * 8) && !gCheatsSandboxMode) - { - return MONEY32_UNDEFINED; - } - - if (!((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode) && !map_is_location_owned(x, y, z * 8)) - return MONEY32_UNDEFINED; - - if (z < 2) - { - gGameCommandErrorText = STR_TOO_LOW; - return MONEY32_UNDEFINED; - } - - if (z > 248) - { - gGameCommandErrorText = STR_TOO_HIGH; - return MONEY32_UNDEFINED; - } - - if (flags & GAME_COMMAND_FLAG_APPLY) - { - if (!(flags & (GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_GHOST))) - { - footpath_remove_litter(x, y, z * 8); - } - } - - gFootpathPrice += 120; - QuarterTile quarterTile = { 0b1111, 0 }; - int32_t zHigh = z + 4; - if (slope & TILE_ELEMENT_SLOPE_S_CORNER_UP) - { - quarterTile = SlopedFootpathQuarterTiles[slope & TILE_ELEMENT_SLOPE_NE_SIDE_UP]; - 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 }); - - 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 (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 (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(); - // This can NEVER happen, but GCC does not want to believe that... - if (pathElement == nullptr) - { - assert(false); - return MONEY32_UNDEFINED; - } - pathElement->clearance_height = z + 4 + ((slope & FOOTPATH_PROPERTIES_FLAG_IS_SLOPED) ? 2 : 0); - pathElement->SetPathEntryIndex(type & 0xF); - pathElement->SetSlopeDirection(slope & FOOTPATH_PROPERTIES_SLOPE_DIRECTION_MASK); - if (slope & FOOTPATH_PROPERTIES_FLAG_IS_SLOPED) - pathElement->SetSloped(true); - if (type & (1 << 7)) - pathElement->SetIsQueue(true); - pathElement->SetAddition(0); - pathElement->SetRideIndex(RIDE_ID_NULL); - pathElement->SetAdditionStatus(255); - pathElement->SetEdges(edges); - pathElement->SetCorners(0); - pathElement->SetIsBroken(false); - if (flags & (1 << 6)) - pathElement->SetGhost(true); - - map_invalidate_tile_full(x, y); - } - } - - if (entranceIsSamePath) - gFootpathPrice = 0; - - return gParkFlags & PARK_FLAGS_NO_MONEY ? 0 : gFootpathPrice; -} - -/** - * - * rct2: 0x006A68AE - */ -void game_command_place_footpath_from_track( - int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, [[maybe_unused]] int32_t* esi, [[maybe_unused]] int32_t* edi, - [[maybe_unused]] int32_t* ebp) -{ - *ebx = footpath_place_from_track( - (*edx >> 8) & 0xFF, *eax & 0xFFFF, *ecx & 0xFFFF, *edx & 0xFF, ((*ebx >> 13) & 0x3) | ((*ebx >> 10) & 0x4), - (*ebx >> 8) & 0xF, *ebx & 0xFF); -} - money32 footpath_remove(int32_t x, int32_t y, int32_t z, int32_t flags) { auto action = FootpathRemoveAction(x, y, z); diff --git a/src/openrct2/world/Footpath.h b/src/openrct2/world/Footpath.h index 8364a44f72..e3e6f1bb68 100644 --- a/src/openrct2/world/Footpath.h +++ b/src/openrct2/world/Footpath.h @@ -173,8 +173,6 @@ 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_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_remove(int32_t x, int32_t y, int32_t z, int32_t flags);