diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp index 7571da114b..e8d2921404 100644 --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -106,7 +106,7 @@ void CheckCargoCapacity(Vehicle *v) 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 @@ static void TransferCargo(Vehicle *old_veh, Vehicle *new_head, bool part_of_chai 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); } } diff --git a/src/cargoaction.cpp b/src/cargoaction.cpp index eabe657da3..fe16546e4e 100644 --- a/src/cargoaction.cpp +++ b/src/cargoaction.cpp @@ -42,11 +42,9 @@ uint CargoRemoval::Preprocess(CargoPacket *cp) { 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; } @@ -70,6 +68,34 @@ bool CargoRemoval::Postprocess(CargoPacket *cp, uint remove) } } +/** + * 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::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::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. @@ -79,6 +105,7 @@ bool CargoRemoval::Postprocess(CargoPacket *cp, uint remove) 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 @@ bool CargoLoad::operator()(CargoPacket *cp) 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 @@ bool CargoTransfer::operator()(CargoPacket *cp) { 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 @@ bool CargoShift::operator()(CargoPacket *cp) { 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; } diff --git a/src/cargoaction.h b/src/cargoaction.h index f7ee1a0015..d59f05279d 100644 --- a/src/cargoaction.h +++ b/src/cargoaction.h @@ -34,13 +34,7 @@ public: */ 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 @@ public: 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 { +public: + CargoReturn(VehicleCargoList *source, StationCargoList *destination, uint max_move) : + CargoMovement(source, destination, max_move) {} + bool operator()(CargoPacket *cp); +}; + /** Action of shifting cargo from one vehicle to another. */ class CargoShift : public CargoMovement { public: diff --git a/src/cargopacket.cpp b/src/cargopacket.cpp index 842bf09131..56ab6b91c2 100644 --- 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 @@ CargoPacket::CargoPacket(uint16 count, byte days_in_transit, StationID source, T * @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 @@ inline CargoPacket *CargoPacket::Split(uint new_size) * 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 @@ inline void CargoPacket::Merge(CargoPacket *cp) * 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); @@ -194,31 +195,9 @@ void CargoList::AddToCache(const CargoPacket *cp) this->cargo_days_in_transit += cp->days_in_transit * cp->count; } -/** - * 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 -void CargoList::Append(CargoPacket *cp) -{ - assert(cp != NULL); - static_cast(this)->AddToCache(cp); - - for (List::reverse_iterator it(this->packets.rbegin()); it != this->packets.rend(); it++) { - if (CargoList::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. */ @@ -230,102 +209,6 @@ uint CargoList::Truncate(uint max_move) return max_move; } -/** - * 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 -template -bool CargoList::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(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(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(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 @@ -416,6 +299,47 @@ template * */ +/** + * 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. @@ -439,6 +363,33 @@ void VehicleCargoList::AddToCache(const CargoPacket *cp) this->Parent::AddToCache(cp); } +/** + * 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. */ @@ -454,6 +405,44 @@ void VehicleCargoList::AgeCargo() } } +/** + * 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 @@ void VehicleCargoList::InvalidateCache() 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; template class CargoList; - -/** Autoreplace Vehicle -> Vehicle 'transfer'. */ -template bool CargoList::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data); -/** Cargo unloading at a station. */ -template bool CargoList::MoveTo(StationCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data); -/** Cargo loading at a station. */ -template bool CargoList::MoveTo(VehicleCargoList *, uint max_move, MoveToAction mta, CargoPayment *payment, uint data); diff --git a/src/cargopacket.h b/src/cargopacket.h index ab0d45f47e..130848d351 100644 --- a/src/cargopacket.h +++ b/src/cargopacket.h @@ -29,6 +29,7 @@ typedef Pool extern CargoPacketPool _cargopacket_pool; template class CargoList; +class StationCargoList; // forward-declare, so we can use it in VehicleCargoList. extern const struct SaveLoad *GetCargoPacketDesc(); /** @@ -203,10 +204,13 @@ public: /** 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 @@ public: return this->count == 0 ? 0 : this->cargo_days_in_transit / this->count; } - - void Append(CargoPacket *cp); uint Truncate(uint max_move = UINT_MAX); - template - bool MoveTo(Tother_inst *dest, uint count, MoveToAction mta, CargoPayment *payment, uint data = 0); - void InvalidateCache(); }; @@ -298,11 +297,26 @@ protected: /** The (direct) parent of this class. */ typedef CargoList 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; @@ -314,6 +328,7 @@ public: friend class CargoDelivery; template friend class CargoRemoval; + friend class CargoReturn; /** * Returns total sum of the feeder share for all packets. @@ -324,10 +339,72 @@ public: 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 @@ public: * CargoList that is used for stations. */ class StationCargoList : public CargoList { +protected: + /** The (direct) parent of this class. */ + typedef CargoList 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; @@ -359,6 +442,17 @@ public: friend class CargoTransfer; template 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 diff --git a/src/economy.cpp b/src/economy.cpp index c3745df33d..f4f3c3a08e 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -1217,6 +1217,9 @@ void PrepareUnload(Vehicle *front_v) 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); } } @@ -1230,6 +1233,86 @@ void PrepareUnload(Vehicle *front_v) front_v->cargo_payment = new CargoPayment(front_v); } +/** + * 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 @@ -1249,31 +1332,24 @@ static bool IsArticulatedVehicleEmpty(Vehicle *v) /** * 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); + } + + /* 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 * out and let the train just leave as it always did. */ @@ -1282,16 +1358,6 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) 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 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) 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(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; - } - - /* 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); + /* ... 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; } - - 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; + /* 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; - unloading_time += amount_unloaded; + /* Deliver goods to the station */ + st->time_since_unload = 0; + } - anything_unloaded = true; if (_settings_game.order.gradual_loading && remaining) { completely_emptied = false; } else { @@ -1419,7 +1453,7 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) Backup 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 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) 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 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) 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 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) /* 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(); + 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); - /* 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; - } - - 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 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) * 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 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) /* 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 @@ static void LoadUnloadVehicle(Vehicle *front, int *cargo_left) 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 @@ void LoadUnloadStation(Station *st) */ 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; } diff --git a/src/saveload/cargopacket_sl.cpp b/src/saveload/cargopacket_sl.cpp index 092301e959..f9ebd34080 100644 --- 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(); + } } /** diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index a21f6242a5..a15a4a1601 100644 --- 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 diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index 9feb1f2742..b1ef9bbe75 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -259,7 +259,7 @@ const SaveLoad *GetGoodsDesc() 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() }; diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index a33ceb6d33..fe2fdba069 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -614,6 +614,7 @@ const SaveLoad *GetVehicleDescription(VehicleType vt) 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), diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 11269db5cf..8a3b517506 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -720,10 +720,11 @@ void Vehicle::PreDestructor() 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 @@ uint8 CalcPercentVehicleFilled(const Vehicle *front, StringID *colour) /* 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; @@ -1980,6 +1981,22 @@ void Vehicle::BeginLoading() this->MarkDirty(); } +/** + * 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 @@ void Vehicle::LeaveStation() 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); diff --git a/src/vehicle_base.h b/src/vehicle_base.h index f0502a2186..7b40d48198 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -244,6 +244,7 @@ public: virtual ~Vehicle(); void BeginLoading(); + void CancelReservation(Station *st); void LeaveStation(); GroundVehicleCache *GetGroundVehicleCache();