(svn r12913) -Add: ability to backup and restore a player's economic data and data for a vehicle (or chain of vehicles)
authorbjarni
Sun, 27 Apr 2008 20:09:29 +0000
changeset 10372 93844492fdd9
parent 10371 f4ebcb863ad6
child 10373 899ed5c4ce9b
(svn r12913) -Add: ability to backup and restore a player's economic data and data for a vehicle (or chain of vehicles)
Autoreplace uses this with the following benefits:
-Mass autoreplace (the button in the depot window) will now estimate costs correctly
-Autoreplace now either replaces correctly or manages to keep the original vehicle (no more broken trains)
Thanks to Ammler for testing this
src/autoreplace_cmd.cpp
src/command.cpp
src/order_cmd.cpp
src/order_func.h
src/player_base.h
src/players.cpp
src/vehicle.cpp
src/vehicle_base.h
--- a/src/autoreplace_cmd.cpp	Sun Apr 27 18:05:48 2008 +0000
+++ b/src/autoreplace_cmd.cpp	Sun Apr 27 20:09:29 2008 +0000
@@ -206,7 +206,11 @@
 				/* 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 @@
 				}
 
 				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 @@
 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 @@
 			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 @@
 			}
 		} 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 @@
 		}
 	}
 
-	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 */
--- a/src/command.cpp	Sun Apr 27 18:05:48 2008 +0000
+++ b/src/command.cpp	Sun Apr 27 20:09:29 2008 +0000
@@ -542,16 +542,12 @@
 	 * 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;
 
--- a/src/order_cmd.cpp	Sun Apr 27 18:05:48 2008 +0000
+++ b/src/order_cmd.cpp	Sun Apr 27 20:09:29 2008 +0000
@@ -1233,7 +1233,7 @@
 
 		/* 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 @@
 	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
--- a/src/order_func.h	Sun Apr 27 18:05:48 2008 +0000
+++ b/src/order_func.h	Sun Apr 27 20:09:29 2008 +0000
@@ -28,6 +28,7 @@
 
 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);
--- a/src/player_base.h	Sun Apr 27 18:05:48 2008 +0000
+++ b/src/player_base.h	Sun Apr 27 20:09:29 2008 +0000
@@ -73,6 +73,18 @@
 	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++)
 
--- a/src/players.cpp	Sun Apr 27 18:05:48 2008 +0000
+++ b/src/players.cpp	Sun Apr 27 20:09:29 2008 +0000
@@ -208,6 +208,24 @@
 	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;
--- a/src/vehicle.cpp	Sun Apr 27 18:05:48 2008 +0000
+++ b/src/vehicle.cpp	Sun Apr 27 20:09:29 2008 +0000
@@ -712,12 +712,7 @@
 				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 @@
 	}
 }
 
+/** Backs up a chain of vehicles
+ * @return a pointer to the chain
+ */
+Vehicle* Vehicle::BackupVehicle() const
+{
+	int length = CountVehiclesInChain(this);
+
+	Vehicle *list = MallocT<Vehicle>(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;
--- a/src/vehicle_base.h	Sun Apr 27 18:05:48 2008 +0000
+++ b/src/vehicle_base.h	Sun Apr 27 20:09:29 2008 +0000
@@ -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 @@
 	 * @return the cost of the depot action.
 	 */
 	CommandCost SendToDepot(uint32 flags, DepotCommand command);
+
+	Vehicle* BackupVehicle() const;
+	Vehicle* RestoreBackupVehicle();
 };
 
 /**
@@ -649,4 +654,21 @@
 
 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 */