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 }

Generated on Wed Oct 1 17:03:20 2008 for openttd by  doxygen 1.5.6