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 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) (limited to 'src/autoreplace_cmd.cpp') 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; +} -- cgit v1.2.3-54-g00ecf