diff --git a/src/vehicle.cpp b/src/vehicle.cpp --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -810,6 +810,10 @@ void Vehicle::HandlePathfindingResult(bo SetBit(this->vehicle_flags, VF_PATHFINDER_LOST); SetWindowWidgetDirty(WC_VEHICLE_VIEW, this->index, WID_VV_START_STOP); InvalidateWindowClassesData(GetWindowClassForVehicleType(this->type)); + + /* Unbunching data is no longer valid. */ + this->ResetDepotUnbunching(); + /* Notify user about the event. */ AI::NewEvent(this->owner, new ScriptEventVehicleLost(this->index)); if (_settings_client.gui.lost_vehicle_warn && this->owner == _local_company) { @@ -1635,12 +1639,23 @@ 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; + + /* Clear unbunching data. */ + v->ResetDepotUnbunching(); + + /* Announce that the vehicle is waiting to players and AIs. */ if (v->owner == _local_company) { SetDParam(0, v->index); AddVehicleAdviceNewsItem(STR_NEWS_TRAIN_IS_WAITING + v->type, v->index); } AI::NewEvent(v->owner, new ScriptEventVehicleWaitingInDepot(v->index)); } + + /* If we've entered our unbunching depot, record the round trip duration. */ + if (v->current_order.GetDepotActionType() & ODATFB_UNBUNCH && v->depot_unbunching_last_departure > 0) { + v->round_trip_time = (TimerGameTick::counter - v->depot_unbunching_last_departure); + } + v->current_order.MakeDummy(); } } @@ -2403,6 +2418,85 @@ void Vehicle::HandleLoading(bool mode) } /** + * Check if the current vehicle has an unbunching order. + * @return true Iff this vehicle has an unbunching order. + */ +bool Vehicle::HasUnbunchingOrder() const +{ + for (Order *o : this->Orders()) { + if (o->IsType(OT_GOTO_DEPOT) && o->GetDepotActionType() & ODATFB_UNBUNCH) return true; + } + return false; +} + +/** + * Leave an unbunching depot and calculate the next departure time for shared order vehicles. + */ +void Vehicle::LeaveUnbunchingDepot() +{ + /* Set the start point for this round trip time. */ + this->depot_unbunching_last_departure = TimerGameTick::counter; + + /* Tell the timetable we are now "on time." */ + this->lateness_counter = 0; + SetWindowDirty(WC_VEHICLE_TIMETABLE, this->index); + + /* Find the average travel time of vehicles that we share orders with. */ + uint num_vehicles = 0; + TimerGameTick::Ticks total_travel_time = 0; + + Vehicle *u = this->FirstShared(); + for (; u != nullptr; u = u->NextShared()) { + /* Ignore vehicles that are manually stopped or crashed. */ + if (u->vehstatus & (VS_STOPPED | VS_CRASHED)) continue; + + num_vehicles++; + total_travel_time += u->round_trip_time; + } + + /* Make sure we cannot divide by 0. */ + num_vehicles = std::max(num_vehicles, 1u); + + /* Calculate the separation by finding the average travel time, then calculating equal separation (minimum 1 tick) between vehicles. */ + TimerGameTick::Ticks separation = std::max((total_travel_time / num_vehicles / num_vehicles), 1u); + TimerGameTick::TickCounter next_departure = TimerGameTick::counter + separation; + + /* Set the departure time of all vehicles that we share orders with. */ + u = this->FirstShared(); + for (; u != nullptr; u = u->NextShared()) { + /* Ignore vehicles that are manually stopped or crashed. */ + if (u->vehstatus & (VS_STOPPED | VS_CRASHED)) continue; + + u->depot_unbunching_next_departure = next_departure; + } +} + +/** + * Check whether a vehicle inside a depot is waiting for unbunching. + * @return True if the vehicle must continue waiting, or false if it may try to leave the depot. + */ +bool Vehicle::IsWaitingForUnbunching() const +{ + assert(this->IsInDepot()); + + /* Don't bother if there are no vehicles sharing orders. */ + if (!this->IsOrderListShared()) return false; + + /* Don't do anything if there aren't enough orders. */ + if (this->GetNumOrders() <= 1) return false; + + /* + * Make sure this is the correct depot for unbunching. + * If we are headed for the first order, we must wrap around back to the last order. + */ + bool is_first_order = (this->GetOrder(this->cur_real_order_index) == this->GetFirstOrder()); + Order *previous_order = (is_first_order) ? this->GetLastOrder() : this->GetOrder(this->cur_real_order_index - 1); + if (previous_order == nullptr || !previous_order->IsType(OT_GOTO_DEPOT) || !(previous_order->GetDepotActionType() & ODATFB_UNBUNCH)) return false; + + return (this->depot_unbunching_next_departure > TimerGameTick::counter); +}; + +/** * 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. @@ -2416,6 +2510,9 @@ CommandCost Vehicle::SendToDepot(DoComma if (this->vehstatus & VS_CRASHED) return CMD_ERROR; if (this->IsStoppedInDepot()) return CMD_ERROR; + /* No matter why we're headed to the depot, unbunching data is no longer valid. */ + if (flags & DC_EXEC) this->ResetDepotUnbunching(); + if (this->current_order.IsType(OT_GOTO_DEPOT)) { bool halt_in_depot = (this->current_order.GetDepotActionType() & ODATFB_HALT) != 0; if (((command & DepotCommand::Service) != DepotCommand::None) == halt_in_depot) {