diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 65a53d95dd..6d41893bda 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -206,7 +206,11 @@ static CommandCost ReplaceVehicle(Vehicle **w, byte flags, Money total_cost, con /* Now we move the old one out of the train */ DoCommand(0, (INVALID_VEHICLE << 16) | old_v->index, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); /* Add the new vehicle */ - DoCommand(0, (front->index << 16) | new_v->index, 1, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); + CommandCost tmp_move = DoCommand(0, (front->index << 16) | new_v->index, 1, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); + if (CmdFailed(tmp_move)) { + cost.AddCost(tmp_move); + DoCommand(0, new_v->index, 1, DC_EXEC, GetCmdSellVeh(VEH_TRAIN)); + } } } else { // copy/clone the orders @@ -232,7 +236,11 @@ static CommandCost ReplaceVehicle(Vehicle **w, byte flags, Money total_cost, con } if (temp_v != NULL) { - DoCommand(0, (new_v->index << 16) | temp_v->index, 1, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); + CommandCost tmp_move = DoCommand(0, (new_v->index << 16) | temp_v->index, 1, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); + if (CmdFailed(tmp_move)) { + cost.AddCost(tmp_move); + DoCommand(0, temp_v->index, 1, DC_EXEC, GetCmdSellVeh(VEH_TRAIN)); + } } } } @@ -363,9 +371,10 @@ static EngineID GetNewEngineType(const Vehicle *v, const Player *p) CommandCost MaybeReplaceVehicle(Vehicle *v, uint32 flags, bool display_costs) { Vehicle *w; - const Player *p = GetPlayer(v->owner); + Player *p = GetPlayer(v->owner); CommandCost cost; bool stopped = false; + BackuppedVehicle backup(true); /* We only want "real" vehicle types. */ assert(IsPlayerBuildableVehicleType(v)); @@ -410,11 +419,14 @@ CommandCost MaybeReplaceVehicle(Vehicle *v, uint32 flags, bool display_costs) EngineID new_engine = GetNewEngineType(w, p); if (new_engine == INVALID_ENGINE) continue; + if (!backup.ContainsBackup()) { + /* We are going to try to replace a vehicle but we don't have any backup so we will make one. */ + backup.Backup(v, p); + } /* Now replace the vehicle */ - cost.AddCost(ReplaceVehicle(&w, flags, cost.GetCost(), p, new_engine)); + cost.AddCost(ReplaceVehicle(&w, DC_EXEC, cost.GetCost(), p, new_engine)); - if (flags & DC_EXEC && - (w->type != VEH_TRAIN || w->u.rail.first_engine == INVALID_ENGINE)) { + if (w->type != VEH_TRAIN || w->u.rail.first_engine == INVALID_ENGINE) { /* now we bought a new engine and sold the old one. We need to fix the * pointers in order to avoid pointing to the old one for trains: these * pointers should point to the front engine and not the cars @@ -423,18 +435,26 @@ CommandCost MaybeReplaceVehicle(Vehicle *v, uint32 flags, bool display_costs) } } while (w->type == VEH_TRAIN && (w = GetNextVehicle(w)) != NULL); + if (v->type == VEH_TRAIN && p->renew_keep_length) { + /* Remove wagons until the wanted length is reached */ + cost.AddCost(WagonRemoval(v, old_total_length)); + } + if (flags & DC_QUERY_COST || cost.GetCost() == 0) { /* We didn't do anything during the replace so we will just exit here */ + v = backup.Restore(v); if (stopped) v->vehstatus &= ~VS_STOPPED; return cost; } - if (display_costs && !(flags & DC_EXEC)) { + if (display_costs) { /* We want to ensure that we will not get below p->engine_renew_money. * We will not actually pay this amount. It's for display and checks only. */ - cost.AddCost((Money)p->engine_renew_money); - if (CmdSucceeded(cost) && GetAvailableMoneyForCommand() < cost.GetCost()) { + CommandCost tmp = cost; + tmp.AddCost((Money)p->engine_renew_money); + if (CmdSucceeded(tmp) && GetAvailableMoneyForCommand() < tmp.GetCost()) { /* We don't have enough money so we will set cost to failed */ + cost.AddCost((Money)p->engine_renew_money); cost.AddCost(CMD_ERROR); } } @@ -457,15 +477,12 @@ CommandCost MaybeReplaceVehicle(Vehicle *v, uint32 flags, bool display_costs) } } - if (flags & DC_EXEC && CmdSucceeded(cost)) { - if (v->type == VEH_TRAIN && p->renew_keep_length) { - /* Remove wagons until the wanted length is reached */ - cost.AddCost(WagonRemoval(v, old_total_length)); - } + if (display_costs && IsLocalPlayer() && (flags & DC_EXEC) && CmdSucceeded(cost)) { + ShowCostOrIncomeAnimation(v->x_pos, v->y_pos, v->z_pos, cost.GetCost()); + } - if (display_costs && IsLocalPlayer()) { - ShowCostOrIncomeAnimation(v->x_pos, v->y_pos, v->z_pos, cost.GetCost()); - } + if (!(flags & DC_EXEC) || CmdFailed(cost)) { + v = backup.Restore(v); } /* Start the vehicle if we stopped it earlier */ diff --git a/src/command.cpp b/src/command.cpp index 7bf1b8cba6..26c380cecf 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -542,16 +542,12 @@ bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, CommandCallback *callback, * fact will trigger an assertion failure. --pasky * CMD_CLONE_VEHICLE: Both building new vehicles and refitting them can be * influenced by newgrf callbacks, which makes it impossible to accurately - * estimate the cost of cloning a vehicle. - * CMD_DEPOT_MASS_AUTOREPLACE: we can't predict wagon removal so - * the test will not include income from any sold wagons. - * This means that the costs can sometimes be lower than estimated. */ + * estimate the cost of cloning a vehicle. */ notest = (cmd & 0xFF) == CMD_CLEAR_AREA || (cmd & 0xFF) == CMD_LEVEL_LAND || (cmd & 0xFF) == CMD_REMOVE_LONG_ROAD || - (cmd & 0xFF) == CMD_CLONE_VEHICLE || - (cmd & 0xFF) == CMD_DEPOT_MASS_AUTOREPLACE; + (cmd & 0xFF) == CMD_CLONE_VEHICLE; _docommand_recursive = 1; diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 59fb9a01f8..f9f849a013 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -1233,7 +1233,7 @@ void BackupVehicleOrders(const Vehicle *v, BackuppedOrders *bak) /* Copy the orders */ FOR_VEHICLE_ORDERS(v, order) { - *dest = *order; + memcpy(dest, order, sizeof(Order)); dest++; } /* End the list with an empty order */ @@ -1285,6 +1285,42 @@ void RestoreVehicleOrders(const Vehicle *v, const BackuppedOrders *bak) DoCommandP(0, bak->group, v->index, NULL, CMD_ADD_VEHICLE_GROUP); } +/** Restores vehicle orders that was previously backed up by BackupVehicleOrders() + * This will restore to the point where it was at the time of the backup meaning + * it will presume the same order indexes can be used. + * This is needed when restoring a backed up vehicle + * @param v The vehicle that should gain the orders + * @param bak the backup of the orders + */ +void RestoreVehicleOrdersBruteForce(Vehicle *v, const BackuppedOrders *bak) +{ + if (bak->name != NULL) { + /* Restore the name. */ + v->name = strdup(bak->name); + } + + /* If we had shared orders, recover that */ + if (bak->clone != INVALID_VEHICLE) { + /* We will place it at the same location in the linked list as it previously was. */ + if (v->prev_shared != NULL) { + assert(v->prev_shared->next_shared == v->next_shared); + v->prev_shared->next_shared = v; + } + if (v->next_shared != NULL) { + assert(v->next_shared->prev_shared == v->prev_shared); + v->next_shared->prev_shared = v; + } + } else { + /* Restore the orders at the indexes they originally were. */ + for (Order *order = bak->order; order->IsValid(); order++) { + Order *dst = GetOrder(order->index); + /* Since we are restoring something we removed a moment ago all the orders should be free. */ + assert(!dst->IsValid()); + memcpy(dst, order, sizeof(Order)); + } + } +} + /** Restore the current order-index of a vehicle and sets service-interval. * @param tile unused * @param flags operation to perform diff --git a/src/order_func.h b/src/order_func.h index eec917b4f5..8d145da935 100644 --- a/src/order_func.h +++ b/src/order_func.h @@ -28,6 +28,7 @@ extern BackuppedOrders _backup_orders_data; void BackupVehicleOrders(const Vehicle *v, BackuppedOrders *order = &_backup_orders_data); void RestoreVehicleOrders(const Vehicle *v, const BackuppedOrders *order = &_backup_orders_data); +void RestoreVehicleOrdersBruteForce(Vehicle *v, const BackuppedOrders *bak); /* Functions */ void RemoveOrderFromAllVehicles(OrderType type, DestinationID destination); diff --git a/src/player_base.h b/src/player_base.h index 2bb3fd56c8..cce41eb600 100644 --- a/src/player_base.h +++ b/src/player_base.h @@ -73,6 +73,18 @@ struct Player { uint16 num_engines[TOTAL_NUM_ENGINES]; ///< caches the number of engines of each type the player owns (no need to save this) }; +struct PlayerMoneyBackup { +private: + Money backup_yearly_expenses[EXPENSES_END]; + PlayerEconomyEntry backup_cur_economy; + Player *p; + +public: + PlayerMoneyBackup(Player *player); + + void Restore(); +}; + extern Player _players[MAX_PLAYERS]; #define FOR_ALL_PLAYERS(p) for (p = _players; p != endof(_players); p++) diff --git a/src/players.cpp b/src/players.cpp index fc90dc2fb4..f4cd472f8a 100644 --- a/src/players.cpp +++ b/src/players.cpp @@ -208,6 +208,24 @@ bool CheckPlayerHasMoney(CommandCost cost) return true; } +/** Backs up current economic data for a player + */ +PlayerMoneyBackup::PlayerMoneyBackup(Player *player) +{ + p = player; + memcpy(backup_yearly_expenses, p->yearly_expenses, EXPENSES_END * sizeof(Money)); + backup_cur_economy = p->cur_economy; +} + +/** Restore the economic data from last backup + * This should only be used right after Player::BackupEconomy() + */ +void PlayerMoneyBackup::Restore() +{ + memcpy(p->yearly_expenses, backup_yearly_expenses, EXPENSES_END * sizeof(Money)); + p->cur_economy = backup_cur_economy; +} + static void SubtractMoneyFromAnyPlayer(Player *p, CommandCost cost) { if (cost.GetCost() == 0) return; diff --git a/src/vehicle.cpp b/src/vehicle.cpp index ec4908669a..03351b149c 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -712,12 +712,7 @@ void CallVehicleTicks() v->leave_depot_instantly = false; v->vehstatus &= ~VS_STOPPED; } - - CommandCost cost = MaybeReplaceVehicle(v, 0, true); - if (CmdSucceeded(cost) && cost.GetCost() != 0) { - /* Looks like we can replace this vehicle so we go ahead and do so */ - MaybeReplaceVehicle(v, DC_EXEC, true); - } + MaybeReplaceVehicle(v, DC_EXEC, true); v = w; } _current_player = OWNER_NONE; @@ -2689,6 +2684,87 @@ void Vehicle::SetNext(Vehicle *next) } } +/** Backs up a chain of vehicles + * @return a pointer to the chain + */ +Vehicle* Vehicle::BackupVehicle() const +{ + int length = CountVehiclesInChain(this); + + Vehicle *list = MallocT(length); + Vehicle *copy = list; // store the pointer so we have something to return later + + const Vehicle *original = this; + + for (; 0 < length; original = original->next, copy++, length--) { + memcpy(copy, original, sizeof(Vehicle)); + } + return list; +} + +/** Restore a backed up row of vehicles + * @return a pointer to the first vehicle in chain + */ +Vehicle* Vehicle::RestoreBackupVehicle() +{ + Vehicle *backup = this; + + Player *p = GetPlayer(backup->owner); + + while (true) { + Vehicle *dest = GetVehicle(backup->index); + /* The vehicle should be free since we are restoring something we just sold. */ + assert(!dest->IsValid()); + memcpy(dest, backup, sizeof(Vehicle)); + + /* We decreased the engine count when we sold the engines so we will increase it again. */ + if (IsEngineCountable(backup)) p->num_engines[backup->engine_type]++; + + Vehicle *dummy = dest; + dest->old_new_hash = &dummy; + dest->left_coord = INVALID_COORD; + UpdateVehiclePosHash(dest, INVALID_COORD, 0); + + if (backup->next == NULL) break; + backup++; + } + return GetVehicle(this->index); +} + +/** Restores a backed up vehicle + * @param *v A vehicle we should sell and take the windows from (NULL for not using this) + * @return The vehicle we restored (front for trains) or v if we didn't have anything to restore + */ +Vehicle *BackuppedVehicle::Restore(Vehicle *v) +{ + if (!ContainsBackup()) return v; + if (v != NULL) { + ChangeVehicleViewWindow(v, INVALID_VEHICLE); + DoCommand(0, v->index, 1, DC_EXEC, GetCmdSellVeh(v)); + } + v = this->vehicles->RestoreBackupVehicle(); + ChangeVehicleViewWindow(INVALID_VEHICLE, v); + if (orders != NULL) RestoreVehicleOrdersBruteForce(v, orders); + if (economy != NULL) economy->Restore(); + return v; +} + +/** Backs up a vehicle + * This should never be called when the object already contains a backup + * @param v the vehicle to backup + * @param p If it's set to the vehicle's owner then economy is backed up. If NULL then economy backup will be skipped. + */ +void BackuppedVehicle::Backup(const Vehicle *v, Player *p) +{ + assert(!ContainsBackup()); + if (p != NULL) { + assert(p->index == v->owner); + economy = new PlayerMoneyBackup(p); + } + vehicles = v->BackupVehicle(); + if (orders != NULL) BackupVehicleOrders(v, orders); +} + void StopAllVehicles() { Vehicle *v; diff --git a/src/vehicle_base.h b/src/vehicle_base.h index f89fd94582..d9dabdf585 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -14,6 +14,7 @@ #include "gfx_type.h" #include "command_type.h" #include "date_type.h" +#include "player_base.h" #include "player_type.h" #include "oldpool.h" #include "order_base.h" @@ -21,6 +22,7 @@ #include "texteff.hpp" #include "group_type.h" #include "engine_type.h" +#include "order_func.h" /** Road vehicle states */ enum RoadVehicleStates { @@ -520,6 +522,9 @@ public: * @return the cost of the depot action. */ CommandCost SendToDepot(uint32 flags, DepotCommand command); + + Vehicle* BackupVehicle() const; + Vehicle* RestoreBackupVehicle(); }; /** @@ -649,4 +654,21 @@ Trackdir GetVehicleTrackdir(const Vehicle* v); void CheckVehicle32Day(Vehicle *v); +struct BackuppedVehicle { +private: + Vehicle *vehicles; + BackuppedOrders *orders; + PlayerMoneyBackup *economy; + +public: + BackuppedVehicle(bool include_orders) : vehicles(NULL), economy(NULL) { + orders = include_orders ? new BackuppedOrders() : NULL; + } + ~BackuppedVehicle() { free(vehicles); delete orders; delete economy; } + + void Backup(const Vehicle *v, Player *p = NULL); + Vehicle *Restore(Vehicle *v); + bool ContainsBackup() { return vehicles != NULL; } +}; + #endif /* VEHICLE_BASE_H */