# autoreplace_cmd.cpp

    00001 /* $Id: autoreplace_cmd.cpp 13827 2008-07-25 19:54:14Z rubidium $ */
    00002
    00003 #include "stdafx.h"
    00004 #include "openttd.h"
    00005 #include "roadveh.h"
    00006 #include "ship.h"
    00007 #include "news.h"
    00008 #include "player_func.h"
    00009 #include "engine.h"
    00010 #include "debug.h"
    00011 #include "vehicle_gui.h"
    00012 #include "depot.h"
    00013 #include "train.h"
    00014 #include "aircraft.h"
    00015 #include "cargotype.h"
    00016 #include "group.h"
    00017 #include "order.h"
    00018 #include "strings_func.h"
    00019 #include "command_func.h"
    00020 #include "vehicle_func.h"
    00021 #include "functions.h"
    00022 #include "variables.h"
    00023 #include "autoreplace_func.h"
    00024 #include "articulated_vehicles.h"
    00025
    00026 #include "table/strings.h"
    00027
    00028
    00029 /*
    00030  * move the cargo from one engine to another if possible
    00031  */
    00032 static void MoveVehicleCargo(Vehicle *dest, Vehicle *source)
    00033 {
    00034   Vehicle *v = dest;
    00035
    00036   do {
    00037     do {
    00038       if (source->cargo_type != dest->cargo_type)
    00039         continue; // cargo not compatible
    00040
    00041       if (dest->cargo.Count() == dest->cargo_cap)
    00042         continue; // the destination vehicle is already full
    00043
    00044       uint units_moved = min(source->cargo.Count(), dest->cargo_cap - dest->cargo.Count());
    00045       source->cargo.MoveTo(&dest->cargo, units_moved);
    00046
    00047       // copy the age of the cargo
    00048       dest->day_counter  = source->day_counter;
    00049       dest->tick_counter = source->tick_counter;
    00050
    00051     } while (source->cargo.Count() > 0 && (dest = dest->Next()) != NULL);
    00052     dest = v;
    00053   } while ((source = source->Next()) != NULL);
    00054
    00055   /*
    00056    * The of the train will be incorrect at this moment. This is due
    00057    * to the fact that removing the old wagon updates the weight of
    00058    * the complete train, which is without the weight of cargo we just
    00059    * moved back into some (of the) new wagon(s).
    00060    */
    00061   if (dest->type == VEH_TRAIN) TrainConsistChanged(dest->First(), true);
    00062 }
    00063
    00064 static bool VerifyAutoreplaceRefitForOrders(const Vehicle *v, const EngineID engine_type)
    00065 {
    00066   const Order *o;
    00067   const Vehicle *u;
    00068
    00069   if (v->type == VEH_TRAIN) {
    00070     u = v->First();
    00071   } else {
    00072     u = v;
    00073   }
    00074
    00075   FOR_VEHICLE_ORDERS(u, o) {
    00076     if (!(o->refit_cargo < NUM_CARGO)) continue;
    00077     if (!CanRefitTo(v->engine_type, o->refit_cargo)) continue;
    00078     if (!CanRefitTo(engine_type, o->refit_cargo)) return false;
    00079   }
    00080
    00081   return true;
    00082 }
    00083
    00092 static CargoID GetNewCargoTypeForReplace(Vehicle *v, EngineID engine_type)
    00093 {
    00094   CargoID new_cargo_type = GetEngineCargoType(engine_type);
    00095
    00096   if (new_cargo_type == CT_INVALID) return CT_NO_REFIT; // Don't try to refit an engine with no cargo capacity
    00097
    00098   if (v->cargo_cap != 0 && (v->cargo_type == new_cargo_type || CanRefitTo(engine_type, v->cargo_type))) {
    00099     if (VerifyAutoreplaceRefitForOrders(v, engine_type)) {
    00100       return v->cargo_type == new_cargo_type ? (CargoID)CT_NO_REFIT : v->cargo_type;
    00101     } else {
    00102       return CT_INVALID;
    00103     }
    00104   }
    00105   if (v->type != VEH_TRAIN) return CT_INVALID; // We can't refit the vehicle to carry the cargo we want
    00106
    00107   /* Below this line it's safe to assume that the vehicle in question is a train */
    00108
    00109   if (v->cargo_cap != 0) return CT_INVALID; // trying to replace a vehicle with cargo capacity into another one with incompatible cargo type
    00110
    00111   /* the old engine didn't have cargo capacity, but the new one does
    00112    * now we will figure out what cargo the train is carrying and refit to fit this */
    00113   v = v->First();
    00114   do {
    00115     if (v->cargo_cap == 0) continue;
    00116     /* Now we found a cargo type being carried on the train and we will see if it is possible to carry to this one */
    00117     if (v->cargo_type == new_cargo_type) return CT_NO_REFIT;
    00118     if (CanRefitTo(engine_type, v->cargo_type)) return v->cargo_type;
    00119   } while ((v = v->Next()) != NULL);
    00120   return CT_NO_REFIT; // We failed to find a cargo type on the old vehicle and we will not refit the new one
    00121 }
    00122
    00123 /* Replaces a vehicle (used to be called autorenew)
    00124  * This function is only called from MaybeReplaceVehicle()
    00125  * Must be called with _current_player set to the owner of the vehicle
    00126  * @param w Vehicle to replace
    00127  * @param flags is the flags to use when calling DoCommand(). Mainly DC_EXEC counts
    00128  * @return value is cost of the replacement or CMD_ERROR
    00129  */
    00130 static CommandCost ReplaceVehicle(Vehicle **w, uint32 flags, Money total_cost)
    00131 {
    00132   CommandCost cost;
    00133   CommandCost sell_value;
    00134   Vehicle *old_v = *w;
    00135   const Player *p = GetPlayer(old_v->owner);
    00136   EngineID new_engine_type;
    00137   const UnitID cached_unitnumber = old_v->unitnumber;
    00138   bool new_front = false;
    00139   Vehicle *new_v = NULL;
    00140   char *vehicle_name = NULL;
    00141   CargoID replacement_cargo_type;
    00142
    00143   /* Check if there is a autoreplacement set for the vehicle */
    00144   new_engine_type = EngineReplacementForPlayer(p, old_v->engine_type, old_v->group_id);
    00145   /* if not, just renew to the same type */
    00146   if (new_engine_type == INVALID_ENGINE) new_engine_type = old_v->engine_type;
    00147
    00148   replacement_cargo_type = GetNewCargoTypeForReplace(old_v, new_engine_type);
    00149
    00150   /* check if we can't refit to the needed type, so no replace takes place to prevent the vehicle from altering cargo type */
    00151   if (replacement_cargo_type == CT_INVALID) return CommandCost();
    00152
    00153   sell_value = DoCommand(0, old_v->index, 0, DC_QUERY_COST, GetCmdSellVeh(old_v));
    00154
    00155   /* We give the player a loan of the same amount as the sell value.
    00156    * This is needed in case he needs the income from the sale to build the new vehicle.
    00157    * We take it back if building fails or when we really sell the old engine */
    00158   SubtractMoneyFromPlayer(sell_value);
    00159
    00160   cost = DoCommand(old_v->tile, new_engine_type, 0, flags | DC_AUTOREPLACE, GetCmdBuildVeh(old_v));
    00161   if (CmdFailed(cost)) {
    00162     /* Take back the money we just gave the player */
    00163     sell_value.MultiplyCost(-1);
    00164     SubtractMoneyFromPlayer(sell_value);
    00165     return cost;
    00166   }
    00167
    00168   if (replacement_cargo_type != CT_NO_REFIT) {
    00169     /* add refit cost */
    00170     CommandCost refit_cost = GetRefitCost(new_engine_type);
    00171     if (old_v->type == VEH_TRAIN && RailVehInfo(new_engine_type)->railveh_type == RAILVEH_MULTIHEAD) {
    00172       /* Since it's a dualheaded engine we have to pay once more because the rear end is being refitted too. */
    00173       refit_cost.AddCost(refit_cost);
    00174     }
    00175     cost.AddCost(refit_cost);
    00176   }
    00177
    00178   if (flags & DC_EXEC) {
    00179     new_v = GetVehicle(_new_vehicle_id);
    00180     *w = new_v; //we changed the vehicle, so MaybeReplaceVehicle needs to work on the new one. Now we tell it what the new one is
    00181
    00182     /* refit if needed */
    00183     if (replacement_cargo_type != CT_NO_REFIT) {
    00184       if (CmdFailed(DoCommand(0, new_v->index, replacement_cargo_type, DC_EXEC, GetCmdRefitVeh(new_v)))) {
    00185         /* Being here shows a failure, which most likely is in GetNewCargoTypeForReplace() or incorrect estimation costs */
    00186         error("Autoreplace failed to refit. Replace engine %d to %d and refit to cargo %d", old_v->engine_type, new_v->engine_type, replacement_cargo_type);
    00187       }
    00188     }
    00189
    00190     if (new_v->type == VEH_TRAIN && HasBit(old_v->u.rail.flags, VRF_REVERSE_DIRECTION) && !IsMultiheaded(new_v) && !(new_v->Next() != NULL && IsArticulatedPart(new_v->Next()))) {
    00191       // we are autorenewing to a single engine, so we will turn it as the old one was turned as well
    00192       SetBit(new_v->u.rail.flags, VRF_REVERSE_DIRECTION);
    00193     }
    00194
    00195     if (old_v->type == VEH_TRAIN && !IsFrontEngine(old_v)) {
    00196       /* this is a railcar. We need to move the car into the train
    00197        * We add the new engine after the old one instead of replacing it. It will give the same result anyway when we
    00198        * sell the old engine in a moment
    00199        */
    00200       /* Get the vehicle in front of the one we move out */
    00201       Vehicle *front = old_v->Previous();
    00202       /* If the vehicle in front is the rear end of a dualheaded engine, then we need to use the one in front of that one */
    00203       if (IsRearDualheaded(front)) front = front->Previous();
    00204       /* Now we move the old one out of the train */
    00205       DoCommand(0, (INVALID_VEHICLE << 16) | old_v->index, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE);
    00206       /* Add the new vehicle */
    00207       DoCommand(0, (front->index << 16) | new_v->index, 1, DC_EXEC, CMD_MOVE_RAIL_VEHICLE);
    00208     } else {
    00209       // copy/clone the orders
    00210       DoCommand(0, (old_v->index << 16) | new_v->index, old_v->IsOrderListShared() ? CO_SHARE : CO_COPY, DC_EXEC, CMD_CLONE_ORDER);
    00211       new_v->cur_order_index = old_v->cur_order_index;
    00212       ChangeVehicleViewWindow(old_v, new_v);
    00213       new_v->profit_this_year = old_v->profit_this_year;
    00214       new_v->profit_last_year = old_v->profit_last_year;
    00215       new_v->service_interval = old_v->service_interval;
    00216       DoCommand(0, old_v->group_id, new_v->index, flags, CMD_ADD_VEHICLE_GROUP);
    00217       new_front = true;
    00218       new_v->unitnumber = old_v->unitnumber; // use the same unit number
    00219       new_v->dest_tile  = old_v->dest_tile;
    00220
    00221       new_v->current_order = old_v->current_order;
    00222       if (old_v->type == VEH_TRAIN && GetNextVehicle(old_v) != NULL){
    00223         Vehicle *temp_v = GetNextVehicle(old_v);
    00224
    00225         // move the entire train to the new engine, excluding the old engine
    00226         if (IsMultiheaded(old_v) && temp_v == old_v->u.rail.other_multiheaded_part) {
    00227           // we got front and rear of a multiheaded engine right after each other. We should work with the next in line instead
    00228           temp_v = GetNextVehicle(temp_v);
    00229         }
    00230
    00231         if (temp_v != NULL) {
    00232           DoCommand(0, (new_v->index << 16) | temp_v->index, 1, DC_EXEC, CMD_MOVE_RAIL_VEHICLE);
    00233         }
    00234       }
    00235     }
    00236     /* We are done setting up the new vehicle. Now we move the cargo from the old one to the new one */
    00237     MoveVehicleCargo(new_v->type == VEH_TRAIN ? new_v->First() : new_v, old_v);
    00238
    00239     // Get the name of the old vehicle if it has a custom name.
    00240     if (old_v->name != NULL) vehicle_name = strdup(old_v->name);
    00241   } else { // flags & DC_EXEC not set
    00242     CommandCost tmp_move;
    00243
    00244     if (old_v->type == VEH_TRAIN && IsFrontEngine(old_v)) {
    00245       Vehicle *next_veh = GetNextUnit(old_v); // don't try to move the rear multiheaded engine or articulated parts
    00246       if (next_veh != NULL) {
    00247         /* Verify that the wagons can be placed on the engine in question.
    00248          * This is done by building an engine, test if the wagons can be added and then sell the test engine. */
    00249         DoCommand(old_v->tile, new_engine_type, 0, DC_EXEC | DC_AUTOREPLACE, GetCmdBuildVeh(old_v));
    00250         Vehicle *temp = GetVehicle(_new_vehicle_id);
    00251         tmp_move = DoCommand(0, (temp->index << 16) | next_veh->index, 1, 0, CMD_MOVE_RAIL_VEHICLE);
    00252         DoCommand(0, temp->index, 0, DC_EXEC, GetCmdSellVeh(old_v));
    00253       }
    00254     }
    00255
    00256     /* Ensure that the player will not end up having negative money while autoreplacing
    00257      * This is needed because the only other check is done after the income from selling the old vehicle is substracted from the cost */
    00258     if (CmdFailed(tmp_move) || p->player_money < (cost.GetCost() + total_cost)) {
    00259       /* Pay back the loan */
    00260       sell_value.MultiplyCost(-1);
    00261       SubtractMoneyFromPlayer(sell_value);
    00262       return CMD_ERROR;
    00263     }
    00264   }
    00265
    00266   /* Take back the money we just gave the player just before building the vehicle
    00267    * The player will get the same amount now that the sale actually takes place */
    00268   sell_value.MultiplyCost(-1);
    00269   SubtractMoneyFromPlayer(sell_value);
    00270
    00271   /* sell the engine/ find out how much you get for the old engine (income is returned as negative cost) */
    00272   cost.AddCost(DoCommand(0, old_v->index, 0, flags, GetCmdSellVeh(old_v)));
    00273
    00274   if (new_front) {
    00275     /* now we assign the old unitnumber to the new vehicle */
    00276     new_v->unitnumber = cached_unitnumber;
    00277   }
    00278
    00279   /* Transfer the name of the old vehicle */
    00280   if ((flags & DC_EXEC) && vehicle_name != NULL) {
    00281     _cmd_text = vehicle_name;
    00282     DoCommand(0, new_v->index, 0, DC_EXEC, CMD_NAME_VEHICLE);
    00283     free(vehicle_name);
    00284   }
    00285
    00286   return cost;
    00287 }
    00288
    00297 CommandCost MaybeReplaceVehicle(Vehicle *v, bool check, bool display_costs)
    00298 {
    00299   Vehicle *w;
    00300   const Player *p = GetPlayer(v->owner);
    00301   byte flags = 0;
    00302   CommandCost cost, temp_cost;
    00303   bool stopped;
    00304
    00305   /* Remember the length in case we need to trim train later on
    00306    * If it's not a train, the value is unused
    00307    * round up to the length of the tiles used for the train instead of the train length instead
    00308    * Useful when newGRF uses custom length */
    00309   uint16 old_total_length = (v->type == VEH_TRAIN ?
    00310     (v->u.rail.cached_total_length + TILE_SIZE - 1) / TILE_SIZE * TILE_SIZE :
    00311     -1
    00312   );
    00313
    00314
    00315   _current_player = v->owner;
    00316
    00317   assert(IsPlayerBuildableVehicleType(v));
    00318
    00319   assert(v->vehstatus & VS_STOPPED); // the vehicle should have been stopped in VehicleEnteredDepotThisTick() if needed
    00320
    00321   /* Remember the flag v->leave_depot_instantly because if we replace the vehicle, the vehicle holding this flag will be sold
    00322    * If it is set, then we only stopped the vehicle to replace it (if needed) and we will need to start it again.
    00323    * 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 */
    00324   stopped = v->leave_depot_instantly;
    00325   v->leave_depot_instantly = false;
    00326
    00327   for (;;) {
    00328     cost = CommandCost(EXPENSES_NEW_VEHICLES);
    00329     w = v;
    00330     do {
    00331       if (w->type == VEH_TRAIN && IsRearDualheaded(w)) {
    00332         /* we build the rear ends of multiheaded trains with the front ones */
    00333         continue;
    00334       }
    00335
    00336       // check if the vehicle should be replaced
    00337       if (!w->NeedsAutorenewing(p) || // replace if engine is too old
    00338           w->max_age == 0) { // rail cars got a max age of 0
    00339         if (!EngineHasReplacementForPlayer(p, w->engine_type, w->group_id)) continue;
    00340       }
    00341
    00342       /* Now replace the vehicle */
    00343       temp_cost = ReplaceVehicle(&w, flags, cost.GetCost());
    00344
    00345       if (CmdFailed(temp_cost)) break; // replace failed for some reason. Leave the vehicle alone
    00346
    00347       if (flags & DC_EXEC &&
    00348           (w->type != VEH_TRAIN || w->u.rail.first_engine == INVALID_ENGINE)) {
    00349         /* now we bought a new engine and sold the old one. We need to fix the
    00350          * pointers in order to avoid pointing to the old one for trains: these
    00351          * pointers should point to the front engine and not the cars
    00352          */
    00353         v = w;
    00354       }
    00355       cost.AddCost(temp_cost);
    00356     } while (w->type == VEH_TRAIN && (w = GetNextVehicle(w)) != NULL);
    00357
    00358     if (!(flags & DC_EXEC) && (p->player_money < (cost.GetCost() + p->engine_renew_money) || cost.GetCost() == 0)) {
    00359       if (!check && p->player_money < (cost.GetCost() + p->engine_renew_money) && ( _local_player == v->owner ) && cost.GetCost() != 0) {
    00360         StringID message;
    00361         SetDParam(0, v->unitnumber);
    00362         switch (v->type) {
    00363           case VEH_TRAIN:    message = STR_TRAIN_AUTORENEW_FAILED;       break;
    00364           case VEH_ROAD:     message = STR_ROADVEHICLE_AUTORENEW_FAILED; break;
    00365           case VEH_SHIP:     message = STR_SHIP_AUTORENEW_FAILED;        break;
    00366           case VEH_AIRCRAFT: message = STR_AIRCRAFT_AUTORENEW_FAILED;    break;
    00367             // This should never happen
    00368           default: NOT_REACHED(); message = 0; break;
    00369         }
    00370
    00371         AddNewsItem(message, NEWS_FLAGS(NM_SMALL, NF_VIEWPORT|NF_VEHICLE, NT_ADVICE, 0), v->index, 0);
    00372       }
    00373       if (stopped) v->vehstatus &= ~VS_STOPPED;
    00374       if (display_costs) _current_player = OWNER_NONE;
    00375       return CMD_ERROR;
    00376     }
    00377
    00378     if (flags & DC_EXEC) {
    00379       break; // we are done replacing since the loop ran once with DC_EXEC
    00380     } else if (check) {
    00381       /* It's a test only and we know that we can do this
    00382        * NOTE: payment for wagon removal is NOT included in this price */
    00383       return cost;
    00384     }
    00385     // now we redo the loop, but this time we actually do stuff since we know that we can do it
    00386     flags |= DC_EXEC;
    00387   }
    00388
    00389   /* If setting is on to try not to exceed the old length of the train with the replacement */
    00390   if (v->type == VEH_TRAIN && p->renew_keep_length) {
    00391     Vehicle *temp;
    00392     w = v;
    00393
    00394     while (v->u.rail.cached_total_length > old_total_length) {
    00395       // the train is too long. We will remove cars one by one from the start of the train until it's short enough
    00396       while (w != NULL && RailVehInfo(w->engine_type)->railveh_type != RAILVEH_WAGON) {
    00397         w = GetNextVehicle(w);
    00398       }
    00399       if (w == NULL) {
    00400         // we failed to make the train short enough
    00401         SetDParam(0, v->unitnumber);
    00402         AddNewsItem(STR_TRAIN_TOO_LONG_AFTER_REPLACEMENT, NEWS_FLAGS(NM_SMALL, NF_VIEWPORT|NF_VEHICLE, NT_ADVICE, 0), v->index, 0);
    00403         break;
    00404       }
    00405       temp = w;
    00406       w = GetNextVehicle(w);
    00407       DoCommand(0, (INVALID_VEHICLE << 16) | temp->index, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE);
    00408       MoveVehicleCargo(v, temp);
    00409       cost.AddCost(DoCommand(0, temp->index, 0, DC_EXEC, CMD_SELL_RAIL_WAGON));
    00410     }
    00411   }
    00412
    00413   if (stopped) v->vehstatus &= ~VS_STOPPED;
    00414   if (display_costs) {
    00415     if (IsLocalPlayer()) ShowCostOrIncomeAnimation(v->x_pos, v->y_pos, v->z_pos, cost.GetCost());
    00416     _current_player = OWNER_NONE;
    00417   }
    00418   return cost;
    00419 }