From 21581b6ab3aee872cca10ff85676e172c3283f5e Mon Sep 17 00:00:00 2001 From: Tyler Trahan Date: Tue, 23 Jan 2024 18:33:54 -0500 Subject: [PATCH] Feature: Setting for minutes per calendar year (#11428) --- src/lang/english.txt | 7 +++++ src/openttd.cpp | 5 ++-- src/saveload/misc_sl.cpp | 1 + src/saveload/saveload.h | 1 + src/settings_gui.cpp | 1 + src/settings_table.cpp | 40 +++++++++++++++++++++++++ src/settings_type.h | 1 + src/table/settings/economy_settings.ini | 16 ++++++++++ src/timer/timer_game_calendar.cpp | 33 +++++++++++++++----- src/timer/timer_game_calendar.h | 8 ++++- src/timer/timer_game_economy.cpp | 8 +++-- src/timer/timer_game_realtime.cpp | 4 ++- src/timer/timer_game_tick.cpp | 4 ++- src/timer/timer_manager.h | 3 +- src/timer/timer_window.cpp | 4 ++- 15 files changed, 118 insertions(+), 18 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index 812a0c94af..b4abe4a19c 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1487,6 +1487,13 @@ STR_CONFIG_SETTING_TIMEKEEPING_UNITS_HELPTEXT :Select the time STR_CONFIG_SETTING_TIMEKEEPING_UNITS_CALENDAR :Calendar STR_CONFIG_SETTING_TIMEKEEPING_UNITS_WALLCLOCK :Wallclock +STR_CONFIG_SETTING_MINUTES_PER_YEAR :Minutes per year: {STRING2} +STR_CONFIG_SETTING_MINUTES_PER_YEAR_HELPTEXT :Choose the number of minutes in a calendar year. The default is 12 minutes. Set to 0 to stop calendar time from changing. This setting does not affect the economic simulation of the game, and is only available when using wallclock timekeeping. + +STR_CONFIG_SETTING_MINUTES_PER_YEAR_VALUE :{NUM} +###setting-zero-is-special +STR_CONFIG_SETTING_MINUTES_PER_YEAR_FROZEN :0 (calendar time frozen) + STR_CONFIG_SETTING_AUTORENEW_VEHICLE :Autorenew vehicle when it gets old: {STRING2} STR_CONFIG_SETTING_AUTORENEW_VEHICLE_HELPTEXT :When enabled, a vehicle nearing its end of life gets automatically replaced when the renew conditions are fulfilled diff --git a/src/openttd.cpp b/src/openttd.cpp index 9a6db6259d..200b9d3d87 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -1472,11 +1472,12 @@ void StateGameLoop() BasePersistentStorageArray::SwitchMode(PSM_ENTER_GAMELOOP); AnimateAnimatedTiles(); - TimerManager::Elapsed(1); + if (TimerManager::Elapsed(1)) { + RunVehicleCalendarDayProc(); + } TimerManager::Elapsed(1); TimerManager::Elapsed(1); RunTileLoop(); - RunVehicleCalendarDayProc(); CallVehicleTicks(); CallLandscapeTick(); BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP); diff --git a/src/saveload/misc_sl.cpp b/src/saveload/misc_sl.cpp index a28021f520..7ec076310a 100644 --- a/src/saveload/misc_sl.cpp +++ b/src/saveload/misc_sl.cpp @@ -88,6 +88,7 @@ static const SaveLoad _date_desc[] = { SLEG_CONDVAR("tick_counter", TimerGameTick::counter, SLE_UINT64, SLV_U64_TICK_COUNTER, SL_MAX_VERSION), SLEG_CONDVAR("economy_date", TimerGameEconomy::date, SLE_INT32, SLV_ECONOMY_DATE, SL_MAX_VERSION), SLEG_CONDVAR("economy_date_fract", TimerGameEconomy::date_fract, SLE_UINT16, SLV_ECONOMY_DATE, SL_MAX_VERSION), + SLEG_CONDVAR("calendar_sub_date_fract", TimerGameCalendar::sub_date_fract, SLE_UINT16, SLV_CALENDAR_SUB_DATE_FRACT, SL_MAX_VERSION), SLEG_CONDVAR("age_cargo_skip_counter", _age_cargo_skip_counter, SLE_UINT8, SL_MIN_VERSION, SLV_162), SLEG_CONDVAR("cur_tileloop_tile", _cur_tileloop_tile, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_6), SLEG_CONDVAR("cur_tileloop_tile", _cur_tileloop_tile, SLE_UINT32, SLV_6, SL_MAX_VERSION), diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 912aac6c97..4564b9d4f3 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -370,6 +370,7 @@ enum SaveLoadVersion : uint16_t { SLV_WATER_REGION_EVAL_SIMPLIFIED, ///< 325 PR#11750 Simplified Water Region evaluation. SLV_ECONOMY_DATE, ///< 326 PR#10700 Split calendar and economy timers and dates. SLV_ECONOMY_MODE_TIMEKEEPING_UNITS, ///< 327 PR#11341 Mode to display economy measurements in wallclock units. + SLV_CALENDAR_SUB_DATE_FRACT, ///< 328 PR#11428 Add sub_date_fract to measure calendar days. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 43b03c1266..b70d2b6b6e 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -2213,6 +2213,7 @@ static SettingsContainer &GetSettingsTree() SettingsPage *time = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_TIME)); { time->Add(new SettingEntry("economy.timekeeping_units")); + time->Add(new SettingEntry("economy.minutes_per_calendar_year")); time->Add(new SettingEntry("game_creation.ending_year")); time->Add(new SettingEntry("gui.pause_on_newgame")); time->Add(new SettingEntry("gui.fast_forward_speed_limit")); diff --git a/src/settings_table.cpp b/src/settings_table.cpp index 38573339db..8075477433 100644 --- a/src/settings_table.cpp +++ b/src/settings_table.cpp @@ -22,6 +22,7 @@ #include "news_func.h" #include "window_func.h" #include "company_func.h" +#include "timer/timer_game_calendar.h" #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA) #define HAS_TRUETYPE_FONT #include "fontcache.h" @@ -500,9 +501,48 @@ static void ChangeTimekeepingUnits(int32_t) UpdateAllServiceInterval(0); } + /* If we are using calendar timekeeping, "minutes per year" must be default. */ + if (!TimerGameEconomy::UsingWallclockUnits(true)) { + _settings_newgame.economy.minutes_per_calendar_year = CalendarTime::DEF_MINUTES_PER_YEAR; + } + InvalidateWindowClassesData(WC_GAME_OPTIONS, 0); } +/** + * Callback after the player changes the minutes per year. + * @param new_value The intended new value of the setting, used for clamping. + */ +static void ChangeMinutesPerYear(int32_t new_value) +{ + /* We don't allow setting Minutes Per Year below default, unless it's to 0 for frozen calendar time. */ + if (new_value < CalendarTime::DEF_MINUTES_PER_YEAR) { + int clamped; + + /* If the new value is 1, we're probably at 0 and trying to increase the value, so we should jump up to default. */ + if (new_value == 1) { + clamped = CalendarTime::DEF_MINUTES_PER_YEAR; + } else { + clamped = CalendarTime::FROZEN_MINUTES_PER_YEAR; + } + + /* Override the setting with the clamped value. */ + if (_game_mode == GM_MENU) { + _settings_newgame.economy.minutes_per_calendar_year = clamped; + } else { + _settings_game.economy.minutes_per_calendar_year = clamped; + } + } + + /* If the setting value is not the default, force the game to use wallclock timekeeping units. + * This can only happen in the menu, since the pre_cb ensures this setting can only be changed there, or if we're already using wallclock units. + */ + if (_game_mode == GM_MENU && (_settings_newgame.economy.minutes_per_calendar_year != CalendarTime::DEF_MINUTES_PER_YEAR)) { + _settings_newgame.economy.timekeeping_units = TKU_WALLCLOCK; + InvalidateWindowClassesData(WC_GAME_OPTIONS, 0); + } +} + /** * Pre-callback check when trying to change the timetable mode. This is locked to Seconds when using wallclock units. * @param Unused. diff --git a/src/settings_type.h b/src/settings_type.h index 82efe5fd39..06ced9330f 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -558,6 +558,7 @@ struct EconomySettings { bool allow_town_level_crossings; ///< towns are allowed to build level crossings bool infrastructure_maintenance; ///< enable monthly maintenance fee for owner infrastructure TimekeepingUnits timekeeping_units; ///< time units to use for the game economy, either calendar or wallclock + uint16_t minutes_per_calendar_year; ///< minutes per calendar year. Special value 0 means that calendar time is frozen. }; struct LinkGraphSettings { diff --git a/src/table/settings/economy_settings.ini b/src/table/settings/economy_settings.ini index f824c50fe0..30545d539e 100644 --- a/src/table/settings/economy_settings.ini +++ b/src/table/settings/economy_settings.ini @@ -10,6 +10,7 @@ [pre-amble] static void TownFoundingChanged(int32_t new_value); static void ChangeTimekeepingUnits(int32_t new_value); +static void ChangeMinutesPerYear(int32_t new_value); static const SettingVariant _economy_settings_table[] = { [post-amble] @@ -294,3 +295,18 @@ strval = STR_CONFIG_SETTING_TIMEKEEPING_UNITS_CALENDAR strhelp = STR_CONFIG_SETTING_TIMEKEEPING_UNITS_HELPTEXT post_cb = ChangeTimekeepingUnits cat = SC_BASIC + +[SDT_VAR] +var = economy.minutes_per_calendar_year +type = SLE_UINT16 +flags = SF_GUI_0_IS_SPECIAL | SF_NO_NETWORK +def = CalendarTime::DEF_MINUTES_PER_YEAR +min = CalendarTime::FROZEN_MINUTES_PER_YEAR +max = CalendarTime::MAX_MINUTES_PER_YEAR +interval = 1 +str = STR_CONFIG_SETTING_MINUTES_PER_YEAR +strhelp = STR_CONFIG_SETTING_MINUTES_PER_YEAR_HELPTEXT +strval = STR_CONFIG_SETTING_MINUTES_PER_YEAR_VALUE +pre_cb = [](auto) { return _game_mode == GM_MENU || _settings_game.economy.timekeeping_units == 1; } +post_cb = ChangeMinutesPerYear +cat = SC_BASIC diff --git a/src/timer/timer_game_calendar.cpp b/src/timer/timer_game_calendar.cpp index 1dcc1c79ee..3b7bc870a8 100644 --- a/src/timer/timer_game_calendar.cpp +++ b/src/timer/timer_game_calendar.cpp @@ -32,6 +32,7 @@ TimerGameCalendar::Year TimerGameCalendar::year = {}; TimerGameCalendar::Month TimerGameCalendar::month = {}; TimerGameCalendar::Date TimerGameCalendar::date = {}; TimerGameCalendar::DateFract TimerGameCalendar::date_fract = {}; +uint16_t TimerGameCalendar::sub_date_fract = {}; /** * Converts a Date to a Year, Month & Day. @@ -93,28 +94,42 @@ void TimeoutTimer::Elapsed(TimerGameCalendar::TElapsed trigge } template<> -void TimerManager::Elapsed([[maybe_unused]] TimerGameCalendar::TElapsed delta) +bool TimerManager::Elapsed([[maybe_unused]] TimerGameCalendar::TElapsed delta) { assert(delta == 1); - if (_game_mode == GM_MENU) return; + if (_game_mode == GM_MENU) return false; + + /* If calendar day progress is frozen, don't try to advance time. */ + if (_settings_game.economy.minutes_per_calendar_year == CalendarTime::FROZEN_MINUTES_PER_YEAR) return false; + + /* If we are using a non-default calendar progression speed, we need to check the sub_date_fract before updating date_fract. */ + if (_settings_game.economy.minutes_per_calendar_year != CalendarTime::DEF_MINUTES_PER_YEAR) { + TimerGameCalendar::sub_date_fract++; + + /* Check if we are ready to increment date_fract */ + if (TimerGameCalendar::sub_date_fract < (Ticks::DAY_TICKS * _settings_game.economy.minutes_per_calendar_year) / CalendarTime::DEF_MINUTES_PER_YEAR) return false; + } TimerGameCalendar::date_fract++; - if (TimerGameCalendar::date_fract < Ticks::DAY_TICKS) return; + + /* Check if we entered a new day. */ + if (TimerGameCalendar::date_fract < Ticks::DAY_TICKS) return true; TimerGameCalendar::date_fract = 0; + TimerGameCalendar::sub_date_fract = 0; - /* increase day counter */ + /* Increase day counter. */ TimerGameCalendar::date++; TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(TimerGameCalendar::date); - /* check if we entered a new month? */ + /* Check if we entered a new month. */ bool new_month = ymd.month != TimerGameCalendar::month; - /* check if we entered a new year? */ + /* Check if we entered a new year. */ bool new_year = ymd.year != TimerGameCalendar::year; - /* update internal variables before calling the daily/monthly/yearly loops */ + /* Update internal variables before calling the daily/monthly/yearly loops. */ TimerGameCalendar::month = ymd.month; TimerGameCalendar::year = ymd.year; @@ -137,7 +152,7 @@ void TimerManager::Elapsed([[maybe_unused]] TimerGameCalendar } } - /* check if we reached the maximum year, decrement dates by a year */ + /* If we reached the maximum year, decrement dates by a year. */ if (TimerGameCalendar::year == CalendarTime::MAX_YEAR + 1) { int days_this_year; @@ -145,6 +160,8 @@ void TimerManager::Elapsed([[maybe_unused]] TimerGameCalendar days_this_year = TimerGameCalendar::IsLeapYear(TimerGameCalendar::year) ? CalendarTime::DAYS_IN_LEAP_YEAR : CalendarTime::DAYS_IN_YEAR; TimerGameCalendar::date -= days_this_year; } + + return true; } #ifdef WITH_ASSERT diff --git a/src/timer/timer_game_calendar.h b/src/timer/timer_game_calendar.h index 7994b57fa7..602d96e429 100644 --- a/src/timer/timer_game_calendar.h +++ b/src/timer/timer_game_calendar.h @@ -33,6 +33,7 @@ public: static Month month; ///< Current month (0..11). static Date date; ///< Current date in days (day counter). static DateFract date_fract; ///< Fractional part of the day. + static uint16_t sub_date_fract; ///< Subpart of date_fract that we use when calendar days are slower than economy days. static YearMonthDay ConvertDateToYMD(Date date); static Date ConvertYMDToDate(Year year, Month month, Day day); @@ -42,6 +43,11 @@ public: /** * Storage class for Calendar time constants. */ -class CalendarTime : public TimerGameConst {}; +class CalendarTime : public TimerGameConst { +public: + static constexpr int DEF_MINUTES_PER_YEAR = 12; + static constexpr int FROZEN_MINUTES_PER_YEAR = 0; + static constexpr int MAX_MINUTES_PER_YEAR = 10080; // One week of real time. The actual max that doesn't overflow TimerGameCalendar::sub_date_fract is 10627, but this is neater. +}; #endif /* TIMER_GAME_CALENDAR_H */ diff --git a/src/timer/timer_game_economy.cpp b/src/timer/timer_game_economy.cpp index 474fcfd461..62f6806213 100644 --- a/src/timer/timer_game_economy.cpp +++ b/src/timer/timer_game_economy.cpp @@ -121,14 +121,14 @@ void TimeoutTimer::Elapsed(TimerGameEconomy::TElapsed trigger) } template<> -void TimerManager::Elapsed([[maybe_unused]] TimerGameEconomy::TElapsed delta) +bool TimerManager::Elapsed([[maybe_unused]] TimerGameEconomy::TElapsed delta) { assert(delta == 1); - if (_game_mode == GM_MENU) return; + if (_game_mode == GM_MENU) return false; TimerGameEconomy::date_fract++; - if (TimerGameEconomy::date_fract < Ticks::DAY_TICKS) return; + if (TimerGameEconomy::date_fract < Ticks::DAY_TICKS) return true; TimerGameEconomy::date_fract = 0; /* increase day counter */ @@ -187,6 +187,8 @@ void TimerManager::Elapsed([[maybe_unused]] TimerGameEconomy:: for (Vehicle *v : Vehicle::Iterate()) v->ShiftDates(-days_this_year); for (LinkGraph *lg : LinkGraph::Iterate()) lg->ShiftDates(-days_this_year); } + + return true; } #ifdef WITH_ASSERT diff --git a/src/timer/timer_game_realtime.cpp b/src/timer/timer_game_realtime.cpp index 62b10f9b05..600e24f317 100644 --- a/src/timer/timer_game_realtime.cpp +++ b/src/timer/timer_game_realtime.cpp @@ -54,11 +54,13 @@ void TimeoutTimer::Elapsed(TimerGameRealtime::TElapsed delta) } template<> -void TimerManager::Elapsed(TimerGameRealtime::TElapsed delta) +bool TimerManager::Elapsed(TimerGameRealtime::TElapsed delta) { for (auto timer : TimerManager::GetTimers()) { timer->Elapsed(delta); } + + return true; } #ifdef WITH_ASSERT diff --git a/src/timer/timer_game_tick.cpp b/src/timer/timer_game_tick.cpp index 225204b668..92399a4e63 100644 --- a/src/timer/timer_game_tick.cpp +++ b/src/timer/timer_game_tick.cpp @@ -51,13 +51,15 @@ void TimeoutTimer::Elapsed(TimerGameTick::TElapsed delta) } template<> -void TimerManager::Elapsed(TimerGameTick::TElapsed delta) +bool TimerManager::Elapsed(TimerGameTick::TElapsed delta) { TimerGameTick::counter++; for (auto timer : TimerManager::GetTimers()) { timer->Elapsed(delta); } + + return true; } #ifdef WITH_ASSERT diff --git a/src/timer/timer_manager.h b/src/timer/timer_manager.h index ca87ec4edc..c3b45a73bd 100644 --- a/src/timer/timer_manager.h +++ b/src/timer/timer_manager.h @@ -78,8 +78,9 @@ public: * Call the Elapsed() method of all active timers. * * @param value The amount of time that has elapsed. + * @return True iff time has progressed. */ - static void Elapsed(TElapsed value); + static bool Elapsed(TElapsed value); private: /** diff --git a/src/timer/timer_window.cpp b/src/timer/timer_window.cpp index bc6a10530d..906d5e0a9f 100644 --- a/src/timer/timer_window.cpp +++ b/src/timer/timer_window.cpp @@ -49,7 +49,7 @@ void TimeoutTimer::Elapsed(TimerWindow::TElapsed delta) } template<> -void TimerManager::Elapsed(TimerWindow::TElapsed delta) +bool TimerManager::Elapsed(TimerWindow::TElapsed delta) { /* Make a temporary copy of the timers, as a timer's callback might add/remove other timers. */ auto timers = TimerManager::GetTimers(); @@ -57,6 +57,8 @@ void TimerManager::Elapsed(TimerWindow::TElapsed delta) for (auto timer : timers) { timer->Elapsed(delta); } + + return true; } #ifdef WITH_ASSERT