diff options
author | Star Rauchenberger <fefferburbia@gmail.com> | 2025-02-22 10:43:41 -0500 |
---|---|---|
committer | Star Rauchenberger <fefferburbia@gmail.com> | 2025-02-22 10:43:41 -0500 |
commit | e58b122fb00a337748df2de2451679f53b092d42 (patch) | |
tree | 3b71d6554f10cdd5cb68ceeca4d6296abf5c434f | |
parent | d1baf62bea385c75ad4e7612e5caa285400ffaa7 (diff) | |
download | manifold-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.cs | 68 | ||||
-rw-r--r-- | GameData.cs | 442 | ||||
-rw-r--r-- | GameState.cs | 167 | ||||
-rw-r--r-- | GameplayPatches.cs | 237 | ||||
-rw-r--r-- | ManifoldGardenArchipelago.csproj | 7 | ||||
-rw-r--r-- | Plugin.cs | 49 | ||||
-rw-r--r-- | Requirements.cs | 288 | ||||
-rw-r--r-- | SlotSave.cs | 18 | ||||
-rw-r--r-- | game_data.yaml | 735 |
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; | |||
4 | using Archipelago.MultiClient.Net.Packets; | 4 | using Archipelago.MultiClient.Net.Packets; |
5 | using System; | 5 | using System; |
6 | using System.Collections.Generic; | 6 | using System.Collections.Generic; |
7 | using System.Linq; | ||
8 | using System.Reflection; | ||
9 | using System.Text; | ||
10 | using System.Threading.Tasks; | 7 | using System.Threading.Tasks; |
11 | using UnityEngine.SceneManagement; | ||
12 | using static ManifoldGardenArchipelago.GameData; | ||
13 | 8 | ||
14 | namespace ManifoldGardenArchipelago | 9 | namespace 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 @@ | |||
1 | using System.Collections.Generic; | 1 | using System.Collections.Generic; |
2 | using System.IO; | ||
3 | using YamlDotNet.Serialization; | ||
2 | 4 | ||
3 | namespace ManifoldGardenArchipelago | 5 | namespace 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 @@ | |||
1 | using System; | ||
2 | using System.Linq; | ||
3 | using System.Reflection; | ||
4 | using UnityEngine; | ||
5 | using UnityEngine.SceneManagement; | ||
6 | |||
7 | namespace 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 @@ | |||
1 | using HarmonyLib; | ||
2 | using System.Reflection; | ||
3 | |||
4 | namespace 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 @@ | |||
1 | using BepInEx; | 1 | using BepInEx; |
2 | using BepInEx.Logging; | 2 | using BepInEx.Logging; |
3 | using HarmonyLib; | 3 | using HarmonyLib; |
4 | using System.Linq; | ||
5 | using System.Reflection; | 4 | using System.Reflection; |
6 | 5 | ||
7 | namespace ManifoldGardenArchipelago | 6 | namespace 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 @@ | |||
1 | using System.Collections.Generic; | ||
2 | using System.Linq; | ||
3 | |||
4 | namespace 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 @@ | |||
1 | using System; | ||
2 | using System.Collections.Generic; | ||
3 | using System.Linq; | ||
4 | using System.Text; | ||
5 | using System.Threading.Tasks; | ||
6 | |||
7 | namespace 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 @@ | |||
1 | World_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 | ||
13 | World_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 | ||
68 | Hallway_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 | ||
77 | World_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 | ||
88 | World_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 | ||
119 | World_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 | ||
128 | Hallway_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 | ||
144 | World_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 | ||
162 | World_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 | ||
174 | Hallway_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 | ||
191 | World_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 | ||
242 | Hallway_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 | ||
253 | Hallway_W041_W018_Optimized: | ||
254 | doors: | ||
255 | 0: | ||
256 | or: | ||
257 | - button: 0 | ||
258 | - button: | ||
259 | scene: World_018_PastaTile_Optimized | ||
260 | index: 0 | ||
261 | World_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 | ||
276 | World_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 | ||
295 | Hallway_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 | ||
309 | World_003_SpherePipe_Optimized: | ||
310 | locations: | ||
311 | Red - Tree Purified: | ||
312 | entry: AudioVisual_003_Optimized | ||
313 | World_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 | ||
322 | Hallway_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 | ||
342 | Hallway_W045_W053_Optimized: | ||
343 | doors: | ||
344 | 1: | ||
345 | item: Path to Green | ||
346 | or: | ||
347 | - button: 0 | ||
348 | - entry: World_053_WaterTechingPuzzle_Optimized | ||
349 | World_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 | ||
358 | World_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 | ||
367 | Hallway_W044_W057_Optimized: | ||
368 | doors: | ||
369 | 1: | ||
370 | or: | ||
371 | - socket: 0 | ||
372 | - waterwheel: | ||
373 | scene: World_057_WaterAndPortals_Optimized | ||
374 | index: 0 | ||
375 | Hallway_W612_W057_Optimized: | ||
376 | doors: | ||
377 | 2: | ||
378 | or: | ||
379 | - button: 0 | ||
380 | - button: | ||
381 | scene: World_057_WaterAndPortals_Optimized | ||
382 | index: 0 | ||
383 | World_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 | ||
399 | Hallway_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 | ||
408 | Hallway_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 | ||
428 | Hallway_W073_W018_Optimized: | ||
429 | doors: | ||
430 | 1: | ||
431 | or: | ||
432 | - button: 0 | ||
433 | - button: | ||
434 | scene: World_018_PastaTile_Optimized | ||
435 | index: 1 | ||
436 | Hallway_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 | ||
450 | World_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 | ||
484 | Hallway_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 | ||
493 | Hallway_W000_W052_Optimized: | ||
494 | doors: | ||
495 | 2: | ||
496 | or: | ||
497 | - button: 0 | ||
498 | - button: | ||
499 | scene: World_052_JerryMcGuirePuzzle_Optimized | ||
500 | index: 1 | ||
501 | World_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 | ||
524 | Hallway_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 | ||
533 | Hallway_W062_W026_Optimized: | ||
534 | doors: | ||
535 | 1: | ||
536 | item: Library Main Entrance | ||
537 | or: | ||
538 | - button: 0 | ||
539 | - entry: World_026_Library_Optimized | ||
540 | World_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 | ||
562 | Hallway_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 | ||
574 | Hallway_W026_W015_Optimized: | ||
575 | doors: | ||
576 | 0: | ||
577 | or: | ||
578 | - button: 0 | ||
579 | - pad: | ||
580 | scene: World_613_LongBridgeLongTower_Optimized | ||
581 | index: 0 | ||
582 | Hallway_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 | ||
592 | World_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 | ||
602 | World_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 | ||
611 | Hallway_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 | ||
624 | Hallway_W073_W026_Optimized: | ||
625 | doors: | ||
626 | 1: | ||
627 | or: | ||
628 | - button: 0 | ||
629 | - button: | ||
630 | scene: Hallway_W044_W026_Optimized | ||
631 | index: 1 | ||
632 | Hallway_W026_W051_Optimized: | ||
633 | doors: | ||
634 | 0: | ||
635 | item: Library - Purple Entrance | ||
636 | or: | ||
637 | - button: 0 | ||
638 | - entry: World_051_CeilingSuspendPuzzle_Optimized | ||
639 | World_051_CeilingSuspendPuzzle_Optimized: | ||
640 | locations: | ||
641 | Purple - Tree Purified: | ||
642 | entry: AudioVisual_051_Optimized | ||
643 | World_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 | ||
652 | Hallway_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 | ||
668 | World_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 | ||
684 | World_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 | ||
699 | Hallway_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 | ||
717 | World_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 | ||
729 | Hallway_W045_W804_Optimized: | ||
730 | doors: | ||
731 | 0: | ||
732 | item: Final Test Door | ||
733 | or: | ||
734 | - button: 0 | ||
735 | - entry: World_804_Optimized | ||