From 9b1783809db6b6510dfbdaf1c8cb9cd2b5c1a7c3 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Wed, 23 Jun 2021 08:21:29 +0200 Subject: [PATCH 1/5] Add station cargo history --- src/date.cpp | 2 + src/graph_gui.cpp | 272 +++++++++++++++++++++++++++++++++++ src/graph_gui.h | 3 + src/lang/english.txt | 7 + src/saveload/station_sl.cpp | 1 + src/station.cpp | 4 +- src/station_base.h | 4 + src/station_cmd.cpp | 28 ++++ src/station_gui.cpp | 8 ++ src/station_type.h | 3 + src/widgets/graph_widget.h | 14 ++ src/widgets/station_widget.h | 1 + src/window_type.h | 6 + 13 files changed, 352 insertions(+), 1 deletion(-) diff --git a/src/date.cpp b/src/date.cpp index 05cf97656f..1ba02b22a9 100644 --- a/src/date.cpp +++ b/src/date.cpp @@ -184,6 +184,7 @@ extern void CompaniesMonthlyLoop(); extern void EnginesMonthlyLoop(); extern void TownsMonthlyLoop(); extern void IndustryMonthlyLoop(); +extern void StationDailyLoop(); extern void StationMonthlyLoop(); extern void SubsidyMonthlyLoop(); @@ -276,6 +277,7 @@ static void OnNewDay() DisasterDailyLoop(); IndustryDailyLoop(); + StationDailyLoop(); if (!_settings_time.time_in_minutes || _settings_client.gui.date_with_time > 0) { SetWindowWidgetDirty(WC_STATUS_BAR, 0, WID_S_LEFT); diff --git a/src/graph_gui.cpp b/src/graph_gui.cpp index 0abc45ab04..9bf64d0473 100644 --- a/src/graph_gui.cpp +++ b/src/graph_gui.cpp @@ -31,6 +31,7 @@ #include #include "safeguards.h" +#include "station_base.h" /* Bitmasks of company and cargo indices that shouldn't be drawn. */ static CompanyMask _legend_excluded_companies; @@ -1649,3 +1650,274 @@ void InitializeGraphGui() _legend_excluded_companies = 0; _legend_excluded_cargo = 0; } + +/*************************/ +/* STATION CARGO HISTORY */ +/*************************/ +struct StationCargoGraphWindow final : BaseGraphWindow { + StationID station_id; + uint line_height {}; ///< Pixel height of each cargo type row. + Scrollbar *vscroll; ///< Cargo list scrollbar. + uint legend_width {}; ///< Width of legend 'blob'. + CargoTypes legend_excluded_cargo; + + StationCargoGraphWindow(WindowDesc *desc, WindowNumber window) : + BaseGraphWindow(desc, WID_SCG_GRAPH, STR_JUST_COMMA) + { + station_id = static_cast(window); + + this->num_on_x_axis = MAX_STATION_CARGO_HISTORY_DAYS; // Four weeks + this->num_vert_lines = MAX_STATION_CARGO_HISTORY_DAYS; + this->month = 0xFF; + this->x_values_start = 2; + this->x_values_increment = 2; + + this->CreateNestedTree(); + this->vscroll = this->GetScrollbar(WID_SCG_MATRIX_SCROLLBAR); + this->vscroll->SetCount(_sorted_standard_cargo_specs_size); + + /* Initialise the data set */ + this->OnHundredthTick(); + + this->FinishInitNested(window); + } + + void OnInit() override + { + /* Width of the legend blob. */ + this->legend_width = (FONT_HEIGHT_SMALL - ScaleFontTrad(1)) * 8 / 5; + this->legend_excluded_cargo = 0; + + const Station* station = Station::GetIfValid(this->station_id); + + if (station == nullptr) return; + + const CargoSpec *cs; + FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + if (station->goods[cs->Index()].cargo.AvailableCount() == 0) { + SetBit(legend_excluded_cargo, cs->Index()); + } + } + } + + void SetStringParameters(int widget) const override + { + if (widget == WID_SCG_CAPTION) { + SetDParam(0, this->station_id); + } + } + + void UpdateExcludedData() + { + this->excluded_data = 0; + + uint8 i = 0; + const CargoSpec *cs; + FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + if (HasBit(legend_excluded_cargo, cs->Index())) SetBit(this->excluded_data, i); + i++; + } + } + + void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override + { + if (widget < WID_SCG_MATRIX) { + BaseGraphWindow::UpdateWidgetSize(widget, size, padding, fill, resize); + return; + } + + const CargoSpec *cs; + FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + SetDParam(0, cs->name); + Dimension d = GetStringBoundingBox(STR_GRAPH_CARGO_PAYMENT_CARGO); + d.width += this->legend_width + 4; // color field + d.width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT; + d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM; + *size = maxdim(d, *size); + } + + this->line_height = size->height; + size->height = this->line_height * 11; /* Default number of cargo types in most climates. */ + resize->width = 0; + resize->height = this->line_height; + } + + void DrawWidget(const Rect &r, int widget) const override + { + if (widget < WID_SCG_MATRIX) { + BaseGraphWindow::DrawWidget(r, widget); + return; + } + + const bool rtl = _current_text_dir == TD_RTL; + + int x = r.left + WD_FRAMERECT_LEFT; + int y = r.top; + const uint row_height = FONT_HEIGHT_SMALL; + const int padding = ScaleFontTrad(1); + + int pos = this->vscroll->GetPosition(); + int max = pos + this->vscroll->GetCapacity(); + + const CargoSpec *cs; + FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + if (pos-- > 0) continue; + if (--max < 0) break; + + const bool lowered = !HasBit(legend_excluded_cargo, cs->Index()); + + /* Redraw box if lowered */ + if (lowered) DrawFrameRect(r.left, y, r.right, y + this->line_height - 1, COLOUR_BROWN, lowered ? FR_LOWERED : FR_NONE); + + const byte clk_dif = lowered ? 1 : 0; + const int rect_x = clk_dif + (rtl ? r.right - this->legend_width - WD_FRAMERECT_RIGHT : r.left + WD_FRAMERECT_LEFT); + + GfxFillRect(rect_x, y + padding + clk_dif, rect_x + this->legend_width, y + row_height - 1 + clk_dif, PC_BLACK); + GfxFillRect(rect_x + 1, y + padding + 1 + clk_dif, rect_x + this->legend_width - 1, y + row_height - 2 + clk_dif, cs->legend_colour); + SetDParam(0, cs->name); + DrawString(rtl ? r.left : x + this->legend_width + 4 + clk_dif, (rtl ? r.right - this->legend_width - 4 + clk_dif : r.right), y + clk_dif, STR_GRAPH_CARGO_PAYMENT_CARGO); + + y += this->line_height; + } + } + + void OnClick(Point pt, int widget, int click_count) override + { + switch (widget) { + case WID_SCG_ENABLE_CARGOES: + /* Remove all cargoes from the excluded lists. */ + legend_excluded_cargo = 0; + this->excluded_data = 0; + this->SetDirty(); + break; + + case WID_SCG_DISABLE_CARGOES: { + /* Add all cargoes to the excluded lists. */ + int i = 0; + const CargoSpec *cs; + FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + SetBit(legend_excluded_cargo, cs->Index()); + SetBit(this->excluded_data, i); + i++; + } + this->SetDirty(); + break; + } + + case WID_SCG_MATRIX: { + uint row = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SCG_MATRIX); + if (row >= this->vscroll->GetCount()) return; + + const CargoSpec *cs; + FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + if (row-- > 0) continue; + + ToggleBit(legend_excluded_cargo, cs->Index()); + this->UpdateExcludedData(); + this->SetDirty(); + break; + } + break; + } + } + } + + void OnResize() override + { + this->vscroll->SetCapacityFromWidget(this, WID_SCG_MATRIX); + } + + void OnGameTick() override + { + /* Override default OnGameTick */ + } + + /** + * Some data on this window has become invalid. + * @param data Information about the changed data. + * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details. + */ + void OnInvalidateData(int data = 0, bool gui_scope = true) override + { + if (!gui_scope) return; + this->OnHundredthTick(); + } + + void OnHundredthTick() override + { + this->UpdateExcludedData(); + + const Station* station = Station::GetIfValid(this->station_id); + + if (station == nullptr) return; + + uint8 i = 0; + const CargoSpec *cs; + FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + this->colours[i] = cs->legend_colour; + + for (uint j = 0; j < MAX_STATION_CARGO_HISTORY_DAYS; j++) { + + this->cost[i][j] = station->station_cargo_history[cs->Index() * MAX_STATION_CARGO_HISTORY_DAYS + j] * STATION_CARGO_HISTORY_FACTOR; + } + i++; + } + + this->num_dataset = i; + + this->SetDirty(); + } +}; + + +static const NWidgetPart _nested_station_cargo_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_GREY), + NWidget(WWT_CAPTION, COLOUR_GREY, WID_SCG_CAPTION), SetDataTip(STR_GRAPH_STATION_CARGO_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_SHADEBOX, COLOUR_GREY), + NWidget(WWT_DEFSIZEBOX, COLOUR_GREY), + NWidget(WWT_STICKYBOX, COLOUR_GREY), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY, WID_SCG_BACKGROUND), SetMinimalSize(568, 128), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0), + NWidget(WWT_TEXT, COLOUR_GREY, WID_SCG_HEADER), SetMinimalSize(0, 6), SetPadding(2, 0, 2, 0), SetDataTip(STR_GRAPH_STATION_CARGO_TITLE, STR_NULL), + NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_EMPTY, COLOUR_GREY, WID_SCG_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1), + NWidget(NWID_VERTICAL), + NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 0), SetResize(0, 1), + NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, WID_SCG_ENABLE_CARGOES), SetDataTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_ORANGE, WID_SCG_DISABLE_CARGOES), SetDataTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0), + NWidget(NWID_SPACER), SetMinimalSize(0, 4), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_BROWN, WID_SCG_MATRIX), SetResize(0, 2), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO), SetScrollbar(WID_SCG_MATRIX_SCROLLBAR), + NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_SCG_MATRIX_SCROLLBAR), + EndContainer(), + NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1), SetResize(0, 1), + EndContainer(), + NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(NWID_SPACER), SetMinimalSize(WD_RESIZEBOX_WIDTH, 0), SetFill(1, 0), SetResize(1, 0), + NWidget(WWT_TEXT, COLOUR_GREY, WID_SCG_FOOTER), SetMinimalSize(0, 6), SetPadding(2, 0, 2, 0), SetDataTip(STR_GRAPH_STATION_CARGO_X_LABEL, STR_NULL), + NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0), + NWidget(WWT_RESIZEBOX, COLOUR_GREY, WID_SCG_RESIZE), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _station_cargo_desc( + WDP_AUTO, "graph_station_cargo", 0, 0, + WC_STATION_CARGO, WC_NONE, + 0, + _nested_station_cargo_widgets, lengthof(_nested_station_cargo_widgets) +); + + +void ShowStationCargo(StationID station_id) +{ + AllocateWindowDescFront(&_station_cargo_desc, station_id); +} + diff --git a/src/graph_gui.h b/src/graph_gui.h index 07a0c2f66a..b1d08b8e7b 100644 --- a/src/graph_gui.h +++ b/src/graph_gui.h @@ -12,6 +12,8 @@ extern uint8 _cargo_payment_x_mode; +typedef uint16 StationID; + void ShowOperatingProfitGraph(); void ShowIncomeGraph(); void ShowDeliveredCargoGraph(); @@ -20,5 +22,6 @@ void ShowCompanyValueGraph(); void ShowCargoPaymentRates(); void ShowCompanyLeagueTable(); void ShowPerformanceRatingDetail(); +void ShowStationCargo(StationID); #endif /* GRAPH_GUI_H */ diff --git a/src/lang/english.txt b/src/lang/english.txt index eba78574a4..6edb153bfd 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -613,6 +613,10 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Display STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Toggle graph for cargo type on/off STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING} +STR_GRAPH_STATION_CARGO_CAPTION :{WHITE}{STATION} - Waiting Cargo History +STR_GRAPH_STATION_CARGO_X_LABEL :{TINY_FONT}{BLACK}Development over the last 48 days +STR_GRAPH_STATION_CARGO_TITLE :{TINY_FONT}{BLACK}Units of cargo waiting at the station + STR_GRAPH_CARGO_DAYS_MODE :{TINY_FONT}{BLACK}Days in transit STR_GRAPH_CARGO_SPEED_MODE :{TINY_FONT}{BLACK}Average speed STR_GRAPH_CARGO_TOOLTIP_DAYS_MODE :{BLACK}Display days in transit on the x-axis of the graph @@ -4181,6 +4185,9 @@ STR_STATION_VIEW_GROUP_D_V_S :Destination-Via STR_STATION_VIEW_DEPARTURES_BUTTON :{BLACK}Departures STR_STATION_VIEW_DEPARTURES_TOOLTIP :{BLACK}Show list of scheduled departures +STR_STATION_VIEW_HISTORY_BUTTON :{BLACK}History +STR_STATION_VIEW_HISTORY_TOOLTIP :{BLACK}Show waiting cargo history + ############ range for rating starts STR_CARGO_RATING_APPALLING :Appalling STR_CARGO_RATING_VERY_POOR :Very Poor diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index 4a6a63787b..8d81d508ac 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -459,6 +459,7 @@ static const SaveLoad _station_desc[] = { SLE_CONDVAR(Station, always_accepted, SLE_FILE_U32 | SLE_VAR_U64, SLV_127, SLV_EXTEND_CARGOTYPES), SLE_CONDVAR(Station, always_accepted, SLE_UINT64, SLV_EXTEND_CARGOTYPES, SL_MAX_VERSION), SLE_CONDNULL_X(32 * 24, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_JOKERPP, SL_JOKER_1_22)), + SLE_CONDARR(Station, station_cargo_history, SLE_UINT8, NUM_CARGO * MAX_STATION_CARGO_HISTORY_DAYS, SL_JOKER_1_22, SL_MAX_VERSION), SLE_END() }; diff --git a/src/station.cpp b/src/station.cpp index 2ccc42e1a0..69e4e91051 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -62,6 +62,7 @@ BaseStation::~BaseStation() DeleteWindowById(WC_SHIPS_LIST, VehicleListIdentifier(VL_STATION_LIST, VEH_SHIP, this->owner, this->index).Pack()); DeleteWindowById(WC_AIRCRAFT_LIST, VehicleListIdentifier(VL_STATION_LIST, VEH_AIRCRAFT, this->owner, this->index).Pack()); DeleteWindowById(WC_DEPARTURES_BOARD, this->index); + DeleteWindowById(WC_STATION_CARGO, this->index); if (HasBit(_display_opt, Station::IsExpected(this) ? DO_SHOW_STATION_NAMES : DO_SHOW_WAYPOINT_NAMES) && !(_local_company != this->owner && this->owner != OWNER_NONE && !HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS))) { @@ -76,7 +77,8 @@ Station::Station(TileIndex tile) : ship_station(INVALID_TILE, 0, 0), indtype(IT_INVALID), time_since_load(255), - time_since_unload(255) + time_since_unload(255), + station_cargo_history{} { /* this->random_bits is set in Station::AddFacility() */ } diff --git a/src/station_base.h b/src/station_base.h index dc011231a6..ad4e9d0625 100644 --- a/src/station_base.h +++ b/src/station_base.h @@ -808,6 +808,8 @@ public: IndustryList industries_near; ///< Cached list of industries near the station that can accept cargo, @see DeliverGoodsToIndustry() Industry *industry; ///< NOSAVE: Associated industry for neutral stations. (Rebuilt on load from Industry->st) + uint8 station_cargo_history[NUM_CARGO * MAX_STATION_CARGO_HISTORY_DAYS]; ///< Station history of waiting cargo. + Station(TileIndex tile = INVALID_TILE); ~Station(); @@ -817,6 +819,8 @@ public: void UpdateVirtCoord() override; + void UpdateCargoHistory(); + void MoveSign(TileIndex new_xy) override; void AfterStationTileSetChange(bool adding, StationType type); diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 966312d04c..4b3a9ad762 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -404,6 +404,23 @@ void Station::GetTileArea(TileArea *ta, StationType type) const } } +/** + * Update the cargo history. + */ +void Station::UpdateCargoHistory() +{ + const CargoSpec* cs; + FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + auto amount = this->goods[cs->Index()].cargo.AvailableCount(); + + std::rotate(std::begin(this->station_cargo_history) + cs->Index() * MAX_STATION_CARGO_HISTORY_DAYS, + std::begin(this->station_cargo_history) + cs->Index() * MAX_STATION_CARGO_HISTORY_DAYS + 1, + std::begin(this->station_cargo_history) + (cs->Index() + 1) * MAX_STATION_CARGO_HISTORY_DAYS); + + this->station_cargo_history[(cs->Index() + 1) * MAX_STATION_CARGO_HISTORY_DAYS - 1] = std::clamp(amount / STATION_CARGO_HISTORY_FACTOR, (uint)0, (uint)UINT8_MAX); + } +} + /** * Update the virtual coords needed to draw the station sign. */ @@ -4240,6 +4257,17 @@ void OnTick_Station() } } +/** Daily loop for stations. */ +void StationDailyLoop() +{ + // Only record cargo history every second day. + if (_date % 2 != 0) { + for (Station *st : Station::Iterate()) { + st->UpdateCargoHistory(); + } + } +} + /** Monthly loop for stations. */ void StationMonthlyLoop() { diff --git a/src/station_gui.cpp b/src/station_gui.cpp index cf2ff5d1f9..b5456d3a27 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -32,6 +32,7 @@ #include "linkgraph/linkgraph.h" #include "zoom_func.h" #include "departures_gui.h" +#include "graph_gui.h" #include "zoning.h" #include "newgrf_debug.h" @@ -831,6 +832,8 @@ static const NWidgetPart _nested_station_view_widgets[] = { NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_ACCEPTS_RATINGS), SetMinimalSize(46, 12), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_STATION_VIEW_RATINGS_BUTTON, STR_STATION_VIEW_RATINGS_TOOLTIP), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_HISTORY), SetMinimalSize(60, 12), SetResize(1, 0), SetFill(1, 1), + SetDataTip(STR_STATION_VIEW_HISTORY_BUTTON, STR_STATION_VIEW_HISTORY_TOOLTIP), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SV_DEPARTURES), SetMinimalSize(46, 12), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_STATION_VIEW_DEPARTURES_BUTTON, STR_STATION_VIEW_DEPARTURES_TOOLTIP), NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SV_CLOSE_AIRPORT), SetMinimalSize(45, 12), SetResize(1, 0), SetFill(1, 1), @@ -2052,6 +2055,11 @@ struct StationViewWindow : public Window { break; } + case WID_SV_HISTORY: { + ShowStationCargo((StationID)this->window_number); + break; + } + case WID_SV_DEPARTURES: { ShowStationDepartures((StationID)this->window_number); break; diff --git a/src/station_type.h b/src/station_type.h index 0ea2175e86..2194f59b23 100644 --- a/src/station_type.h +++ b/src/station_type.h @@ -27,6 +27,9 @@ struct Waypoint; static const StationID NEW_STATION = 0xFFFE; static const StationID INVALID_STATION = 0xFFFF; +static const uint MAX_STATION_CARGO_HISTORY_DAYS = 24; +static const uint STATION_CARGO_HISTORY_FACTOR = 25; + typedef SmallStack StationIDStack; /** Station types */ diff --git a/src/widgets/graph_widget.h b/src/widgets/graph_widget.h index 14205b47b7..1ab0d81142 100644 --- a/src/widgets/graph_widget.h +++ b/src/widgets/graph_widget.h @@ -53,6 +53,20 @@ enum CargoPaymentRatesWidgets { WID_CPR_SPEED, ///< Speed mode. }; +/** Widget of the #StationCargoGraphWindow class. */ +enum StationCargoWidgets { + WID_SCG_CAPTION, ///< Window title + WID_SCG_BACKGROUND, ///< Background of the window. + WID_SCG_HEADER, ///< Header. + WID_SCG_GRAPH, ///< Graph itself. + WID_SCG_RESIZE, ///< Resize button. + WID_SCG_FOOTER, ///< Footer. + WID_SCG_ENABLE_CARGOES, ///< Enable cargoes button. + WID_SCG_DISABLE_CARGOES, ///< Disable cargoes button. + WID_SCG_MATRIX, ///< Cargo list. + WID_SCG_MATRIX_SCROLLBAR, ///< Cargo list scrollbar. +}; + /** Widget of the #CompanyLeagueWindow class. */ enum CompanyLeagueWidgets { WID_CL_BACKGROUND, ///< Background of the window. diff --git a/src/widgets/station_widget.h b/src/widgets/station_widget.h index d8951182d3..ed36fe5b0c 100644 --- a/src/widgets/station_widget.h +++ b/src/widgets/station_widget.h @@ -30,6 +30,7 @@ enum StationViewWidgets { WID_SV_PLANES, ///< List of scheduled planes button. WID_SV_CATCHMENT, ///< Toggle catchment area highlight. WID_SV_DEPARTURES, ///< Departures button. + WID_SV_HISTORY, ///< Cargo history button. }; /** Widgets of the #CompanyStationsWindow class. */ diff --git a/src/window_type.h b/src/window_type.h index c84d6c8a9c..66f7535527 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -581,6 +581,12 @@ enum WindowClass { */ WC_PAYMENT_RATES, + /** + * Station cargo graph; %Window numbers: + * - #StationID = #StationCargoWidgets + */ + WC_STATION_CARGO, + /** * Performance detail window; %Window numbers: * - 0 = #PerformanceRatingDetailsWidgets From f901da344e0da39bce1a283fbdcf51ca8d6bf258 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 29 Jun 2021 18:06:12 +0100 Subject: [PATCH 2/5] Add CargoTypesBit helper method to CargoSpec --- src/cargotype.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/cargotype.h b/src/cargotype.h index e56e3c5955..511fa8c23e 100644 --- a/src/cargotype.h +++ b/src/cargotype.h @@ -90,6 +90,15 @@ struct CargoSpec { return this - CargoSpec::array; } + /** + * Determine CargoTypes bit of this cargospec + * @return CargoTypes bit + */ + inline CargoTypes CargoTypesBit() const + { + return static_cast(1) << this->Index(); + } + /** * Tests for validity of this cargospec * @return is this cargospec valid? From 5698507d0bbc68e9e21d99458590a081f5a08beb Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 29 Jun 2021 18:09:49 +0100 Subject: [PATCH 3/5] Change station cargo history storage format Use uint16 to avoid truncation issues Don't reserve memory for unused cargoes Store history as ring buffer Update history graph immediately on storage date Show total waiting cargo --- src/graph_gui.cpp | 44 ++++++++++++++++++------------------- src/saveload/station_sl.cpp | 1 - src/station.cpp | 3 ++- src/station_base.h | 5 ++++- src/station_cmd.cpp | 35 +++++++++++++++++++---------- src/station_gui.cpp | 6 +++++ src/station_type.h | 1 - 7 files changed, 57 insertions(+), 38 deletions(-) diff --git a/src/graph_gui.cpp b/src/graph_gui.cpp index 9bf64d0473..8c81dcc01f 100644 --- a/src/graph_gui.cpp +++ b/src/graph_gui.cpp @@ -1660,6 +1660,7 @@ struct StationCargoGraphWindow final : BaseGraphWindow { Scrollbar *vscroll; ///< Cargo list scrollbar. uint legend_width {}; ///< Width of legend 'blob'. CargoTypes legend_excluded_cargo; + CargoTypes present_cargoes; StationCargoGraphWindow(WindowDesc *desc, WindowNumber window) : BaseGraphWindow(desc, WID_SCG_GRAPH, STR_JUST_COMMA) @@ -1674,10 +1675,9 @@ struct StationCargoGraphWindow final : BaseGraphWindow { this->CreateNestedTree(); this->vscroll = this->GetScrollbar(WID_SCG_MATRIX_SCROLLBAR); - this->vscroll->SetCount(_sorted_standard_cargo_specs_size); /* Initialise the data set */ - this->OnHundredthTick(); + this->FillGraphData(); this->FinishInitNested(window); } @@ -1687,17 +1687,6 @@ struct StationCargoGraphWindow final : BaseGraphWindow { /* Width of the legend blob. */ this->legend_width = (FONT_HEIGHT_SMALL - ScaleFontTrad(1)) * 8 / 5; this->legend_excluded_cargo = 0; - - const Station* station = Station::GetIfValid(this->station_id); - - if (station == nullptr) return; - - const CargoSpec *cs; - FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { - if (station->goods[cs->Index()].cargo.AvailableCount() == 0) { - SetBit(legend_excluded_cargo, cs->Index()); - } - } } void SetStringParameters(int widget) const override @@ -1714,6 +1703,7 @@ struct StationCargoGraphWindow final : BaseGraphWindow { uint8 i = 0; const CargoSpec *cs; FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + if (!HasBit(this->present_cargoes, cs->Index())) continue; if (HasBit(legend_excluded_cargo, cs->Index())) SetBit(this->excluded_data, i); i++; } @@ -1761,6 +1751,7 @@ struct StationCargoGraphWindow final : BaseGraphWindow { const CargoSpec *cs; FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + if (!HasBit(this->present_cargoes, cs->Index())) continue; if (pos-- > 0) continue; if (--max < 0) break; @@ -1786,17 +1777,18 @@ struct StationCargoGraphWindow final : BaseGraphWindow { switch (widget) { case WID_SCG_ENABLE_CARGOES: /* Remove all cargoes from the excluded lists. */ - legend_excluded_cargo = 0; + this->legend_excluded_cargo = 0; this->excluded_data = 0; this->SetDirty(); break; case WID_SCG_DISABLE_CARGOES: { /* Add all cargoes to the excluded lists. */ + this->legend_excluded_cargo = ~static_cast(0); int i = 0; const CargoSpec *cs; FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { - SetBit(legend_excluded_cargo, cs->Index()); + if (!HasBit(this->present_cargoes, cs->Index())) continue; SetBit(this->excluded_data, i); i++; } @@ -1810,6 +1802,7 @@ struct StationCargoGraphWindow final : BaseGraphWindow { const CargoSpec *cs; FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + if (!HasBit(this->present_cargoes, cs->Index())) continue; if (row-- > 0) continue; ToggleBit(legend_excluded_cargo, cs->Index()); @@ -1840,25 +1833,32 @@ struct StationCargoGraphWindow final : BaseGraphWindow { void OnInvalidateData(int data = 0, bool gui_scope = true) override { if (!gui_scope) return; - this->OnHundredthTick(); + this->FillGraphData(); } - void OnHundredthTick() override + void FillGraphData() { - this->UpdateExcludedData(); - const Station* station = Station::GetIfValid(this->station_id); - if (station == nullptr) return; + this->present_cargoes = station->station_cargo_history_cargoes; + this->vscroll->SetCount(CountBits(this->present_cargoes)); + + this->UpdateExcludedData(); + uint8 i = 0; const CargoSpec *cs; FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { + if (!HasBit(this->present_cargoes, cs->Index())) continue; this->colours[i] = cs->legend_colour; + const auto &history = station->station_cargo_history[CountBits(this->present_cargoes & (cs->CargoTypesBit() - 1))]; + + uint offset = station->station_cargo_history_offset; for (uint j = 0; j < MAX_STATION_CARGO_HISTORY_DAYS; j++) { - - this->cost[i][j] = station->station_cargo_history[cs->Index() * MAX_STATION_CARGO_HISTORY_DAYS + j] * STATION_CARGO_HISTORY_FACTOR; + this->cost[i][j] = history[offset]; + offset++; + if (offset == MAX_STATION_CARGO_HISTORY_DAYS) offset = 0; } i++; } diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index 8d81d508ac..4a6a63787b 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -459,7 +459,6 @@ static const SaveLoad _station_desc[] = { SLE_CONDVAR(Station, always_accepted, SLE_FILE_U32 | SLE_VAR_U64, SLV_127, SLV_EXTEND_CARGOTYPES), SLE_CONDVAR(Station, always_accepted, SLE_UINT64, SLV_EXTEND_CARGOTYPES, SL_MAX_VERSION), SLE_CONDNULL_X(32 * 24, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_JOKERPP, SL_JOKER_1_22)), - SLE_CONDARR(Station, station_cargo_history, SLE_UINT8, NUM_CARGO * MAX_STATION_CARGO_HISTORY_DAYS, SL_JOKER_1_22, SL_MAX_VERSION), SLE_END() }; diff --git a/src/station.cpp b/src/station.cpp index 69e4e91051..effc2c898d 100644 --- a/src/station.cpp +++ b/src/station.cpp @@ -78,7 +78,8 @@ Station::Station(TileIndex tile) : indtype(IT_INVALID), time_since_load(255), time_since_unload(255), - station_cargo_history{} + station_cargo_history_cargoes(0), + station_cargo_history_offset(0) { /* this->random_bits is set in Station::AddFacility() */ } diff --git a/src/station_base.h b/src/station_base.h index ad4e9d0625..c11f1fa24c 100644 --- a/src/station_base.h +++ b/src/station_base.h @@ -23,6 +23,7 @@ #include "core/endian_type.hpp" #include #include +#include #include #include #include @@ -808,7 +809,9 @@ public: IndustryList industries_near; ///< Cached list of industries near the station that can accept cargo, @see DeliverGoodsToIndustry() Industry *industry; ///< NOSAVE: Associated industry for neutral stations. (Rebuilt on load from Industry->st) - uint8 station_cargo_history[NUM_CARGO * MAX_STATION_CARGO_HISTORY_DAYS]; ///< Station history of waiting cargo. + CargoTypes station_cargo_history_cargoes; ///< Bitmask of cargoes in station_cargo_history + uint8 station_cargo_history_offset; ///< Start offset in station_cargo_history cargo ring buffer + std::vector> station_cargo_history; ///< Station history of waiting cargo. Station(TileIndex tile = INVALID_TILE); ~Station(); diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 4b3a9ad762..7c45135f35 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -409,16 +409,26 @@ void Station::GetTileArea(TileArea *ta, StationType type) const */ void Station::UpdateCargoHistory() { - const CargoSpec* cs; - FOR_ALL_SORTED_STANDARD_CARGOSPECS(cs) { - auto amount = this->goods[cs->Index()].cargo.AvailableCount(); - - std::rotate(std::begin(this->station_cargo_history) + cs->Index() * MAX_STATION_CARGO_HISTORY_DAYS, - std::begin(this->station_cargo_history) + cs->Index() * MAX_STATION_CARGO_HISTORY_DAYS + 1, - std::begin(this->station_cargo_history) + (cs->Index() + 1) * MAX_STATION_CARGO_HISTORY_DAYS); - - this->station_cargo_history[(cs->Index() + 1) * MAX_STATION_CARGO_HISTORY_DAYS - 1] = std::clamp(amount / STATION_CARGO_HISTORY_FACTOR, (uint)0, (uint)UINT8_MAX); + uint storage_offset = 0; + bool update_window = false; + for (const CargoSpec *cs : CargoSpec::Iterate()) { + uint amount = this->goods[cs->Index()].cargo.TotalCount(); + if (!HasBit(this->station_cargo_history_cargoes, cs->Index())) { + if (amount == 0) { + /* No cargo present, and no history stored for this cargo, no work to do */ + continue; + } else { + if (this->station_cargo_history_cargoes == 0) update_window = true; + SetBit(this->station_cargo_history_cargoes, cs->Index()); + this->station_cargo_history.emplace(this->station_cargo_history.begin() + storage_offset); + } + } + this->station_cargo_history[storage_offset][this->station_cargo_history_offset] = static_cast(std::clamp(amount, (uint)0, (uint)UINT16_MAX)); + storage_offset++; } + this->station_cargo_history_offset++; + if (this->station_cargo_history_offset == MAX_STATION_CARGO_HISTORY_DAYS) this->station_cargo_history_offset = 0; + if (update_window) InvalidateWindowData(WC_STATION_VIEW, this->index, -1); } /** @@ -3788,7 +3798,7 @@ bool GetNewGrfRating(const Station *st, const CargoSpec *cs, const GoodsEntry *g { *new_grf_rating = 0; bool is_using_newgrf_rating = false; - + /* Perform custom station rating. If it succeeds the speed, days in transit and * waiting cargo ratings must not be executed. */ @@ -3822,7 +3832,7 @@ int GetSpeedRating(const GoodsEntry *ge) int GetWaitTimeRating(const CargoSpec *cs, const GoodsEntry *ge) { int rating = 0; - + uint wait_time = ge->time_since_pickup; if (_settings_game.station.cargo_class_rating_wait_time) { @@ -3895,7 +3905,7 @@ int GetTargetRating(const Station *st, const CargoSpec *cs, const GoodsEntry *ge } else if (HasBit(cs->callback_mask, CBM_CARGO_STATION_RATING_CALC)) { int new_grf_rating; - + if (GetNewGrfRating(st, cs, ge, &new_grf_rating)) { skip = true; rating += new_grf_rating; @@ -4265,6 +4275,7 @@ void StationDailyLoop() for (Station *st : Station::Iterate()) { st->UpdateCargoHistory(); } + InvalidateWindowClassesData(WC_STATION_CARGO); } } diff --git a/src/station_gui.cpp b/src/station_gui.cpp index b5456d3a27..8477a7ad74 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -1366,6 +1366,12 @@ struct StationViewWindow : public Window { SetViewportCatchmentStation(Station::Get(this->window_number), false); } + void OnInit() override + { + const Station *st = Station::Get(this->window_number); + SetWidgetDisabledState(WID_SV_HISTORY, st->station_cargo_history_cargoes == 0); + } + /** * Show a certain cargo entry characterized by source/next/dest station, cargo ID and amount of cargo at the * right place in the cargo view. I.e. update as many rows as are expanded following that characterization. diff --git a/src/station_type.h b/src/station_type.h index 2194f59b23..a8e657efc1 100644 --- a/src/station_type.h +++ b/src/station_type.h @@ -28,7 +28,6 @@ static const StationID NEW_STATION = 0xFFFE; static const StationID INVALID_STATION = 0xFFFF; static const uint MAX_STATION_CARGO_HISTORY_DAYS = 24; -static const uint STATION_CARGO_HISTORY_FACTOR = 25; typedef SmallStack StationIDStack; From 37c7cf98996b47f8561b0e6cf310520b6e105fa7 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 29 Jun 2021 18:26:28 +0100 Subject: [PATCH 4/5] Remove number of days from hard-coded X axis label string To support later day number changes and/or translations --- src/graph_gui.cpp | 3 +++ src/lang/english.txt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/graph_gui.cpp b/src/graph_gui.cpp index 8c81dcc01f..56506672db 100644 --- a/src/graph_gui.cpp +++ b/src/graph_gui.cpp @@ -1694,6 +1694,9 @@ struct StationCargoGraphWindow final : BaseGraphWindow { if (widget == WID_SCG_CAPTION) { SetDParam(0, this->station_id); } + if (widget == WID_SCG_FOOTER) { + SetDParam(0, 48); + } } void UpdateExcludedData() diff --git a/src/lang/english.txt b/src/lang/english.txt index 6edb153bfd..aea9f5f2d9 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -614,7 +614,7 @@ STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Toggle g STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING} STR_GRAPH_STATION_CARGO_CAPTION :{WHITE}{STATION} - Waiting Cargo History -STR_GRAPH_STATION_CARGO_X_LABEL :{TINY_FONT}{BLACK}Development over the last 48 days +STR_GRAPH_STATION_CARGO_X_LABEL :{TINY_FONT}{BLACK}Development over the last {NUM} days STR_GRAPH_STATION_CARGO_TITLE :{TINY_FONT}{BLACK}Units of cargo waiting at the station STR_GRAPH_CARGO_DAYS_MODE :{TINY_FONT}{BLACK}Days in transit From b438380a1e681e49f471402d614dcdef3c8a3a45 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 29 Jun 2021 18:48:39 +0100 Subject: [PATCH 5/5] Add station cargo history save/load support --- src/saveload/extended_ver_sl.cpp | 1 + src/saveload/extended_ver_sl.h | 1 + src/saveload/station_sl.cpp | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index ec53a3fde9..fa4d0b3d51 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -151,6 +151,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_WATER_FLOODING, XSCF_NULL, 2, 2, "water_flooding", nullptr, nullptr, nullptr }, { XSLFI_MORE_HOUSES, XSCF_NULL, 2, 2, "more_houses", nullptr, nullptr, nullptr }, { XSLFI_CUSTOM_TOWN_ZONE, XSCF_IGNORABLE_UNKNOWN, 1, 1, "custom_town_zone", nullptr, nullptr, nullptr }, + { XSLFI_STATION_CARGO_HISTORY, XSCF_NULL, 1, 1, "station_cargo_history", nullptr, nullptr, nullptr }, { XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker }; diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h index 1cc9c89de1..bd347e754f 100644 --- a/src/saveload/extended_ver_sl.h +++ b/src/saveload/extended_ver_sl.h @@ -105,6 +105,7 @@ enum SlXvFeatureIndex { XSLFI_WATER_FLOODING, ///< Water flooding map bit XSLFI_MORE_HOUSES, ///< More house types XSLFI_CUSTOM_TOWN_ZONE, ///< Custom town zones + XSLFI_STATION_CARGO_HISTORY, ///< Station waiting cargo history XSLFI_RIFF_HEADER_60_BIT, ///< Size field in RIFF chunk header is 60 bit XSLFI_HEIGHT_8_BIT, ///< Map tile height is 8 bit instead of 4 bit, but savegame version may be before this became true in trunk diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index 4a6a63787b..0572d55ba0 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -459,6 +459,7 @@ static const SaveLoad _station_desc[] = { SLE_CONDVAR(Station, always_accepted, SLE_FILE_U32 | SLE_VAR_U64, SLV_127, SLV_EXTEND_CARGOTYPES), SLE_CONDVAR(Station, always_accepted, SLE_UINT64, SLV_EXTEND_CARGOTYPES, SL_MAX_VERSION), SLE_CONDNULL_X(32 * 24, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_JOKERPP, SL_JOKER_1_22)), + SLE_CONDVAR_X(Station, station_cargo_history_cargoes, SLE_UINT64, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_STATION_CARGO_HISTORY)), SLE_END() }; @@ -547,6 +548,17 @@ static void RealSave_STNN(BaseStation *bst) SlObjectSaveFiltered(const_cast(&(*it)), _cargo_list_desc); // _cargo_list_desc has no conditionals } } + + assert(st->station_cargo_history.size() == CountBits(st->station_cargo_history_cargoes)); + dumper->CheckBytes(st->station_cargo_history.size() * MAX_STATION_CARGO_HISTORY_DAYS * 2); + for (const auto &history : st->station_cargo_history) { + uint i = st->station_cargo_history_offset; + do { + dumper->RawWriteUint16(history[i]); + i++; + if (i == MAX_STATION_CARGO_HISTORY_DAYS) i = 0; + } while (i != st->station_cargo_history_offset); + } } for (uint i = 0; i < bst->num_specs; i++) { @@ -656,6 +668,15 @@ static void Load_STNN() } if (SlXvIsFeatureMissing(XSLFI_ST_LAST_VEH_TYPE)) st->goods[i].last_vehicle_type = _old_last_vehicle_type; } + + st->station_cargo_history.resize(CountBits(st->station_cargo_history_cargoes)); + buffer->CheckBytes(st->station_cargo_history.size() * MAX_STATION_CARGO_HISTORY_DAYS * 2); + for (auto &history : st->station_cargo_history) { + for (uint16 &amount : history) { + amount = buffer->RawReadUint16(); + } + } + st->station_cargo_history_offset = 0; } if (bst->num_specs != 0) {