From f7826f8a372d3120cc9d56e4d1c11df3e2fd577c Mon Sep 17 00:00:00 2001 From: frosch Date: Sat, 16 Aug 2008 14:02:20 +0000 Subject: (svn r14083) -Fix [FS#1264, FS#2037, FS#2038, FS#2110]: Rewrite the autoreplace kernel. --- src/autoreplace_cmd.cpp | 390 +++++++++++++++++++++++++++++++++++++++++++++++ src/command.cpp | 2 + src/command_type.h | 16 +- src/core/random_func.hpp | 24 +++ src/lang/english.txt | 10 +- src/train.h | 27 ++++ src/train_cmd.cpp | 16 +- src/vehicle.cpp | 62 +++++++- src/vehicle_base.h | 23 +++ src/vehicle_func.h | 1 - 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(num_units); ///< Will store vehicles of the old chain in their order + Vehicle **new_vehs = CallocT(num_units); ///< New vehicles corresponding to old_vehs or NULL if no replacement + Money *new_costs = MallocT(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, ¬hing_to_do)); + RestoreRandomSeeds(saved_seeds); + + if (cost.Succeeded() && (flags & DC_EXEC) != 0) { + CommandCost ret = ReplaceChain(&v, flags, wagon_removal, ¬hing_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 @@ -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) }; /** 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); -- cgit v1.2.3-70-g09d2