Split Main input into partial class
This commit is contained in:
309
godot/Main.Input.cs
Normal file
309
godot/Main.Input.cs
Normal file
@@ -0,0 +1,309 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
using Age.Engine.Diagnostics;
|
||||
using Age.Engine.Vm;
|
||||
|
||||
public partial class Main
|
||||
{
|
||||
private ImageTexture? _ageCursorTexture;
|
||||
private Label _locatorHud = null!;
|
||||
private FullwidthTextEditorDialog? _fullwidthTextEditor;
|
||||
private DebugSceneLauncher? _debugSceneLauncher;
|
||||
private IReadOnlyList<DebugSceneEntry> _debugSceneEntries = System.Array.Empty<DebugSceneEntry>();
|
||||
private PageLocatorState _locator = null!;
|
||||
private bool _locatorHudVisible;
|
||||
|
||||
// _Input (not _UnhandledInput): the root Control consumes mouse clicks as GUI input before they
|
||||
// reach _UnhandledInput, so clicks were swallowed while keyboard ui_accept still got through.
|
||||
public override void _Input(InputEvent e)
|
||||
{
|
||||
if (_selftest) return;
|
||||
if (e is InputEventKey key && key.Pressed && !key.Echo && key.Keycode == Key.F2)
|
||||
{
|
||||
_locatorHudVisible = !_locatorHudVisible;
|
||||
_locatorHud.Visible = _locatorHudVisible;
|
||||
if (_locatorHudVisible) _locatorHud.Text = _locator.CurrentDisplay;
|
||||
return;
|
||||
}
|
||||
if (e is InputEventKey copy && copy.Pressed && !copy.Echo && copy.Keycode == Key.F3)
|
||||
{
|
||||
DisplayServer.ClipboardSet(_locator.CurrentDisplay);
|
||||
_locatorHud.Text = _locator.CurrentDisplay + " · copied";
|
||||
return;
|
||||
}
|
||||
if (e is InputEventKey debugKey && debugKey.Pressed && !debugKey.Echo && debugKey.Keycode == Key.F4)
|
||||
{
|
||||
ToggleDebugSceneLauncher();
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
if (e is InputEventKey diagnosticKey && diagnosticKey.Keycode == Key.F6)
|
||||
{
|
||||
if (diagnosticKey.Pressed && !diagnosticKey.Echo) CaptureStallDiagnostic();
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
if (_debugSceneLauncher?.Visible == true)
|
||||
{
|
||||
if (e is InputEventKey escape && escape.Pressed && !escape.Echo && escape.Keycode == Key.Escape)
|
||||
{
|
||||
_debugSceneLauncher.Hide();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e is InputEventMouseMotion motion)
|
||||
{
|
||||
var p = ToNativeScreen(motion.Position);
|
||||
_vm.UpdatePointer(p.X, p.Y);
|
||||
return;
|
||||
}
|
||||
if (e is InputEventMouseButton wheel
|
||||
&& wheel.Pressed
|
||||
&& (wheel.ButtonIndex == MouseButton.WheelUp || wheel.ButtonIndex == MouseButton.WheelDown))
|
||||
{
|
||||
// WM_MOUSEWHEEL supplies signed multiples of WHEEL_DELTA (120). AGE accumulates that
|
||||
// value until op 0x10d reads and clears it; HISTORY currently uses only its sign.
|
||||
int direction = wheel.ButtonIndex == MouseButton.WheelUp ? 1 : -1;
|
||||
int steps = System.Math.Max(1, (int)System.Math.Round(wheel.Factor));
|
||||
_vm.QueueMouseWheelDelta(direction * 120 * steps);
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
if (e is InputEventMouseButton mb
|
||||
&& (mb.ButtonIndex == MouseButton.Left || mb.ButtonIndex == MouseButton.Right))
|
||||
{
|
||||
var p = ToNativeScreen(mb.Position);
|
||||
bool advPageSuspended = _host.IsAdvPagePresentationSuspended;
|
||||
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);
|
||||
int action = _vm.UpdatePhysicalMouseButtonState(physicalButton, mb.Pressed);
|
||||
if (mb.Pressed && _host.IsModalMovieWaiting)
|
||||
{
|
||||
_host.SignalInput();
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
// Native blocking effect services poll logical action 4 before ADV hotspot dispatch.
|
||||
// Consume the trigger here so a retained hotspot under the transition cannot steal it.
|
||||
if (mb.Pressed && action == 4 && _host.IsTransitionWaiting)
|
||||
{
|
||||
_host.SignalInput();
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
// 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.Pressed && action >= 0 && _vm.TryActivateInputActions(1 << action))
|
||||
{
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
if (mb.ButtonIndex == MouseButton.Left && mb.Pressed && _vm.TryActivatePointer(p.X, p.Y))
|
||||
{
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
// Modal callback scripts return through their own bytecode. Signaling the enclosing ADV wait
|
||||
// here would also advance the restored dialogue page after HISTORY/HIDEWIN exits.
|
||||
if (mb.ButtonIndex == MouseButton.Left && mb.Pressed
|
||||
&& !advPageSuspended && !rawInputCallbackActive) _host.SignalInput();
|
||||
return;
|
||||
}
|
||||
if (e is InputEventKey gameplayKey && !gameplayKey.Echo
|
||||
&& Win32VirtualKeyTranslator.TryTranslate(gameplayKey, out int virtualKey))
|
||||
{
|
||||
int action = _vm.UpdateKeyboardVirtualKeyState(virtualKey, gameplayKey.Pressed);
|
||||
if (gameplayKey.Pressed && action == 4 && _host.IsTransitionWaiting)
|
||||
{
|
||||
_host.SignalInput();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
else if (gameplayKey.Pressed && action >= 0 && _vm.TryActivateInputActions(1 << action))
|
||||
{
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
else if (gameplayKey.Pressed && IsAdvanceAction(action))
|
||||
{
|
||||
if (_host.IsModalMovieWaiting)
|
||||
{
|
||||
_host.SignalInput();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
else if (!_host.IsAdvPagePresentationSuspended && !_vm.IsRawInputCallbackActive)
|
||||
_host.SignalInput();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e is InputEventJoypadButton joyButton)
|
||||
{
|
||||
int actionMask = _vm.UpdateJoystickButtonState((int)joyButton.ButtonIndex, joyButton.Pressed);
|
||||
if (joyButton.Pressed && (actionMask & (1 << 4)) != 0 && _host.IsTransitionWaiting)
|
||||
{
|
||||
_host.SignalInput();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
else if (joyButton.Pressed && _vm.TryActivateInputActions(actionMask))
|
||||
{
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
else 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 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()
|
||||
{
|
||||
if (_debugSceneLauncher == null) return;
|
||||
if (_debugSceneLauncher.Visible)
|
||||
{
|
||||
_debugSceneLauncher.Hide();
|
||||
return;
|
||||
}
|
||||
if (!TryGetTitleDebugFrame(out var frame, out string reason))
|
||||
{
|
||||
_status.Text = reason;
|
||||
GD.Print($"[debug-launcher] unavailable: {reason}");
|
||||
return;
|
||||
}
|
||||
_debugSceneLauncher.Open(_debugSceneEntries, string.Join(" > ", frame.CallStack));
|
||||
}
|
||||
|
||||
private void LaunchDebugScene(DebugSceneEntry entry)
|
||||
{
|
||||
if (_debugSceneLauncher == null || _scripts == null) return;
|
||||
if (!TryGetTitleDebugFrame(out var frame, out string reason))
|
||||
{
|
||||
_debugSceneLauncher.SetStatus(reason);
|
||||
return;
|
||||
}
|
||||
if (!entry.Launchable || _scripts.GetById(entry.PackedId) == null)
|
||||
{
|
||||
_debugSceneLauncher.SetStatus("The selected packed script could not be parsed; no state was changed.");
|
||||
return;
|
||||
}
|
||||
|
||||
var coordinatorWrites = new Dictionary<int, long>
|
||||
{
|
||||
[0] = 1,
|
||||
[0xaba5c] = -1,
|
||||
[0x62ccf] = 0,
|
||||
[0x699] = entry.PackedId,
|
||||
};
|
||||
if (!_vm.TryRequestDebugFrameReturn(frame.FrameId, coordinatorWrites))
|
||||
{
|
||||
_debugSceneLauncher.SetStatus("TITLE changed frames before launch; reopen the launcher and try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
_timeline?.Event("debug-scene-launch-request", new()
|
||||
{
|
||||
["script"] = entry.Name,
|
||||
["packed_id"] = entry.PackedId,
|
||||
});
|
||||
GD.Print($"[debug-launcher] SYSTEM4 dispatch requested: {entry.Name} (0x{entry.PackedId:x8})");
|
||||
_debugSceneLauncher.Hide();
|
||||
// ADV waits need an explicit wake; TITLE's actual menu is a 1 ms sleep/poll loop and will consume
|
||||
// the request at its next opcode boundary without leaving a stale input signal for the child scene.
|
||||
if (_host.IsWaiting) _host.SignalInput();
|
||||
}
|
||||
|
||||
private bool TryGetTitleDebugFrame(out DebugFrameSnapshot frame, out string reason)
|
||||
{
|
||||
frame = _vm.DebugFrame!;
|
||||
if (_done || frame == null)
|
||||
{
|
||||
reason = "Available only while TITLE is the active SYSTEM4 child.";
|
||||
return false;
|
||||
}
|
||||
if (frame.CallStack.Count != 2
|
||||
|| !frame.CallStack[0].Equals("SYSTEM4.BIN", System.StringComparison.OrdinalIgnoreCase)
|
||||
|| !frame.CallStack[1].Equals("TITLE.BIN", System.StringComparison.OrdinalIgnoreCase)
|
||||
|| !frame.CurrentScript.Equals("TITLE.BIN", System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
reason = "Refused: the active stack is not SYSTEM4 > TITLE.";
|
||||
return false;
|
||||
}
|
||||
reason = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
private (int X, int Y) ToNativeScreen(Vector2 position)
|
||||
{
|
||||
// Godot reports input in viewport coordinates after content scaling. This ratio is therefore
|
||||
// normally identity, while remaining correct for any viewport expansion policy.
|
||||
Vector2 size = GetViewport().GetVisibleRect().Size;
|
||||
if (size.X <= 0 || size.Y <= 0) return (0, 0);
|
||||
return ((int)System.Math.Floor(position.X * _screenWidth / size.X),
|
||||
(int)System.Math.Floor(position.Y * _screenHeight / size.Y));
|
||||
}
|
||||
|
||||
public void SetAgeCursor(byte[] rgba, int width, int height, int hotspotX, int hotspotY)
|
||||
{
|
||||
var image = Image.CreateFromData(width, height, false, Image.Format.Rgba8, rgba);
|
||||
_ageCursorTexture = ImageTexture.CreateFromImage(image);
|
||||
Input.SetCustomMouseCursor(_ageCursorTexture, Input.CursorShape.Arrow,
|
||||
new Vector2(hotspotX, hotspotY));
|
||||
}
|
||||
|
||||
public void ClearAgeCursor()
|
||||
{
|
||||
Input.SetCustomMouseCursor(null, Input.CursorShape.Arrow);
|
||||
_ageCursorTexture = null;
|
||||
}
|
||||
|
||||
public void ShowAgeDiagnostic(string text, string caption)
|
||||
{
|
||||
try
|
||||
{
|
||||
OS.Alert(text, caption);
|
||||
}
|
||||
catch (System.Exception error)
|
||||
{
|
||||
GD.PushError($"[diagnostic] native alert failed: {error.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_host?.CompleteDiagnosticMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowAgeFullwidthTextEditor(string initialText)
|
||||
{
|
||||
if (_fullwidthTextEditor != null)
|
||||
{
|
||||
GD.PushWarning("[inputname] replaced an already-open full-width text editor");
|
||||
_fullwidthTextEditor.QueueFree();
|
||||
}
|
||||
|
||||
var editor = new FullwidthTextEditorDialog();
|
||||
_fullwidthTextEditor = editor;
|
||||
editor.EditCompleted += (accepted, text) =>
|
||||
{
|
||||
if (_fullwidthTextEditor == editor) _fullwidthTextEditor = null;
|
||||
_host?.CompleteFullwidthTextEdit(accepted, text);
|
||||
editor.QueueFree();
|
||||
};
|
||||
AddChild(editor);
|
||||
editor.Open(initialText);
|
||||
}
|
||||
|
||||
}
|
||||
299
godot/Main.cs
299
godot/Main.cs
@@ -25,20 +25,13 @@ public partial class Main : Godot.Control
|
||||
private WindowLaunchOptions _windowOptions;
|
||||
private Sys4AssetCatalog _catalog = null!;
|
||||
private IAssetStore _assetStore = null!;
|
||||
private ImageTexture? _ageCursorTexture;
|
||||
private Label _status = null!;
|
||||
private Label _locatorHud = null!;
|
||||
private VirtualMachine _vm = null!;
|
||||
private GodotAdvHost _host = null!;
|
||||
private IDisposable? _glyphRasterizerOwner;
|
||||
private FullwidthTextEditorDialog? _fullwidthTextEditor;
|
||||
private Sys4ScriptProvider? _scripts;
|
||||
private DebugSceneLauncher? _debugSceneLauncher;
|
||||
private IReadOnlyList<DebugSceneEntry> _debugSceneEntries = System.Array.Empty<DebugSceneEntry>();
|
||||
private readonly Age.Engine.Hosting.FrameClock _clock = new();
|
||||
private GodotTraceSink _trace = null!;
|
||||
private PageLocatorState _locator = null!;
|
||||
private bool _locatorHudVisible;
|
||||
private Age.Engine.Diagnostics.HistogramTraceSink? _hist; // --trace-histogram: profile the real run
|
||||
private string? _histFile;
|
||||
private Age.Engine.Model.OpcodeTable? _table;
|
||||
@@ -577,162 +570,6 @@ public partial class Main : Godot.Control
|
||||
finally { perf?.EndFrame(); }
|
||||
}
|
||||
|
||||
// _Input (not _UnhandledInput): the root Control consumes mouse clicks as GUI input before they
|
||||
// reach _UnhandledInput, so clicks were swallowed while keyboard ui_accept still got through.
|
||||
public override void _Input(InputEvent e)
|
||||
{
|
||||
if (_selftest) return;
|
||||
if (e is InputEventKey key && key.Pressed && !key.Echo && key.Keycode == Key.F2)
|
||||
{
|
||||
_locatorHudVisible = !_locatorHudVisible;
|
||||
_locatorHud.Visible = _locatorHudVisible;
|
||||
if (_locatorHudVisible) _locatorHud.Text = _locator.CurrentDisplay;
|
||||
return;
|
||||
}
|
||||
if (e is InputEventKey copy && copy.Pressed && !copy.Echo && copy.Keycode == Key.F3)
|
||||
{
|
||||
DisplayServer.ClipboardSet(_locator.CurrentDisplay);
|
||||
_locatorHud.Text = _locator.CurrentDisplay + " · copied";
|
||||
return;
|
||||
}
|
||||
if (e is InputEventKey debugKey && debugKey.Pressed && !debugKey.Echo && debugKey.Keycode == Key.F4)
|
||||
{
|
||||
ToggleDebugSceneLauncher();
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
if (e is InputEventKey diagnosticKey && diagnosticKey.Keycode == Key.F6)
|
||||
{
|
||||
if (diagnosticKey.Pressed && !diagnosticKey.Echo) CaptureStallDiagnostic();
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
if (_debugSceneLauncher?.Visible == true)
|
||||
{
|
||||
if (e is InputEventKey escape && escape.Pressed && !escape.Echo && escape.Keycode == Key.Escape)
|
||||
{
|
||||
_debugSceneLauncher.Hide();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e is InputEventMouseMotion motion)
|
||||
{
|
||||
var p = ToNativeScreen(motion.Position);
|
||||
_vm.UpdatePointer(p.X, p.Y);
|
||||
return;
|
||||
}
|
||||
if (e is InputEventMouseButton wheel
|
||||
&& wheel.Pressed
|
||||
&& (wheel.ButtonIndex == MouseButton.WheelUp || wheel.ButtonIndex == MouseButton.WheelDown))
|
||||
{
|
||||
// WM_MOUSEWHEEL supplies signed multiples of WHEEL_DELTA (120). AGE accumulates that
|
||||
// value until op 0x10d reads and clears it; HISTORY currently uses only its sign.
|
||||
int direction = wheel.ButtonIndex == MouseButton.WheelUp ? 1 : -1;
|
||||
int steps = System.Math.Max(1, (int)System.Math.Round(wheel.Factor));
|
||||
_vm.QueueMouseWheelDelta(direction * 120 * steps);
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
if (e is InputEventMouseButton mb
|
||||
&& (mb.ButtonIndex == MouseButton.Left || mb.ButtonIndex == MouseButton.Right))
|
||||
{
|
||||
var p = ToNativeScreen(mb.Position);
|
||||
bool advPageSuspended = _host.IsAdvPagePresentationSuspended;
|
||||
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);
|
||||
int action = _vm.UpdatePhysicalMouseButtonState(physicalButton, mb.Pressed);
|
||||
if (mb.Pressed && _host.IsModalMovieWaiting)
|
||||
{
|
||||
_host.SignalInput();
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
// Native blocking effect services poll logical action 4 before ADV hotspot dispatch.
|
||||
// Consume the trigger here so a retained hotspot under the transition cannot steal it.
|
||||
if (mb.Pressed && action == 4 && _host.IsTransitionWaiting)
|
||||
{
|
||||
_host.SignalInput();
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
// 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.Pressed && action >= 0 && _vm.TryActivateInputActions(1 << action))
|
||||
{
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
if (mb.ButtonIndex == MouseButton.Left && mb.Pressed && _vm.TryActivatePointer(p.X, p.Y))
|
||||
{
|
||||
GetViewport().SetInputAsHandled();
|
||||
return;
|
||||
}
|
||||
// Modal callback scripts return through their own bytecode. Signaling the enclosing ADV wait
|
||||
// here would also advance the restored dialogue page after HISTORY/HIDEWIN exits.
|
||||
if (mb.ButtonIndex == MouseButton.Left && mb.Pressed
|
||||
&& !advPageSuspended && !rawInputCallbackActive) _host.SignalInput();
|
||||
return;
|
||||
}
|
||||
if (e is InputEventKey gameplayKey && !gameplayKey.Echo
|
||||
&& Win32VirtualKeyTranslator.TryTranslate(gameplayKey, out int virtualKey))
|
||||
{
|
||||
int action = _vm.UpdateKeyboardVirtualKeyState(virtualKey, gameplayKey.Pressed);
|
||||
if (gameplayKey.Pressed && action == 4 && _host.IsTransitionWaiting)
|
||||
{
|
||||
_host.SignalInput();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
else if (gameplayKey.Pressed && action >= 0 && _vm.TryActivateInputActions(1 << action))
|
||||
{
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
else if (gameplayKey.Pressed && IsAdvanceAction(action))
|
||||
{
|
||||
if (_host.IsModalMovieWaiting)
|
||||
{
|
||||
_host.SignalInput();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
else if (!_host.IsAdvPagePresentationSuspended && !_vm.IsRawInputCallbackActive)
|
||||
_host.SignalInput();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e is InputEventJoypadButton joyButton)
|
||||
{
|
||||
int actionMask = _vm.UpdateJoystickButtonState((int)joyButton.ButtonIndex, joyButton.Pressed);
|
||||
if (joyButton.Pressed && (actionMask & (1 << 4)) != 0 && _host.IsTransitionWaiting)
|
||||
{
|
||||
_host.SignalInput();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
else if (joyButton.Pressed && _vm.TryActivateInputActions(actionMask))
|
||||
{
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
else 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 static bool IsAdvanceAction(int action) => action is 4 or 5;
|
||||
private static bool HasAdvanceAction(int mask) => (mask & ((1 << 4) | (1 << 5))) != 0;
|
||||
|
||||
private void CaptureStallDiagnostic(GodotTraceSnapshot? traceOverride = null,
|
||||
string snapshotKind = "stall")
|
||||
{
|
||||
@@ -829,142 +666,6 @@ public partial class Main : Godot.Control
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleDebugSceneLauncher()
|
||||
{
|
||||
if (_debugSceneLauncher == null) return;
|
||||
if (_debugSceneLauncher.Visible)
|
||||
{
|
||||
_debugSceneLauncher.Hide();
|
||||
return;
|
||||
}
|
||||
if (!TryGetTitleDebugFrame(out var frame, out string reason))
|
||||
{
|
||||
_status.Text = reason;
|
||||
GD.Print($"[debug-launcher] unavailable: {reason}");
|
||||
return;
|
||||
}
|
||||
_debugSceneLauncher.Open(_debugSceneEntries, string.Join(" > ", frame.CallStack));
|
||||
}
|
||||
|
||||
private void LaunchDebugScene(DebugSceneEntry entry)
|
||||
{
|
||||
if (_debugSceneLauncher == null || _scripts == null) return;
|
||||
if (!TryGetTitleDebugFrame(out var frame, out string reason))
|
||||
{
|
||||
_debugSceneLauncher.SetStatus(reason);
|
||||
return;
|
||||
}
|
||||
if (!entry.Launchable || _scripts.GetById(entry.PackedId) == null)
|
||||
{
|
||||
_debugSceneLauncher.SetStatus("The selected packed script could not be parsed; no state was changed.");
|
||||
return;
|
||||
}
|
||||
|
||||
var coordinatorWrites = new Dictionary<int, long>
|
||||
{
|
||||
[0] = 1,
|
||||
[0xaba5c] = -1,
|
||||
[0x62ccf] = 0,
|
||||
[0x699] = entry.PackedId,
|
||||
};
|
||||
if (!_vm.TryRequestDebugFrameReturn(frame.FrameId, coordinatorWrites))
|
||||
{
|
||||
_debugSceneLauncher.SetStatus("TITLE changed frames before launch; reopen the launcher and try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
_timeline?.Event("debug-scene-launch-request", new()
|
||||
{
|
||||
["script"] = entry.Name,
|
||||
["packed_id"] = entry.PackedId,
|
||||
});
|
||||
GD.Print($"[debug-launcher] SYSTEM4 dispatch requested: {entry.Name} (0x{entry.PackedId:x8})");
|
||||
_debugSceneLauncher.Hide();
|
||||
// ADV waits need an explicit wake; TITLE's actual menu is a 1 ms sleep/poll loop and will consume
|
||||
// the request at its next opcode boundary without leaving a stale input signal for the child scene.
|
||||
if (_host.IsWaiting) _host.SignalInput();
|
||||
}
|
||||
|
||||
private bool TryGetTitleDebugFrame(out DebugFrameSnapshot frame, out string reason)
|
||||
{
|
||||
frame = _vm.DebugFrame!;
|
||||
if (_done || frame == null)
|
||||
{
|
||||
reason = "Available only while TITLE is the active SYSTEM4 child.";
|
||||
return false;
|
||||
}
|
||||
if (frame.CallStack.Count != 2
|
||||
|| !frame.CallStack[0].Equals("SYSTEM4.BIN", System.StringComparison.OrdinalIgnoreCase)
|
||||
|| !frame.CallStack[1].Equals("TITLE.BIN", System.StringComparison.OrdinalIgnoreCase)
|
||||
|| !frame.CurrentScript.Equals("TITLE.BIN", System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
reason = "Refused: the active stack is not SYSTEM4 > TITLE.";
|
||||
return false;
|
||||
}
|
||||
reason = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
private (int X, int Y) ToNativeScreen(Vector2 position)
|
||||
{
|
||||
// Godot reports input in viewport coordinates after content scaling. This ratio is therefore
|
||||
// normally identity, while remaining correct for any viewport expansion policy.
|
||||
Vector2 size = GetViewport().GetVisibleRect().Size;
|
||||
if (size.X <= 0 || size.Y <= 0) return (0, 0);
|
||||
return ((int)System.Math.Floor(position.X * _screenWidth / size.X),
|
||||
(int)System.Math.Floor(position.Y * _screenHeight / size.Y));
|
||||
}
|
||||
|
||||
public void SetAgeCursor(byte[] rgba, int width, int height, int hotspotX, int hotspotY)
|
||||
{
|
||||
var image = Image.CreateFromData(width, height, false, Image.Format.Rgba8, rgba);
|
||||
_ageCursorTexture = ImageTexture.CreateFromImage(image);
|
||||
Input.SetCustomMouseCursor(_ageCursorTexture, Input.CursorShape.Arrow,
|
||||
new Vector2(hotspotX, hotspotY));
|
||||
}
|
||||
|
||||
public void ClearAgeCursor()
|
||||
{
|
||||
Input.SetCustomMouseCursor(null, Input.CursorShape.Arrow);
|
||||
_ageCursorTexture = null;
|
||||
}
|
||||
|
||||
public void ShowAgeDiagnostic(string text, string caption)
|
||||
{
|
||||
try
|
||||
{
|
||||
OS.Alert(text, caption);
|
||||
}
|
||||
catch (System.Exception error)
|
||||
{
|
||||
GD.PushError($"[diagnostic] native alert failed: {error.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_host?.CompleteDiagnosticMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowAgeFullwidthTextEditor(string initialText)
|
||||
{
|
||||
if (_fullwidthTextEditor != null)
|
||||
{
|
||||
GD.PushWarning("[inputname] replaced an already-open full-width text editor");
|
||||
_fullwidthTextEditor.QueueFree();
|
||||
}
|
||||
|
||||
var editor = new FullwidthTextEditorDialog();
|
||||
_fullwidthTextEditor = editor;
|
||||
editor.EditCompleted += (accepted, text) =>
|
||||
{
|
||||
if (_fullwidthTextEditor == editor) _fullwidthTextEditor = null;
|
||||
_host?.CompleteFullwidthTextEdit(accepted, text);
|
||||
editor.QueueFree();
|
||||
};
|
||||
AddChild(editor);
|
||||
editor.Open(initialText);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
bool vmStopped = true;
|
||||
|
||||
Reference in New Issue
Block a user