/** * Terraform multiple tiles. * * @param iter Iterator pointing tiles to terraform and their target heights. * @return The cost of all successfull operations and the last error. * * @note _terraform_err_tile will be set to the tile where the last error occured * * @warning Note non-standard return behaviour - booth the cost \b and the error combined. */ static TerraformTilesResult TerraformTiles(TerraformingIterator *iter, DoCommandFlag flags, Money available_money) { TerraformTilesResult result = { 0, // cost false, // had_success STR_NULL // last_error }; TileIndex last_err_tile = INVALID_TILE; const Company *c = Company::GetIfValid(_current_company); int limit = (c == NULL ? INT32_MAX : GB(c->terraform_limit, 16, 16)); if (limit == 0) result.last_error = STR_ERROR_TERRAFORM_LIMIT_REACHED; for (; *iter != INVALID_TILE && limit > 0; ++(*iter)) { int h = iter->GetTileTargetHeight(); TileIndex t = *iter; for (int curh = TileHeight(t); curh != h; curh += (curh > h) ? -1 : 1) { CommandCost ret = DoCommand(t, SLOPE_N, (curh > h) ? 0 : 1, flags & ~DC_EXEC, CMD_TERRAFORM_LAND); if (ret.Failed()) { result.last_error = ret.GetErrorMessage(); last_err_tile = _terraform_err_tile; /* Did we reach the limit? */ if (ret.GetErrorMessage() == STR_ERROR_TERRAFORM_LIMIT_REACHED) limit = 0; break; } if (flags & DC_EXEC) { available_money -= ret.GetCost(); if (available_money < 0) { _additional_cash_required = ret.GetCost(); _terraform_err_tile = t; return result; } DoCommand(t, SLOPE_N, (curh > h) ? 0 : 1, flags, CMD_TERRAFORM_LAND); } else { /* When we're at the terraform limit we better bail (unneeded) testing as well. * This will probably cause the terraforming cost to be underestimated, but only * when it's near the terraforming limit. Even then, the estimation is * completely off due to it basically counting terraforming double, so it being * cut off earlier might even give a better estimate in some cases. */ if (--limit <= 0) { result.had_success = true; break; } } result.cost += ret.GetCost(); result.had_success = true; } } if (!result.had_success && result.last_error == STR_NULL) { result.last_error = STR_ERROR_ALREADY_LEVELLED; last_err_tile = INVALID_TILE; } _terraform_err_tile = last_err_tile; return result; }
bool AIObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text, AISuspendCallbackProc *callback) { if (!AIObject::CanSuspend()) { throw AI_FatalError("You are not allowed to execute any DoCommand (even indirect) in your constructor, Save(), Load(), and any valuator."); } /* Set the default callback to return a true/false result of the DoCommand */ if (callback == NULL) callback = &AIInstance::DoCommandReturn; /* Are we only interested in the estimate costs? */ bool estimate_only = GetDoCommandMode() != NULL && !GetDoCommandMode()(); #ifdef ENABLE_NETWORK /* Only set p2 when the command does not come from the network. */ if (GetCommandFlags(cmd) & CMD_CLIENT_ID && p2 == 0) p2 = UINT32_MAX; #endif /* Try to perform the command. */ CommandCost res = ::DoCommandPInternal(tile, p1, p2, cmd, _networking ? CcAI : NULL, text, false, estimate_only); /* We failed; set the error and bail out */ if (res.Failed()) { SetLastError(AIError::StringToError(res.GetErrorMessage())); return false; } /* No error, then clear it. */ SetLastError(AIError::ERR_NONE); /* Estimates, update the cost for the estimate and be done */ if (estimate_only) { IncreaseDoCommandCosts(res.GetCost()); return true; } /* Costs of this operation. */ SetLastCost(res.GetCost()); SetLastCommandRes(true); if (_networking) { /* Suspend the AI till the command is really executed. */ throw AI_VMSuspend(-(int)GetDoCommandDelay(), callback); } else { IncreaseDoCommandCosts(res.GetCost()); /* Suspend the AI player for 1+ ticks, so it simulates multiplayer. This * both avoids confusion when a developer launched his AI in a * multiplayer game, but also gives time for the GUI and human player * to interact with the game. */ throw AI_VMSuspend(GetDoCommandDelay(), callback); } NOT_REACHED(); }
/*! * Toplevel network safe docommand function for the current company. Must not be called recursively. * The callback is called when the command succeeded or failed. The parameters * \a tile, \a p1, and \a p2 are from the #CommandProc function. The parameter \a cmd is the command to execute. * The parameter \a my_cmd is used to indicate if the command is from a company or the server. * * @param tile The tile to perform a command on (see #CommandProc) * @param p1 Additional data for the command (see #CommandProc) * @param p2 Additional data for the command (see #CommandProc) * @param cmd The command to execute (a CMD_* value) * @param callback A callback function to call after the command is finished * @param text The text to pass * @param my_cmd indicator if the command is from a company or server (to display error messages for a user) * @return \c true if the command succeeded, else \c false. */ bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd) { /* Cost estimation is generally only done when the * local user presses shift while doing somthing. * However, in case of incoming network commands, * map generation or the pause button we do want * to execute. */ bool estimate_only = _shift_pressed && IsLocalCompany() && !_generating_world && !(cmd & CMD_NETWORK_COMMAND) && (cmd & CMD_ID_MASK) != CMD_PAUSE; /* We're only sending the command, so don't do * fancy things for 'success'. */ bool only_sending = _networking && !(cmd & CMD_NETWORK_COMMAND); /* Where to show the message? */ int x = TileX(tile) * TILE_SIZE; int y = TileY(tile) * TILE_SIZE; if (_pause_mode != PM_UNPAUSED && !IsCommandAllowedWhilePaused(cmd)) { ShowErrorMessage(GB(cmd, 16, 16), STR_ERROR_NOT_ALLOWED_WHILE_PAUSED, WL_INFO, x, y); return false; } #ifdef ENABLE_NETWORK /* Only set p2 when the command does not come from the network. */ if (!(cmd & CMD_NETWORK_COMMAND) && GetCommandFlags(cmd) & CMD_CLIENT_ID && p2 == 0) p2 = CLIENT_ID_SERVER; #endif CommandCost res = DoCommandPInternal(tile, p1, p2, cmd, callback, text, my_cmd, estimate_only); if (res.Failed()) { /* Only show the error when it's for us. */ StringID error_part1 = GB(cmd, 16, 16); if (estimate_only || (IsLocalCompany() && error_part1 != 0 && my_cmd)) { ShowErrorMessage(error_part1, res.GetErrorMessage(), WL_INFO, x, y, res.GetTextRefStackSize(), res.GetTextRefStack()); } } else if (estimate_only) { ShowEstimatedCostOrIncome(res.GetCost(), x, y); } else if (!only_sending && res.GetCost() != 0 && tile != 0 && IsLocalCompany() && _game_mode != GM_EDITOR) { /* Only show the cost animation when we did actually * execute the command, i.e. we're not sending it to * the server, when it has cost the local company * something. Furthermore in the editor there is no * concept of cost, so don't show it there either. */ ShowCostOrIncomeAnimation(x, y, GetSlopePixelZ(x, y), res.GetCost()); } if (!estimate_only && !only_sending && callback != NULL) { callback(res, tile, p1, p2); } return res.Succeeded(); }
/** * Verify whether the company can pay the bill. * @param cost [inout] Money to pay, is changed to an error if the company does not have enough money. * @return Function returns \c true if the company has enough money, else it returns \c false. */ bool CheckCompanyHasMoney(CommandCost &cost) { if (cost.GetCost() > 0) { const Company *c = Company::GetIfValid(_current_company); if (c != NULL && cost.GetCost() > c->money) { SetDParam(0, cost.GetCost()); cost.MakeError(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY); return false; } } return true; }
void ScriptInstance::DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) { ScriptObject::ActiveInstance active(this); ScriptObject::SetLastCommandRes(result.Succeeded()); if (result.Failed()) { ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage())); } else { ScriptObject::IncreaseDoCommandCosts(result.GetCost()); ScriptObject::SetLastCost(result.GetCost()); } }
void CcAI(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) { AIObject::SetLastCommandRes(result.Succeeded()); if (result.Failed()) { AIObject::SetLastError(AIError::StringToError(result.GetErrorMessage())); } else { AIObject::IncreaseDoCommandCosts(result.GetCost()); AIObject::SetLastCost(result.GetCost()); } Company::Get(_current_company)->ai_instance->Continue(); }
/*! * This function executes a given command with the parameters from the #CommandProc parameter list. * Depending on the flags parameter it execute or test a command. * * @param tile The tile to apply the command on (for the #CommandProc) * @param p1 Additional data for the command (for the #CommandProc) * @param p2 Additional data for the command (for the #CommandProc) * @param flags Flags for the command and how to execute the command * @param cmd The command-id to execute (a value of the CMD_* enums) * @param text The text to pass * @see CommandProc * @return the cost */ CommandCost DoCommand(TileIndex tile, uint32 p1, uint32 p2, DoCommandFlag flags, uint32 cmd, const char *text) { CommandCost res; /* Do not even think about executing out-of-bounds tile-commands */ if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (flags & DC_ALL_TILES) == 0))) return CMD_ERROR; /* Chop of any CMD_MSG or other flags; we don't need those here */ CommandProc *proc = _command_proc_table[cmd & CMD_ID_MASK].proc; if (_docommand_recursive == 0) _error_message = INVALID_STRING_ID; _docommand_recursive++; /* only execute the test call if it's toplevel, or we're not execing. */ if (_docommand_recursive == 1 || !(flags & DC_EXEC) ) { SetTownRatingTestMode(true); res = proc(tile, flags & ~DC_EXEC, p1, p2, text); SetTownRatingTestMode(false); if (CmdFailed(res)) { res.SetGlobalErrorMessage(); goto error; } if (_docommand_recursive == 1 && !(flags & DC_QUERY_COST) && !(flags & DC_BANKRUPT) && res.GetCost() != 0 && !CheckCompanyHasMoney(res)) { goto error; } if (!(flags & DC_EXEC)) { _docommand_recursive--; return res; } } /* Execute the command here. All cost-relevant functions set the expenses type * themselves to the cost object at some point */ res = proc(tile, flags, p1, p2, text); if (CmdFailed(res)) { res.SetGlobalErrorMessage(); error: _docommand_recursive--; return CMD_ERROR; } /* if toplevel, subtract the money. */ if (--_docommand_recursive == 0 && !(flags & DC_BANKRUPT)) { SubtractMoneyFromCompany(res); } return res; }
/** * Subtract money from a company, including the money fraction. * @param company Company paying the bill. * @param cst Cost of a command. */ void SubtractMoneyFromCompanyFract(CompanyID company, CommandCost cst) { Company *c = Company::Get(company); byte m = c->money_fraction; Money cost = cst.GetCost(); c->money_fraction = m - (byte)cost; cost >>= 8; if (c->money_fraction > m) cost++; if (cost != 0) SubtractMoneyFromAnyCompany(c, CommandCost(cst.GetExpensesType(), cost)); }
/** * Aggreagate paste command costs without calling PastingState::DoCommand. * * The function works similarly to the PastingState::DoCommand but doesn't actually execute any * commands, it just collects a given result. * * When collecting a success, cost must be of type EXPENSES_CONSTRUCTION. A success also makes * STR_ERROR_NOTHING_TO_DO no more applies (we "did" something). * * Call PastingState::IsInterrupted to test whether the paste operation can be continued. * * @param cost The return value of the command, a cost or an error. * @param tile The tile the error concerns. * @param error_message Summary message of the error. * * @pre The company has enough money if DC_EXEC'ing. * * @see PastingState::IsInterrupted * @see PastingState::CollectError * @see PastingState::DoCommand */ void PastingState::CollectCost(const CommandCost &cost, TileIndex tile, StringID error_summary) { if (cost.Succeeded()) { assert(!this->IsInterrupted()); /* Currently only EXPENSES_CONSTRUCTION expenses are allowed when copy/pasting. If this * is not sufficient, some upgrade will be required. To allow proper update of finacial * statistics, the overal cost of paste operation will have to be stored separatly for * each supported type of expenses. */ assert(cost.GetExpensesType() == EXPENSES_CONSTRUCTION); /* make sure we are not expending too much */ assert(!(this->dc_flags & DC_EXEC) || cost.GetCost() <= 0 || this->GetAvailableMoney() >= 0); this->had_success = true; // mark that we had a succes and STR_ERROR_NOTHING_TO_DO no more applies this->overal_cost += cost.GetCost(); this->last_result = cost; } else { this->CollectError(tile, cost.GetErrorMessage(), error_summary); } }
/** * Call a given command as an ingredient of a paste operation. * * Costs and possible errors will be aggregated. After return, call PastingState::IsInterrupted to * test if the paste operation is disallowed to be continued. * * @param tile The tile to apply the command on. * @param p1 Additional data for the command. * @param p2 Additional data for the command. * @param cmd The command-id to execute (a value of the CMD_* enums) and the error summary message (CMD_MSG). * @return The cost of this operation or an error. * * @pre The command is not flagged with CMD_NO_TEST. * @pre The type of the command is CMDT_LANDSCAPE_CONSTRUCTION. * * @see PastingState::IsInterrupted * @see PastingState::CollectCost * @see PastingState::CollectError */ void PastingState::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd) { /* make sure we are still allowed to paste */ if (this->IsInterrupted()) { this->last_result = CMD_ERROR; // mark that the command didn't succeed return; } /* PastingState::DoCommand can handle only fully predictable commands, those without * CMD_NO_TEST flag. Unpredictable command have to be handled separately. */ assert(!(GetCommandFlags(cmd) & CMD_NO_TEST)); /* ignore some of the given flags, instead use those from the command proc table */ DoCommandFlag flags = this->dc_flags; flags &= ~DC_AUTO & ~DC_NO_WATER & ~DC_ALL_TILES; flags |= CommandFlagsToDCFlags(GetCommandFlags(cmd)); /* use given error message or the default one */ StringID summary_error_msg = GB(cmd, 16, 16); if (summary_error_msg == 0) summary_error_msg = STR_ERROR_CAN_T_PASTE_HERE; /* test the command, output is the return value */ CommandCost ret = ::DoCommand(tile, p1, p2, flags & ~DC_EXEC, cmd); /* apply if exec'ing */ if (ret.Succeeded() && (flags & DC_EXEC)) { /* check if there is enough money */ if (ret.GetCost() > 0 && this->GetAvailableMoney() < ret.GetCost()) { SetDParam(0, ret.GetCost()); ret = CommandCost(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY); } else { CommandCost ret2 = ::DoCommand(tile, p1, p2, flags, cmd); assert(ret == ret2); } } /* aggregate costs */ this->CollectCost(ret, tile, summary_error_msg); }
/** * Deduct costs of a command from the money of a company. * @param c Company to pay the bill. * @param cost Money to pay. */ static void SubtractMoneyFromAnyCompany(Company *c, CommandCost cost) { if (cost.GetCost() == 0) return; assert(cost.GetExpensesType() != INVALID_EXPENSES); c->money -= cost.GetCost(); c->yearly_expenses[0][cost.GetExpensesType()] += cost.GetCost(); if (HasBit(1 << EXPENSES_TRAIN_INC | 1 << EXPENSES_ROADVEH_INC | 1 << EXPENSES_AIRCRAFT_INC | 1 << EXPENSES_SHIP_INC, cost.GetExpensesType())) { c->cur_economy.income -= cost.GetCost(); } else if (HasBit(1 << EXPENSES_TRAIN_RUN | 1 << EXPENSES_ROADVEH_RUN | 1 << EXPENSES_AIRCRAFT_RUN | 1 << EXPENSES_SHIP_RUN | 1 << EXPENSES_PROPERTY | 1 << EXPENSES_LOAN_INT, cost.GetExpensesType())) { c->cur_economy.expenses -= cost.GetCost(); } InvalidateCompanyWindows(c); }
virtual void OnInit() { Town *t = ClosestTownFromTile(tile, _settings_game.economy.dist_local_authority); /* Because build_date is not set yet in every TileDesc, we make sure it is empty */ TileDesc td; td.build_date = INVALID_DATE; /* Most tiles have only one owner, but * - drivethrough roadstops can be build on town owned roads (up to 2 owners) and * - roads can have up to four owners (railroad, road, tram, 3rd-roadtype "highway"). */ td.owner_type[0] = STR_LAND_AREA_INFORMATION_OWNER; // At least one owner is displayed, though it might be "N/A". td.owner_type[1] = STR_NULL; // STR_NULL results in skipping the owner td.owner_type[2] = STR_NULL; td.owner_type[3] = STR_NULL; td.owner[0] = OWNER_NONE; td.owner[1] = OWNER_NONE; td.owner[2] = OWNER_NONE; td.owner[3] = OWNER_NONE; td.station_class = STR_NULL; td.station_name = STR_NULL; td.airport_class = STR_NULL; td.airport_name = STR_NULL; td.airport_tile_name = STR_NULL; td.rail_speed = 0; td.grf = NULL; CargoArray acceptance; AddAcceptedCargo(tile, acceptance, NULL); GetTileDesc(tile, &td); uint line_nr = 0; /* Tiletype */ SetDParam(0, td.dparam[0]); GetString(this->landinfo_data[line_nr], td.str, lastof(this->landinfo_data[line_nr])); line_nr++; /* Up to four owners */ for (uint i = 0; i < 4; i++) { if (td.owner_type[i] == STR_NULL) continue; SetDParam(0, STR_LAND_AREA_INFORMATION_OWNER_N_A); if (td.owner[i] != OWNER_NONE && td.owner[i] != OWNER_WATER) GetNameOfOwner(td.owner[i], tile); GetString(this->landinfo_data[line_nr], td.owner_type[i], lastof(this->landinfo_data[line_nr])); line_nr++; } /* Cost to clear/revenue when cleared */ StringID str = STR_LAND_AREA_INFORMATION_COST_TO_CLEAR_N_A; Company *c = Company::GetIfValid(_local_company); if (c != NULL) { Money old_money = c->money; c->money = INT64_MAX; assert(_current_company == _local_company); CommandCost costclear = DoCommand(tile, 0, 0, DC_NONE, CMD_LANDSCAPE_CLEAR); c->money = old_money; if (costclear.Succeeded()) { Money cost = costclear.GetCost(); if (cost < 0) { cost = -cost; // Negate negative cost to a positive revenue str = STR_LAND_AREA_INFORMATION_REVENUE_WHEN_CLEARED; } else { str = STR_LAND_AREA_INFORMATION_COST_TO_CLEAR; } SetDParam(0, cost); } } GetString(this->landinfo_data[line_nr], str, lastof(this->landinfo_data[line_nr])); line_nr++; /* Location */ char tmp[16]; snprintf(tmp, lengthof(tmp), "0x%.4X", tile); SetDParam(0, TileX(tile)); SetDParam(1, TileY(tile)); SetDParam(2, GetTileZ(tile)); SetDParamStr(3, tmp); GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_LANDINFO_COORDS, lastof(this->landinfo_data[line_nr])); line_nr++; /* Local authority */ SetDParam(0, STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY_NONE); if (t != NULL) { SetDParam(0, STR_TOWN_NAME); SetDParam(1, t->index); } GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_LOCAL_AUTHORITY, lastof(this->landinfo_data[line_nr])); line_nr++; /* Build date */ if (td.build_date != INVALID_DATE) { SetDParam(0, td.build_date); GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_BUILD_DATE, lastof(this->landinfo_data[line_nr])); line_nr++; } /* Station class */ if (td.station_class != STR_NULL) { SetDParam(0, td.station_class); GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_STATION_CLASS, lastof(this->landinfo_data[line_nr])); line_nr++; } /* Station type name */ if (td.station_name != STR_NULL) { SetDParam(0, td.station_name); GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_STATION_TYPE, lastof(this->landinfo_data[line_nr])); line_nr++; } /* Airport class */ if (td.airport_class != STR_NULL) { SetDParam(0, td.airport_class); GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_AIRPORT_CLASS, lastof(this->landinfo_data[line_nr])); line_nr++; } /* Airport name */ if (td.airport_name != STR_NULL) { SetDParam(0, td.airport_name); GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_AIRPORT_NAME, lastof(this->landinfo_data[line_nr])); line_nr++; } /* Airport tile name */ if (td.airport_tile_name != STR_NULL) { SetDParam(0, td.airport_tile_name); GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_AIRPORTTILE_NAME, lastof(this->landinfo_data[line_nr])); line_nr++; } /* Rail speed limit */ if (td.rail_speed != 0) { SetDParam(0, td.rail_speed); GetString(this->landinfo_data[line_nr], STR_LANG_AREA_INFORMATION_RAIL_SPEED_LIMIT, lastof(this->landinfo_data[line_nr])); line_nr++; } /* NewGRF name */ if (td.grf != NULL) { SetDParamStr(0, td.grf); GetString(this->landinfo_data[line_nr], STR_LAND_AREA_INFORMATION_NEWGRF_NAME, lastof(this->landinfo_data[line_nr])); line_nr++; } assert(line_nr < LAND_INFO_CENTERED_LINES); /* Mark last line empty */ this->landinfo_data[line_nr][0] = '\0'; /* Cargo acceptance is displayed in a extra multiline */ char *strp = GetString(this->landinfo_data[LAND_INFO_MULTICENTER_LINE], STR_LAND_AREA_INFORMATION_CARGO_ACCEPTED, lastof(this->landinfo_data[LAND_INFO_MULTICENTER_LINE])); bool found = false; for (CargoID i = 0; i < NUM_CARGO; ++i) { if (acceptance[i] > 0) { /* Add a comma between each item. */ if (found) { *strp++ = ','; *strp++ = ' '; } found = true; /* If the accepted value is less than 8, show it in 1/8:ths */ if (acceptance[i] < 8) { SetDParam(0, acceptance[i]); SetDParam(1, CargoSpec::Get(i)->name); strp = GetString(strp, STR_LAND_AREA_INFORMATION_CARGO_EIGHTS, lastof(this->landinfo_data[LAND_INFO_MULTICENTER_LINE])); } else { strp = GetString(strp, CargoSpec::Get(i)->name, lastof(this->landinfo_data[LAND_INFO_MULTICENTER_LINE])); } } } if (!found) this->landinfo_data[LAND_INFO_MULTICENTER_LINE][0] = '\0'; }
/* static */ bool ScriptObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text, Script_SuspendCallbackProc *callback) { if (!ScriptObject::CanSuspend()) { throw Script_FatalError("You are not allowed to execute any DoCommand (even indirect) in your constructor, Save(), Load(), and any valuator."); } if (ScriptObject::GetCompany() != OWNER_DEITY && !::Company::IsValidID(ScriptObject::GetCompany())) { ScriptObject::SetLastError(ScriptError::ERR_PRECONDITION_INVALID_COMPANY); return false; } assert(StrEmpty(text) || (GetCommandFlags(cmd) & CMD_STR_CTRL) != 0 || StrValid(text, text + strlen(text))); /* Set the default callback to return a true/false result of the DoCommand */ if (callback == NULL) callback = &ScriptInstance::DoCommandReturn; /* Are we only interested in the estimate costs? */ bool estimate_only = GetDoCommandMode() != NULL && !GetDoCommandMode()(); #ifdef ENABLE_NETWORK /* Only set p2 when the command does not come from the network. */ if (GetCommandFlags(cmd) & CMD_CLIENT_ID && p2 == 0) p2 = UINT32_MAX; #endif /* Try to perform the command. */ CommandCost res = ::DoCommandPInternal(tile, p1, p2, cmd, (_networking && !_generating_world) ? ScriptObject::GetActiveInstance()->GetDoCommandCallback() : NULL, text, false, estimate_only); /* We failed; set the error and bail out */ if (res.Failed()) { SetLastError(ScriptError::StringToError(res.GetErrorMessage())); return false; } /* No error, then clear it. */ SetLastError(ScriptError::ERR_NONE); /* Estimates, update the cost for the estimate and be done */ if (estimate_only) { IncreaseDoCommandCosts(res.GetCost()); return true; } /* Costs of this operation. */ SetLastCost(res.GetCost()); SetLastCommandRes(true); if (_generating_world) { IncreaseDoCommandCosts(res.GetCost()); if (callback != NULL) { /* Insert return value into to stack and throw a control code that * the return value in the stack should be used. */ callback(GetActiveInstance()); throw SQInteger(1); } return true; } else if (_networking) { /* Suspend the script till the command is really executed. */ throw Script_Suspend(-(int)GetDoCommandDelay(), callback); } else { IncreaseDoCommandCosts(res.GetCost()); /* Suspend the script player for 1+ ticks, so it simulates multiplayer. This * both avoids confusion when a developer launched his script in a * multiplayer game, but also gives time for the GUI and human player * to interact with the game. */ throw Script_Suspend(GetDoCommandDelay(), callback); } NOT_REACHED(); }
/** * Autoreplaces a vehicle * Trains are replaced as a whole chain, free wagons in depot are replaced on their own * @param tile not used * @param flags type of operation * @param p1 Index of vehicle * @param p2 not used * @param text unused * @return the cost of this operation or an error */ CommandCost CmdAutoreplaceVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { Vehicle *v = Vehicle::GetIfValid(p1); if (v == NULL) return CMD_ERROR; CommandCost ret = CheckOwnership(v->owner); if (ret.Failed()) return ret; if (!v->IsInDepot()) return CMD_ERROR; if (v->vehstatus & VS_CRASHED) return CMD_ERROR; bool free_wagon = false; if (v->type == VEH_TRAIN) { Train *t = Train::From(v); if (t->IsArticulatedPart() || t->IsRearDualheaded()) return CMD_ERROR; free_wagon = !t->IsFrontEngine(); if (free_wagon && t->First()->IsFrontEngine()) return CMD_ERROR; } else { if (!v->IsPrimaryVehicle()) return CMD_ERROR; } const Company *c = Company::Get(_current_company); bool wagon_removal = c->settings.renew_keep_length; /* Test whether any replacement is set, before issuing a whole lot of commands that would end in nothing changed */ Vehicle *w = v; bool any_replacements = false; while (w != NULL) { EngineID e; CommandCost cost = GetNewEngineType(w, c, e); if (cost.Failed()) return cost; any_replacements |= (e != INVALID_ENGINE); w = (!free_wagon && w->type == VEH_TRAIN ? Train::From(w)->GetNextUnit() : NULL); } CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, 0); bool nothing_to_do = true; if (any_replacements) { bool was_stopped = free_wagon || ((v->vehstatus & VS_STOPPED) != 0); /* Stop the vehicle */ if (!was_stopped) cost.AddCost(CmdStartStopVehicle(v, true)); if (cost.Failed()) return cost; assert(v->IsStoppedInDepot()); /* We have to construct the new vehicle chain to test whether it is valid. * Vehicle construction needs random bits, so we have to save the random seeds * to prevent desyncs and to replay newgrf callbacks during DC_EXEC */ SavedRandomSeeds saved_seeds; SaveRandomSeeds(&saved_seeds); if (free_wagon) { cost.AddCost(ReplaceFreeUnit(&v, flags & ~DC_EXEC, ¬hing_to_do)); } else { cost.AddCost(ReplaceChain(&v, flags & ~DC_EXEC, wagon_removal, ¬hing_to_do)); } RestoreRandomSeeds(saved_seeds); if (cost.Succeeded() && (flags & DC_EXEC) != 0) { CommandCost ret; if (free_wagon) { ret = ReplaceFreeUnit(&v, flags, ¬hing_to_do); } else { ret = ReplaceChain(&v, flags, wagon_removal, ¬hing_to_do); } assert(ret.Succeeded() && ret.GetCost() == cost.GetCost()); } /* Restart the vehicle */ if (!was_stopped) cost.AddCost(CmdStartStopVehicle(v, false)); } if (cost.Succeeded() && nothing_to_do) cost = CommandCost(STR_ERROR_AUTOREPLACE_NOTHING_TO_DO); return cost; }
/** * Replace a whole vehicle chain * @param chain vehicle chain to let autoreplace/renew operator on * @param flags command flags * @param wagon_removal remove wagons when the resulting chain occupies more tiles than the old did * @param nothing_to_do is set to 'false' when something was done (only valid when not failed) * @return cost or error */ static CommandCost ReplaceChain(Vehicle **chain, DoCommandFlag flags, bool wagon_removal, bool *nothing_to_do) { Vehicle *old_head = *chain; assert(old_head->IsPrimaryVehicle()); CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, 0); if (old_head->type == VEH_TRAIN) { /* Store the length of the old vehicle chain, rounded up to whole tiles */ uint16 old_total_length = CeilDiv(Train::From(old_head)->gcache.cached_total_length, TILE_SIZE) * TILE_SIZE; int num_units = 0; ///< Number of units in the chain for (Train *w = Train::From(old_head); w != NULL; w = w->GetNextUnit()) num_units++; Train **old_vehs = CallocT<Train *>(num_units); ///< Will store vehicles of the old chain in their order Train **new_vehs = CallocT<Train *>(num_units); ///< New vehicles corresponding to old_vehs or NULL if no replacement Money *new_costs = MallocT<Money>(num_units); ///< Costs for buying and refitting the new vehicles /* Collect vehicles and build replacements * Note: The replacement vehicles can only successfully build as long as the old vehicles are still in their chain */ int i; Train *w; for (w = Train::From(old_head), i = 0; w != NULL; w = w->GetNextUnit(), i++) { assert(i < num_units); old_vehs[i] = w; CommandCost ret = BuildReplacementVehicle(old_vehs[i], (Vehicle**)&new_vehs[i], true); cost.AddCost(ret); if (cost.Failed()) break; new_costs[i] = ret.GetCost(); if (new_vehs[i] != NULL) *nothing_to_do = false; } Train *new_head = (new_vehs[0] != NULL ? new_vehs[0] : old_vehs[0]); /* Note: When autoreplace has already failed here, old_vehs[] is not completely initialized. But it is also not needed. */ if (cost.Succeeded()) { /* Separate the head, so we can start constructing the new chain */ Train *second = Train::From(old_head)->GetNextUnit(); if (second != NULL) cost.AddCost(CmdMoveVehicle(second, NULL, DC_EXEC | DC_AUTOREPLACE, true)); assert(Train::From(new_head)->GetNextUnit() == NULL); /* Append engines to the new chain * We do this from back to front, so that the head of the temporary vehicle chain does not change all the time. * That way we also have less trouble when exceeding the unitnumber limit. * OTOH the vehicle attach callback is more expensive this way :s */ Train *last_engine = NULL; ///< Shall store the last engine unit after this step if (cost.Succeeded()) { for (int i = num_units - 1; i > 0; i--) { Train *append = (new_vehs[i] != NULL ? new_vehs[i] : old_vehs[i]); if (RailVehInfo(append->engine_type)->railveh_type == RAILVEH_WAGON) continue; if (new_vehs[i] != NULL) { /* Move the old engine to a separate row with DC_AUTOREPLACE. Else * moving the wagon in front may fail later due to unitnumber limit. * (We have to attach wagons without DC_AUTOREPLACE.) */ CmdMoveVehicle(old_vehs[i], NULL, DC_EXEC | DC_AUTOREPLACE, false); } if (last_engine == NULL) last_engine = append; cost.AddCost(CmdMoveVehicle(append, new_head, DC_EXEC, false)); if (cost.Failed()) break; } if (last_engine == NULL) last_engine = new_head; } /* When wagon removal is enabled and the new engines without any wagons are already longer than the old, we have to fail */ if (cost.Succeeded() && wagon_removal && new_head->gcache.cached_total_length > old_total_length) cost = CommandCost(STR_ERROR_TRAIN_TOO_LONG_AFTER_REPLACEMENT); /* Append/insert wagons into the new vehicle chain * We do this from back to front, so we can stop when wagon removal or maximum train length (i.e. from mammoth-train setting) is triggered. */ if (cost.Succeeded()) { for (int i = num_units - 1; i > 0; i--) { assert(last_engine != NULL); Vehicle *append = (new_vehs[i] != NULL ? new_vehs[i] : old_vehs[i]); if (RailVehInfo(append->engine_type)->railveh_type == RAILVEH_WAGON) { /* Insert wagon after 'last_engine' */ CommandCost res = CmdMoveVehicle(append, last_engine, DC_EXEC, false); /* When we allow removal of wagons, either the move failing due * to the train becoming too long, or the train becoming longer * would move the vehicle to the empty vehicle chain. */ if (wagon_removal && (res.Failed() ? res.GetErrorMessage() == STR_ERROR_TRAIN_TOO_LONG : new_head->gcache.cached_total_length > old_total_length)) { CmdMoveVehicle(append, NULL, DC_EXEC | DC_AUTOREPLACE, false); break; } cost.AddCost(res); if (cost.Failed()) break; } else { /* We have reached 'last_engine', continue with the next engine towards the front */ assert(append == last_engine); last_engine = last_engine->GetPrevUnit(); } } } /* Sell superfluous new vehicles that could not be inserted. */ if (cost.Succeeded() && wagon_removal) { assert(new_head->gcache.cached_total_length <= _settings_game.vehicle.max_train_length * TILE_SIZE); for (int i = 1; i < num_units; i++) { Vehicle *wagon = new_vehs[i]; if (wagon == NULL) continue; if (wagon->First() == new_head) break; assert(RailVehInfo(wagon->engine_type)->railveh_type == RAILVEH_WAGON); /* Sell wagon */ CommandCost ret = DoCommand(0, wagon->index, 0, DC_EXEC, GetCmdSellVeh(wagon)); assert(ret.Succeeded()); new_vehs[i] = NULL; /* Revert the money subtraction when the vehicle was built. * This value is different from the sell value, esp. because of refitting */ cost.AddCost(-new_costs[i]); } } /* The new vehicle chain is constructed, now take over orders and everything... */ if (cost.Succeeded()) cost.AddCost(CopyHeadSpecificThings(old_head, new_head, flags)); if (cost.Succeeded()) { /* Success ! */ if ((flags & DC_EXEC) != 0 && new_head != old_head) { *chain = new_head; } /* Transfer cargo of old vehicles and sell them */ for (int i = 0; i < num_units; i++) { Vehicle *w = old_vehs[i]; /* Is the vehicle again part of the new chain? * Note: We cannot test 'new_vehs[i] != NULL' as wagon removal might cause to remove both */ if (w->First() == new_head) continue; if ((flags & DC_EXEC) != 0) TransferCargo(w, new_head, true); /* Sell the vehicle. * Note: This might temporarly construct new trains, so use DC_AUTOREPLACE to prevent * it from failing due to engine limits. */ cost.AddCost(DoCommand(0, w->index, 0, flags | DC_AUTOREPLACE, GetCmdSellVeh(w))); if ((flags & DC_EXEC) != 0) { old_vehs[i] = NULL; if (i == 0) old_head = NULL; } } if ((flags & DC_EXEC) != 0) CheckCargoCapacity(new_head); } /* If we are not in DC_EXEC undo everything, i.e. rearrange old vehicles. * We do this from back to front, so that the head of the temporary vehicle chain does not change all the time. * Note: The vehicle attach callback is disabled here :) */ if ((flags & DC_EXEC) == 0) { /* Separate the head, so we can reattach the old vehicles */ Train *second = Train::From(old_head)->GetNextUnit(); if (second != NULL) CmdMoveVehicle(second, NULL, DC_EXEC | DC_AUTOREPLACE, true); assert(Train::From(old_head)->GetNextUnit() == NULL); for (int i = num_units - 1; i > 0; i--) { CommandCost ret = CmdMoveVehicle(old_vehs[i], old_head, DC_EXEC | DC_AUTOREPLACE, false); assert(ret.Succeeded()); } } } /* Finally undo buying of new vehicles */ if ((flags & DC_EXEC) == 0) { for (int i = num_units - 1; i >= 0; i--) { if (new_vehs[i] != NULL) { DoCommand(0, new_vehs[i]->index, 0, DC_EXEC, GetCmdSellVeh(new_vehs[i])); new_vehs[i] = NULL; } } } free(old_vehs); free(new_vehs); free(new_costs); } else { /* Build and refit replacement vehicle */ Vehicle *new_head = NULL; cost.AddCost(BuildReplacementVehicle(old_head, &new_head, true)); /* Was a new vehicle constructed? */ if (cost.Succeeded() && new_head != NULL) { *nothing_to_do = false; /* The new vehicle is constructed, now take over orders and everything... */ cost.AddCost(CopyHeadSpecificThings(old_head, new_head, flags)); if (cost.Succeeded()) { /* The new vehicle is constructed, now take over cargo */ if ((flags & DC_EXEC) != 0) { TransferCargo(old_head, new_head, true); *chain = new_head; } /* Sell the old vehicle */ cost.AddCost(DoCommand(0, old_head->index, 0, flags, GetCmdSellVeh(old_head))); } /* If we are not in DC_EXEC undo everything */ if ((flags & DC_EXEC) == 0) { DoCommand(0, new_head->index, 0, DC_EXEC, GetCmdSellVeh(new_head)); } } } return cost; }
/*! * Helper function for the toplevel network safe docommand function for the current company. * * @param tile The tile to perform a command on (see #CommandProc) * @param p1 Additional data for the command (see #CommandProc) * @param p2 Additional data for the command (see #CommandProc) * @param cmd The command to execute (a CMD_* value) * @param callback A callback function to call after the command is finished * @param text The text to pass * @param my_cmd indicator if the command is from a company or server (to display error messages for a user) * @param estimate_only whether to give only the estimate or also execute the command * @return the command cost of this function. */ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd, bool estimate_only) { /* Prevent recursion; it gives a mess over the network */ assert(_docommand_recursive == 0); _docommand_recursive = 1; /* Reset the state. */ _additional_cash_required = 0; /* Get pointer to command handler */ byte cmd_id = cmd & CMD_ID_MASK; assert(cmd_id < lengthof(_command_proc_table)); CommandProc *proc = _command_proc_table[cmd_id].proc; /* Shouldn't happen, but you never know when someone adds * NULLs to the _command_proc_table. */ assert(proc != NULL); /* Command flags are used internally */ CommandFlags cmd_flags = GetCommandFlags(cmd); /* Flags get send to the DoCommand */ DoCommandFlag flags = CommandFlagsToDCFlags(cmd_flags); #ifdef ENABLE_NETWORK /* Make sure p2 is properly set to a ClientID. */ assert(!(cmd_flags & CMD_CLIENT_ID) || p2 != 0); #endif /* 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_dcpi(CMD_ERROR, false); /* Always execute server and spectator commands as spectator */ bool exec_as_spectator = (cmd_flags & (CMD_SPECTATOR | CMD_SERVER)) != 0; /* If the company isn't valid it may only do server command or start a new company! * The server will ditch any server commands a client sends to it, so effectively * this guards the server from executing functions for an invalid company. */ if (_game_mode == GM_NORMAL && !exec_as_spectator && !Company::IsValidID(_current_company) && !(_current_company == OWNER_DEITY && (cmd_flags & CMD_DEITY) != 0)) { return_dcpi(CMD_ERROR, false); } Backup<CompanyByte> cur_company(_current_company, FILE_LINE); if (exec_as_spectator) cur_company.Change(COMPANY_SPECTATOR); bool test_and_exec_can_differ = (cmd_flags & CMD_NO_TEST) != 0; /* Test the command. */ _cleared_object_areas.Clear(); SetTownRatingTestMode(true); ClearStorageChanges(false); CommandCost res = proc(tile, flags, p1, p2, text); SetTownRatingTestMode(false); /* Make sure we're not messing things up here. */ assert(exec_as_spectator ? _current_company == COMPANY_SPECTATOR : cur_company.Verify()); /* If the command fails, we're doing an estimate * or the player does not have enough money * (unless it's a command where the test and * execution phase might return different costs) * we bail out here. */ if (res.Failed() || estimate_only || (!test_and_exec_can_differ && !CheckCompanyHasMoney(res))) { if (!_networking || _generating_world || (cmd & CMD_NETWORK_COMMAND) != 0) { /* Log the failed command as well. Just to be able to be find * causes of desyncs due to bad command test implementations. */ DEBUG(desync, 1, "cmdf: %08x; %02x; %02x; %06x; %08x; %08x; %08x; \"%s\" (%s)", _date, _date_fract, (int)_current_company, tile, p1, p2, cmd & ~CMD_NETWORK_COMMAND, text, GetCommandName(cmd)); } cur_company.Restore(); return_dcpi(res, false); } #ifdef ENABLE_NETWORK /* * If we are in network, and the command is not from the network * send it to the command-queue and abort execution */ if (_networking && !_generating_world && !(cmd & CMD_NETWORK_COMMAND)) { NetworkSendCommand(tile, p1, p2, cmd & ~CMD_FLAGS_MASK, callback, text, _current_company); cur_company.Restore(); /* Don't return anything special here; no error, no costs. * This way it's not handled by DoCommand and only the * actual execution of the command causes messages. Also * reset the storages as we've not executed the command. */ return_dcpi(CommandCost(), false); } #endif /* ENABLE_NETWORK */ DEBUG(desync, 1, "cmd: %08x; %02x; %02x; %06x; %08x; %08x; %08x; \"%s\" (%s)", _date, _date_fract, (int)_current_company, tile, p1, p2, cmd & ~CMD_NETWORK_COMMAND, text, GetCommandName(cmd)); /* Actually try and execute the command. If no cost-type is given * use the construction one */ _cleared_object_areas.Clear(); ClearStorageChanges(false); CommandCost res2 = proc(tile, flags | DC_EXEC, p1, p2, text); if (cmd_id == CMD_COMPANY_CTRL) { cur_company.Trash(); /* We are a new company -> Switch to new local company. * We were closed down -> Switch to spectator * Some other company opened/closed down -> The outside function will switch back */ _current_company = _local_company; } else { /* Make sure nothing bad happened, like changing the current company. */ assert(exec_as_spectator ? _current_company == COMPANY_SPECTATOR : cur_company.Verify()); cur_company.Restore(); } /* If the test and execution can differ we have to check the * return of the command. Otherwise we can check whether the * test and execution have yielded the same result, * i.e. cost and error state are the same. */ if (!test_and_exec_can_differ) { assert(res.GetCost() == res2.GetCost() && res.Failed() == res2.Failed()); // sanity check } else if (res2.Failed()) { return_dcpi(res2, false); } /* If we're needing more money and we haven't done * anything yet, ask for the money! */ if (_additional_cash_required != 0 && res2.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); return_dcpi(CommandCost(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY), false); } /* update last build coordinate of company. */ if (tile != 0) { Company *c = Company::GetIfValid(_current_company); if (c != NULL) c->last_build_coordinate = tile; } SubtractMoneyFromCompany(res2); /* update signals if needed */ UpdateSignalsInBuffer(); return_dcpi(res2, true); }
/** * Deduct costs of a command from the money of a company. * @param c Company to pay the bill. * @param cost Money to pay. */ static void SubtractMoneyFromAnyCompany(Company *c, CommandCost cost) { if (cost.GetCost() == 0) return; assert(cost.GetExpensesType() != INVALID_EXPENSES); if (HasBit(1 << EXPENSES_TRAIN_INC | 1 << EXPENSES_ROADVEH_INC | 1 << EXPENSES_AIRCRAFT_INC | 1 << EXPENSES_SHIP_INC | 1 << EXPENSES_SHARING_INC, cost.GetExpensesType())) { c->cur_economy.income -= cost.GetCost(); } else if (HasBit(1 << EXPENSES_TRAIN_RUN | 1 << EXPENSES_ROADVEH_RUN | 1 << EXPENSES_AIRCRAFT_RUN | // 1 << EXPENSES_SHIP_RUN | // 1 << EXPENSES_LOST_RUN | // 1 << EXPENSES_PROPERTY | // 1 << EXPENSES_LOAN_INT | 1 << EXPENSES_SHIP_RUN , cost.GetExpensesType())) { if ((_settings_game.economy.day_length_balance_type == DBT_ALL_COSTS || _settings_game.economy.day_length_balance_type == DBT_RUN_COST)) { cost.MultiplyCost(_settings_game.economy.day_length_balance_factor); } c->cur_economy.expenses -= cost.GetCost(); } else if (HasBit(1 << EXPENSES_LOST_RUN, cost.GetExpensesType())) { if (_settings_game.economy.day_length_balance_type == DBT_ALL_COSTS) { cost.AffectCost(_settings_game.economy.day_length_balance_factor); } c->cur_economy.expenses -= cost.GetCost(); } else if (HasBit(1 << EXPENSES_SHARING_COST, cost.GetExpensesType())) { if (_settings_game.economy.day_length_balance_type == DBT_ALL_COSTS) { cost.AffectCost(_settings_game.economy.day_length_balance_factor); } c->cur_economy.expenses -= cost.GetCost(); } else if (HasBit(1 << EXPENSES_LOAN_INT, cost.GetExpensesType())) { if (_settings_game.economy.day_length_balance_type == DBT_ALL_COSTS || (_settings_game.economy.day_length_balance_type == DBT_RUN_COST && _settings_game.economy.include_loan_int_to_run)) { cost.AffectCost(_settings_game.economy.day_length_balance_factor); } c->cur_economy.expenses -= cost.GetCost(); } else if (HasBit(1 << EXPENSES_PROPERTY, cost.GetExpensesType())) { if (_settings_game.economy.day_length_balance_type == DBT_ALL_COSTS || (_settings_game.economy.day_length_balance_type == DBT_RUN_COST && _settings_game.economy.include_prop_main_to_run)) { cost.AffectCost(_settings_game.economy.day_length_balance_factor); } c->cur_economy.expenses -= cost.GetCost(); } else if (HasBit(1 << EXPENSES_CONSTRUCTION, cost.GetExpensesType())) { /* Multiply construction costs according to day length balance type. */ if (_settings_game.economy.day_length_balance_type == DBT_ALL_COSTS) { cost.AffectCost(_settings_game.economy.day_length_balance_factor); } } /* Subtract money. */ c->money -= cost.GetCost(); c->yearly_expenses[0][cost.GetExpensesType()] += cost.GetCost(); InvalidateCompanyWindows(c); }