diff --git a/src/aircraft.h b/src/aircraft.h --- a/src/aircraft.h +++ b/src/aircraft.h @@ -107,7 +107,8 @@ struct Aircraft FINAL : public Specializ } bool Tick() override; - void OnNewDay() override; + void OnNewCalendarDay() override; + void OnNewEconomyDay() override; uint Crash(bool flooded = false) override; TileIndex GetOrderStationLocation(StationID station) override; ClosestDepot FindClosestDepot() override; diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -22,6 +22,7 @@ #include "command_func.h" #include "window_func.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "vehicle_func.h" #include "sound_func.h" #include "cheat_type.h" @@ -338,7 +339,7 @@ CommandCost CmdBuildAircraft(DoCommandFl v->SetServiceInterval(Company::Get(_current_company)->settings.vehicle.servint_aircraft); - v->date_of_last_service = TimerGameCalendar::date; + v->date_of_last_service = TimerGameEconomy::date; v->date_of_last_service_newgrf = TimerGameCalendar::date; v->build_year = u->build_year = TimerGameCalendar::year; @@ -439,7 +440,15 @@ Money Aircraft::GetRunningCost() const return GetPrice(PR_RUNNING_AIRCRAFT, cost_factor, e->GetGRF()); } -void Aircraft::OnNewDay() +/** Calendar day handler */ +void Aircraft::OnNewCalendarDay() +{ + if (!this->IsNormalAircraft()) return; + AgeVehicle(this); +} + +/** Economy day handler */ +void Aircraft::OnNewEconomyDay() { if (!this->IsNormalAircraft()) return; @@ -448,7 +457,6 @@ void Aircraft::OnNewDay() CheckOrders(this); CheckVehicleBreakdown(this); - AgeVehicle(this); CheckIfAircraftNeedsService(this); if (this->running_ticks == 0) return; @@ -1555,7 +1563,7 @@ static void AircraftEventHandler_AtTermi if (_settings_game.order.serviceathelipad) { if (v->subtype == AIR_HELICOPTER && apc->num_helipads > 0) { /* an excerpt of ServiceAircraft, without the invisibility stuff */ - v->date_of_last_service = TimerGameCalendar::date; + v->date_of_last_service = TimerGameEconomy::date; v->date_of_last_service_newgrf = TimerGameCalendar::date; v->breakdowns_since_last_service = 0; v->reliability = v->GetEngine()->reliability; diff --git a/src/cheat_gui.cpp b/src/cheat_gui.cpp --- a/src/cheat_gui.cpp +++ b/src/cheat_gui.cpp @@ -31,6 +31,7 @@ #include "core/geometry_func.hpp" #include "timer/timer.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "widgets/cheat_widget.h" @@ -91,7 +92,7 @@ static int32_t ClickSetProdCheat(int32_t return _cheats.setup_prod.value; } -extern void EnginesMonthlyLoop(); +extern void CalendarEnginesMonthlyLoop(); /** * Handle changing of the current year. @@ -105,16 +106,19 @@ static int32_t ClickChangeDateCheat(int3 if (new_year == TimerGameCalendar::year) return TimerGameCalendar::year.base(); TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(TimerGameCalendar::date); - TimerGameCalendar::Date new_date = TimerGameCalendar::ConvertYMDToDate(new_year, ymd.month, ymd.day); + TimerGameCalendar::Date new_calendar_date = TimerGameCalendar::ConvertYMDToDate(new_year, ymd.month, ymd.day); + /* Keep economy and calendar dates synced. */ + TimerGameEconomy::Date new_economy_date = new_calendar_date.base(); /* Shift cached dates before we change the date. */ - for (auto v : Vehicle::Iterate()) v->ShiftDates(new_date - TimerGameCalendar::date); - LinkGraphSchedule::instance.ShiftDates(new_date - TimerGameCalendar::date); + for (auto v : Vehicle::Iterate()) v->ShiftDates(new_economy_date - TimerGameEconomy::date); + LinkGraphSchedule::instance.ShiftDates(new_economy_date - TimerGameEconomy::date); /* Now it's safe to actually change the date. */ - TimerGameCalendar::SetDate(new_date, TimerGameCalendar::date_fract); + TimerGameCalendar::SetDate(new_calendar_date, TimerGameCalendar::date_fract); + TimerGameEconomy::SetDate(new_economy_date, TimerGameEconomy::date_fract); - EnginesMonthlyLoop(); + CalendarEnginesMonthlyLoop(); SetWindowDirty(WC_STATUS_BAR, 0); InvalidateWindowClassesData(WC_BUILD_STATION, 0); InvalidateWindowClassesData(WC_BUS_STATION, 0); diff --git a/src/command.cpp b/src/command.cpp --- a/src/command.cpp +++ b/src/command.cpp @@ -18,7 +18,7 @@ #include "strings_func.h" #include "texteff.hpp" #include "town.h" -#include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "company_func.h" #include "company_base.h" #include "signal_func.h" @@ -274,7 +274,7 @@ void CommandHelperBase::InternalPostResu /** Helper to make a desync log for a command. */ void CommandHelperBase::LogCommandExecution(Commands cmd, StringID err_message, const CommandDataBuffer &args, bool failed) { - Debug(desync, 1, "{}: {:08x}; {:02x}; {:02x}; {:08x}; {:08x}; {} ({})", failed ? "cmdf" : "cmd", (uint32_t)TimerGameCalendar::date.base(), TimerGameCalendar::date_fract, (int)_current_company, cmd, err_message, FormatArrayAsHex(args), GetCommandName(cmd)); + Debug(desync, 1, "{}: {:08x}; {:02x}; {:02x}; {:08x}; {:08x}; {} ({})", failed ? "cmdf" : "cmd", (uint32_t)TimerGameEconomy::date.base(), TimerGameEconomy::date_fract, (int)_current_company, cmd, err_message, FormatArrayAsHex(args), GetCommandName(cmd)); } /** diff --git a/src/company_base.h b/src/company_base.h --- a/src/company_base.h +++ b/src/company_base.h @@ -14,7 +14,7 @@ #include "livery.h" #include "autoreplace_type.h" #include "tile_type.h" -#include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "settings_type.h" #include "group.h" @@ -74,7 +74,7 @@ struct CompanyProperties { TileIndex location_of_HQ; ///< Northern tile of HQ; #INVALID_TILE when there is none. TileIndex last_build_coordinate; ///< Coordinate of the last build thing by this company. - TimerGameCalendar::Year inaugurated_year; ///< Year of starting the company. + TimerGameEconomy::Year inaugurated_year; ///< Economy year of starting the company. byte months_of_bankruptcy; ///< Number of months that the company is unable to pay its debts CompanyMask bankrupt_asked; ///< which companies were asked about buying it? diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp --- a/src/company_cmd.cpp +++ b/src/company_cmd.cpp @@ -36,7 +36,7 @@ #include "widgets/statusbar_widget.h" #include "company_cmd.h" #include "timer/timer.h" -#include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "timer/timer_game_tick.h" #include "table/strings.h" @@ -566,7 +566,7 @@ Company *DoStartupNewCompany(bool is_ai, c->avail_railtypes = GetCompanyRailTypes(c->index); c->avail_roadtypes = GetCompanyRoadTypes(c->index); - c->inaugurated_year = TimerGameCalendar::year; + c->inaugurated_year = TimerGameEconomy::year; /* If starting a player company in singleplayer and a favorite company manager face is selected, choose it. Otherwise, use a random face. * In a network game, we'll choose the favorite face later in CmdCompanyCtrl to sync it to all clients. */ @@ -747,7 +747,7 @@ void OnTick_Companies() * A year has passed, update the economic data of all companies, and perhaps show the * financial overview window of the local company. */ -static IntervalTimer _companies_yearly({TimerGameCalendar::YEAR, TimerGameCalendar::Priority::COMPANY}, [](auto) +static IntervalTimer _economy_companies_yearly({TimerGameEconomy::YEAR, TimerGameEconomy::Priority::COMPANY}, [](auto) { /* Copy statistics */ for (Company *c : Company::Iterate()) { diff --git a/src/company_gui.cpp b/src/company_gui.cpp --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -22,7 +22,7 @@ #include "newgrf.h" #include "company_manager_face.h" #include "strings_func.h" -#include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "widgets/dropdown_type.h" #include "tilehighlight_func.h" #include "company_base.h" @@ -255,7 +255,7 @@ static Money DrawYearCategory(const Rect * @param tbl Reference to table of amounts for \a year. * @note The environment must provide padding at the left and right of \a r. */ -static void DrawYearColumn(const Rect &r, TimerGameCalendar::Year year, const Expenses &tbl) +static void DrawYearColumn(const Rect &r, TimerGameEconomy::Year year, const Expenses &tbl) { int y = r.top; Money sum; @@ -421,10 +421,10 @@ struct CompanyFinancesWindow : Window { case WID_CF_EXPS_PRICE2: case WID_CF_EXPS_PRICE3: { const Company *c = Company::Get((CompanyID)this->window_number); - auto age = std::min(TimerGameCalendar::year - c->inaugurated_year, TimerGameCalendar::Year(2)); + auto age = std::min(TimerGameEconomy::year - c->inaugurated_year, TimerGameEconomy::Year(2)); int wid_offset = widget - WID_CF_EXPS_PRICE1; if (wid_offset <= age) { - DrawYearColumn(r, TimerGameCalendar::year - (age - wid_offset), c->yearly_expenses[(age - wid_offset).base()]); + DrawYearColumn(r, TimerGameEconomy::year - (age - wid_offset), c->yearly_expenses[(age - wid_offset).base()]); } break; } diff --git a/src/core/random_func.cpp b/src/core/random_func.cpp --- a/src/core/random_func.cpp +++ b/src/core/random_func.cpp @@ -84,7 +84,7 @@ void SetRandomSeed(uint32_t seed) uint32_t DoRandom(int line, const char *file) { if (_networking && (!_network_server || (NetworkClientSocket::IsValidID(0) && NetworkClientSocket::Get(0)->status != NetworkClientSocket::STATUS_INACTIVE))) { - Debug(random, 0, "{:08x}; {:02x}; {:04x}; {:02x}; {}:{}", TimerGameCalendar::date, TimerGameCalendar::date_fract, _frame_counter, (byte)_current_company, file, line); + Debug(random, 0, "{:08x}; {:02x}; {:04x}; {:02x}; {}:{}", TimerGameEconomy::date, TimerGameEconomy::date_fract, _frame_counter, (byte)_current_company, file, line); } return _random.Next(); diff --git a/src/date_gui.cpp b/src/date_gui.cpp --- a/src/date_gui.cpp +++ b/src/date_gui.cpp @@ -9,7 +9,7 @@ #include "stdafx.h" #include "strings_func.h" -#include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "window_func.h" #include "window_gui.h" #include "date_gui.h" @@ -25,9 +25,9 @@ struct SetDateWindow : Window { SetDateCallback *callback; ///< Callback to call when a date has been selected void *callback_data; ///< Callback data pointer. - TimerGameCalendar::YearMonthDay date; ///< The currently selected date - TimerGameCalendar::Year min_year; ///< The minimum year in the year dropdown - TimerGameCalendar::Year max_year; ///< The maximum year (inclusive) in the year dropdown + TimerGameEconomy::YearMonthDay date; ///< The currently selected date + TimerGameEconomy::Year min_year; ///< The minimum year in the year dropdown + TimerGameEconomy::Year max_year; ///< The maximum year (inclusive) in the year dropdown /** * Create the new 'set date' window @@ -39,19 +39,19 @@ struct SetDateWindow : Window { * @param max_year the maximum year (inclusive) to show in the year dropdown * @param callback the callback to call once a date has been selected */ - SetDateWindow(WindowDesc *desc, WindowNumber window_number, Window *parent, TimerGameCalendar::Date initial_date, TimerGameCalendar::Year min_year, TimerGameCalendar::Year max_year, SetDateCallback *callback, void *callback_data) : + SetDateWindow(WindowDesc *desc, WindowNumber window_number, Window *parent, TimerGameEconomy::Date initial_date, TimerGameEconomy::Year min_year, TimerGameEconomy::Year max_year, SetDateCallback *callback, void *callback_data) : Window(desc), callback(callback), callback_data(callback_data), - min_year(std::max(CalendarTime::MIN_YEAR, min_year)), - max_year(std::min(CalendarTime::MAX_YEAR, max_year)) + min_year(std::max(EconomyTime::MIN_YEAR, min_year)), + max_year(std::min(EconomyTime::MAX_YEAR, max_year)) { assert(this->min_year <= this->max_year); this->parent = parent; this->InitNested(window_number); - if (initial_date == 0) initial_date = TimerGameCalendar::date; - this->date = TimerGameCalendar::ConvertDateToYMD(initial_date); + if (initial_date == 0) initial_date = TimerGameEconomy::date; + this->date = TimerGameEconomy::ConvertDateToYMD(initial_date); this->date.year = Clamp(this->date.year, min_year, max_year); } @@ -88,7 +88,7 @@ struct SetDateWindow : Window { break; case WID_SD_YEAR: - for (TimerGameCalendar::Year i = this->min_year; i <= this->max_year; i++) { + for (TimerGameEconomy::Year i = this->min_year; i <= this->max_year; i++) { SetDParam(0, i); list.push_back(std::make_unique(STR_JUST_INT, i.base(), false)); } @@ -147,7 +147,7 @@ struct SetDateWindow : Window { break; case WID_SD_SET_DATE: - if (this->callback != nullptr) this->callback(this, TimerGameCalendar::ConvertYMDToDate(this->date.year, this->date.month, this->date.day), this->callback_data); + if (this->callback != nullptr) this->callback(this, TimerGameEconomy::ConvertYMDToDate(this->date.year, this->date.month, this->date.day), this->callback_data); this->Close(); break; } @@ -212,7 +212,7 @@ static WindowDesc _set_date_desc(__FILE_ * @param callback the callback to call once a date has been selected * @param callback_data extra callback data */ -void ShowSetDateWindow(Window *parent, int window_number, TimerGameCalendar::Date initial_date, TimerGameCalendar::Year min_year, TimerGameCalendar::Year max_year, SetDateCallback *callback, void *callback_data) +void ShowSetDateWindow(Window *parent, int window_number, TimerGameEconomy::Date initial_date, TimerGameEconomy::Year min_year, TimerGameEconomy::Year max_year, SetDateCallback *callback, void *callback_data) { CloseWindowByClass(WC_SET_DATE); new SetDateWindow(&_set_date_desc, window_number, parent, initial_date, min_year, max_year, callback, callback_data); diff --git a/src/date_gui.h b/src/date_gui.h --- a/src/date_gui.h +++ b/src/date_gui.h @@ -18,8 +18,8 @@ * @param w the window that sends the callback * @param date the date that has been chosen */ -typedef void SetDateCallback(const Window *w, TimerGameCalendar::Date date, void *data); +typedef void SetDateCallback(const Window *w, TimerGameEconomy::Date date, void *data); -void ShowSetDateWindow(Window *parent, int window_number, TimerGameCalendar::Date initial_date, TimerGameCalendar::Year min_year, TimerGameCalendar::Year max_year, SetDateCallback *callback, void *callback_data); +void ShowSetDateWindow(Window *parent, int window_number, TimerGameEconomy::Date initial_date, TimerGameEconomy::Year min_year, TimerGameEconomy::Year max_year, SetDateCallback *callback, void *callback_data); #endif /* DATE_GUI_H */ diff --git a/src/disaster_vehicle.cpp b/src/disaster_vehicle.cpp --- a/src/disaster_vehicle.cpp +++ b/src/disaster_vehicle.cpp @@ -47,7 +47,7 @@ #include "core/backup_type.hpp" #include "landscape_cmd.h" #include "timer/timer.h" -#include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "table/strings.h" @@ -936,7 +936,7 @@ static void ResetDisasterDelay() _disaster_delay = GB(Random(), 0, 9) + 730; } -static IntervalTimer _disaster_daily({TimerGameCalendar::DAY, TimerGameCalendar::Priority::DISASTER}, [](auto) +static IntervalTimer _economy_disaster_daily({TimerGameEconomy::DAY, TimerGameEconomy::Priority::DISASTER}, [](auto) { if (--_disaster_delay != 0) return; diff --git a/src/economy.cpp b/src/economy.cpp --- a/src/economy.cpp +++ b/src/economy.cpp @@ -54,6 +54,7 @@ #include "vehicle_cmd.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "table/strings.h" #include "table/pricebase.h" @@ -690,8 +691,8 @@ static void CompaniesGenStatistics() } cur_company.Restore(); - /* Only run the economic statics and update company stats every 3rd month (1st of quarter). */ - if (!HasBit(1 << 0 | 1 << 3 | 1 << 6 | 1 << 9, TimerGameCalendar::month)) return; + /* Only run the economic statics and update company stats every 3rd economy month (1st of quarter). */ + if (!HasBit(1 << 0 | 1 << 3 | 1 << 6 | 1 << 9, TimerGameEconomy::month)) return; for (Company *c : Company::Iterate()) { /* Drop the oldest history off the end */ @@ -844,8 +845,8 @@ static void CompaniesPayInterest() if (c->money < 0) { yearly_fee += -c->money *_economy.interest_rate / 100; } - Money up_to_previous_month = yearly_fee * TimerGameCalendar::month / 12; - Money up_to_this_month = yearly_fee * (TimerGameCalendar::month + 1) / 12; + Money up_to_previous_month = yearly_fee * TimerGameEconomy::month / 12; + Money up_to_this_month = yearly_fee * (TimerGameEconomy::month + 1) / 12; SubtractMoneyFromCompany(CommandCost(EXPENSES_LOAN_INTEREST, up_to_this_month - up_to_previous_month)); @@ -1080,7 +1081,7 @@ static uint DeliverGoodsToIndustry(const uint amount = std::min(num_pieces, 0xFFFFu - it->waiting); it->waiting += amount; - it->last_accepted = TimerGameCalendar::date; + it->last_accepted = TimerGameEconomy::date; num_pieces -= amount; accepted += amount; @@ -1980,16 +1981,23 @@ void LoadUnloadStation(Station *st) } /** - * Monthly update of the economic data (of the companies as well as economic fluctuations). + * Every calendar month update of inflation. */ -static IntervalTimer _companies_monthly({TimerGameCalendar::MONTH, TimerGameCalendar::Priority::COMPANY}, [](auto) +static IntervalTimer _calendar_inflation_monthly({TimerGameCalendar::MONTH, TimerGameCalendar::Priority::COMPANY}, [](auto) { - CompaniesPayInterest(); - CompaniesGenStatistics(); if (_settings_game.economy.inflation) { AddInflation(); RecomputePrices(); } +}); + +/** + * Every economy month update of company economic data, plus economy fluctuations. + */ +static IntervalTimer _economy_companies_monthly({ TimerGameEconomy::MONTH, TimerGameEconomy::Priority::COMPANY }, [](auto) +{ + CompaniesGenStatistics(); + CompaniesPayInterest(); HandleEconomyFluctuations(); }); diff --git a/src/engine.cpp b/src/engine.cpp --- a/src/engine.cpp +++ b/src/engine.cpp @@ -919,7 +919,7 @@ static bool IsVehicleTypeDisabled(Vehicl } /** Daily check to offer an exclusive engine preview to the companies. */ -static IntervalTimer _engines_daily({TimerGameCalendar::DAY, TimerGameCalendar::Priority::ENGINE}, [](auto) +static IntervalTimer _calendar_engines_daily({TimerGameCalendar::DAY, TimerGameCalendar::Priority::ENGINE}, [](auto) { for (Company *c : Company::Iterate()) { c->avail_railtypes = AddDateIntroducedRailTypes(c->avail_railtypes, TimerGameCalendar::date); @@ -1104,7 +1104,7 @@ static void NewVehicleAvailable(Engine * } /** Monthly update of the availability, reliability, and preview offers of the engines. */ -void EnginesMonthlyLoop() +void CalendarEnginesMonthlyLoop() { if (TimerGameCalendar::year < _year_engine_aging_stops) { bool refresh = false; @@ -1151,9 +1151,9 @@ void EnginesMonthlyLoop() } } -static IntervalTimer _engines_monthly({TimerGameCalendar::MONTH, TimerGameCalendar::Priority::ENGINE}, [](auto) +static IntervalTimer _calendar_engines_monthly({TimerGameCalendar::MONTH, TimerGameCalendar::Priority::ENGINE}, [](auto) { - EnginesMonthlyLoop(); + CalendarEnginesMonthlyLoop(); }); /** diff --git a/src/graph_gui.cpp b/src/graph_gui.cpp --- a/src/graph_gui.cpp +++ b/src/graph_gui.cpp @@ -23,6 +23,7 @@ #include "timer/timer_window.h" #include "timer/timer_game_tick.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "zoom_func.h" #include "widgets/graph_widget.h" @@ -181,8 +182,8 @@ protected: byte num_vert_lines; /* The starting month and year that values are plotted against. */ - TimerGameCalendar::Month month; - TimerGameCalendar::Year year; + TimerGameEconomy::Month month; + TimerGameEconomy::Year year; bool draw_dates = true; ///< Should we draw months and years on the time axis? @@ -380,8 +381,8 @@ protected: if (this->draw_dates) { x = r.left; y = r.bottom + ScaleGUITrad(2); - TimerGameCalendar::Month month = this->month; - TimerGameCalendar::Year year = this->year; + TimerGameEconomy::Month month = this->month; + TimerGameEconomy::Year year = this->year; for (int i = 0; i < this->num_on_x_axis; i++) { SetDParam(0, month + STR_MONTH_ABBREV_JAN); SetDParam(1, year); @@ -497,8 +498,8 @@ public: /* Draw x-axis labels and markings for graphs based on financial quarters and years. */ if (this->draw_dates) { - TimerGameCalendar::Month month = this->month; - TimerGameCalendar::Year year = this->year; + TimerGameEconomy::Month month = this->month; + TimerGameEconomy::Year year = this->year; for (int i = 0; i < this->num_on_x_axis; i++) { SetDParam(0, month + STR_MONTH_ABBREV_JAN); SetDParam(1, year); @@ -577,8 +578,8 @@ public: nums = std::min(this->num_vert_lines, std::max(nums, c->num_valid_stat_ent)); } - int mo = (TimerGameCalendar::month / 3 - nums) * 3; - auto yr = TimerGameCalendar::year; + int mo = (TimerGameEconomy::month / 3 - nums) * 3; + auto yr = TimerGameEconomy::year; while (mo < 0) { yr--; mo += 12; diff --git a/src/industry.h b/src/industry.h --- a/src/industry.h +++ b/src/industry.h @@ -17,12 +17,13 @@ #include "tilearea_type.h" #include "station_base.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" typedef Pool IndustryPool; extern IndustryPool _industry_pool; -static const TimerGameCalendar::Year PROCESSING_INDUSTRY_ABANDONMENT_YEARS = 5; ///< If a processing industry doesn't produce for this many consecutive years, it may close. +static const TimerGameEconomy::Year PROCESSING_INDUSTRY_ABANDONMENT_YEARS = 5; ///< If a processing industry doesn't produce for this many consecutive economy years, it may close. /** * Production level maximum, minimum and default values. @@ -86,7 +87,7 @@ struct Industry : IndustryPool::PoolItem struct AcceptedCargo { CargoID cargo; ///< Cargo type uint16_t waiting; ///< Amount of cargo waiting to processed - TimerGameCalendar::Date last_accepted; ///< Last day cargo was accepted by this industry + TimerGameEconomy::Date last_accepted; ///< Last day cargo was accepted by this industry }; using ProducedCargoArray = std::array; @@ -103,7 +104,7 @@ struct Industry : IndustryPool::PoolItem IndustryType type; ///< type of industry. Owner owner; ///< owner of the industry. Which SHOULD always be (imho) OWNER_NONE Colours random_colour; ///< randomized colour of the industry, for display purpose - TimerGameCalendar::Year last_prod_year; ///< last year of production + TimerGameEconomy::Year last_prod_year; ///< last economy year of production byte was_cargo_delivered; ///< flag that indicate this has been the closest industry chosen for cargo delivery by a station. see DeliverGoodsToIndustry IndustryControlFlags ctlflags; ///< flags overriding standard behaviours @@ -296,7 +297,7 @@ struct IndustryBuildData { void SetupTargetCount(); void TryBuildNewIndustry(); - void MonthlyLoop(); + void EconomyMonthlyLoop(); }; extern IndustryBuildData _industry_builder; diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -45,6 +45,7 @@ #include "terraform_cmd.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "timer/timer_game_tick.h" #include "table/strings.h" @@ -1782,7 +1783,7 @@ static void DoCreateNewIndustry(Industry i->counter = GB(r, 4, 12); i->random = initial_random_bits; i->was_cargo_delivered = false; - i->last_prod_year = TimerGameCalendar::year; + i->last_prod_year = TimerGameEconomy::year; i->founder = founder; i->ctlflags = INDCTL_NONE; @@ -2393,7 +2394,7 @@ void IndustryBuildData::Reset() } /** Monthly update of industry build data. */ -void IndustryBuildData::MonthlyLoop() +void IndustryBuildData::EconomyMonthlyLoop() { static const int NEWINDS_PER_MONTH = 0x38000 / (10 * 12); // lower 16 bits is a float fraction, 3.5 industries per decade, divided by 10 * 12 months. if (_settings_game.difficulty.industry_density == ID_FUND_ONLY) return; // 'no industries' setting. @@ -2465,7 +2466,7 @@ static void UpdateIndustryStatistics(Ind { for (auto &p : i->produced) { if (IsValidCargoID(p.cargo)) { - if (p.history[THIS_MONTH].production != 0) i->last_prod_year = TimerGameCalendar::year; + if (p.history[THIS_MONTH].production != 0) i->last_prod_year = TimerGameEconomy::year; /* Move history from this month to last month. */ std::rotate(std::rbegin(p.history), std::rbegin(p.history) + 1, std::rend(p.history)); @@ -2888,7 +2889,7 @@ static void ChangeIndustryProduction(Ind } if (!callback_enabled && (indspec->life_type & INDUSTRYLIFE_PROCESSING)) { - if (TimerGameCalendar::year - i->last_prod_year >= PROCESSING_INDUSTRY_ABANDONMENT_YEARS && Chance16(1, original_economy ? 2 : 180)) { + if (TimerGameEconomy::year - i->last_prod_year >= PROCESSING_INDUSTRY_ABANDONMENT_YEARS && Chance16(1, original_economy ? 2 : 180)) { closeit = true; } } @@ -2970,13 +2971,13 @@ static void ChangeIndustryProduction(Ind } /** - * Daily handler for the industry changes + * Every economy day handler for the industry changes * Taking the original map size of 256*256, the number of random changes was always of just one unit. * But it cannot be the same on smaller or bigger maps. That number has to be scaled up or down. * For small maps, it implies that less than one change per month is required, while on bigger maps, * it would be way more. The daily loop handles those changes. */ -static IntervalTimer _industries_daily({TimerGameCalendar::DAY, TimerGameCalendar::Priority::INDUSTRY}, [](auto) +static IntervalTimer _economy_industries_daily({TimerGameEconomy::DAY, TimerGameEconomy::Priority::INDUSTRY}, [](auto) { _economy.industry_daily_change_counter += _economy.industry_daily_increment; @@ -3018,11 +3019,11 @@ static IntervalTimer InvalidateWindowData(WC_INDUSTRY_DIRECTORY, 0, IDIWD_PRODUCTION_CHANGE); }); -static IntervalTimer _industries_monthly({TimerGameCalendar::MONTH, TimerGameCalendar::Priority::INDUSTRY}, [](auto) +static IntervalTimer _economy_industries_monthly({TimerGameEconomy::MONTH, TimerGameEconomy::Priority::INDUSTRY}, [](auto) { Backup cur_company(_current_company, OWNER_NONE, FILE_LINE); - _industry_builder.MonthlyLoop(); + _industry_builder.EconomyMonthlyLoop(); for (Industry *i : Industry::Iterate()) { UpdateIndustryStatistics(i); diff --git a/src/linkgraph/linkgraph.cpp b/src/linkgraph/linkgraph.cpp --- a/src/linkgraph/linkgraph.cpp +++ b/src/linkgraph/linkgraph.cpp @@ -29,7 +29,7 @@ LinkGraph::BaseNode::BaseNode(TileIndex this->supply = 0; this->demand = demand; this->station = st; - this->last_update = CalendarTime::INVALID_DATE; + this->last_update = EconomyTime::INVALID_DATE; } /** @@ -40,8 +40,8 @@ LinkGraph::BaseEdge::BaseEdge(NodeID des this->capacity = 0; this->usage = 0; this->travel_time_sum = 0; - this->last_unrestricted_update = CalendarTime::INVALID_DATE; - this->last_restricted_update = CalendarTime::INVALID_DATE; + this->last_unrestricted_update = EconomyTime::INVALID_DATE; + this->last_restricted_update = EconomyTime::INVALID_DATE; this->dest_node = dest_node; } @@ -50,22 +50,22 @@ LinkGraph::BaseEdge::BaseEdge(NodeID des * This is useful if the date has been modified with the cheat menu. * @param interval Number of days to be added or subtracted. */ -void LinkGraph::ShiftDates(TimerGameCalendar::Date interval) +void LinkGraph::ShiftDates(TimerGameEconomy::Date interval) { this->last_compression += interval; for (NodeID node1 = 0; node1 < this->Size(); ++node1) { BaseNode &source = this->nodes[node1]; - if (source.last_update != CalendarTime::INVALID_DATE) source.last_update += interval; + if (source.last_update != EconomyTime::INVALID_DATE) source.last_update += interval; for (BaseEdge &edge : this->nodes[node1].edges) { - if (edge.last_unrestricted_update != CalendarTime::INVALID_DATE) edge.last_unrestricted_update += interval; - if (edge.last_restricted_update != CalendarTime::INVALID_DATE) edge.last_restricted_update += interval; + if (edge.last_unrestricted_update != EconomyTime::INVALID_DATE) edge.last_unrestricted_update += interval; + if (edge.last_restricted_update != EconomyTime::INVALID_DATE) edge.last_restricted_update += interval; } } } void LinkGraph::Compress() { - this->last_compression = (TimerGameCalendar::date + this->last_compression).base() / 2; + this->last_compression = (TimerGameEconomy::date + this->last_compression).base() / 2; for (NodeID node1 = 0; node1 < this->Size(); ++node1) { this->nodes[node1].supply /= 2; for (BaseEdge &edge : this->nodes[node1].edges) { @@ -89,8 +89,8 @@ void LinkGraph::Compress() */ void LinkGraph::Merge(LinkGraph *other) { - TimerGameCalendar::Date age = TimerGameCalendar::date - this->last_compression + 1; - TimerGameCalendar::Date other_age = TimerGameCalendar::date - other->last_compression + 1; + TimerGameEconomy::Date age = TimerGameEconomy::date - this->last_compression + 1; + TimerGameEconomy::Date other_age = TimerGameEconomy::date - other->last_compression + 1; NodeID first = this->Size(); for (NodeID node1 = 0; node1 < other->Size(); ++node1) { Station *st = Station::Get(other->nodes[node1].station); @@ -172,8 +172,8 @@ void LinkGraph::BaseNode::AddEdge(NodeID edge.capacity = capacity; edge.usage = usage; edge.travel_time_sum = static_cast(travel_time) * capacity; - if (mode & EUM_UNRESTRICTED) edge.last_unrestricted_update = TimerGameCalendar::date; - if (mode & EUM_RESTRICTED) edge.last_restricted_update = TimerGameCalendar::date; + if (mode & EUM_UNRESTRICTED) edge.last_unrestricted_update = TimerGameEconomy::date; + if (mode & EUM_RESTRICTED) edge.last_restricted_update = TimerGameEconomy::date; } /** @@ -239,8 +239,8 @@ void LinkGraph::BaseEdge::Update(uint ca } this->usage = std::max(this->usage, usage); } - if (mode & EUM_UNRESTRICTED) this->last_unrestricted_update = TimerGameCalendar::date; - if (mode & EUM_RESTRICTED) this->last_restricted_update = TimerGameCalendar::date; + if (mode & EUM_UNRESTRICTED) this->last_unrestricted_update = TimerGameEconomy::date; + if (mode & EUM_RESTRICTED) this->last_restricted_update = TimerGameEconomy::date; } /** diff --git a/src/linkgraph/linkgraph.h b/src/linkgraph/linkgraph.h --- a/src/linkgraph/linkgraph.h +++ b/src/linkgraph/linkgraph.h @@ -13,7 +13,7 @@ #include "../core/pool_type.hpp" #include "../station_base.h" #include "../cargotype.h" -#include "../timer/timer_game_calendar.h" +#include "../timer/timer_game_economy.h" #include "../saveload/saveload.h" #include "linkgraph_type.h" #include @@ -43,8 +43,8 @@ public: uint capacity; ///< Capacity of the link. uint usage; ///< Usage of the link. uint64_t travel_time_sum; ///< Sum of the travel times of the link, in ticks. - TimerGameCalendar::Date last_unrestricted_update; ///< When the unrestricted part of the link was last updated. - TimerGameCalendar::Date last_restricted_update; ///< When the restricted part of the link was last updated. + TimerGameEconomy::Date last_unrestricted_update; ///< When the unrestricted part of the link was last updated. + TimerGameEconomy::Date last_restricted_update; ///< When the restricted part of the link was last updated. NodeID dest_node; ///< Destination of the edge. BaseEdge(NodeID dest_node = INVALID_NODE); @@ -59,11 +59,11 @@ public: * Get the date of the last update to any part of the edge's capacity. * @return Last update. */ - TimerGameCalendar::Date LastUpdate() const { return std::max(this->last_unrestricted_update, this->last_restricted_update); } + TimerGameEconomy::Date LastUpdate() const { return std::max(this->last_unrestricted_update, this->last_restricted_update); } void Update(uint capacity, uint usage, uint32_t time, EdgeUpdateMode mode); - void Restrict() { this->last_unrestricted_update = CalendarTime::INVALID_DATE; } - void Release() { this->last_restricted_update = CalendarTime::INVALID_DATE; } + void Restrict() { this->last_unrestricted_update = EconomyTime::INVALID_DATE; } + void Release() { this->last_restricted_update = EconomyTime::INVALID_DATE; } /** Comparison operator based on \c dest_node. */ bool operator <(const BaseEdge &rhs) const @@ -92,7 +92,7 @@ public: uint demand; ///< Acceptance at the station. StationID station; ///< Station ID. TileIndex xy; ///< Location of the station referred to by the node. - TimerGameCalendar::Date last_update; ///< When the supply was last updated. + TimerGameEconomy::Date last_update; ///< When the supply was last updated. std::vector edges; ///< Sorted list of outgoing edges from this node. @@ -105,7 +105,7 @@ public: void UpdateSupply(uint supply) { this->supply += supply; - this->last_update = TimerGameCalendar::date; + this->last_update = TimerGameEconomy::date; } /** @@ -170,10 +170,10 @@ public: static const uint MIN_TIMEOUT_DISTANCE = 32; /** Number of days before deleting links served only by vehicles stopped in depot. */ - static constexpr TimerGameCalendar::Date STALE_LINK_DEPOT_TIMEOUT = 1024; + static constexpr TimerGameEconomy::Date STALE_LINK_DEPOT_TIMEOUT = 1024; /** Minimum number of days between subsequent compressions of a LG. */ - static constexpr TimerGameCalendar::Date COMPRESSION_INTERVAL = 256; + static constexpr TimerGameEconomy::Date COMPRESSION_INTERVAL = 256; /** * Scale a value from a link graph of age orig_age for usage in one of age @@ -183,7 +183,7 @@ public: * @param orig_age Age of the original link graph. * @return scaled value. */ - inline static uint Scale(uint val, TimerGameCalendar::Date target_age, TimerGameCalendar::Date orig_age) + inline static uint Scale(uint val, TimerGameEconomy::Date target_age, TimerGameEconomy::Date orig_age) { return val > 0 ? std::max(1U, val * target_age.base() / orig_age.base()) : 0; } @@ -194,10 +194,10 @@ public: * Real constructor. * @param cargo Cargo the link graph is about. */ - LinkGraph(CargoID cargo) : cargo(cargo), last_compression(TimerGameCalendar::date) {} + LinkGraph(CargoID cargo) : cargo(cargo), last_compression(TimerGameEconomy::date) {} void Init(uint size); - void ShiftDates(TimerGameCalendar::Date interval); + void ShiftDates(TimerGameEconomy::Date interval); void Compress(); void Merge(LinkGraph *other); @@ -233,7 +233,7 @@ public: * Get date of last compression. * @return Date of last compression. */ - inline TimerGameCalendar::Date LastCompression() const { return this->last_compression; } + inline TimerGameEconomy::Date LastCompression() const { return this->last_compression; } /** * Get the cargo ID this component's link graph refers to. @@ -248,7 +248,7 @@ public: */ inline uint Monthly(uint base) const { - return base * 30 / (TimerGameCalendar::date - this->last_compression + 1).base(); + return base * 30 / (TimerGameEconomy::date - this->last_compression + 1).base(); } NodeID AddNode(const Station *st); @@ -262,7 +262,7 @@ protected: friend class LinkGraphJob; CargoID cargo; ///< Cargo of this component's link graph. - TimerGameCalendar::Date last_compression; ///< Last time the capacities and supplies were compressed. + TimerGameEconomy::Date last_compression; ///< Last time the capacities and supplies were compressed. NodeVector nodes; ///< Nodes in the component. }; diff --git a/src/linkgraph/linkgraphjob.cpp b/src/linkgraph/linkgraphjob.cpp --- a/src/linkgraph/linkgraphjob.cpp +++ b/src/linkgraph/linkgraphjob.cpp @@ -37,7 +37,7 @@ LinkGraphJob::LinkGraphJob(const LinkGra * This is on purpose. */ link_graph(orig), settings(_settings_game.linkgraph), - join_date(TimerGameCalendar::date + (_settings_game.linkgraph.recalc_time / CalendarTime::SECONDS_PER_DAY)), + join_date(TimerGameEconomy::date + (_settings_game.linkgraph.recalc_time / EconomyTime::SECONDS_PER_DAY)), job_completed(false), job_aborted(false) { @@ -131,14 +131,14 @@ LinkGraphJob::~LinkGraphJob() if (st2 == nullptr || st2->goods[this->Cargo()].link_graph != this->link_graph.index || st2->goods[this->Cargo()].node != dest_id || !(*lg)[node_id].HasEdgeTo(dest_id) || - (*lg)[node_id][dest_id].LastUpdate() == CalendarTime::INVALID_DATE) { + (*lg)[node_id][dest_id].LastUpdate() == EconomyTime::INVALID_DATE) { /* Edge has been removed. Delete flows. */ StationIDStack erased = flows.DeleteFlows(to); /* Delete old flows for source stations which have been deleted * from the new flows. This avoids flow cycles between old and * new flows. */ while (!erased.IsEmpty()) ge.flows.erase(erased.Pop()); - } else if ((*lg)[node_id][dest_id].last_unrestricted_update == CalendarTime::INVALID_DATE) { + } else if ((*lg)[node_id][dest_id].last_unrestricted_update == EconomyTime::INVALID_DATE) { /* Edge is fully restricted. */ flows.RestrictFlows(to); } diff --git a/src/linkgraph/linkgraphjob.h b/src/linkgraph/linkgraphjob.h --- a/src/linkgraph/linkgraphjob.h +++ b/src/linkgraph/linkgraphjob.h @@ -163,7 +163,7 @@ protected: const LinkGraph link_graph; ///< Link graph to by analyzed. Is copied when job is started and mustn't be modified later. const LinkGraphSettings settings; ///< Copy of _settings_game.linkgraph at spawn time. std::thread thread; ///< Thread the job is running in or a default-constructed thread if it's running in the main thread. - TimerGameCalendar::Date join_date; ///< Date when the job is to be joined. + TimerGameEconomy::Date join_date; ///< Date when the job is to be joined. NodeAnnotationVector nodes; ///< Extra node data necessary for link graph calculation. std::atomic job_completed; ///< Is the job still running. This is accessed by multiple threads and reads may be stale. std::atomic job_aborted; ///< Has the job been aborted. This is accessed by multiple threads and reads may be stale. @@ -178,7 +178,7 @@ public: * settings have to be brutally const-casted in order to populate them. */ LinkGraphJob() : settings(_settings_game.linkgraph), - join_date(CalendarTime::INVALID_DATE), job_completed(false), job_aborted(false) {} + join_date(EconomyTime::INVALID_DATE), job_completed(false), job_aborted(false) {} LinkGraphJob(const LinkGraph &orig); ~LinkGraphJob(); @@ -211,19 +211,19 @@ public: * Check if job is supposed to be finished. * @return True if job should be finished by now, false if not. */ - inline bool IsScheduledToBeJoined() const { return this->join_date <= TimerGameCalendar::date; } + inline bool IsScheduledToBeJoined() const { return this->join_date <= TimerGameEconomy::date; } /** * Get the date when the job should be finished. * @return Join date. */ - inline TimerGameCalendar::Date JoinDate() const { return join_date; } + inline TimerGameEconomy::Date JoinDate() const { return join_date; } /** * Change the join date on date cheating. * @param interval Number of days to add. */ - inline void ShiftJoinDate(TimerGameCalendar::Date interval) { this->join_date += interval; } + inline void ShiftJoinDate(TimerGameEconomy::Date interval) { this->join_date += interval; } /** * Get the link graph settings for this component. @@ -254,7 +254,7 @@ public: * Get the date when the underlying link graph was last compressed. * @return Compression date. */ - inline TimerGameCalendar::Date LastCompression() const { return this->link_graph.LastCompression(); } + inline TimerGameEconomy::Date LastCompression() const { return this->link_graph.LastCompression(); } /** * Get the ID of the underlying link graph. diff --git a/src/linkgraph/linkgraphschedule.cpp b/src/linkgraph/linkgraphschedule.cpp --- a/src/linkgraph/linkgraphschedule.cpp +++ b/src/linkgraph/linkgraphschedule.cpp @@ -132,7 +132,7 @@ void LinkGraphSchedule::SpawnAll() * graph jobs by the number of days given. * @param interval Number of days to be added or subtracted. */ -void LinkGraphSchedule::ShiftDates(TimerGameCalendar::Date interval) +void LinkGraphSchedule::ShiftDates(TimerGameEconomy::Date interval) { for (LinkGraph *lg : LinkGraph::Iterate()) lg->ShiftDates(interval); for (LinkGraphJob *lgj : LinkGraphJob::Iterate()) lgj->ShiftJoinDate(interval); @@ -163,10 +163,10 @@ LinkGraphSchedule::~LinkGraphSchedule() } /** - * Pause the game if in 2 TimerGameCalendar::date_fract ticks, we would do a join with the next + * Pause the game if in 2 TimerGameEconomy::date_fract ticks, we would do a join with the next * link graph job, but it is still running. - * The check is done 2 TimerGameCalendar::date_fract ticks early instead of 1, as in multiplayer - * calls to DoCommandP are executed after a delay of 1 TimerGameCalendar::date_fract tick. + * The check is done 2 TimerGameEconomy::date_fract ticks early instead of 1, as in multiplayer + * calls to DoCommandP are executed after a delay of 1 TimerGameEconomy::date_fract tick. * If we previously paused, unpause if the job is now ready to be joined with. */ void StateGameLoop_LinkGraphPauseControl() @@ -177,10 +177,10 @@ void StateGameLoop_LinkGraphPauseControl Command::Post(PM_PAUSED_LINK_GRAPH, false); } } else if (_pause_mode == PM_UNPAUSED && - TimerGameCalendar::date_fract == LinkGraphSchedule::SPAWN_JOIN_TICK - 2 && - TimerGameCalendar::date.base() % (_settings_game.linkgraph.recalc_interval / CalendarTime::SECONDS_PER_DAY) == (_settings_game.linkgraph.recalc_interval / CalendarTime::SECONDS_PER_DAY) / 2 && + TimerGameEconomy::date_fract == LinkGraphSchedule::SPAWN_JOIN_TICK - 2 && + TimerGameEconomy::date.base() % (_settings_game.linkgraph.recalc_interval / EconomyTime::SECONDS_PER_DAY) == (_settings_game.linkgraph.recalc_interval / EconomyTime::SECONDS_PER_DAY) / 2 && LinkGraphSchedule::instance.IsJoinWithUnfinishedJobDue()) { - /* Perform check two TimerGameCalendar::date_fract ticks before we would join, to make + /* Perform check two TimerGameEconomy::date_fract ticks before we would join, to make * sure it also works in multiplayer. */ Command::Post(PM_PAUSED_LINK_GRAPH, true); } @@ -204,11 +204,11 @@ void AfterLoad_LinkGraphPauseControl() */ void OnTick_LinkGraph() { - if (TimerGameCalendar::date_fract != LinkGraphSchedule::SPAWN_JOIN_TICK) return; - TimerGameCalendar::Date offset = TimerGameCalendar::date.base() % (_settings_game.linkgraph.recalc_interval / CalendarTime::SECONDS_PER_DAY); + if (TimerGameEconomy::date_fract != LinkGraphSchedule::SPAWN_JOIN_TICK) return; + TimerGameEconomy::Date offset = TimerGameEconomy::date.base() % (_settings_game.linkgraph.recalc_interval / EconomyTime::SECONDS_PER_DAY); if (offset == 0) { LinkGraphSchedule::instance.SpawnNext(); - } else if (offset == (_settings_game.linkgraph.recalc_interval / CalendarTime::SECONDS_PER_DAY) / 2) { + } else if (offset == (_settings_game.linkgraph.recalc_interval / EconomyTime::SECONDS_PER_DAY) / 2) { if (!_networking || _network_server) { PerformanceMeasurer::SetInactive(PFE_GL_LINKGRAPH); LinkGraphSchedule::instance.JoinNext(); diff --git a/src/linkgraph/linkgraphschedule.h b/src/linkgraph/linkgraphschedule.h --- a/src/linkgraph/linkgraphschedule.h +++ b/src/linkgraph/linkgraphschedule.h @@ -58,7 +58,7 @@ public: bool IsJoinWithUnfinishedJobDue() const; void JoinNext(); void SpawnAll(); - void ShiftDates(TimerGameCalendar::Date interval); + void ShiftDates(TimerGameEconomy::Date interval); /** * Queue a link graph for execution. diff --git a/src/misc.cpp b/src/misc.cpp --- a/src/misc.cpp +++ b/src/misc.cpp @@ -16,6 +16,7 @@ #include "newgrf_house.h" #include "economy_func.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "timer/timer_game_tick.h" #include "texteff.hpp" #include "gfx_func.h" @@ -109,7 +110,10 @@ void InitializeGame(uint size_x, uint si _newgrf_profilers.clear(); if (reset_date) { - TimerGameCalendar::SetDate(TimerGameCalendar::ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1), 0); + TimerGameCalendar::Date new_date = TimerGameCalendar::ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1); + TimerGameCalendar::SetDate(new_date, 0); + /* Keep the economy date synced with the calendar date. */ + TimerGameEconomy::SetDate(new_date.base(), 0); InitializeOldNames(); } diff --git a/src/network/network.cpp b/src/network/network.cpp --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -12,7 +12,7 @@ #include "../strings_func.h" #include "../command_func.h" #include "../timer/timer_game_tick.h" -#include "../timer/timer_game_calendar.h" +#include "../timer/timer_game_economy.h" #include "network_admin.h" #include "network_client.h" #include "network_query.h" @@ -263,7 +263,7 @@ void NetworkTextMessage(NetworkAction ac Utf8Encode(iterator, _current_text_dir == TD_LTR ? CHAR_TD_LRM : CHAR_TD_RLM); std::string message = stream.str() + GetString(strid); - Debug(desync, 1, "msg: {:08x}; {:02x}; {}", TimerGameCalendar::date, TimerGameCalendar::date_fract, message); + Debug(desync, 1, "msg: {:08x}; {:02x}; {}", TimerGameEconomy::date, TimerGameEconomy::date_fract, message); IConsolePrint(colour, message); NetworkAddChatMessage(colour, _settings_client.gui.network_chat_timeout, message); } @@ -1043,42 +1043,42 @@ void NetworkGameLoop() if (_network_server) { /* Log the sync state to check for in-syncedness of replays. */ - if (TimerGameCalendar::date_fract == 0) { + if (TimerGameEconomy::date_fract == 0) { /* We don't want to log multiple times if paused. */ - static TimerGameCalendar::Date last_log; - if (last_log != TimerGameCalendar::date) { - Debug(desync, 1, "sync: {:08x}; {:02x}; {:08x}; {:08x}", TimerGameCalendar::date, TimerGameCalendar::date_fract, _random.state[0], _random.state[1]); - last_log = TimerGameCalendar::date; + static TimerGameEconomy::Date last_log; + if (last_log != TimerGameEconomy::date) { + Debug(desync, 1, "sync: {:08x}; {:02x}; {:08x}; {:08x}", TimerGameEconomy::date, TimerGameEconomy::date_fract, _random.state[0], _random.state[1]); + last_log = TimerGameEconomy::date; } } #ifdef DEBUG_DUMP_COMMANDS /* Loading of the debug commands from -ddesync>=1 */ static FILE *f = FioFOpenFile("commands.log", "rb", SAVE_DIR); - static TimerGameCalendar::Date next_date(0); + static TimerGameEconomy::Date next_date(0); static uint32_t next_date_fract; static CommandPacket *cp = nullptr; static bool check_sync_state = false; static uint32_t sync_state[2]; if (f == nullptr && next_date == 0) { Debug(desync, 0, "Cannot open commands.log"); - next_date = TimerGameCalendar::Date(1); + next_date = TimerGameEconomy::Date(1); } while (f != nullptr && !feof(f)) { - if (TimerGameCalendar::date == next_date && TimerGameCalendar::date_fract == next_date_fract) { + if (TimerGameEconomy::date == next_date && TimerGameEconomy::date_fract == next_date_fract) { if (cp != nullptr) { NetworkSendCommand(cp->cmd, cp->err_msg, nullptr, cp->company, cp->data); - Debug(desync, 0, "Injecting: {:08x}; {:02x}; {:02x}; {:08x}; {} ({})", TimerGameCalendar::date, TimerGameCalendar::date_fract, (int)_current_company, cp->cmd, FormatArrayAsHex(cp->data), GetCommandName(cp->cmd)); + Debug(desync, 0, "Injecting: {:08x}; {:02x}; {:02x}; {:08x}; {} ({})", TimerGameEconomy::date, TimerGameEconomy::date_fract, (int)_current_company, cp->cmd, FormatArrayAsHex(cp->data), GetCommandName(cp->cmd)); delete cp; cp = nullptr; } if (check_sync_state) { if (sync_state[0] == _random.state[0] && sync_state[1] == _random.state[1]) { - Debug(desync, 0, "Sync check: {:08x}; {:02x}; match", TimerGameCalendar::date, TimerGameCalendar::date_fract); + Debug(desync, 0, "Sync check: {:08x}; {:02x}; match", TimerGameEconomy::date, TimerGameEconomy::date_fract); } else { Debug(desync, 0, "Sync check: {:08x}; {:02x}; mismatch expected {{{:08x}, {:08x}}}, got {{{:08x}, {:08x}}}", - TimerGameCalendar::date, TimerGameCalendar::date_fract, sync_state[0], sync_state[1], _random.state[0], _random.state[1]); + TimerGameEconomy::date, TimerGameEconomy::date_fract, sync_state[0], sync_state[1], _random.state[0], _random.state[1]); NOT_REACHED(); } check_sync_state = false; @@ -1112,7 +1112,7 @@ void NetworkGameLoop() uint32_t next_date_raw; int ret = sscanf(p, "%x; %x; %x; %x; %x; %255s", &next_date_raw, &next_date_fract, &company, &cmd, &cp->err_msg, buffer); assert(ret == 6); - next_date = TimerGameCalendar::Date((int32_t)next_date_raw); + next_date = TimerGameEconomy::Date((int32_t)next_date_raw); cp->company = (CompanyID)company; cp->cmd = (Commands)cmd; @@ -1129,7 +1129,7 @@ void NetworkGameLoop() /* Manually insert a pause when joining; this way the client can join at the exact right time. */ uint32_t next_date_raw; int ret = sscanf(p + 6, "%x; %x", &next_date_raw, &next_date_fract); - next_date = TimerGameCalendar::Date((int32_t)next_date_raw); + next_date = TimerGameEconomy::Date((int32_t)next_date_raw); assert(ret == 2); Debug(desync, 0, "Injecting pause for join at {:08x}:{:02x}; please join when paused", next_date, next_date_fract); cp = new CommandPacket(); @@ -1140,7 +1140,7 @@ void NetworkGameLoop() } else if (strncmp(p, "sync: ", 6) == 0) { uint32_t next_date_raw; int ret = sscanf(p + 6, "%x; %x; %x; %x", &next_date_raw, &next_date_fract, &sync_state[0], &sync_state[1]); - next_date = TimerGameCalendar::Date((int32_t)next_date_raw); + next_date = TimerGameEconomy::Date((int32_t)next_date_raw); assert(ret == 4); check_sync_state = true; } else if (strncmp(p, "msg: ", 5) == 0 || strncmp(p, "client: ", 8) == 0 || diff --git a/src/network/network_base.h b/src/network/network_base.h --- a/src/network/network_base.h +++ b/src/network/network_base.h @@ -14,7 +14,7 @@ #include "core/address.h" #include "../core/pool_type.hpp" #include "../company_type.h" -#include "../timer/timer_game_calendar.h" +#include "../timer/timer_game_economy.h" /** Type for the pool with client information. */ typedef Pool NetworkClientInfoPool; @@ -25,7 +25,7 @@ struct NetworkClientInfo : NetworkClient ClientID client_id; ///< Client identifier (same as ClientState->client_id) std::string client_name; ///< Name of the client CompanyID client_playas; ///< As which company is this client playing (CompanyID) - TimerGameCalendar::Date join_date; ///< Gamedate the client has joined + TimerGameEconomy::Date join_date; ///< Gamedate the client has joined /** * Create a new client. diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -282,7 +282,7 @@ void ClientNetworkGameSocketHandler::Cli if (_sync_seed_1 != _random.state[0]) { #endif ShowNetworkError(STR_NETWORK_ERROR_DESYNC); - Debug(desync, 1, "sync_err: {:08x}; {:02x}", TimerGameCalendar::date, TimerGameCalendar::date_fract); + Debug(desync, 1, "sync_err: {:08x}; {:02x}", TimerGameEconomy::date, TimerGameEconomy::date_fract); Debug(net, 0, "Sync error detected"); my_client->ClientError(NETWORK_RECV_STATUS_DESYNC); return false; diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -32,6 +32,7 @@ #include "../rev.h" #include "../timer/timer.h" #include "../timer/timer_game_calendar.h" +#include "../timer/timer_game_economy.h" #include #include @@ -882,10 +883,10 @@ NetworkRecvStatus ServerNetworkGameSocke assert(NetworkClientInfo::CanAllocateItem()); NetworkClientInfo *ci = new NetworkClientInfo(this->client_id); this->SetInfo(ci); - ci->join_date = TimerGameCalendar::date; + ci->join_date = TimerGameEconomy::date; ci->client_name = client_name; ci->client_playas = playas; - Debug(desync, 1, "client: {:08x}; {:02x}; {:02x}; {:02x}", TimerGameCalendar::date, TimerGameCalendar::date_fract, (int)ci->client_playas, (int)ci->index); + Debug(desync, 1, "client: {:08x}; {:02x}; {:02x}; {:02x}", TimerGameEconomy::date, TimerGameEconomy::date_fract, (int)ci->client_playas, (int)ci->index); /* Make sure companies to which people try to join are not autocleaned */ if (Company::IsValidID(playas)) _network_company_states[playas].months_empty = 0; @@ -1479,7 +1480,7 @@ void NetworkUpdateClientInfo(ClientID cl if (ci == nullptr) return; - Debug(desync, 1, "client: {:08x}; {:02x}; {:02x}; {:04x}", TimerGameCalendar::date, TimerGameCalendar::date_fract, (int)ci->client_playas, client_id); + Debug(desync, 1, "client: {:08x}; {:02x}; {:02x}; {:04x}", TimerGameEconomy::date, TimerGameEconomy::date_fract, (int)ci->client_playas, client_id); for (NetworkClientSocket *cs : NetworkClientSocket::Iterate()) { if (cs->status >= ServerNetworkGameSocketHandler::STATUS_AUTHORIZED) { @@ -1810,17 +1811,23 @@ void NetworkServer_Tick(bool send_frame) } } -/** Yearly "callback". Called whenever the year changes. */ -static IntervalTimer _network_yearly({TimerGameCalendar::YEAR, TimerGameCalendar::Priority::NONE}, [](auto) -{ +/** Calendar yearly "callback". Called whenever the calendar year changes. */ +static IntervalTimer _calendar_network_yearly({ TimerGameCalendar::YEAR, TimerGameCalendar::Priority::NONE }, [](auto) { if (!_network_server) return; NetworkCheckRestartMap(); +}); + +/** Economy yearly "callback". Called whenever the economy year changes. */ +static IntervalTimer _economy_network_yearly({TimerGameEconomy::YEAR, TimerGameEconomy::Priority::NONE}, [](auto) +{ + if (!_network_server) return; + NetworkAdminUpdate(ADMIN_FREQUENCY_ANUALLY); }); -/** Quarterly "callback". Called whenever the quarter changes. */ -static IntervalTimer _network_quarterly({TimerGameCalendar::QUARTER, TimerGameCalendar::Priority::NONE}, [](auto) +/** Quarterly "callback". Called whenever the economy quarter changes. */ +static IntervalTimer _network_quarterly({TimerGameEconomy::QUARTER, TimerGameEconomy::Priority::NONE}, [](auto) { if (!_network_server) return; @@ -1828,8 +1835,8 @@ static IntervalTimer NetworkAdminUpdate(ADMIN_FREQUENCY_QUARTERLY); }); -/** Monthly "callback". Called whenever the month changes. */ -static IntervalTimer _network_monthly({TimerGameCalendar::MONTH, TimerGameCalendar::Priority::NONE}, [](auto) +/** Economy monthly "callback". Called whenever the economy month changes. */ +static IntervalTimer _network_monthly({TimerGameEconomy::MONTH, TimerGameEconomy::Priority::NONE}, [](auto) { if (!_network_server) return; @@ -1837,16 +1844,16 @@ static IntervalTimer NetworkAdminUpdate(ADMIN_FREQUENCY_MONTHLY); }); -/** Weekly "callback". Called whenever the week changes. */ -static IntervalTimer _network_weekly({TimerGameCalendar::WEEK, TimerGameCalendar::Priority::NONE}, [](auto) +/** Economy weekly "callback". Called whenever the economy week changes. */ +static IntervalTimer _network_weekly({TimerGameEconomy::WEEK, TimerGameEconomy::Priority::NONE}, [](auto) { if (!_network_server) return; NetworkAdminUpdate(ADMIN_FREQUENCY_WEEKLY); }); -/** Daily "callback". Called whenever the date changes. */ -static IntervalTimer _network_daily({TimerGameCalendar::DAY, TimerGameCalendar::Priority::NONE}, [](auto) +/** Daily "callback". Called whenever the economy date changes. */ +static IntervalTimer _economy_network_daily({TimerGameEconomy::DAY, TimerGameEconomy::Priority::NONE}, [](auto) { if (!_network_server) return; diff --git a/src/newgrf.cpp b/src/newgrf.cpp --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -9945,6 +9945,11 @@ void LoadNewGRF(uint load_index, uint nu TimerGameCalendar::Date date = TimerGameCalendar::date; TimerGameCalendar::Year year = TimerGameCalendar::year; TimerGameCalendar::DateFract date_fract = TimerGameCalendar::date_fract; + + TimerGameEconomy::Date economy_date = TimerGameEconomy::date; + TimerGameEconomy::Year economy_year = TimerGameEconomy::year; + TimerGameEconomy::DateFract economy_date_fract = TimerGameEconomy::date_fract; + uint64_t tick_counter = TimerGameTick::counter; byte display_opt = _display_opt; @@ -9952,6 +9957,11 @@ void LoadNewGRF(uint load_index, uint nu TimerGameCalendar::year = _settings_game.game_creation.starting_year; TimerGameCalendar::date = TimerGameCalendar::ConvertYMDToDate(TimerGameCalendar::year, 0, 1); TimerGameCalendar::date_fract = 0; + + TimerGameEconomy::year = _settings_game.game_creation.starting_year.base(); + TimerGameEconomy::date = TimerGameEconomy::ConvertYMDToDate(TimerGameEconomy::year, 0, 1); + TimerGameEconomy::date_fract = 0; + TimerGameTick::counter = 0; _display_opt = 0; } @@ -10049,6 +10059,11 @@ void LoadNewGRF(uint load_index, uint nu TimerGameCalendar::year = year; TimerGameCalendar::date = date; TimerGameCalendar::date_fract = date_fract; + + TimerGameEconomy::year = economy_year; + TimerGameEconomy::date = economy_date; + TimerGameEconomy::date_fract = economy_date_fract; + TimerGameTick::counter = tick_counter; _display_opt = display_opt; } diff --git a/src/newgrf_industries.cpp b/src/newgrf_industries.cpp --- a/src/newgrf_industries.cpp +++ b/src/newgrf_industries.cpp @@ -396,7 +396,7 @@ static uint32_t GetCountAndDistanceOfClo case 0xA6: return indspec->grf_prop.local_id; case 0xA7: return this->industry->founder; case 0xA8: return this->industry->random_colour; - case 0xA9: return ClampTo(this->industry->last_prod_year - CalendarTime::ORIGINAL_BASE_YEAR); + case 0xA9: return ClampTo(this->industry->last_prod_year - EconomyTime::ORIGINAL_BASE_YEAR); case 0xAA: return this->industry->counter; case 0xAB: return GB(this->industry->counter, 8, 8); case 0xAC: return this->industry->was_cargo_delivered; @@ -405,7 +405,7 @@ static uint32_t GetCountAndDistanceOfClo case 0xB3: return this->industry->construction_type; // Construction type case 0xB4: { auto it = std::max_element(std::begin(this->industry->accepted), std::end(this->industry->accepted), [](const auto &a, const auto &b) { return a.last_accepted < b.last_accepted; }); - return ClampTo(it->last_accepted - CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR); // Date last cargo accepted since 1920 (in days) + return ClampTo(it->last_accepted - EconomyTime::DAYS_TILL_ORIGINAL_BASE_YEAR); // Date last cargo accepted since 1920 (in days) } } diff --git a/src/news_gui.cpp b/src/news_gui.cpp --- a/src/news_gui.cpp +++ b/src/news_gui.cpp @@ -690,7 +690,7 @@ static void MoveToNextTickerItem() const NewsType type = ni->type; /* check the date, don't show too old items */ - if (TimerGameCalendar::date - _news_type_data[type].age > ni->date) continue; + if (TimerGameEconomy::date - _news_type_data[type].age > ni->economy_date) continue; switch (_news_type_data[type].GetDisplay()) { default: NOT_REACHED(); @@ -727,7 +727,7 @@ static void MoveToNextNewsItem() const NewsType type = ni->type; /* check the date, don't show too old items */ - if (TimerGameCalendar::date - _news_type_data[type].age > ni->date) continue; + if (TimerGameEconomy::date - _news_type_data[type].age > ni->economy_date) continue; switch (_news_type_data[type].GetDisplay()) { default: NOT_REACHED(); @@ -804,7 +804,7 @@ static void DeleteNewsItem(NewsItem *ni) * @see NewsSubtype */ NewsItem::NewsItem(StringID string_id, NewsType type, NewsFlag flags, NewsReferenceType reftype1, uint32_t ref1, NewsReferenceType reftype2, uint32_t ref2, const NewsAllocatedData *data) : - string_id(string_id), date(TimerGameCalendar::date), type(type), flags(flags), reftype1(reftype1), reftype2(reftype2), ref1(ref1), ref2(ref2), data(data) + string_id(string_id), date(TimerGameCalendar::date), economy_date(TimerGameEconomy::date), type(type), flags(flags), reftype1(reftype1), reftype2(reftype2), ref1(ref1), ref2(ref2), data(data) { /* show this news message in colour? */ if (TimerGameCalendar::year >= _settings_client.gui.coloured_news_year) this->flags |= NF_INCOLOUR; @@ -987,7 +987,7 @@ static void RemoveOldNewsItems() NewsItem *next; for (NewsItem *cur = _oldest_news; _total_news > MIN_NEWS_AMOUNT && cur != nullptr; cur = next) { next = cur->next; - if (TimerGameCalendar::date - _news_type_data[cur->type].age * _settings_client.gui.news_message_timeout > cur->date) DeleteNewsItem(cur); + if (TimerGameEconomy::date - _news_type_data[cur->type].age * _settings_client.gui.news_message_timeout > cur->economy_date) DeleteNewsItem(cur); } } @@ -1011,11 +1011,11 @@ void NewsLoop() /* no news item yet */ if (_total_news == 0) return; - static byte _last_clean_month = 0; + static TimerGameEconomy::Month _last_clean_month = 0; - if (_last_clean_month != TimerGameCalendar::month) { + if (_last_clean_month != TimerGameEconomy::month) { RemoveOldNewsItems(); - _last_clean_month = TimerGameCalendar::month; + _last_clean_month = TimerGameEconomy::month; } if (ReadyForNextTickerItem()) MoveToNextTickerItem(); diff --git a/src/news_type.h b/src/news_type.h --- a/src/news_type.h +++ b/src/news_type.h @@ -13,6 +13,7 @@ #include "core/enum_type.hpp" #include "gfx_type.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "strings_type.h" #include "sound_type.h" @@ -128,7 +129,8 @@ struct NewsItem { NewsItem *prev; ///< Previous news item NewsItem *next; ///< Next news item StringID string_id; ///< Message text - TimerGameCalendar::Date date; ///< Date of the news + TimerGameCalendar::Date date; ///< Calendar date to show for the news + TimerGameEconomy::Date economy_date; ///< Economy date of the news item, never shown but used to calculate age NewsType type; ///< Type of the news NewsFlag flags; ///< NewsFlags bits @see NewsFlag diff --git a/src/openttd.cpp b/src/openttd.cpp --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -72,6 +72,7 @@ #include "misc_cmd.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "timer/timer_game_realtime.h" #include "timer/timer_game_tick.h" @@ -830,7 +831,7 @@ static void OnStartScenario() /* Make sure all industries were built "this year", to avoid too early closures. (#9918) */ for (Industry *i : Industry::Iterate()) { - i->last_prod_year = TimerGameCalendar::year; + i->last_prod_year = TimerGameEconomy::year; } } @@ -1396,7 +1397,7 @@ void StateGameLoop() CallWindowGameTickEvent(); NewsLoop(); } else { - if (_debug_desync_level > 2 && TimerGameCalendar::date_fract == 0 && (TimerGameCalendar::date.base() & 0x1F) == 0) { + if (_debug_desync_level > 2 && TimerGameEconomy::date_fract == 0 && (TimerGameEconomy::date.base() & 0x1F) == 0) { /* Save the desync savegame if needed. */ std::string name = fmt::format("dmp_cmds_{:08x}_{:08x}.sav", _settings_game.game_creation.generation_seed, TimerGameCalendar::date); SaveOrLoad(name, SLO_SAVE, DFT_GAME_FILE, AUTOSAVE_DIR, false); @@ -1411,8 +1412,10 @@ void StateGameLoop() BasePersistentStorageArray::SwitchMode(PSM_ENTER_GAMELOOP); AnimateAnimatedTiles(); TimerManager::Elapsed(1); + TimerManager::Elapsed(1); TimerManager::Elapsed(1); RunTileLoop(); + RunVehicleCalendarDayProc(); CallVehicleTicks(); CallLandscapeTick(); BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP); diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -1963,7 +1963,7 @@ bool UpdateOrderDest(Vehicle *v, const O if (v->current_order.GetDepotActionType() & ODATFB_NEAREST_DEPOT) { /* If the vehicle can't find its destination, delay its next search. * In case many vehicles are in this state, use the vehicle index to spread out pathfinder calls. */ - if (v->dest_tile == 0 && TimerGameCalendar::date_fract != (v->index % Ticks::DAY_TICKS)) break; + if (v->dest_tile == 0 && TimerGameEconomy::date_fract != (v->index % Ticks::DAY_TICKS)) break; /* We need to search for the nearest depot (hangar). */ ClosestDepot closestDepot = v->FindClosestDepot(); diff --git a/src/roadveh.h b/src/roadveh.h --- a/src/roadveh.h +++ b/src/roadveh.h @@ -134,7 +134,8 @@ struct RoadVehicle FINAL : public Ground int GetDisplayImageWidth(Point *offset = nullptr) const; bool IsInDepot() const override { return this->state == RVSB_IN_DEPOT; } bool Tick() override; - void OnNewDay() override; + void OnNewCalendarDay() override; + void OnNewEconomyDay() override; uint Crash(bool flooded = false) override; Trackdir GetVehicleTrackdir() const override; TileIndex GetOrderStationLocation(StationID station) override; diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -21,6 +21,7 @@ #include "strings_func.h" #include "tunnelbridge_map.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "vehicle_func.h" #include "sound_func.h" #include "ai/ai.hpp" @@ -299,7 +300,7 @@ CommandCost CmdBuildRoadVehicle(DoComman v->SetServiceInterval(Company::Get(v->owner)->settings.vehicle.servint_roadveh); - v->date_of_last_service = TimerGameCalendar::date; + v->date_of_last_service = TimerGameEconomy::date; v->date_of_last_service_newgrf = TimerGameCalendar::date; v->build_year = TimerGameCalendar::year; @@ -1705,10 +1706,16 @@ static void CheckIfRoadVehNeedsService(R SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); } -void RoadVehicle::OnNewDay() +/** Calandar day handler */ +void RoadVehicle::OnNewCalendarDay() { + if (!this->IsFrontEngine()) return; AgeVehicle(this); +} +/** Economy day handler. */ +void RoadVehicle::OnNewEconomyDay() +{ if (!this->IsFrontEngine()) return; if ((++this->day_counter & 7) == 0) DecreaseVehicleValue(this); diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -60,6 +60,7 @@ #include "../water.h" #include "../timer/timer.h" #include "../timer/timer_game_calendar.h" +#include "../timer/timer_game_economy.h" #include "../timer/timer_game_tick.h" #include "saveload_internal.h" @@ -260,8 +261,8 @@ static void InitializeWindowsAndCaches() /* For each company, verify (while loading a scenario) that the inauguration date is the current year and set it * accordingly if it is not the case. No need to set it on companies that are not been used already, * thus the MIN_YEAR (which is really nothing more than Zero, initialized value) test */ - if (_file_to_saveload.abstract_ftype == FT_SCENARIO && c->inaugurated_year != CalendarTime::MIN_YEAR) { - c->inaugurated_year = TimerGameCalendar::year; + if (_file_to_saveload.abstract_ftype == FT_SCENARIO && c->inaugurated_year != EconomyTime::MIN_YEAR) { + c->inaugurated_year = TimerGameEconomy::year; } } @@ -734,6 +735,13 @@ bool AfterLoadGame() * must be done before loading sprites as some newgrfs check it */ TimerGameCalendar::SetDate(TimerGameCalendar::date, TimerGameCalendar::date_fract); + /* Update economy year. If we don't have a separate economy date saved, follow the calendar date. */ + if (IsSavegameVersionBefore(SLV_ECONOMY_DATE)) { + TimerGameEconomy::SetDate(TimerGameCalendar::date.base(), TimerGameCalendar::date_fract); + } else { + TimerGameEconomy::SetDate(TimerGameEconomy::date, TimerGameEconomy::date_fract); + } + /* * Force the old behaviour for compatibility reasons with old savegames. As new * settings can only be loaded from new savegames loading old savegames with new @@ -1429,11 +1437,11 @@ bool AfterLoadGame() for (Station *st : Station::Iterate()) st->build_date += CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR; for (Waypoint *wp : Waypoint::Iterate()) wp->build_date += CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR; for (Engine *e : Engine::Iterate()) e->intro_date += CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR; - for (Company *c : Company::Iterate()) c->inaugurated_year += CalendarTime::ORIGINAL_BASE_YEAR; - for (Industry *i : Industry::Iterate()) i->last_prod_year += CalendarTime::ORIGINAL_BASE_YEAR; + for (Company *c : Company::Iterate()) c->inaugurated_year += EconomyTime::ORIGINAL_BASE_YEAR; + for (Industry *i : Industry::Iterate()) i->last_prod_year += EconomyTime::ORIGINAL_BASE_YEAR; for (Vehicle *v : Vehicle::Iterate()) { - v->date_of_last_service += CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR; + v->date_of_last_service += EconomyTime::DAYS_TILL_ORIGINAL_BASE_YEAR; v->build_year += CalendarTime::ORIGINAL_BASE_YEAR; } } @@ -3258,7 +3266,7 @@ bool AfterLoadGame() if (IsSavegameVersionBefore(SLV_NEWGRF_LAST_SERVICE)) { /* Set service date provided to NewGRF. */ for (Vehicle *v : Vehicle::Iterate()) { - v->date_of_last_service_newgrf = v->date_of_last_service; + v->date_of_last_service_newgrf = v->date_of_last_service.base(); } } diff --git a/src/saveload/industry_sl.cpp b/src/saveload/industry_sl.cpp --- a/src/saveload/industry_sl.cpp +++ b/src/saveload/industry_sl.cpp @@ -50,12 +50,12 @@ public: /* Old array structure used for savegames before SLV_INDUSTRY_CARGO_REORGANISE. */ static CargoID old_cargo[INDUSTRY_NUM_INPUTS]; static uint16_t old_waiting[INDUSTRY_NUM_INPUTS]; - static TimerGameCalendar::Date old_last_accepted[INDUSTRY_NUM_INPUTS]; + static TimerGameEconomy::Date old_last_accepted[INDUSTRY_NUM_INPUTS]; }; /* static */ CargoID SlIndustryAccepted::old_cargo[INDUSTRY_NUM_INPUTS]; /* static */ uint16_t SlIndustryAccepted::old_waiting[INDUSTRY_NUM_INPUTS]; -/* static */ TimerGameCalendar::Date SlIndustryAccepted::old_last_accepted[INDUSTRY_NUM_INPUTS]; +/* static */ TimerGameEconomy::Date SlIndustryAccepted::old_last_accepted[INDUSTRY_NUM_INPUTS]; class SlIndustryProducedHistory : public DefaultSaveLoadHandler { public: diff --git a/src/saveload/misc_sl.cpp b/src/saveload/misc_sl.cpp --- a/src/saveload/misc_sl.cpp +++ b/src/saveload/misc_sl.cpp @@ -13,6 +13,7 @@ #include "compat/misc_sl_compat.h" #include "../timer/timer_game_calendar.h" +#include "../timer/timer_game_economy.h" #include "../zoom_func.h" #include "../window_gui.h" #include "../window_func.h" @@ -85,6 +86,8 @@ static const SaveLoad _date_desc[] = { SLEG_VAR("date_fract", TimerGameCalendar::date_fract, SLE_UINT16), SLEG_CONDVAR("tick_counter", TimerGameTick::counter, SLE_FILE_U16 | SLE_VAR_U64, SL_MIN_VERSION, SLV_U64_TICK_COUNTER), 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("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/oldloader_sl.cpp b/src/saveload/oldloader_sl.cpp --- a/src/saveload/oldloader_sl.cpp +++ b/src/saveload/oldloader_sl.cpp @@ -859,7 +859,7 @@ static bool LoadOldIndustry(LoadgameStat if (i->type > 0x06) i->type++; // Printing Works were added if (i->type == 0x0A) i->type = 0x12; // Iron Ore Mine has different ID - TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(TimerGameCalendar::date); + TimerGameEconomy::YearMonthDay ymd = TimerGameEconomy::ConvertDateToYMD(TimerGameEconomy::date); i->last_prod_year = ymd.year; i->random_colour = RemapTTOColour(i->random_colour); @@ -1034,7 +1034,7 @@ static bool LoadOldCompany(LoadgameState } _company_colours[num] = c->colour; - c->inaugurated_year -= CalendarTime::ORIGINAL_BASE_YEAR; + c->inaugurated_year -= EconomyTime::ORIGINAL_BASE_YEAR; return true; } diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -368,6 +368,7 @@ enum SaveLoadVersion : uint16_t { SLV_WATER_REGIONS, ///< 324 PR#10543 Water Regions for ship pathfinder. 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. SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/ship.h b/src/ship.h --- a/src/ship.h +++ b/src/ship.h @@ -45,7 +45,8 @@ struct Ship FINAL : public SpecializedVe Money GetRunningCost() const override; bool IsInDepot() const override { return this->state == TRACK_BIT_DEPOT; } bool Tick() override; - void OnNewDay() override; + void OnNewCalendarDay() override; + void OnNewEconomyDay() override; Trackdir GetVehicleTrackdir() const override; TileIndex GetOrderStationLocation(StationID station) override; ClosestDepot FindClosestDepot() override; diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -23,6 +23,7 @@ #include "strings_func.h" #include "window_func.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "vehicle_func.h" #include "sound_func.h" #include "ai/ai.hpp" @@ -222,14 +223,20 @@ Money Ship::GetRunningCost() const return GetPrice(PR_RUNNING_SHIP, cost_factor, e->GetGRF()); } -void Ship::OnNewDay() +/** Calendar day handler. */ +void Ship::OnNewCalendarDay() +{ + AgeVehicle(this); +} + +/** Economy day handler. */ +void Ship::OnNewEconomyDay() { if ((++this->day_counter & 7) == 0) { DecreaseVehicleValue(this); } CheckVehicleBreakdown(this); - AgeVehicle(this); CheckIfShipNeedsService(this); CheckOrders(this); @@ -902,7 +909,7 @@ CommandCost CmdBuildShip(DoCommandFlag f v->state = TRACK_BIT_DEPOT; v->SetServiceInterval(Company::Get(_current_company)->settings.vehicle.servint_ships); - v->date_of_last_service = TimerGameCalendar::date; + v->date_of_last_service = TimerGameEconomy::date; v->date_of_last_service_newgrf = TimerGameCalendar::date; v->build_year = TimerGameCalendar::year; v->sprite_cache.sprite_seq.Set(SPR_IMG_QUERY); diff --git a/src/station.cpp b/src/station.cpp --- a/src/station.cpp +++ b/src/station.cpp @@ -105,7 +105,7 @@ Station::~Station() for (NodeID node = 0; node < lg->Size(); ++node) { Station *st = Station::Get((*lg)[node].station); st->goods[c].flows.erase(this->index); - if ((*lg)[node].HasEdgeTo(this->goods[c].node) && (*lg)[node][this->goods[c].node].LastUpdate() != CalendarTime::INVALID_DATE) { + if ((*lg)[node].HasEdgeTo(this->goods[c].node) && (*lg)[node][this->goods[c].node].LastUpdate() != EconomyTime::INVALID_DATE) { st->goods[c].flows.DeleteFlows(this->index); RerouteCargo(st, c, this->index, st->index); } diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -63,6 +63,7 @@ #include "newgrf_roadstop.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "timer/timer_game_tick.h" #include "cheat_type.h" @@ -3824,9 +3825,9 @@ void DeleteStaleLinks(Station *from) for (Edge &edge : (*lg)[ge.node].edges) { Station *to = Station::Get((*lg)[edge.dest_node].station); assert(to->goods[c].node == edge.dest_node); - assert(TimerGameCalendar::date >= edge.LastUpdate()); - auto timeout = TimerGameCalendar::Date(LinkGraph::MIN_TIMEOUT_DISTANCE + (DistanceManhattan(from->xy, to->xy) >> 3)); - if (TimerGameCalendar::date - edge.LastUpdate() > timeout) { + assert(TimerGameEconomy::date >= edge.LastUpdate()); + auto timeout = TimerGameEconomy::Date(LinkGraph::MIN_TIMEOUT_DISTANCE + (DistanceManhattan(from->xy, to->xy) >> 3)); + if (TimerGameEconomy::date - edge.LastUpdate() > timeout) { bool updated = false; if (auto_distributed) { @@ -3854,10 +3855,10 @@ void DeleteStaleLinks(Station *from) while (iter != vehicles.end()) { Vehicle *v = *iter; /* Do not refresh links of vehicles that have been stopped in depot for a long time. */ - if (!v->IsStoppedInDepot() || TimerGameCalendar::date - v->date_of_last_service <= LinkGraph::STALE_LINK_DEPOT_TIMEOUT) { + if (!v->IsStoppedInDepot() || TimerGameEconomy::date - v->date_of_last_service <= LinkGraph::STALE_LINK_DEPOT_TIMEOUT) { LinkRefresher::Run(v, false); // Don't allow merging. Otherwise lg might get deleted. } - if (edge.LastUpdate() == TimerGameCalendar::date) { + if (edge.LastUpdate() == TimerGameEconomy::date) { updated = true; break; } @@ -3880,19 +3881,19 @@ void DeleteStaleLinks(Station *from) ge.flows.DeleteFlows(to->index); RerouteCargo(from, c, to->index, from->index); } - } else if (edge.last_unrestricted_update != CalendarTime::INVALID_DATE && TimerGameCalendar::date - edge.last_unrestricted_update > timeout) { + } else if (edge.last_unrestricted_update != EconomyTime::INVALID_DATE && TimerGameEconomy::date - edge.last_unrestricted_update > timeout) { edge.Restrict(); ge.flows.RestrictFlows(to->index); RerouteCargo(from, c, to->index, from->index); - } else if (edge.last_restricted_update != CalendarTime::INVALID_DATE && TimerGameCalendar::date - edge.last_restricted_update > timeout) { + } else if (edge.last_restricted_update != EconomyTime::INVALID_DATE && TimerGameEconomy::date - edge.last_restricted_update > timeout) { edge.Release(); } } /* Remove dead edges. */ for (NodeID r : to_remove) (*lg)[ge.node].RemoveEdge(r); - assert(TimerGameCalendar::date >= lg->LastCompression()); - if (TimerGameCalendar::date - lg->LastCompression() > LinkGraph::COMPRESSION_INTERVAL) { + assert(TimerGameEconomy::date >= lg->LastCompression()); + if (TimerGameEconomy::date - lg->LastCompression() > LinkGraph::COMPRESSION_INTERVAL) { lg->Compress(); } } @@ -4014,8 +4015,8 @@ void OnTick_Station() } } -/** Monthly loop for stations. */ -static IntervalTimer _stations_monthly({TimerGameCalendar::MONTH, TimerGameCalendar::Priority::STATION}, [](auto) +/** Economy monthly loop for stations. */ +static IntervalTimer _economy_stations_monthly({TimerGameEconomy::MONTH, TimerGameEconomy::Priority::STATION}, [](auto) { for (Station *st : Station::Iterate()) { for (GoodsEntry &ge : st->goods) { diff --git a/src/subsidy.cpp b/src/subsidy.cpp --- a/src/subsidy.cpp +++ b/src/subsidy.cpp @@ -27,7 +27,7 @@ #include "tile_cmd.h" #include "subsidy_cmd.h" #include "timer/timer.h" -#include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "table/strings.h" @@ -474,8 +474,8 @@ bool FindSubsidyCargoDestination(CargoID return true; } -/** Perform the monthly update of open subsidies, and try to create a new one. */ -static IntervalTimer _subsidies_monthly({TimerGameCalendar::MONTH, TimerGameCalendar::Priority::SUBSIDY}, [](auto) +/** Perform the economy monthly update of open subsidies, and try to create a new one. */ +static IntervalTimer _economy_subsidies_monthly({TimerGameEconomy::MONTH, TimerGameEconomy::Priority::SUBSIDY}, [](auto) { bool modified = false; diff --git a/src/subsidy_gui.cpp b/src/subsidy_gui.cpp --- a/src/subsidy_gui.cpp +++ b/src/subsidy_gui.cpp @@ -142,7 +142,7 @@ struct SubsidyListWindow : Window { { if (widget != WID_SUL_PANEL) return; - TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(TimerGameCalendar::date); + TimerGameEconomy::YearMonthDay ymd = TimerGameEconomy::ConvertDateToYMD(TimerGameEconomy::date); Rect tr = r.Shrink(WidgetDimensions::scaled.framerect); @@ -159,7 +159,7 @@ struct SubsidyListWindow : Window { if (IsInsideMM(pos, 0, cap)) { /* Displays the two offered towns */ SetupSubsidyDecodeParam(s, SubsidyDecodeParamType::Gui); - SetDParam(7, TimerGameCalendar::date - ymd.day + s->remaining * 32); + SetDParam(7, TimerGameEconomy::date - ymd.day + s->remaining * 32); DrawString(tr.left, tr.right, tr.top + pos * GetCharacterHeight(FS_NORMAL), STR_SUBSIDIES_OFFERED_FROM_TO); } pos++; @@ -183,7 +183,7 @@ struct SubsidyListWindow : Window { if (IsInsideMM(pos, 0, cap)) { SetupSubsidyDecodeParam(s, SubsidyDecodeParamType::Gui); SetDParam(7, s->awarded); - SetDParam(8, TimerGameCalendar::date - ymd.day + s->remaining * 32); + SetDParam(8, TimerGameEconomy::date - ymd.day + s->remaining * 32); /* Displays the two connected stations */ DrawString(tr.left, tr.right, tr.top + pos * GetCharacterHeight(FS_NORMAL), STR_SUBSIDIES_SUBSIDISED_FROM_TO); diff --git a/src/survey.cpp b/src/survey.cpp --- a/src/survey.cpp +++ b/src/survey.cpp @@ -16,6 +16,8 @@ #include "rev.h" #include "settings_type.h" #include "timer/timer_game_tick.h" +#include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "currency.h" #include "fontcache.h" @@ -317,6 +319,9 @@ void SurveyTimers(nlohmann::json &survey survey["ticks"] = TimerGameTick::counter; survey["seconds"] = std::chrono::duration_cast(std::chrono::steady_clock::now() - _switch_mode_time).count(); + TimerGameEconomy::YearMonthDay economy_ymd = TimerGameEconomy::ConvertDateToYMD(TimerGameEconomy::date); + survey["economy"] = fmt::format("{:04}-{:02}-{:02} ({})", economy_ymd.year, economy_ymd.month + 1, economy_ymd.day, TimerGameEconomy::date_fract); + TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(TimerGameCalendar::date); survey["calendar"] = fmt::format("{:04}-{:02}-{:02} ({})", ymd.year, ymd.month + 1, ymd.day, TimerGameCalendar::date_fract); } diff --git a/src/timer/CMakeLists.txt b/src/timer/CMakeLists.txt --- a/src/timer/CMakeLists.txt +++ b/src/timer/CMakeLists.txt @@ -1,6 +1,10 @@ add_files( + timer_game_common.cpp + timer_game_common.h timer_game_calendar.cpp timer_game_calendar.h + timer_game_economy.cpp + timer_game_economy.h timer_game_realtime.cpp timer_game_realtime.h timer_game_tick.cpp diff --git a/src/timer/timer_game_calendar.cpp b/src/timer/timer_game_calendar.cpp --- a/src/timer/timer_game_calendar.cpp +++ b/src/timer/timer_game_calendar.cpp @@ -10,12 +10,21 @@ * This file implements the timer logic for the game-calendar-timer. */ +/** + * Calendar time is used for technology and time-of-year changes, including: + * - Vehicle, airport, station, object introduction and obsolescence + * - Vehicle and engine age + * - NewGRF variables for visual styles or behavior based on year or time of year (e.g. variable snow line) + * - Inflation, since it is tied to original game years. One interpretation of inflation is that it compensates for faster and higher capacity vehicles, + * 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 + * so we follow a previous decision to tie inflation to original TTD game years. + */ + #include "../stdafx.h" #include "../openttd.h" #include "timer.h" #include "timer_game_calendar.h" #include "../vehicle_base.h" -#include "../linkgraph/linkgraph.h" #include "../safeguards.h" @@ -24,127 +33,6 @@ TimerGameCalendar::Month TimerGameCalend TimerGameCalendar::Date TimerGameCalendar::date = {}; TimerGameCalendar::DateFract TimerGameCalendar::date_fract = {}; -#define M(a, b) ((a << 5) | b) -static const uint16_t _month_date_from_year_day[] = { - M(0, 1), M(0, 2), M(0, 3), M(0, 4), M(0, 5), M(0, 6), M(0, 7), M(0, 8), M(0, 9), M(0, 10), M(0, 11), M(0, 12), M(0, 13), M(0, 14), M(0, 15), M(0, 16), M(0, 17), M(0, 18), M(0, 19), M(0, 20), M(0, 21), M(0, 22), M(0, 23), M(0, 24), M(0, 25), M(0, 26), M(0, 27), M(0, 28), M(0, 29), M(0, 30), M(0, 31), - M(1, 1), M(1, 2), M(1, 3), M(1, 4), M(1, 5), M(1, 6), M(1, 7), M(1, 8), M(1, 9), M(1, 10), M(1, 11), M(1, 12), M(1, 13), M(1, 14), M(1, 15), M(1, 16), M(1, 17), M(1, 18), M(1, 19), M(1, 20), M(1, 21), M(1, 22), M(1, 23), M(1, 24), M(1, 25), M(1, 26), M(1, 27), M(1, 28), M(1, 29), - M(2, 1), M(2, 2), M(2, 3), M(2, 4), M(2, 5), M(2, 6), M(2, 7), M(2, 8), M(2, 9), M(2, 10), M(2, 11), M(2, 12), M(2, 13), M(2, 14), M(2, 15), M(2, 16), M(2, 17), M(2, 18), M(2, 19), M(2, 20), M(2, 21), M(2, 22), M(2, 23), M(2, 24), M(2, 25), M(2, 26), M(2, 27), M(2, 28), M(2, 29), M(2, 30), M(2, 31), - M(3, 1), M(3, 2), M(3, 3), M(3, 4), M(3, 5), M(3, 6), M(3, 7), M(3, 8), M(3, 9), M(3, 10), M(3, 11), M(3, 12), M(3, 13), M(3, 14), M(3, 15), M(3, 16), M(3, 17), M(3, 18), M(3, 19), M(3, 20), M(3, 21), M(3, 22), M(3, 23), M(3, 24), M(3, 25), M(3, 26), M(3, 27), M(3, 28), M(3, 29), M(3, 30), - M(4, 1), M(4, 2), M(4, 3), M(4, 4), M(4, 5), M(4, 6), M(4, 7), M(4, 8), M(4, 9), M(4, 10), M(4, 11), M(4, 12), M(4, 13), M(4, 14), M(4, 15), M(4, 16), M(4, 17), M(4, 18), M(4, 19), M(4, 20), M(4, 21), M(4, 22), M(4, 23), M(4, 24), M(4, 25), M(4, 26), M(4, 27), M(4, 28), M(4, 29), M(4, 30), M(4, 31), - M(5, 1), M(5, 2), M(5, 3), M(5, 4), M(5, 5), M(5, 6), M(5, 7), M(5, 8), M(5, 9), M(5, 10), M(5, 11), M(5, 12), M(5, 13), M(5, 14), M(5, 15), M(5, 16), M(5, 17), M(5, 18), M(5, 19), M(5, 20), M(5, 21), M(5, 22), M(5, 23), M(5, 24), M(5, 25), M(5, 26), M(5, 27), M(5, 28), M(5, 29), M(5, 30), - M(6, 1), M(6, 2), M(6, 3), M(6, 4), M(6, 5), M(6, 6), M(6, 7), M(6, 8), M(6, 9), M(6, 10), M(6, 11), M(6, 12), M(6, 13), M(6, 14), M(6, 15), M(6, 16), M(6, 17), M(6, 18), M(6, 19), M(6, 20), M(6, 21), M(6, 22), M(6, 23), M(6, 24), M(6, 25), M(6, 26), M(6, 27), M(6, 28), M(6, 29), M(6, 30), M(6, 31), - M(7, 1), M(7, 2), M(7, 3), M(7, 4), M(7, 5), M(7, 6), M(7, 7), M(7, 8), M(7, 9), M(7, 10), M(7, 11), M(7, 12), M(7, 13), M(7, 14), M(7, 15), M(7, 16), M(7, 17), M(7, 18), M(7, 19), M(7, 20), M(7, 21), M(7, 22), M(7, 23), M(7, 24), M(7, 25), M(7, 26), M(7, 27), M(7, 28), M(7, 29), M(7, 30), M(7, 31), - M(8, 1), M(8, 2), M(8, 3), M(8, 4), M(8, 5), M(8, 6), M(8, 7), M(8, 8), M(8, 9), M(8, 10), M(8, 11), M(8, 12), M(8, 13), M(8, 14), M(8, 15), M(8, 16), M(8, 17), M(8, 18), M(8, 19), M(8, 20), M(8, 21), M(8, 22), M(8, 23), M(8, 24), M(8, 25), M(8, 26), M(8, 27), M(8, 28), M(8, 29), M(8, 30), - M(9, 1), M(9, 2), M(9, 3), M(9, 4), M(9, 5), M(9, 6), M(9, 7), M(9, 8), M(9, 9), M(9, 10), M(9, 11), M(9, 12), M(9, 13), M(9, 14), M(9, 15), M(9, 16), M(9, 17), M(9, 18), M(9, 19), M(9, 20), M(9, 21), M(9, 22), M(9, 23), M(9, 24), M(9, 25), M(9, 26), M(9, 27), M(9, 28), M(9, 29), M(9, 30), M(9, 31), - M(10, 1), M(10, 2), M(10, 3), M(10, 4), M(10, 5), M(10, 6), M(10, 7), M(10, 8), M(10, 9), M(10, 10), M(10, 11), M(10, 12), M(10, 13), M(10, 14), M(10, 15), M(10, 16), M(10, 17), M(10, 18), M(10, 19), M(10, 20), M(10, 21), M(10, 22), M(10, 23), M(10, 24), M(10, 25), M(10, 26), M(10, 27), M(10, 28), M(10, 29), M(10, 30), - M(11, 1), M(11, 2), M(11, 3), M(11, 4), M(11, 5), M(11, 6), M(11, 7), M(11, 8), M(11, 9), M(11, 10), M(11, 11), M(11, 12), M(11, 13), M(11, 14), M(11, 15), M(11, 16), M(11, 17), M(11, 18), M(11, 19), M(11, 20), M(11, 21), M(11, 22), M(11, 23), M(11, 24), M(11, 25), M(11, 26), M(11, 27), M(11, 28), M(11, 29), M(11, 30), M(11, 31), -}; -#undef M - -enum DaysTillMonth { - ACCUM_JAN = 0, - ACCUM_FEB = ACCUM_JAN + 31, - ACCUM_MAR = ACCUM_FEB + 29, - ACCUM_APR = ACCUM_MAR + 31, - ACCUM_MAY = ACCUM_APR + 30, - ACCUM_JUN = ACCUM_MAY + 31, - ACCUM_JUL = ACCUM_JUN + 30, - ACCUM_AUG = ACCUM_JUL + 31, - ACCUM_SEP = ACCUM_AUG + 31, - ACCUM_OCT = ACCUM_SEP + 30, - ACCUM_NOV = ACCUM_OCT + 31, - ACCUM_DEC = ACCUM_NOV + 30, -}; - -/** Number of days to pass from the first day in the year before reaching the first of a month. */ -static const uint16_t _accum_days_for_month[] = { - ACCUM_JAN, ACCUM_FEB, ACCUM_MAR, ACCUM_APR, - ACCUM_MAY, ACCUM_JUN, ACCUM_JUL, ACCUM_AUG, - ACCUM_SEP, ACCUM_OCT, ACCUM_NOV, ACCUM_DEC, -}; - -/** - * Converts a Date to a Year, Month & Day. - * @param date the date to convert from - * @returns YearMonthDay representation of the Date. - */ -/* static */ TimerGameCalendar::YearMonthDay TimerGameCalendar::ConvertDateToYMD(TimerGameCalendar::Date date) -{ - /* Year determination in multiple steps to account for leap - * years. First do the large steps, then the smaller ones. - */ - - /* There are 97 leap years in 400 years */ - TimerGameCalendar::Year yr = 400 * (date.base() / (CalendarTime::DAYS_IN_YEAR * 400 + 97)); - int rem = date.base() % (CalendarTime::DAYS_IN_YEAR * 400 + 97); - - if (rem >= CalendarTime::DAYS_IN_YEAR * 100 + 25) { - /* There are 25 leap years in the first 100 years after - * every 400th year, as every 400th year is a leap year */ - yr += 100; - rem -= CalendarTime::DAYS_IN_YEAR * 100 + 25; - - /* There are 24 leap years in the next couple of 100 years */ - yr += 100 * (rem / (CalendarTime::DAYS_IN_YEAR * 100 + 24)); - rem = (rem % (CalendarTime::DAYS_IN_YEAR * 100 + 24)); - } - - if (!TimerGameCalendar::IsLeapYear(yr) && rem >= CalendarTime::DAYS_IN_YEAR * 4) { - /* The first 4 year of the century are not always a leap year */ - yr += 4; - rem -= CalendarTime::DAYS_IN_YEAR * 4; - } - - /* There is 1 leap year every 4 years */ - yr += 4 * (rem / (CalendarTime::DAYS_IN_YEAR * 4 + 1)); - rem = rem % (CalendarTime::DAYS_IN_YEAR * 4 + 1); - - /* The last (max 3) years to account for; the first one - * can be, but is not necessarily a leap year */ - while (rem >= (TimerGameCalendar::IsLeapYear(yr) ? CalendarTime::DAYS_IN_LEAP_YEAR : CalendarTime::DAYS_IN_YEAR)) { - rem -= TimerGameCalendar::IsLeapYear(yr) ? CalendarTime::DAYS_IN_LEAP_YEAR : CalendarTime::DAYS_IN_YEAR; - yr++; - } - - /* Skip the 29th of February in non-leap years */ - if (!TimerGameCalendar::IsLeapYear(yr) && rem >= ACCUM_MAR - 1) rem++; - - uint16_t x = _month_date_from_year_day[rem]; - - YearMonthDay ymd; - ymd.year = yr; - ymd.month = x >> 5; - ymd.day = x & 0x1F; - return ymd; -} - -/** - * Converts a tuple of Year, Month and Day to a Date. - * @param year is a number between 0..MAX_YEAR - * @param month is a number between 0..11 - * @param day is a number between 1..31 - */ -/* static */ TimerGameCalendar::Date TimerGameCalendar::ConvertYMDToDate(TimerGameCalendar::Year year, TimerGameCalendar::Month month, TimerGameCalendar::Day day) -{ - /* Day-offset in a leap year */ - int days = _accum_days_for_month[month] + day - 1; - - /* Account for the missing of the 29th of February in non-leap years */ - if (!TimerGameCalendar::IsLeapYear(year) && days >= ACCUM_MAR) days--; - - return TimerGameCalendar::DateAtStartOfYear(year) + days; -} - -/** - * Checks whether the given year is a leap year or not. - * @param yr The year to check. - * @return True if \c yr is a leap year, otherwise false. - */ -/* static */ bool TimerGameCalendar::IsLeapYear(TimerGameCalendar::Year yr) -{ - return yr.base() % 4 == 0 && (yr.base() % 100 != 0 || yr.base() % 400 == 0); -} - /** * Set the date. * @param date New date @@ -213,22 +101,10 @@ void TimerManager::El timer->Elapsed(TimerGameCalendar::DAY); } - if ((TimerGameCalendar::date.base() % 7) == 3) { - for (auto timer : timers) { - timer->Elapsed(TimerGameCalendar::WEEK); - } - } - if (new_month) { for (auto timer : timers) { timer->Elapsed(TimerGameCalendar::MONTH); } - - if ((TimerGameCalendar::month % 3) == 0) { - for (auto timer : timers) { - timer->Elapsed(TimerGameCalendar::QUARTER); - } - } } if (new_year) { @@ -244,8 +120,6 @@ void TimerManager::El TimerGameCalendar::year--; days_this_year = TimerGameCalendar::IsLeapYear(TimerGameCalendar::year) ? CalendarTime::DAYS_IN_LEAP_YEAR : CalendarTime::DAYS_IN_YEAR; TimerGameCalendar::date -= days_this_year; - for (Vehicle *v : Vehicle::Iterate()) v->ShiftDates(-days_this_year); - for (LinkGraph *lg : LinkGraph::Iterate()) lg->ShiftDates(-days_this_year); } } diff --git a/src/timer/timer_game_calendar.h b/src/timer/timer_game_calendar.h --- a/src/timer/timer_game_calendar.h +++ b/src/timer/timer_game_calendar.h @@ -12,6 +12,7 @@ #include "../stdafx.h" #include "../core/strong_typedef_type.hpp" +#include "timer_game_common.h" /** * Timer that is increased every 27ms, and counts towards ticks / days / months / years. @@ -19,173 +20,26 @@ * The amount of days in a month depends on the month and year (leap-years). * There are always 74 ticks in a day (and with 27ms, this makes 1 day 1.998 seconds). * - * IntervalTimer and TimeoutTimer based on this Timer are a bit unusual, as their count is always one. - * You create those timers based on a transition: a new day, a new month or a new year. - * - * Additionally, you need to set a priority. To ensure deterministic behaviour, events are executed - * in priority. It is important that if you assign NONE, you do not use Random() in your callback. - * Other than that, make sure you only set one callback per priority. - * - * For example: - * IntervalTimer({TimerGameCalendar::DAY, TimerGameCalendar::Priority::NONE}, [](uint count){}); - * - * @note Callbacks are executed in the game-thread. + * Calendar time is used for technology and time-of-year changes, including: + * - Vehicle, airport, station, object introduction and obsolescence + * - NewGRF variables for visual styles or behavior based on year or time of year (e.g. variable snow line) + * - Inflation, since it is tied to original game years. One interpretation of inflation is that it compensates for faster and higher capacity vehicles, + * 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 + * so we follow a previous decision to tie inflation to original TTD game years. */ -class TimerGameCalendar { +class TimerGameCalendar : public TimerGame { public: - /** The type to store our dates in. */ - using Date = StrongType::Typedef; - - /** The fraction of a date we're in, i.e. the number of ticks since the last date changeover. */ - using DateFract = uint16_t; - - /** Type for the year, note: 0 based, i.e. starts at the year 0. */ - using Year = StrongType::Typedef; - /** Type for the month, note: 0 based, i.e. 0 = January, 11 = December. */ - using Month = uint8_t; - /** Type for the day of the month, note: 1 based, first day of a month is 1. */ - using Day = uint8_t; - - /** - * Data structure to convert between Date and triplet (year, month, and day). - * @see TimerGameCalendar::ConvertDateToYMD(), TimerGameCalendar::ConvertYMDToDate() - */ - struct YearMonthDay { - Year year; ///< Year (0...) - Month month; ///< Month (0..11) - Day day; ///< Day (1..31) - }; - - enum Trigger { - DAY, - WEEK, - MONTH, - QUARTER, - YEAR, - }; - enum Priority { - NONE, ///< These timers can be executed in any order; there is no Random() in them, so order is not relevant. - - /* All other may have a Random() call in them, so order is important. - * For safety, you can only setup a single timer on a single priority. */ - COMPANY, - DISASTER, - ENGINE, - INDUSTRY, - STATION, - SUBSIDY, - TOWN, - VEHICLE, - }; - - struct TPeriod { - Trigger trigger; - Priority priority; - - TPeriod(Trigger trigger, Priority priority) : trigger(trigger), priority(priority) {} - - bool operator < (const TPeriod &other) const - { - if (this->trigger != other.trigger) return this->trigger < other.trigger; - return this->priority < other.priority; - } - - bool operator == (const TPeriod &other) const - { - return this->trigger == other.trigger && this->priority == other.priority; - } - }; - - using TElapsed = uint; - struct TStorage { - }; - - static bool IsLeapYear(Year yr); - static YearMonthDay ConvertDateToYMD(Date date); - static Date ConvertYMDToDate(Year year, Month month, Day day); - static void SetDate(Date date, DateFract fract); - - /** - * Calculate the year of a given date. - * @param date The date to consider. - * @return the year. - */ - static constexpr Year DateToYear(Date date) - { - /* Hardcode the number of days in a year because we can't access CalendarTime from here. */ - return date.base() / 366; - } - - /** - * Calculate the date of the first day of a given year. - * @param year the year to get the first day of. - * @return the date. - */ - static constexpr Date DateAtStartOfYear(Year year) - { - int32_t year_as_int = year.base(); - uint number_of_leap_years = (year == 0) ? 0 : ((year_as_int - 1) / 4 - (year_as_int - 1) / 100 + (year_as_int - 1) / 400 + 1); - - /* Hardcode the number of days in a year because we can't access CalendarTime from here. */ - return (365 * year_as_int) + number_of_leap_years; - } - static Year year; ///< Current year, starting at 0. 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 void SetDate(Date date, DateFract fract); }; /** * Storage class for Calendar time constants. */ -class CalendarTime { -public: - static constexpr int DAYS_IN_YEAR = 365; ///< days per year - static constexpr int DAYS_IN_LEAP_YEAR = 366; ///< sometimes, you need one day more... - static constexpr int MONTHS_IN_YEAR = 12; ///< months per year - - static constexpr int SECONDS_PER_DAY = 2; ///< approximate seconds per day, not for precise calculations - - /* - * ORIGINAL_BASE_YEAR, ORIGINAL_MAX_YEAR and DAYS_TILL_ORIGINAL_BASE_YEAR are - * primarily used for loading newgrf and savegame data and returning some - * newgrf (callback) functions that were in the original (TTD) inherited - * format, where 'TimerGameCalendar::date == 0' meant that it was 1920-01-01. - */ - - /** The minimum starting year/base year of the original TTD */ - static constexpr TimerGameCalendar::Year ORIGINAL_BASE_YEAR = 1920; - /** The original ending year */ - static constexpr TimerGameCalendar::Year ORIGINAL_END_YEAR = 2051; - /** The maximum year of the original TTD */ - static constexpr TimerGameCalendar::Year ORIGINAL_MAX_YEAR = 2090; - - /** The absolute minimum & maximum years in OTTD */ - static constexpr TimerGameCalendar::Year MIN_YEAR = 0; - - /** The default starting year */ - static constexpr TimerGameCalendar::Year DEF_START_YEAR = 1950; - /** The default scoring end year */ - static constexpr TimerGameCalendar::Year DEF_END_YEAR = ORIGINAL_END_YEAR - 1; - - /** - * MAX_YEAR, nicely rounded value of the number of years that can - * be encoded in a single 32 bits date, about 2^31 / 366 years. - */ - static constexpr TimerGameCalendar::Year MAX_YEAR = 5000000; - - /** The date of the first day of the original base year. */ - static constexpr TimerGameCalendar::Date DAYS_TILL_ORIGINAL_BASE_YEAR = TimerGameCalendar::DateAtStartOfYear(ORIGINAL_BASE_YEAR); - - /** The absolute minimum date. */ - static constexpr TimerGameCalendar::Date MIN_DATE = 0; - - /** The date of the last day of the max year. */ - static constexpr TimerGameCalendar::Date MAX_DATE = TimerGameCalendar::DateAtStartOfYear(CalendarTime::MAX_YEAR + 1) - 1; - - static constexpr TimerGameCalendar::Year INVALID_YEAR = -1; ///< Representation of an invalid year - static constexpr TimerGameCalendar::Date INVALID_DATE = -1; ///< Representation of an invalid date -}; +class CalendarTime : public TimerGameConst {}; #endif /* TIMER_GAME_CALENDAR_H */ diff --git a/src/timer/timer_game_common.cpp b/src/timer/timer_game_common.cpp new file mode 100644 --- /dev/null +++ b/src/timer/timer_game_common.cpp @@ -0,0 +1,140 @@ +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/** + * @file timer_game_common.cpp + * This file implements the common timer logic for the game-calendar timers. + */ + +#include "../stdafx.h" + +#include "timer_game_common.h" +#include "timer_game_calendar.h" +#include "timer_game_economy.h" + +#include "../safeguards.h" + +#define M(a, b) ((a << 5) | b) +static constexpr uint16_t _month_date_from_year_day[] = { + M(0, 1), M(0, 2), M(0, 3), M(0, 4), M(0, 5), M(0, 6), M(0, 7), M(0, 8), M(0, 9), M(0, 10), M(0, 11), M(0, 12), M(0, 13), M(0, 14), M(0, 15), M(0, 16), M(0, 17), M(0, 18), M(0, 19), M(0, 20), M(0, 21), M(0, 22), M(0, 23), M(0, 24), M(0, 25), M(0, 26), M(0, 27), M(0, 28), M(0, 29), M(0, 30), M(0, 31), + M(1, 1), M(1, 2), M(1, 3), M(1, 4), M(1, 5), M(1, 6), M(1, 7), M(1, 8), M(1, 9), M(1, 10), M(1, 11), M(1, 12), M(1, 13), M(1, 14), M(1, 15), M(1, 16), M(1, 17), M(1, 18), M(1, 19), M(1, 20), M(1, 21), M(1, 22), M(1, 23), M(1, 24), M(1, 25), M(1, 26), M(1, 27), M(1, 28), M(1, 29), + M(2, 1), M(2, 2), M(2, 3), M(2, 4), M(2, 5), M(2, 6), M(2, 7), M(2, 8), M(2, 9), M(2, 10), M(2, 11), M(2, 12), M(2, 13), M(2, 14), M(2, 15), M(2, 16), M(2, 17), M(2, 18), M(2, 19), M(2, 20), M(2, 21), M(2, 22), M(2, 23), M(2, 24), M(2, 25), M(2, 26), M(2, 27), M(2, 28), M(2, 29), M(2, 30), M(2, 31), + M(3, 1), M(3, 2), M(3, 3), M(3, 4), M(3, 5), M(3, 6), M(3, 7), M(3, 8), M(3, 9), M(3, 10), M(3, 11), M(3, 12), M(3, 13), M(3, 14), M(3, 15), M(3, 16), M(3, 17), M(3, 18), M(3, 19), M(3, 20), M(3, 21), M(3, 22), M(3, 23), M(3, 24), M(3, 25), M(3, 26), M(3, 27), M(3, 28), M(3, 29), M(3, 30), + M(4, 1), M(4, 2), M(4, 3), M(4, 4), M(4, 5), M(4, 6), M(4, 7), M(4, 8), M(4, 9), M(4, 10), M(4, 11), M(4, 12), M(4, 13), M(4, 14), M(4, 15), M(4, 16), M(4, 17), M(4, 18), M(4, 19), M(4, 20), M(4, 21), M(4, 22), M(4, 23), M(4, 24), M(4, 25), M(4, 26), M(4, 27), M(4, 28), M(4, 29), M(4, 30), M(4, 31), + M(5, 1), M(5, 2), M(5, 3), M(5, 4), M(5, 5), M(5, 6), M(5, 7), M(5, 8), M(5, 9), M(5, 10), M(5, 11), M(5, 12), M(5, 13), M(5, 14), M(5, 15), M(5, 16), M(5, 17), M(5, 18), M(5, 19), M(5, 20), M(5, 21), M(5, 22), M(5, 23), M(5, 24), M(5, 25), M(5, 26), M(5, 27), M(5, 28), M(5, 29), M(5, 30), + M(6, 1), M(6, 2), M(6, 3), M(6, 4), M(6, 5), M(6, 6), M(6, 7), M(6, 8), M(6, 9), M(6, 10), M(6, 11), M(6, 12), M(6, 13), M(6, 14), M(6, 15), M(6, 16), M(6, 17), M(6, 18), M(6, 19), M(6, 20), M(6, 21), M(6, 22), M(6, 23), M(6, 24), M(6, 25), M(6, 26), M(6, 27), M(6, 28), M(6, 29), M(6, 30), M(6, 31), + M(7, 1), M(7, 2), M(7, 3), M(7, 4), M(7, 5), M(7, 6), M(7, 7), M(7, 8), M(7, 9), M(7, 10), M(7, 11), M(7, 12), M(7, 13), M(7, 14), M(7, 15), M(7, 16), M(7, 17), M(7, 18), M(7, 19), M(7, 20), M(7, 21), M(7, 22), M(7, 23), M(7, 24), M(7, 25), M(7, 26), M(7, 27), M(7, 28), M(7, 29), M(7, 30), M(7, 31), + M(8, 1), M(8, 2), M(8, 3), M(8, 4), M(8, 5), M(8, 6), M(8, 7), M(8, 8), M(8, 9), M(8, 10), M(8, 11), M(8, 12), M(8, 13), M(8, 14), M(8, 15), M(8, 16), M(8, 17), M(8, 18), M(8, 19), M(8, 20), M(8, 21), M(8, 22), M(8, 23), M(8, 24), M(8, 25), M(8, 26), M(8, 27), M(8, 28), M(8, 29), M(8, 30), + M(9, 1), M(9, 2), M(9, 3), M(9, 4), M(9, 5), M(9, 6), M(9, 7), M(9, 8), M(9, 9), M(9, 10), M(9, 11), M(9, 12), M(9, 13), M(9, 14), M(9, 15), M(9, 16), M(9, 17), M(9, 18), M(9, 19), M(9, 20), M(9, 21), M(9, 22), M(9, 23), M(9, 24), M(9, 25), M(9, 26), M(9, 27), M(9, 28), M(9, 29), M(9, 30), M(9, 31), + M(10, 1), M(10, 2), M(10, 3), M(10, 4), M(10, 5), M(10, 6), M(10, 7), M(10, 8), M(10, 9), M(10, 10), M(10, 11), M(10, 12), M(10, 13), M(10, 14), M(10, 15), M(10, 16), M(10, 17), M(10, 18), M(10, 19), M(10, 20), M(10, 21), M(10, 22), M(10, 23), M(10, 24), M(10, 25), M(10, 26), M(10, 27), M(10, 28), M(10, 29), M(10, 30), + M(11, 1), M(11, 2), M(11, 3), M(11, 4), M(11, 5), M(11, 6), M(11, 7), M(11, 8), M(11, 9), M(11, 10), M(11, 11), M(11, 12), M(11, 13), M(11, 14), M(11, 15), M(11, 16), M(11, 17), M(11, 18), M(11, 19), M(11, 20), M(11, 21), M(11, 22), M(11, 23), M(11, 24), M(11, 25), M(11, 26), M(11, 27), M(11, 28), M(11, 29), M(11, 30), M(11, 31), +}; +#undef M + +enum DaysTillMonth { + ACCUM_JAN = 0, + ACCUM_FEB = ACCUM_JAN + 31, + ACCUM_MAR = ACCUM_FEB + 29, + ACCUM_APR = ACCUM_MAR + 31, + ACCUM_MAY = ACCUM_APR + 30, + ACCUM_JUN = ACCUM_MAY + 31, + ACCUM_JUL = ACCUM_JUN + 30, + ACCUM_AUG = ACCUM_JUL + 31, + ACCUM_SEP = ACCUM_AUG + 31, + ACCUM_OCT = ACCUM_SEP + 30, + ACCUM_NOV = ACCUM_OCT + 31, + ACCUM_DEC = ACCUM_NOV + 30, +}; + +/** Number of days to pass from the first day in the year before reaching the first of a month. */ +static constexpr uint16_t _accum_days_for_month[] = { + ACCUM_JAN, ACCUM_FEB, ACCUM_MAR, ACCUM_APR, + ACCUM_MAY, ACCUM_JUN, ACCUM_JUL, ACCUM_AUG, + ACCUM_SEP, ACCUM_OCT, ACCUM_NOV, ACCUM_DEC, +}; + +/** + * Converts a Date to a Year, Month & Day. + * @param date the date to convert from + * @returns YearMonthDay representation of the Date. + */ +template +/* static */ typename TimerGame::YearMonthDay TimerGame::ConvertDateToYMD(Date date) +{ + /* Year determination in multiple steps to account for leap + * years. First do the large steps, then the smaller ones. + */ + + /* There are 97 leap years in 400 years */ + Year yr = 400 * (date.base() / (TimerGameConst::DAYS_IN_YEAR * 400 + 97)); + int rem = date.base() % (TimerGameConst::DAYS_IN_YEAR * 400 + 97); + + if (rem >= TimerGameConst::DAYS_IN_YEAR * 100 + 25) { + /* There are 25 leap years in the first 100 years after + * every 400th year, as every 400th year is a leap year */ + yr += 100; + rem -= TimerGameConst::DAYS_IN_YEAR * 100 + 25; + + /* There are 24 leap years in the next couple of 100 years */ + yr += 100 * (rem / (TimerGameConst::DAYS_IN_YEAR * 100 + 24)); + rem = (rem % (TimerGameConst::DAYS_IN_YEAR * 100 + 24)); + } + + if (!IsLeapYear(yr) && rem >= TimerGameConst::DAYS_IN_YEAR * 4) { + /* The first 4 year of the century are not always a leap year */ + yr += 4; + rem -= TimerGameConst::DAYS_IN_YEAR * 4; + } + + /* There is 1 leap year every 4 years */ + yr += 4 * (rem / (TimerGameConst::DAYS_IN_YEAR * 4 + 1)); + rem = rem % (TimerGameConst::DAYS_IN_YEAR * 4 + 1); + + /* The last (max 3) years to account for; the first one + * can be, but is not necessarily a leap year */ + while (rem >= (IsLeapYear(yr) ? TimerGameConst::DAYS_IN_LEAP_YEAR : TimerGameConst::DAYS_IN_YEAR)) { + rem -= IsLeapYear(yr) ? TimerGameConst::DAYS_IN_LEAP_YEAR : TimerGameConst::DAYS_IN_YEAR; + yr++; + } + + /* Skip the 29th of February in non-leap years */ + if (!IsLeapYear(yr) && rem >= ACCUM_MAR - 1) rem++; + + uint16_t x = _month_date_from_year_day[rem]; + + YearMonthDay ymd; + ymd.year = yr; + ymd.month = x >> 5; + ymd.day = x & 0x1F; + return ymd; +} + +/** + * Converts a tuple of Year, Month and Day to a Date. + * @param year is a number between 0..MAX_YEAR + * @param month is a number between 0..11 + * @param day is a number between 1..31 + */ +template +/* static */ typename TimerGame::Date TimerGame::ConvertYMDToDate(Year year, Month month, Day day) +{ + /* Day-offset in a leap year */ + int days = _accum_days_for_month[month] + day - 1; + + /* Account for the missing of the 29th of February in non-leap years */ + if (!IsLeapYear(year) && days >= ACCUM_MAR) days--; + + return DateAtStartOfYear(year) + days; +} + +/* Create instances of the two template variants that we have. + * This is needed, as this templated functions are not in a header-file. */ +template TimerGame::YearMonthDay TimerGame::ConvertDateToYMD(Date date); +template TimerGame::YearMonthDay TimerGame::ConvertDateToYMD(Date date); + +template TimerGame::Date TimerGame::ConvertYMDToDate(Year year, Month month, Day day); +template TimerGame::Date TimerGame::ConvertYMDToDate(Year year, Month month, Day day); diff --git a/src/timer/timer_game_common.h b/src/timer/timer_game_common.h new file mode 100644 --- /dev/null +++ b/src/timer/timer_game_common.h @@ -0,0 +1,196 @@ +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/** @file timer_game_common.h Definition of the common class inherited by both calendar and economy timers. */ + +#ifndef TIMER_GAME_COMMON_H +#define TIMER_GAME_COMMON_H + +#include "../core/strong_typedef_type.hpp" + +/** + * Template class for all TimerGame based timers. As Calendar and Economy are very similar, this class is used to share code between them. + * + * IntervalTimer and TimeoutTimer based on this Timer are a bit unusual, as their count is always one. + * You create those timers based on a transition: a new day, a new month or a new year. + * + * Additionally, you need to set a priority. To ensure deterministic behaviour, events are executed + * in priority. It is important that if you assign NONE, you do not use Random() in your callback. + * Other than that, make sure you only set one callback per priority. + * + * For example: + * IntervalTimer({TimerGameCalendar::DAY, TimerGameCalendar::Priority::NONE}, [](uint count){}); + * + * @note Callbacks are executed in the game-thread. + */ +template +class TimerGame { +public: + /** The type to store our dates in. */ + template struct DateTag; + using Date = StrongType::Typedef, StrongType::Compare, StrongType::Integer>; + + /** The fraction of a date we're in, i.e. the number of ticks since the last date changeover. */ + using DateFract = uint16_t; + + /** Type for the year, note: 0 based, i.e. starts at the year 0. */ + template struct YearTag; + using Year = StrongType::Typedef, StrongType::Compare, StrongType::Integer>; + /** Type for the month, note: 0 based, i.e. 0 = January, 11 = December. */ + using Month = uint8_t; + /** Type for the day of the month, note: 1 based, first day of a month is 1. */ + using Day = uint8_t; + + /** + * Data structure to convert between Date and triplet (year, month, and day). + * @see ConvertDateToYMD(), ConvertYMDToDate() + */ + struct YearMonthDay { + Year year; ///< Year (0...) + Month month; ///< Month (0..11) + Day day; ///< Day (1..31) + }; + + /** + * Checks whether the given year is a leap year or not. + * @param year The year to check. + * @return True if \c year is a leap year, otherwise false. + */ + static constexpr bool IsLeapYear(Year year) + { + int32_t year_as_int = year.base(); + return year_as_int % 4 == 0 && (year_as_int % 100 != 0 || year_as_int % 400 == 0); + } + + static YearMonthDay ConvertDateToYMD(Date date); + static Date ConvertYMDToDate(Year year, Month month, Day day); + + /** + * Calculate the year of a given date. + * @param date The date to consider. + * @return the year. + */ + static constexpr Year DateToYear(Date date) + { + /* Hardcode the number of days in a year because we can't access CalendarTime from here. */ + return date.base() / 366; + } + + /** + * Calculate the date of the first day of a given year. + * @param year the year to get the first day of. + * @return the date. + */ + static constexpr Date DateAtStartOfYear(Year year) + { + int32_t year_as_int = year.base(); + uint number_of_leap_years = (year == 0) ? 0 : ((year_as_int - 1) / 4 - (year_as_int - 1) / 100 + (year_as_int - 1) / 400 + 1); + + /* Hardcode the number of days in a year because we can't access CalendarTime from here. */ + return (365 * year_as_int) + number_of_leap_years; + } + + enum Trigger { + DAY, + WEEK, + MONTH, + QUARTER, + YEAR, + }; + + enum Priority { + NONE, ///< These timers can be executed in any order; there is no Random() in them, so order is not relevant. + + /* All other may have a Random() call in them, so order is important. + * For safety, you can only setup a single timer on a single priority. */ + COMPANY, + DISASTER, + ENGINE, + INDUSTRY, + STATION, + SUBSIDY, + TOWN, + VEHICLE, + }; + + struct TPeriod { + Trigger trigger; + Priority priority; + + TPeriod(Trigger trigger, Priority priority) : trigger(trigger), priority(priority) + {} + + bool operator < (const TPeriod &other) const + { + if (this->trigger != other.trigger) return this->trigger < other.trigger; + return this->priority < other.priority; + } + + bool operator == (const TPeriod &other) const + { + return this->trigger == other.trigger && this->priority == other.priority; + } + }; + + using TElapsed = uint; + struct TStorage {}; +}; + +/** + * Template class for time constants shared by both Calendar and Economy time. + */ +template +class TimerGameConst { +public: + static constexpr int DAYS_IN_YEAR = 365; ///< days per year + static constexpr int DAYS_IN_LEAP_YEAR = 366; ///< sometimes, you need one day more... + static constexpr int MONTHS_IN_YEAR = 12; ///< months per year + + static constexpr int SECONDS_PER_DAY = 2; ///< approximate seconds per day, not for precise calculations + + /* + * ORIGINAL_BASE_YEAR, ORIGINAL_MAX_YEAR and DAYS_TILL_ORIGINAL_BASE_YEAR are + * primarily used for loading newgrf and savegame data and returning some + * newgrf (callback) functions that were in the original (TTD) inherited + * format, where 'TimerGame::date == 0' meant that it was 1920-01-01. + */ + + /** The minimum starting year/base year of the original TTD */ + static constexpr typename TimerGame::Year ORIGINAL_BASE_YEAR = 1920; + /** The original ending year */ + static constexpr typename TimerGame::Year ORIGINAL_END_YEAR = 2051; + /** The maximum year of the original TTD */ + static constexpr typename TimerGame::Year ORIGINAL_MAX_YEAR = 2090; + + /** + * MAX_YEAR, nicely rounded value of the number of years that can + * be encoded in a single 32 bits date, about 2^31 / 366 years. + */ + static constexpr typename TimerGame::Year MAX_YEAR = 5000000; + + /** The absolute minimum year in OTTD */ + static constexpr typename TimerGame::Year MIN_YEAR = 0; + + /** The default starting year */ + static constexpr typename TimerGame::Year DEF_START_YEAR = 1950; + /** The default scoring end year */ + static constexpr typename TimerGame::Year DEF_END_YEAR = ORIGINAL_END_YEAR - 1; + + /** The date of the first day of the original base year. */ + static constexpr typename TimerGame::Date DAYS_TILL_ORIGINAL_BASE_YEAR = TimerGame::DateAtStartOfYear(ORIGINAL_BASE_YEAR); + + /** The date of the last day of the max year. */ + static constexpr typename TimerGame::Date MAX_DATE = TimerGame::DateAtStartOfYear(MAX_YEAR + 1) - 1; + + /** The date on January 1, year 0. */ + static constexpr typename TimerGame::Date MIN_DATE = 0; + + static constexpr typename TimerGame::Year INVALID_YEAR = -1; ///< Representation of an invalid year + static constexpr typename TimerGame::Date INVALID_DATE = -1; ///< Representation of an invalid date +}; + +#endif /* TIMER_GAME_COMMON_H */ diff --git a/src/timer/timer_game_economy.cpp b/src/timer/timer_game_economy.cpp new file mode 100644 --- /dev/null +++ b/src/timer/timer_game_economy.cpp @@ -0,0 +1,160 @@ +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/** + * @file timer_game_economy.cpp + * This file implements the timer logic for the game-economy-timer. + */ + +/** + * Economy time is used for the regular pace of the game, including: + * - Industry and house production/consumption + * - Industry production changes, closure, and spawning + * - Town growth + * - Company age and financial statistics + * - Vehicle financial statistics + * - Vehicle aging, depreciation, reliability, and renewal + * - Payment intervals for running and maintenance costs, loan interest, etc. + * - Cargo payment "time" calculation + * - Local authority and station ratings change intervals + */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "timer.h" +#include "timer_game_economy.h" +#include "timer_game_tick.h" +#include "../vehicle_base.h" +#include "../linkgraph/linkgraph.h" + +#include "../safeguards.h" + +TimerGameEconomy::Year TimerGameEconomy::year = {}; +TimerGameEconomy::Month TimerGameEconomy::month = {}; +TimerGameEconomy::Date TimerGameEconomy::date = {}; +TimerGameEconomy::DateFract TimerGameEconomy::date_fract = {}; + +/** + * Set the date. + * @param date The new date + * @param fract The number of ticks that have passed on this date. + */ +/* static */ void TimerGameEconomy::SetDate(TimerGameEconomy::Date date, TimerGameEconomy::DateFract fract) +{ + assert(fract < Ticks::DAY_TICKS); + + TimerGameEconomy::date = date; + TimerGameEconomy::date_fract = fract; + TimerGameEconomy::YearMonthDay ymd = TimerGameEconomy::ConvertDateToYMD(date); + TimerGameEconomy::year = ymd.year; + TimerGameEconomy::month = ymd.month; +} + +template<> +void IntervalTimer::Elapsed(TimerGameEconomy::TElapsed trigger) +{ + if (trigger == this->period.trigger) { + this->callback(1); + } +} + +template<> +void TimeoutTimer::Elapsed(TimerGameEconomy::TElapsed trigger) +{ + if (this->fired) return; + + if (trigger == this->period.trigger) { + this->callback(); + this->fired = true; + } +} + +template<> +void TimerManager::Elapsed([[maybe_unused]] TimerGameEconomy::TElapsed delta) +{ + assert(delta == 1); + + if (_game_mode == GM_MENU) return; + + TimerGameEconomy::date_fract++; + if (TimerGameEconomy::date_fract < Ticks::DAY_TICKS) return; + TimerGameEconomy::date_fract = 0; + + /* increase day counter */ + TimerGameEconomy::date++; + + TimerGameEconomy::YearMonthDay ymd = TimerGameEconomy::ConvertDateToYMD(TimerGameEconomy::date); + + /* check if we entered a new month? */ + bool new_month = ymd.month != TimerGameEconomy::month; + + /* check if we entered a new year? */ + bool new_year = ymd.year != TimerGameEconomy::year; + + /* update internal variables before calling the daily/monthly/yearly loops */ + TimerGameEconomy::month = ymd.month; + TimerGameEconomy::year = ymd.year; + + /* Make a temporary copy of the timers, as a timer's callback might add/remove other timers. */ + auto timers = TimerManager::GetTimers(); + + for (auto timer : timers) { + timer->Elapsed(TimerGameEconomy::DAY); + } + + if ((TimerGameEconomy::date.base() % 7) == 3) { + for (auto timer : timers) { + timer->Elapsed(TimerGameEconomy::WEEK); + } + } + + if (new_month) { + for (auto timer : timers) { + timer->Elapsed(TimerGameEconomy::MONTH); + } + + if ((TimerGameEconomy::month % 3) == 0) { + for (auto timer : timers) { + timer->Elapsed(TimerGameEconomy::QUARTER); + } + } + } + + if (new_year) { + for (auto timer : timers) { + timer->Elapsed(TimerGameEconomy::YEAR); + } + } + + /* check if we reached the maximum year, decrement dates by a year */ + if (TimerGameEconomy::year == EconomyTime::MAX_YEAR + 1) { + int days_this_year; + + TimerGameEconomy::year--; + days_this_year = TimerGameEconomy::IsLeapYear(TimerGameEconomy::year) ? EconomyTime::DAYS_IN_LEAP_YEAR : EconomyTime::DAYS_IN_YEAR; + TimerGameEconomy::date -= days_this_year; + for (Vehicle *v : Vehicle::Iterate()) v->ShiftDates(-days_this_year); + for (LinkGraph *lg : LinkGraph::Iterate()) lg->ShiftDates(-days_this_year); + } +} + +#ifdef WITH_ASSERT +template<> +void TimerManager::Validate(TimerGameEconomy::TPeriod period) +{ + if (period.priority == TimerGameEconomy::Priority::NONE) return; + + /* Validate we didn't make a developer error and scheduled more than one + * entry on the same priority/trigger. There can only be one timer on + * a specific trigger/priority, to ensure we are deterministic. */ + for (const auto &timer : TimerManager::GetTimers()) { + if (timer->period.trigger != period.trigger) continue; + + assert(timer->period.priority != period.priority); + } +} +#endif /* WITH_ASSERT */ diff --git a/src/timer/timer_game_economy.h b/src/timer/timer_game_economy.h new file mode 100644 --- /dev/null +++ b/src/timer/timer_game_economy.h @@ -0,0 +1,48 @@ +/* + * This file is part of OpenTTD. + * 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. + * 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. + * 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 . + */ + +/** @file timer_game_economy.h Definition of the game-economy-timer */ + +#ifndef TIMER_GAME_ECONOMY_H +#define TIMER_GAME_ECONOMY_H + +#include "../core/strong_typedef_type.hpp" +#include "timer_game_common.h" + +/** + * Timer that is increased every 27ms, and counts towards economy time units, expressed in days / months / years. + * + * For now, this is kept in sync with the calendar date, so the amount of days in a month depends on the month and year (leap-years). + * There are always 74 ticks in a day (and with 27ms, this makes 1 day 1.998 seconds). + * + * Economy time is used for the regular pace of the game, including: + * - Industry and house production/consumption + * - Industry production changes, closure, and spawning + * - Town growth + * - Company age and periodical finance stats + * - Vehicle age and profit statistics, both individual and group + * - Vehicle aging, depreciation, reliability, and renewal + * - Payment intervals for running and maintenance costs, loan interest, etc. + * - Cargo payment "time" calculation + * - Local authority and station ratings change intervals + */ +class TimerGameEconomy : public TimerGame { +public: + static Year year; ///< Current year, starting at 0. + 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 void SetDate(Date date, DateFract fract); +}; + +/** + * Storage class for Economy time constants. + */ +class EconomyTime : public TimerGameConst {}; + +#endif /* TIMER_GAME_ECONOMY_H */ diff --git a/src/timetable.h b/src/timetable.h --- a/src/timetable.h +++ b/src/timetable.h @@ -11,10 +11,10 @@ #define TIMETABLE_H #include "timer/timer_game_tick.h" -#include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "vehicle_type.h" -static const TimerGameCalendar::Year MAX_TIMETABLE_START_YEARS = 15; ///< The maximum start date offset, in years. +static const TimerGameEconomy::Year MAX_TIMETABLE_START_YEARS = 15; ///< The maximum start date offset, in economy years. enum class TimetableMode : uint8_t { Days, @@ -22,8 +22,8 @@ enum class TimetableMode : uint8_t { Ticks, }; -TimerGameTick::TickCounter GetStartTickFromDate(TimerGameCalendar::Date start_date); -TimerGameCalendar::Date GetDateFromStartTick(TimerGameTick::TickCounter start_tick); +TimerGameTick::TickCounter GetStartTickFromDate(TimerGameEconomy::Date start_date); +TimerGameEconomy::Date GetDateFromStartTick(TimerGameTick::TickCounter start_tick); void ShowTimetableWindow(const Vehicle *v); void UpdateVehicleTimetable(Vehicle *v, bool travelling); diff --git a/src/timetable_cmd.cpp b/src/timetable_cmd.cpp --- a/src/timetable_cmd.cpp +++ b/src/timetable_cmd.cpp @@ -11,7 +11,7 @@ #include "command_func.h" #include "company_func.h" #include "timer/timer_game_tick.h" -#include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "window_func.h" #include "vehicle_base.h" #include "timetable_cmd.h" @@ -26,13 +26,13 @@ * @param start_date The date when the timetable starts. * @return The first tick of this date. */ -TimerGameTick::TickCounter GetStartTickFromDate(TimerGameCalendar::Date start_date) +TimerGameTick::TickCounter GetStartTickFromDate(TimerGameEconomy::Date start_date) { /* Calculate the offset in ticks from the current date. */ - TimerGameTick::Ticks tick_offset = (start_date - TimerGameCalendar::date).base() * Ticks::DAY_TICKS; + TimerGameTick::Ticks tick_offset = (start_date - TimerGameEconomy::date).base() * Ticks::DAY_TICKS; /* Compensate for the current date_fract. */ - tick_offset -= TimerGameCalendar::date_fract; + tick_offset -= TimerGameEconomy::date_fract; /* Return the current tick plus the offset. */ return TimerGameTick::counter + tick_offset; @@ -43,16 +43,16 @@ TimerGameTick::TickCounter GetStartTickF * @param start_tick The TimerGameTick::TickCounter when the timetable starts. * @return The date when we reach this tick. */ -TimerGameCalendar::Date GetDateFromStartTick(TimerGameTick::TickCounter start_tick) +TimerGameEconomy::Date GetDateFromStartTick(TimerGameTick::TickCounter start_tick) { /* Calculate the offset in ticks from the current counter tick. */ TimerGameTick::Ticks tick_offset = start_tick - TimerGameTick::counter; /* Compensate for the current date_fract. */ - tick_offset += TimerGameCalendar::date_fract; + tick_offset += TimerGameEconomy::date_fract; /* Return the current date plus the offset in days. */ - return TimerGameCalendar::date + (tick_offset / Ticks::DAY_TICKS); + return TimerGameEconomy::date + (tick_offset / Ticks::DAY_TICKS); } /** @@ -347,21 +347,21 @@ CommandCost CmdSetTimetableStart(DoComma TimerGameTick::Ticks total_duration = v->orders->GetTimetableTotalDuration(); - TimerGameCalendar::Date start_date = GetDateFromStartTick(start_tick); + TimerGameEconomy::Date start_date = GetDateFromStartTick(start_tick); /* Don't let a timetable start at an invalid date. */ - if (start_date < 0 || start_date > CalendarTime::MAX_DATE) return CMD_ERROR; + if (start_date < 0 || start_date > EconomyTime::MAX_DATE) return CMD_ERROR; /* Don't let a timetable start more than 15 years into the future... */ - if (start_date - TimerGameCalendar::date > TimerGameCalendar::DateAtStartOfYear(MAX_TIMETABLE_START_YEARS)) return CMD_ERROR; + if (start_date - TimerGameEconomy::date > TimerGameEconomy::DateAtStartOfYear(MAX_TIMETABLE_START_YEARS)) return CMD_ERROR; /* ...or 1 year in the past. */ - if (TimerGameCalendar::date - start_date > CalendarTime::DAYS_IN_LEAP_YEAR) return CMD_ERROR; + if (TimerGameEconomy::date - start_date > EconomyTime::DAYS_IN_LEAP_YEAR) return CMD_ERROR; /* If trying to distribute start dates over a shared order group, we need to know the total duration. */ if (timetable_all && !v->orders->IsCompleteTimetable()) return CommandCost(STR_ERROR_TIMETABLE_INCOMPLETE); /* Don't allow invalid start dates for other vehicles in the shared order group. */ - if (timetable_all && start_date + (total_duration / Ticks::DAY_TICKS) > CalendarTime::MAX_DATE) return CMD_ERROR; + if (timetable_all && start_date + (total_duration / Ticks::DAY_TICKS) > EconomyTime::MAX_DATE) return CMD_ERROR; if (flags & DC_EXEC) { std::vector vehs; diff --git a/src/timetable_gui.cpp b/src/timetable_gui.cpp --- a/src/timetable_gui.cpp +++ b/src/timetable_gui.cpp @@ -20,7 +20,7 @@ #include "company_func.h" #include "timer/timer.h" #include "timer/timer_game_tick.h" -#include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "timer/timer_window.h" #include "date_gui.h" #include "vehicle_gui.h" @@ -192,7 +192,7 @@ static void FillTimetableArrivalDepartur * @param w the window related to the setting of the date * @param date the actually chosen date */ -static void ChangeTimetableStartCallback(const Window *w, TimerGameCalendar::Date date, void *data) +static void ChangeTimetableStartCallback(const Window *w, TimerGameEconomy::Date date, void *data) { Command::Post(STR_ERROR_CAN_T_TIMETABLE_VEHICLE, (VehicleID)w->window_number, reinterpret_cast(data) != 0, GetStartTickFromDate(date)); } @@ -235,7 +235,7 @@ struct TimetableWindow : Window { 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; + if (_settings_client.gui.timetable_mode != TimetableMode::Seconds) start_time += TimerGameEconomy::date_fract; FillTimetableArrivalDepartureTable(v, v->cur_real_order_index % v->GetNumOrders(), travelling, table, start_time); @@ -252,7 +252,7 @@ struct TimetableWindow : Window { 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); + SetDParamMaxValue(1, TimerGameEconomy::DateAtStartOfYear(EconomyTime::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; @@ -525,7 +525,7 @@ struct TimetableWindow : Window { 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); + SetDParam(1, TimerGameEconomy::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); } } @@ -538,7 +538,7 @@ struct TimetableWindow : Window { 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); + SetDParam(1, TimerGameEconomy::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); } } @@ -572,7 +572,7 @@ struct TimetableWindow : Window { 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. */ + /* Other units use dates. */ SetDParam(0, STR_JUST_DATE_TINY); SetDParam(1, GetDateFromStartTick(v->timetable_start)); DrawString(tr, STR_TIMETABLE_STATUS_START_AT_DATE); @@ -643,7 +643,7 @@ struct TimetableWindow : Window { 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))); + ShowSetDateWindow(this, v->index, TimerGameEconomy::date, TimerGameEconomy::year, TimerGameEconomy::year + MAX_TIMETABLE_START_YEARS, ChangeTimetableStartCallback, reinterpret_cast(static_cast(_ctrl_pressed))); } break; diff --git a/src/toolbar_gui.cpp b/src/toolbar_gui.cpp --- a/src/toolbar_gui.cpp +++ b/src/toolbar_gui.cpp @@ -1103,10 +1103,18 @@ void ToggleWidgetOutlines() void SetStartingYear(TimerGameCalendar::Year year) { _settings_game.game_creation.starting_year = Clamp(year, CalendarTime::MIN_YEAR, CalendarTime::MAX_YEAR); - TimerGameCalendar::Date new_date = TimerGameCalendar::ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1); - /* If you open a savegame as scenario there may already be link graphs.*/ - LinkGraphSchedule::instance.ShiftDates(new_date - TimerGameCalendar::date); - TimerGameCalendar::SetDate(new_date, 0); + TimerGameCalendar::Date new_calendar_date = TimerGameCalendar::ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1); + TimerGameEconomy::Date new_economy_date = new_calendar_date.base(); + + /* We must set both Calendar and Economy dates to keep them in sync. Calendar first. */ + TimerGameCalendar::SetDate(new_calendar_date, 0); + + /* If you open a savegame as a scenario, there may already be link graphs and/or vehicles. These use economy date. */ + LinkGraphSchedule::instance.ShiftDates(new_economy_date - TimerGameEconomy::date); + for (auto v : Vehicle::Iterate()) v->ShiftDates(new_economy_date - TimerGameEconomy::date); + + /* Only change the date after changing cached values above. */ + TimerGameEconomy::SetDate(new_economy_date, 0); } /** diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -54,6 +54,7 @@ #include "tunnelbridge_cmd.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "timer/timer_game_tick.h" #include "table/strings.h" @@ -3837,7 +3838,7 @@ CommandCost CheckforTownRating(DoCommand return CommandCost(); } -static IntervalTimer _towns_monthly({TimerGameCalendar::MONTH, TimerGameCalendar::Priority::TOWN}, [](auto) +static IntervalTimer _economy_towns_monthly({TimerGameEconomy::MONTH, TimerGameEconomy::Priority::TOWN}, [](auto) { for (Town *t : Town::Iterate()) { /* Check for active town actions and decrement their counters. */ @@ -3864,7 +3865,7 @@ static IntervalTimer } }); -static IntervalTimer _towns_yearly({TimerGameCalendar::YEAR, TimerGameCalendar::Priority::TOWN}, [](auto) +static IntervalTimer _economy_towns_yearly({TimerGameEconomy::YEAR, TimerGameEconomy::Priority::TOWN}, [](auto) { /* Increment house ages */ for (TileIndex t = 0; t < Map::Size(); t++) { diff --git a/src/train.h b/src/train.h --- a/src/train.h +++ b/src/train.h @@ -123,7 +123,8 @@ struct Train FINAL : public GroundVehicl int GetDisplayImageWidth(Point *offset = nullptr) const; bool IsInDepot() const override { return this->track == TRACK_BIT_DEPOT; } bool Tick() override; - void OnNewDay() override; + void OnNewCalendarDay() override; + void OnNewEconomyDay() override; uint Crash(bool flooded = false) override; Trackdir GetVehicleTrackdir() const override; TileIndex GetOrderStationLocation(StationID station) override; diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -38,6 +38,7 @@ #include "train_cmd.h" #include "misc_cmd.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "table/strings.h" #include "table/train_sprites.h" @@ -653,7 +654,7 @@ static CommandCost CmdBuildRailWagon(DoC v->railtype = rvi->railtype; - v->date_of_last_service = TimerGameCalendar::date; + v->date_of_last_service = TimerGameEconomy::date; v->date_of_last_service_newgrf = TimerGameCalendar::date; v->build_year = TimerGameCalendar::year; v->sprite_cache.sprite_seq.Set(SPR_IMG_QUERY); @@ -787,7 +788,7 @@ CommandCost CmdBuildRailVehicle(DoComman v->railtype = rvi->railtype; v->SetServiceInterval(Company::Get(_current_company)->settings.vehicle.servint_trains); - v->date_of_last_service = TimerGameCalendar::date; + v->date_of_last_service = TimerGameEconomy::date; v->date_of_last_service_newgrf = TimerGameCalendar::date; v->build_year = TimerGameCalendar::year; v->sprite_cache.sprite_seq.Set(SPR_IMG_QUERY); @@ -4163,11 +4164,15 @@ static void CheckIfTrainNeedsService(Tra SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP); } -/** Update day counters of the train vehicle. */ -void Train::OnNewDay() +/** Calendar day handler. */ +void Train::OnNewCalendarDay() { AgeVehicle(this); - +} + +/** Economy day handler. */ +void Train::OnNewEconomyDay() +{ if ((++this->day_counter & 7) == 0) DecreaseVehicleValue(this); if (this->IsFrontEngine()) { diff --git a/src/vehicle.cpp b/src/vehicle.cpp --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -58,6 +58,7 @@ #include "newgrf_roadstop.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_game_economy.h" #include "timer/timer_game_tick.h" #include "table/strings.h" @@ -169,7 +170,7 @@ void VehicleServiceInDepot(Vehicle *v) SetWindowDirty(WC_VEHICLE_DETAILS, v->index); // ensure that last service date and reliability are updated do { - v->date_of_last_service = TimerGameCalendar::date; + v->date_of_last_service = TimerGameEconomy::date; v->date_of_last_service_newgrf = TimerGameCalendar::date; v->breakdowns_since_last_service = 0; v->reliability = v->GetEngine()->reliability; @@ -196,7 +197,7 @@ bool Vehicle::NeedsServicing() const const Company *c = Company::Get(this->owner); if (this->ServiceIntervalIsPercent() ? (this->reliability >= this->GetEngine()->reliability * (100 - this->GetServiceInterval()) / 100) : - (this->date_of_last_service + this->GetServiceInterval() >= TimerGameCalendar::date)) { + (this->date_of_last_service + this->GetServiceInterval() >= TimerGameEconomy::date)) { return false; } @@ -766,9 +767,9 @@ uint32_t Vehicle::GetGRFID() const * This is useful if the date has been modified with the cheat menu. * @param interval Number of days to be added or substracted. */ -void Vehicle::ShiftDates(TimerGameCalendar::Date interval) +void Vehicle::ShiftDates(TimerGameEconomy::Date interval) { - this->date_of_last_service = std::max(this->date_of_last_service + interval, TimerGameCalendar::Date(0)); + this->date_of_last_service = std::max(this->date_of_last_service + interval, TimerGameEconomy::Date(0)); /* date_of_last_service_newgrf is not updated here as it must stay stable * for vehicles outside of a depot. */ } @@ -916,16 +917,31 @@ void VehicleEnteredDepotThisTick(Vehicle } /** + * Age all vehicles, spreading out the action using the current TimerGameCalendar::date_fract. + */ +void RunVehicleCalendarDayProc() +{ + if (_game_mode != GM_NORMAL) return; + + /* Run the calendar day proc for every DAY_TICKS vehicle starting at TimerGameCalendar::date_fract. */ + for (size_t i = TimerGameCalendar::date_fract; i < Vehicle::GetPoolSize(); i += Ticks::DAY_TICKS) { + Vehicle *v = Vehicle::Get(i); + if (v == nullptr) continue; + v->OnNewCalendarDay(); + } +} + +/** * Increases the day counter for all vehicles and calls 1-day and 32-day handlers. - * Each tick, it processes vehicles with "index % DAY_TICKS == TimerGameCalendar::date_fract", + * Each tick, it processes vehicles with "index % DAY_TICKS == TimerGameEconomy::date_fract", * so each day, all vehicles are processes in DAY_TICKS steps. */ -static void RunVehicleDayProc() +static void RunEconomyVehicleDayProc() { if (_game_mode != GM_NORMAL) return; - /* Run the day_proc for every DAY_TICKS vehicle starting at TimerGameCalendar::date_fract. */ - for (size_t i = TimerGameCalendar::date_fract; i < Vehicle::GetPoolSize(); i += Ticks::DAY_TICKS) { + /* Run the economy day proc for every DAY_TICKS vehicle starting at TimerGameEconomy::date_fract. */ + for (size_t i = TimerGameEconomy::date_fract; i < Vehicle::GetPoolSize(); i += Ticks::DAY_TICKS) { Vehicle *v = Vehicle::Get(i); if (v == nullptr) continue; @@ -946,7 +962,7 @@ static void RunVehicleDayProc() } /* This is called once per day for each vehicle, but not in the first tick of the day */ - v->OnNewDay(); + v->OnNewEconomyDay(); } } @@ -954,7 +970,7 @@ void CallVehicleTicks() { _vehicles_to_autoreplace.clear(); - RunVehicleDayProc(); + RunEconomyVehicleDayProc(); { PerformanceMeasurer framerate(PFE_GL_ECONOMY); @@ -2819,7 +2835,7 @@ void Vehicle::RemoveFromShared() this->previous_shared = nullptr; } -static IntervalTimer _vehicles_yearly({TimerGameCalendar::YEAR, TimerGameCalendar::Priority::VEHICLE}, [](auto) +static IntervalTimer _economy_vehicles_yearly({TimerGameEconomy::YEAR, TimerGameEconomy::Priority::VEHICLE}, [](auto) { for (Vehicle *v : Vehicle::Iterate()) { if (v->IsPrimaryVehicle()) { diff --git a/src/vehicle_base.h b/src/vehicle_base.h --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -287,8 +287,8 @@ public: TimerGameCalendar::Year build_year; ///< Year the vehicle has been built. TimerGameCalendar::Date age; ///< Age in days TimerGameCalendar::Date max_age; ///< Maximum age - TimerGameCalendar::Date date_of_last_service; ///< Last date the vehicle had a service at a depot. - TimerGameCalendar::Date date_of_last_service_newgrf; ///< Last date the vehicle had a service at a depot, unchanged by the date cheat to protect against unsafe NewGRF behavior. + TimerGameEconomy::Date date_of_last_service; ///< Last economy date the vehicle had a service at a depot. + TimerGameCalendar::Date date_of_last_service_newgrf; ///< Last calendar date the vehicle had a service at a depot, unchanged by the date cheat to protect against unsafe NewGRF behavior. uint16_t reliability; ///< Reliability. uint16_t reliability_spd_dec; ///< Reliability decrease speed. byte breakdown_ctr; ///< Counter for managing breakdown events. @see Vehicle::HandleBreakdown @@ -567,11 +567,16 @@ public: virtual bool Tick() { return true; }; /** - * Calls the new day handler of the vehicle + * Calls the new calendar day handler of the vehicle. */ - virtual void OnNewDay() {}; + virtual void OnNewCalendarDay() {}; - void ShiftDates(TimerGameCalendar::Date interval); + /** + * Calls the new economy day handler of the vehicle. + */ + virtual void OnNewEconomyDay() {}; + + void ShiftDates(TimerGameEconomy::Date interval); /** * Crash the (whole) vehicle chain. diff --git a/src/vehicle_func.h b/src/vehicle_func.h --- a/src/vehicle_func.h +++ b/src/vehicle_func.h @@ -62,6 +62,7 @@ CommandCost TunnelBridgeIsFree(TileIndex void DecreaseVehicleValue(Vehicle *v); void CheckVehicleBreakdown(Vehicle *v); void AgeVehicle(Vehicle *v); +void RunVehicleCalendarDayProc(); void VehicleEnteredDepotThisTick(Vehicle *v); UnitID GetFreeUnitNumber(VehicleType type); diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -2431,7 +2431,7 @@ struct VehicleDetailsWindow : Window { case WID_VD_SERVICING_INTERVAL: SetDParamMaxValue(0, MAX_SERVINT_DAYS); // Roughly the maximum interval - SetDParamMaxValue(1, TimerGameCalendar::DateAtStartOfYear(CalendarTime::MAX_YEAR)); // Roughly the maximum year + SetDParamMaxValue(1, TimerGameEconomy::DateAtStartOfYear(EconomyTime::MAX_YEAR)); // Roughly the maximum year size->width = std::max( GetStringBoundingBox(STR_VEHICLE_DETAILS_SERVICING_INTERVAL_PERCENT).width, GetStringBoundingBox(STR_VEHICLE_DETAILS_SERVICING_INTERVAL_DAYS).width