summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/autoreplace_cmd.cpp390
-rw-r--r--src/command.cpp2
-rw-r--r--src/command_type.h16
-rw-r--r--src/core/random_func.hpp24
-rw-r--r--src/lang/english.txt10
-rw-r--r--src/train.h27
-rw-r--r--src/train_cmd.cpp16
-rw-r--r--src/vehicle.cpp62
-rw-r--r--src/vehicle_base.h23
-rw-r--r--src/vehicle_func.h1
10 files changed, 550 insertions, 21 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;
+}
diff --git a/src/command.cpp b/src/command.cpp
index fde4424a5..b94146e2a 100644
--- a/src/command.cpp
+++ b/src/command.cpp
@@ -179,6 +179,7 @@ DEF_COMMAND(CmdSetAutoReplace);
DEF_COMMAND(CmdCloneVehicle);
DEF_COMMAND(CmdStartStopVehicle);
DEF_COMMAND(CmdMassStartStopVehicle);
+DEF_COMMAND(CmdAutoreplaceVehicle);
DEF_COMMAND(CmdDepotSellAllVehicles);
DEF_COMMAND(CmdDepotMassAutoReplace);
@@ -326,6 +327,7 @@ static const Command _command_proc_table[] = {
{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 */
diff --git a/src/command_type.h b/src/command_type.h
index f3e4116a7..0637673cf 100644
--- a/src/command_type.h
+++ b/src/command_type.h
@@ -101,6 +101,19 @@ public:
}
/**
+ * 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 @@ enum {
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 @@ enum {
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)
};
/**
diff --git a/src/core/random_func.hpp b/src/core/random_func.hpp
index d77d38906..82f751003 100644
--- a/src/core/random_func.hpp
+++ b/src/core/random_func.hpp
@@ -52,6 +52,30 @@ struct Randomizer {
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__)
diff --git a/src/lang/english.txt b/src/lang/english.txt
index 2bf550160..b2a2d297f 100644
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -1013,11 +1013,13 @@ STR_AIRCRAFT_HAS_DUPLICATE_ENTRY :{WHITE}Aircraft
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
diff --git a/src/train.h b/src/train.h
index 0e92a962c..22ed95b04 100644
--- a/src/train.h
+++ b/src/train.h
@@ -247,6 +247,20 @@ static inline Vehicle *GetNextVehicle(const Vehicle *v)
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 @@ static inline Vehicle *GetNextUnit(Vehicle *v)
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();
diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp
index 327e9c01a..29b907654 100644
--- a/src/train_cmd.cpp
+++ b/src/train_cmd.cpp
@@ -1011,6 +1011,7 @@ static void NormaliseTrainConsist(Vehicle *v)
/** 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,12 +1070,13 @@ CommandCost CmdMoveRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p
/* 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();
- {
- 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 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 the destination row if the source and destination aren't the same. */
if (src_head != dst_head) {
@@ -1112,7 +1114,7 @@ CommandCost CmdMoveRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p
/* 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 @@ CommandCost CmdMoveRailVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p
* 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
diff --git a/src/vehicle.cpp b/src/vehicle.cpp
index 1654fd177..79e72e71a 100644
--- a/src/vehicle.cpp
+++ b/src/vehicle.cpp
@@ -717,7 +717,47 @@ void CallVehicleTicks()
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 @@ void AgeVehicle(Vehicle *v)
* @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 @@ CommandCost CmdStartStopVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32
/* 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 @@ CommandCost CmdStartStopVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32
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 @@ CommandCost CmdDepotMassAutoReplace(TileIndex tile, uint32 flags, uint32 p1, uin
/* 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 @@ CommandCost CmdDepotMassAutoReplace(TileIndex tile, uint32 flags, uint32 p1, uin
}
}
- 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 @@ CommandCost CmdNameVehicle(TileIndex tile, uint32 flags, uint32 p1, uint32 p2)
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);
diff --git a/src/vehicle_base.h b/src/vehicle_base.h
index 440f30d1d..f7d646f9b 100644
--- a/src/vehicle_base.h
+++ b/src/vehicle_base.h
@@ -483,6 +483,29 @@ public:
*/
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;
/**
diff --git a/src/vehicle_func.h b/src/vehicle_func.h
index 89862b8e7..ce0c7407f 100644
--- a/src/vehicle_func.h
+++ b/src/vehicle_func.h
@@ -71,7 +71,6 @@ Money GetTrainRunningCost(const Vehicle *v);
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);