aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/src/Godosa.Core/Runs/Scripts/InputPlans.cs
blob: 9c150b3510e7cf321d8a8f343ad157324102397e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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);
    }
}