about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--Lingo 2.asl145
-rw-r--r--README.md1
2 files changed, 146 insertions, 0 deletions
diff --git a/Lingo 2.asl b/Lingo 2.asl new file mode 100644 index 0000000..9c59387 --- /dev/null +++ b/Lingo 2.asl
@@ -0,0 +1,145 @@
1// Autosplitter script for Lingo 2, by hatkirby.
2//
3// Massive thanks to the game developer, Brenton, for working with me to
4// make this possible.
5
6state("Lingo2") {}
7
8startup
9{
10 // Relative to Livesplit.exe
11 vars.logFilePath = Directory.GetCurrentDirectory() + "\\autosplitter_lingo2.log";
12 vars.log = (Action<string>)((string logLine) => {
13 print("[Lingo 2 ASL] " + logLine);
14 string time = System.DateTime.Now.ToString("dd/MM/yy hh:mm:ss.fff");
15 // AppendAllText will create the file if it doesn't exist.
16 System.IO.File.AppendAllText(vars.logFilePath, time + ": " + logLine + "\r\n");
17 });
18
19 settings.Add("panels", false, "Split on solving a panel");
20 settings.Add("panels_first", false, "Only split the first time each panel is solved", "panels");
21 settings.Add("maps", false, "Split on changing maps");
22 settings.Add("maps_first", false, "Only split the first time a map is entered", "maps");
23 settings.Add("ends", false, "Split on any ending");
24 settings.Add("keys", false, "Split on unlocking a key");
25 settings.Add("paintings", false, "Split on unlocking a gallery painting");
26 settings.Add("graves", false, "Split on completing a gravestone");
27 settings.Add("masteries", false, "Split on collecting a mastery");
28
29 vars.prevPanelMap = "";
30 vars.prevPanelPath = "";
31 vars.prevMap = "";
32 vars.collectedKeys = new HashSet<string>();
33 vars.latestKeyKey = null;
34
35 vars.log("Autosplitter loaded");
36}
37
38init
39{
40 // magic byte array format:
41 // [0-7]: 9c 46 9f b0 4b 6a e0 8d (random bytes used for sigscanning)
42 // [8]: Loading flag
43 // [9]: First movement flag
44 // [10]: Latest unlocked key (0 if run just started)
45 // [11]: 1 or 2, indicating the level of the latest unlocked key
46 // [12-52]: Latest collectable unlocked (null terminated, truncated at 40 chars excluding null)
47 // [53-93]: Current map name (null terminated, truncated at 40 chars excluding null)
48 // [94-134]: Map name of latest solved panel (null terminated, truncated at 40 chars excluding null)
49 // [135-235]: Full nodepath of latest solved panel (null terminated, truncated at 100 chars excluding null)
50 IntPtr ptr = IntPtr.Zero;
51 foreach (var page in game.MemoryPages(true).Reverse()) {
52 var scanner = new SignatureScanner(game, page.BaseAddress, (int)page.RegionSize);
53 ptr = scanner.Scan(new SigScanTarget(0, "9c 46 9f b0 4b 6a e0 8d"));
54 if (ptr != IntPtr.Zero) {
55 break;
56 }
57 }
58 if (ptr == IntPtr.Zero) {
59 throw new Exception("Could not find magic autosplitter array!");
60 }
61 vars.loading = new MemoryWatcher<byte>(ptr + 8);
62 vars.firstInput = new MemoryWatcher<byte>(ptr + 9);
63 vars.latestKey = new MemoryWatcher<byte>(ptr + 10);
64 vars.latestKeyLevel = new MemoryWatcher<byte>(ptr + 11);
65 vars.latestCollectible = new StringWatcher(ptr + 12, 41);
66 vars.currentMap = new StringWatcher(ptr + 53, 41);
67 vars.lastPanelMap = new StringWatcher(ptr + 94, 41);
68 vars.lastPanelPath = new StringWatcher(ptr + 135, 101);
69
70 vars.log(String.Format("Magic autosplitter array: {0}", ptr.ToString("X")));
71}
72
73update
74{
75 vars.loading.Update(game);
76 vars.firstInput.Update(game);
77 vars.latestKey.Update(game);
78 vars.latestKeyLevel.Update(game);
79 vars.latestCollectible.Update(game);
80 vars.currentMap.Update(game);
81 vars.lastPanelMap.Update(game);
82 vars.lastPanelPath.Update(game);
83
84 if (vars.latestKeyLevel.Current > 0)
85 {
86 vars.latestKeyKey = string.Format("{0}{1}", (char)vars.latestKey.Current, vars.latestKeyLevel.Current);
87 }
88}
89
90start
91{
92 return vars.firstInput.Old == 0 && vars.firstInput.Current == 1;
93}
94
95onStart
96{
97 vars.prevPanelMap = vars.lastPanelMap.Current;
98 vars.prevPanelPath = vars.lastPanelPath.Current;
99 vars.prevMap = vars.currentMap.Current;
100}
101
102reset
103{
104 return vars.firstInput.Old == 1 && vars.firstInput.Current == 0;
105}
106
107isLoading
108{
109 return vars.loading.Current == 1;
110}
111
112split
113{
114 bool should_split = false;
115
116 if (vars.lastPanelMap.Current != vars.prevPanelMap || vars.lastPanelPath.Current != vars.prevPanelPath) {
117 if (settings["panels"]) {
118 should_split = true;
119 vars.log("Split on any panel: " + vars.lastPanelMap.Current + " / " + vars.lastPanelPath.Current);
120 }
121
122 vars.prevPanelMap = vars.lastPanelMap.Current;
123 vars.prevPanelPath = vars.lastPanelPath.Current;
124 }
125 else if (vars.currentMap.Current != vars.prevMap)
126 {
127 if (settings["maps"]) {
128 should_split = true;
129 vars.log("Split on map change: " + vars.currentMap.Current);
130 }
131
132 vars.prevMap = vars.currentMap.Current;
133 }
134 else if (vars.latestKeyKey != null && !vars.collectedKeys.Contains(vars.latestKeyKey))
135 {
136 if (settings["keys"]) {
137 should_split = true;
138 vars.log("Split on collected key: " + vars.latestKeyKey);
139 }
140
141 vars.collectedKeys.Add(vars.latestKeyKey);
142 }
143
144 return should_split;
145}
diff --git a/README.md b/README.md index 041270d..82c67d5 100644 --- a/README.md +++ b/README.md
@@ -6,6 +6,7 @@ These are automatic splitter scripts for the speedrunning timer application,
6I have written autosplitters for the following games: 6I have written autosplitters for the following games:
7 7
8- [Lingo](https://lingothegame.com/) 8- [Lingo](https://lingothegame.com/)
9- [Lingo 2](https://store.steampowered.com/app/2523310/Lingo_2/)
9- [Manifold Garden](https://manifold.garden/) 10- [Manifold Garden](https://manifold.garden/)
10- [Taiji](https://store.steampowered.com/app/1141580/Taiji/) 11- [Taiji](https://store.steampowered.com/app/1141580/Taiji/)
11- [Temple of Starlight](https://store.steampowered.com/app/1970050/Temple_of_Starlight/) 12- [Temple of Starlight](https://store.steampowered.com/app/1970050/Temple_of_Starlight/)