changeset 20078:ad738879626a draft

(svn r25012) -Codechange: persistently keep 'reserved' cargo (for full-load improved loading) instead of calculating if for every cycle
author rubidium <rubidium@openttd.org>
date Sun, 17 Feb 2013 14:54:50 +0000
parents 579bc020cf63
children 27d20dcc9664
files src/autoreplace_cmd.cpp src/cargoaction.cpp src/cargoaction.h src/cargopacket.cpp src/cargopacket.h src/economy.cpp src/saveload/cargopacket_sl.cpp src/saveload/saveload.cpp src/saveload/station_sl.cpp src/saveload/vehicle_sl.cpp src/vehicle.cpp src/vehicle_base.h
diffstat 12 files changed, 615 insertions(+), 336 deletions(-) [+]
line wrap: on
line diff
--- a/src/autoreplace_cmd.cpp
+++ b/src/autoreplace_cmd.cpp
@@ -106,7 +106,7 @@
 			if (dest->cargo.Count() >= dest->cargo_cap || dest->cargo_type != src->cargo_type) continue;
 
 			uint amount = min(to_spread, dest->cargo_cap - dest->cargo.Count());
-			src->cargo.MoveTo(&dest->cargo, amount, VehicleCargoList::MTA_UNLOAD, NULL);
+			src->cargo.Shift(amount, &dest->cargo);
 			to_spread -= amount;
 		}
 
@@ -145,7 +145,7 @@
 			uint amount = min(src->cargo.Count(), dest->cargo_cap - dest->cargo.Count());
 			if (amount <= 0) continue;
 
-			src->cargo.MoveTo(&dest->cargo, amount, VehicleCargoList::MTA_UNLOAD, NULL);
+			src->cargo.Shift(amount, &dest->cargo);
 		}
 	}
 
--- a/src/cargoaction.cpp
+++ b/src/cargoaction.cpp
@@ -42,11 +42,9 @@
 {
 	if (this->max_move >= cp->Count()) {
 		this->max_move -= cp->Count();
-		this->source->RemoveFromCache(cp, cp->Count());
 		return cp->Count();
 	} else {
 		uint ret = this->max_move;
-		this->source->RemoveFromCache(cp, ret);
 		this->max_move = 0;
 		return ret;
 	}
@@ -71,6 +69,34 @@
 }
 
 /**
+ * Removes some cargo from a StationCargoList.
+ * @param cp Packet to be removed.
+ * @return True if the packet was completely delivered, false if only part of
+ *         it was.
+ */
+template<>
+bool CargoRemoval<StationCargoList>::operator()(CargoPacket *cp)
+{
+	uint remove = this->Preprocess(cp);
+	this->source->RemoveFromCache(cp, remove);
+	return this->Postprocess(cp, remove);
+}
+
+/**
+ * Removes some cargo from a VehicleCargoList.
+ * @param cp Packet to be removed.
+ * @return True if the packet was completely delivered, false if only part of
+ *         it was.
+ */
+template<>
+bool CargoRemoval<VehicleCargoList>::operator()(CargoPacket *cp)
+{
+	uint remove = this->Preprocess(cp);
+	this->source->RemoveFromMeta(cp, VehicleCargoList::MTA_KEEP, remove);
+	return this->Postprocess(cp, remove);
+}
+
+/**
  * Delivers some cargo.
  * @param cp Packet to be delivered.
  * @return True if the packet was completely delivered, false if only part of
@@ -79,6 +105,7 @@
 bool CargoDelivery::operator()(CargoPacket *cp)
 {
 	uint remove = this->Preprocess(cp);
+	this->source->RemoveFromMeta(cp, VehicleCargoList::MTA_DELIVER, remove);
 	this->payment->PayFinalDelivery(cp, remove);
 	return this->Postprocess(cp, remove);
 }
@@ -94,6 +121,38 @@
 	if (cp_new == NULL) return false;
 	cp_new->SetLoadPlace(this->load_place);
 	this->source->RemoveFromCache(cp_new, cp_new->Count());
+	this->destination->Append(cp_new, VehicleCargoList::MTA_KEEP);
+	return cp_new == cp;
+}
+
+/**
+ * Reserves some cargo for loading.
+ * @param cp Packet to be reserved.
+ * @return True if the packet was completely reserved, false if part of it was.
+ */
+bool CargoReservation::operator()(CargoPacket *cp)
+{
+	CargoPacket *cp_new = this->Preprocess(cp);
+	if (cp_new == NULL) return false;
+	cp_new->SetLoadPlace(this->load_place);
+	this->source->reserved_count += cp_new->Count();
+	this->source->RemoveFromCache(cp_new, cp_new->Count());
+	this->destination->Append(cp_new, VehicleCargoList::MTA_LOAD);
+	return cp_new == cp;
+}
+
+/**
+ * Returns some reserved cargo.
+ * @param cp Packet to be returned.
+ * @return True if the packet was completely returned, false if part of it was.
+ */
+bool CargoReturn::operator()(CargoPacket *cp)
+{
+	CargoPacket *cp_new = this->Preprocess(cp);
+	if (cp_new == NULL) cp_new = cp;
+	assert(cp_new->Count() <= this->destination->reserved_count);
+	this->source->RemoveFromMeta(cp_new, VehicleCargoList::MTA_LOAD, cp_new->Count());
+	this->destination->reserved_count -= cp_new->Count();
 	this->destination->Append(cp_new);
 	return cp_new == cp;
 }
@@ -107,7 +166,7 @@
 {
 	CargoPacket *cp_new = this->Preprocess(cp);
 	if (cp_new == NULL) return false;
-	this->source->RemoveFromCache(cp_new, cp_new->Count());
+	this->source->RemoveFromMeta(cp_new, VehicleCargoList::MTA_TRANSFER, cp_new->Count());
 	this->destination->Append(cp_new);
 	return cp_new == cp;
 }
@@ -121,8 +180,8 @@
 {
 	CargoPacket *cp_new = this->Preprocess(cp);
 	if (cp_new == NULL) cp_new = cp;
-	this->source->RemoveFromCache(cp_new, cp_new->Count());
-	this->destination->Append(cp_new);
+	this->source->RemoveFromMeta(cp_new, VehicleCargoList::MTA_KEEP, cp_new->Count());
+	this->destination->Append(cp_new, VehicleCargoList::MTA_KEEP);
 	return cp_new == cp;
 }
 
--- a/src/cargoaction.h
+++ b/src/cargoaction.h
@@ -34,13 +34,7 @@
 	 */
 	uint MaxMove() { return this->max_move; }
 
-	/**
-	 * Removes some cargo.
-	 * @param cp Packet to be removed.
-	 * @return True if the packet was completely delivered, false if only part of
-	 *         it was.
-	 */
-	inline bool operator()(CargoPacket *cp) { return this->Postprocess(cp, this->Preprocess(cp)); }
+	bool operator()(CargoPacket *cp);
 };
 
 /** Action of final delivery of cargo. */
@@ -95,6 +89,22 @@
 	bool operator()(CargoPacket *cp);
 };
 
+/** Action of reserving cargo from a station to be loaded onto a vehicle. */
+class CargoReservation: public CargoLoad {
+public:
+	CargoReservation(StationCargoList *source, VehicleCargoList *destination, uint max_move, TileIndex load_place) :
+			CargoLoad(source, destination, max_move, load_place) {}
+	bool operator()(CargoPacket *cp);
+};
+
+/** Action of returning previously reserved cargo from the vehicle to the station. */
+class CargoReturn: public CargoMovement<VehicleCargoList, StationCargoList> {
+public:
+	CargoReturn(VehicleCargoList *source, StationCargoList *destination, uint max_move) :
+			CargoMovement<VehicleCargoList, StationCargoList>(source, destination, max_move) {}
+	bool operator()(CargoPacket *cp);
+};
+
 /** Action of shifting cargo from one vehicle to another. */
 class CargoShift : public CargoMovement<VehicleCargoList, VehicleCargoList> {
 public:
--- a/src/cargopacket.cpp
+++ b/src/cargopacket.cpp
@@ -13,6 +13,7 @@
 #include "core/pool_func.hpp"
 #include "economy_base.h"
 #include "cargoaction.h"
+#include "order_type.h"
 
 /* Initialize the cargopacket-pool */
 CargoPacketPool _cargopacket_pool("CargoPacket");
@@ -83,7 +84,7 @@
  * @param new_size Size of the split part.
  * @return Split off part, or NULL if no packet could be allocated!
  */
-inline CargoPacket *CargoPacket::Split(uint new_size)
+CargoPacket *CargoPacket::Split(uint new_size)
 {
 	if (!CargoPacket::CanAllocateItem()) return NULL;
 
@@ -98,7 +99,7 @@
  * Merge another packet into this one.
  * @param cp Packet to be merged in.
  */
-inline void CargoPacket::Merge(CargoPacket *cp)
+void CargoPacket::Merge(CargoPacket *cp)
 {
 	this->count += cp->count;
 	this->feeder_share += cp->feeder_share;
@@ -109,7 +110,7 @@
  * Reduce the packet by the given amount and remove the feeder share.
  * @param count Amount to be removed.
  */
-inline void CargoPacket::Reduce(uint count)
+void CargoPacket::Reduce(uint count)
 {
 	assert(count < this->count);
 	this->feeder_share -= this->FeederShare(count);
@@ -195,30 +196,8 @@
 }
 
 /**
- * Appends the given cargo packet. Tries to merge it with another one in the
- * packets list. If no fitting packet is found, appends it.
- * @warning After appending this packet may not exist anymore!
- * @note Do not use the cargo packet anymore after it has been appended to this CargoList!
- * @param cp Cargo packet to add.
- * @pre cp != NULL
- */
-template <class Tinst>
-void CargoList<Tinst>::Append(CargoPacket *cp)
-{
-	assert(cp != NULL);
-	static_cast<Tinst *>(this)->AddToCache(cp);
-
-	for (List::reverse_iterator it(this->packets.rbegin()); it != this->packets.rend(); it++) {
-		if (CargoList<Tinst>::TryMerge(*it, cp)) return;
-	}
-
-	/* The packet could not be merged with another one */
-	this->packets.push_back(cp);
-}
-
-/**
  * Truncates the cargo in this list to the given amount. It leaves the
- * first count cargo entities and removes the rest.
+ * first cargo entities and removes max_move from the back of the list.
  * @param max_move Maximum amount of entities to be removed from the list.
  * @return Amount of entities actually moved.
  */
@@ -231,102 +210,6 @@
 }
 
 /**
- * Moves the given amount of cargo to another list.
- * Depending on the value of mta the side effects of this function differ:
- *  - MTA_FINAL_DELIVERY: Destroys the packets that do not originate from a specific station.
- *  - MTA_CARGO_LOAD:     Sets the loaded_at_xy value of the moved packets.
- *  - MTA_TRANSFER:       Just move without side effects.
- *  - MTA_UNLOAD:         Just move without side effects.
- * @param dest  Destination to move the cargo to.
- * @param max_move Amount of cargo entities to move.
- * @param mta   How to handle the moving (side effects).
- * @param data  Depending on mta the data of this variable differs:
- *              - MTA_FINAL_DELIVERY - Station ID of packet's origin not to remove.
- *              - MTA_CARGO_LOAD     - Station's tile index of load.
- *              - MTA_TRANSFER       - Unused.
- *              - MTA_UNLOAD         - Unused.
- * @param payment The payment helper.
- *
- * @pre mta == MTA_FINAL_DELIVERY || dest != NULL
- * @pre mta == MTA_UNLOAD || mta == MTA_CARGO_LOAD || payment != NULL
- * @return True if there are still packets that might be moved from this cargo list.
- */
-template <class Tinst>
-template <class Tother_inst>
-bool CargoList<Tinst>::MoveTo(Tother_inst *dest, uint max_move, MoveToAction mta, CargoPayment *payment, uint data)
-{
-	assert(mta == MTA_FINAL_DELIVERY || dest != NULL);
-	assert(mta == MTA_UNLOAD || mta == MTA_CARGO_LOAD || payment != NULL);
-
-	Iterator it(this->packets.begin());
-	while (it != this->packets.end() && max_move > 0) {
-		CargoPacket *cp = *it;
-		if (cp->source == data && mta == MTA_FINAL_DELIVERY) {
-			/* Skip cargo that originated from this station. */
-			++it;
-			continue;
-		}
-
-		if (cp->count <= max_move) {
-			/* Can move the complete packet */
-			max_move -= cp->count;
-			it = this->packets.erase(it);
-			static_cast<Tinst *>(this)->RemoveFromCache(cp, cp->count);
-			switch (mta) {
-				case MTA_FINAL_DELIVERY:
-					payment->PayFinalDelivery(cp, cp->count);
-					delete cp;
-					continue; // of the loop
-
-				case MTA_CARGO_LOAD:
-					cp->loaded_at_xy = data;
-					break;
-
-				case MTA_TRANSFER:
-					cp->feeder_share += payment->PayTransfer(cp, cp->count);
-					break;
-
-				case MTA_UNLOAD:
-					break;
-			}
-			dest->Append(cp);
-			continue;
-		}
-
-		/* Can move only part of the packet */
-		if (mta == MTA_FINAL_DELIVERY) {
-			/* Final delivery doesn't need package splitting. */
-			payment->PayFinalDelivery(cp, max_move);
-
-			/* Remove the delivered data from the cache */
-			static_cast<Tinst *>(this)->RemoveFromCache(cp, max_move);
-			cp->Reduce(max_move);
-		} else {
-			/* But... the rest needs package splitting. */
-			CargoPacket *cp_new = cp->Split(max_move);
-
-			/* We could not allocate a CargoPacket? Is the map that full? */
-			if (cp_new == NULL) return false;
-
-			static_cast<Tinst *>(this)->RemoveFromCache(cp_new, max_move); // this reflects the changes in cp.
-
-			if (mta == MTA_TRANSFER) {
-				/* Add the feeder share before inserting in dest. */
-				cp_new->feeder_share += payment->PayTransfer(cp_new, max_move);
-			} else if (mta == MTA_CARGO_LOAD) {
-				cp_new->loaded_at_xy = data;
-			}
-
-			dest->Append(cp_new);
-		}
-
-		max_move = 0;
-	}
-
-	return it != packets.end();
-}
-
-/**
  * Shifts cargo from the front of the packet list and applies some action to it.
  * @tparam Taction Action class or function to be used. It should define
  *                 "bool operator()(CargoPacket *)". If true is returned the
@@ -417,6 +300,47 @@
  */
 
 /**
+ * Appends the given cargo packet. Tries to merge it with another one in the
+ * packets list. If no fitting packet is found, appends it. You can only append
+ * packets to the ranges of packets designated for keeping or loading.
+ * Furthermore if there are already packets reserved for loading you cannot
+ * directly add packets to the "keep" list. You first have to load the reserved
+ * ones.
+ * @warning After appending this packet may not exist anymore!
+ * @note Do not use the cargo packet anymore after it has been appended to this CargoList!
+ * @param cp Cargo packet to add.
+ * @param action Either MTA_KEEP if you want to add the packet directly or MTA_LOAD
+ * if you want to reserve it first.
+ * @pre cp != NULL
+ * @pre action == MTA_LOAD || (action == MTA_KEEP && this->designation_counts[MTA_LOAD] == 0)
+ */
+void VehicleCargoList::Append(CargoPacket *cp, MoveToAction action)
+{
+	assert(cp != NULL);
+	assert(action == MTA_LOAD ||
+			(action == MTA_KEEP && this->action_counts[MTA_LOAD] == 0));
+	this->AddToMeta(cp, action);
+
+	if (this->count == cp->count) {
+		this->packets.push_back(cp);
+		return;
+	}
+
+	uint sum = cp->count;
+	for (ReverseIterator it(this->packets.rbegin()); it != this->packets.rend(); it++) {
+		CargoPacket *icp = *it;
+		if (VehicleCargoList::TryMerge(icp, cp)) return;
+		sum += icp->count;
+		if (sum >= this->action_counts[action]) {
+			this->packets.push_back(cp);
+			return;
+		}
+	}
+
+	NOT_REACHED();
+}
+
+/**
  * Update the cached values to reflect the removal of this packet or part of it.
  * Decreases count, feeder share and days_in_transit.
  * @param cp Packet to be removed from cache.
@@ -440,6 +364,33 @@
 }
 
 /**
+ * Removes a packet or part of it from the metadata.
+ * @param cp Packet to be removed.
+ * @param action MoveToAction of the packet (for updating the counts).
+ * @param count Amount of cargo to be removed.
+ */
+void VehicleCargoList::RemoveFromMeta(const CargoPacket *cp, MoveToAction action, uint count)
+{
+	this->AssertCountConsistency();
+	this->RemoveFromCache(cp, count);
+	this->action_counts[action] -= count;
+	this->AssertCountConsistency();
+}
+
+/**
+ * Adds a packet to the metadata.
+ * @param cp Packet to be added.
+ * @param action MoveToAction of the packet.
+ */
+void VehicleCargoList::AddToMeta(const CargoPacket *cp, MoveToAction action)
+{
+	this->AssertCountConsistency();
+	this->AddToCache(cp);
+	this->action_counts[action] += cp->count;
+	this->AssertCountConsistency();
+}
+
+/**
  * Ages the all cargo in this list.
  */
 void VehicleCargoList::AgeCargo()
@@ -454,6 +405,44 @@
 	}
 }
 
+/**
+ * Stages cargo for unloading. The cargo is sorted so that packets to be
+ * transferred, delivered or kept are in consecutive chunks in the list. At the
+ * same time the designation_counts are updated to reflect the size of those
+ * chunks.
+ * @param accepted If the cargo will be accepted at the station.
+ * @param current_station ID of the station.
+ * @param order_flags OrderUnloadFlags that will apply to the unload operation.
+ * return If any cargo will be unloaded.
+ */
+bool VehicleCargoList::Stage(bool accepted, StationID current_station, uint8 order_flags)
+{
+	this->AssertCountConsistency();
+	assert(this->action_counts[MTA_LOAD] == 0);
+	this->action_counts[MTA_TRANSFER] = this->action_counts[MTA_DELIVER] = this->action_counts[MTA_KEEP] = 0;
+	Iterator deliver = this->packets.end();
+	Iterator it = this->packets.begin();
+	uint sum = 0;
+	while (sum < this->count) {
+		CargoPacket *cp = *it;
+		this->packets.erase(it++);
+		if ((order_flags & OUFB_TRANSFER) != 0 || (!accepted && (order_flags & OUFB_UNLOAD) != 0)) {
+			this->packets.push_front(cp);
+			this->action_counts[MTA_TRANSFER] += cp->count;
+		} else if (accepted && current_station != cp->source && (order_flags & OUFB_NO_UNLOAD) == 0) {
+			this->packets.insert(deliver, cp);
+			this->action_counts[MTA_DELIVER] += cp->count;
+		} else {
+			this->packets.push_back(cp);
+			if (deliver == this->packets.end()) --deliver;
+			this->action_counts[MTA_KEEP] += cp->count;
+		}
+		sum += cp->count;
+	}
+	this->AssertCountConsistency();
+	return this->action_counts[MTA_DELIVER] > 0 || this->action_counts[MTA_TRANSFER] > 0;
+}
+
 /** Invalidates the cached data and rebuild it. */
 void VehicleCargoList::InvalidateCache()
 {
@@ -461,15 +450,135 @@
 	this->Parent::InvalidateCache();
 }
 
+/**
+ * Moves some cargo from one designation to another. You can only move
+ * between adjacent designations. E.g. you can keep cargo that was
+ * previously reserved (MTA_LOAD) or you can mark cargo to be transferred
+ * that was previously marked as to be delivered, but you can't reserve
+ * cargo that's marked as to be delivered.
+ */
+uint VehicleCargoList::Reassign(uint max_move, MoveToAction from, MoveToAction to)
+{
+	max_move = min(this->action_counts[from], max_move);
+	assert(Delta((int)from, (int)to) == 1);
+	this->action_counts[from] -= max_move;
+	this->action_counts[to] += max_move;
+	return max_move;
+}
+
+/**
+ * Returns reserved cargo to the station and removes it from the cache.
+ * @param dest Station the cargo is returned to.
+ * @param max_move Maximum amount of cargo to move.
+ * @return Amount of cargo actually returned.
+ */
+uint VehicleCargoList::Return(uint max_move, StationCargoList *dest)
+{
+	max_move = min(this->action_counts[MTA_LOAD], max_move);
+	this->PopCargo(CargoReturn(this, dest, max_move));
+	return max_move;
+}
+
+/**
+ * Shifts cargo between two vehicles.
+ * @param dest Other vehicle's cargo list.
+ * @param max_move Maximum amount of cargo to be moved.
+ * @return Amount of cargo actually moved.
+ */
+uint VehicleCargoList::Shift(uint max_move, VehicleCargoList *dest)
+{
+	max_move = min(this->count, max_move);
+	this->PopCargo(CargoShift(this, dest, max_move));
+	return max_move;
+}
+
+/**
+ * Unloads cargo at the given station. Deliver or transfer, depending on the
+ * ranges defined by designation_counts.
+ * @param dest StationCargoList to add transferred cargo to.
+ * @param max_move Maximum amount of cargo to move.
+ * @param payment Payment object to register payments in.
+ * @return Amount of cargo actually unloaded.
+ */
+uint VehicleCargoList::Unload(uint max_move, StationCargoList *dest, CargoPayment *payment)
+{
+	uint moved = 0;
+	if (this->action_counts[MTA_TRANSFER] > 0) {
+		uint move = min(this->action_counts[MTA_TRANSFER], max_move);
+		this->ShiftCargo(CargoTransfer(this, dest, move, payment));
+		moved += move;
+	}
+	if (this->action_counts[MTA_TRANSFER] == 0 && this->action_counts[MTA_DELIVER] > 0 && moved < max_move) {
+		uint move = min(this->action_counts[MTA_DELIVER], max_move - moved);
+		this->ShiftCargo(CargoDelivery(this, move, payment));
+		moved += move;
+	}
+	return moved;
+}
+
+/*
+ *
+ * Station cargo list implementation.
+ *
+ */
+
+/**
+ * Appends the given cargo packet. Tries to merge it with another one in the
+ * packets list. If no fitting packet is found, appends it.
+ * @warning After appending this packet may not exist anymore!
+ * @note Do not use the cargo packet anymore after it has been appended to this CargoList!
+ * @param cp Cargo packet to add.
+ * @pre cp != NULL
+ */
+void StationCargoList::Append(CargoPacket *cp)
+{
+	assert(cp != NULL);
+	this->AddToCache(cp);
+
+	for (List::reverse_iterator it(this->packets.rbegin()); it != this->packets.rend(); it++) {
+		if (StationCargoList::TryMerge(*it, cp)) return;
+	}
+
+	/* The packet could not be merged with another one */
+	this->packets.push_back(cp);
+}
+
+/**
+ * Reserves cargo for loading onto the vehicle.
+ * @param dest VehicleCargoList to reserve for.
+ * @param max_move Maximum amount of cargo to reserve.
+ * @param load_place Tile index of the current station.
+ * @return Amount of cargo actually reserved.
+ */
+uint StationCargoList::Reserve(uint max_move, VehicleCargoList *dest, TileIndex load_place)
+{
+	max_move = min(this->count, max_move);
+	this->ShiftCargo(CargoReservation(this, dest, max_move, load_place));
+	return max_move;
+}
+
+/**
+ * Loads cargo onto a vehicle. If the vehicle has reserved cargo load that.
+ * Otherwise load cargo from the station.
+ * @param dest Vehicle cargo list where the cargo resides.
+ * @param max_move Amount of cargo to load.
+ * @return Amount of cargo actually loaded.
+ */
+uint StationCargoList::Load(uint max_move, VehicleCargoList *dest, TileIndex load_place)
+{
+	uint move = min(dest->ActionCount(VehicleCargoList::MTA_LOAD), max_move);
+	if (move > 0) {
+		this->reserved_count -= move;
+		dest->Reassign(move, VehicleCargoList::MTA_LOAD, VehicleCargoList::MTA_KEEP);
+	} else {
+		move = min(this->count, max_move);
+		this->ShiftCargo(CargoLoad(this, dest, move, load_place));
+	}
+	return move;
+}
+
 /*
  * We have to instantiate everything we want to be usable.
  */
 template class CargoList<VehicleCargoList>;
 template class CargoList<StationCargoList>;
-
-/** Autoreplace Vehicle -> Vehicle 'transfer'. */
-template bool CargoList<VehicleCargoList>::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data);
-/** Cargo unloading at a station. */
-template bool CargoList<VehicleCargoList>::MoveTo(StationCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data);
-/** Cargo loading at a station. */
-template bool CargoList<StationCargoList>::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data);
--- a/src/cargopacket.h
+++ b/src/cargopacket.h
@@ -29,6 +29,7 @@
 extern CargoPacketPool _cargopacket_pool;
 
 template <class Tinst> class CargoList;
+class StationCargoList; // forward-declare, so we can use it in VehicleCargoList.
 extern const struct SaveLoad *GetCargoPacketDesc();
 
 /**
@@ -203,10 +204,13 @@
 
 	/** Kind of actions that could be done with packets on move. */
 	enum MoveToAction {
-		MTA_FINAL_DELIVERY, ///< "Deliver" the packet to the final destination, i.e. destroy the packet.
-		MTA_CARGO_LOAD,     ///< Load the packet onto a vehicle, i.e. set the last loaded station ID.
-		MTA_TRANSFER,       ///< The cargo is moved as part of a transfer.
-		MTA_UNLOAD,         ///< The cargo is moved as part of a forced unload.
+		MTA_BEGIN = 0,
+		MTA_TRANSFER = 0, ///< Transfer the cargo to the station.
+		MTA_DELIVER,      ///< Deliver the cargo to some town or industry.
+		MTA_KEEP,         ///< Keep the cargo in the vehicle.
+		MTA_LOAD,         ///< Load the cargo from the station.
+		MTA_END,
+		NUM_MOVE_TO_ACTION = MTA_END
 	};
 
 protected:
@@ -280,13 +284,8 @@
 		return this->count == 0 ? 0 : this->cargo_days_in_transit / this->count;
 	}
 
-
-	void Append(CargoPacket *cp);
 	uint Truncate(uint max_move = UINT_MAX);
 
-	template <class Tother_inst>
-	bool MoveTo(Tother_inst *dest, uint count, MoveToAction mta, CargoPayment *payment, uint data = 0);
-
 	void InvalidateCache();
 };
 
@@ -298,11 +297,26 @@
 	/** The (direct) parent of this class. */
 	typedef CargoList<VehicleCargoList> Parent;
 
-	Money feeder_share; ///< Cache for the feeder share.
+	Money feeder_share;                     ///< Cache for the feeder share.
+	uint action_counts[NUM_MOVE_TO_ACTION]; ///< Counts of cargo to be transfered, delivered, kept and loaded.
+
+	/**
+	 * Assert that the designation counts add up.
+	 */
+	inline void AssertCountConsistency() const
+	{
+		assert(this->action_counts[MTA_KEEP] +
+				this->action_counts[MTA_DELIVER] +
+				this->action_counts[MTA_TRANSFER] +
+				this->action_counts[MTA_LOAD] == this->count);
+	}
 
 	void AddToCache(const CargoPacket *cp);
 	void RemoveFromCache(const CargoPacket *cp, uint count);
 
+	void AddToMeta(const CargoPacket *cp, MoveToAction action);
+	void RemoveFromMeta(const CargoPacket *cp, MoveToAction action, uint count);
+
 public:
 	/** The super class ought to know what it's doing. */
 	friend class CargoList<VehicleCargoList>;
@@ -314,6 +328,7 @@
 	friend class CargoDelivery;
 	template<class Tsource>
 	friend class CargoRemoval;
+	friend class CargoReturn;
 
 	/**
 	 * Returns total sum of the feeder share for all packets.
@@ -324,10 +339,72 @@
 		return this->feeder_share;
 	}
 
+	/**
+	 * Returns the amount of cargo designated for a given purpose.
+	 * @param action Action the cargo is designated for.
+	 * @return Amount of cargo designated for the given action.
+	 */
+	inline uint ActionCount(MoveToAction action) const
+	{
+		return this->action_counts[action];
+	}
+
+	/**
+	 * Returns sum of cargo on board the vehicle (ie not only
+	 * reserved).
+	 * @return Cargo on board the vehicle.
+	 */
+	inline uint OnboardCount() const
+	{
+		return this->count - this->action_counts[MTA_LOAD];
+	}
+
+	/**
+	 * Returns sum of cargo to be moved out of the vehicle at the current station.
+	 * @return Cargo to be moved.
+	 */
+	inline uint UnloadCount() const
+	{
+		return this->action_counts[MTA_TRANSFER] + this->action_counts[MTA_DELIVER];
+	}
+
+	/**
+	 * Returns the sum of cargo to be kept in the vehicle at the current station.
+	 * @return Cargo to be kept or loaded.
+	 */
+	inline uint RemainingCount() const
+	{
+		return this->action_counts[MTA_KEEP] + this->action_counts[MTA_LOAD];
+	}
+
+	void Append(CargoPacket *cp, MoveToAction action = MTA_KEEP);
+
 	void AgeCargo();
 
 	void InvalidateCache();
 
+	bool Stage(bool accepted, StationID current_station, uint8 order_flags);
+
+	/**
+	 * Marks all cargo in the vehicle as to be kept. This is mostly useful for
+	 * loading old savegames. When loading is aborted the reserved cargo has
+	 * to be returned first.
+	 */
+	inline void KeepAll()
+	{
+		this->action_counts[MTA_DELIVER] = this->action_counts[MTA_TRANSFER] = this->action_counts[MTA_LOAD] = 0;
+		this->action_counts[MTA_KEEP] = this->count;
+	}
+
+	/* Methods for moving cargo around. First parameter is always maximum
+	 * amount of cargo to be moved. Second parameter is destination (if
+	 * applicable), return value is amount of cargo actually moved. */
+
+	uint Reassign(uint max_move, MoveToAction from, MoveToAction to);
+	uint Return(uint max_move, StationCargoList *dest);
+	uint Unload(uint max_move, StationCargoList *dest, CargoPayment *payment);
+	uint Shift(uint max_move, VehicleCargoList *dest);
+
 	/**
 	 * Are two the two CargoPackets mergeable in the context of
 	 * a list of CargoPackets for a Vehicle?
@@ -349,6 +426,12 @@
  * CargoList that is used for stations.
  */
 class StationCargoList : public CargoList<StationCargoList> {
+protected:
+	/** The (direct) parent of this class. */
+	typedef CargoList<StationCargoList> Parent;
+
+	uint reserved_count; ///< Amount of cargo being reserved for loading.
+
 public:
 	/** The super class ought to know what it's doing. */
 	friend class CargoList<StationCargoList>;
@@ -359,6 +442,17 @@
 	friend class CargoTransfer;
 	template<class Tsource>
 	friend class CargoRemoval;
+	friend class CargoReservation;
+	friend class CargoReturn;
+
+	void Append(CargoPacket *cp);
+
+	/* Methods for moving cargo around. First parameter is always maximum
+	 * amount of cargo to be moved. Second parameter is destination (if
+	 * applicable), return value is amount of cargo actually moved. */
+
+	uint Reserve(uint max_move, VehicleCargoList *dest, TileIndex load_place);
+	uint Load(uint max_move, VehicleCargoList *dest, TileIndex load_place);
 
 	/**
 	 * Are two the two CargoPackets mergeable in the context of
--- a/src/economy.cpp
+++ b/src/economy.cpp
@@ -1217,6 +1217,9 @@
 	if ((front_v->current_order.GetUnloadType() & OUFB_NO_UNLOAD) == 0) {
 		for (Vehicle *v = front_v; v != NULL; v = v->Next()) {
 			if (v->cargo_cap > 0 && !v->cargo.Empty()) {
+				v->cargo.Stage(
+						HasBit(Station::Get(front_v->last_station_visited)->goods[v->cargo_type].acceptance_pickup, GoodsEntry::GES_ACCEPTANCE),
+						front_v->last_station_visited, front_v->current_order.GetUnloadType());
 				SetBit(v->vehicle_flags, VF_CARGO_UNLOADING);
 			}
 		}
@@ -1231,6 +1234,86 @@
 }
 
 /**
+ * Gets the amount of cargo the given vehicle can load in the current tick.
+ * This is only about loading speed. The free capacity is ignored.
+ * @param v Vehicle to be queried.
+ * @return Amount of cargo the vehicle can load at once.
+ */
+static byte GetLoadAmount(Vehicle *v)
+{
+	const Engine *e = v->GetEngine();
+	byte load_amount = e->info.load_amount;
+
+	/* The default loadamount for mail is 1/4 of the load amount for passengers */
+	if (v->type == VEH_AIRCRAFT && !Aircraft::From(v)->IsNormalAircraft()) load_amount = CeilDiv(load_amount, 4);
+
+	if (_settings_game.order.gradual_loading) {
+		uint16 cb_load_amount = CALLBACK_FAILED;
+		if (e->GetGRF() != NULL && e->GetGRF()->grf_version >= 8) {
+			/* Use callback 36 */
+			cb_load_amount = GetVehicleProperty(v, PROP_VEHICLE_LOAD_AMOUNT, CALLBACK_FAILED);
+		} else if (HasBit(e->info.callback_mask, CBM_VEHICLE_LOAD_AMOUNT)) {
+			/* Use callback 12 */
+			cb_load_amount = GetVehicleCallback(CBID_VEHICLE_LOAD_AMOUNT, 0, 0, v->engine_type, v);
+		}
+		if (cb_load_amount != CALLBACK_FAILED) {
+			if (e->GetGRF()->grf_version < 8) cb_load_amount = GB(cb_load_amount, 0, 8);
+			if (cb_load_amount >= 0x100) {
+				ErrorUnknownCallbackResult(e->GetGRFID(), CBID_VEHICLE_LOAD_AMOUNT, cb_load_amount);
+			} else if (cb_load_amount != 0) {
+				load_amount = cb_load_amount;
+			}
+		}
+	}
+	return load_amount;
+}
+
+/**
+ * Reserves cargo if the full load order and improved_load is set or if the
+ * current order allows autorefit.
+ * @param st Station where the consist is loading at the moment.
+ * @param u Front of the loading vehicle consist.
+ * @param consist_capleft If given, save free capacities after reserving there.
+ */
+static void ReserveConsist(Station *st, Vehicle *u, CargoArray *consist_capleft)
+{
+	Vehicle *next_cargo = u;
+	uint32 seen_cargos = 0;
+
+	while (next_cargo != NULL) {
+		if (next_cargo->cargo_cap == 0) {
+			/* No need to reserve for vehicles without capacity. */
+			next_cargo = next_cargo->Next();
+			continue;
+		}
+
+		CargoID current_cargo = next_cargo->cargo_type;
+
+		Vehicle *v = next_cargo;
+		SetBit(seen_cargos, current_cargo);
+		next_cargo = NULL;
+		for (; v != NULL; v = v->Next()) {
+			if (v->cargo_type != current_cargo) {
+				/* Save start point for next cargo type. */
+				if (next_cargo == NULL && !HasBit(seen_cargos, v->cargo_type)) next_cargo = v;
+				continue;
+			}
+
+			uint cap = v->cargo_cap - v->cargo.RemainingCount();
+
+			/* Nothing to do if the vehicle is full */
+			if (cap > 0) {
+				cap -= st->goods[v->cargo_type].cargo.Reserve(cap, &v->cargo, st->xy);
+			}
+
+			if (consist_capleft != NULL) {
+				(*consist_capleft)[current_cargo] += cap;
+			}
+		}
+	}
+}
+
+/**
  * Checks whether an articulated vehicle is empty.
  * @param v Vehicle
  * @return true if all parts are empty.
@@ -1249,30 +1332,23 @@
 /**
  * Loads/unload the vehicle if possible.
  * @param front the vehicle to be (un)loaded
- * @param cargo_left the amount of each cargo type that is
- *                   virtually left on the platform to be
- *                   picked up by another vehicle when all
- *                   previous vehicles have loaded.
  */
-static void LoadUnloadVehicle(Vehicle *front, int *cargo_left)
+static void LoadUnloadVehicle(Vehicle *front)
 {
 	assert(front->current_order.IsType(OT_LOADING));
 
-	/* We have not waited enough time till the next round of loading/unloading */
-	if (front->load_unload_ticks != 0) {
-		if (_settings_game.order.improved_load && (front->current_order.GetLoadType() & OLFB_FULL_LOAD)) {
-			/* 'Reserve' this cargo for this vehicle, because we were first. */
-			for (Vehicle *v = front; v != NULL; v = v->Next()) {
-				int cap_left = v->cargo_cap;
-				if (!HasBit(v->vehicle_flags, VF_CARGO_UNLOADING)) cap_left -= v->cargo.Count();
-				if (cap_left > 0) cargo_left[v->cargo_type] -= cap_left;
-			}
-		}
-		return;
+	StationID last_visited = front->last_station_visited;
+	Station *st = Station::Get(last_visited);
+
+	bool use_autorefit = front->current_order.IsRefit() && front->current_order.GetRefitCargo() == CT_AUTO_REFIT;
+	CargoArray consist_capleft;
+	if (_settings_game.order.improved_load &&
+			((front->current_order.GetLoadType() & OLFB_FULL_LOAD) != 0 || use_autorefit)) {
+		ReserveConsist(st, front, (use_autorefit && front->load_unload_ticks != 0) ? &consist_capleft : NULL);
 	}
 
-	StationID last_visited = front->last_station_visited;
-	Station *st = Station::Get(last_visited);
+	/* We have not waited enough time till the next round of loading/unloading */
+	if (front->load_unload_ticks != 0) return;
 
 	if (front->type == VEH_TRAIN && (!IsTileType(front->tile, MP_STATION) || GetStationIndex(front->tile) != st->index)) {
 		/* The train reversed in the station. Take the "easy" way
@@ -1282,16 +1358,6 @@
 		return;
 	}
 
-	bool use_autorefit = front->current_order.IsRefit() && front->current_order.GetRefitCargo() == CT_AUTO_REFIT;
-	CargoArray consist_capleft;
-	if (use_autorefit) {
-		/* Sum cargo, that can be loaded without refitting */
-		for (Vehicle *v = front; v != NULL; v = v->Next()) {
-			int cap_left = v->cargo_cap - v->cargo.Count();
-			if (cap_left > 0) consist_capleft[v->cargo_type] += cap_left;
-		}
-	}
-
 	int unloading_time = 0;
 	bool dirty_vehicle = false;
 	bool dirty_station = false;
@@ -1314,81 +1380,49 @@
 		if (v->cargo_cap == 0) continue;
 		artic_part++;
 
-		const Engine *e = v->GetEngine();
-		byte load_amount = e->info.load_amount;
-
-		/* The default loadamount for mail is 1/4 of the load amount for passengers */
-		if (v->type == VEH_AIRCRAFT && !Aircraft::From(v)->IsNormalAircraft()) load_amount = CeilDiv(load_amount, 4);
-
-		if (_settings_game.order.gradual_loading) {
-			uint16 cb_load_amount = CALLBACK_FAILED;
-			if (e->GetGRF() != NULL && e->GetGRF()->grf_version >= 8) {
-				/* Use callback 36 */
-				cb_load_amount = GetVehicleProperty(v, PROP_VEHICLE_LOAD_AMOUNT, CALLBACK_FAILED);
-			} else if (HasBit(e->info.callback_mask, CBM_VEHICLE_LOAD_AMOUNT)) {
-				/* Use callback 12 */
-				cb_load_amount = GetVehicleCallback(CBID_VEHICLE_LOAD_AMOUNT, 0, 0, v->engine_type, v);
-			}
-			if (cb_load_amount != CALLBACK_FAILED) {
-				if (e->GetGRF()->grf_version < 8) cb_load_amount = GB(cb_load_amount, 0, 8);
-				if (cb_load_amount >= 0x100) {
-					ErrorUnknownCallbackResult(e->GetGRFID(), CBID_VEHICLE_LOAD_AMOUNT, cb_load_amount);
-				} else if (cb_load_amount != 0) {
-					load_amount = cb_load_amount;
-				}
-			}
-		}
+		byte load_amount = GetLoadAmount(v);
 
 		GoodsEntry *ge = &st->goods[v->cargo_type];
 
 		if (HasBit(v->vehicle_flags, VF_CARGO_UNLOADING) && (front->current_order.GetUnloadType() & OUFB_NO_UNLOAD) == 0) {
-			uint cargo_count = v->cargo.Count();
+			uint cargo_count = v->cargo.UnloadCount();
 			uint amount_unloaded = _settings_game.order.gradual_loading ? min(cargo_count, load_amount) : cargo_count;
 			bool remaining = false; // Are there cargo entities in this vehicle that can still be unloaded here?
-			bool accepted  = false; // Is the cargo accepted by the station?
 
 			payment->SetCargo(v->cargo_type);
 
-			if (HasBit(ge->acceptance_pickup, GoodsEntry::GES_ACCEPTANCE) && !(front->current_order.GetUnloadType() & OUFB_TRANSFER)) {
-				/* The cargo has reached its final destination, the packets may now be destroyed */
-				remaining = v->cargo.MoveTo<StationCargoList>(NULL, amount_unloaded, VehicleCargoList::MTA_FINAL_DELIVERY, payment, last_visited);
+			if (!HasBit(ge->acceptance_pickup, GoodsEntry::GES_ACCEPTANCE) && v->cargo.ActionCount(VehicleCargoList::MTA_DELIVER) > 0) {
+				/* The station does not accept our goods anymore. */
+				if (front->current_order.GetUnloadType() & (OUFB_TRANSFER | OUFB_UNLOAD)) {
+					/* Transfer instead of delivering. */
+					v->cargo.Reassign(v->cargo.ActionCount(VehicleCargoList::MTA_DELIVER),
+							VehicleCargoList::MTA_DELIVER, VehicleCargoList::MTA_TRANSFER);
+				} else {
+					/* Keep instead of delivering. This may lead to no cargo being unloaded, so ...*/
+					v->cargo.Reassign(v->cargo.ActionCount(VehicleCargoList::MTA_DELIVER),
+							VehicleCargoList::MTA_DELIVER, VehicleCargoList::MTA_KEEP);
 
-				dirty_vehicle = true;
-				accepted = true;
+					/* ... say we unloaded something, otherwise we'll think we didn't unload
+					 * something and we didn't load something, so we must be finished
+					 * at this station. Setting the unloaded means that we will get a
+					 * retry for loading in the next cycle. */
+					anything_unloaded = true;
+				}
 			}
 
-			/* The !accepted || v->cargo.Count == cargo_count clause is there
-			 * to make it possible to force unload vehicles at the station where
-			 * they were loaded, but to not force unload the vehicle when the
-			 * station is still accepting the cargo in the vehicle. It doesn't
-			 * accept cargo that was loaded at the same station. */
-			if ((front->current_order.GetUnloadType() & (OUFB_UNLOAD | OUFB_TRANSFER)) && (!accepted || v->cargo.Count() == cargo_count)) {
-				remaining = v->cargo.MoveTo(&ge->cargo, amount_unloaded, front->current_order.GetUnloadType() & OUFB_TRANSFER ? VehicleCargoList::MTA_TRANSFER : VehicleCargoList::MTA_UNLOAD, payment);
-				if (!HasBit(ge->acceptance_pickup, GoodsEntry::GES_PICKUP)) {
-					SetBit(ge->acceptance_pickup, GoodsEntry::GES_PICKUP);
-					InvalidateWindowData(WC_STATION_LIST, last_visited);
-				}
+			/* Mark the station dirty if we transfer, but not if we only deliver. */
+			dirty_station = v->cargo.ActionCount(VehicleCargoList::MTA_TRANSFER) > 0;
+			amount_unloaded = v->cargo.Unload(amount_unloaded, &ge->cargo, payment);
+			remaining = v->cargo.UnloadCount() > 0;
+			if (amount_unloaded > 0) {
+				dirty_vehicle = true;
+				anything_unloaded = true;
+				unloading_time += amount_unloaded;
 
-				dirty_vehicle = dirty_station = true;
-			} else if (!accepted) {
-				/* The order changed while unloading (unset unload/transfer) or the
-				 * station does not accept our goods. */
-				ClrBit(v->vehicle_flags, VF_CARGO_UNLOADING);
-
-				/* Say we loaded something, otherwise we'll think we didn't unload
-				 * something and we didn't load something, so we must be finished
-				 * at this station. Setting the unloaded means that we will get a
-				 * retry for loading in the next cycle. */
-				anything_unloaded = true;
-				continue;
+				/* Deliver goods to the station */
+				st->time_since_unload = 0;
 			}
 
-			/* Deliver goods to the station */
-			st->time_since_unload = 0;
-
-			unloading_time += amount_unloaded;
-
-			anything_unloaded = true;
 			if (_settings_game.order.gradual_loading && remaining) {
 				completely_emptied = false;
 			} else {
@@ -1419,7 +1453,7 @@
 			Backup<CompanyByte> cur_company(_current_company, front->owner, FILE_LINE);
 
 			/* Check if all articulated parts are empty and collect refit mask. */
-			uint32 refit_mask = e->info.refit_mask;
+			uint32 refit_mask = v->GetEngine()->info.refit_mask;
 			Vehicle *w = v_start;
 			while (w->HasArticulatedPart()) {
 				w = w->GetNextArticulatedPart();
@@ -1434,13 +1468,13 @@
 				FOR_EACH_SET_CARGO_ID(cid, refit_mask) {
 					/* Consider refitting to this cargo, if other vehicles of the consist cannot
 					 * already take the cargo without refitting */
-					if (cargo_left[cid] > (int)consist_capleft[cid] + amount) {
+					if ((int)st->goods[cid].cargo.Count() > (int)consist_capleft[cid] + amount) {
 						/* Try to find out if auto-refitting would succeed. In case the refit is allowed,
 						 * the returned refit capacity will be greater than zero. */
 						new_subtype = GetBestFittingSubType(v, v, cid);
 						DoCommand(v_start->tile, v_start->index, cid | 1U << 6 | new_subtype << 8 | 1U << 16, DC_QUERY_COST, GetCmdRefitVeh(v_start)); // Auto-refit and only this vehicle including artic parts.
 						if (_returned_refit_capacity > 0) {
-							amount = cargo_left[cid] - consist_capleft[cid];
+							amount = st->goods[cid].cargo.Count() - consist_capleft[cid];
 							new_cid = cid;
 						}
 					}
@@ -1454,12 +1488,13 @@
 				ge = &st->goods[v->cargo_type];
 			}
 
-			/* Add new capacity to consist capacity */
-			consist_capleft[v_start->cargo_type] += v_start->cargo_cap;
-			for (Vehicle *w = v_start; w->HasArticulatedPart(); ) {
-				w = w->GetNextArticulatedPart();
-				consist_capleft[w->cargo_type] += w->cargo_cap;
-			}
+			/* Add new capacity to consist capacity and reserve cargo */
+			w = v_start;
+			do {
+				st->goods[w->cargo_type].cargo.Reserve(w->cargo_cap, &w->cargo, st->xy);
+				consist_capleft[w->cargo_type] += w->cargo_cap - w->cargo.Count();
+				w = w->HasArticulatedPart() ? w->GetNextArticulatedPart() : NULL;
+			} while (w != NULL);
 
 			cur_company.Restore();
 		}
@@ -1490,54 +1525,25 @@
 
 		/* If there's goods waiting at the station, and the vehicle
 		 * has capacity for it, load it on the vehicle. */
-		int cap_left = v->cargo_cap - v->cargo.Count();
-		if (!ge->cargo.Empty() && cap_left > 0) {
-			uint cap = cap_left;
-			uint count = ge->cargo.Count();
-
-			/* Skip loading this vehicle if another train/vehicle is already handling
-			 * the same cargo type at this station */
-			if (_settings_game.order.improved_load && cargo_left[v->cargo_type] <= 0) {
-				SetBit(cargo_not_full, v->cargo_type);
-				continue;
-			}
+		int cap_left = v->cargo_cap - v->cargo.OnboardCount();
+		if (cap_left > 0 && (v->cargo.ActionCount(VehicleCargoList::MTA_LOAD) > 0 || !ge->cargo.Empty())) {
+			if (_settings_game.order.gradual_loading) cap_left = min(cap_left, load_amount);
+			if (v->cargo.OnboardCount() == 0) TriggerVehicle(v, VEHICLE_TRIGGER_NEW_CARGO);
 
-			if (cap > count) cap = count;
-			if (_settings_game.order.gradual_loading) {
-				cap = min(cap, load_amount);
-				cap_left = min(cap_left, load_amount);
-			}
-			if (_settings_game.order.improved_load) {
-				/* Don't load stuff that is already 'reserved' for other vehicles */
-				cap = min((uint)cargo_left[v->cargo_type], cap);
-				count = cargo_left[v->cargo_type];
-				if (use_autorefit) {
-					/* When using autorefit, reserve all cargo for this wagon to prevent other wagons
-					 * from feeling the need to refit. */
-					uint total_cap_left = v->cargo_cap - v->cargo.Count();
-					cargo_left[v->cargo_type] -= total_cap_left;
-					consist_capleft[v->cargo_type] -= total_cap_left;
-					if (total_cap_left > cap && count > cap) {
-						/* Remember if there are reservations left so that we don't stop
-						 * loading before they're loaded. */
-						SetBit(reservation_left, v->cargo_type);
-					}
-				} else {
-					/* Update cargo left; but don't reserve everything yet, so other wagons
-					 * of the same consist load in parallel. */
-					cargo_left[v->cargo_type] -= cap;
-				}
+			uint loaded = ge->cargo.Load(cap_left, &v->cargo, st->xy);
+			if (v->cargo.ActionCount(VehicleCargoList::MTA_LOAD) > 0) {
+				/* Remember if there are reservations left so that we don't stop
+				 * loading before they're loaded. */
+				SetBit(reservation_left, v->cargo_type);
 			}
 
 			/* Store whether the maximum possible load amount was loaded or not.*/
-			if (count >= (uint)cap_left) {
+			if (loaded == (uint)cap_left) {
 				SetBit(full_load_amount, v->cargo_type);
 			} else {
 				ClrBit(full_load_amount, v->cargo_type);
 			}
 
-			if (v->cargo.Empty()) TriggerVehicle(v, VEHICLE_TRIGGER_NEW_CARGO);
-
 			/* TODO: Regarding this, when we do gradual loading, we
 			 * should first unload all vehicles and then start
 			 * loading them. Since this will cause
@@ -1545,26 +1551,26 @@
 			 * the whole vehicle chain is really totally empty, the
 			 * completely_emptied assignment can then be safely
 			 * removed; that's how TTDPatch behaves too. --pasky */
-			completely_emptied = false;
-			anything_loaded = true;
+			if (loaded > 0) {
+				completely_emptied = false;
+				anything_loaded = true;
 
-			ge->cargo.MoveTo(&v->cargo, cap, StationCargoList::MTA_CARGO_LOAD, NULL, st->xy);
-
-			st->time_since_load = 0;
-			st->last_vehicle_type = v->type;
+				st->time_since_load = 0;
+				st->last_vehicle_type = v->type;
 
-			if (ge->cargo.Empty()) {
-				TriggerStationRandomisation(st, st->xy, SRT_CARGO_TAKEN, v->cargo_type);
-				TriggerStationAnimation(st, st->xy, SAT_CARGO_TAKEN, v->cargo_type);
-				AirportAnimationTrigger(st, AAT_STATION_CARGO_TAKEN, v->cargo_type);
+				if (ge->cargo.Empty()) {
+					TriggerStationRandomisation(st, st->xy, SRT_CARGO_TAKEN, v->cargo_type);
+					TriggerStationAnimation(st, st->xy, SAT_CARGO_TAKEN, v->cargo_type);
+					AirportAnimationTrigger(st, AAT_STATION_CARGO_TAKEN, v->cargo_type);
+				}
+
+				unloading_time += loaded;
+
+				dirty_vehicle = dirty_station = true;
 			}
-
-			unloading_time += cap;
-
-			dirty_vehicle = dirty_station = true;
 		}
 
-		if (v->cargo.Count() >= v->cargo_cap) {
+		if (v->cargo.OnboardCount() >= v->cargo_cap) {
 			SetBit(cargo_full, v->cargo_type);
 		} else {
 			SetBit(cargo_not_full, v->cargo_type);
@@ -1581,27 +1587,6 @@
 	/* Only set completely_emptied, if we just unloaded all remaining cargo */
 	completely_emptied &= anything_unloaded;
 
-	/* For consists without autorefit-order we adjust the reserved cargo at the station after loading,
-	 * so that all wagons start loading if the consist is the first consist.
-	 *
-	 * If we use autorefit otoh, we only want to load/refit a vehicle if the other wagons cannot already hold the cargo,
-	 * to keep the option to still refit the vehicle when new cargo of different type shows up.
-	 */
-	if (_settings_game.order.improved_load && (front->current_order.GetLoadType() & OLFB_FULL_LOAD)) {
-		/* Update left cargo */
-		for (Vehicle *v = front; v != NULL; v = v->Next()) {
-			int cap_left = v->cargo_cap;
-			if (!HasBit(v->vehicle_flags, VF_CARGO_UNLOADING)) {
-				if (use_autorefit) {
-					continue;
-				} else {
-					cap_left -= v->cargo.Count();
-				}
-			}
-			if (cap_left > 0) cargo_left[v->cargo_type] -= cap_left;
-		}
-	}
-
 	if (!anything_unloaded) delete payment;
 
 	ClrBit(front->vehicle_flags, VF_STOP_LOADING);
@@ -1625,7 +1610,7 @@
 			if (front->current_order.GetLoadType() == OLF_FULL_LOAD_ANY) {
 				/* if the aircraft carries passengers and is NOT full, then
 				 * continue loading, no matter how much mail is in */
-				if ((front->type == VEH_AIRCRAFT && IsCargoInClass(front->cargo_type, CC_PASSENGERS) && front->cargo_cap > front->cargo.Count()) ||
+				if ((front->type == VEH_AIRCRAFT && IsCargoInClass(front->cargo_type, CC_PASSENGERS) && front->cargo_cap > front->cargo.OnboardCount()) ||
 						(cargo_not_full && (cargo_full & ~cargo_not_full) == 0)) { // There are still non-full cargoes
 					finished_loading = false;
 				}
@@ -1716,13 +1701,9 @@
 	 */
 	if (last_loading == NULL) return;
 
-	int cargo_left[NUM_CARGO];
-
-	for (uint i = 0; i < NUM_CARGO; i++) cargo_left[i] = st->goods[i].cargo.Count();
-
 	for (iter = st->loading_vehicles.begin(); iter != st->loading_vehicles.end(); ++iter) {
 		Vehicle *v = *iter;
-		if (!(v->vehstatus & (VS_STOPPED | VS_CRASHED))) LoadUnloadVehicle(v, cargo_left);
+		if (!(v->vehstatus & (VS_STOPPED | VS_CRASHED))) LoadUnloadVehicle(v);
 		if (v == last_loading) break;
 	}
 
--- a/src/saveload/cargopacket_sl.cpp
+++ b/src/saveload/cargopacket_sl.cpp
@@ -77,6 +77,11 @@
 			for (CargoID c = 0; c < NUM_CARGO; c++) st->goods[c].cargo.InvalidateCache();
 		}
 	}
+
+	if (IsSavegameVersionBefore(181)) {
+		Vehicle *v;
+		FOR_ALL_VEHICLES(v) v->cargo.KeepAll();
+	}
 }
 
 /**
--- a/src/saveload/saveload.cpp
+++ b/src/saveload/saveload.cpp
@@ -244,8 +244,9 @@
  *  178   24789
  *  179   24810
  *  180   24998
+ *  181   25012
  */
-extern const uint16 SAVEGAME_VERSION = 180; ///< Current savegame version of OpenTTD.
+extern const uint16 SAVEGAME_VERSION = 181; ///< Current savegame version of OpenTTD.
 
 SavegameType _savegame_type; ///< type of savegame we are loading
 
--- a/src/saveload/station_sl.cpp
+++ b/src/saveload/station_sl.cpp
@@ -259,7 +259,7 @@
 		SLEG_CONDVAR(            _cargo_feeder_share, SLE_INT64,                  65, 67),
 		 SLE_CONDVAR(GoodsEntry, amount_fract,        SLE_UINT8,                 150, SL_MAX_VERSION),
 		 SLE_CONDLST(GoodsEntry, cargo.packets,       REF_CARGO_PACKET,           68, SL_MAX_VERSION),
-
+		 SLE_CONDVAR(GoodsEntry, cargo.reserved_count,SLE_UINT,                  181, SL_MAX_VERSION),
 		SLE_END()
 	};
 
--- a/src/saveload/vehicle_sl.cpp
+++ b/src/saveload/vehicle_sl.cpp
@@ -614,6 +614,7 @@
 		     SLE_VAR(Vehicle, cargo_cap,             SLE_UINT16),
 		SLEG_CONDVAR(         _cargo_count,          SLE_UINT16,                   0,  67),
 		 SLE_CONDLST(Vehicle, cargo.packets,         REF_CARGO_PACKET,            68, SL_MAX_VERSION),
+		 SLE_CONDARR(Vehicle, cargo.action_counts,   SLE_UINT, VehicleCargoList::NUM_MOVE_TO_ACTION, 181, SL_MAX_VERSION),
 		 SLE_CONDVAR(Vehicle, cargo_age_counter,     SLE_UINT16,                 162, SL_MAX_VERSION),
 
 		     SLE_VAR(Vehicle, day_counter,           SLE_UINT8),
--- a/src/vehicle.cpp
+++ b/src/vehicle.cpp
@@ -720,10 +720,11 @@
 	if (CleaningPool()) return;
 
 	if (Station::IsValidID(this->last_station_visited)) {
-		Station::Get(this->last_station_visited)->loading_vehicles.remove(this);
+		Station *st = Station::Get(this->last_station_visited);
+		st->loading_vehicles.remove(this);
 
 		HideFillingPercent(&this->fill_percent_te_id);
-
+		this->CancelReservation(st);
 		delete this->cargo_payment;
 	}
 
@@ -1255,7 +1256,7 @@
 
 	/* Count up max and used */
 	for (const Vehicle *v = front; v != NULL; v = v->Next()) {
-		count += v->cargo.Count();
+		count += v->cargo.OnboardCount();
 		max += v->cargo_cap;
 		if (v->cargo_cap != 0 && colour != NULL) {
 			unloading += HasBit(v->vehicle_flags, VF_CARGO_UNLOADING) ? 1 : 0;
@@ -1981,6 +1982,22 @@
 }
 
 /**
+ * Return all reserved cargo packets to the station.
+ * @param st the station where the reserved packets should go.
+ */
+void Vehicle::CancelReservation(Station *st)
+{
+	for (Vehicle *v = this; v != NULL; v = v->next) {
+		VehicleCargoList &cargo = v->cargo;
+		if (cargo.ActionCount(VehicleCargoList::MTA_LOAD) > 0) {
+			DEBUG(misc, 1, "cancelling cargo reservation");
+			cargo.Return(UINT_MAX, &st->goods[v->cargo_type].cargo);
+			cargo.KeepAll();
+		}
+	}
+}
+
+/**
  * Perform all actions when leaving a station.
  * @pre this->current_order.IsType(OT_LOADING)
  */
@@ -1995,6 +2012,7 @@
 
 	this->current_order.MakeLeaveStation();
 	Station *st = Station::Get(this->last_station_visited);
+	this->CancelReservation(st);
 	st->loading_vehicles.remove(this);
 
 	HideFillingPercent(&this->fill_percent_te_id);
--- a/src/vehicle_base.h
+++ b/src/vehicle_base.h
@@ -244,6 +244,7 @@
 	virtual ~Vehicle();
 
 	void BeginLoading();
+	void CancelReservation(Station *st);
 	void LeaveStation();
 
 	GroundVehicleCache *GetGroundVehicleCache();