(svn r22518) -Feature: [NewGRF] Advanced sprite layouts with register modifiers.

pull/155/head
frosch 13 years ago
parent 2d24e00b27
commit becf6c823f

@ -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!

@ -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 &regs = const_cast<TileLayoutRegisters&>(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<DrawTileSeqStruct*>(&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<DrawTileSeqStruct, 8> 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<DrawTileSeqStruct*>(&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;
}

@ -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;

@ -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<DrawTileSeqStruct, 8> 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<TileLayoutRegisters>(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<TileLayoutRegisters>(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++;
}
}

@ -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<DrawTileSeqStruct*>(this->seq));
free(const_cast<TileLayoutRegisters*>(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<DrawTileSeqStruct, 8> result_seq; ///< Temporary storage when preprocessing spritelayouts.
};
/**

@ -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));

@ -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

@ -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)) {

@ -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;
}

@ -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 {

@ -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;
}

@ -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);

@ -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 */

Loading…
Cancel
Save