1
0
mirror of https://github.com/OpenTTD/OpenTTD.git synced 2025-08-16 11:09:11 +00:00

Compare commits

...

8 Commits

Author SHA1 Message Date
345bd52af4 Merge 065f88eec8 into 557fb0bd78 2025-07-30 00:15:16 +00:00
557fb0bd78 Change: Add support for different horizontal graph scales. 2025-07-30 01:13:05 +01:00
d09dfd843c Codechange: Extend industry cargo history to 24 years.
Monthly data is stored for the current 24 months.
Quarterly data is stored for a further 2-6 years.
Yearly data is stored for a further 6-24 years.
2025-07-30 01:13:05 +01:00
Susan
065f88eec8 Feature: Allow stations under bridges.
Bridges above stations will have pillars excluded if they conflict with the station layout.

Based on the system implemented in JGRPP.

Co-authored-by: <peter1138@openttd.org>
2025-07-28 01:18:34 +01:00
00631729a4 Codechange: Add bridge pillar exclusion information to stations and roadstops. 2025-07-28 01:18:34 +01:00
9f29f9fa56 Codechange: Add extra information to bridges about pillars. 2025-07-28 01:18:33 +01:00
adc221f0d4 Codechange: Replace station TileFlags with a TileSpec struct.
This allows storing more per-layout-tile data.
2025-07-28 01:18:33 +01:00
cdadfd8e83 Codechange: Implement tile proc handler to test for building bridge over tile. 2025-07-28 01:18:32 +01:00
33 changed files with 880 additions and 177 deletions

View File

@@ -91,6 +91,7 @@ add_files(
bridge_gui.cpp
bridge_map.cpp
bridge_map.h
bridge_type.h
build_vehicle_gui.cpp
cachecheck.cpp
cargo_type.h

View File

@@ -13,29 +13,16 @@
#include "gfx_type.h"
#include "tile_cmd.h"
#include "timer/timer_game_calendar.h"
/**
* This enum is related to the definition of bridge pieces,
* which is used to determine the proper sprite table to use
* while drawing a given bridge part.
*/
enum BridgePieces : uint8_t {
BRIDGE_PIECE_NORTH = 0,
BRIDGE_PIECE_SOUTH,
BRIDGE_PIECE_INNER_NORTH,
BRIDGE_PIECE_INNER_SOUTH,
BRIDGE_PIECE_MIDDLE_ODD,
BRIDGE_PIECE_MIDDLE_EVEN,
BRIDGE_PIECE_HEAD,
NUM_BRIDGE_PIECES,
};
DECLARE_INCREMENT_DECREMENT_OPERATORS(BridgePieces)
#include "bridge_type.h"
static const uint MAX_BRIDGES = 13; ///< Maximal number of available bridge specs.
constexpr uint SPRITES_PER_BRIDGE_PIECE = 32; ///< Number of sprites there are per bridge piece.
typedef uint BridgeType; ///< Bridge spec number.
enum class BridgeSpecCtrlFlag : uint8_t{
CustomPillarFlags,
InvalidPillarFlags,
};
using BridgeSpecCtrlFlags = EnumBitSet<BridgeSpecCtrlFlag, uint8_t>;
/**
* Struct containing information about a single bridge type
@@ -52,6 +39,8 @@ struct BridgeSpec {
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
uint8_t flags; ///< bit 0 set: disable drawing of far pillars.
BridgeSpecCtrlFlags ctrl_flags{}; ///< control flags
std::array<std::array<BridgePillarFlags, AXIS_END>, NUM_BRIDGE_PIECES - 1> pillar_flags{}; ///< bridge pillar flags, 6 for each axis.
};
extern BridgeSpec _bridge[MAX_BRIDGES];
@@ -70,11 +59,13 @@ inline const BridgeSpec *GetBridgeSpec(BridgeType i)
return &_bridge[i];
}
void DrawBridgeMiddle(const TileInfo *ti);
void DrawBridgeMiddle(const TileInfo *ti, BridgePillarFlags blocked_pillars);
CommandCost CheckBridgeAvailability(BridgeType bridge_type, uint bridge_len, DoCommandFlags flags = {});
int CalcBridgeLenCostFactor(int x);
BridgePillarFlags GetBridgeTilePillarFlags(TileIndex tile, TileIndex rampnorth, TileIndex rampsouth, BridgeType type, TransportType transport_type);
void ResetBridges();
#endif /* BRIDGE_H */

View File

@@ -383,9 +383,9 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo
CommandCost ret = Command<CMD_BUILD_BRIDGE>::Do(CommandFlagsToDCFlags(GetCommandFlags<CMD_BUILD_BRIDGE>()) | DoCommandFlag::QueryCost, end, start, transport_type, 0, road_rail_type);
GUIBridgeList bl;
if (ret.Failed()) {
errmsg = ret.GetErrorMessage();
} else {
if (ret.Failed()) errmsg = ret.GetErrorMessage();
bool query_per_bridge_type = errmsg == STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION;
if (ret.Succeeded() || query_per_bridge_type) {
/* check which bridges can be built */
const uint tot_bridgedata_len = CalcBridgeLenCostFactor(bridge_len + 2);
@@ -415,11 +415,13 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo
}
bool any_available = false;
StringID type_errmsg = INVALID_STRING_ID;
CommandCost type_check;
/* loop for all bridgetypes */
for (BridgeType brd_type = 0; brd_type != MAX_BRIDGES; brd_type++) {
type_check = CheckBridgeAvailability(brd_type, bridge_len);
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 */
BuildBridgeData &item = bl.emplace_back();
item.index = brd_type;
@@ -428,13 +430,12 @@ void ShowBuildBridgeWindow(TileIndex start, TileIndex end, TransportType transpo
* 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;
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*/
if (!any_available)
{
errmsg = type_check.GetErrorMessage();
}
if (!any_available && type_errmsg != INVALID_STRING_ID) errmsg = type_errmsg;
}
if (!bl.empty()) {

52
src/bridge_type.h Normal file
View File

@@ -0,0 +1,52 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file bridge_type.h Header file for bridge types. */
#ifndef BRIDGE_TYPE_H
#define BRIDGE_TYPE_H
#include "core/enum_type.hpp"
using BridgeType = uint; ///< Bridge spec number.
/**
* This enum is related to the definition of bridge pieces,
* which is used to determine the proper sprite table to use
* while drawing a given bridge part.
*/
enum BridgePieces : uint8_t {
BRIDGE_PIECE_NORTH = 0,
BRIDGE_PIECE_SOUTH,
BRIDGE_PIECE_INNER_NORTH,
BRIDGE_PIECE_INNER_SOUTH,
BRIDGE_PIECE_MIDDLE_ODD,
BRIDGE_PIECE_MIDDLE_EVEN,
BRIDGE_PIECE_HEAD,
NUM_BRIDGE_PIECES,
};
DECLARE_INCREMENT_DECREMENT_OPERATORS(BridgePieces)
enum class BridgePillarFlag : uint8_t {
CornerW = 0,
CornerS = 1,
CornerE = 2,
CornerN = 3,
EdgeNE = 4,
EdgeSE = 5,
EdgeSW = 6,
EdgeNW = 7,
};
using BridgePillarFlags = EnumBitSet<BridgePillarFlag, uint8_t>;
static constexpr BridgePillarFlags BRIDGEPILLARFLAGS_ALL{
BridgePillarFlag::CornerW, BridgePillarFlag::CornerS, BridgePillarFlag::CornerE, BridgePillarFlag::CornerN,
BridgePillarFlag::EdgeNE, BridgePillarFlag::EdgeSE, BridgePillarFlag::EdgeSW, BridgePillarFlag::EdgeNW,
};
#endif /* BRIDGE_TYPE_H */

View File

@@ -152,7 +152,7 @@ static void DrawTile_Clear(TileInfo *ti)
break;
}
DrawBridgeMiddle(ti);
DrawBridgeMiddle(ti, {});
}
static int GetSlopePixelZ_Clear(TileIndex tile, uint x, uint y, bool)
@@ -390,6 +390,11 @@ static CommandCost TerraformTile_Clear(TileIndex tile, DoCommandFlags flags, int
return Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
}
static CommandCost CheckBuildAbove_Clear(TileIndex, DoCommandFlags, Axis, int)
{
return CommandCost();
}
extern const TileTypeProcs _tile_type_clear_procs = {
DrawTile_Clear, ///< draw_tile_proc
GetSlopePixelZ_Clear, ///< get_slope_z_proc
@@ -405,4 +410,5 @@ extern const TileTypeProcs _tile_type_clear_procs = {
nullptr, ///< vehicle_enter_tile_proc
GetFoundation_Clear, ///< get_foundation_proc
TerraformTile_Clear, ///< terraform_tile_proc
CheckBuildAbove_Clear, // check_build_above_proc
};

View File

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

View File

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

View File

@@ -622,6 +622,14 @@ STR_GRAPH_COMPANY_VALUES_CAPTION :{WHITE}Company
STR_GRAPH_LAST_24_MINUTES_TIME_LABEL :{TINY_FONT}{BLACK}Last 24 minutes
STR_GRAPH_LAST_72_MINUTES_TIME_LABEL :{TINY_FONT}{BLACK}Last 72 minutes
STR_GRAPH_LAST_288_MINUTES_TIME_LABEL :{TINY_FONT}{BLACK}Last 288 minutes
STR_GRAPH_LAST_24_MONTHS :{TINY_FONT}{BLACK}2 years (monthly)
STR_GRAPH_LAST_24_QUARTERS :{TINY_FONT}{BLACK}6 years (quarterly)
STR_GRAPH_LAST_24_YEARS :{TINY_FONT}{BLACK}24 years (yearly)
STR_GRAPH_TOGGLE_RANGE :Toggle graph for this data range
STR_GRAPH_SELECT_SCALE :Change horizontal scale of graph
STR_GRAPH_CARGO_PAYMENT_RATES_CAPTION :{WHITE}Cargo Payment Rates
STR_GRAPH_CARGO_PAYMENT_RATES_DAYS :{TINY_FONT}{BLACK}Days in transit
@@ -5244,6 +5252,7 @@ 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_BRIDGE_TOO_LONG :{WHITE}... bridge too long
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
# Tunnel related errors
STR_ERROR_CAN_T_BUILD_TUNNEL_HERE :{WHITE}Can't build tunnel here...

View File

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

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

@@ -0,0 +1,65 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file history.cpp Implementation of functions for storing historical data. */
#include "../stdafx.h"
#include "../core/bitmath_func.hpp"
#include "history_type.hpp"
#include "history_func.hpp"
#include "../safeguards.h"
/**
* Update mask of valid records for a historical data.
* @note Call only for the largest history range sub-division.
* @param[in,out] valid_history Valid history records.
* @param hr History range to update mask for.
* @param cur_month Current economy month.
*/
void UpdateValidHistory(ValidHistoryMask &valid_history, const HistoryRange &hr, uint cur_month)
{
/* Update for subdivisions first. */
if (hr.hr != nullptr) UpdateValidHistory(valid_history, *hr.hr, cur_month);
/* No need to update if our last entry is marked valid. */
if (HasBit(valid_history, hr.last - 1)) return;
/* Is it the right time for this history range? */
if (cur_month % hr.total_division != 0) return;
/* Is the previous history range valid yet? */
if (hr.division != 1 && !HasBit(valid_history, hr.first - hr.division)) return;
SB(valid_history, hr.first, hr.records, GB(valid_history, hr.first, hr.records) << 1ULL | 1ULL);
}
/**
* Test if history data is valid, without extracting data.
* @param valid_history Mask of valid history records.
* @param hr History range to test.
* @param age Age of data to test.
* @return True iff the data for history range and age is valid.
*/
bool IsValidHistory(ValidHistoryMask valid_history, const HistoryRange &hr, uint age)
{
if (hr.hr == nullptr) {
if (age < hr.periods) {
uint slot = hr.first + age;
return HasBit(valid_history, slot);
}
} else {
if (age * hr.division < static_cast<uint>(hr.hr->periods - hr.division)) {
uint start = age * hr.division + ((TimerGameEconomy::month / hr.hr->division) % hr.division);
return IsValidHistory(valid_history, *hr.hr, start);
}
if (age < hr.periods) {
uint slot = hr.first + age - ((hr.hr->periods / hr.division) - 1);
return HasBit(valid_history, slot);
}
}
return false;
}

View File

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

View File

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

View File

@@ -93,6 +93,7 @@ static ChangeInfoResult BridgeChangeInfo(uint first, uint last, int prop, ByteRe
MapSpriteMappingRecolour(&bridge->sprite_table[tableid][sprite]);
}
}
if (!bridge->ctrl_flags.Test(BridgeSpecCtrlFlag::CustomPillarFlags)) bridge->ctrl_flags.Set(BridgeSpecCtrlFlag::InvalidPillarFlags);
break;
}
@@ -120,6 +121,20 @@ static ChangeInfoResult BridgeChangeInfo(uint first, uint last, int prop, ByteRe
bridge->price = buf.ReadWord();
break;
case 0x14:
buf.ReadWord();
buf.ReadWord();
break;
case 0x15: // Pillars
for (uint j = 0; j != 6; ++j) {
bridge->pillar_flags[j][AXIS_X] = BridgePillarFlags{buf.ReadByte()};
bridge->pillar_flags[j][AXIS_Y] = BridgePillarFlags{buf.ReadByte()};
}
bridge->ctrl_flags.Reset(BridgeSpecCtrlFlag::InvalidPillarFlags);
bridge->ctrl_flags.Set(BridgeSpecCtrlFlag::CustomPillarFlags);
break;
default:
ret = CIR_UNKNOWN;
break;

View File

@@ -49,6 +49,13 @@ static ChangeInfoResult IgnoreRoadStopProperty(uint prop, ByteReader &buf)
buf.ReadDWord();
break;
case 0x13:
case 0x14:
buf.ReadWord();
buf.ReadWord();
buf.ReadWord();
break;
case 0x16: // Badge list
SkipBadgeList(buf);
break;
@@ -134,6 +141,18 @@ static ChangeInfoResult RoadStopChangeInfo(uint first, uint last, int prop, Byte
rs->flags = static_cast<RoadStopSpecFlags>(buf.ReadDWord()); // Future-proofing, size this as 4 bytes, but we only need two byte's worth of flags at present
break;
case 0x13: // Bridge height.
for (uint j = 0; j != 6; ++j) {
rs->tilespecs[j].height = buf.ReadByte();
}
break;
case 0x14: // Disallow pillars.
for (uint j = 0; j != 6; ++j) {
rs->tilespecs[j].disallowed_pillars = BridgePillarFlags{buf.ReadByte()};
}
break;
case 0x15: // Cost multipliers
rs->build_cost_multiplier = buf.ReadByte();
rs->clear_cost_multiplier = buf.ReadByte();

View File

@@ -184,12 +184,12 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR
case 0x11: { // Pylon placement
uint8_t pylons = buf.ReadByte();
if (statspec->tileflags.size() < 8) statspec->tileflags.resize(8);
if (statspec->tilespecs.size() < 8) statspec->tilespecs.resize(8);
for (int j = 0; j < 8; ++j) {
if (HasBit(pylons, j)) {
statspec->tileflags[j].Set(StationSpec::TileFlag::Pylons);
statspec->tilespecs[j].flags.Set(StationSpec::TileFlag::Pylons);
} else {
statspec->tileflags[j].Reset(StationSpec::TileFlag::Pylons);
statspec->tilespecs[j].flags.Reset(StationSpec::TileFlag::Pylons);
}
}
break;
@@ -209,12 +209,12 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR
case 0x14: { // Overhead wire placement
uint8_t wires = buf.ReadByte();
if (statspec->tileflags.size() < 8) statspec->tileflags.resize(8);
if (statspec->tilespecs.size() < 8) statspec->tilespecs.resize(8);
for (int j = 0; j < 8; ++j) {
if (HasBit(wires, j)) {
statspec->tileflags[j].Set(StationSpec::TileFlag::NoWires);
statspec->tilespecs[j].flags.Set(StationSpec::TileFlag::NoWires);
} else {
statspec->tileflags[j].Reset(StationSpec::TileFlag::NoWires);
statspec->tilespecs[j].flags.Reset(StationSpec::TileFlag::NoWires);
}
}
break;
@@ -222,12 +222,12 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR
case 0x15: { // Blocked tiles
uint8_t blocked = buf.ReadByte();
if (statspec->tileflags.size() < 8) statspec->tileflags.resize(8);
if (statspec->tilespecs.size() < 8) statspec->tilespecs.resize(8);
for (int j = 0; j < 8; ++j) {
if (HasBit(blocked, j)) {
statspec->tileflags[j].Set(StationSpec::TileFlag::Blocked);
statspec->tilespecs[j].flags.Set(StationSpec::TileFlag::Blocked);
} else {
statspec->tileflags[j].Reset(StationSpec::TileFlag::Blocked);
statspec->tilespecs[j].flags.Reset(StationSpec::TileFlag::Blocked);
}
}
break;
@@ -268,11 +268,11 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR
break;
}
case 0x1B: // Minimum bridge height (not implemented)
buf.ReadWord();
buf.ReadWord();
buf.ReadWord();
buf.ReadWord();
case 0x1B: // Minimum bridge height (old variable)
if (statspec->tilespecs.size() < 8) statspec->tilespecs.resize(8);
for (int j = 0; j < 8; ++j) {
statspec->tilespecs[j].height = buf.ReadByte();
}
break;
case 0x1C: // Station Name
@@ -285,8 +285,10 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR
case 0x1E: { // Extended tile flags (replaces prop 11, 14 and 15)
uint16_t tiles = buf.ReadExtendedByte();
auto flags = reinterpret_cast<const StationSpec::TileFlags *>(buf.ReadBytes(tiles));
statspec->tileflags.assign(flags, flags + tiles);
if (statspec->tilespecs.size() < tiles) statspec->tilespecs.resize(tiles);
for (uint j = 0; j != tiles; ++j) {
statspec->tilespecs[j].flags = StationSpec::TileFlags{buf.ReadByte()};
}
break;
}
@@ -294,6 +296,24 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR
statspec->badges = ReadBadgeList(buf, GSF_STATIONS);
break;
case 0x20: { // Minimum bridge height (extended)
uint16_t tiles = buf.ReadExtendedByte();
if (statspec->tilespecs.size() < tiles) statspec->tilespecs.resize(tiles);
for (int j = 0; j != tiles; ++j) {
statspec->tilespecs[j].height = buf.ReadByte();
}
break;
}
case 0x21: { // Disallowed bridge pillars
uint16_t tiles = buf.ReadExtendedByte();
if (statspec->tilespecs.size() < tiles) statspec->tilespecs.resize(tiles);
for (int j = 0; j != tiles; ++j) {
statspec->tilespecs[j].disallowed_pillars = BridgePillarFlags{buf.ReadByte()};
}
break;
}
default:
ret = CIR_UNKNOWN;
break;

View File

@@ -12,6 +12,7 @@
#ifndef NEWGRF_ROADSTATION_H
#define NEWGRF_ROADSTATION_H
#include "bridge_type.h"
#include "newgrf_animation_type.h"
#include "newgrf_spritegroup.h"
#include "newgrf_badge_type.h"
@@ -138,12 +139,17 @@ struct RoadStopSpec : NewGRFSpecBase<RoadStopClassID> {
AnimationInfo<StationAnimationTriggers> animation;
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
struct TileSpec {
uint8_t height = 0; ///< Minimum height for a bridge above, 0 for none
BridgePillarFlags disallowed_pillars = BRIDGEPILLARFLAGS_ALL; ///< Disallowed pillar flags for a bridge above
};
std::array<TileSpec, 6> tilespecs{}; ///< Per tile information.
uint8_t build_cost_multiplier = 16; ///< Build 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;
/**

View File

@@ -10,6 +10,7 @@
#ifndef NEWGRF_STATION_H
#define NEWGRF_STATION_H
#include "bridge_type.h"
#include "core/enum_type.hpp"
#include "newgrf_animation_type.h"
#include "newgrf_badge_type.h"
@@ -168,7 +169,13 @@ struct StationSpec : NewGRFSpecBase<StationClassID> {
Blocked = 2, ///< Tile is blocked to vehicles.
};
using TileFlags = EnumBitSet<TileFlag, uint8_t>;
std::vector<TileFlags> tileflags; ///< List of tile flags.
struct TileSpec {
TileFlags flags{}; ///< Tile flags.
uint8_t height = 0; ///< Minimum height for a bridge above, 0 for none
BridgePillarFlags disallowed_pillars = BRIDGEPILLARFLAGS_ALL; ///< Disallowed pillar flags for a bridge above
};
std::vector<TileSpec> tilespecs; ///< Per-layout-tile information.
AnimationInfo<StationAnimationTriggers> animation;

View File

@@ -483,7 +483,7 @@ static void DrawTile_Object(TileInfo *ti)
DrawNewObjectTile(ti, spec);
}
DrawBridgeMiddle(ti);
DrawBridgeMiddle(ti, {});
}
static int GetSlopePixelZ_Object(TileIndex tile, uint x, uint y, bool)
@@ -929,6 +929,16 @@ static CommandCost TerraformTile_Object(TileIndex tile, DoCommandFlags flags, in
return Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
}
static CommandCost CheckBuildAbove_Object(TileIndex tile, DoCommandFlags flags, Axis, int height)
{
const ObjectSpec *spec = ObjectSpec::GetByTile(tile);
if (spec->flags.Test(ObjectFlag::AllowUnderBridge) && GetTileMaxZ(tile) + spec->height <= height) {
return CommandCost();
}
return Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
}
extern const TileTypeProcs _tile_type_object_procs = {
DrawTile_Object, // draw_tile_proc
GetSlopePixelZ_Object, // get_slope_z_proc
@@ -944,4 +954,5 @@ extern const TileTypeProcs _tile_type_object_procs = {
nullptr, // vehicle_enter_tile_proc
GetFoundation_Object, // get_foundation_proc
TerraformTile_Object, // terraform_tile_proc
CheckBuildAbove_Object, // check_build_above_proc
};

View File

@@ -2525,7 +2525,7 @@ static void DrawTile_Track(TileInfo *ti)
DrawRailTileSeq(ti, dts, TO_BUILDINGS, relocation, 0, _drawtile_track_palette);
}
DrawBridgeMiddle(ti);
DrawBridgeMiddle(ti, {});
}
void DrawTrainDepotSprite(int x, int y, int dir, RailType railtype)
@@ -3070,6 +3070,11 @@ static CommandCost TerraformTile_Track(TileIndex tile, DoCommandFlags flags, int
return Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
}
static CommandCost CheckBuildAbove_Track(TileIndex tile, DoCommandFlags flags, Axis, int)
{
if (IsPlainRail(tile)) return CommandCost();
return Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
}
extern const TileTypeProcs _tile_type_rail_procs = {
DrawTile_Track, // draw_tile_proc
@@ -3086,4 +3091,5 @@ extern const TileTypeProcs _tile_type_rail_procs = {
VehicleEnter_Track, // vehicle_enter_tile_proc
GetFoundation_Track, // get_foundation_proc
TerraformTile_Track, // terraform_tile_proc
CheckBuildAbove_Track, // check_build_above_proc
};

View File

@@ -1858,7 +1858,7 @@ static void DrawTile_Road(TileInfo *ti)
break;
}
}
DrawBridgeMiddle(ti);
DrawBridgeMiddle(ti, {});
}
/**
@@ -2616,6 +2616,11 @@ CommandCost CmdConvertRoad(DoCommandFlags flags, TileIndex tile, TileIndex area_
return found_convertible_road ? cost : error;
}
static CommandCost CheckBuildAbove_Road(TileIndex tile, DoCommandFlags flags, Axis, int)
{
if (!IsRoadDepot(tile)) return CommandCost();
return Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
}
/** Tile callback functions for road tiles */
extern const TileTypeProcs _tile_type_road_procs = {
@@ -2633,4 +2638,5 @@ extern const TileTypeProcs _tile_type_road_procs = {
VehicleEnter_Road, // vehicle_enter_tile_proc
GetFoundation_Road, // get_foundation_proc
TerraformTile_Road, // terraform_tile_proc
CheckBuildAbove_Road, // check_build_above_proc
};

View File

@@ -406,6 +406,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.
SLV_STATIONS_UNDER_BRIDGES, ///< 358 PR#14477 Allow stations under bridges.
SL_MAX_VERSION, ///< Highest possible saveload version
};

View File

@@ -77,6 +77,8 @@
#include "safeguards.h"
static StationSpec::TileFlags GetStationTileFlags(StationGfx gfx, const StationSpec *statspec);
/**
* Static instance of FlowStat::SharesMap.
* Note: This instance is created on task start.
@@ -864,6 +866,29 @@ static CommandCost CheckFlatLandAirport(AirportTileTableIterator tile_iter, DoCo
return cost;
}
static CommandCost IsRailStationBridgeAboveOk(TileIndex tile, const StationSpec *statspec, uint8_t layout, int bridge_height)
{
if (statspec == nullptr) {
/* Default stations/waypoints */
int height_above = layout < 4 ? 2 : 5;
if (GetTileMaxZ(tile) + height_above > bridge_height) return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION);
} else {
int height_above = layout < std::size(statspec->tilespecs) ? statspec->tilespecs[layout].height : 0;
if (height_above == 0) return CommandCost(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
if (GetTileMaxZ(tile) + height_above > bridge_height) return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION);
}
return CommandCost();
}
CommandCost IsRailStationBridgeAboveOk(TileIndex tile, const StationSpec *statspec, uint8_t layout)
{
if (!IsBridgeAbove(tile)) return CommandCost();
TileIndex rampsouth = GetSouthernBridgeEnd(tile);
return IsRailStationBridgeAboveOk(tile, statspec, layout, GetBridgeHeight(rampsouth));
}
/**
* Checks if a rail station can be built at the given tile.
* @param tile_cur Tile to check.
@@ -888,7 +913,7 @@ static CommandCost CheckFlatLandRailStation(TileIndex tile_cur, TileIndex north_
const StationSpec *statspec = StationClass::Get(spec_class)->GetSpec(spec_index);
bool slope_cb = statspec != nullptr && statspec->callback_mask.Test(StationCallbackMask::SlopeCheck);
CommandCost ret = CheckBuildableTile(tile_cur, invalid_dirs, allowed_z, false);
CommandCost ret = CheckBuildableTile(tile_cur, invalid_dirs, allowed_z, false, false);
if (ret.Failed()) return ret;
cost.AddCost(ret.GetCost());
@@ -949,6 +974,33 @@ static CommandCost CheckFlatLandRailStation(TileIndex tile_cur, TileIndex north_
return cost;
}
static CommandCost IsRoadStopBridgeAboveOk(TileIndex tile, const RoadStopSpec *spec, bool drive_through, DiagDirection entrance, int bridge_height)
{
uint layout = drive_through ? (GFX_TRUCK_BUS_DRIVETHROUGH_OFFSET + DiagDirToAxis(entrance)) : entrance;
if (spec == nullptr) {
if (GetTileMaxZ(tile) + (drive_through ? 1 : 2) > bridge_height) {
return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION);
}
} else {
int height = spec->tilespecs[layout].height;
if (height == 0) return CommandCost(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
if (GetTileMaxZ(tile) + height > bridge_height) {
return CommandCost(STR_ERROR_BRIDGE_TOO_LOW_FOR_STATION);
}
}
return CommandCost();
}
static CommandCost IsRoadStopBridgeAboveOk(TileIndex tile, const RoadStopSpec *spec, bool drive_through, DiagDirection entrance)
{
if (!IsBridgeAbove(tile)) return CommandCost();
TileIndex rampsouth = GetSouthernBridgeEnd(tile);
return IsRoadStopBridgeAboveOk(tile, spec, drive_through, entrance, GetBridgeHeight(rampsouth));
}
/**
* Checks if a road stop can be built at the given tile.
* @param cur_tile Tile to check.
@@ -962,14 +1014,17 @@ static CommandCost CheckFlatLandRailStation(TileIndex tile_cur, TileIndex north_
* @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)
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);
CommandCost ret = CheckBuildableTile(cur_tile, invalid_dirs, allowed_z, !is_drive_through);
CommandCost ret = CheckBuildableTile(cur_tile, invalid_dirs, allowed_z, !is_drive_through, false);
if (ret.Failed()) return ret;
cost.AddCost(ret.GetCost());
ret = IsRoadStopBridgeAboveOk(cur_tile, spec, is_drive_through, DiagDirection{FindFirstBit(invalid_dirs.base())});
if (ret.Failed()) return 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. */
@@ -1307,8 +1362,8 @@ static CommandCost CalculateRailStationCost(TileArea tile_area, DoCommandFlags f
static StationSpec::TileFlags GetStationTileFlags(StationGfx gfx, const StationSpec *statspec)
{
/* Default stations do not draw pylons under roofs (gfx >= 4) */
if (statspec == nullptr || gfx >= statspec->tileflags.size()) return gfx < 4 ? StationSpec::TileFlag::Pylons : StationSpec::TileFlags{};
return statspec->tileflags[gfx];
if (statspec == nullptr || gfx >= statspec->tilespecs.size()) return gfx < 4 ? StationSpec::TileFlag::Pylons : StationSpec::TileFlags{};
return statspec->tilespecs[gfx].flags;
}
/**
@@ -1362,6 +1417,9 @@ CommandCost CmdBuildRailStation(DoCommandFlags flags, TileIndex tile_org, RailTy
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);
if (!reuse) station_to_join = StationID::Invalid();
bool distant_join = (station_to_join != StationID::Invalid());
@@ -1392,8 +1450,29 @@ CommandCost CmdBuildRailStation(DoCommandFlags flags, TileIndex tile_org, RailTy
if (ret.Failed()) return ret;
}
/* Check if we can allocate a custom stationspec to this station */
const StationSpec *statspec = StationClass::Get(spec_class)->GetSpec(spec_index);
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
std::vector<uint8_t> layouts(numtracks * plat_len);
GetStationLayout(layouts.data(), numtracks, plat_len, statspec);
{
auto it = std::begin(layouts);
TileIndex tile_track = tile_org;
for (uint i = 0; i < numtracks; i++) {
TileIndex tile = tile_track;
for (uint j = 0; j < plat_len; j++) {
uint8_t layout = ((*it++) & ~1) + axis; // Adjust layout piece to match axis.
ret = IsRailStationBridgeAboveOk(tile, statspec, layout);
if (ret.Failed()) return ret;
tile += tile_delta;
}
tile_track += track_delta;
}
}
/* Check if we can allocate a custom stationspec to this station */
int specindex = AllocateSpecToStation(statspec, st, flags.Test(DoCommandFlag::Execute));
if (specindex == -1) return CommandCost(STR_ERROR_TOO_MANY_STATION_SPECS);
@@ -1423,13 +1502,7 @@ CommandCost CmdBuildRailStation(DoCommandFlags flags, TileIndex tile_org, RailTy
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);
GetStationLayout(layouts.data(), numtracks, plat_len, statspec);
uint8_t numtracks_orig = numtracks;
Company *c = Company::Get(st->owner);
@@ -1440,6 +1513,7 @@ CommandCost CmdBuildRailStation(DoCommandFlags flags, TileIndex tile_org, RailTy
int w = plat_len;
do {
uint8_t layout = layouts[layout_idx++];
if (IsRailStationTile(tile) && HasStationReservation(tile)) {
/* Check for trains having a reservation for this tile. */
Train *v = GetTrainForReservation(tile, AxisToTrack(GetRailStationAxis(tile)));
@@ -1913,7 +1987,7 @@ static CommandCost FindJoiningRoadStop(StationID existing_stop, StationID statio
* @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.
*/
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{};
if (is_drive_through) {
@@ -1927,7 +2001,7 @@ CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlags flags, bool
int allowed_z = -1;
CommandCost cost(EXPENSES_CONSTRUCTION);
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;
bool is_preexisting_roadstop = IsTileType(cur_tile, MP_STATION) && IsAnyRoadStop(cur_tile);
@@ -2008,7 +2082,7 @@ CommandCost CmdBuildRoadStop(DoCommandFlags flags, TileIndex tile, uint8_t width
unit_cost = _price[is_truck_stop ? PR_BUILD_STATION_TRUCK : PR_BUILD_STATION_BUS];
}
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;
Station *st = nullptr;
@@ -3073,6 +3147,51 @@ bool SplitGroundSpriteForOverlay(const TileInfo *ti, SpriteID *ground, RailTrack
return true;
}
static BridgePillarFlags GetRailStationBlockedPillars(const StationSpec *statspec, uint8_t layout)
{
static const BridgePillarFlags default_pillar_flags[] = {
{BridgePillarFlag::EdgeSW, BridgePillarFlag::EdgeNE}, // X empty
{BridgePillarFlag::EdgeNW, BridgePillarFlag::EdgeSE}, // Y empty
{BridgePillarFlag::EdgeSW, BridgePillarFlag::EdgeNE}, // X small
{BridgePillarFlag::EdgeNW, BridgePillarFlag::EdgeSE}, // Y small
{BridgePillarFlag::EdgeSW, BridgePillarFlag::EdgeNE, BridgePillarFlag::EdgeSE, BridgePillarFlag::CornerE, BridgePillarFlag::CornerS}, // X large
{BridgePillarFlag::EdgeNW, BridgePillarFlag::EdgeSE, BridgePillarFlag::EdgeSW, BridgePillarFlag::CornerS, BridgePillarFlag::CornerW}, // Y large
{BridgePillarFlag::EdgeSW, BridgePillarFlag::EdgeNE, BridgePillarFlag::EdgeNW, BridgePillarFlag::CornerN, BridgePillarFlag::CornerW}, // X large
{BridgePillarFlag::EdgeNW, BridgePillarFlag::EdgeSE, BridgePillarFlag::EdgeNE, BridgePillarFlag::CornerN, BridgePillarFlag::CornerE}, // Y large
};
if (statspec == nullptr) {
/* Default stations/waypoints */
if (layout < 8) return default_pillar_flags[layout];
return {};
}
if (layout < std::size(statspec->tilespecs) && statspec->tilespecs[layout].disallowed_pillars != BRIDGEPILLARFLAGS_ALL) {
/* Pllar flags set by NewGRF */
return statspec->tilespecs[layout].disallowed_pillars;
}
if (GetStationTileFlags(layout, statspec).Test(StationSpec::TileFlag::Blocked)) {
/* Blocked station tile. */
return {};
}
/* Non-blocked station tile. */
return default_pillar_flags[layout % 2];
}
static BridgePillarFlags GetRoadStopBlockedPillars(const RoadStopSpec *spec, bool drive_through, uint8_t layout)
{
static constexpr BridgePillarFlags default_pillar_flags_x{BridgePillarFlag::EdgeSW, BridgePillarFlag::EdgeNE};
static constexpr BridgePillarFlags default_pillar_flags_y{BridgePillarFlag::EdgeNW, BridgePillarFlag::EdgeSE};
if (spec != nullptr && spec->tilespecs[layout].disallowed_pillars != BRIDGEPILLARFLAGS_ALL) {
return spec->tilespecs[layout].disallowed_pillars;
}
if (drive_through) {
return HasBit(layout, 0) ? default_pillar_flags_y : default_pillar_flags_x;
}
return {BridgePillarFlag::EdgeNE + (DiagDirection)layout};
}
static void DrawTile_Station(TileInfo *ti)
{
const NewGRFSpriteLayout *layout = nullptr;
@@ -3086,6 +3205,7 @@ static void DrawTile_Station(TileInfo *ti)
BaseStation *st = nullptr;
const StationSpec *statspec = nullptr;
uint tile_layout = 0;
BridgePillarFlags blocked_pillars = {};
if (HasStationRail(ti->tile)) {
rti = GetRailTypeInfo(GetRailType(ti->tile));
@@ -3114,6 +3234,7 @@ static void DrawTile_Station(TileInfo *ti)
}
}
}
blocked_pillars = GetRailStationBlockedPillars(statspec, GetStationGfx(ti->tile));
} else {
total_offset = 0;
}
@@ -3358,6 +3479,7 @@ draw_default_foundation:
uint sprite_offset = GetDriveThroughStopAxis(ti->tile) == AXIS_X ? 1 : 0;
DrawRoadOverlays(ti, PAL_NONE, road_rti, tram_rti, sprite_offset, sprite_offset);
}
blocked_pillars = GetRoadStopBlockedPillars(stopspec, true, view);
} else {
/* Non-drivethrough road stops are only valid for roads. */
assert(road_rt != INVALID_ROADTYPE && tram_rt == INVALID_ROADTYPE);
@@ -3366,6 +3488,7 @@ draw_default_foundation:
SpriteID ground = GetCustomRoadSprite(road_rti, ti->tile, ROTSG_ROADSTOP);
DrawGroundSprite(ground + view, PAL_NONE);
}
blocked_pillars = GetRoadStopBlockedPillars(stopspec, false, view);
}
if (stopspec == nullptr || !stopspec->flags.Test(RoadStopSpecFlag::NoCatenary)) {
@@ -3380,6 +3503,7 @@ draw_default_foundation:
}
DrawRailTileSeq(ti, t, TO_BUILDINGS, total_offset, relocation, palette);
DrawBridgeMiddle(ti, blocked_pillars);
}
void StationPickerDrawSprite(int x, int y, StationType st, RailType railtype, RoadType roadtype, int image)
@@ -5162,6 +5286,40 @@ uint FlowStatMap::GetFlowFromVia(StationID from, StationID via) const
return i->second.GetShare(via);
}
static CommandCost CheckBuildAbove_Station(TileIndex tile, DoCommandFlags flags, Axis, int height)
{
switch (GetStationType(tile)) {
case StationType::Rail:
case StationType::RailWaypoint: {
CommandCost ret = IsRailStationBridgeAboveOk(tile, GetStationSpec(tile), GetStationGfx(tile), height + 1);
if (ret.Failed()) {
if (ret.GetErrorMessage() != INVALID_STRING_ID) return ret;
break;
}
return CommandCost();
}
case StationType::Bus:
case StationType::Truck:
case StationType::RoadWaypoint: {
CommandCost ret = IsRoadStopBridgeAboveOk(tile, GetRoadStopSpec(tile), IsDriveThroughStopTile(tile),
IsDriveThroughStopTile(tile) ? AxisToDiagDir(GetDriveThroughStopAxis(tile)) : GetBayRoadStopDir(tile), height + 1);
if (ret.Failed()) {
if (ret.GetErrorMessage() != INVALID_STRING_ID) return ret;
break;
}
return CommandCost();
}
case StationType::Buoy:
return CommandCost();
default: break;
}
return Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
}
extern const TileTypeProcs _tile_type_station_procs = {
DrawTile_Station, // draw_tile_proc
GetSlopePixelZ_Station, // get_slope_z_proc
@@ -5177,4 +5335,5 @@ extern const TileTypeProcs _tile_type_station_procs = {
VehicleEnter_Station, // vehicle_enter_tile_proc
GetFoundation_Station, // get_foundation_proc
TerraformTile_Station, // terraform_tile_proc
CheckBuildAbove_Station, // check_build_above_proc
};

View File

@@ -745,8 +745,42 @@ static const std::span<const std::span<const PalSpriteID>> _bridge_sprite_table[
* @param nrl description of the rail 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) \
{TimerGameCalendar::Year{y}, mnl, mxl, p, mxs, spr, plt, dsc, { nrl, nrd }, {}, 0}
#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, {}, pillars}
static constexpr BridgePillarFlags BRIDGEPILLARFLAGS_ALL_CORNERS = {
BridgePillarFlag::CornerW, BridgePillarFlag::CornerS, BridgePillarFlag::CornerE, BridgePillarFlag::CornerN
};
/** Pillar flags for bridges which have pillars on the all corners on each piece. */
static const std::array<std::array<BridgePillarFlags, AXIS_END>, NUM_BRIDGE_PIECES - 1> ALL_PILLARS = {{
{{BRIDGEPILLARFLAGS_ALL_CORNERS, BRIDGEPILLARFLAGS_ALL_CORNERS}},
{{BRIDGEPILLARFLAGS_ALL_CORNERS, BRIDGEPILLARFLAGS_ALL_CORNERS}},
{{BRIDGEPILLARFLAGS_ALL_CORNERS, BRIDGEPILLARFLAGS_ALL_CORNERS}},
{{BRIDGEPILLARFLAGS_ALL_CORNERS, BRIDGEPILLARFLAGS_ALL_CORNERS}},
{{BRIDGEPILLARFLAGS_ALL_CORNERS, BRIDGEPILLARFLAGS_ALL_CORNERS}},
{{BRIDGEPILLARFLAGS_ALL_CORNERS, BRIDGEPILLARFLAGS_ALL_CORNERS}},
}};
/** Pillar flags for suspension style bridges. */
static const std::array<std::array<BridgePillarFlags, AXIS_END>, NUM_BRIDGE_PIECES - 1> SUSPENSION_PILLARS = {{
{{{BridgePillarFlag::CornerW, BridgePillarFlag::CornerS}, {BridgePillarFlag::CornerS, BridgePillarFlag::CornerE}}},
{{{BridgePillarFlag::CornerE, BridgePillarFlag::CornerN}, {BridgePillarFlag::CornerW, BridgePillarFlag::CornerN}}},
{{{BridgePillarFlag::CornerE, BridgePillarFlag::CornerN}, {BridgePillarFlag::CornerW, BridgePillarFlag::CornerN}}},
{{{BridgePillarFlag::CornerW, BridgePillarFlag::CornerS}, {BridgePillarFlag::CornerS, BridgePillarFlag::CornerE}}},
{{BRIDGEPILLARFLAGS_ALL_CORNERS, BRIDGEPILLARFLAGS_ALL_CORNERS}},
{{{}, {}}},
}};
/** Pillar flags for cantilever style bridges. */
static const std::array<std::array<BridgePillarFlags, AXIS_END>, NUM_BRIDGE_PIECES - 1> CANTILEVER_PILLARS = {{
{{{}, {}}},
{{{BridgePillarFlag::CornerE, BridgePillarFlag::CornerN}, {BridgePillarFlag::CornerW, BridgePillarFlag::CornerN}}},
{{{BridgePillarFlag::CornerE, BridgePillarFlag::CornerN}, {BridgePillarFlag::CornerW, BridgePillarFlag::CornerN}}},
{{{BridgePillarFlag::CornerE, BridgePillarFlag::CornerN}, {BridgePillarFlag::CornerW, BridgePillarFlag::CornerN}}},
{{{BridgePillarFlag::CornerE, BridgePillarFlag::CornerN}, {BridgePillarFlag::CornerW, BridgePillarFlag::CornerN}}},
{{{BridgePillarFlag::CornerE, BridgePillarFlag::CornerN}, {BridgePillarFlag::CornerW, BridgePillarFlag::CornerN}}},
}};
const BridgeSpec _orig_bridge[] = {
/*
@@ -760,43 +794,43 @@ const BridgeSpec _orig_bridge[] = {
string with description name on rail name on road
| | | | */
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,
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,
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,
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, SUSPENSION_PILLARS),
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, SUSPENSION_PILLARS),
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, SUSPENSION_PILLARS),
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, CANTILEVER_PILLARS),
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, CANTILEVER_PILLARS),
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, CANTILEVER_PILLARS),
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,
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, CANTILEVER_PILLARS),
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, CANTILEVER_PILLARS),
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, CANTILEVER_PILLARS),
};
#undef MBR

View File

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

View File

@@ -0,0 +1,83 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file history_func.cpp Test functionality for misc/history_func. */
#include "../stdafx.h"
#include "../3rdparty/catch2/catch.hpp"
#include "../misc/history_type.hpp"
#include "../misc/history_func.hpp"
#include "../safeguards.h"
template <>
uint16_t SumHistory(std::span<const uint16_t> history)
{
uint32_t total = std::accumulate(std::begin(history), std::end(history), 0, [](uint32_t r, const uint16_t &value) { return r + value; });
return ClampTo<uint16_t>(total);
}
/**
* Helper to get history records and return the value, instead returning its validity.
* @param history History data to extract from.
* @param hr History range to get.
* @param age Age of data to get.
* @return Historical value for the period and age.
*/
template <typename T>
T GetHistory(const HistoryData<T> &history, const HistoryRange &hr, uint age)
{
T result;
GetHistory(history, 0, hr, age, result);
return result;
}
TEST_CASE("History Rotation and Reporting tests")
{
HistoryData<uint16_t> history{};
ValidHistoryMask valid_history = 0;
/* Fill the history with decreasing data points for 24 years of history. This ensures that no data period should
* contain the same value as another period. */
uint16_t i = 12 * HISTORY_PERIODS;
for (uint date = 1; date <= 12 * HISTORY_PERIODS; ++date, --i) {
history[THIS_MONTH] = i;
UpdateValidHistory(valid_history, HISTORY_YEAR, date % 12);
RotateHistory(history, valid_history, HISTORY_YEAR, date % 12);
}
/* With the decreasing sequence, the expected value is triangle number (x*x+n)/2 and the square of the total divisions.
* for quarters: 1 + 2 + 3 = 6, 4 + 5 + 6 = 15, 7 + 8 + 9 = 24, 10 + 11 + 12 = 33
* 13 + 14 + 15 = 42, 16 + 17 + 18 = 51, 19 + 20 + 21 = 60, 22 + 23 + 24 = 69...
* for years: 6 + 15 + 24 + 33 = 78, 42 + 51 + 60 + 69 = 222...
*/
for (uint j = 0; j < HISTORY_PERIODS; ++j) {
CHECK(GetHistory(history, HISTORY_MONTH, j) == (( 1 * 1 + 1) / 2) + 1 * 1 * j);
CHECK(GetHistory(history, HISTORY_QUARTER, j) == (( 3 * 3 + 3) / 2) + 3 * 3 * j);
CHECK(GetHistory(history, HISTORY_YEAR, j) == ((12 * 12 + 12) / 2) + 12 * 12 * j);
}
/* Double-check quarter history matches summed month history. */
CHECK(GetHistory(history, HISTORY_MONTH, 0) + GetHistory(history, HISTORY_MONTH, 1) + GetHistory(history, HISTORY_MONTH, 2) == GetHistory(history, HISTORY_QUARTER, 0));
CHECK(GetHistory(history, HISTORY_MONTH, 3) + GetHistory(history, HISTORY_MONTH, 4) + GetHistory(history, HISTORY_MONTH, 5) == GetHistory(history, HISTORY_QUARTER, 1));
CHECK(GetHistory(history, HISTORY_MONTH, 6) + GetHistory(history, HISTORY_MONTH, 7) + GetHistory(history, HISTORY_MONTH, 8) == GetHistory(history, HISTORY_QUARTER, 2));
CHECK(GetHistory(history, HISTORY_MONTH, 9) + GetHistory(history, HISTORY_MONTH, 10) + GetHistory(history, HISTORY_MONTH, 11) == GetHistory(history, HISTORY_QUARTER, 3));
CHECK(GetHistory(history, HISTORY_MONTH, 12) + GetHistory(history, HISTORY_MONTH, 13) + GetHistory(history, HISTORY_MONTH, 14) == GetHistory(history, HISTORY_QUARTER, 4));
CHECK(GetHistory(history, HISTORY_MONTH, 15) + GetHistory(history, HISTORY_MONTH, 16) + GetHistory(history, HISTORY_MONTH, 17) == GetHistory(history, HISTORY_QUARTER, 5));
CHECK(GetHistory(history, HISTORY_MONTH, 18) + GetHistory(history, HISTORY_MONTH, 19) + GetHistory(history, HISTORY_MONTH, 20) == GetHistory(history, HISTORY_QUARTER, 6));
CHECK(GetHistory(history, HISTORY_MONTH, 21) + GetHistory(history, HISTORY_MONTH, 22) + GetHistory(history, HISTORY_MONTH, 23) == GetHistory(history, HISTORY_QUARTER, 7));
/* Double-check year history matches summed quarter history. */
CHECK(GetHistory(history, HISTORY_QUARTER, 0) + GetHistory(history, HISTORY_QUARTER, 1) + GetHistory(history, HISTORY_QUARTER, 2) + GetHistory(history, HISTORY_QUARTER, 3) == GetHistory(history, HISTORY_YEAR, 0));
CHECK(GetHistory(history, HISTORY_QUARTER, 4) + GetHistory(history, HISTORY_QUARTER, 5) + GetHistory(history, HISTORY_QUARTER, 6) + GetHistory(history, HISTORY_QUARTER, 7) == GetHistory(history, HISTORY_YEAR, 1));
CHECK(GetHistory(history, HISTORY_QUARTER, 8) + GetHistory(history, HISTORY_QUARTER, 9) + GetHistory(history, HISTORY_QUARTER, 10) + GetHistory(history, HISTORY_QUARTER, 11) == GetHistory(history, HISTORY_YEAR, 2));
CHECK(GetHistory(history, HISTORY_QUARTER, 12) + GetHistory(history, HISTORY_QUARTER, 13) + GetHistory(history, HISTORY_QUARTER, 14) + GetHistory(history, HISTORY_QUARTER, 15) == GetHistory(history, HISTORY_YEAR, 3));
CHECK(GetHistory(history, HISTORY_QUARTER, 16) + GetHistory(history, HISTORY_QUARTER, 17) + GetHistory(history, HISTORY_QUARTER, 18) + GetHistory(history, HISTORY_QUARTER, 19) == GetHistory(history, HISTORY_YEAR, 4));
CHECK(GetHistory(history, HISTORY_QUARTER, 20) + GetHistory(history, HISTORY_QUARTER, 21) + GetHistory(history, HISTORY_QUARTER, 22) + GetHistory(history, HISTORY_QUARTER, 23) == GetHistory(history, HISTORY_YEAR, 5));
}

View File

@@ -135,6 +135,8 @@ typedef Foundation GetFoundationProc(TileIndex tile, Slope tileh);
*/
typedef CommandCost TerraformTileProc(TileIndex tile, DoCommandFlags flags, int z_new, Slope tileh_new);
using CheckBuildAboveProc = CommandCost(TileIndex tile, DoCommandFlags flags, Axis axis, int height);
/**
* Set of callback functions for performing tile operations of a given tile type.
* @see TileType
@@ -154,6 +156,7 @@ struct TileTypeProcs {
VehicleEnterTileProc *vehicle_enter_tile_proc; ///< Called when a vehicle enters a tile
GetFoundationProc *get_foundation_proc;
TerraformTileProc *terraform_tile_proc; ///< Called when a terraforming operation is about to take place
CheckBuildAboveProc *check_build_above_proc;
};
extern const TileTypeProcs * const _tile_type_procs[16];

View File

@@ -4130,6 +4130,7 @@ extern const TileTypeProcs _tile_type_town_procs = {
nullptr, // vehicle_enter_tile_proc
GetFoundation_Town, // get_foundation_proc
TerraformTile_Town, // terraform_tile_proc
nullptr, // check_build_above_proc
};
std::span<const DrawBuildingsTileStruct> GetTownDrawTileData()

View File

@@ -1031,4 +1031,5 @@ extern const TileTypeProcs _tile_type_trees_procs = {
nullptr, // vehicle_enter_tile_proc
GetFoundation_Trees, // get_foundation_proc
TerraformTile_Trees, // terraform_tile_proc
nullptr, // check_build_above_proc
};

View File

@@ -12,7 +12,6 @@
*/
#include "stdafx.h"
#include "newgrf_object.h"
#include "viewport_func.h"
#include "command_func.h"
#include "town.h"
@@ -20,7 +19,6 @@
#include "ship.h"
#include "roadveh.h"
#include "pathfinder/yapf/yapf_cache.h"
#include "pathfinder/water_regions.h"
#include "newgrf_sound.h"
#include "autoslope.h"
#include "tunnelbridge_map.h"
@@ -39,7 +37,6 @@
#include "object_base.h"
#include "water.h"
#include "company_gui.h"
#include "station_func.h"
#include "tunnelbridge_cmd.h"
#include "landscape_cmd.h"
#include "terraform_cmd.h"
@@ -278,6 +275,16 @@ static Money TunnelBridgeClearCost(TileIndex tile, Price base_price)
return base_cost;
}
static CommandCost CheckBuildAbove(TileIndex tile, DoCommandFlags flags, Axis axis, int height)
{
if (_tile_type_procs[GetTileType(tile)]->check_build_above_proc != nullptr) {
return _tile_type_procs[GetTileType(tile)]->check_build_above_proc(tile, flags, axis, height);
}
/* A tile without a handler must be cleared. */
return Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
}
/**
* Build a Bridge
* @param flags type of operation
@@ -434,6 +441,14 @@ CommandCost CmdBuildBridge(DoCommandFlags flags, TileIndex tile_end, TileIndex t
/* If bridge belonged to bankrupt company, it has a new owner now */
is_new_owner = (owner == OWNER_NONE);
if (is_new_owner) owner = company;
/* Check if the new bridge is compatible with tiles underneath. */
TileIndexDiff delta = (direction == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1));
for (TileIndex tile = tile_start + delta; tile != tile_end; tile += delta) {
CommandCost ret = CheckBuildAbove(tile, flags, direction, z_start);
if (ret.Failed()) return ret;
cost.AddCost(ret.GetCost());
}
} else {
/* Build a new bridge. */
@@ -484,43 +499,9 @@ CommandCost CmdBuildBridge(DoCommandFlags flags, TileIndex tile_end, TileIndex t
return CommandCost(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
}
switch (GetTileType(tile)) {
case MP_WATER:
if (!IsWater(tile) && !IsCoast(tile)) goto not_valid_below;
break;
case MP_RAILWAY:
if (!IsPlainRail(tile)) goto not_valid_below;
break;
case MP_ROAD:
if (IsRoadDepot(tile)) goto not_valid_below;
break;
case MP_TUNNELBRIDGE:
if (IsTunnel(tile)) break;
if (direction == DiagDirToAxis(GetTunnelBridgeDirection(tile))) goto not_valid_below;
if (z_start < GetBridgeHeight(tile)) goto not_valid_below;
break;
case MP_OBJECT: {
const ObjectSpec *spec = ObjectSpec::GetByTile(tile);
if (!spec->flags.Test(ObjectFlag::AllowUnderBridge)) goto not_valid_below;
if (GetTileMaxZ(tile) + spec->height > z_start) goto not_valid_below;
break;
}
case MP_CLEAR:
break;
default:
not_valid_below:;
/* try and clear the middle landscape */
ret = Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
if (ret.Failed()) return ret;
cost.AddCost(ret.GetCost());
break;
}
ret = CheckBuildAbove(tile, flags, direction, z_start);
if (ret.Failed()) return ret;
cost.AddCost(ret.GetCost());
if (flags.Test(DoCommandFlag::Execute)) {
/* We do this here because when replacing a bridge with another
@@ -1445,7 +1426,7 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, *ti, rear_sep[tunnelbridge_direction]);
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, *ti, front_sep[tunnelbridge_direction]);
DrawBridgeMiddle(ti);
DrawBridgeMiddle(ti, {});
} else { // IsBridge(ti->tile)
DrawFoundation(ti, GetBridgeFoundation(ti->tileh, DiagDirToAxis(tunnelbridge_direction)));
@@ -1536,7 +1517,7 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
}
}
DrawBridgeMiddle(ti);
DrawBridgeMiddle(ti, {});
}
}
@@ -1574,11 +1555,26 @@ static BridgePieces CalcBridgePiece(uint north, uint south)
}
}
BridgePillarFlags GetBridgeTilePillarFlags(TileIndex tile, TileIndex rampnorth, TileIndex rampsouth, BridgeType type, TransportType transport_type)
{
if (transport_type == TRANSPORT_WATER) return BRIDGEPILLARFLAGS_ALL_CORNERS;
const BridgeSpec *spec = GetBridgeSpec(type);
if (!spec->ctrl_flags.Test(BridgeSpecCtrlFlag::InvalidPillarFlags)) {
BridgePieces piece = CalcBridgePiece(GetTunnelBridgeLength(tile, rampnorth) + 1, GetTunnelBridgeLength(tile, rampsouth) + 1);
Axis axis = TileX(rampnorth) == TileX(rampsouth) ? AXIS_Y : AXIS_X;
return spec->pillar_flags[piece][axis == AXIS_Y ? 1 : 0];
}
return BRIDGEPILLARFLAGS_ALL_CORNERS;
}
/**
* Draw the middle bits of a bridge.
* @param ti Tile information of the tile to draw it on.
*/
void DrawBridgeMiddle(const TileInfo *ti)
void DrawBridgeMiddle(const TileInfo *ti, BridgePillarFlags blocked_pillars)
{
/* Sectional view of bridge bounding boxes:
*
@@ -1602,6 +1598,7 @@ void DrawBridgeMiddle(const TileInfo *ti)
TileIndex rampsouth = GetSouthernBridgeEnd(ti->tile);
TransportType transport_type = GetTunnelBridgeTransportType(rampsouth);
Axis axis = GetBridgeAxis(ti->tile);
BridgePillarFlags pillars;
uint base_offset = GetBridgeMiddleAxisBaseOffset(axis);
std::span<const PalSpriteID> psid;
@@ -1611,9 +1608,11 @@ void DrawBridgeMiddle(const TileInfo *ti)
drawfarpillar = !HasBit(GetBridgeSpec(bridge_type)->flags, 0);
base_offset += GetBridgeSpriteTableBaseOffset(transport_type, rampsouth);
psid = GetBridgeSpriteTable(bridge_type, CalcBridgePiece(GetTunnelBridgeLength(ti->tile, rampnorth) + 1, GetTunnelBridgeLength(ti->tile, rampsouth) + 1));
pillars = GetBridgeTilePillarFlags(ti->tile, rampnorth, rampsouth, bridge_type, transport_type);
} else {
drawfarpillar = true;
psid = _aqueduct_sprite_table_middle;
pillars = BRIDGEPILLARFLAGS_ALL_CORNERS;
}
psid = psid.subspan(base_offset, 3);
@@ -1682,6 +1681,7 @@ void DrawBridgeMiddle(const TileInfo *ti)
/* Do not draw anything more if bridges are invisible */
if (IsInvisibilitySet(TO_BRIDGES)) return;
if (blocked_pillars.Any(pillars)) return;
DrawBridgePillars(psid[2], ti, axis, drawfarpillar, x, y, z);
}
@@ -2084,6 +2084,17 @@ static CommandCost TerraformTile_TunnelBridge(TileIndex tile, DoCommandFlags fla
return Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
}
static CommandCost CheckBuildAbove_TunnelBridge(TileIndex tile, DoCommandFlags flags, Axis axis, int height)
{
if (IsTunnel(tile)) return CommandCost();
if (axis != DiagDirToAxis(GetTunnelBridgeDirection(tile)) && height >= GetBridgeHeight(tile)) {
return CommandCost();
}
return Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
}
extern const TileTypeProcs _tile_type_tunnelbridge_procs = {
DrawTile_TunnelBridge, // draw_tile_proc
GetSlopePixelZ_TunnelBridge, // get_slope_z_proc
@@ -2099,4 +2110,5 @@ extern const TileTypeProcs _tile_type_tunnelbridge_procs = {
VehicleEnter_TunnelBridge, // vehicle_enter_tile_proc
GetFoundation_TunnelBridge, // get_foundation_proc
TerraformTile_TunnelBridge, // terraform_tile_proc
CheckBuildAbove_TunnelBridge, // check_build_above_proc
};

View File

@@ -87,4 +87,5 @@ extern const TileTypeProcs _tile_type_void_procs = {
nullptr, // vehicle_enter_tile_proc
GetFoundation_Void, // get_foundation_proc
TerraformTile_Void, // terraform_tile_proc
nullptr, // check_build_above_proc
};

View File

@@ -926,12 +926,12 @@ static void DrawTile_Water(TileInfo *ti)
switch (GetWaterTileType(ti->tile)) {
case WATER_TILE_CLEAR:
DrawWaterClassGround(ti);
DrawBridgeMiddle(ti);
DrawBridgeMiddle(ti, {});
break;
case WATER_TILE_COAST: {
DrawShoreTile(ti->tileh);
DrawBridgeMiddle(ti);
DrawBridgeMiddle(ti, {});
break;
}
@@ -1410,6 +1410,11 @@ static CommandCost TerraformTile_Water(TileIndex tile, DoCommandFlags flags, int
return Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
}
static CommandCost CheckBuildAbove_Water(TileIndex tile, DoCommandFlags flags, Axis, int)
{
if (IsWater(tile) || IsCoast(tile)) return CommandCost();
return Command<CMD_LANDSCAPE_CLEAR>::Do(flags, tile);
}
extern const TileTypeProcs _tile_type_water_procs = {
DrawTile_Water, // draw_tile_proc
@@ -1426,4 +1431,5 @@ extern const TileTypeProcs _tile_type_water_procs = {
VehicleEnter_Water, // vehicle_enter_tile_proc
GetFoundation_Water, // get_foundation_proc
TerraformTile_Water, // terraform_tile_proc
CheckBuildAbove_Water, // check_build_above_proc
};

View File

@@ -174,15 +174,15 @@ static CommandCost IsValidTileForWaypoint(TileIndex tile, Axis axis, StationID *
return CommandCost(STR_ERROR_FLAT_LAND_REQUIRED);
}
if (IsBridgeAbove(tile)) return CommandCost(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST);
return CommandCost();
}
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 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);
/**
@@ -231,12 +231,22 @@ CommandCost CmdBuildRailWaypoint(DoCommandFlags flags, TileIndex start_tile, Axi
/* Make sure the area below consists of clear tiles. (OR tiles belonging to a certain rail station) */
StationID est = StationID::Invalid();
const StationSpec *spec = StationClass::Get(spec_class)->GetSpec(spec_index);
std::vector<uint8_t> layout(count);
if (spec != nullptr) {
/* For NewGRF waypoints we like to have their style. */
GetStationLayout(layout.data(), count, 1, spec);
}
/* Check whether the tiles we're building on are valid rail or not. */
TileIndexDiff offset = TileOffsByAxis(OtherAxis(axis));
for (int i = 0; i < count; i++) {
TileIndex tile = start_tile + i * offset;
CommandCost ret = IsValidTileForWaypoint(tile, axis, &est);
if (ret.Failed()) return ret;
ret = IsRailStationBridgeAboveOk(tile, spec, layout[i]);
if (ret.Failed()) return ret;
}
Waypoint *wp = nullptr;
@@ -285,12 +295,6 @@ CommandCost CmdBuildRailWaypoint(DoCommandFlags flags, TileIndex start_tile, Axi
wp->UpdateVirtCoord();
const StationSpec *spec = StationClass::Get(spec_class)->GetSpec(spec_index);
std::vector<uint8_t> layout(count);
if (spec != nullptr) {
/* For NewGRF waypoints we like to have their style. */
GetStationLayout(layout.data(), count, 1, spec);
}
uint8_t map_spec_index = AllocateSpecToStation(spec, wp, true);
Company *c = Company::Get(wp->owner);
@@ -364,7 +368,7 @@ CommandCost CmdBuildRoadWaypoint(DoCommandFlags flags, TileIndex start_tile, Axi
unit_cost = _price[PR_BUILD_STATION_TRUCK];
}
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;
Waypoint *wp = nullptr;

View File

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