Implement looping SFX and immediate rotation opcodes
This commit is contained in:
@@ -384,6 +384,25 @@ public class GfxAnimationTests
|
||||
Assert.Equal((12L, 34L, 5L), o.V16c);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CurrentRotationSetter_ReplacesTheLiveAxisAngleImmediately()
|
||||
{
|
||||
var t = T();
|
||||
var scene = ScriptAssembler.Assemble(t, "CURRENTROTATION", new List<(int, Operand[])>
|
||||
{
|
||||
MovGI(1, 0xcb20), MovGI(2, 0), MovGI(3, 1), MovGI(4, 0), MovGI(5, 180),
|
||||
(0x1fe, new[] { G(1), G(2), G(3), G(4), G(5) }),
|
||||
Exit(),
|
||||
}, System.Array.Empty<string>());
|
||||
var vm = new VirtualMachine(scene, t, new RecordingHost());
|
||||
|
||||
vm.Run();
|
||||
|
||||
var o = vm.Gfx.TryGet(0xcb20)!;
|
||||
Assert.Equal((0.0, 1.0, 0.0, 180.0), o.RotationCurrent);
|
||||
Assert.False(o.RotationChannelEnabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetAnimClock_DispatchClearsGlobalServiceClock()
|
||||
{
|
||||
|
||||
@@ -41,6 +41,8 @@ public class SfxOpsTests
|
||||
{
|
||||
(0xb4, new[] { new Operand(0, 0x28), new Operand(0, 0) }),
|
||||
(0xb5, new[] { new Operand(0, 0) }),
|
||||
(0xb4, new[] { new Operand(0, 0x2aea), new Operand(0, 9) }),
|
||||
(0xba, new[] { new Operand(0, 9) }),
|
||||
(0x1cf, new[] { new Operand(0, 1) }),
|
||||
(0x2bf, new[] { new Operand(0, 4), new Operand(0, 0), new Operand(0, 100) }),
|
||||
(0xb6, new[] { new Operand(0, 0) }),
|
||||
@@ -54,8 +56,9 @@ public class SfxOpsTests
|
||||
vm.Run();
|
||||
|
||||
Assert.Equal("exit", vm.HaltReason);
|
||||
Assert.Equal((0x28L, 0), Assert.Single(host.SfxLoads));
|
||||
Assert.Equal(0, Assert.Single(host.SfxStarts));
|
||||
Assert.Equal([(0x28L, 0), (0x2aeaL, 9)], host.SfxLoads);
|
||||
Assert.Equal([0, 9], host.SfxStarts);
|
||||
Assert.Equal([(0, 0), (9, 1)], host.SfxStartRequests);
|
||||
Assert.Equal(1, Assert.Single(host.VoiceBgmDuckControls));
|
||||
Assert.Equal((4, 0, 100L), Assert.Single(host.ScheduledSfxStarts));
|
||||
Assert.Equal(0, Assert.Single(host.SfxReleases));
|
||||
|
||||
@@ -39,6 +39,7 @@ internal class RecordingHost : IHost
|
||||
public readonly List<(long Id, int PlaybackVariant, long DelayMs)> ScheduledVoiceRequests = new();
|
||||
public readonly List<long> VoiceBgmDuckControls = new();
|
||||
public readonly List<int> SfxStarts = new();
|
||||
public readonly List<(int Channel, int StartMode)> SfxStartRequests = new();
|
||||
public readonly List<(int Channel, int StartMode, long DelayMs)> ScheduledSfxStarts = new();
|
||||
public readonly List<int> SfxReleases = new();
|
||||
public readonly List<long> BgmTracks = new();
|
||||
@@ -171,7 +172,12 @@ internal class RecordingHost : IHost
|
||||
public void ScheduleVoicePlayback(long id, int playbackVariant, long delayMs)
|
||||
=> ScheduledVoiceRequests.Add((id, playbackVariant, delayMs));
|
||||
public void LoadSoundEffect(long resourceId, int channel) => SfxLoads.Add((resourceId, channel));
|
||||
public void StartSoundEffect(int channel) => SfxStarts.Add(channel);
|
||||
public void StartSoundEffect(int channel) => StartSoundEffect(channel, 0);
|
||||
public void StartSoundEffect(int channel, int startMode)
|
||||
{
|
||||
SfxStarts.Add(channel);
|
||||
SfxStartRequests.Add((channel, startMode));
|
||||
}
|
||||
public void ScheduleSoundEffectStart(int channel, int startMode, long delayMs)
|
||||
=> ScheduledSfxStarts.Add((channel, startMode, delayMs));
|
||||
public void ReleaseSoundEffect(int channel) => SfxReleases.Add(channel);
|
||||
|
||||
@@ -133,6 +133,9 @@ public interface IHost
|
||||
void SetVoiceBgmDuckControl(long flags) { }
|
||||
void LoadSoundEffect(long resourceId, int channel) { }
|
||||
void StartSoundEffect(int channel) { }
|
||||
// Ops 0xb5/0xba share the native channel-start worker. Mode 0 plays once; mode 1
|
||||
// rewinds the decoder at EOF. The one-argument seam remains for simple hosts.
|
||||
void StartSoundEffect(int channel, int startMode) => StartSoundEffect(channel);
|
||||
// Native SetDelay (op 0x2bf) starts an already-loaded channel after delayMs.
|
||||
// startMode is forwarded to the same worker used by immediate SFX starts.
|
||||
void ScheduleSoundEffectStart(int channel, int startMode, long delayMs) { }
|
||||
|
||||
@@ -979,6 +979,17 @@ public sealed class GfxState
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Op 0x1fe: immediately replace the object's current axis-angle rotation matrix at
|
||||
/// obj+0xec. Axis components and angle are retained as native floating-point values.</summary>
|
||||
public void SetCurrentRotation(long handle, (long X, long Y, long Z) axis, long angleDegrees)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var o = GetOrCreate(handle);
|
||||
o.RotationCurrent = (axis.X, axis.Y, axis.Z, angleDegrees);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Op 0x1ff: immediately replace the object's current translation matrix at obj+0x16c.</summary>
|
||||
public void SetCurrentTranslation(long handle, (long X, long Y, long Z) translation)
|
||||
{
|
||||
|
||||
@@ -2398,8 +2398,10 @@ public sealed class VirtualMachine
|
||||
_host.LoadSoundEffect(resourceId, channel);
|
||||
return pc + 1;
|
||||
}
|
||||
case "u0041D050": // 0xb5 / semantics: sfx-start
|
||||
_host.StartSoundEffect((int)Read(a[0])); return pc + 1;
|
||||
case "u0041D050": // 0xb5 / semantics: sfx-start-once
|
||||
_host.StartSoundEffect((int)Read(a[0]), 0); return pc + 1;
|
||||
case "sfx-start-loop": // 0xba / same worker, logical decoder rewind at EOF
|
||||
_host.StartSoundEffect((int)Read(a[0]), 1); return pc + 1;
|
||||
case "u0041D080": // 0xb6 / semantics: sfx-release
|
||||
{
|
||||
int channel = (int)Read(a[0]);
|
||||
@@ -2554,6 +2556,9 @@ public sealed class VirtualMachine
|
||||
case "u00420620": // upstream ABI label
|
||||
case "gfx-set-scale-current": // 0x1fd (handle)(sx%)(sy%)(sz%) -> current scale matrix
|
||||
Gfx.SetCurrentScale(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3]))); return pc + 1;
|
||||
case "set-current-rotation-axis-angle": // 0x1fe (handle)(axis x/y/z)(angle degrees)
|
||||
Gfx.SetCurrentRotation(Read(a[0]), (Read(a[1]), Read(a[2]), Read(a[3])), Read(a[4]));
|
||||
return pc + 1;
|
||||
case "set-adv-wait-indicator-handle": // 0x212 (layout)(retained handle)
|
||||
{
|
||||
TextHistory.SetWaitIndicatorObjectHandle((int)Read(a[0]), Read(a[1]));
|
||||
|
||||
Reference in New Issue
Block a user