OpenTTD Source 20260721-master-g25ec12c62d
newgrf_act0_stations.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "../stdafx.h"
11#include "../debug.h"
12#include "../newgrf_station.h"
13#include "newgrf_bytereader.h"
14#include "newgrf_internal.h"
16
17#include "../safeguards.h"
18
20static const uint NUM_STATIONS_PER_GRF = UINT16_MAX - 1;
21
30static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteReader &buf)
31{
33
34 if (last > NUM_STATIONS_PER_GRF) {
35 GrfMsg(1, "StationChangeInfo: Station {} is invalid, max {}, ignoring", last, NUM_STATIONS_PER_GRF);
37 }
38
39 /* Allocate station specs if necessary */
40 if (_cur_gps.grffile->stations.size() < last) _cur_gps.grffile->stations.resize(last);
41
42 for (uint id = first; id < last; ++id) {
43 auto &statspec = _cur_gps.grffile->stations[id];
44
45 /* Check that the station we are modifying is defined. */
46 if (statspec == nullptr && prop != 0x08) {
47 GrfMsg(2, "StationChangeInfo: Attempt to modify undefined station {}, ignoring", id);
49 }
50
51 switch (prop) {
52 case 0x08: // Class ID
53 /* Property 0x08 is special; it is where the station is allocated */
54 if (statspec == nullptr) {
55 statspec = std::make_unique<StationSpec>();
56 }
57
58 statspec->class_index = StationClass::Allocate(buf.ReadLabel<StationClass::GlobalID>());
59 break;
60
61 case 0x09: { // Define sprite layout
62 uint16_t tiles = buf.ReadExtendedByte();
63 statspec->renderdata.clear(); // delete earlier loaded stuff
64 statspec->renderdata.reserve(tiles);
65
66 for (uint t = 0; t < tiles; t++) {
67 NewGRFSpriteLayout *dts = &statspec->renderdata.emplace_back();
68 dts->consistent_max_offset = UINT16_MAX; // Spritesets are unknown, so no limit.
69
70 if (buf.HasData(4) && buf.PeekDWord() == 0) {
71 buf.Skip(4);
72 extern const DrawTileSpriteSpan _station_display_datas_rail[8];
73 const DrawTileSpriteSpan &dtss = _station_display_datas_rail[t % 8];
74 dts->ground = dtss.ground;
75 dts->seq.insert(dts->seq.end(), dtss.GetSequence().begin(), dtss.GetSequence().end());
76 continue;
77 }
78
79 ReadSpriteLayoutSprite(buf, false, false, false, GrfSpecFeature::Stations, &dts->ground);
80 /* On error, bail out immediately. Temporary GRF data was already freed */
81 if (_cur_gps.skip_sprites < 0) return ChangeInfoResult::Disabled;
82
83 std::vector<DrawTileSeqStruct> tmp_layout;
84 for (;;) {
85 uint8_t delta_x = buf.ReadByte();
86 if (delta_x == 0x80) break;
87
88 /* no relative bounding box support */
89 DrawTileSeqStruct &dtss = tmp_layout.emplace_back();
90 dtss.origin.x = delta_x;
91 dtss.origin.y = buf.ReadByte();
92 dtss.origin.z = buf.ReadByte();
93 dtss.extent.x = buf.ReadByte();
94 dtss.extent.y = buf.ReadByte();
95 dtss.extent.z = buf.ReadByte();
96
97 ReadSpriteLayoutSprite(buf, false, true, false, GrfSpecFeature::Stations, &dtss.image);
98 /* On error, bail out immediately. Temporary GRF data was already freed */
99 if (_cur_gps.skip_sprites < 0) return ChangeInfoResult::Disabled;
100 }
101 dts->seq = std::move(tmp_layout);
102 }
103
104 /* Number of layouts must be even, alternating X and Y */
105 if (statspec->renderdata.size() & 1) {
106 GrfMsg(1, "StationChangeInfo: Station {} defines an odd number of sprite layouts, dropping the last item", id);
107 statspec->renderdata.pop_back();
108 }
109 break;
110 }
111
112 case 0x0A: { // Copy sprite layout
113 uint16_t srcid = buf.ReadExtendedByte();
114 const StationSpec *srcstatspec = srcid >= _cur_gps.grffile->stations.size() ? nullptr : _cur_gps.grffile->stations[srcid].get();
115
116 if (srcstatspec == nullptr) {
117 GrfMsg(1, "StationChangeInfo: Station {} is not defined, cannot copy sprite layout to {}.", srcid, id);
118 continue;
119 }
120
121 statspec->renderdata.clear(); // delete earlier loaded stuff
122 statspec->renderdata.reserve(srcstatspec->renderdata.size());
123
124 for (const auto &it : srcstatspec->renderdata) {
125 statspec->renderdata.emplace_back(it);
126 }
127 break;
128 }
129
130 case 0x0B: // Callback mask
131 statspec->callback_mask = static_cast<StationCallbackMasks>(buf.ReadByte());
132 break;
133
134 case 0x0C: // Disallowed number of platforms
135 statspec->disallowed_platforms = buf.ReadByte();
136 break;
137
138 case 0x0D: // Disallowed platform lengths
139 statspec->disallowed_lengths = buf.ReadByte();
140 break;
141
142 case 0x0E: // Define custom layout
143 while (buf.HasData()) {
144 uint8_t length = buf.ReadByte();
145 uint8_t number = buf.ReadByte();
146
147 if (length == 0 || number == 0) break;
148
149 const uint8_t *buf_layout = buf.ReadBytes(length * number);
150
151 /* Create entry in layouts and assign the layout to it. */
152 auto &layout = statspec->layouts[GetStationLayoutKey(number, length)];
153 layout.assign(buf_layout, buf_layout + length * number);
154
155 /* Ensure the first bit, axis, is zero. The rest of the value is validated during rendering, as we don't know the range yet. */
156 for (auto &tile : layout) {
157 if ((tile & ~1U) != tile) {
158 GrfMsg(1, "StationChangeInfo: Invalid tile {} in layout {}x{}", tile, length, number);
159 tile &= ~1U;
160 }
161 }
162 }
163 break;
164
165 case 0x0F: { // Copy custom layout
166 uint16_t srcid = buf.ReadExtendedByte();
167 const StationSpec *srcstatspec = srcid >= _cur_gps.grffile->stations.size() ? nullptr : _cur_gps.grffile->stations[srcid].get();
168
169 if (srcstatspec == nullptr) {
170 GrfMsg(1, "StationChangeInfo: Station {} is not defined, cannot copy tile layout to {}.", srcid, id);
171 continue;
172 }
173
174 statspec->layouts = srcstatspec->layouts;
175 break;
176 }
177
178 case 0x10: // Little/lots cargo threshold
179 statspec->cargo_threshold = buf.ReadWord();
180 break;
181
182 case 0x11: { // Pylon placement
183 uint8_t pylons = buf.ReadByte();
184 if (statspec->tileflags.size() < 8) statspec->tileflags.resize(8);
185 for (int j = 0; j < 8; ++j) {
186 if (HasBit(pylons, j)) {
187 statspec->tileflags[j].Set(StationSpec::TileFlag::Pylons);
188 } else {
189 statspec->tileflags[j].Reset(StationSpec::TileFlag::Pylons);
190 }
191 }
192 break;
193 }
194
195 case 0x12: // Cargo types for random triggers
196 if (_cur_gps.grffile->grf_version >= 7) {
197 statspec->cargo_triggers = TranslateRefitMask(buf.ReadDWord());
198 } else {
199 statspec->cargo_triggers = (CargoTypes)buf.ReadDWord();
200 }
201 break;
202
203 case 0x13: // General flags
204 statspec->flags = StationSpecFlags{buf.ReadByte()};
205 break;
206
207 case 0x14: { // Overhead wire placement
208 uint8_t wires = buf.ReadByte();
209 if (statspec->tileflags.size() < 8) statspec->tileflags.resize(8);
210 for (int j = 0; j < 8; ++j) {
211 if (HasBit(wires, j)) {
212 statspec->tileflags[j].Set(StationSpec::TileFlag::NoWires);
213 } else {
214 statspec->tileflags[j].Reset(StationSpec::TileFlag::NoWires);
215 }
216 }
217 break;
218 }
219
220 case 0x15: { // Blocked tiles
221 uint8_t blocked = buf.ReadByte();
222 if (statspec->tileflags.size() < 8) statspec->tileflags.resize(8);
223 for (int j = 0; j < 8; ++j) {
224 if (HasBit(blocked, j)) {
225 statspec->tileflags[j].Set(StationSpec::TileFlag::Blocked);
226 } else {
227 statspec->tileflags[j].Reset(StationSpec::TileFlag::Blocked);
228 }
229 }
230 break;
231 }
232
233 case 0x16: // Animation info
234 statspec->animation.frames = buf.ReadByte();
235 statspec->animation.status = static_cast<AnimationStatus>(buf.ReadByte());
236 break;
237
238 case 0x17: // Animation speed
239 statspec->animation.speed = buf.ReadByte();
240 break;
241
242 case 0x18: // Animation triggers
243 statspec->animation.triggers = static_cast<StationAnimationTriggers>(buf.ReadWord());
244 break;
245
246 /* 0x19 road routing (not implemented) */
247
248 case 0x1A: { // Advanced sprite layout
249 uint16_t tiles = buf.ReadExtendedByte();
250 statspec->renderdata.clear(); // delete earlier loaded stuff
251 statspec->renderdata.reserve(tiles);
252
253 for (uint t = 0; t < tiles; t++) {
254 NewGRFSpriteLayout *dts = &statspec->renderdata.emplace_back();
255 uint num_building_sprites = buf.ReadByte();
256 /* On error, bail out immediately. Temporary GRF data was already freed */
257 if (ReadSpriteLayout(buf, num_building_sprites, false, GrfSpecFeature::Stations, true, false, dts)) return ChangeInfoResult::Disabled;
258 }
259
260 /* Number of layouts must be even, alternating X and Y */
261 if (statspec->renderdata.size() & 1) {
262 GrfMsg(1, "StationChangeInfo: Station {} defines an odd number of sprite layouts, dropping the last item", id);
263 statspec->renderdata.pop_back();
264 }
265 break;
266 }
267
268 case 0x1B: // Minimum bridge height (not implemented)
269 buf.ReadWord();
270 buf.ReadWord();
271 buf.ReadWord();
272 buf.ReadWord();
273 break;
274
275 case 0x1C: // Station Name
276 AddStringForMapping(GRFStringID{buf.ReadWord()}, &statspec->name);
277 break;
278
279 case 0x1D: // Station Class name
280 AddStringForMapping(GRFStringID{buf.ReadWord()}, [statspec = statspec.get()](StringID str) { StationClass::Get(statspec->class_index)->name = str; });
281 break;
282
283 case 0x1E: { // Extended tile flags (replaces prop 11, 14 and 15)
284 uint16_t tiles = buf.ReadExtendedByte();
285 auto flags = reinterpret_cast<const StationSpec::TileFlags *>(buf.ReadBytes(tiles));
286 statspec->tileflags.assign(flags, flags + tiles);
287 break;
288 }
289
290 case 0x1F: // Badge list
291 statspec->badges = ReadBadgeList(buf, GrfSpecFeature::Stations);
292 break;
293
294 case 0x20: { // Minimum bridge height (extended)
295 uint16_t tiles = buf.ReadExtendedByte();
296 if (statspec->bridgeable_info.size() < tiles) statspec->bridgeable_info.resize(tiles);
297 for (int j = 0; j != tiles; ++j) {
298 statspec->bridgeable_info[j].height = buf.ReadByte();
299 }
300 break;
301 }
302
303 case 0x21: { // Disallowed bridge pillars
304 uint16_t tiles = buf.ReadExtendedByte();
305 if (statspec->bridgeable_info.size() < tiles) statspec->bridgeable_info.resize(tiles);
306 for (int j = 0; j != tiles; ++j) {
307 statspec->bridgeable_info[j].disallowed_pillars = BridgePillarFlags{buf.ReadByte()};
308 }
309 break;
310 }
311
312 default:
314 break;
315 }
316 }
317
318 return ret;
319}
320
324template <> ChangeInfoResult GrfChangeInfoHandler<GrfSpecFeature::Stations>::Activation(uint first, uint last, int prop, ByteReader &buf) { return StationChangeInfo(first, last, prop, buf); }
constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
EnumBitSet< BridgePillarFlag, uint8_t > BridgePillarFlags
Bitset of BridgePillarFlag elements.
Definition bridge_type.h:53
EnumBitSet< CargoType, uint64_t > CargoTypes
Bitset of CargoType elements.
Definition cargo_type.h:113
Class to read from a NewGRF file.
uint32_t PeekDWord()
Read a single DWord (32 bits).
uint32_t ReadDWord()
Read a single DWord (32 bits).
uint16_t ReadWord()
Read a single Word (16 bits).
T ReadLabel()
Read a label.
uint16_t ReadExtendedByte()
Read a single Extended Byte (8 or 16 bits).
uint8_t ReadByte()
Read a single byte (8 bits).
static NewGRFClass * Get(StationClassID class_index)
StringID name
Name of this class.
Label< NewGRFClass< StationSpec, StationClassID > > GlobalID
static StationClassID Allocate(GlobalID global_id)
Functions related to debugging.
CargoTypes TranslateRefitMask(uint32_t refit_mask)
Translate the refit mask.
Definition newgrf.cpp:322
@ Stations
Stations feature.
Definition newgrf.h:83
std::vector< BadgeID > ReadBadgeList(ByteReader &buf, GrfSpecFeature feature)
Read a list of badges.
static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteReader &buf)
Define properties for stations.
static const uint NUM_STATIONS_PER_GRF
The maximum amount of stations a single GRF is allowed to add.
TileLayoutFlags ReadSpriteLayoutSprite(ByteReader &buf, bool read_flags, bool invert_action1_flag, bool use_cur_spritesets, GrfSpecFeature feature, PalSpriteID *grf_sprite, uint16_t *max_sprite_offset, uint16_t *max_palette_offset)
Read a sprite and a palette from the GRF and convert them into a format suitable to OpenTTD.
bool ReadSpriteLayout(ByteReader &buf, uint num_building_sprites, bool use_cur_spritesets, GrfSpecFeature feature, bool allow_var10, bool no_z_position, NewGRFSpriteLayout *dts)
Read a spritelayout from the GRF.
AnimationStatus
Statuses of animation within NewGRFs.
NewGRF buffer reader definition.
EnumBitSet< StationCallbackMask, uint16_t > StationCallbackMasks
Bitset of StationCallbackMask elements.
NewGRF internal processing state.
ChangeInfoResult
Possible return values for the GrfChangeInfoHandler functions.
@ Success
Variable was parsed and read.
@ Unhandled
Variable was parsed but unread.
@ Unknown
Variable is unknown.
@ Disabled
GRF was disabled due to error.
@ InvalidId
Attempt to modify an invalid ID.
Header file for NewGRF stations.
EnumBitSet< StationSpecFlag, uint8_t > StationSpecFlags
Bitset of StationSpecFlag elements.
uint16_t GetStationLayoutKey(uint8_t platforms, uint8_t length)
Get the station layout key for a given station layout size.
void AddStringForMapping(GRFStringID source, std::function< void(StringID)> &&func)
Record a static StringID for getting translated later.
NewGRF string mapping definition.
StrongType::Typedef< uint32_t, struct GRFStringIDTag, StrongType::Compare, StrongType::Integer > GRFStringID
Type for GRF-internal string IDs.
A number of safeguards to prevent using unsafe methods.
EnumBitSet< StationAnimationTrigger, uint16_t > StationAnimationTriggers
Bitset of StationAnimationTrigger elements.
Definition of base types and functions in a cross-platform compatible way.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
T x
X coordinate.
T y
Y coordinate.
T z
Z coordinate.
A tile child sprite and palette to draw for stations etc, with 3D bounding box.
Definition sprite.h:33
Ground palette sprite of a tile, together with its sprite layout.
Definition sprite.h:76
std::span< const DrawTileSeqStruct > GetSequence() const override
The child sprites to draw.
Definition sprite.h:83
PalSpriteID ground
Palette and sprite for the ground.
Definition sprite.h:56
static ChangeInfoResult Reserve(uint first, uint last, int prop, ByteReader &buf)
Implementation of the GrfLoadingStage::Reserve stage of this feature.
static ChangeInfoResult Activation(uint first, uint last, int prop, ByteReader &buf)
Implementation of the GrfLoadingStage::Activation stage of this feature.
NewGRF supplied spritelayout.
uint consistent_max_offset
Number of sprites in all referenced spritesets.
Coord3D< int8_t > origin
Position of northern corner within tile.
Definition sprite.h:19
Coord3D< uint8_t > extent
Size of bounding box.
Definition sprite.h:20
Station specification.
std::unordered_map< uint16_t, std::vector< uint8_t > > layouts
Custom platform layouts, keyed by platform and length combined.
std::vector< NewGRFSpriteLayout > renderdata
Number of tile layouts.
EnumBitSet< TileFlag, uint8_t > TileFlags
Bitset of TileFlag elements.
@ NoWires
Tile should NOT contain catenary wires.
@ Pylons
Tile should contain catenary pylons.
@ Blocked
Tile is blocked to vehicles.