Feature: Setting for minutes per calendar year (#11428)

This commit is contained in:
Tyler Trahan 2024-01-23 18:33:54 -05:00 committed by GitHub
parent be8ed26db6
commit 21581b6ab3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 119 additions and 19 deletions

View File

@ -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

View File

@ -1472,11 +1472,12 @@ void StateGameLoop()
BasePersistentStorageArray::SwitchMode(PSM_ENTER_GAMELOOP);
AnimateAnimatedTiles();
TimerManager<TimerGameCalendar>::Elapsed(1);
if (TimerManager<TimerGameCalendar>::Elapsed(1)) {
RunVehicleCalendarDayProc();
}
TimerManager<TimerGameEconomy>::Elapsed(1);
TimerManager<TimerGameTick>::Elapsed(1);
RunTileLoop();
RunVehicleCalendarDayProc();
CallVehicleTicks();
CallLandscapeTick();
BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP);

View File

@ -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),

View File

@ -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
};

View File

@ -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"));

View File

@ -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.

View File

@ -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 {

View File

@ -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

View File

@ -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<TimerGameCalendar>::Elapsed(TimerGameCalendar::TElapsed trigge
}
template<>
void TimerManager<TimerGameCalendar>::Elapsed([[maybe_unused]] TimerGameCalendar::TElapsed delta)
bool TimerManager<TimerGameCalendar>::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;
TimerGameCalendar::date_fract = 0;
/* increase day counter */
/* 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. */
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<TimerGameCalendar>::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<TimerGameCalendar>::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

View File

@ -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<struct Calendar> {};
class CalendarTime : public TimerGameConst<struct Calendar> {
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 */

View File

@ -121,14 +121,14 @@ void TimeoutTimer<TimerGameEconomy>::Elapsed(TimerGameEconomy::TElapsed trigger)
}
template<>
void TimerManager<TimerGameEconomy>::Elapsed([[maybe_unused]] TimerGameEconomy::TElapsed delta)
bool TimerManager<TimerGameEconomy>::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<TimerGameEconomy>::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

View File

@ -54,11 +54,13 @@ void TimeoutTimer<TimerGameRealtime>::Elapsed(TimerGameRealtime::TElapsed delta)
}
template<>
void TimerManager<TimerGameRealtime>::Elapsed(TimerGameRealtime::TElapsed delta)
bool TimerManager<TimerGameRealtime>::Elapsed(TimerGameRealtime::TElapsed delta)
{
for (auto timer : TimerManager<TimerGameRealtime>::GetTimers()) {
timer->Elapsed(delta);
}
return true;
}
#ifdef WITH_ASSERT

View File

@ -51,13 +51,15 @@ void TimeoutTimer<TimerGameTick>::Elapsed(TimerGameTick::TElapsed delta)
}
template<>
void TimerManager<TimerGameTick>::Elapsed(TimerGameTick::TElapsed delta)
bool TimerManager<TimerGameTick>::Elapsed(TimerGameTick::TElapsed delta)
{
TimerGameTick::counter++;
for (auto timer : TimerManager<TimerGameTick>::GetTimers()) {
timer->Elapsed(delta);
}
return true;
}
#ifdef WITH_ASSERT

View File

@ -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:
/**

View File

@ -49,7 +49,7 @@ void TimeoutTimer<TimerWindow>::Elapsed(TimerWindow::TElapsed delta)
}
template<>
void TimerManager<TimerWindow>::Elapsed(TimerWindow::TElapsed delta)
bool TimerManager<TimerWindow>::Elapsed(TimerWindow::TElapsed delta)
{
/* Make a temporary copy of the timers, as a timer's callback might add/remove other timers. */
auto timers = TimerManager<TimerWindow>::GetTimers();
@ -57,6 +57,8 @@ void TimerManager<TimerWindow>::Elapsed(TimerWindow::TElapsed delta)
for (auto timer : timers) {
timer->Elapsed(delta);
}
return true;
}
#ifdef WITH_ASSERT