1
0
Fork 0

Feature: Depot action to unbunch shared order vehicles

pull/11945/head
Tyler Trahan 2024-01-27 17:22:13 -05:00
parent 46b1114c67
commit c09f8963a8
13 changed files with 143 additions and 5 deletions

View File

@ -1477,6 +1477,7 @@ void AircraftLeaveHangar(Aircraft *v, Direction exit_dir)
} }
VehicleServiceInDepot(v); VehicleServiceInDepot(v);
v->LeaveUnbunchingDepot();
SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos); SetAircraftPosition(v, v->x_pos, v->y_pos, v->z_pos);
InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile);
SetWindowClassesDirty(WC_AIRCRAFT_LIST); SetWindowClassesDirty(WC_AIRCRAFT_LIST);
@ -1521,6 +1522,9 @@ static void AircraftEventHandler_InHangar(Aircraft *v, const AirportFTAClass *ap
return; return;
} }
/* Check if we should wait here for unbunching. */
if (v->IsWaitingForUnbunching()) return;
if (!v->current_order.IsType(OT_GOTO_STATION) && if (!v->current_order.IsType(OT_GOTO_STATION) &&
!v->current_order.IsType(OT_GOTO_DEPOT)) !v->current_order.IsType(OT_GOTO_DEPOT))
return; return;

View File

@ -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); 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;
}

View File

@ -22,6 +22,10 @@ struct BaseConsist {
TimerGameTick::Ticks lateness_counter; ///< How many ticks late (or early if negative) this vehicle is. 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 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 %. 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 VehicleOrderID cur_real_order_index;///< The index to the current real (non-implicit) order
@ -32,6 +36,7 @@ struct BaseConsist {
virtual ~BaseConsist() = default; virtual ~BaseConsist() = default;
void CopyConsistPropertiesFrom(const BaseConsist *src); void CopyConsistPropertiesFrom(const BaseConsist *src);
void ResetDepotUnbunching();
}; };
#endif /* BASE_CONSIST_H */ #endif /* BASE_CONSIST_H */

View File

@ -144,7 +144,7 @@ public:
/** What caused us going to the depot? */ /** What caused us going to the depot? */
inline OrderDepotTypeFlags GetDepotOrderType() const { return (OrderDepotTypeFlags)GB(this->flags, 0, 3); } inline OrderDepotTypeFlags GetDepotOrderType() const { return (OrderDepotTypeFlags)GB(this->flags, 0, 3); }
/** What are we going to do when in the depot. */ /** 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? */ /** What variable do we have to compare? */
inline OrderConditionVariable GetConditionVariable() const { return (OrderConditionVariable)GB(this->dest, 11, 5); } inline OrderConditionVariable GetConditionVariable() const { return (OrderConditionVariable)GB(this->dest, 11, 5); }
/** What is the comparator to use? */ /** What is the comparator to use? */
@ -165,7 +165,7 @@ public:
/** Set the cause to go to the depot. */ /** Set the cause to go to the depot. */
inline void SetDepotOrderType(OrderDepotTypeFlags depot_order_type) { SB(this->flags, 0, 3, depot_order_type); } 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. */ /** 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. */ /** Set variable we have to compare. */
inline void SetConditionVariable(OrderConditionVariable condition_variable) { SB(this->dest, 11, 5, condition_variable); } inline void SetConditionVariable(OrderConditionVariable condition_variable) { SB(this->dest, 11, 5, condition_variable); }
/** Set the comparator to use. */ /** Set the comparator to use. */

View File

@ -103,6 +103,7 @@ enum OrderDepotActionFlags {
ODATF_SERVICE_ONLY = 0, ///< Only service the vehicle. ODATF_SERVICE_ONLY = 0, ///< Only service the vehicle.
ODATFB_HALT = 1 << 0, ///< Service the vehicle and then halt it. ODATFB_HALT = 1 << 0, ///< Service the vehicle and then halt it.
ODATFB_NEAREST_DEPOT = 1 << 1, ///< Send the vehicle to the nearest depot. 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) DECLARE_ENUM_AS_BIT_SET(OrderDepotActionFlags)

View File

@ -1030,6 +1030,7 @@ bool RoadVehLeaveDepot(RoadVehicle *v, bool first)
if (RoadVehFindCloseTo(v, x, y, v->direction, false) != nullptr) return true; if (RoadVehFindCloseTo(v, x, y, v->direction, false) != nullptr) return true;
VehicleServiceInDepot(v); VehicleServiceInDepot(v);
v->LeaveUnbunchingDepot();
StartRoadVehSound(v); StartRoadVehSound(v);
@ -1587,7 +1588,11 @@ static bool RoadVehController(RoadVehicle *v)
if (v->current_order.IsType(OT_LOADING)) return true; 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(); v->ShowVisualEffect();

View File

@ -1765,6 +1765,12 @@ bool AfterLoadGame()
v->current_order.SetLoadType(OLFB_NO_LOAD); 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. */ /* The water class was moved/unified. */

View File

@ -374,6 +374,7 @@ enum SaveLoadVersion : uint16_t {
SLV_SHIP_ACCELERATION, ///< 329 PR#10734 Start using Vehicle's acceleration field for ships too. 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_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 SL_MAX_VERSION, ///< Highest possible saveload version
}; };

View File

@ -738,6 +738,10 @@ public:
SLE_CONDVAR(Vehicle, current_order_time, SLE_INT32, SLV_TIMETABLE_TICKS_TYPE, SL_MAX_VERSION), 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, 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, 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) #if defined(_MSC_VER) && (_MSC_VER == 1915 || _MSC_VER == 1916)
return description; return description;

View File

@ -387,6 +387,9 @@ static bool CheckShipLeaveDepot(Ship *v)
{ {
if (!v->IsChainInDepot()) return false; 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 */ /* We are leaving a depot, but have to go to the exact same one; re-enter */
if (v->current_order.IsType(OT_GOTO_DEPOT) && if (v->current_order.IsType(OT_GOTO_DEPOT) &&
IsShipDepotTile(v->tile) && GetDepotIndex(v->tile) == v->current_order.GetDestination()) { IsShipDepotTile(v->tile) && GetDepotIndex(v->tile) == v->current_order.GetDestination()) {
@ -433,8 +436,9 @@ static bool CheckShipLeaveDepot(Ship *v)
v->UpdateViewport(true, true); v->UpdateViewport(true, true);
SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); SetWindowDirty(WC_VEHICLE_DEPOT, v->tile);
v->PlayLeaveStationSound();
VehicleServiceInDepot(v); VehicleServiceInDepot(v);
v->LeaveUnbunchingDepot();
v->PlayLeaveStationSound();
InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile);
SetWindowClassesDirty(WC_SHIPS_LIST); SetWindowClassesDirty(WC_SHIPS_LIST);

View File

@ -2275,6 +2275,9 @@ static bool CheckTrainStayInDepot(Train *v)
return true; return true;
} }
/* Check if we should wait here for unbunching. */
if (v->IsWaitingForUnbunching()) return true;
SigSegState seg_state; SigSegState seg_state;
if (v->force_proceed == TFP_NONE) { 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); if (_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(v->tile);
VehicleServiceInDepot(v); VehicleServiceInDepot(v);
SetWindowClassesDirty(WC_TRAINS_LIST); v->LeaveUnbunchingDepot();
v->PlayLeaveStationSound(); v->PlayLeaveStationSound();
SetWindowClassesDirty(WC_TRAINS_LIST);
v->track = TRACK_BIT_X; v->track = TRACK_BIT_X;
if (v->direction & 2) v->track = TRACK_BIT_Y; if (v->direction & 2) v->track = TRACK_BIT_Y;

View File

@ -1635,12 +1635,23 @@ void VehicleEnterDepot(Vehicle *v)
* before the stop to the station after the stop can't be predicted * 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. */ * we shouldn't construct it when the vehicle visits the next stop. */
v->last_loading_station = INVALID_STATION; 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) { if (v->owner == _local_company) {
SetDParam(0, v->index); SetDParam(0, v->index);
AddVehicleAdviceNewsItem(STR_NEWS_TRAIN_IS_WAITING + v->type, v->index); AddVehicleAdviceNewsItem(STR_NEWS_TRAIN_IS_WAITING + v->type, v->index);
} }
AI::NewEvent(v->owner, new ScriptEventVehicleWaitingInDepot(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(); v->current_order.MakeDummy();
} }
} }
@ -2402,6 +2413,85 @@ void Vehicle::HandleLoading(bool mode)
this->IncrementImplicitOrderIndex(); 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). * Send this vehicle to the depot using the given command(s).
* @param flags the command flags (like execute and such). * @param flags the command flags (like execute and such).

View File

@ -817,6 +817,10 @@ public:
inline void SetServiceIntervalIsPercent(bool on) { SB(this->vehicle_flags, VF_SERVINT_IS_PERCENT, 1, on); } inline void SetServiceIntervalIsPercent(bool on) { SB(this->vehicle_flags, VF_SERVINT_IS_PERCENT, 1, on); }
bool HasUnbunchingOrder() const;
void LeaveUnbunchingDepot();
bool IsWaitingForUnbunching() const;
private: private:
/** /**
* Advance cur_real_order_index to the next real order. * Advance cur_real_order_index to the next real order.