2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
9 * @file timer_game_calendar.cpp
10 * This file implements the timer logic for the game-calendar-timer.
14 * Calendar time is used for technology and time-of-year changes, including:
15 * - Vehicle, airport, station, object introduction and obsolescence
16 * - Vehicle and engine age
17 * - NewGRF variables for visual styles or behavior based on year or time of year (e.g. variable snow line)
18 * - Inflation, since it is tied to original game years. One interpretation of inflation is that it compensates for faster and higher capacity vehicles,
19 * another is that it compensates for more established companies. Each of these point to a different choice of calendar versus economy time, but we have to pick one
20 * so we follow a previous decision to tie inflation to original TTD game years.
23 #include "../stdafx.h"
24 #include "../openttd.h"
26 #include "timer_game_calendar.h"
27 #include "../vehicle_base.h"
29 #include "../safeguards.h"
31 TimerGameCalendar::Year
TimerGameCalendar::year
= {};
32 TimerGameCalendar::Month
TimerGameCalendar::month
= {};
33 TimerGameCalendar::Date
TimerGameCalendar::date
= {};
34 TimerGameCalendar::DateFract
TimerGameCalendar::date_fract
= {};
35 uint16_t TimerGameCalendar::sub_date_fract
= {};
38 * Converts a Date to a Year, Month & Day.
39 * @param date the date to convert from
40 * @returns YearMonthDay representation of the Date.
42 /* static */ TimerGameCalendar::YearMonthDay
TimerGameCalendar::ConvertDateToYMD(TimerGameCalendar::Date date
)
44 /* This wrapper function only exists because economy time sometimes does things differently, when using wallclock units. */
45 return CalendarConvertDateToYMD(date
);
49 * Converts a tuple of Year, Month and Day to a Date.
50 * @param year is a number between 0..MAX_YEAR
51 * @param month is a number between 0..11
52 * @param day is a number between 1..31
53 * @returns The equivalent date.
55 /* static */ TimerGameCalendar::Date
TimerGameCalendar::ConvertYMDToDate(TimerGameCalendar::Year year
, TimerGameCalendar::Month month
, TimerGameCalendar::Day day
)
57 /* This wrapper function only exists because economy time sometimes does things differently, when using wallclock units. */
58 return CalendarConvertYMDToDate(year
, month
, day
);
63 * @param date New date
64 * @param fract The number of ticks that have passed on this date.
66 /* static */ void TimerGameCalendar::SetDate(TimerGameCalendar::Date date
, TimerGameCalendar::DateFract fract
)
68 assert(fract
< Ticks::DAY_TICKS
);
70 TimerGameCalendar::date
= date
;
71 TimerGameCalendar::date_fract
= fract
;
72 TimerGameCalendar::YearMonthDay ymd
= TimerGameCalendar::ConvertDateToYMD(date
);
73 TimerGameCalendar::year
= ymd
.year
;
74 TimerGameCalendar::month
= ymd
.month
;
78 void IntervalTimer
<TimerGameCalendar
>::Elapsed(TimerGameCalendar::TElapsed trigger
)
80 if (trigger
== this->period
.trigger
) {
86 void TimeoutTimer
<TimerGameCalendar
>::Elapsed(TimerGameCalendar::TElapsed trigger
)
88 if (this->fired
) return;
90 if (trigger
== this->period
.trigger
) {
97 bool TimerManager
<TimerGameCalendar
>::Elapsed([[maybe_unused
]] TimerGameCalendar::TElapsed delta
)
101 if (_game_mode
== GM_MENU
) return false;
103 /* If calendar day progress is frozen, don't try to advance time. */
104 if (_settings_game
.economy
.minutes_per_calendar_year
== CalendarTime::FROZEN_MINUTES_PER_YEAR
) return false;
106 /* If we are using a non-default calendar progression speed, we need to check the sub_date_fract before updating date_fract. */
107 if (_settings_game
.economy
.minutes_per_calendar_year
!= CalendarTime::DEF_MINUTES_PER_YEAR
) {
108 TimerGameCalendar::sub_date_fract
+= Ticks::DAY_TICKS
;
110 /* Check if we are ready to increment date_fract */
111 const uint16_t threshold
= (_settings_game
.economy
.minutes_per_calendar_year
* Ticks::DAY_TICKS
) / CalendarTime::DEF_MINUTES_PER_YEAR
;
112 if (TimerGameCalendar::sub_date_fract
< threshold
) return false;
114 TimerGameCalendar::sub_date_fract
= std::min
<uint16_t>(TimerGameCalendar::sub_date_fract
- threshold
, Ticks::DAY_TICKS
- 1);
117 TimerGameCalendar::date_fract
++;
119 /* Check if we entered a new day. */
120 if (TimerGameCalendar::date_fract
< Ticks::DAY_TICKS
) return true;
121 TimerGameCalendar::date_fract
= 0;
122 TimerGameCalendar::sub_date_fract
= 0;
124 /* Increase day counter. */
125 TimerGameCalendar::date
++;
127 TimerGameCalendar::YearMonthDay ymd
= TimerGameCalendar::ConvertDateToYMD(TimerGameCalendar::date
);
129 /* Check if we entered a new month. */
130 bool new_month
= ymd
.month
!= TimerGameCalendar::month
;
132 /* Check if we entered a new year. */
133 bool new_year
= ymd
.year
!= TimerGameCalendar::year
;
135 /* Update internal variables before calling the daily/monthly/yearly loops. */
136 TimerGameCalendar::month
= ymd
.month
;
137 TimerGameCalendar::year
= ymd
.year
;
139 /* Make a temporary copy of the timers, as a timer's callback might add/remove other timers. */
140 auto timers
= TimerManager
<TimerGameCalendar
>::GetTimers();
142 for (auto timer
: timers
) {
143 timer
->Elapsed(TimerGameCalendar::DAY
);
147 for (auto timer
: timers
) {
148 timer
->Elapsed(TimerGameCalendar::MONTH
);
153 for (auto timer
: timers
) {
154 timer
->Elapsed(TimerGameCalendar::YEAR
);
158 /* If we reached the maximum year, decrement dates by a year. */
159 if (TimerGameCalendar::year
== CalendarTime::MAX_YEAR
+ 1) {
162 TimerGameCalendar::year
--;
163 days_this_year
= TimerGameCalendar::IsLeapYear(TimerGameCalendar::year
) ? CalendarTime::DAYS_IN_LEAP_YEAR
: CalendarTime::DAYS_IN_YEAR
;
164 TimerGameCalendar::date
-= days_this_year
;
172 void TimerManager
<TimerGameCalendar
>::Validate(TimerGameCalendar::TPeriod period
)
174 if (period
.priority
== TimerGameCalendar::Priority::NONE
) return;
176 /* Validate we didn't make a developer error and scheduled more than one
177 * entry on the same priority/trigger. There can only be one timer on
178 * a specific trigger/priority, to ensure we are deterministic. */
179 for (const auto &timer
: TimerManager
<TimerGameCalendar
>::GetTimers()) {
180 if (timer
->period
.trigger
!= period
.trigger
) continue;
182 assert(timer
->period
.priority
!= period
.priority
);
185 #endif /* WITH_ASSERT */