diff --git a/src/economy.cpp b/src/economy.cpp index fb4fc6dcc8..5c12a97550 100644 --- a/src/economy.cpp +++ b/src/economy.cpp @@ -1287,7 +1287,7 @@ void PrepareUnload(Vehicle *front_v) assert(CargoPayment::CanAllocateItem()); front_v->cargo_payment = new CargoPayment(front_v); - StationIDStack next_station = front_v->GetNextStoppingStation(); + CargoStationIDStackSet next_station = front_v->GetNextStoppingStation(); if (front_v->orders.list == NULL || (front_v->current_order.GetUnloadType() & OUFB_NO_UNLOAD) == 0) { Station *st = Station::Get(front_v->last_station_visited); for (Vehicle *v = front_v; v != NULL; v = v->Next()) { @@ -1296,7 +1296,7 @@ void PrepareUnload(Vehicle *front_v) if (v->cargo_cap > 0 && v->cargo.TotalCount() > 0) { v->cargo.Stage( HasBit(ge->status, GoodsEntry::GES_ACCEPTANCE), - front_v->last_station_visited, next_station, + front_v->last_station_visited, next_station.Get(v->cargo_type), GetUnloadType(v), ge, front_v->cargo_payment); if (v->cargo.UnloadCount() > 0) SetBit(v->vehicle_flags, VF_CARGO_UNLOADING); @@ -1450,7 +1450,7 @@ struct FinalizeRefitAction { CargoArray &consist_capleft; ///< Capacities left in the consist. Station *st; ///< Station to reserve cargo from. - StationIDStack &next_station; ///< Next hops to reserve cargo for. + const CargoStationIDStackSet &next_station; ///< Next hops to reserve cargo for. bool do_reserve; ///< If the vehicle should reserve. Vehicle *cargo_type_loading; ///< Non-null if vehicle should reserve if the cargo type of the vehicle is a cargo-specific full-load order using this pointer @@ -1460,8 +1460,9 @@ struct FinalizeRefitAction * @param st Station to reserve cargo from. * @param next_station Next hops to reserve cargo for. * @param do_reserve If we should reserve cargo or just add up the capacities. + * @param cargo_type_loading Non-null if vehicle should reserve if the cargo type of the vehicle is a cargo-specific full-load order using this pointer */ - FinalizeRefitAction(CargoArray &consist_capleft, Station *st, StationIDStack &next_station, bool do_reserve, Vehicle *cargo_type_loading) : + FinalizeRefitAction(CargoArray &consist_capleft, Station *st, const CargoStationIDStackSet &next_station, bool do_reserve, Vehicle *cargo_type_loading) : consist_capleft(consist_capleft), st(st), next_station(next_station), do_reserve(do_reserve), cargo_type_loading(cargo_type_loading) {} /** @@ -1474,7 +1475,7 @@ struct FinalizeRefitAction { if (this->do_reserve || (cargo_type_loading == NULL || (cargo_type_loading->current_order.GetCargoLoadTypeRaw(v->cargo_type) & OLFB_FULL_LOAD))) { this->st->goods[v->cargo_type].cargo.Reserve(v->cargo_cap - v->cargo.RemainingCount(), - &v->cargo, st->xy, this->next_station); + &v->cargo, st->xy, this->next_station.Get(v->cargo_type)); } this->consist_capleft[v->cargo_type] += v->cargo_cap - v->cargo.RemainingCount(); return true; @@ -1489,7 +1490,7 @@ struct FinalizeRefitAction * @param next_station Possible next stations the vehicle can travel to. * @param new_cid Target cargo for refit. */ -static void HandleStationRefit(Vehicle *v, CargoArray &consist_capleft, Station *st, StationIDStack next_station, CargoID new_cid) +static void HandleStationRefit(Vehicle *v, CargoArray &consist_capleft, Station *st, CargoStationIDStackSet next_station, CargoID new_cid) { Vehicle *v_start = v->GetFirstEnginePart(); if (!IterateVehicleParts(v_start, IsEmptyAction())) return; @@ -1507,7 +1508,7 @@ static void HandleStationRefit(Vehicle *v, CargoArray &consist_capleft, Station CargoID cid; new_cid = v_start->cargo_type; FOR_EACH_SET_CARGO_ID(cid, refit_mask) { - if (st->goods[cid].cargo.HasCargoFor(next_station)) { + if (st->goods[cid].cargo.HasCargoFor(next_station.Get(cid))) { /* Try to find out if auto-refitting would succeed. In case the refit is allowed, * the returned refit capacity will be greater than zero. */ DoCommand(v_start->tile, v_start->index, cid | 1U << 6 | 0xFF << 8 | 1U << 16, DC_QUERY_COST, GetCmdRefitVeh(v_start)); // Auto-refit and only this vehicle including artic parts. @@ -1547,10 +1548,10 @@ static void HandleStationRefit(Vehicle *v, CargoArray &consist_capleft, Station struct ReserveCargoAction { Station *st; - StationIDStack *next_station; + const CargoStationIDStackSet &next_station; Vehicle *cargo_type_loading; - ReserveCargoAction(Station *st, StationIDStack *next_station, Vehicle *cargo_type_loading) : + ReserveCargoAction(Station *st, const CargoStationIDStackSet &next_station, Vehicle *cargo_type_loading) : st(st), next_station(next_station), cargo_type_loading(cargo_type_loading) {} bool operator()(Vehicle *v) @@ -1558,7 +1559,7 @@ struct ReserveCargoAction { if (cargo_type_loading != NULL && !(cargo_type_loading->current_order.GetCargoLoadTypeRaw(v->cargo_type) & OLFB_FULL_LOAD)) return true; if (v->cargo_cap > v->cargo.RemainingCount()) { st->goods[v->cargo_type].cargo.Reserve(v->cargo_cap - v->cargo.RemainingCount(), - &v->cargo, st->xy, *next_station); + &v->cargo, st->xy, next_station.Get(v->cargo_type)); } return true; @@ -1575,7 +1576,7 @@ struct ReserveCargoAction { * @param next_station Station(s) the vehicle will stop at next. * @param cargo_type_loading check cargo-specific loading type */ -static void ReserveConsist(Station *st, Vehicle *u, CargoArray *consist_capleft, StationIDStack *next_station, bool cargo_type_loading) +static void ReserveConsist(Station *st, Vehicle *u, CargoArray *consist_capleft, const CargoStationIDStackSet &next_station, bool cargo_type_loading) { /* If there is a cargo payment not all vehicles of the consist have tried to do the refit. * In that case, only reserve if it's a fixed refit and the equivalent of "articulated chain" @@ -1631,24 +1632,26 @@ static void LoadUnloadVehicle(Vehicle *front) StationID last_visited = front->last_station_visited; Station *st = Station::Get(last_visited); - StationIDStack next_station = front->GetNextStoppingStation(); + CargoStationIDStackSet next_station = front->GetNextStoppingStation(); + bool use_autorefit = front->current_order.IsRefit() && front->current_order.GetRefitCargo() == CT_AUTO_REFIT; CargoArray consist_capleft; + bool should_reserve_consist = false; + bool reserve_consist_cargo_type_loading = false; if (_settings_game.order.improved_load && use_autorefit) { - if (front->cargo_payment == NULL) { - ReserveConsist(st, front, - (use_autorefit && front->load_unload_ticks != 0) ? &consist_capleft : NULL, - &next_station, - false); - } + if (front->cargo_payment == NULL) should_reserve_consist = true; } else { if ((front->current_order.GetLoadType() & OLFB_FULL_LOAD) || (front->current_order.GetLoadType() == OLFB_CARGO_TYPE_LOAD)) { - ReserveConsist(st, front, - (use_autorefit && front->load_unload_ticks != 0) ? &consist_capleft : NULL, - &next_station, - (front->current_order.GetLoadType() == OLFB_CARGO_TYPE_LOAD)); + should_reserve_consist = true; + reserve_consist_cargo_type_loading = (front->current_order.GetLoadType() == OLFB_CARGO_TYPE_LOAD); } } + if (should_reserve_consist) { + ReserveConsist(st, front, + (use_autorefit && front->load_unload_ticks != 0) ? &consist_capleft : NULL, + next_station, + reserve_consist_cargo_type_loading); + } /* We have not waited enough time till the next round of loading/unloading */ if (front->load_unload_ticks != 0) return; @@ -1797,7 +1800,7 @@ static void LoadUnloadVehicle(Vehicle *front) if (_settings_game.order.gradual_loading) cap_left = min(cap_left, load_amount); if (v->cargo.StoredCount() == 0) TriggerVehicle(v, VEHICLE_TRIGGER_NEW_CARGO); - uint loaded = ge->cargo.Load(cap_left, &v->cargo, st->xy, next_station); + uint loaded = ge->cargo.Load(cap_left, &v->cargo, st->xy, next_station.Get(v->cargo_type)); 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. */ diff --git a/src/order_base.h b/src/order_base.h index ff0a003c13..cc462e2a20 100644 --- a/src/order_base.h +++ b/src/order_base.h @@ -21,6 +21,8 @@ #include "vehicle_type.h" #include "date_type.h" +#include + typedef Pool OrderPool; typedef Pool OrderListPool; extern OrderPool _order_pool; @@ -325,6 +327,35 @@ public: void InsertOrder(Vehicle *v, Order *new_o, VehicleOrderID sel_ord); void DeleteOrder(Vehicle *v, VehicleOrderID sel_ord); +struct CargoMaskedStationIDStack { + uint32 cargo_mask; + StationIDStack station; + + CargoMaskedStationIDStack(uint32 cargo_mask, StationIDStack station) + : cargo_mask(cargo_mask), station(station) {} +}; + +struct CargoStationIDStackSet { +private: + CargoMaskedStationIDStack first; + std::vector more; + +public: + CargoStationIDStackSet() + : first(~0, INVALID_STATION) {} + + const StationIDStack& Get(CargoID cargo) const + { + if (HasBit(first.cargo_mask, cargo)) return first.station; + for (size_t i = 0; i < more.size(); i++) { + if (HasBit(more[i].cargo_mask, cargo)) return more[i].station; + } + NOT_REACHED(); + } + + void FillNextStoppingStation(const Vehicle *v, const OrderList *o, const Order *first = NULL, uint hops = 0); +}; + /** * Shared order list linking together the linked list of orders and the list * of vehicles sharing this order list. @@ -397,7 +428,7 @@ public: */ inline VehicleOrderID GetNumManualOrders() const { return this->num_manual_orders; } - StationIDStack GetNextStoppingStation(const Vehicle *v, const Order *first = NULL, uint hops = 0) const; + 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; void InsertOrderAt(Order *new_order, int index); diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 9ade1b726a..54f642e65e 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -19,6 +19,7 @@ #include "timetable.h" #include "vehicle_func.h" #include "depot_base.h" +#include "core/bitmath_func.hpp" #include "core/pool_func.hpp" #include "core/random_func.hpp" #include "aircraft.h" @@ -290,6 +291,19 @@ void Order::AssignOrder(const Order &other) } } +void CargoStationIDStackSet::FillNextStoppingStation(const Vehicle *v, const OrderList *o, const Order *first, uint hops) +{ + this->more.clear(); + this->first = o->GetNextStoppingStation(v, ~0, first, hops); + if (this->first.cargo_mask != (uint32) ~0) { + uint32 have_cargoes = this->first.cargo_mask; + do { + this->more.push_back(o->GetNextStoppingStation(v, ~have_cargoes, first, hops)); + have_cargoes |= this->more.back().cargo_mask; + } while (have_cargoes != (uint32) ~0); + } +} + /** * Recomputes everything. * @param chain first order in the chain @@ -401,21 +415,26 @@ const Order *OrderList::GetNextDecisionNode(const Order *next, uint hops) const /** * Recursively determine the next deterministic station to stop at. * @param v The vehicle we're looking at. + * @param uint32 cargo_mask Bit-set of the cargo IDs of interest. * @param first Order to start searching at or NULL to start at cur_implicit_order_index + 1. * @param hops Number of orders we have already looked at. - * @return Next stoppping station or INVALID_STATION. + * @return A CargoMaskedStationIDStack of the cargo mask the result is valid for, and the next stoppping station or INVALID_STATION. * @pre The vehicle is currently loading and v->last_station_visited is meaningful. * @note This function may draw a random number. Don't use it from the GUI. */ -StationIDStack OrderList::GetNextStoppingStation(const Vehicle *v, const Order *first, 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) { next = this->GetOrderAt(v->cur_implicit_order_index); if (next == NULL) { next = this->GetFirstOrder(); - if (next == NULL) return INVALID_STATION; + if (next == NULL) return CargoMaskedStationIDStack(cargo_mask, INVALID_STATION); } else { /* GetNext never returns NULL if there is a valid station in the list. * As the given "next" is already valid and a station in the list, we @@ -440,23 +459,54 @@ StationIDStack OrderList::GetNextStoppingStation(const Vehicle *v, const Order * } else if (skip_to == NULL || skip_to == first) { next = (advance == first) ? NULL : advance; } else { - StationIDStack st1 = this->GetNextStoppingStation(v, skip_to, hops); - StationIDStack st2 = this->GetNextStoppingStation(v, advance, hops); - while (!st2.IsEmpty()) st1.Push(st2.Pop()); + CargoMaskedStationIDStack st1 = this->GetNextStoppingStation(v, cargo_mask, skip_to, hops); + cargo_mask &= st1.cargo_mask; + CargoMaskedStationIDStack st2 = this->GetNextStoppingStation(v, cargo_mask, advance, hops); + st1.cargo_mask &= st2.cargo_mask; + while (!st2.station.IsEmpty()) st1.station.Push(st2.station.Pop()); return st1; } ++hops; } + 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 == NULL || ((next->IsType(OT_GOTO_STATION) || next->IsType(OT_IMPLICIT)) && - next->GetDestination() == v->last_station_visited && - (next->GetUnloadType() & (OUFB_TRANSFER | OUFB_UNLOAD)) != 0)) { - return INVALID_STATION; + if ((next->IsType(OT_GOTO_STATION) || next->IsType(OT_IMPLICIT)) && + next->GetDestination() == v->last_station_visited) { + 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. */ + 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); } } while (next->IsType(OT_GOTO_DEPOT) || next->GetDestination() == v->last_station_visited); - return next->GetDestination(); + return CargoMaskedStationIDStack(cargo_mask, next->GetDestination()); } /** diff --git a/src/vehicle_base.h b/src/vehicle_base.h index f923c2d791..9aedc7facd 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -639,9 +639,11 @@ public: * Get the next station the vehicle will stop at. * @return ID of the next station the vehicle will stop at or INVALID_STATION. */ - inline StationIDStack GetNextStoppingStation() const + inline CargoStationIDStackSet GetNextStoppingStation() const { - return (this->orders.list == NULL) ? INVALID_STATION : this->orders.list->GetNextStoppingStation(this); + CargoStationIDStackSet set; + if (this->orders.list != NULL) set.FillNextStoppingStation(this, this->orders.list); + return set; } void ResetRefitCaps();