diff --git a/engine/Age.Engine.Tests/AnimChannelTests.cs b/engine/Age.Engine.Tests/AnimChannelTests.cs new file mode 100644 index 0000000..6e342c6 --- /dev/null +++ b/engine/Age.Engine.Tests/AnimChannelTests.cs @@ -0,0 +1,48 @@ +using Age.Engine.Model; +using Xunit; + +public class AnimChannelTests +{ + private static GfxState VisibleObj(long handle) + { + var g = new GfxState(); + g.SetSurface(1, resId: 5, colorKey: -1); + g.BindDraw(handle, 1, 0, 0, 256, 64, 100, 100); // 256x64 sheet at base pos (100,100) + return g; + } + + [Fact] + public void SetSrcRect_StoresGridCellPeriod() + { + var g = VisibleObj(0x100); + g.SetSrcRect(0x100, gridW: 4, gridH: 1, cell: 2, period: 800); + var o = g.TryGet(0x100)!; + Assert.Equal(4, o.SrcGridW); + Assert.Equal(2, o.SrcCell); + Assert.Equal(800, o.SrcPeriod); + Assert.True(o.SrcAnim); + Assert.Equal(-1, o.SrcStart); // uninitialized until first interpolated frame + } + + [Fact] + public void SetSrcRect_ClampsGridToAtLeastOne() + { + var g = VisibleObj(0x100); + g.SetSrcRect(0x100, gridW: 0, gridH: 0, cell: 0, period: 0); + var o = g.TryGet(0x100)!; + Assert.Equal(1, o.SrcGridW); + Assert.Equal(1, o.SrcGridH); + } + + [Fact] + public void SetColorAnim_StoresPeriodAndTarget() + { + var g = VisibleObj(0x100); + long target = GfxState.PackColor(0x80, 0xFF0000); // half-alpha red glow + g.SetColorAnim(0x100, period: 1000, target: target); + var o = g.TryGet(0x100)!; + Assert.Equal(1000, o.ColorPeriod); + Assert.Equal(target, o.ColorTarget); + Assert.True(o.ColorAnim); + } +} diff --git a/engine/Age.Engine/Model/GfxState.cs b/engine/Age.Engine/Model/GfxState.cs index 318870a..c7ca1a2 100644 --- a/engine/Age.Engine/Model/GfxState.cs +++ b/engine/Age.Engine/Model/GfxState.cs @@ -32,6 +32,15 @@ public sealed class GfxState public long Field64, Field68, Field6c; public long Color; public bool HasColor; // true once op 0x202/0x203 set a color/alpha modulation on this object + + // ---- src-rect / spritesheet-cell channel (ops 0x239 static cell, 0x231 animate). Interpolator + // SRC-RECT SCROLL channel: period obj+0x230, start obj+0x21c, grid obj+0x238/0x23c. ---- + public long SrcGridW = 1, SrcGridH = 1, SrcCell, SrcPeriod, SrcStart = -1; + public bool SrcAnim; + // ---- animated color/glow channel (op 0x232). Interpolator COLOR channel: period obj+0x220, + // start obj+0x20c, target obj+0x240 — PING-PONG (distinct from static 0x202/0x203). ---- + public long ColorPeriod, ColorStart = -1, ColorTarget; + public bool ColorAnim; // draw-texture bind (gfx_object_bind_draw): the surface to draw + its source rect + the visible flag. public int SourceSlot = -1; public (int X, int Y, int W, int H) SrcRect; @@ -137,6 +146,29 @@ public sealed class GfxState { lock (_lock) { var o = GetOrCreate(handle); o.Color = packed; o.HasColor = true; } } + + /// Ops 0x239 (static cell, period=0) / 0x231 (animate, period>0): set the spritesheet grid + + /// the visible cell. When animated, the interpolator ping-pongs the cell across the grid over the period. + public void SetSrcRect(long handle, long gridW, long gridH, long cell, long period) + { + lock (_lock) + { + var o = GetOrCreate(handle); + o.SrcGridW = gridW < 1 ? 1 : gridW; o.SrcGridH = gridH < 1 ? 1 : gridH; + o.SrcCell = cell; o.SrcPeriod = period; o.SrcStart = -1; o.SrcAnim = true; + } + } + + /// Op 0x232 (anim-color): ping-pong the object's color/alpha toward + /// (packed 0xAARRGGBB) over ms — the pulsing glow. Distinct from static 0x202/0x203. + public void SetColorAnim(long handle, long period, long target) + { + lock (_lock) + { + var o = GetOrCreate(handle); + o.ColorPeriod = period; o.ColorTarget = target; o.ColorStart = -1; o.ColorAnim = true; + } + } public void ClearSurface(int slot) { lock (_lock) { _surfaces[slot] = (0, 0); } } // create-texture (blank) /// draw-texture bind (gfx_object_bind_draw): object draws surface