mirror of https://github.com/OpenTTD/OpenTTD
Merge 642f4d0ab6
into baced00e9f
commit
d7c06dfd32
|
@ -1067,6 +1067,7 @@ static uint DeliverGoodsToIndustry(const Station *st, CargoType cargo_type, uint
|
|||
|
||||
uint amount = std::min(num_pieces, 0xFFFFu - it->waiting);
|
||||
it->waiting += amount;
|
||||
it->GetOrCreateHistory()[THIS_MONTH].accepted += amount;
|
||||
it->last_accepted = TimerGameEconomy::date;
|
||||
num_pieces -= amount;
|
||||
accepted += amount;
|
||||
|
|
|
@ -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,8 +184,27 @@ 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.
|
||||
|
||||
uint64_t excluded_data = 0; ///< bitmask of the datasets that shouldn't be displayed.
|
||||
uint64_t excluded_range = 0; ///< bitmask of ranges that should not be displayed.
|
||||
struct GraphScale {
|
||||
StringID label = STR_NULL;
|
||||
uint8_t month_increment = 0;
|
||||
int16_t x_values_increment = 0;
|
||||
};
|
||||
|
||||
static inline constexpr GraphScale MONTHLY_SCALE_WALLCLOCK[] = {
|
||||
{STR_GRAPH_LAST_24_MINUTES_TIME_LABEL, HISTORY_MONTH.total_division, ECONOMY_MONTH_MINUTES},
|
||||
{STR_GRAPH_LAST_72_MINUTES_TIME_LABEL, HISTORY_QUARTER.total_division, ECONOMY_QUARTER_MINUTES},
|
||||
{STR_GRAPH_LAST_288_MINUTES_TIME_LABEL, HISTORY_YEAR.total_division, ECONOMY_YEAR_MINUTES},
|
||||
};
|
||||
|
||||
static inline constexpr GraphScale MONTHLY_SCALE_CALENDAR[] = {
|
||||
{STR_GRAPH_LAST_24_MONTHS, HISTORY_MONTH.total_division, ECONOMY_MONTH_MINUTES},
|
||||
{STR_GRAPH_LAST_24_QUARTERS, HISTORY_QUARTER.total_division, ECONOMY_QUARTER_MINUTES},
|
||||
{STR_GRAPH_LAST_24_YEARS, HISTORY_YEAR.total_division, ECONOMY_YEAR_MINUTES},
|
||||
};
|
||||
|
||||
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.
|
||||
uint8_t num_on_x_axis = 0;
|
||||
uint8_t num_vert_lines = GRAPH_NUM_MONTHS;
|
||||
|
||||
|
@ -210,19 +231,28 @@ 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.
|
||||
bool highlight_state = false; ///< Current state of highlight, toggled every TIMER_BLINK_INTERVAL period.
|
||||
|
||||
template <typename Tprojection>
|
||||
struct Filler {
|
||||
struct BaseFiller {
|
||||
DataSet &dataset; ///< Dataset to fill.
|
||||
|
||||
inline void MakeZero(uint i) const { this->dataset.values[i] = 0; }
|
||||
inline void MakeInvalid(uint i) const { this->dataset.values[i] = INVALID_DATAPOINT; }
|
||||
};
|
||||
|
||||
template <typename Tprojection>
|
||||
struct Filler : BaseFiller {
|
||||
const Tprojection &proj; ///< Projection to apply.
|
||||
|
||||
constexpr Filler(DataSet &dataset, const Tprojection &proj) : BaseFiller(dataset), proj(proj) {}
|
||||
|
||||
inline void Fill(uint i, const auto &data) const { this->dataset.values[i] = std::invoke(this->proj, data); }
|
||||
inline void MakeInvalid(uint i) const { this->dataset.values[i] = INVALID_DATAPOINT; }
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -609,24 +639,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: {
|
||||
|
@ -675,13 +715,17 @@ public:
|
|||
uint index = 0;
|
||||
Rect line = r.WithHeight(line_height);
|
||||
for (const auto &str : this->ranges) {
|
||||
bool lowered = !HasBit(this->excluded_range, index);
|
||||
bool lowered = !HasBit(this->excluded_range, index) && !HasBit(this->masked_range, index);
|
||||
|
||||
/* Redraw frame if lowered */
|
||||
if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
|
||||
|
||||
const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
|
||||
DrawString(text, str, TC_BLACK, SA_CENTER, false, FS_SMALL);
|
||||
DrawString(text, str, (this->highlight_state && this->highlight_range == index) ? TC_WHITE : TC_BLACK, SA_CENTER, false, FS_SMALL);
|
||||
|
||||
if (HasBit(this->masked_range, index)) {
|
||||
GfxFillRect(line.Shrink(WidgetDimensions::scaled.bevel), GetColourGradient(COLOUR_BROWN, SHADE_DARKER), FILLRECT_CHECKER);
|
||||
}
|
||||
|
||||
line = line.Translate(0, line_height);
|
||||
++index;
|
||||
|
@ -689,6 +733,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;
|
||||
}
|
||||
}
|
||||
|
@ -704,11 +763,24 @@ public:
|
|||
case WID_GRAPH_RANGE_MATRIX: {
|
||||
int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical());
|
||||
|
||||
if (HasBit(this->masked_range, row)) break;
|
||||
ToggleBit(this->excluded_range, row);
|
||||
this->SetDirty();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -1115,6 +1187,7 @@ struct BaseCargoGraphWindow : BaseGraphWindow {
|
|||
{
|
||||
this->CreateNestedTree();
|
||||
|
||||
this->excluded_range = this->masked_range;
|
||||
this->cargo_types = this->GetCargoTypes(number);
|
||||
|
||||
this->vscroll = this->GetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR);
|
||||
|
@ -1608,7 +1681,9 @@ CompanyID PerformanceRatingDetailWindow::company = CompanyID::Invalid();
|
|||
struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
|
||||
static inline constexpr StringID RANGE_LABELS[] = {
|
||||
STR_GRAPH_INDUSTRY_RANGE_PRODUCED,
|
||||
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED
|
||||
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED,
|
||||
STR_GRAPH_INDUSTRY_RANGE_DELIVERED,
|
||||
STR_GRAPH_INDUSTRY_RANGE_WAITING,
|
||||
};
|
||||
|
||||
static inline CargoTypes excluded_cargo_types{};
|
||||
|
@ -1623,13 +1698,27 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
|
|||
this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
|
||||
this->ranges = RANGE_LABELS;
|
||||
|
||||
const Industry *i = Industry::Get(window_number);
|
||||
if (!i->IsCargoProduced()) this->masked_range = (1U << 0) | (1U << 1);
|
||||
if (!i->IsCargoAccepted()) this->masked_range = (1U << 2) | (1U << 3);
|
||||
|
||||
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{};
|
||||
const Industry *i = Industry::Get(window_number);
|
||||
for (const auto &a : i->accepted) {
|
||||
if (IsValidCargoType(a.cargo)) SetBit(cargo_types, a.cargo);
|
||||
}
|
||||
for (const auto &p : i->produced) {
|
||||
if (IsValidCargoType(p.cargo)) SetBit(cargo_types, p.cargo);
|
||||
}
|
||||
|
@ -1643,14 +1732,14 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
|
|||
|
||||
std::string GetWidgetString(WidgetID widget, StringID stringid) const override
|
||||
{
|
||||
if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_INDUSTRY_PRODUCTION_CAPTION, this->window_number);
|
||||
if (widget == WID_GRAPH_CAPTION) return GetString(STR_GRAPH_INDUSTRY_CAPTION, this->window_number);
|
||||
|
||||
return this->Window::GetWidgetString(widget, stringid);
|
||||
}
|
||||
|
||||
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--;
|
||||
|
@ -1688,7 +1777,34 @@ 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->selected_scale, produced_filler, transported_filler);
|
||||
}
|
||||
|
||||
for (const auto &a : i->accepted) {
|
||||
if (!IsValidCargoType(a.cargo)) continue;
|
||||
const CargoSpec *cs = CargoSpec::Get(a.cargo);
|
||||
|
||||
this->data.reserve(this->data.size() + 2);
|
||||
|
||||
DataSet &accepted = this->data.emplace_back();
|
||||
accepted.colour = cs->legend_colour;
|
||||
accepted.exclude_bit = cs->Index();
|
||||
accepted.range_bit = 2;
|
||||
accepted.dash = 1;
|
||||
auto accepted_filler = Filler{accepted, &Industry::AcceptedHistory::accepted};
|
||||
|
||||
DataSet &waiting = this->data.emplace_back();
|
||||
waiting.colour = cs->legend_colour;
|
||||
waiting.exclude_bit = cs->Index();
|
||||
waiting.range_bit = 3;
|
||||
waiting.dash = 4;
|
||||
auto waiting_filler = Filler{waiting, &Industry::AcceptedHistory::waiting};
|
||||
|
||||
if (a.history == nullptr) {
|
||||
FillFromEmpty<GRAPH_NUM_MONTHS>(i->valid_history, this->selected_scale, accepted_filler, waiting_filler);
|
||||
} else {
|
||||
FillFromHistory<GRAPH_NUM_MONTHS>(*a.history, i->valid_history, this->selected_scale, accepted_filler, waiting_filler);
|
||||
}
|
||||
}
|
||||
|
||||
this->SetDirty();
|
||||
|
@ -1708,7 +1824,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),
|
||||
|
@ -1717,6 +1833,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),
|
||||
|
|
|
@ -77,10 +77,27 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> {
|
|||
HistoryData<ProducedHistory> history{}; ///< History of cargo produced and transported for this month and 24 previous months
|
||||
};
|
||||
|
||||
struct AcceptedHistory {
|
||||
uint16_t accepted = 0; /// Total accepted.
|
||||
uint16_t waiting = 0; /// Average waiting.
|
||||
};
|
||||
|
||||
struct AcceptedCargo {
|
||||
CargoType cargo = 0; ///< Cargo type
|
||||
uint16_t waiting = 0; ///< Amount of cargo waiting to processed
|
||||
uint32_t accumulated_waiting = 0; ///< Accumulated waiting total over the last month, used to calculate average.
|
||||
TimerGameEconomy::Date last_accepted{}; ///< Last day cargo was accepted by this industry
|
||||
std::unique_ptr<HistoryData<AcceptedHistory>> history{}; ///< History of accepted and waiting cargo.
|
||||
|
||||
/**
|
||||
* Get history data, creating it if necessary.
|
||||
* @return Accepted history data.
|
||||
*/
|
||||
inline HistoryData<AcceptedHistory> &GetOrCreateHistory()
|
||||
{
|
||||
if (this->history == nullptr) this->history = std::make_unique<HistoryData<AcceptedHistory>>();
|
||||
return *this->history;
|
||||
}
|
||||
};
|
||||
|
||||
using ProducedCargoes = std::vector<ProducedCargo>;
|
||||
|
@ -151,7 +168,7 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> {
|
|||
*/
|
||||
inline const AcceptedCargo &GetAccepted(size_t slot) const
|
||||
{
|
||||
static const AcceptedCargo empty{INVALID_CARGO, 0, {}};
|
||||
static const AcceptedCargo empty{INVALID_CARGO, 0, 0, {}, {}};
|
||||
return slot < this->accepted.size() ? this->accepted[slot] : empty;
|
||||
}
|
||||
|
||||
|
|
|
@ -1245,6 +1245,10 @@ void OnTick_Industry()
|
|||
|
||||
for (Industry *i : Industry::Iterate()) {
|
||||
ProduceIndustryGoods(i);
|
||||
|
||||
if ((TimerGameTick::counter + i->index) % Ticks::DAY_TICKS == 0) {
|
||||
for (auto &a : i->accepted) a.accumulated_waiting += a.waiting;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1838,7 +1842,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, TimerGameEconomy::month);
|
||||
}
|
||||
|
||||
if (indspec->callback_mask.Test(IndustryCallbackMask::DecideColour)) {
|
||||
|
@ -2490,21 +2494,48 @@ 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, 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, month);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &a : i->accepted) {
|
||||
if (!IsValidCargoType(a.cargo)) continue;
|
||||
if (a.history == nullptr) continue;
|
||||
|
||||
(*a.history)[THIS_MONTH].waiting = GetAndResetAccumulatedAverage<uint16_t>(a.accumulated_waiting);
|
||||
RotateHistory(*a.history, i->valid_history, month);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -845,7 +845,7 @@ public:
|
|||
nvp->InitializeViewport(this, Industry::Get(window_number)->location.GetCenterTile(), ScaleZoomGUI(ZoomLevel::Industry));
|
||||
|
||||
const Industry *i = Industry::Get(window_number);
|
||||
if (!i->IsCargoProduced()) this->DisableWidget(WID_IV_GRAPH);
|
||||
if (!i->IsCargoProduced() && !i->IsCargoAccepted()) this->DisableWidget(WID_IV_GRAPH);
|
||||
|
||||
this->InvalidateData();
|
||||
}
|
||||
|
@ -1242,7 +1242,7 @@ static constexpr NWidgetPart _nested_industry_view_widgets[] = {
|
|||
EndContainer(),
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_DISPLAY), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_INDUSTRY_DISPLAY_CHAIN, STR_INDUSTRY_DISPLAY_CHAIN_TOOLTIP),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_GRAPH), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_INDUSTRY_VIEW_PRODUCTION_GRAPH, STR_INDUSTRY_VIEW_PRODUCTION_GRAPH_TOOLTIP),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_CREAM, WID_IV_GRAPH), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_INDUSTRY_VIEW_CARGO_GRAPH, STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP),
|
||||
NWidget(WWT_RESIZEBOX, COLOUR_CREAM),
|
||||
EndContainer(),
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
@ -634,9 +642,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Display
|
|||
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Toggle graph of this cargo type
|
||||
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
|
||||
|
||||
STR_GRAPH_INDUSTRY_PRODUCTION_CAPTION :{WHITE}{INDUSTRY} - Production History
|
||||
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} - Cargo History
|
||||
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Produced
|
||||
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Transported
|
||||
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :Delivered
|
||||
STR_GRAPH_INDUSTRY_RANGE_WAITING :Waiting
|
||||
|
||||
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Show detailed performance ratings
|
||||
|
||||
|
@ -4024,8 +4034,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}Producti
|
|||
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Production last minute:
|
||||
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{RAW_STRING}{BLACK} ({COMMA}% transported)
|
||||
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Centre the main view on industry location. Ctrl+Click to open a new viewport on industry location
|
||||
STR_INDUSTRY_VIEW_PRODUCTION_GRAPH :{BLACK}Production Graph
|
||||
STR_INDUSTRY_VIEW_PRODUCTION_GRAPH_TOOLTIP :{BLACK}Shows the graph of industry production history
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}Cargo Graph
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}Shows the graph of industry cargo history
|
||||
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Production level: {YELLOW}{COMMA}%
|
||||
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}The industry has announced imminent closure!
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ add_files(
|
|||
getoptdata.cpp
|
||||
getoptdata.h
|
||||
hashtable.hpp
|
||||
history.cpp
|
||||
history_func.hpp
|
||||
history_type.hpp
|
||||
lrucache.hpp
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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. */
|
||||
|
||||
#ifndef HISTORY_CPP
|
||||
#define HISTORY_CPP
|
||||
|
||||
#include "../stdafx.h"
|
||||
|
||||
#include "../core/bitmath_func.hpp"
|
||||
#include "history_type.hpp"
|
||||
#include "history_func.hpp"
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
static void UpdateValidHistory(ValidHistoryMask &valid_history, const HistoryRange &hr, uint cur_month)
|
||||
{
|
||||
if (cur_month % hr.total_division != 0) return;
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update mask of valid records.
|
||||
* @param[in,out] valid_history Valid history records.
|
||||
* @param age Current economy month.
|
||||
*/
|
||||
void UpdateValidHistory(ValidHistoryMask &valid_history, uint cur_month)
|
||||
{
|
||||
UpdateValidHistory(valid_history, HISTORY_MONTH, cur_month);
|
||||
UpdateValidHistory(valid_history, HISTORY_QUARTER, cur_month);
|
||||
UpdateValidHistory(valid_history, HISTORY_YEAR, cur_month);
|
||||
}
|
||||
|
||||
static 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/* + hr.division - 1*/);
|
||||
}
|
||||
if (age < hr.periods) {
|
||||
uint slot = hr.first + age - ((hr.hr->periods / hr.division) - 1);
|
||||
return HasBit(valid_history, slot);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsValidHistory(ValidHistoryMask valid_history, uint period, uint age)
|
||||
{
|
||||
switch (period) {
|
||||
case 0: return IsValidHistory(valid_history, HISTORY_MONTH, age);
|
||||
case 1: return IsValidHistory(valid_history, HISTORY_QUARTER, age);
|
||||
case 2: return IsValidHistory(valid_history, HISTORY_YEAR, age);
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* HISTORY_CPP */
|
|
@ -11,16 +11,22 @@
|
|||
#define HISTORY_FUNC_HPP
|
||||
|
||||
#include "../core/bitmath_func.hpp"
|
||||
#include "../core/math_func.hpp"
|
||||
#include "../timer/timer_game_economy.h"
|
||||
#include "history_type.hpp"
|
||||
|
||||
void UpdateValidHistory(ValidHistoryMask &valid_history, uint cur_month);
|
||||
bool IsValidHistory(ValidHistoryMask valid_history, uint period, uint age);
|
||||
|
||||
/**
|
||||
* Update mask of valid history records.
|
||||
* @param[in,out] valid_history Valid history records.
|
||||
* 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 History elements to sum.
|
||||
* @return Sum of history elements.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
template <typename T>
|
||||
T SumHistory(typename std::span<const T> history);
|
||||
|
||||
/**
|
||||
* Rotate history.
|
||||
|
@ -28,24 +34,105 @@ inline void UpdateValidHistory(ValidHistoryMask &valid_history)
|
|||
* @param history Historical data to rotate.
|
||||
*/
|
||||
template <typename T>
|
||||
void RotateHistory(HistoryData<T> &history)
|
||||
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 (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.division == 1) {
|
||||
history[hr.first] = history[hr.first - 1];
|
||||
} 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});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void RotateHistory(HistoryData<T> &history, ValidHistoryMask valid_history, uint cur_month)
|
||||
{
|
||||
RotateHistory(history, valid_history, HISTORY_MONTH, cur_month);
|
||||
RotateHistory(history, valid_history, HISTORY_QUARTER, cur_month);
|
||||
RotateHistory(history, valid_history, HISTORY_YEAR, cur_month);
|
||||
history.front() = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an average value for the previous month, as reset for the next month.
|
||||
* @param total Accrued total to average. Will be reset to zero.
|
||||
* @return Average value for the month.
|
||||
*/
|
||||
template <typename T, typename Taccrued>
|
||||
T GetAndResetAccumulatedAverage(Taccrued &total)
|
||||
{
|
||||
T result = ClampTo<T>(total / std::max(1U, TimerGameEconomy::days_since_last_month));
|
||||
total = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get history data for the specified period and age within that period.
|
||||
* @param history History data to extract from.
|
||||
* @param valid_history Mask of valid history records.
|
||||
* @param period Period to get.
|
||||
* @param age Age of data to get.
|
||||
* @param[out] result Variable to store historical value for period and age.
|
||||
* @return True if the value is valid.
|
||||
*/
|
||||
template <typename T>
|
||||
bool GetHistory(const HistoryData<T> &history, ValidHistoryMask valid_history, uint period, uint age, T &result)
|
||||
{
|
||||
switch (period) {
|
||||
case 0: return GetHistory(history, valid_history, HISTORY_MONTH, age, result);
|
||||
case 1: return GetHistory(history, valid_history, HISTORY_QUARTER, age, result);
|
||||
case 2: return GetHistory(history, valid_history, HISTORY_YEAR, age, result);
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill some data with historical data.
|
||||
* @param history Historical data to fill from.
|
||||
* @param valid_history Mask of valid history records.
|
||||
* @param period Period (monthly, quarterly, yearly) 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, uint period, Tfillers... fillers)
|
||||
{
|
||||
T data{};
|
||||
for (uint i = 0; i != N; ++i) {
|
||||
if (HasBit(valid_history, N - i)) {
|
||||
auto &data = history[N - i];
|
||||
if (GetHistory(history, valid_history, period, N - i - 1, data)) {
|
||||
(fillers.Fill(i, data), ...);
|
||||
} else {
|
||||
(fillers.MakeInvalid(i), ...);
|
||||
|
@ -53,4 +140,22 @@ void FillFromHistory(const HistoryData<T> &history, ValidHistoryMask valid_histo
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill some data with empty records.
|
||||
* @param valid_history Mask of valid history records.
|
||||
* @param period Period (monthly, quarterly, yearly) to fill with.
|
||||
* @param fillers Fillers to fill with history data.
|
||||
*/
|
||||
template <uint N, typename... Tfillers>
|
||||
void FillFromEmpty(ValidHistoryMask valid_history, uint period, Tfillers... fillers)
|
||||
{
|
||||
for (uint i = 0; i != N; ++i) {
|
||||
if (IsValidHistory(valid_history, period, N - i - 1)) {
|
||||
(fillers.MakeZero(i), ...);
|
||||
} else {
|
||||
(fillers.MakeInvalid(i), ...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* HISTORY_FUNC_HPP */
|
||||
|
|
|
@ -10,7 +10,40 @@
|
|||
#ifndef HISTORY_TYPE_HPP
|
||||
#define HISTORY_TYPE_HPP
|
||||
|
||||
static constexpr uint8_t HISTORY_RECORDS = 25;
|
||||
#include "../stdafx.h"
|
||||
|
||||
static constexpr uint8_t HISTORY_PERIODS = 24;
|
||||
|
||||
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 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;
|
||||
|
|
|
@ -19,12 +19,50 @@
|
|||
|
||||
static OldPersistentStorage _old_ind_persistent_storage;
|
||||
|
||||
class SlIndustryAcceptedHistory : public DefaultSaveLoadHandler<SlIndustryAcceptedHistory, Industry::AcceptedCargo> {
|
||||
public:
|
||||
static inline const SaveLoad description[] = {
|
||||
SLE_VAR(Industry::AcceptedHistory, accepted, SLE_UINT16),
|
||||
SLE_VAR(Industry::AcceptedHistory, waiting, SLE_UINT16),
|
||||
};
|
||||
static inline const SaveLoadCompatTable compat_description = _industry_produced_history_sl_compat;
|
||||
|
||||
void Save(Industry::AcceptedCargo *a) const override
|
||||
{
|
||||
if (!IsValidCargoType(a->cargo) || a->history == nullptr) {
|
||||
/* Don't save any history if cargo slot isn't used. */
|
||||
SlSetStructListLength(0);
|
||||
return;
|
||||
}
|
||||
|
||||
SlSetStructListLength(a->history->size());
|
||||
|
||||
for (auto &h : *a->history) {
|
||||
SlObject(&h, this->GetDescription());
|
||||
}
|
||||
}
|
||||
|
||||
void Load(Industry::AcceptedCargo *a) const override
|
||||
{
|
||||
size_t len = SlGetStructListLength(UINT32_MAX);
|
||||
if (len == 0) return;
|
||||
|
||||
auto &history = a->GetOrCreateHistory();
|
||||
for (auto &h : history) {
|
||||
if (--len > history.size()) break; // unsigned so wraps after hitting zero.
|
||||
SlObject(&h, this->GetDescription());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SlIndustryAccepted : public VectorSaveLoadHandler<SlIndustryAccepted, Industry, Industry::AcceptedCargo, INDUSTRY_NUM_INPUTS> {
|
||||
public:
|
||||
static inline const SaveLoad description[] = {
|
||||
SLE_VAR(Industry::AcceptedCargo, cargo, SLE_UINT8),
|
||||
SLE_VAR(Industry::AcceptedCargo, waiting, SLE_UINT16),
|
||||
SLE_VAR(Industry::AcceptedCargo, last_accepted, SLE_INT32),
|
||||
SLE_CONDVAR(Industry::AcceptedCargo, accumulated_waiting, SLE_UINT32, SLV_INDUSTRY_ACCEPTED_HISTORY, SL_MAX_VERSION),
|
||||
SLEG_CONDSTRUCTLIST("history", SlIndustryAcceptedHistory, SLV_INDUSTRY_ACCEPTED_HISTORY, SL_MAX_VERSION),
|
||||
};
|
||||
static inline const SaveLoadCompatTable compat_description = _industry_accepts_sl_compat;
|
||||
|
||||
|
|
|
@ -858,7 +858,7 @@ static bool LoadOldIndustry(LoadgameState &ls, int num)
|
|||
|
||||
if (i->location.tile != 0) {
|
||||
/* Copy data from old fixed arrays to industry. */
|
||||
std::copy(std::begin(_old_accepted), std::end(_old_accepted), std::back_inserter(i->accepted));
|
||||
std::move(std::begin(_old_accepted), std::end(_old_accepted), std::back_inserter(i->accepted));
|
||||
std::copy(std::begin(_old_produced), std::end(_old_produced), std::back_inserter(i->produced));
|
||||
|
||||
i->town = RemapTown(i->location.tile);
|
||||
|
|
|
@ -405,6 +405,7 @@ enum SaveLoadVersion : uint16_t {
|
|||
|
||||
SLV_FACE_STYLES, ///< 355 PR#14319 Addition of face styles, replacing gender and ethnicity.
|
||||
SLV_INDUSTRY_NUM_VALID_HISTORY, ///< 356 PR#14416 Store number of valid history records for industries.
|
||||
SLV_INDUSTRY_ACCEPTED_HISTORY, ///< 357 PR#14321 Add per-industry history of cargo delivered and waiting.
|
||||
|
||||
SL_MAX_VERSION, ///< Highest possible saveload version
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 period Period 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, uint period, uint age)
|
||||
{
|
||||
T result;
|
||||
switch (period) {
|
||||
case 0: GetHistory(history, 0, HISTORY_MONTH, age, result); break;
|
||||
case 1: GetHistory(history, 0, HISTORY_QUARTER, age, result); break;
|
||||
case 2: GetHistory(history, 0, HISTORY_YEAR, age, result); break;
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST_CASE("History Rotation and Reporting tests")
|
||||
{
|
||||
HistoryData<uint16_t> history{};
|
||||
uint64_t 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;
|
||||
RotateHistory(history, valid_history, 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, 0, j) == (( 1 * 1 + 1) / 2) + 1 * 1 * j);
|
||||
CHECK(GetHistory(history, 1, j) == (( 3 * 3 + 3) / 2) + 3 * 3 * j);
|
||||
CHECK(GetHistory(history, 2, j) == ((12 * 12 + 12) / 2) + 12 * 12 * j);
|
||||
}
|
||||
|
||||
/* Double-check quarter history matches summed month history. */
|
||||
CHECK(GetHistory(history, 0, 0) + GetHistory(history, 0, 1) + GetHistory(history, 0, 2) == GetHistory(history, 1, 0));
|
||||
CHECK(GetHistory(history, 0, 3) + GetHistory(history, 0, 4) + GetHistory(history, 0, 5) == GetHistory(history, 1, 1));
|
||||
CHECK(GetHistory(history, 0, 6) + GetHistory(history, 0, 7) + GetHistory(history, 0, 8) == GetHistory(history, 1, 2));
|
||||
CHECK(GetHistory(history, 0, 9) + GetHistory(history, 0, 10) + GetHistory(history, 0, 11) == GetHistory(history, 1, 3));
|
||||
CHECK(GetHistory(history, 0, 12) + GetHistory(history, 0, 13) + GetHistory(history, 0, 14) == GetHistory(history, 1, 4));
|
||||
CHECK(GetHistory(history, 0, 15) + GetHistory(history, 0, 16) + GetHistory(history, 0, 17) == GetHistory(history, 1, 5));
|
||||
CHECK(GetHistory(history, 0, 18) + GetHistory(history, 0, 19) + GetHistory(history, 0, 20) == GetHistory(history, 1, 6));
|
||||
CHECK(GetHistory(history, 0, 21) + GetHistory(history, 0, 22) + GetHistory(history, 0, 23) == GetHistory(history, 1, 7));
|
||||
|
||||
/* Double-check year history matches summed quarter history. */
|
||||
CHECK(GetHistory(history, 1, 0) + GetHistory(history, 1, 1) + GetHistory(history, 1, 2) + GetHistory(history, 1, 3) == GetHistory(history, 2, 0));
|
||||
CHECK(GetHistory(history, 1, 4) + GetHistory(history, 1, 5) + GetHistory(history, 1, 6) + GetHistory(history, 1, 7) == GetHistory(history, 2, 1));
|
||||
CHECK(GetHistory(history, 1, 8) + GetHistory(history, 1, 9) + GetHistory(history, 1, 10) + GetHistory(history, 1, 11) == GetHistory(history, 2, 2));
|
||||
CHECK(GetHistory(history, 1, 12) + GetHistory(history, 1, 13) + GetHistory(history, 1, 14) + GetHistory(history, 1, 15) == GetHistory(history, 2, 3));
|
||||
CHECK(GetHistory(history, 1, 16) + GetHistory(history, 1, 17) + GetHistory(history, 1, 18) + GetHistory(history, 1, 19) == GetHistory(history, 2, 4));
|
||||
CHECK(GetHistory(history, 1, 20) + GetHistory(history, 1, 21) + GetHistory(history, 1, 22) + GetHistory(history, 1, 23) == GetHistory(history, 2, 5));
|
||||
}
|
|
@ -37,6 +37,7 @@ TimerGameEconomy::Year TimerGameEconomy::year = {};
|
|||
TimerGameEconomy::Month TimerGameEconomy::month = {};
|
||||
TimerGameEconomy::Date TimerGameEconomy::date = {};
|
||||
TimerGameEconomy::DateFract TimerGameEconomy::date_fract = {};
|
||||
uint TimerGameEconomy::days_since_last_month = {};
|
||||
|
||||
/**
|
||||
* Converts a Date to a Year, Month & Day.
|
||||
|
@ -133,6 +134,7 @@ bool TimerManager<TimerGameEconomy>::Elapsed([[maybe_unused]] TimerGameEconomy::
|
|||
|
||||
/* increase day counter */
|
||||
TimerGameEconomy::date++;
|
||||
++TimerGameEconomy::days_since_last_month;
|
||||
|
||||
TimerGameEconomy::YearMonthDay ymd = TimerGameEconomy::ConvertDateToYMD(TimerGameEconomy::date);
|
||||
|
||||
|
@ -177,6 +179,8 @@ bool TimerManager<TimerGameEconomy>::Elapsed([[maybe_unused]] TimerGameEconomy::
|
|||
}
|
||||
}
|
||||
|
||||
if (new_month) TimerGameEconomy::days_since_last_month = 0;
|
||||
|
||||
/* check if we reached the maximum year, decrement dates by a year */
|
||||
if (TimerGameEconomy::year == EconomyTime::MAX_YEAR + 1) {
|
||||
TimerGameEconomy::year--;
|
||||
|
|
|
@ -37,6 +37,8 @@ public:
|
|||
static Date date; ///< Current date in days (day counter).
|
||||
static DateFract date_fract; ///< Fractional part of the day.
|
||||
|
||||
static uint days_since_last_month; ///< Number of days that have elapsed since the last month.
|
||||
|
||||
static YearMonthDay ConvertDateToYMD(Date date);
|
||||
static Date ConvertYMDToDate(Year year, Month month, Day day);
|
||||
static void SetDate(Date date, DateFract fract);
|
||||
|
|
|
@ -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.
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue