Skip to content

Commit

Permalink
Feature OpenTTD#8095: Allow automatically separating vehicles in shar…
Browse files Browse the repository at this point in the history
…ed orders
  • Loading branch information
twpol committed Dec 27, 2020
1 parent 4f8e7b2 commit 5f801f9
Show file tree
Hide file tree
Showing 15 changed files with 193 additions and 3 deletions.
9 changes: 9 additions & 0 deletions src/base_consist.cpp
Expand Up @@ -42,3 +42,12 @@ 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 automatic separation
*/
void BaseConsist::ResetAutomaticSeparation()
{
this->first_order_last_departure = 0;
this->first_order_round_trip_time = 0;
}
4 changes: 4 additions & 0 deletions src/base_consist.h
Expand Up @@ -23,6 +23,9 @@ struct BaseConsist {
int32 lateness_counter; ///< How many ticks late (or early if negative) this vehicle is.
Date timetable_start; ///< When the vehicle is supposed to start the timetable.

Date first_order_last_departure; ///< When the vehicle last left the first order.
Ticks first_order_round_trip_time; ///< How many ticks for a single circumnavigation of the orders.

uint16 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
Expand All @@ -33,6 +36,7 @@ struct BaseConsist {
virtual ~BaseConsist() {}

void CopyConsistPropertiesFrom(const BaseConsist *src);
void ResetAutomaticSeparation();
};

#endif /* BASE_CONSIST_H */
3 changes: 3 additions & 0 deletions src/command.cpp
Expand Up @@ -94,6 +94,8 @@ CommandProc CmdModifyOrder;
CommandProc CmdSkipToOrder;
CommandProc CmdDeleteOrder;
CommandProc CmdInsertOrder;
CommandProc CmdOrderAutomaticSeparation;

CommandProc CmdChangeServiceInt;

CommandProc CmdBuildIndustry;
Expand Down Expand Up @@ -262,6 +264,7 @@ static const Command _command_proc_table[] = {
DEF_CMD(CmdSkipToOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_SKIP_TO_ORDER
DEF_CMD(CmdDeleteOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_DELETE_ORDER
DEF_CMD(CmdInsertOrder, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_INSERT_ORDER
DEF_CMD(CmdOrderAutomaticSeparation, 0, CMDT_ROUTE_MANAGEMENT ), // CMD_ORDER_AUTOMATIC_SEPARATION

DEF_CMD(CmdChangeServiceInt, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_CHANGE_SERVICE_INT

Expand Down
1 change: 1 addition & 0 deletions src/command_type.h
Expand Up @@ -226,6 +226,7 @@ enum Commands {
CMD_SKIP_TO_ORDER, ///< skip an order to the next of specific one
CMD_DELETE_ORDER, ///< delete an order
CMD_INSERT_ORDER, ///< insert a new order
CMD_ORDER_AUTOMATIC_SEPARATION, ///< set automatic separation

CMD_CHANGE_SERVICE_INT, ///< change the server interval of a vehicle

Expand Down
3 changes: 3 additions & 0 deletions src/lang/english.txt
Expand Up @@ -3991,6 +3991,9 @@ STR_ORDERS_DELETE_ALL_TOOLTIP :{BLACK}Delete a
STR_ORDERS_STOP_SHARING_BUTTON :{BLACK}Stop sharing
STR_ORDERS_STOP_SHARING_TOOLTIP :{BLACK}Stop sharing the order list. Ctrl+Click additionally deletes all orders for this vehicle

STR_ORDERS_AUTOMATIC_SEPARATION :{BLACK}Automatic separation
STR_ORDERS_AUTOMATIC_SEPARATION_TOOLTIP :{BLACK}Automatically separate all vehicles sharing this order

STR_ORDERS_GO_TO_BUTTON :{BLACK}Go To
STR_ORDER_GO_TO_NEAREST_DEPOT :Go to nearest depot
STR_ORDER_GO_TO_NEAREST_HANGAR :Go to nearest hangar
Expand Down
14 changes: 14 additions & 0 deletions src/order_base.h
Expand Up @@ -263,6 +263,8 @@ struct OrderList : OrderListPool::PoolItem<&_orderlist_pool> {
Ticks timetable_duration; ///< NOSAVE: Total timetabled duration of the order list.
Ticks total_duration; ///< NOSAVE: Total (timetabled or not) duration of the order list.

bool automatic_separation; ///< Is automatic separation enabled?

public:
/** Default constructor producing an invalid order list. */
OrderList(VehicleOrderID num_orders = INVALID_VEH_ORDER_ID)
Expand Down Expand Up @@ -387,6 +389,18 @@ struct OrderList : OrderListPool::PoolItem<&_orderlist_pool> {
*/
void UpdateTotalDuration(Ticks delta) { this->total_duration += delta; }

/**
* Is this order list using automatic separation?
* @return whether automatic separation is enabled
*/
inline bool AutomaticSeparationIsEnabled() const { return this->automatic_separation; }

/**
* Enables or disables automatic separation for this order list
* @param enabled whether to enable (true) or disable (false) automatic separation
*/
void SetAutomaticSeparationIsEnabled(bool enabled) { this->automatic_separation = enabled; }

void FreeChain(bool keep_orderlist = false);

void DebugCheckSanity() const;
Expand Down
36 changes: 36 additions & 0 deletions src/order_cmd.cpp
Expand Up @@ -1517,6 +1517,40 @@ static bool CheckAircraftOrderDistance(const Aircraft *v_new, const Vehicle *v_o
return true;
}

/**
* Enable or disable automatic separation for a vehicle's order list
* @param tile unused
* @param flags operation to perform
* @param p1 VehicleIndex of the vehicle who's order list is being modified
* @param p2 value indicating whether to enable (1) or disable (0) automatic separation
* @param text unused
* @return the cost of this operation or an error
*/
CommandCost CmdOrderAutomaticSeparation(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
{
VehicleID veh = GB(p1, 0, 20);

Vehicle *vehicle = Vehicle::GetIfValid(veh);
if (vehicle == nullptr || !vehicle->IsPrimaryVehicle()) return CMD_ERROR;

CommandCost ret = CheckOwnership(vehicle->owner);
if (ret.Failed()) return ret;

if (flags & DC_EXEC) {
vehicle->SetAutomaticSeparationIsEnabled(p2);

if (!vehicle->AutomaticSeparationIsEnabled()) {
Vehicle *v = vehicle->FirstShared();
while (v != nullptr) {
v->ResetAutomaticSeparation();
v = v->NextShared();
}
}
}

return CommandCost();
}

/**
* Clone/share/copy an order-list of another vehicle.
* @param tile unused
Expand Down Expand Up @@ -1901,6 +1935,8 @@ void DeleteVehicleOrders(Vehicle *v, bool keep_orderlist, bool reset_order_indic
if (!keep_orderlist) v->orders.list = nullptr;
}

v->ResetAutomaticSeparation();

if (reset_order_indices) {
v->cur_implicit_order_index = v->cur_real_order_index = 0;
if (v->current_order.IsType(OT_LOADING)) {
Expand Down
28 changes: 25 additions & 3 deletions src/order_gui.cpp
Expand Up @@ -470,9 +470,9 @@ enum {
* \section bottom-row Bottom row
* The second row (the bottom row) is for manipulating the list of orders:
* \verbatim
* +-----------------+-----------------+-----------------+
* | SKIP | DELETE | GOTO |
* +-----------------+-----------------+-----------------+
* +-----------------+-----------------+-----------------+-----------------+
* | SKIP | DELETE | AUTO SEPARATION | GOTO |
* +-----------------+-----------------+-----------------+-----------------+
* \endverbatim
*
* For vehicles of other companies, both button rows are not displayed.
Expand Down Expand Up @@ -555,6 +555,16 @@ struct OrdersWindow : public Window {
return (sel <= vehicle->GetNumOrders() && sel >= 0) ? sel : INVALID_VEH_ORDER_ID;
}

/**
* Handle the click on the automatic separation button
*/
void OrderClick_AutomaticSeparation()
{
if (DoCommandP(this->vehicle->tile, this->vehicle->index, !this->vehicle->AutomaticSeparationIsEnabled(), CMD_ORDER_AUTOMATIC_SEPARATION | CMD_MSG(STR_ERROR_CAN_T_DELETE_THIS_ORDER))) {
this->UpdateButtonState();
}
}

/**
* Handle the click on the goto button.
*/
Expand Down Expand Up @@ -944,6 +954,10 @@ struct OrdersWindow : public Window {
}
}

/* automatic separation */
this->SetWidgetDisabledState(WID_O_AUTOMATIC_SEPARATION, this->vehicle->GetNumOrders() == 0);
this->SetWidgetLoweredState(WID_O_AUTOMATIC_SEPARATION, this->vehicle->AutomaticSeparationIsEnabled());

/* First row. */
this->RaiseWidget(WID_O_FULL_LOAD);
this->RaiseWidget(WID_O_UNLOAD);
Expand Down Expand Up @@ -1238,6 +1252,10 @@ struct OrdersWindow : public Window {
}
break;

case WID_O_AUTOMATIC_SEPARATION:
this->OrderClick_AutomaticSeparation();
break;

case WID_O_GOTO:
if (this->GetWidget<NWidgetLeaf>(widget)->ButtonHit(pt)) {
if (this->goto_type != OPOS_NONE) {
Expand Down Expand Up @@ -1599,6 +1617,8 @@ static const NWidgetPart _nested_orders_train_widgets[] = {
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_O_STOP_SHARING), SetMinimalSize(124, 12), SetFill(1, 0),
SetDataTip(STR_ORDERS_STOP_SHARING_BUTTON, STR_ORDERS_STOP_SHARING_TOOLTIP), SetResize(1, 0),
EndContainer(),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_O_AUTOMATIC_SEPARATION), SetMinimalSize(124, 12), SetFill(1, 0),
SetDataTip(STR_ORDERS_AUTOMATIC_SEPARATION, STR_ORDERS_AUTOMATIC_SEPARATION_TOOLTIP), SetResize(1, 0),
NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, WID_O_GOTO), SetMinimalSize(124, 12), SetFill(1, 0),
SetDataTip(STR_ORDERS_GO_TO_BUTTON, STR_ORDERS_GO_TO_TOOLTIP), SetResize(1, 0),
EndContainer(),
Expand Down Expand Up @@ -1673,6 +1693,8 @@ static const NWidgetPart _nested_orders_widgets[] = {
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_O_STOP_SHARING), SetMinimalSize(124, 12), SetFill(1, 0),
SetDataTip(STR_ORDERS_STOP_SHARING_BUTTON, STR_ORDERS_STOP_SHARING_TOOLTIP), SetResize(1, 0),
EndContainer(),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_O_AUTOMATIC_SEPARATION), SetMinimalSize(124, 12), SetFill(1, 0),
SetDataTip(STR_ORDERS_AUTOMATIC_SEPARATION, STR_ORDERS_AUTOMATIC_SEPARATION_TOOLTIP), SetResize(1, 0),
NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, WID_O_GOTO), SetMinimalSize(124, 12), SetFill(1, 0),
SetDataTip(STR_ORDERS_GO_TO_BUTTON, STR_ORDERS_GO_TO_TOOLTIP), SetResize(1, 0),
NWidget(WWT_RESIZEBOX, COLOUR_GREY),
Expand Down
1 change: 1 addition & 0 deletions src/saveload/order_sl.cpp
Expand Up @@ -200,6 +200,7 @@ const SaveLoad *GetOrderListDescription()
{
static const SaveLoad _orderlist_desc[] = {
SLE_REF(OrderList, first, REF_ORDER),
SLE_CONDVAR(OrderList, automatic_separation, SLE_BOOL, SLV_AUTOMATIC_SEPARATION, SL_MAX_VERSION),
SLE_END()
};

Expand Down
1 change: 1 addition & 0 deletions src/saveload/saveload.h
Expand Up @@ -322,6 +322,7 @@ enum SaveLoadVersion : uint16 {
SLV_END_PATCHPACKS = 286, ///< 286 Last known patchpack to use a version just above ours.

SLV_GS_INDUSTRY_CONTROL, ///< 287 PR#7912 and PR#8115 GS industry control.
SLV_AUTOMATIC_SEPARATION, ///< 288

SL_MAX_VERSION, ///< Highest possible saveload version
};
Expand Down
3 changes: 3 additions & 0 deletions src/saveload/vehicle_sl.cpp
Expand Up @@ -709,6 +709,9 @@ const SaveLoad *GetVehicleDescription(VehicleType vt)
SLE_CONDVAR(Vehicle, current_order_time, SLE_UINT32, SLV_67, SL_MAX_VERSION),
SLE_CONDVAR(Vehicle, lateness_counter, SLE_INT32, SLV_67, SL_MAX_VERSION),

SLE_CONDVAR(Vehicle, first_order_last_departure, SLE_INT32, SLV_AUTOMATIC_SEPARATION, SL_MAX_VERSION),
SLE_CONDVAR(Vehicle, first_order_round_trip_time, SLE_INT32, SLV_AUTOMATIC_SEPARATION, SL_MAX_VERSION),

SLE_CONDNULL(10, SLV_2, SLV_144), // old reserved space

SLE_END()
Expand Down
86 changes: 86 additions & 0 deletions src/vehicle.cpp
Expand Up @@ -1486,6 +1486,7 @@ void VehicleEnterDepot(Vehicle *v)

v->vehstatus |= VS_HIDDEN;
v->cur_speed = 0;
v->ResetAutomaticSeparation();

VehicleServiceInDepot(v);

Expand Down Expand Up @@ -2249,6 +2250,10 @@ void Vehicle::HandleLoading(bool mode)
/* Not the first call for this tick, or still loading */
if (mode || !HasBit(this->vehicle_flags, VF_LOADING_FINISHED) || this->current_order_time < wait_time) return;

this->UpdateAutomaticSeparation();

if (this->IsWaitingForAutomaticSeparation()) return;

this->PlayLeaveStationSound();

this->LeaveStation();
Expand All @@ -2271,6 +2276,87 @@ void Vehicle::HandleLoading(bool mode)
this->IncrementImplicitOrderIndex();
}

/**
* Checks whether a vehicle is waiting for automatic separation (if not,
* it is ready to depart)
*/
bool Vehicle::IsWaitingForAutomaticSeparation() const {
Ticks now = _date * DAY_TICKS + _date_fract;
return this->AutomaticSeparationIsEnabled() && this->first_order_last_departure > now;
};

/**
* If enabled, calculates the departure time for this vehicle based on the
* automatic separation feature.
*/
void Vehicle::UpdateAutomaticSeparation()
{
/* Check this feature is enabled on the vehicle's orders */
if (!this->AutomaticSeparationIsEnabled()) return;

/* Only perform the separation at the first manual order (saves on storage) */
VehicleOrderID first_manual_order = 0;
for (Order *o = this->GetFirstOrder(); o != nullptr && o->IsType(OT_IMPLICIT); o = o->next) {
++first_manual_order;
}
if (this->cur_implicit_order_index != first_manual_order) return;

/* A "last departure" >= now means we've already calculated the separation */
Ticks now = _date * DAY_TICKS + _date_fract;
if (this->first_order_last_departure >= now) return;

/* Calculate round trip time from last departure and now - automatic separation waiting time is not included */
if (this->first_order_last_departure > 0) {
this->first_order_round_trip_time = max(0, now - this->first_order_last_departure);
}

/* To work out the automatic separation waiting time we need to know:
* - When the last vehicle departed or will depart
* - Average time to perform the order list (as sum/count)
* - How many vehicles are currently operating the order list
* - How many vehicles are currently queuing for the first manual order
*/
Date last_departure = 0;
uint round_trip_sum = 0;
uint round_trip_count = 0;
uint vehicles_operating = 0;
uint vehicles_queuing = 0;
Vehicle *v = this->FirstShared();
while (v != nullptr) {
last_departure = max(last_departure, v->first_order_last_departure);
if (v->first_order_round_trip_time > 0) {
round_trip_sum += v->first_order_round_trip_time;
round_trip_count++;
}
/* A stopped vehicle is not included; it might be stopped by player or parked in a depot */
if (!(v->vehstatus & VS_STOPPED)) {
vehicles_operating++;
/* Count vehicles queing for the first manual order but not currently in the station */
if (v != this && v->cur_speed == 0 && v->cur_implicit_order_index == first_manual_order && !v->current_order.IsType(OT_LOADING)) {
vehicles_queuing++;
}
}
v = v->NextShared();
}

/* Calculate the mean round trip time and separation; round trip time is scaled down based on number of queuing
* vehicles, so that the extra time queuing does not have an adverse effect on separation */
int round_trip_time = round_trip_count > 0 ? round_trip_sum / round_trip_count : 0;
int vehicles = vehicles_operating + vehicles_queuing;
int separation = max(1, vehicles > 0 ? (int)(round_trip_time * ((float)vehicles_operating / vehicles) / (int)vehicles) : 1);

/* Finally we can calculate when this vehicle should depart; if that's in the past, it'll depart right now */
this->first_order_last_departure = max(last_departure + separation, now);

/* Debug logging can be quite spammy as it prints a line every time a vehicle departs the first manual order */
if (_debug_misc_level >= 4) {
char buffer[128];
SetDParam(0, this->index);
GetString(buffer, STR_VEHICLE_NAME, lastof(buffer));
DEBUG(misc, 4, "Orders %p RTT = %d [%.2f days] [%d veh], gap = %d [%.2f days] [%d veh + %d q] / %s gap = %d [%.2f days], wait = %d [%.2f days]", this->orders, round_trip_time, (float)round_trip_time / DAY_TICKS, round_trip_count, separation, (float)separation / DAY_TICKS, vehicles_operating, vehicles_queuing, buffer, now - last_departure, (float)(now - last_departure) / DAY_TICKS, this->first_order_last_departure - now, (float)(this->first_order_last_departure - now) / DAY_TICKS);
}
}

/**
* Get a map of cargoes and free capacities in the consist.
* @param capacities Map to be filled with cargoes and capacities.
Expand Down
5 changes: 5 additions & 0 deletions src/vehicle_base.h
Expand Up @@ -771,6 +771,11 @@ struct Vehicle : VehiclePool::PoolItem<&_vehicle_pool>, BaseVehicle, BaseConsist

inline void SetServiceIntervalIsPercent(bool on) { SB(this->vehicle_flags, VF_SERVINT_IS_PERCENT, 1, on); }

inline bool AutomaticSeparationIsEnabled() const { return (this->orders.list == nullptr) ? false : this->orders.list->AutomaticSeparationIsEnabled(); }
inline void SetAutomaticSeparationIsEnabled(bool enabled) const { if (this->orders.list != nullptr) this->orders.list->SetAutomaticSeparationIsEnabled(enabled); }
bool IsWaitingForAutomaticSeparation() const;
void UpdateAutomaticSeparation();

private:
/**
* Advance cur_real_order_index to the next real order.
Expand Down
1 change: 1 addition & 0 deletions src/vehicle_cmd.cpp
Expand Up @@ -619,6 +619,7 @@ CommandCost CmdStartStopVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1,
if (v->IsStoppedInDepot() && (flags & DC_AUTOREPLACE) == 0) DeleteVehicleNews(p1, STR_NEWS_TRAIN_IS_WAITING + v->type);

v->vehstatus ^= VS_STOPPED;
if (v->vehstatus & VS_STOPPED) v->ResetAutomaticSeparation();
if (v->type != VEH_TRAIN) v->cur_speed = 0; // trains can stop 'slowly'
v->MarkDirty();
SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
Expand Down
1 change: 1 addition & 0 deletions src/widgets/order_widget.h
Expand Up @@ -20,6 +20,7 @@ enum OrderWidgets {
WID_O_DELETE, ///< Delete selected order.
WID_O_STOP_SHARING, ///< Stop sharing orders.
WID_O_NON_STOP, ///< Goto non-stop to destination.
WID_O_AUTOMATIC_SEPARATION, ///< Toggle automatic separation.
WID_O_GOTO, ///< Goto destination.
WID_O_FULL_LOAD, ///< Select full load.
WID_O_UNLOAD, ///< Select unload.
Expand Down

0 comments on commit 5f801f9

Please sign in to comment.