mirror of https://github.com/OpenTTD/OpenTTD.git
(svn r14083) -Fix [FS#1264, FS#2037, FS#2038, FS#2110]: Rewrite the autoreplace kernel.
This commit is contained in:
parent
56459cab81
commit
f7826f8a37
|
@ -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, ¬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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
27
src/train.h
27
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();
|
||||
|
||||
|
|
|
@ -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…
Reference in New Issue