(svn r14083) -Fix [FS#1264, FS#2037, FS#2038, FS#2110]: Rewrite the autoreplace kernel.

pull/155/head
frosch 16 years ago
parent 302cdf69a4
commit 92f3c368b8

@ -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;
}

@ -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 */

@ -100,6 +100,19 @@ public:
if (this->message != INVALID_STRING_ID) _error_message = this->message;
}
/**
* 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)
};
/**

@ -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__)

@ -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

@ -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();

@ -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

@ -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);

@ -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;
/**

@ -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);

Loading…
Cancel
Save