From a241a4ce97ffe3f519ecf656ad10c518d646d423 Mon Sep 17 00:00:00 2001 From: frosch Date: Sun, 29 May 2011 16:56:22 +0000 Subject: (svn r22518) -Feature: [NewGRF] Advanced sprite layouts with register modifiers. --- src/lang/english.txt | 1 + src/newgrf.cpp | 190 +++++++++++++++++++++++++++++++++++++------ src/newgrf_airporttiles.cpp | 2 +- src/newgrf_commons.cpp | 170 ++++++++++++++++++++++++++++++++++++++ src/newgrf_commons.h | 92 +++++++++++++++++++++ src/newgrf_house.cpp | 3 +- src/newgrf_industrytiles.cpp | 3 +- src/newgrf_object.cpp | 4 +- src/newgrf_spritegroup.cpp | 23 ++++++ src/newgrf_spritegroup.h | 5 ++ src/newgrf_station.cpp | 51 ++++++++---- src/sprite.cpp | 30 ++++++- src/station_cmd.cpp | 50 ++++++++---- 13 files changed, 558 insertions(+), 66 deletions(-) (limited to 'src') diff --git a/src/lang/english.txt b/src/lang/english.txt index f61962892..ef4dadd43 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2475,6 +2475,7 @@ STR_NEWGRF_ERROR_READ_BOUNDS :Read past end o STR_NEWGRF_ERROR_MISSING_SPRITES :{WHITE}The currently used base graphics set is missing a number of sprites.{}Please update the base graphics set STR_NEWGRF_ERROR_GRM_FAILED :Requested GRF resources not available STR_NEWGRF_ERROR_FORCEFULLY_DISABLED :{2:RAW_STRING} was disabled by {4:RAW_STRING} +STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT :Invalid/unknown sprite layout format # NewGRF related 'general' warnings STR_NEWGRF_POPUP_CAUTION_CAPTION :{WHITE}Caution! diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 605b17023..a2ff7a5a1 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -482,16 +482,19 @@ static void MapSpriteMappingRecolour(PalSpriteID *grf_sprite) * Read a sprite and a palette from the GRF and convert them into a format * suitable to OpenTTD. * @param buf Input stream. + * @param read_flags Whether to read TileLayoutFlags. * @param invert_action1_flag Set to true, if palette bit 15 means 'not from action 1'. * @param action1_offset Offset to add to action 1 sprites. * @param action1_pitch Factor to multiply action 1 sprite indices with. * @param action1_max Maximal valid action 1 index. * @param [out] grf_sprite Read sprite and palette. + * @return read TileLayoutFlags. */ -static void ReadSpriteLayoutSprite(ByteReader *buf, bool invert_action1_flag, uint action1_offset, uint action1_pitch, uint action1_max, PalSpriteID *grf_sprite) +static TileLayoutFlags ReadSpriteLayoutSprite(ByteReader *buf, bool read_flags, bool invert_action1_flag, uint action1_offset, uint action1_pitch, uint action1_max, PalSpriteID *grf_sprite) { grf_sprite->sprite = buf->ReadWord(); grf_sprite->pal = buf->ReadWord(); + TileLayoutFlags flags = read_flags ? (TileLayoutFlags)buf->ReadWord() : TLF_NOTHING; MapSpriteMappingRecolour(grf_sprite); @@ -509,7 +512,143 @@ static void ReadSpriteLayoutSprite(ByteReader *buf, bool invert_action1_flag, ui SB(grf_sprite->sprite, 0, SPRITE_WIDTH, sprite); SetBit(grf_sprite->sprite, SPRITE_MODIFIER_CUSTOM_SPRITE); } + } else if ((flags & TLF_SPRITE_VAR10) && !(flags & TLF_SPRITE_REG_FLAGS)) { + grfmsg(1, "ReadSpriteLayoutSprite: Spritelayout specifies var10 value for non-action-1 sprite"); + DisableGrf(STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT); + return flags; } + + if (flags & TLF_CUSTOM_PALETTE) { + /* Use palette from Action 1 */ + uint index = GB(grf_sprite->pal, 0, 14); + if (action1_pitch == 0 || index >= action1_max) { + grfmsg(1, "ReadSpriteLayoutSprite: Spritelayout uses undefined custom spriteset %d for 'palette'", index); + grf_sprite->pal = PAL_NONE; + } else { + SpriteID sprite = action1_offset + index * action1_pitch; + SB(grf_sprite->pal, 0, SPRITE_WIDTH, sprite); + SetBit(grf_sprite->pal, SPRITE_MODIFIER_CUSTOM_SPRITE); + } + } else if ((flags & TLF_PALETTE_VAR10) && !(flags & TLF_PALETTE_REG_FLAGS)) { + grfmsg(1, "ReadSpriteLayoutRegisters: Spritelayout specifies var10 value for non-action-1 palette"); + DisableGrf(STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT); + return flags; + } + + return flags; +} + +/** + * Preprocess the TileLayoutFlags and read register modifiers from the GRF. + * @param buf Input stream. + * @param flags TileLayoutFlags to process. + * @param is_parent Whether the sprite is a parentsprite with a bounding box. + * @param dts Sprite layout to insert data into. + * @param index Sprite index to process; 0 for ground sprite. + */ +static void ReadSpriteLayoutRegisters(ByteReader *buf, TileLayoutFlags flags, bool is_parent, NewGRFSpriteLayout *dts, uint index) +{ + if (!(flags & TLF_DRAWING_FLAGS)) return; + + if (dts->registers == NULL) dts->AllocateRegisters(); + TileLayoutRegisters ®s = const_cast(dts->registers[index]); + regs.flags = flags & TLF_DRAWING_FLAGS; + + if (flags & TLF_DODRAW) regs.dodraw = buf->ReadByte(); + if (flags & TLF_SPRITE) regs.sprite = buf->ReadByte(); + if (flags & TLF_PALETTE) regs.palette = buf->ReadByte(); + + if (is_parent) { + if (flags & TLF_BB_XY_OFFSET) { + regs.delta.parent[0] = buf->ReadByte(); + regs.delta.parent[1] = buf->ReadByte(); + } + if (flags & TLF_BB_Z_OFFSET) regs.delta.parent[2] = buf->ReadByte(); + } else { + if (flags & TLF_CHILD_X_OFFSET) regs.delta.child[0] = buf->ReadByte(); + if (flags & TLF_CHILD_Y_OFFSET) regs.delta.child[1] = buf->ReadByte(); + } + + if (flags & TLF_SPRITE_VAR10) { + regs.sprite_var10 = buf->ReadByte(); + if (regs.sprite_var10 > TLR_MAX_VAR10) { + grfmsg(1, "ReadSpriteLayoutRegisters: Spritelayout specifies var10 (%d) exceeding the maximal allowed value %d", regs.sprite_var10, TLR_MAX_VAR10); + DisableGrf(STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT); + return; + } + } + + if (flags & TLF_PALETTE_VAR10) { + regs.palette_var10 = buf->ReadByte(); + if (regs.palette_var10 > TLR_MAX_VAR10) { + grfmsg(1, "ReadSpriteLayoutRegisters: Spritelayout specifies var10 (%d) exceeding the maximal allowed value %d", regs.palette_var10, TLR_MAX_VAR10); + DisableGrf(STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT); + return; + } + } +} + +/** + * Read a spritelayout from the GRF. + * @param buf Input + * @param num_building_sprites Number of building sprites to read + * @param action1_offset Offset to add to action 1 sprites + * @param action1_pitch Factor to multiply action 1 sprite indices with + * @param action1_max Maximal valid action 1 index + * @param allow_var10 Whether the spritelayout may specifiy var10 values for resolving multiple action-1-2-3 chains + * @param no_z_position Whether bounding boxes have no Z offset + * @param dts Layout container to output into + * @return true on error (GRF was disabled) + */ +static bool ReadSpriteLayout(ByteReader *buf, uint num_building_sprites, uint action1_offset, uint action1_pitch, uint action1_max, bool allow_var10, bool no_z_position, NewGRFSpriteLayout *dts) +{ + bool has_flags = HasBit(num_building_sprites, 6); + ClrBit(num_building_sprites, 6); + TileLayoutFlags valid_flags = TLF_KNOWN_FLAGS; + if (!allow_var10) valid_flags &= ~TLF_VAR10_FLAGS; + dts->Allocate(num_building_sprites); // allocate before reading groundsprite flags + + /* Groundsprite */ + TileLayoutFlags flags = ReadSpriteLayoutSprite(buf, has_flags, false, action1_offset, action1_pitch, action1_max, &dts->ground); + if (_skip_sprites < 0) return true; + + if (flags & ~(valid_flags & ~TLF_NON_GROUND_FLAGS)) { + grfmsg(1, "ReadSpriteLayout: Spritelayout uses invalid flag 0x%x for ground sprite", flags & ~(valid_flags & ~TLF_NON_GROUND_FLAGS)); + DisableGrf(STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT); + return true; + } + + ReadSpriteLayoutRegisters(buf, flags, false, dts, 0); + if (_skip_sprites < 0) return true; + + for (uint i = 0; i < num_building_sprites; i++) { + DrawTileSeqStruct *seq = const_cast(&dts->seq[i]); + + flags = ReadSpriteLayoutSprite(buf, has_flags, false, action1_offset, action1_pitch, action1_max, &seq->image); + if (_skip_sprites < 0) return true; + + if (flags & ~valid_flags) { + grfmsg(1, "ReadSpriteLayout: Spritelayout uses unknown flag 0x%x", flags & ~valid_flags); + DisableGrf(STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT); + return true; + } + + seq->delta_x = buf->ReadByte(); + seq->delta_y = buf->ReadByte(); + + if (!no_z_position) seq->delta_z = buf->ReadByte(); + + if (seq->IsParentSprite()) { + seq->size_x = buf->ReadByte(); + seq->size_y = buf->ReadByte(); + seq->size_z = buf->ReadByte(); + } + + ReadSpriteLayoutRegisters(buf, flags, seq->IsParentSprite(), dts, i + 1); + if (_skip_sprites < 0) return true; + } + + return false; } /** @@ -540,6 +679,7 @@ static void ConvertTTDBasePrice(uint32 base_pointer, const char *error_location, enum ChangeInfoResult { CIR_SUCCESS, ///< Variable was parsed and read + CIR_DISABLED, ///< GRF was disabled due to error CIR_UNHANDLED, ///< Variable was parsed but unread CIR_UNKNOWN, ///< Variable is unknown CIR_INVALID_ID, ///< Attempt to modify an invalid ID @@ -1269,7 +1409,9 @@ static ChangeInfoResult StationChangeInfo(uint stid, int numinfo, int prop, Byte continue; } - ReadSpriteLayoutSprite(buf, false, 0, 1, UINT_MAX, &dts->ground); + ReadSpriteLayoutSprite(buf, false, false, 0, 1, UINT_MAX, &dts->ground); + /* On error, bail out immediately. Temporary GRF data was already freed */ + if (_skip_sprites < 0) return CIR_DISABLED; static SmallVector tmp_layout; tmp_layout.Clear(); @@ -1286,7 +1428,9 @@ static ChangeInfoResult StationChangeInfo(uint stid, int numinfo, int prop, Byte dtss->size_y = buf->ReadByte(); dtss->size_z = buf->ReadByte(); - ReadSpriteLayoutSprite(buf, true, 0, 1, UINT_MAX, &dtss->image); + ReadSpriteLayoutSprite(buf, false, true, 0, 1, UINT_MAX, &dtss->image); + /* On error, bail out immediately. Temporary GRF data was already freed */ + if (_skip_sprites < 0) return CIR_DISABLED; } dts->Clone(tmp_layout.Begin()); } @@ -1428,6 +1572,19 @@ static ChangeInfoResult StationChangeInfo(uint stid, int numinfo, int prop, Byte statspec->animation.triggers = buf->ReadWord(); break; + case 0x20: // Advanced sprite layout + statspec->tiles = buf->ReadExtendedByte(); + delete[] statspec->renderdata; // delete earlier loaded stuff + statspec->renderdata = new NewGRFSpriteLayout[statspec->tiles]; + + for (uint t = 0; t < statspec->tiles; t++) { + NewGRFSpriteLayout *dts = &statspec->renderdata[t]; + uint num_building_sprites = buf->ReadByte(); + /* On error, bail out immediately. Temporary GRF data was already freed */ + if (ReadSpriteLayout(buf, num_building_sprites, 0, 1, UINT_MAX, true, false, dts)) return CIR_DISABLED; + } + break; + default: ret = CIR_UNKNOWN; break; @@ -3524,6 +3681,10 @@ static bool HandleChangeInfoResult(const char *caller, ChangeInfoResult cir, uin switch (cir) { default: NOT_REACHED(); + case CIR_DISABLED: + /* Error has already been printed; just stop parsing */ + return true; + case CIR_SUCCESS: return false; @@ -3961,7 +4122,6 @@ static void NewSpriteGroup(ByteReader *buf) byte num_spriteset_ents = _cur_grffile->spriteset_numents; byte num_spritesets = _cur_grffile->spriteset_numsets; byte num_building_sprites = max((uint8)1, type); - uint i; assert(TileLayoutSpriteGroup::CanAllocateItem()); TileLayoutSpriteGroup *group = new TileLayoutSpriteGroup(); @@ -3969,26 +4129,8 @@ static void NewSpriteGroup(ByteReader *buf) /* num_building_stages should be 1, if we are only using non-custom sprites */ group->num_building_stages = max((uint8)1, num_spriteset_ents); - /* Groundsprite */ - ReadSpriteLayoutSprite(buf, false, _cur_grffile->spriteset_start, num_spriteset_ents, num_spritesets, &group->dts.ground); - - group->dts.Allocate(num_building_sprites); - for (i = 0; i < num_building_sprites; i++) { - DrawTileSeqStruct *seq = const_cast(&group->dts.seq[i]); - - ReadSpriteLayoutSprite(buf, false, _cur_grffile->spriteset_start, num_spriteset_ents, num_spritesets, &seq->image); - seq->delta_x = buf->ReadByte(); - seq->delta_y = buf->ReadByte(); - - if (type > 0) { - seq->delta_z = buf->ReadByte(); - if (!seq->IsParentSprite()) continue; - } - - seq->size_x = buf->ReadByte(); - seq->size_y = buf->ReadByte(); - seq->size_z = buf->ReadByte(); - } + /* On error, bail out immediately. Temporary GRF data was already freed */ + if (ReadSpriteLayout(buf, num_building_sprites, _cur_grffile->spriteset_start, num_spriteset_ents, num_spritesets, false, type == 0, &group->dts)) return; break; } diff --git a/src/newgrf_airporttiles.cpp b/src/newgrf_airporttiles.cpp index 329c66075..e00d23ab0 100644 --- a/src/newgrf_airporttiles.cpp +++ b/src/newgrf_airporttiles.cpp @@ -264,7 +264,7 @@ uint16 GetAirportTileCallback(CallbackID callback, uint32 param1, uint32 param2, static void AirportDrawTileLayout(const TileInfo *ti, const TileLayoutSpriteGroup *group, byte colour, StationGfx gfx) { - const DrawTileSprites *dts = &group->dts; + const DrawTileSprites *dts = group->ProcessRegisters(NULL); SpriteID image = dts->ground.sprite; SpriteID pal = dts->ground.pal; diff --git a/src/newgrf_commons.cpp b/src/newgrf_commons.cpp index ce4a6807a..d0d1c1c5f 100644 --- a/src/newgrf_commons.cpp +++ b/src/newgrf_commons.cpp @@ -23,6 +23,7 @@ #include "tunnelbridge_map.h" #include "newgrf_object.h" #include "genworld.h" +#include "newgrf_spritegroup.h" /** * Constructor of generic class @@ -435,6 +436,8 @@ uint32 GetNearbyTileInformation(TileIndex tile) return tile_type << 24 | z << 16 | terrain_type << 8 | tileh; } +/* static */ SmallVector NewGRFSpriteLayout::result_seq; + /** * Clone the building sprites of a spritelayout. * @param source The building sprites to copy. @@ -453,6 +456,26 @@ void NewGRFSpriteLayout::Clone(const DrawTileSeqStruct *source) this->seq = sprites; } +/** + * Clone a spritelayout. + * @param source The spritelayout to copy. + */ +void NewGRFSpriteLayout::Clone(const NewGRFSpriteLayout *source) +{ + this->Clone((const DrawTileSprites*)source); + + if (source->registers != NULL) { + size_t count = 1; // 1 for the ground sprite + const DrawTileSeqStruct *element; + foreach_draw_tile_seq(element, source->seq) count++; + + TileLayoutRegisters *regs = MallocT(count); + MemCpyT(regs, source->registers, count); + this->registers = regs; + } +} + + /** * Allocate a spritelayout for \a num_sprites building sprites. * @param num_sprites Number of building sprites to allocate memory for. (not counting the terminator) @@ -465,3 +488,150 @@ void NewGRFSpriteLayout::Allocate(uint num_sprites) sprites[num_sprites].MakeTerminator(); this->seq = sprites; } + +/** + * Allocate memory for register modifiers. + */ +void NewGRFSpriteLayout::AllocateRegisters() +{ + assert(this->seq != NULL); + assert(this->registers == NULL); + + size_t count = 1; // 1 for the ground sprite + const DrawTileSeqStruct *element; + foreach_draw_tile_seq(element, this->seq) count++; + + this->registers = CallocT(count); +} + +/** + * Prepares a sprite layout before resolving action-1-2-3 chains. + * Integrates offsets into the layout and determines which chains to resolve. + * @note The function uses statically allocated temporary storage, which is reused everytime when calling the function. + * That means, you have to use the sprite layout before calling #PrepareLayout() the next time. + * @param orig_offset Offset to apply to non-action-1 sprites. + * @param newgrf_ground_offset Offset to apply to action-1 ground sprites. + * @param newgrf_offset Offset to apply to action-1 non-ground sprites. + * @param separate_ground Whether the ground sprite shall be resolved by a separate action-1-2-3 chain by default. + * @return Bitmask of values for variable 10 to resolve action-1-2-3 chains for. + */ +uint32 NewGRFSpriteLayout::PrepareLayout(uint32 orig_offset, uint32 newgrf_ground_offset, uint32 newgrf_offset, bool separate_ground) const +{ + result_seq.Clear(); + uint32 var10_values = 0; + + /* Create a copy of the spritelayout, so we can modify some values. + * Also include the groundsprite into the sequence for easier processing. */ + DrawTileSeqStruct *result = result_seq.Append(); + result->image = ground; + result->delta_x = 0; + result->delta_y = 0; + result->delta_z = 0x80; + + const DrawTileSeqStruct *dtss; + foreach_draw_tile_seq(dtss, this->seq) { + *result_seq.Append() = *dtss; + } + result_seq.Append()->MakeTerminator(); + + /* Determine the var10 values the action-1-2-3 chains needs to be resolved for, + * and apply the default sprite offsets (unless disabled). */ + const TileLayoutRegisters *regs = this->registers; + bool ground = true; + foreach_draw_tile_seq(result, result_seq.Begin()) { + TileLayoutFlags flags = TLF_NOTHING; + if (regs != NULL) flags = regs->flags; + + /* Record var10 value for the sprite */ + if (HasBit(result->image.sprite, SPRITE_MODIFIER_CUSTOM_SPRITE) || (flags & TLF_SPRITE_REG_FLAGS)) { + uint8 var10 = (flags & TLF_SPRITE_VAR10) ? regs->sprite_var10 : (ground && separate_ground ? 1 : 0); + SetBit(var10_values, var10); + } + + /* Add default sprite offset, unless there is a custom one */ + if (!(flags & TLF_SPRITE)) { + if (HasBit(result->image.sprite, SPRITE_MODIFIER_CUSTOM_SPRITE)) { + result->image.sprite += ground ? newgrf_ground_offset : newgrf_offset; + } else { + result->image.sprite += orig_offset; + } + } + + /* Record var10 value for the palette */ + if (HasBit(result->image.pal, SPRITE_MODIFIER_CUSTOM_SPRITE) || (flags & TLF_PALETTE_REG_FLAGS)) { + uint8 var10 = (flags & TLF_PALETTE_VAR10) ? regs->palette_var10 : (ground && separate_ground ? 1 : 0); + SetBit(var10_values, var10); + } + + /* Add default palette offset, unless there is a custom one */ + if (!(flags & TLF_PALETTE)) { + if (HasBit(result->image.pal, SPRITE_MODIFIER_CUSTOM_SPRITE)) { + result->image.sprite += ground ? newgrf_ground_offset : newgrf_offset; + } + } + + ground = false; + if (regs != NULL) regs++; + } + + return var10_values; +} + +/** + * Evaluates the register modifiers and integrates them into the preprocessed sprite layout. + * @pre #PrepareLayout() needs calling first. + * @param resolved_var10 The value of var10 the action-1-2-3 chain was evaluated for. + * @param resolved_sprite Result sprite of the action-1-2-3 chain. + * @param separate_ground Whether the ground sprite is resolved by a separate action-1-2-3 chain. + * @return Resulting spritelayout after processing the registers. + */ +void NewGRFSpriteLayout::ProcessRegisters(uint8 resolved_var10, uint32 resolved_sprite, bool separate_ground) const +{ + DrawTileSeqStruct *result; + const TileLayoutRegisters *regs = this->registers; + bool ground = true; + foreach_draw_tile_seq(result, result_seq.Begin()) { + TileLayoutFlags flags = TLF_NOTHING; + if (regs != NULL) flags = regs->flags; + + /* Is the sprite or bounding box affected by an action-1-2-3 chain? */ + if (HasBit(result->image.sprite, SPRITE_MODIFIER_CUSTOM_SPRITE) || (flags & TLF_SPRITE_REG_FLAGS)) { + /* Does the var10 value apply to this sprite? */ + uint8 var10 = (flags & TLF_SPRITE_VAR10) ? regs->sprite_var10 : (ground && separate_ground ? 1 : 0); + if (var10 == resolved_var10) { + /* Apply registers */ + if ((flags & TLF_DODRAW) && GetRegister(regs->dodraw) == 0) { + result->image.sprite = 0; + continue; + } + if (HasBit(result->image.sprite, SPRITE_MODIFIER_CUSTOM_SPRITE)) result->image.sprite += resolved_sprite; + if (flags & TLF_SPRITE) result->image.sprite += (int16)GetRegister(regs->sprite); // mask to 16 bits to avoid trouble + + if (result->IsParentSprite()) { + if (flags & TLF_BB_XY_OFFSET) { + result->delta_x += (int32)GetRegister(regs->delta.parent[0]); + result->delta_y += (int32)GetRegister(regs->delta.parent[1]); + } + if (flags & TLF_BB_Z_OFFSET) result->delta_z += (int32)GetRegister(regs->delta.parent[2]); + } else { + if (flags & TLF_CHILD_X_OFFSET) result->delta_x += (int32)GetRegister(regs->delta.child[0]); + if (flags & TLF_CHILD_Y_OFFSET) result->delta_y += (int32)GetRegister(regs->delta.child[1]); + } + } + } + + /* Is the palette affected by an action-1-2-3 chain? */ + if (HasBit(result->image.pal, SPRITE_MODIFIER_CUSTOM_SPRITE) || (flags & TLF_PALETTE_REG_FLAGS)) { + /* Does the var10 value apply to this sprite? */ + uint8 var10 = (flags & TLF_PALETTE_VAR10) ? regs->palette_var10 : (ground && separate_ground ? 1 : 0); + if (var10 == resolved_var10) { + /* Apply registers */ + if (HasBit(result->image.pal, SPRITE_MODIFIER_CUSTOM_SPRITE)) result->image.pal += resolved_sprite; + if (flags & TLF_PALETTE) result->image.pal += (int16)GetRegister(regs->palette); // mask to 16 bits to avoid trouble + } + } + + ground = false; + if (regs != NULL) regs++; + } +} diff --git a/src/newgrf_commons.h b/src/newgrf_commons.h index 1ddfdc181..d0e556faf 100644 --- a/src/newgrf_commons.h +++ b/src/newgrf_commons.h @@ -18,6 +18,7 @@ #include "tile_type.h" #include "sprite.h" #include "core/alloc_type.hpp" +#include "core/smallvec_type.hpp" /** Context for tile accesses */ enum TileContext { @@ -26,14 +27,75 @@ enum TileContext { TCX_ON_BRIDGE, ///< Querying information about stuff on the bridge (via some bridgehead). }; +/** + * Flags to enable register usage in sprite layouts. + */ +enum TileLayoutFlags { + TLF_NOTHING = 0x00, + + TLF_DODRAW = 0x01, ///< Only draw sprite if value of register TileLayoutRegisters::dodraw is non-zero. + TLF_SPRITE = 0x02, ///< Add signed offset to sprite from register TileLayoutRegisters::sprite. + TLF_PALETTE = 0x04, ///< Add signed offset to palette from register TileLayoutRegisters::palette. + TLF_CUSTOM_PALETTE = 0x08, ///< Palette is from Action 1 (moved to SPRITE_MODIFIER_CUSTOM_SPRITE in palette during loading). + + TLF_BB_XY_OFFSET = 0x10, ///< Add signed offset to bounding box X and Y positions from register TileLayoutRegisters::delta.parent[0..1]. + TLF_BB_Z_OFFSET = 0x20, ///< Add signed offset to bounding box Z positions from register TileLayoutRegisters::delta.parent[2]. + + TLF_CHILD_X_OFFSET = 0x10, ///< Add signed offset to child sprite X positions from register TileLayoutRegisters::delta.child[0]. + TLF_CHILD_Y_OFFSET = 0x20, ///< Add signed offset to child sprite Y positions from register TileLayoutRegisters::delta.child[1]. + + TLF_SPRITE_VAR10 = 0x40, ///< Resolve sprite with a specific value in variable 10. + TLF_PALETTE_VAR10 = 0x80, ///< Resolve palette with a specific value in variable 10. + + TLF_KNOWN_FLAGS = 0x7F, ///< Known flags. Any unknown set flag will disable the GRF. + + /** Flags which are still required after loading the GRF. */ + TLF_DRAWING_FLAGS = ~TLF_CUSTOM_PALETTE, + + /** Flags which do not work for the (first) ground sprite. */ + TLF_NON_GROUND_FLAGS = TLF_BB_XY_OFFSET | TLF_BB_Z_OFFSET | TLF_CHILD_X_OFFSET | TLF_CHILD_Y_OFFSET, + + /** Flags which refer to using multiple action-1-2-3 chains. */ + TLF_VAR10_FLAGS = TLF_SPRITE_VAR10 | TLF_PALETTE_VAR10, + + /** Flags which require resolving the action-1-2-3 chain for the sprite, even if it is no action-1 sprite. */ + TLF_SPRITE_REG_FLAGS = TLF_DODRAW | TLF_SPRITE | TLF_BB_XY_OFFSET | TLF_BB_Z_OFFSET | TLF_CHILD_X_OFFSET | TLF_CHILD_Y_OFFSET, + + /** Flags which require resolving the action-1-2-3 chain for the palette, even if it is no action-1 palette. */ + TLF_PALETTE_REG_FLAGS = TLF_PALETTE, +}; +DECLARE_ENUM_AS_BIT_SET(TileLayoutFlags) + +/** + * Additional modifiers for items in sprite layouts. + */ +struct TileLayoutRegisters { + TileLayoutFlags flags; ///< Flags defining which members are valid and to be used. + uint8 dodraw; ///< Register deciding whether the sprite shall be drawn at all. Non-zero means drawing. + uint8 sprite; ///< Register specifying a signed offset for the sprite. + uint8 palette; ///< Register specifying a signed offset for the palette. + union { + uint8 parent[3]; ///< Registers for signed offsets for the bounding box position of parent sprites. + uint8 child[2]; ///< Registers for signed offsets for the position of child sprites. + } delta; + uint8 sprite_var10; ///< Value for variable 10 when resolving the sprite. + uint8 palette_var10; ///< Value for variable 10 when resolving the palette. +}; + +static const uint TLR_MAX_VAR10 = 7; ///< Maximum value for var 10. + /** * NewGRF supplied spritelayout. * In contrast to #DrawTileSprites this struct is for allocated * layouts on the heap. It allocates data and frees them on destruction. */ struct NewGRFSpriteLayout : ZeroedMemoryAllocator, DrawTileSprites { + const TileLayoutRegisters *registers; + void Allocate(uint num_sprites); + void AllocateRegisters(); void Clone(const DrawTileSeqStruct *source); + void Clone(const NewGRFSpriteLayout *source); /** * Clone a spritelayout. @@ -49,7 +111,37 @@ struct NewGRFSpriteLayout : ZeroedMemoryAllocator, DrawTileSprites { virtual ~NewGRFSpriteLayout() { free(const_cast(this->seq)); + free(const_cast(this->registers)); + } + + /** + * Tests whether this spritelayout needs preprocessing by + * #PrepareLayout() and #ProcessRegisters(), or whether it can be + * used directly. + * @return true if preprocessing is needed + */ + bool NeedsPreprocessing() const + { + return this->registers != NULL; + } + + uint32 PrepareLayout(uint32 orig_offset, uint32 newgrf_ground_offset, uint32 newgrf_offset, bool separate_ground) const; + void ProcessRegisters(uint8 resolved_var10, uint32 resolved_sprite, bool separate_ground) const; + + /** + * Returns the result spritelayout after preprocessing. + * @pre #PrepareLayout() and #ProcessRegisters() need calling first. + * @return result spritelayout + */ + const DrawTileSeqStruct *GetLayout(PalSpriteID *ground) const + { + DrawTileSeqStruct *front = result_seq.Begin(); + *ground = front->image; + return front + 1; } + +private: + static SmallVector result_seq; ///< Temporary storage when preprocessing spritelayouts. }; /** diff --git a/src/newgrf_house.cpp b/src/newgrf_house.cpp index f9fb4b16b..1f525bd8e 100644 --- a/src/newgrf_house.cpp +++ b/src/newgrf_house.cpp @@ -412,7 +412,7 @@ uint16 GetHouseCallback(CallbackID callback, uint32 param1, uint32 param2, House static void DrawTileLayout(const TileInfo *ti, const TileLayoutSpriteGroup *group, byte stage, HouseID house_id) { - const DrawTileSprites *dts = &group->dts; + const DrawTileSprites *dts = group->ProcessRegisters(&stage); const HouseSpec *hs = HouseSpec::Get(house_id); PaletteID palette = hs->random_colour[TileHash2Bit(ti->x, ti->y)] + PALETTE_RECOLOUR_START; @@ -428,6 +428,7 @@ static void DrawTileLayout(const TileInfo *ti, const TileLayoutSpriteGroup *grou PaletteID pal = dts->ground.pal; if (HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE)) image += stage; + if (HasBit(pal, SPRITE_MODIFIER_CUSTOM_SPRITE)) pal += stage; if (GB(image, 0, SPRITE_WIDTH) != 0) { DrawGroundSprite(image, GroundSpritePaletteTransform(image, pal, palette)); diff --git a/src/newgrf_industrytiles.cpp b/src/newgrf_industrytiles.cpp index 9d38ab5af..397fecad7 100644 --- a/src/newgrf_industrytiles.cpp +++ b/src/newgrf_industrytiles.cpp @@ -176,12 +176,13 @@ static void NewIndustryTileResolver(ResolverObject *res, IndustryGfx gfx, TileIn static void IndustryDrawTileLayout(const TileInfo *ti, const TileLayoutSpriteGroup *group, byte rnd_colour, byte stage, IndustryGfx gfx) { - const DrawTileSprites *dts = &group->dts; + const DrawTileSprites *dts = group->ProcessRegisters(&stage); SpriteID image = dts->ground.sprite; PaletteID pal = dts->ground.pal; if (HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE)) image += stage; + if (HasBit(pal, SPRITE_MODIFIER_CUSTOM_SPRITE)) pal += stage; if (GB(image, 0, SPRITE_WIDTH) != 0) { /* If the ground sprite is the default flat water sprite, draw also canal/river borders diff --git a/src/newgrf_object.cpp b/src/newgrf_object.cpp index 4c3b7c3e1..967017039 100644 --- a/src/newgrf_object.cpp +++ b/src/newgrf_object.cpp @@ -417,7 +417,7 @@ uint16 GetObjectCallback(CallbackID callback, uint32 param1, uint32 param2, cons */ static void DrawTileLayout(const TileInfo *ti, const TileLayoutSpriteGroup *group, const ObjectSpec *spec) { - const DrawTileSprites *dts = &group->dts; + const DrawTileSprites *dts = group->ProcessRegisters(NULL); PaletteID palette = ((spec->flags & OBJECT_FLAG_2CC_COLOUR) ? SPR_2CCMAP_BASE : PALETTE_RECOLOUR_START) + Object::GetByTile(ti->tile)->colour; SpriteID image = dts->ground.sprite; @@ -468,7 +468,7 @@ void DrawNewObjectTileInGUI(int x, int y, const ObjectSpec *spec, uint8 view) const SpriteGroup *group = SpriteGroup::Resolve(GetObjectSpriteGroup(spec, NULL), &object); if (group == NULL || group->type != SGT_TILELAYOUT) return; - const DrawTileSprites *dts = &((const TileLayoutSpriteGroup *)group)->dts; + const DrawTileSprites *dts = ((const TileLayoutSpriteGroup *)group)->ProcessRegisters(NULL); PaletteID palette; if (Company::IsValidID(_local_company)) { diff --git a/src/newgrf_spritegroup.cpp b/src/newgrf_spritegroup.cpp index b36df7016..3b1693b4e 100644 --- a/src/newgrf_spritegroup.cpp +++ b/src/newgrf_spritegroup.cpp @@ -226,3 +226,26 @@ const SpriteGroup *RealSpriteGroup::Resolve(ResolverObject *object) const { return object->ResolveReal(object, this); } + +/** + * Process registers and the construction stage into the sprite layout. + * The passed construction stage might get reset to zero, if it gets incorporated into the layout + * during the preprocessing. + * @param [in, out] stage Construction stage (0-3), or NULL if not applicable. + * @return sprite layout to draw. + */ +const DrawTileSprites *TileLayoutSpriteGroup::ProcessRegisters(uint8 *stage) const +{ + if (!this->dts.NeedsPreprocessing()) return &this->dts; + + static DrawTileSprites result; + uint8 actual_stage = stage != NULL ? *stage : 0; + this->dts.PrepareLayout(0, actual_stage, actual_stage, false); + this->dts.ProcessRegisters(0, 0, false); + result.seq = this->dts.GetLayout(&result.ground); + + /* Stage has been processed by PrepareLayout(), set it to zero. */ + if (stage != NULL) *stage = 0; + + return &result; +} diff --git a/src/newgrf_spritegroup.h b/src/newgrf_spritegroup.h index 89be1dc08..3a13a3bbe 100644 --- a/src/newgrf_spritegroup.h +++ b/src/newgrf_spritegroup.h @@ -278,12 +278,17 @@ struct ResultSpriteGroup : SpriteGroup { byte GetNumResults() const { return this->num_sprites; } }; +/** + * Action 2 sprite layout for houses, industry tiles, objects and airport tiles. + */ struct TileLayoutSpriteGroup : SpriteGroup { TileLayoutSpriteGroup() : SpriteGroup(SGT_TILELAYOUT) {} ~TileLayoutSpriteGroup() {} byte num_building_stages; ///< Number of building stages to show for this house/industry tile NewGRFSpriteLayout dts; + + const DrawTileSprites *ProcessRegisters(uint8 *stage) const; }; struct IndustryProductionSpriteGroup : SpriteGroup { diff --git a/src/newgrf_station.cpp b/src/newgrf_station.cpp index 5d2707b70..af0945c05 100644 --- a/src/newgrf_station.cpp +++ b/src/newgrf_station.cpp @@ -746,7 +746,7 @@ void DeallocateSpecFromStation(BaseStation *st, byte specindex) bool DrawStationTile(int x, int y, RailType railtype, Axis axis, StationClassID sclass, uint station) { const StationSpec *statspec; - const DrawTileSprites *sprites; + const DrawTileSprites *sprites = NULL; const RailtypeInfo *rti = GetRailTypeInfo(railtype); PaletteID palette = COMPANY_SPRITE_COLOUR(_local_company); uint tile = 2; @@ -754,36 +754,57 @@ bool DrawStationTile(int x, int y, RailType railtype, Axis axis, StationClassID statspec = StationClass::Get(sclass, station); if (statspec == NULL) return false; - uint relocation = GetCustomStationRelocation(statspec, NULL, INVALID_TILE); - if (HasBit(statspec->callback_mask, CBM_STATION_SPRITE_LAYOUT)) { uint16 callback = GetStationCallback(CBID_STATION_SPRITE_LAYOUT, 0x2110000, 0, statspec, NULL, INVALID_TILE); if (callback != CALLBACK_FAILED) tile = callback; } + uint32 total_offset = rti->GetRailtypeSpriteOffset(); + uint32 relocation = 0; + uint32 ground_relocation = 0; + const NewGRFSpriteLayout *layout = NULL; + DrawTileSprites tmp_rail_layout; + if (statspec->renderdata == NULL) { sprites = GetStationTileLayout(STATION_RAIL, tile + axis); } else { - sprites = &statspec->renderdata[(tile < statspec->tiles) ? tile + axis : (uint)axis]; + layout = &statspec->renderdata[(tile < statspec->tiles) ? tile + axis : (uint)axis]; + if (!layout->NeedsPreprocessing()) { + sprites = layout; + layout = NULL; + } } - SpriteID image = sprites->ground.sprite; - PaletteID pal = sprites->ground.pal; - if (HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE)) { - if (HasBit(statspec->flags, SSF_SEPARATE_GROUND)) { - /* Use separate action 1-2-3 chain for ground sprite */ - image += GetCustomStationRelocation(statspec, NULL, INVALID_TILE, 1); - } else { - image += relocation; + if (layout != NULL) { + /* Sprite layout which needs preprocessing */ + bool separate_ground = HasBit(statspec->flags, SSF_SEPARATE_GROUND); + uint32 var10_values = layout->PrepareLayout(total_offset, rti->fallback_railtype, 0, separate_ground); + uint8 var10; + FOR_EACH_SET_BIT(var10, var10_values) { + uint32 var10_relocation = GetCustomStationRelocation(statspec, NULL, INVALID_TILE, var10); + layout->ProcessRegisters(var10, var10_relocation, separate_ground); } - image += rti->fallback_railtype; + + tmp_rail_layout.seq = layout->GetLayout(&tmp_rail_layout.ground); + sprites = &tmp_rail_layout; + total_offset = 0; } else { - image += rti->GetRailtypeSpriteOffset(); + /* Simple sprite layout */ + ground_relocation = relocation = GetCustomStationRelocation(statspec, NULL, INVALID_TILE, 0); + if (HasBit(sprites->ground.sprite, SPRITE_MODIFIER_CUSTOM_SPRITE)) { + ground_relocation = GetCustomStationRelocation(statspec, NULL, INVALID_TILE, 1); + } + ground_relocation += rti->fallback_railtype; } + SpriteID image = sprites->ground.sprite; + PaletteID pal = sprites->ground.pal; + image += HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE) ? ground_relocation : total_offset; + if (HasBit(pal, SPRITE_MODIFIER_CUSTOM_SPRITE)) pal += ground_relocation; + DrawSprite(image, GroundSpritePaletteTransform(image, pal, palette), x, y); - DrawRailTileSeqInGUI(x, y, sprites, rti->GetRailtypeSpriteOffset(), relocation, palette); + DrawRailTileSeqInGUI(x, y, sprites, total_offset, relocation, palette); return true; } diff --git a/src/sprite.cpp b/src/sprite.cpp index e9a83052c..ee051dc7e 100644 --- a/src/sprite.cpp +++ b/src/sprite.cpp @@ -32,18 +32,29 @@ void DrawCommonTileSeq(const TileInfo *ti, const DrawTileSprites *dts, Transpare { bool parent_sprite_encountered = false; const DrawTileSeqStruct *dtss; + bool skip_childs = false; foreach_draw_tile_seq(dtss, dts->seq) { SpriteID image = dtss->image.sprite; + PaletteID pal = dtss->image.pal; + + if (skip_childs) { + if (!dtss->IsParentSprite()) continue; + skip_childs = false; + } /* TTD sprite 0 means no sprite */ - if (GB(image, 0, SPRITE_WIDTH) == 0 && !HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE)) continue; + if (GB(image, 0, SPRITE_WIDTH) == 0 && !HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE)) { + skip_childs = dtss->IsParentSprite(); + continue; + } /* Stop drawing sprite sequence once we meet a sprite that doesn't have to be opaque */ if (IsInvisibilitySet(to) && !HasBit(image, SPRITE_MODIFIER_OPAQUE)) return; image += (HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE) ? newgrf_offset : orig_offset); + if (HasBit(pal, SPRITE_MODIFIER_CUSTOM_SPRITE)) pal += newgrf_offset; - PaletteID pal = SpriteLayoutPaletteTransform(image, dtss->image.pal, default_palette); + pal = SpriteLayoutPaletteTransform(image, pal, default_palette); if (dtss->IsParentSprite()) { parent_sprite_encountered = true; @@ -86,15 +97,26 @@ void DrawCommonTileSeqInGUI(int x, int y, const DrawTileSprites *dts, int32 orig const DrawTileSeqStruct *dtss; Point child_offset = {0, 0}; + bool skip_childs = false; foreach_draw_tile_seq(dtss, dts->seq) { SpriteID image = dtss->image.sprite; + PaletteID pal = dtss->image.pal; + + if (skip_childs) { + if (!dtss->IsParentSprite()) continue; + skip_childs = false; + } /* TTD sprite 0 means no sprite */ - if (GB(image, 0, SPRITE_WIDTH) == 0 && !HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE)) continue; + if (GB(image, 0, SPRITE_WIDTH) == 0 && !HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE)) { + skip_childs = dtss->IsParentSprite(); + continue; + } image += (HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE) ? newgrf_offset : orig_offset); + if (HasBit(pal, SPRITE_MODIFIER_CUSTOM_SPRITE)) pal += newgrf_offset; - PaletteID pal = SpriteLayoutPaletteTransform(image, dtss->image.pal, default_palette); + pal = SpriteLayoutPaletteTransform(image, pal, default_palette); if (dtss->IsParentSprite()) { Point pt = RemapCoords(dtss->delta_x, dtss->delta_y, dtss->delta_z); diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 7cd32972d..73be9d1d2 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -2495,12 +2495,14 @@ const DrawTileSprites *GetStationTileLayout(StationType st, byte gfx) static void DrawTile_Station(TileInfo *ti) { + const NewGRFSpriteLayout *layout = NULL; + DrawTileSprites tmp_rail_layout; const DrawTileSprites *t = NULL; RoadTypes roadtypes; int32 total_offset; - int32 custom_ground_offset; const RailtypeInfo *rti = NULL; uint32 relocation = 0; + uint32 ground_relocation = 0; const BaseStation *st = NULL; const StationSpec *statspec = NULL; uint tile_layout = 0; @@ -2509,7 +2511,6 @@ static void DrawTile_Station(TileInfo *ti) rti = GetRailTypeInfo(GetRailType(ti->tile)); roadtypes = ROADTYPES_NONE; total_offset = rti->GetRailtypeSpriteOffset(); - custom_ground_offset = rti->fallback_railtype; if (IsCustomStationSpecIndex(ti->tile)) { /* look for customization */ @@ -2519,8 +2520,6 @@ static void DrawTile_Station(TileInfo *ti) if (statspec != NULL) { tile_layout = GetStationGfx(ti->tile); - relocation = GetCustomStationRelocation(statspec, st, ti->tile); - if (HasBit(statspec->callback_mask, CBM_STATION_SPRITE_LAYOUT)) { uint16 callback = GetStationCallback(CBID_STATION_SPRITE_LAYOUT, 0, 0, statspec, st, ti->tile); if (callback != CALLBACK_FAILED) tile_layout = (callback & ~1) + GetRailStationAxis(ti->tile); @@ -2528,14 +2527,17 @@ static void DrawTile_Station(TileInfo *ti) /* Ensure the chosen tile layout is valid for this custom station */ if (statspec->renderdata != NULL) { - t = &statspec->renderdata[tile_layout < statspec->tiles ? tile_layout : (uint)GetRailStationAxis(ti->tile)]; + layout = &statspec->renderdata[tile_layout < statspec->tiles ? tile_layout : (uint)GetRailStationAxis(ti->tile)]; + if (!layout->NeedsPreprocessing()) { + t = layout; + layout = NULL; + } } } } } else { roadtypes = IsRoadStop(ti->tile) ? GetRoadTypes(ti->tile) : ROADTYPES_NONE; total_offset = 0; - custom_ground_offset = 0; } if (IsAirport(ti->tile)) { @@ -2579,7 +2581,7 @@ static void DrawTile_Station(TileInfo *ti) palette = PALETTE_TO_GREY; } - if (t == NULL || t->seq == NULL) t = GetStationTileLayout(GetStationType(ti->tile), GetStationGfx(ti->tile)); + if (layout == NULL && (t == NULL || t->seq == NULL)) t = GetStationTileLayout(GetStationType(ti->tile), GetStationGfx(ti->tile)); /* don't show foundation for docks */ if (ti->tileh != SLOPE_FLAT && !IsDock(ti->tile)) { @@ -2665,6 +2667,27 @@ draw_default_foundation: } } } else { + if (layout != NULL) { + /* Sprite layout which needs preprocessing */ + bool separate_ground = HasBit(statspec->flags, SSF_SEPARATE_GROUND); + uint32 var10_values = layout->PrepareLayout(total_offset, rti->fallback_railtype, 0, separate_ground); + uint8 var10; + FOR_EACH_SET_BIT(var10, var10_values) { + uint32 var10_relocation = GetCustomStationRelocation(statspec, st, ti->tile, var10); + layout->ProcessRegisters(var10, var10_relocation, separate_ground); + } + tmp_rail_layout.seq = layout->GetLayout(&tmp_rail_layout.ground); + t = &tmp_rail_layout; + total_offset = 0; + } else if (statspec != NULL) { + /* Simple sprite layout */ + ground_relocation = relocation = GetCustomStationRelocation(statspec, st, ti->tile, 0); + if (HasBit(statspec->flags, SSF_SEPARATE_GROUND)) { + ground_relocation = GetCustomStationRelocation(statspec, st, ti->tile, 1); + } + ground_relocation += rti->fallback_railtype; + } + SpriteID image = t->ground.sprite; PaletteID pal = t->ground.pal; if (rti != NULL && rti->UsesOverlay() && (image == SPR_RAIL_TRACK_X || image == SPR_RAIL_TRACK_Y)) { @@ -2677,17 +2700,8 @@ draw_default_foundation: DrawGroundSprite(overlay + (image == SPR_RAIL_TRACK_X ? RTO_X : RTO_Y), PALETTE_CRASH); } } else { - if (HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE)) { - if (HasBit(statspec->flags, SSF_SEPARATE_GROUND)) { - /* Use separate action 1-2-3 chain for ground sprite */ - image += GetCustomStationRelocation(statspec, st, ti->tile, 1); - } else { - image += relocation; - } - image += custom_ground_offset; - } else { - image += total_offset; - } + image += HasBit(image, SPRITE_MODIFIER_CUSTOM_SPRITE) ? ground_relocation : total_offset; + if (HasBit(pal, SPRITE_MODIFIER_CUSTOM_SPRITE)) pal += ground_relocation; DrawGroundSprite(image, GroundSpritePaletteTransform(image, pal, palette)); /* PBS debugging, draw reserved tracks darker */ -- cgit v1.2.3-70-g09d2