diff --git a/src/openrct2-ui/windows/LandRights.cpp b/src/openrct2-ui/windows/LandRights.cpp index ff97ba56c3..85b090d743 100644 --- a/src/openrct2-ui/windows/LandRights.cpp +++ b/src/openrct2-ui/windows/LandRights.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -364,11 +365,13 @@ static void window_land_rights_tool_update_land_rights(int16_t x, int16_t y) if (!state_changed) return; - _landRightsCost = game_do_command( - gMapSelectPositionA.x, GAME_COMMAND_FLAG_NO_SPEND, gMapSelectPositionA.y, - (_landRightsMode == LAND_RIGHTS_MODE_BUY_LAND) ? BUY_LAND_RIGHTS_FLAG_BUY_LAND - : BUY_LAND_RIGHTS_FLAG_BUY_CONSTRUCTION_RIGHTS, - GAME_COMMAND_BUY_LAND_RIGHTS, gMapSelectPositionB.x, gMapSelectPositionB.y); + auto landBuyRightsAction = LandBuyRightsAction( + { gMapSelectPositionA.x, gMapSelectPositionA.y, gMapSelectPositionB.x, gMapSelectPositionB.y }, + (_landRightsMode == LAND_RIGHTS_MODE_BUY_LAND) ? LandBuyRightSetting::BuyLand + : LandBuyRightSetting::BuyConstructionRights); + auto res = GameActions::Query(&landBuyRightsAction); + + _landRightsCost = res->Error == GA_ERROR::OK ? res->Cost : MONEY32_UNDEFINED; } /** @@ -407,21 +410,20 @@ static void window_land_rights_tooldown(rct_window* w, rct_widgetindex widgetInd { if (x != LOCATION_NULL) { - gGameCommandErrorTitle = STR_CANT_BUY_LAND; - game_do_command( - gMapSelectPositionA.x, GAME_COMMAND_FLAG_APPLY, gMapSelectPositionA.y, BUY_LAND_RIGHTS_FLAG_BUY_LAND, - GAME_COMMAND_BUY_LAND_RIGHTS, gMapSelectPositionB.x, gMapSelectPositionB.y); + auto landBuyRightsAction = LandBuyRightsAction( + { gMapSelectPositionA.x, gMapSelectPositionA.y, gMapSelectPositionB.x, gMapSelectPositionB.y }, + LandBuyRightSetting::BuyLand); + GameActions::Execute(&landBuyRightsAction); } } else { if (x != LOCATION_NULL) { - gGameCommandErrorTitle = STR_CANT_BUY_CONSTRUCTION_RIGHTS_HERE; - game_do_command( - gMapSelectPositionA.x, GAME_COMMAND_FLAG_APPLY, gMapSelectPositionA.y, - BUY_LAND_RIGHTS_FLAG_BUY_CONSTRUCTION_RIGHTS, GAME_COMMAND_BUY_LAND_RIGHTS, gMapSelectPositionB.x, - gMapSelectPositionB.y); + auto landBuyRightsAction = LandBuyRightsAction( + { gMapSelectPositionA.x, gMapSelectPositionA.y, gMapSelectPositionB.x, gMapSelectPositionB.y }, + LandBuyRightSetting::BuyConstructionRights); + GameActions::Execute(&landBuyRightsAction); } } } @@ -436,21 +438,20 @@ static void window_land_rights_tooldrag(rct_window* w, rct_widgetindex widgetInd { if (x != LOCATION_NULL) { - gGameCommandErrorTitle = STR_CANT_BUY_LAND; - game_do_command( - gMapSelectPositionA.x, GAME_COMMAND_FLAG_APPLY, gMapSelectPositionA.y, BUY_LAND_RIGHTS_FLAG_BUY_LAND, - GAME_COMMAND_BUY_LAND_RIGHTS, gMapSelectPositionB.x, gMapSelectPositionB.y); + auto landBuyRightsAction = LandBuyRightsAction( + { gMapSelectPositionA.x, gMapSelectPositionA.y, gMapSelectPositionB.x, gMapSelectPositionB.y }, + LandBuyRightSetting::BuyLand); + GameActions::Execute(&landBuyRightsAction); } } else { if (x != LOCATION_NULL) { - gGameCommandErrorTitle = STR_CANT_BUY_CONSTRUCTION_RIGHTS_HERE; - game_do_command( - gMapSelectPositionA.x, GAME_COMMAND_FLAG_APPLY, gMapSelectPositionA.y, - BUY_LAND_RIGHTS_FLAG_BUY_CONSTRUCTION_RIGHTS, GAME_COMMAND_BUY_LAND_RIGHTS, gMapSelectPositionB.x, - gMapSelectPositionB.y); + auto landBuyRightsAction = LandBuyRightsAction( + { gMapSelectPositionA.x, gMapSelectPositionA.y, gMapSelectPositionB.x, gMapSelectPositionB.y }, + LandBuyRightSetting::BuyConstructionRights); + GameActions::Execute(&landBuyRightsAction); } } } diff --git a/src/openrct2-ui/windows/Map.cpp b/src/openrct2-ui/windows/Map.cpp index 2e70b6a74f..1d50f6c495 100644 --- a/src/openrct2-ui/windows/Map.cpp +++ b/src/openrct2-ui/windows/Map.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -505,10 +506,10 @@ static void window_map_tooldrag(rct_window* w, rct_widgetindex widgetIndex, int3 case WIDX_SET_LAND_RIGHTS: if (gMapSelectFlags & MAP_SELECT_FLAG_ENABLE) { - gGameCommandErrorTitle = 0; - game_do_command( - gMapSelectPositionA.x, GAME_COMMAND_FLAG_APPLY, gMapSelectPositionA.y, _activeTool, - GAME_COMMAND_SET_LAND_OWNERSHIP, gMapSelectPositionB.x, gMapSelectPositionB.y); + auto landSetRightsAction = LandSetRightsAction( + { gMapSelectPositionA.x, gMapSelectPositionA.y, gMapSelectPositionB.x, gMapSelectPositionB.y }, + LandSetRightSetting::SetOwnershipWithChecks, _activeTool << 4); + GameActions::Execute(&landSetRightsAction); } break; } @@ -614,10 +615,10 @@ static void window_map_scrollmousedown(rct_window* w, int32_t scrollIndex, int32 gMapSelectPositionB.y = mapY + size; map_invalidate_selection_rect(); - gGameCommandErrorTitle = 0; - game_do_command( - gMapSelectPositionA.x, GAME_COMMAND_FLAG_APPLY, gMapSelectPositionA.y, _activeTool, GAME_COMMAND_SET_LAND_OWNERSHIP, - gMapSelectPositionB.x, gMapSelectPositionB.y); + auto landSetRightsAction = LandSetRightsAction( + { gMapSelectPositionA.x, gMapSelectPositionA.y, gMapSelectPositionB.x, gMapSelectPositionB.y }, + LandSetRightSetting::SetOwnershipWithChecks, _activeTool << 4); + GameActions::Execute(&landSetRightsAction); } } diff --git a/src/openrct2/Editor.cpp b/src/openrct2/Editor.cpp index 511cb2491b..f22d4036a4 100644 --- a/src/openrct2/Editor.cpp +++ b/src/openrct2/Editor.cpp @@ -23,6 +23,8 @@ #include "management/NewsItem.h" #include "object/ObjectManager.h" #include "object/ObjectRepository.h" +#include "actions/LandBuyRightsAction.hpp" +#include "actions/LandSetRightsAction.hpp" #include "peep/Staff.h" #include "rct1/RCT1.h" #include "scenario/Scenario.h" @@ -193,7 +195,14 @@ namespace Editor { int32_t mapSize = gMapSize; - game_do_command(64, 1, 64, 2, GAME_COMMAND_SET_LAND_OWNERSHIP, (mapSize - 3) * 32, (mapSize - 3) * 32); + MapRange range = { 64, 64, (mapSize - 3) * 32, (mapSize - 3) * 32 }; + auto landSetRightsAction = LandSetRightsAction(range, LandSetRightSetting::SetForSale); + landSetRightsAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND); + GameActions::Execute(&landSetRightsAction); + + auto landBuyRightsAction = LandBuyRightsAction(range, LandBuyRightSetting::BuyLand); + landBuyRightsAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND); + GameActions::Execute(&landBuyRightsAction); } /** diff --git a/src/openrct2/Game.cpp b/src/openrct2/Game.cpp index 08d131f8f3..4fae71dd06 100644 --- a/src/openrct2/Game.cpp +++ b/src/openrct2/Game.cpp @@ -1191,7 +1191,7 @@ GAME_COMMAND_POINTER* new_game_command_table[GAME_COMMAND_COUNT] = { nullptr, nullptr, nullptr, - game_command_buy_land_rights, + nullptr, game_command_place_park_entrance, nullptr, game_command_set_maze_track, @@ -1212,7 +1212,7 @@ GAME_COMMAND_POINTER* new_game_command_table[GAME_COMMAND_COUNT] = { nullptr, nullptr, nullptr, - game_command_set_land_ownership, + nullptr, nullptr, nullptr, nullptr, diff --git a/src/openrct2/Game.h b/src/openrct2/Game.h index 2627ab9d37..177f165e1a 100644 --- a/src/openrct2/Game.h +++ b/src/openrct2/Game.h @@ -54,18 +54,18 @@ enum GAME_COMMAND GAME_COMMAND_SET_STAFF_ORDERS, // GA GAME_COMMAND_SET_PARK_NAME, // GA GAME_COMMAND_SET_PARK_OPEN, // GA - GAME_COMMAND_BUY_LAND_RIGHTS, - GAME_COMMAND_PLACE_PARK_ENTRANCE, // GA - GAME_COMMAND_REMOVE_PARK_ENTRANCE, // GA - GAME_COMMAND_SET_MAZE_TRACK, // GA - GAME_COMMAND_SET_PARK_ENTRANCE_FEE, // GA - GAME_COMMAND_SET_STAFF_COLOUR, // GA - GAME_COMMAND_PLACE_WALL, // GA - GAME_COMMAND_REMOVE_WALL, // GA - GAME_COMMAND_PLACE_LARGE_SCENERY, // GA - GAME_COMMAND_REMOVE_LARGE_SCENERY, // GA - GAME_COMMAND_SET_CURRENT_LOAN, // GA - GAME_COMMAND_SET_RESEARCH_FUNDING, // GA + GAME_COMMAND_BUY_LAND_RIGHTS, // GA + GAME_COMMAND_PLACE_PARK_ENTRANCE, // GA + GAME_COMMAND_REMOVE_PARK_ENTRANCE, // GA + GAME_COMMAND_SET_MAZE_TRACK, // GA + GAME_COMMAND_SET_PARK_ENTRANCE_FEE, // GA + GAME_COMMAND_SET_STAFF_COLOUR, // GA + GAME_COMMAND_PLACE_WALL, // GA + GAME_COMMAND_REMOVE_WALL, // GA + GAME_COMMAND_PLACE_LARGE_SCENERY, // GA + GAME_COMMAND_REMOVE_LARGE_SCENERY, // GA + GAME_COMMAND_SET_CURRENT_LOAN, // GA + GAME_COMMAND_SET_RESEARCH_FUNDING, // GA GAME_COMMAND_PLACE_TRACK_DESIGN, GAME_COMMAND_START_MARKETING_CAMPAIGN, // GA GAME_COMMAND_PLACE_MAZE_DESIGN, @@ -75,13 +75,13 @@ enum GAME_COMMAND GAME_COMMAND_SET_WALL_COLOUR, // GA GAME_COMMAND_SET_LARGE_SCENERY_COLOUR, // GA GAME_COMMAND_SET_BANNER_COLOUR, // GA - GAME_COMMAND_SET_LAND_OWNERSHIP, - GAME_COMMAND_CLEAR_SCENERY, // GA - GAME_COMMAND_SET_BANNER_NAME, // GA - GAME_COMMAND_SET_SIGN_NAME, // GA - GAME_COMMAND_SET_BANNER_STYLE, // GA - GAME_COMMAND_SET_SIGN_STYLE, // GA - GAME_COMMAND_SET_PLAYER_GROUP, // GA + GAME_COMMAND_SET_LAND_OWNERSHIP, // GA + GAME_COMMAND_CLEAR_SCENERY, // GA + GAME_COMMAND_SET_BANNER_NAME, // GA + GAME_COMMAND_SET_SIGN_NAME, // GA + GAME_COMMAND_SET_BANNER_STYLE, // GA + GAME_COMMAND_SET_SIGN_STYLE, // GA + GAME_COMMAND_SET_PLAYER_GROUP, // GA GAME_COMMAND_MODIFY_GROUPS, GAME_COMMAND_KICK_PLAYER, GAME_COMMAND_CHEAT, diff --git a/src/openrct2/actions/GameActionRegistration.cpp b/src/openrct2/actions/GameActionRegistration.cpp index 67fe2817c6..a4740bb520 100644 --- a/src/openrct2/actions/GameActionRegistration.cpp +++ b/src/openrct2/actions/GameActionRegistration.cpp @@ -22,9 +22,11 @@ #include "GameAction.h" #include "GuestSetFlagsAction.hpp" #include "GuestSetNameAction.hpp" +#include "LandBuyRightsAction.hpp" #include "LandLowerAction.hpp" #include "LandRaiseAction.hpp" #include "LandSetHeightAction.hpp" +#include "LandSetRightsAction.hpp" #include "LandSmoothAction.hpp" #include "LargeSceneryPlaceAction.hpp" #include "LargeSceneryRemoveAction.hpp" @@ -138,6 +140,7 @@ namespace GameActions Register(); Register(); Register(); + Register(); Register(); Register(); Register(); diff --git a/src/openrct2/actions/LandBuyRightsAction.hpp b/src/openrct2/actions/LandBuyRightsAction.hpp new file mode 100644 index 0000000000..573821b266 --- /dev/null +++ b/src/openrct2/actions/LandBuyRightsAction.hpp @@ -0,0 +1,184 @@ +/***************************************************************************** + * 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 "../Context.h" +#include "../OpenRCT2.h" +#include "../actions/LandSetHeightAction.hpp" +#include "../audio/audio.h" +#include "../interface/Window.h" +#include "../localisation/Localisation.h" +#include "../localisation/StringIds.h" +#include "../management/Finance.h" +#include "../ride/RideData.h" +#include "../windows/Intent.h" +#include "../world/Park.h" +#include "../world/Scenery.h" +#include "../world/Sprite.h" +#include "../world/Surface.h" +#include "GameAction.h" + +enum class LandBuyRightSetting : uint8_t +{ + BuyLand, + BuyConstructionRights, + Count +}; + +DEFINE_GAME_ACTION(LandBuyRightsAction, GAME_COMMAND_BUY_LAND_RIGHTS, GameActionResult) +{ +private: + MapRange _range; + uint8_t _setting = static_cast(LandBuyRightSetting::Count); + + constexpr static rct_string_id _ErrorTitles[] = { STR_CANT_BUY_LAND, STR_CANT_BUY_CONSTRUCTION_RIGHTS_HERE }; + +public: + LandBuyRightsAction() + { + } + LandBuyRightsAction(MapRange range, LandBuyRightSetting setting) + : _range(range) + , _setting(static_cast(setting)) + { + } + + LandBuyRightsAction(CoordsXY coord, LandBuyRightSetting setting) + : _range(coord.x, coord.y, coord.x, coord.y) + , _setting(static_cast(setting)) + { + } + + uint16_t GetActionFlags() const override + { + return GameAction::GetActionFlags(); + } + + void Serialise(DataSerialiser & stream) override + { + GameAction::Serialise(stream); + + stream << DS_TAG(_range) << DS_TAG(_setting); + } + + GameActionResult::Ptr Query() const override + { + return QueryExecute(false); + } + + GameActionResult::Ptr Execute() const override + { + return QueryExecute(true); + } + +private: + GameActionResult::Ptr QueryExecute(bool isExecuting) const + { + auto res = MakeResult(); + + MapRange normRange = _range.Normalise(); + // Keep big coordinates within map boundaries + auto aX = std::max(32, normRange.GetLeft()); + auto bX = std::min(gMapSizeMaxXY, normRange.GetRight()); + auto aY = std::max(32, normRange.GetTop()); + auto bY = std::min(gMapSizeMaxXY, normRange.GetBottom()); + + MapRange validRange = MapRange{ aX, aY, bX, bY }; + + CoordsXYZ centre{ (validRange.GetLeft() + validRange.GetRight()) / 2 + 16, + (validRange.GetTop() + validRange.GetBottom()) / 2 + 16, 0 }; + centre.z = tile_element_height(centre.x, centre.y); + + res->Position = centre; + res->ExpenditureType = RCT_EXPENDITURE_TYPE_LAND_PURCHASE; + + // Game command modified to accept selection size + for (auto y = validRange.GetTop(); y <= validRange.GetBottom(); y += 32) + { + for (auto x = validRange.GetLeft(); x <= validRange.GetRight(); x += 32) + { + auto result = map_buy_land_rights_for_tile({ x, y }, isExecuting); + if (result->Error == GA_ERROR::OK) + { + res->Cost += result->Cost; + } + } + } + if (isExecuting) + { + map_count_remaining_land_rights(); + } + return res; + } + + GameActionResult::Ptr map_buy_land_rights_for_tile(const CoordsXY loc, bool isExecuting) const + { + if (_setting >= static_cast(LandBuyRightSetting::Count)) + { + log_warning("Tried calling buy land rights with an incorrect setting. setting = %u", _setting); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, _ErrorTitles[0], STR_NONE); + } + + SurfaceElement* surfaceElement = map_get_surface_element_at(loc)->AsSurface(); + if (surfaceElement == nullptr) + { + log_error("Could not find surface. x = %d, y = %d", loc.x, loc.y); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, _ErrorTitles[_setting], STR_NONE); + } + + auto res = MakeResult(); + switch (static_cast(_setting)) + { + case LandBuyRightSetting::BuyLand: // 0 + if ((surfaceElement->GetOwnership() & OWNERSHIP_OWNED) != 0) + { // If the land is already owned + return res; + } + + if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) != 0 + || (surfaceElement->GetOwnership() & OWNERSHIP_AVAILABLE) == 0) + { + return MakeResult(GA_ERROR::NOT_OWNED, _ErrorTitles[_setting], STR_LAND_NOT_FOR_SALE); + } + if (isExecuting) + { + surfaceElement->SetOwnership(OWNERSHIP_OWNED); + update_park_fences_around_tile(loc); + } + res->Cost = gLandPrice; + return res; + + case LandBuyRightSetting::BuyConstructionRights: // 2 + if ((surfaceElement->GetOwnership() & (OWNERSHIP_OWNED | OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED)) != 0) + { // If the land or construction rights are already owned + return res; + } + + if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) != 0 + || (surfaceElement->GetOwnership() & OWNERSHIP_CONSTRUCTION_RIGHTS_AVAILABLE) == 0) + { + return MakeResult(GA_ERROR::NOT_OWNED, _ErrorTitles[_setting], STR_CONSTRUCTION_RIGHTS_NOT_FOR_SALE); + } + + if (isExecuting) + { + surfaceElement->SetOwnership(surfaceElement->GetOwnership() | OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED); + uint16_t baseHeight = surfaceElement->base_height * 8; + map_invalidate_tile(loc.x, loc.y, baseHeight, baseHeight + 16); + } + res->Cost = gConstructionRightsPrice; + return res; + + default: + log_warning("Tried calling buy land rights with an incorrect setting. setting = %u", _setting); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, _ErrorTitles[0], STR_NONE); + } + } +}; diff --git a/src/openrct2/actions/LandSetRightsAction.hpp b/src/openrct2/actions/LandSetRightsAction.hpp new file mode 100644 index 0000000000..392f353bdb --- /dev/null +++ b/src/openrct2/actions/LandSetRightsAction.hpp @@ -0,0 +1,231 @@ +/***************************************************************************** + * 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 "../Context.h" +#include "../OpenRCT2.h" +#include "../actions/LandSetHeightAction.hpp" +#include "../audio/audio.h" +#include "../interface/Window.h" +#include "../localisation/Localisation.h" +#include "../localisation/StringIds.h" +#include "../management/Finance.h" +#include "../ride/RideData.h" +#include "../windows/Intent.h" +#include "../world/Park.h" +#include "../world/Scenery.h" +#include "../world/Sprite.h" +#include "../world/Surface.h" +#include "GameAction.h" + +enum class LandSetRightSetting : uint8_t +{ + UnownLand, + UnownConstructionRights, + SetForSale, + SetConstructionRightsForSale, + SetOwnershipWithChecks, + Count +}; + +DEFINE_GAME_ACTION(LandSetRightsAction, GAME_COMMAND_SET_LAND_OWNERSHIP, GameActionResult) +{ +private: + MapRange _range; + uint8_t _setting = static_cast(LandSetRightSetting::Count); + uint8_t _ownership; + +public: + LandSetRightsAction() + { + } + LandSetRightsAction(MapRange range, LandSetRightSetting setting, uint8_t ownership = 0) + : _range(range) + , _setting(static_cast(setting)) + , _ownership(ownership) + { + } + + LandSetRightsAction(CoordsXY coord, LandSetRightSetting setting, uint8_t ownership = 0) + : _range(coord.x, coord.y, coord.x, coord.y) + , _setting(static_cast(setting)) + , _ownership(ownership) + { + } + + uint16_t GetActionFlags() const override + { + return GameAction::GetActionFlags() | GA_FLAGS::EDITOR_ONLY; + } + + void Serialise(DataSerialiser & stream) override + { + GameAction::Serialise(stream); + + stream << DS_TAG(_range) << DS_TAG(_setting) << DS_TAG(_ownership); + } + + GameActionResult::Ptr Query() const override + { + return QueryExecute(false); + } + + GameActionResult::Ptr Execute() const override + { + return QueryExecute(true); + } + +private: + GameActionResult::Ptr QueryExecute(bool isExecuting) const + { + auto res = MakeResult(); + + MapRange normRange = _range.Normalise(); + // Keep big coordinates within map boundaries + auto aX = std::max(32, normRange.GetLeft()); + auto bX = std::min(gMapSizeMaxXY, normRange.GetRight()); + auto aY = std::max(32, normRange.GetTop()); + auto bY = std::min(gMapSizeMaxXY, normRange.GetBottom()); + + MapRange validRange = MapRange{ aX, aY, bX, bY }; + + CoordsXYZ centre{ (validRange.GetLeft() + validRange.GetRight()) / 2 + 16, + (validRange.GetTop() + validRange.GetBottom()) / 2 + 16, 0 }; + centre.z = tile_element_height(centre.x, centre.y); + + res->Position = centre; + res->ExpenditureType = RCT_EXPENDITURE_TYPE_LAND_PURCHASE; + + if (!(gScreenFlags & SCREEN_FLAGS_EDITOR) && !gCheatsSandboxMode) + { + return MakeResult(GA_ERROR::NOT_IN_EDITOR_MODE, STR_NONE, STR_LAND_NOT_FOR_SALE); + } + + // Game command modified to accept selection size + for (auto y = validRange.GetTop(); y <= validRange.GetBottom(); y += 32) + { + for (auto x = validRange.GetLeft(); x <= validRange.GetRight(); x += 32) + { + auto result = map_buy_land_rights_for_tile({ x, y }, isExecuting); + if (result->Error == GA_ERROR::OK) + { + res->Cost += result->Cost; + } + } + } + + if (isExecuting) + { + map_count_remaining_land_rights(); + audio_play_sound_at_location(SOUND_PLACE_ITEM, centre.x, centre.y, centre.z); + } + return res; + } + + GameActionResult::Ptr map_buy_land_rights_for_tile(const CoordsXY loc, bool isExecuting) const + { + SurfaceElement* surfaceElement = map_get_surface_element_at(loc)->AsSurface(); + if (surfaceElement == nullptr) + { + log_error("Could not find surface. x = %d, y = %d", loc.x, loc.y); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_NONE, STR_NONE); + } + + auto res = MakeResult(); + switch (static_cast(_setting)) + { + case LandSetRightSetting::UnownLand: + if (isExecuting) + { + surfaceElement->SetOwnership( + surfaceElement->GetOwnership() & ~(OWNERSHIP_OWNED | OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED)); + update_park_fences_around_tile(loc); + } + return res; + case LandSetRightSetting::UnownConstructionRights: + if (isExecuting) + { + surfaceElement->SetOwnership(surfaceElement->GetOwnership() & ~OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED); + uint16_t baseHeight = surfaceElement->base_height * 8; + map_invalidate_tile(loc.x, loc.y, baseHeight, baseHeight + 16); + } + return res; + case LandSetRightSetting::SetForSale: + if (isExecuting) + { + surfaceElement->SetOwnership(surfaceElement->GetOwnership() | OWNERSHIP_AVAILABLE); + uint16_t baseHeight = surfaceElement->base_height * 8; + map_invalidate_tile(loc.x, loc.y, baseHeight, baseHeight + 16); + } + return res; + case LandSetRightSetting::SetConstructionRightsForSale: + if (isExecuting) + { + surfaceElement->SetOwnership(surfaceElement->GetOwnership() | OWNERSHIP_CONSTRUCTION_RIGHTS_AVAILABLE); + uint16_t baseHeight = surfaceElement->base_height * 8; + map_invalidate_tile(loc.x, loc.y, baseHeight, baseHeight + 16); + } + return res; + case LandSetRightSetting::SetOwnershipWithChecks: + { + if (_ownership == surfaceElement->GetOwnership()) + { + return res; + } + + TileElement* tileElement = map_get_first_element_at(loc.x / 32, loc.y / 32); + do + { + if (tileElement->GetType() != TILE_ELEMENT_TYPE_ENTRANCE) + continue; + + if (tileElement->AsEntrance()->GetEntranceType() != ENTRANCE_TYPE_PARK_ENTRANCE) + continue; + + // Do not allow ownership of park entrance. + if (_ownership == OWNERSHIP_OWNED || _ownership == OWNERSHIP_AVAILABLE) + return res; + + // Allow construction rights available / for sale on park entrances on surface. + // There is no need to check the height if _ownership is 0 (unowned and no rights available). + if (_ownership == OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED + || _ownership == OWNERSHIP_CONSTRUCTION_RIGHTS_AVAILABLE) + { + if (tileElement->base_height - 3 > surfaceElement->base_height + || tileElement->base_height < surfaceElement->base_height) + return res; + } + } while (!(tileElement++)->IsLastForTile()); + + res->Cost = gLandPrice; + if (isExecuting) + { + if (_ownership != OWNERSHIP_UNOWNED) + { + gPeepSpawns.erase( + std::remove_if( + gPeepSpawns.begin(), gPeepSpawns.end(), + [x = loc.x, y = loc.y](const auto& spawn) { + return floor2(spawn.x, 32) == x && floor2(spawn.y, 32) == y; + }), + gPeepSpawns.end()); + } + surfaceElement->SetOwnership(_ownership); + update_park_fences_around_tile(loc); + gMapLandRightsUpdateSuccess = true; + } + return res; + } + default: + log_warning("Tried calling set land rights with an incorrect setting. setting = %u", _setting); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_NONE, STR_NONE); + } + } +}; diff --git a/src/openrct2/world/Footpath.cpp b/src/openrct2/world/Footpath.cpp index 762ed93d09..30bc3b0249 100644 --- a/src/openrct2/world/Footpath.cpp +++ b/src/openrct2/world/Footpath.cpp @@ -7,6 +7,7 @@ * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ +#include "../actions/LandSetRightsAction.hpp" #include "../Cheats.h" #include "../Context.h" #include "../Game.h" @@ -1238,7 +1239,9 @@ static void footpath_fix_ownership(int32_t x, int32_t y) ownership = OWNERSHIP_UNOWNED; } - map_buy_land_rights(x, y, x, y, BUY_LAND_RIGHTS_FLAG_SET_OWNERSHIP_WITH_CHECKS, (ownership << 4) | GAME_COMMAND_FLAG_APPLY); + auto landSetRightsAction = LandSetRightsAction({ x, y }, LandSetRightSetting::SetOwnershipWithChecks, ownership); + landSetRightsAction.SetFlags(GAME_COMMAND_FLAG_NO_SPEND); + GameActions::Execute(&landSetRightsAction); } static bool get_next_direction(int32_t edges, int32_t* direction) diff --git a/src/openrct2/world/Map.cpp b/src/openrct2/world/Map.cpp index e612c344a5..88d59b0a14 100644 --- a/src/openrct2/world/Map.cpp +++ b/src/openrct2/world/Map.cpp @@ -19,6 +19,7 @@ #include "../actions/LandLowerAction.hpp" #include "../actions/LandRaiseAction.hpp" #include "../actions/LandSetHeightAction.hpp" +#include "../actions/LandSetRightsAction.hpp" #include "../actions/LargeSceneryRemoveAction.hpp" #include "../actions/ParkEntranceRemoveAction.hpp" #include "../actions/SmallSceneryRemoveAction.hpp" @@ -924,52 +925,6 @@ int32_t tile_element_get_corner_height(const TileElement* tileElement, int32_t d return map_get_corner_height(z, slope, direction); } -static money32 map_set_land_ownership(uint8_t flags, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint8_t newOwnership) -{ - gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LAND_PURCHASE; - - if (!(flags & GAME_COMMAND_FLAG_APPLY)) - return 0; - - // Clamp to maximum addressable element to prevent long loop spamming the log - x1 = std::clamp(x1, 32, gMapSizeUnits - 32); - y1 = std::clamp(y1, 32, gMapSizeUnits - 32); - x2 = std::clamp(x2, 32, gMapSizeUnits - 32); - y2 = std::clamp(y2, 32, gMapSizeUnits - 32); - gMapLandRightsUpdateSuccess = false; - map_buy_land_rights(x1, y1, x2, y2, BUY_LAND_RIGHTS_FLAG_SET_OWNERSHIP_WITH_CHECKS, flags | (newOwnership << 8)); - - if (!gMapLandRightsUpdateSuccess) - return 0; - - int16_t x = std::clamp(x1, 32, gMapSizeUnits - 32); - int16_t y = std::clamp(y1, 32, gMapSizeUnits - 32); - - x += 16; - y += 16; - - int16_t z = tile_element_height(x, y); - audio_play_sound_at_location(SOUND_PLACE_ITEM, x, y, z); - return 0; -} - -/** - * - * rct2: 0x006648E3 - */ -void game_command_set_land_ownership( - int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, [[maybe_unused]] int32_t* esi, int32_t* edi, int32_t* ebp) -{ - int32_t flags = *ebx & 0xFF; - - *ebx = map_set_land_ownership(flags, *eax & 0xFFFF, *ecx & 0xFFFF, *edi & 0xFFFF, *ebp & 0xFFFF, *edx & 0xFF); - - if (flags & GAME_COMMAND_FLAG_APPLY) - { - map_count_remaining_land_rights(); - } -} - uint8_t map_get_lowest_land_height(int32_t xMin, int32_t xMax, int32_t yMin, int32_t yMax) { xMin = std::max(xMin, 32); @@ -1648,7 +1603,13 @@ void map_remove_out_of_range_elements() { if (x == 0 || y == 0 || x >= mapMaxXY || y >= mapMaxXY) { - map_buy_land_rights(x, y, x, y, BUY_LAND_RIGHTS_FLAG_UNOWN_TILE, GAME_COMMAND_FLAG_APPLY); + // Note this purposely does not use LandSetRightsAction as X Y coordinates are outside of normal range. + auto surfaceElement = map_get_surface_element_at({ x, y }); + if (surfaceElement != nullptr) + { + surfaceElement->AsSurface()->SetOwnership(OWNERSHIP_UNOWNED); + update_park_fences_around_tile({ x, y }); + } clear_elements_at(x, y); } } diff --git a/src/openrct2/world/Map.h b/src/openrct2/world/Map.h index b8a6bb281d..8b00d2ba35 100644 --- a/src/openrct2/world/Map.h +++ b/src/openrct2/world/Map.h @@ -188,8 +188,6 @@ 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); -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); void game_command_place_park_entrance( int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp); void game_command_set_banner_name( diff --git a/src/openrct2/world/Park.cpp b/src/openrct2/world/Park.cpp index 39c17944df..06ae680b0c 100644 --- a/src/openrct2/world/Park.cpp +++ b/src/openrct2/world/Park.cpp @@ -203,213 +203,6 @@ void park_set_name(const char* name) } } -static money32 map_buy_land_rights_for_tile(int32_t x, int32_t y, int32_t setting, int32_t flags) -{ - SurfaceElement* surfaceElement = map_get_surface_element_at({ x, y })->AsSurface(); - if (surfaceElement == nullptr) - return MONEY32_UNDEFINED; - - switch (setting) - { - case BUY_LAND_RIGHTS_FLAG_BUY_LAND: // 0 - if ((surfaceElement->GetOwnership() & OWNERSHIP_OWNED) != 0) - { // If the land is already owned - return 0; - } - - if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) != 0 - || (surfaceElement->GetOwnership() & OWNERSHIP_AVAILABLE) == 0) - { - gGameCommandErrorText = STR_LAND_NOT_FOR_SALE; - return MONEY32_UNDEFINED; - } - if (flags & GAME_COMMAND_FLAG_APPLY) - { - surfaceElement->SetOwnership(OWNERSHIP_OWNED); - update_park_fences_around_tile({ x, y }); - } - return gLandPrice; - case BUY_LAND_RIGHTS_FLAG_UNOWN_TILE: // 1 - if (flags & GAME_COMMAND_FLAG_APPLY) - { - surfaceElement->SetOwnership( - surfaceElement->GetOwnership() & ~(OWNERSHIP_OWNED | OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED)); - update_park_fences_around_tile({ x, y }); - } - return 0; - case BUY_LAND_RIGHTS_FLAG_BUY_CONSTRUCTION_RIGHTS: // 2 - if ((surfaceElement->GetOwnership() & (OWNERSHIP_OWNED | OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED)) != 0) - { // If the land or construction rights are already owned - return 0; - } - - if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) != 0 - || (surfaceElement->GetOwnership() & OWNERSHIP_CONSTRUCTION_RIGHTS_AVAILABLE) == 0) - { - gGameCommandErrorText = STR_CONSTRUCTION_RIGHTS_NOT_FOR_SALE; - return MONEY32_UNDEFINED; - } - - if (flags & GAME_COMMAND_FLAG_APPLY) - { - surfaceElement->SetOwnership(surfaceElement->GetOwnership() | OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED); - uint16_t baseHeight = surfaceElement->base_height * 8; - map_invalidate_tile(x, y, baseHeight, baseHeight + 16); - } - return gConstructionRightsPrice; - case BUY_LAND_RIGHTS_FLAG_UNOWN_CONSTRUCTION_RIGHTS: // 3 - if (flags & GAME_COMMAND_FLAG_APPLY) - { - surfaceElement->SetOwnership(surfaceElement->GetOwnership() & ~OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED); - uint16_t baseHeight = surfaceElement->base_height * 8; - map_invalidate_tile(x, y, baseHeight, baseHeight + 16); - } - return 0; - case BUY_LAND_RIGHTS_FLAG_SET_FOR_SALE: // 4 - if (flags & GAME_COMMAND_FLAG_APPLY) - { - surfaceElement->SetOwnership(surfaceElement->GetOwnership() | OWNERSHIP_AVAILABLE); - uint16_t baseHeight = surfaceElement->base_height * 8; - map_invalidate_tile(x, y, baseHeight, baseHeight + 16); - } - return 0; - case BUY_LAND_RIGHTS_FLAG_SET_CONSTRUCTION_RIGHTS_FOR_SALE: // 5 - if (flags & GAME_COMMAND_FLAG_APPLY) - { - surfaceElement->SetOwnership(surfaceElement->GetOwnership() | OWNERSHIP_CONSTRUCTION_RIGHTS_AVAILABLE); - uint16_t baseHeight = surfaceElement->base_height * 8; - map_invalidate_tile(x, y, baseHeight, baseHeight + 16); - } - return 0; - case BUY_LAND_RIGHTS_FLAG_SET_OWNERSHIP_WITH_CHECKS: - { - if (!(gScreenFlags & SCREEN_FLAGS_EDITOR) && !gCheatsSandboxMode) - { - return MONEY32_UNDEFINED; - } - - if (x <= 0 || y <= 0) - { - gGameCommandErrorText = STR_TOO_CLOSE_TO_EDGE_OF_MAP; - return MONEY32_UNDEFINED; - } - - if (x >= gMapSizeUnits || y >= gMapSizeUnits) - { - gGameCommandErrorText = STR_TOO_CLOSE_TO_EDGE_OF_MAP; - return MONEY32_UNDEFINED; - } - - uint8_t newOwnership = (flags & 0xFF00) >> 4; - if (newOwnership == surfaceElement->GetOwnership()) - { - return 0; - } - - TileElement* tileElement = map_get_first_element_at(x / 32, y / 32); - do - { - if (tileElement->GetType() == TILE_ELEMENT_TYPE_ENTRANCE - && tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_PARK_ENTRANCE) - { - // Do not allow ownership of park entrance. - if (newOwnership == OWNERSHIP_OWNED || newOwnership == OWNERSHIP_AVAILABLE) - return 0; - // Allow construction rights available / for sale on park entrances on surface. - // There is no need to check the height if newOwnership is 0 (unowned and no rights available). - if ((newOwnership == OWNERSHIP_CONSTRUCTION_RIGHTS_OWNED - || newOwnership == OWNERSHIP_CONSTRUCTION_RIGHTS_AVAILABLE) - && (tileElement->base_height - 3 > surfaceElement->base_height - || tileElement->base_height < surfaceElement->base_height)) - return 0; - } - } while (!(tileElement++)->IsLastForTile()); - - if (!(flags & GAME_COMMAND_FLAG_APPLY)) - { - return gLandPrice; - } - - if ((newOwnership & 0xF0) != 0) - { - gPeepSpawns.erase( - std::remove_if( - gPeepSpawns.begin(), gPeepSpawns.end(), - [x, y](const auto& spawn) { return floor2(spawn.x, 32) == x && floor2(spawn.y, 32) == y; }), - gPeepSpawns.end()); - } - surfaceElement->SetOwnership(newOwnership); - update_park_fences_around_tile({ x, y }); - gMapLandRightsUpdateSuccess = true; - return 0; - } - default: - log_warning("Tried calling map_buy_land_rights_for_tile() with an incorrect setting!"); - assert(false); - return MONEY32_UNDEFINED; - } -} - -int32_t map_buy_land_rights(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t setting, int32_t flags) -{ - int32_t x, y, z; - money32 totalCost, cost; - gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LAND_PURCHASE; - - if (x1 == 0 && y1 == 0) - { - x1 = x0; - y1 = y0; - } - - x = (x0 + x1) / 2 + 16; - y = (y0 + y1) / 2 + 16; - z = tile_element_height(x, y); - gCommandPosition.x = x; - gCommandPosition.y = y; - gCommandPosition.z = z; - - // Game command modified to accept selection size - totalCost = 0; - gGameCommandErrorText = STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED; - if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) != 0 || game_is_not_paused() || gCheatsBuildInPauseMode) - { - for (y = y0; y <= y1; y += 32) - { - for (x = x0; x <= x1; x += 32) - { - cost = map_buy_land_rights_for_tile(x, y, setting, flags); - if (cost != MONEY32_UNDEFINED) - { - totalCost += cost; - } - } - } - } - - return totalCost; -} - -/** - * - * rct2: 0x006649BD - */ -void game_command_buy_land_rights( - int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, [[maybe_unused]] int32_t* esi, int32_t* edi, int32_t* ebp) -{ - int32_t flags = *ebx & 0xFFFF; - - *ebx = map_buy_land_rights((*eax & 0xFFFF), (*ecx & 0xFFFF), (*edi & 0xFFFF), (*ebp & 0xFFFF), (*edx & 0x00FF), flags); - - // Too expensive to always call in map_buy_land_rights. - // It's already counted when the park is loaded, after - // that it should only be called for user actions. - if (flags & GAME_COMMAND_FLAG_APPLY) - { - map_count_remaining_land_rights(); - } -} - void set_forced_park_rating(int32_t rating) { _forcedParkRating = rating; diff --git a/src/openrct2/world/Park.h b/src/openrct2/world/Park.h index 4b7199ebf8..7d938d36e0 100644 --- a/src/openrct2/world/Park.h +++ b/src/openrct2/world/Park.h @@ -87,17 +87,6 @@ namespace OpenRCT2 }; } // namespace OpenRCT2 -enum -{ - BUY_LAND_RIGHTS_FLAG_BUY_LAND, - BUY_LAND_RIGHTS_FLAG_UNOWN_TILE, - BUY_LAND_RIGHTS_FLAG_BUY_CONSTRUCTION_RIGHTS, - BUY_LAND_RIGHTS_FLAG_UNOWN_CONSTRUCTION_RIGHTS, - BUY_LAND_RIGHTS_FLAG_SET_FOR_SALE, - BUY_LAND_RIGHTS_FLAG_SET_CONSTRUCTION_RIGHTS_FOR_SALE, - BUY_LAND_RIGHTS_FLAG_SET_OWNERSHIP_WITH_CHECKS, // Used in scenario editor -}; - extern rct_string_id gParkName; extern uint32_t gParkNameArgs; extern uint32_t gParkFlags; @@ -137,12 +126,8 @@ int32_t park_entrance_get_index(int32_t x, int32_t y, int32_t z); void park_set_name(const char* name); void park_set_entrance_fee(money32 value); -int32_t map_buy_land_rights(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t setting, int32_t flags); - void game_command_set_park_entrance_fee( int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp); -void game_command_buy_land_rights( - int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, int32_t* esi, int32_t* edi, int32_t* ebp); money16 park_get_entrance_fee();