summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2025-02-22 10:43:41 -0500
committerStar Rauchenberger <fefferburbia@gmail.com>2025-02-22 10:43:41 -0500
commite58b122fb00a337748df2de2451679f53b092d42 (patch)
tree3b71d6554f10cdd5cb68ceeca4d6296abf5c434f
parentd1baf62bea385c75ad4e7612e5caa285400ffaa7 (diff)
downloadmanifold-garden-archipelago-e58b122fb00a337748df2de2451679f53b092d42.tar.gz
manifold-garden-archipelago-e58b122fb00a337748df2de2451679f53b092d42.tar.bz2
manifold-garden-archipelago-e58b122fb00a337748df2de2451679f53b092d42.zip
Mapped out most of the game
-rw-r--r--ArchipelagoManager.cs68
-rw-r--r--GameData.cs442
-rw-r--r--GameState.cs167
-rw-r--r--GameplayPatches.cs237
-rw-r--r--ManifoldGardenArchipelago.csproj7
-rw-r--r--Plugin.cs49
-rw-r--r--Requirements.cs288
-rw-r--r--SlotSave.cs18
-rw-r--r--game_data.yaml735
9 files changed, 1913 insertions, 98 deletions
diff --git a/ArchipelagoManager.cs b/ArchipelagoManager.cs index 7affa64..611ef30 100644 --- a/ArchipelagoManager.cs +++ b/ArchipelagoManager.cs
@@ -4,12 +4,7 @@ using Archipelago.MultiClient.Net.Models;
4using Archipelago.MultiClient.Net.Packets; 4using Archipelago.MultiClient.Net.Packets;
5using System; 5using System;
6using System.Collections.Generic; 6using System.Collections.Generic;
7using System.Linq;
8using System.Reflection;
9using System.Text;
10using System.Threading.Tasks; 7using System.Threading.Tasks;
11using UnityEngine.SceneManagement;
12using static ManifoldGardenArchipelago.GameData;
13 8
14namespace ManifoldGardenArchipelago 9namespace ManifoldGardenArchipelago
15{ 10{
@@ -18,24 +13,8 @@ namespace ManifoldGardenArchipelago
18 private ArchipelagoSession _session; 13 private ArchipelagoSession _session;
19 private int _itemIndex = 0; 14 private int _itemIndex = 0;
20 15
21 public Dictionary<string, Dictionary<int, bool>> chainListenersByScene = new(); 16 private HashSet<string> _items = [];
22 17 private HashSet<string> _locations = [];
23 public ArchipelagoManager()
24 {
25 foreach (var item in GameData.door_by_item)
26 {
27 foreach (var chainListenerReference in item.Value)
28 {
29 if (!chainListenersByScene.TryGetValue(chainListenerReference.scene, out var listenersInScene))
30 {
31 listenersInScene = [];
32 chainListenersByScene.Add(chainListenerReference.scene, listenersInScene);
33 }
34
35 listenersInScene[chainListenerReference.index] = false;
36 }
37 }
38 }
39 18
40 public async Task<LoginResult> Connect(string url, string slotName, string password) 19 public async Task<LoginResult> Connect(string url, string slotName, string password)
41 { 20 {
@@ -72,6 +51,23 @@ namespace ManifoldGardenArchipelago
72 _session = null; 51 _session = null;
73 } 52 }
74 53
54 public bool HasItem(string itemName)
55 {
56 return _items.Contains(itemName);
57 }
58
59 public void CheckLocation(string locationName)
60 {
61 if (_locations.Contains(locationName))
62 {
63 return;
64 }
65
66 _locations.Add(locationName);
67
68 _session.Locations.CompleteLocationChecks(_session.Locations.GetLocationIdFromName("Manifold Garden", locationName));
69 }
70
75 public void Update() 71 public void Update()
76 { 72 {
77 if (_session == null) 73 if (_session == null)
@@ -85,29 +81,13 @@ namespace ManifoldGardenArchipelago
85 { 81 {
86 ItemInfo item = _session.Items.AllItemsReceived[i]; 82 ItemInfo item = _session.Items.AllItemsReceived[i];
87 string itemName = item.ItemName; 83 string itemName = item.ItemName;
84 Plugin.Logger.LogInfo($"Received {itemName}");
85
86 _items.Add(itemName);
88 87
89 if (GameData.door_by_item.TryGetValue(itemName, out List<GameData.ChainListenerReference> door)) 88 if (GameData.listenersByItem.TryGetValue(itemName, out var listeners))
90 { 89 {
91 foreach (GameData.ChainListenerReference chainListenerReference in door) 90 GameState.EvaluateGameStateListeners(listeners);
92 {
93 chainListenersByScene[chainListenerReference.scene][chainListenerReference.index] = true;
94
95 if (SceneManager.GetSceneByName(chainListenerReference.scene) is Scene doorScene && doorScene.isLoaded)
96 {
97 LevelSystems levelSystems = doorScene.GetRootGameObjects().Single((obj) => obj.name == "Level Systems").GetComponent<LevelSystems>();
98 if (levelSystems.isActiveAndEnabled)
99 {
100 LineDoorController ldc = levelSystems.chainListeners[chainListenerReference.index].GetComponent<LineDoorController>();
101 ldc.chains.Clear();
102
103 FieldInfo fieldInfo = typeof(LineDoorController).GetField("doorShouldOpenImmediatelyOnEnable", BindingFlags.Instance | BindingFlags.NonPublic);
104 fieldInfo.SetValue(ldc, true);
105
106 MethodInfo methodInfo = typeof(LineDoorController).GetMethod("SetDoorOpenCloseStateAnimated", BindingFlags.NonPublic | BindingFlags.Instance);
107 methodInfo.Invoke(ldc, [true, false]);
108 }
109 }
110 }
111 } 91 }
112 } 92 }
113 93
diff --git a/GameData.cs b/GameData.cs index 87278ac..f131e85 100644 --- a/GameData.cs +++ b/GameData.cs
@@ -1,18 +1,446 @@
1using System.Collections.Generic; 1using System.Collections.Generic;
2using System.IO;
3using YamlDotNet.Serialization;
2 4
3namespace ManifoldGardenArchipelago 5namespace ManifoldGardenArchipelago
4{ 6{
5 public class GameData 7 public readonly struct SceneItemReference(string scene_arg, int index_arg)
6 { 8 {
7 public readonly struct ChainListenerReference(string scene_arg, int index_arg) 9 public readonly string scene = scene_arg;
10 public readonly int index = index_arg;
11
12 public override string ToString()
13 {
14 return $"({scene}, {index})";
15 }
16 }
17
18 public readonly struct LocationDescription(string name_arg, Requirement requirement_arg)
19 {
20 public readonly string name = name_arg;
21 public readonly Requirement requirement = requirement_arg;
22 }
23
24 public class SceneDescription
25 {
26 public readonly List<LocationDescription> locations = [];
27 public readonly Dictionary<int, Requirement> doors = [];
28 public readonly Dictionary<int, Requirement> smokeWalls = [];
29 public readonly Dictionary<int, Requirement> worldGrows = [];
30 }
31
32 public class GameStateListeners
33 {
34 public readonly Dictionary<string, Requirement> locations = [];
35 public readonly Dictionary<SceneItemReference, Requirement> doors = [];
36 public readonly Dictionary<SceneItemReference, Requirement> smokeWalls = [];
37 public readonly Dictionary<SceneItemReference, Requirement> worldGrows = [];
38 }
39
40 public static class GameData
41 {
42 public static readonly Dictionary<string, SceneDescription> scenes = [];
43
44 public static readonly Dictionary<string, GameStateListeners> listenersByScene = [];
45 public static readonly Dictionary<SceneItemReference, GameStateListeners> listenersByButton = [];
46 public static readonly Dictionary<SceneItemReference, GameStateListeners> listenersBySocket = [];
47 public static readonly Dictionary<SceneItemReference, GameStateListeners> listenersByPad = [];
48 public static readonly Dictionary<SceneItemReference, GameStateListeners> listenersByWaterwheel = [];
49 public static readonly Dictionary<SceneItemReference, GameStateListeners> listenersBySphere = [];
50 public static readonly Dictionary<string, GameStateListeners> listenersByItem = [];
51
52 public static Requirement ParseRequirement(string scene, Dictionary<object, object> yamlReq)
53 {
54 List<Requirement> reqs = [];
55
56 if (yamlReq.ContainsKey("item"))
57 {
58 if (yamlReq["item"] is string itemName)
59 {
60 reqs.Add(new ItemRequirement(itemName));
61 }
62 else if (yamlReq["item"] is List<string> items)
63 {
64 foreach (var item in items)
65 {
66 reqs.Add(new ItemRequirement(item));
67 }
68 }
69 }
70
71 if (yamlReq.ContainsKey("button"))
72 {
73 if (yamlReq["button"] is string index)
74 {
75 reqs.Add(new ButtonRequirement(new(scene, int.Parse(index))));
76 }
77 else if (yamlReq["button"] is Dictionary<object, object> details)
78 {
79 reqs.Add(new ButtonRequirement(new((string)details["scene"], int.Parse((string)details["index"]))));
80 }
81 else if (yamlReq["button"] is List<object> buttons)
82 {
83 foreach (var button in buttons)
84 {
85 if (button is string index2)
86 {
87 reqs.Add(new ButtonRequirement(new(scene, int.Parse(index2))));
88 }
89 else if (button is Dictionary<object, object> details2)
90 {
91 reqs.Add(new ButtonRequirement(new((string)details2["scene"], int.Parse((string)details2["index"]))));
92 }
93 }
94 }
95 }
96
97 if (yamlReq.ContainsKey("socket"))
98 {
99 if (yamlReq["socket"] is string index)
100 {
101 reqs.Add(new SocketRequirement(new(scene, int.Parse(index))));
102 }
103 else if (yamlReq["socket"] is Dictionary<object, object> details)
104 {
105 reqs.Add(new SocketRequirement(new((string)details["scene"], int.Parse((string)details["index"]))));
106 }
107 else if (yamlReq["socket"] is List<object> buttons)
108 {
109 foreach (var button in buttons)
110 {
111 if (button is string index2)
112 {
113 reqs.Add(new SocketRequirement(new(scene, int.Parse(index2))));
114 }
115 else if (button is Dictionary<object, object> details2)
116 {
117 reqs.Add(new SocketRequirement(new((string)details2["scene"], int.Parse((string)details2["index"]))));
118 }
119 }
120 }
121 }
122
123 if (yamlReq.ContainsKey("pad"))
124 {
125 if (yamlReq["pad"] is string index)
126 {
127 reqs.Add(new PadRequirement(new(scene, int.Parse(index))));
128 }
129 else if (yamlReq["pad"] is Dictionary<object, object> details)
130 {
131 reqs.Add(new PadRequirement(new((string)details["scene"], int.Parse((string)details["index"]))));
132 }
133 else if (yamlReq["pad"] is List<object> buttons)
134 {
135 foreach (var button in buttons)
136 {
137 if (button is string index2)
138 {
139 reqs.Add(new PadRequirement(new(scene, int.Parse(index2))));
140 }
141 else if (button is Dictionary<object, object> details2)
142 {
143 reqs.Add(new PadRequirement(new((string)details2["scene"], int.Parse((string)details2["index"]))));
144 }
145 }
146 }
147 }
148
149 if (yamlReq.ContainsKey("waterwheel"))
150 {
151 if (yamlReq["waterwheel"] is string index)
152 {
153 reqs.Add(new WaterwheelRequirement(new(scene, int.Parse(index))));
154 }
155 else if (yamlReq["waterwheel"] is Dictionary<object, object> details)
156 {
157 reqs.Add(new WaterwheelRequirement(new((string)details["scene"], int.Parse((string)details["index"]))));
158 }
159 else if (yamlReq["waterwheel"] is List<object> buttons)
160 {
161 foreach (var button in buttons)
162 {
163 if (button is string index2)
164 {
165 reqs.Add(new WaterwheelRequirement(new(scene, int.Parse(index2))));
166 }
167 else if (button is Dictionary<object, object> details2)
168 {
169 reqs.Add(new WaterwheelRequirement(new((string)details2["scene"], int.Parse((string)details2["index"]))));
170 }
171 }
172 }
173 }
174
175 if (yamlReq.ContainsKey("sphere"))
176 {
177 if (yamlReq["sphere"] is string index)
178 {
179 reqs.Add(new SphereRequirement(new(scene, int.Parse(index))));
180 }
181 else if (yamlReq["sphere"] is Dictionary<object, object> details)
182 {
183 reqs.Add(new SphereRequirement(new((string)details["scene"], int.Parse((string)details["index"]))));
184 }
185 else if (yamlReq["sphere"] is List<object> buttons)
186 {
187 foreach (var button in buttons)
188 {
189 if (button is string index2)
190 {
191 reqs.Add(new SphereRequirement(new(scene, int.Parse(index2))));
192 }
193 else if (button is Dictionary<object, object> details2)
194 {
195 reqs.Add(new SphereRequirement(new((string)details2["scene"], int.Parse((string)details2["index"]))));
196 }
197 }
198 }
199 }
200
201 if (yamlReq.ContainsKey("or"))
202 {
203 List<Requirement> unionReq = [];
204
205 foreach (var reqObj in (List<object>)yamlReq["or"])
206 {
207 unionReq.Add(ParseRequirement(scene, (Dictionary<object, object>)reqObj));
208 }
209
210 reqs.Add(new OrRequirement(unionReq));
211 }
212
213 if (yamlReq.ContainsKey("inverted"))
214 {
215 reqs.Add(new InvertedRequirement(ParseRequirement(scene, (Dictionary<object, object>)yamlReq["inverted"])));
216 }
217
218 if (reqs.Count == 1)
219 {
220 return reqs[0];
221 }
222 else
223 {
224 return new AndRequirement(reqs);
225 }
226 }
227
228 private static GameStateListeners GetOrAddListeners<T>(Dictionary<T, GameStateListeners> kvp, T key)
8 { 229 {
9 public readonly string scene = scene_arg; 230 if (!kvp.TryGetValue(key, out GameStateListeners listeners))
10 public readonly int index = index_arg; 231 {
232 listeners = new();
233 kvp[key] = listeners;
234 }
235
236 return listeners;
11 } 237 }
12 238
13 public static readonly Dictionary<string, List<ChainListenerReference>> door_by_item = new() 239 public static void Load()
14 { 240 {
15 {"Blue Secret Path", [new ChainListenerReference("World_000_Optimized", 16)] } 241 Plugin.Logger.LogInfo(Path.Combine(BepInEx.Paths.BepInExRootPath, "plugins", "game_data.yaml"));
16 }; 242 var yamlText = File.OpenText(Path.Combine(BepInEx.Paths.BepInExRootPath, "plugins", "game_data.yaml"));
243
244 var deserializer = new DeserializerBuilder().Build();
245 var yamlObject = deserializer.Deserialize<Dictionary<string, object>>(yamlText);
246
247 foreach (var scenePair in yamlObject)
248 {
249 Plugin.Logger.LogInfo($"Scene: {scenePair.Key}");
250 SceneDescription sceneDescription = new();
251
252 var sceneDetails = (Dictionary<object, object>)scenePair.Value;
253
254 if (sceneDetails.ContainsKey("locations"))
255 {
256 var sceneLocations = (Dictionary<object, object>)sceneDetails["locations"];
257
258 foreach (var locationPair in sceneLocations)
259 {
260 string locationName = (string)locationPair.Key;
261 Requirement req = ParseRequirement(scenePair.Key, (Dictionary<object, object>)locationPair.Value);
262 sceneDescription.locations.Add(new(locationName, req));
263
264 Plugin.Logger.LogInfo($"{locationName} requirements: {req}");
265
266 foreach (var reqScene in req.references.scenes)
267 {
268 GetOrAddListeners(listenersByScene, reqScene).locations[locationName] = req;
269 }
270
271 foreach (var button in req.references.buttons)
272 {
273 GetOrAddListeners(listenersByButton, button).locations[locationName] = req;
274 }
275
276 foreach (var socket in req.references.sockets)
277 {
278 GetOrAddListeners(listenersBySocket, socket).locations[locationName] = req;
279 }
280
281 foreach (var pad in req.references.pads)
282 {
283 GetOrAddListeners(listenersByPad, pad).locations[locationName] = req;
284 }
285
286 foreach (var waterwheel in req.references.waterwheels)
287 {
288 GetOrAddListeners(listenersByWaterwheel, waterwheel).locations[locationName] = req;
289 }
290
291 foreach (var sphere in req.references.spheres)
292 {
293 GetOrAddListeners(listenersBySphere, sphere).locations[locationName] = req;
294 }
295
296 foreach (var item in req.references.items)
297 {
298 GetOrAddListeners(listenersByItem, item).locations[locationName] = req;
299 }
300 }
301 }
302
303 if (sceneDetails.ContainsKey("doors"))
304 {
305 var sceneDoors = (Dictionary<object, object>)sceneDetails["doors"];
306
307 foreach (var doorPair in sceneDoors)
308 {
309 SceneItemReference sir = new(scenePair.Key, int.Parse((string)doorPair.Key));
310 Requirement req = ParseRequirement(scenePair.Key, (Dictionary<object, object>)doorPair.Value);
311 sceneDescription.doors[sir.index] = req;
312
313 Plugin.Logger.LogInfo($"Door {sir} requirements: {req}");
314
315 foreach (var reqScene in req.references.scenes)
316 {
317 GetOrAddListeners(listenersByScene, reqScene).doors[sir] = req;
318 }
319
320 foreach (var button in req.references.buttons)
321 {
322 GetOrAddListeners(listenersByButton, button).doors[sir] = req;
323 }
324
325 foreach (var socket in req.references.sockets)
326 {
327 GetOrAddListeners(listenersBySocket, socket).doors[sir] = req;
328 }
329
330 foreach (var pad in req.references.pads)
331 {
332 GetOrAddListeners(listenersByPad, pad).doors[sir] = req;
333 }
334
335 foreach (var waterwheel in req.references.waterwheels)
336 {
337 GetOrAddListeners(listenersByWaterwheel, waterwheel).doors[sir] = req;
338 }
339
340 foreach (var sphere in req.references.spheres)
341 {
342 GetOrAddListeners(listenersBySphere, sphere).doors[sir] = req;
343 }
344
345 foreach (var item in req.references.items)
346 {
347 GetOrAddListeners(listenersByItem, item).doors[sir] = req;
348 }
349 }
350 }
351
352 if (sceneDetails.ContainsKey("smoke"))
353 {
354 foreach (var smokePair in (Dictionary<object, object>)sceneDetails["smoke"])
355 {
356 SceneItemReference sir = new(scenePair.Key, int.Parse((string)smokePair.Key));
357 Requirement req = ParseRequirement(scenePair.Key, (Dictionary<object, object>)smokePair.Value);
358 sceneDescription.smokeWalls[sir.index] = req;
359
360 foreach (var reqScene in req.references.scenes)
361 {
362 GetOrAddListeners(listenersByScene, reqScene).smokeWalls[sir] = req;
363 }
364
365 foreach (var button in req.references.buttons)
366 {
367 GetOrAddListeners(listenersByButton, button).smokeWalls[sir] = req;
368 }
369
370 foreach (var socket in req.references.sockets)
371 {
372 GetOrAddListeners(listenersBySocket, socket).smokeWalls[sir] = req;
373 }
374
375 foreach (var pad in req.references.pads)
376 {
377 GetOrAddListeners(listenersByPad, pad).smokeWalls[sir] = req;
378 }
379
380 foreach (var waterwheel in req.references.waterwheels)
381 {
382 GetOrAddListeners(listenersByWaterwheel, waterwheel).smokeWalls[sir] = req;
383 }
384
385 foreach (var sphere in req.references.spheres)
386 {
387 GetOrAddListeners(listenersBySphere, sphere).smokeWalls[sir] = req;
388 }
389
390 foreach (var item in req.references.items)
391 {
392 GetOrAddListeners(listenersByItem, item).smokeWalls[sir] = req;
393 }
394 }
395 }
396
397 if (sceneDetails.ContainsKey("world_grow"))
398 {
399 foreach (var worldPair in (Dictionary<object, object>)sceneDetails["world_grow"])
400 {
401 SceneItemReference sir = new(scenePair.Key, int.Parse((string)worldPair.Key));
402 Requirement req = ParseRequirement(scenePair.Key, (Dictionary<object, object>)worldPair.Value);
403 sceneDescription.worldGrows[sir.index] = req;
404
405 foreach (var reqScene in req.references.scenes)
406 {
407 GetOrAddListeners(listenersByScene, reqScene).worldGrows[sir] = req;
408 }
409
410 foreach (var button in req.references.buttons)
411 {
412 GetOrAddListeners(listenersByButton, button).worldGrows[sir] = req;
413 }
414
415 foreach (var socket in req.references.sockets)
416 {
417 GetOrAddListeners(listenersBySocket, socket).worldGrows[sir] = req;
418 }
419
420 foreach (var pad in req.references.pads)
421 {
422 GetOrAddListeners(listenersByPad, pad).worldGrows[sir] = req;
423 }
424
425 foreach (var waterwheel in req.references.waterwheels)
426 {
427 GetOrAddListeners(listenersByWaterwheel, waterwheel).worldGrows[sir] = req;
428 }
429
430 foreach (var sphere in req.references.spheres)
431 {
432 GetOrAddListeners(listenersBySphere, sphere).worldGrows[sir] = req;
433 }
434
435 foreach (var item in req.references.items)
436 {
437 GetOrAddListeners(listenersByItem, item).worldGrows[sir] = req;
438 }
439 }
440 }
441
442 scenes[scenePair.Key] = sceneDescription;
443 }
444 }
17 } 445 }
18} 446}
diff --git a/GameState.cs b/GameState.cs new file mode 100644 index 0000000..d3bea06 --- /dev/null +++ b/GameState.cs
@@ -0,0 +1,167 @@
1using System;
2using System.Linq;
3using System.Reflection;
4using UnityEngine;
5using UnityEngine.SceneManagement;
6
7namespace ManifoldGardenArchipelago
8{
9 public class GameState
10 {
11 public static LevelSystems GetLevelSystems(Component component)
12 {
13 return component.gameObject.scene.GetRootGameObjects().Single((obj) => obj.name == "Level Systems").GetComponent<LevelSystems>();
14 }
15
16 public static LevelSystems GetLevelSystems(Scene scene)
17 {
18 return scene.GetRootGameObjects().Single((obj) => obj.name == "Level Systems").GetComponent<LevelSystems>();
19 }
20
21 public static SceneItemReference GetChainListenerSceneReference(Component component)
22 {
23 LevelSystems levelSystem = GetLevelSystems(component);
24
25 for (int i = 0; i < levelSystem.chainListeners.Count(); i++)
26 {
27 if (levelSystem.chainListeners[i] == component)
28 {
29 return new(component.gameObject.scene.name, i);
30 }
31 }
32
33 throw new Exception("Shouldn't happen");
34 }
35
36 public static SceneItemReference GetButtonSceneReference(ButtonLineActivator arg)
37 {
38 LevelSystems levelSystem = GetLevelSystems(arg);
39
40 for (int i = 0; i < levelSystem.buttonLineActivators.Count(); i++)
41 {
42 if (levelSystem.buttonLineActivators[i] == arg)
43 {
44 return new(arg.gameObject.scene.name, i);
45 }
46 }
47
48 throw new Exception("Shouldn't happen");
49 }
50
51 public static SceneItemReference GetPadSceneReference(CubeLineActivator arg)
52 {
53 LevelSystems levelSystem = GetLevelSystems(arg);
54
55 for (int i = 0; i < levelSystem.cubeLineActivators.Count(); i++)
56 {
57 if (levelSystem.cubeLineActivators[i] == arg)
58 {
59 return new(arg.gameObject.scene.name, i);
60 }
61 }
62
63 throw new Exception("Shouldn't happen");
64 }
65
66 public static SceneItemReference GetSocketSceneReference(CubeReceiverController arg)
67 {
68 LevelSystems levelSystem = GetLevelSystems(arg);
69
70 for (int i = 0; i < levelSystem.cubeReceiverControllers.Count(); i++)
71 {
72 if (levelSystem.cubeReceiverControllers[i] == arg)
73 {
74 return new(arg.gameObject.scene.name, i);
75 }
76 }
77
78 throw new Exception("Shouldn't happen");
79 }
80
81 public static SceneItemReference GetSphereSceneReference(SphereController arg)
82 {
83 LevelSystems levelSystem = GetLevelSystems(arg);
84
85 for (int i = 0; i < levelSystem.sphereControllers.Count(); i++)
86 {
87 if (levelSystem.sphereControllers[i] == arg)
88 {
89 return new(arg.gameObject.scene.name, i);
90 }
91 }
92
93 throw new Exception("Shouldn't happen");
94 }
95
96 public static SceneItemReference GetWaterwheelSceneReference(WaterDetector arg)
97 {
98 LevelSystems levelSystem = GetLevelSystems(arg);
99
100 for (int i = 0; i < levelSystem.waterDetectors.Count(); i++)
101 {
102 if (levelSystem.waterDetectors[i] == arg)
103 {
104 return new(arg.gameObject.scene.name, i);
105 }
106 }
107
108 throw new Exception("Shouldn't happen");
109 }
110
111 public static void EvaluateGameStateListeners(GameStateListeners listeners)
112 {
113 foreach (var location in listeners.locations)
114 {
115 if (location.Value.Check())
116 {
117 Plugin.archipelagoManager.CheckLocation(location.Key);
118 }
119 }
120
121 foreach (var door in listeners.doors)
122 {
123 bool shouldOpen = door.Value.Check();
124 Plugin.Logger.LogInfo($"{door.Key}: {door.Value} -> {shouldOpen}");
125
126 if (SceneManager.GetSceneByName(door.Key.scene) is Scene doorScene && doorScene.isLoaded)
127 {
128 LevelSystems levelSystems = GetLevelSystems(doorScene);
129 if (levelSystems.isActiveAndEnabled)
130 {
131 LineDoorController ldc = levelSystems.chainListeners[door.Key.index].GetComponent<LineDoorController>();
132 ldc.chains.Clear();
133
134 FieldInfo fieldInfo = typeof(LineDoorController).GetField("doorShouldOpenImmediatelyOnEnable", BindingFlags.Instance | BindingFlags.NonPublic);
135 fieldInfo.SetValue(ldc, shouldOpen);
136
137 MethodInfo methodInfo = typeof(LineDoorController).GetMethod("SetDoorOpenCloseStateAnimated", BindingFlags.NonPublic | BindingFlags.Instance);
138 methodInfo.Invoke(ldc, [shouldOpen, false]);
139
140 ldc.saveData.isOpen = shouldOpen;
141 }
142 }
143 }
144
145
146
147 foreach (var worldGrow in listeners.worldGrows)
148 {
149 if (worldGrow.Value.Check())
150 {
151 if (SceneManager.GetSceneByName(worldGrow.Key.scene) is Scene gardenScene && gardenScene.isLoaded)
152 {
153 LevelSystems levelSystems = GetLevelSystems(gardenScene);
154 if (levelSystems.isActiveAndEnabled)
155 {
156 DarkModeCollapsedCubeWorldGrow ldc = levelSystems.chainListeners[worldGrow.Key.index].GetComponent<DarkModeCollapsedCubeWorldGrow>();
157 ldc.chains.Clear();
158
159 FieldInfo fieldInfo = typeof(DarkModeCollapsedCubeWorldGrow).GetField("m_grown", BindingFlags.Instance | BindingFlags.NonPublic);
160 fieldInfo.SetValue(ldc, true);
161 }
162 }
163 }
164 }
165 }
166 }
167}
diff --git a/GameplayPatches.cs b/GameplayPatches.cs new file mode 100644 index 0000000..e0767c4 --- /dev/null +++ b/GameplayPatches.cs
@@ -0,0 +1,237 @@
1using HarmonyLib;
2using System.Reflection;
3
4namespace ManifoldGardenArchipelago
5{
6 [HarmonyPatch(typeof(LineDoorController), "SetDoorOpenCloseStateAnimated")]
7 static class LineDoorControllerSetDoorOpenCloseStateAnimatedPatch
8 {
9 static bool Prefix(LineDoorController __instance, bool open)
10 {
11 SceneItemReference sir = GameState.GetChainListenerSceneReference(__instance);
12
13 if (GameData.scenes.TryGetValue(sir.scene, out var sceneDescription))
14 {
15 if (sceneDescription.doors.TryGetValue(sir.index, out var requirement))
16 {
17 if (open != requirement.Check())
18 {
19 return false;
20 }
21 }
22 }
23
24 return true;
25 }
26 }
27
28 [HarmonyPatch(typeof(ButtonLineActivator), "ToggleButtonPress")]
29 static class ButtonLineActivatorToggleButtonPressPatch
30 {
31 static void Prefix(ButtonLineActivator __instance, bool setPressed)
32 {
33 SceneItemReference sir = GameState.GetButtonSceneReference(__instance);
34 Plugin.Logger.LogInfo($"Button {sir} state {setPressed}");
35
36 if (!GameData.listenersByButton.TryGetValue(sir, out GameStateListeners listeners))
37 {
38 return;
39 }
40
41 if (setPressed)
42 {
43 Plugin.slotSave.ActivatedButtons.Add(sir);
44 }
45 else
46 {
47 Plugin.slotSave.ActivatedButtons.Remove(sir);
48 }
49
50 GameState.EvaluateGameStateListeners(listeners);
51 }
52 }
53
54 [HarmonyPatch(typeof(CubeReceiverController), "SetActivatorActive")]
55 static class CubeReceiverControllerSetActivatorActivePatch
56 {
57 static void Prefix(CubeReceiverController __instance, bool active)
58 {
59 SceneItemReference sir = GameState.GetSocketSceneReference(__instance);
60 Plugin.Logger.LogInfo($"Socket {sir} state {active}");
61
62 if (!GameData.listenersBySocket.TryGetValue(sir, out GameStateListeners listeners))
63 {
64 return;
65 }
66
67 if (active)
68 {
69 Plugin.slotSave.ActivatedSockets.Add(sir);
70 }
71 else
72 {
73 Plugin.slotSave.ActivatedSockets.Remove(sir);
74 }
75
76 GameState.EvaluateGameStateListeners(listeners);
77 }
78 }
79
80 [HarmonyPatch(typeof(CubeLineActivator), "SetActivatorActive")]
81 static class CubeLineActivatorSetActivatorActivePatch
82 {
83 static void Prefix(CubeLineActivator __instance, bool setActive)
84 {
85 SceneItemReference sir = GameState.GetPadSceneReference(__instance);
86 Plugin.Logger.LogInfo($"Pad {sir} state {setActive}");
87
88 if (!GameData.listenersByPad.TryGetValue(sir, out GameStateListeners listeners))
89 {
90 return;
91 }
92
93 if (setActive)
94 {
95 Plugin.slotSave.ActivatedPads.Add(sir);
96 }
97 else
98 {
99 Plugin.slotSave.ActivatedPads.Remove(sir);
100 }
101
102 GameState.EvaluateGameStateListeners(listeners);
103 }
104 }
105
106 [HarmonyPatch(typeof(WaterDetector), "SetActivatorActive")]
107 static class WaterDetectorSetActivatorActivePatch
108 {
109 static void Prefix(WaterDetector __instance, bool setActive)
110 {
111 SceneItemReference sir = GameState.GetWaterwheelSceneReference(__instance);
112 Plugin.Logger.LogInfo($"Waterwheel {sir} state {setActive}");
113
114 if (!GameData.listenersByWaterwheel.TryGetValue(sir, out GameStateListeners listeners))
115 {
116 return;
117 }
118
119 if (setActive)
120 {
121 Plugin.slotSave.ActivatedWaterwheels.Add(sir);
122 }
123 else
124 {
125 Plugin.slotSave.ActivatedWaterwheels.Remove(sir);
126 }
127
128 GameState.EvaluateGameStateListeners(listeners);
129 }
130 }
131
132 [HarmonyPatch(typeof(SphereController), nameof(SphereController.SetState))]
133 static class SphereControllerSetStatePatch
134 {
135 static void Prefix(SphereController __instance, SphereController.State newState)
136 {
137 SceneItemReference sir = GameState.GetSphereSceneReference(__instance);
138 Plugin.Logger.LogInfo($"Sphere {sir} state {newState}");
139
140 if (!GameData.listenersBySphere.TryGetValue(sir, out GameStateListeners listeners))
141 {
142 return;
143 }
144
145 if (newState == SphereController.State.AtDestination)
146 {
147 Plugin.slotSave.ActivatedSpheres.Add(sir);
148 }
149 else
150 {
151 Plugin.slotSave.ActivatedSpheres.Remove(sir);
152 }
153
154 GameState.EvaluateGameStateListeners(listeners);
155 }
156 }
157
158 [HarmonyPatch(typeof(LineDoorController), nameof(LineDoorController.OnLevelEnable))]
159 static class LineDoorControllerOnLevelEnablePatch
160 {
161 static void Prefix(LineDoorController __instance)
162 {
163 SceneItemReference sir = GameState.GetChainListenerSceneReference(__instance);
164
165 if (GameData.scenes.TryGetValue(sir.scene, out var sceneDescription))
166 {
167 if (sceneDescription.doors.TryGetValue(sir.index, out var requirement))
168 {
169 FieldInfo fieldInfo = typeof(LineDoorController).GetField("doorShouldOpenImmediatelyOnEnable", BindingFlags.Instance | BindingFlags.NonPublic);
170 fieldInfo.SetValue(__instance, requirement.Check());
171
172 __instance.chains.Clear();
173 }
174 }
175 }
176 }
177
178 [HarmonyPatch(typeof(LineDoorController), nameof(LineDoorController.OnLoad))]
179 static class LineDoorControllerOnLoadPatch
180 {
181 static void Prefix(LineDoorController __instance)
182 {
183 SceneItemReference sir = GameState.GetChainListenerSceneReference(__instance);
184
185 if (GameData.scenes.TryGetValue(sir.scene, out var sceneDescription))
186 {
187 if (sceneDescription.doors.TryGetValue(sir.index, out var requirement))
188 {
189 FieldInfo fieldInfo = typeof(LineDoorController).GetField("doorShouldOpenImmediatelyOnEnable", BindingFlags.Instance | BindingFlags.NonPublic);
190 fieldInfo.SetValue(__instance, requirement.Check());
191
192 __instance.chains.Clear();
193 }
194 }
195 }
196 }
197
198 [HarmonyPatch(typeof(LevelLoader), nameof(LevelLoader.MovePlayerIntoNewScene))]
199 static class LevelLoaderMovePlayerIntoNewScenePatch
200 {
201 static void Postfix(LevelSystems newLevel)
202 {
203 Plugin.slotSave.VisitedScenes.Add(newLevel.levelName);
204
205 if (GameData.listenersByScene.TryGetValue(newLevel.levelName, out var listeners))
206 {
207 GameState.EvaluateGameStateListeners(listeners);
208 }
209 }
210 }
211
212 [HarmonyPatch(typeof(DarkModeCollapsedCubeWorldGrow), nameof(DarkModeCollapsedCubeWorldGrow.OnChainFillComplete))]
213 static class DarkModeCollapsedCubeWorldGrowOnChainFillCompletePatch
214 {
215 static bool Prefix()
216 {
217 return false;
218 }
219 }
220
221 [HarmonyPatch(typeof(DarkModeCollapsedCubeWorldGrow), nameof(DarkModeCollapsedCubeWorldGrow.OnLevelEnable))]
222 static class DarkModeCollapsedCubeWorldGrowOnLevelEnablePatch
223 {
224 static void Prefix(DarkModeCollapsedCubeWorldGrow __instance)
225 {
226 SceneItemReference sir = GameState.GetChainListenerSceneReference(__instance);
227
228 if (GameData.scenes.TryGetValue(sir.scene, out var sceneDescription) &&
229 sceneDescription.worldGrows.TryGetValue(sir.index, out var requirement) &&
230 requirement.Check())
231 {
232 FieldInfo fieldInfo = typeof(DarkModeCollapsedCubeWorldGrow).GetField("m_grown", BindingFlags.Instance | BindingFlags.NonPublic);
233 fieldInfo.SetValue(__instance, true);
234 }
235 }
236 }
237}
diff --git a/ManifoldGardenArchipelago.csproj b/ManifoldGardenArchipelago.csproj index 5d20001..67e0dab 100644 --- a/ManifoldGardenArchipelago.csproj +++ b/ManifoldGardenArchipelago.csproj
@@ -25,6 +25,7 @@
25 <PackageReference Include="BepInEx.Core" Version="5.*" /> 25 <PackageReference Include="BepInEx.Core" Version="5.*" />
26 <PackageReference Include="BepInEx.PluginInfoProps" Version="2.*" /> 26 <PackageReference Include="BepInEx.PluginInfoProps" Version="2.*" />
27 <PackageReference Include="UnityEngine.Modules" Version="2019.3.15" IncludeAssets="compile" /> 27 <PackageReference Include="UnityEngine.Modules" Version="2019.3.15" IncludeAssets="compile" />
28 <PackageReference Include="YamlDotNet" Version="13.7.1" />
28 </ItemGroup> 29 </ItemGroup>
29 30
30 <ItemGroup Condition="'$(TargetFramework.TrimEnd(`0123456789`))' == 'net'"> 31 <ItemGroup Condition="'$(TargetFramework.TrimEnd(`0123456789`))' == 'net'">
@@ -36,4 +37,10 @@
36 <HintPath>D:\SteamLibrary\steamapps\common\Manifold Garden\ManifoldGarden_Data\Managed\Assembly-CSharp.dll</HintPath> 37 <HintPath>D:\SteamLibrary\steamapps\common\Manifold Garden\ManifoldGarden_Data\Managed\Assembly-CSharp.dll</HintPath>
37 </Reference> 38 </Reference>
38 </ItemGroup> 39 </ItemGroup>
40
41 <ItemGroup>
42 <None Update="game_data.yaml">
43 <CopyToOutputDirectory>Always</CopyToOutputDirectory>
44 </None>
45 </ItemGroup>
39</Project> 46</Project>
diff --git a/Plugin.cs b/Plugin.cs index 50e6e2b..df53e96 100644 --- a/Plugin.cs +++ b/Plugin.cs
@@ -1,7 +1,6 @@
1using BepInEx; 1using BepInEx;
2using BepInEx.Logging; 2using BepInEx.Logging;
3using HarmonyLib; 3using HarmonyLib;
4using System.Linq;
5using System.Reflection; 4using System.Reflection;
6 5
7namespace ManifoldGardenArchipelago 6namespace ManifoldGardenArchipelago
@@ -12,6 +11,7 @@ namespace ManifoldGardenArchipelago
12 internal static new ManualLogSource Logger; 11 internal static new ManualLogSource Logger;
13 12
14 public static ArchipelagoManager archipelagoManager = new(); 13 public static ArchipelagoManager archipelagoManager = new();
14 public static SlotSave slotSave = new();
15 15
16 private void Awake() 16 private void Awake()
17 { 17 {
@@ -21,6 +21,7 @@ namespace ManifoldGardenArchipelago
21 21
22 Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly()); 22 Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly());
23 23
24 GameData.Load();
24 archipelagoManager.Connect("localhost:38281", "Manifold", "").RunSynchronously(); 25 archipelagoManager.Connect("localhost:38281", "Manifold", "").RunSynchronously();
25 } 26 }
26 } 27 }
@@ -34,52 +35,6 @@ namespace ManifoldGardenArchipelago
34 } 35 }
35 } 36 }
36 37
37 [HarmonyPatch(typeof(LineDoorController), "SetDoorOpenCloseStateAnimated")]
38 static class LineDoorControllerSetDoorOpenCloseStateAnimatedPatch
39 {
40 static bool Prefix(LineDoorController __instance, bool open)
41 {
42 if (Plugin.archipelagoManager.chainListenersByScene.TryGetValue(__instance.gameObject.scene.name, out var activatedChainListeners))
43 {
44 LevelSystems levelSystem = __instance.gameObject.scene.GetRootGameObjects().Single((obj) => obj.name == "Level Systems").GetComponent<LevelSystems>();
45
46 foreach (var listener in activatedChainListeners)
47 {
48 if (levelSystem.chainListeners[listener.Key] == __instance)
49 {
50 if (open != listener.Value)
51 {
52 return false;
53 }
54 }
55 }
56 }
57
58 return true;
59 }
60 }
61
62 [HarmonyPatch(typeof(LineDoorController), nameof(LineDoorController.OnLevelEnable))]
63 static class LineDoorControllerOnLevelEnablePatch
64 {
65 static void Prefix(LineDoorController __instance)
66 {
67 if (Plugin.archipelagoManager.chainListenersByScene.TryGetValue(__instance.gameObject.scene.name, out var activatedChainListeners))
68 {
69 LevelSystems levelSystem = __instance.gameObject.scene.GetRootGameObjects().Single((obj) => obj.name == "Level Systems").GetComponent<LevelSystems>();
70
71 foreach (var listener in activatedChainListeners)
72 {
73 if (levelSystem.chainListeners[listener.Key] == __instance)
74 {
75 FieldInfo fieldInfo = typeof(LineDoorController).GetField("doorShouldOpenImmediatelyOnEnable", BindingFlags.Instance | BindingFlags.NonPublic);
76 fieldInfo.SetValue(__instance, listener.Value);
77 }
78 }
79 }
80 }
81 }
82
83 [HarmonyPatch(typeof(ButtonLineActivator), nameof(ButtonLineActivator.OnInteract))] 38 [HarmonyPatch(typeof(ButtonLineActivator), nameof(ButtonLineActivator.OnInteract))]
84 static class ButtonLineActivatorOnInteractPatch 39 static class ButtonLineActivatorOnInteractPatch
85 { 40 {
diff --git a/Requirements.cs b/Requirements.cs new file mode 100644 index 0000000..987a299 --- /dev/null +++ b/Requirements.cs
@@ -0,0 +1,288 @@
1using System.Collections.Generic;
2using System.Linq;
3
4namespace ManifoldGardenArchipelago
5{
6 public struct RequirementReferences
7 {
8 public RequirementReferences() { }
9
10 public readonly HashSet<string> scenes = [];
11 public readonly HashSet<SceneItemReference> buttons = [];
12 public readonly HashSet<SceneItemReference> sockets = [];
13 public readonly HashSet<SceneItemReference> pads = [];
14 public readonly HashSet<SceneItemReference> waterwheels = [];
15 public readonly HashSet<SceneItemReference> spheres = [];
16 public readonly HashSet<string> items = [];
17
18 public void Merge(RequirementReferences other)
19 {
20 foreach (var item in other.scenes)
21 {
22 scenes.Add(item);
23 }
24 foreach (var item in other.buttons)
25 {
26 buttons.Add(item);
27 }
28 foreach (var item in other.sockets)
29 {
30 sockets.Add(item);
31 }
32 foreach (var item in other.pads)
33 {
34 pads.Add(item);
35 }
36 foreach (var item in other.waterwheels)
37 {
38 waterwheels.Add(item);
39 }
40 foreach (var item in other.spheres)
41 {
42 spheres.Add(item);
43 }
44 foreach (var item in other.items)
45 {
46 items.Add(item);
47 }
48 }
49 }
50
51 public abstract class Requirement
52 {
53 public RequirementReferences references = new();
54
55 public abstract bool Check();
56 }
57
58 public class AndRequirement : Requirement
59 {
60 private readonly List<Requirement> _requirements;
61
62 public AndRequirement(List<Requirement> requirements)
63 {
64 _requirements = requirements;
65
66 foreach (var subreq in _requirements)
67 {
68 references.Merge(subreq.references);
69 }
70 }
71
72 public override bool Check()
73 {
74 return _requirements.All(x => x.Check());
75 }
76
77 public override string ToString()
78 {
79 return "And(" + string.Join(", ", _requirements.Select((req) => req.ToString())) + ")";
80 }
81 }
82
83 public class OrRequirement : Requirement
84 {
85 private readonly List<Requirement> _requirements;
86
87 public OrRequirement(List<Requirement> requirements)
88 {
89 _requirements = requirements;
90
91 foreach (var subreq in _requirements)
92 {
93 references.Merge(subreq.references);
94 }
95 }
96
97 public override bool Check()
98 {
99 return _requirements.Any(x => x.Check());
100 }
101
102 public override string ToString()
103 {
104 return "Or(" + string.Join(", ", _requirements.Select((req) => req.ToString())) + ")";
105 }
106 }
107
108 public class ItemRequirement : Requirement
109 {
110 private readonly string _itemName;
111
112 public ItemRequirement(string itemName)
113 {
114 _itemName = itemName;
115
116 references.items.Add(itemName);
117 }
118
119 public override bool Check()
120 {
121 return Plugin.archipelagoManager.HasItem(_itemName);
122 }
123
124 public override string ToString()
125 {
126 return $"Item({_itemName})";
127 }
128 }
129
130 public class EntryRequirement : Requirement
131 {
132 private readonly string _sceneName;
133
134 public EntryRequirement(string sceneName)
135 {
136 _sceneName = sceneName;
137
138 references.scenes.Add(sceneName);
139 }
140
141 public override bool Check()
142 {
143 return Plugin.slotSave.VisitedScenes.Contains(_sceneName);
144 }
145
146 public override string ToString()
147 {
148 return $"Entry({_sceneName})";
149 }
150 }
151
152 public class ButtonRequirement : Requirement
153 {
154 private readonly SceneItemReference _button;
155
156 public ButtonRequirement(SceneItemReference button)
157 {
158 _button = button;
159
160 references.scenes.Add(button.scene);
161 references.buttons.Add(button);
162 }
163
164 public override bool Check()
165 {
166 return Plugin.slotSave.ActivatedButtons.Contains(_button);
167 }
168
169 public override string ToString()
170 {
171 return $"Button{_button}";
172 }
173 }
174
175 public class SocketRequirement : Requirement
176 {
177 private readonly SceneItemReference _socket;
178
179 public SocketRequirement(SceneItemReference socket)
180 {
181 _socket = socket;
182
183 references.scenes.Add(socket.scene);
184 references.sockets.Add(socket);
185 }
186
187 public override bool Check()
188 {
189 return Plugin.slotSave.ActivatedSockets.Contains(_socket);
190 }
191
192 public override string ToString()
193 {
194 return $"Socket{_socket}";
195 }
196 }
197
198 public class PadRequirement : Requirement
199 {
200 private readonly SceneItemReference _pad;
201
202 public PadRequirement(SceneItemReference pad)
203 {
204 _pad = pad;
205
206 references.scenes.Add(pad.scene);
207 references.pads.Add(pad);
208 }
209
210 public override bool Check()
211 {
212 return Plugin.slotSave.ActivatedPads.Contains(_pad);
213 }
214
215 public override string ToString()
216 {
217 return $"Pad{_pad}";
218 }
219 }
220
221 public class WaterwheelRequirement : Requirement
222 {
223 private readonly SceneItemReference _waterwheel;
224
225 public WaterwheelRequirement(SceneItemReference waterwheel)
226 {
227 _waterwheel = waterwheel;
228
229 references.scenes.Add(waterwheel.scene);
230 references.waterwheels.Add(waterwheel);
231 }
232
233 public override bool Check()
234 {
235 return Plugin.slotSave.ActivatedWaterwheels.Contains(_waterwheel);
236 }
237
238 public override string ToString()
239 {
240 return $"Waterwheel{_waterwheel}";
241 }
242 }
243
244 public class SphereRequirement : Requirement
245 {
246 private readonly SceneItemReference _sphere;
247
248 public SphereRequirement(SceneItemReference sphere)
249 {
250 _sphere = sphere;
251
252 references.scenes.Add(sphere.scene);
253 references.spheres.Add(sphere);
254 }
255
256 public override bool Check()
257 {
258 return Plugin.slotSave.ActivatedSpheres.Contains(_sphere);
259 }
260
261 public override string ToString()
262 {
263 return $"Sphere{_sphere}";
264 }
265 }
266
267 public class InvertedRequirement : Requirement
268 {
269 private readonly Requirement _requirement;
270
271 public InvertedRequirement(Requirement requirement)
272 {
273 _requirement = requirement;
274
275 references = requirement.references;
276 }
277
278 public override bool Check()
279 {
280 return !_requirement.Check();
281 }
282
283 public override string ToString()
284 {
285 return $"Not({_requirement})";
286 }
287 }
288}
diff --git a/SlotSave.cs b/SlotSave.cs new file mode 100644 index 0000000..6261890 --- /dev/null +++ b/SlotSave.cs
@@ -0,0 +1,18 @@
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Text;
5using System.Threading.Tasks;
6
7namespace ManifoldGardenArchipelago
8{
9 public class SlotSave
10 {
11 public readonly HashSet<string> VisitedScenes = [];
12 public readonly HashSet<SceneItemReference> ActivatedButtons = [];
13 public readonly HashSet<SceneItemReference> ActivatedSockets = [];
14 public readonly HashSet<SceneItemReference> ActivatedPads = [];
15 public readonly HashSet<SceneItemReference> ActivatedWaterwheels = [];
16 public readonly HashSet<SceneItemReference> ActivatedSpheres = [];
17 }
18}
diff --git a/game_data.yaml b/game_data.yaml new file mode 100644 index 0000000..93c3612 --- /dev/null +++ b/game_data.yaml
@@ -0,0 +1,735 @@
1World_000_Optimized:
2 locations:
3 Tutorial Completed:
4 socket: 0
5 doors:
6 16:
7 item: Blue Secret Path
8 or:
9 - socket: 2
10 - button:
11 scene: Hallway_W000_W052_Optimized
12 index: 0
13World_062_LargeGap_Optimized:
14 locations:
15 Large Gap Puzzle Solved:
16 socket: 0
17 Secret Hub Green Button:
18 button: 3
19 Secret Hub Garnier Button:
20 button: 5
21 Secret Hub Back Door Button:
22 button: 1
23 Secret Hub Front Door Button:
24 button: 4
25 doors:
26 10:
27 item: Secret Hub Garnier Entrance
28 or:
29 - entry: Hallway_W062_W900_Optimized
30 - button: 5
31 0:
32 item: Large Gap Exterior Door
33 or:
34 - button: 0
35 - socket: 0
36 8:
37 item: Large Gap Exterior Secret Door
38 or:
39 - button: 0
40 - button: 4
41 7:
42 or:
43 - pad: 0
44 - button: 2
45 4:
46 or:
47 - socket: 0
48 - button: 2
49 3:
50 item: Library Main Entrance
51 or:
52 - button: 2
53 - button:
54 scene: Hallway_W062_W026_Optimized
55 index: 0
56 1:
57 item: Large Gap Interior Secret Door
58 or:
59 - button: 2
60 - button: 1
61 5:
62 item: Secret Hub Orange Entrance
63 or:
64 - button: 3
65 - button:
66 scene: Hallway_W062_W063_Optimized
67 index: 0
68Hallway_W062_W063_Optimized:
69 doors:
70 0:
71 item: Secret Hub Orange Entrance
72 or:
73 - button: 0
74 - socket:
75 scene: World_063_TowerX_Optimized
76 index: 0
77World_001_Optimized:
78 # lasers are called DarkModeCageControllers; will handle them separately bc most worlds don't item-lock them
79 locations:
80 Blue - Red Laser Activated:
81 socket: 5
82 Blue - Yellow Laser Activated:
83 socket: 4
84 Blue - Green Laser Activated:
85 socket: 3
86 Blue - Tree Purified:
87 entry: AudioVisual_001_Optimized
88World_044_CubicSpaceDivision_Optimized:
89 locations:
90 Blue God Cube Planted:
91 socket: 5
92 Red God Cube Planted:
93 socket: 2
94 Green God Cube Planted:
95 socket: 3
96 Yellow God Cube Planted:
97 socket: 0
98 Purple God Cube Planted:
99 socket: 1
100 Orange God Cube Planted:
101 socket: 4
102 world_grow:
103 # These are in chainListeners just like doors
104 5:
105 item: Blue Garden
106 0:
107 item: Yellow Garden
108 1:
109 item: Purple Garden
110 2:
111 item: Red Garden
112 3:
113 item: Green Garden
114 4:
115 item: Orange Garden
116 doors:
117 12:
118 item: Orange Garden
119World_044B_CubicSpaceDivision_Optimized:
120 doors:
121 0:
122 item: Blue Garden
123 or:
124 - button: 0
125 - button:
126 scene: Hallway_W044_W015_Optimized
127 index: 1
128Hallway_W044_W015_Optimized:
129 locations:
130 Tower Orange Tree Puzzle Solved:
131 socket: [0, 1]
132 doors:
133 5:
134 or:
135 - button: 1
136 - socket: [0, 1]
137 1:
138 item: Stepwell Entrance
139 or:
140 - socket: [0, 1]
141 - button:
142 scene: World_015_Stepwell_Optimized
143 index: 1
144World_613_LongBridgeLongTower_Optimized:
145 locations:
146 Long Bridge Long Tower Puzzle Solved:
147 pad: 0
148 doors:
149 1:
150 item: Yellow Secret Path
151 or:
152 - socket:
153 - scene: Hallway_W044_W015_Optimized
154 index: 0
155 - scene: Hallway_W044_W015_Optimized
156 index: 1
157 - button: 0
158 0:
159 or:
160 - button: 0
161 - pad: 0
162World_015_Stepwell_Optimized:
163 doors:
164 1:
165 or:
166 - button: 0
167 - button: 1
168 0:
169 or:
170 - button: 1
171 - pad:
172 scene: Hallway_W015_W041_Optimized
173 index: 2
174Hallway_W015_W041_Optimized:
175 doors:
176 3:
177 or:
178 - pad: 2
179 - button: 0
180 1:
181 or:
182 - button: 0
183 - pad: 3
184 4:
185 item: Stepwell Entrance
186 or:
187 - pad: 3
188 - button:
189 scene: World_045_FirstHub_Optimized
190 index: 0
191World_045_FirstHub_Optimized:
192 locations:
193 Pyramid First Exit Opened:
194 button: 0
195 Pyramid Second Exit Opened:
196 socket: 0
197 Pyramid Waterwheel Activated:
198 waterwheel: 0
199 Pyramid Lower Tree Reached:
200 inverted:
201 pad: 0
202 Pyramid Upper Tree Reached:
203 inverted:
204 pad: 1
205 doors:
206 11:
207 item: Path to Red
208 or:
209 - button: 0
210 - pad:
211 - scene: Hallway_W045_W018_Optimized
212 index: 0
213 - scene: Hallway_W045_W018_Optimized
214 index: 1
215 0:
216 item: Path to Green
217 or:
218 - button: 0
219 - button:
220 scene: Hallway_045_053_Optimized
221 index: 0
222 6:
223 item: Apartments Entrance
224 or:
225 - button: 0
226 - button:
227 scene: Hallway_W045_W073_Optimized
228 index: 0
229 smoke:
230 2:
231 item: Pyramid - Red Smoke Wall
232 or:
233 - button: 0
234 - inverted:
235 pad: 0
236 4:
237 item: Pyramid - Green Smoke Wall
238 or:
239 - button: 0
240 - inverted:
241 pad: 1
242Hallway_W045_W018_Optimized:
243 locations:
244 Purple/Orange Tree Puzzle Solved:
245 pad: [0, 1]
246 doors:
247 1:
248 or:
249 - pad: [0, 1]
250 - button:
251 scene: Hallway_W041_W018_Optimized
252 index: 0
253Hallway_W041_W018_Optimized:
254 doors:
255 0:
256 or:
257 - button: 0
258 - button:
259 scene: World_018_PastaTile_Optimized
260 index: 0
261World_612_BlindSpherePuzzle_Optimized:
262 locations:
263 Blind Sphere Puzzle Solved:
264 sphere: 0
265 doors:
266 1:
267 item: Red Secret Path
268 or:
269 - sphere: 0
270 - button:
271 scene: Hallway_W041_W018_Optimized
272 index: 0
273 - button:
274 scene: Hallway_W612_W057_Optimized
275 index: 0
276World_018_PastaTile_Optimized:
277 0:
278 or:
279 - button: 0
280 - pad: 0
281 3:
282 or:
283 - pad: 0
284 - sphere:
285 scene: Hallway_W018_W003B_Optimized
286 index: 0
287 5:
288 or:
289 - button: 1
290 - button:
291 - scene: Hallway_W018_W063_Optimized
292 index: 0
293 - scene: Hallway_W018_W063_Optimized
294 index: 1
295Hallway_W018_W003B_Optimized:
296 locations:
297 Mini Sphere Puzzle Solved:
298 sphere: 0
299 doors:
300 1:
301 or:
302 - sphere: 0
303 - button: 1
304 4:
305 item: Red Entrance
306 or:
307 - button: 1
308 - entry: World_003_SpherePipe_Optimized
309World_003_SpherePipe_Optimized:
310 locations:
311 Red - Tree Purified:
312 entry: AudioVisual_003_Optimized
313World_044C_CubicSpaceDivision_Optimized:
314 doors:
315 1:
316 item: Red Garden
317 or:
318 - button: 0
319 - button:
320 scene: Hallway_W044_W045_Optimized
321 index: 0
322Hallway_W044_W045_Optimized:
323 locations:
324 Final Test Puzzle Solved:
325 socket: [0, 1]
326 doors:
327 3:
328 item: Final Test Door
329 or:
330 - socket: [0, 1]
331 - button: 0
332 - button:
333 scene: Hallway_W045_W804_Optimized
334 index: 0
335 2:
336 or:
337 - button: 0
338 - inverted:
339 pad:
340 scene: World_045_FirstHub_Optimized
341 index: 0
342Hallway_W045_W053_Optimized:
343 doors:
344 1:
345 item: Path to Green
346 or:
347 - button: 0
348 - entry: World_053_WaterTechingPuzzle_Optimized
349World_053_WaterTechingPuzzle_Optimized:
350 locations:
351 Green - Blue Waterwheel Activated:
352 waterwheel: 0
353 Green - Tree Purified:
354 entry: AudioVisual_053_Optimized
355 doors:
356 4:
357 item: Green - Water Room Entrance
358World_044E_CubicSpaceDivision_Optimized:
359 doors:
360 1:
361 item: Green Garden
362 or:
363 - button: 0
364 - socket:
365 scene: Hallway_W044_W057_Optimized
366 index: 0
367Hallway_W044_W057_Optimized:
368 doors:
369 1:
370 or:
371 - socket: 0
372 - waterwheel:
373 scene: World_057_WaterAndPortals_Optimized
374 index: 0
375Hallway_W612_W057_Optimized:
376 doors:
377 2:
378 or:
379 - button: 0
380 - button:
381 scene: World_057_WaterAndPortals_Optimized
382 index: 0
383World_057_WaterAndPortals_Optimized:
384 locations:
385 Water Through Portals Puzzle Solved:
386 waterwheel: 0
387 doors:
388 0:
389 or:
390 - waterwheel: 0
391 - button:
392 scene: Hallway_W057_W045_Optimized
393 index: 0
394 5:
395 item: Red Secret Path
396 or:
397 - waterwheel: 0
398 - button: 0
399Hallway_W057_W045_Optimized:
400 doors:
401 1:
402 or:
403 - button: 0
404 - inverted:
405 pad:
406 scene: World_045_FirstHub_Optimized
407 index: 1
408Hallway_W045_W073_Optimized:
409 locations:
410 Apartments Waterwheel Activated:
411 waterwheel: 0
412 doors:
413 0:
414 item: Apartments Entrance
415 or:
416 - button: 0
417 - waterwheel:
418 scene: World_073_WaterThruStairs_Optimized
419 index: 0
420 5:
421 item: Orange Secret Path
422 or:
423 - button: 0
424 - waterwheel: 0
425 - button:
426 scene: Hallway_W073_W018_Optimized
427 index: 0
428Hallway_W073_W018_Optimized:
429 doors:
430 1:
431 or:
432 - button: 0
433 - button:
434 scene: World_018_PastaTile_Optimized
435 index: 1
436Hallway_W018_W063_Optimized:
437 doors:
438 0:
439 item: Secret Apartments Shortcut
440 or:
441 - button: [0, 1]
442 - button:
443 scene: Hallway_W045_W073_Optimized
444 index: 0
445 1:
446 item: Orange Shortcut from Apartments
447 or:
448 - button: [0, 1]
449 - entry: World_063_TowerX_Optimized
450World_073_WaterThruStairs_Optimized:
451 locations:
452 Skylight Room Waterwheel Activated:
453 waterwheel: 0
454 Skylight Room Orange Secret:
455 socket: 0
456 Skylight Room Red Secret:
457 socket: 1
458 doors:
459 3:
460 item: Large Gap Main Entrance
461 or:
462 - waterwheel: 0
463 - button: 0
464 1:
465 or:
466 - button: 0
467 - button:
468 scene: Hallway_W073_W062_Optimized
469 index: 0
470 0:
471 item: Skylight to Library Shortcut
472 or:
473 - waterwheel: 0
474 - button:
475 scene: Hallway_W073_W026_Optimized
476 index: 0
477 6:
478 item: Arches Secret Passage
479 or:
480 - waterwheel: 0
481 - button:
482 scene: Hallway_W044_W045_Optimized
483 index: 0
484Hallway_W073_W062_Optimized:
485 doors:
486 1:
487 item: Large Gap Main Entrance
488 or:
489 - button: 0
490 - socket:
491 scene: World_062_LargeGap_Optimized
492 index: 0
493Hallway_W000_W052_Optimized:
494 doors:
495 2:
496 or:
497 - button: 0
498 - button:
499 scene: World_052_JerryMcGuirePuzzle_Optimized
500 index: 1
501World_052_JerryMcGuirePuzzle_Optimized:
502 locations:
503 Jerry McGuire Puzzle Solved:
504 pad: 0
505 doors:
506 2:
507 or:
508 - button: 1
509 - pad: 0
510 0:
511 or:
512 - pad: 0
513 - button: 0
514 1:
515 or:
516 - button: 0
517 - button: 2
518 5:
519 or:
520 - button: 2
521 - button:
522 scene: Hallway_W052_W062_Optimized
523 index: 0
524Hallway_W052_W062_Optimized:
525 doors:
526 2:
527 item: Blue Secret Path
528 or:
529 - button: 0
530 - button:
531 scene: World_062_LargeGap_Optimized
532 index: 0
533Hallway_W062_W026_Optimized:
534 doors:
535 1:
536 item: Library Main Entrance
537 or:
538 - button: 0
539 - entry: World_026_Library_Optimized
540World_026_Library_Optimized:
541 locations:
542 Single Socket Puzzle Solved:
543 socket: 0
544 Double Socket Puzzle Solved:
545 socket: [1, 2]
546 doors:
547 1:
548 item: Path to Yellow
549 or:
550 - entry: World_026_Library_Optimized
551 - entry: Hallway_W026_W002_Optimized
552 6:
553 item: Library - Purple Entrance
554 or:
555 - entry: World_026_Library_Optimized
556 - button:
557 scene: Hallway_W026_W051_Optimized
558 index: 0
559 smoke:
560 3:
561 item: Library - Yellow Smoke Wall
562Hallway_W026_W002_Optimized:
563 locations:
564 Yellow Cube Fetch Quest Completed:
565 socket: 0
566 doors:
567 0:
568 item: Yellow Secret Path
569 or:
570 - entry: Hallway_W026_W002_Optimized
571 - button:
572 scene: Hallway_W026_W015_Optimized
573 index: 0
574Hallway_W026_W015_Optimized:
575 doors:
576 0:
577 or:
578 - button: 0
579 - pad:
580 scene: World_613_LongBridgeLongTower_Optimized
581 index: 0
582Hallway_W026_W002_PuzzleRoom_Optimized:
583 locations:
584 Yellow Entrance Puzzle Solved:
585 socket: [0, 1]
586 doors:
587 1:
588 item: Yellow Entrance
589 or:
590 - socket: [0, 1]
591 - entry: World_002_Optimized
592World_002_Optimized:
593 locations:
594 Yellow - Blue Tower Solved:
595 socket: 3
596 Yellow - Green Tower Solved:
597 socket: 5
598 Yellow - Red Tower Solved:
599 socket: 4
600 Yellow - Tree Purified:
601 entry: AudioVisual_002_Optimized
602World_044D_CubicSpaceDivision_Optimized:
603 doors:
604 0:
605 item: Yellow Garden
606 or:
607 - button: 0
608 - button:
609 scene: Hallway_W044_W026_Optimized
610 index: 0
611Hallway_W044_W026_Optimized:
612 doors:
613 2:
614 or:
615 - button: 0
616 - pad:
617 scene: World_026_Library_Optimized
618 index: 0
619 4:
620 item: Skylight to Library Shortcut
621 or:
622 - button: 0
623 - button: 1
624Hallway_W073_W026_Optimized:
625 doors:
626 1:
627 or:
628 - button: 0
629 - button:
630 scene: Hallway_W044_W026_Optimized
631 index: 1
632Hallway_W026_W051_Optimized:
633 doors:
634 0:
635 item: Library - Purple Entrance
636 or:
637 - button: 0
638 - entry: World_051_CeilingSuspendPuzzle_Optimized
639World_051_CeilingSuspendPuzzle_Optimized:
640 locations:
641 Purple - Tree Purified:
642 entry: AudioVisual_051_Optimized
643World_044G_CubicSpaceDivision_Optimized:
644 doors:
645 1:
646 item: Purple Garden
647 or:
648 - button: 0
649 - button:
650 scene: Hallway_W033_W063_Optimized
651 index: 0
652Hallway_W033_W063_Optimized:
653 locations:
654 Shattered Bridges Puzzle Solved:
655 waterwheel: 0
656 doors:
657 3:
658 or:
659 - button: 0
660 - waterwheel: 0
661 0:
662 item: Orange Entrance
663 or:
664 - waterwheel: 0
665 - socket:
666 scene: World_063_TowerX_Optimized
667 index: 0
668World_611_SecretRoom1_Optimized:
669 locations:
670 Shattered Bridges Secret Puzzle Solved:
671 socket: 0
672 doors:
673 2:
674 item: Secret Hub Shattered Bridges Entrance
675 or:
676 - entry: Hallway_W033_W063_Optimized
677 - button: 0
678 1:
679 or:
680 - button: 0
681 - socket:
682 scene: World_604_TetrominoNonEuclidean_Optimized
683 index: 0
684World_604_TetrominoNonEuclidean_Optimized:
685 locations:
686 Non-Euclidean Tetromino Puzzle Solved:
687 socket: 0
688 doors:
689 0:
690 or:
691 - socket: 0
692 - button: 0
693 2:
694 or:
695 - button: 0
696 - button:
697 scene: Hallway_W604_W062_Optimized
698 index: 0
699Hallway_W604_W062_Optimized:
700 doors:
701 0:
702 item: Secret Hub Shattered Bridges Entrance
703 or:
704 - button: 0
705 - button:
706 scene: World_062_LargeGap_Optimized
707 index: 3
708 - button:
709 scene: World_062_LargeGap_Optimized
710 index: 5
711 - button:
712 scene: World_062_LargeGap_Optimized
713 index: 1
714 - button:
715 scene: World_062_LargeGap_Optimized
716 index: 4
717World_063_TowerX_Optimized:
718 locations:
719 Orange - Inside Puzzle Solved:
720 socket: 0
721 Orange - Tree Purified:
722 entry: AudioVisual_063_Optimized
723 doors:
724 0:
725 item: Orange Tower Door
726 or:
727 - socket: 0
728 - socket: 1
729Hallway_W045_W804_Optimized:
730 doors:
731 0:
732 item: Final Test Door
733 or:
734 - button: 0
735 - entry: World_804_Optimized