From 29f5018bd1982ee0166bd9e9520f24d768d6aea4 Mon Sep 17 00:00:00 2001 From: duncanspumpkin Date: Mon, 25 Mar 2019 19:20:31 +0000 Subject: [PATCH 1/5] Implement wall place game action --- src/openrct2-ui/windows/TopToolbar.cpp | 71 +- src/openrct2/Game.cpp | 7 +- src/openrct2/Game.h | 4 +- .../actions/GameActionRegistration.cpp | 2 + src/openrct2/actions/WallPlaceAction.hpp | 649 ++++++++++++++++++ src/openrct2/rct1/S4Importer.cpp | 10 +- src/openrct2/ride/TrackDesign.cpp | 18 +- src/openrct2/world/Map.h | 4 - src/openrct2/world/Wall.cpp | 553 --------------- 9 files changed, 716 insertions(+), 602 deletions(-) create mode 100644 src/openrct2/actions/WallPlaceAction.hpp diff --git a/src/openrct2-ui/windows/TopToolbar.cpp b/src/openrct2-ui/windows/TopToolbar.cpp index a1a02ab3b9..2d18b041df 100644 --- a/src/openrct2-ui/windows/TopToolbar.cpp +++ b/src/openrct2-ui/windows/TopToolbar.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -1805,24 +1806,19 @@ static void window_top_toolbar_scenery_tool_down(int16_t x, int16_t y, rct_windo for (; zAttemptRange != 0; zAttemptRange--) { - int32_t flags = (parameter_1 & 0xFF00) | GAME_COMMAND_FLAG_APPLY; + auto primaryColour = (parameter_2 >> 8) & 0xFF; + auto edges = parameter_2 & 0xFF; + auto type = (parameter_1 >> 8) & 0xFF; + auto wallPlaceAction = WallPlaceAction( + type, { gridX, gridY, gSceneryPlaceZ }, edges, primaryColour, _secondaryColour, _tertiaryColour); - gDisableErrorWindowSound = true; - gGameCommandErrorTitle = STR_CANT_BUILD_PARK_ENTRANCE_HERE; - int32_t cost = game_do_command( - gridX, flags, gridY, parameter_2, GAME_COMMAND_PLACE_WALL, gSceneryPlaceZ, - _secondaryColour | (_tertiaryColour << 8)); - gDisableErrorWindowSound = false; - - if (cost != MONEY32_UNDEFINED) + auto res = GameActions::Query(&wallPlaceAction); + if (res->Error == GA_ERROR::OK) { - window_close_by_class(WC_ERROR); - audio_play_sound_at_location(SOUND_PLACE_ITEM, gCommandPosition.x, gCommandPosition.y, gCommandPosition.z); - return; + break; } - if (gGameCommandErrorText == STR_NOT_ENOUGH_CASH_REQUIRES - || gGameCommandErrorText == STR_CAN_ONLY_BUILD_THIS_ON_WATER) + if (res->ErrorMessage == STR_NOT_ENOUGH_CASH_REQUIRES || res->ErrorMessage == STR_CAN_ONLY_BUILD_THIS_ON_WATER) { break; } @@ -1830,7 +1826,19 @@ static void window_top_toolbar_scenery_tool_down(int16_t x, int16_t y, rct_windo gSceneryPlaceZ += 8; } - audio_play_sound_at_location(SOUND_ERROR, gCommandPosition.x, gCommandPosition.y, gCommandPosition.z); + auto primaryColour = (parameter_2 >> 8) & 0xFF; + auto edges = parameter_2 & 0xFF; + auto type = (parameter_1 >> 8) & 0xFF; + auto wallPlaceAction = WallPlaceAction( + type, { gridX, gridY, gSceneryPlaceZ }, edges, primaryColour, _secondaryColour, _tertiaryColour); + + wallPlaceAction.SetCallback([](const GameAction* ga, const GameActionResult* result) { + if (result->Error == GA_ERROR::OK) + { + audio_play_sound_at_location(SOUND_PLACE_ITEM, result->Position.x, result->Position.y, result->Position.z); + } + }); + auto res = GameActions::Execute(&wallPlaceAction); break; } case SCENERY_TYPE_LARGE: @@ -2495,24 +2503,33 @@ static money32 try_place_ghost_scenery( break; } case 2: + { // Walls // 6e26b0 - cost = game_do_command( - map_tile.x, parameter_1 | 0x69, map_tile.y, parameter_2, GAME_COMMAND_PLACE_WALL, gSceneryPlaceZ, - _secondaryColour | (_tertiaryColour << 8)); + auto primaryColour = (parameter_2 >> 8) & 0xFF; + auto edges = parameter_2 & 0xFF; + auto type = (parameter_1 >> 8) & 0xFF; + auto wallPlaceAction = WallPlaceAction( + type, { map_tile.x, map_tile.y, gSceneryPlaceZ }, edges, primaryColour, _secondaryColour, _tertiaryColour); + wallPlaceAction.SetFlags(GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED); + wallPlaceAction.SetCallback([=](const GameAction* ga, const GameActionResult* result){ + if (result->Error != GA_ERROR::OK) + return; - if (cost == MONEY32_UNDEFINED) - return cost; + gSceneryGhostPosition.x = map_tile.x; + gSceneryGhostPosition.y = map_tile.y; + gSceneryGhostWallRotation = edges; + gSceneryGhostPosition.z = gSceneryTileElement->base_height; - gSceneryGhostPosition.x = map_tile.x; - gSceneryGhostPosition.y = map_tile.y; - gSceneryGhostWallRotation = (parameter_2 & 0xFF); + gSceneryGhostType |= SCENERY_GHOST_FLAG_2; + }); - tileElement = gSceneryTileElement; - gSceneryGhostPosition.z = tileElement->base_height; - - gSceneryGhostType |= SCENERY_GHOST_FLAG_2; + auto res = GameActions::Execute(&wallPlaceAction); + if (res->Error != GA_ERROR::OK) + return MONEY32_UNDEFINED; + cost = res->Cost; break; + } case 3: // Large Scenery // 6e25a7 diff --git a/src/openrct2/Game.cpp b/src/openrct2/Game.cpp index 996f430970..bc25685b6a 100644 --- a/src/openrct2/Game.cpp +++ b/src/openrct2/Game.cpp @@ -409,7 +409,7 @@ int32_t game_do_command_p( // Remove ghost scenery so it doesn't interfere with incoming network command if ((flags & GAME_COMMAND_FLAG_NETWORKED) && !(flags & GAME_COMMAND_FLAG_GHOST) - && (command == GAME_COMMAND_PLACE_WALL || command == GAME_COMMAND_PLACE_SCENERY + && (command == GAME_COMMAND_PLACE_SCENERY || command == GAME_COMMAND_PLACE_LARGE_SCENERY || command == GAME_COMMAND_PLACE_BANNER || command == GAME_COMMAND_PLACE_PATH)) { @@ -611,8 +611,7 @@ void game_log_multiplayer_command(int command, const int* eax, const int* ebx, c format_string(log_msg, 256, STR_LOG_DEMOLISH_RIDE, args); network_append_server_log(log_msg); } - else if ( - command == GAME_COMMAND_PLACE_WALL || command == GAME_COMMAND_PLACE_LARGE_SCENERY + else if (command == GAME_COMMAND_PLACE_LARGE_SCENERY || command == GAME_COMMAND_PLACE_BANNER) { uint8_t flags = *ebx & 0xFF; @@ -1284,7 +1283,7 @@ GAME_COMMAND_POINTER* new_game_command_table[GAME_COMMAND_COUNT] = { game_command_set_maze_track, game_command_set_park_entrance_fee, nullptr, - game_command_place_wall, + nullptr, nullptr, game_command_place_large_scenery, nullptr, diff --git a/src/openrct2/Game.h b/src/openrct2/Game.h index 4700b2ecd9..0034c6119a 100644 --- a/src/openrct2/Game.h +++ b/src/openrct2/Game.h @@ -60,8 +60,8 @@ enum GAME_COMMAND GAME_COMMAND_SET_MAZE_TRACK, // GA GAME_COMMAND_SET_PARK_ENTRANCE_FEE, // GA GAME_COMMAND_SET_STAFF_COLOUR, // GA - GAME_COMMAND_PLACE_WALL, - GAME_COMMAND_REMOVE_WALL, // GA + GAME_COMMAND_PLACE_WALL, // GA + GAME_COMMAND_REMOVE_WALL, // GA GAME_COMMAND_PLACE_LARGE_SCENERY, GAME_COMMAND_REMOVE_LARGE_SCENERY, // GA GAME_COMMAND_SET_CURRENT_LOAN, // GA diff --git a/src/openrct2/actions/GameActionRegistration.cpp b/src/openrct2/actions/GameActionRegistration.cpp index 54937b3c56..cda9214e33 100644 --- a/src/openrct2/actions/GameActionRegistration.cpp +++ b/src/openrct2/actions/GameActionRegistration.cpp @@ -62,6 +62,7 @@ #include "TrackPlaceAction.hpp" #include "TrackRemoveAction.hpp" #include "TrackSetBrakeSpeedAction.hpp" +#include "WallPlaceAction.hpp" #include "WallRemoveAction.hpp" #include "WaterLowerAction.hpp" #include "WaterRaiseAction.hpp" @@ -110,6 +111,7 @@ namespace GameActions Register(); Register(); Register(); + Register(); Register(); Register(); Register(); diff --git a/src/openrct2/actions/WallPlaceAction.hpp b/src/openrct2/actions/WallPlaceAction.hpp new file mode 100644 index 0000000000..5a42ddc354 --- /dev/null +++ b/src/openrct2/actions/WallPlaceAction.hpp @@ -0,0 +1,649 @@ +/***************************************************************************** + * 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 "../OpenRCT2.h" +#include "../management/Finance.h" +#include "../ride/RideGroupManager.h" +#include "../ride/Track.h" +#include "../ride/TrackData.h" +#include "../world/Banner.h" +#include "../world/LargeScenery.h" +#include "../world/MapAnimation.h" +#include "../world/Scenery.h" +#include "../world/SmallScenery.h" +#include "../world/Surface.h" +#include "GameAction.h" + +DEFINE_GAME_ACTION(WallPlaceAction, GAME_COMMAND_PLACE_WALL, GameActionResult) +{ +private: + int32_t _wallType; + CoordsXYZ _loc; + uint8_t _edge; + int32_t _primaryColour; + int32_t _secondaryColour; + int32_t _tertiaryColour; + +public: + WallPlaceAction() + { + } + + WallPlaceAction( + int32_t wallType, CoordsXYZ loc, uint8_t edge, int32_t primaryColour, int32_t secondaryColour, int32_t tertiaryColour) + : _wallType(wallType) + , _loc(loc) + , _edge(edge) + , _primaryColour(primaryColour) + , _secondaryColour(secondaryColour) + , _tertiaryColour(tertiaryColour) + { + } + + uint16_t GetActionFlags() const override + { + return GameAction::GetActionFlags(); + } + + void Serialise(DataSerialiser & stream) override + { + GameAction::Serialise(stream); + + stream << DS_TAG(_wallType) << DS_TAG(_loc) << DS_TAG(_edge) << DS_TAG(_primaryColour) << DS_TAG(_secondaryColour) + << DS_TAG(_tertiaryColour); + } + + GameActionResult::Ptr Query() const override + { + auto res = MakeResult(); + res->ErrorTitle = STR_CANT_BUILD_PARK_ENTRANCE_HERE; + res->Position = _loc; + + res->ExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING; + res->Position.x += 16; + res->Position.y += 16; + + if (res->Position.z == 0) + { + res->Position.z = tile_element_height(res->Position.x, res->Position.y) & 0xFFFF; + } + + if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !(GetFlags() & GAME_COMMAND_FLAG_PATH_SCENERY) + && !gCheatsSandboxMode) + { + if (_loc.z == 0) + { + if (!map_is_location_in_park({ _loc.x, _loc.y })) + { + return MakeResult(GA_ERROR::NOT_OWNED, STR_CANT_BUILD_PARK_ENTRANCE_HERE); + } + } + else if (!map_is_location_owned(_loc.x, _loc.y, _loc.z)) + { + return MakeResult(GA_ERROR::NOT_OWNED, STR_CANT_BUILD_PARK_ENTRANCE_HERE); + } + } + + uint8_t edgeSlope = 0; + auto targetHeight = _loc.z; + if (targetHeight == 0) + { + TileElement* surfaceElement = map_get_surface_element_at({ _loc.x, _loc.y }); + if (surfaceElement == nullptr) + { + log_error("Surface element not found at %d, %d.", _loc.x, _loc.y); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_PARK_ENTRANCE_HERE); + } + targetHeight = surfaceElement->base_height * 8; + + uint8_t slope = surfaceElement->AsSurface()->GetSlope(); + edgeSlope = EdgeSlopes[slope][_edge & 3]; + if (edgeSlope & EDGE_SLOPE_ELEVATED) + { + targetHeight += 16; + edgeSlope &= ~EDGE_SLOPE_ELEVATED; + } + } + + TileElement* surfaceElement = map_get_surface_element_at({ _loc.x, _loc.y }); + if (surfaceElement == nullptr) + { + log_error("Surface element not found at %d, %d.", _loc.x, _loc.y); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_PARK_ENTRANCE_HERE); + } + + if (surfaceElement->AsSurface()->GetWaterHeight() > 0) + { + uint16_t waterHeight = surfaceElement->AsSurface()->GetWaterHeight() * 16; + + if (targetHeight < waterHeight && !gCheatsDisableClearanceChecks) + { + return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_BUILD_PARK_ENTRANCE_HERE, STR_CANT_BUILD_THIS_UNDERWATER); + } + } + + if (targetHeight / 8 < surfaceElement->base_height && !gCheatsDisableClearanceChecks) + { + return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_BUILD_PARK_ENTRANCE_HERE, STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND); + } + + if (!(edgeSlope & (EDGE_SLOPE_UPWARDS | EDGE_SLOPE_DOWNWARDS))) + { + uint8_t newEdge = (_edge + 2) & 3; + uint8_t newBaseHeight = surfaceElement->base_height; + newBaseHeight += 2; + if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge)) + { + if (targetHeight / 8 < newBaseHeight) + { + return MakeResult( + GA_ERROR::DISALLOWED, STR_CANT_BUILD_PARK_ENTRANCE_HERE, STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND); + } + + if (surfaceElement->AsSurface()->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) + { + newEdge = (newEdge - 1) & 3; + + if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge)) + { + newEdge = (newEdge + 2) & 3; + if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge)) + { + newBaseHeight += 2; + if (targetHeight / 8 < newBaseHeight) + { + return MakeResult( + GA_ERROR::DISALLOWED, STR_CANT_BUILD_PARK_ENTRANCE_HERE, + STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND); + } + newBaseHeight -= 2; + } + } + } + } + + newEdge = (_edge + 3) & 3; + if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge)) + { + if (targetHeight / 8 < newBaseHeight) + { + return MakeResult( + GA_ERROR::DISALLOWED, STR_CANT_BUILD_PARK_ENTRANCE_HERE, STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND); + } + + if (surfaceElement->AsSurface()->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) + { + newEdge = (newEdge - 1) & 3; + + if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge)) + { + newEdge = (newEdge + 2) & 3; + if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge)) + { + newBaseHeight += 2; + if (targetHeight / 8 < newBaseHeight) + { + return MakeResult( + GA_ERROR::DISALLOWED, STR_CANT_BUILD_PARK_ENTRANCE_HERE, + STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND); + } + } + } + } + } + } + + rct_scenery_entry* wallEntry = get_wall_entry(_wallType); + + if (wallEntry == nullptr) + { + log_error("Wall Type not found %d", _wallType); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_PARK_ENTRANCE_HERE); + } + + if (wallEntry->wall.scrolling_mode != SCROLLING_MODE_NONE) + { + auto bannerIndex = create_new_banner(0); + + if (bannerIndex == 0xFF) + { + return MakeResult(GA_ERROR::NO_FREE_ELEMENTS, STR_CANT_BUILD_PARK_ENTRANCE_HERE, STR_TOO_MANY_BANNERS_IN_GAME); + } + } + + uint8_t clearanceHeight = targetHeight / 8; + if (edgeSlope & (EDGE_SLOPE_UPWARDS | EDGE_SLOPE_DOWNWARDS)) + { + if (wallEntry->wall.flags & WALL_SCENERY_CANT_BUILD_ON_SLOPE) + { + return MakeResult( + GA_ERROR::DISALLOWED, STR_CANT_BUILD_PARK_ENTRANCE_HERE, STR_ERR_UNABLE_TO_BUILD_THIS_ON_SLOPE); + } + clearanceHeight += 2; + } + clearanceHeight += wallEntry->wall.height; + + bool wallAcrossTrack = false; + if (!(GetFlags() & GAME_COMMAND_FLAG_PATH_SCENERY) && !gCheatsDisableClearanceChecks) + { + if (!WallCheckObstruction(wallEntry, targetHeight / 8, clearanceHeight, &wallAcrossTrack)) + { + return MakeResult( + GA_ERROR::NO_CLEARANCE, STR_CANT_BUILD_PARK_ENTRANCE_HERE, gGameCommandErrorText, gCommonFormatArgs); + } + } + + if (!map_check_free_elements_and_reorganise(1)) + { + return MakeResult(GA_ERROR::NO_FREE_ELEMENTS, STR_CANT_BUILD_PARK_ENTRANCE_HERE, gGameCommandErrorText); + } + + res->Cost = wallEntry->wall.price; + return res; + } + + GameActionResult::Ptr Execute() const override + { + auto res = MakeResult(); + res->ErrorTitle = STR_CANT_BUILD_PARK_ENTRANCE_HERE; + res->Position = _loc; + + res->ExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING; + res->Position.x += 16; + res->Position.y += 16; + + if (res->Position.z == 0) + { + res->Position.z = tile_element_height(res->Position.x, res->Position.y) & 0xFFFF; + } + + uint8_t edgeSlope = 0; + auto targetHeight = _loc.z; + if (targetHeight == 0) + { + TileElement* surfaceElement = map_get_surface_element_at({ _loc.x, _loc.y }); + if (surfaceElement == nullptr) + { + log_error("Surface element not found at %d, %d.", _loc.x, _loc.y); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_PARK_ENTRANCE_HERE); + } + targetHeight = surfaceElement->base_height * 8; + + uint8_t slope = surfaceElement->AsSurface()->GetSlope(); + edgeSlope = EdgeSlopes[slope][_edge & 3]; + if (edgeSlope & EDGE_SLOPE_ELEVATED) + { + targetHeight += 16; + edgeSlope &= ~EDGE_SLOPE_ELEVATED; + } + } + + BannerIndex bannerIndex = BANNER_INDEX_NULL; + rct_scenery_entry* wallEntry = get_wall_entry(_wallType); + + if (wallEntry == nullptr) + { + log_error("Wall Type not found %d", _wallType); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_PARK_ENTRANCE_HERE); + } + + if (wallEntry->wall.scrolling_mode != SCROLLING_MODE_NONE) + { + bannerIndex = create_new_banner(GAME_COMMAND_FLAG_APPLY); + + if (bannerIndex == 0xFF) + { + return MakeResult(GA_ERROR::NO_FREE_ELEMENTS, STR_CANT_BUILD_PARK_ENTRANCE_HERE, STR_TOO_MANY_BANNERS_IN_GAME); + } + + rct_banner* banner = &gBanners[bannerIndex]; + banner->flags |= BANNER_FLAG_IS_WALL; + banner->type = 0; + banner->x = _loc.x / 32; + banner->y = _loc.y / 32; + + ride_id_t rideIndex = banner_get_closest_ride_index(_loc.x, _loc.y, targetHeight); + if (rideIndex != RIDE_ID_NULL) + { + banner->ride_index = rideIndex; + banner->flags |= BANNER_FLAG_LINKED_TO_RIDE; + } + } + + uint8_t clearanceHeight = targetHeight / 8; + if (edgeSlope & (EDGE_SLOPE_UPWARDS | EDGE_SLOPE_DOWNWARDS)) + { + clearanceHeight += 2; + } + clearanceHeight += wallEntry->wall.height; + + bool wallAcrossTrack = false; + if (!(GetFlags() & GAME_COMMAND_FLAG_PATH_SCENERY) && !gCheatsDisableClearanceChecks) + { + if (!WallCheckObstruction(wallEntry, targetHeight / 8, clearanceHeight, &wallAcrossTrack)) + { + return MakeResult( + GA_ERROR::NO_CLEARANCE, STR_CANT_BUILD_PARK_ENTRANCE_HERE, gGameCommandErrorText, gCommonFormatArgs); + } + } + + if (!map_check_free_elements_and_reorganise(1)) + { + return MakeResult(GA_ERROR::NO_FREE_ELEMENTS, STR_CANT_BUILD_PARK_ENTRANCE_HERE, gGameCommandErrorText); + } + + TileElement* tileElement = tile_element_insert(_loc.x / 32, _loc.y / 32, targetHeight / 8, 0); + assert(tileElement != nullptr); + + map_animation_create(MAP_ANIMATION_TYPE_WALL, _loc.x, _loc.y, targetHeight / 8); + + tileElement->SetType(TILE_ELEMENT_TYPE_WALL); + WallElement* wallElement = tileElement->AsWall(); + wallElement->clearance_height = clearanceHeight; + wallElement->SetDirection(_edge); + // TODO: Normalise the edge slope code. + wallElement->SetSlope(edgeSlope >> 6); + + wallElement->SetPrimaryColour(_primaryColour); + wallElement->SetSecondaryColour(_secondaryColour); + + if (wallAcrossTrack) + { + wallElement->SetAcrossTrack(true); + } + + wallElement->SetEntryIndex(_wallType); + if (bannerIndex != 0xFF) + { + wallElement->SetBannerIndex(bannerIndex); + } + + if (wallEntry->wall.flags & WALL_SCENERY_HAS_TERNARY_COLOUR) + { + wallElement->SetTertiaryColour(_tertiaryColour); + } + + if (GetFlags() & GAME_COMMAND_FLAG_GHOST) + { + wallElement->SetGhost(true); + } + + gSceneryTileElement = tileElement; + map_invalidate_tile_zoom1(_loc.x, _loc.y, wallElement->base_height * 8, wallElement->base_height * 8 + 72); + + res->Cost = wallEntry->wall.price; + return res; + } + +private: +#pragma region Edge Slopes Table + + // clang-format off + enum EDGE_SLOPE + { + EDGE_SLOPE_ELEVATED = (1 << 0), // 0x01 + EDGE_SLOPE_UPWARDS = (1 << 6), // 0x40 + EDGE_SLOPE_DOWNWARDS = (1 << 7), // 0x80 + + EDGE_SLOPE_UPWARDS_ELEVATED = EDGE_SLOPE_UPWARDS | EDGE_SLOPE_ELEVATED, + EDGE_SLOPE_DOWNWARDS_ELEVATED = EDGE_SLOPE_DOWNWARDS | EDGE_SLOPE_ELEVATED, + }; + + /** rct2: 0x009A3FEC */ + static constexpr const uint8_t EdgeSlopes[][4] = { + // Top right Bottom right Bottom left Top left + { 0, 0, 0, 0 }, + { 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, 0 }, + { 0, 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS }, + { 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS }, + { EDGE_SLOPE_DOWNWARDS, 0, 0, EDGE_SLOPE_UPWARDS }, + { EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS }, + { EDGE_SLOPE_DOWNWARDS, 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED }, + { EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED }, + { EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, 0, 0 }, + { EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, 0 }, + { EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS }, + { EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS }, + { EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, 0, EDGE_SLOPE_UPWARDS }, + { EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS }, + { EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED }, + { EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_UPWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS_ELEVATED }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { EDGE_SLOPE_UPWARDS, EDGE_SLOPE_UPWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS }, + { 0, 0, 0, 0 }, + { EDGE_SLOPE_UPWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS }, + { EDGE_SLOPE_DOWNWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_UPWARDS_ELEVATED }, + { 0, 0, 0, 0 }, + }; + // clang-format on + +#pragma endregion + + /** + * + * rct2: 0x006E5CBA + */ + bool WallCheckObstructionWithTrack(rct_scenery_entry * wall, int32_t z0, TrackElement * trackElement, bool* wallAcrossTrack) + const + { + int32_t trackType = trackElement->GetTrackType(); + int32_t sequence = trackElement->GetSequenceIndex(); + int32_t direction = (_edge - trackElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK; + Ride* ride = get_ride(trackElement->GetRideIndex()); + + if (TrackIsAllowedWallEdges(ride->type, trackType, sequence, direction)) + { + return true; + } + + if (!(wall->wall.flags & WALL_SCENERY_IS_DOOR)) + { + return false; + } + + if (RideGroupManager::RideTypeHasRideGroups(ride->type)) + { + auto rideGroup = RideGroupManager::GetRideGroup(ride->type, get_ride_entry(ride->subtype)); + if (!(rideGroup->Flags & RIDE_GROUP_FLAG_ALLOW_DOORS_ON_TRACK)) + { + return false; + } + } + else if (!(RideData4[ride->type].flags & RIDE_TYPE_FLAG4_ALLOW_DOORS_ON_TRACK)) + { + return false; + } + + *wallAcrossTrack = true; + if (z0 & 1) + { + return false; + } + + int32_t z; + if (sequence == 0) + { + if (TrackSequenceProperties[trackType][0] & TRACK_SEQUENCE_FLAG_DISALLOW_DOORS) + { + return false; + } + + if (TrackDefinitions[trackType].bank_start == 0) + { + if (!(TrackCoordinates[trackType].rotation_begin & 4)) + { + direction = trackElement->GetDirectionWithOffset(2); + if (direction == _edge) + { + const rct_preview_track* trackBlock = &TrackBlocks[trackType][sequence]; + z = TrackCoordinates[trackType].z_begin; + z = trackElement->base_height + ((z - trackBlock->z) * 8); + if (z == z0) + { + return true; + } + } + } + } + } + + const rct_preview_track* trackBlock = &TrackBlocks[trackType][sequence + 1]; + if (trackBlock->index != 0xFF) + { + return false; + } + + if (TrackDefinitions[trackType].bank_end != 0) + { + return false; + } + + direction = TrackCoordinates[trackType].rotation_end; + if (direction & 4) + { + return false; + } + + direction = trackElement->GetDirection(); + if (direction != _edge) + { + return false; + } + + trackBlock = &TrackBlocks[trackType][sequence]; + z = TrackCoordinates[trackType].z_end; + z = trackElement->base_height + ((z - trackBlock->z) * 8); + return z == z0; + } + + /** + * + * rct2: 0x006E5C1A + */ + bool WallCheckObstruction(rct_scenery_entry * wall, int32_t z0, int32_t z1, bool* wallAcrossTrack) const + { + int32_t entryType, sequence; + rct_scenery_entry* entry; + rct_large_scenery_tile* tile; + + *wallAcrossTrack = false; + gMapGroundFlags = ELEMENT_IS_ABOVE_GROUND; + if (map_is_location_at_edge(_loc.x, _loc.y)) + { + gGameCommandErrorText = STR_OFF_EDGE_OF_MAP; + return false; + } + + TileElement* tileElement = map_get_first_element_at(_loc.x / 32, _loc.y / 32); + do + { + int32_t elementType = tileElement->GetType(); + if (elementType == TILE_ELEMENT_TYPE_SURFACE) + continue; + if (z0 >= tileElement->clearance_height) + continue; + if (z1 <= tileElement->base_height) + continue; + if (elementType == TILE_ELEMENT_TYPE_WALL) + { + int32_t direction = tileElement->GetDirection(); + if (_edge == direction) + { + map_obstruction_set_error_text(tileElement); + return false; + } + continue; + } + if ((tileElement->flags & 0x0F) == 0) + continue; + + switch (elementType) + { + case TILE_ELEMENT_TYPE_ENTRANCE: + map_obstruction_set_error_text(tileElement); + return false; + case TILE_ELEMENT_TYPE_PATH: + if (tileElement->AsPath()->GetEdges() & (1 << _edge)) + { + map_obstruction_set_error_text(tileElement); + return false; + } + break; + case TILE_ELEMENT_TYPE_LARGE_SCENERY: + entryType = tileElement->AsLargeScenery()->GetEntryIndex(); + sequence = tileElement->AsLargeScenery()->GetSequenceIndex(); + entry = get_large_scenery_entry(entryType); + tile = &entry->large_scenery.tiles[sequence]; + { + int32_t direction = ((_edge - tileElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK) + 8; + if (!(tile->flags & (1 << direction))) + { + map_obstruction_set_error_text(tileElement); + return false; + } + } + break; + case TILE_ELEMENT_TYPE_SMALL_SCENERY: + entry = tileElement->AsSmallScenery()->GetEntry(); + if (scenery_small_entry_has_flag(entry, SMALL_SCENERY_FLAG_NO_WALLS)) + { + map_obstruction_set_error_text(tileElement); + return false; + } + break; + case TILE_ELEMENT_TYPE_TRACK: + if (!WallCheckObstructionWithTrack(wall, z0, tileElement->AsTrack(), wallAcrossTrack)) + { + return false; + } + break; + } + } while (!(tileElement++)->IsLastForTile()); + + return true; + } + + /** + * Gets whether the given track type can have a wall placed on the edge of the given direction. + * Some thin tracks for example are allowed to have walls either side of the track, but wider tracks can not. + */ + static bool TrackIsAllowedWallEdges(uint8_t rideType, uint8_t trackType, uint8_t trackSequence, uint8_t direction) + { + if (!ride_type_has_flag(rideType, RIDE_TYPE_FLAG_TRACK_NO_WALLS)) + { + if (ride_type_has_flag(rideType, RIDE_TYPE_FLAG_FLAT_RIDE)) + { + if (FlatRideTrackSequenceElementAllowedWallEdges[trackType][trackSequence] & (1 << direction)) + { + return true; + } + } + else + { + if (TrackSequenceElementAllowedWallEdges[trackType][trackSequence] & (1 << direction)) + { + return true; + } + } + } + return false; + } +}; diff --git a/src/openrct2/rct1/S4Importer.cpp b/src/openrct2/rct1/S4Importer.cpp index 84cf8d0073..af6cf9d4c8 100644 --- a/src/openrct2/rct1/S4Importer.cpp +++ b/src/openrct2/rct1/S4Importer.cpp @@ -13,6 +13,7 @@ #include "../Game.h" #include "../GameState.h" #include "../ParkImporter.h" +#include "../actions/WallPlaceAction.hpp" #include "../audio/audio.h" #include "../core/Collections.hpp" #include "../core/Console.hpp" @@ -2773,10 +2774,13 @@ private: ConvertWall(&type, &colourA, &colourB); type = _wallTypeToEntryMap[type]; - const uint8_t flags = GAME_COMMAND_FLAG_APPLY | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED - | GAME_COMMAND_FLAG_5 | GAME_COMMAND_FLAG_PATH_SCENERY; - wall_place(type, x * 32, y * 32, 0, edge, colourA, colourB, colourC, flags); + auto wallPlaceAction = WallPlaceAction( + type, { x * 32, y * 32, 0 }, edge, colourA, colourB, colourC); + wallPlaceAction.SetFlags( + GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_5 + | GAME_COMMAND_FLAG_PATH_SCENERY); + GameActions::Execute(&wallPlaceAction); } } break; diff --git a/src/openrct2/ride/TrackDesign.cpp b/src/openrct2/ride/TrackDesign.cpp index ba3d69aa1f..59229bc8be 100644 --- a/src/openrct2/ride/TrackDesign.cpp +++ b/src/openrct2/ride/TrackDesign.cpp @@ -21,6 +21,7 @@ #include "../actions/SmallSceneryRemoveAction.hpp" #include "../actions/TrackPlaceAction.hpp" #include "../actions/TrackRemoveAction.hpp" +#include "../actions/WallPlaceAction.hpp" #include "../actions/WallRemoveAction.hpp" #include "../audio/audio.h" #include "../core/File.h" @@ -1021,6 +1022,7 @@ static int32_t track_design_place_scenery( } break; case OBJECT_TYPE_WALLS: + { if (mode != 0) { continue; @@ -1050,17 +1052,15 @@ static int32_t track_design_place_scenery( flags = 0; } - gGameCommandErrorTitle = STR_CANT_BUILD_PARK_ENTRANCE_HERE; + auto wallPlaceAction = WallPlaceAction( + entry_index, { mapCoord.x, mapCoord.y, z }, rotation, scenery->primary_colour, + scenery->secondary_colour, scenery->flags & 0xFC); + auto res = flags & GAME_COMMAND_FLAG_APPLY ? GameActions::Execute(&wallPlaceAction) + : GameActions::Query(&wallPlaceAction); - cost = game_do_command( - mapCoord.x, flags | (entry_index << 8), mapCoord.y, rotation | (scenery->primary_colour << 8), - GAME_COMMAND_PLACE_WALL, z, scenery->secondary_colour | ((scenery->flags & 0xFC) << 6)); - - if (cost == MONEY32_UNDEFINED) - { - cost = 0; - } + cost = res->Cost; break; + } case OBJECT_TYPE_PATHS: if (_trackDesignPlaceOperation == PTD_OPERATION_GET_PLACE_Z) { diff --git a/src/openrct2/world/Map.h b/src/openrct2/world/Map.h index 64660ef626..3328e74d56 100644 --- a/src/openrct2/world/Map.h +++ b/src/openrct2/world/Map.h @@ -186,9 +186,6 @@ int32_t map_can_construct_at(int32_t x, int32_t y, int32_t zLow, int32_t zHigh, void rotate_map_coordinates(int16_t* x, int16_t* y, int32_t rotation); LocationXY16 coordinate_3d_to_2d(const LocationXYZ16* coordinate_3d, int32_t rotation); money32 map_clear_scenery(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t clear, int32_t flags); -money32 wall_place( - int32_t type, int32_t x, int32_t y, int32_t z, int32_t edge, int32_t primaryColour, int32_t secondaryColour, - int32_t tertiaryColour, int32_t flags); void game_command_set_land_ownership( int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp); @@ -204,7 +201,6 @@ void game_command_set_banner_colour( 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_banner( 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_wall(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_large_scenery( 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_park_entrance( diff --git a/src/openrct2/world/Wall.cpp b/src/openrct2/world/Wall.cpp index 230e1ddebd..137c26ca9e 100644 --- a/src/openrct2/world/Wall.cpp +++ b/src/openrct2/world/Wall.cpp @@ -29,532 +29,6 @@ #include "Surface.h" #include "Wall.h" -/** - * Gets whether the given track type can have a wall placed on the edge of the given direction. - * Some thin tracks for example are allowed to have walls either side of the track, but wider tracks can not. - */ -static bool TrackIsAllowedWallEdges(uint8_t rideType, uint8_t trackType, uint8_t trackSequence, uint8_t direction) -{ - if (!ride_type_has_flag(rideType, RIDE_TYPE_FLAG_TRACK_NO_WALLS)) - { - if (ride_type_has_flag(rideType, RIDE_TYPE_FLAG_FLAT_RIDE)) - { - if (FlatRideTrackSequenceElementAllowedWallEdges[trackType][trackSequence] & (1 << direction)) - { - return true; - } - } - else - { - if (TrackSequenceElementAllowedWallEdges[trackType][trackSequence] & (1 << direction)) - { - return true; - } - } - } - return false; -} - -/** - * - * rct2: 0x006E5CBA - */ -static bool WallCheckObstructionWithTrack( - rct_scenery_entry* wall, int32_t z0, int32_t edge, TileElement* trackElement, bool* wallAcrossTrack) -{ - int32_t trackType = trackElement->AsTrack()->GetTrackType(); - int32_t sequence = trackElement->AsTrack()->GetSequenceIndex(); - int32_t direction = (edge - trackElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK; - Ride* ride = get_ride(trackElement->AsTrack()->GetRideIndex()); - - if (TrackIsAllowedWallEdges(ride->type, trackType, sequence, direction)) - { - return true; - } - - if (!(wall->wall.flags & WALL_SCENERY_IS_DOOR)) - { - return false; - } - - if (RideGroupManager::RideTypeHasRideGroups(ride->type)) - { - auto rideGroup = RideGroupManager::GetRideGroup(ride->type, get_ride_entry(ride->subtype)); - if (!(rideGroup->Flags & RIDE_GROUP_FLAG_ALLOW_DOORS_ON_TRACK)) - { - return false; - } - } - else if (!(RideData4[ride->type].flags & RIDE_TYPE_FLAG4_ALLOW_DOORS_ON_TRACK)) - { - return false; - } - - *wallAcrossTrack = true; - if (z0 & 1) - { - return false; - } - - int32_t z; - if (sequence == 0) - { - if (TrackSequenceProperties[trackType][0] & TRACK_SEQUENCE_FLAG_DISALLOW_DOORS) - { - return false; - } - - if (TrackDefinitions[trackType].bank_start == 0) - { - if (!(TrackCoordinates[trackType].rotation_begin & 4)) - { - direction = trackElement->GetDirectionWithOffset(2); - if (direction == edge) - { - const rct_preview_track* trackBlock = &TrackBlocks[trackType][sequence]; - z = TrackCoordinates[trackType].z_begin; - z = trackElement->base_height + ((z - trackBlock->z) * 8); - if (z == z0) - { - return true; - } - } - } - } - } - - const rct_preview_track* trackBlock = &TrackBlocks[trackType][sequence + 1]; - if (trackBlock->index != 0xFF) - { - return false; - } - - if (TrackDefinitions[trackType].bank_end != 0) - { - return false; - } - - direction = TrackCoordinates[trackType].rotation_end; - if (direction & 4) - { - return false; - } - - direction = trackElement->GetDirection(); - if (direction != edge) - { - return false; - } - - trackBlock = &TrackBlocks[trackType][sequence]; - z = TrackCoordinates[trackType].z_end; - z = trackElement->base_height + ((z - trackBlock->z) * 8); - return z == z0; -} - -/** - * - * rct2: 0x006E5C1A - */ -static bool WallCheckObstruction( - rct_scenery_entry* wall, int32_t x, int32_t y, int32_t z0, int32_t z1, int32_t edge, bool* wallAcrossTrack) -{ - int32_t entryType, sequence; - rct_scenery_entry* entry; - rct_large_scenery_tile* tile; - - *wallAcrossTrack = false; - gMapGroundFlags = ELEMENT_IS_ABOVE_GROUND; - if (map_is_location_at_edge(x, y)) - { - gGameCommandErrorText = STR_OFF_EDGE_OF_MAP; - return false; - } - - TileElement* tileElement = map_get_first_element_at(x / 32, y / 32); - do - { - int32_t elementType = tileElement->GetType(); - if (elementType == TILE_ELEMENT_TYPE_SURFACE) - continue; - if (z0 >= tileElement->clearance_height) - continue; - if (z1 <= tileElement->base_height) - continue; - if (elementType == TILE_ELEMENT_TYPE_WALL) - { - int32_t direction = tileElement->GetDirection(); - if (edge == direction) - { - map_obstruction_set_error_text(tileElement); - return false; - } - continue; - } - if ((tileElement->flags & 0x0F) == 0) - continue; - - switch (elementType) - { - case TILE_ELEMENT_TYPE_ENTRANCE: - map_obstruction_set_error_text(tileElement); - return false; - case TILE_ELEMENT_TYPE_PATH: - if (tileElement->AsPath()->GetEdges() & (1 << edge)) - { - map_obstruction_set_error_text(tileElement); - return false; - } - break; - case TILE_ELEMENT_TYPE_LARGE_SCENERY: - entryType = tileElement->AsLargeScenery()->GetEntryIndex(); - sequence = tileElement->AsLargeScenery()->GetSequenceIndex(); - entry = get_large_scenery_entry(entryType); - tile = &entry->large_scenery.tiles[sequence]; - { - int32_t direction = ((edge - tileElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK) + 8; - if (!(tile->flags & (1 << direction))) - { - map_obstruction_set_error_text(tileElement); - return false; - } - } - break; - case TILE_ELEMENT_TYPE_SMALL_SCENERY: - entry = tileElement->AsSmallScenery()->GetEntry(); - if (scenery_small_entry_has_flag(entry, SMALL_SCENERY_FLAG_NO_WALLS)) - { - map_obstruction_set_error_text(tileElement); - return false; - } - break; - case TILE_ELEMENT_TYPE_TRACK: - if (!WallCheckObstructionWithTrack(wall, z0, edge, tileElement, wallAcrossTrack)) - { - return false; - } - break; - } - } while (!(tileElement++)->IsLastForTile()); - - return true; -} - -#pragma region Edge Slopes Table - -// clang-format off -enum EDGE_SLOPE -{ - EDGE_SLOPE_ELEVATED = (1 << 0), // 0x01 - EDGE_SLOPE_UPWARDS = (1 << 6), // 0x40 - EDGE_SLOPE_DOWNWARDS = (1 << 7), // 0x80 - - EDGE_SLOPE_UPWARDS_ELEVATED = EDGE_SLOPE_UPWARDS | EDGE_SLOPE_ELEVATED, - EDGE_SLOPE_DOWNWARDS_ELEVATED = EDGE_SLOPE_DOWNWARDS | EDGE_SLOPE_ELEVATED, -}; - -/** rct2: 0x009A3FEC */ -static constexpr const uint8_t EdgeSlopes[][4] = { -// Top right Bottom right Bottom left Top left - { 0, 0, 0, 0 }, - { 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, 0 }, - { 0, 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS }, - { 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS }, - { EDGE_SLOPE_DOWNWARDS, 0, 0, EDGE_SLOPE_UPWARDS }, - { EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS }, - { EDGE_SLOPE_DOWNWARDS, 0, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED }, - { EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED }, - { EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, 0, 0 }, - { EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, 0 }, - { EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_DOWNWARDS }, - { EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS }, - { EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, 0, EDGE_SLOPE_UPWARDS }, - { EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS }, - { EDGE_SLOPE_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_ELEVATED }, - { EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED, EDGE_SLOPE_ELEVATED }, - { 0, 0, 0, 0 }, - { 0, 0, 0, 0 }, - { 0, 0, 0, 0 }, - { 0, 0, 0, 0 }, - { 0, 0, 0, 0 }, - { 0, 0, 0, 0 }, - { 0, 0, 0, 0 }, - { EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_UPWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS_ELEVATED }, - { 0, 0, 0, 0 }, - { 0, 0, 0, 0 }, - { 0, 0, 0, 0 }, - { EDGE_SLOPE_UPWARDS, EDGE_SLOPE_UPWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS }, - { 0, 0, 0, 0 }, - { EDGE_SLOPE_UPWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS }, - { EDGE_SLOPE_DOWNWARDS_ELEVATED, EDGE_SLOPE_DOWNWARDS, EDGE_SLOPE_UPWARDS, EDGE_SLOPE_UPWARDS_ELEVATED }, - { 0, 0, 0, 0 }, -}; -// clang-format on - -#pragma endregion - -static money32 WallPlace( - uint8_t wallType, int16_t x, int16_t y, int16_t z, uint8_t edge, uint8_t primaryColour, uint8_t secondaryColour, - uint8_t tertiaryColour, uint8_t flags) -{ - LocationXYZ16 position = { x, y, z }; - - gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING; - gCommandPosition.x = position.x + 16; - gCommandPosition.y = position.y + 16; - gCommandPosition.z = position.z; - - if (position.z == 0) - { - gCommandPosition.z = tile_element_height(position.x, position.y) & 0xFFFF; - } - - if (game_is_paused() && !gCheatsBuildInPauseMode) - { - gGameCommandErrorText = STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED; - return MONEY32_UNDEFINED; - } - - if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !(flags & GAME_COMMAND_FLAG_PATH_SCENERY) && !gCheatsSandboxMode) - { - if (position.z == 0) - { - if (!map_is_location_in_park({ position.x, position.y })) - { - return MONEY32_UNDEFINED; - } - } - else if (!map_is_location_owned(position.x, position.y, position.z)) - { - return MONEY32_UNDEFINED; - } - } - - uint8_t edgeSlope = 0; - if (position.z == 0) - { - TileElement* surfaceElement = map_get_surface_element_at({ position.x, position.y }); - if (surfaceElement == nullptr) - { - return MONEY32_UNDEFINED; - } - position.z = surfaceElement->base_height * 8; - - uint8_t slope = surfaceElement->AsSurface()->GetSlope(); - edgeSlope = EdgeSlopes[slope][edge & 3]; - if (edgeSlope & EDGE_SLOPE_ELEVATED) - { - position.z += 16; - edgeSlope &= ~EDGE_SLOPE_ELEVATED; - } - } - - TileElement* surfaceElement = map_get_surface_element_at({ position.x, position.y }); - if (surfaceElement == nullptr) - { - return MONEY32_UNDEFINED; - } - - if (surfaceElement->AsSurface()->GetWaterHeight() > 0) - { - uint16_t waterHeight = surfaceElement->AsSurface()->GetWaterHeight() * 16; - - if (position.z < waterHeight && !gCheatsDisableClearanceChecks) - { - gGameCommandErrorText = STR_CANT_BUILD_THIS_UNDERWATER; - return MONEY32_UNDEFINED; - } - } - - if (position.z / 8 < surfaceElement->base_height && !gCheatsDisableClearanceChecks) - { - gGameCommandErrorText = STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND; - return MONEY32_UNDEFINED; - } - - if (!(edgeSlope & (EDGE_SLOPE_UPWARDS | EDGE_SLOPE_DOWNWARDS))) - { - uint8_t newEdge = (edge + 2) & 3; - uint8_t newBaseHeight = surfaceElement->base_height; - newBaseHeight += 2; - if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge)) - { - if (position.z / 8 < newBaseHeight) - { - gGameCommandErrorText = STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND; - return MONEY32_UNDEFINED; - } - - if (surfaceElement->AsSurface()->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) - { - newEdge = (newEdge - 1) & 3; - - if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge)) - { - newEdge = (newEdge + 2) & 3; - if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge)) - { - newBaseHeight += 2; - if (position.z / 8 < newBaseHeight) - { - gGameCommandErrorText = STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND; - return MONEY32_UNDEFINED; - } - newBaseHeight -= 2; - } - } - } - } - - newEdge = (edge + 3) & 3; - if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge)) - { - if (position.z / 8 < newBaseHeight) - { - gGameCommandErrorText = STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND; - return MONEY32_UNDEFINED; - } - - if (surfaceElement->AsSurface()->GetSlope() & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) - { - newEdge = (newEdge - 1) & 3; - - if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge)) - { - newEdge = (newEdge + 2) & 3; - if (surfaceElement->AsSurface()->GetSlope() & (1 << newEdge)) - { - newBaseHeight += 2; - if (position.z / 8 < newBaseHeight) - { - gGameCommandErrorText = STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND; - return MONEY32_UNDEFINED; - } - } - } - } - } - } - BannerIndex bannerIndex = BANNER_INDEX_NULL; - rct_scenery_entry* wallEntry = get_wall_entry(wallType); - - if (wallEntry == nullptr) - { - return MONEY32_UNDEFINED; - } - - if (wallEntry->wall.scrolling_mode != SCROLLING_MODE_NONE) - { - bannerIndex = create_new_banner(flags); - - if (bannerIndex == 0xFF) - { - return MONEY32_UNDEFINED; - } - - rct_banner* banner = &gBanners[bannerIndex]; - if (flags & GAME_COMMAND_FLAG_APPLY) - { - banner->flags |= BANNER_FLAG_IS_WALL; - banner->type = 0; - banner->x = position.x / 32; - banner->y = position.y / 32; - - ride_id_t rideIndex = banner_get_closest_ride_index(position.x, position.y, position.z); - if (rideIndex != RIDE_ID_NULL) - { - banner->ride_index = rideIndex; - banner->flags |= BANNER_FLAG_LINKED_TO_RIDE; - } - } - } - - uint8_t clearanceHeight = position.z / 8; - if (edgeSlope & (EDGE_SLOPE_UPWARDS | EDGE_SLOPE_DOWNWARDS)) - { - if (wallEntry->wall.flags & WALL_SCENERY_CANT_BUILD_ON_SLOPE) - { - gGameCommandErrorText = STR_ERR_UNABLE_TO_BUILD_THIS_ON_SLOPE; - return MONEY32_UNDEFINED; - } - clearanceHeight += 2; - } - clearanceHeight += wallEntry->wall.height; - - bool wallAcrossTrack = false; - if (!(flags & GAME_COMMAND_FLAG_PATH_SCENERY) && !gCheatsDisableClearanceChecks) - { - if (!WallCheckObstruction(wallEntry, position.x, position.y, position.z / 8, clearanceHeight, edge, &wallAcrossTrack)) - { - return MONEY32_UNDEFINED; - } - } - - if (!map_check_free_elements_and_reorganise(1)) - { - return MONEY32_UNDEFINED; - } - - if (flags & GAME_COMMAND_FLAG_APPLY) - { - if (gGameCommandNestLevel == 1 && !(flags & GAME_COMMAND_FLAG_GHOST)) - { - LocationXYZ16 coord; - coord.x = position.x + 16; - coord.y = position.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); - } - - TileElement* tileElement = tile_element_insert(position.x / 32, position.y / 32, position.z / 8, 0); - assert(tileElement != nullptr); - - map_animation_create(MAP_ANIMATION_TYPE_WALL, position.x, position.y, position.z / 8); - - tileElement->clearance_height = clearanceHeight; - tileElement->SetType(TILE_ELEMENT_TYPE_WALL); - tileElement->SetDirection(edge); - // TODO: Normalise the edge slope code. - tileElement->AsWall()->SetSlope(edgeSlope >> 6); - - tileElement->AsWall()->SetPrimaryColour(primaryColour); - tileElement->AsWall()->SetSecondaryColour(secondaryColour); - - if (wallAcrossTrack) - { - tileElement->AsWall()->SetAcrossTrack(true); - } - - tileElement->AsWall()->SetEntryIndex(wallType); - if (bannerIndex != 0xFF) - { - tileElement->AsWall()->SetBannerIndex(bannerIndex); - } - - if (wallEntry->wall.flags & WALL_SCENERY_HAS_TERNARY_COLOUR) - { - tileElement->AsWall()->SetTertiaryColour(tertiaryColour); - } - - if (flags & GAME_COMMAND_FLAG_GHOST) - { - tileElement->SetGhost(true); - } - - gSceneryTileElement = tileElement; - map_invalidate_tile_zoom1(position.x, position.y, tileElement->base_height * 8, tileElement->base_height * 8 + 72); - } - - if (gParkFlags & PARK_FLAGS_NO_MONEY) - { - return 0; - } - else - { - return wallEntry->wall.price; - } -} - static money32 WallSetColour( int16_t x, int16_t y, uint8_t baseHeight, uint8_t direction, uint8_t primaryColour, uint8_t secondaryColour, uint8_t tertiaryColour, uint8_t flags) @@ -662,33 +136,6 @@ void wall_remove_intersecting_walls(int32_t x, int32_t y, int32_t z0, int32_t z1 } while (!(tileElement++)->IsLastForTile()); } -/** - * - * rct2: 0x006E519A - */ -void game_command_place_wall( - int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, [[maybe_unused]] int32_t* esi, int32_t* edi, int32_t* ebp) -{ - *ebx = WallPlace( - (*ebx >> 8) & 0xFF, *eax & 0xFFFF, *ecx & 0xFFFF, *edi & 0xFFFF, *edx & 0xFF, (*edx >> 8) & 0xFF, *ebp & 0xFF, - (*ebp >> 8) & 0xFF, *ebx & 0xFF); -} - -money32 wall_place( - int32_t type, int32_t x, int32_t y, int32_t z, int32_t edge, int32_t primaryColour, int32_t secondaryColour, - int32_t tertiaryColour, int32_t flags) -{ - int32_t eax = x; - int32_t ebx = flags | (type << 8); - int32_t ecx = y; - int32_t edx = edge | (primaryColour << 8); - int32_t esi = 0; - int32_t edi = z; - int32_t ebp = secondaryColour | (tertiaryColour << 8); - game_command_place_wall(&eax, &ebx, &ecx, &edx, &esi, &edi, &ebp); - return ebx; -} - /** * * rct2: 0x006E56B5 From 7e6254f30c4e387b207e761fc5b2a0971445025a Mon Sep 17 00:00:00 2001 From: duncanspumpkin Date: Thu, 28 Mar 2019 17:45:39 +0000 Subject: [PATCH 2/5] Fix function. --- src/openrct2-ui/windows/TopToolbar.cpp | 10 +++++++--- src/openrct2/Game.cpp | 8 +++----- src/openrct2/actions/WallPlaceAction.hpp | 2 ++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/openrct2-ui/windows/TopToolbar.cpp b/src/openrct2-ui/windows/TopToolbar.cpp index 2d18b041df..b1c8fddf82 100644 --- a/src/openrct2-ui/windows/TopToolbar.cpp +++ b/src/openrct2-ui/windows/TopToolbar.cpp @@ -1823,7 +1823,10 @@ static void window_top_toolbar_scenery_tool_down(int16_t x, int16_t y, rct_windo break; } - gSceneryPlaceZ += 8; + if (zAttemptRange != 1) + { + gSceneryPlaceZ += 8; + } } auto primaryColour = (parameter_2 >> 8) & 0xFF; @@ -2511,8 +2514,9 @@ static money32 try_place_ghost_scenery( auto type = (parameter_1 >> 8) & 0xFF; auto wallPlaceAction = WallPlaceAction( type, { map_tile.x, map_tile.y, gSceneryPlaceZ }, edges, primaryColour, _secondaryColour, _tertiaryColour); - wallPlaceAction.SetFlags(GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED); - wallPlaceAction.SetCallback([=](const GameAction* ga, const GameActionResult* result){ + wallPlaceAction.SetFlags( + GAME_COMMAND_FLAG_GHOST | GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED | GAME_COMMAND_FLAG_PATH_SCENERY); + wallPlaceAction.SetCallback([=](const GameAction* ga, const GameActionResult* result) { if (result->Error != GA_ERROR::OK) return; diff --git a/src/openrct2/Game.cpp b/src/openrct2/Game.cpp index bc25685b6a..4aab2a6511 100644 --- a/src/openrct2/Game.cpp +++ b/src/openrct2/Game.cpp @@ -409,9 +409,8 @@ int32_t game_do_command_p( // Remove ghost scenery so it doesn't interfere with incoming network command if ((flags & GAME_COMMAND_FLAG_NETWORKED) && !(flags & GAME_COMMAND_FLAG_GHOST) - && (command == GAME_COMMAND_PLACE_SCENERY - || command == GAME_COMMAND_PLACE_LARGE_SCENERY || command == GAME_COMMAND_PLACE_BANNER - || command == GAME_COMMAND_PLACE_PATH)) + && (command == GAME_COMMAND_PLACE_SCENERY || command == GAME_COMMAND_PLACE_LARGE_SCENERY + || command == GAME_COMMAND_PLACE_BANNER || command == GAME_COMMAND_PLACE_PATH)) { scenery_remove_ghost_tool_placement(); } @@ -611,8 +610,7 @@ void game_log_multiplayer_command(int command, const int* eax, const int* ebx, c format_string(log_msg, 256, STR_LOG_DEMOLISH_RIDE, args); network_append_server_log(log_msg); } - else if (command == GAME_COMMAND_PLACE_LARGE_SCENERY - || command == GAME_COMMAND_PLACE_BANNER) + else if (command == GAME_COMMAND_PLACE_LARGE_SCENERY || command == GAME_COMMAND_PLACE_BANNER) { uint8_t flags = *ebx & 0xFF; if (flags & GAME_COMMAND_FLAG_GHOST) diff --git a/src/openrct2/actions/WallPlaceAction.hpp b/src/openrct2/actions/WallPlaceAction.hpp index 5a42ddc354..78a0744804 100644 --- a/src/openrct2/actions/WallPlaceAction.hpp +++ b/src/openrct2/actions/WallPlaceAction.hpp @@ -558,6 +558,8 @@ private: int32_t elementType = tileElement->GetType(); if (elementType == TILE_ELEMENT_TYPE_SURFACE) continue; + if (tileElement->IsGhost()) + continue; if (z0 >= tileElement->clearance_height) continue; if (z1 <= tileElement->base_height) From 09875311b3d7849d1884ea5153d36aacd7f2a34d Mon Sep 17 00:00:00 2001 From: duncanspumpkin Date: Mon, 1 Apr 2019 17:39:48 +0100 Subject: [PATCH 3/5] Increment network version --- src/openrct2/network/Network.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openrct2/network/Network.cpp b/src/openrct2/network/Network.cpp index 84da62b965..21bda33c66 100644 --- a/src/openrct2/network/Network.cpp +++ b/src/openrct2/network/Network.cpp @@ -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 "14" +#define NETWORK_STREAM_VERSION "15" #define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION static Peep* _pickup_peep = nullptr; From 78527f7af42d6993e98cf4238d5aa47772133137 Mon Sep 17 00:00:00 2001 From: duncanspumpkin Date: Tue, 2 Apr 2019 18:52:52 +0100 Subject: [PATCH 4/5] Extra checks for nullptrs and bad values --- src/openrct2/actions/WallPlaceAction.hpp | 29 +++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/openrct2/actions/WallPlaceAction.hpp b/src/openrct2/actions/WallPlaceAction.hpp index 78a0744804..4697f9529a 100644 --- a/src/openrct2/actions/WallPlaceAction.hpp +++ b/src/openrct2/actions/WallPlaceAction.hpp @@ -25,9 +25,9 @@ DEFINE_GAME_ACTION(WallPlaceAction, GAME_COMMAND_PLACE_WALL, GameActionResult) { private: - int32_t _wallType; + int32_t _wallType{ -1 }; CoordsXYZ _loc; - uint8_t _edge; + uint8_t _edge{ std::numeric_limits::max() }; int32_t _primaryColour; int32_t _secondaryColour; int32_t _tertiaryColour; @@ -91,6 +91,16 @@ public: return MakeResult(GA_ERROR::NOT_OWNED, STR_CANT_BUILD_PARK_ENTRANCE_HERE); } } + else if ((_loc.x > gMapSizeMaxXY || _loc.y > gMapSizeMaxXY)) + { + log_error("Invalid x/y coordinates. x = %d y = %d", _loc.x, _loc.y); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_PARK_ENTRANCE_HERE); + } + + if (_edge > 3) + { + return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_CANT_BUILD_PARK_ENTRANCE_HERE); + } uint8_t edgeSlope = 0; auto targetHeight = _loc.z; @@ -448,6 +458,10 @@ private: int32_t sequence = trackElement->GetSequenceIndex(); int32_t direction = (_edge - trackElement->GetDirection()) & TILE_ELEMENT_DIRECTION_MASK; Ride* ride = get_ride(trackElement->GetRideIndex()); + if (ride == nullptr) + { + return false; + } if (TrackIsAllowedWallEdges(ride->type, trackType, sequence, direction)) { @@ -461,7 +475,16 @@ private: if (RideGroupManager::RideTypeHasRideGroups(ride->type)) { - auto rideGroup = RideGroupManager::GetRideGroup(ride->type, get_ride_entry(ride->subtype)); + auto rideEntry = get_ride_entry(ride->subtype); + if (rideEntry == nullptr) + { + return false; + } + auto rideGroup = RideGroupManager::GetRideGroup(ride->type, rideEntry); + if (rideGroup == nullptr) + { + return false; + } if (!(rideGroup->Flags & RIDE_GROUP_FLAG_ALLOW_DOORS_ON_TRACK)) { return false; From c8523b18b9e4213f25ce59e74252b209794a307d Mon Sep 17 00:00:00 2001 From: duncanspumpkin Date: Tue, 2 Apr 2019 19:08:06 +0100 Subject: [PATCH 5/5] One more null check --- src/openrct2/actions/WallPlaceAction.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/openrct2/actions/WallPlaceAction.hpp b/src/openrct2/actions/WallPlaceAction.hpp index 4697f9529a..62bb6835bf 100644 --- a/src/openrct2/actions/WallPlaceAction.hpp +++ b/src/openrct2/actions/WallPlaceAction.hpp @@ -578,6 +578,8 @@ private: TileElement* tileElement = map_get_first_element_at(_loc.x / 32, _loc.y / 32); do { + if (tileElement == nullptr) + break; int32_t elementType = tileElement->GetType(); if (elementType == TILE_ELEMENT_TYPE_SURFACE) continue;