From 3507b51d9e11b7d4260b9e06c56365e412071dfd Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Sun, 4 Sep 2016 16:49:27 +0100 Subject: [PATCH] Initial LinkRefresher support for cargo-specific links. --- src/linkgraph/refresh.cpp | 82 ++++++++++++++++++++++++++++++++------- src/linkgraph/refresh.h | 5 ++- src/order_base.h | 5 +-- src/order_cmd.cpp | 75 +++++++++++++++++------------------ src/station_cmd.cpp | 22 ----------- src/train_cmd.cpp | 9 +++++ src/vehicle.cpp | 80 ++++++++++++++++++++++++++++++-------- src/vehicle_base.h | 4 +- 8 files changed, 188 insertions(+), 94 deletions(-) diff --git a/src/linkgraph/refresh.cpp b/src/linkgraph/refresh.cpp index 501f063520..ae2c5fa6e7 100644 --- a/src/linkgraph/refresh.cpp +++ b/src/linkgraph/refresh.cpp @@ -25,19 +25,69 @@ * @param allow_merge If the refresher is allowed to merge or extend link graphs. * @param is_full_loading If the vehicle is full loading. */ -/* static */ void LinkRefresher::Run(Vehicle *v, bool allow_merge, bool is_full_loading) +/* static */ void LinkRefresher::Run(Vehicle *v, bool allow_merge, bool is_full_loading, bool check_cargo_can_leave) { /* If there are no orders we can't predict anything.*/ if (v->orders.list == NULL) return; - /* Make sure the first order is a useful order. */ - const Order *first = v->orders.list->GetNextDecisionNode(v->GetOrder(v->cur_implicit_order_index), 0); - if (first == NULL) return; + /* Scan orders for cargo-specific load/unload, and run LinkRefresher separately for each set of cargoes where they differ. */ + uint32 cargoes_to_check = ~0; + while (cargoes_to_check != 0) { + uint32 cargo_mask = cargoes_to_check; + const CargoID first_cargo_id = FindFirstBit(cargo_mask); + for (const Order *o = v->orders.list->GetFirstOrder(); o != NULL; o = o->next) { + if (o->IsType(OT_GOTO_STATION) || o->IsType(OT_IMPLICIT)) { + if (o->GetUnloadType() == OUFB_CARGO_TYPE_UNLOAD) { + OrderUnloadFlags ouf = o->GetCargoUnloadType(first_cargo_id); + uint32 other_cargo_mask = cargo_mask; + ClrBit(other_cargo_mask, first_cargo_id); + CargoID cargo; + FOR_EACH_SET_BIT(cargo, other_cargo_mask) { + if (((ouf ^ o->GetCargoUnloadType(cargo)) & (OUFB_TRANSFER | OUFB_UNLOAD | OUFB_NO_UNLOAD)) != 0) ClrBit(cargo_mask, cargo); + } + } + if (o->GetLoadType() == OLFB_CARGO_TYPE_LOAD) { + OrderLoadFlags olf = o->GetCargoLoadType(first_cargo_id); + uint32 other_cargo_mask = cargo_mask; + ClrBit(other_cargo_mask, first_cargo_id); + CargoID cargo; + FOR_EACH_SET_BIT(cargo, other_cargo_mask) { + if (((olf ^ o->GetCargoLoadType(cargo)) & (OLFB_NO_LOAD)) != 0) ClrBit(cargo_mask, cargo); + } + } + } + } - HopSet seen_hops; - LinkRefresher refresher(v, &seen_hops, allow_merge, is_full_loading); + bool has_cargo; + if (!HasBit(v->vehicle_flags, VF_LAST_LOAD_ST_SEP)) { + has_cargo = v->last_loading_station != INVALID_STATION; + } else { + has_cargo = false; + for (const Vehicle *u = v; u != NULL; u = u->Next()) { + if (u->cargo_type < NUM_CARGO && HasBit(cargo_mask, u->cargo_type) && u->last_loading_station != INVALID_STATION) { + has_cargo = true; + break; + } + } + } - refresher.RefreshLinks(first, first, v->last_loading_station != INVALID_STATION ? 1 << HAS_CARGO : 0); + if (check_cargo_can_leave && (v->current_order.IsType(OT_GOTO_STATION) || v->current_order.IsType(OT_IMPLICIT)) && + (!v->current_order.CanLeaveWithCargo(has_cargo, first_cargo_id))) { + cargoes_to_check &= ~cargo_mask; + continue; + } + + /* Make sure the first order is a useful order. */ + const Order *first = v->orders.list->GetNextDecisionNode(v->GetOrder(v->cur_implicit_order_index), 0, cargo_mask); + if (first != NULL) { + HopSet seen_hops; + LinkRefresher refresher(v, &seen_hops, allow_merge, is_full_loading, cargo_mask); + + refresher.RefreshLinks(first, first, has_cargo ? 1 << HAS_CARGO : 0); + } + + cargoes_to_check &= ~cargo_mask; + } } /** @@ -68,9 +118,9 @@ bool LinkRefresher::Hop::operator<(const Hop &other) const * @param allow_merge If the refresher is allowed to merge or extend link graphs. * @param is_full_loading If the vehicle is full loading. */ -LinkRefresher::LinkRefresher(Vehicle *vehicle, HopSet *seen_hops, bool allow_merge, bool is_full_loading) : +LinkRefresher::LinkRefresher(Vehicle *vehicle, HopSet *seen_hops, bool allow_merge, bool is_full_loading, uint32 cargo_mask) : vehicle(vehicle), seen_hops(seen_hops), cargo(CT_INVALID), allow_merge(allow_merge), - is_full_loading(is_full_loading) + is_full_loading(is_full_loading), cargo_mask(cargo_mask) { memset(this->capacities, 0, sizeof(this->capacities)); @@ -175,8 +225,10 @@ const Order *LinkRefresher::PredictNextOrder(const Order *cur, const Order *next SetBit(flags, USE_NEXT); if (next->IsType(OT_CONDITIONAL)) { + uint32 this_cargo_mask = this->cargo_mask; const Order *skip_to = this->vehicle->orders.list->GetNextDecisionNode( - this->vehicle->orders.list->GetOrderAt(next->GetConditionSkipToOrder()), num_hops); + this->vehicle->orders.list->GetOrderAt(next->GetConditionSkipToOrder()), num_hops, this_cargo_mask); + assert(this_cargo_mask == this->cargo_mask); if (skip_to != NULL && num_hops < this->vehicle->orders.list->GetNumOrders()) { /* Make copies of capacity tracking lists. There is potential * for optimization here: If the vehicle never refits we don't @@ -189,8 +241,10 @@ const Order *LinkRefresher::PredictNextOrder(const Order *cur, const Order *next /* Reassign next with the following stop. This can be a station or a * depot.*/ + uint32 this_cargo_mask = this->cargo_mask; next = this->vehicle->orders.list->GetNextDecisionNode( - this->vehicle->orders.list->GetNext(next), num_hops++); + this->vehicle->orders.list->GetNext(next), num_hops++, this_cargo_mask); + assert(this_cargo_mask == this->cargo_mask); } return next; } @@ -208,6 +262,8 @@ void LinkRefresher::RefreshStats(const Order *cur, const Order *next) for (CargoID c = 0; c < NUM_CARGO; c++) { /* Refresh the link and give it a minimum capacity. */ + if (!HasBit(this->cargo_mask, c)) continue; + uint cargo_quantity = this->capacities[c]; if (cargo_quantity == 0) continue; @@ -218,7 +274,7 @@ void LinkRefresher::RefreshStats(const Order *cur, const Order *next) } /* A link is at least partly restricted if a vehicle can't load at its source. */ - EdgeUpdateMode restricted_mode = (cur->GetLoadType() & OLFB_NO_LOAD) == 0 ? + EdgeUpdateMode restricted_mode = (cur->GetCargoLoadType(c) & OLFB_NO_LOAD) == 0 ? EUM_UNRESTRICTED : EUM_RESTRICTED; /* If the vehicle is currently full loading, increase the capacities at the station @@ -309,7 +365,7 @@ void LinkRefresher::RefreshLinks(const Order *cur, const Order *next, uint8 flag } if (cur->IsType(OT_GOTO_STATION) || cur->IsType(OT_IMPLICIT)) { - if (cur->CanLeaveWithCargo(HasBit(flags, HAS_CARGO))) { + if (cur->CanLeaveWithCargo(HasBit(flags, HAS_CARGO), FindFirstBit(this->cargo_mask))) { SetBit(flags, HAS_CARGO); this->RefreshStats(cur, next); } else { diff --git a/src/linkgraph/refresh.h b/src/linkgraph/refresh.h index 42067ebcf9..3d30853274 100644 --- a/src/linkgraph/refresh.h +++ b/src/linkgraph/refresh.h @@ -23,7 +23,7 @@ */ class LinkRefresher { public: - static void Run(Vehicle *v, bool allow_merge = true, bool is_full_loading = false); + static void Run(Vehicle *v, bool allow_merge = true, bool is_full_loading = false, bool check_cargo_can_leave = false); protected: /** @@ -89,8 +89,9 @@ protected: CargoID cargo; ///< Cargo given in last refit order. bool allow_merge; ///< If the refresher is allowed to merge or extend link graphs. bool is_full_loading; ///< If the vehicle is full loading. + uint32 cargo_mask; ///< Bit-mask of cargo IDs to refresh. - LinkRefresher(Vehicle *v, HopSet *seen_hops, bool allow_merge, bool is_full_loading); + LinkRefresher(Vehicle *v, HopSet *seen_hops, bool allow_merge, bool is_full_loading, uint32 cargo_mask); bool HandleRefit(CargoID refit_cargo); void ResetRefit(); diff --git a/src/order_base.h b/src/order_base.h index cc462e2a20..11cb78c47b 100644 --- a/src/order_base.h +++ b/src/order_base.h @@ -300,8 +300,7 @@ public: inline void SetMaxSpeed(uint16 speed) { this->max_speed = speed; } bool ShouldStopAtStation(const Vehicle *v, StationID station) const; - bool CanLoadOrUnload() const; - bool CanLeaveWithCargo(bool has_cargo) const; + bool CanLeaveWithCargo(bool has_cargo, CargoID cargo) const; TileIndex GetLocation(const Vehicle *v, bool airport = false) const; @@ -429,7 +428,7 @@ public: inline VehicleOrderID GetNumManualOrders() const { return this->num_manual_orders; } CargoMaskedStationIDStack GetNextStoppingStation(const Vehicle *v, uint32 cargo_mask, const Order *first = NULL, uint hops = 0) const; - const Order *GetNextDecisionNode(const Order *next, uint hops) const; + const Order *GetNextDecisionNode(const Order *next, uint hops, uint32 &cargo_mask) const; void InsertOrderAt(Order *new_order, int index); void DeleteOrderAt(int index); diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 54f642e65e..9d93ba1cbc 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -380,14 +380,17 @@ Order *OrderList::GetOrderAt(int index) const * or refit at a depot or evaluate a non-trivial condition. * @param next The order to start looking at. * @param hops The number of orders we have already looked at. + * @param cargo_mask The bit set of cargoes that the we are looking at, this may be reduced to indicate the set of cargoes that the result is valid for. * @return Either of * \li a station order * \li a refitting depot order * \li a non-trivial conditional order * \li NULL if the vehicle won't stop anymore. */ -const Order *OrderList::GetNextDecisionNode(const Order *next, uint hops) const +const Order *OrderList::GetNextDecisionNode(const Order *next, uint hops, uint32 &cargo_mask) const { + assert(cargo_mask != 0); + if (hops > this->GetNumOrders() || next == NULL) return NULL; if (next->IsType(OT_CONDITIONAL)) { @@ -397,7 +400,7 @@ const Order *OrderList::GetNextDecisionNode(const Order *next, uint hops) const * the same as regular order progression. */ return this->GetNextDecisionNode( this->GetOrderAt(next->GetConditionSkipToOrder()), - hops + 1); + hops + 1, cargo_mask); } if (next->IsType(OT_GOTO_DEPOT)) { @@ -405,8 +408,30 @@ const Order *OrderList::GetNextDecisionNode(const Order *next, uint hops) const if (next->IsRefit()) return next; } - if (!next->CanLoadOrUnload()) { - return this->GetNextDecisionNode(this->GetNext(next), hops + 1); + bool can_load_or_unload = false; + if ((next->IsType(OT_GOTO_STATION) || next->IsType(OT_IMPLICIT)) && + (next->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) == 0) { + if (next->GetUnloadType() == OUFB_CARGO_TYPE_UNLOAD || next->GetLoadType() == OLFB_CARGO_TYPE_LOAD) { + /* This is a cargo-specific load/unload order. + * If the first cargo is both a no-load and no-unload order, skip it. + * Drop cargoes which don't match the first one. */ + CargoID first_cargo_id = FindFirstBit(cargo_mask); + can_load_or_unload = ((next->GetCargoLoadType(first_cargo_id) & OLFB_NO_LOAD) == 0 || (next->GetCargoUnloadType(first_cargo_id) & OUFB_NO_UNLOAD) == 0); + uint32 other_cargo_mask = cargo_mask; + ClrBit(other_cargo_mask, first_cargo_id); + CargoID cargo; + FOR_EACH_SET_BIT(cargo, other_cargo_mask) { + if (can_load_or_unload != ((next->GetCargoLoadType(cargo) & OLFB_NO_LOAD) == 0 || (next->GetCargoUnloadType(cargo) & OUFB_NO_UNLOAD) == 0)) { + ClrBit(cargo_mask, cargo); + } + } + } else if ((next->GetLoadType() & OLFB_NO_LOAD) == 0 || (next->GetUnloadType() & OUFB_NO_UNLOAD) == 0) { + can_load_or_unload = true; + } + } + + if (!can_load_or_unload) { + return this->GetNextDecisionNode(this->GetNext(next), hops + 1, cargo_mask); } return next; @@ -425,9 +450,6 @@ const Order *OrderList::GetNextDecisionNode(const Order *next, uint hops) const CargoMaskedStationIDStack OrderList::GetNextStoppingStation(const Vehicle *v, uint32 cargo_mask, const Order *first, uint hops) const { assert(cargo_mask != 0); - CargoID first_cargo_id = FindFirstBit(cargo_mask); - uint32 other_cargo_mask = cargo_mask; - ClrBit(other_cargo_mask, first_cargo_id); const Order *next = first; if (first == NULL) { @@ -445,15 +467,15 @@ CargoMaskedStationIDStack OrderList::GetNextStoppingStation(const Vehicle *v, ui } do { - next = this->GetNextDecisionNode(next, ++hops); + next = this->GetNextDecisionNode(next, ++hops, cargo_mask); /* Resolve possibly nested conditionals by estimation. */ while (next != NULL && next->IsType(OT_CONDITIONAL)) { /* We return both options of conditional orders. */ const Order *skip_to = this->GetNextDecisionNode( - this->GetOrderAt(next->GetConditionSkipToOrder()), hops); + this->GetOrderAt(next->GetConditionSkipToOrder()), hops, cargo_mask); const Order *advance = this->GetNextDecisionNode( - this->GetNext(next), hops); + this->GetNext(next), hops, cargo_mask); if (advance == NULL || advance == first || skip_to == advance) { next = (skip_to == first) ? NULL : skip_to; } else if (skip_to == NULL || skip_to == first) { @@ -471,36 +493,23 @@ CargoMaskedStationIDStack OrderList::GetNextStoppingStation(const Vehicle *v, ui if (next == NULL) return CargoMaskedStationIDStack(cargo_mask, INVALID_STATION); - if ((next->IsType(OT_GOTO_STATION) || next->IsType(OT_IMPLICIT)) && (next->GetUnloadType() == OUFB_CARGO_TYPE_UNLOAD || next->GetLoadType() == OLFB_CARGO_TYPE_LOAD)) { - /* This is a cargo-specific load/unload order. - * If the first cargo is both a no-load and no-unload order, skip it. - * Drop cargoes which don't match the first one. */ - bool should_drop = next->GetCargoLoadType(first_cargo_id) & OLFB_NO_LOAD && next->GetCargoUnloadType(first_cargo_id) & OUFB_NO_UNLOAD; - CargoID cargo; - FOR_EACH_SET_BIT(cargo, other_cargo_mask) { - if (should_drop != (next->GetCargoLoadType(cargo) & OLFB_NO_LOAD && next->GetCargoUnloadType(cargo) & OUFB_NO_UNLOAD)) { - ClrBit(cargo_mask, cargo); - } - } - other_cargo_mask &= cargo_mask; - if (should_drop) continue; - } - /* Don't return a next stop if the vehicle has to unload everything. */ if ((next->IsType(OT_GOTO_STATION) || next->IsType(OT_IMPLICIT)) && next->GetDestination() == v->last_station_visited) { + CargoID first_cargo_id = FindFirstBit(cargo_mask); bool invalid = ((next->GetCargoUnloadType(first_cargo_id) & (OUFB_TRANSFER | OUFB_UNLOAD)) != 0); if (next->GetUnloadType() == OUFB_CARGO_TYPE_UNLOAD) { /* This is a cargo-specific load/unload order. * Don't return a next stop if first cargo has transfer or unload set. * Drop cargoes which don't match the first one. */ + uint32 other_cargo_mask = cargo_mask; + ClrBit(other_cargo_mask, first_cargo_id); CargoID cargo; FOR_EACH_SET_BIT(cargo, other_cargo_mask) { if (invalid != ((next->GetCargoUnloadType(cargo) & (OUFB_TRANSFER | OUFB_UNLOAD)) != 0)) { ClrBit(cargo_mask, cargo); } } - other_cargo_mask &= cargo_mask; } if (invalid) return CargoMaskedStationIDStack(cargo_mask, INVALID_STATION); } @@ -2339,22 +2348,14 @@ bool Order::ShouldStopAtStation(const Vehicle *v, StationID station) const !(this->GetNonStopType() & (is_dest_station ? ONSF_NO_STOP_AT_DESTINATION_STATION : ONSF_NO_STOP_AT_INTERMEDIATE_STATIONS)); } -bool Order::CanLoadOrUnload() const -{ - return (this->IsType(OT_GOTO_STATION) || this->IsType(OT_IMPLICIT)) && - (this->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) == 0 && - ((this->GetLoadType() & OLFB_NO_LOAD) == 0 || - (this->GetUnloadType() & OUFB_NO_UNLOAD) == 0); -} - /** * A vehicle can leave the current station with cargo if: * 1. it can load cargo here OR * 2a. it could leave the last station with cargo AND * 2b. it doesn't have to unload all cargo here. */ -bool Order::CanLeaveWithCargo(bool has_cargo) const +bool Order::CanLeaveWithCargo(bool has_cargo, CargoID cargo) const { - return (this->GetLoadType() & OLFB_NO_LOAD) == 0 || (has_cargo && - (this->GetUnloadType() & (OUFB_UNLOAD | OUFB_TRANSFER)) == 0); + return (this->GetCargoLoadType(cargo) & OLFB_NO_LOAD) == 0 || (has_cargo && + (this->GetCargoUnloadType(cargo) & (OUFB_UNLOAD | OUFB_TRANSFER)) == 0); } diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index eb90c29ab5..f14d85e9bf 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -3563,28 +3563,6 @@ void IncreaseStats(Station *st, CargoID cargo, StationID next_station_id, uint c } } -/** - * Increase capacity for all link stats associated with vehicles in the given consist. - * @param st Station to get the link stats from. - * @param front First vehicle in the consist. - * @param next_station_id Station the consist will be travelling to next. - */ -void IncreaseStats(Station *st, const Vehicle *front, StationID next_station_id) -{ - for (const Vehicle *v = front; v != NULL; v = v->Next()) { - if (v->refit_cap > 0) { - /* The cargo count can indeed be higher than the refit_cap if - * wagons have been auto-replaced and subsequently auto- - * refitted to a higher capacity. The cargo gets redistributed - * among the wagons in that case. - * As usage is not such an important figure anyway we just - * ignore the additional cargo then.*/ - IncreaseStats(st, v->cargo_type, next_station_id, v->refit_cap, - min(v->refit_cap, v->cargo.StoredCount()), EUM_INCREASE); - } - } -} - /* called for every station each tick */ static void StationHandleSmallTick(BaseStation *st) { diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index dca456d976..9d0b942984 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -1332,6 +1332,15 @@ CommandCost CmdMoveRailVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, u CheckCargoCapacity(dst_head); } + if (src_head != NULL) { + src_head->last_loading_station = INVALID_STATION; + ClrBit(src_head->vehicle_flags, VF_LAST_LOAD_ST_SEP); + } + if (dst_head != NULL) { + dst_head->last_loading_station = INVALID_STATION; + ClrBit(dst_head->vehicle_flags, VF_LAST_LOAD_ST_SEP); + } + if (src_head != NULL) src_head->First()->MarkDirty(); if (dst_head != NULL) dst_head->First()->MarkDirty(); diff --git a/src/vehicle.cpp b/src/vehicle.cpp index da2cfeb807..44f279a95f 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1460,6 +1460,7 @@ void VehicleEnterDepot(Vehicle *v) * before the stop to the station after the stop can't be predicted * we shouldn't construct it when the vehicle visits the next stop. */ v->last_loading_station = INVALID_STATION; + ClrBit(v->vehicle_flags, VF_LAST_LOAD_ST_SEP); if (v->owner == _local_company) { SetDParam(0, v->index); AddVehicleAdviceNewsItem(STR_NEWS_TRAIN_IS_WAITING + v->type, v->index); @@ -1930,6 +1931,33 @@ void Vehicle::DeleteUnreachedImplicitOrders() } } +/** + * Increase capacity for all link stats associated with vehicles in the given consist. + * @param st Station to get the link stats from. + * @param front First vehicle in the consist. + * @param next_station_id Station the consist will be travelling to next. + */ +static void VehicleIncreaseStats(const Vehicle *front) +{ + for (const Vehicle *v = front; v != NULL; v = v->Next()) { + StationID last_loading_station = HasBit(front->vehicle_flags, VF_LAST_LOAD_ST_SEP) ? v->last_loading_station : front->last_loading_station; + if (v->refit_cap > 0 && + last_loading_station != INVALID_STATION && + last_loading_station != front->last_station_visited && + ((front->current_order.GetCargoLoadType(v->cargo_type) & OLFB_NO_LOAD) == 0 || + (front->current_order.GetCargoUnloadType(v->cargo_type) & OUFB_NO_UNLOAD) == 0)) { + /* The cargo count can indeed be higher than the refit_cap if + * wagons have been auto-replaced and subsequently auto- + * refitted to a higher capacity. The cargo gets redistributed + * among the wagons in that case. + * As usage is not such an important figure anyway we just + * ignore the additional cargo then.*/ + IncreaseStats(Station::Get(last_loading_station), v->cargo_type, front->last_station_visited, v->refit_cap, + min(v->refit_cap, v->cargo.StoredCount()), EUM_INCREASE); + } + } +} + /** * Prepare everything to begin the loading when arriving at a station. * @pre IsTileType(this->tile, MP_STATION) || this->type == VEH_SHIP. @@ -2040,12 +2068,7 @@ void Vehicle::BeginLoading() this->current_order.MakeLoading(false); } - if (this->last_loading_station != INVALID_STATION && - this->last_loading_station != this->last_station_visited && - ((this->current_order.GetLoadType() & OLFB_NO_LOAD) == 0 || - (this->current_order.GetUnloadType() & OUFB_NO_UNLOAD) == 0)) { - IncreaseStats(Station::Get(this->last_loading_station), this, this->last_station_visited); - } + VehicleIncreaseStats(this); PrepareUnload(this); @@ -2093,19 +2116,44 @@ void Vehicle::LeaveStation() if ((this->current_order.GetLoadType() & OLFB_NO_LOAD) == 0 || (this->current_order.GetUnloadType() & OUFB_NO_UNLOAD) == 0) { - if (this->current_order.CanLeaveWithCargo(this->last_loading_station != INVALID_STATION)) { - /* Refresh next hop stats to make sure we've done that at least once - * during the stop and that refit_cap == cargo_cap for each vehicle in - * the consist. */ + if (HasBit(this->vehicle_flags, VF_LAST_LOAD_ST_SEP) || (this->current_order.GetLoadType() == OLFB_CARGO_TYPE_LOAD) || (this->current_order.GetUnloadType() == OUFB_CARGO_TYPE_UNLOAD)) { + /* Always run these, check is handled by check_cargo_can_leave param of LinkRefresher::Run */ this->ResetRefitCaps(); - LinkRefresher::Run(this); + LinkRefresher::Run(this, true, false, true); - /* if the vehicle could load here or could stop with cargo loaded set the last loading station */ - this->last_loading_station = this->last_station_visited; + /* NB: this is saved here as we overwrite it on the first iteration of the loop below */ + StationID head_last_loading_station = this->last_loading_station; + for (Vehicle *u = this; u != NULL; u = u->Next()) { + StationID last_loading_station = HasBit(this->vehicle_flags, VF_LAST_LOAD_ST_SEP) ? u->last_loading_station : head_last_loading_station; + if ((this->current_order.GetCargoLoadType(u->cargo_type) & OLFB_NO_LOAD) == 0 || + (this->current_order.GetCargoUnloadType(u->cargo_type) & OUFB_NO_UNLOAD) == 0) { + if (this->current_order.CanLeaveWithCargo(last_loading_station != INVALID_STATION, u->cargo_type)) { + u->last_loading_station = this->last_station_visited; + } else { + u->last_loading_station = INVALID_STATION; + } + } else { + u->last_loading_station = last_loading_station; + } + } + SetBit(this->vehicle_flags, VF_LAST_LOAD_ST_SEP); } else { - /* if the vehicle couldn't load and had to unload or transfer everything - * set the last loading station to invalid as it will leave empty. */ - this->last_loading_station = INVALID_STATION; + if (this->current_order.CanLeaveWithCargo(this->last_loading_station != INVALID_STATION, 0)) { + /* Refresh next hop stats to make sure we've done that at least once + * during the stop and that refit_cap == cargo_cap for each vehicle in + * the consist. */ + this->ResetRefitCaps(); + LinkRefresher::Run(this); + + /* if the vehicle could load here or could stop with cargo loaded set the last loading station */ + this->last_loading_station = this->last_station_visited; + ClrBit(this->vehicle_flags, VF_LAST_LOAD_ST_SEP); + } else { + /* if the vehicle couldn't load and had to unload or transfer everything + * set the last loading station to invalid as it will leave empty. */ + this->last_loading_station = INVALID_STATION; + ClrBit(this->vehicle_flags, VF_LAST_LOAD_ST_SEP); + } } } diff --git a/src/vehicle_base.h b/src/vehicle_base.h index 9aedc7facd..555ab54bf9 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -51,6 +51,8 @@ enum VehicleFlags { VF_PATHFINDER_LOST, ///< Vehicle's pathfinder is lost. VF_SERVINT_IS_CUSTOM, ///< Service interval is custom. VF_SERVINT_IS_PERCENT, ///< Service interval is percent. + + VF_LAST_LOAD_ST_SEP = 13, ///< Each vehicle of this chain has its last_loading_station field set separately }; /** Bit numbers used to indicate which of the #NewGRFCache values are valid. */ @@ -243,7 +245,7 @@ public: byte waiting_triggers; ///< Triggers to be yet matched before rerandomizing the random bits. StationID last_station_visited; ///< The last station we stopped at. - StationID last_loading_station; ///< Last station the vehicle has stopped at and could possibly leave from with any cargo loaded. + StationID last_loading_station; ///< Last station the vehicle has stopped at and could possibly leave from with any cargo loaded. (See VF_LAST_LOAD_ST_SEP). CargoID cargo_type; ///< type of cargo this vehicle is carrying byte cargo_subtype; ///< Used for livery refits (NewGRF variations)