Implement ADV History voice replay
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using Age.Engine.Hosting;
|
||||
using Age.Engine.Model;
|
||||
using Age.Engine.Sys4;
|
||||
using Age.Engine.Vm;
|
||||
@@ -10,6 +11,18 @@ public class HistoryDataOpsTests
|
||||
private static Operand G(int address) => new(T_GINT, address);
|
||||
private static Operand L(int address) => new(T_LINT, address);
|
||||
|
||||
private sealed class HistoryVoiceHost : RecordingHost
|
||||
{
|
||||
public AdvAutoWaitState AutoState;
|
||||
|
||||
public override void WaitForInput(int layoutSlot, Func<bool> serviceInputCallback,
|
||||
Func<AdvAutoWaitState> autoWaitState)
|
||||
{
|
||||
AutoState = autoWaitState();
|
||||
Waits++;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyInlineIntArrayWritesConsecutiveSignedDwords()
|
||||
{
|
||||
@@ -161,4 +174,49 @@ public class HistoryDataOpsTests
|
||||
Assert.False(history.TryFindVoicePair(0, out long voiceId, out long voiceArgument));
|
||||
Assert.Equal((-1L, -1L), (voiceId, voiceArgument));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlayHistoryVoiceUsesNativeVariantAndAutoServices()
|
||||
{
|
||||
var history = new AdvTextHistory();
|
||||
var script = ScriptAssembler.Assemble(Table, "HISTORY_VOICE_REPLAY",
|
||||
new List<(int, Operand[])>
|
||||
{
|
||||
(0x1bd, new[] { I(77) }),
|
||||
(0x72, new[] { I(1) }),
|
||||
(0x2, Array.Empty<Operand>()),
|
||||
}, Array.Empty<string>());
|
||||
var host = new HistoryVoiceHost();
|
||||
var vm = new VirtualMachine(script, Table, host, textHistory: history);
|
||||
|
||||
vm.Run();
|
||||
|
||||
Assert.Equal("exit", vm.HaltReason);
|
||||
Assert.Equal(new long[] { 77 }, host.Voices);
|
||||
Assert.Equal(new[] { (77L, 1) }, host.VoiceRequests);
|
||||
Assert.True(host.AutoState.VoicePending);
|
||||
var voice = Assert.Single(history.Records);
|
||||
Assert.Equal(AdvTextHistoryRecordKind.Voice, voice.Kind);
|
||||
Assert.Equal((77L, 1L), (voice.Value, voice.AuxValue));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SuppressedHistoryReplayDoesNotRecordItself()
|
||||
{
|
||||
var history = new AdvTextHistory();
|
||||
history.SetRecordingEnabled(false);
|
||||
var script = ScriptAssembler.Assemble(Table, "HISTORY_VOICE_REPLAY_SUPPRESSED",
|
||||
new List<(int, Operand[])>
|
||||
{
|
||||
(0x1bd, new[] { I(88) }),
|
||||
(0x2, Array.Empty<Operand>()),
|
||||
}, Array.Empty<string>());
|
||||
var host = new RecordingHost();
|
||||
var vm = new VirtualMachine(script, Table, host, textHistory: history);
|
||||
|
||||
vm.Run();
|
||||
|
||||
Assert.Equal(new[] { (88L, 1) }, host.VoiceRequests);
|
||||
Assert.Empty(history.Records);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ internal class RecordingHost : IHost
|
||||
public readonly List<AdvWaitIndicatorConfig> WaitIndicators = new();
|
||||
public readonly List<long> SleptDurations = new();
|
||||
public readonly List<(long Resource, int Channel)> SfxLoads = new();
|
||||
public readonly List<long> Voices = new();
|
||||
public readonly List<(long Id, int PlaybackVariant)> VoiceRequests = new();
|
||||
public readonly List<int> SfxStarts = new();
|
||||
public readonly List<int> SfxReleases = new();
|
||||
public readonly List<(int Target, long Duration)> BgmFades = new();
|
||||
@@ -101,7 +103,12 @@ internal class RecordingHost : IHost
|
||||
public void DrawTexture(int slot, int sx, int sy, int w, int h, int dx, int dy) { }
|
||||
public (int Width, int Height) GetTextureSize(int slot) => (0, 0);
|
||||
public void PlayBgm(long id) { }
|
||||
public void PlayVoice(long id) { }
|
||||
public void PlayVoice(long id) => Voices.Add(id);
|
||||
public void PlayVoice(long id, int playbackVariant)
|
||||
{
|
||||
Voices.Add(id);
|
||||
VoiceRequests.Add((id, playbackVariant));
|
||||
}
|
||||
public void LoadSoundEffect(long resourceId, int channel) => SfxLoads.Add((resourceId, channel));
|
||||
public void StartSoundEffect(int channel) => SfxStarts.Add(channel);
|
||||
public void ReleaseSoundEffect(int channel) => SfxReleases.Add(channel);
|
||||
|
||||
@@ -72,6 +72,9 @@ public interface IHost
|
||||
(int Width, int Height) GetTextureSize(int slot);
|
||||
void PlayBgm(long id);
|
||||
void PlayVoice(long id);
|
||||
// Native voice playback retains a second start argument: ordinary dialogue passes 0,
|
||||
// while History replay (0x1bd) passes 1. Existing non-audio hosts may ignore it.
|
||||
void PlayVoice(long id, int playbackVariant) => PlayVoice(id);
|
||||
void LoadSoundEffect(long resourceId, int channel) { }
|
||||
void StartSoundEffect(int channel) { }
|
||||
void ReleaseSoundEffect(int channel) { }
|
||||
|
||||
@@ -882,7 +882,12 @@ public sealed class VirtualMachine
|
||||
case "play-voice":
|
||||
_autoVoicePending = true;
|
||||
TextHistory.AppendVoice(Read(a[0]), 0, _advTextStyle);
|
||||
_host.PlayVoice(Read(a[0])); return pc + 1;
|
||||
_host.PlayVoice(Read(a[0]), 0); return pc + 1;
|
||||
case "play-history-voice": // 0x1bd: native voice start/history argument is one
|
||||
case "u0041D910":
|
||||
_autoVoicePending = true;
|
||||
TextHistory.AppendVoice(Read(a[0]), 1, _advTextStyle);
|
||||
_host.PlayVoice(Read(a[0]), 1); return pc + 1;
|
||||
case "play-sound-effect": // 0xb4 / semantics: sfx-load
|
||||
_host.LoadSoundEffect(Read(a[0]), (int)Read(a[1])); return pc + 1;
|
||||
case "u0041D050": // 0xb5 / semantics: sfx-start
|
||||
|
||||
Reference in New Issue
Block a user