changeset 9928:0cc9c6c36c43 draft

(svn r14083) -Fix [FS#1264, FS#2037, FS#2038, FS#2110]: Rewrite the autoreplace kernel.
author frosch <frosch@openttd.org>
date Sat, 16 Aug 2008 14:02:20 +0000
parents 70dfec1a8501
children 12d55b8b7630
files src/autoreplace_cmd.cpp src/command.cpp src/command_type.h src/core/random_func.hpp src/lang/english.txt src/train.h src/train_cmd.cpp src/vehicle.cpp src/vehicle_base.h src/vehicle_func.h
diffstat 10 files changed, 550 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/src/autoreplace_cmd.cpp
+++ b/src/autoreplace_cmd.cpp
@@ -124,6 +124,31 @@
 }
 
 
+/** 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 @@
 
 	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;
+}
--- a/src/command.cpp
+++ b/src/command.cpp
@@ -179,6 +179,7 @@
 DEF_COMMAND(CmdCloneVehicle);
 DEF_COMMAND(CmdStartStopVehicle);
 DEF_COMMAND(CmdMassStartStopVehicle);
+DEF_COMMAND(CmdAutoreplaceVehicle);
 DEF_COMMAND(CmdDepotSellAllVehicles);
 DEF_COMMAND(CmdDepotMassAutoReplace);
 
@@ -326,6 +327,7 @@
 	{CmdCloneVehicle,                        0}, /* CMD_CLONE_VEHICLE */
 	{CmdStartStopVehicle,                    0}, /* CMD_START_STOP_VEHICLE */
 	{CmdMassStartStopVehicle,                0}, /* CMD_MASS_START_STOP */
+	{CmdAutoreplaceVehicle,                  0}, /* CMD_AUTOREPLACE_VEHICLE */
 	{CmdDepotSellAllVehicles,                0}, /* CMD_DEPOT_SELL_ALL_VEHICLES */
 	{CmdDepotMassAutoReplace,                0}, /* CMD_DEPOT_MASS_AUTOREPLACE */
 	{CmdCreateGroup,                         0}, /* CMD_CREATE_GROUP */
--- a/src/command_type.h
+++ b/src/command_type.h
@@ -101,6 +101,19 @@
 	}
 
 	/**
+	 * Returns the error message of a command
+	 * @return the error message, if succeeded INVALID_STRING_ID
+	 */
+	StringID GetErrorMessage() const
+	{
+		extern StringID _error_message;
+
+		if (this->success) return INVALID_STRING_ID;
+		if (this->message != INVALID_STRING_ID) return this->message;
+		return _error_message;
+	}
+
+	/**
 	 * Did this command succeed?
 	 * @return true if and only if it succeeded
 	 */
@@ -259,6 +272,7 @@
 	CMD_CLONE_VEHICLE,                ///< clone a vehicle
 	CMD_START_STOP_VEHICLE,           ///< start or stop a vehicle
 	CMD_MASS_START_STOP,              ///< start/stop all vehicles (in a depot)
+	CMD_AUTOREPLACE_VEHICLE,          ///< replace/renew a vehicle while it is in a depot
 	CMD_DEPOT_SELL_ALL_VEHICLES,      ///< sell all vehicles which are in a given depot
 	CMD_DEPOT_MASS_AUTOREPLACE,       ///< force the autoreplace to take action in a given depot
 
@@ -290,7 +304,7 @@
 	DC_AI_BUILDING     = 0x020, ///< special building rules for AI
 	DC_NO_TOWN_RATING  = 0x040, ///< town rating does not disallow you from building
 	DC_BANKRUPT        = 0x080, ///< company bankrupts, skip money check, skip vehicle on tile check in some cases
-	DC_AUTOREPLACE     = 0x100, ///< autoreplace/autorenew is in progress
+	DC_AUTOREPLACE     = 0x100, ///< autoreplace/autorenew is in progress, this shall disable vehicle limits when building, and ignore certain restrictions when undoing things (like vehicle attach callback)
 };
 
 /**
--- a/src/core/random_func.hpp
+++ b/src/core/random_func.hpp
@@ -52,6 +52,30 @@
 extern Randomizer _random; ///< Random used in the game state calculations
 extern Randomizer _interactive_random; ///< Random used every else where is does not (directly) influence the game state
 
+/** Stores the state of all random number generators */
+struct SavedRandomSeeds {
+	Randomizer random;
+	Randomizer interactive_random;
+};
+
+/** Saves the current seeds
+ * @param storage Storage for saving
+ */
+static inline void SaveRandomSeeds(SavedRandomSeeds *storage)
+{
+	storage->random = _random;
+	storage->interactive_random = _interactive_random;
+}
+
+/** Restores previously saved seeds
+ * @param storage Storage where SaveRandomSeeds() stored th seeds
+ */
+static inline void RestoreRandomSeeds(const SavedRandomSeeds &storage)
+{
+	_random = storage.random;
+	_interactive_random = storage.interactive_random;
+}
+
 void SetRandomSeed(uint32 seed);
 #ifdef RANDOM_DEBUG
 	#define Random() DoRandom(__LINE__, __FILE__)
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -1013,11 +1013,13 @@
 STR_AIRCRAFT_HAS_INVALID_ENTRY                                  :{WHITE}Aircraft {COMMA} has an invalid station in its orders
 # end of order system
 
-STR_TRAIN_AUTORENEW_FAILED                                      :{WHITE}Autorenew failed on train {COMMA} (money limit)
-STR_ROADVEHICLE_AUTORENEW_FAILED                                :{WHITE}Autorenew failed on road vehicle {COMMA} (money limit)
-STR_SHIP_AUTORENEW_FAILED                                       :{WHITE}Autorenew failed on ship {COMMA} (money limit)
-STR_AIRCRAFT_AUTORENEW_FAILED                                   :{WHITE}Autorenew failed on aircraft {COMMA} (money limit)
+STR_TRAIN_AUTORENEW_FAILED                                      :{WHITE}Autorenew failed on train {COMMA}{}{STRING}
+STR_ROADVEHICLE_AUTORENEW_FAILED                                :{WHITE}Autorenew failed on road vehicle {COMMA}{}{STRING}
+STR_SHIP_AUTORENEW_FAILED                                       :{WHITE}Autorenew failed on ship {COMMA}{}{STRING}
+STR_AIRCRAFT_AUTORENEW_FAILED                                   :{WHITE}Autorenew failed on aircraft {COMMA}{}{STRING}
 STR_TRAIN_TOO_LONG_AFTER_REPLACEMENT                            :{WHITE}Train {COMMA} is too long after replacement
+STR_AUTOREPLACE_NOTHING_TO_DO                                   :{WHITE}No autoreplace/renew rules applied.
+STR_AUTOREPLACE_MONEY_LIMIT                                     :(money limit)
 
 STR_CONFIG_PATCHES                                              :{BLACK}Configure Patches
 STR_CONFIG_PATCHES_TIP                                          :{BLACK}Configure the patches
--- a/src/train.h
+++ b/src/train.h
@@ -247,6 +247,20 @@
 	return v->Next();
 }
 
+/** Get the previous real (non-articulated part) vehicle in the consist.
+ * @param w Vehicle.
+ * @return Previous vehicle in the consist.
+ */
+static inline Vehicle *GetPrevVehicle(const Vehicle *w)
+{
+	assert(w->type == VEH_TRAIN);
+
+	Vehicle *v = w->Previous();
+	while (v != NULL && IsArticulatedPart(v)) v = v->Previous();
+
+	return v;
+}
+
 /** Get the next real (non-articulated part and non rear part of dualheaded engine) vehicle in the consist.
  * @param v Vehicle.
  * @return Next vehicle in the consist.
@@ -260,6 +274,19 @@
 	return v;
 }
 
+/** Get the previous real (non-articulated part and non rear part of dualheaded engine) vehicle in the consist.
+ * @param v Vehicle.
+ * @return Previous vehicle in the consist.
+ */
+static inline Vehicle *GetPrevUnit(Vehicle *v)
+{
+	assert(v->type == VEH_TRAIN);
+	v = GetPrevVehicle(v);
+	if (v != NULL && IsRearDualheaded(v)) v = GetPrevVehicle(v);
+
+	return v;
+}
+
 void ConvertOldMultiheadToNew();
 void ConnectMultiheadedTrains();
 
--- a/src/train_cmd.cpp
+++ b/src/train_cmd.cpp
@@ -1011,6 +1011,7 @@
 /** Move a rail vehicle around inside the depot.
  * @param tile unused
  * @param flags type of operation
+ *              Note: DC_AUTOREPLACE is set when autoreplace tries to undo its modifications or moves vehicles to temporary locations inside the depot.
  * @param p1 various bitstuffed elements
  * - p1 (bit  0 - 15) source vehicle index
  * - p1 (bit 16 - 31) what wagon to put the source wagon AFTER, XXX - INVALID_VEHICLE to make a new line
@@ -1069,13 +1070,14 @@
 	/* when moving all wagons, we can't have the same src_head and dst_head */
 	if (HasBit(p2, 0) && src_head == dst_head) return CommandCost();
 
-	{
+	/* check if all vehicles in the source train are stopped inside a depot. */
+	int src_len = CheckTrainStoppedInDepot(src_head);
+	if (src_len < 0) return_cmd_error(STR_881A_TRAINS_CAN_ONLY_BE_ALTERED);
+
+	if ((flags & DC_AUTOREPLACE) == 0) {
+		/* Check whether there are more than 'max_len' train units (articulated parts and rear heads do not count) in the new chain */
 		int max_len = _settings_game.vehicle.mammoth_trains ? 100 : 10;
 
-		/* check if all vehicles in the source train are stopped inside a depot. */
-		int src_len = CheckTrainStoppedInDepot(src_head);
-		if (src_len < 0) return_cmd_error(STR_881A_TRAINS_CAN_ONLY_BE_ALTERED);
-
 		/* check the destination row if the source and destination aren't the same. */
 		if (src_head != dst_head) {
 			int dst_len = 0;
@@ -1112,7 +1114,7 @@
 
 	/* moving a loco to a new line?, then we need to assign a unitnumber. */
 	if (dst == NULL && !IsFrontEngine(src) && IsTrainEngine(src)) {
-		UnitID unit_num = GetFreeUnitNumber(VEH_TRAIN);
+		UnitID unit_num = ((flags & DC_AUTOREPLACE) != 0 ? 0 : GetFreeUnitNumber(VEH_TRAIN));
 		if (unit_num > _settings_game.vehicle.max_trains)
 			return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME);
 
@@ -1137,7 +1139,7 @@
 	 * one 'attaches' an already 'attached' vehicle causing more trouble
 	 * than it actually solves (infinite loops and such).
 	 */
-	if (dst_head != NULL && !src_in_dst) {
+	if (dst_head != NULL && !src_in_dst && (flags & DC_AUTOREPLACE) == 0) {
 		/*
 		 * When performing the 'allow wagon attach' callback, we have to check
 		 * that for each and every wagon, not only the first one. This means
--- a/src/vehicle.cpp
+++ b/src/vehicle.cpp
@@ -717,7 +717,47 @@
 				v->leave_depot_instantly = false;
 				v->vehstatus &= ~VS_STOPPED;
 			}
-			MaybeReplaceVehicle(v, DC_EXEC, true);
+
+			/* Store the position of the effect as the vehicle pointer will become invalid later */
+			int x = v->x_pos;
+			int y = v->y_pos;
+			int z = v->z_pos;
+
+			const Player *p = GetPlayer(_current_player);
+			SubtractMoneyFromPlayer(CommandCost(EXPENSES_NEW_VEHICLES, (Money)p->engine_renew_money));
+			CommandCost res = DoCommand(0, v->index, 0, DC_EXEC, CMD_AUTOREPLACE_VEHICLE);
+			if (res.Succeeded()) v = NULL; // no longer valid
+			SubtractMoneyFromPlayer(CommandCost(EXPENSES_NEW_VEHICLES, -(Money)p->engine_renew_money));
+
+			if (IsLocalPlayer()) {
+				if (res.Succeeded()) {
+					ShowCostOrIncomeAnimation(x, y, z, res.GetCost());
+				} else {
+					StringID error_message = res.GetErrorMessage();
+
+					if (error_message != STR_AUTOREPLACE_NOTHING_TO_DO && error_message != INVALID_STRING_ID) {
+						if (error_message == STR_0003_NOT_ENOUGH_CASH_REQUIRES) error_message = STR_AUTOREPLACE_MONEY_LIMIT;
+
+						StringID message;
+						if (error_message == STR_TRAIN_TOO_LONG_AFTER_REPLACEMENT) {
+							message = error_message;
+						} else {
+							switch (v->type) {
+								case VEH_TRAIN:    message = STR_TRAIN_AUTORENEW_FAILED;       break;
+								case VEH_ROAD:     message = STR_ROADVEHICLE_AUTORENEW_FAILED; break;
+								case VEH_SHIP:     message = STR_SHIP_AUTORENEW_FAILED;        break;
+								case VEH_AIRCRAFT: message = STR_AIRCRAFT_AUTORENEW_FAILED;    break;
+								default: NOT_REACHED();
+							}
+						}
+
+						SetDParam(0, v->unitnumber);
+						SetDParam(1, error_message);
+						AddNewsItem(message, NS_ADVICE, v->index, 0);
+					}
+				}
+			}
+
 			v = w;
 		}
 		_current_player = OWNER_NONE;
@@ -982,11 +1022,14 @@
  * @param tile unused
  * @param flags type of operation
  * @param p1 vehicle to start/stop
- * @param p2 unused
+ * @param p2 bit 0: Shall the start/stop newgrf callback be evaluated (only valid with DC_AUTOREPLACE for network safety)
  * @return result of operation.  Nothing if everything went well
  */
 CommandCost CmdStartStopVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
 {
+	/* Disable the effect of p2 bit 0, when DC_AUTOREPLACE is not set */
+	if ((flags & DC_AUTOREPLACE) == 0) SetBit(p2, 0);
+
 	if (!IsValidVehicleID(p1)) return CMD_ERROR;
 
 	Vehicle *v = GetVehicle(p1);
@@ -1013,7 +1056,7 @@
 	/* Check if this vehicle can be started/stopped. The callback will fail or
 	 * return 0xFF if it can. */
 	uint16 callback = GetVehicleCallback(CBID_VEHICLE_START_STOP_CHECK, 0, 0, v->engine_type, v);
-	if (callback != CALLBACK_FAILED && GB(callback, 0, 8) != 0xFF) {
+	if (callback != CALLBACK_FAILED && GB(callback, 0, 8) != 0xFF && HasBit(p2, 0)) {
 		StringID error = GetGRFStringID(GetEngineGRFID(v->engine_type), 0xD000 + callback);
 		return_cmd_error(error);
 	}
@@ -1033,7 +1076,7 @@
 			WC_AIRCRAFT_LIST,
 		};
 
-		if (v->IsStoppedInDepot()) DeleteVehicleNews(p1, vehicle_waiting_in_depot[v->type]);
+		if (v->IsStoppedInDepot() && (flags & DC_AUTOREPLACE) == 0) DeleteVehicleNews(p1, vehicle_waiting_in_depot[v->type]);
 
 		v->vehstatus ^= VS_STOPPED;
 		v->cur_speed = 0;
@@ -1153,18 +1196,21 @@
 	/* Get the list of vehicles in the depot */
 	BuildDepotVehicleList(vehicle_type, tile, &list, &list);
 
+	bool did_something = false;
+
 	for (uint i = 0; i < list.Length(); i++) {
 		Vehicle *v = (Vehicle*)list[i];
 
 		/* Ensure that the vehicle completely in the depot */
 		if (!v->IsInDepot()) continue;
 
-		CommandCost ret = MaybeReplaceVehicle(v, flags, false);
+		CommandCost ret = DoCommand(0, v->index, 0, flags, CMD_AUTOREPLACE_VEHICLE);
 
 		if (CmdSucceeded(ret)) {
+			did_something = true;
 			cost.AddCost(ret);
 		} else {
-			if (all_or_nothing) {
+			if (ret.GetErrorMessage() != STR_AUTOREPLACE_NOTHING_TO_DO && all_or_nothing) {
 				/* We failed to replace a vehicle even though we set all or nothing.
 				 * We should never reach this if DC_EXEC is set since then it should
 				 * have failed the estimation guess. */
@@ -1175,7 +1221,7 @@
 		}
 	}
 
-	if (cost.GetCost() == 0) {
+	if (!did_something) {
 		/* Either we didn't replace anything or something went wrong.
 		 * Either way we want to return an error and not execute this command. */
 		cost = CMD_ERROR;
@@ -1567,7 +1613,7 @@
 
 	if (!CheckOwnership(v->owner)) return CMD_ERROR;
 
-	if (!IsUniqueVehicleName(_cmd_text)) return_cmd_error(STR_NAME_MUST_BE_UNIQUE);
+	if ((flags & DC_AUTOREPLACE) == 0 && !IsUniqueVehicleName(_cmd_text)) return_cmd_error(STR_NAME_MUST_BE_UNIQUE);
 
 	if (flags & DC_EXEC) {
 		free(v->name);
--- a/src/vehicle_base.h
+++ b/src/vehicle_base.h
@@ -483,6 +483,29 @@
 	 */
 	inline bool IsOrderListShared() const { return this->next_shared != NULL || this->prev_shared != NULL; };
 
+	/**
+	 * Copy certain configurations and statistics of a vehicle after successful autoreplace/renew
+	 * The function shall copy everything that cannot be copied by a command (like orders / group etc),
+	 * and that shall not be resetted for the new vehicle.
+	 * @param src The old vehicle
+	 */
+	inline void CopyVehicleConfigAndStatistics(const Vehicle *src)
+	{
+		this->unitnumber = src->unitnumber;
+
+		this->cur_order_index = src->cur_order_index;
+		this->current_order = src->current_order;
+		this->dest_tile  = src->dest_tile;
+
+		this->profit_this_year = src->profit_this_year;
+		this->profit_last_year = src->profit_last_year;
+
+		this->current_order_time = src->current_order_time;
+		this->lateness_counter = src->lateness_counter;
+
+		this->service_interval = src->service_interval;
+	}
+
 	bool NeedsAutorenewing(const Player *p) const;
 
 	/**
--- a/src/vehicle_func.h
+++ b/src/vehicle_func.h
@@ -71,7 +71,6 @@
 CommandCost SendAllVehiclesToDepot(VehicleType type, uint32 flags, bool service, PlayerID owner, uint16 vlw_flag, uint32 id);
 void VehicleEnterDepot(Vehicle *v);
 
-CommandCost MaybeReplaceVehicle(Vehicle *v, uint32 flags, bool display_costs);
 bool CanBuildVehicleInfrastructure(VehicleType type);
 
 void CcCloneVehicle(bool success, TileIndex tile, uint32 p1, uint32 p2);