diff --git a/src/command.cpp b/src/command.cpp index 08fe7b2659..c02796fef4 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -287,9 +287,6 @@ void CommandHelperBase::LogCommandExecution(Commands cmd, StringID err_message, */ bool CommandHelperBase::InternalExecutePrepTest(CommandFlags cmd_flags, TileIndex tile, Backup &cur_company) { - /* Reset the state. */ - _additional_cash_required = 0; - /* Do not even think about executing out-of-bounds tile-commands */ if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (cmd_flags & CMD_ALL_TILES) == 0))) return false; @@ -356,11 +353,12 @@ std::tuple CommandHelperBase::InternalExecuteValidateTestAndPr * @param cmd_flags Command flags. * @param res_test Command result of test run. * @param tes_exec Command result of real run. + * @param extra_cash Additional cash required for successful command execution. * @param tile Tile of command execution. * @param[in,out] cur_company Backup of current company at start of command execution. * @return Final command result. */ -CommandCost CommandHelperBase::InternalExecuteProcessResult(Commands cmd, CommandFlags cmd_flags, const CommandCost &res_test, const CommandCost &res_exec, TileIndex tile, Backup &cur_company) +CommandCost CommandHelperBase::InternalExecuteProcessResult(Commands cmd, CommandFlags cmd_flags, const CommandCost &res_test, const CommandCost &res_exec, Money extra_cash, TileIndex tile, Backup &cur_company) { BasePersistentStorageArray::SwitchMode(PSM_LEAVE_COMMAND); @@ -389,11 +387,11 @@ CommandCost CommandHelperBase::InternalExecuteProcessResult(Commands cmd, Comman /* If we're needing more money and we haven't done * anything yet, ask for the money! */ - if (_additional_cash_required != 0 && res_exec.GetCost() == 0) { + if (extra_cash != 0 && res_exec.GetCost() == 0) { /* It could happen we removed rail, thus gained money, and deleted something else. * So make sure the signal buffer is empty even in this case */ UpdateSignalsInBuffer(); - SetDParam(0, _additional_cash_required); + SetDParam(0, extra_cash); return CommandCost(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY); } diff --git a/src/command_func.h b/src/command_func.h index 117fed9177..861dd480b2 100644 --- a/src/command_func.h +++ b/src/command_func.h @@ -39,8 +39,6 @@ static const CommandCost CMD_ERROR = CommandCost(INVALID_STRING_ID); void NetworkSendCommand(Commands cmd, StringID err_message, CommandCallback *callback, CompanyID company, TileIndex location, const CommandDataBuffer &cmd_data); -extern Money _additional_cash_required; - bool IsValidCommand(Commands cmd); CommandFlags GetCommandFlags(Commands cmd); const char *GetCommandName(Commands cmd); @@ -103,7 +101,7 @@ protected: static void InternalPostResult(const CommandCost &res, TileIndex tile, bool estimate_only, bool only_sending, StringID err_message, bool my_cmd); static bool InternalExecutePrepTest(CommandFlags cmd_flags, TileIndex tile, Backup &cur_company); static std::tuple InternalExecuteValidateTestAndPrepExec(CommandCost &res, CommandFlags cmd_flags, bool estimate_only, bool network_command, Backup &cur_company); - static CommandCost InternalExecuteProcessResult(Commands cmd, CommandFlags cmd_flags, const CommandCost &res_test, const CommandCost &res_exec, TileIndex tile, Backup &cur_company); + static CommandCost InternalExecuteProcessResult(Commands cmd, CommandFlags cmd_flags, const CommandCost &res_test, const CommandCost &res_exec, Money extra_cash, TileIndex tile, Backup &cur_company); static void LogCommandExecution(Commands cmd, StringID err_message, TileIndex tile, const CommandDataBuffer &args, bool failed); }; @@ -354,6 +352,16 @@ protected: return (ClientIdIsSet(std::get(values)) && ...); } + template + static inline Money ExtractAdditionalMoney(Ttuple &values) + { + if constexpr (std::is_same_v, Money>) { + return std::get<1>(values); + } else { + return {}; + } + } + static Tret Execute(StringID err_message, CommandCallback *callback, bool my_cmd, bool estimate_only, bool network_command, TileIndex tile, std::tuple args) { /* Prevent recursion; it gives a mess over the network */ @@ -403,10 +411,17 @@ protected: /* Actually try and execute the command. */ Tret res2 = std::apply(CommandTraits::proc, std::tuple_cat(std::make_tuple(flags | DC_EXEC), args)); + /* Convention: If the second result element is of type Money, + * this is the additional cash required for the command. */ + Money additional_money{}; + if constexpr (!std::is_same_v) { // No short-circuiting for 'if constexpr'. + additional_money = ExtractAdditionalMoney(res2); + } + if constexpr (std::is_same_v) { - return InternalExecuteProcessResult(Tcmd, cmd_flags, res, res2, tile, cur_company); + return InternalExecuteProcessResult(Tcmd, cmd_flags, res, res2, additional_money, tile, cur_company); } else { - std::get<0>(res2) = InternalExecuteProcessResult(Tcmd, cmd_flags, ExtractCommandCost(res), ExtractCommandCost(res2), tile, cur_company); + std::get<0>(res2) = InternalExecuteProcessResult(Tcmd, cmd_flags, ExtractCommandCost(res), ExtractCommandCost(res2), additional_money, tile, cur_company); return res2; } } diff --git a/src/economy.cpp b/src/economy.cpp index 7080aad8e5..f2e70f34f2 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -99,7 +99,6 @@ const ScoreInfo _score_info[] = { int64 _score_part[MAX_COMPANIES][SCORE_END]; Economy _economy; Prices _price; -Money _additional_cash_required; static PriceMultipliers _price_base_multiplier; /** diff --git a/src/landscape.cpp b/src/landscape.cpp index 43bc238ac9..b921deef22 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -736,9 +736,9 @@ CommandCost CmdLandscapeClear(DoCommandFlag flags, TileIndex tile) * @param diagonal Whether to use the Orthogonal (false) or Diagonal (true) iterator. * @return the cost of this operation or an error */ -CommandCost CmdClearArea(DoCommandFlag flags, TileIndex tile, TileIndex start_tile, bool diagonal) +std::tuple CmdClearArea(DoCommandFlag flags, TileIndex tile, TileIndex start_tile, bool diagonal) { - if (start_tile >= MapSize()) return CMD_ERROR; + if (start_tile >= MapSize()) return { CMD_ERROR, 0 }; Money money = GetAvailableMoneyForCommand(); CommandCost cost(EXPENSES_CONSTRUCTION); @@ -764,9 +764,8 @@ CommandCost CmdClearArea(DoCommandFlag flags, TileIndex tile, TileIndex start_ti if (flags & DC_EXEC) { money -= ret.GetCost(); if (ret.GetCost() > 0 && money < 0) { - _additional_cash_required = ret.GetCost(); delete iter; - return cost; + return { cost, ret.GetCost() }; } Command::Do(flags, t); @@ -786,7 +785,7 @@ CommandCost CmdClearArea(DoCommandFlag flags, TileIndex tile, TileIndex start_ti } delete iter; - return had_success ? cost : last_error; + return { had_success ? cost : last_error, 0 }; } diff --git a/src/landscape_cmd.h b/src/landscape_cmd.h index 5af2787231..40c0d20e39 100644 --- a/src/landscape_cmd.h +++ b/src/landscape_cmd.h @@ -13,7 +13,7 @@ #include "command_type.h" CommandCost CmdLandscapeClear(DoCommandFlag flags, TileIndex tile); -CommandCost CmdClearArea(DoCommandFlag flags, TileIndex tile, TileIndex start_tile, bool diagonal); +std::tuple CmdClearArea(DoCommandFlag flags, TileIndex tile, TileIndex start_tile, bool diagonal); DEF_CMD_TRAIT(CMD_LANDSCAPE_CLEAR, CmdLandscapeClear, CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_CLEAR_AREA, CmdClearArea, CMD_NO_TEST, CMDT_LANDSCAPE_CONSTRUCTION) // destroying multi-tile houses makes town rating differ between test and execution diff --git a/src/road_cmd.cpp b/src/road_cmd.cpp index 8cf07e8d61..0701481119 100644 --- a/src/road_cmd.cpp +++ b/src/road_cmd.cpp @@ -1070,16 +1070,16 @@ CommandCost CmdBuildLongRoad(DoCommandFlag flags, TileIndex start_tile, TileInde * @param end_half end tile starts in the 2nd half of tile (p2 & 2) * @return the cost of this operation or an error */ -CommandCost CmdRemoveLongRoad(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, RoadType rt, Axis axis, bool start_half, bool end_half) +std::tuple CmdRemoveLongRoad(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, RoadType rt, Axis axis, bool start_half, bool end_half) { CommandCost cost(EXPENSES_CONSTRUCTION); - if (end_tile >= MapSize()) return CMD_ERROR; - if (!ValParamRoadType(rt) || !IsEnumValid(axis)) return CMD_ERROR; + if (end_tile >= MapSize()) return { CMD_ERROR, 0 }; + if (!ValParamRoadType(rt) || !IsEnumValid(axis)) return { CMD_ERROR, 0 }; /* Only drag in X or Y direction dictated by the direction variable */ - if (axis == AXIS_X && TileY(start_tile) != TileY(end_tile)) return CMD_ERROR; // x-axis - if (axis == AXIS_Y && TileX(start_tile) != TileX(end_tile)) return CMD_ERROR; // y-axis + if (axis == AXIS_X && TileY(start_tile) != TileY(end_tile)) return { CMD_ERROR, 0 }; // x-axis + if (axis == AXIS_Y && TileX(start_tile) != TileX(end_tile)) return { CMD_ERROR, 0 }; // y-axis /* Swap start and ending tile, also the half-tile drag vars. */ if (start_tile > end_tile || (start_tile == end_tile && start_half)) { @@ -1107,8 +1107,7 @@ CommandCost CmdRemoveLongRoad(DoCommandFlag flags, TileIndex start_tile, TileInd if (flags & DC_EXEC) { money_spent += ret.GetCost(); if (money_spent > 0 && money_spent > money_available) { - _additional_cash_required = Command::Do(flags & ~DC_EXEC, start_tile, end_tile, rt, axis, start_half, end_half).GetCost(); - return cost; + return { cost, std::get<0>(Command::Do(flags & ~DC_EXEC, start_tile, end_tile, rt, axis, start_half, end_half)).GetCost() }; } RemoveRoad(tile, flags, bits, rtt, true, false); } @@ -1125,7 +1124,7 @@ CommandCost CmdRemoveLongRoad(DoCommandFlag flags, TileIndex start_tile, TileInd tile += (axis == AXIS_Y) ? TileDiffXY(0, 1) : TileDiffXY(1, 0); } - return had_success ? cost : last_error; + return { had_success ? cost : last_error, 0 }; } /** diff --git a/src/road_cmd.h b/src/road_cmd.h index 35903dd7a3..82f09d2fa9 100644 --- a/src/road_cmd.h +++ b/src/road_cmd.h @@ -18,7 +18,7 @@ void DrawRoadDepotSprite(int x, int y, DiagDirection dir, RoadType rt); void UpdateNearestTownForRoadTiles(bool invalidate); CommandCost CmdBuildLongRoad(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, RoadType rt, Axis axis, DisallowedRoadDirections drd, bool start_half, bool end_half, bool is_ai); -CommandCost CmdRemoveLongRoad(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, RoadType rt, Axis axis, bool start_half, bool end_half); +std::tuple CmdRemoveLongRoad(DoCommandFlag flags, TileIndex start_tile, TileIndex end_tile, RoadType rt, Axis axis, bool start_half, bool end_half); CommandCost CmdBuildRoad(DoCommandFlag flags, TileIndex tile, RoadBits pieces, RoadType rt, DisallowedRoadDirections toggle_drd, TownID town_id); CommandCost CmdBuildRoadDepot(DoCommandFlag flags, TileIndex tile, RoadType rt, DiagDirection dir); CommandCost CmdConvertRoad(DoCommandFlag flags, TileIndex tile, TileIndex area_start, RoadType to_type); diff --git a/src/terraform_cmd.cpp b/src/terraform_cmd.cpp index e4f6b141a9..e1f7944124 100644 --- a/src/terraform_cmd.cpp +++ b/src/terraform_cmd.cpp @@ -341,9 +341,9 @@ CommandCost CmdTerraformLand(DoCommandFlag flags, TileIndex tile, Slope slope, b * @param LevelMode Mode of leveling \c LevelMode. * @return the cost of this operation or an error */ -CommandCost CmdLevelLand(DoCommandFlag flags, TileIndex tile, TileIndex start_tile, bool diagonal, LevelMode lm) +std::tuple CmdLevelLand(DoCommandFlag flags, TileIndex tile, TileIndex start_tile, bool diagonal, LevelMode lm) { - if (start_tile >= MapSize()) return CMD_ERROR; + if (start_tile >= MapSize()) return { CMD_ERROR, 0 }; _terraform_err_tile = INVALID_TILE; @@ -356,11 +356,11 @@ CommandCost CmdLevelLand(DoCommandFlag flags, TileIndex tile, TileIndex start_ti case LM_LEVEL: break; case LM_RAISE: h++; break; case LM_LOWER: h--; break; - default: return CMD_ERROR; + default: return { CMD_ERROR, 0 }; } /* Check range of destination height */ - if (h > _settings_game.construction.map_height_limit) return_cmd_error((oldh == 0) ? STR_ERROR_ALREADY_AT_SEA_LEVEL : STR_ERROR_TOO_HIGH); + if (h > _settings_game.construction.map_height_limit) return { CommandCost(oldh == 0 ? STR_ERROR_ALREADY_AT_SEA_LEVEL : STR_ERROR_TOO_HIGH), 0 }; Money money = GetAvailableMoneyForCommand(); CommandCost cost(EXPENSES_CONSTRUCTION); @@ -369,7 +369,7 @@ CommandCost CmdLevelLand(DoCommandFlag flags, TileIndex tile, TileIndex start_ti const Company *c = Company::GetIfValid(_current_company); int limit = (c == nullptr ? INT32_MAX : GB(c->terraform_limit, 16, 16)); - if (limit == 0) return_cmd_error(STR_ERROR_TERRAFORM_LIMIT_REACHED); + if (limit == 0) return { CommandCost(STR_ERROR_TERRAFORM_LIMIT_REACHED), 0 }; TileIterator *iter = diagonal ? (TileIterator *)new DiagonalTileIterator(tile, start_tile) : new OrthogonalTileIterator(tile, start_tile); for (; *iter != INVALID_TILE; ++(*iter)) { @@ -388,9 +388,8 @@ CommandCost CmdLevelLand(DoCommandFlag flags, TileIndex tile, TileIndex start_ti if (flags & DC_EXEC) { money -= ret.GetCost(); if (money < 0) { - _additional_cash_required = ret.GetCost(); delete iter; - return cost; + return { cost, ret.GetCost() }; } Command::Do(flags, t, SLOPE_N, curh <= h); } else { @@ -414,5 +413,5 @@ CommandCost CmdLevelLand(DoCommandFlag flags, TileIndex tile, TileIndex start_ti } delete iter; - return had_success ? cost : last_error; + return { had_success ? cost : last_error, 0 }; } diff --git a/src/terraform_cmd.h b/src/terraform_cmd.h index 4daeeac48b..52d7a78fe7 100644 --- a/src/terraform_cmd.h +++ b/src/terraform_cmd.h @@ -15,7 +15,7 @@ #include "slope_type.h" CommandCost CmdTerraformLand(DoCommandFlag flags, TileIndex tile, Slope slope, bool dir_up); -CommandCost CmdLevelLand(DoCommandFlag flags, TileIndex tile, TileIndex start_tile, bool diagonal, LevelMode lm); +std::tuple CmdLevelLand(DoCommandFlag flags, TileIndex tile, TileIndex start_tile, bool diagonal, LevelMode lm); DEF_CMD_TRAIT(CMD_TERRAFORM_LAND, CmdTerraformLand, CMD_ALL_TILES | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION) DEF_CMD_TRAIT(CMD_LEVEL_LAND, CmdLevelLand, CMD_ALL_TILES | CMD_AUTO | CMD_NO_TEST, CMDT_LANDSCAPE_CONSTRUCTION) // test run might clear tiles multiple times, in execution that only happens once diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 5afc1d2c9a..87d42628a3 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -1935,41 +1935,41 @@ static bool IsUniqueTownName(const std::string &name) * @param text Custom name for the town. If empty, the town name parts will be used. * @return the cost of this operation or an error */ -std::tuple CmdFoundTown(DoCommandFlag flags, TileIndex tile, TownSize size, bool city, TownLayout layout, bool random_location, uint32 townnameparts, const std::string &text) +std::tuple CmdFoundTown(DoCommandFlag flags, TileIndex tile, TownSize size, bool city, TownLayout layout, bool random_location, uint32 townnameparts, const std::string &text) { TownNameParams par(_settings_game.game_creation.town_name); - if (size >= TSZ_END) return { CMD_ERROR, INVALID_TOWN }; - if (layout >= NUM_TLS) return { CMD_ERROR, INVALID_TOWN }; + if (size >= TSZ_END) return { CMD_ERROR, 0, INVALID_TOWN }; + if (layout >= NUM_TLS) return { CMD_ERROR, 0, INVALID_TOWN }; /* Some things are allowed only in the scenario editor and for game scripts. */ if (_game_mode != GM_EDITOR && _current_company != OWNER_DEITY) { - if (_settings_game.economy.found_town == TF_FORBIDDEN) return { CMD_ERROR, INVALID_TOWN }; - if (size == TSZ_LARGE) return { CMD_ERROR, INVALID_TOWN }; - if (random_location) return { CMD_ERROR, INVALID_TOWN }; + if (_settings_game.economy.found_town == TF_FORBIDDEN) return { CMD_ERROR, 0, INVALID_TOWN }; + if (size == TSZ_LARGE) return { CMD_ERROR, 0, INVALID_TOWN }; + if (random_location) return { CMD_ERROR, 0, INVALID_TOWN }; if (_settings_game.economy.found_town != TF_CUSTOM_LAYOUT && layout != _settings_game.economy.town_layout) { - return { CMD_ERROR, INVALID_TOWN }; + return { CMD_ERROR, 0, INVALID_TOWN }; } } else if (_current_company == OWNER_DEITY && random_location) { /* Random parameter is not allowed for Game Scripts. */ - return { CMD_ERROR, INVALID_TOWN }; + return { CMD_ERROR, 0, INVALID_TOWN }; } if (text.empty()) { /* If supplied name is empty, townnameparts has to generate unique automatic name */ - if (!VerifyTownName(townnameparts, &par)) return { CommandCost(STR_ERROR_NAME_MUST_BE_UNIQUE), INVALID_TOWN }; + if (!VerifyTownName(townnameparts, &par)) return { CommandCost(STR_ERROR_NAME_MUST_BE_UNIQUE), 0, INVALID_TOWN }; } else { /* If name is not empty, it has to be unique custom name */ - if (Utf8StringLength(text) >= MAX_LENGTH_TOWN_NAME_CHARS) return { CMD_ERROR, INVALID_TOWN }; - if (!IsUniqueTownName(text)) return { CommandCost(STR_ERROR_NAME_MUST_BE_UNIQUE), INVALID_TOWN }; + if (Utf8StringLength(text) >= MAX_LENGTH_TOWN_NAME_CHARS) return { CMD_ERROR, 0, INVALID_TOWN }; + if (!IsUniqueTownName(text)) return { CommandCost(STR_ERROR_NAME_MUST_BE_UNIQUE), 0, INVALID_TOWN }; } /* Allocate town struct */ - if (!Town::CanAllocateItem()) return { CommandCost(STR_ERROR_TOO_MANY_TOWNS), INVALID_TOWN }; + if (!Town::CanAllocateItem()) return { CommandCost(STR_ERROR_TOO_MANY_TOWNS), 0, INVALID_TOWN }; if (!random_location) { CommandCost ret = TownCanBePlacedHere(tile); - if (ret.Failed()) return { ret, INVALID_TOWN }; + if (ret.Failed()) return { ret, 0, INVALID_TOWN }; } static const byte price_mult[][TSZ_RANDOM + 1] = {{ 15, 25, 40, 25 }, { 20, 35, 55, 35 }}; @@ -1985,8 +1985,7 @@ std::tuple CmdFoundTown(DoCommandFlag flags, TileIndex tile TownID new_town = INVALID_TOWN; if (flags & DC_EXEC) { if (cost.GetCost() > GetAvailableMoneyForCommand()) { - _additional_cash_required = cost.GetCost(); - return { CommandCost(EXPENSES_OTHER), INVALID_TOWN }; + return { CommandCost(EXPENSES_OTHER), cost.GetCost(), INVALID_TOWN }; } Backup old_generating_world(_generating_world, true, FILE_LINE); @@ -2031,7 +2030,7 @@ std::tuple CmdFoundTown(DoCommandFlag flags, TileIndex tile Game::NewEvent(new ScriptEventTownFounded(t->index)); } } - return { cost, new_town }; + return { cost, 0, new_town }; } /** diff --git a/src/town_cmd.h b/src/town_cmd.h index e98fa571e0..74af1e10e1 100644 --- a/src/town_cmd.h +++ b/src/town_cmd.h @@ -16,7 +16,7 @@ enum TownEffect : byte; -std::tuple CmdFoundTown(DoCommandFlag flags, TileIndex tile, TownSize size, bool city, TownLayout layout, bool random_location, uint32 townnameparts, const std::string &text); +std::tuple CmdFoundTown(DoCommandFlag flags, TileIndex tile, TownSize size, bool city, TownLayout layout, bool random_location, uint32 townnameparts, const std::string &text); CommandCost CmdRenameTown(DoCommandFlag flags, TownID town_id, const std::string &text); CommandCost CmdDoTownAction(DoCommandFlag flags, TownID town_id, uint8 action); CommandCost CmdTownGrowthRate(DoCommandFlag flags, TownID town_id, uint16 growth_rate); @@ -37,6 +37,6 @@ DEF_CMD_TRAIT(CMD_EXPAND_TOWN, CmdExpandTown, CMD_DEITY, DEF_CMD_TRAIT(CMD_DELETE_TOWN, CmdDeleteTown, CMD_OFFLINE, CMDT_LANDSCAPE_CONSTRUCTION) CommandCallback CcFoundTown; -void CcFoundRandomTown(Commands cmd, const CommandCost &result, TownID town_id); +void CcFoundRandomTown(Commands cmd, const CommandCost &result, Money, TownID town_id); #endif /* TOWN_CMD_H */ diff --git a/src/town_gui.cpp b/src/town_gui.cpp index fbc6a842a3..6206ffc894 100644 --- a/src/town_gui.cpp +++ b/src/town_gui.cpp @@ -1017,7 +1017,7 @@ void CcFoundTown(Commands cmd, const CommandCost &result, TileIndex tile) if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace(); } -void CcFoundRandomTown(Commands cmd, const CommandCost &result, TownID town_id) +void CcFoundRandomTown(Commands cmd, const CommandCost &result, Money, TownID town_id) { if (result.Succeeded()) ScrollMainWindowToTile(Town::Get(town_id)->xy); }