diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 6002a05f20..e6c979f872 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -1477,6 +1477,7 @@ void AircraftLeaveHangar(Aircraft *v, Direction exit_dir) } VehicleServiceInDepot(v); + v->LeaveUnbunchingDepot(); SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); SetWindowClassesDirty(WC_AIRCRAFT_LIST); @@ -1521,6 +1522,9 @@ static void AircraftEventHandler_InHangar(Aircraft *v, const AirportFTAClass *ap return; } + /* Check if we should wait here for unbunching. */ + if (v->IsWaitingForUnbunching()) return; + if (!v->current_order.IsType(OT_GOTO_STATION) && !v->current_order.IsType(OT_GOTO_DEPOT)) return; diff --git a/src/base_consist.cpp b/src/base_consist.cpp index ad9d476b66..0a30893be2 100644 --- a/src/base_consist.cpp +++ b/src/base_consist.cpp @@ -42,3 +42,13 @@ void BaseConsist::CopyConsistPropertiesFrom(const BaseConsist *src) } if (HasBit(src->vehicle_flags, VF_SERVINT_IS_CUSTOM)) SetBit(this->vehicle_flags, VF_SERVINT_IS_CUSTOM); } + +/** + * Resets all the data used for depot unbunching. + */ +void BaseConsist::ResetDepotUnbunching() +{ + this->depot_unbunching_last_departure = 0; + this->depot_unbunching_next_departure = 0; + this->round_trip_time = 0; +} diff --git a/src/base_consist.h b/src/base_consist.h index 6189604537..632e77c630 100644 --- a/src/base_consist.h +++ b/src/base_consist.h @@ -22,6 +22,10 @@ struct BaseConsist { TimerGameTick::Ticks lateness_counter; ///< How many ticks late (or early if negative) this vehicle is. TimerGameTick::TickCounter timetable_start; ///< At what tick of TimerGameTick::counter the vehicle should start its timetable. + TimerGameTick::TickCounter depot_unbunching_last_departure; ///< When the vehicle last left its unbunching depot. + TimerGameTick::TickCounter depot_unbunching_next_departure; ///< When the vehicle will next try to leave its unbunching depot. + TimerGameTick::Ticks round_trip_time; ///< How many ticks for a single circumnavigation of the orders. + uint16_t service_interval; ///< The interval for (automatic) servicing; either in days or %. VehicleOrderID cur_real_order_index;///< The index to the current real (non-implicit) order @@ -32,6 +36,7 @@ struct BaseConsist { virtual ~BaseConsist() = default; void CopyConsistPropertiesFrom(const BaseConsist *src); + void ResetDepotUnbunching(); }; #endif /* BASE_CONSIST_H */ diff --git a/src/order_base.h b/src/order_base.h index 4e8e8bd818..73cd3c27fc 100644 --- a/src/order_base.h +++ b/src/order_base.h @@ -144,7 +144,7 @@ public: /** What caused us going to the depot? */ inline OrderDepotTypeFlags GetDepotOrderType() const { return (OrderDepotTypeFlags)GB(this->flags, 0, 3); } /** What are we going to do when in the depot. */ - inline OrderDepotActionFlags GetDepotActionType() const { return (OrderDepotActionFlags)GB(this->flags, 4, 3); } + inline OrderDepotActionFlags GetDepotActionType() const { return (OrderDepotActionFlags)GB(this->flags, 3, 4); } /** What variable do we have to compare? */ inline OrderConditionVariable GetConditionVariable() const { return (OrderConditionVariable)GB(this->dest, 11, 5); } /** What is the comparator to use? */ @@ -165,7 +165,7 @@ public: /** Set the cause to go to the depot. */ inline void SetDepotOrderType(OrderDepotTypeFlags depot_order_type) { SB(this->flags, 0, 3, depot_order_type); } /** Set what we are going to do in the depot. */ - inline void SetDepotActionType(OrderDepotActionFlags depot_service_type) { SB(this->flags, 4, 3, depot_service_type); } + inline void SetDepotActionType(OrderDepotActionFlags depot_service_type) { SB(this->flags, 3, 4, depot_service_type); } /** Set variable we have to compare. */ inline void SetConditionVariable(OrderConditionVariable condition_variable) { SB(this->dest, 11, 5, condition_variable); } /** Set the comparator to use. */ diff --git a/src/order_type.h b/src/order_type.h index 2e4717c7f2..b9963704c8 100644 --- a/src/order_type.h +++ b/src/order_type.h @@ -103,6 +103,7 @@ enum OrderDepotActionFlags { ODATF_SERVICE_ONLY = 0, ///< Only service the vehicle. ODATFB_HALT = 1 << 0, ///< Service the vehicle and then halt it. ODATFB_NEAREST_DEPOT = 1 << 1, ///< Send the vehicle to the nearest depot. + ODATFB_UNBUNCH = 1 << 2, ///< Service the vehicle and then unbunch it. }; DECLARE_ENUM_AS_BIT_SET(OrderDepotActionFlags) diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 066ea84762..4f24f6c519 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -1030,6 +1030,7 @@ bool RoadVehLeaveDepot(RoadVehicle *v, bool first) if (RoadVehFindCloseTo(v, x, y, v->direction, false) != nullptr) return true; VehicleServiceInDepot(v); + v->LeaveUnbunchingDepot(); StartRoadVehSound(v); @@ -1587,7 +1588,11 @@ static bool RoadVehController(RoadVehicle *v) if (v->current_order.IsType(OT_LOADING)) return true; - if (v->IsInDepot() && RoadVehLeaveDepot(v, true)) return true; + if (v->IsInDepot()) { + /* Check if we should wait here for unbunching. */ + if (v->IsWaitingForUnbunching()) return true; + if (RoadVehLeaveDepot(v, true)) return true; + } v->ShowVisualEffect(); diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index bc9072a3a5..7c4226ddc3 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -1765,6 +1765,12 @@ bool AfterLoadGame() v->current_order.SetLoadType(OLFB_NO_LOAD); } } + } else if (IsSavegameVersionBefore(SLV_DEPOT_UNBUNCHING)) { + /* OrderDepotActionFlags were moved, instead of starting at bit 4 they now start at bit 3. */ + for (Order *order : Order::Iterate()) { + if (!order->IsType(OT_GOTO_DEPOT)) continue; + order->SetDepotActionType((OrderDepotActionFlags)(order->GetDepotActionType() >> 1)); + } } /* The water class was moved/unified. */ diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 7ba2fdf967..9cdecc7cd7 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -374,6 +374,7 @@ enum SaveLoadVersion : uint16_t { SLV_SHIP_ACCELERATION, ///< 329 PR#10734 Start using Vehicle's acceleration field for ships too. SLV_MAX_LOAN_FOR_COMPANY, ///< 330 PR#11224 Separate max loan for each company. + SLV_DEPOT_UNBUNCHING, ///< 330 PR#11945 Allow unbunching shared order vehicles at a depot. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index 0befccc1dc..1dbdb53c7d 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -738,6 +738,10 @@ public: SLE_CONDVAR(Vehicle, current_order_time, SLE_INT32, SLV_TIMETABLE_TICKS_TYPE, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, last_loading_tick, SLE_UINT64, SLV_LAST_LOADING_TICK, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, lateness_counter, SLE_INT32, SLV_67, SL_MAX_VERSION), + + SLE_CONDVAR(Vehicle, depot_unbunching_last_departure, SLE_UINT64, SLV_DEPOT_UNBUNCHING, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, depot_unbunching_next_departure, SLE_UINT64, SLV_DEPOT_UNBUNCHING, SL_MAX_VERSION), + SLE_CONDVAR(Vehicle, round_trip_time, SLE_INT32, SLV_DEPOT_UNBUNCHING, SL_MAX_VERSION), }; #if defined(_MSC_VER) && (_MSC_VER == 1915 || _MSC_VER == 1916) return description; diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index 34d96bf273..920a881214 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -387,6 +387,9 @@ static bool CheckShipLeaveDepot(Ship *v) { if (!v->IsChainInDepot()) return false; + /* Check if we should wait here for unbunching. */ + if (v->IsWaitingForUnbunching()) return true; + /* We are leaving a depot, but have to go to the exact same one; re-enter */ if (v->current_order.IsType(OT_GOTO_DEPOT) && IsShipDepotTile(v->tile) && GetDepotIndex(v->tile) == v->current_order.GetDestination()) { @@ -433,8 +436,9 @@ static bool CheckShipLeaveDepot(Ship *v) v->UpdateViewport(true, true); SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); - v->PlayLeaveStationSound(); VehicleServiceInDepot(v); + v->LeaveUnbunchingDepot(); + v->PlayLeaveStationSound(); InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); SetWindowClassesDirty(WC_SHIPS_LIST); diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 5d019380d2..bc1552f0ff 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2275,6 +2275,9 @@ static bool CheckTrainStayInDepot(Train *v) return true; } + /* Check if we should wait here for unbunching. */ + if (v->IsWaitingForUnbunching()) return true; + SigSegState seg_state; if (v->force_proceed == TFP_NONE) { @@ -2315,8 +2318,9 @@ static bool CheckTrainStayInDepot(Train *v) if (_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(v->tile); VehicleServiceInDepot(v); - SetWindowClassesDirty(WC_TRAINS_LIST); + v->LeaveUnbunchingDepot(); v->PlayLeaveStationSound(); + SetWindowClassesDirty(WC_TRAINS_LIST); v->track = TRACK_BIT_X; if (v->direction & 2) v->track = TRACK_BIT_Y; diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 5acb56f853..1c073f6061 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1635,12 +1635,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(); } } @@ -2402,6 +2413,85 @@ void Vehicle::HandleLoading(bool mode) this->IncrementImplicitOrderIndex(); } +/** + * 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). diff --git a/src/vehicle_base.h b/src/vehicle_base.h index 313ec18346..560bd41f1c 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -817,6 +817,10 @@ public: inline void SetServiceIntervalIsPercent(bool on) { SB(this->vehicle_flags, VF_SERVINT_IS_PERCENT, 1, on); } + bool HasUnbunchingOrder() const; + void LeaveUnbunchingDepot(); + bool IsWaitingForUnbunching() const; + private: /** * Advance cur_real_order_index to the next real order.