aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/src/Godosa.Core/Runs/Scripts/InputPlans.cs
diff options
context:
space:
mode:
authorgodosa <godosa@godosa.eu>2026-10-06 23:39:36 +0200
committergodosa <godosa@godosa.eu>2026-10-06 23:39:36 +0200
commit39066900773e7857faf2d02e7ed51b71d219d97d (patch)
treeffab8e4ddd971626776c3b1f4ce1da2c186c2187 /src/Godosa.Core/Runs/Scripts/InputPlans.cs
downloadgodosa-engine-39066900773e7857faf2d02e7ed51b71d219d97d.tar.gz
godosa-engine-39066900773e7857faf2d02e7ed51b71d219d97d.zip
godosa-engine: initial public history
Diffstat (limited to 'src/Godosa.Core/Runs/Scripts/InputPlans.cs')
-rw-r--r--src/Godosa.Core/Runs/Scripts/InputPlans.cs111
1 files changed, 111 insertions, 0 deletions
diff --git a/src/Godosa.Core/Runs/Scripts/InputPlans.cs b/src/Godosa.Core/Runs/Scripts/InputPlans.cs
new file mode 100644
index 0000000..9c150b3
--- /dev/null
+++ b/src/Godosa.Core/Runs/Scripts/InputPlans.cs
@@ -0,0 +1,111 @@
+// Provenance: own design — shared run scripts (spec §4.1)
+using System.Globalization;
+
+namespace Godosa.Core.Runs.Scripts;
+
+public static class Positions
+{
+ /// <summary><c>x,y</c> or a locator <c>@name(args)</c> (args comma separated).</summary>
+ public static (int X, int Y) Resolve(string token, IRunGame game)
+ {
+ if (token.StartsWith('@'))
+ {
+ var paren = token.IndexOf('(');
+ var name = paren > 0 ? token[1..paren] : token[1..];
+ var args = paren > 0 && token.EndsWith(')')
+ ? token[(paren + 1)..^1].Split(',', StringSplitOptions.TrimEntries).Where(a => a.Length > 0).ToArray()
+ : [];
+ if (!game.Locators.TryGetValue(name, out var locator))
+ throw new ScriptFault($"unknown locator @{name}");
+ var r = locator.Locate(args);
+ return r.IsOk ? (r.X, r.Y) : throw new ScriptFault($"locator {token}: {r.Error}");
+ }
+ var parts = token.Split(',');
+ if (parts.Length == 2 && int.TryParse(parts[0], NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var x)
+ && int.TryParse(parts[1], NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var y))
+ return (x, y);
+ throw new ScriptFault($"bad position '{token}'");
+ }
+}
+
+public static class InputPlans
+{
+ public static bool IsInputVerb(string verb) => verb is "click" or "rclick" or "dclick" or "drag" or "move" or "wheel"
+ or "key" or "hold" or "chord" or "type";
+
+ public static InputPlan For(string verb, IReadOnlyList<Token> args, Func<string, (int X, int Y)> position)
+ {
+ List<PlannedInput> e = [];
+ void Add(int frame, InputEvent ev) => e.Add(new PlannedInput(frame, ev));
+ switch (verb)
+ {
+ case "click" or "rclick" or "dclick":
+ {
+ var (x, y) = position(args[0].Text);
+ var button = verb == "rclick" ? MouseButton.Right : MouseButton.Left;
+ Add(0, new InputEvent(InputKind.Move, x, y));
+ Add(0, new InputEvent(InputKind.Down, x, y, button));
+ Add(1, new InputEvent(InputKind.Up, x, y, button));
+ if (verb == "dclick")
+ {
+ Add(2, new InputEvent(InputKind.Down, x, y, button));
+ Add(3, new InputEvent(InputKind.Up, x, y, button));
+ }
+ break;
+ }
+ case "drag":
+ {
+ var (ax, ay) = position(args[0].Text);
+ var (bx, by) = position(args[1].Text);
+ Add(0, new InputEvent(InputKind.Move, ax, ay));
+ Add(0, new InputEvent(InputKind.Down, ax, ay));
+ for (var step = 1; step <= 3; step++)
+ Add(step, new InputEvent(InputKind.Move, ax + (bx - ax) * step / 3, ay + (by - ay) * step / 3));
+ Add(4, new InputEvent(InputKind.Up, bx, by));
+ break;
+ }
+ case "move":
+ {
+ var (x, y) = position(args[0].Text);
+ Add(0, new InputEvent(InputKind.Move, x, y));
+ break;
+ }
+ case "wheel":
+ {
+ var delta = int.TryParse(args[0].Text, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var d)
+ ? d : throw new ScriptFault($"bad wheel delta '{args[0].Text}'");
+ var (x, y) = args.Count > 1 ? position(args[1].Text) : (0, 0);
+ if (args.Count > 1)
+ Add(0, new InputEvent(InputKind.Move, x, y));
+ Add(0, new InputEvent(InputKind.Wheel, x, y, Delta: delta));
+ break;
+ }
+ case "key":
+ Add(0, new InputEvent(InputKind.KeyDown, Key: args[0].Text));
+ Add(1, new InputEvent(InputKind.KeyUp, Key: args[0].Text));
+ break;
+ case "hold":
+ {
+ var frames = Durations.Frames(args[1].Text) is { } f and > 0 ? f : throw new ScriptFault($"bad hold frames '{args[1].Text}'");
+ Add(0, new InputEvent(InputKind.KeyDown, Key: args[0].Text));
+ Add(frames, new InputEvent(InputKind.KeyUp, Key: args[0].Text));
+ break;
+ }
+ case "chord":
+ {
+ var keys = args[0].Text.Split('+', StringSplitOptions.RemoveEmptyEntries);
+ foreach (var k in keys)
+ Add(0, new InputEvent(InputKind.KeyDown, Key: k));
+ foreach (var k in keys.Reverse())
+ Add(1, new InputEvent(InputKind.KeyUp, Key: k));
+ break;
+ }
+ case "type":
+ Add(0, new InputEvent(InputKind.Text, Text: args[0].Text));
+ break;
+ default:
+ throw new ScriptFault($"not an input verb: {verb}");
+ }
+ return new InputPlan(e);
+ }
+}