diff --git a/src/vehicle.cpp b/src/vehicle.cpp --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2002,6 +2002,13 @@ void Vehicle::BeginLoading() Station::Get(this->last_station_visited)->loading_vehicles.push_back(this); + 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); + } + PrepareUnload(this); SetWindowDirty(GetWindowClassForVehicleType(this->type), this->owner); @@ -2043,6 +2050,23 @@ void Vehicle::LeaveStation() /* Only update the timetable if the vehicle was supposed to stop here. */ if (this->current_order.GetNonStopType() != ONSF_STOP_EVERYWHERE) UpdateVehicleTimetable(this, false); + 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. */ + this->RefreshNextHopsStats(); + + /* 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; + } 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; + } + } + this->current_order.MakeLeaveStation(); Station *st = Station::Get(this->last_station_visited); this->CancelReservation(st); @@ -2061,6 +2085,116 @@ void Vehicle::LeaveStation() } } +/** + * Predict a vehicle's course from its current state and refresh all links it + * will visit. As a side effect reset the refit_cap of all vehicles in the + * consist to the cargo_cap. This method is expected to be called when loading + * at a station so it's safe to do so. + */ +void Vehicle::RefreshNextHopsStats() +{ + /* Assemble list of capacities and set last loading stations to 0. */ + SmallMap capacities; + for (Vehicle *v = this; v != NULL; v = v->Next()) { + v->refit_cap = v->cargo_cap; + if (v->refit_cap == 0) continue; + + SmallPair *i = capacities.Find(v->cargo_type); + if (i == capacities.End()) { + /* Braindead smallmap not providing a good method for that... + * capacities[v->cargo_type] += v->cargo_cap won't do as that just + * allocates the memory, but doesn't initialize it if the key isn't + * there, yet. So we still have to check if the key exists and that + * is best done with Find(). */ + i = capacities.Append(); + i->first = v->cargo_type; + i->second = v->refit_cap; + } else { + i->second += v->refit_cap; + } + } + + /* If orders were deleted while loading, we're done here.*/ + if (this->orders.list == NULL) return; + + uint hops = 0; + const Order *first = this->GetOrder(this->cur_implicit_order_index); + do { + /* Make sure the first order is a station order. */ + first = this->orders.list->GetNextStoppingOrder(this, first, hops++); + if (first == NULL) return; + } while (!first->IsType(OT_GOTO_STATION)); + hops = 0; + + const Order *cur = first; + const Order *next = first; + while (next != NULL && cur->CanLeaveWithCargo(true)) { + next = this->orders.list->GetNextStoppingOrder(this, + this->orders.list->GetNext(next), ++hops); + if (next == NULL) break; + + /* If the refit cargo is CT_AUTO_REFIT, we're optimistic and assume the + * cargo will stay the same. The point of this method is to avoid + * deadlocks due to vehicles waiting for cargo that isn't being routed, + * yet. That situation will not occur if the vehicle is actually + * carrying a different cargo in the end. */ + if (next->IsAutoRefit() && next->GetRefitCargo() != CT_AUTO_REFIT) { + /* Handle refit by dropping some vehicles. */ + CargoID new_cid = next->GetRefitCargo(); + for (Vehicle *v = this; v != NULL; v = v->Next()) { + const Engine *e = Engine::Get(v->engine_type); + if (!HasBit(e->info.refit_mask, new_cid)) continue; + + /* Back up the vehicle's cargo type */ + CargoID temp_cid = v->cargo_type; + byte temp_subtype = v->cargo_subtype; + v->cargo_type = new_cid; + v->cargo_subtype = GetBestFittingSubType(v, v, new_cid); + + uint16 mail_capacity = 0; + uint amount = e->DetermineCapacity(v, &mail_capacity); + + /* Restore the original cargo type */ + v->cargo_type = temp_cid; + v->cargo_subtype = temp_subtype; + + /* Skip on next refit. */ + if (new_cid != v->cargo_type && v->refit_cap > 0) { + capacities[v->cargo_type] -= v->refit_cap; + v->refit_cap = 0; + } else if (amount < v->refit_cap) { + capacities[v->cargo_type] -= v->refit_cap - amount; + v->refit_cap = amount; + } + + /* Special case for aircraft with mail. */ + if (v->type == VEH_AIRCRAFT) { + Vehicle *u = v->Next(); + if (mail_capacity < u->refit_cap) { + capacities[u->cargo_type] -= u->refit_cap - mail_capacity; + u->refit_cap = mail_capacity; + } + break; // aircraft have only one vehicle + } + } + } + + if (next->IsType(OT_GOTO_STATION)) { + StationID next_station = next->GetDestination(); + Station *st = Station::GetIfValid(cur->GetDestination()); + if (st != NULL && next_station != INVALID_STATION && next_station != st->index) { + for (const SmallPair *i = capacities.Begin(); i != capacities.End(); ++i) { + /* Refresh the link and give it a minimum capacity. */ + if (i->second > 0) IncreaseStats(st, i->first, next_station, i->second, UINT_MAX); + } + } + cur = next; + if (cur == first) break; + } + } + + for (Vehicle *v = this; v != NULL; v = v->Next()) v->refit_cap = v->cargo_cap; +} /** * Handle the loading of the vehicle; when not it skips through dummy @@ -2099,6 +2233,34 @@ void Vehicle::HandleLoading(bool mode) } /** + * Get a map of cargoes and free capacities in the consist. + * @param capacities Map to be filled with cargoes and capacities. + */ +void Vehicle::GetConsistFreeCapacities(SmallMap &capacities) const +{ + for (const Vehicle *v = this; v != NULL; v = v->Next()) { + if (v->cargo_cap == 0) continue; + SmallPair *pair = capacities.Find(v->cargo_type); + if (pair == capacities.End()) { + pair = capacities.Append(); + pair->first = v->cargo_type; + pair->second = v->cargo_cap - v->cargo.StoredCount(); + } else { + pair->second += v->cargo_cap - v->cargo.StoredCount(); + } + } +} + +uint Vehicle::GetConsistTotalCapacity() const +{ + uint result = 0; + for (const Vehicle *v = this; v != NULL; v = v->Next()) { + result += v->cargo_cap; + } + return result; +} + +/** * Send this vehicle to the depot using the given command(s). * @param flags the command flags (like execute and such). * @param command the command to execute.