OpenTTD Source 20250331-master-g3c15e0c889
newgrf_act2.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 <http://www.gnu.org/licenses/>.
6 */
7
10#include "../stdafx.h"
11#include <ranges>
12#include "../debug.h"
13#include "../newgrf_engine.h"
14#include "../newgrf_cargo.h"
15#include "../error.h"
16#include "../vehicle_base.h"
17#include "../road.h"
18#include "newgrf_bytereader.h"
19#include "newgrf_internal.h"
20
21#include "../table/strings.h"
22
23#include "../safeguards.h"
24
30{
31 if (HasBit(grf_sprite->pal, 14)) {
32 ClrBit(grf_sprite->pal, 14);
34 }
35
36 if (HasBit(grf_sprite->sprite, 14)) {
37 ClrBit(grf_sprite->sprite, 14);
39 }
40
41 if (HasBit(grf_sprite->sprite, 15)) {
42 ClrBit(grf_sprite->sprite, 15);
44 }
45}
46
60TileLayoutFlags ReadSpriteLayoutSprite(ByteReader &buf, bool read_flags, bool invert_action1_flag, bool use_cur_spritesets, int feature, PalSpriteID *grf_sprite, uint16_t *max_sprite_offset, uint16_t *max_palette_offset)
61{
62 grf_sprite->sprite = buf.ReadWord();
63 grf_sprite->pal = buf.ReadWord();
64 TileLayoutFlags flags = read_flags ? (TileLayoutFlags)buf.ReadWord() : TLF_NOTHING;
65
66 MapSpriteMappingRecolour(grf_sprite);
67
68 bool custom_sprite = HasBit(grf_sprite->pal, 15) != invert_action1_flag;
69 ClrBit(grf_sprite->pal, 15);
70 if (custom_sprite) {
71 /* Use sprite from Action 1 */
72 uint index = GB(grf_sprite->sprite, 0, 14);
73 if (use_cur_spritesets && (!_cur.IsValidSpriteSet(feature, index) || _cur.GetNumEnts(feature, index) == 0)) {
74 GrfMsg(1, "ReadSpriteLayoutSprite: Spritelayout uses undefined custom spriteset {}", index);
75 grf_sprite->sprite = SPR_IMG_QUERY;
76 grf_sprite->pal = PAL_NONE;
77 } else {
78 SpriteID sprite = use_cur_spritesets ? _cur.GetSprite(feature, index) : index;
79 if (max_sprite_offset != nullptr) *max_sprite_offset = use_cur_spritesets ? _cur.GetNumEnts(feature, index) : UINT16_MAX;
80 SB(grf_sprite->sprite, 0, SPRITE_WIDTH, sprite);
82 }
83 } else if ((flags & TLF_SPRITE_VAR10) && !(flags & TLF_SPRITE_REG_FLAGS)) {
84 GrfMsg(1, "ReadSpriteLayoutSprite: Spritelayout specifies var10 value for non-action-1 sprite");
85 DisableGrf(STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT);
86 return flags;
87 }
88
89 if (flags & TLF_CUSTOM_PALETTE) {
90 /* Use palette from Action 1 */
91 uint index = GB(grf_sprite->pal, 0, 14);
92 if (use_cur_spritesets && (!_cur.IsValidSpriteSet(feature, index) || _cur.GetNumEnts(feature, index) == 0)) {
93 GrfMsg(1, "ReadSpriteLayoutSprite: Spritelayout uses undefined custom spriteset {} for 'palette'", index);
94 grf_sprite->pal = PAL_NONE;
95 } else {
96 SpriteID sprite = use_cur_spritesets ? _cur.GetSprite(feature, index) : index;
97 if (max_palette_offset != nullptr) *max_palette_offset = use_cur_spritesets ? _cur.GetNumEnts(feature, index) : UINT16_MAX;
98 SB(grf_sprite->pal, 0, SPRITE_WIDTH, sprite);
100 }
101 } else if ((flags & TLF_PALETTE_VAR10) && !(flags & TLF_PALETTE_REG_FLAGS)) {
102 GrfMsg(1, "ReadSpriteLayoutRegisters: Spritelayout specifies var10 value for non-action-1 palette");
103 DisableGrf(STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT);
104 return flags;
105 }
106
107 return flags;
108}
109
118static void ReadSpriteLayoutRegisters(ByteReader &buf, TileLayoutFlags flags, bool is_parent, NewGRFSpriteLayout *dts, uint index)
119{
120 if (!(flags & TLF_DRAWING_FLAGS)) return;
121
122 if (dts->registers.empty()) dts->AllocateRegisters();
123 TileLayoutRegisters &regs = const_cast<TileLayoutRegisters&>(dts->registers[index]);
124 regs.flags = flags & TLF_DRAWING_FLAGS;
125
126 if (flags & TLF_DODRAW) regs.dodraw = buf.ReadByte();
127 if (flags & TLF_SPRITE) regs.sprite = buf.ReadByte();
128 if (flags & TLF_PALETTE) regs.palette = buf.ReadByte();
129
130 if (is_parent) {
131 if (flags & TLF_BB_XY_OFFSET) {
132 regs.delta.parent[0] = buf.ReadByte();
133 regs.delta.parent[1] = buf.ReadByte();
134 }
135 if (flags & TLF_BB_Z_OFFSET) regs.delta.parent[2] = buf.ReadByte();
136 } else {
137 if (flags & TLF_CHILD_X_OFFSET) regs.delta.child[0] = buf.ReadByte();
138 if (flags & TLF_CHILD_Y_OFFSET) regs.delta.child[1] = buf.ReadByte();
139 }
140
141 if (flags & TLF_SPRITE_VAR10) {
142 regs.sprite_var10 = buf.ReadByte();
143 if (regs.sprite_var10 > TLR_MAX_VAR10) {
144 GrfMsg(1, "ReadSpriteLayoutRegisters: Spritelayout specifies var10 ({}) exceeding the maximal allowed value {}", regs.sprite_var10, TLR_MAX_VAR10);
145 DisableGrf(STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT);
146 return;
147 }
148 }
149
150 if (flags & TLF_PALETTE_VAR10) {
151 regs.palette_var10 = buf.ReadByte();
152 if (regs.palette_var10 > TLR_MAX_VAR10) {
153 GrfMsg(1, "ReadSpriteLayoutRegisters: Spritelayout specifies var10 ({}) exceeding the maximal allowed value {}", regs.palette_var10, TLR_MAX_VAR10);
154 DisableGrf(STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT);
155 return;
156 }
157 }
158}
159
171bool ReadSpriteLayout(ByteReader &buf, uint num_building_sprites, bool use_cur_spritesets, uint8_t feature, bool allow_var10, bool no_z_position, NewGRFSpriteLayout *dts)
172{
173 bool has_flags = HasBit(num_building_sprites, 6);
174 ClrBit(num_building_sprites, 6);
175 TileLayoutFlags valid_flags = TLF_KNOWN_FLAGS;
176 if (!allow_var10) valid_flags &= ~TLF_VAR10_FLAGS;
177 dts->Allocate(num_building_sprites); // allocate before reading groundsprite flags
178
179 std::vector<uint16_t> max_sprite_offset(num_building_sprites + 1, 0);
180 std::vector<uint16_t> max_palette_offset(num_building_sprites + 1, 0);
181
182 /* Groundsprite */
183 TileLayoutFlags flags = ReadSpriteLayoutSprite(buf, has_flags, false, use_cur_spritesets, feature, &dts->ground, max_sprite_offset.data(), max_palette_offset.data());
184 if (_cur.skip_sprites < 0) return true;
185
186 if (flags & ~(valid_flags & ~TLF_NON_GROUND_FLAGS)) {
187 GrfMsg(1, "ReadSpriteLayout: Spritelayout uses invalid flag 0x{:X} for ground sprite", flags & ~(valid_flags & ~TLF_NON_GROUND_FLAGS));
188 DisableGrf(STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT);
189 return true;
190 }
191
192 ReadSpriteLayoutRegisters(buf, flags, false, dts, 0);
193 if (_cur.skip_sprites < 0) return true;
194
195 for (uint i = 0; i < num_building_sprites; i++) {
196 DrawTileSeqStruct *seq = const_cast<DrawTileSeqStruct*>(&dts->seq[i]);
197
198 flags = ReadSpriteLayoutSprite(buf, has_flags, false, use_cur_spritesets, feature, &seq->image, max_sprite_offset.data() + i + 1, max_palette_offset.data() + i + 1);
199 if (_cur.skip_sprites < 0) return true;
200
201 if (flags & ~valid_flags) {
202 GrfMsg(1, "ReadSpriteLayout: Spritelayout uses unknown flag 0x{:X}", flags & ~valid_flags);
203 DisableGrf(STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT);
204 return true;
205 }
206
207 seq->delta_x = buf.ReadByte();
208 seq->delta_y = buf.ReadByte();
209
210 if (!no_z_position) seq->delta_z = buf.ReadByte();
211
212 if (seq->IsParentSprite()) {
213 seq->size_x = buf.ReadByte();
214 seq->size_y = buf.ReadByte();
215 seq->size_z = buf.ReadByte();
216 }
217
218 ReadSpriteLayoutRegisters(buf, flags, seq->IsParentSprite(), dts, i + 1);
219 if (_cur.skip_sprites < 0) return true;
220 }
221
222 /* Check if the number of sprites per spriteset is consistent */
223 bool is_consistent = true;
224 dts->consistent_max_offset = 0;
225 for (uint i = 0; i < num_building_sprites + 1; i++) {
226 if (max_sprite_offset[i] > 0) {
227 if (dts->consistent_max_offset == 0) {
228 dts->consistent_max_offset = max_sprite_offset[i];
229 } else if (dts->consistent_max_offset != max_sprite_offset[i]) {
230 is_consistent = false;
231 break;
232 }
233 }
234 if (max_palette_offset[i] > 0) {
235 if (dts->consistent_max_offset == 0) {
236 dts->consistent_max_offset = max_palette_offset[i];
237 } else if (dts->consistent_max_offset != max_palette_offset[i]) {
238 is_consistent = false;
239 break;
240 }
241 }
242 }
243
244 /* When the Action1 sets are unknown, everything should be 0 (no spriteset usage) or UINT16_MAX (some spriteset usage) */
245 assert(use_cur_spritesets || (is_consistent && (dts->consistent_max_offset == 0 || dts->consistent_max_offset == UINT16_MAX)));
246
247 if (!is_consistent || !dts->registers.empty()) {
248 dts->consistent_max_offset = 0;
249 if (dts->registers.empty()) dts->AllocateRegisters();
250
251 for (uint i = 0; i < num_building_sprites + 1; i++) {
252 TileLayoutRegisters &regs = const_cast<TileLayoutRegisters&>(dts->registers[i]);
253 regs.max_sprite_offset = max_sprite_offset[i];
254 regs.max_palette_offset = max_palette_offset[i];
255 }
256 }
257
258 return false;
259}
260
261using CachedCallback = std::pair<uint16_t, SpriteGroupID>;
262static std::vector<CachedCallback> _cached_callback_groups;
263
264void ResetCallbacks(bool final)
265{
267 if (final) _cached_callback_groups.shrink_to_fit();
268}
269
270static const SpriteGroup *GetCallbackResultGroup(uint16_t value)
271{
272 /* Old style callback results (only valid for version < 8) have the highest byte 0xFF to signify it is a callback result.
273 * New style ones only have the highest bit set (allows 15-bit results, instead of just 8) */
274 if (_cur.grffile->grf_version < 8 && GB(value, 8, 8) == 0xFF) {
275 value &= ~0xFF00;
276 } else {
277 value &= ~0x8000;
278 }
279
280 /* Find position for value within the cached callback list. */
281 auto it = std::ranges::lower_bound(_cached_callback_groups, value, std::less{}, &CachedCallback::first);
282 if (it != std::end(_cached_callback_groups) && it->first == value) return SpriteGroup::Get(it->second);
283
284 /* Result value is not present, so make it and add to cache. */
286 const SpriteGroup *group = new CallbackResultSpriteGroup(value);
287 it = _cached_callback_groups.emplace(it, value, group->index);
288 return group;
289}
290
291/* Helper function to either create a callback or link to a previously
292 * defined spritegroup. */
293static const SpriteGroup *GetGroupFromGroupID(uint8_t setid, uint8_t type, uint16_t groupid)
294{
295 if (HasBit(groupid, 15)) return GetCallbackResultGroup(groupid);
296
297 if (groupid > MAX_SPRITEGROUP || _cur.spritegroups[groupid] == nullptr) {
298 GrfMsg(1, "GetGroupFromGroupID(0x{:02X}:0x{:02X}): Groupid 0x{:04X} does not exist, leaving empty", setid, type, groupid);
299 return nullptr;
300 }
301
302 return _cur.spritegroups[groupid];
303}
304
313static const SpriteGroup *CreateGroupFromGroupID(uint8_t feature, uint8_t setid, uint8_t type, uint16_t spriteid)
314{
315 if (HasBit(spriteid, 15)) return GetCallbackResultGroup(spriteid);
316
317 if (!_cur.IsValidSpriteSet(feature, spriteid)) {
318 GrfMsg(1, "CreateGroupFromGroupID(0x{:02X}:0x{:02X}): Sprite set {} invalid", setid, type, spriteid);
319 return nullptr;
320 }
321
322 SpriteID spriteset_start = _cur.GetSprite(feature, spriteid);
323 uint num_sprites = _cur.GetNumEnts(feature, spriteid);
324
325 /* Ensure that the sprites are loeded */
326 assert(spriteset_start + num_sprites <= _cur.spriteid);
327
329 return new ResultSpriteGroup(spriteset_start, num_sprites);
330}
331
332/* Action 0x02 */
333static void NewSpriteGroup(ByteReader &buf)
334{
335 /* <02> <feature> <set-id> <type/num-entries> <feature-specific-data...>
336 *
337 * B feature see action 1
338 * B set-id ID of this particular definition
339 * B type/num-entries
340 * if 80 or greater, this is a randomized or variational
341 * list definition, see below
342 * otherwise it specifies a number of entries, the exact
343 * meaning depends on the feature
344 * V feature-specific-data (huge mess, don't even look it up --pasky) */
345 const SpriteGroup *act_group = nullptr;
346
347 uint8_t feature = buf.ReadByte();
348 if (feature >= GSF_END) {
349 GrfMsg(1, "NewSpriteGroup: Unsupported feature 0x{:02X}, skipping", feature);
350 return;
351 }
352
353 uint8_t setid = buf.ReadByte();
354 uint8_t type = buf.ReadByte();
355
356 /* Sprite Groups are created here but they are allocated from a pool, so
357 * we do not need to delete anything if there is an exception from the
358 * ByteReader. */
359
360 switch (type) {
361 /* Deterministic Sprite Group */
362 case 0x81: // Self scope, byte
363 case 0x82: // Parent scope, byte
364 case 0x85: // Self scope, word
365 case 0x86: // Parent scope, word
366 case 0x89: // Self scope, dword
367 case 0x8A: // Parent scope, dword
368 {
369 uint8_t varadjust;
370 uint8_t varsize;
371
374 group->nfo_line = _cur.nfo_line;
375 act_group = group;
376 group->var_scope = HasBit(type, 1) ? VSG_SCOPE_PARENT : VSG_SCOPE_SELF;
377
378 switch (GB(type, 2, 2)) {
379 default: NOT_REACHED();
380 case 0: group->size = DSG_SIZE_BYTE; varsize = 1; break;
381 case 1: group->size = DSG_SIZE_WORD; varsize = 2; break;
382 case 2: group->size = DSG_SIZE_DWORD; varsize = 4; break;
383 }
384
385 /* Loop through the var adjusts. Unfortunately we don't know how many we have
386 * from the outset, so we shall have to keep reallocing. */
387 do {
388 DeterministicSpriteGroupAdjust &adjust = group->adjusts.emplace_back();
389
390 /* The first var adjust doesn't have an operation specified, so we set it to add. */
391 adjust.operation = group->adjusts.size() == 1 ? DSGA_OP_ADD : (DeterministicSpriteGroupAdjustOperation)buf.ReadByte();
392 adjust.variable = buf.ReadByte();
393 if (adjust.variable == 0x7E) {
394 /* Link subroutine group */
395 adjust.subroutine = GetGroupFromGroupID(setid, type, buf.ReadByte());
396 } else {
397 adjust.parameter = IsInsideMM(adjust.variable, 0x60, 0x80) ? buf.ReadByte() : 0;
398 }
399
400 varadjust = buf.ReadByte();
401 adjust.shift_num = GB(varadjust, 0, 5);
402 adjust.type = (DeterministicSpriteGroupAdjustType)GB(varadjust, 6, 2);
403 adjust.and_mask = buf.ReadVarSize(varsize);
404
405 if (adjust.type != DSGA_TYPE_NONE) {
406 adjust.add_val = buf.ReadVarSize(varsize);
407 adjust.divmod_val = buf.ReadVarSize(varsize);
408 if (adjust.divmod_val == 0) adjust.divmod_val = 1; // Ensure that divide by zero cannot occur
409 } else {
410 adjust.add_val = 0;
411 adjust.divmod_val = 0;
412 }
413
414 /* Continue reading var adjusts while bit 5 is set. */
415 } while (HasBit(varadjust, 5));
416
417 std::vector<DeterministicSpriteGroupRange> ranges;
418 ranges.resize(buf.ReadByte());
419 for (auto &range : ranges) {
420 range.group = GetGroupFromGroupID(setid, type, buf.ReadWord());
421 range.low = buf.ReadVarSize(varsize);
422 range.high = buf.ReadVarSize(varsize);
423 }
424
425 group->default_group = GetGroupFromGroupID(setid, type, buf.ReadWord());
426 group->error_group = ranges.empty() ? group->default_group : ranges[0].group;
427 /* nvar == 0 is a special case -- we turn our value into a callback result */
428 group->calculated_result = ranges.empty();
429
430 /* Sort ranges ascending. When ranges overlap, this may required clamping or splitting them */
431 std::vector<uint32_t> bounds;
432 bounds.reserve(ranges.size());
433 for (const auto &range : ranges) {
434 bounds.push_back(range.low);
435 if (range.high != UINT32_MAX) bounds.push_back(range.high + 1);
436 }
437 std::sort(bounds.begin(), bounds.end());
438 bounds.erase(std::unique(bounds.begin(), bounds.end()), bounds.end());
439
440 std::vector<const SpriteGroup *> target;
441 target.reserve(bounds.size());
442 for (const auto &bound : bounds) {
443 const SpriteGroup *t = group->default_group;
444 for (const auto &range : ranges) {
445 if (range.low <= bound && bound <= range.high) {
446 t = range.group;
447 break;
448 }
449 }
450 target.push_back(t);
451 }
452 assert(target.size() == bounds.size());
453
454 for (uint j = 0; j < bounds.size(); ) {
455 if (target[j] != group->default_group) {
456 DeterministicSpriteGroupRange &r = group->ranges.emplace_back();
457 r.group = target[j];
458 r.low = bounds[j];
459 while (j < bounds.size() && target[j] == r.group) {
460 j++;
461 }
462 r.high = j < bounds.size() ? bounds[j] - 1 : UINT32_MAX;
463 } else {
464 j++;
465 }
466 }
467
468 break;
469 }
470
471 /* Randomized Sprite Group */
472 case 0x80: // Self scope
473 case 0x83: // Parent scope
474 case 0x84: // Relative scope
475 {
478 group->nfo_line = _cur.nfo_line;
479 act_group = group;
480 group->var_scope = HasBit(type, 1) ? VSG_SCOPE_PARENT : VSG_SCOPE_SELF;
481
482 if (HasBit(type, 2)) {
483 if (feature <= GSF_AIRCRAFT) group->var_scope = VSG_SCOPE_RELATIVE;
484 group->count = buf.ReadByte();
485 }
486
487 uint8_t triggers = buf.ReadByte();
488 group->triggers = GB(triggers, 0, 7);
489 group->cmp_mode = HasBit(triggers, 7) ? RSG_CMP_ALL : RSG_CMP_ANY;
490 group->lowest_randbit = buf.ReadByte();
491
492 uint8_t num_groups = buf.ReadByte();
493 if (!HasExactlyOneBit(num_groups)) {
494 GrfMsg(1, "NewSpriteGroup: Random Action 2 nrand should be power of 2");
495 }
496
497 group->groups.reserve(num_groups);
498 for (uint i = 0; i < num_groups; i++) {
499 group->groups.push_back(GetGroupFromGroupID(setid, type, buf.ReadWord()));
500 }
501
502 break;
503 }
504
505 /* Neither a variable or randomized sprite group... must be a real group */
506 default:
507 {
508 switch (feature) {
509 case GSF_TRAINS:
510 case GSF_ROADVEHICLES:
511 case GSF_SHIPS:
512 case GSF_AIRCRAFT:
513 case GSF_STATIONS:
514 case GSF_CANALS:
515 case GSF_CARGOES:
516 case GSF_AIRPORTS:
517 case GSF_RAILTYPES:
518 case GSF_ROADTYPES:
519 case GSF_TRAMTYPES:
520 case GSF_BADGES:
521 {
522 uint8_t num_loaded = type;
523 uint8_t num_loading = buf.ReadByte();
524
525 if (!_cur.HasValidSpriteSets(feature)) {
526 GrfMsg(0, "NewSpriteGroup: No sprite set to work on! Skipping");
527 return;
528 }
529
530 GrfMsg(6, "NewSpriteGroup: New SpriteGroup 0x{:02X}, {} loaded, {} loading",
531 setid, num_loaded, num_loading);
532
533 if (num_loaded + num_loading == 0) {
534 GrfMsg(1, "NewSpriteGroup: no result, skipping invalid RealSpriteGroup");
535 break;
536 }
537
538 if (num_loaded + num_loading == 1) {
539 /* Avoid creating 'Real' sprite group if only one option. */
540 uint16_t spriteid = buf.ReadWord();
541 act_group = CreateGroupFromGroupID(feature, setid, type, spriteid);
542 GrfMsg(8, "NewSpriteGroup: one result, skipping RealSpriteGroup = subset {}", spriteid);
543 break;
544 }
545
546 std::vector<uint16_t> loaded;
547 std::vector<uint16_t> loading;
548
549 loaded.reserve(num_loaded);
550 for (uint i = 0; i < num_loaded; i++) {
551 loaded.push_back(buf.ReadWord());
552 GrfMsg(8, "NewSpriteGroup: + rg->loaded[{}] = subset {}", i, loaded[i]);
553 }
554
555 loading.reserve(num_loading);
556 for (uint i = 0; i < num_loading; i++) {
557 loading.push_back(buf.ReadWord());
558 GrfMsg(8, "NewSpriteGroup: + rg->loading[{}] = subset {}", i, loading[i]);
559 }
560
561 bool loaded_same = !loaded.empty() && std::adjacent_find(loaded.begin(), loaded.end(), std::not_equal_to<>()) == loaded.end();
562 bool loading_same = !loading.empty() && std::adjacent_find(loading.begin(), loading.end(), std::not_equal_to<>()) == loading.end();
563 if (loaded_same && loading_same && loaded[0] == loading[0]) {
564 /* Both lists only contain the same value, so don't create 'Real' sprite group */
565 act_group = CreateGroupFromGroupID(feature, setid, type, loaded[0]);
566 GrfMsg(8, "NewSpriteGroup: same result, skipping RealSpriteGroup = subset {}", loaded[0]);
567 break;
568 }
569
571 RealSpriteGroup *group = new RealSpriteGroup();
572 group->nfo_line = _cur.nfo_line;
573 act_group = group;
574
575 if (loaded_same && loaded.size() > 1) loaded.resize(1);
576 group->loaded.reserve(loaded.size());
577 for (uint16_t spriteid : loaded) {
578 const SpriteGroup *t = CreateGroupFromGroupID(feature, setid, type, spriteid);
579 group->loaded.push_back(t);
580 }
581
582 if (loading_same && loading.size() > 1) loading.resize(1);
583 group->loading.reserve(loading.size());
584 for (uint16_t spriteid : loading) {
585 const SpriteGroup *t = CreateGroupFromGroupID(feature, setid, type, spriteid);
586 group->loading.push_back(t);
587 }
588
589 break;
590 }
591
592 case GSF_HOUSES:
593 case GSF_AIRPORTTILES:
594 case GSF_OBJECTS:
595 case GSF_INDUSTRYTILES:
596 case GSF_ROADSTOPS: {
597 uint8_t num_building_sprites = std::max((uint8_t)1, type);
598
601 group->nfo_line = _cur.nfo_line;
602 act_group = group;
603
604 /* On error, bail out immediately. Temporary GRF data was already freed */
605 if (ReadSpriteLayout(buf, num_building_sprites, true, feature, false, type == 0, &group->dts)) return;
606 break;
607 }
608
609 case GSF_INDUSTRIES: {
610 if (type > 2) {
611 GrfMsg(1, "NewSpriteGroup: Unsupported industry production version {}, skipping", type);
612 break;
613 }
614
617 group->nfo_line = _cur.nfo_line;
618 act_group = group;
619 group->version = type;
620 if (type == 0) {
622 for (uint i = 0; i < INDUSTRY_ORIGINAL_NUM_INPUTS; i++) {
623 group->subtract_input[i] = (int16_t)buf.ReadWord(); // signed
624 }
626 for (uint i = 0; i < INDUSTRY_ORIGINAL_NUM_OUTPUTS; i++) {
627 group->add_output[i] = buf.ReadWord(); // unsigned
628 }
629 group->again = buf.ReadByte();
630 } else if (type == 1) {
632 for (uint i = 0; i < INDUSTRY_ORIGINAL_NUM_INPUTS; i++) {
633 group->subtract_input[i] = buf.ReadByte();
634 }
636 for (uint i = 0; i < INDUSTRY_ORIGINAL_NUM_OUTPUTS; i++) {
637 group->add_output[i] = buf.ReadByte();
638 }
639 group->again = buf.ReadByte();
640 } else if (type == 2) {
641 group->num_input = buf.ReadByte();
642 if (group->num_input > std::size(group->subtract_input)) {
643 GRFError *error = DisableGrf(STR_NEWGRF_ERROR_INDPROD_CALLBACK);
644 error->data = "too many inputs (max 16)";
645 return;
646 }
647 for (uint i = 0; i < group->num_input; i++) {
648 uint8_t rawcargo = buf.ReadByte();
649 CargoType cargo = GetCargoTranslation(rawcargo, _cur.grffile);
650 if (!IsValidCargoType(cargo)) {
651 /* The mapped cargo is invalid. This is permitted at this point,
652 * as long as the result is not used. Mark it invalid so this
653 * can be tested later. */
654 group->version = 0xFF;
655 } else if (auto v = group->cargo_input | std::views::take(i); std::ranges::find(v, cargo) != v.end()) {
656 GRFError *error = DisableGrf(STR_NEWGRF_ERROR_INDPROD_CALLBACK);
657 error->data = "duplicate input cargo";
658 return;
659 }
660 group->cargo_input[i] = cargo;
661 group->subtract_input[i] = buf.ReadByte();
662 }
663 group->num_output = buf.ReadByte();
664 if (group->num_output > std::size(group->add_output)) {
665 GRFError *error = DisableGrf(STR_NEWGRF_ERROR_INDPROD_CALLBACK);
666 error->data = "too many outputs (max 16)";
667 return;
668 }
669 for (uint i = 0; i < group->num_output; i++) {
670 uint8_t rawcargo = buf.ReadByte();
671 CargoType cargo = GetCargoTranslation(rawcargo, _cur.grffile);
672 if (!IsValidCargoType(cargo)) {
673 /* Mark this result as invalid to use */
674 group->version = 0xFF;
675 } else if (auto v = group->cargo_output | std::views::take(i); std::ranges::find(v, cargo) != v.end()) {
676 GRFError *error = DisableGrf(STR_NEWGRF_ERROR_INDPROD_CALLBACK);
677 error->data = "duplicate output cargo";
678 return;
679 }
680 group->cargo_output[i] = cargo;
681 group->add_output[i] = buf.ReadByte();
682 }
683 group->again = buf.ReadByte();
684 } else {
685 NOT_REACHED();
686 }
687 break;
688 }
689
690 /* Loading of Tile Layout and Production Callback groups would happen here */
691 default: GrfMsg(1, "NewSpriteGroup: Unsupported feature 0x{:02X}, skipping", feature);
692 }
693 }
694 }
695
696 _cur.spritegroups[setid] = act_group;
697}
698
699template <> void GrfActionHandler<0x02>::FileScan(ByteReader &) { }
701template <> void GrfActionHandler<0x02>::LabelScan(ByteReader &) { }
702template <> void GrfActionHandler<0x02>::Init(ByteReader &) { }
703template <> void GrfActionHandler<0x02>::Reserve(ByteReader &) { }
704template <> void GrfActionHandler<0x02>::Activation(ByteReader &buf) { NewSpriteGroup(buf); }
debug_inline constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
constexpr bool HasExactlyOneBit(T value)
Test whether value has exactly 1 bit set.
constexpr T SB(T &x, const uint8_t s, const uint8_t n, const U d)
Set n bits in x starting at bit s to d.
constexpr T SetBit(T &x, const uint8_t y)
Set a bit in a variable.
debug_inline static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
constexpr T ClrBit(T &x, const uint8_t y)
Clears a bit in a variable.
uint8_t CargoType
Cargo slots to indicate a cargo type within a game.
Definition cargo_type.h:23
bool IsValidCargoType(CargoType cargo)
Test whether cargo type is not INVALID_CARGO.
Definition cargo_type.h:106
Class to read from a NewGRF file.
uint16_t ReadWord()
Read a single Word (16 bits).
uint32_t ReadVarSize(uint8_t size)
Read a value of the given number of bytes.
uint8_t ReadByte()
Read a single byte (8 bits).
uint32_t SpriteID
The number of a sprite, without mapping bits and colourtables.
Definition gfx_type.h:17
static const int INDUSTRY_ORIGINAL_NUM_INPUTS
Original number of accepted cargo types.
static const int INDUSTRY_ORIGINAL_NUM_OUTPUTS
Original number of produced cargo types.
constexpr bool IsInsideMM(const size_t x, const size_t min, const size_t max) noexcept
Checks if a value is in an interval.
GRFError * DisableGrf(StringID message, GRFConfig *config)
Disable a GRF.
Definition newgrf.cpp:131
static const SpriteGroup * CreateGroupFromGroupID(uint8_t feature, uint8_t setid, uint8_t type, uint16_t spriteid)
Helper function to either create a callback or a result sprite group.
static void ReadSpriteLayoutRegisters(ByteReader &buf, TileLayoutFlags flags, bool is_parent, NewGRFSpriteLayout *dts, uint index)
Preprocess the TileLayoutFlags and read register modifiers from the GRF.
bool ReadSpriteLayout(ByteReader &buf, uint num_building_sprites, bool use_cur_spritesets, uint8_t feature, bool allow_var10, bool no_z_position, NewGRFSpriteLayout *dts)
Read a spritelayout from the GRF.
TileLayoutFlags ReadSpriteLayoutSprite(ByteReader &buf, bool read_flags, bool invert_action1_flag, bool use_cur_spritesets, int 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.
void MapSpriteMappingRecolour(PalSpriteID *grf_sprite)
Map the colour modifiers of TTDPatch to those that Open is using.
static std::vector< CachedCallback > _cached_callback_groups
Sorted list of cached callback result spritegroups.
NewGRF buffer reader definition.
CargoType GetCargoTranslation(uint8_t cargo, const GRFFile *grffile, bool usebit)
Translate a GRF-local cargo slot/bitnum into a CargoType.
TileLayoutFlags
Flags to enable register usage in sprite layouts.
@ TLF_BB_Z_OFFSET
Add signed offset to bounding box Z positions from register TileLayoutRegisters::delta....
@ TLF_CUSTOM_PALETTE
Palette is from Action 1 (moved to SPRITE_MODIFIER_CUSTOM_SPRITE in palette during loading).
@ TLF_SPRITE
Add signed offset to sprite from register TileLayoutRegisters::sprite.
@ TLF_CHILD_X_OFFSET
Add signed offset to child sprite X positions from register TileLayoutRegisters::delta....
@ TLF_DRAWING_FLAGS
Flags which are still required after loading the GRF.
@ TLF_DODRAW
Only draw sprite if value of register TileLayoutRegisters::dodraw is non-zero.
@ TLF_PALETTE_REG_FLAGS
Flags which require resolving the action-1-2-3 chain for the palette, even if it is no action-1 palet...
@ TLF_NON_GROUND_FLAGS
Flags which do not work for the (first) ground sprite.
@ TLF_BB_XY_OFFSET
Add signed offset to bounding box X and Y positions from register TileLayoutRegisters::delta....
@ TLF_SPRITE_REG_FLAGS
Flags which require resolving the action-1-2-3 chain for the sprite, even if it is no action-1 sprite...
@ TLF_PALETTE_VAR10
Resolve palette with a specific value in variable 10.
@ TLF_SPRITE_VAR10
Resolve sprite with a specific value in variable 10.
@ TLF_KNOWN_FLAGS
Known flags. Any unknown set flag will disable the GRF.
@ TLF_PALETTE
Add signed offset to palette from register TileLayoutRegisters::palette.
@ TLF_CHILD_Y_OFFSET
Add signed offset to child sprite Y positions from register TileLayoutRegisters::delta....
static const uint TLR_MAX_VAR10
Maximum value for var 10.
NewGRF internal processing state.
static constexpr uint MAX_SPRITEGROUP
Maximum GRF-local ID for a spritegroup.
DeterministicSpriteGroupAdjustOperation
@ DSGA_OP_ADD
a + b
@ VSG_SCOPE_SELF
Resolved object itself.
@ VSG_SCOPE_PARENT
Related object of the resolved one.
@ VSG_SCOPE_RELATIVE
Relative position (vehicles only)
static constexpr uint8_t SPRITE_MODIFIER_OPAQUE
Set when a sprite must not ever be displayed transparently.
Definition sprites.h:1550
static constexpr uint8_t PALETTE_MODIFIER_TRANSPARENT
when a sprite is to be displayed transparently, this bit needs to be set.
Definition sprites.h:1551
static constexpr uint8_t SPRITE_MODIFIER_CUSTOM_SPRITE
these masks change the colours of the palette for a sprite.
Definition sprites.h:1549
static constexpr uint8_t SPRITE_WIDTH
number of bits for the sprite number
Definition sprites.h:1539
static constexpr uint8_t PALETTE_MODIFIER_COLOUR
this bit is set when a recolouring process is in action
Definition sprites.h:1552
uint8_t parameter
Used for variables between 0x60 and 0x7F inclusive.
A tile child sprite and palette to draw for stations etc, with 3D bounding box.
Definition sprite.h:25
int8_t delta_z
0x80 identifies child sprites
Definition sprite.h:28
bool IsParentSprite() const
Check whether this is a parent sprite with a boundingbox.
Definition sprite.h:35
PalSpriteID ground
Palette and sprite for the ground.
Definition sprite.h:47
Information about why GRF had problems during initialisation.
std::string data
Additional data for message and custom_message.
GRF action handler.
GRFFile * grffile
Currently processed GRF file.
uint32_t nfo_line
Currently processed pseudo sprite number in the GRF.
SpriteID spriteid
First available SpriteID for loading realsprites.
SpriteID GetSprite(uint8_t feature, uint set) const
Returns the first sprite of a spriteset.
bool IsValidSpriteSet(uint8_t feature, uint set) const
Check whether a specific set is defined.
uint GetNumEnts(uint8_t feature, uint set) const
Returns the number of sprites in a spriteset.
bool HasValidSpriteSets(uint8_t feature) const
Check whether there are any valid spritesets for a feature.
int skip_sprites
Number of pseudo sprites to skip before processing the next one. (-1 to skip to end of file)
std::array< uint16_t, INDUSTRY_NUM_OUTPUTS > add_output
Add this much output cargo when successful (unsigned, is indirect in cb version 1+)
std::array< CargoType, INDUSTRY_NUM_OUTPUTS > cargo_output
Which output cargoes to add to (only cb version 2)
std::array< CargoType, INDUSTRY_NUM_INPUTS > cargo_input
Which input cargoes to take from (only cb version 2)
std::array< int16_t, INDUSTRY_NUM_INPUTS > subtract_input
Take this much of the input cargo (can be negative, is indirect in cb version 1+)
uint8_t num_input
How many subtract_input values are valid.
uint8_t version
Production callback version used, or 0xFF if marked invalid.
uint8_t num_output
How many add_output values are valid.
NewGRF supplied spritelayout.
void Allocate(uint num_sprites)
Allocate a spritelayout for num_sprites building sprites.
uint consistent_max_offset
Number of sprites in all referenced spritesets.
void AllocateRegisters()
Allocate memory for register modifiers.
Combination of a palette sprite and a 'real' sprite.
Definition gfx_type.h:22
SpriteID sprite
The 'real' sprite.
Definition gfx_type.h:23
PaletteID pal
The palette (use PAL_NONE) if not needed)
Definition gfx_type.h:24
static Titem * Get(auto index)
Returns Titem with given index.
Tindex index
Index of this pool item.
static bool CanAllocateItem(size_t n=1)
Helper functions so we can use PoolItem::Function() instead of _poolitem_pool.Function()
uint8_t lowest_randbit
Look for this in the per-object randomized bitmask:
VarSpriteGroupScope var_scope
Take this object:
std::vector< const SpriteGroup * > groups
Take the group with appropriate index:
RandomizedSpriteGroupCompareMode cmp_mode
Check for these triggers:
std::vector< const SpriteGroup * > loaded
List of loaded groups (can be SpriteIDs or Callback results)
std::vector< const SpriteGroup * > loading
List of loading groups (can be SpriteIDs or Callback results)
Additional modifiers for items in sprite layouts.
uint8_t parent[3]
Registers for signed offsets for the bounding box position of parent sprites.
TileLayoutFlags flags
Flags defining which members are valid and to be used.
uint8_t dodraw
Register deciding whether the sprite shall be drawn at all. Non-zero means drawing.
uint16_t max_sprite_offset
Maximum offset to add to the sprite. (limited by size of the spriteset)
uint8_t palette
Register specifying a signed offset for the palette.
uint8_t sprite_var10
Value for variable 10 when resolving the sprite.
uint16_t max_palette_offset
Maximum offset to add to the palette. (limited by size of the spriteset)
uint8_t palette_var10
Value for variable 10 when resolving the palette.
uint8_t child[2]
Registers for signed offsets for the position of child sprites.
uint8_t sprite
Register specifying a signed offset for the sprite.
Action 2 sprite layout for houses, industry tiles, objects and airport tiles.