1
0
Fork 0

Compare commits

...

13 Commits

Author SHA1 Message Date
Su 672c1cc10b
Merge 5e984eec1a into 2cdd50f40e 2025-07-20 13:32: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
Susan 5e984eec1a Cleanup: Deduplicate rewritten code 2025-07-17 08:59:37 +01:00
Susan a48f491f09 Cleanup: CodeQL fixes 2025-07-11 07:54:29 +01:00
Susan 3ef5783bf9 Codechange: Reorder returns 2025-07-11 07:03:03 +01:00
Susan 7f6b5d3103 Codechange: use BridgePiecePillarFlags in more places 2025-07-11 05:31:49 +01:00
Susan 5a1a8adb3f Codechange: Less indentation 2025-07-11 02:29:58 +01:00
Susan 1000875f88 Codechange: use BridgePiecePillarFlags instead of uint8_t 2025-07-10 03:06:34 +01:00
Susan 1f5ae37e2c Codechange: use BridgeSpecCtrlFlags instead of uint8_t 2025-07-09 23:48:28 +01:00
Susan 3be979cc05 Feature: Stations under bridges 2025-07-09 06:14:43 +01:00
23 changed files with 711 additions and 194 deletions

View File

@ -37,6 +37,30 @@ constexpr uint SPRITES_PER_BRIDGE_PIECE = 32; ///< Number of sprites there are p
typedef uint BridgeType; ///< Bridge spec number. typedef uint BridgeType; ///< Bridge spec number.
/**
* Actions that can be performed when the vehicle enters the depot.
*/
enum class BridgePiecePillarFlag : uint8_t {
BPPF_CORNER_W = 1 << 0,
BPPF_CORNER_S = 1 << 1,
BPPF_CORNER_E = 1 << 2,
BPPF_CORNER_N = 1 << 3,
BPPF_ALL_CORNERS = 0xF,
BPPF_EDGE_NE = 1 << 4,
BPPF_EDGE_SE = 1 << 5,
BPPF_EDGE_SW = 1 << 6,
BPPF_EDGE_NW = 1 << 7,
};
using BridgePiecePillarFlags = EnumBitSet<BridgePiecePillarFlag, uint8_t>;
enum class BridgeSpecCtrlFlag : uint8_t{
BSCF_CUSTOM_PILLAR_FLAGS,
BSCF_INVALID_PILLAR_FLAGS,
BSCF_NOT_AVAILABLE_TOWN,
BSCF_NOT_AVAILABLE_AI_GS,
};
using BridgeSpecCtrlFlags = EnumBitSet<BridgeSpecCtrlFlag, uint8_t>;
/** /**
* Struct containing information about a single bridge type * Struct containing information about a single bridge type
*/ */
@ -52,6 +76,8 @@ struct BridgeSpec {
StringID transport_name[2]; ///< description of the bridge, when built for road or rail StringID transport_name[2]; ///< description of the bridge, when built for road or rail
std::vector<std::vector<PalSpriteID>> sprite_table; ///< table of sprites for drawing the bridge std::vector<std::vector<PalSpriteID>> sprite_table; ///< table of sprites for drawing the bridge
uint8_t flags; ///< bit 0 set: disable drawing of far pillars. uint8_t flags; ///< bit 0 set: disable drawing of far pillars.
BridgeSpecCtrlFlags ctrl_flags; ///< control flags
std::array<BridgePiecePillarFlags, 12> pillar_flags; ///< bridge pillar flags: 6 x pairs of x and y flags
}; };
extern BridgeSpec _bridge[MAX_BRIDGES]; extern BridgeSpec _bridge[MAX_BRIDGES];
@ -75,6 +101,15 @@ void DrawBridgeMiddle(const TileInfo *ti);
CommandCost CheckBridgeAvailability(BridgeType bridge_type, uint bridge_len, DoCommandFlags flags = {}); CommandCost CheckBridgeAvailability(BridgeType bridge_type, uint bridge_len, DoCommandFlags flags = {});
int CalcBridgeLenCostFactor(int x); int CalcBridgeLenCostFactor(int x);
BridgePiecePillarFlags GetBridgeTilePillarFlags(TileIndex tile, TileIndex northern_bridge_end, TileIndex southern_bridge_end, BridgeType bridge_type, TransportType bridge_transport_type);
struct BridgePieceDebugInfo {
BridgePieces piece;
BridgePiecePillarFlags pillar_flags;
uint pillar_index;
};
BridgePieceDebugInfo GetBridgePieceDebugInfo(TileIndex tile);
void ResetBridges(); void ResetBridges();
#endif /* BRIDGE_H */ #endif /* BRIDGE_H */

View File

@ -382,6 +382,8 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo
StringID errmsg = INVALID_STRING_ID; StringID errmsg = INVALID_STRING_ID;
CommandCost ret = Command<CMD_BUILD_BRIDGE>::Do(CommandFlagsToDCFlags(GetCommandFlags<CMD_BUILD_BRIDGE>()) | DoCommandFlag::QueryCost, end, start, transport_type, 0, road_rail_type); CommandCost ret = Command<CMD_BUILD_BRIDGE>::Do(CommandFlagsToDCFlags(GetCommandFlags<CMD_BUILD_BRIDGE>()) | DoCommandFlag::QueryCost, end, start, transport_type, 0, road_rail_type);
const bool query_per_bridge_type = ret.Failed() && (ret.GetErrorMessage() == STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION || ret.GetErrorMessage() == STR_ERROR_BRIDGE_PILLARS_OBSTRUCT_STATION);
GUIBridgeList bl; GUIBridgeList bl;
if (ret.Failed()) { if (ret.Failed()) {
errmsg = ret.GetErrorMessage(); errmsg = ret.GetErrorMessage();
@ -415,11 +417,13 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo
} }
bool any_available = false; bool any_available = false;
StringID type_errmsg = INVALID_STRING_ID;
CommandCost type_check; CommandCost type_check;
/* loop for all bridgetypes */ /* loop for all bridgetypes */
for (BridgeType brd_type = 0; brd_type != MAX_BRIDGES; brd_type++) { for (BridgeType brd_type = 0; brd_type != MAX_BRIDGES; brd_type++) {
type_check = CheckBridgeAvailability(brd_type, bridge_len); type_check = CheckBridgeAvailability(brd_type, bridge_len);
if (type_check.Succeeded()) { if (type_check.Succeeded()) {
if (query_per_bridge_type && Command<CMD_BUILD_BRIDGE>::Do(CommandFlagsToDCFlags(GetCommandFlags<CMD_BUILD_BRIDGE>()) | DoCommandFlag::QueryCost, end, start, transport_type, brd_type, road_rail_type).Failed()) continue;
/* bridge is accepted, add to list */ /* bridge is accepted, add to list */
BuildBridgeData &item = bl.emplace_back(); BuildBridgeData &item = bl.emplace_back();
item.index = brd_type; item.index = brd_type;
@ -428,10 +432,12 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo
* bridge itself (not computed with DoCommandFlag::QueryCost) */ * bridge itself (not computed with DoCommandFlag::QueryCost) */
item.cost = ret.GetCost() + (((int64_t)tot_bridgedata_len * _price[PR_BUILD_BRIDGE] * item.spec->price) >> 8) + infra_cost; item.cost = ret.GetCost() + (((int64_t)tot_bridgedata_len * _price[PR_BUILD_BRIDGE] * item.spec->price) >> 8) + infra_cost;
any_available = true; any_available = true;
} else if (type_check.GetErrorMessage() != INVALID_STRING_ID && !query_per_bridge_type) {
type_errmsg = type_check.GetErrorMessage();
} }
} }
/* give error cause if no bridges available here*/ /* give error cause if no bridges available here*/
if (!any_available) if (!any_available && type_errmsg != INVALID_STRING_ID) errmsg = type_errmsg;
{ {
errmsg = type_check.GetErrorMessage(); errmsg = type_check.GetErrorMessage();
} }

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); uint amount = std::min(num_pieces, 0xFFFFu - it->waiting);
it->waiting += amount; it->waiting += amount;
it->GetOrCreateHistory()[THIS_MONTH].accepted += amount;
it->last_accepted = TimerGameEconomy::date; it->last_accepted = TimerGameEconomy::date;
num_pieces -= amount; num_pieces -= amount;
accepted += 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); 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; const DrawPixelInfo *dpi = _cur_dpi;
int dpi_left = dpi->left; int dpi_left = dpi->left;
int dpi_right = dpi->left + dpi->width - 1; int dpi_right = dpi->left + dpi->width - 1;
TextColour last_colour = initial_colour;
for (int run_index = 0; run_index < line.CountRuns(); run_index++) { for (int run_index = 0; run_index < line.CountRuns(); run_index++) {
const ParagraphLayouter::VisualRun &run = line.GetVisualRun(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; FontCache *fc = f->fc;
TextColour colour = f->colour; TextColour colour = f->colour;
if (colour == TC_INVALID || HasFlag(last_colour, TC_FORCED)) { if (colour == TC_INVALID || HasFlag(initial_colour, TC_FORCED)) colour = initial_colour;
colour = last_colour;
} else {
/* Update the last colour for the truncation ellipsis. */
last_colour = colour;
}
bool colour_has_shadow = (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK; 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; if (do_shadow && (!fc->GetDrawGlyphShadow() || !colour_has_shadow)) continue;
SetColourRemap(do_shadow ? TC_BLACK : colour); 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); GfxMainBlitter(sprite, begin_x + (do_shadow ? shadow_offset : 0), top + (do_shadow ? shadow_offset : 0), BlitterMode::ColourRemap);
} }
} }
return last_colour;
}; };
/* Draw shadow, then foreground */ /* Draw shadow, then foreground */
for (bool do_shadow : {true, false}) { for (bool do_shadow : {true, false}) {
TextColour last_colour = default_colour; TextColour colour = draw_line(line, do_shadow, left - offset_x, min_x, max_x, truncation, default_colour);
draw_line(line, do_shadow, left - offset_x, min_x, max_x, truncation, last_colour);
if (truncation) { if (truncation) {
int x = (_current_text_dir == TD_RTL) ? left : (right - truncation_width); 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_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. 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_data = 0; ///< bitmask of datasets hidden by the player.
uint64_t excluded_range = 0; ///< bitmask of ranges that should not be displayed. 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_on_x_axis = 0;
uint8_t num_vert_lines = GRAPH_NUM_MONTHS; 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. 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. bool highlight_state = false; ///< Current state of highlight, toggled every TIMER_BLINK_INTERVAL period.
template <typename Tprojection> struct BaseFiller {
struct Filler {
DataSet &dataset; ///< Dataset to fill. 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. 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 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; uint index = 0;
Rect line = r.WithHeight(line_height); Rect line = r.WithHeight(line_height);
for (const auto &str : this->ranges) { 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 */ /* Redraw frame if lowered */
if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered); if (lowered) DrawFrameRect(line, COLOUR_BROWN, FrameFlag::Lowered);
const Rect text = line.Shrink(WidgetDimensions::scaled.framerect); 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); line = line.Translate(0, line_height);
++index; ++index;
@ -704,6 +716,7 @@ public:
case WID_GRAPH_RANGE_MATRIX: { case WID_GRAPH_RANGE_MATRIX: {
int row = GetRowFromWidget(pt.y, widget, 0, GetCharacterHeight(FS_SMALL) + WidgetDimensions::scaled.framerect.Vertical()); 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); ToggleBit(this->excluded_range, row);
this->SetDirty(); this->SetDirty();
break; break;
@ -1115,6 +1128,7 @@ struct BaseCargoGraphWindow : BaseGraphWindow {
{ {
this->CreateNestedTree(); this->CreateNestedTree();
this->excluded_range = this->masked_range;
this->cargo_types = this->GetCargoTypes(number); this->cargo_types = this->GetCargoTypes(number);
this->vscroll = this->GetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR); this->vscroll = this->GetScrollbar(WID_GRAPH_MATRIX_SCROLLBAR);
@ -1608,7 +1622,9 @@ CompanyID PerformanceRatingDetailWindow::company = CompanyID::Invalid();
struct IndustryProductionGraphWindow : BaseCargoGraphWindow { struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
static inline constexpr StringID RANGE_LABELS[] = { static inline constexpr StringID RANGE_LABELS[] = {
STR_GRAPH_INDUSTRY_RANGE_PRODUCED, 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{}; static inline CargoTypes excluded_cargo_types{};
@ -1623,6 +1639,10 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
this->draw_dates = !TimerGameEconomy::UsingWallclockUnits(); this->draw_dates = !TimerGameEconomy::UsingWallclockUnits();
this->ranges = RANGE_LABELS; 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); this->InitializeWindow(window_number, STR_GRAPH_LAST_24_MINUTES_TIME_LABEL);
} }
@ -1630,6 +1650,9 @@ struct IndustryProductionGraphWindow : BaseCargoGraphWindow {
{ {
CargoTypes cargo_types{}; CargoTypes cargo_types{};
const Industry *i = Industry::Get(window_number); 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) { for (const auto &p : i->produced) {
if (IsValidCargoType(p.cargo)) SetBit(cargo_types, p.cargo); 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 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); 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); 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(); 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 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 { struct AcceptedCargo {
CargoType cargo = 0; ///< Cargo type CargoType cargo = 0; ///< Cargo type
uint16_t waiting = 0; ///< Amount of cargo waiting to processed 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 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>; using ProducedCargoes = std::vector<ProducedCargo>;
@ -151,7 +168,7 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> {
*/ */
inline const AcceptedCargo &GetAccepted(size_t slot) const 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; return slot < this->accepted.size() ? this->accepted[slot] : empty;
} }

View File

@ -1245,6 +1245,10 @@ void OnTick_Industry()
for (Industry *i : Industry::Iterate()) { for (Industry *i : Industry::Iterate()) {
ProduceIndustryGoods(i); 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); 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_TEXT, CmdIndustrySetText, CommandFlags({CommandFlag::Deity, CommandFlag::StrCtrl}), CMDT_OTHER_MANAGEMENT)
DEF_CMD_TRAIT(CMD_INDUSTRY_SET_PRODUCTION, CmdIndustrySetProduction, CommandFlag::Deity, 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 */ #endif /* INDUSTRY_CMD_H */

View File

@ -257,25 +257,6 @@ void SortIndustryTypes()
std::sort(_sorted_industry_types.begin(), _sorted_industry_types.end(), IndustryTypeNameSorter); 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[] = { static constexpr NWidgetPart _nested_build_industry_widgets[] = {
NWidget(NWID_HORIZONTAL), NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
@ -745,7 +726,7 @@ public:
AutoRestoreBackup backup_generating_world(_generating_world, true); AutoRestoreBackup backup_generating_world(_generating_world, true);
AutoRestoreBackup backup_ignore_industry_restritions(_ignore_industry_restrictions, 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 { } else {
success = Command<CMD_BUILD_INDUSTRY>::Post(STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY, tile, this->selected_type, layout_index, false, seed); 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)); nvp->InitializeViewport(this, Industry::Get(window_number)->location.GetCenterTile(), ScaleZoomGUI(ZoomLevel::Industry));
const Industry *i = Industry::Get(window_number); 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(); this->InvalidateData();
} }
@ -1242,7 +1223,7 @@ static constexpr NWidgetPart _nested_industry_view_widgets[] = {
EndContainer(), EndContainer(),
NWidget(NWID_HORIZONTAL), 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_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), NWidget(WWT_RESIZEBOX, COLOUR_CREAM),
EndContainer(), 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_TOGGLE_CARGO :{BLACK}Toggle graph of this cargo type
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING} 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_PRODUCED :Produced
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Transported 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 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_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Production last minute:
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{RAW_STRING}{BLACK} ({COMMA}% transported) 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_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_CARGO_GRAPH :{BLACK}Cargo Graph
STR_INDUSTRY_VIEW_PRODUCTION_GRAPH_TOOLTIP :{BLACK}Shows the graph of industry production history 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_PRODUCTION_LEVEL :{BLACK}Production level: {YELLOW}{COMMA}%
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}The industry has announced imminent closure! STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}The industry has announced imminent closure!
@ -5242,6 +5244,8 @@ STR_ERROR_START_AND_END_MUST_BE_IN :{WHITE}Start an
STR_ERROR_ENDS_OF_BRIDGE_MUST_BOTH :{WHITE}... ends of bridge must both be on land STR_ERROR_ENDS_OF_BRIDGE_MUST_BOTH :{WHITE}... ends of bridge must both be on land
STR_ERROR_BRIDGE_TOO_LONG :{WHITE}... bridge too long STR_ERROR_BRIDGE_TOO_LONG :{WHITE}... bridge too long
STR_ERROR_BRIDGE_THROUGH_MAP_BORDER :{WHITE}Bridge would end out of the map STR_ERROR_BRIDGE_THROUGH_MAP_BORDER :{WHITE}Bridge would end out of the map
STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION :{WHITE}Bridge is too low for station
STR_ERROR_BRIDGE_PILLARS_OBSTRUCT_STATION :{WHITE}Bridge pillars obstruct station
# Tunnel related errors # Tunnel related errors
STR_ERROR_CAN_T_BUILD_TUNNEL_HERE :{WHITE}Can't build tunnel here... STR_ERROR_CAN_T_BUILD_TUNNEL_HERE :{WHITE}Can't build tunnel here...

View File

@ -11,6 +11,8 @@
#define HISTORY_FUNC_HPP #define HISTORY_FUNC_HPP
#include "../core/bitmath_func.hpp" #include "../core/bitmath_func.hpp"
#include "../core/math_func.hpp"
#include "../timer/timer_game_economy.h"
#include "history_type.hpp" #include "history_type.hpp"
/** /**
@ -34,6 +36,19 @@ void RotateHistory(HistoryData<T> &history)
history[THIS_MONTH] = {}; 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. * Fill some data with historical data.
* @param history Historical data to fill from. * @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 */ #endif /* HISTORY_FUNC_HPP */

View File

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

View File

@ -12,6 +12,7 @@
#ifndef NEWGRF_ROADSTATION_H #ifndef NEWGRF_ROADSTATION_H
#define NEWGRF_ROADSTATION_H #define NEWGRF_ROADSTATION_H
#include "bridge.h"
#include "newgrf_animation_type.h" #include "newgrf_animation_type.h"
#include "newgrf_spritegroup.h" #include "newgrf_spritegroup.h"
#include "newgrf_badge_type.h" #include "newgrf_badge_type.h"
@ -71,6 +72,12 @@ enum class RoadStopSpecFlag : uint8_t {
}; };
using RoadStopSpecFlags = EnumBitSet<RoadStopSpecFlag, uint8_t>; using RoadStopSpecFlags = EnumBitSet<RoadStopSpecFlag, uint8_t>;
enum class RoadStopSpecIntlFlag : uint8_t {
BridgeHeightsSet, ///< bridge_height[6] is set.
BridgeDisallowedPillarsSet, ///< bridge_disallowed_pillars[6] is set.
};
using RoadStopSpecIntlFlags = EnumBitSet<RoadStopSpecIntlFlag, uint8_t>;
enum RoadStopView : uint8_t { enum RoadStopView : uint8_t {
RSV_BAY_NE = 0, ///< Bay road stop, facing Northeast RSV_BAY_NE = 0, ///< Bay road stop, facing Northeast
RSV_BAY_SE = 1, ///< Bay road stop, facing Southeast RSV_BAY_SE = 1, ///< Bay road stop, facing Southeast
@ -133,17 +140,20 @@ struct RoadStopSpec : NewGRFSpecBase<RoadStopClassID> {
RoadStopDrawModes draw_mode = {RoadStopDrawMode::Road, RoadStopDrawMode::Overlay}; RoadStopDrawModes draw_mode = {RoadStopDrawMode::Road, RoadStopDrawMode::Overlay};
RoadStopCallbackMasks callback_mask{}; RoadStopCallbackMasks callback_mask{};
RoadStopSpecFlags flags{}; RoadStopSpecFlags flags{};
RoadStopSpecIntlFlags internal_flags{};
CargoTypes cargo_triggers = 0; ///< Bitmask of cargo types which cause trigger re-randomizing CargoTypes cargo_triggers = 0; ///< Bitmask of cargo types which cause trigger re-randomizing
AnimationInfo<StationAnimationTriggers> animation; AnimationInfo<StationAnimationTriggers> animation;
uint8_t bridge_height[6]; ///< Minimum height for a bridge above, 0 for none uint8_t bridge_height[6]; ///< Minimum height for a bridge above, 0 for none
uint8_t bridge_disallowed_pillars[6]; ///< Disallowed pillar flags for a bridge above BridgePiecePillarFlags bridge_disallowed_pillars[6]; ///< Disallowed pillar flags for a bridge above
uint8_t build_cost_multiplier = 16; ///< Build cost multiplier per tile. uint8_t build_cost_multiplier = 16; ///< Build cost multiplier per tile.
uint8_t clear_cost_multiplier = 16; ///< Clear cost multiplier per tile. uint8_t clear_cost_multiplier = 16; ///< Clear cost multiplier per tile.
uint8_t height; ///< The height of this structure, in heightlevels; max MAX_TILE_HEIGHT.
std::vector<BadgeID> badges; std::vector<BadgeID> badges;
/** /**

View File

@ -10,6 +10,7 @@
#ifndef NEWGRF_STATION_H #ifndef NEWGRF_STATION_H
#define NEWGRF_STATION_H #define NEWGRF_STATION_H
#include "bridge.h"
#include "core/enum_type.hpp" #include "core/enum_type.hpp"
#include "newgrf_animation_type.h" #include "newgrf_animation_type.h"
#include "newgrf_badge_type.h" #include "newgrf_badge_type.h"
@ -118,6 +119,12 @@ enum class StationSpecFlag : uint8_t {
}; };
using StationSpecFlags = EnumBitSet<StationSpecFlag, uint8_t>; using StationSpecFlags = EnumBitSet<StationSpecFlag, uint8_t>;
enum class StationSpecIntlFlag : uint8_t {
BridgeHeightsSet, ///< bridge_height[8] is set.
BridgeDisallowedPillarsSet, ///< bridge_disallowed_pillars[8] is set.
};
using StationSpecIntlFlags = EnumBitSet<StationSpecIntlFlag, uint8_t>;
/** Station specification. */ /** Station specification. */
struct StationSpec : NewGRFSpecBase<StationClassID> { struct StationSpec : NewGRFSpecBase<StationClassID> {
StationSpec() : name(0), StationSpec() : name(0),
@ -162,6 +169,12 @@ struct StationSpec : NewGRFSpecBase<StationClassID> {
StationSpecFlags flags; ///< Bitmask of flags, bit 0: use different sprite set; bit 1: divide cargo about by station size StationSpecFlags flags; ///< Bitmask of flags, bit 0: use different sprite set; bit 1: divide cargo about by station size
struct BridgeAboveFlags {
uint8_t height = UINT8_MAX; ///< Minimum height for a bridge above, 0 for none
BridgePiecePillarFlags disallowed_pillars = {}; ///< Disallowed pillar flags for a bridge above
};
std::vector<BridgeAboveFlags> bridge_above_flags; ///< List of bridge above flags.
enum class TileFlag : uint8_t { enum class TileFlag : uint8_t {
Pylons = 0, ///< Tile should contain catenary pylons. Pylons = 0, ///< Tile should contain catenary pylons.
NoWires = 1, ///< Tile should NOT contain catenary wires. NoWires = 1, ///< Tile should NOT contain catenary wires.
@ -172,10 +185,18 @@ struct StationSpec : NewGRFSpecBase<StationClassID> {
AnimationInfo<StationAnimationTriggers> animation; AnimationInfo<StationAnimationTriggers> animation;
StationSpecIntlFlags internal_flags{}; ///< Bitmask of internal spec flags
/** Custom platform layouts, keyed by platform and length combined. */ /** Custom platform layouts, keyed by platform and length combined. */
std::unordered_map<uint16_t, std::vector<uint8_t>> layouts; std::unordered_map<uint16_t, std::vector<uint8_t>> layouts;
std::vector<BadgeID> badges; std::vector<BadgeID> badges;
BridgeAboveFlags GetBridgeAboveFlags(uint gfx) const
{
if (gfx < this->bridge_above_flags.size()) return this->bridge_above_flags[gfx];
return {};
}
}; };
/** Class containing information relating to station classes. */ /** Class containing information relating to station classes. */

View File

@ -19,12 +19,50 @@
static OldPersistentStorage _old_ind_persistent_storage; 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> { class SlIndustryAccepted : public VectorSaveLoadHandler<SlIndustryAccepted, Industry, Industry::AcceptedCargo, INDUSTRY_NUM_INPUTS> {
public: public:
static inline const SaveLoad description[] = { static inline const SaveLoad description[] = {
SLE_VAR(Industry::AcceptedCargo, cargo, SLE_UINT8), SLE_VAR(Industry::AcceptedCargo, cargo, SLE_UINT8),
SLE_VAR(Industry::AcceptedCargo, waiting, SLE_UINT16), SLE_VAR(Industry::AcceptedCargo, waiting, SLE_UINT16),
SLE_VAR(Industry::AcceptedCargo, last_accepted, SLE_INT32), 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; 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) { if (i->location.tile != 0) {
/* Copy data from old fixed arrays to industry. */ /* 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)); std::copy(std::begin(_old_produced), std::end(_old_produced), std::back_inserter(i->produced));
i->town = RemapTown(i->location.tile); 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_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_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 SL_MAX_VERSION, ///< Highest possible saveload version
}; };

View File

@ -7,6 +7,7 @@
/** @file station_cmd.cpp Handling of station tiles. */ /** @file station_cmd.cpp Handling of station tiles. */
#include "core/enum_type.hpp"
#include "stdafx.h" #include "stdafx.h"
#include "core/flatset_type.hpp" #include "core/flatset_type.hpp"
#include "aircraft.h" #include "aircraft.h"
@ -949,125 +950,6 @@ static CommandCost CheckFlatLandRailStation(TileIndex tile_cur, TileIndex north_
return cost; return cost;
} }
/**
* Checks if a road stop can be built at the given tile.
* @param cur_tile Tile to check.
* @param allowed_z Height allowed for the tile. If allowed_z is negative, it will be set to the height of this tile.
* @param flags Operation to perform.
* @param invalid_dirs Prohibited directions (set of DiagDirections).
* @param is_drive_through True if trying to build a drive-through station.
* @param station_type Station type (bus, truck or road waypoint).
* @param axis Axis of a drive-through road stop.
* @param station StationID to be queried and returned if available.
* @param rt Road type to build, may be INVALID_ROADTYPE if an existing road is required.
* @return The cost in case of success, or an error code if it failed.
*/
static CommandCost CheckFlatLandRoadStop(TileIndex cur_tile, int &allowed_z, DoCommandFlags flags, DiagDirections invalid_dirs, bool is_drive_through, StationType station_type, Axis axis, StationID *station, RoadType rt)
{
CommandCost cost(EXPENSES_CONSTRUCTION);
CommandCost ret = CheckBuildableTile(cur_tile, invalid_dirs, allowed_z, !is_drive_through);
if (ret.Failed()) return ret;
cost.AddCost(ret.GetCost());
/* If station is set, then we have special handling to allow building on top of already existing stations.
* Station points to StationID::Invalid() if we can build on any station.
* Or it points to a station if we're only allowed to build on exactly that station. */
if (station != nullptr && IsTileType(cur_tile, MP_STATION)) {
if (!IsAnyRoadStop(cur_tile)) {
return ClearTile_Station(cur_tile, DoCommandFlag::Auto); // Get error message.
} else {
if (station_type != GetStationType(cur_tile) ||
is_drive_through != IsDriveThroughStopTile(cur_tile)) {
return ClearTile_Station(cur_tile, DoCommandFlag::Auto); // Get error message.
}
/* Drive-through station in the wrong direction. */
if (is_drive_through && IsDriveThroughStopTile(cur_tile) && GetDriveThroughStopAxis(cur_tile) != axis) {
return CommandCost(STR_ERROR_DRIVE_THROUGH_DIRECTION);
}
StationID st = GetStationIndex(cur_tile);
if (*station == StationID::Invalid()) {
*station = st;
} else if (*station != st) {
return CommandCost(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING);
}
}
} else {
bool build_over_road = is_drive_through && IsNormalRoadTile(cur_tile);
/* Road bits in the wrong direction. */
RoadBits rb = IsNormalRoadTile(cur_tile) ? GetAllRoadBits(cur_tile) : ROAD_NONE;
if (build_over_road && (rb & (axis == AXIS_X ? ROAD_Y : ROAD_X)) != 0) {
/* Someone was pedantic and *NEEDED* three fracking different error messages. */
switch (CountBits(rb)) {
case 1:
return CommandCost(STR_ERROR_DRIVE_THROUGH_DIRECTION);
case 2:
if (rb == ROAD_X || rb == ROAD_Y) return CommandCost(STR_ERROR_DRIVE_THROUGH_DIRECTION);
return CommandCost(STR_ERROR_DRIVE_THROUGH_CORNER);
default: // 3 or 4
return CommandCost(STR_ERROR_DRIVE_THROUGH_JUNCTION);
}
}
if (build_over_road) {
/* There is a road, check if we can build road+tram stop over it. */
RoadType road_rt = GetRoadType(cur_tile, RTT_ROAD);
if (road_rt != INVALID_ROADTYPE) {
Owner road_owner = GetRoadOwner(cur_tile, RTT_ROAD);
if (road_owner == OWNER_TOWN) {
if (!_settings_game.construction.road_stop_on_town_road) return CommandCost(STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD);
} else if (!_settings_game.construction.road_stop_on_competitor_road && road_owner != OWNER_NONE) {
ret = CheckOwnership(road_owner);
if (ret.Failed()) return ret;
}
uint num_pieces = CountBits(GetRoadBits(cur_tile, RTT_ROAD));
if (rt != INVALID_ROADTYPE && RoadTypeIsRoad(rt) && !HasPowerOnRoad(rt, road_rt)) return CommandCost(STR_ERROR_NO_SUITABLE_ROAD);
if (GetDisallowedRoadDirections(cur_tile) != DRD_NONE && road_owner != OWNER_TOWN) {
ret = CheckOwnership(road_owner);
if (ret.Failed()) return ret;
}
cost.AddCost(RoadBuildCost(road_rt) * (2 - num_pieces));
} else if (rt != INVALID_ROADTYPE && RoadTypeIsRoad(rt)) {
cost.AddCost(RoadBuildCost(rt) * 2);
}
/* There is a tram, check if we can build road+tram stop over it. */
RoadType tram_rt = GetRoadType(cur_tile, RTT_TRAM);
if (tram_rt != INVALID_ROADTYPE) {
Owner tram_owner = GetRoadOwner(cur_tile, RTT_TRAM);
if (Company::IsValidID(tram_owner) &&
(!_settings_game.construction.road_stop_on_competitor_road ||
/* Disallow breaking end-of-line of someone else
* so trams can still reverse on this tile. */
HasExactlyOneBit(GetRoadBits(cur_tile, RTT_TRAM)))) {
ret = CheckOwnership(tram_owner);
if (ret.Failed()) return ret;
}
uint num_pieces = CountBits(GetRoadBits(cur_tile, RTT_TRAM));
if (rt != INVALID_ROADTYPE && RoadTypeIsTram(rt) && !HasPowerOnRoad(rt, tram_rt)) return CommandCost(STR_ERROR_NO_SUITABLE_ROAD);
cost.AddCost(RoadBuildCost(tram_rt) * (2 - num_pieces));
} else if (rt != INVALID_ROADTYPE && RoadTypeIsTram(rt)) {
cost.AddCost(RoadBuildCost(rt) * 2);
}
} else if (rt == INVALID_ROADTYPE) {
return CommandCost(STR_ERROR_THERE_IS_NO_ROAD);
} else {
ret = Command<CMD_LANDSCAPE_CLEAR>::Do(flags, cur_tile);
if (ret.Failed()) return ret;
cost.AddCost(ret.GetCost());
cost.AddCost(RoadBuildCost(rt) * 2);
}
}
return cost;
}
/** /**
* Check whether we can expand the rail part of the given station. * Check whether we can expand the rail part of the given station.
@ -1324,6 +1206,230 @@ void SetRailStationTileFlags(TileIndex tile, const StationSpec *statspec)
SetStationTileHaveWires(tile, !flags.Test(StationSpec::TileFlag::NoWires)); SetStationTileHaveWires(tile, !flags.Test(StationSpec::TileFlag::NoWires));
} }
CommandCost IsRailStationBridgeAboveOk(TileIndex tile, const StationSpec *statspec, uint8_t layout, TileIndex northern_bridge_end, TileIndex southern_bridge_end, int bridge_height,
BridgeType bridge_type, TransportType bridge_transport_type)
{
if (statspec != nullptr && statspec->internal_flags.Test(StationSpecIntlFlag::BridgeHeightsSet)) {
int height_above = statspec->GetBridgeAboveFlags(layout).height;
if (height_above == 0) return CommandCost(INVALID_STRING_ID);
if (GetTileMaxZ(tile) + height_above > bridge_height) {
return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION);
}
} else if (!statspec) {
/* Default stations/waypoints */
const int height = layout < 4 ? 2 : 5;
if (GetTileMaxZ(tile) + height > bridge_height) return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION);
} else {
return CommandCost(INVALID_STRING_ID);
}
BridgePiecePillarFlags disallowed_pillar_flags;
if (statspec != nullptr && statspec->internal_flags.Test(StationSpecIntlFlag::BridgeDisallowedPillarsSet)) {
/* Pillar flags set by NewGRF */
disallowed_pillar_flags = statspec->GetBridgeAboveFlags(layout).disallowed_pillars;
} else if (!statspec) {
/* Default stations/waypoints */
if (layout < 8) {
static const BridgePiecePillarFlags st_flags[8] = {
{BridgePiecePillarFlag::BPPF_EDGE_SW, BridgePiecePillarFlag::BPPF_EDGE_NE}, //0x50,
{BridgePiecePillarFlag::BPPF_EDGE_NW, BridgePiecePillarFlag::BPPF_EDGE_SE}, //0xA0,
{BridgePiecePillarFlag::BPPF_EDGE_SW, BridgePiecePillarFlag::BPPF_EDGE_NE}, //0x50,
{BridgePiecePillarFlag::BPPF_EDGE_NW, BridgePiecePillarFlag::BPPF_EDGE_SE}, //0xA0,
{BridgePiecePillarFlag::BPPF_EDGE_SW, BridgePiecePillarFlag::BPPF_EDGE_NE, BridgePiecePillarFlag::BPPF_EDGE_SE, BridgePiecePillarFlag::BPPF_CORNER_E, BridgePiecePillarFlag::BPPF_CORNER_S}, //0x50 | 0x26,
{BridgePiecePillarFlag::BPPF_EDGE_NW, BridgePiecePillarFlag::BPPF_EDGE_SE, BridgePiecePillarFlag::BPPF_EDGE_NE, BridgePiecePillarFlag::BPPF_CORNER_N, BridgePiecePillarFlag::BPPF_CORNER_E}, //0xA0 | 0x1C,
{BridgePiecePillarFlag::BPPF_EDGE_NW, BridgePiecePillarFlag::BPPF_EDGE_SE, BridgePiecePillarFlag::BPPF_EDGE_NW, BridgePiecePillarFlag::BPPF_CORNER_N, BridgePiecePillarFlag::BPPF_CORNER_W}, //0x50 | 0x89,
{BridgePiecePillarFlag::BPPF_EDGE_NW, BridgePiecePillarFlag::BPPF_EDGE_SE, BridgePiecePillarFlag::BPPF_EDGE_SW, BridgePiecePillarFlag::BPPF_CORNER_S, BridgePiecePillarFlag::BPPF_CORNER_W} //0xA0 | 0x43
};
disallowed_pillar_flags = st_flags[layout];
} else {
disallowed_pillar_flags = {};
}
} else if (GetStationTileFlags(layout, statspec).Test(StationSpec::TileFlag::Blocked)) {
/* Non-track station tiles */
disallowed_pillar_flags = {};
} else {
/* Tracked station tiles */
const Axis axis = HasBit(layout, 0) ? AXIS_Y : AXIS_X;
disallowed_pillar_flags = axis == AXIS_X ? BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_EDGE_SW, BridgePiecePillarFlag::BPPF_EDGE_NE}) : BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_EDGE_NW, BridgePiecePillarFlag::BPPF_EDGE_SE}); //0x50, 0xA0
}
if (!(GetBridgeTilePillarFlags(tile, northern_bridge_end, southern_bridge_end, bridge_type, bridge_transport_type) & disallowed_pillar_flags).Any()) return CommandCost(STR_ERROR_BRIDGE_PILLARS_OBSTRUCT_STATION);
return CommandCost();
}
CommandCost IsRailStationBridgeAboveOk(TileIndex tile, const StationSpec *statspec, uint8_t layout)
{
if (!IsBridgeAbove(tile)) return CommandCost();
TileIndex southern_bridge_end = GetSouthernBridgeEnd(tile);
TileIndex northern_bridge_end = GetNorthernBridgeEnd(tile);
return IsRailStationBridgeAboveOk(tile, statspec, layout, northern_bridge_end, southern_bridge_end, GetBridgeHeight(southern_bridge_end),
GetBridgeType(southern_bridge_end), GetTunnelBridgeTransportType(southern_bridge_end));
}
CommandCost IsRoadStopBridgeAboveOK(TileIndex tile, const RoadStopSpec *spec, bool drive_through, DiagDirection entrance,
TileIndex northern_bridge_end, TileIndex southern_bridge_end, int bridge_height,
BridgeType bridge_type, TransportType bridge_transport_type)
{
if (spec != nullptr && spec->internal_flags.Test(RoadStopSpecIntlFlag::BridgeHeightsSet)) {
int height = spec->bridge_height[drive_through ? (GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET + DiagDirToAxis(entrance)) : entrance];
if (height == 0) return CommandCost(INVALID_STRING_ID);
if (GetTileMaxZ(tile) + height > bridge_height) {
return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION);
}
} else {
return CommandCost(INVALID_STRING_ID);
if (GetTileMaxZ(tile) + (drive_through ? 1 : 2) > bridge_height) {
return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION);
}
}
BridgePiecePillarFlags disallowed_pillar_flags = {};
if (spec != nullptr && spec->internal_flags.Test(RoadStopSpecIntlFlag::BridgeDisallowedPillarsSet)) {
disallowed_pillar_flags = spec->bridge_disallowed_pillars[drive_through ? (GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET + DiagDirToAxis(entrance)) : entrance];
} else if (drive_through) {
disallowed_pillar_flags = DiagDirToAxis(entrance) == AXIS_X ? BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_EDGE_SW, BridgePiecePillarFlag::BPPF_EDGE_NE}) : BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_EDGE_NW, BridgePiecePillarFlag::BPPF_EDGE_SE}); //0x50, 0xA0
} else {
disallowed_pillar_flags.Set((BridgePiecePillarFlags) (4 + entrance));
}
if (!(GetBridgeTilePillarFlags(tile, northern_bridge_end, southern_bridge_end, bridge_type, bridge_transport_type) & disallowed_pillar_flags).Any()) return CommandCost(STR_ERROR_BRIDGE_PILLARS_OBSTRUCT_STATION);
return CommandCost();
}
/**
* Checks if a road stop can be built at the given tile.
* @param cur_tile Tile to check.
* @param allowed_z Height allowed for the tile. If allowed_z is negative, it will be set to the height of this tile.
* @param flags Operation to perform.
* @param invalid_dirs Prohibited directions (set of DiagDirections).
* @param is_drive_through True if trying to build a drive-through station.
* @param station_type Station type (bus, truck or road waypoint).
* @param axis Axis of a drive-through road stop.
* @param station StationID to be queried and returned if available.
* @param rt Road type to build, may be INVALID_ROADTYPE if an existing road is required.
* @return The cost in case of success, or an error code if it failed.
*/
static CommandCost CheckFlatLandRoadStop(TileIndex cur_tile, int &allowed_z, const RoadStopSpec *spec, DoCommandFlags flags, DiagDirections invalid_dirs, bool is_drive_through, StationType station_type, Axis axis, StationID *station, RoadType rt)
{
CommandCost cost(EXPENSES_CONSTRUCTION);
bool allow_under_bridge = spec != nullptr && spec->internal_flags.Test(RoadStopSpecIntlFlag::BridgeHeightsSet);
CommandCost ret = CheckBuildableTile(cur_tile, invalid_dirs, allowed_z, !is_drive_through, true);
if (ret.Failed()) return ret;
cost.AddCost(ret.GetCost());
if (allow_under_bridge && IsBridgeAbove(cur_tile)) {
TileIndex southern_bridge_end = GetSouthernBridgeEnd(cur_tile);
TileIndex northern_bridge_end = GetNorthernBridgeEnd(cur_tile);
CommandCost bridge_ret = IsRoadStopBridgeAboveOK(cur_tile, spec, is_drive_through, DiagDirection::DIAGDIR_NE /*obviously wrong, but how do you get the "first bit" from invalid_dirs? and how would that be correct?? */,
northern_bridge_end, southern_bridge_end, GetBridgeHeight(southern_bridge_end),
GetBridgeType(southern_bridge_end), GetTunnelBridgeTransportType(southern_bridge_end));
if (bridge_ret.Failed()) return bridge_ret;
}
/* If station is set, then we have special handling to allow building on top of already existing stations.
* Station points to StationID::Invalid() if we can build on any station.
* Or it points to a station if we're only allowed to build on exactly that station. */
if (station != nullptr && IsTileType(cur_tile, MP_STATION)) {
if (!IsAnyRoadStop(cur_tile)) {
return ClearTile_Station(cur_tile, DoCommandFlag::Auto); // Get error message.
} else {
if (station_type != GetStationType(cur_tile) ||
is_drive_through != IsDriveThroughStopTile(cur_tile)) {
return ClearTile_Station(cur_tile, DoCommandFlag::Auto); // Get error message.
}
/* Drive-through station in the wrong direction. */
if (is_drive_through && IsDriveThroughStopTile(cur_tile) && GetDriveThroughStopAxis(cur_tile) != axis) {
return CommandCost(STR_ERROR_DRIVE_THROUGH_DIRECTION);
}
StationID st = GetStationIndex(cur_tile);
if (*station == StationID::Invalid()) {
*station = st;
} else if (*station != st) {
return CommandCost(STR_ERROR_ADJOINS_MORE_THAN_ONE_EXISTING);
}
}
} else {
bool build_over_road = is_drive_through && IsNormalRoadTile(cur_tile);
/* Road bits in the wrong direction. */
RoadBits rb = IsNormalRoadTile(cur_tile) ? GetAllRoadBits(cur_tile) : ROAD_NONE;
if (build_over_road && (rb & (axis == AXIS_X ? ROAD_Y : ROAD_X)) != 0) {
/* Someone was pedantic and *NEEDED* three fracking different error messages. */
switch (CountBits(rb)) {
case 1:
return CommandCost(STR_ERROR_DRIVE_THROUGH_DIRECTION);
case 2:
if (rb == ROAD_X || rb == ROAD_Y) return CommandCost(STR_ERROR_DRIVE_THROUGH_DIRECTION);
return CommandCost(STR_ERROR_DRIVE_THROUGH_CORNER);
default: // 3 or 4
return CommandCost(STR_ERROR_DRIVE_THROUGH_JUNCTION);
}
}
if (build_over_road) {
/* There is a road, check if we can build road+tram stop over it. */
RoadType road_rt = GetRoadType(cur_tile, RTT_ROAD);
if (road_rt != INVALID_ROADTYPE) {
Owner road_owner = GetRoadOwner(cur_tile, RTT_ROAD);
if (road_owner == OWNER_TOWN) {
if (!_settings_game.construction.road_stop_on_town_road) return CommandCost(STR_ERROR_DRIVE_THROUGH_ON_TOWN_ROAD);
} else if (!_settings_game.construction.road_stop_on_competitor_road && road_owner != OWNER_NONE) {
ret = CheckOwnership(road_owner);
if (ret.Failed()) return ret;
}
uint num_pieces = CountBits(GetRoadBits(cur_tile, RTT_ROAD));
if (rt != INVALID_ROADTYPE && RoadTypeIsRoad(rt) && !HasPowerOnRoad(rt, road_rt)) return CommandCost(STR_ERROR_NO_SUITABLE_ROAD);
if (GetDisallowedRoadDirections(cur_tile) != DRD_NONE && road_owner != OWNER_TOWN) {
ret = CheckOwnership(road_owner);
if (ret.Failed()) return ret;
}
cost.AddCost(RoadBuildCost(road_rt) * (2 - num_pieces));
} else if (rt != INVALID_ROADTYPE && RoadTypeIsRoad(rt)) {
cost.AddCost(RoadBuildCost(rt) * 2);
}
/* There is a tram, check if we can build road+tram stop over it. */
RoadType tram_rt = GetRoadType(cur_tile, RTT_TRAM);
if (tram_rt != INVALID_ROADTYPE) {
Owner tram_owner = GetRoadOwner(cur_tile, RTT_TRAM);
if (Company::IsValidID(tram_owner) &&
(!_settings_game.construction.road_stop_on_competitor_road ||
/* Disallow breaking end-of-line of someone else
* so trams can still reverse on this tile. */
HasExactlyOneBit(GetRoadBits(cur_tile, RTT_TRAM)))) {
ret = CheckOwnership(tram_owner);
if (ret.Failed()) return ret;
}
uint num_pieces = CountBits(GetRoadBits(cur_tile, RTT_TRAM));
if (rt != INVALID_ROADTYPE && RoadTypeIsTram(rt) && !HasPowerOnRoad(rt, tram_rt)) return CommandCost(STR_ERROR_NO_SUITABLE_ROAD);
cost.AddCost(RoadBuildCost(tram_rt) * (2 - num_pieces));
} else if (rt != INVALID_ROADTYPE && RoadTypeIsTram(rt)) {
cost.AddCost(RoadBuildCost(rt) * 2);
}
} else if (rt == INVALID_ROADTYPE) {
return CommandCost(STR_ERROR_THERE_IS_NO_ROAD);
} else {
ret = Command<CMD_LANDSCAPE_CLEAR>::Do(flags, cur_tile);
if (ret.Failed()) return ret;
cost.AddCost(ret.GetCost());
cost.AddCost(RoadBuildCost(rt) * 2);
}
}
return cost;
}
/** /**
* Build rail station * Build rail station
* @param flags operation to perform * @param flags operation to perform
@ -1362,6 +1468,9 @@ CommandCost CmdBuildRailStation(DoCommandFlags flags, TileIndex tile_org, RailTy
w_org = numtracks; w_org = numtracks;
} }
/* Check if the first tile and the last tile are valid */
if (!IsValidTile(tile_org) || TileAddWrap(tile_org, w_org - 1, h_org - 1) == INVALID_TILE) return CMD_ERROR;
bool reuse = (station_to_join != NEW_STATION); bool reuse = (station_to_join != NEW_STATION);
if (!reuse) station_to_join = StationID::Invalid(); if (!reuse) station_to_join = StationID::Invalid();
bool distant_join = (station_to_join != StationID::Invalid()); bool distant_join = (station_to_join != StationID::Invalid());
@ -1376,6 +1485,7 @@ CommandCost CmdBuildRailStation(DoCommandFlags flags, TileIndex tile_org, RailTy
/* Make sure the area below consists of clear tiles. (OR tiles belonging to a certain rail station) */ /* Make sure the area below consists of clear tiles. (OR tiles belonging to a certain rail station) */
StationID est = StationID::Invalid(); StationID est = StationID::Invalid();
std::vector<Train *> affected_vehicles; std::vector<Train *> affected_vehicles;
/* Add construction and clearing expenses. */ /* Add construction and clearing expenses. */
CommandCost cost = CalculateRailStationCost(new_location, flags, axis, &est, rt, affected_vehicles, spec_class, spec_index, plat_len, numtracks); CommandCost cost = CalculateRailStationCost(new_location, flags, axis, &est, rt, affected_vehicles, spec_class, spec_index, plat_len, numtracks);
if (cost.Failed()) return cost; if (cost.Failed()) return cost;
@ -1423,23 +1533,30 @@ CommandCost CmdBuildRailStation(DoCommandFlags flags, TileIndex tile_org, RailTy
st->cached_anim_triggers.Set(statspec->animation.triggers); st->cached_anim_triggers.Set(statspec->animation.triggers);
} }
TileIndexDiff tile_delta = TileOffsByAxis(axis); // offset to go to the next platform tile
TileIndexDiff track_delta = TileOffsByAxis(OtherAxis(axis)); // offset to go to the next track
Track track = AxisToTrack(axis);
std::vector<uint8_t> layouts(numtracks * plat_len); std::vector<uint8_t> layouts(numtracks * plat_len);
GetStationLayout(layouts.data(), numtracks, plat_len, statspec); GetStationLayout(layouts.data(), numtracks, plat_len, statspec);
TileIndexDiff tile_delta = TileOffsByAxis(axis); // offset to go to the next platform tile
TileIndexDiff track_delta = TileOffsByAxis(OtherAxis(axis)); // offset to go to the next track
Track track = AxisToTrack(axis);
uint8_t numtracks_orig = numtracks; uint8_t numtracks_orig = numtracks;
Company *c = Company::Get(st->owner); Company *c = Company::Get(st->owner);
size_t layout_idx = 0; size_t layout_idx = 0;
TileIndex tile_track = tile_org; TileIndex tile_track = tile_org;
do { do {
TileIndex tile = tile_track; TileIndex tile = tile_track;
int w = plat_len; int w = plat_len;
do { do {
uint8_t layout = layouts[layout_idx++]; uint8_t layout = layouts[layout_idx++];
ret = IsRailStationBridgeAboveOk(tile, statspec, layout);
if (ret.Failed()) {
//return CommandCost::DualErrorMessage(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST, ret.GetErrorMessage()); //FIXME
return ret;
}
if (IsRailStationTile(tile) && HasStationReservation(tile)) { if (IsRailStationTile(tile) && HasStationReservation(tile)) {
/* Check for trains having a reservation for this tile. */ /* Check for trains having a reservation for this tile. */
Train *v = GetTrainForReservation(tile, AxisToTrack(GetRailStationAxis(tile))); Train *v = GetTrainForReservation(tile, AxisToTrack(GetRailStationAxis(tile)));
@ -1913,7 +2030,7 @@ static CommandCost FindJoiningRoadStop(StationID existing_stop, StationID statio
* @param unit_cost The cost to build one road stop of the current type. * @param unit_cost The cost to build one road stop of the current type.
* @return The cost in case of success, or an error code if it failed. * @return The cost in case of success, or an error code if it failed.
*/ */
CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlags flags, bool is_drive_through, StationType station_type, Axis axis, DiagDirection ddir, StationID *est, RoadType rt, Money unit_cost) CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlags flags, bool is_drive_through, StationType station_type, const RoadStopSpec *roadstopspec, Axis axis, DiagDirection ddir, StationID *est, RoadType rt, Money unit_cost)
{ {
DiagDirections invalid_dirs{}; DiagDirections invalid_dirs{};
if (is_drive_through) { if (is_drive_through) {
@ -1927,7 +2044,7 @@ CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlags flags, bool
int allowed_z = -1; int allowed_z = -1;
CommandCost cost(EXPENSES_CONSTRUCTION); CommandCost cost(EXPENSES_CONSTRUCTION);
for (TileIndex cur_tile : tile_area) { for (TileIndex cur_tile : tile_area) {
CommandCost ret = CheckFlatLandRoadStop(cur_tile, allowed_z, flags, invalid_dirs, is_drive_through, station_type, axis, est, rt); CommandCost ret = CheckFlatLandRoadStop(cur_tile, allowed_z, roadstopspec, flags, invalid_dirs, is_drive_through, station_type, axis, est, rt);
if (ret.Failed()) return ret; if (ret.Failed()) return ret;
bool is_preexisting_roadstop = IsTileType(cur_tile, MP_STATION) && IsAnyRoadStop(cur_tile); bool is_preexisting_roadstop = IsTileType(cur_tile, MP_STATION) && IsAnyRoadStop(cur_tile);
@ -2008,7 +2125,7 @@ CommandCost CmdBuildRoadStop(DoCommandFlags flags, TileIndex tile, uint8_t width
unit_cost = _price[is_truck_stop ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS]; unit_cost = _price[is_truck_stop ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS];
} }
StationID est = StationID::Invalid(); StationID est = StationID::Invalid();
CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, is_drive_through, is_truck_stop ? StationType::Truck : StationType::Bus, axis, ddir, &est, rt, unit_cost); CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, is_drive_through, is_truck_stop ? StationType::Truck : StationType::Bus, roadstopspec, axis, ddir, &est, rt, unit_cost);
if (cost.Failed()) return cost; if (cost.Failed()) return cost;
Station *st = nullptr; Station *st = nullptr;
@ -3380,6 +3497,7 @@ draw_default_foundation:
} }
DrawRailTileSeq(ti, t, TO_BUILDINGS, total_offset, relocation, palette); DrawRailTileSeq(ti, t, TO_BUILDINGS, total_offset, relocation, palette);
DrawBridgeMiddle(ti);
} }
void StationPickerDrawSprite(int x, int y, StationType st, RailType railtype, RoadType roadtype, int image) void StationPickerDrawSprite(int x, int y, StationType st, RailType railtype, RoadType roadtype, int image)

View File

@ -740,8 +740,53 @@ static const std::span<const std::span<const PalSpriteID>> _bridge_sprite_table[
* @param nrl description of the rail bridge in query tool * @param nrl description of the rail bridge in query tool
* @param nrd description of the road bridge in query tool * @param nrd description of the road bridge in query tool
*/ */
#define MBR(y, mnl, mxl, p, mxs, spr, plt, dsc, nrl, nrd) \ #define MBR(y, mnl, mxl, p, mxs, spr, plt, dsc, nrl, nrd, pillars) \
{TimerGameCalendar::Year{y}, mnl, mxl, p, mxs, spr, plt, dsc, { nrl, nrd }, {}, 0} {TimerGameCalendar::Year{y}, mnl, mxl, p, mxs, spr, plt, dsc, { nrl, nrd }, {}, 0, BridgeSpecCtrlFlags(), pillars}
static constexpr std::array<BridgePiecePillarFlags, 12> all_pillars = {
BridgePiecePillarFlag::BPPF_ALL_CORNERS, //0x0F,
BridgePiecePillarFlag::BPPF_ALL_CORNERS, //0x0F,
BridgePiecePillarFlag::BPPF_ALL_CORNERS, //0x0F,
BridgePiecePillarFlag::BPPF_ALL_CORNERS, //0x0F,
BridgePiecePillarFlag::BPPF_ALL_CORNERS, //0x0F,
BridgePiecePillarFlag::BPPF_ALL_CORNERS, //0x0F,
BridgePiecePillarFlag::BPPF_ALL_CORNERS, //0x0F,
BridgePiecePillarFlag::BPPF_ALL_CORNERS, //0x0F,
BridgePiecePillarFlag::BPPF_ALL_CORNERS, //0x0F,
BridgePiecePillarFlag::BPPF_ALL_CORNERS, //0x0F,
BridgePiecePillarFlag::BPPF_ALL_CORNERS, //0x0F,
BridgePiecePillarFlag::BPPF_ALL_CORNERS, //0x0F
};
static constexpr std::array<BridgePiecePillarFlags, 12> susp_pillars = {
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_W, BridgePiecePillarFlag::BPPF_CORNER_S}), //0x03,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_S, BridgePiecePillarFlag::BPPF_CORNER_E}), //0x06,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_E, BridgePiecePillarFlag::BPPF_CORNER_N}), //0x0C,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_W, BridgePiecePillarFlag::BPPF_CORNER_N}), //0x09,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_E, BridgePiecePillarFlag::BPPF_CORNER_N}), //0x0C,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_W, BridgePiecePillarFlag::BPPF_CORNER_N}), //0x09,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_W, BridgePiecePillarFlag::BPPF_CORNER_S}), //0x03,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_S, BridgePiecePillarFlag::BPPF_CORNER_E}), //0x06,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_ALL_CORNERS}), //0x0F,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_ALL_CORNERS}), //0x0F,
BridgePiecePillarFlags({BridgePiecePillarFlag()}), //0x00,
BridgePiecePillarFlags({BridgePiecePillarFlag()}), //0x00
};
static constexpr std::array<BridgePiecePillarFlags, 12> cant_pillars = {
BridgePiecePillarFlags({BridgePiecePillarFlag()}), //0x00,
BridgePiecePillarFlags({BridgePiecePillarFlag()}), //0x00,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_E, BridgePiecePillarFlag::BPPF_CORNER_N}), //0x0C,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_W, BridgePiecePillarFlag::BPPF_CORNER_N}), //0x09,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_E, BridgePiecePillarFlag::BPPF_CORNER_N}), //0x0C,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_W, BridgePiecePillarFlag::BPPF_CORNER_N}), //0x09,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_E, BridgePiecePillarFlag::BPPF_CORNER_N}), //0x0C,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_W, BridgePiecePillarFlag::BPPF_CORNER_N}), //0x09,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_E, BridgePiecePillarFlag::BPPF_CORNER_N}), //0x0C,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_W, BridgePiecePillarFlag::BPPF_CORNER_N}), //0x09,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_E, BridgePiecePillarFlag::BPPF_CORNER_N}), //0x0C,
BridgePiecePillarFlags({BridgePiecePillarFlag::BPPF_CORNER_W, BridgePiecePillarFlag::BPPF_CORNER_N}), //0x09
};
const BridgeSpec _orig_bridge[] = { const BridgeSpec _orig_bridge[] = {
/* /*
@ -755,43 +800,43 @@ const BridgeSpec _orig_bridge[] = {
string with description name on rail name on road string with description name on rail name on road
| | | | */ | | | | */
MBR( 0, 0, 0xFFFF, 80, 32, 0xA24, PAL_NONE, MBR( 0, 0, 0xFFFF, 80, 32, 0xA24, PAL_NONE,
STR_BRIDGE_NAME_WOODEN, STR_LAI_BRIDGE_DESCRIPTION_RAIL_WOODEN, STR_LAI_BRIDGE_DESCRIPTION_ROAD_WOODEN), STR_BRIDGE_NAME_WOODEN, STR_LAI_BRIDGE_DESCRIPTION_RAIL_WOODEN, STR_LAI_BRIDGE_DESCRIPTION_ROAD_WOODEN, all_pillars),
MBR( 0, 0, 2, 112, 48, 0xA26, PALETTE_TO_STRUCT_RED, MBR( 0, 0, 2, 112, 48, 0xA26, PALETTE_TO_STRUCT_RED,
STR_BRIDGE_NAME_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CONCRETE), STR_BRIDGE_NAME_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CONCRETE, all_pillars),
MBR(1930, 0, 5, 144, 64, 0xA25, PAL_NONE, MBR(1930, 0, 5, 144, 64, 0xA25, PAL_NONE,
STR_BRIDGE_NAME_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_GIRDER_STEEL), STR_BRIDGE_NAME_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_GIRDER_STEEL, all_pillars),
MBR( 0, 2, 10, 168, 80, 0xA22, PALETTE_TO_STRUCT_CONCRETE, MBR( 0, 2, 10, 168, 80, 0xA22, PALETTE_TO_STRUCT_CONCRETE,
STR_BRIDGE_NAME_SUSPENSION_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_ROAD_SUSPENSION_CONCRETE), STR_BRIDGE_NAME_SUSPENSION_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_CONCRETE, STR_LAI_BRIDGE_DESCRIPTION_ROAD_SUSPENSION_CONCRETE, susp_pillars),
MBR(1930, 3, 0xFFFF, 185, 96, 0xA22, PAL_NONE, MBR(1930, 3, 0xFFFF, 185, 96, 0xA22, PAL_NONE,
STR_BRIDGE_NAME_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_SUSPENSION_STEEL), STR_BRIDGE_NAME_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_SUSPENSION_STEEL, susp_pillars),
MBR(1930, 3, 0xFFFF, 192, 112, 0xA22, PALETTE_TO_STRUCT_YELLOW, MBR(1930, 3, 0xFFFF, 192, 112, 0xA22, PALETTE_TO_STRUCT_YELLOW,
STR_BRIDGE_NAME_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_SUSPENSION_STEEL), STR_BRIDGE_NAME_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_SUSPENSION_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_SUSPENSION_STEEL, susp_pillars),
MBR(1930, 3, 7, 224, 160, 0xA23, PAL_NONE, MBR(1930, 3, 7, 224, 160, 0xA23, PAL_NONE,
STR_BRIDGE_NAME_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CANTILEVER_STEEL), STR_BRIDGE_NAME_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CANTILEVER_STEEL, cant_pillars),
MBR(1930, 3, 8, 232, 208, 0xA23, PALETTE_TO_STRUCT_BROWN, MBR(1930, 3, 8, 232, 208, 0xA23, PALETTE_TO_STRUCT_BROWN,
STR_BRIDGE_NAME_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CANTILEVER_STEEL), STR_BRIDGE_NAME_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CANTILEVER_STEEL, cant_pillars),
MBR(1930, 3, 9, 248, 240, 0xA23, PALETTE_TO_STRUCT_RED, MBR(1930, 3, 9, 248, 240, 0xA23, PALETTE_TO_STRUCT_RED,
STR_BRIDGE_NAME_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CANTILEVER_STEEL), STR_BRIDGE_NAME_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_CANTILEVER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_CANTILEVER_STEEL, cant_pillars),
MBR(1930, 0, 2, 240, 256, 0xA27, PAL_NONE, MBR(1930, 0, 2, 240, 256, 0xA27, PAL_NONE,
STR_BRIDGE_NAME_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_GIRDER_STEEL), STR_BRIDGE_NAME_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_GIRDER_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_GIRDER_STEEL, all_pillars),
MBR(1995, 2, 0xFFFF, 255, 320, 0xA28, PAL_NONE, MBR(1995, 2, 0xFFFF, 255, 320, 0xA28, PAL_NONE,
STR_BRIDGE_NAME_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_TUBULAR_STEEL), STR_BRIDGE_NAME_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_TUBULAR_STEEL, cant_pillars),
MBR(2005, 2, 0xFFFF, 380, 512, 0xA28, PALETTE_TO_STRUCT_YELLOW, MBR(2005, 2, 0xFFFF, 380, 512, 0xA28, PALETTE_TO_STRUCT_YELLOW,
STR_BRIDGE_NAME_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_TUBULAR_STEEL), STR_BRIDGE_NAME_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_RAIL_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_TUBULAR_STEEL, cant_pillars),
MBR(2010, 2, 0xFFFF, 510, 608, 0xA28, PALETTE_TO_STRUCT_CONCRETE, MBR(2010, 2, 0xFFFF, 510, 608, 0xA28, PALETTE_TO_STRUCT_CONCRETE,
STR_BRIDGE_TUBULAR_SILICON, STR_LAI_BRIDGE_DESCRIPTION_RAIL_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_TUBULAR_STEEL) STR_BRIDGE_TUBULAR_SILICON, STR_LAI_BRIDGE_DESCRIPTION_RAIL_TUBULAR_STEEL, STR_LAI_BRIDGE_DESCRIPTION_ROAD_TUBULAR_STEEL, cant_pillars)
}; };
#undef MBR #undef MBR

View File

@ -37,6 +37,7 @@ TimerGameEconomy::Year TimerGameEconomy::year = {};
TimerGameEconomy::Month TimerGameEconomy::month = {}; TimerGameEconomy::Month TimerGameEconomy::month = {};
TimerGameEconomy::Date TimerGameEconomy::date = {}; TimerGameEconomy::Date TimerGameEconomy::date = {};
TimerGameEconomy::DateFract TimerGameEconomy::date_fract = {}; TimerGameEconomy::DateFract TimerGameEconomy::date_fract = {};
uint TimerGameEconomy::days_since_last_month = {};
/** /**
* Converts a Date to a Year, Month & Day. * Converts a Date to a Year, Month & Day.
@ -133,6 +134,7 @@ bool TimerManager<TimerGameEconomy>::Elapsed([[maybe_unused]] TimerGameEconomy::
/* increase day counter */ /* increase day counter */
TimerGameEconomy::date++; TimerGameEconomy::date++;
++TimerGameEconomy::days_since_last_month;
TimerGameEconomy::YearMonthDay ymd = TimerGameEconomy::ConvertDateToYMD(TimerGameEconomy::date); 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 */ /* check if we reached the maximum year, decrement dates by a year */
if (TimerGameEconomy::year == EconomyTime::MAX_YEAR + 1) { if (TimerGameEconomy::year == EconomyTime::MAX_YEAR + 1) {
TimerGameEconomy::year--; TimerGameEconomy::year--;

View File

@ -37,6 +37,8 @@ public:
static Date date; ///< Current date in days (day counter). static Date date; ///< Current date in days (day counter).
static DateFract date_fract; ///< Fractional part of the day. 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 YearMonthDay ConvertDateToYMD(Date date);
static Date ConvertYMDToDate(Year year, Month month, Day day); static Date ConvertYMDToDate(Year year, Month month, Day day);
static void SetDate(Date date, DateFract fract); static void SetDate(Date date, DateFract fract);

View File

@ -39,10 +39,13 @@
#include "object_base.h" #include "object_base.h"
#include "water.h" #include "water.h"
#include "company_gui.h" #include "company_gui.h"
#include "newgrf_roadstop.h"
#include "station_func.h" #include "station_func.h"
#include "station_map.h"
#include "tunnelbridge_cmd.h" #include "tunnelbridge_cmd.h"
#include "landscape_cmd.h" #include "landscape_cmd.h"
#include "terraform_cmd.h" #include "terraform_cmd.h"
#include "newgrf_station.h"
#include "table/strings.h" #include "table/strings.h"
#include "table/bridge_land.h" #include "table/bridge_land.h"
@ -55,6 +58,12 @@ TileIndex _build_tunnel_endtile; ///< The end of a tunnel; as hidden return from
/** Z position of the bridge sprites relative to bridge height (downwards) */ /** Z position of the bridge sprites relative to bridge height (downwards) */
static const int BRIDGE_Z_START = 3; static const int BRIDGE_Z_START = 3;
extern CommandCost IsRailStationBridgeAboveOk(TileIndex tile, const StationSpec *statspec, uint8_t layout, TileIndex northern_bridge_end, TileIndex southern_bridge_end, int bridge_height,
BridgeType bridge_type, TransportType bridge_transport_type);
extern CommandCost IsRoadStopBridgeAboveOK(TileIndex tile, const RoadStopSpec *spec, bool drive_through, DiagDirection entrance,
TileIndex northern_bridge_end, TileIndex southern_bridge_end, int bridge_height,
BridgeType bridge_type, TransportType bridge_transport_type);
/** /**
* Mark bridge tiles dirty. * Mark bridge tiles dirty.
@ -391,6 +400,43 @@ CommandCost CmdBuildBridge(DoCommandFlags flags, TileIndex tile_end, TileIndex t
/* If bridge belonged to bankrupt company, it has a new owner now */ /* If bridge belonged to bankrupt company, it has a new owner now */
is_new_owner = (owner == OWNER_NONE); is_new_owner = (owner == OWNER_NONE);
if (is_new_owner) owner = company; if (is_new_owner) owner = company;
TileIndexDiff delta = (direction == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1));
for (TileIndex tile = tile_start + delta; tile != tile_end; tile += delta) {
if (!IsTileType(tile, MP_STATION)) continue;
switch (GetStationType(tile)) {
case StationType::Rail:
case StationType::RailWaypoint: {
CommandCost ret = IsRailStationBridgeAboveOk(tile, GetStationSpec(tile), GetStationGfx(tile), tile_start, tile_end, z_start + 1, bridge_type, transport_type);
if (ret.Failed()) {
if (ret.GetErrorMessage() != INVALID_STRING_ID) return ret;
ret = Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
if (ret.Failed()) return ret;
}
break;
}
case StationType::Bus:
case StationType::Truck:
case StationType::RoadWaypoint: {
CommandCost ret = IsRoadStopBridgeAboveOK(tile, GetRoadStopSpec(tile), IsDriveThroughStopTile(tile), IsDriveThroughStopTile(tile) ? AxisToDiagDir(GetDriveThroughStopAxis(tile)) : GetBayRoadStopDir(tile),
tile_start, tile_end, z_start + 1, bridge_type, transport_type);
if (ret.Failed()) {
if (ret.GetErrorMessage() != INVALID_STRING_ID) return ret;
ret = Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
if (ret.Failed()) return ret;
}
break;
}
case StationType::Buoy:
/* Buoys are always allowed */
break;
default:
break;
}
}
} else { } else {
/* Build a new bridge. */ /* Build a new bridge. */
@ -467,6 +513,45 @@ CommandCost CmdBuildBridge(DoCommandFlags flags, TileIndex tile_end, TileIndex t
break; break;
} }
case MP_STATION: {
switch (GetStationType(tile)) {
case StationType::Airport:
goto not_valid_below;
case StationType::Rail:
case StationType::RailWaypoint: {
CommandCost ret = IsRailStationBridgeAboveOk(tile, GetStationSpec(tile), GetStationGfx(tile), tile_start, tile_end, z_start + 1, bridge_type, transport_type);
if (ret.Failed()) {
if (ret.GetErrorMessage() != INVALID_STRING_ID) return ret;
goto not_valid_below;
}
break;
}
case StationType::Bus:
case StationType::Truck:
case StationType::RoadWaypoint: {
CommandCost ret = IsRoadStopBridgeAboveOK(tile, GetRoadStopSpec(tile), IsDriveThroughStopTile(tile), IsDriveThroughStopTile(tile) ? AxisToDiagDir(GetDriveThroughStopAxis(tile)) : GetBayRoadStopDir(tile),
tile_start, tile_end, z_start + 1, bridge_type, transport_type);
if (ret.Failed()) {
if (ret.GetErrorMessage() != INVALID_STRING_ID) return ret;
goto not_valid_below;
}
break;
}
case StationType::Buoy:
/* Buoys are always allowed */
break;
default:
//if (!(GetStationType(tile) == StationType::Dock && _settings_game.construction.allow_docks_under_bridges)) goto not_valid_below;
break;
}
break;
}
case MP_CLEAR: case MP_CLEAR:
break; break;
@ -1517,6 +1602,49 @@ static BridgePieces CalcBridgePiece(uint north, uint south)
} }
} }
BridgePiecePillarFlags GetBridgeTilePillarFlags(TileIndex tile, TileIndex northern_bridge_end, TileIndex southern_bridge_end, BridgeType bridge_type, TransportType bridge_transport_type)
{
if (bridge_transport_type == TRANSPORT_WATER) return BridgePiecePillarFlag::BPPF_ALL_CORNERS;
BridgePieces piece = CalcBridgePiece(
GetTunnelBridgeLength(tile, northern_bridge_end) + 1,
GetTunnelBridgeLength(tile, southern_bridge_end) + 1
);
assert(piece < BRIDGE_PIECE_HEAD);
const BridgeSpec *spec = GetBridgeSpec(bridge_type);
const Axis axis = TileX(northern_bridge_end) == TileX(southern_bridge_end) ? AXIS_Y : AXIS_X;
if (!spec->ctrl_flags.Test(BridgeSpecCtrlFlag::BSCF_INVALID_PILLAR_FLAGS)) {
return spec->pillar_flags[piece * 2 + (axis == AXIS_Y ? 1 : 0)];
} else {
uint base_offset;
if (bridge_transport_type == TRANSPORT_RAIL) {
base_offset = GetRailTypeInfo(GetRailType(southern_bridge_end))->bridge_offset;
} else {
base_offset = 8;
}
const PalSpriteID *psid = &GetBridgeSpriteTable(bridge_type, piece)[base_offset];
if (axis == AXIS_Y) psid += 4;
return psid[2].sprite != 0 ? BridgePiecePillarFlag::BPPF_ALL_CORNERS : BridgePiecePillarFlags();
}
}
BridgePieceDebugInfo GetBridgePieceDebugInfo(TileIndex tile)
{
TileIndex rampnorth = GetNorthernBridgeEnd(tile);
TileIndex rampsouth = GetSouthernBridgeEnd(tile);
BridgePieces piece = CalcBridgePiece(
GetTunnelBridgeLength(tile, rampnorth) + 1,
GetTunnelBridgeLength(tile, rampsouth) + 1
);
BridgePiecePillarFlags pillar_flags = GetBridgeTilePillarFlags(tile, rampnorth, rampsouth, GetBridgeType(rampnorth), GetTunnelBridgeTransportType(rampnorth));
const Axis axis = TileX(rampnorth) == TileX(rampsouth) ? AXIS_Y : AXIS_X;
uint pillar_index = piece * 2 + (axis == AXIS_Y ? 1 : 0);
return { piece, pillar_flags, pillar_index };
}
/** /**
* Draw the middle bits of a bridge. * Draw the middle bits of a bridge.
* @param ti Tile information of the tile to draw it on. * @param ti Tile information of the tile to draw it on.

View File

@ -182,7 +182,9 @@ static CommandCost IsValidTileForWaypoint(TileIndex tile, Axis axis, StationID *
extern void GetStationLayout(uint8_t *layout, uint numtracks, uint plat_len, const StationSpec *statspec); extern void GetStationLayout(uint8_t *layout, uint numtracks, uint plat_len, const StationSpec *statspec);
extern CommandCost FindJoiningWaypoint(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Waypoint **wp, bool is_road); extern CommandCost FindJoiningWaypoint(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Waypoint **wp, bool is_road);
extern CommandCost CanExpandRailStation(const BaseStation *st, TileArea &new_ta); extern CommandCost CanExpandRailStation(const BaseStation *st, TileArea &new_ta);
extern CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlags flags, bool is_drive_through, StationType station_type, Axis axis, DiagDirection ddir, StationID *est, RoadType rt, Money unit_cost); extern CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlags flags, bool is_drive_through, StationType station_type, const RoadStopSpec *roadstopspec, Axis axis, DiagDirection ddir, StationID *est, RoadType rt, Money unit_cost);
extern CommandCost IsRailStationBridgeAboveOk(TileIndex tile, const StationSpec *statspec, uint8_t layout);
extern CommandCost RemoveRoadWaypointStop(TileIndex tile, DoCommandFlags flags, int replacement_spec_index); extern CommandCost RemoveRoadWaypointStop(TileIndex tile, DoCommandFlags flags, int replacement_spec_index);
/** /**
@ -220,6 +222,17 @@ CommandCost CmdBuildRailWaypoint(DoCommandFlags flags, TileIndex start_tile, Axi
if (distant_join && (!_settings_game.station.distant_join_stations || !Waypoint::IsValidID(station_to_join))) return CMD_ERROR; if (distant_join && (!_settings_game.station.distant_join_stations || !Waypoint::IsValidID(station_to_join))) return CMD_ERROR;
const StationSpec *spec = StationClass::Get(spec_class)->GetSpec(spec_index);
std::vector<uint8_t> layout_ptr;
layout_ptr.resize(count);
if (spec == nullptr) {
/* The layout must be 0 for the 'normal' waypoints by design. */
//memset(layout_ptr, 0, count); //FIXME
} else {
/* But for NewGRF waypoints we like to have their style. */
GetStationLayout(&layout_ptr[0], count, 1, spec);
}
TileArea new_location(start_tile, width, height); TileArea new_location(start_tile, width, height);
/* only AddCost for non-existing waypoints */ /* only AddCost for non-existing waypoints */
@ -237,6 +250,10 @@ CommandCost CmdBuildRailWaypoint(DoCommandFlags flags, TileIndex start_tile, Axi
TileIndex tile = start_tile + i * offset; TileIndex tile = start_tile + i * offset;
CommandCost ret = IsValidTileForWaypoint(tile, axis, &est); CommandCost ret = IsValidTileForWaypoint(tile, axis, &est);
if (ret.Failed()) return ret; if (ret.Failed()) return ret;
ret = IsRailStationBridgeAboveOk(tile, spec, layout_ptr[i]);
if (ret.Failed()) {
return ret;
}
} }
Waypoint *wp = nullptr; Waypoint *wp = nullptr;
@ -364,7 +381,7 @@ CommandCost CmdBuildRoadWaypoint(DoCommandFlags flags, TileIndex start_tile, Axi
unit_cost = _price[PR_BUILD_STATION_TRUCK]; unit_cost = _price[PR_BUILD_STATION_TRUCK];
} }
StationID est = StationID::Invalid(); StationID est = StationID::Invalid();
CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, true, StationType::RoadWaypoint, axis, AxisToDiagDir(axis), &est, INVALID_ROADTYPE, unit_cost); CommandCost cost = CalculateRoadStopCost(roadstop_area, flags, true, StationType::RoadWaypoint, roadstopspec, axis, AxisToDiagDir(axis), &est, INVALID_ROADTYPE, unit_cost);
if (cost.Failed()) return cost; if (cost.Failed()) return cost;
Waypoint *wp = nullptr; Waypoint *wp = nullptr;