diff -r 7856e972f8aa -r 7798ae816af8 src/autoreplace_cmd.cpp --- a/src/autoreplace_cmd.cpp Mon Apr 21 20:52:54 2008 +0000 +++ b/src/autoreplace_cmd.cpp Mon Apr 21 21:15:50 2008 +0000 @@ -1,5 +1,7 @@ /* $Id$ */ +/** @file autoreplace_cmd.cpp Deals with autoreplace execution but not the setup */ + #include "stdafx.h" #include "openttd.h" #include "roadveh.h" @@ -117,31 +119,26 @@ return CT_NO_REFIT; // We failed to find a cargo type on the old vehicle and we will not refit the new one } -/* Replaces a vehicle (used to be called autorenew) +/** Replaces a vehicle (used to be called autorenew) * This function is only called from MaybeReplaceVehicle() * Must be called with _current_player set to the owner of the vehicle * @param w Vehicle to replace * @param flags is the flags to use when calling DoCommand(). Mainly DC_EXEC counts + * @param p The vehicle owner (faster than refinding the pointer) + * @param new_engine_type The EngineID to replace to * @return value is cost of the replacement or CMD_ERROR */ -static CommandCost ReplaceVehicle(Vehicle **w, byte flags, Money total_cost) +static CommandCost ReplaceVehicle(Vehicle **w, byte flags, Money total_cost, const Player *p, EngineID new_engine_type) { CommandCost cost; CommandCost sell_value; Vehicle *old_v = *w; - const Player *p = GetPlayer(old_v->owner); - EngineID new_engine_type; const UnitID cached_unitnumber = old_v->unitnumber; bool new_front = false; Vehicle *new_v = NULL; char *vehicle_name = NULL; CargoID replacement_cargo_type; - /* Check if there is a autoreplacement set for the vehicle */ - new_engine_type = EngineReplacementForPlayer(p, old_v->engine_type, old_v->group_id); - /* if not, just renew to the same type */ - if (new_engine_type == INVALID_ENGINE) new_engine_type = old_v->engine_type; - replacement_cargo_type = GetNewCargoTypeForReplace(old_v, new_engine_type); /* check if we can't refit to the needed type, so no replace takes place to prevent the vehicle from altering cargo type */ @@ -292,21 +289,103 @@ return cost; } +/** Removes wagons from a train until it get a certain length + * @param v The vehicle + * @param old_total_length The wanted max length + * @return The profit from selling the wagons + */ +static CommandCost WagonRemoval(Vehicle *v, uint16 old_total_length) +{ + if (v->type != VEH_TRAIN) return CommandCost(); + Vehicle *front = v; + + CommandCost cost = CommandCost(); + + while (front->u.rail.cached_total_length > old_total_length) { + /* the train is too long. We will remove cars one by one from the start of the train until it's short enough */ + while (v != NULL && RailVehInfo(v->engine_type)->railveh_type != RAILVEH_WAGON) { + /* We move backwards in the train until we find a wagon */ + v = GetNextVehicle(v); + } + + if (v == NULL) { + /* We sold all the wagons and the train is still not short enough */ + SetDParam(0, front->unitnumber); + AddNewsItem(STR_TRAIN_TOO_LONG_AFTER_REPLACEMENT, NM_SMALL, NF_VIEWPORT | NF_VEHICLE, NT_ADVICE, DNC_NONE, front->index, 0); + return cost; + } + + /* We found a wagon we can sell */ + Vehicle *temp = v; + v = GetNextVehicle(v); + DoCommand(0, (INVALID_VEHICLE << 16) | temp->index, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); // remove the wagon from the train + MoveVehicleCargo(front, temp); // move the cargo back on the train + cost.AddCost(DoCommand(0, temp->index, 0, DC_EXEC, CMD_SELL_RAIL_WAGON)); // sell the wagon + } + return cost; +} + +/** Get the EngineID of the replacement for a vehicle + * @param v The vehicle to find a replacement for + * @param p The vehicle's owner (it's faster to forward the pointer than refinding it) + * @return the EngineID of the replacement. INVALID_ENGINE if no buildable replacement is found + */ +static EngineID GetNewEngineType(const Vehicle *v, const Player *p) +{ + if (v->type == VEH_TRAIN && IsRearDualheaded(v)) { + /* we build the rear ends of multiheaded trains with the front ones */ + return INVALID_ENGINE; + } + + EngineID e = EngineReplacementForPlayer(p, v->engine_type, v->group_id); + + if (e != INVALID_ENGINE && IsEngineBuildable(e, v->type, _current_player)) { + return e; + } + + if (v->NeedsAutorenewing(p) && // replace if engine is too old + IsEngineBuildable(v->engine_type, v->type, _current_player)) { // engine can still be build + return v->engine_type; + } + + return INVALID_ENGINE; +} + /** replaces a vehicle if it's set for autoreplace or is too old * (used to be called autorenew) * @param v The vehicle to replace * if the vehicle is a train, v needs to be the front engine - * @param check Checks if the replace is valid. No action is done at all - * @param display_costs If set, a cost animation is shown (only if check is false) - * @return CMD_ERROR if something went wrong. Otherwise the price of the replace + * @param flags + * @param display_costs If set, a cost animation is shown (only if DC_EXEC is set) + * This bool also takes autorenew money into consideration + * @return the costs, the success bool and sometimes an error message */ -CommandCost MaybeReplaceVehicle(Vehicle *v, bool check, bool display_costs) +CommandCost MaybeReplaceVehicle(Vehicle *v, uint32 flags, bool display_costs) { Vehicle *w; const Player *p = GetPlayer(v->owner); - byte flags = 0; - CommandCost cost, temp_cost; - bool stopped; + CommandCost cost; + bool stopped = false; + + /* We only want "real" vehicle types. */ + assert(IsPlayerBuildableVehicleType(v)); + + /* Ensure that this bool is cleared. */ + assert(!v->leave_depot_instantly); + + /* We can't sell if the current player don't own the vehicle. */ + assert(v->owner == _current_player); + + if (!v->IsInDepot()) { + /* The vehicle should be inside the depot */ + switch (v->type) { + default: NOT_REACHED(); + case VEH_TRAIN: return_cmd_error(STR_881A_TRAINS_CAN_ONLY_BE_ALTERED); break; + case VEH_ROAD: return_cmd_error(STR_9013_MUST_BE_STOPPED_INSIDE); break; + case VEH_SHIP: return_cmd_error(STR_980B_SHIP_MUST_BE_STOPPED_IN); break; + case VEH_AIRCRAFT: return_cmd_error(STR_A01B_AIRCRAFT_MUST_BE_STOPPED); break; + } + } /* Remember the length in case we need to trim train later on * If it's not a train, the value is unused @@ -317,38 +396,22 @@ -1 ); - - _current_player = v->owner; - - assert(IsPlayerBuildableVehicleType(v)); - - assert(v->vehstatus & VS_STOPPED); // the vehicle should have been stopped in VehicleEnteredDepotThisTick() if needed + if (!(v->vehstatus & VS_STOPPED)) { + /* The vehicle is moving so we better stop it before we might alter consist or sell it */ + v->vehstatus |= VS_STOPPED; + /* Remember that we stopped the vehicle */ + stopped = true; + } - /* Remember the flag v->leave_depot_instantly because if we replace the vehicle, the vehicle holding this flag will be sold - * If it is set, then we only stopped the vehicle to replace it (if needed) and we will need to start it again. - * We also need to reset the flag since it should remain false except from when the vehicle enters a depot until autoreplace is handled in the same tick */ - stopped = v->leave_depot_instantly; - v->leave_depot_instantly = false; - - for (;;) { + { cost = CommandCost(EXPENSES_NEW_VEHICLES); w = v; do { - if (w->type == VEH_TRAIN && IsRearDualheaded(w)) { - /* we build the rear ends of multiheaded trains with the front ones */ - continue; - } - - // check if the vehicle should be replaced - if (!w->NeedsAutorenewing(p) || // replace if engine is too old - w->max_age == 0) { // rail cars got a max age of 0 - if (!EngineHasReplacementForPlayer(p, w->engine_type, w->group_id)) continue; - } + EngineID new_engine = GetNewEngineType(w, p); + if (new_engine == INVALID_ENGINE) continue; /* Now replace the vehicle */ - temp_cost = ReplaceVehicle(&w, flags, cost.GetCost()); - - if (CmdFailed(temp_cost)) break; // replace failed for some reason. Leave the vehicle alone + cost.AddCost(ReplaceVehicle(&w, flags, cost.GetCost(), p, new_engine)); if (flags & DC_EXEC && (w->type != VEH_TRAIN || w->u.rail.first_engine == INVALID_ENGINE)) { @@ -358,11 +421,26 @@ */ v = w; } - cost.AddCost(temp_cost); } while (w->type == VEH_TRAIN && (w = GetNextVehicle(w)) != NULL); - if (!(flags & DC_EXEC) && (p->player_money < (cost.GetCost() + p->engine_renew_money) || cost.GetCost() == 0)) { - if (!check && p->player_money < (cost.GetCost() + p->engine_renew_money) && ( _local_player == v->owner ) && cost.GetCost() != 0) { + if (flags & DC_QUERY_COST || cost.GetCost() == 0) { + /* We didn't do anything during the replace so we will just exit here */ + if (stopped) v->vehstatus &= ~VS_STOPPED; + return cost; + } + + if (display_costs && !(flags & DC_EXEC)) { + /* We want to ensure that we will not get below p->engine_renew_money. + * We will not actually pay this amount. It's for display and checks only. */ + cost.AddCost((Money)p->engine_renew_money); + if (CmdSucceeded(cost) && GetAvailableMoneyForCommand() < cost.GetCost()) { + /* We don't have enough money so we will set cost to failed */ + cost.AddCost(CMD_ERROR); + } + } + + if (display_costs && CmdFailed(cost)) { + if (GetAvailableMoneyForCommand() < cost.GetCost() && IsLocalPlayer()) { StringID message; SetDParam(0, v->unitnumber); switch (v->type) { @@ -376,50 +454,22 @@ AddNewsItem(message, NM_SMALL, NF_VIEWPORT|NF_VEHICLE, NT_ADVICE, DNC_NONE, v->index, 0); } - if (stopped) v->vehstatus &= ~VS_STOPPED; - if (display_costs) _current_player = OWNER_NONE; - return CMD_ERROR; - } - - if (flags & DC_EXEC) { - break; // we are done replacing since the loop ran once with DC_EXEC - } else if (check) { - /* It's a test only and we know that we can do this - * NOTE: payment for wagon removal is NOT included in this price */ - return cost; - } - // now we redo the loop, but this time we actually do stuff since we know that we can do it - flags |= DC_EXEC; - } - - /* If setting is on to try not to exceed the old length of the train with the replacement */ - if (v->type == VEH_TRAIN && p->renew_keep_length) { - Vehicle *temp; - w = v; - - while (v->u.rail.cached_total_length > old_total_length) { - // the train is too long. We will remove cars one by one from the start of the train until it's short enough - while (w != NULL && RailVehInfo(w->engine_type)->railveh_type != RAILVEH_WAGON) { - w = GetNextVehicle(w); - } - if (w == NULL) { - // we failed to make the train short enough - SetDParam(0, v->unitnumber); - AddNewsItem(STR_TRAIN_TOO_LONG_AFTER_REPLACEMENT, NM_SMALL, NF_VIEWPORT | NF_VEHICLE, NT_ADVICE, DNC_NONE, v->index, 0); - break; - } - temp = w; - w = GetNextVehicle(w); - DoCommand(0, (INVALID_VEHICLE << 16) | temp->index, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); - MoveVehicleCargo(v, temp); - cost.AddCost(DoCommand(0, temp->index, 0, DC_EXEC, CMD_SELL_RAIL_WAGON)); } } + if (flags & DC_EXEC && CmdSucceeded(cost)) { + if (v->type == VEH_TRAIN && p->renew_keep_length) { + /* Remove wagons until the wanted length is reached */ + cost.AddCost(WagonRemoval(v, old_total_length)); + } + + if (display_costs && IsLocalPlayer()) { + ShowCostOrIncomeAnimation(v->x_pos, v->y_pos, v->z_pos, cost.GetCost()); + } + } + + /* Start the vehicle if we stopped it earlier */ if (stopped) v->vehstatus &= ~VS_STOPPED; - if (display_costs) { - if (IsLocalPlayer()) ShowCostOrIncomeAnimation(v->x_pos, v->y_pos, v->z_pos, cost.GetCost()); - _current_player = OWNER_NONE; - } + return cost; }