1
0
Fork 0

Compare commits

..

10 Commits

Author SHA1 Message Date
glx22 9e9a343f72 Codechange: [Actions] Use NuGet for vcpkg binary caching 2025-07-20 16:50:56 +02:00
glx22 efd7ca827f Codechange: [Actions] Setup vcpkg via a composite action 2025-07-20 16:50:54 +02:00
Jonathan G Rennison 821784004d Fix: [Linkgraph] Incorrect NodeID to StationID conversion for EraseFlows 2025-07-20 16:07:11 +02:00
Jonathan G Rennison f0447d59d4 Codechange: Use StationID as StationIDStack Titem type 2025-07-20 16:06:03 +02:00
Jonathan G Rennison cbdd358ae8 Codechange: Allow SmallStack Titem type to be non-structural 2025-07-20 16:06:03 +02:00
Peter Nelson 2cdd50f40e
Fix 03f5f7145f: Wrong colour used when string POP_COLOURs back to initial colour. (#14468)
Fixing ellipsis colour broke the PUSH_COLOUR/POP_COLOUR system, reverting to the last used colour instead of the initial colour.
2025-07-20 14:30:18 +01:00
Peter Nelson 56942a15c7 Add: Industry accepted and waiting history graphs.
Records amount of cargo accepted, and a rolling average of the waiting amount.

Average waiting samples the waiting amount once per day for each industry, spread out over an economy day.
2025-07-20 14:03:54 +01:00
Peter Nelson 5eeda026a4 Codechange: Allow unused graph ranges to be masked. 2025-07-20 14:03:54 +01:00
Peter Nelson edc5b8ea1f
Fix #14464: Invalid string parameter in scenario editor when unable to build industry. (#14465)
Resolved by removing the Build Industry command callback. This was used to display an error message in the scenario editor, however an error is already automatically displayed.
2025-07-20 14:03:29 +01:00
Peter Nelson a8650c6b06
Codechange: Make SpriteCacheCtrlFlags an enum bit set. (#14462)
Due to header dependencies, this requires types to split from the spritecache header.
2025-07-19 23:49:15 +01:00
37 changed files with 259 additions and 106 deletions

View File

@ -15,8 +15,6 @@ jobs:
name: CI
runs-on: windows-latest
permissions:
packages: write
steps:
- name: Checkout

View File

@ -21,7 +21,6 @@ jobs:
actions: read
contents: read
security-events: write
packages: write
steps:
- name: Checkout

View File

@ -454,6 +454,7 @@ add_files(
spritecache.cpp
spritecache.h
spritecache_internal.h
spritecache_type.h
station.cpp
station_base.h
station_cmd.cpp

View File

@ -410,7 +410,7 @@ void VehicleCargoList::AgeCargo()
return (accepted && cp->first_station != current_station) ? MTA_DELIVER : MTA_KEEP;
} else if (cargo_next == current_station) {
return MTA_DELIVER;
} else if (next_station.Contains(cargo_next.base())) {
} else if (next_station.Contains(cargo_next)) {
return MTA_KEEP;
} else {
return MTA_TRANSFER;
@ -470,7 +470,7 @@ bool VehicleCargoList::Stage(bool accepted, StationID current_station, StationID
new_shares.ChangeShare(current_station, INT_MIN);
StationIDStack excluded = next_station;
while (!excluded.IsEmpty() && !new_shares.GetShares()->empty()) {
new_shares.ChangeShare(StationID{excluded.Pop()}, INT_MIN);
new_shares.ChangeShare(excluded.Pop(), INT_MIN);
}
if (new_shares.GetShares()->empty()) {
cargo_next = StationID::Invalid();
@ -743,7 +743,7 @@ uint StationCargoList::ShiftCargo(Taction action, StationIDStack next, bool incl
{
uint max_move = action.MaxMove();
while (!next.IsEmpty()) {
this->ShiftCargo(action, StationID{next.Pop()});
this->ShiftCargo(action, next.Pop());
if (action.MaxMove() == 0) break;
}
if (include_invalid && action.MaxMove() > 0) {
@ -853,7 +853,7 @@ uint StationCargoList::Load(uint max_move, VehicleCargoList *dest, StationIDStac
*/
uint StationCargoList::Reroute(uint max_move, StationCargoList *dest, StationID avoid, StationID avoid2, const GoodsEntry *ge)
{
return this->ShiftCargo(StationCargoReroute(this, dest, max_move, avoid, avoid2, ge), avoid.base(), false);
return this->ShiftCargo(StationCargoReroute(this, dest, max_move, avoid, avoid2, ge), avoid, false);
}
/*

View File

@ -555,7 +555,7 @@ public:
inline bool HasCargoFor(StationIDStack next) const
{
while (!next.IsEmpty()) {
if (this->packets.find(StationID{next.Pop()}) != this->packets.end()) return true;
if (this->packets.find(next.Pop()) != this->packets.end()) return true;
}
/* Packets for StationID::Invalid() can go anywhere. */
return this->packets.find(StationID::Invalid()) != this->packets.end();

View File

@ -113,13 +113,14 @@ struct SmallStackItem {
* index types of the same length.
* @tparam Titem Value type to be used.
* @tparam Tindex Index type to use for the pool.
* @tparam Tinvalid Invalid item to keep at the bottom of each stack.
* @tparam Tinvalid_value Value to construct invalid item to keep at the bottom of each stack.
* @tparam Tgrowth_step Growth step for pool.
* @tparam Tmax_size Maximum size for pool.
*/
template <typename Titem, typename Tindex, Titem Tinvalid, Tindex Tgrowth_step, Tindex Tmax_size>
template <typename Titem, typename Tindex, auto Tinvalid_value, Tindex Tgrowth_step, Tindex Tmax_size>
class SmallStack : public SmallStackItem<Titem, Tindex> {
public:
static constexpr Titem Tinvalid{Tinvalid_value};
typedef SmallStackItem<Titem, Tindex> Item;

View File

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

View File

@ -580,10 +580,11 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
const uint shadow_offset = ScaleGUITrad(1);
auto draw_line = [&](const ParagraphLayouter::Line &line, bool do_shadow, int left, int min_x, int max_x, bool truncation, TextColour &last_colour) {
auto draw_line = [&](const ParagraphLayouter::Line &line, bool do_shadow, int left, int min_x, int max_x, bool truncation, TextColour initial_colour) {
const DrawPixelInfo *dpi = _cur_dpi;
int dpi_left = dpi->left;
int dpi_right = dpi->left + dpi->width - 1;
TextColour last_colour = initial_colour;
for (int run_index = 0; run_index < line.CountRuns(); run_index++) {
const ParagraphLayouter::VisualRun &run = line.GetVisualRun(run_index);
@ -593,13 +594,10 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
FontCache *fc = f->fc;
TextColour colour = f->colour;
if (colour == TC_INVALID || HasFlag(last_colour, TC_FORCED)) {
colour = last_colour;
} else {
if (colour == TC_INVALID || HasFlag(initial_colour, TC_FORCED)) colour = initial_colour;
bool colour_has_shadow = (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK;
/* Update the last colour for the truncation ellipsis. */
last_colour = colour;
}
bool colour_has_shadow = (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK;
if (do_shadow && (!fc->GetDrawGlyphShadow() || !colour_has_shadow)) continue;
SetColourRemap(do_shadow ? TC_BLACK : colour);
@ -625,16 +623,16 @@ static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left,
GfxMainBlitter(sprite, begin_x + (do_shadow ? shadow_offset : 0), top + (do_shadow ? shadow_offset : 0), BlitterMode::ColourRemap);
}
}
return last_colour;
};
/* Draw shadow, then foreground */
for (bool do_shadow : {true, false}) {
TextColour last_colour = default_colour;
draw_line(line, do_shadow, left - offset_x, min_x, max_x, truncation, last_colour);
TextColour colour = draw_line(line, do_shadow, left - offset_x, min_x, max_x, truncation, default_colour);
if (truncation) {
int x = (_current_text_dir == TD_RTL) ? left : (right - truncation_width);
draw_line(*truncation_layout->front(), do_shadow, x, INT32_MIN, INT32_MAX, false, last_colour);
draw_line(*truncation_layout->front(), do_shadow, x, INT32_MIN, INT32_MAX, false, colour);
}
}

View File

@ -182,8 +182,9 @@ 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.
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;
@ -216,13 +217,20 @@ protected:
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; }
};
/**
@ -675,13 +683,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;
@ -704,6 +716,7 @@ 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;
@ -1115,6 +1128,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 +1622,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,6 +1639,10 @@ 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);
}
@ -1630,6 +1650,9 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
{
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,7 +1666,7 @@ 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);
}
@ -1691,6 +1714,33 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
FillFromHistory<GRAPH_NUM_MONTHS>(p.history, i->valid_history, 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, accepted_filler, waiting_filler);
} else {
FillFromHistory<GRAPH_NUM_MONTHS>(*a.history, i->valid_history, accepted_filler, waiting_filler);
}
}
this->SetDirty();
}
};

View File

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

View File

@ -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;
}
}
}
@ -2505,6 +2509,14 @@ static void UpdateIndustryStatistics(Industry *i)
RotateHistory(p.history);
}
}
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);
}
}
/**

View File

@ -27,6 +27,4 @@ DEF_CMD_TRAIT(CMD_INDUSTRY_SET_EXCLUSIVITY, CmdIndustrySetExclusivity, CommandFl
DEF_CMD_TRAIT(CMD_INDUSTRY_SET_TEXT, CmdIndustrySetText, CommandFlags({CommandFlag::Deity, CommandFlag::StrCtrl}), CMDT_OTHER_MANAGEMENT)
DEF_CMD_TRAIT(CMD_INDUSTRY_SET_PRODUCTION, CmdIndustrySetProduction, CommandFlag::Deity, CMDT_OTHER_MANAGEMENT)
void CcBuildIndustry(Commands cmd, const CommandCost &result, TileIndex tile, IndustryType indtype, uint32_t, bool, uint32_t);
#endif /* INDUSTRY_CMD_H */

View File

@ -257,25 +257,6 @@ void SortIndustryTypes()
std::sort(_sorted_industry_types.begin(), _sorted_industry_types.end(), IndustryTypeNameSorter);
}
/**
* Command callback. In case of failure to build an industry, show an error message.
* @param result Result of the command.
* @param tile Tile where the industry is placed.
* @param indtype Industry type.
*/
void CcBuildIndustry(Commands, const CommandCost &result, TileIndex tile, IndustryType indtype, uint32_t, bool, uint32_t)
{
if (result.Succeeded()) return;
if (indtype < NUM_INDUSTRYTYPES) {
const IndustrySpec *indsp = GetIndustrySpec(indtype);
if (indsp->enabled) {
ShowErrorMessage(GetEncodedString(STR_ERROR_CAN_T_BUILD_HERE, indsp->name),
GetEncodedString(result.GetErrorMessage()), WL_INFO, TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE);
}
}
}
static constexpr NWidgetPart _nested_build_industry_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
@ -745,7 +726,7 @@ public:
AutoRestoreBackup backup_generating_world(_generating_world, true);
AutoRestoreBackup backup_ignore_industry_restritions(_ignore_industry_restrictions, true);
Command<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, &CcBuildIndustry, tile, this->selected_type, layout_index, false, seed);
Command<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, tile, this->selected_type, layout_index, false, seed);
} else {
success = Command<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, tile, this->selected_type, layout_index, false, seed);
}
@ -845,7 +826,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 +1223,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(),
};

View File

@ -634,9 +634,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 +4026,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!

View File

@ -42,13 +42,13 @@ LinkGraphJob::LinkGraphJob(const LinkGraph &orig) :
}
/**
* Erase all flows originating at a specific node.
* @param from Node to erase flows for.
* Erase all flows originating at a specific station.
* @param from StationID to erase flows for.
*/
void LinkGraphJob::EraseFlows(NodeID from)
void LinkGraphJob::EraseFlows(StationID from)
{
for (NodeID node_id = 0; node_id < this->Size(); ++node_id) {
(*this)[node_id].flows.erase(StationID{from});
(*this)[node_id].flows.erase(from);
}
}
@ -106,7 +106,7 @@ LinkGraphJob::~LinkGraphJob()
/* The station can have been deleted. Remove all flows originating from it then. */
Station *st = Station::GetIfValid(from.base.station);
if (st == nullptr) {
this->EraseFlows(node_id);
this->EraseFlows(from.base.station);
continue;
}
@ -114,7 +114,7 @@ LinkGraphJob::~LinkGraphJob()
* sure that everything is still consistent or ignore it otherwise. */
GoodsEntry &ge = st->goods[this->Cargo()];
if (ge.link_graph != this->link_graph.index || ge.node != node_id) {
this->EraseFlows(node_id);
this->EraseFlows(from.base.station);
continue;
}
@ -136,7 +136,7 @@ LinkGraphJob::~LinkGraphJob()
/* Delete old flows for source stations which have been deleted
* from the new flows. This avoids flow cycles between old and
* new flows. */
while (!erased.IsEmpty()) geflows.erase(StationID{erased.Pop()});
while (!erased.IsEmpty()) geflows.erase(erased.Pop());
} else if ((*lg)[node_id][dest_id].last_unrestricted_update == EconomyTime::INVALID_DATE) {
/* Edge is fully restricted. */
flows.RestrictFlows(to);

View File

@ -167,7 +167,7 @@ protected:
std::atomic<bool> job_completed = false; ///< Is the job still running. This is accessed by multiple threads and reads may be stale.
std::atomic<bool> job_aborted = false; ///< Has the job been aborted. This is accessed by multiple threads and reads may be stale.
void EraseFlows(NodeID from);
void EraseFlows(StationID from);
void JoinThread();
void SpawnThread();

View File

@ -11,6 +11,8 @@
#define HISTORY_FUNC_HPP
#include "../core/bitmath_func.hpp"
#include "../core/math_func.hpp"
#include "../timer/timer_game_economy.h"
#include "history_type.hpp"
/**
@ -34,6 +36,19 @@ void RotateHistory(HistoryData<T> &history)
history[THIS_MONTH] = {};
}
/**
* 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;
}
/**
* Fill some data with historical data.
* @param history Historical data to fill from.
@ -53,4 +68,21 @@ void FillFromHistory(const HistoryData<T> &history, ValidHistoryMask valid_histo
}
}
/**
* Fill some data with empty records.
* @param valid_history Mask of valid history records.
* @param fillers Fillers to fill with history data.
*/
template <uint N, typename... Tfillers>
void FillFromEmpty(ValidHistoryMask valid_history, Tfillers... fillers)
{
for (uint i = 0; i != N; ++i) {
if (HasBit(valid_history, N - i)) {
(fillers.MakeZero(i), ...);
} else {
(fillers.MakeInvalid(i), ...);
}
}
}
#endif /* HISTORY_FUNC_HPP */

View File

@ -79,7 +79,6 @@ static constexpr auto _callback_tuple = std::make_tuple(
&CcCreateGroup,
&CcFoundRandomTown,
&CcRoadStop,
&CcBuildIndustry,
&CcStartStopVehicle,
&CcGame,
&CcAddVehicleNewGroup

View File

@ -368,7 +368,7 @@ StationIDStack OrderList::GetNextStoppingStation(const Vehicle *v, VehicleOrderI
next = v->cur_implicit_order_index;
if (next >= this->GetNumOrders()) {
next = this->GetFirstOrder();
if (next == INVALID_VEH_ORDER_ID) return StationID::Invalid().base();
if (next == INVALID_VEH_ORDER_ID) return StationID::Invalid();
} else {
/* GetNext never returns INVALID_VEH_ORDER_ID if there is a valid station in the list.
* As the given "next" is already valid and a station in the list, we
@ -404,11 +404,11 @@ StationIDStack OrderList::GetNextStoppingStation(const Vehicle *v, VehicleOrderI
if (next == INVALID_VEH_ORDER_ID || ((orders[next].IsType(OT_GOTO_STATION) || orders[next].IsType(OT_IMPLICIT)) &&
orders[next].GetDestination() == v->last_station_visited &&
(orders[next].GetUnloadType() & (OUFB_TRANSFER | OUFB_UNLOAD)) != 0)) {
return StationID::Invalid().base();
return StationID::Invalid();
}
} while (orders[next].IsType(OT_GOTO_DEPOT) || orders[next].GetDestination() == v->last_station_visited);
return orders[next].GetDestination().ToStationID().base();
return orders[next].GetDestination().ToStationID();
}
/**

View File

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

View File

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

View File

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

View File

@ -535,7 +535,7 @@ static void *ReadSprite(const SpriteCache *sc, SpriteID id, SpriteType sprite_ty
struct GrfSpriteOffset {
size_t file_pos;
uint8_t control_flags;
SpriteCacheCtrlFlags control_flags{};
};
/** Map from sprite numbers to position in the GRF file. */
@ -565,7 +565,7 @@ void ReadGRFSpriteOffsets(SpriteFile &file)
size_t old_pos = file.GetPos();
file.SeekTo(data_offset, SEEK_CUR);
GrfSpriteOffset offset = { 0, 0 };
GrfSpriteOffset offset{0};
/* Loop over all sprite section entries and store the file
* offset for each newly encountered ID. */
@ -574,7 +574,6 @@ void ReadGRFSpriteOffsets(SpriteFile &file)
if (id != prev_id) {
_grf_sprite_offsets[prev_id] = offset;
offset.file_pos = file.GetPos() - 4;
offset.control_flags = 0;
}
prev_id = id;
uint length = file.ReadDword();
@ -585,11 +584,11 @@ void ReadGRFSpriteOffsets(SpriteFile &file)
uint8_t zoom = file.ReadByte();
length--;
if (colour.Any() && zoom == 0) { // ZoomLevel::Normal (normal zoom)
SetBit(offset.control_flags, (colour != SpriteComponent::Palette) ? SCCF_ALLOW_ZOOM_MIN_1X_32BPP : SCCF_ALLOW_ZOOM_MIN_1X_PAL);
SetBit(offset.control_flags, (colour != SpriteComponent::Palette) ? SCCF_ALLOW_ZOOM_MIN_2X_32BPP : SCCF_ALLOW_ZOOM_MIN_2X_PAL);
offset.control_flags.Set((colour != SpriteComponent::Palette) ? SpriteCacheCtrlFlag::AllowZoomMin1x32bpp : SpriteCacheCtrlFlag::AllowZoomMin1xPal);
offset.control_flags.Set((colour != SpriteComponent::Palette) ? SpriteCacheCtrlFlag::AllowZoomMin2x32bpp : SpriteCacheCtrlFlag::AllowZoomMin2xPal);
}
if (colour.Any() && zoom == 2) { // ZoomLevel::In2x (2x zoomed in)
SetBit(offset.control_flags, (colour != SpriteComponent::Palette) ? SCCF_ALLOW_ZOOM_MIN_2X_32BPP : SCCF_ALLOW_ZOOM_MIN_2X_PAL);
offset.control_flags.Set((colour != SpriteComponent::Palette) ? SpriteCacheCtrlFlag::AllowZoomMin2x32bpp : SpriteCacheCtrlFlag::AllowZoomMin2xPal);
}
}
}
@ -621,7 +620,7 @@ bool LoadNextSprite(SpriteID load_index, SpriteFile &file, uint file_sprite_id)
uint8_t grf_type = file.ReadByte();
SpriteType type;
uint8_t control_flags = 0;
SpriteCacheCtrlFlags control_flags;
if (grf_type == 0xFF) {
/* Some NewGRF files have "empty" pseudo-sprites which are 1
* byte long. Catch these so the sprites won't be displayed. */

View File

@ -11,24 +11,9 @@
#define SPRITECACHE_H
#include "gfx_type.h"
#include "spritecache_type.h"
#include "spriteloader/spriteloader.hpp"
/** Data structure describing a sprite. */
struct Sprite {
uint16_t height; ///< Height of the sprite.
uint16_t width; ///< Width of the sprite.
int16_t x_offs; ///< Number of pixels to shift the sprite to the right.
int16_t y_offs; ///< Number of pixels to shift the sprite downwards.
std::byte data[]; ///< Sprite data.
};
enum SpriteCacheCtrlFlags : uint8_t {
SCCF_ALLOW_ZOOM_MIN_1X_PAL = 0, ///< Allow use of sprite min zoom setting at 1x in palette mode.
SCCF_ALLOW_ZOOM_MIN_1X_32BPP = 1, ///< Allow use of sprite min zoom setting at 1x in 32bpp mode.
SCCF_ALLOW_ZOOM_MIN_2X_PAL = 2, ///< Allow use of sprite min zoom setting at 2x in palette mode.
SCCF_ALLOW_ZOOM_MIN_2X_32BPP = 3, ///< Allow use of sprite min zoom setting at 2x in 32bpp mode.
};
extern uint _sprite_cache_size;
/** SpriteAllocator that allocates memory via a unique_ptr array. */

View File

@ -12,6 +12,7 @@
#include "core/math_func.hpp"
#include "gfx_type.h"
#include "spritecache_type.h"
#include "spriteloader/spriteloader.hpp"
#include "table/sprites.h"
@ -27,7 +28,7 @@ struct SpriteCache {
uint32_t lru = 0;
SpriteType type = SpriteType::Invalid; ///< In some cases a single sprite is misused by two NewGRFs. Once as real sprite and once as recolour sprite. If the recolour sprite gets into the cache it might be drawn as real sprite which causes enormous trouble.
bool warned = false; ///< True iff the user has been warned about incorrect use of this sprite
uint8_t control_flags = 0; ///< Control flags, see SpriteCacheCtrlFlags
SpriteCacheCtrlFlags control_flags{}; ///< Control flags, see SpriteCacheCtrlFlags
void ClearSpriteData();
};

View File

@ -0,0 +1,33 @@
/*
* 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 spritecache_type.h Types related to the sprite cache. */
#ifndef SPRITECACHE_TYPE_H
#define SPRITECACHE_TYPE_H
#include "core/enum_type.hpp"
/** Data structure describing a sprite. */
struct Sprite {
uint16_t height; ///< Height of the sprite.
uint16_t width; ///< Width of the sprite.
int16_t x_offs; ///< Number of pixels to shift the sprite to the right.
int16_t y_offs; ///< Number of pixels to shift the sprite downwards.
std::byte data[]; ///< Sprite data.
};
enum class SpriteCacheCtrlFlag : uint8_t {
AllowZoomMin1xPal, ///< Allow use of sprite min zoom setting at 1x in palette mode.
AllowZoomMin1x32bpp, ///< Allow use of sprite min zoom setting at 1x in 32bpp mode.
AllowZoomMin2xPal, ///< Allow use of sprite min zoom setting at 2x in palette mode.
AllowZoomMin2x32bpp, ///< Allow use of sprite min zoom setting at 2x in 32bpp mode.
};
using SpriteCacheCtrlFlags = EnumBitSet<SpriteCacheCtrlFlag, uint8_t>;
#endif /* SPRITECACHE_TYPE_H */

View File

@ -256,7 +256,7 @@ static ZoomLevels LoadSpriteV1(SpriteLoader::SpriteCollection &sprite, SpriteFil
return {};
}
static ZoomLevels LoadSpriteV2(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint8_t control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp)
static ZoomLevels LoadSpriteV2(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, SpriteCacheCtrlFlags control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp)
{
static const ZoomLevel zoom_lvl_map[6] = {ZoomLevel::Normal, ZoomLevel::In4x, ZoomLevel::In2x, ZoomLevel::Out2x, ZoomLevel::Out4x, ZoomLevel::Out8x};
@ -295,11 +295,11 @@ static ZoomLevels LoadSpriteV2(SpriteLoader::SpriteCollection &sprite, SpriteFil
is_wanted_zoom_lvl = true;
ZoomLevel zoom_min = sprite_type == SpriteType::Font ? ZoomLevel::Min : _settings_client.gui.sprite_zoom_min;
if (zoom_min >= ZoomLevel::In2x &&
HasBit(control_flags, load_32bpp ? SCCF_ALLOW_ZOOM_MIN_2X_32BPP : SCCF_ALLOW_ZOOM_MIN_2X_PAL) && zoom_lvl < ZoomLevel::In2x) {
control_flags.Test(load_32bpp ? SpriteCacheCtrlFlag::AllowZoomMin2x32bpp : SpriteCacheCtrlFlag::AllowZoomMin2xPal) && zoom_lvl < ZoomLevel::In2x) {
is_wanted_zoom_lvl = false;
}
if (zoom_min >= ZoomLevel::Normal &&
HasBit(control_flags, load_32bpp ? SCCF_ALLOW_ZOOM_MIN_1X_32BPP : SCCF_ALLOW_ZOOM_MIN_1X_PAL) && zoom_lvl < ZoomLevel::Normal) {
control_flags.Test(load_32bpp ? SpriteCacheCtrlFlag::AllowZoomMin1x32bpp : SpriteCacheCtrlFlag::AllowZoomMin1xPal) && zoom_lvl < ZoomLevel::Normal) {
is_wanted_zoom_lvl = false;
}
} else {
@ -359,7 +359,7 @@ static ZoomLevels LoadSpriteV2(SpriteLoader::SpriteCollection &sprite, SpriteFil
return loaded_sprites;
}
ZoomLevels SpriteLoaderGrf::LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint8_t control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp)
ZoomLevels SpriteLoaderGrf::LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, SpriteCacheCtrlFlags control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp)
{
if (this->container_ver >= 2) {
return LoadSpriteV2(sprite, file, file_pos, sprite_type, load_32bpp, control_flags, avail_8bpp, avail_32bpp);

View File

@ -17,7 +17,7 @@ class SpriteLoaderGrf : public SpriteLoader {
uint8_t container_ver;
public:
SpriteLoaderGrf(uint8_t container_ver) : container_ver(container_ver) {}
ZoomLevels LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint8_t control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp) override;
ZoomLevels LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, SpriteCacheCtrlFlags control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp) override;
};
#endif /* SPRITELOADER_GRF_HPP */

View File

@ -48,7 +48,7 @@ static void Convert32bppTo8bpp(SpriteLoader::Sprite &sprite)
}
}
ZoomLevels SpriteLoaderMakeIndexed::LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool, uint8_t control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp)
ZoomLevels SpriteLoaderMakeIndexed::LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool, SpriteCacheCtrlFlags control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp)
{
ZoomLevels avail = this->baseloader.LoadSprite(sprite, file, file_pos, sprite_type, true, control_flags, avail_8bpp, avail_32bpp);

View File

@ -17,7 +17,7 @@ class SpriteLoaderMakeIndexed : public SpriteLoader {
SpriteLoader &baseloader;
public:
SpriteLoaderMakeIndexed(SpriteLoader &baseloader) : baseloader(baseloader) {}
ZoomLevels LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint8_t control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp) override;
ZoomLevels LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, SpriteCacheCtrlFlags control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp) override;
};
#endif /* SPRITELOADER_MAKEINDEXED_H */

View File

@ -13,6 +13,7 @@
#include "../core/alloc_type.hpp"
#include "../core/enum_type.hpp"
#include "../gfx_type.h"
#include "../spritecache_type.h"
#include "sprite_file_type.hpp"
struct Sprite;
@ -94,7 +95,7 @@ public:
* @param[out] avail_32bpp Available 32bpp sprites.
* @return Available sprites matching \a load_32bpp.
*/
virtual ZoomLevels LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint8_t control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp) = 0;
virtual ZoomLevels LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, SpriteCacheCtrlFlags control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp) = 0;
virtual ~SpriteLoader() = default;
};

View File

@ -5079,7 +5079,7 @@ StationIDStack FlowStatMap::DeleteFlows(StationID via)
FlowStat &s_flows = f_it->second;
s_flows.ChangeShare(via, INT_MIN);
if (s_flows.GetShares()->empty()) {
ret.Push(f_it->first.base());
ret.Push(f_it->first);
this->erase(f_it++);
} else {
++f_it;

View File

@ -26,7 +26,7 @@ struct RoadStop;
struct StationSpec;
struct Waypoint;
using StationIDStack = SmallStack<StationID::BaseType, StationID::BaseType, StationID::Invalid().base(), 8, StationID::End().base()>;
using StationIDStack = SmallStack<StationID, StationID::BaseType, StationID::Invalid().base(), 8, StationID::End().base()>;
/** Station types */
enum class StationType : uint8_t {

View File

@ -33,7 +33,7 @@ static bool MockLoadNextSprite(SpriteID load_index)
sc->id = 0;
sc->type = is_mapgen ? SpriteType::MapGen : SpriteType::Normal;
sc->warned = false;
sc->control_flags = 0;
sc->control_flags = {};
/* Fill with empty sprites up until the default sprite count. */
return load_index < SPR_OPENTTD_BASE + OPENTTD_SPRITE_COUNT;

View File

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

View File

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

View File

@ -741,7 +741,7 @@ public:
*/
inline StationIDStack GetNextStoppingStation() const
{
return (this->orders == nullptr) ? StationID::Invalid().base() : this->orders->GetNextStoppingStation(this);
return (this->orders == nullptr) ? StationID::Invalid() : this->orders->GetNextStoppingStation(this);
}
void ResetRefitCaps();