Implement script-configured input bindings
This commit is contained in:
@@ -240,8 +240,11 @@ public partial class Main : Godot.Control
|
||||
// alongside the inherited SO000/SO001 state below. Full Phase-B SYSTEM4 replay will replace this
|
||||
// bootstrap as one unit; HISTORY depends on the 650x150 dimensions of layouts 2..6 for clipping.
|
||||
if (directSceneHarness)
|
||||
AdvTextLayoutBootstrap.ApplyLeadingDefinitionsAndResets(
|
||||
scripts!.RequireByName("SYSTEM4.BIN"), table, _vm.TextHistory);
|
||||
{
|
||||
var systemScript = scripts!.RequireByName("SYSTEM4.BIN");
|
||||
AdvTextLayoutBootstrap.ApplyLeadingDefinitionsAndResets(systemScript, table, _vm.TextHistory);
|
||||
InputBindingBootstrap.Apply(systemScript, _vm.InputBindings);
|
||||
}
|
||||
// SYSTEM4 loads the shared SO001 chrome sheet into surface slot 17 before any scene runs.
|
||||
// Seed that inherited retained-surface state without replaying the entrypoint's unrelated UI flow.
|
||||
if (directSceneHarness && resources.ResolveName("SO001.AGF") is { } systemChrome)
|
||||
@@ -418,18 +421,17 @@ public partial class Main : Godot.Control
|
||||
bool rawInputCallbackActive = _vm.IsRawInputCallbackActive;
|
||||
_vm.UpdatePointer(p.X, p.Y);
|
||||
int nativeButtonBit = mb.ButtonIndex == MouseButton.Left ? 0x1 : 0x2;
|
||||
int physicalButton = mb.ButtonIndex == MouseButton.Left ? 0 : 1;
|
||||
_vm.UpdateMouseButtonState(nativeButtonBit, mb.Pressed);
|
||||
_vm.UpdatePhysicalMouseButtonState(physicalButton, mb.Pressed);
|
||||
if (mb.Pressed && _host.IsModalMovieWaiting)
|
||||
{
|
||||
_host.SignalInput();
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
// AGE exposes the physical left button twice: raw mask 0x1 for the timed mouse callback,
|
||||
// and the configured primary action (default input callback index 4). Script-owned callback
|
||||
// loops consume both channels without releasing the enclosing ADV page wait.
|
||||
if (mb.ButtonIndex == MouseButton.Left && (advPageSuspended || rawInputCallbackActive))
|
||||
_vm.QueueInputCallback(mb.Pressed ? 4 : 10);
|
||||
// AGE exposes mouse buttons twice: op 0x108 reads the raw bitmask while op 0xff translates
|
||||
// the held physical button through the script-configured logical action map.
|
||||
if (mb.ButtonIndex == MouseButton.Left && mb.Pressed && _vm.TryActivatePointer(p.X, p.Y))
|
||||
{
|
||||
GetViewport().SetInputAsHandled();
|
||||
@@ -441,32 +443,43 @@ public partial class Main : Godot.Control
|
||||
&& !advPageSuspended && !rawInputCallbackActive) _host.SignalInput();
|
||||
return;
|
||||
}
|
||||
UpdateAgeInputCallback(e, "ui_down", 0);
|
||||
UpdateAgeInputCallback(e, "ui_left", 1);
|
||||
UpdateAgeInputCallback(e, "ui_up", 2);
|
||||
UpdateAgeInputCallback(e, "ui_right", 3);
|
||||
UpdateAgeInputCallback(e, "ui_accept", 4);
|
||||
UpdateAgeInputCallback(e, "ui_cancel", 5);
|
||||
if (_host.IsModalMovieWaiting
|
||||
&& (e.IsActionPressed("ui_accept") || e.IsActionPressed("ui_cancel")))
|
||||
if (e is InputEventKey gameplayKey && !gameplayKey.Echo
|
||||
&& Win32VirtualKeyTranslator.TryTranslate(gameplayKey, out int virtualKey))
|
||||
{
|
||||
_host.SignalInput();
|
||||
GetViewport().SetInputAsHandled();
|
||||
int action = _vm.UpdateKeyboardVirtualKeyState(virtualKey, gameplayKey.Pressed);
|
||||
if (gameplayKey.Pressed && IsAdvanceAction(action))
|
||||
{
|
||||
if (_host.IsModalMovieWaiting)
|
||||
{
|
||||
_host.SignalInput();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
else if (!_host.IsAdvPagePresentationSuspended && !_vm.IsRawInputCallbackActive)
|
||||
_host.SignalInput();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.IsActionPressed("ui_accept")
|
||||
&& !_host.IsAdvPagePresentationSuspended && !_vm.IsRawInputCallbackActive) _host.SignalInput();
|
||||
if (e is InputEventJoypadButton joyButton)
|
||||
{
|
||||
int actionMask = _vm.UpdateJoystickButtonState((int)joyButton.ButtonIndex, joyButton.Pressed);
|
||||
if (joyButton.Pressed && HasAdvanceAction(actionMask))
|
||||
{
|
||||
if (_host.IsModalMovieWaiting)
|
||||
{
|
||||
_host.SignalInput();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
else if (!_host.IsAdvPagePresentationSuspended && !_vm.IsRawInputCallbackActive)
|
||||
_host.SignalInput();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e is InputEventJoypadMotion joyMotion && (int)joyMotion.Axis is 0 or 1)
|
||||
_vm.UpdateJoystickAxisState((int)joyMotion.Axis, joyMotion.AxisValue);
|
||||
}
|
||||
|
||||
private void UpdateAgeInputCallback(InputEvent e, StringName action, int index)
|
||||
{
|
||||
if (e.IsActionPressed(action)) _vm.UpdateInputCallbackState(index, true);
|
||||
if (e.IsActionReleased(action))
|
||||
{
|
||||
_vm.UpdateInputCallbackState(index, false);
|
||||
_vm.QueueInputCallback(10); // common release callback registered by HISTORY/HIDEWIN
|
||||
}
|
||||
}
|
||||
private static bool IsAdvanceAction(int action) => action is 4 or 5;
|
||||
private static bool HasAdvanceAction(int mask) => (mask & ((1 << 4) | (1 << 5))) != 0;
|
||||
|
||||
private void ToggleDebugSceneLauncher()
|
||||
{
|
||||
@@ -1217,11 +1230,19 @@ public partial class Main : Godot.Control
|
||||
launcherSmoke.QueueFree();
|
||||
bool sleepMinimumOk = GodotAdvHost.NormalizeSleepMilliseconds(0, 1.0) == 1
|
||||
&& GodotAdvHost.NormalizeSleepMilliseconds(100, 1.0) == 100;
|
||||
ok &= launcherOk && sleepMinimumOk;
|
||||
bool inputTranslationOk = Win32VirtualKeyTranslator.TryTranslate(
|
||||
new InputEventKey { PhysicalKeycode = Key.Z }, out int zVk) && zVk == 0x5a
|
||||
&& Win32VirtualKeyTranslator.TryTranslate(
|
||||
new InputEventKey { PhysicalKeycode = Key.Up }, out int upVk) && upVk == 0x26
|
||||
&& Win32VirtualKeyTranslator.TryTranslate(
|
||||
new InputEventKey { PhysicalKeycode = Key.Ctrl }, out int ctrlVk) && ctrlVk == 0x11;
|
||||
ok &= launcherOk && sleepMinimumOk && inputTranslationOk;
|
||||
if (ok) GD.Print($"SELFTEST OK: threaded host matches headless ({actual.Count} lines, full handling); " +
|
||||
$"debug launcher catalog/UI smoke ({debugEntries.Count} packed scripts); sleep-min=1ms");
|
||||
$"debug launcher catalog/UI smoke ({debugEntries.Count} packed scripts); " +
|
||||
$"sleep-min=1ms; native-key-translation=ok");
|
||||
else GD.Print($"SELFTEST FAIL: threaded={actual.Count} vs headless={expected.Count}; " +
|
||||
$"debug-launcher={launcherOk}; sleep-min={sleepMinimumOk}");
|
||||
$"debug-launcher={launcherOk}; sleep-min={sleepMinimumOk}; " +
|
||||
$"native-key-translation={inputTranslationOk}");
|
||||
GetTree().Quit(ok ? 0 : 1);
|
||||
}
|
||||
|
||||
|
||||
88
godot/Win32VirtualKeyTranslator.cs
Normal file
88
godot/Win32VirtualKeyTranslator.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using Godot;
|
||||
|
||||
/// <summary>Translate Godot's layout-independent physical key identity into the Win32 VK namespace
|
||||
/// used by AGE's native DIK translation table and GetAsyncKeyState poller.</summary>
|
||||
internal static class Win32VirtualKeyTranslator
|
||||
{
|
||||
public static bool TryTranslate(InputEventKey input, out int virtualKey)
|
||||
{
|
||||
Key key = input.PhysicalKeycode is not (Key.None or Key.Unknown)
|
||||
? input.PhysicalKeycode : input.Keycode;
|
||||
long value = (long)key;
|
||||
if (value is >= (long)Key.Key0 and <= (long)Key.Key9
|
||||
|| value is >= (long)Key.A and <= (long)Key.Z)
|
||||
{
|
||||
virtualKey = (int)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtualKey = key switch
|
||||
{
|
||||
Key.Backspace => 0x08,
|
||||
Key.Tab => 0x09,
|
||||
Key.Enter or Key.KpEnter => 0x0d,
|
||||
Key.Shift => 0x10,
|
||||
Key.Ctrl => 0x11,
|
||||
Key.Alt => 0x12,
|
||||
Key.Pause => 0x13,
|
||||
Key.Capslock => 0x14,
|
||||
Key.Escape => 0x1b,
|
||||
Key.Space => 0x20,
|
||||
Key.Pageup => 0x21,
|
||||
Key.Pagedown => 0x22,
|
||||
Key.End => 0x23,
|
||||
Key.Home => 0x24,
|
||||
Key.Left => 0x25,
|
||||
Key.Up => 0x26,
|
||||
Key.Right => 0x27,
|
||||
Key.Down => 0x28,
|
||||
Key.Print or Key.Sysreq => 0x2c,
|
||||
Key.Insert => 0x2d,
|
||||
Key.Delete => 0x2e,
|
||||
Key.Meta => input.Location == KeyLocation.Right ? 0x5c : 0x5b,
|
||||
Key.Menu => 0x5d,
|
||||
Key.Kp0 => 0x60,
|
||||
Key.Kp1 => 0x61,
|
||||
Key.Kp2 => 0x62,
|
||||
Key.Kp3 => 0x63,
|
||||
Key.Kp4 => 0x64,
|
||||
Key.Kp5 => 0x65,
|
||||
Key.Kp6 => 0x66,
|
||||
Key.Kp7 => 0x67,
|
||||
Key.Kp8 => 0x68,
|
||||
Key.Kp9 => 0x69,
|
||||
Key.KpMultiply => 0x6a,
|
||||
Key.KpAdd => 0x6b,
|
||||
Key.KpSubtract => 0x6d,
|
||||
Key.KpPeriod => 0x6e,
|
||||
Key.KpDivide => 0x6f,
|
||||
Key.F1 => 0x70,
|
||||
Key.F2 => 0x71,
|
||||
Key.F3 => 0x72,
|
||||
Key.F4 => 0x73,
|
||||
Key.F5 => 0x74,
|
||||
Key.F6 => 0x75,
|
||||
Key.F7 => 0x76,
|
||||
Key.F8 => 0x77,
|
||||
Key.F9 => 0x78,
|
||||
Key.F10 => 0x79,
|
||||
Key.F11 => 0x7a,
|
||||
Key.F12 => 0x7b,
|
||||
Key.Numlock => 0x90,
|
||||
Key.Scrolllock => 0x91,
|
||||
Key.Semicolon or Key.Colon => 0xba,
|
||||
Key.Equal or Key.Plus => 0xbb,
|
||||
Key.Comma or Key.Less => 0xbc,
|
||||
Key.Minus or Key.Underscore => 0xbd,
|
||||
Key.Period or Key.Greater => 0xbe,
|
||||
Key.Slash or Key.Question => 0xbf,
|
||||
Key.Quoteleft or Key.Asciitilde => 0xc0,
|
||||
Key.Bracketleft or Key.Braceleft => 0xdb,
|
||||
Key.Backslash or Key.Bar => 0xdc,
|
||||
Key.Bracketright or Key.Braceright => 0xdd,
|
||||
Key.Apostrophe or Key.Quotedbl => 0xde,
|
||||
_ => 0,
|
||||
};
|
||||
return virtualKey != 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user