diff options
| -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 | ||
