# HG changeset patch # User Tyler Trahan # Date 2024-02-03 13:04:24 # Node ID ee447a88ccab746505203feaec1a9dbe49f56072 # Parent 29f31f80076a1d87fc25a2ece0f8bec6a760d901 Feature: Order flag to unbunch vehicles at depot (#11945) diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -1477,6 +1477,7 @@ void AircraftLeaveHangar(Aircraft *v, Di } 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_InHanga 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 --- a/src/base_consist.cpp +++ b/src/base_consist.cpp @@ -42,3 +42,13 @@ void BaseConsist::CopyConsistPropertiesF } 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 --- 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/lang/english.txt b/src/lang/english.txt --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -4376,6 +4376,7 @@ STR_VEHICLE_VIEW_AIRCRAFT_STATUS_START_S # Messages in the start stop button in the vehicle view STR_VEHICLE_STATUS_LOADING_UNLOADING :{LTBLUE}Loading / Unloading STR_VEHICLE_STATUS_LEAVING :{LTBLUE}Leaving +STR_VEHICLE_STATUS_WAITING_UNBUNCHING :{LTBLUE}Waiting to unbunch STR_VEHICLE_STATUS_CRASHED :{RED}Crashed! STR_VEHICLE_STATUS_BROKEN_DOWN :{RED}Broken down STR_VEHICLE_STATUS_STOPPED :{RED}Stopped @@ -4389,6 +4390,7 @@ STR_VEHICLE_STATUS_NO_ORDERS_VEL STR_VEHICLE_STATUS_HEADING_FOR_WAYPOINT_VEL :{LTBLUE}Heading for {WAYPOINT}, {VELOCITY} STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_VEL :{ORANGE}Heading for {DEPOT}, {VELOCITY} STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_SERVICE_VEL :{LTBLUE}Service at {DEPOT}, {VELOCITY} +STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_UNBUNCH_VEL :{LTBLUE}Unbunch and service at {DEPOT}, {VELOCITY} STR_VEHICLE_STATUS_CANNOT_REACH_STATION_VEL :{LTBLUE}Cannot reach {STATION}, {VELOCITY} STR_VEHICLE_STATUS_CANNOT_REACH_WAYPOINT_VEL :{LTBLUE}Cannot reach {WAYPOINT}, {VELOCITY} @@ -4519,7 +4521,7 @@ STR_ORDERS_TIMETABLE_VIEW_TOOLTIP STR_ORDERS_LIST_TOOLTIP :{BLACK}Order list - click on an order to highlight it. Ctrl+Click to scroll to the order's destination STR_ORDER_INDEX :{COMMA}:{NBSP} -STR_ORDER_TEXT :{STRING4} {STRING2} {STRING} +STR_ORDER_TEXT :{STRING4} {STRING2} {STRING} {STRING} STR_ORDERS_END_OF_ORDERS :- - End of Orders - - STR_ORDERS_END_OF_SHARED_ORDERS :- - End of Shared Orders - - @@ -4553,11 +4555,11 @@ STR_ORDER_REFIT_AUTO_TOOLTIP STR_ORDER_DROP_REFIT_AUTO :Fixed cargo STR_ORDER_DROP_REFIT_AUTO_ANY :Available cargo -STR_ORDER_SERVICE :{BLACK}Service STR_ORDER_DROP_GO_ALWAYS_DEPOT :Always go STR_ORDER_DROP_SERVICE_DEPOT :Service if needed STR_ORDER_DROP_HALT_DEPOT :Stop -STR_ORDER_SERVICE_TOOLTIP :{BLACK}Skip this order unless a service is needed +STR_ORDER_DROP_UNBUNCH :Unbunch +STR_ORDER_DEPOT_ACTION_TOOLTIP :{BLACK}Select the action to take at this depot STR_ORDER_CONDITIONAL_VARIABLE_TOOLTIP :{BLACK}Vehicle data to base jumping on @@ -4626,6 +4628,8 @@ STR_ORDER_REFIT_ORDER STR_ORDER_REFIT_STOP_ORDER :(Refit to {STRING} and stop) STR_ORDER_STOP_ORDER :(Stop) +STR_ORDER_WAIT_TO_UNBUNCH :(wait to unbunch) + STR_ORDER_GO_TO_STATION :{STRING} {STATION} {STRING1} STR_ORDER_GO_TO_STATION_CAN_T_USE_STATION :{PUSH_COLOUR}{RED}(Can't use station){POP_COLOUR} {STRING} {STATION} {STRING1} @@ -5095,6 +5099,14 @@ STR_ERROR_UNABLE_TO_FIND_LOCAL_DEPOT STR_ERROR_DEPOT_WRONG_DEPOT_TYPE :Wrong depot type +# Depot unbunching related errors +STR_ERROR_UNBUNCHING_ONLY_ONE_ALLOWED :{WHITE}... can only have one unbunching order +STR_ERROR_UNBUNCHING_NO_FULL_LOAD :{WHITE}... cannot use full load orders when vehicle has an unbunching order +STR_ERROR_UNBUNCHING_NO_UNBUNCHING_FULL_LOAD :{WHITE}... cannot unbunch a vehicle with a full load order +STR_ERROR_UNBUNCHING_NO_CONDITIONAL :{WHITE}... cannot use conditional orders when vehicle has an unbunching order +STR_ERROR_UNBUNCHING_NO_UNBUNCHING_CONDITIONAL :{WHITE}... cannot unbunch a vehicle with a conditional order +STR_ERROR_UNBUNCHING_NO_SERVICE_IF_NEEDED :{WHITE}... vehicle must always visit the depot to unbunch there + # Autoreplace related errors STR_ERROR_TRAIN_TOO_LONG_AFTER_REPLACEMENT :{WHITE}{VEHICLE} is too long after replacement STR_ERROR_AUTOREPLACE_NOTHING_TO_DO :{WHITE}No autoreplace/renew rules applied diff --git a/src/order_base.h b/src/order_base.h --- 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_cmd.cpp b/src/order_cmd.cpp --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -743,8 +743,17 @@ CommandCost CmdInsertOrder(DoCommandFlag /* Filter invalid load/unload types. */ switch (new_order.GetLoadType()) { - case OLF_LOAD_IF_POSSIBLE: case OLFB_FULL_LOAD: case OLF_FULL_LOAD_ANY: case OLFB_NO_LOAD: break; - default: return CMD_ERROR; + case OLF_LOAD_IF_POSSIBLE: + case OLFB_NO_LOAD: + break; + + case OLFB_FULL_LOAD: + case OLF_FULL_LOAD_ANY: + if (v->HasUnbunchingOrder()) return_cmd_error(STR_ERROR_UNBUNCHING_NO_FULL_LOAD); + break; + + default: + return CMD_ERROR; } switch (new_order.GetUnloadType()) { case OUF_UNLOAD_IF_POSSIBLE: case OUFB_UNLOAD: case OUFB_TRANSFER: case OUFB_NO_UNLOAD: break; @@ -849,6 +858,7 @@ CommandCost CmdInsertOrder(DoCommandFlag VehicleOrderID skip_to = new_order.GetConditionSkipToOrder(); if (skip_to != 0 && skip_to >= v->GetNumOrders()) return CMD_ERROR; // Always allow jumping to the first (even when there is no order). if (new_order.GetConditionVariable() >= OCV_END) return CMD_ERROR; + if (v->HasUnbunchingOrder()) return_cmd_error(STR_ERROR_UNBUNCHING_NO_CONDITIONAL); OrderConditionComparator occ = new_order.GetConditionComparator(); if (occ >= OCC_END) return CMD_ERROR; @@ -937,6 +947,9 @@ void InsertOrder(Vehicle *v, Order *new_ u->cur_implicit_order_index = cur; } } + /* Unbunching data is no longer valid. */ + u->ResetDepotUnbunching(); + /* Update any possible open window of the vehicle */ InvalidateVehicleOrder(u, INVALID_VEH_ORDER_ID | (sel_ord << 8)); } @@ -1051,6 +1064,8 @@ void DeleteOrder(Vehicle *v, VehicleOrde if (u->cur_implicit_order_index >= u->GetNumOrders()) u->cur_implicit_order_index = 0; } } + /* Unbunching data is no longer valid. */ + u->ResetDepotUnbunching(); /* Update any possible open window of the vehicle */ InvalidateVehicleOrder(u, sel_ord | (INVALID_VEH_ORDER_ID << 8)); @@ -1097,6 +1112,9 @@ CommandCost CmdSkipToOrder(DoCommandFlag v->cur_implicit_order_index = v->cur_real_order_index = sel_ord; v->UpdateRealOrderIndex(); + /* Unbunching data is no longer valid. */ + v->ResetDepotUnbunching(); + InvalidateVehicleOrder(v, VIWD_MODIFY_ORDERS); /* We have an aircraft/ship, they have a mini-schedule, so update them all */ @@ -1173,6 +1191,9 @@ CommandCost CmdMoveOrder(DoCommandFlag f } else if (u->cur_implicit_order_index < moving_order && u->cur_implicit_order_index >= target_order) { u->cur_implicit_order_index++; } + /* Unbunching data is no longer valid. */ + u->ResetDepotUnbunching(); + assert(v->orders == u->orders); /* Update any possible open window of the vehicle */ @@ -1274,10 +1295,27 @@ CommandCost CmdModifyOrder(DoCommandFlag if (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION) return CMD_ERROR; if (data > OLFB_NO_LOAD || data == 1) return CMD_ERROR; if (data == order->GetLoadType()) return CMD_ERROR; + if ((data & (OLFB_FULL_LOAD | OLF_FULL_LOAD_ANY)) && v->HasUnbunchingOrder()) return_cmd_error(STR_ERROR_UNBUNCHING_NO_FULL_LOAD); break; case MOF_DEPOT_ACTION: if (data >= DA_END) return CMD_ERROR; + /* The vehicle must always go to the depot (not just if it needs servicing) in order to unbunch there. */ + if ((data == DA_SERVICE) && (order->GetDepotActionType() & ODATFB_UNBUNCH)) return_cmd_error(STR_ERROR_UNBUNCHING_NO_SERVICE_IF_NEEDED); + + /* Check if we are allowed to add unbunching. We are always allowed to remove it. */ + if (data == DA_UNBUNCH) { + /* Only one unbunching order is allowed in a vehicle's orders. If this order already has an unbunching action, no error is needed. */ + if (v->HasUnbunchingOrder() && !(order->GetDepotActionType() & ODATFB_UNBUNCH)) return_cmd_error(STR_ERROR_UNBUNCHING_ONLY_ONE_ALLOWED); + for (Order *o : v->Orders()) { + /* We don't allow unbunching if the vehicle has a conditional order. */ + if (o->IsType(OT_CONDITIONAL)) return_cmd_error(STR_ERROR_UNBUNCHING_NO_UNBUNCHING_CONDITIONAL); + /* We don't allow unbunching if the vehicle has a full load order. */ + if (o->IsType(OT_GOTO_STATION) && o->GetLoadType() & (OLFB_FULL_LOAD | OLF_FULL_LOAD_ANY)) return_cmd_error(STR_ERROR_UNBUNCHING_NO_UNBUNCHING_FULL_LOAD); + /* The vehicle must always go to the depot (not just if it needs servicing) in order to unbunch there. */ + if (o->IsType(OT_GOTO_DEPOT) && o->GetDepotOrderType() & ODTFB_SERVICE) return_cmd_error(STR_ERROR_UNBUNCHING_NO_SERVICE_IF_NEEDED); + } + } break; case MOF_COND_VARIABLE: @@ -1350,6 +1388,7 @@ CommandCost CmdModifyOrder(DoCommandFlag case DA_ALWAYS_GO: order->SetDepotOrderType((OrderDepotTypeFlags)(order->GetDepotOrderType() & ~ODTFB_SERVICE)); order->SetDepotActionType((OrderDepotActionFlags)(order->GetDepotActionType() & ~ODATFB_HALT)); + order->SetDepotActionType((OrderDepotActionFlags)(order->GetDepotActionType() & ~ODATFB_UNBUNCH)); break; case DA_SERVICE: @@ -1360,10 +1399,17 @@ CommandCost CmdModifyOrder(DoCommandFlag case DA_STOP: order->SetDepotOrderType((OrderDepotTypeFlags)(order->GetDepotOrderType() & ~ODTFB_SERVICE)); + order->SetDepotActionType((OrderDepotActionFlags)(order->GetDepotActionType() & ~ODATFB_UNBUNCH)); order->SetDepotActionType((OrderDepotActionFlags)(order->GetDepotActionType() | ODATFB_HALT)); order->SetRefit(CARGO_NO_REFIT); break; + case DA_UNBUNCH: + order->SetDepotOrderType((OrderDepotTypeFlags)(order->GetDepotOrderType() & ~ODTFB_SERVICE)); + order->SetDepotActionType((OrderDepotActionFlags)(order->GetDepotActionType() & ~ODATFB_HALT)); + order->SetDepotActionType((OrderDepotActionFlags)(order->GetDepotActionType() | ODATFB_UNBUNCH)); + break; + default: NOT_REACHED(); } @@ -1430,6 +1476,10 @@ CommandCost CmdModifyOrder(DoCommandFlag u->current_order.GetLoadType() != order->GetLoadType()) { u->current_order.SetLoadType(order->GetLoadType()); } + + /* Unbunching data is no longer valid. */ + u->ResetDepotUnbunching(); + InvalidateVehicleOrder(u, VIWD_MODIFY_ORDERS); } } @@ -1835,6 +1885,9 @@ void DeleteVehicleOrders(Vehicle *v, boo if (!keep_orderlist) v->orders = nullptr; } + /* Unbunching data is no longer valid. */ + v->ResetDepotUnbunching(); + if (reset_order_indices) { v->cur_implicit_order_index = v->cur_real_order_index = 0; if (v->current_order.IsType(OT_LOADING)) { diff --git a/src/order_gui.cpp b/src/order_gui.cpp --- a/src/order_gui.cpp +++ b/src/order_gui.cpp @@ -184,6 +184,7 @@ static const StringID _order_depot_actio STR_ORDER_DROP_GO_ALWAYS_DEPOT, STR_ORDER_DROP_SERVICE_DEPOT, STR_ORDER_DROP_HALT_DEPOT, + STR_ORDER_DROP_UNBUNCH, INVALID_STRING_ID }; @@ -243,11 +244,12 @@ void DrawOrderString(const Vehicle *v, c SetDParam(5, STR_EMPTY); SetDParam(8, STR_EMPTY); + SetDParam(9, STR_EMPTY); /* Check range for aircraft. */ if (v->type == VEH_AIRCRAFT && Aircraft::From(v)->GetRange() > 0 && order->IsGotoOrder()) { const Order *next = order->next != nullptr ? order->next : v->GetFirstOrder(); - if (GetOrderDistance(order, next, v) > Aircraft::From(v)->acache.cached_max_range_sqr) SetDParam(8, STR_ORDER_OUT_OF_RANGE); + if (GetOrderDistance(order, next, v) > Aircraft::From(v)->acache.cached_max_range_sqr) SetDParam(9, STR_ORDER_OUT_OF_RANGE); } switch (order->GetType()) { @@ -332,6 +334,12 @@ void DrawOrderString(const Vehicle *v, c SetDParam(5, (order->GetDepotActionType() & ODATFB_HALT) ? STR_ORDER_REFIT_STOP_ORDER : STR_ORDER_REFIT_ORDER); SetDParam(6, CargoSpec::Get(order->GetRefitCargo())->name); } + + /* Do not show unbunching in the depot in the timetable window. */ + if (!timetable && (order->GetDepotActionType() & ODATFB_UNBUNCH)) { + SetDParam(8, STR_ORDER_WAIT_TO_UNBUNCH); + } + break; case OT_GOTO_WAYPOINT: @@ -941,7 +949,6 @@ public: /* First row. */ this->RaiseWidget(WID_O_FULL_LOAD); this->RaiseWidget(WID_O_UNLOAD); - this->RaiseWidget(WID_O_SERVICE); /* Selection widgets. */ /* Train or road vehicle. */ @@ -1027,7 +1034,6 @@ public: this->SetWidgetDisabledState(WID_O_REFIT, (order->GetDepotOrderType() & ODTFB_SERVICE) || (order->GetDepotActionType() & ODATFB_HALT) || (!this->can_do_refit && !order->IsRefit())); - this->SetWidgetLoweredState(WID_O_SERVICE, order->GetDepotOrderType() & ODTFB_SERVICE); break; case OT_CONDITIONAL: { @@ -1155,6 +1161,24 @@ public: case WID_O_CAPTION: SetDParam(0, this->vehicle->index); break; + + case WID_O_DEPOT_ACTION: { + VehicleOrderID sel = this->OrderGetSel(); + const Order *order = this->vehicle->GetOrder(sel); + if (order == nullptr || !order->IsType(OT_GOTO_DEPOT)) break; + + /* Select the current action selected in the dropdown. The flags don't match the dropdown so we can't just use an index. */ + if (order->GetDepotOrderType() & ODTFB_SERVICE) { + SetDParam(0, STR_ORDER_DROP_SERVICE_DEPOT); + } else if (order->GetDepotActionType() & ODATFB_HALT) { + SetDParam(0, STR_ORDER_DROP_HALT_DEPOT); + } else if (order->GetDepotActionType() & ODATFB_UNBUNCH) { + SetDParam(0, STR_ORDER_DROP_UNBUNCH); + } else { + SetDParam(0, STR_ORDER_DROP_GO_ALWAYS_DEPOT); + } + break; + } } } @@ -1273,12 +1297,8 @@ public: this->OrderClick_Refit(0, false); break; - case WID_O_SERVICE: - if (this->GetWidget(widget)->ButtonHit(pt)) { - this->OrderClick_Service(-1); - } else { - ShowDropDownMenu(this, _order_depot_action_dropdown, DepotActionStringIndex(this->vehicle->GetOrder(this->OrderGetSel())), WID_O_SERVICE, 0, 0); - } + case WID_O_DEPOT_ACTION: + ShowDropDownMenu(this, _order_depot_action_dropdown, DepotActionStringIndex(this->vehicle->GetOrder(this->OrderGetSel())), WID_O_DEPOT_ACTION, 0, 0); break; case WID_O_REFIT_DROPDOWN: @@ -1373,7 +1393,7 @@ public: } break; - case WID_O_SERVICE: + case WID_O_DEPOT_ACTION: this->OrderClick_Service(index); break; @@ -1593,8 +1613,8 @@ static constexpr NWidgetPart _nested_ord NWidget(NWID_SELECTION, INVALID_COLOUR, WID_O_SEL_TOP_MIDDLE), NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, WID_O_UNLOAD), SetMinimalSize(93, 12), SetFill(1, 0), SetDataTip(STR_ORDER_TOGGLE_UNLOAD, STR_ORDER_TOOLTIP_UNLOAD), SetResize(1, 0), - NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, WID_O_SERVICE), SetMinimalSize(93, 12), SetFill(1, 0), - SetDataTip(STR_ORDER_SERVICE, STR_ORDER_SERVICE_TOOLTIP), SetResize(1, 0), + NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, WID_O_DEPOT_ACTION), SetMinimalSize(93, 12), SetFill(1, 0), + SetDataTip(STR_JUST_STRING, STR_ORDER_DEPOT_ACTION_TOOLTIP), SetResize(1, 0), EndContainer(), NWidget(NWID_SELECTION, INVALID_COLOUR, WID_O_SEL_TOP_RIGHT), NWidget(WWT_PANEL, COLOUR_GREY), SetMinimalSize(93, 12), SetFill(1, 0), SetResize(1, 0), EndContainer(), @@ -1671,8 +1691,8 @@ static constexpr NWidgetPart _nested_ord NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_O_REFIT), SetMinimalSize(186, 12), SetFill(1, 0), SetDataTip(STR_ORDER_REFIT, STR_ORDER_REFIT_TOOLTIP), SetResize(1, 0), - NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, WID_O_SERVICE), SetMinimalSize(124, 12), SetFill(1, 0), - SetDataTip(STR_ORDER_SERVICE, STR_ORDER_SERVICE_TOOLTIP), SetResize(1, 0), + NWidget(NWID_BUTTON_DROPDOWN, COLOUR_GREY, WID_O_DEPOT_ACTION), SetMinimalSize(124, 12), SetFill(1, 0), + SetDataTip(STR_JUST_STRING, STR_ORDER_DEPOT_ACTION_TOOLTIP), SetResize(1, 0), EndContainer(), /* Buttons for setting a condition. */ diff --git a/src/order_type.h b/src/order_type.h --- 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) @@ -160,6 +161,7 @@ enum OrderDepotAction { DA_ALWAYS_GO, ///< Always go to the depot DA_SERVICE, ///< Service only if needed DA_STOP, ///< Go to the depot and stop there + DA_UNBUNCH, ///< Go to the depot and unbunch DA_END }; diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -387,7 +387,12 @@ CommandCost CmdTurnRoadVeh(DoCommandFlag if (IsTileType(v->tile, MP_TUNNELBRIDGE) && DirToDiagDir(v->direction) == GetTunnelBridgeDirection(v->tile)) return CMD_ERROR; - if (flags & DC_EXEC) v->reverse_ctr = 180; + if (flags & DC_EXEC) { + v->reverse_ctr = 180; + + /* Unbunching data is no longer valid. */ + v->ResetDepotUnbunching(); + } return CommandCost(); } @@ -1030,6 +1035,7 @@ bool RoadVehLeaveDepot(RoadVehicle *v, b if (RoadVehFindCloseTo(v, x, y, v->direction, false) != nullptr) return true; VehicleServiceInDepot(v); + v->LeaveUnbunchingDepot(); StartRoadVehSound(v); @@ -1587,7 +1593,11 @@ static bool RoadVehController(RoadVehicl 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 --- 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 --- 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 --- 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 --- 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); + VehicleServiceInDepot(v); + v->LeaveUnbunchingDepot(); v->PlayLeaveStationSound(); - VehicleServiceInDepot(v); InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile); SetWindowClassesDirty(WC_SHIPS_LIST); diff --git a/src/timetable_cmd.cpp b/src/timetable_cmd.cpp --- a/src/timetable_cmd.cpp +++ b/src/timetable_cmd.cpp @@ -205,6 +205,12 @@ CommandCost CmdChangeTimetable(DoCommand default: break; } + + /* Unbunching data is no longer valid for any vehicle in this shared order group. */ + Vehicle *u = v->FirstShared(); + for (; u != nullptr; u = u->NextShared()) { + u->ResetDepotUnbunching(); + } } return CommandCost(); @@ -272,6 +278,9 @@ CommandCost CmdSetVehicleOnTime(DoComman if (u->lateness_counter > most_late) { most_late = u->lateness_counter; } + + /* Unbunching data is no longer valid. */ + u->ResetDepotUnbunching(); } if (most_late > 0) { for (Vehicle *u = v->FirstShared(); u != nullptr; u = u->NextShared()) { @@ -284,6 +293,8 @@ CommandCost CmdSetVehicleOnTime(DoComman } } else { v->lateness_counter = 0; + /* Unbunching data is no longer valid. */ + v->ResetDepotUnbunching(); SetWindowDirty(WC_VEHICLE_TIMETABLE, v->index); } } @@ -383,11 +394,14 @@ CommandCost CmdSetTimetableStart(DoComma int idx = 0; for (Vehicle *w : vehs) { - w->lateness_counter = 0; ClrBit(w->vehicle_flags, VF_TIMETABLE_STARTED); /* Do multiplication, then division to reduce rounding errors. */ w->timetable_start = start_tick + (idx * total_duration / num_vehs); + + /* Unbunching data is no longer valid. */ + v->ResetDepotUnbunching(); + SetWindowDirty(WC_VEHICLE_TIMETABLE, w->index); ++idx; } diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2112,6 +2112,9 @@ CommandCost CmdReverseTrainDirection(DoC HideFillingPercent(&v->fill_percent_te_id); ReverseTrainDirection(v); } + + /* Unbunching data is no longer valid. */ + v->ResetDepotUnbunching(); } } return CommandCost(); @@ -2142,6 +2145,9 @@ CommandCost CmdForceTrainProceed(DoComma * next signal we encounter. */ t->force_proceed = t->force_proceed == TFP_SIGNAL ? TFP_NONE : HasBit(t->flags, VRF_TRAIN_STUCK) || t->IsChainInDepot() ? TFP_STUCK : TFP_SIGNAL; SetWindowDirty(WC_VEHICLE_VIEW, t->index); + + /* Unbunching data is no longer valid. */ + t->ResetDepotUnbunching(); } return CommandCost(); @@ -2275,6 +2281,9 @@ static bool CheckTrainStayInDepot(Train 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 +2324,9 @@ static bool CheckTrainStayInDepot(Train if (_settings_client.gui.show_track_reservation) MarkTileDirtyByTile(v->tile); VehicleServiceInDepot(v); + v->LeaveUnbunchingDepot(); + v->PlayLeaveStationSound(); SetWindowClassesDirty(WC_TRAINS_LIST); - v->PlayLeaveStationSound(); v->track = TRACK_BIT_X; if (v->direction & 2) v->track = TRACK_BIT_Y; 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) { diff --git a/src/vehicle_base.h b/src/vehicle_base.h --- 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. diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -629,6 +629,10 @@ CommandCost CmdStartStopVehicle(DoComman v->vehstatus ^= VS_STOPPED; if (v->type != VEH_TRAIN) v->cur_speed = 0; // trains can stop 'slowly' + + /* Unbunching data is no longer valid. */ + v->ResetDepotUnbunching(); + v->MarkDirty(); SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); SetWindowDirty(WC_VEHICLE_DEPOT, v->tile); diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -3087,7 +3087,7 @@ public: { if (widget != WID_VV_START_STOP) return; - const Vehicle *v = Vehicle::Get(this->window_number); + Vehicle *v = Vehicle::Get(this->window_number); StringID str; TextColour text_colour = TC_FROMSTRING; if (v->vehstatus & VS_CRASHED) { @@ -3113,6 +3113,8 @@ public: str = STR_VEHICLE_STATUS_TRAIN_STUCK; } else if (v->type == VEH_AIRCRAFT && HasBit(Aircraft::From(v)->flags, VAF_DEST_TOO_FAR) && !v->current_order.IsType(OT_LOADING)) { str = STR_VEHICLE_STATUS_AIRCRAFT_TOO_FAR; + } else if (v->IsInDepot() && v->IsWaitingForUnbunching()) { + str = STR_VEHICLE_STATUS_WAITING_UNBUNCHING; } else { // vehicle is in a "normal" state, show current order if (mouse_over_start_stop) { if (v->vehstatus & VS_STOPPED) { @@ -3143,6 +3145,8 @@ public: str = STR_EMPTY; } else if (v->current_order.GetDepotActionType() & ODATFB_HALT) { str = HasBit(v->vehicle_flags, VF_PATHFINDER_LOST) ? STR_VEHICLE_STATUS_CANNOT_REACH_DEPOT_VEL : STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_VEL; + } else if (v->current_order.GetDepotActionType() & ODATFB_UNBUNCH) { + str = HasBit(v->vehicle_flags, VF_PATHFINDER_LOST) ? STR_VEHICLE_STATUS_CANNOT_REACH_DEPOT_SERVICE_VEL : STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_UNBUNCH_VEL; } else { str = HasBit(v->vehicle_flags, VF_PATHFINDER_LOST) ? STR_VEHICLE_STATUS_CANNOT_REACH_DEPOT_SERVICE_VEL : STR_VEHICLE_STATUS_HEADING_FOR_DEPOT_SERVICE_VEL; } diff --git a/src/widgets/order_widget.h b/src/widgets/order_widget.h --- a/src/widgets/order_widget.h +++ b/src/widgets/order_widget.h @@ -20,11 +20,12 @@ enum OrderWidgets : WidgetID { WID_O_DELETE, ///< Delete selected order. WID_O_STOP_SHARING, ///< Stop sharing orders. WID_O_NON_STOP, ///< Goto non-stop to destination. + WID_O_DEPOT_UNBUNCHING, ///< Toggle unbunching. WID_O_GOTO, ///< Goto destination. WID_O_FULL_LOAD, ///< Select full load. WID_O_UNLOAD, ///< Select unload. WID_O_REFIT, ///< Select refit. - WID_O_SERVICE, ///< Select service (at depot). + WID_O_DEPOT_ACTION, ///< Dropdown to select the depot action (stop, service if needed, unbunch). WID_O_REFIT_DROPDOWN, ///< Open refit options. WID_O_COND_VARIABLE, ///< Choose condition variable. WID_O_COND_COMPARATOR, ///< Choose condition type.