From 2bada59193ad54ca5b563b32ffccc7ffeb7f201d Mon Sep 17 00:00:00 2001 From: Tyler Trahan Date: Sun, 24 Sep 2023 19:43:10 -0400 Subject: [PATCH] Feature: Mode to display timetable in seconds --- src/lang/english.txt | 29 ++++-- src/settings_gui.cpp | 2 +- src/settings_type.h | 3 +- src/table/settings/gui_settings.ini | 18 ++-- src/timetable.h | 6 ++ src/timetable_cmd.cpp | 7 +- src/timetable_gui.cpp | 151 +++++++++++++++++++++++----- 7 files changed, 170 insertions(+), 46 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index d82f9702ce..ec6f3ec932 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -255,6 +255,10 @@ STR_UNITS_HEIGHT_IMPERIAL :{DECIMAL}{NBSP} STR_UNITS_HEIGHT_METRIC :{DECIMAL}{NBSP}m STR_UNITS_HEIGHT_SI :{DECIMAL}{NBSP}m +STR_UNITS_DAYS :{COMMA}{NBSP}day{P "" s} +STR_UNITS_SECONDS :{COMMA}{NBSP}second{P "" s} +STR_UNITS_TICKS :{COMMA}{NBSP}tick{P "" s} + # Common window strings STR_LIST_FILTER_TITLE :{BLACK}Filter: STR_LIST_FILTER_OSKTITLE :{BLACK}Enter one or more keywords to filter the list for @@ -1655,8 +1659,12 @@ STR_CONFIG_SETTING_ADVANCED_VEHICLE_LISTS_HELPTEXT :Enable usage of STR_CONFIG_SETTING_LOADING_INDICATORS :Use loading indicators: {STRING2} STR_CONFIG_SETTING_LOADING_INDICATORS_HELPTEXT :Select whether loading indicators are displayed above loading or unloading vehicles -STR_CONFIG_SETTING_TIMETABLE_IN_TICKS :Show timetable in ticks rather than days: {STRING2} -STR_CONFIG_SETTING_TIMETABLE_IN_TICKS_HELPTEXT :Show travel times in time tables in game ticks instead of days +STR_CONFIG_SETTING_TIMETABLE_MODE :Time units for timetables: {STRING2} +STR_CONFIG_SETTING_TIMETABLE_MODE_HELPTEXT :Select the time units used for vehicle timetables +###length 3 +STR_CONFIG_SETTING_TIMETABLE_MODE_DAYS :Days +STR_CONFIG_SETTING_TIMETABLE_MODE_SECONDS :Seconds +STR_CONFIG_SETTING_TIMETABLE_MODE_TICKS :Ticks STR_CONFIG_SETTING_TIMETABLE_SHOW_ARRIVAL_DEPARTURE :Show arrival and departure in timetables: {STRING2} STR_CONFIG_SETTING_TIMETABLE_SHOW_ARRIVAL_DEPARTURE_HELPTEXT :Display anticipated arrival and departure times in timetables @@ -4565,8 +4573,6 @@ STR_TIMETABLE_STAY_FOR_ESTIMATED :(stay for {STRI STR_TIMETABLE_AND_TRAVEL_FOR_ESTIMATED :(travel for {STRING1}, not timetabled) STR_TIMETABLE_STAY_FOR :and stay for {STRING1} STR_TIMETABLE_AND_TRAVEL_FOR :and travel for {STRING1} -STR_TIMETABLE_DAYS :{COMMA}{NBSP}day{P "" s} -STR_TIMETABLE_TICKS :{COMMA}{NBSP}tick{P "" s} STR_TIMETABLE_TOTAL_TIME :{BLACK}This timetable will take {STRING1} to complete STR_TIMETABLE_TOTAL_TIME_INCOMPLETE :{BLACK}This timetable will take at least {STRING1} to complete (not all timetabled) @@ -4575,10 +4581,13 @@ STR_TIMETABLE_STATUS_ON_TIME :{BLACK}This veh STR_TIMETABLE_STATUS_LATE :{BLACK}This vehicle is currently running {STRING1} late STR_TIMETABLE_STATUS_EARLY :{BLACK}This vehicle is currently running {STRING1} early STR_TIMETABLE_STATUS_NOT_STARTED :{BLACK}This timetable has not yet started -STR_TIMETABLE_STATUS_START_AT :{BLACK}This timetable will start at {STRING1} +STR_TIMETABLE_STATUS_START_AT_DATE :{BLACK}This timetable will start at {STRING1} +STR_TIMETABLE_STATUS_START_IN_SECONDS :{BLACK}This timetable will start in {COMMA} seconds -STR_TIMETABLE_STARTING_DATE :{BLACK}Start date -STR_TIMETABLE_STARTING_DATE_TOOLTIP :{BLACK}Select a date as starting point of this timetable. Ctrl+Click distributes all vehicles sharing this order evenly from the given date based on their relative order, if the order is completely timetabled +STR_TIMETABLE_START :{BLACK}Start Timetable +STR_TIMETABLE_START_TOOLTIP :{BLACK}Select when this timetable starts. Ctrl+Click evenly distributes the start of all vehicles sharing this order based on their relative order, if the order is completely timetabled + +STR_TIMETABLE_START_SECONDS_QUERY :Seconds until timetable starts STR_TIMETABLE_CHANGE_TIME :{BLACK}Change Time STR_TIMETABLE_WAIT_TIME_TOOLTIP :{BLACK}Change the amount of time that the highlighted order should take. Ctrl+Click sets the time for all orders @@ -4602,8 +4611,10 @@ STR_TIMETABLE_EXPECTED :{BLACK}Expected STR_TIMETABLE_SCHEDULED :{BLACK}Scheduled STR_TIMETABLE_EXPECTED_TOOLTIP :{BLACK}Switch between expected and scheduled -STR_TIMETABLE_ARRIVAL :A: {COLOUR}{DATE_TINY} -STR_TIMETABLE_DEPARTURE :D: {COLOUR}{DATE_TINY} +STR_TIMETABLE_ARRIVAL_DATE :A: {COLOUR}{DATE_TINY} +STR_TIMETABLE_DEPARTURE_DATE :D: {COLOUR}{DATE_TINY} +STR_TIMETABLE_ARRIVAL_SECONDS_IN_FUTURE :A: {COLOUR}{COMMA} sec +STR_TIMETABLE_DEPARTURE_SECONDS_IN_FUTURE :D: {COLOUR}{COMMA} sec # Date window (for timetable) diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index c80bcc2766..b6d9a9118d 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1803,7 +1803,7 @@ static SettingsContainer &GetSettingsTree() interface->Add(new SettingEntry("gui.statusbar_pos")); interface->Add(new SettingEntry("gui.prefer_teamchat")); interface->Add(new SettingEntry("gui.advanced_vehicle_list")); - interface->Add(new SettingEntry("gui.timetable_in_ticks")); + interface->Add(new SettingEntry("gui.timetable_mode")); interface->Add(new SettingEntry("gui.timetable_arrival_departure")); interface->Add(new SettingEntry("gui.show_newgrf_name")); interface->Add(new SettingEntry("gui.show_cargo_in_vehicle_lists")); diff --git a/src/settings_type.h b/src/settings_type.h index c70f5f98ae..8ac229ed72 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -22,6 +22,7 @@ #include "openttd.h" #include "rail_gui.h" #include "signal_type.h" +#include "timetable.h" /* Used to validate sizes of "max" value in settings. */ const size_t MAX_SLE_UINT8 = UINT8_MAX; @@ -167,7 +168,7 @@ struct GUISettings { SignalCycleSettings cycle_signal_types; ///< Which signal types to cycle with the build signal tool. SignalType default_signal_type; ///< The default signal type, which is set automatically by the last signal used. Not available in Settings. TimerGameCalendar::Year coloured_news_year; ///< when does newspaper become coloured? - bool timetable_in_ticks; ///< whether to show the timetable in ticks rather than days + TimetableMode timetable_mode; ///< Time units for timetables: days, seconds, or ticks bool quick_goto; ///< Allow quick access to 'goto button' in vehicle orders window bool auto_euro; ///< automatically switch to euro in 2002 byte drag_signals_density; ///< many signals density diff --git a/src/table/settings/gui_settings.ini b/src/table/settings/gui_settings.ini index ab6fdc4bb2..2986b25ebe 100644 --- a/src/table/settings/gui_settings.ini +++ b/src/table/settings/gui_settings.ini @@ -420,14 +420,18 @@ str = STR_CONFIG_SETTING_ADVANCED_VEHICLE_LISTS strhelp = STR_CONFIG_SETTING_ADVANCED_VEHICLE_LISTS_HELPTEXT strval = STR_CONFIG_SETTING_COMPANIES_OFF -[SDTC_BOOL] -var = gui.timetable_in_ticks -flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC -def = false -str = STR_CONFIG_SETTING_TIMETABLE_IN_TICKS -strhelp = STR_CONFIG_SETTING_TIMETABLE_IN_TICKS_HELPTEXT +[SDTC_VAR] +var = gui.timetable_mode +type = SLE_UINT8 +flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC | SF_GUI_DROPDOWN +def = 0 +min = 0 +max = 2 +str = STR_CONFIG_SETTING_TIMETABLE_MODE +strhelp = STR_CONFIG_SETTING_TIMETABLE_MODE_HELPTEXT +strval = STR_CONFIG_SETTING_TIMETABLE_MODE_DAYS post_cb = [](auto) { InvalidateWindowClassesData(WC_VEHICLE_TIMETABLE, VIWD_MODIFY_ORDERS); } -cat = SC_EXPERT +cat = SC_ADVANCED [SDTC_BOOL] var = gui.timetable_arrival_departure diff --git a/src/timetable.h b/src/timetable.h index 8aacf98483..5f8bb2d6ec 100644 --- a/src/timetable.h +++ b/src/timetable.h @@ -16,6 +16,12 @@ static const TimerGameCalendar::Year MAX_TIMETABLE_START_YEARS = 15; ///< The maximum start date offset, in years. +enum class TimetableMode : uint8_t { + Days, + Seconds, + Ticks, +}; + TimerGameTick::TickCounter GetStartTickFromDate(TimerGameCalendar::Date start_date); TimerGameCalendar::Date GetDateFromStartTick(TimerGameTick::TickCounter start_tick); diff --git a/src/timetable_cmd.cpp b/src/timetable_cmd.cpp index 0d7cd58a5f..9c923bdeff 100644 --- a/src/timetable_cmd.cpp +++ b/src/timetable_cmd.cpp @@ -505,16 +505,15 @@ void UpdateVehicleTimetable(Vehicle *v, bool travelling) /* Before modifying waiting times, check whether we want to preserve bigger ones. */ if (!real_current_order->IsType(OT_CONDITIONAL) && (travelling || time_taken > real_current_order->GetWaitTime() || remeasure_wait_time)) { - /* Round the time taken up to the nearest day, as this will avoid - * confusion for people who are timetabling in days, and can be - * adjusted later by people who aren't. + /* Round up to the smallest unit of time commonly shown in the GUI (seconds) to avoid confusion. + * Players timetabling in Ticks can adjust later. * For trains/aircraft multiple movement cycles are done in one * tick. This makes it possible to leave the station and process * e.g. a depot order in the same tick, causing it to not fill * the timetable entry like is done for road vehicles/ships. * Thus always make sure at least one tick is used between the * processing of different orders when filling the timetable. */ - uint time_to_set = CeilDiv(std::max(time_taken, 1), Ticks::DAY_TICKS) * Ticks::DAY_TICKS; + uint time_to_set = CeilDiv(std::max(time_taken, 1), Ticks::TICKS_PER_SECOND) * Ticks::TICKS_PER_SECOND; if (travelling && (autofilling || !real_current_order->IsTravelTimetabled())) { ChangeTimetable(v, v->cur_real_order_index, time_to_set, MTF_TRAVEL_TIME, autofilling); diff --git a/src/timetable_gui.cpp b/src/timetable_gui.cpp index c2ac3ca415..be8941187e 100644 --- a/src/timetable_gui.cpp +++ b/src/timetable_gui.cpp @@ -18,8 +18,10 @@ #include "string_func.h" #include "gfx_func.h" #include "company_func.h" +#include "timer/timer.h" #include "timer/timer_game_tick.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_window.h" #include "date_gui.h" #include "vehicle_gui.h" #include "settings_type.h" @@ -47,12 +49,59 @@ struct TimetableArrivalDeparture { */ void SetTimetableParams(int param1, int param2, TimerGameTick::Ticks ticks) { - if (_settings_client.gui.timetable_in_ticks) { - SetDParam(param1, STR_TIMETABLE_TICKS); - SetDParam(param2, ticks); - } else { - SetDParam(param1, STR_TIMETABLE_DAYS); - SetDParam(param2, ticks / Ticks::DAY_TICKS); + switch (_settings_client.gui.timetable_mode) { + case TimetableMode::Days: + SetDParam(param1, STR_UNITS_DAYS); + SetDParam(param2, ticks / Ticks::DAY_TICKS); + break; + case TimetableMode::Seconds: + SetDParam(param1, STR_UNITS_SECONDS); + SetDParam(param2, ticks / Ticks::TICKS_PER_SECOND); + break; + case TimetableMode::Ticks: + SetDParam(param1, STR_UNITS_TICKS); + SetDParam(param2, ticks); + break; + default: + NOT_REACHED(); + } +} + +/** + * Get the number of ticks in the current timetable display unit. + * @return The number of ticks per day, second, or tick, to match the timetable display. + */ +static inline TimerGameTick::Ticks TicksPerTimetableUnit() +{ + switch (_settings_client.gui.timetable_mode) { + case TimetableMode::Days: + return Ticks::DAY_TICKS; + case TimetableMode::Seconds: + return Ticks::TICKS_PER_SECOND; + case TimetableMode::Ticks: + return 1; + default: + NOT_REACHED(); + } +} + +/** + * Determine if a vehicle should be shown as late, depending on the timetable display setting. + * @param v The vehicle in question. + * @param round_to_day When using ticks, if we should round up to the nearest day. + * @return True if the vehicle is later than the threshold. + */ +bool VehicleIsAboveLatenessThreshold(const Vehicle *v, bool round_to_day) +{ + switch (_settings_client.gui.timetable_mode) { + case TimetableMode::Days: + return v->lateness_counter > Ticks::DAY_TICKS; + case TimetableMode::Seconds: + return v->lateness_counter > Ticks::TICKS_PER_SECOND; + case TimetableMode::Ticks: + return v->lateness_counter > (round_to_day ? Ticks::DAY_TICKS : 0); + default: + NOT_REACHED(); } } @@ -183,7 +232,10 @@ struct TimetableWindow : Window { assert(HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)); bool travelling = (!v->current_order.IsType(OT_LOADING) || v->current_order.GetNonStopType() == ONSF_STOP_EVERYWHERE); - TimerGameTick::Ticks start_time = TimerGameCalendar::date_fract - v->current_order_time; + TimerGameTick::Ticks start_time = -v->current_order_time; + + /* If arrival and departure times are in days, compensate for the current date_fract. */ + if (_settings_client.gui.timetable_mode != TimetableMode::Seconds) start_time += TimerGameCalendar::date_fract; FillTimetableArrivalDepartureTable(v, v->cur_real_order_index % v->GetNumOrders(), travelling, table, start_time); @@ -194,8 +246,15 @@ struct TimetableWindow : Window { { switch (widget) { case WID_VT_ARRIVAL_DEPARTURE_PANEL: - SetDParamMaxValue(1, TimerGameCalendar::DateAtStartOfYear(CalendarTime::MAX_YEAR), 0, FS_SMALL); - size->width = std::max(GetStringBoundingBox(STR_TIMETABLE_ARRIVAL).width, GetStringBoundingBox(STR_TIMETABLE_DEPARTURE).width) + WidgetDimensions::scaled.hsep_wide + padding.width; + /* We handle this differently depending on the timetable mode. */ + if (_settings_client.gui.timetable_mode == TimetableMode::Seconds) { + /* A five-digit number would fit a timetable lasting 2.7 real-world hours, which should be plenty. */ + SetDParamMaxDigits(1, 4, FS_SMALL); + size->width = std::max(GetStringBoundingBox(STR_TIMETABLE_ARRIVAL_SECONDS_IN_FUTURE).width, GetStringBoundingBox(STR_TIMETABLE_DEPARTURE_SECONDS_IN_FUTURE).width) + WidgetDimensions::scaled.hsep_wide + padding.width; + } else { + SetDParamMaxValue(1, TimerGameCalendar::DateAtStartOfYear(CalendarTime::MAX_YEAR), 0, FS_SMALL); + size->width = std::max(GetStringBoundingBox(STR_TIMETABLE_ARRIVAL_DATE).width, GetStringBoundingBox(STR_TIMETABLE_DEPARTURE_DATE).width) + WidgetDimensions::scaled.hsep_wide + padding.width; + } FALLTHROUGH; case WID_VT_ARRIVAL_DEPARTURE_SELECTION: @@ -436,7 +495,7 @@ struct TimetableWindow : Window { int selected = this->sel_index; Rect tr = r.Shrink(WidgetDimensions::scaled.framerect); - bool show_late = this->show_expected && v->lateness_counter > Ticks::DAY_TICKS; + bool show_late = this->show_expected && VehicleIsAboveLatenessThreshold(v, true); TimerGameTick::Ticks offset = show_late ? 0 : -v->lateness_counter; for (int i = this->vscroll->GetPosition(); i / 2 < v->GetNumOrders(); ++i) { // note: i is also incremented in the loop @@ -460,14 +519,28 @@ struct TimetableWindow : Window { } /* Now actually draw the arrival time. */ - SetDParam(1, TimerGameCalendar::date + (arr_dep[i / 2].arrival + this_offset) / Ticks::DAY_TICKS); - DrawString(tr.left, tr.right, tr.top, STR_TIMETABLE_ARRIVAL, i == selected ? TC_WHITE : TC_BLACK); + if (_settings_client.gui.timetable_mode == TimetableMode::Seconds) { + /* Display seconds from now. */ + SetDParam(1, ((arr_dep[i / 2].arrival + offset) / Ticks::TICKS_PER_SECOND)); + DrawString(tr.left, tr.right, tr.top, STR_TIMETABLE_ARRIVAL_SECONDS_IN_FUTURE, i == selected ? TC_WHITE : TC_BLACK); + } else { + /* Show a date. */ + SetDParam(1, TimerGameCalendar::date + (arr_dep[i / 2].arrival + this_offset) / Ticks::DAY_TICKS); + DrawString(tr.left, tr.right, tr.top, STR_TIMETABLE_ARRIVAL_DATE, i == selected ? TC_WHITE : TC_BLACK); + } } } else { /* Draw a departure time. */ if (arr_dep[i / 2].departure != Ticks::INVALID_TICKS) { - SetDParam(1, TimerGameCalendar::date + (arr_dep[i / 2].departure + offset) / Ticks::DAY_TICKS); - DrawString(tr.left, tr.right, tr.top, STR_TIMETABLE_DEPARTURE, i == selected ? TC_WHITE : TC_BLACK); + if (_settings_client.gui.timetable_mode == TimetableMode::Seconds) { + /* Display seconds from now. */ + SetDParam(1, ((arr_dep[i / 2].departure + offset) / Ticks::TICKS_PER_SECOND)); + DrawString(tr.left, tr.right, tr.top, STR_TIMETABLE_DEPARTURE_SECONDS_IN_FUTURE, i == selected ? TC_WHITE : TC_BLACK); + } else { + /* Show a date. */ + SetDParam(1, TimerGameCalendar::date + (arr_dep[i / 2].departure + offset) / Ticks::DAY_TICKS); + DrawString(tr.left, tr.right, tr.top, STR_TIMETABLE_DEPARTURE_DATE, i == selected ? TC_WHITE : TC_BLACK); + } } } tr.top += GetCharacterHeight(FS_NORMAL); @@ -490,19 +563,28 @@ struct TimetableWindow : Window { } tr.top += GetCharacterHeight(FS_NORMAL); + /* Draw the lateness display, or indicate that the timetable has not started yet. */ if (v->timetable_start != 0) { /* We are running towards the first station so we can start the * timetable at the given time. */ - SetDParam(0, STR_JUST_DATE_TINY); - SetDParam(1, GetDateFromStartTick(v->timetable_start)); - DrawString(tr, STR_TIMETABLE_STATUS_START_AT); + if (_settings_client.gui.timetable_mode == TimetableMode::Seconds) { + /* Real time units use seconds relative to now. */ + SetDParam(0, (static_cast(v->timetable_start - TimerGameTick::counter) / Ticks::TICKS_PER_SECOND)); + DrawString(tr, STR_TIMETABLE_STATUS_START_IN_SECONDS); + } else { + /* Calendar units use dates. */ + SetDParam(0, STR_JUST_DATE_TINY); + SetDParam(1, GetDateFromStartTick(v->timetable_start)); + DrawString(tr, STR_TIMETABLE_STATUS_START_AT_DATE); + } } else if (!HasBit(v->vehicle_flags, VF_TIMETABLE_STARTED)) { - /* We aren't running on a timetable yet, so how can we be "on time" - * when we aren't even "on service"/"on duty"? */ + /* We aren't running on a timetable yet. */ DrawString(tr, STR_TIMETABLE_STATUS_NOT_STARTED); - } else if (v->lateness_counter == 0 || (!_settings_client.gui.timetable_in_ticks && v->lateness_counter / Ticks::DAY_TICKS == 0)) { + } else if (!VehicleIsAboveLatenessThreshold(v, false)) { + /* We are on time. */ DrawString(tr, STR_TIMETABLE_STATUS_ON_TIME); } else { + /* We are late. */ SetTimetableParams(0, 1, abs(v->lateness_counter)); DrawString(tr, v->lateness_counter < 0 ? STR_TIMETABLE_STATUS_EARLY : STR_TIMETABLE_STATUS_LATE); } @@ -556,7 +638,13 @@ struct TimetableWindow : Window { } case WID_VT_START_DATE: // Change the date that the timetable starts. - ShowSetDateWindow(this, v->index, TimerGameCalendar::date, TimerGameCalendar::year, TimerGameCalendar::year + MAX_TIMETABLE_START_YEARS, ChangeTimetableStartCallback, reinterpret_cast(static_cast(_ctrl_pressed))); + if (_settings_client.gui.timetable_mode == TimetableMode::Seconds) { + this->query_widget = WID_VT_START_DATE; + this->change_timetable_all = _ctrl_pressed; + ShowQueryString(STR_EMPTY, STR_TIMETABLE_START_SECONDS_QUERY, 6, this, CS_NUMERAL, QSF_ACCEPT_UNCHANGED); + } else { + ShowSetDateWindow(this, v->index, TimerGameCalendar::date, TimerGameCalendar::year, TimerGameCalendar::year + MAX_TIMETABLE_START_YEARS, ChangeTimetableStartCallback, reinterpret_cast(static_cast(_ctrl_pressed))); + } break; case WID_VT_CHANGE_TIME: { // "Wait For" button. @@ -571,7 +659,7 @@ struct TimetableWindow : Window { if (order != nullptr) { uint time = (selected % 2 != 0) ? order->GetTravelTime() : order->GetWaitTime(); - if (!_settings_client.gui.timetable_in_ticks) time /= Ticks::DAY_TICKS; + time /= TicksPerTimetableUnit(); if (time != 0) { SetDParam(0, time); @@ -667,7 +755,7 @@ struct TimetableWindow : Window { } case WID_VT_CHANGE_TIME: - if (!_settings_client.gui.timetable_in_ticks) val *= Ticks::DAY_TICKS; + val *= TicksPerTimetableUnit(); if (this->change_timetable_all) { Command::Post(STR_ERROR_CAN_T_TIMETABLE_VEHICLE, v->index, mtf, ClampTo(val)); @@ -676,6 +764,12 @@ struct TimetableWindow : Window { } break; + case WID_VT_START_DATE: { + TimerGameTick::TickCounter start_tick = TimerGameTick::counter + (val * Ticks::TICKS_PER_SECOND); + Command::Post(STR_ERROR_CAN_T_TIMETABLE_VEHICLE, v->index, this->change_timetable_all, start_tick); + break; + } + default: NOT_REACHED(); } @@ -695,6 +789,15 @@ struct TimetableWindow : Window { this->GetWidget(WID_VT_ARRIVAL_DEPARTURE_SELECTION)->SetDisplayedPlane(_settings_client.gui.timetable_arrival_departure ? 0 : SZSP_NONE); this->GetWidget(WID_VT_EXPECTED_SELECTION)->SetDisplayedPlane(_settings_client.gui.timetable_arrival_departure ? 0 : 1); } + + /** + * In real-time mode, the timetable GUI shows relative times and needs to be redrawn every second. + */ + IntervalTimer redraw_interval = {Ticks::TICKS_PER_SECOND, [this](auto) { + if (_settings_client.gui.timetable_mode == TimetableMode::Seconds) { + this->SetDirty(); + } + }}; }; static const NWidgetPart _nested_timetable_widgets[] = { @@ -725,7 +828,7 @@ static const NWidgetPart _nested_timetable_widgets[] = { NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_CLEAR_SPEED), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_CLEAR_SPEED, STR_TIMETABLE_CLEAR_SPEED_TOOLTIP), EndContainer(), NWidget(NWID_VERTICAL, NC_EQUALSIZE), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_START_DATE), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_STARTING_DATE, STR_TIMETABLE_STARTING_DATE_TOOLTIP), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_START_DATE), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_START, STR_TIMETABLE_START_TOOLTIP), NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_VT_RESET_LATENESS), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_TIMETABLE_RESET_LATENESS, STR_TIMETABLE_RESET_LATENESS_TOOLTIP), EndContainer(), NWidget(NWID_VERTICAL, NC_EQUALSIZE),