summaryrefslogtreecommitdiff
path: root/src/autoreplace_cmd.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/autoreplace_cmd.cpp')
-rw-r--r--src/autoreplace_cmd.cpp390
1 files changed, 390 insertions, 0 deletions
diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp
index 8710ac171..cb1d11556 100644
--- a/src/autoreplace_cmd.cpp
+++ b/src/autoreplace_cmd.cpp
@@ -124,6 +124,31 @@ static void MoveVehicleCargo(Vehicle *dest, Vehicle *source)
}
+/** Transfer cargo from a single (articulated )old vehicle to the new vehicle chain
+ * @param old_veh Old vehicle that will be sold
+ * @param new_head Head of the completely constructed new vehicle chain
+ */
+static void TransferCargo(Vehicle *old_veh, Vehicle *new_head)
+{
+ /* Loop through source parts */
+ for (Vehicle *src = old_veh; src != NULL; src = src->Next()) {
+ if (src->cargo_type >= NUM_CARGO || src->cargo.Count() == 0) continue;
+
+ /* Find free space in the new chain */
+ for (Vehicle *dest = new_head; dest != NULL && src->cargo.Count() > 0; dest = dest->Next()) {
+ if (dest->cargo_type != src->cargo_type) continue;
+
+ uint amount = min(src->cargo.Count(), dest->cargo_cap - dest->cargo.Count());
+ if (amount <= 0) continue;
+
+ src->cargo.MoveTo(&dest->cargo, amount);
+ }
+ }
+
+ /* Update train weight etc., the old vehicle will be sold anyway */
+ if (new_head->type == VEH_TRAIN) TrainConsistChanged(new_head, true);
+}
+
/**
* Tests whether refit orders that applied to v will also apply to the new vehicle type
* @param v The vehicle to be replaced
@@ -590,3 +615,368 @@ CommandCost MaybeReplaceVehicle(Vehicle *v, uint32 flags, bool display_costs)
return cost;
}
+
+/** Builds and refits a replacement vehicle
+ * Important: The old vehicle is still in the original vehicle chain (used for determining the cargo when the old vehicle did not carry anything, but the new one does)
+ * @param old_veh A single (articulated/multiheaded) vehicle that shall be replaced.
+ * @param new_vehicle Returns the newly build and refittet vehicle
+ * @return cost or error
+ */
+static CommandCost BuildReplacementVehicle(Vehicle *old_veh, Vehicle **new_vehicle)
+{
+ *new_vehicle = NULL;
+
+ /* Shall the vehicle be replaced? */
+ const Player *p = GetPlayer(_current_player);
+ EngineID e = GetNewEngineType(old_veh, p);
+ if (e == INVALID_ENGINE) return CommandCost(); // neither autoreplace is set, nor autorenew is triggered
+
+ /* Does it need to be refitted */
+ CargoID refit_cargo = GetNewCargoTypeForReplace(old_veh, e);
+ if (refit_cargo == CT_INVALID) return CommandCost(); // incompatible cargos
+
+ /* Build the new vehicle */
+ CommandCost cost = DoCommand(old_veh->tile, e, 0, DC_EXEC | DC_AUTOREPLACE, GetCmdBuildVeh(old_veh));
+ if (cost.Failed()) return cost;
+
+ Vehicle *new_veh = GetVehicle(_new_vehicle_id);
+ *new_vehicle = new_veh;
+
+ /* Refit the vehicle if needed */
+ if (refit_cargo != CT_NO_REFIT) {
+ cost.AddCost(DoCommand(0, new_veh->index, refit_cargo, DC_EXEC, GetCmdRefitVeh(new_veh)));
+ assert(cost.Succeeded()); // This should be ensured by GetNewCargoTypeForReplace()
+ }
+
+ /* Try to reverse the vehicle, but do not care if it fails as the new type might not be reversible */
+ if (new_veh->type == VEH_TRAIN && HasBit(old_veh->u.rail.flags, VRF_REVERSE_DIRECTION)) {
+ DoCommand(0, new_veh->index, true, DC_EXEC, CMD_REVERSE_TRAIN_DIRECTION);
+ }
+
+ return cost;
+}
+
+/** Issue a start/stop command
+ * @param v a vehicle
+ * @param evaluate_callback shall the start/stop callback be evaluated?
+ * @return success or error
+ */
+static inline CommandCost StartStopVehicle(const Vehicle *v, bool evaluate_callback)
+{
+ return DoCommand(0, v->index, evaluate_callback ? 1 : 0, DC_EXEC | DC_AUTOREPLACE, CMD_START_STOP_VEHICLE);
+}
+
+/** Issue a train vehicle move command
+ * @param v The vehicle to move
+ * @param after The vehicle to insert 'v' after, or NULL to start new chain
+ * @param whole_chain move all vehicles following 'v' (true), or only 'v' (false)
+ * @return success or error
+ */
+static inline CommandCost MoveVehicle(const Vehicle *v, const Vehicle *after, uint32 flags, bool whole_chain)
+{
+ return DoCommand(0, v->index | (after != NULL ? after->index : INVALID_VEHICLE) << 16, whole_chain ? 1 : 0, flags, CMD_MOVE_RAIL_VEHICLE);
+}
+
+/** Copy head specific things to the new vehicle chain after it was successfully constructed
+ * @param old_head The old front vehicle (no wagons attached anymore)
+ * @param new_head The new head of the completely replaced vehicle chain
+ * @param flags the command flags to use
+ */
+static CommandCost CopyHeadSpecificThings(Vehicle *old_head, Vehicle *new_head, uint32 flags)
+{
+ CommandCost cost = CommandCost();
+
+ /* Share orders */
+ if (cost.Succeeded() && old_head != new_head) cost.AddCost(DoCommand(0, (old_head->index << 16) | new_head->index, CO_SHARE, DC_EXEC, CMD_CLONE_ORDER));
+
+ /* Copy group membership */
+ if (cost.Succeeded() && old_head != new_head) cost.AddCost(DoCommand(0, old_head->group_id, new_head->index, DC_EXEC, CMD_ADD_VEHICLE_GROUP));
+
+ /* Perform start/stop check whether the new vehicle suits newgrf restrictions etc. */
+ if (cost.Succeeded()) {
+ /* Start the vehicle, might be denied by certain things */
+ assert((new_head->vehstatus & VS_STOPPED) != 0);
+ cost.AddCost(StartStopVehicle(new_head, true));
+
+ /* Stop the vehicle again, but do not care about evil newgrfs allowing starting but not stopping :p */
+ if (cost.Succeeded()) cost.AddCost(StartStopVehicle(new_head, false));
+ }
+
+ /* Last do those things which do never fail (resp. we do not care about), but which are not undo-able */
+ if (cost.Succeeded() && old_head != new_head && (flags & DC_EXEC) != 0) {
+ /* Copy vehicle name */
+ if (old_head->name != NULL) {
+ _cmd_text = old_head->name;
+ DoCommand(0, new_head->index, 0, DC_EXEC | DC_AUTOREPLACE, CMD_NAME_VEHICLE);
+ _cmd_text = NULL;
+ }
+
+ /* Copy other things which cannot be copied by a command and which shall not stay resetted from the build vehicle command */
+ new_head->CopyVehicleConfigAndStatistics(old_head);
+
+ /* Switch vehicle windows to the new vehicle, so they are not closed when the old vehicle is sold */
+ ChangeVehicleViewWindow(old_head->index, new_head->index);
+ }
+
+ 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, uint32 flags, bool wagon_removal, bool *nothing_to_do)
+{
+ Vehicle *old_head = *chain;
+
+ 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 = (old_head->u.rail.cached_total_length + TILE_SIZE - 1) / TILE_SIZE * TILE_SIZE;
+
+ int num_units = 0; ///< Number of units in the chain
+ for (Vehicle *w = old_head; w != NULL; w = GetNextUnit(w)) num_units++;
+
+ Vehicle **old_vehs = CallocT<Vehicle *>(num_units); ///< Will store vehicles of the old chain in their order
+ Vehicle **new_vehs = CallocT<Vehicle *>(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;
+ Vehicle *w;
+ for (w = old_head, i = 0; w != NULL; w = GetNextUnit(w), i++) {
+ assert(i < num_units);
+ old_vehs[i] = w;
+
+ CommandCost ret = BuildReplacementVehicle(old_vehs[i], &new_vehs[i]);
+ cost.AddCost(ret);
+ if (cost.Failed()) break;
+
+ new_costs[i] = ret.GetCost();
+ if (new_vehs[i] != NULL) *nothing_to_do = false;
+ }
+ Vehicle *new_head = (new_vehs[0] != NULL ? new_vehs[0] : old_vehs[0]);
+
+ /* Separate the head, so we can start constructing the new chain */
+ if (cost.Succeeded()) {
+ Vehicle *second = GetNextUnit(old_head);
+ if (second != NULL) cost.AddCost(MoveVehicle(second, NULL, DC_EXEC | DC_AUTOREPLACE, true));
+
+ assert(GetNextUnit(new_head) == 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.
+ * OTOH the vehicle attach callback is more expensive this way :s */
+ Vehicle *last_engine = NULL; ///< Shall store the last engine unit after this step
+ if (cost.Succeeded()) {
+ for (int i = num_units - 1; i > 0; i--) {
+ Vehicle *append = (new_vehs[i] != NULL ? new_vehs[i] : old_vehs[i]);
+
+ if (RailVehInfo(append->engine_type)->railveh_type == RAILVEH_WAGON) continue;
+
+ if (last_engine == NULL) last_engine = append;
+ cost.AddCost(MoveVehicle(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->u.rail.cached_total_length > old_total_length) cost = CommandCost(STR_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 = MoveVehicle(append, last_engine, DC_EXEC, false);
+
+ if (res.Succeeded() && wagon_removal && new_head->u.rail.cached_total_length > old_total_length) {
+ MoveVehicle(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 = GetPrevUnit(last_engine);
+ }
+ }
+ }
+
+ /* Sell superfluous new vehicles that could not be inserted. */
+ if (cost.Succeeded() && wagon_removal) {
+ 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);
+
+ cost.AddCost(DoCommand(0, w->index, 0, flags, GetCmdSellVeh(w)));
+ if ((flags & DC_EXEC) != 0) {
+ old_vehs[i] = NULL;
+ if (i == 0) old_head = NULL;
+ }
+ }
+ }
+
+ /* If we are not in DC_EXEC undo everything */
+ if ((flags & DC_EXEC) == 0) {
+ /* Separate the head, so we can reattach the old vehicles */
+ Vehicle *second = GetNextUnit(old_head);
+ if (second != NULL) MoveVehicle(second, NULL, DC_EXEC | DC_AUTOREPLACE, true);
+
+ assert(GetNextUnit(old_head) == NULL);
+
+ /* Rearrange old vehicles and sell new
+ * 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 :) */
+
+ for (int i = num_units - 1; i >= 0; i--) {
+ if (i > 0) {
+ CommandCost ret = MoveVehicle(old_vehs[i], old_head, DC_EXEC | DC_AUTOREPLACE, false);
+ assert(ret.Succeeded());
+ }
+ 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));
+
+ /* 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);
+ *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;
+}
+
+/** Autoreplace a vehicles
+ * @param tile not used
+ * @param flags type of operation
+ * @param p1 Index of vehicle
+ * @param p2 not used
+ */
+CommandCost CmdAutoreplaceVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
+{
+ CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, 0);
+ bool nothing_to_do = true;
+
+ if (!IsValidVehicleID(p1)) return CMD_ERROR;
+ Vehicle *v = GetVehicle(p1);
+ if (!CheckOwnership(v->owner)) return CMD_ERROR;
+ if (!v->IsInDepot()) return CMD_ERROR;
+ if (HASBITS(v->vehstatus, VS_CRASHED)) return CMD_ERROR;
+
+ const Player *p = GetPlayer(_current_player);
+ bool wagon_removal = p->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 && !any_replacements) {
+ any_replacements = (GetNewEngineType(w, p) != INVALID_ENGINE);
+ w = (w->type == VEH_TRAIN ? GetNextUnit(w) : NULL);
+ }
+
+ if (any_replacements) {
+ bool was_stopped = (v->vehstatus & VS_STOPPED) != 0;
+
+ /* Stop the vehicle */
+ if (!was_stopped) cost.AddCost(StartStopVehicle(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);
+ cost.AddCost(ReplaceChain(&v, flags & ~DC_EXEC, wagon_removal, &nothing_to_do));
+ RestoreRandomSeeds(saved_seeds);
+
+ if (cost.Succeeded() && (flags & DC_EXEC) != 0) {
+ CommandCost ret = ReplaceChain(&v, flags, wagon_removal, &nothing_to_do);
+ assert(ret.Succeeded() && ret.GetCost() == cost.GetCost());
+ }
+
+ /* Restart the vehicle */
+ if (!was_stopped) cost.AddCost(StartStopVehicle(v, false));
+ }
+
+ if (cost.Succeeded() && nothing_to_do) cost = CommandCost(STR_AUTOREPLACE_NOTHING_TO_DO);
+ return cost;
+}