about summary refs log tree commit diff stats
path: root/Manifold Garden.asl
diff options
context:
space:
mode:
Diffstat (limited to 'Manifold Garden.asl')
-rw-r--r--Manifold Garden.asl206
1 files changed, 155 insertions, 51 deletions
diff --git a/Manifold Garden.asl b/Manifold Garden.asl index a5009ad..71cb203 100644 --- a/Manifold Garden.asl +++ b/Manifold Garden.asl
@@ -1,79 +1,164 @@
1// AutoSplit script for Manifold Garden 1.0.30.14704 1// AutoSplit script for Manifold Garden
2// 2//
3// Written by hatkirby, with help from preshing and Gelly. 3// Written by hatkirby, with help from preshing and Gelly.
4// 4//
5// Automatically starts the timer when a new game is started. You must still reset the timer 5// Automatically starts the timer when a new game is started. You must still
6// manually between runs. 6// reset the timer manually between runs.
7// 7//
8// A split is also triggered after being in one of the ending cutscenes for 1.1 seconds, 8// A split is also triggered after being in one of the ending cutscenes for 1.1
9// since this is when the kaleidoscope appears. 9// seconds, since this is when the kaleidoscope appears.
10// 10//
11// If you check "All God Cubes waypoints" in the script's Advanced settings (below), the script 11// If you check "All God Cubes waypoints" in the script's Advanced settings
12// will only split at mandala scenes. This is useful when running "All God Cubes" categories. 12// (below), the script will only split at mandala scenes. This is useful when
13// running "All God Cubes" categories.
13// 14//
14// The pointer path to the current level often changes when a new version of Manifold Garden is 15// This should be mostly version-independent, but it works best on 1.0.0.14704
15// released. When that happens, a new pointer path must be found using CheatEngine. If the 16// (Steam "Speedrunning Branch").
16// current pointer path stops working (even for a frame or two), a message is logged to the
17// debug output.
18// 17//
19// To view debug output (print statements from this script), use DebugView: 18// To view debug output (print statements from this script), use DebugView:
20// https://technet.microsoft.com/en-us/Library/bb896647.aspx 19// https://technet.microsoft.com/en-us/Library/bb896647.aspx
21 20
22state("ManifoldGarden") { 21state("ManifoldGarden") {}
23 // These pointer paths seem to work with Manifold Garden 1.1.0.14704 (2020-11-09):
24 int level: "mono-2.0-bdwgc.dll", 0x00494DC8, 0x48, 0x120, 0x120, 0x120, 0x120, 0x120, 0xC60, 0x1A0;
25 bool transitionFadeHeld: "UnityPlayer.dll", 0x017945A8, 0x80, 0x10, 0x48, 0xA0, 0x10, 0xE40;
26 bool isLoadingGameFromUI: "UnityPlayer.dll", 0x017945A8, 0x90, 0x100, 0xC0, 0xC0, 0xC0, 0xC0, 0xDC1;
27 bool startScreenActive: "UnityPlayer.dll", 0x0178BBC0, 0x3B8, 0x38, 0x18, 0x8, 0x198, 0x0, 0x8ab;
28
29 // Older pointer paths:
30 //int level: "UnityPlayer.dll", 0x014BE300, 0x60, 0xA8, 0x38, 0x30, 0xB0, 0x118, 0x5C; // 1.0.30.13294 (2020-02-25)
31 //int level: "UnityPlayer.dll", 0x01552858, 0x8, 0x0, 0xB8, 0x80, 0x80, 0x28, 0x5C; // 13294
32 //int level: "UnityPlayer.dll", 0x01552858, 0x28, 0x8, 0xB8, 0x80, 0x80, 0x28, 0x5C; // 13294
33 //int level: "UnityPlayer.dll", 0x01507BE0, 0x0, 0x928, 0x38, 0x30, 0xB0, 0x118, 0x5C; // 1.0.29.12904 (2020-02-??), 1.0.29.12830 (2019-12-18), 1.0.29.12781 (2019-12-11)
34 //int level: "UnityPlayer.dll", 0x01507C68, 0x8, 0x38, 0xA8, 0x58, 0x118, 0x5C;
35}
36 22
37startup { 23startup {
24 var bytes = File.ReadAllBytes(@"Components\LiveSplit.ASLHelper.bin");
25 var type = Assembly.Load(bytes).GetType("ASLHelper.Unity");
26 vars.Helper = Activator.CreateInstance(type, timer, this);
27 vars.Helper.LoadSceneManager = true;
28
38 settings.Add("every",true,"Split on every level change"); 29 settings.Add("every",true,"Split on every level change");
39 settings.Add("fall",false,"Including all ending falling scenes","every"); 30 settings.Add("fall",false,"Including all ending falling scenes","every");
40 settings.Add("allGodCubes", false, "All God Cubes waypoints"); 31 settings.Add("allGodCubes", false, "All God Cubes waypoints");
41 settings.Add("zero", false, "Zero% waypoints"); 32 settings.Add("zero", false, "Zero% waypoints");
42 settings.Add("raymarchitecture", true, "Split on Raymarchitecture (ending cutscene)"); 33 settings.Add("raymarchitecture", true, "Split on Raymarchitecture (ending cutscene)");
43 settings.Add("norepeats",false,"Split only on the first encounter of each level"); 34 settings.Add("norepeats",false,"Split only on the first encounter of each level");
35
44 vars.waypoints = null; 36 vars.waypoints = null;
45 vars.prevLevel = 0; 37 vars.prevLevel = 0;
46 vars.stopwatch = null; // Used for the final split 38 vars.stopwatch = null; // Used for the final split
47 vars.prev = new List<int>(); 39 vars.prev = new List<int>();
48 vars.firstRoom = false; 40 vars.firstRoom = false;
49 vars.fall = new List<int>{97, 98, 99, 101, 102, 103, 104}; 41 vars.inEnding = false;
42 vars.noSplitScenes = new List<String>{
43 "StudioLogoScreen",
44 "RequiredComponents",
45 "StartScreen_01",
46 "StartScreen_02",
47 "StartScreen_03",
48 "StartScreen_15",
49 "StartScreen_51",
50 "StartScreen_53",
51 "StartScreen_63"
52 };
53 vars.startScreens = new List<String>{
54 "StartScreen_01",
55 "StartScreen_02",
56 "StartScreen_03",
57 "StartScreen_15",
58 "StartScreen_51",
59 "StartScreen_53",
60 "StartScreen_63"
61 };
62 vars.endings = new List<string>{
63 "World_905_EndingCollapseCutscene_Optimized",
64 "World_907_EndingZeroCollapseCutscene_Optimized",
65 "World_906_EndingDarkCollapseCutscene_Optimized"
66 };
67 vars.fall = new List<string>{
68 "World_903_EndingFallTwo_Optimized",
69 "World_904_EndingFallThree_Optimized",
70 "World_905_EndingCollapseCutscene_Optimized",
71 "World_923_AlternateEndingFallTwo_Optimized",
72 "World_924_AlternateEndingFallThree_Optimized",
73 "World_907_EndingZeroCollapseCutscene_Optimized",
74 "World_906_EndingDarkCollapseCutscene_Optimized"
75 };
76 vars.mandalaScenes = new List<string>{
77 "AudioVisual_001_Optimized",
78 "AudioVisual_002_Optimized",
79 "AudioVisual_053_Optimized",
80 "AudioVisual_051_Optimized",
81 "AudioVisual_003_Optimized",
82 "AudioVisual_063_Optimized",
83 "AudioVisual_071_Optimized"
84 };
85 vars.zeroPercentPoints = new List<string>{
86 "Hallway_W000_W052_Optimized",
87 "World_002_Optimized",
88 "Hallway_W026_W015_Optimized",
89 "Hallway_W612_W057_Optimized",
90 "Hallway_W073_W026_Optimized",
91 "World_804_Optimized",
92 "World_071_AkshardhamTemple_Optimized"
93 };
50} 94}
51 95
52init { 96init {
53 print(String.Format("**** AUTOSPLIT: Game found, pointer path {0} ****", 97 vars.studioScreenDone = true;
54 current.level == 0 ? "DOESN'T work (this is normal at startup)" : "works")); 98 vars.doneFirstLook = false;
99 vars.Helper.TryOnLoad = (Func<dynamic, bool>)(mono =>
100 {
101 var gameMan = mono.GetClass("GameManager");
102 vars.Helper["isLoadingGameFromUI"] = gameMan.Make<bool>("isLoadingGameFromUI");
103
104 var versionNum = mono.GetClass("VersionNumber");
105 vars.Helper["version"] = versionNum.MakeString("instance", "_text");
106
107 current.onStartScreen = false;
108
109 return true;
110 });
111
112 vars.Helper.Load();
55} 113}
56 114
57update { 115update {
58 // Log a message when the pointer path starts/stops working: 116 if (!vars.Helper.Update())
59 if (current.level == 0 && old.level != 0) { 117 return false;
60 print("**** AUTOSPLIT: Pointer path STOPPED working ****"); 118
61 } else if (current.level != 0 && old.level == 0) { 119 current.level = vars.Helper.Scenes.Active.Index;
62 print("**** AUTOSPLIT: Pointer path STARTED working ****"); 120 current.isLoadingGameFromUI = vars.Helper["isLoadingGameFromUI"].Current;
121
122 if (!vars.doneFirstLook) {
123 vars.doneFirstLook = true;
124 print(String.Format("Connected to Manifold Garden version {0}", vars.Helper["version"].Current));
125
126 current.onStartScreen = vars.startScreens.Contains(vars.Helper.Scenes.Active.Name);
127
128 // The "isLoadingGameFromUI" boolean is set while the studio screen is
129 // showing during game startup, which means that if the autosplitter is
130 // running before the game opens, it'll erroneously start a run. To
131 // avoid this, when the autosplitter initialises, we check if we are on
132 // a noSplitScene (the game usually reports itself as being on a start
133 // screen rather than the studio screen) and if the game is loading. If
134 // so, we disable starting a run until the load is complete. If the
135 // splitter is opened after the game starts up, this shouldn't activate,
136 // which means run starting should work as expected.
137 if (vars.noSplitScenes.Contains(vars.Helper.Scenes.Active.Name)
138 && current.isLoadingGameFromUI) {
139 vars.studioScreenDone = false;
140 }
141 } else {
142 if (current.level != old.level) {
143 current.onStartScreen = vars.startScreens.Contains(vars.Helper.Scenes.Active.Name);
144 }
145 if (!vars.studioScreenDone) {
146 vars.studioScreenDone = !current.isLoadingGameFromUI;
147 }
63 } 148 }
64} 149}
65 150
66start { 151start {
67 // Start the timer as soon as a game is being loaded (specifically the moment you click 152 // Start the timer as soon as a game is being loaded (specifically the
68 // a save slot to start a new game in, although it will also start if you just load a file). 153 // moment you click a save slot to start a new game in, although it will
69 // This boolean is set to true during the studio logo when the game starts up, so we check 154 // also start if you just load a file). This boolean is set to true during
70 // for that as well. 155 // the studio logo when the game starts up, so we check for that as well.
71 if (current.transitionFadeHeld && current.isLoadingGameFromUI) { 156 if (vars.studioScreenDone && current.isLoadingGameFromUI) {
72 print(String.Format("Level changed from {0} to {1}: START", old.level, current.level)); 157 print(String.Format("Level changed from {0} to {1}: START", old.level, current.level));
73 if (settings["zero"]) { 158 if (settings["zero"]) {
74 vars.waypoints = new List<int>{106, 17, 110, 115, 111, 36, 44}; 159 vars.waypoints = vars.zeroPercentPoints;
75 } else if (settings["allGodCubes"]) { 160 } else if (settings["allGodCubes"]) {
76 vars.waypoints = new List<int>{82, 83, 84, 85, 86, 87, 88}; 161 vars.waypoints = vars.mandalaScenes;
77 } else { 162 } else {
78 vars.waypoints = null; 163 vars.waypoints = null;
79 } 164 }
@@ -81,26 +166,31 @@ start {
81 vars.stopwatch = Stopwatch.StartNew(); 166 vars.stopwatch = Stopwatch.StartNew();
82 vars.prev.Clear(); 167 vars.prev.Clear();
83 vars.firstRoom = false; 168 vars.firstRoom = false;
169 vars.inEnding = false;
84 return true; 170 return true;
85 } 171 }
86} 172}
87 173
88split { 174split {
89 // Split when level index changes. We don't split for the first room change in a run, 175 // Split when level index changes. We don't split for the first room change
90 // because that is always going to be changing from -1 to 9, and it happens a couple of 176 // in a run, because that is always going to be changing from 3 to 73, and
91 // seconds after the timer starts. 177 // it happens a couple of seconds after the timer starts.
92 if (vars.firstRoom && current.level != vars.prevLevel && current.level > 0) { 178 if (vars.firstRoom
179 && current.level != vars.prevLevel
180 && current.level > 0
181 && !vars.noSplitScenes.Contains(vars.Helper.Scenes.Active.Name)) {
182 print(String.Format("{0}: '{1}'", current.level, vars.Helper.Scenes.Active.Name));
183
93 string action = "NO SPLIT"; 184 string action = "NO SPLIT";
94 185
95 // Ignore the split rules when script is reloaded mid-game:
96 if (vars.prevLevel != 0) { 186 if (vars.prevLevel != 0) {
97 // Split rules: 187 // Split rules:
98 if (settings["every"]) { 188 if (settings["every"]) {
99 if (settings["fall"] || !vars.fall.Contains(current.level)) { 189 if (settings["fall"] || !vars.fall.Contains(vars.Helper.Scenes.Active.Name)) {
100 action = "SPLIT"; 190 action = "SPLIT";
101 } 191 }
102 } else if (vars.waypoints != null) { 192 } else if (vars.waypoints != null) {
103 if (vars.waypoints.Contains(current.level)) { 193 if (vars.waypoints.Contains(vars.Helper.Scenes.Active.Name)) {
104 action = "SPLIT"; 194 action = "SPLIT";
105 } 195 }
106 } 196 }
@@ -115,19 +205,23 @@ split {
115 print(String.Format("Level changed from {0} to {1}: {2}", vars.prevLevel, current.level, action)); 205 print(String.Format("Level changed from {0} to {1}: {2}", vars.prevLevel, current.level, action));
116 } 206 }
117 207
208 if (vars.endings.Contains(vars.Helper.Scenes.Active.Name)) {
209 vars.inEnding = true;
210 }
211
118 vars.prevLevel = current.level; 212 vars.prevLevel = current.level;
119 vars.stopwatch = Stopwatch.StartNew(); 213 vars.stopwatch = Stopwatch.StartNew();
120 return action.StartsWith("SPLIT"); 214 return action.StartsWith("SPLIT");
121 } else if (!vars.firstRoom && current.level == 9) { 215 } else if (!vars.firstRoom && vars.Helper.Scenes.Active.Name == "World_000_Optimized") {
122 vars.firstRoom = true; 216 vars.firstRoom = true;
123 vars.prevLevel = current.level; 217 vars.prevLevel = current.level;
124 vars.prev.Add(9); 218 vars.prev.Add(current.level);
125 } 219 }
126 220
127 // Final split of the game: 221 // Final split of the game:
128 // Split after being in one of the ending cutscenes for 1.1 seconds. 222 // Split after being in one of the ending cutscenes for 1.1 seconds.
129 if (settings["raymarchitecture"] 223 if (settings["raymarchitecture"]
130 && (current.level == 99 || current.level == 103 || current.level == 104) 224 && vars.inEnding
131 && vars.stopwatch != null 225 && vars.stopwatch != null
132 && vars.stopwatch.ElapsedMilliseconds >= 1100) { 226 && vars.stopwatch.ElapsedMilliseconds >= 1100) {
133 print("SPLIT on Raymarchitecture"); 227 print("SPLIT on Raymarchitecture");
@@ -137,5 +231,15 @@ split {
137} 231}
138 232
139reset { 233reset {
140 return current.startScreenActive; 234 return current.onStartScreen && !old.onStartScreen;
235}
236
237exit
238{
239 vars.Helper.Dispose();
240}
241
242shutdown
243{
244 vars.Helper.Dispose();
141} 245}