diff --git a/src/lang/english.txt b/src/lang/english.txt index 813e85d5b8..cc08099187 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1789,6 +1789,9 @@ STR_CONFIG_SETTING_PAY_FOR_REPAIR_VEHICLE_HELPTEXT :Pay for repairi STR_CONFIG_SETTING_REPAIR_COST :Cost of repairing vehicle: 1/{STRING2} of total cost STR_CONFIG_SETTING_REPAIR_COST_HELPTEXT :Cost of repairing vehicle +STR_CONFIG_OCCUPANCY_SMOOTHNESS :Smoothness of order occupancy measurement: {STRING2} +STR_CONFIG_OCCUPANCY_SMOOTHNESS_HELPTEXT :0% sets the measurement to the most recent value, 100% leaves it unchanged + # Config errors STR_CONFIG_ERROR :{WHITE}Error with the configuration file... STR_CONFIG_ERROR_ARRAY :{WHITE}... error in array '{RAW_STRING}' @@ -4236,6 +4239,11 @@ STR_ORDERS_GO_TO_TOOLTIP :{BLACK}Insert a STR_ORDERS_VEH_WITH_SHARED_ORDERS_LIST_TOOLTIP :{BLACK}Show all vehicles that share this schedule +STR_ORDERS_OCCUPANCY_BUTTON :{BLACK}% +STR_ORDERS_OCCUPANCY_BUTTON_TOOLTIP :{BLACK}Show occupancy running averages +STR_ORDERS_OCCUPANCY_LIST_TOOLTIP :{BLACK}Order occupancy - this shows runnings averages of recent occupancy levels when leaving a station, for all vehicles sharing these orders +STR_ORDERS_OCCUPANCY_PERCENT :{NUM}% + # String parts to build the order string STR_ORDER_GO_TO_WAYPOINT :Go via {WAYPOINT} STR_ORDER_GO_NON_STOP_TO_WAYPOINT :Go non-stop via {WAYPOINT} diff --git a/src/order_base.h b/src/order_base.h index 4f0d0b489a..d36f23594e 100644 --- a/src/order_base.h +++ b/src/order_base.h @@ -43,6 +43,8 @@ private: CargoID refit_cargo; ///< Refit CargoID + uint8 occupancy; ///< Estimate of vehicle occupancy on departure, for the current order, 0 indicates invalid, 1 - 101 indicate 0 - 100% + uint16 wait_time; ///< How long in ticks to wait at the destination. uint16 travel_time; ///< How long in ticks the journey to this destination should take. uint16 max_speed; ///< How fast the vehicle may go on the way to the destination. @@ -218,6 +220,18 @@ public: */ inline void SetMaxSpeed(uint16 speed) { this->max_speed = speed; } + /** + * Get the occupancy value + * @return occupancy + */ + inline uint8 GetOccupancy() const { return this->occupancy; } + + /** + * Set the occupancy value + * @param occupancy The occupancy to set + */ + inline void SetOccupancy(uint8 occupancy) { this->occupancy = occupancy; } + bool ShouldStopAtStation(const Vehicle *v, StationID station) const; bool CanLoadOrUnload() const; bool CanLeaveWithCargo(bool has_cargo) const; diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index 00f524a2a1..b440624275 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -245,6 +245,7 @@ Order::Order(uint32 packed) this->dest = GB(packed, 16, 16); this->next = NULL; this->refit_cargo = CT_NO_REFIT; + this->occupancy = 0; this->wait_time = 0; this->travel_time = 0; this->max_speed = UINT16_MAX; diff --git a/src/order_gui.cpp b/src/order_gui.cpp index ea8146231b..341b85db93 100644 --- a/src/order_gui.cpp +++ b/src/order_gui.cpp @@ -782,6 +782,7 @@ public: this->CreateNestedTree(); this->vscroll = this->GetScrollbar(WID_O_SCROLLBAR); + this->GetWidget(WID_O_SEL_OCCUPANCY)->SetDisplayedPlane(SZSP_NONE); this->FinishInitNested(v->index); if (v->owner == _local_company) { this->DisableWidget(WID_O_EMPTY); @@ -817,6 +818,12 @@ public: virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) { switch (widget) { + case WID_O_OCCUPANCY_LIST: + SetDParamMaxValue(0, 100); + size->width = WD_FRAMERECT_LEFT + GetStringBoundingBox(STR_ORDERS_OCCUPANCY_PERCENT).width + 10 + WD_FRAMERECT_RIGHT; + /* FALL THROUGH */ + + case WID_O_SEL_OCCUPANCY: case WID_O_ORDER_LIST: resize->height = FONT_HEIGHT_NORMAL; size->height = 6 * resize->height + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM; @@ -1083,6 +1090,8 @@ public: /* Disable list of vehicles with the same shared orders if there is no list */ this->SetWidgetDisabledState(WID_O_SHARED_ORDER_LIST, !shared_orders); + this->GetWidget(WID_O_SEL_OCCUPANCY)->SetDisplayedPlane(IsWidgetLowered(WID_O_OCCUPANCY_TOGGLE) ? 0 : SZSP_NONE); + this->SetDirty(); } @@ -1098,8 +1107,19 @@ public: virtual void DrawWidget(const Rect &r, int widget) const { - if (widget != WID_O_ORDER_LIST) return; + switch (widget) { + case WID_O_ORDER_LIST: + DrawOrderListWidget(r); + break; + case WID_O_OCCUPANCY_LIST: + DrawOccupancyListWidget(r); + break; + } + } + + void DrawOrderListWidget(const Rect &r) const + { bool rtl = _current_text_dir == TD_RTL; SetDParamMaxValue(0, this->vehicle->GetNumOrders(), 2); int index_column_width = GetStringBoundingBox(STR_ORDER_INDEX).width + 2 * GetSpriteSize(rtl ? SPR_ARROW_RIGHT : SPR_ARROW_LEFT).width + 3; @@ -1154,6 +1174,30 @@ public: } } + void DrawOccupancyListWidget(const Rect &r) const + { + int y = r.top + WD_FRAMERECT_TOP; + int line_height = this->GetWidget(WID_O_ORDER_LIST)->resize_y; + + int i = this->vscroll->GetPosition(); + const Order *order = this->vehicle->GetOrder(i); + /* Draw the orders. */ + while (order != NULL) { + /* Don't draw anything if it extends past the end of the window. */ + if (!this->vscroll->IsVisible(i)) break; + + uint8 occupancy = order->GetOccupancy(); + if (occupancy > 0) { + SetDParam(0, occupancy - 1); + DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, STR_ORDERS_OCCUPANCY_PERCENT, (i == this->selected_order) ? TC_WHITE : TC_BLACK); + } + y += line_height; + + i++; + order = order->next; + } + } + virtual void SetStringParameters(int widget) const { switch (widget) { @@ -1339,6 +1383,12 @@ public: case WID_O_SHARED_ORDER_LIST: ShowVehicleListWindow(this->vehicle); break; + + case WID_O_OCCUPANCY_TOGGLE: + ToggleWidgetLoweredState(WID_O_OCCUPANCY_TOGGLE); + this->UpdateButtonState(); + this->ReInit(); + break; } } @@ -1585,6 +1635,10 @@ static const NWidgetPart _nested_orders_train_widgets[] = { EndContainer(), NWidget(NWID_HORIZONTAL), NWidget(WWT_PANEL, COLOUR_GREY, WID_O_ORDER_LIST), SetMinimalSize(372, 62), SetDataTip(0x0, STR_ORDERS_LIST_TOOLTIP), SetResize(1, 1), SetScrollbar(WID_O_SCROLLBAR), EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_O_SEL_OCCUPANCY), + NWidget(WWT_PANEL, COLOUR_GREY, WID_O_OCCUPANCY_LIST), SetMinimalSize(50, 0), SetFill(0, 1), SetDataTip(STR_NULL, STR_ORDERS_OCCUPANCY_LIST_TOOLTIP), + SetScrollbar(WID_O_SCROLLBAR), EndContainer(), + EndContainer(), NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_O_SCROLLBAR), EndContainer(), @@ -1622,6 +1676,7 @@ static const NWidgetPart _nested_orders_train_widgets[] = { SetDataTip(STR_BLACK_COMMA, STR_ORDER_CONDITIONAL_VALUE_TOOLTIP), SetResize(1, 0), EndContainer(), EndContainer(), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_O_OCCUPANCY_TOGGLE), SetMinimalSize(12, 12), SetDataTip(STR_ORDERS_OCCUPANCY_BUTTON, STR_ORDERS_OCCUPANCY_BUTTON_TOOLTIP), NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_O_SHARED_ORDER_LIST), SetMinimalSize(12, 12), SetDataTip(SPR_SHARED_ORDERS_ICON, STR_ORDERS_VEH_WITH_SHARED_ORDERS_LIST_TOOLTIP), EndContainer(), @@ -1663,6 +1718,10 @@ static const NWidgetPart _nested_orders_widgets[] = { EndContainer(), NWidget(NWID_HORIZONTAL), NWidget(WWT_PANEL, COLOUR_GREY, WID_O_ORDER_LIST), SetMinimalSize(372, 62), SetDataTip(0x0, STR_ORDERS_LIST_TOOLTIP), SetResize(1, 1), SetScrollbar(WID_O_SCROLLBAR), EndContainer(), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_O_SEL_OCCUPANCY), + NWidget(WWT_PANEL, COLOUR_GREY, WID_O_OCCUPANCY_LIST), SetMinimalSize(50, 0), SetFill(0, 1), SetDataTip(STR_NULL, STR_ORDERS_OCCUPANCY_LIST_TOOLTIP), + SetScrollbar(WID_O_SCROLLBAR), EndContainer(), + EndContainer(), NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_O_SCROLLBAR), EndContainer(), @@ -1697,6 +1756,7 @@ static const NWidgetPart _nested_orders_widgets[] = { EndContainer(), EndContainer(), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_O_OCCUPANCY_TOGGLE), SetMinimalSize(12, 12), SetDataTip(STR_ORDERS_OCCUPANCY_BUTTON, STR_ORDERS_OCCUPANCY_BUTTON_TOOLTIP), NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_O_SHARED_ORDER_LIST), SetMinimalSize(12, 12), SetDataTip(SPR_SHARED_ORDERS_ICON, STR_ORDERS_VEH_WITH_SHARED_ORDERS_LIST_TOOLTIP), EndContainer(), diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index 4d0fdafaa1..3e382c1b89 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -60,6 +60,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_ENH_VIEWPORT_PLANS, XSCF_IGNORABLE_ALL, 1, 1, "enh_viewport_plans", NULL, NULL, "PLAN,PLLN" }, { XSLFI_INFRA_SHARING, XSCF_NULL, 1, 1, "infra_sharing", NULL, NULL, NULL }, { XSLFI_VARIABLE_DAY_LENGTH, XSCF_NULL, 1, 1, "variable_day_length", NULL, NULL, NULL }, + { XSLFI_ORDER_OCCUPANCY, XSCF_NULL, 1, 1, "order_occupancy", NULL, NULL, NULL }, { XSLFI_NULL, XSCF_NULL, 0, 0, NULL, NULL, NULL, NULL },// This is the end marker }; diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h index b8ce1ea0fe..ee926ae5e7 100644 --- a/src/saveload/extended_ver_sl.h +++ b/src/saveload/extended_ver_sl.h @@ -35,6 +35,7 @@ enum SlXvFeatureIndex { XSLFI_ENH_VIEWPORT_PLANS, ///< Enhanced viewport patch: plans XSLFI_INFRA_SHARING, ///< Infrastructure sharing patch XSLFI_VARIABLE_DAY_LENGTH, ///< Variable day length patch + XSLFI_ORDER_OCCUPANCY, ///< Running average of order occupancy XSLFI_SIZE, ///< Total count of features, including null feature }; diff --git a/src/saveload/order_sl.cpp b/src/saveload/order_sl.cpp index d55d577cb6..f3ddcb31af 100644 --- a/src/saveload/order_sl.cpp +++ b/src/saveload/order_sl.cpp @@ -110,6 +110,7 @@ const SaveLoad *GetOrderDescription() SLE_REF(Order, next, REF_ORDER), SLE_CONDVAR(Order, refit_cargo, SLE_UINT8, 36, SL_MAX_VERSION), SLE_CONDNULL(1, 36, 181), // refit_subtype + SLE_CONDVAR_X(Order, occupancy, SLE_UINT8, 0, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_ORDER_OCCUPANCY)), SLE_CONDVAR(Order, wait_time, SLE_UINT16, 67, SL_MAX_VERSION), SLE_CONDVAR(Order, travel_time, SLE_UINT16, 67, SL_MAX_VERSION), SLE_CONDVAR(Order, max_speed, SLE_UINT16, 172, SL_MAX_VERSION), diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 9f712e8a8c..0d9795f173 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1676,6 +1676,7 @@ static SettingsContainer &GetSettingsTree() vehicles->Add(new SettingEntry("order.timetable_automated")); vehicles->Add(new SettingEntry("order.timetable_separation")); vehicles->Add(new SettingEntry("vehicle.adjacent_crossings")); + vehicles->Add(new SettingEntry("order.occupancy_smoothness")); } SettingsPage *limitations = main->Add(new SettingsPage(STR_CONFIG_SETTING_LIMITATIONS)); diff --git a/src/settings_type.h b/src/settings_type.h index 902727db39..8daacaed02 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -475,6 +475,7 @@ struct OrderSettings { bool timetable_automated; ///< whether to automatically manage timetables bool timetable_separation; ///< whether to perform automatic separation based on timetable bool serviceathelipad; ///< service helicopters at helipads automatically (no need to send to depot) + uint8 occupancy_smoothness; ///< percentage smoothness of occupancy measurement changes }; /** Settings related to vehicles. */ diff --git a/src/table/settings.ini b/src/table/settings.ini index 479b57dc26..d574aa1e6c 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -2336,6 +2336,21 @@ min = 0 max = 1000000 cat = SC_EXPERT +[SDT_VAR] +base = GameSettings +var = order.occupancy_smoothness +type = SLE_UINT8 +def = 75 +min = 0 +max = 100 +interval = 10 +str = STR_CONFIG_OCCUPANCY_SMOOTHNESS +strhelp = STR_CONFIG_OCCUPANCY_SMOOTHNESS_HELPTEXT +strval = STR_CONFIG_SETTING_PERCENTAGE +cat = SC_EXPERT +extver = SlXvFeatureTest(XSLFTO_AND, XSLFI_ORDER_OCCUPANCY) +patxname = ""order_occupancy.order.occupancy_smoothness"" + ## [SDT_VAR] base = GameSettings diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 0a96490085..3e3c5aa427 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2450,6 +2450,28 @@ void Vehicle::LeaveStation() SetBit(Train::From(this)->flags, VRF_LEAVING_STATION); } + if (this->cur_real_order_index < this->GetNumOrders()) { + Order *real_current_order = this->GetOrder(this->cur_real_order_index); + uint current_occupancy = CalcPercentVehicleFilled(this, NULL); + uint old_occupancy = real_current_order->GetOccupancy(); + uint new_occupancy; + if (old_occupancy == 0) { + new_occupancy = current_occupancy; + } else { + // Exponential weighted moving average using occupancy_smoothness + new_occupancy = (old_occupancy - 1) * _settings_game.order.occupancy_smoothness; + new_occupancy += current_occupancy * (100 - _settings_game.order.occupancy_smoothness); + new_occupancy += 50; // round to nearest integer percent, rather than just floor + new_occupancy /= 100; + } + if (new_occupancy + 1 != old_occupancy) { + real_current_order->SetOccupancy(static_cast(new_occupancy + 1)); + for (const Vehicle *v = this->FirstShared(); v != NULL; v = v->NextShared()) { + SetWindowDirty(WC_VEHICLE_ORDERS, v->index); + } + } + } + this->MarkDirty(); } diff --git a/src/widgets/order_widget.h b/src/widgets/order_widget.h index 825f791ea8..aaf86a0ea9 100644 --- a/src/widgets/order_widget.h +++ b/src/widgets/order_widget.h @@ -39,6 +39,9 @@ enum OrderWidgets { WID_O_SEL_TOP_ROW, ///< #NWID_SELECTION widget for the top row of the 'your non-trains' order window. WID_O_SEL_BOTTOM_MIDDLE, ///< #NWID_SELECTION widget for the middle part of the bottom row of the 'your train' order window. WID_O_SHARED_ORDER_LIST, ///< Open list of shared vehicles. + WID_O_SEL_OCCUPANCY, ///< #NWID_SELECTION widget for the occupancy list panel. + WID_O_OCCUPANCY_LIST, ///< Occupancy list panel. + WID_O_OCCUPANCY_TOGGLE, ///< Toggle display of occupancy measures. }; #endif /* WIDGETS_ORDER_WIDGET_H */