1
0
Fork 0

Compare commits

...

6 Commits

Author SHA1 Message Date
Peter Nelson 65653074ba
Merge 13f24c25ca into 2cdd50f40e 2025-07-20 13:30:28 +00: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 13f24c25ca
Add: Variable to test for a badge on a nearby tile. 2025-05-21 22:10:13 +01:00
24 changed files with 380 additions and 51 deletions

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 {
/* Update the last colour for the truncation ellipsis. */
last_colour = colour;
}
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;
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

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

@ -193,6 +193,7 @@ static uint32_t GetAirportTileIDAtOffset(TileIndex tile, const Station *st, uint
/* Get airport tile ID at offset */
case 0x62: return GetAirportTileIDAtOffset(GetNearbyTile(parameter, this->tile), this->st, this->ro.grffile->grfid);
case 0x79: return GetNearbyBadgeVariableResult(*this->ro.grffile, GetNearbyTile(parameter, this->tile), this->ro);
case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, this->ats->badges, parameter);
}

View File

@ -8,13 +8,25 @@
/** @file newgrf_badge.cpp Functionality for NewGRF badges. */
#include "stdafx.h"
#include "house.h"
#include "industry_map.h"
#include "newgrf.h"
#include "newgrf_airporttiles.h"
#include "newgrf_badge.h"
#include "newgrf_badge_type.h"
#include "newgrf_object.h"
#include "newgrf_roadstop.h"
#include "newgrf_station.h"
#include "newgrf_spritegroup.h"
#include "rail.h"
#include "rail_map.h"
#include "station_map.h"
#include "stringfilter_type.h"
#include "strings_func.h"
#include "tile_map.h"
#include "timer/timer_game_calendar.h"
#include "town_map.h"
#include "tunnelbridge_map.h"
#include "table/strings.h"
@ -216,7 +228,173 @@ BadgeResolverObject::BadgeResolverObject(const Badge &badge, GrfSpecFeature feat
}
/**
* Test for a matching badge in a list of badges, returning the number of matching bits.
* Test if a list of badges contains a badge.
* @param badges List of badges.
* @param badge Badge to find.
* @returns true iff the badge appears in the list.
*/
static bool BadgesContains(std::span<const BadgeID> badges, BadgeID badge)
{
return std::ranges::find(badges, badge) != std::end(badges);
}
/**
* Test if a rail type has a badge.
* @param rt Rail type to test.
* @param badge Badge to find.
* @returns true iff the rail type has the badge.
*/
static bool RailTypeHasBadge(RailType rt, BadgeID badge)
{
return rt != INVALID_RAILTYPE && BadgesContains(GetRailTypeInfo(rt)->badges, badge);
}
/**
* Test if a road type has a badge.
* @param rt Road type to test.
* @param badge Badge to find.
* @returns true iff the road type has the badge.
*/
static bool RoadTypeHasBadge(RoadType rt, BadgeID badge)
{
return rt != INVALID_ROADTYPE && BadgesContains(GetRoadTypeInfo(rt)->badges, badge);
}
using TileHasBadgeProc = bool(*)(TileIndex tile, BadgeID badge, GrfSpecFeatures features);
static bool TileHasBadge_Rail(TileIndex tile, BadgeID badge, GrfSpecFeatures features)
{
if (features.Test(GrfSpecFeature::GSF_RAILTYPES) && RailTypeHasBadge(GetRailType(tile), badge)) return true;
return false;
}
static bool TileHasBadge_Road(TileIndex tile, BadgeID badge, GrfSpecFeatures features)
{
if (features.Test(GrfSpecFeature::GSF_ROADTYPES) && RoadTypeHasBadge(GetRoadTypeRoad(tile), badge)) return true;
if (features.Test(GrfSpecFeature::GSF_TRAMTYPES) && RoadTypeHasBadge(GetRoadTypeTram(tile), badge)) return true;
if (features.Test(GrfSpecFeature::GSF_RAILTYPES) && IsLevelCrossing(tile) && RailTypeHasBadge(GetRailType(tile), badge)) return true;
return false;
}
static bool TileHasBadge_Town(TileIndex tile, BadgeID badge, GrfSpecFeatures features)
{
if (features.Test(GrfSpecFeature::GSF_HOUSES) && BadgesContains(HouseSpec::Get(GetHouseType(tile))->badges, badge)) return true;
return false;
}
static bool TileHasBadge_Station(TileIndex tile, BadgeID badge, GrfSpecFeatures features)
{
switch (GetStationType(tile)) {
case StationType::Rail:
case StationType::RailWaypoint:
if (features.Test(GrfSpecFeature::GSF_STATIONS)) {
if (const StationSpec *spec = GetStationSpec(tile); spec != nullptr && BadgesContains(spec->badges, badge)) return true;
}
if (features.Test(GrfSpecFeature::GSF_RAILTYPES) && RailTypeHasBadge(GetRailType(tile), badge)) return true;
return false;
case StationType::Bus:
case StationType::Truck:
case StationType::RoadWaypoint:
if (features.Test(GrfSpecFeature::GSF_ROADSTOPS)) {
if (const RoadStopSpec *spec = GetRoadStopSpec(tile); spec != nullptr && BadgesContains(spec->badges, badge)) return true;
}
if (features.Test(GrfSpecFeature::GSF_ROADTYPES) && RoadTypeHasBadge(GetRoadTypeRoad(tile), badge)) return true;
if (features.Test(GrfSpecFeature::GSF_TRAMTYPES) && RoadTypeHasBadge(GetRoadTypeTram(tile), badge)) return true;
return false;
case StationType::Airport:
if (features.Test(GrfSpecFeature::GSF_AIRPORTTILES) && BadgesContains(AirportTileSpec::GetByTile(tile)->badges, badge)) return true;
return false;
default:
return false;
}
}
static bool TileHasBadge_Industry(TileIndex tile, BadgeID badge, GrfSpecFeatures features)
{
if (features.Test(GrfSpecFeature::GSF_INDUSTRYTILES) && BadgesContains(GetIndustryTileSpec(GetIndustryGfx(tile))->badges, badge)) return true;
if (features.Test(GrfSpecFeature::GSF_INDUSTRIES) && BadgesContains(GetIndustrySpec(GetIndustryType(tile))->badges, badge)) return true;
return false;
}
static bool TileHasBadge_TunnelBridge(TileIndex tile, BadgeID badge, GrfSpecFeatures features)
{
switch (GetTunnelBridgeTransportType(tile)) {
case TransportType::TRANSPORT_RAIL:
if (features.Test(GrfSpecFeature::GSF_RAILTYPES) && RailTypeHasBadge(GetRailType(tile), badge)) return true;
return false;
case TransportType::TRANSPORT_ROAD:
if (features.Test(GrfSpecFeature::GSF_ROADTYPES) && RoadTypeHasBadge(GetRoadTypeRoad(tile), badge)) return true;
if (features.Test(GrfSpecFeature::GSF_TRAMTYPES) && RoadTypeHasBadge(GetRoadTypeTram(tile), badge)) return true;
return false;
default:
return false;
}
}
static bool TileHasBadge_Object(TileIndex tile, BadgeID badge, GrfSpecFeatures features)
{
if (features.Test(GrfSpecFeature::GSF_OBJECTS) && BadgesContains(ObjectSpec::GetByTile(tile)->badges, badge)) return true;
return false;
}
/**
* Test if a tile has an item containing the specified badge.
* @param tile Tile to query.
* @param badge Badge to search for.
* @param features GRF features to include in the test.
* @returns true iff the badge 'is on' the tile.
*/
static bool TileHasBadge(TileIndex tile, BadgeID badge, GrfSpecFeatures features)
{
/* Per-tiletype functions for badge testing. Like _tile_type_procs, this is 16 entries as GetTileType() reads 4 bits. */
static constexpr std::array<TileHasBadgeProc, 16> tile_procs = {
nullptr,
TileHasBadge_Rail,
TileHasBadge_Road,
TileHasBadge_Town,
nullptr,
TileHasBadge_Station,
nullptr,
nullptr,
TileHasBadge_Industry,
TileHasBadge_TunnelBridge,
TileHasBadge_Object,
};
TileHasBadgeProc proc = tile_procs[GetTileType(tile)];
return proc != nullptr && proc(tile, badge, features);
}
/**
* Test for a matching badge 'on' a specific map tile.
* @param grffile GRF file of the current varaction.
* @param tile Tile to test.
* @param parameter GRF-local badge index.
* @param features GRF features to include in the test.
* @returns true iff the badge is present.
*/
uint32_t GetNearbyBadgeVariableResult(const GRFFile &grffile, TileIndex tile, const ResolverObject &object)
{
GrfSpecFeatures features = static_cast<GrfSpecFeatures>(object.GetRegister(0x101));
if (features.None()) return 0;
uint32_t parameter = object.GetRegister(0x100);
if (parameter >= std::size(grffile.badge_list)) return UINT_MAX;
/* NewGRF cannot be expected to know the bounds of the map. If the tile is invalid it doesn't have the queried badge. */
if (!IsValidTile(tile)) return 0;
BadgeID index = grffile.badge_list[parameter];
return TileHasBadge(tile, index, features);
}
/**
* Test for a matching badge in a list of badges.
* @param grffile GRF file of the current varaction.
* @param badges List of badges to test.
* @param parameter GRF-local badge index.
@ -227,7 +405,7 @@ uint32_t GetBadgeVariableResult(const GRFFile &grffile, std::span<const BadgeID>
if (parameter >= std::size(grffile.badge_list)) return UINT_MAX;
BadgeID index = grffile.badge_list[parameter];
return std::ranges::find(badges, index) != std::end(badges);
return BadgesContains(badges, index);
}
/**

View File

@ -64,6 +64,7 @@ Badge *GetBadgeByLabel(std::string_view label);
Badge *GetClassBadge(BadgeClassID class_index);
std::span<const BadgeID> GetClassBadges();
uint32_t GetNearbyBadgeVariableResult(const GRFFile &grffile, TileIndex tile, const ResolverObject &object);
uint32_t GetBadgeVariableResult(const struct GRFFile &grffile, std::span<const BadgeID> badges, uint32_t parameter);
PalSpriteID GetBadgeSprite(const Badge &badge, GrfSpecFeature feature, std::optional<TimerGameCalendar::Date> introduction_date, PaletteID remap);

View File

@ -444,6 +444,7 @@ static uint32_t GetDistanceFromNearbyHouse(uint8_t parameter, TileIndex start_ti
return _house_mngr.GetGRFID(house_id);
}
case 0x79: return GetNearbyBadgeVariableResult(*this->ro.grffile, GetNearbyTile(parameter, this->tile), this->ro);
case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, HouseSpec::Get(this->house_id)->badges, parameter);
}

View File

@ -168,6 +168,7 @@ static uint32_t GetCountAndDistanceOfClosestInstance(const ResolverObject &objec
/* Variables available during construction check. */
switch (variable) {
case 0x79: return GetNearbyBadgeVariableResult(*this->ro.grffile, GetNearbyTile(parameter, this->tile), this->ro);
case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, GetIndustrySpec(this->type)->badges, parameter);
case 0x80: return this->tile.base();
@ -354,6 +355,7 @@ static uint32_t GetCountAndDistanceOfClosestInstance(const ResolverObject &objec
NOT_REACHED();
}
case 0x79: return GetNearbyBadgeVariableResult(*this->ro.grffile, GetNearbyTile(parameter, this->tile), this->ro);
case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, GetIndustrySpec(this->type)->badges, parameter);
/* Get a variable from the persistent storage */

View File

@ -93,6 +93,7 @@ uint32_t GetRelativePosition(TileIndex tile, TileIndex ind_tile)
/* Get industry tile ID at offset */
case 0x62: return GetIndustryIDAtOffset(GetNearbyTile(parameter, this->tile), this->industry, this->ro.grffile->grfid);
case 0x79: return GetNearbyBadgeVariableResult(*this->ro.grffile, GetNearbyTile(parameter, this->tile), this->ro);
case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, GetIndustryTileSpec(GetIndustryGfx(this->tile))->badges, parameter);
}

View File

@ -291,6 +291,7 @@ static uint32_t GetCountAndDistanceOfClosestInstance(const ResolverObject &objec
/* Object view */
case 0x48: return this->view;
case 0x79: return GetNearbyBadgeVariableResult(*this->ro.grffile, GetNearbyTile(parameter, this->tile), this->ro);
case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, this->spec->badges, parameter);
/*
@ -364,6 +365,7 @@ static uint32_t GetCountAndDistanceOfClosestInstance(const ResolverObject &objec
/* Count of object, distance of closest instance */
case 0x64: return GetCountAndDistanceOfClosestInstance(this->ro, parameter, this->ro.grffile->grfid, this->tile, this->obj);
case 0x79: return GetNearbyBadgeVariableResult(*this->ro.grffile, GetNearbyTile(parameter, this->tile), this->ro);
case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, this->spec->badges, parameter);
}

View File

@ -205,6 +205,7 @@ uint32_t RoadStopScopeResolver::GetVariable(uint8_t variable, [[maybe_unused]] u
return 0xFFFE;
}
case 0x79: return GetNearbyBadgeVariableResult(*this->ro.grffile, GetNearbyTile(parameter, this->tile), this->ro);
case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, this->roadstopspec->badges, parameter);
case 0xF0: return this->st == nullptr ? 0 : this->st->facilities.base(); // facilities

View File

@ -295,6 +295,12 @@ TownScopeResolver *StationResolverObject::GetTown()
}
break;
case 0x79:
if (this->axis != INVALID_AXIS && this->tile != INVALID_TILE) {
return GetNearbyBadgeVariableResult(*this->ro.grffile, GetNearbyTile(parameter, this->tile, true, this->axis), this->ro);
}
break;
case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, this->statspec->badges, parameter);
case 0xFA: return ClampTo<uint16_t>(TimerGameCalendar::date - CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR); // Build date, clamped to a 16 bit value
@ -398,6 +404,7 @@ TownScopeResolver *StationResolverObject::GetTown()
return 0xFFFE;
}
case 0x79: return GetNearbyBadgeVariableResult(*this->ro.grffile, GetNearbyTile(parameter, this->tile), this->ro);
case 0x7A: return GetBadgeVariableResult(*this->ro.grffile, this->statspec->badges, parameter);
/* General station variables */

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

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