1
0
mirror of https://github.com/OpenTTD/OpenTTD.git synced 2025-08-18 03:59:09 +00:00

Compare commits

...

4 Commits

Author SHA1 Message Date
Loïc Guilloux
08a7cc66ee Merge 185ca3a7f5 into 557fb0bd78 2025-07-30 00:13:14 +00:00
557fb0bd78 Change: Add support for different horizontal graph scales. 2025-07-30 01:13:05 +01:00
d09dfd843c Codechange: Extend industry cargo history to 24 years.
Monthly data is stored for the current 24 months.
Quarterly data is stored for a further 2-6 years.
Yearly data is stored for a further 6-24 years.
2025-07-30 01:13:05 +01:00
glx22
185ca3a7f5 Change: [Script] Replace ScriptDate with ScriptEconomyDate and ScriptCalendarDate 2025-05-24 23:23:48 +02:00
32 changed files with 889 additions and 163 deletions

View File

@@ -7,6 +7,19 @@
/* This file contains code to downgrade the API from 15 to 14. */
AIDate <- AIEconomyDate;
AIDate.IsValidDate <- function(date) { return AIDate(date).IsValid(); }
AIDate.GetCurrentDateCompat14 <- AIDate.GetCurrentDate;
AIDate.GetCurrentDate <- function() { return AIDate.GetCurrentDateCompat14().date(); }
AIDate.GetYearCompat14 <- AIDate.GetYear;
AIDate.GetYear <- function(date) { return AIDate(date).GetYearCompat14(); }
AIDate.GetMonthCompat14 <- AIDate.GetMonth;
AIDate.GetMonth <- function(date) { return AIDate(date).GetMonthCompat14(); }
AIDate.GetDayOfMonthCompat14 <- AIDate.GetDayOfMonth;
AIDate.GetDayOfMonth <- function(date) { return AIDate(date).GetDayOfMonthCompat14(); }
AIDate.GetDateCompat14 <- AIDate.GetDate;
AIDate.GetDate <- function(year, month, day) { return AIDate.GetDateCompat14(year, month, day).date(); }
AIBridge.GetBridgeID <- AIBridge.GetBridgeType;
class AICompat14 {
@@ -17,6 +30,8 @@ class AICompat14 {
}
}
AIBaseStation.GetConstructionDateCompat14 <- AIBaseStation.GetConstructionDate;
AIBaseStation.GetConstructionDate <- function(station_id) { return AIBaseStation.GetConstructionDateCompat14(station_id).date(); }
AIBaseStation.SetNameCompat14 <- AIBaseStation.SetName;
AIBaseStation.SetName <- function(id, name) { return AIBaseStation.SetNameCompat14(id, AICompat14.Text(name)); }
@@ -25,12 +40,18 @@ AICompany.SetName <- function(name) { return AICompany.SetNameCompat14(AICompat1
AICompany.SetPresidentNameCompat14 <- AICompany.SetPresidentName;
AICompany.SetPresidentName <- function(name) { return AICompany.SetPresidentNameCompat14(AICompat14.Text(name)); }
AIEngine.GetDesignDateCompat14 <- AIEngine.GetDesignDate;
AIEngine.GetDesignDate <- function(engine_id) { return AIEngine.GetDesignDateCompat14(engine_id).date(); }
AIGroup.SetNameCompat14 <- AIGroup.SetName;
AIGroup.SetName <- function(id, name) { return AIGroup.SetNameCompat14(id, AICompat14.Text(name)); }
AISign.BuildSignCompat14 <- AISign.BuildSign;
AISign.BuildSign <- function(id, name) { return AISign.BuildSignCompat14(id, AICompat14.Text(name)); }
AISubsidy.GetExpireDateCompat14 <- AISubsidy.GetExpireDate;
AISubsidy.GetExpireDate <- function(subsidy_id) { return AISubsidy.GetExpireDateCompat14(subsidy_id).date(); }
AITown.FoundTownCompat14 <- AITown.FoundTown;
AITown.FoundTown <- function(tile, size, city, layout, name) { return AITown.FoundTownCompat14(tile, size, city, layout, AICompat14.Text(name)); }

View File

@@ -7,6 +7,19 @@
/* This file contains code to downgrade the API from 15 to 14. */
GSDate <- GSEconomyDate;
GSDate.IsValidDate <- function(date) { return GSDate(date).IsValid(); }
GSDate.GetCurrentDateCompat14 <- GSDate.GetCurrentDate;
GSDate.GetCurrentDate <- function() { return GSDate.GetCurrentDateCompat14().date(); }
GSDate.GetYearCompat14 <- GSDate.GetYear;
GSDate.GetYear <- function(date) { return GSDate(date).GetYearCompat14(); }
GSDate.GetMonthCompat14 <- GSDate.GetMonth;
GSDate.GetMonth <- function(date) { return GSDate(date).GetMonthCompat14(); }
GSDate.GetDayOfMonthCompat14 <- GSDate.GetDayOfMonth;
GSDate.GetDayOfMonth <- function(date) { return GSDate(date).GetDayOfMonthCompat14(); }
GSDate.GetDateCompat14 <- GSDate.GetDate;
GSDate.GetDate <- function(year, month, day) { return GSDate.GetDateCompat14(year, month, day).date(); }
GSBridge.GetBridgeID <- GSBridge.GetBridgeType;
/* Emulate old GSText parameter padding behaviour */
@@ -21,6 +34,8 @@ class GSCompat14 {
}
}
GSBaseStation.GetConstructionDateCompat14 <- GSBaseStation.GetConstructionDate;
GSBaseStation.GetConstructionDate <- function(station_id) { return GSBaseStation.GetConstructionDateCompat14(station_id).date(); }
GSBaseStation.SetNameCompat14 <- GSBaseStation.SetName;
GSBaseStation.SetName <- function(id, name) { return GSBaseStation.SetNameCompat14(id, GSCompat14.Text(name)); }
@@ -29,6 +44,9 @@ GSCompany.SetName <- function(name) { return GSCompany.SetNameCompat14(GSCompat1
GSCompany.SetPresidentNameCompat14 <- GSCompany.SetPresidentName;
GSCompany.SetPresidentName <- function(name) { return GSCompany.SetPresidentNameCompat14(GSCompat14.Text(name)); }
GSEngine.GetDesignDateCompat14 <- GSEngine.GetDesignDate;
GSEngine.GetDesignDate <- function(engine_id) { return GSEngine.GetDesignDateCompat14(engine_id).date(); }
GSGoal.NewCompat14 <- GSGoal.New;
GSGoal.New <- function(company, goal, type, dest) { return GSGoal.NewCompat14(company, GSCompat14.Text(goal), type, dest); }
GSGoal.SetTextCompat14 <- GSGoal.SetText;
@@ -43,6 +61,10 @@ GSGoal.QuestionClient <- function(id, target, is_client, question, type, buttons
GSGroup.SetNameCompat14 <- GSGroup.SetName;
GSGroup.SetName <- function(id, name) { return GSGroup.SetNameCompat14(id, GSCompat14.Text(name)); }
GSIndustry.GetConstructionDateCompat14 <- GSIndustry.GetConstructionDate;
GSIndustry.GetConstructionDate <- function(industry_id) { return GSIndustry.GetConstructionDateCompat14(industry_id).date(); }
GSIndustry.GetCargoLastAcceptedDateCompat14 <- GSIndustry.GetCargoLastAcceptedDate;
GSIndustry.GetCargoLastAcceptedDate <- function(industry_id, cargo_type) { return GSIndustry.GetCargoLastAcceptedDateCompat14(industry_id, cargo_type).date(); }
GSIndustry.SetTextCompat14 <- GSIndustry.SetText;
GSIndustry.SetText <- function(id, text) { return GSIndustry.SetTextCompat14(id, GSCompat14.Text(text)); }
GSIndustry.SetProductionLevelCompat14 <- GSIndustry.SetProductionLevel;
@@ -63,15 +85,22 @@ GSNews.Create <- function(type, text, company, ref_type, ref) { return GSNews.Cr
GSSign.BuildSignCompat14 <- GSSign.BuildSign;
GSSign.BuildSign <- function(id, name) { return GSSign.BuildSignCompat14(id, GSCompat14.Text(name)); }
GSStoryPage.GetDateCompat14 <- GSStoryPage.GetDate;
GSStoryPage.GetDate <- function(story_page_id) { return GSStoryPage.GetDateCompat14(story_page_id).date(); }
GSStoryPage.NewCompat14 <- GSStoryPage.New;
GSStoryPage.New <- function(company, title) { return GSStoryPage.NewCompat14(company, GSCompat14.Text(title)); }
GSStoryPage.NewElementCompat14 <- GSStoryPage.NewElement;
GSStoryPage.NewElement <- function(page, type, ref, text) { return GSStoryPage.NewElementCompat14(page, type, ref, GSCompat14.Text(text)); }
GSStoryPage.UpdateElementCompat14 <- GSStoryPage.UpdateElement;
GSStoryPage.UpdateElement <- function(id, ref, text) { return GSStoryPage.UpdateElementCompat14(id, ref, GSCompat14.Text(text)); }
GSStoryPage.SetDateCompat14 <- GSStoryPage.SetDate;
GSStoryPage.SetDate <- function(story_page_id, date) { return GSStoryPage.SetDateCompat14(story_page_id, GSCalendarDate(date)); }
GSStoryPage.SetTitleCompat14 <- GSStoryPage.SetTitle;
GSStoryPage.SetTitle <- function(page, tile) { return GSStoryPage.SetTitleCompat14(page, GSCompat14.Text(title)); }
GSSubsidy.GetExpireDateCompat14 <- GSSubsidy.GetExpireDate;
GSSubsidy.GetExpireDate <- function(subsidy_id) { return GSSubsidy.GetExpireDateCompat14(subsidy_id).date(); }
GSTown.SetNameCompat14 <- GSTown.SetName;
GSTown.SetName <- function(id, name) { return GSTown.SetNameCompat14(id, GSCompat14.Text(name)); }
GSTown.SetTextCompat14 <- GSTown.SetText;

View File

@@ -8,6 +8,7 @@
/** @file graph_gui.cpp GUI that shows performance graphs. */
#include "stdafx.h"
#include <ranges>
#include "misc/history_func.hpp"
#include "graph_gui.h"
#include "window_gui.h"
@@ -174,6 +175,7 @@ protected:
static const int GRAPH_PAYMENT_RATE_STEPS = 20; ///< Number of steps on Payment rate graph.
static const int PAYMENT_GRAPH_X_STEP_DAYS = 10; ///< X-axis step label for cargo payment rates "Days in transit".
static const int PAYMENT_GRAPH_X_STEP_SECONDS = 20; ///< X-axis step label for cargo payment rates "Seconds in transit".
static const int ECONOMY_YEAR_MINUTES = 12; ///< Minutes per economic year.
static const int ECONOMY_QUARTER_MINUTES = 3; ///< Minutes per economic quarter.
static const int ECONOMY_MONTH_MINUTES = 1; ///< Minutes per economic month.
@@ -182,6 +184,25 @@ protected:
static const int MIN_GRAPH_NUM_LINES_Y = 9; ///< Minimal number of horizontal lines to draw.
static const int MIN_GRID_PIXEL_SIZE = 20; ///< Minimum distance between graph lines.
struct GraphScale {
StringID label = STR_NULL;
uint8_t month_increment = 0;
int16_t x_values_increment = 0;
const HistoryRange *history_range = nullptr;
};
static inline constexpr GraphScale MONTHLY_SCALE_WALLCLOCK[] = {
{STR_GRAPH_LAST_24_MINUTES_TIME_LABEL, HISTORY_MONTH.total_division, ECONOMY_MONTH_MINUTES, &HISTORY_MONTH},
{STR_GRAPH_LAST_72_MINUTES_TIME_LABEL, HISTORY_QUARTER.total_division, ECONOMY_QUARTER_MINUTES, &HISTORY_QUARTER},
{STR_GRAPH_LAST_288_MINUTES_TIME_LABEL, HISTORY_YEAR.total_division, ECONOMY_YEAR_MINUTES, &HISTORY_YEAR},
};
static inline constexpr GraphScale MONTHLY_SCALE_CALENDAR[] = {
{STR_GRAPH_LAST_24_MONTHS, HISTORY_MONTH.total_division, ECONOMY_MONTH_MINUTES, &HISTORY_MONTH},
{STR_GRAPH_LAST_24_QUARTERS, HISTORY_QUARTER.total_division, ECONOMY_QUARTER_MINUTES, &HISTORY_QUARTER},
{STR_GRAPH_LAST_24_YEARS, HISTORY_YEAR.total_division, ECONOMY_YEAR_MINUTES, &HISTORY_YEAR},
};
uint64_t excluded_data = 0; ///< bitmask of datasets hidden by the player.
uint64_t excluded_range = 0; ///< bitmask of ranges hidden by the player.
uint64_t masked_range = 0; ///< bitmask of ranges that are not available for the current data.
@@ -211,7 +232,9 @@ protected:
};
std::vector<DataSet> data{};
std::span<const StringID> ranges = {};
std::span<const StringID> ranges{};
std::span<const GraphScale> scales{};
uint8_t selected_scale = 0;
uint8_t highlight_data = UINT8_MAX; ///< Data set that should be highlighted, or UINT8_MAX for none.
uint8_t highlight_range = UINT8_MAX; ///< Data range that should be highlighted, or UINT8_MAX for none.
@@ -617,24 +640,34 @@ protected:
this->SetDirty();
}};
void UpdateMatrixSize(WidgetID widget, Dimension &size, Dimension &resize, auto labels)
{
size = {};
for (const StringID &str : labels) {
size = maxdim(size, GetStringBoundingBox(str, FS_SMALL));
}
size.width += WidgetDimensions::scaled.framerect.Horizontal();
size.height += WidgetDimensions::scaled.framerect.Vertical();
/* Set fixed height for number of ranges. */
size.height *= static_cast<uint>(std::size(labels));
resize.width = 0;
resize.height = 0;
this->GetWidget<NWidgetCore>(widget)->SetMatrixDimension(1, ClampTo<uint32_t>(std::size(labels)));
}
public:
void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
{
switch (widget) {
case WID_GRAPH_RANGE_MATRIX:
for (const StringID &str : this->ranges) {
size = maxdim(size, GetStringBoundingBox(str, FS_SMALL));
}
this->UpdateMatrixSize(widget, size, resize, this->ranges);
break;
size.width += WidgetDimensions::scaled.framerect.Horizontal();
size.height += WidgetDimensions::scaled.framerect.Vertical();
/* Set fixed height for number of ranges. */
size.height *= static_cast<uint>(std::size(this->ranges));
resize.width = 0;
resize.height = 0;
this->GetWidget<NWidgetCore>(WID_GRAPH_RANGE_MATRIX)->SetMatrixDimension(1, ClampTo<uint32_t>(std::size(this->ranges)));
case WID_GRAPH_SCALE_MATRIX:
this->UpdateMatrixSize(widget, size, resize, this->scales | std::views::transform(&GraphScale::label));
break;
case WID_GRAPH_GRAPH: {
@@ -701,6 +734,21 @@ public:
break;
}
case WID_GRAPH_SCALE_MATRIX: {
uint line_height = GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical();
uint8_t selected_month_increment = this->scales[this->selected_scale].month_increment;
Rect line = r.WithHeight(line_height);
for (const auto &scale : this->scales) {
/* Redraw frame if selected */
if (selected_month_increment == scale.month_increment) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
DrawString(line.Shrink(WidgetDimensions::scaled.framerect), scale.label, TC_BLACK, SA_CENTER, false, FS_SMALL);
line = line.Translate(0, line_height);
}
break;
}
default: break;
}
}
@@ -722,6 +770,18 @@ public:
break;
}
case WID_GRAPH_SCALE_MATRIX: {
int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
const auto &scale = this->scales[row];
if (this->selected_scale != row) {
this->selected_scale = row;
this->month_increment = scale.month_increment;
this->x_values_increment = scale.x_values_increment;
this->InvalidateData();
}
break;
}
default: break;
}
}
@@ -1646,6 +1706,13 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
this->InitializeWindow(window_number, STR_GRAPH_LAST_24_MINUTES_TIME_LABEL);
}
void OnInit() override
{
this->BaseCargoGraphWindow::OnInit();
this->scales = TimerGameEconomy::UsingWallclockUnits() ? MONTHLY_SCALE_WALLCLOCK : MONTHLY_SCALE_CALENDAR;
}
CargoTypes GetCargoTypes(WindowNumber window_number) const override
{
CargoTypes cargo_types{};
@@ -1673,7 +1740,7 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
void UpdateStatistics(bool initialize) override
{
int mo = TimerGameEconomy::month - this->num_vert_lines;
int mo = (TimerGameEconomy::month / this->month_increment - this->num_vert_lines) * this->month_increment;
auto yr = TimerGameEconomy::year;
while (mo < 0) {
yr--;
@@ -1711,7 +1778,7 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
transported.dash = 2;
auto transported_filler = Filler{transported, &Industry::ProducedHistory::transported};
FillFromHistory<GRAPH_NUM_MONTHS>(p.history, i->valid_history, produced_filler, transported_filler);
FillFromHistory<GRAPH_NUM_MONTHS>(p.history, i->valid_history, *this->scales[this->selected_scale].history_range, produced_filler, transported_filler);
}
for (const auto &a : i->accepted) {
@@ -1735,9 +1802,9 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
auto waiting_filler = Filler{waiting, &Industry::AcceptedHistory::waiting};
if (a.history == nullptr) {
FillFromEmpty<GRAPH_NUM_MONTHS>(i->valid_history, accepted_filler, waiting_filler);
FillFromEmpty<GRAPH_NUM_MONTHS>(i->valid_history, *this->scales[this->selected_scale].history_range, accepted_filler, waiting_filler);
} else {
FillFromHistory<GRAPH_NUM_MONTHS>(*a.history, i->valid_history, accepted_filler, waiting_filler);
FillFromHistory<GRAPH_NUM_MONTHS>(*a.history, i->valid_history, *this->scales[this->selected_scale].history_range, accepted_filler, waiting_filler);
}
}
@@ -1758,7 +1825,7 @@ static constexpr NWidgetPart _nested_industry_production_widgets[] = {
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GRAPH_GRAPH), SetMinimalSize(495, 0), SetFill(1, 1), SetResize(1, 1),
NWidget(NWID_VERTICAL),
NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_RANGE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO),
NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_RANGE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_TOGGLE_RANGE),
NWidget(NWID_SPACER), SetMinimalSize(0, 4),
NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_ENABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_ENABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_ENABLE_ALL), SetFill(1, 0),
NWidget(WWT_PUSHTXTBTN, COLOUR_BROWN, WID_GRAPH_DISABLE_CARGOES), SetStringTip(STR_GRAPH_CARGO_DISABLE_ALL, STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL), SetFill(1, 0),
@@ -1767,6 +1834,8 @@ static constexpr NWidgetPart _nested_industry_production_widgets[] = {
NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_MATRIX), SetFill(1, 0), SetResize(0, 2), SetMatrixDataTip(1, 0, STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO), SetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR),
NWidget(NWID_VSCROLLBAR, COLOUR_BROWN, WID_GRAPH_MATRIX_SCROLLBAR),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(0, 4),
NWidget(WWT_MATRIX, COLOUR_BROWN, WID_GRAPH_SCALE_MATRIX), SetFill(1, 0), SetResize(0, 0), SetMatrixDataTip(1, 0, STR_GRAPH_SELECT_SCALE),
NWidget(NWID_SPACER), SetMinimalSize(0, 24), SetFill(0, 1),
EndContainer(),
NWidget(NWID_SPACER), SetMinimalSize(5, 0), SetFill(0, 1), SetResize(0, 1),

View File

@@ -1836,7 +1836,7 @@ static void DoCreateNewIndustry(Industry *i, TileIndex tile, IndustryType type,
p.history[LAST_MONTH].production += ScaleByCargoScale(p.rate * 8, false);
}
UpdateValidHistory(i->valid_history);
UpdateValidHistory(i->valid_history, HISTORY_YEAR, TimerGameEconomy::month);
}
if (indspec->callback_mask.Test(IndustryCallbackMask::DecideColour)) {
@@ -2488,19 +2488,38 @@ void GenerateIndustries()
_industry_builder.Reset();
}
template <>
Industry::ProducedHistory SumHistory(std::span<const Industry::ProducedHistory> history)
{
uint32_t production = std::accumulate(std::begin(history), std::end(history), 0, [](uint32_t r, const auto &p) { return r + p.production; });
uint32_t transported = std::accumulate(std::begin(history), std::end(history), 0, [](uint32_t r, const auto &p) { return r + p.transported; });
auto count = std::size(history);
return {.production = ClampTo<uint16_t>(production / count), .transported = ClampTo<uint16_t>(transported / count)};
}
template <>
Industry::AcceptedHistory SumHistory(std::span<const Industry::AcceptedHistory> history)
{
uint32_t accepted = std::accumulate(std::begin(history), std::end(history), 0, [](uint32_t r, const auto &a) { return r + a.accepted; });
uint32_t waiting = std::accumulate(std::begin(history), std::end(history), 0, [](uint32_t r, const auto &a) { return r + a.waiting; });;
auto count = std::size(history);
return {.accepted = ClampTo<uint16_t>(accepted / count), .waiting = ClampTo<uint16_t>(waiting / count)};
}
/**
* Monthly update of industry statistics.
* @param i Industry to update.
*/
static void UpdateIndustryStatistics(Industry *i)
{
UpdateValidHistory(i->valid_history);
auto month = TimerGameEconomy::month;
UpdateValidHistory(i->valid_history, HISTORY_YEAR, month);
for (auto &p : i->produced) {
if (IsValidCargoType(p.cargo)) {
if (p.history[THIS_MONTH].production != 0) i->last_prod_year = TimerGameEconomy::year;
RotateHistory(p.history);
RotateHistory(p.history, i->valid_history, HISTORY_YEAR, month);
}
}
@@ -2509,7 +2528,7 @@ static void UpdateIndustryStatistics(Industry *i)
if (a.history == nullptr) continue;
(*a.history)[THIS_MONTH].waiting = GetAndResetAccumulatedAverage<uint16_t>(a.accumulated_waiting);
RotateHistory(*a.history);
RotateHistory(*a.history, i->valid_history, HISTORY_YEAR, month);
}
}

View File

@@ -622,6 +622,14 @@ STR_GRAPH_COMPANY_VALUES_CAPTION :{WHITE}Company
STR_GRAPH_LAST_24_MINUTES_TIME_LABEL :{TINY_FONT}{BLACK}Last 24 minutes
STR_GRAPH_LAST_72_MINUTES_TIME_LABEL :{TINY_FONT}{BLACK}Last 72 minutes
STR_GRAPH_LAST_288_MINUTES_TIME_LABEL :{TINY_FONT}{BLACK}Last 288 minutes
STR_GRAPH_LAST_24_MONTHS :{TINY_FONT}{BLACK}2 years (monthly)
STR_GRAPH_LAST_24_QUARTERS :{TINY_FONT}{BLACK}6 years (quarterly)
STR_GRAPH_LAST_24_YEARS :{TINY_FONT}{BLACK}24 years (yearly)
STR_GRAPH_TOGGLE_RANGE :Toggle graph for this data range
STR_GRAPH_SELECT_SCALE :Change horizontal scale of graph
STR_GRAPH_CARGO_PAYMENT_RATES_CAPTION :{WHITE}Cargo Payment Rates
STR_GRAPH_CARGO_PAYMENT_RATES_DAYS :{TINY_FONT}{BLACK}Days in transit

View File

@@ -8,6 +8,7 @@ add_files(
getoptdata.cpp
getoptdata.h
hashtable.hpp
history.cpp
history_func.hpp
history_type.hpp
lrucache.hpp

65
src/misc/history.cpp Normal file
View File

@@ -0,0 +1,65 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
/** @file history.cpp Implementation of functions for storing historical data. */
#include "../stdafx.h"
#include "../core/bitmath_func.hpp"
#include "history_type.hpp"
#include "history_func.hpp"
#include "../safeguards.h"
/**
* Update mask of valid records for a historical data.
* @note Call only for the largest history range sub-division.
* @param[in,out] valid_history Valid history records.
* @param hr History range to update mask for.
* @param cur_month Current economy month.
*/
void UpdateValidHistory(ValidHistoryMask &valid_history, const HistoryRange &hr, uint cur_month)
{
/* Update for subdivisions first. */
if (hr.hr != nullptr) UpdateValidHistory(valid_history, *hr.hr, cur_month);
/* No need to update if our last entry is marked valid. */
if (HasBit(valid_history, hr.last - 1)) return;
/* Is it the right time for this history range? */
if (cur_month % hr.total_division != 0) return;
/* Is the previous history range valid yet? */
if (hr.division != 1 && !HasBit(valid_history, hr.first - hr.division)) return;
SB(valid_history, hr.first, hr.records, GB(valid_history, hr.first, hr.records) << 1ULL | 1ULL);
}
/**
* Test if history data is valid, without extracting data.
* @param valid_history Mask of valid history records.
* @param hr History range to test.
* @param age Age of data to test.
* @return True iff the data for history range and age is valid.
*/
bool IsValidHistory(ValidHistoryMask valid_history, const HistoryRange &hr, uint age)
{
if (hr.hr == nullptr) {
if (age < hr.periods) {
uint slot = hr.first + age;
return HasBit(valid_history, slot);
}
} else {
if (age * hr.division < static_cast<uint>(hr.hr->periods - hr.division)) {
uint start = age * hr.division + ((TimerGameEconomy::month / hr.hr->division) % hr.division);
return IsValidHistory(valid_history, *hr.hr, start);
}
if (age < hr.periods) {
uint slot = hr.first + age - ((hr.hr->periods / hr.division) - 1);
return HasBit(valid_history, slot);
}
}
return false;
}

View File

@@ -15,25 +15,44 @@
#include "../timer/timer_game_economy.h"
#include "history_type.hpp"
/**
* Update mask of valid history records.
* @param[in,out] valid_history Valid history records.
*/
inline void UpdateValidHistory(ValidHistoryMask &valid_history)
{
SB(valid_history, LAST_MONTH, HISTORY_RECORDS - LAST_MONTH, GB(valid_history, LAST_MONTH, HISTORY_RECORDS - LAST_MONTH) << 1ULL | 1ULL);
}
void UpdateValidHistory(ValidHistoryMask &valid_history, const HistoryRange &hr, uint cur_month);
bool IsValidHistory(ValidHistoryMask valid_history, const HistoryRange &hr, uint age);
/**
* Rotate history.
* Sum history data elements.
* @note The summation should prevent overflowing, and perform transformations relevant to the type of data.
* @tparam T type of history data element.
* @param history Historical data to rotate.
* @param history History elements to sum.
* @return Sum of history elements.
*/
template <typename T>
void RotateHistory(HistoryData<T> &history)
T SumHistory(typename std::span<const T> history);
/**
* Rotate historical data.
* @note Call only for the largest history range sub-division.
* @tparam T type of history data element.
* @param history Historical data to rotate.
* @param valid_history Mask of valid history records.
* @param hr History range to rotate..
* @param cur_month Current economy month.
*/
template <typename T>
void RotateHistory(HistoryData<T> &history, ValidHistoryMask valid_history, const HistoryRange &hr, uint cur_month)
{
std::rotate(std::rbegin(history), std::rbegin(history) + 1, std::rend(history));
history[THIS_MONTH] = {};
if (hr.hr != nullptr) RotateHistory(history, valid_history, *hr.hr, cur_month);
if (cur_month % hr.total_division != 0) return;
std::move_backward(std::next(std::begin(history), hr.first), std::next(std::begin(history), hr.last - 1), std::next(std::begin(history), hr.last));
if (hr.total_division == 1) {
history[hr.first] = history[hr.first - 1];
history.front() = {};
} else if (HasBit(valid_history, hr.first - hr.division)) {
auto first = std::next(std::begin(history), hr.first - hr.division);
auto last = std::next(first, hr.division);
history[hr.first] = SumHistory<T>(std::span{first, last});
}
}
/**
@@ -49,19 +68,60 @@ T GetAndResetAccumulatedAverage(Taccrued &total)
return result;
}
/**
* Get historical data.
* @tparam T type of history data element.
* @param history History data to extract from.
* @param valid_history Mask of valid history records.
* @param hr History range to get.
* @param age Age of data to get.
* @param cur_month Current economy month.
* @param[out] result Extracted historical data.
* @return True iff the data for this history range and age is valid.
*/
template <typename T>
bool GetHistory(const HistoryData<T> &history, ValidHistoryMask valid_history, const HistoryRange &hr, uint age, T &result)
{
if (hr.hr == nullptr) {
if (age < hr.periods) {
uint slot = hr.first + age;
result = history[slot];
return HasBit(valid_history, slot);
}
} else {
if (age * hr.division < static_cast<uint>(hr.hr->periods - hr.division)) {
bool is_valid = false;
std::array<T, HISTORY_MAX_DIVISION> tmp_result; // No need to clear as we fill every element we use.
uint start = age * hr.division + ((TimerGameEconomy::month / hr.hr->division) % hr.division);
for (auto i = start; i != start + hr.division; ++i) {
is_valid |= GetHistory(history, valid_history, *hr.hr, i, tmp_result[i - start]);
}
result = SumHistory<T>(std::span{std::begin(tmp_result), hr.division});
return is_valid;
}
if (age < hr.periods) {
uint slot = hr.first + age - ((hr.hr->periods / hr.division) - 1);
result = history[slot];
return HasBit(valid_history, slot);
}
}
NOT_REACHED();
}
/**
* Fill some data with historical data.
* @param history Historical data to fill from.
* @param valid_history Mask of valid history records.
* @param hr History range to fill with.
* @param fillers Fillers to fill with history data.
*/
template <uint N, typename T, typename... Tfillers>
void FillFromHistory(const HistoryData<T> &history, ValidHistoryMask valid_history, Tfillers... fillers)
void FillFromHistory(const HistoryData<T> &history, ValidHistoryMask valid_history, const HistoryRange &hr, Tfillers... fillers)
{
T result{};
for (uint i = 0; i != N; ++i) {
if (HasBit(valid_history, N - i)) {
auto &data = history[N - i];
(fillers.Fill(i, data), ...);
if (GetHistory(history, valid_history, hr, N - i - 1, result)) {
(fillers.Fill(i, result), ...);
} else {
(fillers.MakeInvalid(i), ...);
}
@@ -71,13 +131,14 @@ void FillFromHistory(const HistoryData<T> &history, ValidHistoryMask valid_histo
/**
* Fill some data with empty records.
* @param valid_history Mask of valid history records.
* @param hr History range to fill with.
* @param fillers Fillers to fill with history data.
*/
template <uint N, typename... Tfillers>
void FillFromEmpty(ValidHistoryMask valid_history, Tfillers... fillers)
void FillFromEmpty(ValidHistoryMask valid_history, const HistoryRange &hr, Tfillers... fillers)
{
for (uint i = 0; i != N; ++i) {
if (HasBit(valid_history, N - i)) {
if (IsValidHistory(valid_history, hr, N - i - 1)) {
(fillers.MakeZero(i), ...);
} else {
(fillers.MakeInvalid(i), ...);

View File

@@ -10,7 +10,37 @@
#ifndef HISTORY_TYPE_HPP
#define HISTORY_TYPE_HPP
static constexpr uint8_t HISTORY_RECORDS = 25;
struct HistoryRange {
const HistoryRange *hr;
const uint8_t periods; ///< Number of periods for this range.
const uint8_t records; ///< Number of records needed for this range.
const uint8_t first; ///< Index of first element in history data.
const uint8_t last; ///< Index of last element in history data.
const uint8_t division; ///< Number of divisions of the previous history range.
const uint8_t total_division; ///< Number of divisions of the initial history range.
explicit constexpr HistoryRange(uint8_t periods) :
hr(nullptr), periods(periods), records(this->periods), first(1), last(this->first + this->records), division(1), total_division(1)
{
}
constexpr HistoryRange(const HistoryRange &hr, uint8_t division, uint8_t periods) :
hr(&hr), periods(periods), records(this->periods - ((hr.periods / division) - 1)), first(hr.last), last(this->first + this->records),
division(division), total_division(division * hr.total_division)
{
}
};
static constexpr uint8_t HISTORY_PERIODS = 24;
static constexpr HistoryRange HISTORY_MONTH{HISTORY_PERIODS};
static constexpr HistoryRange HISTORY_QUARTER{HISTORY_MONTH, 3, HISTORY_PERIODS};
static constexpr HistoryRange HISTORY_YEAR{HISTORY_QUARTER, 4, HISTORY_PERIODS};
/** Maximum number of divisions from previous history range. */
static constexpr uint8_t HISTORY_MAX_DIVISION = std::max({HISTORY_MONTH.division, HISTORY_QUARTER.division, HISTORY_YEAR.division});
/** Total number of records require for all history data. */
static constexpr uint8_t HISTORY_RECORDS = HISTORY_YEAR.last;
static constexpr uint8_t THIS_MONTH = 0;
static constexpr uint8_t LAST_MONTH = 1;

View File

@@ -40,13 +40,15 @@ foreach(API "ai;AI" "game;GS" "template;Template")
list(GET API 1 APIUC)
foreach(SCRIPT_API_FILE IN LISTS SCRIPT_API_FILES)
if("${SCRIPT_API_FILE}" MATCHES ".*script_controller.*")
continue()
endif()
get_filename_component(SCRIPT_API_FILE_NAME "${SCRIPT_API_FILE}" NAME_WE)
string(REPLACE "script_" "${APILC}_" SCRIPT_API_FILE_NAME "${SCRIPT_API_FILE_NAME}")
set(SCRIPT_API_BINARY_FILE "${CMAKE_BINARY_DIR}/generated/script/api/${APILC}/${SCRIPT_API_FILE_NAME}.sq.hpp")
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${APILC}/${SCRIPT_API_FILE_NAME}.sq.hpp")
file(REMOVE "${SCRIPT_API_BINARY_FILE}")
continue()
endif()
add_custom_command_timestamp(OUTPUT ${SCRIPT_API_BINARY_FILE}
COMMAND ${CMAKE_COMMAND}
-DSCRIPT_API_SOURCE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/squirrel_export.sq.hpp.in
@@ -74,6 +76,7 @@ foreach(API "ai;AI" "game;GS" "template;Template")
if(NOT "${APILC}" STREQUAL "template")
list(APPEND SCRIPT_${APIUC}_BINARY_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${APILC}/${APILC}_controller.sq.hpp")
list(APPEND SCRIPT_${APIUC}_BINARY_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${APILC}/${APILC}_date.sq.hpp")
set(INCLUDES_BINARY_FILE "${CMAKE_BINARY_DIR}/generated/script/api/${APILC}/${APILC}_includes.hpp")
set(API_FILES "${CMAKE_CURRENT_BINARY_DIR}/${APILC}.files")
file(GENERATE OUTPUT ${API_FILES} CONTENT "${SCRIPT_${APIUC}_BINARY_FILES}")
@@ -120,6 +123,7 @@ foreach(API "ai;AI" "game;GS" "template;Template")
)
endif()
else()
list(APPEND SCRIPT_Template_BINARY_FILES "${CMAKE_CURRENT_SOURCE_DIR}/template/template_date.sq.hpp")
add_dependencies(script_api
script_${APILC}
)
@@ -233,7 +237,6 @@ add_files(
script_company.cpp
script_companymode.cpp
script_controller.cpp
script_date.cpp
script_depotlist.cpp
script_engine.cpp
script_enginelist.cpp

View File

@@ -0,0 +1,62 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "../script_date.hpp"
#include "../template/template_date.sq.hpp"
template <> SQInteger PushClassName<ScriptEconomyDate, ScriptType::AI>(HSQUIRRELVM vm) { sq_pushstring(vm, "AIEconomyDate"); return 1; }
template <> SQInteger PushClassName<ScriptCalendarDate, ScriptType::AI>(HSQUIRRELVM vm) { sq_pushstring(vm, "AICalendarDate"); return 1; }
void SQAIDate_Register(Squirrel &engine)
{
DefSQClass<ScriptEconomyDate, ScriptType::AI> SQAIEconomyDate("AIEconomyDate");
SQAIEconomyDate.PreRegister(engine, "AIObject");
SQAIEconomyDate.AddSQAdvancedConstructor(engine);
SQAIEconomyDate.DefSQConst(engine, ScriptEconomyDate::DATE_INVALID, "DATE_INVALID");
SQAIEconomyDate.DefSQStaticMethod(engine, &ScriptEconomyDate::GetCurrentDate, "GetCurrentDate", ".");
SQAIEconomyDate.DefSQStaticMethod(engine, &ScriptEconomyDate::GetDate, "GetDate", ".iii");
SQAIEconomyDate.DefSQMethod(engine, &ScriptEconomyDate::IsValid, "IsValid", ".");
SQAIEconomyDate.DefSQMethod(engine, &ScriptEconomyDate::GetYear, "GetYear", ".");
SQAIEconomyDate.DefSQMethod(engine, &ScriptEconomyDate::GetMonth, "GetMonth", ".");
SQAIEconomyDate.DefSQMethod(engine, &ScriptEconomyDate::GetDayOfMonth, "GetDayOfMonth", ".");
SQAIEconomyDate.DefSQMethod(engine, &ScriptEconomyDate::date, "date", ".");
SQAIEconomyDate.DefSQAdvancedMethod(engine, &ScriptEconomyDate::_tostring, "_tostring");
SQAIEconomyDate.DefSQAdvancedMethod(engine, &ScriptEconomyDate::_add, "_add");
SQAIEconomyDate.DefSQAdvancedMethod(engine, &ScriptEconomyDate::_sub, "_sub");
SQAIEconomyDate.DefSQAdvancedMethod(engine, &ScriptEconomyDate::_cmp, "_cmp");
SQAIEconomyDate.PostRegister(engine);
DefSQClass<ScriptCalendarDate, ScriptType::AI> SQAICalendarDate("AICalendarDate");
SQAICalendarDate.PreRegister(engine, "AIObject");
SQAICalendarDate.AddSQAdvancedConstructor(engine);
SQAICalendarDate.DefSQConst(engine, ScriptCalendarDate::DATE_INVALID, "DATE_INVALID");
SQAICalendarDate.DefSQStaticMethod(engine, &ScriptCalendarDate::GetCurrentDate, "GetCurrentDate", ".");
SQAICalendarDate.DefSQStaticMethod(engine, &ScriptCalendarDate::GetDate, "GetDate", ".iii");
SQAICalendarDate.DefSQMethod(engine, &ScriptCalendarDate::IsValid, "IsValid", ".");
SQAICalendarDate.DefSQMethod(engine, &ScriptCalendarDate::GetYear, "GetYear", ".");
SQAICalendarDate.DefSQMethod(engine, &ScriptCalendarDate::GetMonth, "GetMonth", ".");
SQAICalendarDate.DefSQMethod(engine, &ScriptCalendarDate::GetDayOfMonth, "GetDayOfMonth", ".");
SQAICalendarDate.DefSQMethod(engine, &ScriptCalendarDate::date, "date", ".");
SQAICalendarDate.DefSQAdvancedMethod(engine, &ScriptCalendarDate::_tostring, "_tostring");
SQAICalendarDate.DefSQAdvancedMethod(engine, &ScriptCalendarDate::_add, "_add");
SQAICalendarDate.DefSQAdvancedMethod(engine, &ScriptCalendarDate::_sub, "_sub");
SQAICalendarDate.DefSQAdvancedMethod(engine, &ScriptCalendarDate::_cmp, "_cmp");
SQAICalendarDate.PostRegister(engine);
}

View File

@@ -28,6 +28,11 @@
* \li AICargo::CC_POTABLE
* \li AICargo::CC_NON_POTABLE
* \li AIVehicleList_Waypoint
* \li AIEconomyDate
* \li AICalendarDate
*
* API removals:
* \li AIDate
*
* Other changes:
* \li AIBridge::GetBridgeID renamed to AIBridge::GetBridgeType

View File

@@ -0,0 +1,62 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "../script_date.hpp"
#include "../template/template_date.sq.hpp"
template <> SQInteger PushClassName<ScriptEconomyDate, ScriptType::GS>(HSQUIRRELVM vm) { sq_pushstring(vm, "GSEconomyDate"); return 1; }
template <> SQInteger PushClassName<ScriptCalendarDate, ScriptType::GS>(HSQUIRRELVM vm) { sq_pushstring(vm, "GSCalendarDate"); return 1; }
void SQGSDate_Register(Squirrel &engine)
{
DefSQClass<ScriptEconomyDate, ScriptType::GS> SQGSEconomyDate("GSEconomyDate");
SQGSEconomyDate.PreRegister(engine, "GSObject");
SQGSEconomyDate.AddSQAdvancedConstructor(engine);
SQGSEconomyDate.DefSQConst(engine, ScriptEconomyDate::DATE_INVALID, "DATE_INVALID");
SQGSEconomyDate.DefSQStaticMethod(engine, &ScriptEconomyDate::GetCurrentDate, "GetCurrentDate", ".");
SQGSEconomyDate.DefSQStaticMethod(engine, &ScriptEconomyDate::GetDate, "GetDate", ".iii");
SQGSEconomyDate.DefSQStaticMethod(engine, &ScriptEconomyDate::GetSystemTime, "GetSystemTime", ".");
SQGSEconomyDate.DefSQMethod(engine, &ScriptEconomyDate::IsValid, "IsValid", ".");
SQGSEconomyDate.DefSQMethod(engine, &ScriptEconomyDate::GetYear, "GetYear", ".");
SQGSEconomyDate.DefSQMethod(engine, &ScriptEconomyDate::GetMonth, "GetMonth", ".");
SQGSEconomyDate.DefSQMethod(engine, &ScriptEconomyDate::GetDayOfMonth, "GetDayOfMonth", ".");
SQGSEconomyDate.DefSQMethod(engine, &ScriptEconomyDate::date, "date", ".");
SQGSEconomyDate.DefSQAdvancedMethod(engine, &ScriptEconomyDate::_tostring, "_tostring");
SQGSEconomyDate.DefSQAdvancedMethod(engine, &ScriptEconomyDate::_add, "_add");
SQGSEconomyDate.DefSQAdvancedMethod(engine, &ScriptEconomyDate::_sub, "_sub");
SQGSEconomyDate.DefSQAdvancedMethod(engine, &ScriptEconomyDate::_cmp, "_cmp");
SQGSEconomyDate.PostRegister(engine);
DefSQClass<ScriptCalendarDate, ScriptType::GS> SQGSCalendarDate("GSCalendarDate");
SQGSCalendarDate.PreRegister(engine, "GSObject");
SQGSCalendarDate.AddSQAdvancedConstructor(engine);
SQGSCalendarDate.DefSQConst(engine, ScriptCalendarDate::DATE_INVALID, "DATE_INVALID");
SQGSCalendarDate.DefSQStaticMethod(engine, &ScriptCalendarDate::GetCurrentDate, "GetCurrentDate", ".");
SQGSCalendarDate.DefSQStaticMethod(engine, &ScriptCalendarDate::GetDate, "GetDate", ".iii");
SQGSCalendarDate.DefSQStaticMethod(engine, &ScriptCalendarDate::GetSystemTime, "GetSystemTime", ".");
SQGSCalendarDate.DefSQMethod(engine, &ScriptCalendarDate::IsValid, "IsValid", ".");
SQGSCalendarDate.DefSQMethod(engine, &ScriptCalendarDate::GetYear, "GetYear", ".");
SQGSCalendarDate.DefSQMethod(engine, &ScriptCalendarDate::GetMonth, "GetMonth", ".");
SQGSCalendarDate.DefSQMethod(engine, &ScriptCalendarDate::GetDayOfMonth, "GetDayOfMonth", ".");
SQGSCalendarDate.DefSQMethod(engine, &ScriptCalendarDate::date, "date", ".");
SQGSCalendarDate.DefSQAdvancedMethod(engine, &ScriptCalendarDate::_tostring, "_tostring");
SQGSCalendarDate.DefSQAdvancedMethod(engine, &ScriptCalendarDate::_add, "_add");
SQGSCalendarDate.DefSQAdvancedMethod(engine, &ScriptCalendarDate::_sub, "_sub");
SQGSCalendarDate.DefSQAdvancedMethod(engine, &ScriptCalendarDate::_cmp, "_cmp");
SQGSCalendarDate.PostRegister(engine);
}

View File

@@ -29,6 +29,11 @@
* \li GSCargo::CC_NON_POTABLE
* \li GSVehicleList_Waypoint
* \li GSBaseStation::GetOwner
* \li GSEconomyDate
* \li GSCalendarDate
*
* API removals:
* \li GSDate
*
* Other changes:
* \li GSBridge::GetBridgeID renamed to GSBridge::GetBridgeType

View File

@@ -66,9 +66,9 @@
return ::BaseStation::Get(station_id)->xy;
}
/* static */ ScriptDate::Date ScriptBaseStation::GetConstructionDate(StationID station_id)
/* static */ ScriptCalendarDate *ScriptBaseStation::GetConstructionDate(StationID station_id)
{
if (!IsValidBaseStation(station_id)) return ScriptDate::DATE_INVALID;
if (!IsValidBaseStation(station_id)) return new ScriptCalendarDate();
return (ScriptDate::Date)::BaseStation::Get(station_id)->build_date.base();
return new ScriptCalendarDate(static_cast<ScriptCalendarDate::Date>(::BaseStation::Get(station_id)->build_date.base()));
}

View File

@@ -78,7 +78,7 @@ public:
* @return The last calendar-date some part of this station was build.
* @see \ref ScriptCalendarTime
*/
static ScriptDate::Date GetConstructionDate(StationID station_id);
static ScriptCalendarDate *GetConstructionDate(StationID station_id);
};
#endif /* SCRIPT_BASESTATION_HPP */

View File

@@ -46,9 +46,9 @@ static NetworkClientInfo *FindClientInfo(ScriptClient::ClientID client)
return ScriptCompany::ToScriptCompanyID(ci->client_playas);
}
/* static */ ScriptDate::Date ScriptClient::GetJoinDate(ScriptClient::ClientID client)
/* static */ ScriptEconomyDate *ScriptClient::GetJoinDate(ScriptClient::ClientID client)
{
NetworkClientInfo *ci = FindClientInfo(client);
if (ci == nullptr) return ScriptDate::DATE_INVALID;
return (ScriptDate::Date)ci->join_date.base();
if (ci == nullptr) return new ScriptEconomyDate();
return new ScriptEconomyDate(static_cast<ScriptEconomyDate::Date>(ci->join_date.base()));
}

View File

@@ -62,7 +62,7 @@ public:
* @return The economy-date when client has joined.
* @see \ref ScriptEconomyTime
*/
static ScriptDate::Date GetJoinDate(ClientID client);
static ScriptEconomyDate *GetJoinDate(ClientID client);
};

View File

@@ -1,68 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
/** @file script_date.cpp Implementation of ScriptDate. */
#include "../../stdafx.h"
#include "script_date.hpp"
#include "../../timer/timer_game_economy.h"
#include <time.h>
#include "../../safeguards.h"
/* static */ bool ScriptDate::IsValidDate(Date date)
{
return date >= 0;
}
/* static */ ScriptDate::Date ScriptDate::GetCurrentDate()
{
return (ScriptDate::Date)TimerGameEconomy::date.base();
}
/* static */ SQInteger ScriptDate::GetYear(ScriptDate::Date date)
{
if (date < 0) return DATE_INVALID;
::TimerGameEconomy::YearMonthDay ymd = ::TimerGameEconomy::ConvertDateToYMD(::TimerGameEconomy::Date{date});
return ymd.year.base();
}
/* static */ SQInteger ScriptDate::GetMonth(ScriptDate::Date date)
{
if (date < 0) return DATE_INVALID;
::TimerGameEconomy::YearMonthDay ymd = ::TimerGameEconomy::ConvertDateToYMD(::TimerGameEconomy::Date{date});
return ymd.month + 1;
}
/* static */ SQInteger ScriptDate::GetDayOfMonth(ScriptDate::Date date)
{
if (date < 0) return DATE_INVALID;
::TimerGameEconomy::YearMonthDay ymd = ::TimerGameEconomy::ConvertDateToYMD(::TimerGameEconomy::Date{date});
return ymd.day;
}
/* static */ ScriptDate::Date ScriptDate::GetDate(SQInteger year, SQInteger month, SQInteger day_of_month)
{
if (month < 1 || month > 12) return DATE_INVALID;
if (day_of_month < 1 || day_of_month > 31) return DATE_INVALID;
::TimerGameEconomy::Year timer_year{ClampTo<int32_t>(year)};
if (timer_year < EconomyTime::MIN_YEAR || timer_year > EconomyTime::MAX_YEAR) return DATE_INVALID;
return static_cast<ScriptDate::Date>(::TimerGameEconomy::ConvertYMDToDate(timer_year, month - 1, day_of_month).base());
}
/* static */ SQInteger ScriptDate::GetSystemTime()
{
time_t t;
time(&t);
return t;
}

View File

@@ -12,7 +12,175 @@
#include "script_object.hpp"
#include "../../timer/timer_game_economy.h"
#include "../squirrel_helper.hpp"
#ifndef DOXYGEN_API
template <size_t N>
struct StringLiteral {
constexpr StringLiteral(const char (&str)[N])
{
std::copy_n(str, N, this->value);
}
char value[N];
};
template <class Timer, class Time, StringLiteral Tag>
class ScriptDateBase : public ScriptObject {
public:
using YearMonthDay = Timer::YearMonthDay;
using Year = Timer::Year;
enum Date {
DATE_INVALID = Time::INVALID_DATE.base(),
};
ScriptDateBase(Date date = DATE_INVALID) : _date(date) {}
ScriptDateBase(HSQUIRRELVM vm) : _date(DATE_INVALID)
{
int nparam = sq_gettop(vm) - 1;
if (nparam >= 1) {
SQInteger date;
if (SQ_FAILED(sq_getinteger(vm, 2, &date))) {
throw sq_throwerror(vm, "Argument must be an integer");
}
this->_date = static_cast<Date>(date);
}
}
bool IsValid()
{
return this->_date > 0;
}
static ScriptDateBase *GetCurrentDate()
{
return new ScriptDateBase(static_cast<Date>(Timer::date.base()));
}
SQInteger GetYear()
{
if (!this->IsValid()) return DATE_INVALID;
YearMonthDay ymd = Timer::ConvertDateToYMD(static_cast<Timer::Date>(this->_date));
return ymd.year.base();
}
SQInteger GetMonth()
{
if (!this->IsValid()) return DATE_INVALID;
YearMonthDay ymd = Timer::ConvertDateToYMD(static_cast<Timer::Date>(this->_date));
return ymd.month + 1;
}
SQInteger GetDayOfMonth()
{
if (!this->IsValid()) return DATE_INVALID;
YearMonthDay ymd = Timer::ConvertDateToYMD(static_cast<Timer::Date>(this->_date));
return ymd.day;
}
static ScriptDateBase *GetDate(SQInteger year, SQInteger month, SQInteger day_of_month)
{
if (month < 1 || month > 12) return new ScriptDateBase();
if (day_of_month < 1 || day_of_month > 31) return new ScriptDateBase();
Year timer_year{ClampTo<int32_t>(year)};
if (timer_year < Time::MIN_YEAR || timer_year > Time::MAX_YEAR) return new ScriptDateBase();
return new ScriptDateBase(static_cast<Date>(Timer::ConvertYMDToDate(timer_year, month - 1, day_of_month).base()));
}
static SQInteger GetSystemTime()
{
time_t t;
time(&t);
return t;
}
Date date() { return this->_date; }
SQInteger _tostring(HSQUIRRELVM vm)
{
sq_pushstring(vm, fmt::format("{}", this->_date));
return 1;
}
SQInteger _add(HSQUIRRELVM vm)
{
SQInteger days = 0;
switch (sq_gettype(vm, 2)) {
case OT_INTEGER:
sq_getinteger(vm, 2, &days);
break;
case OT_INSTANCE: {
ScriptDateBase *other = static_cast<ScriptDateBase *>(Squirrel::GetRealInstance(vm, 2, Tag.value));
if (other == nullptr) return SQ_ERROR;
days = other->_date;
break;
}
default: return SQ_ERROR;
}
ScriptDateBase *res = new ScriptDateBase(static_cast<Date>(this->_date + days));
res->AddRef();
Squirrel::CreateClassInstanceVM(vm, Tag.value, res, nullptr, SQConvert::DefSQDestructorCallback<ScriptDateBase>, true);
return 1;
}
SQInteger _sub(HSQUIRRELVM vm)
{
SQInteger days = 0;
switch (sq_gettype(vm, 2)) {
case OT_INTEGER:
sq_getinteger(vm, 2, &days);
break;
case OT_INSTANCE: {
ScriptDateBase *other = static_cast<ScriptDateBase *>(Squirrel::GetRealInstance(vm, 2, Tag.value));
if (other == nullptr) return SQ_ERROR;
days = other->_date;
break;
}
default: return SQ_ERROR;
}
ScriptDateBase *res = new ScriptDateBase(static_cast<Date>(this->_date - days));
res->AddRef();
Squirrel::CreateClassInstanceVM(vm, Tag.value, res, nullptr, SQConvert::DefSQDestructorCallback<ScriptDateBase>, true);
return 1;
}
SQInteger _cmp(HSQUIRRELVM vm)
{
ScriptDateBase *other = static_cast<ScriptDateBase *>(Squirrel::GetRealInstance(vm, 2, Tag.value));
if (other == nullptr) return SQ_ERROR;
sq_pushinteger(vm, this->_date - other->_date);
return 1;
}
bool SaveObject(HSQUIRRELVM vm) override
{
sq_pushstring(vm, Tag.value);
sq_pushinteger(vm, this->_date);
return true;
}
bool LoadObject(HSQUIRRELVM vm) override
{
if (sq_gettype(vm, -1) != OT_INTEGER) return false;
SQInteger val;
sq_getinteger(vm, -1, &val);
this->_date = static_cast<Date>(val);
return true;
}
protected:
Date _date = DATE_INVALID;
};
using ScriptEconomyDate = ScriptDateBase<TimerGameEconomy, EconomyTime, "EconomyDate">;
using ScriptCalendarDate = ScriptDateBase<TimerGameCalendar, CalendarTime, "CalendarDate">;
#else
/**
* Class that handles all date related (calculation) functions.
* @api ai game
@@ -48,22 +216,23 @@
* Economy-time will use a 360 day calendar (12 months with 30 days each), which runs at a constant speed of one economy-month per realtime-minute.
* Calendar-time will use a Gregorian calendar, which can be slowed to stopped via game settings.
*/
class ScriptDate : public ScriptObject {
class ScriptEconomyDate : public ScriptObject {
public:
/**
* Date data type is an integer value. Use ScriptDate::GetDate to
* Date data type is an integer value. Use ScriptEconomyDate::GetDate to
* compose valid date values for a known year, month and day.
*/
enum Date {
DATE_INVALID = ::EconomyTime::INVALID_DATE.base(), ///< A value representing an invalid date.
DATE_INVALID, ///< A value representing an invalid date.
};
ScriptEconomyDate(Date date = DATE_INVALID);
/**
* Validates if a date value represent a valid date.
* @param date The date to validate.
* @return True if the date is valid, otherwise false
*/
static bool IsValidDate(Date date);
bool IsValid();
/**
* Get the current date.
@@ -72,28 +241,25 @@ public:
* 100 but not by 400.
* @return The current date.
*/
static Date GetCurrentDate();
static ScriptEconomyDate GetCurrentDate();
/**
* Get the year of the given date.
* @param date The date to get the year of.
* @return The year.
*/
static SQInteger GetYear(Date date);
SQInteger GetYear();
/**
* Get the month of the given date.
* @param date The date to get the month of.
* @return The month.
*/
static SQInteger GetMonth(Date date);
SQInteger GetMonth();
/**
* Get the day (of the month) of the given date.
* @param date The date to get the day of.
* @return The day.
*/
static SQInteger GetDayOfMonth(Date date);
SQInteger GetDayOfMonth();
/**
* Get the date given a year, month and day of month.
@@ -102,7 +268,7 @@ public:
* @param day_of_month The day of month of the to-be determined date.
* @return The date.
*/
static Date GetDate(SQInteger year, SQInteger month, SQInteger day_of_month);
static ScriptEconomyDate GetDate(SQInteger year, SQInteger month, SQInteger day_of_month);
/**
* Get the time of the host system.
@@ -111,6 +277,84 @@ public:
* @note This uses the clock of the host system, which can skew or be set back. Use with caution.
*/
static SQInteger GetSystemTime();
/**
* Get the date.
* @return The date.
*/
Date date();
};
class ScriptCalendarDate : public ScriptObject {
public:
/**
* Date data type is an integer value. Use ScriptCalendarDate::GetDate to
* compose valid date values for a known year, month and day.
*/
enum Date {
DATE_INVALID, ///< A value representing an invalid date.
};
ScriptCalendarDate(Date date = DATE_INVALID);
/**
* Validates if a date value represent a valid date.
* @return True if the date is valid, otherwise false
*/
bool IsValid();
/**
* Get the current date.
* This is the number of days since epoch under the assumption that
* there is a leap year every 4 years, except when dividable by
* 100 but not by 400.
* @return The current date.
*/
static ScriptCalendarDate GetCurrentDate();
/**
* Get the year of the given date.
* @return The year.
*/
SQInteger GetYear();
/**
* Get the month of the given date.
* @return The month.
*/
SQInteger GetMonth();
/**
* Get the day (of the month) of the given date.
* @return The day.
*/
SQInteger GetDayOfMonth();
/**
* Get the date given a year, month and day of month.
* @param year The year of the to-be determined date.
* @param month The month of the to-be determined date.
* @param day_of_month The day of month of the to-be determined date.
* @return The date.
*/
static ScriptCalendarDate GetDate(SQInteger year, SQInteger month, SQInteger day_of_month);
/**
* Get the time of the host system.
* @return The amount of seconds passed since 1 Jan 1970.
* @api -ai
* @note This uses the clock of the host system, which can skew or be set back. Use with caution.
*/
static SQInteger GetSystemTime();
/**
* Get the date.
* @return The date.
*/
Date date();
};
#endif /* DOXYGEN_API */
#endif /* SCRIPT_DATE_HPP */

View File

@@ -169,11 +169,11 @@
return ::Engine::Get(engine_id)->GetDisplayMaxTractiveEffort() / 1000;
}
/* static */ ScriptDate::Date ScriptEngine::GetDesignDate(EngineID engine_id)
/* static */ ScriptCalendarDate *ScriptEngine::GetDesignDate(EngineID engine_id)
{
if (!IsValidEngine(engine_id)) return ScriptDate::DATE_INVALID;
if (!IsValidEngine(engine_id)) return new ScriptCalendarDate();
return (ScriptDate::Date)::Engine::Get(engine_id)->intro_date.base();
return new ScriptCalendarDate(static_cast<ScriptCalendarDate::Date>(::Engine::Get(engine_id)->intro_date.base()));
}
/* static */ ScriptVehicle::VehicleType ScriptEngine::GetVehicleType(EngineID engine_id)

View File

@@ -173,7 +173,7 @@ public:
* @return The calendar-date this engine was designed.
* @see \ref ScriptCalendarTime
*/
static ScriptDate::Date GetDesignDate(EngineID engine_id);
static ScriptCalendarDate *GetDesignDate(EngineID engine_id);
/**
* Get the type of an engine.

View File

@@ -49,11 +49,11 @@
return ::StrMakeValid(::GetString(STR_INDUSTRY_NAME, industry_id), {});
}
/* static */ ScriptDate::Date ScriptIndustry::GetConstructionDate(IndustryID industry_id)
/* static */ ScriptCalendarDate *ScriptIndustry::GetConstructionDate(IndustryID industry_id)
{
const Industry *i = Industry::GetIfValid(industry_id);
if (i == nullptr) return ScriptDate::DATE_INVALID;
return (ScriptDate::Date)i->construction_date.base();
if (i == nullptr) return new ScriptCalendarDate();
return new ScriptCalendarDate(static_cast<ScriptCalendarDate::Date>(i->construction_date.base()));
}
/* static */ bool ScriptIndustry::SetText(IndustryID industry_id, Text *text)
@@ -226,18 +226,18 @@
return i->last_prod_year.base();
}
/* static */ ScriptDate::Date ScriptIndustry::GetCargoLastAcceptedDate(IndustryID industry_id, CargoType cargo_type)
/* static */ ScriptEconomyDate *ScriptIndustry::GetCargoLastAcceptedDate(IndustryID industry_id, CargoType cargo_type)
{
const Industry *i = Industry::GetIfValid(industry_id);
if (i == nullptr) return ScriptDate::DATE_INVALID;
if (i == nullptr) return new ScriptEconomyDate();
if (!::IsValidCargoType(cargo_type)) {
auto it = std::max_element(std::begin(i->accepted), std::end(i->accepted), [](const auto &a, const auto &b) { return a.last_accepted < b.last_accepted; });
return (ScriptDate::Date)it->last_accepted.base();
return new ScriptEconomyDate(static_cast<ScriptEconomyDate::Date>(it->last_accepted.base()));
} else {
auto it = i->GetCargoAccepted(cargo_type);
if (it == std::end(i->accepted)) return ScriptDate::DATE_INVALID;
return (ScriptDate::Date)it->last_accepted.base();
if (it == std::end(i->accepted)) return new ScriptEconomyDate();
return new ScriptEconomyDate(static_cast<ScriptEconomyDate::Date>(it->last_accepted.base()));
}
}

View File

@@ -93,7 +93,7 @@ public:
* @see \ref ScriptCalendarTime
* @api -ai
*/
static ScriptDate::Date GetConstructionDate(IndustryID industry_id);
static ScriptCalendarDate *GetConstructionDate(IndustryID industry_id);
/**
* Set the custom text of an industry, shown in the GUI.
@@ -265,11 +265,11 @@ public:
* @param cargo_type The cargo to query, or INVALID_CARGO to query latest of all accepted cargoes.
* @pre IsValidIndustry(industry_id).
* @pre IsValidCargo(cargo_type) || cargo_type == INVALID_CARGO.
* @return Economy-date the industry last received cargo from a delivery, or ScriptDate::DATE_INVALID on error.
* @return Economy-date the industry last received cargo from a delivery, ScriptEconomyDate::IsValid() returns false on error.
* @see \ref ScriptEconomyTime
* @api -ai
*/
static ScriptDate::Date GetCargoLastAcceptedDate(IndustryID industry_id, CargoType cargo_type);
static ScriptEconomyDate *GetCargoLastAcceptedDate(IndustryID industry_id, CargoType cargo_type);
/**
* Get the current control flags for an industry.

View File

@@ -179,20 +179,21 @@ static inline bool StoryPageElementTypeRequiresText(StoryPageElementType type)
return ScriptCompany::ToScriptCompanyID(StoryPage::Get(story_page_id)->company);
}
/* static */ ScriptDate::Date ScriptStoryPage::GetDate(StoryPageID story_page_id)
/* static */ ScriptCalendarDate *ScriptStoryPage::GetDate(StoryPageID story_page_id)
{
EnforcePrecondition(ScriptDate::DATE_INVALID, IsValidStoryPage(story_page_id));
EnforceDeityMode(ScriptDate::DATE_INVALID);
EnforcePrecondition(new ScriptCalendarDate(), IsValidStoryPage(story_page_id));
EnforceDeityMode(new ScriptCalendarDate());
return (ScriptDate::Date)StoryPage::Get(story_page_id)->date.base();
return new ScriptCalendarDate(static_cast<ScriptCalendarDate::Date>(StoryPage::Get(story_page_id)->date.base()));
}
/* static */ bool ScriptStoryPage::SetDate(StoryPageID story_page_id, ScriptDate::Date date)
/* static */ bool ScriptStoryPage::SetDate(StoryPageID story_page_id, ScriptCalendarDate *date)
{
EnforcePrecondition(false, IsValidStoryPage(story_page_id));
EnforceDeityMode(false);
if (date == nullptr) date = new ScriptCalendarDate();
return ScriptObject::Command<CMD_SET_STORY_PAGE_DATE>::Do(story_page_id, ::TimerGameCalendar::Date{date});
return ScriptObject::Command<CMD_SET_STORY_PAGE_DATE>::Do(story_page_id, ::TimerGameCalendar::Date{date->date()});
}

View File

@@ -256,18 +256,18 @@ public:
* @pre IsValidStoryPage(story_page_id).
* @see \ref ScriptCalendarTime
*/
static ScriptDate::Date GetDate(StoryPageID story_page_id);
static ScriptCalendarDate *GetDate(StoryPageID story_page_id);
/**
* Update date of a story page. The date is shown in the top left of the page
* @param story_page_id The story page to set the date for.
* @param date Calendar-date to display at the top of story page or ScriptDate::DATE_INVALID to disable showing date on this page. (also, @see ScriptDate)
* @param date Calendar-date to display at the top of story page or null to disable showing date on this page. (also, @see ScriptCalendarDate)
* @return True if the action succeeded.
* @pre ScriptCompanyMode::IsDeity().
* @pre IsValidStoryPage(story_page_id).
* @see \ref ScriptCalendarTime
*/
static bool SetDate(StoryPageID story_page_id, ScriptDate::Date date);
static bool SetDate(StoryPageID story_page_id, ScriptCalendarDate *date);
/**
* Update title of a story page. The title is shown in the page selector drop down.

View File

@@ -53,9 +53,9 @@
return ScriptCompany::ToScriptCompanyID(::Subsidy::Get(subsidy_id)->awarded);
}
/* static */ ScriptDate::Date ScriptSubsidy::GetExpireDate(SubsidyID subsidy_id)
/* static */ ScriptEconomyDate *ScriptSubsidy::GetExpireDate(SubsidyID subsidy_id)
{
if (!IsValidSubsidy(subsidy_id)) return ScriptDate::DATE_INVALID;
if (!IsValidSubsidy(subsidy_id)) return new ScriptEconomyDate();
TimerGameEconomy::YearMonthDay ymd = TimerGameEconomy::ConvertDateToYMD(TimerGameEconomy::date);
ymd.day = 1;
@@ -63,7 +63,7 @@
ymd.month = m % 12;
ymd.year += TimerGameEconomy::Year{m / 12};
return (ScriptDate::Date)TimerGameEconomy::ConvertYMDToDate(ymd.year, ymd.month, ymd.day).base();
return ScriptEconomyDate::GetDate(ymd.year.base(), ymd.month + 1, ymd.day);
}
/* static */ CargoType ScriptSubsidy::GetCargoType(SubsidyID subsidy_id)

View File

@@ -85,7 +85,7 @@ public:
* awarded.
* @see \ref ScriptEconomyTime
*/
static ScriptDate::Date GetExpireDate(SubsidyID subsidy_id);
static ScriptEconomyDate *GetExpireDate(SubsidyID subsidy_id);
/**
* Get the cargo type that has to be transported in order to be awarded this

View File

@@ -0,0 +1,24 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "../script_date.hpp"
namespace SQConvert {
/* Allow ScriptEconomyDate to be used as Squirrel parameter */
template <> struct Param<ScriptEconomyDate *> { static inline ScriptEconomyDate *Get(HSQUIRRELVM vm, int index) { return static_cast<ScriptEconomyDate *>(sq_gettype(vm, index) == OT_NULL ? nullptr : Squirrel::GetRealInstance(vm, index, "EconomyDate")); } };
template <> struct Param<ScriptEconomyDate &> { static inline ScriptEconomyDate &Get(HSQUIRRELVM vm, int index) { return *static_cast<ScriptEconomyDate *>(Squirrel::GetRealInstance(vm, index, "EconomyDate")); } };
template <> struct Param<const ScriptEconomyDate *> { static inline const ScriptEconomyDate *Get(HSQUIRRELVM vm, int index) { return static_cast<ScriptEconomyDate *>(sq_gettype(vm, index) == OT_NULL ? nullptr : Squirrel::GetRealInstance(vm, index, "EconomyDate")); } };
template <> struct Param<const ScriptEconomyDate &> { static inline const ScriptEconomyDate &Get(HSQUIRRELVM vm, int index) { return *static_cast<ScriptEconomyDate *>(Squirrel::GetRealInstance(vm, index, "EconomyDate")); } };
template <> struct Return<ScriptEconomyDate *> { static inline int Set(HSQUIRRELVM vm, ScriptEconomyDate *res) { if (res == nullptr) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "EconomyDate", res, nullptr, DefSQDestructorCallback<ScriptEconomyDate>, true); return 1; } };
/* Allow ScriptCalendarDate to be used as Squirrel parameter */
template <> struct Param<ScriptCalendarDate *> { static inline ScriptCalendarDate *Get(HSQUIRRELVM vm, int index) { return static_cast<ScriptCalendarDate *>(sq_gettype(vm, index) == OT_NULL ? nullptr : Squirrel::GetRealInstance(vm, index, "CalendarDate")); } };
template <> struct Param<ScriptCalendarDate &> { static inline ScriptCalendarDate &Get(HSQUIRRELVM vm, int index) { return *static_cast<ScriptCalendarDate *>(Squirrel::GetRealInstance(vm, index, "CalendarDate")); } };
template <> struct Param<const ScriptCalendarDate *> { static inline const ScriptCalendarDate *Get(HSQUIRRELVM vm, int index) { return static_cast<ScriptCalendarDate *>(sq_gettype(vm, index) == OT_NULL ? nullptr : Squirrel::GetRealInstance(vm, index, "CalendarDate")); } };
template <> struct Param<const ScriptCalendarDate &> { static inline const ScriptCalendarDate &Get(HSQUIRRELVM vm, int index) { return *static_cast<ScriptCalendarDate *>(Squirrel::GetRealInstance(vm, index, "CalendarDate")); } };
template <> struct Return<ScriptCalendarDate *> { static inline int Set(HSQUIRRELVM vm, ScriptCalendarDate *res) { if (res == nullptr) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "CalendarDate", res, nullptr, DefSQDestructorCallback<ScriptCalendarDate>, true); return 1; } };
} // namespace SQConvert

View File

@@ -3,6 +3,7 @@ add_test_files(
bitmath_func.cpp
enum_over_optimisation.cpp
flatset_type.cpp
history_func.cpp
landscape_partial_pixel_z.cpp
math_func.cpp
mock_environment.h

View File

@@ -0,0 +1,83 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
/** @file history_func.cpp Test functionality for misc/history_func. */
#include "../stdafx.h"
#include "../3rdparty/catch2/catch.hpp"
#include "../misc/history_type.hpp"
#include "../misc/history_func.hpp"
#include "../safeguards.h"
template <>
uint16_t SumHistory(std::span<const uint16_t> history)
{
uint32_t total = std::accumulate(std::begin(history), std::end(history), 0, [](uint32_t r, const uint16_t &value) { return r + value; });
return ClampTo<uint16_t>(total);
}
/**
* Helper to get history records and return the value, instead returning its validity.
* @param history History data to extract from.
* @param hr History range to get.
* @param age Age of data to get.
* @return Historical value for the period and age.
*/
template <typename T>
T GetHistory(const HistoryData<T> &history, const HistoryRange &hr, uint age)
{
T result;
GetHistory(history, 0, hr, age, result);
return result;
}
TEST_CASE("History Rotation and Reporting tests")
{
HistoryData<uint16_t> history{};
ValidHistoryMask valid_history = 0;
/* Fill the history with decreasing data points for 24 years of history. This ensures that no data period should
* contain the same value as another period. */
uint16_t i = 12 * HISTORY_PERIODS;
for (uint date = 1; date <= 12 * HISTORY_PERIODS; ++date, --i) {
history[THIS_MONTH] = i;
UpdateValidHistory(valid_history, HISTORY_YEAR, date % 12);
RotateHistory(history, valid_history, HISTORY_YEAR, date % 12);
}
/* With the decreasing sequence, the expected value is triangle number (x*x+n)/2 and the square of the total divisions.
* for quarters: 1 + 2 + 3 = 6, 4 + 5 + 6 = 15, 7 + 8 + 9 = 24, 10 + 11 + 12 = 33
* 13 + 14 + 15 = 42, 16 + 17 + 18 = 51, 19 + 20 + 21 = 60, 22 + 23 + 24 = 69...
* for years: 6 + 15 + 24 + 33 = 78, 42 + 51 + 60 + 69 = 222...
*/
for (uint j = 0; j < HISTORY_PERIODS; ++j) {
CHECK(GetHistory(history, HISTORY_MONTH, j) == (( 1 * 1 + 1) / 2) + 1 * 1 * j);
CHECK(GetHistory(history, HISTORY_QUARTER, j) == (( 3 * 3 + 3) / 2) + 3 * 3 * j);
CHECK(GetHistory(history, HISTORY_YEAR, j) == ((12 * 12 + 12) / 2) + 12 * 12 * j);
}
/* Double-check quarter history matches summed month history. */
CHECK(GetHistory(history, HISTORY_MONTH, 0) + GetHistory(history, HISTORY_MONTH, 1) + GetHistory(history, HISTORY_MONTH, 2) == GetHistory(history, HISTORY_QUARTER, 0));
CHECK(GetHistory(history, HISTORY_MONTH, 3) + GetHistory(history, HISTORY_MONTH, 4) + GetHistory(history, HISTORY_MONTH, 5) == GetHistory(history, HISTORY_QUARTER, 1));
CHECK(GetHistory(history, HISTORY_MONTH, 6) + GetHistory(history, HISTORY_MONTH, 7) + GetHistory(history, HISTORY_MONTH, 8) == GetHistory(history, HISTORY_QUARTER, 2));
CHECK(GetHistory(history, HISTORY_MONTH, 9) + GetHistory(history, HISTORY_MONTH, 10) + GetHistory(history, HISTORY_MONTH, 11) == GetHistory(history, HISTORY_QUARTER, 3));
CHECK(GetHistory(history, HISTORY_MONTH, 12) + GetHistory(history, HISTORY_MONTH, 13) + GetHistory(history, HISTORY_MONTH, 14) == GetHistory(history, HISTORY_QUARTER, 4));
CHECK(GetHistory(history, HISTORY_MONTH, 15) + GetHistory(history, HISTORY_MONTH, 16) + GetHistory(history, HISTORY_MONTH, 17) == GetHistory(history, HISTORY_QUARTER, 5));
CHECK(GetHistory(history, HISTORY_MONTH, 18) + GetHistory(history, HISTORY_MONTH, 19) + GetHistory(history, HISTORY_MONTH, 20) == GetHistory(history, HISTORY_QUARTER, 6));
CHECK(GetHistory(history, HISTORY_MONTH, 21) + GetHistory(history, HISTORY_MONTH, 22) + GetHistory(history, HISTORY_MONTH, 23) == GetHistory(history, HISTORY_QUARTER, 7));
/* Double-check year history matches summed quarter history. */
CHECK(GetHistory(history, HISTORY_QUARTER, 0) + GetHistory(history, HISTORY_QUARTER, 1) + GetHistory(history, HISTORY_QUARTER, 2) + GetHistory(history, HISTORY_QUARTER, 3) == GetHistory(history, HISTORY_YEAR, 0));
CHECK(GetHistory(history, HISTORY_QUARTER, 4) + GetHistory(history, HISTORY_QUARTER, 5) + GetHistory(history, HISTORY_QUARTER, 6) + GetHistory(history, HISTORY_QUARTER, 7) == GetHistory(history, HISTORY_YEAR, 1));
CHECK(GetHistory(history, HISTORY_QUARTER, 8) + GetHistory(history, HISTORY_QUARTER, 9) + GetHistory(history, HISTORY_QUARTER, 10) + GetHistory(history, HISTORY_QUARTER, 11) == GetHistory(history, HISTORY_YEAR, 2));
CHECK(GetHistory(history, HISTORY_QUARTER, 12) + GetHistory(history, HISTORY_QUARTER, 13) + GetHistory(history, HISTORY_QUARTER, 14) + GetHistory(history, HISTORY_QUARTER, 15) == GetHistory(history, HISTORY_YEAR, 3));
CHECK(GetHistory(history, HISTORY_QUARTER, 16) + GetHistory(history, HISTORY_QUARTER, 17) + GetHistory(history, HISTORY_QUARTER, 18) + GetHistory(history, HISTORY_QUARTER, 19) == GetHistory(history, HISTORY_YEAR, 4));
CHECK(GetHistory(history, HISTORY_QUARTER, 20) + GetHistory(history, HISTORY_QUARTER, 21) + GetHistory(history, HISTORY_QUARTER, 22) + GetHistory(history, HISTORY_QUARTER, 23) == GetHistory(history, HISTORY_YEAR, 5));
}

View File

@@ -37,6 +37,7 @@ enum GraphWidgets : WidgetID {
WID_GRAPH_MATRIX_SCROLLBAR,///< Cargo list scrollbar.
WID_GRAPH_RANGE_MATRIX, ///< Range list.
WID_GRAPH_SCALE_MATRIX, ///< Horizontal axis scale list.
WID_PHG_DETAILED_PERFORMANCE, ///< Detailed performance.
};