TilesetDoc.cs
namespace PixelPusher;
public enum TileFlag
{
None = 0,
Solid = 1,
OneWay = 2,
Hazard = 4,
Spring = 8,
Climb = 16,
Ice = 32,
Breakable = 64
}
public sealed class TilesetDoc
{
public string Name { get; set; } = "tileset";
public int TileSize { get; set; } = 16;
public int Columns { get; set; } = 8;
public PixelDoc Sheet { get; set; }
public TileFlag[] Flags { get; set; }
public int FlagVersion { get; private set; }
public int Version => (Sheet?.Version ?? 0) + FlagVersion;
public int TileCount => Flags?.Length ?? 0;
public int Rows => Sheet is null || TileSize <= 0 ? 1 : Math.Max( 1, Sheet.Height / TileSize );
public TilesetDoc( PixelDoc sheet, int tileSize = 16 )
{
Sheet = sheet ?? new PixelDoc( 128, 64 );
TileSize = Math.Clamp( tileSize, 8, 64 );
Columns = Math.Max( 1, Sheet.Width / TileSize );
var count = Math.Max( 1, Columns * Math.Max( 1, Sheet.Height / TileSize ) );
Flags = new TileFlag[count];
Name = sheet?.Name ?? "tileset";
}
public int TileIndex( int tx, int ty ) => ty * Columns + tx;
public void TileOrigin( int id, out int ox, out int oy )
{
if ( id <= 0 || Columns <= 0 )
{
ox = 0;
oy = 0;
return;
}
var i = id;
ox = (i % Columns) * TileSize;
oy = (i / Columns) * TileSize;
}
public TileFlag Flag( int id )
{
if ( Flags is null || (uint)id >= (uint)Flags.Length )
return TileFlag.None;
return Flags[id];
}
public bool IsSolid( int id ) => (Flag( id ) & TileFlag.Solid) != 0;
public bool IsOneWay( int id ) => (Flag( id ) & TileFlag.OneWay) != 0;
public bool IsHazard( int id ) => (Flag( id ) & TileFlag.Hazard) != 0;
public bool IsSpring( int id ) => (Flag( id ) & TileFlag.Spring) != 0;
public void SetFlag( int id, TileFlag flag )
{
if ( Flags is null || (uint)id >= (uint)Flags.Length )
return;
if ( Flags[id] == flag )
return;
Flags[id] = flag;
FlagVersion++;
}
public static TileFlag FlagFromRole( StampRole role ) => role switch
{
StampRole.Solid => TileFlag.Solid,
StampRole.OneWay => TileFlag.OneWay,
StampRole.Hazard => TileFlag.Hazard,
StampRole.Spring => TileFlag.Spring,
_ => TileFlag.None
};
public static TileFlag CycleFlag( TileFlag flag ) => flag switch
{
TileFlag.None => TileFlag.Solid,
TileFlag.Solid => TileFlag.OneWay,
TileFlag.OneWay => TileFlag.Hazard,
TileFlag.Hazard => TileFlag.Spring,
_ => TileFlag.None
};
public static string FlagName( TileFlag flag ) => flag switch
{
TileFlag.Solid => "SOLID",
TileFlag.OneWay => "ONE-WAY",
TileFlag.Hazard => "HAZARD",
TileFlag.Spring => "SPRING",
_ => "DECOR"
};
public string PreviewHex( int id )
{
if ( Sheet is null || id <= 0 )
return "#1a2450";
TileOrigin( id, out var ox, out var oy );
var r = 0f;
var g = 0f;
var b = 0f;
var n = 0;
var ts = TileSize;
for ( var y = 0; y < ts; y += 2 )
for ( var x = 0; x < ts; x += 2 )
{
Color c = Sheet.ColorAt( ox + x, oy + y );
if ( c.a < 0.04f )
continue;
r += c.r;
g += c.g;
b += c.b;
n++;
}
if ( n == 0 )
return "#1a2450";
return $"#{(int)MathF.Round( r / n * 255f ):x2}{(int)MathF.Round( g / n * 255f ):x2}{(int)MathF.Round( b / n * 255f ):x2}";
}
public void StampFromSprite( PixelDoc sprite, int tileId )
{
if ( sprite is null || Sheet is null || tileId <= 0 )
return;
TileOrigin( tileId, out var ox, out var oy );
var ts = TileSize;
Sheet.BeginStroke();
for ( var y = 0; y < ts; y++ )
for ( var x = 0; x < ts; x++ )
{
var sx = sprite.Width == ts ? x : x * sprite.Width / ts;
var sy = sprite.Height == ts ? y : y * sprite.Height / ts;
Sheet.SetIndex( ox + x, oy + y, (byte)sprite.IndexAt( sx, sy ) );
}
}
public TilesetSave ToSave()
{
var flags = new int[Flags.Length];
for ( var i = 0; i < Flags.Length; i++ )
flags[i] = (int)Flags[i];
return new TilesetSave
{
Name = Name,
TileSize = TileSize,
Columns = Columns,
Sheet = Sheet?.ToSave(),
Flags = flags
};
}
public static TilesetDoc FromSave( TilesetSave save )
{
if ( save is null )
return StarterPack.CornerStore();
var sheet = PixelDoc.FromSave( save.Sheet );
var doc = new TilesetDoc( sheet, save.TileSize > 0 ? save.TileSize : 16 )
{
Name = save.Name ?? "tileset",
Columns = save.Columns > 0 ? save.Columns : Math.Max( 1, sheet.Width / 16 )
};
if ( save.Flags is { Length: > 0 } )
{
doc.Flags = new TileFlag[Math.Max( doc.Flags.Length, save.Flags.Length )];
for ( var i = 0; i < save.Flags.Length && i < doc.Flags.Length; i++ )
doc.Flags[i] = (TileFlag)save.Flags[i];
}
return doc;
}
}
public sealed class TilesetSave
{
public string Name { get; set; }
public int TileSize { get; set; }
public int Columns { get; set; }
public PixelSave Sheet { get; set; }
public int[] Flags { get; set; }
}