Implement ROOM fades and character voices

This commit is contained in:
gamer147
2026-07-21 00:47:29 -04:00
parent 0dfba316f1
commit f6479209ea
18 changed files with 442 additions and 71 deletions

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Age.Engine.Model;
using Age.Engine.Sys4;
using Age.Engine.Vm;
using Xunit;
public class CoreScalarAndScreenTransitionOpsTests
{
private const int Imm = 0, GlobalInt = 3, LocalInt = 9;
[Fact]
public void ZeroIntRange_ClearsEveryConsecutiveDestinationCell()
{
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
(int, Operand[]) Mov(int address, long value)
=> (0x55, new[] { new Operand(GlobalInt, address), new Operand(Imm, value) });
var ops = new List<(int, Operand[])>
{
Mov(0x2e49, 7), Mov(0x2e4a, 8), Mov(0x2e4b, 9),
(0x6c, new[] { new Operand(GlobalInt, 0x2e49), new Operand(Imm, 3) }),
(0x2, Array.Empty<Operand>()),
};
var vm = new VirtualMachine(ScriptAssembler.Assemble(table, "ZERO_RANGE", ops, Array.Empty<string>()), table,
new RecordingHost());
vm.Run();
Assert.All(Enumerable.Range(0x2e49, 3), address => Assert.Equal(0, vm.Globals[address]));
}
[Fact]
public void RandomModulo_WritesAValueInsideTheNativeRange()
{
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
var ops = new List<(int, Operand[])>
{
(0x60, new[] { new Operand(LocalInt, 0), new Operand(Imm, 4) }),
(0x55, new[] { new Operand(GlobalInt, 0x100), new Operand(LocalInt, 0) }),
(0x2, Array.Empty<Operand>()),
};
var vm = new VirtualMachine(ScriptAssembler.Assemble(table, "RANDOM_MODULO", ops, Array.Empty<string>()), table,
new RecordingHost());
vm.Run();
Assert.InRange(vm.Globals[0x100], 0, 3);
}
[Fact]
public void CrossfadeSurfaces_ForwardsBothCapturedSlotsAndTimingArgument()
{
var table = OpcodeTableJson.Load(Paths.OpcodesJson);
var script = ScriptAssembler.Assemble(table, "SURFACE_CROSSFADE", new List<(int, Operand[])>
{
(0x25, new[] { new Operand(Imm, 1), new Operand(Imm, 2), new Operand(Imm, 10) }),
(0x2, Array.Empty<Operand>()),
}, Array.Empty<string>());
var host = new RecordingHost();
new VirtualMachine(script, table, host).Run();
Assert.Equal((1, 2, 10L), Assert.Single(host.SurfaceCrossfades));
Assert.Equal(160, LegacyScreenTransitionTiming.DurationMilliseconds(10));
Assert.Equal(480, LegacyScreenTransitionTiming.DurationMilliseconds(30));
Assert.Equal(1024, LegacyScreenTransitionTiming.DurationMilliseconds(65));
}
}

View File

@@ -168,7 +168,7 @@ public class Sys4AssetStoreTests
}
[Fact]
public void Sc0000AndTitleAudioPayloadsReadDirectlyFromTheirNativeAddressSpaces()
public void Sc0000RoomAndTitleAudioPayloadsReadDirectlyFromTheirNativeAddressSpaces()
{
var catalog = Sys4AssetCatalog.Load(Paths.Sys4Ini);
var archiveOnly = new Sys4AssetStore(catalog, Paths.GameDir);
@@ -178,10 +178,15 @@ public class Sys4AssetStoreTests
Assert.Equal("BGM005.OGG", bgm?.Name);
AssertOgg(resources.ReadAudio(bgm!));
var voice = resources.Resolve("SC0000", 0x24);
var voice = resources.ResolveVoice("SC0000", 0x24);
Assert.Equal("MAN999.OGG", voice?.Name);
AssertOgg(resources.ReadAudio(voice!));
var roomVoice = resources.ResolveVoice("ROOM", 0x3365);
Assert.Equal("EUA0016.OGG", roomVoice?.Name);
AssertOgg(resources.ReadAudio(roomVoice!));
Assert.Null(resources.ResolveVoice("ROOM", 0x337e)); // SO001.AGF is not voice audio.
var sfx = resources.ResolveSoundEffect(0x28);
Assert.Equal("E0808.WAV", sfx?.Name);
var wav = resources.ReadAudio(sfx!);

View File

@@ -39,6 +39,7 @@ internal class RecordingHost : IHost
public readonly List<(long Resource, int Surface, long Flags)> ModalMovies = new();
public readonly List<int> ClearedRenderTargets = new();
public readonly List<(int First, int Count)> ReleasedSurfaceRanges = new();
public readonly List<(int Source, int Target, long Interval)> SurfaceCrossfades = new();
public readonly List<bool> MessageSkipChanges = new();
public readonly List<long> CursorResources = new();
public readonly List<bool> AdvPagePresentationSuspended = new();
@@ -109,6 +110,8 @@ internal class RecordingHost : IHost
gfx.StartForegroundTransitions(100);
gfx.CompleteForegroundTransitions(100);
}
public void CrossfadeSurfaces(GfxState gfx, int sourceSurface, int targetSurface, long intervalArgument)
=> SurfaceCrossfades.Add((sourceSurface, targetSurface, intervalArgument));
public void CreateTexture(int slot, int w, int h) { }
public void SetTexture(long resId, int slot) { }
public void ClearRenderTarget(int surfaceSlot) => ClearedRenderTargets.Add(surfaceSlot);