diff --git a/GauntletLegendsClient.py b/GauntletLegendsClient.py new file mode 100644 index 00000000000..b076eafd36c --- /dev/null +++ b/GauntletLegendsClient.py @@ -0,0 +1,7 @@ +import ModuleUpdate +ModuleUpdate.update() + +from worlds.gl.GauntletLegendsClient import launch + +if __name__ == "__main__": + launch() diff --git a/README.md b/README.md index 0e57bce53b5..b4216542987 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ Currently, the following games are supported: * Kingdom Hearts 1 * Mega Man 2 * Yacht Dice +* Gauntlet Legends For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/). Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index ee7fd7ed863..96c653d6fae 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -66,6 +66,9 @@ # Final Fantasy Mystic Quest /worlds/ffmq/ @Alchav @wildham0 +# Gauntlet Legends +/worlds/gl/ @jamesbrq + # Heretic /worlds/heretic/ @Daivuk diff --git a/inno_setup.iss b/inno_setup.iss index 38e655d917c..dffbe2c95cd 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -216,6 +216,11 @@ Root: HKCR; Subkey: "{#MyAppName}ygo06patch"; ValueData: "Ar Root: HKCR; Subkey: "{#MyAppName}ygo06patch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}ygo06patch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: ".apgl"; ValueData: "{#MyAppName}glpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}glpatch"; ValueData: "Archipelago Gauntlet Legends Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}glpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoGauntletLegendsClient.exe,0"; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}glpatch\shell\open\command"; ValueData: """{app}\ArchipelagoGauntletLegendsClient.exe"" ""%1"""; ValueType: string; ValueName: ""; + Root: HKCR; Subkey: ".archipelago"; ValueData: "{#MyAppName}multidata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Archipelago Server Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\ArchipelagoServer.exe,0"; ValueType: string; ValueName: ""; diff --git a/worlds/gl/Arrays.py b/worlds/gl/Arrays.py new file mode 100644 index 00000000000..1bfdd524acd --- /dev/null +++ b/worlds/gl/Arrays.py @@ -0,0 +1,692 @@ +from typing import Dict, List, Tuple + +from .Locations import ( + LocationData, + arctic_docks, + battle_towers, + battle_trenches, + castle_courtyard, + castle_treasury, + chimeras_keep, + cliffs_of_desolation, + crystal_mine, + dagger_peak, + desecrated_temple, + dragons_lair, + dungeon_of_torment, + erupting_fissure, + frozen_camp, + gates_of_the_underworld, + haunted_cemetery, + infernal_fortress, + lost_cave, + plague_fiend, + poisoned_fields, + tower_armory, + toxic_air_ship, + valley_of_fire, + venomous_spire, + volcanic_cavern, + yeti, +) + +# Item name to ram value conversion +inv_dict: Dict[Tuple, str] = { + (0x0, 0x6, 0x0): "Gold", + (0x0, 0x7, 0x0): "Key", + (0x0, 0xA, 0x0): "Level", + (0x0, 0xD, 0x0): "Max", + (0x0, 0x70, 0x2): "Compass", + (0x0, 0x8, 0x10): "Lightning Potion", + (0x0, 0x8, 0x20): "Light Potion", + (0x0, 0x8, 0x30): "Acid Potion", + (0x0, 0x8, 0x40): "Fire Potion", + (0x28, 0x80, 0x4): "Acid Breath", + (0x28, 0xF0, 0x4): "Lightning Breath", + (0x28, 0x60, 0x4): "Fire Breath", + (0x18, 0x70, 0x8): "Light Amulet", + (0x18, 0x80, 0x8): "Acid Amulet", + (0x18, 0xF0, 0x8): "Lightning Amulet", + (0x18, 0x60, 0x8): "Fire Amulet", + (0x14, 0xF0, 0xC): "Lightning Shield", + (0x14, 0x60, 0xC): "Fire Shield", + (0x10, 0xE0, 0x0): "Invisibility", + (0x10, 0x30, 0x0): "Levitate", + (0x10, 0x73, 0x1): "Speed Boots", + (0x50, 0x20, 0x0): "3-Way Shot", + (0x50, 0x60, 0x1): "5-Way Shot", + (0x10, 0x50, 0x0): "Rapid Fire", + (0x18, 0x40, 0x8): "Reflective Shot", + (0x14, 0x40, 0xC): "Reflective Shield", + (0x28, 0xB0, 0x8): "Super Shot", + (0x10, 0xD0, 0x0): "Timestop", + (0x10, 0x10, 0x1): "Phoenix Familiar", + (0x10, 0x20, 0x1): "Growth", + (0x10, 0x30, 0x1): "Shrink", + (0x28, 0x40, 0x9): "Thunder Hammer", + (0x11, 0xA0, 0x0): "Anti-Death Halo", + (0x11, 0xC0, 0x0): "Invulnerability", + (0x0, 0x1, 0x0): "Fruit", + (0x0, 0x1, 0x0): "Meat", + (0x0, 0x1, 0x0): "Health", + (0x0, 0x10, 0x82): "Runestone", + (0x0, 0x60, 0x82): "Mirror Shard", + (0x22, 0xC0, 0x1): "Ice Axe of Untar", + (0x22, 0xD0, 0x1): "Flame of Tarkana", + (0x22, 0xE0, 0x1): "Scimitar of Decapitation", + (0x22, 0xF0, 0x1): "Marker's Javelin", + (0x22, 0x0, 0x2): "Soul Savior", + (0x0, 0x4A, 0x80): "Mountain", + (0x0, 0x1A, 0x80): "Castle", + (0x0, 0x6A, 0x80): "Hell", + (0x0, 0x7A, 0x80): "Ice", + (0x0, 0x8A, 0x80): "Town", + (0x0, 0x9A, 0x80): "Temple", + (0x0, 0xDA, 0x80): "Battlefield", + (0x0, 0xEA, 0x80): "Skorne", + (0x0, 0xFA, 0x80): "Secret", # I have no clue if this is correct + (0x0, 0xC, 0x80): "Obelisk", + (0x0, 0xA1, 0x1): "Minotaur", + (0x0, 0xA2, 0x1): "Falconess", + (0x0, 0xA3, 0x1): "Jackal", + (0x0, 0xA4, 0x1): "Tigress", + (0x0, 0xA5, 0x1): "Sumner", +} + +# Character names used for slot data +characters = ["Minotaur", "Falconess", "Tigress", "Jackal", "Sumner"] + +# Item ID to rom ID conversion +item_dict: Dict[int, bytes] = { + 77780000: [0x0, 0x0], + 77780001: [0x1, 0x1], + 77780002: [0x1, 0x2], + 77780003: [0x1, 0x3], + 77780004: [0x1, 0x4], + 77780005: [0x2, 0x12], + 77780006: [0x2, 0x11], + 77780007: [0x2, 0x10], + 77780008: [0x2, 0x9], + 77780009: [0x2, 0xA], + 77780010: [0x2, 0x8], + 77780011: [0x2, 0x7], + 77780012: [0x2, 0x18], + 77780013: [0x2, 0x17], + 77780014: [0x2, 0x5], + 77780015: [0x2, 0x1C], + 77780016: [0x2, 0x0], + 77780017: [0x2, 0x4], + 77780018: [0x2, 0xE], + 77780019: [0x2, 0x1A], + 77780020: [0x2, 0x2], + 77780021: [0x2, 0x16], + 77780022: [0x2, 0x3], + 77780023: [0x2, 0xC], + 77780024: [0x2, 0x13], + 77780025: [0x2, 0x14], + 77780026: [0x2, 0x15], + 77780027: [0x2, 0x19], + 77780028: [0x2, 0xD], + 77780029: [0x2, 0x6], + 77780030: [0x4, 0x1], + 77780031: [0x4, 0x3], + 77780032: [0x15, 0x1], + 77780033: [0x15, 0x2], + 77780034: [0x15, 0x3], + 77780035: [0x15, 0x4], + 77780036: [0x15, 0x5], + 77780037: [0x15, 0x6], + 77780038: [0x15, 0x7], + 77780039: [0x15, 0x8], + 77780040: [0x15, 0x9], + 77780041: [0x15, 0xA], + 77780042: [0x15, 0xB], + 77780043: [0x15, 0xC], + 77780044: [0x15, 0xD], + 77780045: [0x2B, 0x1], + 77780046: [0x2B, 0x2], + 77780047: [0x2B, 0x3], + 77780048: [0x2B, 0x4], + 77780049: [0x29, 0x1], + 77780050: [0x29, 0x2], + 77780051: [0x29, 0x3], + 77780052: [0x29, 0x4], + 77780053: [0x29, 0x5], + 77780054: [0x3, 0x2], + 77780062: [0x21, 0x1], + 77780063: [0x4, 0x2], +} + +# Items that use a timer +timers = [ + "Light Amulet", + "Acid Amulet", + "Lightning Amulet", + "Fire Amulet", + "Lightning Shield", + "Fire Shield", + "Invisibility", + "Levitate", + "Speed Boots", + "3-Way Shot", + "5-Way Shot", + "Rapid Fire", + "Reflective Shot", + "Reflective Shield", + "Timestop", + "Phoenix Familiar", + "Growth", + "Shrink", + "Anti-Death Halo", + "Invulnerability", +] + +# Base item charge count per pickup +# Some items are bitwise +base_count: Dict[str, int] = { + "Key": 1, + "Lightning Potion": 1, + "Light Potion": 1, + "Acid Potion": 1, + "Fire Potion": 1, + "Acid Breath": 5, + "Lightning Breath": 5, + "Fire Breath": 5, + "Light Amulet": 30, + "Acid Amulet": 30, + "Lightning Amulet": 30, + "Fire Amulet": 30, + "Lightning Shield": 10, + "Fire Shield": 10, + "Invisibility": 30, + "Levitate": 30, + "Speed Boots": 30, + "3-Way Shot": 30, + "5-Way Shot": 30, + "Rapid Fire": 30, + "Reflective Shot": 30, + "Reflective Shield": 30, + "Super Shot": 3, + "Timestop": 15, + "Phoenix Familiar": 30, + "Growth": 30, + "Shrink": 30, + "Thunder Hammer": 3, + "Anti-Death Halo": 30, + "Invulnerability": 30, + "Fruit": 50, + "Meat": 100, + "Gold": 100, + "Runestone 1": 1, + "Runestone 2": 2, + "Runestone 3": 4, + "Runestone 4": 8, + "Runestone 5": 16, + "Runestone 6": 32, + "Runestone 7": 64, + "Runestone 8": 128, + "Runestone 9": 256, + "Runestone 10": 512, + "Runestone 11": 1024, + "Runestone 12": 2048, + "Runestone 13": 4096, + "Dragon Mirror Shard": 1, + "Chimera Mirror Shard": 4, + "Plague Fiend Mirror Shard": 8, + "Yeti Mirror Shard": 2, + "Mountain Obelisk 1": 1, + "Mountain Obelisk 2": 2, + "Mountain Obelisk 3": 4, + "Town Obelisk 1": 8, + "Town Obelisk 2": 16, + "Castle Obelisk 1": 32, + "Castle Obelisk 2": 64, + "Ice Axe of Untar": 1, + "Flame of Tarkana": 1, + "Scimitar of Decapitation": 1, + "Marker's Javelin": 1, + "Soul Savior": 1, + "Minotaur": 1, + "Falconess": 1, + "Tigress": 1, + "Jackal": 1, + "Sumner": 1, + "Poison Fruit": -50, +} + +# Castle level ID order +castle_id = [1, 6, 3, 4, 5] + +# Area ID << 4 + Level ID to raw location list conversion +level_locations: Dict[int, List[LocationData]] = { + 0x11: castle_courtyard, + 0x12: dungeon_of_torment, + 0x13: tower_armory, + 0x14: castle_treasury, + 0x15: chimeras_keep, + 0x21: valley_of_fire, + 0x22: dagger_peak, + 0x23: cliffs_of_desolation, + 0x24: lost_cave, + 0x25: volcanic_cavern, + 0x26: dragons_lair, + 0x71: poisoned_fields, + 0x72: haunted_cemetery, + 0x73: venomous_spire, + 0x74: toxic_air_ship, + 0x75: plague_fiend, + 0x81: gates_of_the_underworld, + 0x91: arctic_docks, + 0x92: frozen_camp, + 0x93: crystal_mine, + 0x94: erupting_fissure, + 0x95: yeti, + 0xF1: desecrated_temple, + 0x111: battle_trenches, + 0x112: battle_towers, + 0x113: infernal_fortress, +} + +level_names: Dict[int, str] = { + 0x11: "Castle Courtyard", + 0x12: "Dungeon of Torment", + 0x13: "Tower Armory", + 0x14: "Castle Treasury", + 0x15: "Chimera's Keep", + 0x21: "Valley of Fire", + 0x22: "Dagger Peak", + 0x23: "Cliffs of Desolation", + 0x24: "Lost Cave", + 0x25: "Volcanic Cavern", + 0x26: "Dragon's Lair", + 0x71: "Poisoned Fields", + 0x72: "Haunted Cemetery", + 0x73: "Venomous Spire", + 0x74: "Toxic Air Ship", + 0x75: "Vat of the Plague Fiend", + 0x81: "Gates of the Underworld", + 0x91: "Arctic Docks", + 0x92: "Frozen Camp", + 0x93: "Crystal Mine", + 0x94: "Erupting Fissure", + 0x95: "Yeti's Cavern", + 0xF1: "Desecrated Temple", + 0x111: "Battle Trenches", + 0x112: "Battle Towers", + 0x113: "Infernal Fortress", +} + +# Count of all spawners in a level +# Used for obj_read address offset calculation +# Vaules are spawner difficulty +spawners: Dict[int, List[int]] = { + 0x11: [0, 0, 0, 4, 3, 2, 0, 0, 2, 4, 2, 0, 2, 4, 0, 3, 4, 4, 3, 0, 2, 0, 0, 3, 4, 0, 2, 2, 0, 3, 0, 4, 3, 0, 0, 2, 0, 2, 2, 4, 3, 0, 3, 2, 4, 2, 2, 2, 4, 0, 4, 3, 2, 0, 4, 0, 3, 3, 4, 2, 3, 0, 0, 0, 2, 3, 4, 2, 2, 2, 2, 0, 3, 2, 0, 2, 0, 0, 3, 0, 4, 2, 2, 0, 0, 0, 3, 0, 3, 0, 3, 3, 0, 0, 4, 4, 3, 0, 2, 0, 2, 3, 0, 4, 0, 2, 2, 0, 2, 4, 0, 2, 0, 3, 4, 0, 4, 3, 2, 0, 3, 0], + 0x12: [0, 0, 0, 2, 3, 2, 0, 2, 0, 2, 3, 0, 2, 0, 2, 0, 3, 2, 0, 3, 2, 0, 3, 0, 0, 2, 0, 3, 0, 0, 4, 3, 0, 0, 0, 2, 4, 3, 2, 2, 0, 0, 0, 0, 2, 2, 0, 2, 0, 3, 4, 3, 0, 0, 2, 2, 3, 3, 3, 2, 2, 4, 4, 4], + 0x13: [0, 0, 0, 2, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 4, 4, 2, 3, 0, 0, 3, 0, 2, 0, 2, 3, 3, 2, 0, 0, 2, 3, 0, 2, 0, 4, 2, 4, 2, 2, 3, 0, 2, 0, 3, 4, 3, 0, 0, 3, 3, 3, 4, 2, 3, 4, 3, 0, 0, 3, 2, 2, 2, 0, 2, 0, 3, 2, 0, 2, 0, 4, 4, 3, 0, 4, 3, 0, 0, 2, 3, 4], + 0x14: [3, 2, 3, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 2, 0, 3, 0, 0, 0, 2, 0, 0, 0, 3, 0, 4, 0, 0, 3, 2, 4, 3, 0, 0, 4, 3, 2, 4, 3, 4, 3, 2, 0, 2, 4, 2, 0, 3, 3, 4, 2, 0, 0, 0, 2, 2, 3, 4, 0, 3, 4, 4, 0, 2, 0, 2, 2, 0, 3, 2, 4, 0, 3, 2, 0, 0, 0, 4, 4, 0, 0, 0], + 0x15: [], + 0x21: [4, 0, 4, 2, 2, 0, 0, 2, 0, 3, 3, 0, 0, 2, 2, 0, 4, 0, 3, 2, 3, 0, 0, 2, 4, 2, 3, 2, 3, 4, 2, 3, 4], + 0x22: [0, 2, 2, 0, 2, 3, 2, 0, 3, 3, 4, 4, 4, 4, 2, 2, 3, 0, 4, 0, 0, 0, 2, 0, 2, 0, 0, 3, 3, 4, 3, 2, 4, 0, 0, 3, 0, 4, 0, 3, 0, 2, 0, 0, 2, 0, 3, 2, 4, 0, 0, 0, 3, 2, 0], + 0x23: [3, 0, 0, 2, 3, 0, 3, 2, 4, 3, 2, 0, 2, 3, 0, 3, 0, 0, 4, 2, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 4, 3, 3, 4, 0, 0, 0, 3, 0, 3, 0, 3, 0, 4, 3, 2, 2, 2, 0, 0, 4, 3, 4, 0, 2, 2, 2, 0, 2, 3, 0, 4, 2, 4, 4, 2, 0, 4, 2, 0, 2, 2, 3, 0, 2, 3, 4, 0, 0, 3, 4, 0, 0, 3, 0, 2], + 0x24: [0, 2, 3, 0, 3, 0, 0, 0, 4, 0, 2, 0, 4, 0, 3, 0, 3, 0, 3, 2, 4, 2, 0, 0, 0, 0, 0, 3, 0, 0, 4, 0, 4, 2, 2, 2, 0, 2, 2, 3, 0, 2, 0, 2, 0, 0, 2, 2, 3, 4, 0, 3, 2, 2, 0, 0, 3, 2, 3, 3, 4, 2, 3, 4, 0, 2, 3, 0, 0], + 0x25: [3, 0, 4, 0, 0, 2, 3, 0, 4, 2, 0, 2, 3, 0, 3, 0, 4, 2, 0, 2, 3, 4, 3, 2, 0, 2, 0, 2, 2, 3, 4, 3, 0, 0, 3, 0, 2, 4, 3, 0, 4, 2, 4, 3, 2, 0, 2, 0, 2, 3, 3, 0, 2, 3, 4, 2, 0, 4, 0, 0, 3, 0, 0, 2, 2, 4, 0, 3, 4, 3, 2, 3, 4, 0, 0, 0, 0, 2, 3, 3, 0, 4, 0], + 0x26: [], + 0x71: [0, 0, 2, 3, 0, 0, 2, 0, 0, 3, 4, 4, 4, 0, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 2, 0, 0, 0, 2, 3, 3, 0, 2, 0, 3, 3, 2, 2, 0, 2, 0, 2, 3, 0, 4, 0, 3, 2, 3, 0, 2, 4, 3, 0, 4, 4, 0, 3, 0, 0, 2, 4, 3, 0, 2, 4, 0, 3, 2, 4, 2, 4, 3, 2, 0, 0, 2, 3, 0, 0, 2, 2, 3, 0, 3, 2, 0, 0, 0, 0, 2, 0, 2, 3, 2, 2, 2, 2, 0, 0, 4, 4, 2, 0, 2, 0, 2, 3, 4, 0, 2, 4, 2, 0, 2, 3, 3, 3, 0, 2, 0, 4, 0, 2, 0, 4, 4, 0, 4, 0, 0, 3, 2, 3, 0, 4, 2, 0, 0, 4, 4, 0, 3, 0, 0, 0, 0, 0, 2, 3, 0, 2, 0, 4, 2, 2, 3, 4, 0, 0, 0, 0, 3, 3], + 0x72: [0, 0, 4, 4, 4, 4, 3, 0, 4, 0, 4, 0, 3, 0, 0, 3, 0, 0, 2, 0, 0, 2, 0, 3, 0, 2, 0, 0, 2, 0, 0, 0, 3, 0, 0, 2, 2, 4, 0, 4, 0, 2, 2, 4, 2, 2, 3, 3, 3, 2, 3, 0, 4, 2, 4, 0, 4, 0, 0, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 3, 0, 2, 2, 3, 4, 2, 3, 0, 3, 3, 2, 2, 0, 0, 0, 0, 0, 2, 4, 3, 0, 0, 2, 0, 3, 3, 0, 0, 2, 2, 0, 0, 2, 0, 2, 0, 2, 0, 4, 2, 4, 2, 0, 0, 0, 4, 3, 2, 0, 0, 2, 0, 3, 3, 0, 0, 3, 3, 0, 0, 2, 0, 3, 4, 3, 0, 2, 3, 0, 0, 4, 0, 0, 3, 2, 0, 2, 3, 2, 0, 0, 3], + 0x73: [0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0, 2, 0, 2, 3, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 2, 0, 2, 0, 3, 0, 0, 0, 2, 0, 0, 3, 0, 0, 2, 4, 0, 4, 0, 3, 0, 2, 0, 2, 2, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 3, 0, 2, 0, 3, 0, 2, 3, 2, 3, 0, 4, 2, 0, 0, 0, 4, 0, 2, 0, 2, 0, 4, 0, 4, 3, 0, 0, 0, 2, 0, 2, 2, 3, 2, 0, 3, 0, 2, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 0, 3, 2, 0, 2, 0, 4, 3, 2], + 0x74: [4, 4, 0, 3, 4, 4, 4, 0, 3, 4, 4, 0, 2, 0, 0, 3, 0, 2, 0, 0, 2, 3, 0, 0, 0, 2, 0, 2, 2, 3, 2, 3, 3, 0, 2, 3, 0, 3, 0, 0, 0, 2, 0, 0, 2, 3, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2, 2, 0, 2, 2, 2, 0, 2, 0, 0, 2, 0, 3, 0, 0, 0, 2, 3, 2, 0, 0, 0, 3, 0, 3, 0, 0, 2, 0, 2, 0, 3, 3, 2, 0, 0, 4, 3, 2, 2, 0, 2, 3, 0, 2, 0, 2, 0, 2, 3, 0, 3, 0, 2, 2, 0, 3, 0, 4, 4], + 0x75: [], + 0x81: [4, 0, 0, 3, 2, 0, 0, 2, 0, 3, 3, 3, 0, 4, 0, 0, 3, 0, 3, 2, 0, 4, 0, 0, 4, 4, 3, 0, 2, 0, 0, 0, 0, 0, 0, 4, 3, 0, 2, 0, 2, 0, 0, 0, 4, 0, 2, 0, 3, 2, 0, 0, 4, 2, 0, 0, 2, 0, 3, 4, 3, 4, 3, 4, 3, 3, 4, 3, 0, 3, 4, 3, 4, 3, 3, 4, 4, 3, 4, 3, 4, 3, 4, 3, 3, 4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, 0, 0, 3, 0, 0, 0, 0, 0, 3, 2, 0, 0, 0, 2, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 2, 0, 0, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 2, 3, 4, 4, 4, 0, 3, 2, 0, 0, 0], + 0x91: [0, 0, 0, 0, 2, 0, 0, 3, 4, 4, 2, 0, 0, 2, 0, 4, 3, 0, 3, 4, 0, 2, 3, 4, 2, 0, 3, 0, 0, 0, 2, 3, 2, 0, 0, 0, 0, 0, 4, 2, 0, 3, 0, 2, 3, 0, 0, 2, 4, 3, 0, 3, 2, 4, 0, 2, 0, 0, 2, 3, 4, 2, 0, 0, 2, 0, 3, 2, 0, 2, 2, 0, 0, 3, 2, 0, 0, 2, 3, 0, 3, 0, 0, 2, 3, 4, 0, 0, 2, 2, 0, 3, 2, 0, 3, 0, 4, 3, 2, 4, 0, 2, 0, 2, 0, 3, 0, 0, 2, 0, 0, 4, 0, 0, 3, 0, 2, 0, 0, 0, 0, 3, 0, 4, 2, 3, 0, 2, 2, 4, 3, 2, 0, 2, 3, 0, 2, 0, 2, 3, 4, 0, 4, 2, 0, 0, 2, 0, 0, 2, 0, 2, 3, 0, 2, 0, 2, 0, 4, 3, 0, 2, 0, 0, 2, 3, 0, 2, 0, 4, 3, 2, 0, 4, 0, 0, 2, 0, 3, 0, 2, 0, 2, 0, 0, 2, 3, 2, 0, 3, 0, 4, 2, 3, 0, 2, 2, 0, 3, 0, 3, 2, 2, 0, 4, 0, 2, 0, 3, 0, 2, 0, 0, 2, 3, 2, 0, 3, 3, 2, 0, 3, 2, 2, 3, 2, 2, 0, 2, 0, 0, 2, 2, 0, 3, 2, 0, 0], + 0x92: [0, 0, 0, 3, 0, 0, 2, 0, 4, 0, 3, 4, 0, 4, 0, 0, 0, 3, 0, 0, 2, 4, 3, 0, 0, 3, 3, 4, 3, 0, 2, 0, 2, 0, 0, 3, 3, 4, 0, 0, 2, 0, 0, 2, 0, 4, 0, 2, 4, 0, 3, 2, 0, 4, 3, 0, 2, 3, 2, 0, 3, 0, 2, 4, 2, 0, 3, 0, 4, 0, 3, 0, 2, 0, 4, 3, 2, 0, 2, 0, 2, 0, 3, 0, 0, 0, 3, 0, 2, 0, 2, 0, 2, 2, 0, 2, 2, 0, 0, 2, 3, 2, 2, 4, 0, 3, 4, 0, 0, 2, 0, 0, 3, 0, 4, 0, 0, 0, 0, 0, 3, 3, 4, 0, 2, 0, 0, 2, 0, 3, 2, 0, 4, 3, 4, 2, 0, 0, 3, 0, 2, 0, 0, 3, 0, 0, 3, 2, 0, 2, 0, 3, 4, 3, 4, 2, 0, 0, 2, 4, 3, 0, 4, 2, 0, 0, 2, 3, 2, 0, 0], + 0x93: [0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 4, 0, 2, 0, 2, 0, 4, 3, 0, 0, 2, 3, 0, 3, 0, 3, 4, 2, 0, 3, 4, 2, 0, 0, 2, 0, 3, 2, 4, 0, 0, 2, 0, 0, 3, 2, 0, 0, 0, 2, 3, 2, 3, 0, 0, 2, 0, 4, 3, 2, 0, 4, 3, 0, 2, 2, 0, 0, 4, 0, 3, 0, 2, 0, 4, 3, 3, 0, 2, 3, 4, 0, 2, 2, 0, 3, 0, 2, 0, 3, 0, 2, 0, 4, 3, 2, 0, 4, 0, 2, 0, 3, 4, 2, 3, 0, 2, 0, 0, 0, 3, 4, 2, 2, 0, 3, 4, 0, 2, 0, 3, 0, 2, 3, 0, 0, 3, 2, 4, 2, 0, 0, 3, 0, 2, 0, 4, 3, 2, 0, 2, 0, 2, 0, 0, 2, 3, 2, 0, 0, 0, 3, 4, 0, 3, 0, 2, 2, 2, 2, 3, 4, 0, 4, 0, 2, 3, 0, 2, 0, 2, 3, 2, 4, 0, 0, 3, 3, 0, 2, 3, 4, 0, 2, 2, 3, 0, 0, 4, 3], + 0x94: [4, 4, 4, 3, 3, 0, 0, 0, 0, 0, 4, 0, 4, 0, 0, 3, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 3, 2, 4, 4, 0, 4, 0, 2, 0, 0, 0, 2, 2, 2, 0, 0, 2, 3, 2, 0, 2, 4, 3, 0, 0, 2, 0, 3, 0, 2, 2, 3, 0, 0, 0, 2, 0, 3, 4, 3, 2, 2, 3, 0, 3, 0, 2, 2, 0, 4, 0, 3, 0, 4, 0, 2, 0, 3, 4, 2, 0, 2, 3, 4, 0, 0, 3, 0, 0, 2, 2, 4, 0, 2, 4, 4, 2, 2, 4, 0, 0, 3, 4, 0, 2, 0, 3, 0, 2, 0, 2, 2, 3, 2, 0, 0, 4, 3, 2, 0, 2, 3, 3, 4, 0, 0, 0, 4, 0, 3, 2, 0, 3, 2, 4, 0, 2, 3, 3, 0, 3, 4, 2, 3, 0, 2, 0, 0, 4, 2, 3, 0, 3, 0, 0, 2, 2, 3, 0, 0, 3, 3, 3, 0, 2, 3, 2, 0, 2, 4, 0, 2, 0, 0, 3, 3, 0, 3, 0, 2, 0, 0, 3, 4, 0, 0, 4, 0, 2, 3, 3, 0, 2, 4, 0, 0], + 0x95: [], + 0xF1: [0, 3, 2, 2, 2, 0, 3, 4, 0, 3, 3, 0, 2, 4, 2, 2, 0, 4, 0, 0, 0, 2, 0, 2, 2, 0, 0, 3, 3, 0, 0, 4, 2, 2, 0, 2, 3, 3, 3, 2, 0, 0, 0, 0, 0, 4, 4, 0, 0, 4, 2, 0, 0, 0, 3, 0, 0, 2, 0, 0, 0, 4, 0, 3, 0, 4, 3, 2, 0, 0, 2, 0], + 0x111: [0, 3, 0, 0, 4, 3, 0, 3, 2, 0, 0, 0, 0, 0, 3, 0, 0, 2, 0, 2, 2, 0, 2, 3, 0, 3, 0, 2, 0, 0, 0, 4, 3, 2, 0, 2, 0, 0, 2, 0, 2, 0, 3, 2, 2, 3, 0, 2, 0, 4, 0, 2, 0, 3, 0, 3, 4, 0, 0, 3, 0, 2, 0, 3, 0, 0, 2, 0, 3, 4, 0, 0, 4, 0, 3, 0, 2, 0, 0, 3, 0, 0, 2, 4, 2, 0, 3, 0, 0, 0, 0, 2, 4, 0, 0, 0, 2, 0, 0, 2, 0, 3, 2, 4, 3, 0, 0, 2, 0, 2, 0, 3, 3, 2, 0, 2, 0, 4, 0, 2, 0, 4, 3, 2, 0, 4, 0, 2, 0, 2, 2, 4, 0, 4, 0, 2, 3, 2, 0, 0, 0, 3, 0, 0, 0, 0, 4, 2, 0, 0, 0, 0, 2, 3, 0, 0, 2, 3, 0, 0, 2, 2, 0, 3, 4, 0], + 0x112: [0, 3, 2, 0, 0, 2, 3, 4, 2, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 2, 3, 4, 2, 3, 4, 3, 2, 0, 0, 2, 0, 0, 0, 0, 4, 2, 0, 0, 0, 4, 0, 4, 0, 0, 3, 2, 0, 3, 0, 4, 0, 3, 0, 0, 0, 2, 2, 0, 2, 0, 3, 3, 0, 0, 0, 0, 3, 0, 0, 0, 2, 0, 3, 2, 0, 0, 3, 4, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 2, 0, 2, 4, 0, 0, 2, 0, 0, 3, 2, 0, 0, 2, 2, 0, 0, 2, 0, 0, 4, 3, 0, 3, 0, 3, 2, 4, 0, 0, 2, 3, 0, 4, 3, 0, 2, 0, 2, 0, 3, 0, 0, 2, 0, 0, 0, 0, 0], + 0x113: [0, 4, 2, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 4, 0, 3, 0, 0, 3, 0, 3, 0, 4, 0, 0, 3, 0, 0, 2, 0, 3, 0, 0, 2, 3, 0, 0, 3, 2, 0, 0, 4, 0, 3, 0, 0, 0, 4, 0, 2, 2, 3, 0, 0, 0, 0, 3, 0, 2, 4, 3, 2, 3, 0, 2, 0, 3, 0, 0, 2, 0, 2, 0, 3, 0, 2, 0, 0, 0, 4, 0, 2, 3, 4, 3, 0, 0, 2, 0, 3, 4, 0, 3, 0, 0, 0, 2, 4, 0, 0, 2, 2, 0, 3, 2, 0, 2, 0, 0, 4, 2, 2, 0, 0, 3, 4, 4, 0, 3, 0, 2, 0, 2, 0, 2, 4, 0, 3, 0, 0, 2, 3, 3, 0, 2, 4, 0, 3, 0, 2, 0, 3, 0, 3, 0, 2, 0, 2, 4, 0, 2, 3, 0, 0, 3, 2, 0, 3, 2, 0, 0] +} + +# Compressed level size in ROM +level_size = [ + 0x9E0, + 0x5E0, + 0x740, + 0x8A0, + 0x90, + 0x3B0, + 0x5A0, + 0x890, + 0x670, + 0x7D0, + 0x90, + 0xCE0, + 0xA50, + 0xA30, + 0x8E0, + 0x20, + 0x760, + 0xE90, + 0xE40, + 0xE00, + 0xCD0, + 0x20, + 0x3F0, + 0xB00, + 0xA30, + 0xB30, +] + +# Level address in ROM +level_address = [ + 0xF939B0, + 0xF958B0, + 0xF945B0, + 0xF94EE0, + 0xF84CC0, + 0xF910B0, + 0xF915B0, + 0xF91D00, + 0xF92710, + 0xF92F40, + 0xF84B70, + 0xF84FA0, + 0xF85EB0, + 0xF86B60, + 0xF877C0, + 0xF880E0, + 0xF901C0, + 0xF884E0, + 0xF89370, + 0xF8A5F0, + 0xF8B760, + 0xF8C7C0, + 0xF90B50, + 0xF8D960, + 0xF8E6E0, + 0xF8F110, +] + +# Level header address in ROM +level_header = [ + 0xF9DD9C, + 0xF9E07C, + 0xF9DE54, + 0xF9DF0C, + 0xF9DFC4, + 0xF9E6E0, + 0xF9E798, + 0xF9E850, + 0xF9E908, + 0xF9E9C0, + 0xF9EA78, + 0xF9F004, + 0xF9F0BC, + 0xF9F174, + 0xF9F22C, + 0xF9F2E4, + 0xF9E1C0, + 0xF9E330, + 0xF9E3E4, + 0xF9E498, + 0xF9E54C, + 0xF9E600, + 0xF9DC2C, + 0xF9DA04, + 0xF9DABC, + 0xF9DB74, +] + +# Convert area to base level +# Used for difficulty scaling +difficulty_convert: Dict[int, int] = {0x2: 0, 0x1: 10, 0x7: 20, 0x9: 30, 0xF: 35, 0x11: 40, 0x8: 45} + +# Runestones required to access difficulties +# Used in Rules.py for access calculation +difficulty_lambda: Dict[int, List[int]] = { + 0x2: [0, 1, 2, 3], + 0x1: [0, 3, 4, 5], + 0x7: [0, 6, 7, 8], + 0x9: [0, 6, 7, 8], + 0xF: [0, 7, 8, 9], + 0x11: [0, 9, 10, 11], + 0x8: [0, 13, 13, 13], +} + +# Area ID's with bosses in them +boss_realm = [2, 1, 7, 9] + +# Area and Level ID's for boss levels +boss_level = [bytes([0x6, 0x2]), bytes([0x5, 0x9]), bytes([0x5, 0x1]), bytes([0x5, 0x7]), bytes([0x2, 0xF])] + +# Area and Level ID's for mirror levels +mirror_levels = [bytes([0x6, 0x2]), bytes([0x5, 0x9]), bytes([0x5, 0x1]), bytes([0x5, 0x7])] + +# ID's for names said by announcer +sounds = { + 0: 0x9D, + 1: 0x9E, + 2: 0xA7, + 3: 0xA5, + 5: 0x9F, + 6: 0xA1, + 7: 0xA0, + 8: 0xA2, + 9: 0xA7, +} + +# ID's for colors said by announcer +colors = { + 0: 0xA3, + 1: 0xA6, + 2: 0x9C, + 3: 0xA4, +} + +# Level for vanilla scaling +vanilla: Dict[int, int] = { + 0x2: 10, + 0x1: 25, + 0x7: 40, + 0x9: 50, + 0xF: 100, + 0x11: 70, + 0x8: 80, +} + +no_obelisks = [ + 88870012, + 88870010, + 88873518, + 88870014, + 88873515, + 88870013, + 88873514, + 88870050, + 88870057, + 88870056, + 88870034, + 88870039, + 88870047, + 88870043, + 88870026, + 88870051, + 88873748, + 88870085, + 88870095, + 88873726, + 88873746, + 88873715, + 88870063, + 88870059, + 88873743, + 88870093, + 88870062, + 88870094, + 8887380, + 88870124, + 88870109, + 88873836, + 88870119, + 88870104, + 88870135, + 88870106, + 8887382, + 88873816, + 88870101, + 88870136, + 88873823, + 88870137, + 88873943, + 88873924, + 88873945, + 88873917, + 8887393, + 88870166, + 88870154, + 88870159, + 88873022, + 88873024, + 8887302, + 88873048, + 88873010, + 88873032, + 88870188, + 88870200, + 88870191, + 88870192, + 88870212, + 88873012, + 88870216, + 88873035, + 88873023, + 88873038, + 88870202, + 88870234, + 88870220, + 88870229, + 88873118, + 88873128, + 88870224, + 88873124, + 88870235, + 88870252, + 88870263, + 88870241, + 88870278, + 88870260, + 88870259, + 88870247, + 88870276, + 8887326, + 88873214, + 88870313, + 88870284, + 88870321, + 88870302, + 88870316, + 88870311, + 88870295, + 88873323, + 88870294, + 88870317, + 8887330, + 88870285, + 88873316, + 88873314, + 88870375, + 88871143, + 88871231, + 88871234, + 88871229, + 88870400, + 88870394, + 88871225, + 88871237, + 88870396, + 88870398, + 88871241, + 88871353, + 88871363, + 88870402, + 88870406, + 88870405, + 88871315, + 88871357, + 88870411, + 88871358, + 88870409, + 88871316, + 88871317, + 88871318, + 88871325, + 88871312, + 88871417, + 8887145, + 88870431, + 88870435, + 88870428, + 88871438, + 88870429, + 88871436, + 88871422, + 88870422, + 88871425, + 88870437, + 88871630, + 88871764, + 88871857, + 88871830, + 88871829, + 88870498, + 8887185, + 88871822, + 88870518, + 88870515, + 88870512, + 8887194, + 88870520, + 88871952, + 88871944, + 88871942, + 88870535, + 88870534, + 88872031, + 88872026, + 88870533, + 88870532, + 88872032, + 88870544, + 88870541, + 88872111, + 88870540, + 88870546, + 8887217, + 88870538, + 88870542, + 88872232, + 88872227, + 88870561, + 8887228, + 88870560, + 88872323, + 88872321, + 88872320, + 88872311, + 88872347, + 88872346, + 88872345, + 88872332, + 88872330, + 88870576, + 88872334, + 88872333, + 88872332, + 88870575, + 88872331, + 88870573, + 88870574, + 88870603, + 88870596 +] diff --git a/worlds/gl/GauntletLegendsClient.py b/worlds/gl/GauntletLegendsClient.py new file mode 100644 index 00000000000..dc47e7c1952 --- /dev/null +++ b/worlds/gl/GauntletLegendsClient.py @@ -0,0 +1,1040 @@ +import asyncio +import datetime +import os +import socket +import traceback +from typing import List, Optional + +import Patch +import Utils +from BaseClasses import ItemClassification +from CommonClient import ClientCommandProcessor, CommonContext, get_base_parser, gui_enabled, logger, server_loop +from NetUtils import ClientStatus, NetworkItem +from Utils import user_path + +from .Arrays import ( + base_count, + boss_level, + castle_id, + characters, + difficulty_convert, + inv_dict, + level_locations, + mirror_levels, + spawners, + timers, + sounds, + colors, + vanilla, level_names, +) +from .Items import ItemData, items_by_id +from .Locations import LocationData + +READ = "READ_CORE_RAM" +WRITE = "WRITE_CORE_RAM" +INV_ADDR = 0xC5BF0 +OBJ_ADDR = 0xBB5C0 +INV_UPDATE_ADDR = 0x56094 +INV_LAST_ADDR = 0x56084 +ACTIVE_POTION = 0xFD313 +ACTIVE_LEVEL = 0x4EFC0 +PLAYER_COUNT = 0x127764 +PLAYER_LEVEL = 0xFD31B +PLAYER_ALIVE = 0xFD2EB +PLAYER_CLASS = 0xFD30F +PLAYER_COLOR = 0xFD30E +PLAYER_PORTAL = 0x64A50 +PLAYER_BOSSING = 0x64A54 +PLAYER_MOVEMENT = 0xFD307 +SOUND_ADDRESS = 0xAE740 +SOUND_START = 0xEEFC +PLAYER_KILL = 0xFD300 +PAUSED = 0xC5B18 + +BOSS_ADDR = 0x289C08 +TIME = 0xC5B1C +INPUT = 0xC5BCD + + +class RetroSocket: + def __init__(self): + self.host = "localhost" + self.port = 55355 + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket.setblocking(False) + + def send(self, message: str): + try: + self.socket.sendto(message.encode(), (self.host, self.port)) + except Exception as e: + raise Exception("An error occurred while sending a message.") + + async def read(self, message: str) -> Optional[bytes]: + self.send(message) + try: + response = await asyncio.wait_for(asyncio.get_event_loop().sock_recv(self.socket, 30000), 1.0) + data = response.decode().split(" ") + b = b"" + for s in data[2:]: + if "-1" in s: + logger.info("-1 response") + raise Exception("Client tried to read from an invalid address or ROM is not open.") + b += bytes.fromhex(s) + return b + except asyncio.TimeoutError: + logger.error("Timeout while waiting for socket response") + return None + + async def status(self) -> str: + message = "GET_STATUS" + self.send(message) + data = await asyncio.wait_for(asyncio.get_event_loop().sock_recv(self.socket, 4096), 1.0) + return data.decode() + + +class RamChunk: + def __init__(self, arr: bytes): + self.raw = arr + self.split = [] + + def iterate(self, length: int): + self.split = [self.raw[i: i + length] for i in range(0, len(self.raw), length)] + + +def type_to_name(arr) -> str: + target_tuple = tuple(arr) + return inv_dict.get(target_tuple, None) + + +def name_to_type(name) -> bytes: + if name == "Fruit" or name == "Meat": + return bytes([0x0, 0x1, 0x0]) + for key, value in inv_dict.items(): + if value == name: + return bytes(key) + logger.info(f"Invalid Item: {name}") + raise ValueError("Value not found in the dictionary") + + +class InventoryEntry: + def __init__(self, arr=None, index=None, player=None): + if arr is not None: + self.raw = arr + self.addr = 0xC5BF0 + (0x400 * player) + (index * 0x10) + self.on: int = arr[0] + self.type: bytes = arr[1:4] + self.name = type_to_name(self.type) + self.count: int = int.from_bytes(arr[4:8], "little") + self.n_addr: int = int.from_bytes(arr[12:15], "little") + self.p_addr: int = int.from_bytes(arr[8:11], "little") + else: + self.raw: bytes + self.addr: int + self.on = 0 + self.type: bytes + self.name = "" + self.count: int + self.n_addr: int + self.p_addr: int + + +class ObjectEntry: + def __init__(self, arr=None): + if arr is not None: + self.raw = arr + else: + self.raw: bytes + + +def message_format(arg: str, params: str) -> str: + return f"{arg} {params}" + + +def param_format(adr: int, arr: bytes) -> str: + return " ".join([hex(adr)] + [f"0x{byte:02X}" for byte in arr]) + + +class GauntletLegendsCommandProcessor(ClientCommandProcessor): + def __init__(self, ctx: CommonContext): + super().__init__(ctx) + + def _cmd_connected(self): + logger.info(f"Retroarch Connected Status: {self.ctx.retro_connected}") + + def _cmd_deathlink_toggle(self): + self.ctx.deathlink_enabled = not self.ctx.deathlink_enabled + logger.info(f"Deathlink {'Enabled.' if self.ctx.deathlink_enabled else 'Disabled.'}") + + def _cmd_players(self, value: int): + """Set number of local players""" + value = int(value) + logger.info(f"Players set from {self.ctx.players} to {min(value, 4)}.") + self.ctx.players = min(value, 4) + + +class GauntletLegendsContext(CommonContext): + command_processor = GauntletLegendsCommandProcessor + game = "Gauntlet Legends" + items_handling = 0b101 + + def __init__(self, server_address, password): + super().__init__(server_address, password) + self.useful: List[NetworkItem] = [] + self.deathlink_pending: bool = False + self.deathlink_enabled: bool = False + self.deathlink_triggered: bool = False + self.ignore_deathlink: bool = False + self.difficulty: int = 0 + self.players: int = 1 + self.gl_sync_task = None + self.glslotdata = None + self.socket = RetroSocket() + self.rom_loaded: bool = False + self.locations_checked: List[int] = [] + self.inventory: List[List[InventoryEntry]] = [] + self.inventory_raw: List[RamChunk] = [] + self.item_objects: List[ObjectEntry] = [] + self.chest_objects: List[ObjectEntry] = [] + self.item_objects_init: List[ObjectEntry] = [] + self.chest_objects_init: List[ObjectEntry] = [] + self.retro_connected: bool = False + self.level_loading: bool = False + self.in_game: bool = False + self.objects_loaded: bool = False + self.obelisks: List[NetworkItem] = [] + self.item_locations: List[LocationData] = [] + self.obelisk_locations: List[LocationData] = [] + self.chest_locations: List[LocationData] = [] + self.extra_items: int = 0 + self.extra_spawners: int = 0 + self.extra_chests: int = 0 + self.limbo: bool = False + self.in_portal: bool = False + self.scaled: bool = False + self.offset: int = -1 + self.clear_counts = None + self.current_level: bytes = b"" + self.logged: bool = False + self.output_file: str = "" + self.movement: int = 0 + self.init_refactor: bool = False + self.location_scouts: list[NetworkItem] = [] + + def on_deathlink(self, data: dict): + self.deathlink_pending = True + super().on_deathlink(data) + + # Return number of items in inventory + def inv_count(self, player: int) -> int: + return len(self.inventory[player]) + + # Update self.inventory to current in-game values + async def inv_read(self): + self.inventory = [] + self.inventory_raw = [] + for player in range(self.players): + _inv: List[InventoryEntry] = [] + b = RamChunk(await self.socket.read(message_format(READ, f"0x{format(INV_ADDR + (0x400 * player), 'x')} 1008"))) + if b is None: + return + b.iterate(0x10) + self.inventory_raw += [b] + for i, arr in enumerate(b.split): + _inv += [InventoryEntry(arr, i, player)] + for i in range(len(_inv)): + if _inv[i].p_addr == 0: + _inv = _inv[i:] + break + new_inv: List[InventoryEntry] = [] + new_inv += [_inv[0]] + addr = new_inv[0].n_addr + while True: + if addr == 0: + break + new_inv += [inv for inv in _inv if inv.addr == addr] + addr = new_inv[-1].n_addr + self.inventory += [new_inv] + + # Return InventoryEntry if item of name is in inv, else return None + async def item_from_name(self, name: str, player: int) -> InventoryEntry | None: + await self.inv_read() + for i in range(0, self.inv_count(player)): + if self.inventory[player][i].name == name: + return self.inventory[player][i] + return None + + # Return True if bitwise and evaluates to non-zero value + async def inv_bitwise(self, name: str, bit: int, player: int) -> bool: + item = await self.item_from_name(name, player) + if item is None: + return False + return (item.count & bit) != 0 + + # Return pointer of object section of RAM + async def get_obj_addr(self) -> int: + return ( + int.from_bytes(await self.socket.read(message_format(READ, f"0x{format(OBJ_ADDR, 'x')} 4")), "little") + ) & 0xFFFFF + + # Read a subsection of the objects loaded into RAM + # Objects are 0x3C bytes long + # Modes: 0 = items, 1 = chests/barrels + async def obj_read(self, mode=0): + _obj: List[ObjectEntry] = [] + b: RamChunk + if self.offset == -1: + log_arr = [] + for i in range(5): + b = RamChunk(await self.socket.read(message_format(READ, f"0x{format(OBJ_ADDR + ((i * 100) * 0x3C), 'x')} {100 * 0x3C}"))) + b.iterate(0x3C) + log_arr += [arr for arr in b.split] + output_folder = user_path("logs") + os.makedirs(output_folder, exist_ok=True) + _id = self.current_level[0] + if self.current_level[1] == 1: + _id = castle_id.index(self.current_level[0]) + 1 + self.output_file = os.path.join(output_folder, f"({datetime.datetime.now().strftime('%Y-%m-%d - %I-%M-%S-%p')}) Gauntlet Legends RAMSTATE - {level_names[(self.current_level[1] << 4) + _id]}.txt") + with open(self.output_file, 'w+') as f: + for i, arr in enumerate(log_arr): + f.write(f"0x{format(OBJ_ADDR + (0x3C * i), 'x')}: " + " ".join(f"{int(byte):02x}" for byte in arr) + '\n') + b = RamChunk(await self.socket.read(message_format(READ, f"0x{format(OBJ_ADDR, 'x')} {0x40 * 0x3C}"))) + b.iterate(0x3C) + for i, obj in enumerate(b.split): + if obj[1] != 0xFF: + self.offset = i + break + if mode == 0: + while True: + b = RamChunk( + await self.socket.read( + message_format( + READ, + f"0x{format(OBJ_ADDR + (self.offset * 0x3C), 'x')} {(len(self.item_locations) + self.extra_items) * 0x3C}", + ), + ), + ) + b.iterate(0x3C) + count = sum(1 for obj in b.split if obj[1] == 0xFF) - self.extra_items + if count <= 0: + break + self.extra_items += count + if not self.logged: + with open(self.output_file, 'a') as f: + f.write(f"Item Address: 0x{format(OBJ_ADDR + (self.offset * 0x3C), 'x')}\n") + f.write(f"Extra Items: {self.extra_items}\n") + else: + spawner_count = len([spawner for spawner in spawners[(self.current_level[1] << 4) + ( + self.current_level[0] if self.current_level[1] != 1 else castle_id.index(self.current_level[0]) + 1)] + if self.difficulty >= spawner]) + if self.extra_spawners == 0: + count = 0 + for i in range((spawner_count + 99) // 100): + b = RamChunk( + await self.socket.read( + message_format( + READ, + f"0x{format(OBJ_ADDR + ((len(self.item_locations) + self.offset + self.extra_items + (i * 100)) * 0x3C), 'x')} {min((spawner_count - (100 * i)), 100) * 0x3C}", + ), + ), + ) + b.iterate(0x3C) + count += sum(1 for obj in b.split if obj[1] == 0xFF) + if not self.logged: + with open(self.output_file, 'a') as f: + f.write(f"Spawner Address {i + 1}: 0x{format(OBJ_ADDR + ((len(self.item_locations) + self.offset + self.extra_items + (i * 100)) * 0x3C), 'x')}\n") + f.write(f"Count: {count}\n") + self.extra_spawners += count + count = 0 + if self.extra_spawners != 0: + while True: + b = RamChunk( + await self.socket.read( + message_format( + READ, + f"0x{format(OBJ_ADDR + ((len(self.item_locations) + self.offset + self.extra_items + spawner_count) * 0x3C), 'x')} {(self.extra_spawners + count) * 0x3C}", + ), + ), + ) + b.iterate(0x3C) + current_count = sum(1 for obj in b.split if obj[1] == 0xFF) - count + if current_count <= 0: + break + count += current_count + self.extra_spawners += count + if not self.logged: + with open(self.output_file, 'a') as f: + f.write(f"Total Extra Spawners: {self.extra_spawners}\n") + while True: + b = RamChunk( + await self.socket.read( + message_format( + READ, + f"0x{format(OBJ_ADDR + ((len(self.item_locations) + self.offset + self.extra_items + self.extra_spawners + spawner_count) * 0x3C), 'x')} {(len(self.chest_locations) + self.extra_chests) * 0x3C}", + ), + ), + ) + b.iterate(0x3C) + count = sum(1 for obj in b.split if obj[1] == 0xFF) - self.extra_chests + if count <= 0: + break + self.extra_chests += count + if not self.logged: + with open(self.output_file, 'a') as f: + f.write( + f"Chest Address: 0x{format(OBJ_ADDR + ((len(self.item_locations) + self.offset + self.extra_items + self.extra_spawners + spawner_count) * 0x3C), 'x')}\n") + f.write(f"Extra Chests: {self.extra_chests}\n") + self.logged = True + for arr in b.split: + _obj += [ObjectEntry(arr)] + _obj = [obj for obj in _obj if obj.raw[1] != 0xFF] + if mode == 1: + self.chest_objects = _obj[:len(self.chest_locations)] + if not self.chest_objects_init: + self.chest_objects_init = self.chest_objects + else: + self.item_objects = _obj[:len(self.item_locations)] + if not self.item_objects_init: + self.item_objects_init = self.item_objects + + + # Update item count of an item. + # If the item is new, add it to your inventory + async def inv_update(self, name: str, count: int, one=None): + await self.inv_read() + if "Runestone" in name: + name = "Runestone" + if "Fruit" in name or "Meat" in name: + name = "Health" + if "Obelisk" in name: + name = "Obelisk" + if "Mirror" in name: + name = "Mirror Shard" + players_to_check = range(self.players) if one is None else [one] + zero = False + + for player in players_to_check: + added = False + for item in self.inventory[player]: + if item.name == name: + zero = (item.count == 0 and "Health" not in name) + if name in timers: + count *= 96 + if "Compass" in name: + item.count = count + elif "Health" in name: + max_health = await self.item_from_name("Max", player) + item.count = min(max(item.count + count, 1), max_health.count) + elif "Runestone" in name or "Mirror" in name or "Obelisk" in name: + item.count |= count + else: + item.count += count + await self.write_item(item) + added = True + break + if not added: + await self.inv_add(name, count, player) + continue + if zero: + await self.inv_refactor() + + # Rewrite entire inventory in RAM. + # This is necessary since item entries are not cleared after full use until a level is completed. + async def inv_refactor(self, new=None, one=None): + await self.inv_read() + if new is not None: + if one is None: + for player in range(self.players): + self.inventory[player] += [new] + else: + self.inventory[one] += [new] + + players_to_check = range(self.players) if one is None else [one] + for player in players_to_check: + for i, item in enumerate(self.inventory[player]): + if item.name is not None: + if "Potion" in item.name and item.count != 0: + self.socket.send( + message_format( + WRITE, param_format(ACTIVE_POTION + (0x1F0 * player), int.to_bytes(item.type[2] // 0x10, 1, "little")), + ), + ) + if i == 0: + item.p_addr = 0 + item.addr = INV_ADDR + (0x400 * player) + item.n_addr = item.addr + 0x10 + self.inventory[player][i] = item + continue + item.addr = INV_ADDR + (0x400 * player) + (0x10 * i) + item.p_addr = item.addr - 0x10 + if i == (len(self.inventory[player]) - 1): + item.n_addr = 0 + self.inventory[player][i] = item + break + item.n_addr = item.addr + 0x10 + self.inventory[player][i] = item + + for item in self.inventory[player]: + await self.write_item(item) + + for i, raw in enumerate(self.inventory_raw[player].split[len(self.inventory[player]):], len(self.inventory[player])): + item = InventoryEntry(raw, i, player) + if item.type != bytes([0, 0, 0]): + await self.write_item( + InventoryEntry( + bytes([0, 0, 0, 0, 0, 0, 0, 0]) + + int.to_bytes(item.addr + 0x10, 3, "little") + + bytes([0xE0, 0, 0, 0, 0]), + i, + player), + ) + + self.socket.send( + message_format(WRITE, param_format(INV_UPDATE_ADDR + (4 * player), int.to_bytes(self.inventory[player][-1].addr, 3, "little"))), + ) + self.socket.send( + message_format(WRITE, + param_format(INV_LAST_ADDR + (4 * player), int.to_bytes(self.inventory[player][-1].addr + 0x10, 3, "little"))), + ) + + self.socket.send(f"{WRITE} 0x{format(0xC6BF0 + (4 * player), 'x')} 0x{format(self.inv_count(player), 'x')}") + + # Add new item to inventory + # Call refactor at the end to write it into ram correctly + async def inv_add(self, name: str, count: int, one=None): + new = InventoryEntry() + new.name = name + if name == "Key": + new.on = 1 + new.count = count + if name in timers: + new.count *= 0x96 + new.type = name_to_type(name) + await self.inv_refactor(new, one) + + # Write a single item entry into RAM + async def write_item(self, item: InventoryEntry): + b = ( + int.to_bytes(item.on, 1) + + item.type + + int.to_bytes(item.count, 4, "little") + + int.to_bytes(item.p_addr, 3, "little") + + (int.to_bytes(0xE0) if item.p_addr != 0 else int.to_bytes(0x0)) + + int.to_bytes(item.n_addr, 3, "little") + + (int.to_bytes(0xE0) if item.n_addr != 0 else int.to_bytes(0x0)) + ) + self.socket.send(message_format(WRITE, param_format(item.addr, b))) + + async def server_auth(self, password_requested: bool = False): + if password_requested and not self.password: + await super(GauntletLegendsContext, self).server_auth(password_requested) + await self.get_username() + await self.send_connect() + + def on_package(self, cmd: str, args: dict): + if cmd in {"Connected"}: + self.slot = args["slot"] + self.glslotdata = args["slot_data"] + self.players = self.glslotdata["players"] + self.var_reset() + logger.info(f"Players set to {self.players}.") + logger.info("If this is incorrect, Use /players to set the number of people playing locally.") + elif cmd == "Retrieved": + if "keys" not in args: + logger.warning(f"invalid Retrieved packet to GLClient: {args}") + return + cc = None + try: + cc = self.stored_data.get(f"gl_cc_T{self.team}_P{self.slot}", None) + except Exception: + logger.info(traceback.format_exc()) + if cc is not None: + logger.info("Received clear counts from server") + self.clear_counts = cc + else: + self.clear_counts = {} + elif cmd == "LocationInfo": + self.location_scouts = args["locations"] + + # Update inventory based on items received from server + # Also adds starting items based on a few yaml options + async def handle_items(self): + compass = None + for player in range(self.players): + compass = await self.item_from_name("Compass", player) + if compass is None: + break + if compass is not None: + for player in range(self.players): + if self.glslotdata["characters"][player] != 0: + temp = await self.item_from_name(characters[self.glslotdata["characters"][player] - 1], player) + if temp is None: + await self.inv_update(characters[self.glslotdata["characters"][player] - 1], 50, player) + temp = await self.item_from_name("Key", player) + if temp is None and self.glslotdata["keys"] == 1: + await self.inv_update("Key", 9000, player) + temp = await self.item_from_name("Speed Boots", player) + if temp is None and self.glslotdata["speed"] == 1: + await self.inv_update("Speed Boots", 2000, player) + i = compass.count + if i - 1 < len(self.items_received): + for index in range(i - 1, len(self.items_received)): + item = self.items_received[index].item + if items_by_id[item].item_name == "Death": + continue + await self.inv_update(items_by_id[item].item_name, base_count[items_by_id[item].item_name]) + await self.inv_update("Compass", len(self.items_received) + 1) + + # Read current timer in RAM + async def read_time(self) -> int: + return int.from_bytes(await self.socket.read(message_format(READ, f"0x{format(TIME, 'x')} 2")), "little") + + # Read currently loaded level in RAM + async def read_level(self) -> bytes: + level = await self.socket.read(message_format(READ, f"0x{format(ACTIVE_LEVEL, 'x')} 2")) + return level + + # Read value that is 1 while a level is currently loading + async def check_loading(self) -> bool: + if self.in_portal or self.level_loading: + time = await self.read_time() + return time == 0 + return False + + # Read number of loaded players in RAM + async def active_players(self) -> int: + temp = await self.socket.read(message_format(READ, f"0x{format(PLAYER_COUNT, 'x')} 1")) + return temp[0] + + # Read level of player 1 in RAM + async def player_level(self) -> int: + temp = await self.socket.read(message_format(READ, f"0x{format(PLAYER_LEVEL, 'x')} 1")) + return temp[0] + + # Update value at player count address + # This directly impacts the difficulty of the level when it is loaded + async def scale(self): + level = await self.read_level() + if self.movement != 0x12: + level = [0x1, 0xF] + players = await self.active_players() + player_level = await self.player_level() + max_value: int = max(self.glslotdata["max"], self.players) + scale_value = min(max(((player_level - difficulty_convert[level[1]]) // 5), 0), 3) + if self.glslotdata["instant_max"] == 1: + scale_value = max_value + # mountain_value = min(player_level // 10, 3) + self.socket.send( + message_format(WRITE, f"0x{format(PLAYER_COUNT, 'x')} 0x{format(min(players + scale_value, max_value), 'x')}"), + ) + self.scaled = True + + # Prepare locations that are going to be in the currently loading level + async def scout_locations(self, ctx: "GauntletLegendsContext") -> None: + level = await self.read_level() + if level in boss_level: + for i in range(4): + self.socket.send( + message_format( + WRITE, + param_format( + BOSS_ADDR + (0x10 * i), bytes([self.glslotdata["shards"][i][1], 0x0, self.glslotdata["shards"][i][0]]), + ), + ), + ) + if self.movement != 0x12: + level = [0x1, 0xF] + self.current_level = level + players = await self.active_players() + player_level = await self.player_level() + if self.clear_counts.get(str(level), 0) != 0: + self.difficulty = min(players + (min(player_level // vanilla[level[1]], 3)), 4) + else: + self.difficulty = players + _id = level[0] + if level[1] == 1: + _id = castle_id.index(level[0]) + 1 + raw_locations = [] + for location in [location for location in level_locations.get((level[1] << 4) + _id, []) if self.difficulty >= location.difficulty]: + if "Chest" in location.name: + if self.glslotdata["chests"]: + raw_locations += [location] + elif "Barrel" in location.name and "Barrel of Gold" not in location.name: + if self.glslotdata["barrels"]: + raw_locations += [location] + elif "Mirror" not in location.name: + raw_locations += [location] + if len(raw_locations) > 0: + await ctx.send_msgs( + [ + { + "cmd": "LocationScouts", + "locations": [ + location.id + for location in [location for location in raw_locations if min(self.difficulty, self.glslotdata["max"]) >= location.difficulty] + ], + "create_as_hint": 0, + }, + ], + ) + while len(self.location_scouts) == 0: + await asyncio.sleep(0.1) + self.obelisks = [ + item + for item in self.location_scouts + if "Obelisk" in items_by_id.get(item.item, ItemData(0, "", ItemClassification.filler)).item_name + and item.player == self.slot + ] + self.useful = [ + item + for item in self.location_scouts + if "Obelisk" not in items_by_id.get(item.item, ItemData(0, "", ItemClassification.filler)).item_name + and (items_by_id.get(item.item, ItemData(0, "", ItemClassification.filler)).progression == ItemClassification.useful + or items_by_id.get(item.item, ItemData(0, "", ItemClassification.filler)).progression == ItemClassification.progression) + and item.player == self.slot + ] + self.obelisk_locations = [ + location for location in raw_locations if location.id in [item.location for item in self.obelisks] + ] + self.item_locations = [ + location for location in raw_locations + if ("Chest" not in location.name + and ("Barrel" not in location.name or "Barrel of Gold" in location.name)) + and location not in self.obelisk_locations + or location.id in [item.location for item in self.useful] + ] + logger.info(f"Item Locations: {len(self.item_locations)}") + self.chest_locations = [ + location for location in raw_locations + if location not in self.obelisk_locations and location not in self.item_locations] + logger.info(f"Chest Locations: {len(self.chest_locations)}") + max_value: int = self.glslotdata['max'] + logger.info(f"Items: {len(self.item_locations)} Chests: {len(self.chest_locations)} Obelisks: {len(self.obelisk_locations)}") + logger.info( + f"Locations: {len([location for location in self.obelisk_locations + self.item_locations + self.chest_locations if location.difficulty <= max_value and location.id not in self.locations_checked])} Difficulty: { max_value if self.glslotdata['instant_max'] else self.difficulty}", + ) + + # Compare values of loaded objects to see if they have been collected + # Sends locations out to server based on object lists read in obj_read() + # Local obelisks and mirror shards have special cases + async def location_loop(self) -> List[int]: + if not self.logged: + await asyncio.sleep(0.5) + await self.obj_read() + await self.obj_read(1) + acquired = [] + for i, obj in enumerate(self.item_objects): + if int.from_bytes(obj.raw[8:12], "little") != int.from_bytes(self.item_objects_init[i].raw[8:12], "little"): + if self.item_locations[i].id not in self.locations_checked: + acquired += [self.item_locations[i].id] + for j in range(len(self.obelisk_locations)): + ob = await self.inv_bitwise("Obelisk", base_count[items_by_id[self.obelisks[j].item].item_name], 0) + if ob: + acquired += [self.obelisk_locations[j].id] + for k, obj in enumerate(self.chest_objects): + if int.from_bytes(obj.raw[8:12], "little") != int.from_bytes(self.chest_objects_init[k].raw[8:12], "little"): + if self.chest_locations[k].id not in self.locations_checked: + acquired += [self.chest_locations[k].id] + paused = await self.paused() + dead = await self.dead() + if paused or dead: + return [] + return acquired + + # Returns 1 if players are spinning in a portal + async def portaling(self) -> int: + temp = await self.socket.read(message_format(READ, f"0x{format(PLAYER_PORTAL, 'x')} 1")) + return temp[0] + + # Returns a number that shows if the player currently has control or not + async def limbo_check(self, offset=0) -> int: + temp = await self.socket.read(message_format(READ, f"0x{format(PLAYER_MOVEMENT + offset, 'x')} 1")) + return temp[0] + + # Returns True of the player is dead + async def dead(self) -> bool: + temp = await self.socket.read(message_format(READ, f"0x{format(PLAYER_KILL, 'x')} 1")) + if (temp[0] & 0xF) == 0x1: + self.ignore_deathlink = True + return ((temp[0] & 0xF) == 0x8) or ((temp[0] & 0xF) == 0x1) + + # Returns a number that tells if the player is fighting a boss currently + async def boss(self) -> int: + temp = await self.socket.read(message_format(READ, f"0x{format(PLAYER_BOSSING, 'x')} 1")) + return temp[0] + + async def paused(self) -> int: + temp = await self.socket.read(message_format(READ, f"0x{format(PAUSED, 'x')} 1")) + return temp[0] != 0x3 + + # Checks if a player is currently exiting a level + # Checks for both death and completion + # Resets values since level is no longer being played + async def level_status(self, ctx: "GauntletLegendsContext") -> bool: + portaling = await self.portaling() + dead = await self.dead() + boss = await self.boss() + if portaling or dead or (self.current_level in boss_level and boss == 0): + if portaling or (self.current_level in boss_level and boss == 0): + self.clear_counts[str(self.current_level)] = self.clear_counts.get(str(self.current_level), 0) + 1 + if self.current_level in mirror_levels: + await ctx.send_msgs( + [ + { + "cmd": "LocationChecks", + "locations": [ + location.id + for location in level_locations[ + (self.current_level[1] << 4) + self.current_level[0] + ] + if "Mirror" in location.name + ], + }, + ], + ) + if dead and not (self.current_level in boss_level and boss == 0): + if self.deathlink_triggered: + self.deathlink_triggered = False + elif self.ignore_deathlink: + self.ignore_deathlink = False + elif self.deathlink_enabled: + await ctx.send_death(f"{ctx.auth} didn't eat enough meat.") + self.var_reset() + return True + return False + + def var_reset(self): + self.objects_loaded = False + self.logged = False + self.output_file = "" + self.extra_items = 0 + self.extra_spawners = 0 + self.extra_chests = 0 + self.item_locations = [] + self.item_objects = [] + self.chest_locations = [] + self.chest_objects = [] + self.obelisk_locations = [] + self.obelisks = [] + self.item_objects_init = [] + self.chest_objects_init = [] + self.in_game = False + self.level_loading = False + self.scaled = False + self.offset = -1 + self.movement = 0 + self.difficulty = 0 + self.location_scouts = [] + self.ignore_deathlink = False + + # Prep arrays with locations and objects + async def load_objects(self, ctx: "GauntletLegendsContext"): + await self.scout_locations(ctx) + await self.obj_read() + await self.obj_read(1) + self.objects_loaded = True + + async def die(self): + self.deathlink_triggered = True + char = await self.socket.read(message_format(READ, f"0x{format(PLAYER_CLASS, 'x')} 1")) + char = char[0] + color = await self.socket.read(message_format(READ, f"0x{format(PLAYER_COLOR, 'x')} 1")) + color = color[0] + self.socket.send(message_format(WRITE, param_format(SOUND_ADDRESS, int.to_bytes(colors[color], 4, "little") + int.to_bytes(sounds[char], 4, "little") + int.to_bytes(0xBB, 4, "little")))) + self.socket.send(message_format(WRITE, param_format(SOUND_START, int.to_bytes(0xE00AE718, 4, "little")))) + await asyncio.sleep(2) + self.socket.send(message_format(WRITE, param_format(SOUND_START, int.to_bytes(0x0, 4, "little")))) + self.socket.send(message_format(WRITE, param_format(PLAYER_KILL, int.to_bytes(0x7, 1, "little")))) + + def run_gui(self): + from kvui import GameManager + + class GLManager(GameManager): + logging_pairs = [("Client", "Archipelago")] + base_title = "Archipelago Gauntlet Legends Client" + + self.ui = GLManager(self) + self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") + + +async def _patch_game(patch_file: str): + metadata, output_file = Patch.create_rom_file(patch_file) + + +# Sends player items from server +# Checks for player status to see if they are in/loading a level +# Checks location status inside of levels +async def gl_sync_task(ctx: GauntletLegendsContext): + logger.info("Starting N64 connector...") + while not ctx.exit_event.is_set(): + if ctx.retro_connected: + try: + if not ctx.rom_loaded: + status = await ctx.socket.status() + status = status.split(" ") + if status[1] == "CONTENTLESS": + logger.info("No ROM loaded, waiting...") + await asyncio.sleep(3) + continue + else: + logger.info("ROM Loaded") + ctx.rom_loaded = True + cc_str: str = f"gl_cc_T{ctx.team}_P{ctx.slot}" + pl_str: str = f"gl_pl_T{ctx.team}_P{ctx.slot}" + ctx.set_notify(cc_str) + if not ctx.auth: + await asyncio.sleep(1) + continue + player_level = await ctx.player_level() + await ctx.send_msgs( + [ + { + "cmd": "Set", + "key": pl_str, + "default": {}, + "want_reply": True, + "operations": [ + { + "operation": "replace", + "value": player_level, + }, + ], + }, + ], + ) + bitwise = await ctx.inv_bitwise("Hell", 0x100, 0) + if not ctx.finished_game and bitwise: + await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) + ctx.finished_game = True + if ctx.limbo: + limbo = await ctx.limbo_check(0x78) + if limbo: + ctx.limbo = False + await asyncio.sleep(3) + else: + await asyncio.sleep(0.05) + continue + await ctx.handle_items() + if ctx.deathlink_pending and ctx.deathlink_enabled: + ctx.deathlink_pending = False + await ctx.die() + if not ctx.level_loading and not ctx.in_game: + if not ctx.in_portal: + ctx.in_portal = await ctx.portaling() + if ctx.in_portal and not ctx.init_refactor: + await asyncio.sleep(0.1) + ctx.movement = await ctx.limbo_check() + await ctx.inv_refactor() + ctx.init_refactor = True + ctx.level_loading = await ctx.check_loading() + if ctx.level_loading: + ctx.in_portal = False + ctx.init_refactor = False + if not ctx.scaled: + logger.info("Scaling level...") + await asyncio.sleep(0.2) + await ctx.scale() + ctx.in_game = not await ctx.check_loading() + if ctx.in_game: + ctx.level_loading = False + if not ctx.objects_loaded: + logger.info("Loading Objects...") + await ctx.load_objects(ctx) + await asyncio.sleep(1) + status = await ctx.level_status(ctx) + if status: + await ctx.send_msgs( + [ + { + "cmd": "Set", + "key": cc_str, + "default": {}, + "want_reply": True, + "operations": [ + { + "operation": "replace", + "value": ctx.clear_counts, + }, + ], + }, + ], + ) + ctx.limbo = True + await asyncio.sleep(0.05) + continue + await asyncio.sleep(0.1) + checking = await ctx.location_loop() + if len(checking) > 0: + ctx.locations_checked += checking + await ctx.send_msgs([{"cmd": "LocationChecks", "locations": checking}]) + await asyncio.sleep(0.1) + except TimeoutError: + logger.info("Connection Timed Out, Reconnecting") + ctx.socket = RetroSocket() + ctx.retro_connected = False + await asyncio.sleep(2) + except ConnectionResetError: + logger.info("Connection Lost, Reconnecting") + ctx.socket = RetroSocket() + ctx.retro_connected = False + await asyncio.sleep(2) + except Exception as e: + logger.error(f"Unknown Error Occurred: {e}") + logger.info(traceback.format_exc()) + ctx.socket = RetroSocket() + ctx.retro_connected = False + await asyncio.sleep(2) + else: + try: + logger.info("Attempting to connect to Retroarch...") + status = await ctx.socket.status() + ctx.retro_connected = True + status = status.split(" ") + if status[1] == "CONTENTLESS": + ctx.rom_loaded = False + logger.info("Connected to Retroarch") + continue + except TimeoutError: + logger.info("Connection Timed Out, Trying Again") + await asyncio.sleep(2) + continue + except ConnectionRefusedError: + logger.info("Connection Refused, Trying Again") + await asyncio.sleep(2) + continue + except ConnectionResetError: + logger.info("Connection Lost, Trying Again") + await asyncio.sleep(2) + continue + except Exception as e: + logger.error(f"Unknown Error Occurred: {e}") + logger.info(traceback.format_exc()) + await asyncio.sleep(2) + continue + + +def launch(): + Utils.init_logging("GauntletLegendsClient", exception_logger="Client") + + async def main(): + parser = get_base_parser() + parser.add_argument("patch_file", default="", type=str, nargs="?", help="Path to an APGL file") + args = parser.parse_args() + if args.patch_file: + await asyncio.create_task(_patch_game(args.patch_file)) + ctx = GauntletLegendsContext(args.connect, args.password) + ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop") + if gui_enabled: + ctx.run_gui() + ctx.run_cli() + ctx.gl_sync_task = asyncio.create_task(gl_sync_task(ctx), name="Gauntlet Legends Sync Task") + + await ctx.exit_event.wait() + ctx.server_address = None + + await ctx.shutdown() + + import colorama + + colorama.init() + asyncio.run(main()) + colorama.deinit() diff --git a/worlds/gl/Items.py b/worlds/gl/Items.py new file mode 100644 index 00000000000..8c384038757 --- /dev/null +++ b/worlds/gl/Items.py @@ -0,0 +1,122 @@ +import typing + +from BaseClasses import Item, ItemClassification + + +class ItemData(typing.NamedTuple): + code: int + item_name: str + progression: ItemClassification + + +class GLItem(Item): + game: str = "Gauntlet Legends" + + +item_list: typing.List[ItemData] = [ + ItemData(77780000, "Key", ItemClassification.filler), + ItemData(77780001, "Lightning Potion", ItemClassification.filler), + ItemData(77780002, "Light Potion", ItemClassification.filler), + ItemData(77780003, "Acid Potion", ItemClassification.filler), + ItemData(77780004, "Fire Potion", ItemClassification.filler), + ItemData(77780005, "Acid Breath", ItemClassification.filler), + ItemData(77780006, "Lightning Breath", ItemClassification.filler), + ItemData(77780007, "Fire Breath", ItemClassification.filler), + ItemData(77780008, "Light Amulet", ItemClassification.filler), + ItemData(77780009, "Acid Amulet", ItemClassification.filler), + ItemData(77780010, "Lightning Amulet", ItemClassification.filler), + ItemData(77780011, "Fire Amulet", ItemClassification.filler), + ItemData(77780012, "Lightning Shield", ItemClassification.filler), + ItemData(77780013, "Fire Shield", ItemClassification.filler), + ItemData(77780014, "Invisibility", ItemClassification.filler), + ItemData(77780015, "Levitate", ItemClassification.filler), + ItemData(77780016, "Speed Boots", ItemClassification.filler), + ItemData(77780017, "3-Way Shot", ItemClassification.filler), + ItemData(77780018, "5-Way Shot", ItemClassification.filler), + ItemData(77780019, "Rapid Fire", ItemClassification.filler), + ItemData(77780020, "Reflective Shot", ItemClassification.filler), + ItemData(77780021, "Reflective Shield", ItemClassification.filler), + ItemData(77780022, "Super Shot", ItemClassification.filler), + ItemData(77780023, "Timestop", ItemClassification.filler), + ItemData(77780024, "Phoenix Familiar", ItemClassification.filler), + ItemData(77780025, "Growth", ItemClassification.filler), + ItemData(77780026, "Shrink", ItemClassification.filler), + ItemData(77780027, "Thunder Hammer", ItemClassification.filler), + ItemData(77780028, "Anti-Death Halo", ItemClassification.filler), + ItemData(77780029, "Invulnerability", ItemClassification.filler), + ItemData(77780030, "Fruit", ItemClassification.filler), + ItemData(77780031, "Meat", ItemClassification.filler), + ItemData(77780032, "Runestone 1", ItemClassification.progression), + ItemData(77780033, "Runestone 2", ItemClassification.progression), + ItemData(77780034, "Runestone 3", ItemClassification.progression), + ItemData(77780035, "Runestone 4", ItemClassification.progression), + ItemData(77780036, "Runestone 5", ItemClassification.progression), + ItemData(77780037, "Runestone 6", ItemClassification.progression), + ItemData(77780038, "Runestone 7", ItemClassification.progression), + ItemData(77780039, "Runestone 8", ItemClassification.progression), + ItemData(77780040, "Runestone 9", ItemClassification.progression), + ItemData(77780041, "Runestone 10", ItemClassification.progression), + ItemData(77780042, "Runestone 11", ItemClassification.progression), + ItemData(77780043, "Runestone 12", ItemClassification.progression), + ItemData(77780044, "Runestone 13", ItemClassification.progression), + ItemData(77780045, "Dragon Mirror Shard", ItemClassification.progression), + ItemData(77780046, "Yeti Mirror Shard", ItemClassification.progression), + ItemData(77780047, "Chimera Mirror Shard", ItemClassification.progression), + ItemData(77780048, "Plague Fiend Mirror Shard", ItemClassification.progression), + ItemData(77780049, "Ice Axe of Untar", ItemClassification.useful), + ItemData(77780050, "Flame of Tarkana", ItemClassification.useful), + ItemData(77780051, "Scimitar of Decapitation", ItemClassification.useful), + ItemData(77780052, "Marker's Javelin", ItemClassification.useful), + ItemData(77780053, "Soul Savior", ItemClassification.useful), + ItemData(77780054, "Gold", ItemClassification.filler), + ItemData(77780055, "Mountain Obelisk 1", ItemClassification.progression), + ItemData(77780056, "Mountain Obelisk 2", ItemClassification.progression), + ItemData(77780057, "Mountain Obelisk 3", ItemClassification.progression), + ItemData(77780058, "Town Obelisk 1", ItemClassification.progression), + ItemData(77780059, "Town Obelisk 2", ItemClassification.progression), + ItemData(77780060, "Castle Obelisk 1", ItemClassification.progression), + ItemData(77780061, "Castle Obelisk 2", ItemClassification.progression), + ItemData(77780062, "Death", ItemClassification.trap), + ItemData(77780063, "Poison Fruit", ItemClassification.trap), +] + +item_frequencies: typing.Dict[str, int] = { + "Key": 1000, + "Lightning Potion": 50, + "Light Potion": 50, + "Acid Potion": 50, + "Fire Potion": 50, + "Acid Breath": 50, + "Lightning Breath": 50, + "Fire Breath": 50, + "Light Amulet": 50, + "Acid Amulet": 50, + "Lightning Amulet": 50, + "Fire Amulet": 50, + "Lightning Shield": 50, + "Fire Shield": 50, + "Invisibility": 50, + "Levitate": 50, + "Speed Boots": 50, + "3-Way Shot": 50, + "5-Way Shot": 50, + "Rapid Fire": 50, + "Reflective Shot": 50, + "Reflective Shield": 50, + "Super Shot": 50, + "Timestop": 50, + "Phoenix Familiar": 50, + "Growth": 50, + "Shrink": 50, + "Thunder Hammer": 50, + "Invulnerability": 25, + "Fruit": 100, + "Meat": 100, + "Gold": 150, + "Anti-Death Halo": 30, + "Death": 75, + "Poison Fruit": 75, +} + +item_table: typing.Dict[str, ItemData] = {item.item_name: item for item in item_list} +items_by_id: typing.Dict[int, ItemData] = {item.code: item for item in item_list} diff --git a/worlds/gl/Locations.py b/worlds/gl/Locations.py new file mode 100644 index 00000000000..d02a2802fc3 --- /dev/null +++ b/worlds/gl/Locations.py @@ -0,0 +1,1811 @@ +import typing + +from BaseClasses import Location + + +class LocationData: + name: str = "" + id: int = 0 + difficulty: int = 0 + + def __init__(self, name, id_, difficulty): + self.name = name + self.id = id_ + self.difficulty = difficulty + + +class GLLocation(Location): + game: str = "Gauntlet Legends" + + +valley_of_fire: typing.List[LocationData] = [ + LocationData("Valley of Fire - Scroll", 88870001, 1), + LocationData("Valley of Fire - Key 1", 88870002, 1), + LocationData("Valley of Fire - Key 2", 88870003, 1), + LocationData("Valley of Fire - Key 3 (Dif. 3)", 88870004, 3), + LocationData("Valley of Fire - Key 4", 88870005, 1), + LocationData("Valley of Fire - Fire Amulet", 88870006, 1), + LocationData("Valley of Fire - Key 5", 88870007, 1), + LocationData("Valley of Fire - Key 6 (Dif. 2)", 88870008, 2), + LocationData("Valley of Fire - Key 7", 88870009, 1), + LocationData("Valley of Fire - Fruit 1 (Dif. 2)", 88870010, 2), + LocationData("Valley of Fire - Key 8 (Dif. 3)", 88870011, 3), + LocationData("Valley of Fire - Key 9 (Dif. 2)", 88870012, 2), + LocationData("Valley of Fire - Fire Potion 1 (Dif. 3)", 88870013, 3), + LocationData("Valley of Fire - Thunder Hammer (Dif. 2)", 88870014, 2), + LocationData("Valley of Fire - Fire Potion 2 (Dif. 4)", 88870015, 4), + LocationData("Valley of Fire - Large Gold Pile (Dif. 3)", 88870016, 3), + LocationData("Valley of Fire - Small Gold Pile 1 (Dif. 4)", 88870017, 4), + LocationData("Valley of Fire - Meat Slab (Dif. 4)", 88870018, 4), + LocationData("Valley of Fire - Drumstick (Dif. 3)", 88870019, 3), + LocationData("Valley of Fire - Fruit 2 (Dif. 4)", 88870020, 4), + LocationData("Valley of Fire - Key 10", 88870021, 1), + LocationData("Valley of Fire - Small Gold Pile 2 (Dif. 2)", 88870022, 2), + LocationData("Valley of Fire - Obelisk", 88870611, 1), + LocationData("Valley of Fire Barrel - Nothing 1", 88873500, 1), + LocationData("Valley of Fire Chest - Meat 1 (Dif. 2)", 88873501, 2), + LocationData("Valley of Fire Chest - Potion 1", 88873502, 1), + LocationData("Valley of Fire Chest - Potion 2 (Dif. 2)", 88873503, 2), + LocationData("Valley of Fire Chest - Invisibility 1 (Dif. 3)", 88873504, 3), + LocationData("Valley of Fire Chest - Gold 1", 88873505, 1), + LocationData("Valley of Fire Chest - Random Chest 1 (Dif. 3)", 88873506, 3), + LocationData("Valley of Fire Chest - Gold 2", 88873507, 1), + LocationData("Valley of Fire Chest - Fruit 1", 88873508, 1), + LocationData("Valley of Fire Barrel - Nothing 2", 88873509, 1), + LocationData("Valley of Fire Barrel - Gold 1", 88873510, 1), + LocationData("Valley of Fire Barrel - Fruit 1 (Dif. 3)", 88873511, 3), + LocationData("Valley of Fire Chest - 3-Way Shot 1 (Dif. 4)", 88873512, 4), + LocationData("Valley of Fire Barrel - Nothing 3", 88873513, 1), + LocationData("Valley of Fire Barrel - Scroll 1", 88873514, 1), + LocationData("Valley of Fire Barrel - Nothing 4", 88873515, 1), + LocationData("Valley of Fire Barrel - Key 1 (Dif. 4)", 88873516, 4), + LocationData("Valley of Fire Barrel - Speed Boots 1", 88873517, 1), + LocationData("Valley of Fire Barrel - Fire Breath 1", 88873518, 1), + LocationData("Valley of Fire Barrel - Key 2", 88873519, 1), + LocationData("Valley of Fire Barrel - Potion 1", 88873520, 1), +] + +dagger_peak: typing.List[LocationData] = [ + LocationData("Dagger Peak - Growth", 88870023, 1), + LocationData("Dagger Peak - Runestone", 88870024, 1), + LocationData("Dagger Peak - Key 1", 88870025, 1), + LocationData("Dagger Peak - Speed Boots (Dif. 2)", 88870026, 2), + LocationData("Dagger Peak - Key 2", 88870027, 1), + LocationData("Dagger Peak - Key 3", 88870028, 1), + LocationData("Dagger Peak - Key 4 (Dif. 4)", 88870029, 4), + LocationData("Dagger Peak - Key 5 (Dif. 4)", 88870030, 4), + LocationData("Dagger Peak - Key 6 (Dif. 3)", 88870031, 3), + LocationData("Dagger Peak - Large Pile of Gold 1 (Dif. 2)", 88870032, 2), + LocationData("Dagger Peak - Large Pile of Gold 2 (Dif. 3)", 88870033, 3), + LocationData("Dagger Peak - Full Barrel of Gold 1 (Dif. 4)", 88870034, 4), + LocationData("Dagger Peak - Small Pile of Gold 1 (Dif. 2)", 88870035, 2), + LocationData("Dagger Peak - Small Pile of Gold 2 (Dif. 3)", 88870036, 3), + LocationData("Dagger Peak - Small Pile of Gold 3", 88870037, 1), + LocationData("Dagger Peak - Fire Potion 1", 88870038, 1), + LocationData("Dagger Peak - Fruit", 88870039, 1), + LocationData("Dagger Peak - Key 7 (Dif. 3)", 88870040, 3), + LocationData("Dagger Peak - Key 8 (Dif. 4)", 88870041, 4), + LocationData("Dagger Peak - Half Barrel of Gold 1 (Dif. 3)", 88870042, 3), + LocationData("Dagger Peak - Key 9 (Dif. 2)", 88870043, 2), + LocationData("Dagger Peak - Fire Potion 2 (Dif. 3)", 88870044, 3), + LocationData("Dagger Peak - Poison Fruit (Dif. 3)", 88870045, 3), + LocationData("Dagger Peak - Key 10", 88870046, 1), + LocationData("Dagger Peak - Key 11 (Dif. 3)", 88870047, 3), + LocationData("Dagger Peak - Key 12", 88870048, 1), + LocationData("Dagger Peak - Key 13", 88870049, 1), + LocationData("Dagger Peak - Key 14 (Dif. 2)", 88870050, 2), + LocationData("Dagger Peak - Fire Potion 3 (Dif. 2)", 88870051, 2), + LocationData("Dagger Peak - Meat 1 (Dif. 3)", 88870052, 3), + LocationData("Dagger Peak - Key 15", 88870053, 1), + LocationData("Dagger Peak - Key 16 (Dif. 2)", 88870054, 2), + LocationData("Dagger Peak - Key 17", 88870055, 1), + LocationData("Dagger Peak - Meat 2", 88870056, 1), + LocationData("Dagger Peak - Key 18 (Dif. 4)", 88870057, 4), + LocationData("Dagger Peak - Obelisk", 88870612, 1), + LocationData("Dagger Peak Barrel - Rapid Fire 1", 88873600, 1), + LocationData("Dagger Peak Barrel - Key 1", 88873601, 1), + LocationData("Dagger Peak Barrel - Levitate 1", 88873602, 1), + LocationData("Dagger Peak Barrel - Fire Amulet 1 (Dif. 2)", 88873603, 2), + LocationData("Dagger Peak Barrel - Death 1", 88873604, 1), + LocationData("Dagger Peak Barrel - Fire Shield 1 (Dif. 2)", 88873605, 2), + LocationData("Dagger Peak Barrel - Scroll 1", 88873606, 1), + LocationData("Dagger Peak Chest - Invulnerability 1 (Dif. 4)", 88873607, 4), + LocationData("Dagger Peak Chest - Gold 1", 88873608, 1), + LocationData("Dagger Peak Chest - Potion 1 (Dif. 4)", 88873609, 4), + LocationData("Dagger Peak Chest - Gold 2", 88873610, 1), + LocationData("Dagger Peak Barrel - Fruit 1 (Dif. 4)", 88873611, 4), + LocationData("Dagger Peak Barrel - Fruit 2", 88873612, 1), + LocationData("Dagger Peak Barrel - Fruit 3 (Dif. 2)", 88873613, 2), + LocationData("Dagger Peak Barrel - Poison Fruit 1", 88873614, 1), + LocationData("Dagger Peak Chest - Scroll 1", 88873615, 1), + LocationData("Dagger Peak Chest - Meat 1 (Dif. 2)", 88873616, 2), + LocationData("Dagger Peak Chest - Gold 3 (Dif. 2)", 88873617, 2), + LocationData("Dagger Peak Chest - 3-Way Shot 1", 88873618, 1), + LocationData("Dagger Peak Chest - Gold 4", 88873619, 1), + LocationData("Dagger Peak Chest - Gold 5 (Dif. 3)", 88873620, 3), + LocationData("Dagger Peak Chest - Random 1 (Dif. 2)", 88873621, 2), + LocationData("Dagger Peak Chest - Gold 6 (Dif. 4)", 88873622, 4), + LocationData("Dagger Peak Chest - Potion 2", 88873623, 1), + LocationData("Dagger Peak Chest - Death 1 (Dif. 3)", 88873624, 3), + LocationData("Dagger Peak Chest - Meat 2 (Dif. 4)", 88873625, 4), + LocationData("Dagger Peak Chest - Timestop 1 (Dif. 3)", 88873626, 3), +] + +cliffs_of_desolation: typing.List[LocationData] = [ + LocationData("Cliffs of Desolation - Scroll 1", 88870058, 1), + LocationData("Cliffs of Desolation - Reflective Shot (Dif. 4)", 88870059, 4), + LocationData("Cliffs of Desolation - Key 1 (Dif. 2)", 88870060, 2), + LocationData("Cliffs of Desolation - Key 2 (Dif. 3)", 88870061, 3), + LocationData("Cliffs of Desolation - Invisibility", 88870062, 1), + LocationData("Cliffs of Desolation - Fire Potion 1 (Dif. 3)", 88870063, 3), + LocationData("Cliffs of Desolation - Half Barrel of Gold 1 (Dif. 4)", 88870064, 4), + LocationData("Cliffs of Desolation - Invulnerability (Dif. 4)", 88870065, 4), + LocationData("Cliffs of Desolation - Rapid Fire (Dif. 3)", 88870066, 3), + LocationData("Cliffs of Desolation - Half Barrel of Gold 2 (Dif. 3)", 88870067, 3), + LocationData("Cliffs of Desolation - Large Pile of Gold 1", 88870068, 1), + LocationData("Cliffs of Desolation - Meat Slab 1", 88870069, 1), + LocationData("Cliffs of Desolation - Poison Fruit 1 (Dif. 4)", 88870070, 4), + LocationData("Cliffs of Desolation - Key 3 (Dif. 4)", 88870071, 4), + LocationData("Cliffs of Desolation - Key 4 (Dif. 3)", 88870072, 3), + LocationData("Cliffs of Desolation - Key 5 (Dif. 4)", 88870073, 4), + LocationData("Cliffs of Desolation - Key 6 (Dif. 2)", 88870074, 2), + LocationData("Cliffs of Desolation - Key 7", 88870075, 1), + LocationData("Cliffs of Desolation - Small Pile of Gold 1", 88870076, 1), + LocationData("Cliffs of Desolation - Small Pile of Gold 2 (Dif. 2)", 88870077, 2), + LocationData("Cliffs of Desolation - Key 8", 88870078, 1), + LocationData("Cliffs of Desolation - Key 9", 88870079, 1), + LocationData("Cliffs of Desolation - Key 10", 88870080, 1), + LocationData("Cliffs of Desolation - Key 11 (Dif. 2)", 88870081, 2), + LocationData("Cliffs of Desolation - Key 12 (Dif. 3)", 88870082, 3), + LocationData("Cliffs of Desolation - Key 13 (Dif. 3)", 88870083, 3), + LocationData("Cliffs of Desolation - Key 14", 88870084, 1), + LocationData("Cliffs of Desolation - Key 15", 88870085, 1), + LocationData("Cliffs of Desolation - Key 16", 88870086, 1), + LocationData("Cliffs of Desolation - Key 17", 88870087, 1), + LocationData("Cliffs of Desolation - Key 18 (Dif. 4)", 88870088, 4), + LocationData("Cliffs of Desolation - Key 19", 88870089, 1), + LocationData("Cliffs of Desolation - Key 20 (Dif. 2)", 88870090, 2), + LocationData("Cliffs of Desolation - Full Barrel of Gold 1 (Dif. 4)", 88870091, 4), + LocationData("Cliffs of Desolation - Key 21", 88870092, 1), + LocationData("Cliffs of Desolation - Key 22 (Dif. 4)", 88870093, 4), + LocationData("Cliffs of Desolation - Key 23", 88870094, 1), + LocationData("Cliffs of Desolation - Key 24", 88870095, 1), + LocationData("Cliffs of Desolation - Key 25", 88870096, 1), + LocationData("Cliffs of Desolation - Key 26", 88870097, 1), + LocationData("Cliffs of Desolation - Key 27", 88870098, 1), + LocationData("Cliffs of Desolation - Obelisk", 88870613, 1), + LocationData("Cliffs of Desolation Barrel - Key 1 (Dif. 3)", 8887370, 3), + LocationData("Cliffs of Desolation Barrel - Death 1 (Dif. 3)", 8887371, 3), + LocationData("Cliffs of Desolation Chest - Random 1 (Dif. 2)", 8887372, 2), + LocationData("Cliffs of Desolation Barrel - Shrink 1 (Dif. 2)", 8887373, 2), + LocationData("Cliffs of Desolation Chest - 5-Way Shot 1 (Dif. 3)", 8887374, 3), + LocationData("Cliffs of Desolation Barrel - Nothing 1", 8887375, 1), + LocationData("Cliffs of Desolation Barrel - Nothing 2", 8887376, 1), + LocationData("Cliffs of Desolation Barrel - Scroll 1", 8887377, 1), + LocationData("Cliffs of Desolation Barrel - Nothing 3 (Dif. 2)", 8887378, 2), + LocationData("Cliffs of Desolation Chest - Potion 1 (Dif. 4)", 8887379, 4), + LocationData("Cliffs of Desolation Barrel - Poison Fruit 1", 88873710, 1), + LocationData("Cliffs of Desolation Barrel - Nothing 4 (Dif. 2)", 88873711, 2), + LocationData("Cliffs of Desolation Barrel - Fruit 1", 88873712, 1), + LocationData("Cliffs of Desolation Barrel - Key 2 (Dif. 2)", 88873713, 2), + LocationData("Cliffs of Desolation Barrel - Key 3 (Dif. 2)", 88873714, 2), + LocationData("Cliffs of Desolation Barrel - Fruit 2 (Dif. 2)", 88873715, 2), + LocationData("Cliffs of Desolation Barrel - Poison Fruit 2 (Dif. 2)", 88873716, 2), + LocationData("Cliffs of Desolation Barrel - Key 4 (Dif. 2)", 88873717, 2), + LocationData("Cliffs of Desolation Barrel - Key 5", 88873718, 1), + LocationData("Cliffs of Desolation Barrel - Key 6", 88873719, 1), + LocationData("Cliffs of Desolation Chest - Meat 1", 88873720, 1), + LocationData("Cliffs of Desolation Chest - Potion 2", 88873721, 1), + LocationData("Cliffs of Desolation Chest - Levitate 1 (Dif. 2)", 88873722, 2), + LocationData("Cliffs of Desolation Chest - Gold 1 (Dif. 2)", 88873723, 2), + LocationData("Cliffs of Desolation Chest - Fruit 1 (Dif. 3)", 88873724, 3), + LocationData("Cliffs of Desolation Chest - Death 1 (Dif. 4)", 88873725, 4), + LocationData("Cliffs of Desolation Chest - Gold 2 (Dif. 2)", 88873726, 2), + LocationData("Cliffs of Desolation Chest - Potion 3", 88873727, 1), + LocationData("Cliffs of Desolation Chest - Gold 3", 88873728, 1), + LocationData("Cliffs of Desolation Chest - Death 2 (Dif. 2)", 88873729, 2), + LocationData("Cliffs of Desolation Chest - Gold 4 (Dif. 4)", 88873730, 4), + LocationData("Cliffs of Desolation Barrel - Key 7", 88873731, 1), + LocationData("Cliffs of Desolation Chest - Gold 5 (Dif. 3)", 88873732, 3), + LocationData("Cliffs of Desolation Barrel - Key 8", 88873733, 1), + LocationData("Cliffs of Desolation Chest - Potion 4", 88873734, 1), + LocationData("Cliffs of Desolation Chest - Meat 2 (Dif. 3)", 88873735, 3), + LocationData("Cliffs of Desolation Chest - Gold 6", 88873736, 1), + LocationData("Cliffs of Desolation Chest - Death 3", 88873737, 1), + LocationData("Cliffs of Desolation Chest - Meat 3 (Dif. 4)", 88873738, 4), + LocationData("Cliffs of Desolation Chest - Potion 5 (Dif. 2)", 88873739, 2), + LocationData("Cliffs of Desolation Dark Chest - Random 1 (Dif. 3)", 88873740, 3), + LocationData("Cliffs of Desolation Chest - Speed Boots 1", 88873741, 1), + LocationData("Cliffs of Desolation Chest - Potion 6", 88873742, 1), + LocationData("Cliffs of Desolation Barrel - Key 9", 88873743, 1), + LocationData("Cliffs of Desolation Chest - Meat 4 (Dif. 2)", 88873744, 2), + LocationData("Cliffs of Desolation Chest - Fire Amulet 1", 88873745, 1), + LocationData("Cliffs of Desolation Chest - Fruit 2", 88873746, 1), + LocationData("Cliffs of Desolation Barrel - Invulnerability 1", 88873747, 1), + LocationData("Cliffs of Desolation Chest - Fruit 3", 88873748, 1), +] + +lost_cave: typing.List[LocationData] = [ + LocationData("Lost Cave - Scroll 1", 88870099, 1), + LocationData("Lost Cave - Runestone", 88870100, 1), + LocationData("Lost Cave - Very Small Pile of Gold 1 (Dif. 2)", 88870101, 2), + LocationData("Lost Cave - Large Pile of Gold 1 (Dif. 4)", 88870102, 4), + LocationData("Lost Cave - Key 1", 88870103, 1), + LocationData("Lost Cave - Very Small Pile of Gold 2", 88870104, 1), + LocationData("Lost Cave - Very Small Pile of Gold 3 (Dif. 3)", 88870105, 3), + LocationData("Lost Cave - Key 2", 88870106, 1), + LocationData("Lost Cave - Small Pile of Gold 1 (Dif. 2)", 88870107, 2), + LocationData("Lost Cave - Half Barrel of Gold 1 (Dif. 3)", 88870108, 3), + LocationData("Lost Cave - Large Pile of Gold 2", 88870109, 1), + LocationData("Lost Cave - Key 3", 88870110, 1), + LocationData("Lost Cave - Key 4 (Dif. 2)", 88870111, 2), + LocationData("Lost Cave - Fire Potion 1", 88870112, 1), + LocationData("Lost Cave - Key 5", 88870113, 1), + LocationData("Lost Cave - Key 6 (Dif. 3)", 88870114, 3), + LocationData("Lost Cave - Meat Slab 1 (Dif. 2)", 88870115, 2), + LocationData("Lost Cave - Key 7 (Dif. 2)", 88870116, 2), + LocationData("Lost Cave - Fruit 1 (Dif. 2)", 88870117, 2), + LocationData("Lost Cave - Fire Potion 2 (Dif. 2)", 88870118, 2), + LocationData("Lost Cave - Fruit 2", 88870119, 1), + LocationData("Lost Cave - Key 8", 88870120, 1), + LocationData("Lost Cave - Key 9", 88870121, 1), + LocationData("Lost Cave - Key 10", 88870122, 1), + LocationData("Lost Cave - Key 11 (Dif. 2)", 88870123, 2), + LocationData("Lost Cave - Small Pile of Gold 2 (Dif. 2)", 88870124, 2), + LocationData("Lost Cave - Large Pile of Gold 3 (Dif. 2)", 88870125, 2), + LocationData("Lost Cave - Full Barrel of Gold 1 (Dif. 4)", 88870126, 4), + LocationData("Lost Cave - Drumstick 1 (Dif. 2)", 88870127, 2), + LocationData("Lost Cave - Drumstick 2 (Dif. 4)", 88870128, 4), + LocationData("Lost Cave - Key 12 (Dif. 4)", 88870129, 4), + LocationData("Lost Cave - Key 13 (Dif. 3)", 88870130, 3), + LocationData("Lost Cave - Key 14 (Dif. 4)", 88870131, 4), + LocationData("Lost Cave - Key 15 (Dif. 3)", 88870132, 3), + LocationData("Lost Cave - Key 16 (Dif. 3)", 88870133, 3), + LocationData("Lost Cave - Key 17", 88870134, 1), + LocationData("Lost Cave - Key 18 (Dif. 2)", 88870135, 2), + LocationData("Lost Cave - Key 19 (Dif. 3)", 88870136, 3), + LocationData("Lost Cave - Key 20 (Dif. 4)", 88870137, 4), + LocationData("Lost Cave - Fruit 3", 88870138, 1), + LocationData("Lost Cave - Fruit 4 (Dif. 2)", 88870139, 2), + LocationData("Lost Cave - Reflective Shot", 88870140, 1), + LocationData("Lost Cave - Fire Breath", 88870141, 1), + LocationData("Lost Cave - Key 21", 88870142, 1), + LocationData("Lost Cave Barrel - Scroll 1", 8887380, 1), + LocationData("Lost Cave Chest - Invulnerability 1 (Dif. 3)", 8887381, 3), + LocationData("Lost Cave Barrel - Nothing 1 (Dif. 2)", 8887382, 2), + LocationData("Lost Cave Chest - Gold 1", 8887383, 1), + LocationData("Lost Cave Chest - Gold 2 (Dif. 3)", 8887384, 3), + LocationData("Lost Cave Barrel - Rapid Fire 1", 8887385, 1), + LocationData("Lost Cave Barrel - Nothing 2", 8887386, 1), + LocationData("Lost Cave Chest - Speed Boots 1 (Dif. 4)", 8887387, 4), + LocationData("Lost Cave Chest - Fruit 1 (Dif. 2)", 8887388, 2), + LocationData("Lost Cave Dark Chest - Random 1", 8887389, 1), + LocationData("Lost Cave Chest - Gold 3", 88873810, 1), + LocationData("Lost Cave Barrel - Fruit 1", 88873811, 1), + LocationData("Lost Cave Chest - Death 1", 88873812, 1), + LocationData("Lost Cave Chest - Potion 1", 88873813, 1), + LocationData("Lost Cave Chest - Meat 1 (Dif. 3)", 88873814, 3), + LocationData("Lost Cave Barrel - Potion 1 (Dif. 3)", 88873815, 3), + LocationData("Lost Cave Barrel - Potion 2 (Dif. 4)", 88873816, 4), + LocationData("Lost Cave Chest - Meat 2", 88873817, 1), + LocationData("Lost Cave Chest - Fire Amulet 1", 88873818, 1), + LocationData("Lost Cave Chest - Gold 4 (Dif. 2)", 88873819, 2), + LocationData("Lost Cave Chest - Potion 2", 88873820, 1), + LocationData("Lost Cave Chest - Fruit 2 (Dif. 3)", 88873821, 3), + LocationData("Lost Cave Barrel - Meat 1", 88873822, 1), + LocationData("Lost Cave Barrel - Potion 3 (Dif. 2)", 88873823, 2), + LocationData("Lost Cave Barrel - Potion 4 (Dif. 4)", 88873824, 4), + LocationData("Lost Cave Barrel - Potion 5 (Dif. 2)", 88873825, 2), + LocationData("Lost Cave Barrel - Acid Breath 1 (Dif. 2)", 88873826, 2), + LocationData("Lost Cave Barrel - Fruit 2 (Dif. 4)", 88873827, 4), + LocationData("Lost Cave Barrel - Nothing 3", 88873828, 1), + LocationData("Lost Cave Chest - Poison Fruit 1 (Dif. 3)", 88873829, 3), + LocationData("Lost Cave Chest - Gold 5 (Dif. 4)", 88873830, 4), + LocationData("Lost Cave Chest - Poison Fruit 2 (Dif. 4)", 88873831, 4), + LocationData("Lost Cave Chest - Light Amulet 1 (Dif. 2)", 88873832, 2), + LocationData("Lost Cave Chest - 3-Way Shot 1 (Dif. 2)", 88873833, 2), + LocationData("Lost Cave Barrel - Reflective Shield 1 (Dif. 3)", 88873834, 3), + LocationData("Lost Cave Barrel - Super Shot 1 (Dif. 4)", 88873835, 4), + LocationData("Lost Cave Chest - Gold 6", 88873836, 1), +] + +volcanic_cavern: typing.List[LocationData] = [ + LocationData("Volcanic Cavern - Key 1", 88870143, 1), + LocationData("Volcanic Cavern - Scimitar of Decapitation", 88870144, 1), + LocationData("Volcanic Cavern - Runestone", 88870145, 1), + LocationData("Volcanic Cavern - Key 2", 88870146, 1), + LocationData("Volcanic Cavern - Key 3 (Dif. 3)", 88870147, 3), + LocationData("Volcanic Cavern - Fire Potion 1", 88870148, 1), + LocationData("Volcanic Cavern - Key 4", 88870149, 1), + LocationData("Volcanic Cavern - Key 5", 88870150, 1), + LocationData("Volcanic Cavern - Key 6", 88870151, 1), + LocationData("Volcanic Cavern - Key 7 (Dif. 2)", 88870152, 2), + LocationData("Volcanic Cavern - Key 8 (Dif. 2)", 88870153, 2), + LocationData("Volcanic Cavern - Key 9", 88870154, 1), + LocationData("Volcanic Cavern - Large Pile of Gold 1 (Dif. 2)", 88870155, 2), + LocationData("Volcanic Cavern - Small Pile of Gold 1", 88870156, 1), + LocationData("Volcanic Cavern - Half Barrel of Gold 1 (Dif. 3)", 88870157, 3), + LocationData("Volcanic Cavern - Key 10", 88870158, 1), + LocationData("Volcanic Cavern - Key 11 (Dif. 2)", 88870159, 2), + LocationData("Volcanic Cavern - Key 12 (Dif. 3)", 88870160, 3), + LocationData("Volcanic Cavern - Key 13 (Dif. 2)", 88870161, 2), + LocationData("Volcanic Cavern - Very Small Pile of Gold 1", 88870162, 1), + LocationData("Volcanic Cavern - Key 14 (Dif. 2)", 88870163, 2), + LocationData("Volcanic Cavern - Key 15 (Dif. 3)", 88870164, 3), + LocationData("Volcanic Cavern - Full Barrel of Gold 2 (Dif. 4)", 88870165, 4), + LocationData("Volcanic Cavern - Small Pile of Gold 3", 88870166, 1), + LocationData("Volcanic Cavern - Death (Dif. 2)", 88870167, 2), + LocationData("Volcanic Cavern - Key 16 (Dif. 3)", 88870168, 3), + LocationData("Volcanic Cavern - Key 17 (Dif. 4)", 88870169, 4), + LocationData("Volcanic Cavern - Key 18 (Dif. 3)", 88870170, 3), + LocationData("Volcanic Cavern - Key 19 (Dif. 4)", 88870171, 4), + LocationData("Volcanic Cavern - Key 20 (Dif. 2)", 88870172, 2), + LocationData("Volcanic Cavern - Fire Potion 2 (Dif. 4)", 88870173, 4), + LocationData("Volcanic Cavern - 5-Way Shot (Dif. 4)", 88870174, 4), + LocationData("Volcanic Cavern - Invisibility (Dif. 3)", 88870175, 3), + LocationData("Volcanic Cavern Barrel - Key 1 (Dif. 4)", 8887390, 4), + LocationData("Volcanic Cavern Barrel - Key 2 (Dif. 3)", 8887391, 3), + LocationData("Volcanic Cavern Barrel - Key 3", 8887392, 1), + LocationData("Volcanic Cavern Barrel - Lightning Amulet 1", 8887393, 1), + LocationData("Volcanic Cavern Chest - Lightning Shield 1", 8887394, 1), + LocationData("Volcanic Cavern Barrel - Fruit 1 (Dif. 4)", 8887395, 4), + LocationData("Volcanic Cavern Chest - Meat 1", 8887396, 1), + LocationData("Volcanic Cavern Barrel - Nothing 1 (Dif. 4)", 8887397, 4), + LocationData("Volcanic Cavern Chest - Scroll 1", 8887398, 1), + LocationData("Volcanic Cavern Dark Chest - Random 1 (Dif. 3)", 8887399, 3), + LocationData("Volcanic Cavern Chest - Death 1", 88873910, 1), + LocationData("Volcanic Cavern Chest - Fruit 1", 88873911, 1), + LocationData("Volcanic Cavern Barrel - Potion 1 (Dif. 3)", 88873912, 3), + LocationData("Volcanic Cavern Chest - Poison Fruit 1 (Dif. 3)", 88873913, 3), + LocationData("Volcanic Cavern Chest - Super Shot 1", 88873914, 1), + LocationData("Volcanic Cavern Barrel - Nothing 2", 88873915, 1), + LocationData("Volcanic Cavern Barrel - Poison Fruit 1", 88873916, 1), + LocationData("Volcanic Cavern Barrel - Anti-Death Halo 1 (Dif. 2)", 88873917, 2), + LocationData("Volcanic Cavern Chest - Potion 1 (Dif. 3)", 88873918, 3), + LocationData("Volcanic Cavern Chest - 3-Way Shot 1", 88873919, 1), + LocationData("Volcanic Cavern Chest - Fruit 2 (Dif. 2)", 88873920, 2), + LocationData("Volcanic Cavern Chest - Timestop 1 (Dif. 3)", 88873921, 3), + LocationData("Volcanic Cavern Chest - Meat 2", 88873922, 1), + LocationData("Volcanic Cavern Barrel - Potion 2 (Dif. 2)", 88873923, 2), + LocationData("Volcanic Cavern Barrel - Meat 1", 88873924, 1), + LocationData("Volcanic Cavern Chest - Potion 2 (Dif. 4)", 88873925, 4), + LocationData("Volcanic Cavern Chest - Fruit 3 (Dif. 2)", 88873926, 2), + LocationData("Volcanic Cavern Chest - Fruit 4 (Dif. 3)", 88873927, 3), + LocationData("Volcanic Cavern Barrel - Scroll 1", 88873928, 1), + LocationData("Volcanic Cavern Barrel - Nothing 3", 88873929, 1), + LocationData("Volcanic Cavern Barrel - Gold 1 (Dif. 3)", 88873930, 3), + LocationData("Volcanic Cavern Chest - Gold 1 (Dif. 4)", 88873931, 4), + LocationData("Volcanic Cavern Barrel - Gold 2", 88873932, 1), + LocationData("Volcanic Cavern Chest - Meat 3 (Dif. 2)", 88873933, 2), + LocationData("Volcanic Cavern Barrel - Fruit 2 (Dif. 4)", 88873934, 4), + LocationData("Volcanic Cavern Barrel - Meat 2 (Dif. 3)", 88873935, 3), + LocationData("Volcanic Cavern Barrel - Potion 3", 88873936, 1), + LocationData("Volcanic Cavern Barrel - Gold 3 (Dif. 2)", 88873937, 2), + LocationData("Volcanic Cavern Chest - Lightning Breath 1 (Dif. 2)", 88873938, 2), + LocationData("Volcanic Cavern Chest - Fruit 5 (Dif. 3)", 88873939, 3), + LocationData("Volcanic Cavern Chest - Speed Boots 1 (Dif. 4)", 88873940, 4), + LocationData("Volcanic Cavern Chest - Potion 3 (Dif. 2)", 88873941, 2), + LocationData("Volcanic Cavern Barrel - Nothing 4 (Dif. 3)", 88873942, 3), + LocationData("Volcanic Cavern Barrel - Potion 4", 88873943, 1), + LocationData("Volcanic Cavern Barrel - Death 1 (Dif. 3)", 88873944, 3), + LocationData("Volcanic Cavern Barrel - Meat 3 (Dif. 4)", 88873945, 4), + LocationData("Volcanic Cavern Chest - Phoenix Familiar 1 (Dif. 2)", 88873946, 2), + LocationData("Volcanic Cavern Barrel - Nothing 5 (Dif. 2)", 88873947, 2), + LocationData("Volcanic Cavern Barrel - Nothing 6 (Dif. 2)", 88873948, 2), + LocationData("Volcanic Cavern Barrel - Nothing 7 (Dif. 3)", 88873949, 3), + LocationData("Volcanic Cavern Barrel - Nothing 8", 88873950, 1), + LocationData("Volcanic Cavern Barrel - Nothing 9 (Dif. 4)", 88873951, 4), + LocationData("Volcanic Cavern Barrel - Nothing 10 (Dif. 2)", 88873952, 2), + LocationData("Volcanic Cavern Barrel - Nothing 11 (Dif. 3)", 88873953, 3), + LocationData("Volcanic Cavern Barrel - Scroll 2", 88873954, 1), + LocationData("Volcanic Cavern Barrel - Nothing 12 (Dif. 3)", 88873955, 3), + LocationData("Volcanic Cavern Barrel - Nothing 13 (Dif. 2)", 88873956, 2), +] + +dragons_lair: typing.List[LocationData] = [ + LocationData("Dragon's Lair - Slab of Meat (Dif. 2)", 88870176, 2), + LocationData("Dragon's Lair - Rapid Fire (Dif. 2)", 88870177, 2), + LocationData("Dragon's Lair - Growth (Dif. 4)", 88870178, 4), + LocationData("Dragon's Lair - Speed Boots (Dif. 3)", 88870179, 3), + LocationData("Dragon's Lair - 3-Way Shot", 88870180, 1), + LocationData("Dragon's Lair - Drumstick", 88870181, 1), + LocationData("Dragon's Lair - Dragon Mirror Shard", 88870607, 1), +] + +castle_courtyard: typing.List[LocationData] = [ + LocationData("Castle Courtyard - Runestone", 88870182, 1), + LocationData("Castle Courtyard - Key 1", 88870183, 1), + LocationData("Castle Courtyard - Key 2", 88870184, 1), + LocationData("Castle Courtyard - Key 3", 88870185, 1), + LocationData("Castle Courtyard - Key 4", 88870186, 1), + LocationData("Castle Courtyard - Key 5", 88870187, 1), + LocationData("Castle Courtyard - Key 6", 88870188, 1), + LocationData("Castle Courtyard - Key 7", 88870189, 1), + LocationData("Castle Courtyard - Key 8", 88870190, 1), + LocationData("Castle Courtyard - Key 9", 88870191, 1), + LocationData("Castle Courtyard - Scroll 1", 88870192, 1), + LocationData("Castle Courtyard - Full Barrel of Gold 1 (Dif. 4)", 88870193, 4), + LocationData("Castle Courtyard - Lightning Potion 1", 88870194, 1), + LocationData("Castle Courtyard - Key 10", 88870195, 1), + LocationData("Castle Courtyard - Lightning Potion 2 (Dif. 4)", 88870196, 4), + LocationData("Castle Courtyard - Scroll 2", 88870197, 1), + LocationData("Castle Courtyard - Poison Fruit 1", 88870198, 1), + LocationData("Castle Courtyard - Key 11 (Dif. 2)", 88870199, 2), + LocationData("Castle Courtyard - Key 12", 88870200, 1), + LocationData("Castle Courtyard - Key 13 (Dif. 4)", 88870201, 4), + LocationData("Castle Courtyard - Key 14 (Dif. 4)", 88870202, 4), + LocationData("Castle Courtyard - Large Pile of Gold 1 (Dif. 2)", 88870203, 2), + LocationData("Castle Courtyard - Key 15", 88870204, 1), + LocationData("Castle Courtyard - Key 16 (Dif. 2)", 88870205, 2), + LocationData("Castle Courtyard - Death 1", 88870206, 1), + LocationData("Castle Courtyard - Death 2 (Dif. 3)", 88870207, 3), + LocationData("Castle Courtyard - Invulnerability", 88870208, 1), + LocationData("Castle Courtyard - Half Barrel of Gold 1 (Dif. 3)", 88870209, 3), + LocationData("Castle Courtyard - Lightning Potion 3 (Dif. 2)", 88870210, 2), + LocationData("Castle Courtyard - Timestop (Dif. 4)", 88870211, 4), + LocationData("Castle Courtyard - Super Shot (Dif. 2)", 88870212, 2), + LocationData("Castle Courtyard - Phoenix Familiar", 88870213, 1), + LocationData("Castle Courtyard - Fire Breath (Dif. 2)", 88870214, 2), + LocationData("Castle Courtyard - Key 17 (Dif. 2)", 88870215, 2), + LocationData("Castle Courtyard - Key 18 (Dif. 3)", 88870216, 3), + LocationData("Castle Courtyard - Key 19 (Dif. 4)", 88870217, 4), + LocationData("Castle Courtyard - Lightning Shield", 88870218, 1), + LocationData("Castle Courtyard - Obelisk", 88870616, 1), + LocationData("Castle Courtyard Barrel - Key 1", 8887300, 1), + LocationData("Castle Courtyard Chest - Shrink 1 (Dif. 4)", 8887301, 4), + LocationData("Castle Courtyard Barrel - Nothing 1 (Dif. 3)", 8887302, 3), + LocationData("Castle Courtyard Barrel - Nothing 2", 8887303, 1), + LocationData("Castle Courtyard Barrel - Nothing 3", 8887304, 1), + LocationData("Castle Courtyard Barrel - Fruit 1", 8887305, 1), + LocationData("Castle Courtyard Barrel - Potion 1", 8887306, 1), + LocationData("Castle Courtyard Barrel - Nothing 4", 8887307, 1), + LocationData("Castle Courtyard Barrel - Key 2 (Dif. 2)", 8887308, 2), + LocationData("Castle Courtyard Barrel - Poison Fruit 1 (Dif. 2)", 8887309, 2), + LocationData("Castle Courtyard Barrel - Nothing 5 (Dif. 2)", 88873010, 2), + LocationData("Castle Courtyard Barrel - Gold 1 (Dif. 3)", 88873011, 3), + LocationData("Castle Courtyard Barrel - Key 3 (Dif. 2)", 88873012, 2), + LocationData("Castle Courtyard Chest - Scroll 1", 88873013, 1), + LocationData("Castle Courtyard Dark Chest - Random 1 (Dif. 4)", 88873014, 4), + LocationData("Castle Courtyard Chest - Gold 1 (Dif. 2)", 88873015, 2), + LocationData("Castle Courtyard Chest - Nothing 1", 88873016, 1), + LocationData("Castle Courtyard Chest - Reflective Shot 1", 88873017, 1), + LocationData("Castle Courtyard Chest - Gold 2", 88873018, 1), + LocationData("Castle Courtyard Chest - Death 1 (Dif. 2)", 88873019, 2), + LocationData("Castle Courtyard Chest - Speed Boots 1 (Dif. 3)", 88873020, 3), + LocationData("Castle Courtyard Barrel - Invisibility 1 (Dif. 4)", 88873021, 4), + LocationData("Castle Courtyard Barrel - Nothing 6", 88873022, 1), + LocationData("Castle Courtyard Barrel - Gold 2", 88873023, 1), + LocationData("Castle Courtyard Barrel - Key 4 (Dif. 3)", 88873024, 3), + LocationData("Castle Courtyard Barrel - Fruit 2 (Dif. 3)", 88873025, 3), + LocationData("Castle Courtyard Chest - Scroll 2", 88873026, 1), + LocationData("Castle Courtyard Chest - Potion 1 (Dif. 3)", 88873027, 3), + LocationData("Castle Courtyard Chest - Gold 3 (Dif. 2)", 88873028, 2), + LocationData("Castle Courtyard Barrel - Key 5 (Dif. 3)", 88873029, 3), + LocationData("Castle Courtyard Barrel - Key 6", 88873030, 1), + LocationData("Castle Courtyard Chest - Meat 1 (Dif. 4)", 88873031, 4), + LocationData("Castle Courtyard Chest - Meat 2 (Dif. 3)", 88873032, 3), + LocationData("Castle Courtyard Chest - Gold 4 (Dif. 4)", 88873033, 4), + LocationData("Castle Courtyard Chest - Potion 2 (Dif. 3)", 88873034, 3), + LocationData("Castle Courtyard Chest - Meat 3 (Dif. 2)", 88873035, 2), + LocationData("Castle Courtyard Chest - Lightning Amulet 1 (Dif. 3)", 88873036, 3), + LocationData("Castle Courtyard Chest - Fruit 1 (Dif. 4)", 88873037, 4), + LocationData("Castle Courtyard Barrel - Key 7 (Dif. 3)", 88873038, 3), + LocationData("Castle Courtyard Chest - Potion 3 (Dif. 4)", 88873039, 4), + LocationData("Castle Courtyard Chest - Gold 5", 88873040, 1), + LocationData("Castle Courtyard Barrel - Key 8 (Dif. 3)", 88873041, 3), + LocationData("Castle Courtyard Barrel - Key 9", 88873042, 1), + LocationData("Castle Courtyard Chest - Fruit 2 (Dif. 2)", 88873043, 2), + LocationData("Castle Courtyard Barrel - Gold 3", 88873044, 1), + LocationData("Castle Courtyard Chest - Meat 4", 88873045, 1), + LocationData("Castle Courtyard Barrel - Key 10 (Dif. 4)", 88873046, 4), + LocationData("Castle Courtyard Barrel - 3-Way Shot 1 (Dif. 2)", 88873047, 2), + LocationData("Castle Courtyard Barrel - Nothing 7 (Dif. 4)", 88873048, 4), + LocationData("Castle Courtyard Barrel - Lightning Breath 1 (Dif. 3)", 88873049, 3), + LocationData("Castle Courtyard Barrel - Poison Fruit 2 (Dif. 4)", 88873050, 4), + LocationData("Castle Courtyard Barrel - Nothing 8 (Dif. 2)", 88873051, 2), + LocationData("Castle Courtyard Barrel - Nothing 9 (Dif. 4)", 88873052, 4), + LocationData("Castle Courtyard Barrel - Key 11 (Dif. 4)", 88873053, 4), + LocationData("Castle Courtyard Barrel - Nothing 10 (Dif. 3)", 88873054, 3), + LocationData("Castle Courtyard Barrel - Nothing 11 (Dif. 3)", 88873055, 3), + LocationData("Castle Courtyard Barrel - Nothing 12", 88873056, 1), + LocationData("Castle Courtyard Barrel - Key 12 (Dif. 4)", 88873057, 4), +] + +dungeon_of_torment: typing.List[LocationData] = [ + LocationData("Dungeon of Torment - Lightning Potion 1 (Dif. 4)", 88870219, 4), + LocationData("Dungeon of Torment - Key 1", 88870220, 1), + LocationData("Dungeon of Torment - Runestone", 88870221, 1), + LocationData("Dungeon of Torment - Key 2 (Dif. 3)", 88870222, 3), + LocationData("Dungeon of Torment - Key 3", 88870223, 1), + LocationData("Dungeon of Torment - Key 4 (Dif. 2)", 88870224, 2), + LocationData("Dungeon of Torment - Key 5 (Dif. 3)", 88870225, 3), + LocationData("Dungeon of Torment - Key 6 (Dif. 4)", 88870226, 4), + LocationData("Dungeon of Torment - Very Small Pile of Gold 1", 88870227, 1), + LocationData("Dungeon of Torment - Small Pile of Gold 1 (Dif. 2)", 88870228, 2), + LocationData("Dungeon of Torment - Full Barrel of Gold 1 (Dif. 4)", 88870229, 4), + LocationData("Dungeon of Torment - Half Barrel of Gold 1 (Dif. 3)", 88870230, 3), + LocationData("Dungeon of Torment - Small Pile of Gold 2", 88870231, 1), + LocationData("Dungeon of Torment - Large Pile fo Gold 1 (Dif. 3)", 88870232, 3), + LocationData("Dungeon of Torment - Half Barrel of Gold 2 (Dif. 4)", 88870233, 4), + LocationData("Dungeon of Torment - Lightning Potion 2", 88870234, 1), + LocationData("Dungeon of Torment - Acid Amulet", 88870235, 1), + LocationData("Dungeon of Torment - Poison Fruit", 88870236, 1), + LocationData("Dungeon of Torment - Key 7 (Dif. 2)", 88870237, 2), + LocationData("Dungeon of Torment - Key 8", 88870238, 1), + LocationData("Dungeon of Torment - Obelisk", 88870617, 1), + LocationData("Dungeon of Torment Barrel - Nothing 1", 8887310, 1), + LocationData("Dungeon of Torment Chest - Meat 1 (Dif. 4)", 8887311, 4), + LocationData("Dungeon of Torment Chest - Meat 2 (Dif. 3)", 8887312, 3), + LocationData("Dungeon of Torment Chest - Acid Amulet 1", 8887313, 1), + LocationData("Dungeon of Torment Barrel - Death 1", 8887314, 1), + LocationData("Dungeon of Torment Chest - Invulnerability 1 (Dif. 4)", 8887315, 4), + LocationData("Dungeon of Torment Barrel - Key 1 (Dif. 3)", 8887316, 3), + LocationData("Dungeon of Torment Barrel - Key 2 (Dif. 2)", 8887317, 2), + LocationData("Dungeon of Torment Barrel - Nothing 2 (Dif. 4)", 8887318, 4), + LocationData("Dungeon of Torment Barrel - Nothing 3 (Dif. 3)", 8887319, 3), + LocationData("Dungeon of Torment Barrel - Nothing 4 (Dif. 4)", 88873110, 4), + LocationData("Dungeon of Torment Barrel - Fruit 1 (Dif. 2)", 88873111, 2), + LocationData("Dungeon of Torment Barrel - Super Shot 1 (Dif. 3)", 88873112, 3), + LocationData("Dungeon of Torment Barrel - Potion 1 (Dif. 2)", 88873113, 2), + LocationData("Dungeon of Torment Barrel - Gold 1 (Dif. 2)", 88873114, 2), + LocationData("Dungeon of Torment Barrel - Gold 2", 88873115, 1), + LocationData("Dungeon of Torment Barrel - Key 3 (Dif. 4)", 88873116, 4), + LocationData("Dungeon of Torment Chest - Meat 3 (Dif. 2)", 88873117, 2), + LocationData("Dungeon of Torment Chest - Meat 4", 88873118, 1), + LocationData("Dungeon of Torment Chest - Potion 1 (Dif. 3)", 88873119, 3), + LocationData("Dungeon of Torment Chest - Fruit 1 (Dif. 3)", 88873120, 3), + LocationData("Dungeon of Torment Dark Chest - Random 1 (Dif. 2)", 88873121, 2), + LocationData("Dungeon of Torment Chest - Potion 2", 88873122, 1), + LocationData("Dungeon of Torment Chest - Gold 1 (Dif. 2)", 88873123, 2), + LocationData("Dungeon of Torment Barrel - Fruit 2 (Dif. 4)", 88873124, 4), + LocationData("Dungeon of Torment Barrel - Reflective Shot 1 (Dif. 2)", 88873125, 2), + LocationData("Dungeon of Torment Barrel - Scroll 1", 88873126, 1), + LocationData("Dungeon of Torment Barrel - Nothing 5 (Dif. 3)", 88873127, 3), + LocationData("Dungeon of Torment Barrel - Nothing 6 (Dif. 2)", 88873128, 2), + LocationData("Dungeon of Torment Barrel - Nothing 7 (Dif. 3)", 88873129, 3), + LocationData("Dungeon of Torment Barrel - Nothing 8 (Dif. 3)", 88873130, 3), + LocationData("Dungeon of Torment Barrel - Scroll 2", 88873131, 1), +] + +tower_armory: typing.List[LocationData] = [ + LocationData("Tower Armory - Key 1 (Dif. 4)", 88870239, 4), + LocationData("Tower Armory - Key 2", 88870240, 1), + LocationData("Tower Armory - Key 3", 88870241, 1), + LocationData("Tower Armory - Key 4", 88870242, 1), + LocationData("Tower Armory - Key 5", 88870243, 1), + LocationData("Tower Armory - Key 6", 88870244, 1), + LocationData("Tower Armory - Key 7 (Dif. 4)", 88870245, 4), + LocationData("Tower Armory - Slab of Meat 1", 88870246, 1), + LocationData("Tower Armory - Lightning Potion 1 (Dif. 3)", 88870247, 3), + LocationData("Tower Armory - Key 8 (Dif. 2)", 88870248, 2), + LocationData("Tower Armory - Key 9", 88870249, 1), + LocationData("Tower Armory - Key 10 (Dif. 2)", 88870250, 2), + LocationData("Tower Armory - Key 11", 88870251, 1), + LocationData("Tower Armory - Key 12 (Dif. 3)", 88870252, 3), + LocationData("Tower Armory - Key 13", 88870253, 1), + LocationData("Tower Armory - Key 14 (Dif. 2)", 88870254, 2), + LocationData("Tower Armory - Key 15 (Dif. 3)", 88870255, 3), + LocationData("Tower Armory - Key 16 (Dif. 3)", 88870256, 3), + LocationData("Tower Armory - Key 17", 88870257, 1), + LocationData("Tower Armory - Key 18 (Dif. 2)", 88870258, 2), + LocationData("Tower Armory - Key 19 (Dif. 3)", 88870259, 3), + LocationData("Tower Armory - Fruit 1", 88870260, 1), + LocationData("Tower Armory - Potion Pile (Dif. 2)", 88870261, 2), + LocationData("Tower Armory - Lightning Potion 2", 88870262, 1), + LocationData("Tower Armory - Poison Fruit 1 (Dif. 3)", 88870263, 3), + LocationData("Tower Armory - Key 20 (Dif. 2)", 88870264, 2), + LocationData("Tower Armory - Slab of Meat 2 (Dif. 4)", 88870265, 4), + LocationData("Tower Armory - Lightning Potion 3 (Dif. 4)", 88870266, 4), + LocationData("Tower Armory - Key 21 (Dif. 2)", 88870267, 2), + LocationData("Tower Armory - Fruit 2 (Dif. 2)", 88870268, 2), + LocationData("Tower Armory - Fruit 3", 88870269, 1), + LocationData("Tower Armory - Reflective Shot 1 (Dif. 2)", 88870270, 2), + LocationData("Tower Armory - Death 1 (Dif. 3)", 88870271, 3), + LocationData("Tower Armory - Very Small Pile of Gold 1 (Dif. 4)", 88870272, 4), + LocationData("Tower Armory - Very Small Pile of Gold 2", 88870273, 1), + LocationData("Tower Armory - Half Barrel of Gold 1 (Dif. 4)", 88870274, 4), + LocationData("Tower Armory - Small Pile of Gold 1 (Dif. 2)", 88870275, 2), + LocationData("Tower Armory - Large Pile of Gold 1 (Dif. 3)", 88870276, 3), + LocationData("Tower Armory - Very Small Pile of Gold 3", 88870277, 1), + LocationData("Tower Armory - Key 22 (Dif. 3)", 88870278, 3), + LocationData("Tower Armory - Acid Breath 1", 88870279, 1), + LocationData("Tower Armory - Runestone", 88870280, 1), + LocationData("Tower Armory - Key 23", 88870281, 1), + LocationData("Tower Armory - Key 24 (Dif. 2)", 88870282, 2), + LocationData("Tower Armory - Key 25", 88870283, 1), + LocationData("Tower Armory Barrel - Key 1 (Dif. 4)", 8887320, 4), + LocationData("Tower Armory Barrel - Scroll 1", 8887321, 1), + LocationData("Tower Armory Barrel - Light Amulet 1 (Dif. 4)", 8887322, 4), + LocationData("Tower Armory Barrel - Poison Fruit 1 (Dif. 4)", 8887323, 4), + LocationData("Tower Armory Chest - Fire Breath 1 (Dif. 4)", 8887324, 4), + LocationData("Tower Armory Chest - Gold 1", 8887325, 1), + LocationData("Tower Armory Chest - Invisibility 1 (Dif. 2)", 8887326, 2), + LocationData("Tower Armory Chest - Potion 1", 8887327, 1), + LocationData("Tower Armory Chest - Lightning Amulet 1", 8887328, 1), + LocationData("Tower Armory Chest - Poison Fruit 1", 8887329, 1), + LocationData("Tower Armory Chest - Meat 1 (Dif. 3)", 88873210, 3), + LocationData("Tower Armory Chest - Death 1 (Dif. 2)", 88873211, 2), + LocationData("Tower Armory Chest - Super Shot 1 (Dif. 3)", 88873212, 3), + LocationData("Tower Armory Chest - Growth 1 (Dif. 3)", 88873213, 3), + LocationData("Tower Armory Chest - Potion 2 (Dif. 2)", 88873214, 2), + LocationData("Tower Armory Chest - Death 2", 88873215, 1), + LocationData("Tower Armory Chest - Gold 2 (Dif. 3)", 88873216, 3), + LocationData("Tower Armory Chest - Gold 3 (Dif. 2)", 88873217, 2), + LocationData("Tower Armory Chest - Fruit 1 (Dif. 4)", 88873218, 4), + LocationData("Tower Armory Chest - Potion 3", 88873219, 1), + LocationData("Tower Armory Chest - Fruit 2 (Dif. 2)", 88873220, 2), + LocationData("Tower Armory Dark Chest - Random 1 (Dif. 3)", 88873221, 3), + LocationData("Tower Armory Chest - Meat 2", 88873222, 1), + LocationData("Tower Armory Barrel - Scroll 2", 88873223, 1), + LocationData("Tower Armory Barrel - 3-Way Shot 1", 88873224, 1), + LocationData("Tower Armory Chest - Meat 3 (Dif. 2)", 88873225, 2), + LocationData("Tower Armory Chest - Invulnerability 1 (Dif. 4)", 88873226, 4), + LocationData("Tower Armory Barrel - Gold 1", 88873227, 1), + LocationData("Tower Armory Barrel - Key 2", 88873228, 1), + LocationData("Tower Armory Barrel - Nothing 1 (Dif. 3)", 88873229, 3), + LocationData("Tower Armory Barrel - Key 3 (Dif. 2)", 88873230, 2), + LocationData("Tower Armory Dark Chest - Random 2 (Dif. 2)", 88873231, 2), + LocationData("Tower Armory Barrel - Scroll 3", 88873232, 1), + LocationData("Tower Armory Barrel - Speed Boots 1", 88873233, 1), + LocationData("Tower Armory Barrel - Levitate 1 (Dif. 3)", 88873234, 3), + LocationData("Tower Armory Barrel - Nothing 2 (Dif. 4)", 88873235, 4), + LocationData("Tower Armory Chest - Reflective Shield 1 (Dif. 2)", 88873236, 2), +] + +castle_treasury: typing.List[LocationData] = [ + LocationData("Castle Treasury - Lightning Potion 1", 88870284, 1), + LocationData("Castle Treasury - Key 1", 88870285, 1), + LocationData("Castle Treasury - Key 2", 88870286, 1), + LocationData("Castle Treasury - Key 3", 88870287, 1), + LocationData("Castle Treasury - Key 4", 88870288, 1), + LocationData("Castle Treasury - Key 5", 88870289, 1), + LocationData("Castle Treasury - Key 6 (Dif. 3)", 88870290, 3), + LocationData("Castle Treasury - Key 7", 88870291, 1), + LocationData("Castle Treasury - Key 8", 88870292, 1), + LocationData("Castle Treasury - Half Barrel of Gold 1 (Dif. 4)", 88870293, 4), + LocationData("Castle Treasury - Very Small Pile of Gold 1 (Dif. 4)", 88870294, 4), + LocationData("Castle Treasury - Small Pile of Gold 1 (Dif. 4)", 88870295, 4), + LocationData("Castle Treasury - Full Barrel of Gold 1 (Dif. 4)", 88870296, 4), + LocationData("Castle Treasury - Large Pile of Gold 1 (Dif. 2)", 88870297, 2), + LocationData("Castle Treasury - Small Pile of Gold 2 (Dif. 3)", 88870298, 3), + LocationData("Castle Treasury - Scroll 1", 88870299, 1), + LocationData("Castle Treasury - Meat Slab 1 (Dif. 2)", 88870300, 2), + LocationData("Castle Treasury - Poison Fruit 1", 88870301, 1), + LocationData("Castle Treasury - Lightning Potion 2 (Dif. 4)", 88870302, 4), + LocationData("Castle Treasury - Key 9", 88870303, 1), + LocationData("Castle Treasury - Lightning Potion 3 (Dif. 2)", 88870304, 2), + LocationData("Castle Treasury - Bananas", 88870305, 1), + LocationData("Castle Treasury - Drumstick 1", 88870306, 1), + LocationData("Castle Treasury - Lightning Potion 4 (Dif. 3)", 88870307, 3), + LocationData("Castle Treasury - Lightning Potion 5", 88870308, 1), + LocationData("Castle Treasury - Pineapple (Dif. 2)", 88870309, 2), + LocationData("Castle Treasury - Key 10 (Dif. 2)", 88870310, 2), + LocationData("Castle Treasury - Poison Fruit 2 (Dif. 3)", 88870311, 3), + LocationData("Castle Treasury - Death 1 (Dif. 2)", 88870312, 2), + LocationData("Castle Treasury - Invulnerability 1 (Dif. 2)", 88870313, 2), + LocationData("Castle Treasury - Large Pile of Gold 2 (Dif. 4)", 88870314, 4), + LocationData("Castle Treasury - Very Small Pile of Gold 2", 88870315, 1), + LocationData("Castle Treasury - Half Barrel of Gold 2 (Dif. 3)", 88870316, 3), + LocationData("Castle Treasury - Very Small Pile of Gold 3 (Dif. 3)", 88870317, 3), + LocationData("Castle Treasury - Small Pile of Gold 3 (Dif. 2)", 88870318, 2), + LocationData("Castle Treasury - Large Pile of Gold 3 (Dif. 3)", 88870319, 3), + LocationData("Castle Treasury - Small Pile of Gold 4", 88870320, 1), + LocationData("Castle Treasury - Speed Boots 1 (Dif. 3)", 88870321, 3), + LocationData("Castle Treasury - Fruit 1 (Dif. 4)", 88870322, 4), + LocationData("Castle Treasury - Key 11 (Dif. 2)", 88870323, 2), + LocationData("Castle Treasury - Key 12 (Dif. 3)", 88870324, 3), + LocationData("Castle Treasury - Key 13 (Dif. 4)", 88870325, 4), + LocationData("Castle Treasury - Death 2 (Dif. 3)", 88870326, 3), + LocationData("Castle Treasury - Ice Axe of Untar", 88870327, 1), + LocationData("Castle Treasury - Key 14 (Dif. 4)", 88870328, 4), + LocationData("Castle Treasury Barrel - Scroll 1", 8887330, 1), + LocationData("Castle Treasury Barrel - Scroll 2", 8887331, 1), + LocationData("Castle Treasury Barrel - Key 1 (Dif. 2)", 8887332, 2), + LocationData("Castle Treasury Barrel - Key 2", 8887333, 1), + LocationData("Castle Treasury Barrel - Key 3", 8887334, 1), + LocationData("Castle Treasury Barrel - Key 4", 8887335, 1), + LocationData("Castle Treasury Barrel - Key 5 (Dif. 2)", 8887336, 2), + LocationData("Castle Treasury Chest - Gold 1", 8887337, 1), + LocationData("Castle Treasury Barrel - Key 6 (Dif. 3)", 8887338, 3), + LocationData("Castle Treasury Barrel - Poison Fruit 1 (Dif. 3)", 8887339, 3), + LocationData("Castle Treasury Barrel - Key 7 (Dif. 4)", 88873310, 4), + LocationData("Castle Treasury Chest - Gold 2", 88873311, 1), + LocationData("Castle Treasury Chest - Gold 3", 88873312, 1), + LocationData("Castle Treasury Chest - Potion 1 (Dif. 4)", 88873313, 4), + LocationData("Castle Treasury Chest - Potion 2 (Dif. 3)", 88873314, 3), + LocationData("Castle Treasury Chest - Lightning Amulet 1", 88873315, 1), + LocationData("Castle Treasury Chest - 3-Way Shot 1 (Dif. 2)", 88873316, 2), + LocationData("Castle Treasury Chest - Potion 3", 88873317, 1), + LocationData("Castle Treasury Dark Chest - Random 1 (Dif. 3)", 88873318, 3), + LocationData("Castle Treasury Chest - Gold 4 (Dif. 2)", 88873319, 2), + LocationData("Castle Treasury Chest - Potion 4 (Dif. 2)", 88873320, 2), + LocationData("Castle Treasury Chest - Scroll 1", 88873321, 1), + LocationData("Castle Treasury Chest - Meat 1 (Dif. 3)", 88873322, 3), + LocationData("Castle Treasury Chest - Fruit 1 (Dif. 2)", 88873323, 2), + LocationData("Castle Treasury Chest - Meat 2 (Dif. 4)", 88873324, 4), + LocationData("Castle Treasury Chest - Thunder Hammer 1 (Dif. 4)", 88873325, 4), + LocationData("Castle Treasury Barrel - Timestop 1 (Dif. 4)", 88873326, 4), + LocationData("Castle Treasury Barrel - Levitate 1 (Dif. 3)", 88873327, 3), + LocationData("Castle Treasury Barrel - Lightning Shield 1", 88873328, 1), + LocationData("Castle Treasury Barrel - Death 1", 88873329, 1), + LocationData("Castle Treasury Chest - Fire Shield 1", 88873330, 1), + LocationData("Castle Treasury Barrel - Levitate 2 (Dif. 2)", 88873331, 2), + LocationData("Castle Treasury Chest - Random 1", 88873332, 1), +] + +chimeras_keep: typing.List[LocationData] = [ + LocationData("Chimera's Keep - Speed Boots", 88870329, 1), + LocationData("Chimera's Keep - Growth (Dif. 4)", 88870330, 4), + LocationData("Chimera's Keep - 3-Way Shot (Dif. 2)", 88870331, 2), + LocationData("Chimera's Keep - (Dif. 3)", 88870332, 3), + LocationData("Chimera's Keep - Ham (Dif. 2)", 88870333, 2), + LocationData("Chimera's Keep - Drumstick", 88870334, 1), + LocationData("Chimera's Keep - Chimera Mirror Shard", 88870608, 1), +] + +poisoned_fields: typing.List[LocationData] = [ + LocationData("Poisoned Fields - Fruit Pile 1", 88870335, 1), + LocationData("Poisoned Fields - Fruit Pile 2 (Dif. 4)", 88870336, 4), + LocationData("Poisoned Fields - Fruit Pile 3 (Dif. 3)", 88870337, 3), + LocationData("Poisoned Fields - Fruit Pile 4", 88870338, 1), + LocationData("Poisoned Fields - Death 1", 88870339, 1), + LocationData("Poisoned Fields - Key 1", 88870340, 1), + LocationData("Poisoned Fields - Key 2 (Dif. 2)", 88870341, 2), + LocationData("Poisoned Fields - Key 3 (Dif. 3)", 88870342, 3), + LocationData("Poisoned Fields - Key 4", 88870343, 1), + LocationData("Poisoned Fields - Key 5 (Dif. 3)", 88870344, 3), + LocationData("Poisoned Fields - Key 6", 88870345, 1), + LocationData("Poisoned Fields - Key 7 (Dif. 2)", 88870346, 2), + LocationData("Poisoned Fields - Key 8", 88870347, 1), + LocationData("Poisoned Fields - Key 9 (Dif. 3)", 88870348, 3), + LocationData("Poisoned Fields - Fruit Pile 5 (Dif. 3)", 88870349, 3), + LocationData("Poisoned Fields - Fruit Pile 6 (Dif. 2)", 88870350, 2), + LocationData("Poisoned Fields - Fruit Pile 7 (Dif. 2)", 88870351, 2), + LocationData("Poisoned Fields - Fruit Pile 8 (Dif. 4)", 88870352, 4), + LocationData("Poisoned Fields - Acid Potion 1 (Dif. 4)", 88870353, 4), + LocationData("Poisoned Fields - Key 10 (Dif. 2)", 88870354, 2), + LocationData("Poisoned Fields - Shrink", 88870355, 1), + LocationData("Poisoned Fields - Reflective Shield (Dif. 2)", 88870356, 2), + LocationData("Poisoned Fields - Ham 1 (Dif. 3)", 88870357, 3), + LocationData("Poisoned Fields - Key 11", 88870358, 1), + LocationData("Poisoned Fields - Key 12", 88870359, 1), + LocationData("Poisoned Fields - Key 13 (Dif. 3)", 88870360, 3), + LocationData("Poisoned Fields - Key 14", 88870361, 1), + LocationData("Poisoned Fields - Key 15 (Dif. 2)", 88870362, 2), + LocationData("Poisoned Fields - Key 16", 88870363, 1), + LocationData("Poisoned Fields - Key 17 (Dif. 4)", 88870364, 4), + LocationData("Poisoned Fields - Key 18", 88870365, 1), + LocationData("Poisoned Fields - Small Pile of Gold 1 (Dif. 3)", 88870366, 3), + LocationData("Poisoned Fields - Bananas", 88870367, 1), + LocationData("Poisoned Fields - Acid Potion 2 (Dif. 3)", 88870368, 3), + LocationData("Poisoned Fields - Drumstick 1 (Dif. 3)", 88870369, 3), + LocationData("Poisoned Fields - Potion Pile 1 (Dif. 2)", 88870370, 2), + LocationData("Poisoned Fields - Small Pile of Gold 2 (Dif. 2)", 88870371, 2), + LocationData("Poisoned Fields - Very Small Pile of Gold 1 (Dif. 4)", 88870372, 4), + LocationData("Poisoned Fields - Half Barrel of Gold 1 (Dif. 3)", 88870373, 3), + LocationData("Poisoned Fields - Key 19", 88870374, 1), + LocationData("Poisoned Fields - Death 2 (Dif. 3)", 88870375, 3), + LocationData("Poisoned Fields - Fruit 1", 88870376, 1), + LocationData("Poisoned Fields - Speed Boots 1 (Dif. 4)", 88870377, 4), + LocationData("Poisoned Fields - Death 3", 88870378, 1), + LocationData("Poisoned Fields - Small Pile of Gold 3", 88870379, 1), + LocationData("Poisoned Fields - Full Barrel of Gold 1 (Dif. 4)", 88870380, 4), + LocationData("Poisoned Fields - Phoenix Familiar 1 (Dif. 2)", 88870381, 2), + LocationData("Poisoned Fields - Obelisk", 88870614, 1), + LocationData("Poisoned Fields Barrel - Scroll 1", 8887110, 1), + LocationData("Poisoned Fields Barrel - Potion 1", 8887111, 1), + LocationData("Poisoned Fields Barrel - Nothing 1", 8887112, 1), + LocationData("Poisoned Fields Barrel - Nothing 2", 8887113, 1), + LocationData("Poisoned Fields Barrel - Key 1", 8887114, 1), + LocationData("Poisoned Fields Barrel - Nothing 3", 8887115, 1), + LocationData("Poisoned Fields Barrel - Nothing 4", 8887116, 1), + LocationData("Poisoned Fields Barrel - Nothing 5", 8887117, 1), + LocationData("Poisoned Fields Barrel - Scroll 2", 8887118, 1), + LocationData("Poisoned Fields Barrel - Key 2", 8887119, 1), + LocationData("Poisoned Fields Barrel - Fruit 1 (Dif. 4)", 88871110, 4), + LocationData("Poisoned Fields Barrel - Scroll 3", 88871111, 1), + LocationData("Poisoned Fields Barrel - Meat 1 (Dif. 2)", 88871112, 2), + LocationData("Poisoned Fields Barrel - Fruit 2 (Dif. 4)", 88871113, 4), + LocationData("Poisoned Fields Barrel - Fruit 3 (Dif. 3)", 88871114, 3), + LocationData("Poisoned Fields Chest - Gold 1 (Dif. 3)", 88871115, 3), + LocationData("Poisoned Fields Barrel - Key 3", 88871116, 1), + LocationData("Poisoned Fields Barrel - Nothing 6", 88871117, 1), + LocationData("Poisoned Fields Barrel - Nothing 7", 88871118, 1), + LocationData("Poisoned Fields Barrel - Nothing 8", 88871119, 1), + LocationData("Poisoned Fields Barrel - Key 4 (Dif. 4)", 88871120, 4), + LocationData("Poisoned Fields Barrel - Key 5 (Dif. 3)", 88871121, 3), + LocationData("Poisoned Fields Barrel - Key 6", 88871122, 1), + LocationData("Poisoned Fields Barrel - Key 7 (Dif. 2)", 88871123, 2), + LocationData("Poisoned Fields Barrel - Key 8", 88871124, 1), + LocationData("Poisoned Fields Barrel - Nothing 9", 88871125, 1), + LocationData("Poisoned Fields Barrel - Nothing 10", 88871126, 1), + LocationData("Poisoned Fields Barrel - Nothing 11", 88871127, 1), + LocationData("Poisoned Fields Barrel - Key 9", 88871128, 1), + LocationData("Poisoned Fields Barrel - Thunder Hammer 1 (Dif. 2)", 88871129, 2), + LocationData("Poisoned Fields Barrel - Death 1", 88871130, 1), + LocationData("Poisoned Fields Barrel - Key 10", 88871131, 1), + LocationData("Poisoned Fields Chest - Anti-Death Halo 1", 88871132, 1), + LocationData("Poisoned Fields Chest - Potion 1 (Dif. 2)", 88871133, 2), + LocationData("Poisoned Fields Chest - Gold 2 (Dif. 2)", 88871134, 2), + LocationData("Poisoned Fields Chest - Gold 3 (Dif. 2)", 88871135, 2), + LocationData("Poisoned Fields Chest - Gold 4 (Dif. 2)", 88871136, 2), + LocationData("Poisoned Fields Chest - Gold 5", 88871137, 1), + LocationData("Poisoned Fields Chest - Gold 6 (Dif. 4)", 88871138, 4), + LocationData("Poisoned Fields Barrel - Nothing 12", 88871139, 1), + LocationData("Poisoned Fields Barrel - Gold 1 (Dif. 4)", 88871140, 4), + LocationData("Poisoned Fields Chest - Potion 2", 88871141, 1), + LocationData("Poisoned Fields Chest - Gold 7", 88871142, 1), + LocationData("Poisoned Fields Chest - Gold 8 (Dif. 3)", 88871143, 3), + LocationData("Poisoned Fields Chest - Gold 9 (Dif. 3)", 88871144, 3), + LocationData("Poisoned Fields Barrel - Fruit 4 (Dif. 2)", 88871145, 2), + LocationData("Poisoned Fields Barrel - Key 11", 88871146, 1), + LocationData("Poisoned Fields Barrel - Nothing 13", 88871147, 1), + LocationData("Poisoned Fields Barrel - Nothing 14", 88871148, 1), + LocationData("Poisoned Fields Chest - 3-Way Shot 1", 88871149, 1), + LocationData("Poisoned Fields Barrel - Meat 2 (Dif. 2)", 88871150, 2), + LocationData("Poisoned Fields Barrel - Nothing 15", 88871151, 1), + LocationData("Poisoned Fields Barrel - Nothing 16", 88871152, 1), + LocationData("Poisoned Fields Barrel - Key 12", 88871153, 1), + LocationData("Poisoned Fields Barrel - Death 2 (Dif. 4)", 88871154, 4), + LocationData("Poisoned Fields Barrel - Nothing 17", 88871155, 1), + LocationData("Poisoned Fields Barrel - Lightning Shield 1 (Dif. 3)", 88871156, 3), + LocationData("Poisoned Fields Chest - Fire Amulet 1 (Dif. 3)", 88871157, 3), + LocationData("Poisoned Fields Chest - Gold 10", 88871158, 1), + LocationData("Poisoned Fields Barrel - Potion 2 (Dif. 4)", 88871159, 4), + LocationData("Poisoned Fields Chest - Gold 11 (Dif. 3)", 88871160, 3), + LocationData("Poisoned Fields Chest - Meat 1", 88871161, 1), + LocationData("Poisoned Fields Barrel - Speed Boots 1", 88871162, 1), + LocationData("Poisoned Fields Barrel - Nothing 18", 88871163, 1), + LocationData("Poisoned Fields Barrel - Nothing 19", 88871164, 1), + LocationData("Poisoned Fields Barrel - Nothing 20", 88871165, 1), + LocationData("Poisoned Fields Barrel - Nothing 21", 88871166, 1), + LocationData("Poisoned Fields Barrel - Meat 3 (Dif. 4)", 88871167, 4), + LocationData("Poisoned Fields Chest - Rapid Fire 1 (Dif. 2)", 88871168, 2), + LocationData("Poisoned Fields Chest - Meat 2", 88871169, 1), + LocationData("Poisoned Fields Chest - Potion 3", 88871170, 1), + LocationData("Poisoned Fields Barrel - Key 13", 88871171, 1), + LocationData("Poisoned Fields Barrel - Gold 2", 88871172, 1), + LocationData("Poisoned Fields Barrel - 3-Way Shot 1 (Dif. 4)", 88871173, 4), + LocationData("Poisoned Fields Barrel - Scroll 4", 88871174, 1), + LocationData("Poisoned Fields Barrel - Nothing 22", 88871175, 1), + LocationData("Poisoned Fields Chest - Gold 12 (Dif. 3)", 88871176, 3), + LocationData("Poisoned Fields Barrel - Fruit 5 (Dif. 3)", 88871177, 3), + LocationData("Poisoned Fields Barrel - Key 14 (Dif. 3)", 88871178, 3), + LocationData("Poisoned Fields Barrel - Key 15", 88871179, 1), + LocationData("Poisoned Fields Barrel - Potion 3 (Dif. 2)", 88871180, 2), + LocationData("Poisoned Fields Barrel - Death 3 (Dif. 2)", 88871181, 2), + LocationData("Poisoned Fields Chest - Gold 13 (Dif. 4)", 88871182, 4), + LocationData("Poisoned Fields Chest - Gold 14", 88871183, 1), +] + +haunted_cemetery: typing.List[LocationData] = [ + LocationData("Haunted Cemetery - Acid Potion 1", 88870382, 1), + LocationData("Haunted Cemetery - Runestone", 88870383, 1), + LocationData("Haunted Cemetery - Small Pile of Gold", 88870384, 1), + LocationData("Haunted Cemetery - Key 1 (Dif. 3)", 88870385, 3), + LocationData("Haunted Cemetery - Acid Potion 2", 88870386, 1), + LocationData("Haunted Cemetery - 3-Way Shot", 88870387, 1), + LocationData("Haunted Cemetery - Key 2 (Dif. 2)", 88870388, 2), + LocationData("Haunted Cemetery - Key 3 (Dif. 3)", 88870389, 3), + LocationData("Haunted Cemetery - Key 4", 88870390, 1), + LocationData("Haunted Cemetery - Key 5 (Dif. 2)", 88870391, 2), + LocationData("Haunted Cemetery - Key 6", 88870392, 1), + LocationData("Haunted Cemetery - Key 7 (Dif. 2)", 88870393, 2), + LocationData("Haunted Cemetery - Speed Boots (Dif. 3)", 88870394, 3), + LocationData("Haunted Cemetery - Key 8 (Dif. 4)", 88870395, 4), + LocationData("Haunted Cemetery - Death 1", 88870396, 1), + LocationData("Haunted Cemetery - Phoenix Familiar", 88870397, 1), + LocationData("Haunted Cemetery - Timestop", 88870398, 1), + LocationData("Haunted Cemetery - Fire Amulet (Dif. 2)", 88870399, 2), + LocationData("Haunted Cemetery - Death 2 (Dif. 2)", 88870400, 2), + LocationData("Haunted Cemetery - Obelisk", 88870615, 1), + LocationData("Haunted Cemetery Barrel - Invulnerability 1", 8887120, 1), + LocationData("Haunted Cemetery Barrel - Fire Shield 1 (Dif. 4)", 8887121, 4), + LocationData("Haunted Cemetery Barrel - Fruit 1 (Dif. 4)", 8887122, 4), + LocationData("Haunted Cemetery Barrel - Meat 1", 8887123, 1), + LocationData("Haunted Cemetery Barrel - Fruit 2 (Dif. 2)", 8887124, 2), + LocationData("Haunted Cemetery Barrel - Potion 1 (Dif. 4)", 8887125, 4), + LocationData("Haunted Cemetery Barrel - Key 1 (Dif. 2)", 8887126, 2), + LocationData("Haunted Cemetery Chest - Potion 1 (Dif. 2)", 8887127, 2), + LocationData("Haunted Cemetery Barrel - Poison Fruit 1 (Dif. 4)", 8887128, 4), + LocationData("Haunted Cemetery Barrel - Key 2 (Dif. 2)", 8887129, 2), + LocationData("Haunted Cemetery Dark Chest - Random 1 (Dif. 2)", 88871210, 2), + LocationData("Haunted Cemetery Barrel - Key 3 (Dif. 3)", 88871211, 3), + LocationData("Haunted Cemetery Chest - Scroll 1", 88871212, 1), + LocationData("Haunted Cemetery Barrel - Poison Fruit 2 (Dif. 2)", 88871213, 2), + LocationData("Haunted Cemetery Barrel - Poison Fruit 3 (Dif. 3)", 88871214, 3), + LocationData("Haunted Cemetery Chest - Rapid Fire 1 (Dif. 2)", 88871215, 2), + LocationData("Haunted Cemetery Chest - Gold 1 (Dif. 4)", 88871216, 4), + LocationData("Haunted Cemetery Chest - Shrink 1 (Dif. 3)", 88871217, 3), + LocationData("Haunted Cemetery Chest - Potion 2 (Dif. 3)", 88871218, 3), + LocationData("Haunted Cemetery Barrel - Scroll 1", 88871219, 1), + LocationData("Haunted Cemetery Barrel - Scroll 2", 88871220, 1), + LocationData("Haunted Cemetery Barrel - Meat 2 (Dif. 3)", 88871221, 3), + LocationData("Haunted Cemetery Chest - Meat 1 (Dif. 2)", 88871222, 2), + LocationData("Haunted Cemetery Barrel - Key 4 (Dif. 2)", 88871223, 2), + LocationData("Haunted Cemetery Barrel - Potion 2", 88871224, 1), + LocationData("Haunted Cemetery Barrel - Death 1 (Dif. 4)", 88871225, 4), + LocationData("Haunted Cemetery Barrel - Gold 1", 88871226, 1), + LocationData("Haunted Cemetery Chest - Levitate 1", 88871227, 1), + LocationData("Haunted Cemetery Barrel - Fruit 3 (Dif. 3)", 88871228, 3), + LocationData("Haunted Cemetery Chest - Gold 2 (Dif. 2)", 88871229, 2), + LocationData("Haunted Cemetery Chest - Fruit 1", 88871230, 1), + LocationData("Haunted Cemetery Barrel - Nothing 1", 88871231, 1), + LocationData("Haunted Cemetery Barrel - Key 5", 88871232, 1), + LocationData("Haunted Cemetery Barrel - Key 6", 88871233, 1), + LocationData("Haunted Cemetery Barrel - Gold 2 (Dif. 3)", 88871234, 3), + LocationData("Haunted Cemetery Barrel - Key 7 (Dif. 4)", 88871235, 4), + LocationData("Haunted Cemetery Barrel - Key 8", 88871236, 1), + LocationData("Haunted Cemetery Barrel - Death 2", 88871237, 1), + LocationData("Haunted Cemetery Barrel - Gold 3 (Dif. 2)", 88871238, 2), + LocationData("Haunted Cemetery Chest - Death 1 (Dif. 3)", 88871239, 3), + LocationData("Haunted Cemetery Chest - 3-Way Shot 1 (Dif. 2)", 88871240, 2), + LocationData("Haunted Cemetery Chest - Gold 3", 88871241, 1), + LocationData("Haunted Cemetery Chest - Meat 2 (Dif. 4)", 88871242, 4), +] + +venomous_spire: typing.List[LocationData] = [ + LocationData("Venemous Spire - Acid Potion 1", 88870401, 1), + LocationData("Venemous Spire - Invisibility", 88870402, 1), + LocationData("Venemous Spire - Flame of Tarkana", 88870403, 1), + LocationData("Venemous Spire - Runestone", 88870404, 1), + LocationData("Venemous Spire - Super Shot", 88870405, 1), + LocationData("Venemous Spire - Death 1", 88870406, 1), + LocationData("Venemous Spire - Key 1 (Dif. 4)", 88870407, 4), + LocationData("Venemous Spire - Key 2 (Dif. 3)", 88870408, 3), + LocationData("Venemous Spire - Lightning Amulet (Dif. 2)", 88870409, 2), + LocationData("Venemous Spire - Pineapple (Dif. 3)", 88870410, 3), + LocationData("Venemous Spire - Half Barrel of Gold 1 (Dif. 3)", 88870411, 3), + LocationData("Venemous Spire - Death 2 (Dif. 2)", 88870412, 2), + LocationData("Venemous Spire - Key 3 (Dif. 3)", 88870413, 3), + LocationData("Venemous Spire - Key 4 (Dif. 2)", 88870414, 2), + LocationData("Venemous Spire - Phoenix Familiar (Dif. 4)", 88870415, 4), + LocationData("Venemous Spire - Key 5", 88870416, 1), + LocationData("Venemous Spire Barrel - Speed Boots 1", 8887130, 1), + LocationData("Venemous Spire Barrel - Potion 1", 8887131, 1), + LocationData("Venemous Spire Barrel - Poison Fruit 1 (Dif. 2)", 8887132, 2), + LocationData("Venemous Spire Barrel - Poison Fruit 2", 8887133, 1), + LocationData("Venemous Spire Barrel - Key 1 (Dif. 3)", 8887134, 3), + LocationData("Venemous Spire Chest - Meat 1 (Dif. 3)", 8887135, 3), + LocationData("Venemous Spire Barrel - Key 2", 8887136, 1), + LocationData("Venemous Spire Chest - Scroll 1", 8887137, 1), + LocationData("Venemous Spire Barrel - Scroll 1", 8887138, 1), + LocationData("Venemous Spire Barrel - Scroll 2", 8887139, 1), + LocationData("Venemous Spire Barrel - Scroll 3", 88871310, 1), + LocationData("Venemous Spire Barrel - Scroll 4", 88871311, 1), + LocationData("Venemous Spire Barrel - Key 3", 88871312, 1), + LocationData("Venemous Spire Barrel - Key 4 (Dif. 3)", 88871313, 3), + LocationData("Venemous Spire Barrel - Key 5", 88871314, 1), + LocationData("Venemous Spire Barrel - Scroll 5", 88871315, 1), + LocationData("Venemous Spire Barrel - Key 6 (Dif. 4)", 88871316, 4), + LocationData("Venemous Spire Barrel - Key 7", 88871317, 1), + LocationData("Venemous Spire Barrel - Key 8 (Dif. 4)", 88871318, 4), + LocationData("Venemous Spire Barrel - Key 9", 88871319, 1), + LocationData("Venemous Spire Barrel - Key 10", 88871320, 1), + LocationData("Venemous Spire Barrel - Acid Breath 1 (Dif. 2)", 88871321, 2), + LocationData("Venemous Spire Barrel - Key 11 (Dif. 2)", 88871322, 2), + LocationData("Venemous Spire Chest - Speed Boots 1", 88871323, 1), + LocationData("Venemous Spire Barrel - Key 12", 88871324, 1), + LocationData("Venemous Spire Barrel - Gold 1 (Dif. 2)", 88871325, 2), + LocationData("Venemous Spire Barrel - Gold 2", 88871326, 1), + LocationData("Venemous Spire Barrel - Meat 1 (Dif. 2)", 88871327, 2), + LocationData("Venemous Spire Chest - Potion 1", 88871328, 1), + LocationData("Venemous Spire Dark Chest - Random 1", 88871329, 1), + LocationData("Venemous Spire Chest - Fruit 1", 88871330, 1), + LocationData("Venemous Spire Chest - Meat 2", 88871331, 1), + LocationData("Venemous Spire Chest - Death 1", 88871332, 1), + LocationData("Venemous Spire Barrel - Key 13", 88871333, 1), + LocationData("Venemous Spire Chest - Fruit 2 (Dif. 4)", 88871334, 4), + LocationData("Venemous Spire Barrel - Gold 3 (Dif. 3)", 88871335, 3), + LocationData("Venemous Spire Barrel - Key 14", 88871336, 1), + LocationData("Venemous Spire Barrel - Death 1 (Dif. 3)", 88871337, 3), + LocationData("Venemous Spire Chest - Potion 2 (Dif. 3)", 88871338, 3), + LocationData("Venemous Spire Chest - Death 2", 88871339, 1), + LocationData("Venemous Spire Chest - Fruit 3 (Dif. 2)", 88871340, 2), + LocationData("Venemous Spire Chest - Gold 1 (Dif. 3)", 88871341, 3), + LocationData("Venemous Spire Chest - Speed Boots 2 (Dif. 3)", 88871342, 3), + LocationData("Venemous Spire Chest - Gold 2 (Dif. 4)", 88871343, 4), + LocationData("Venemous Spire Chest - Gold 3 (Dif. 4)", 88871344, 4), + LocationData("Venemous Spire Barrel - Gold 4 (Dif. 2)", 88871345, 2), + LocationData("Venemous Spire Barrel - Reflective Shield 1", 88871346, 1), + LocationData("Venemous Spire Barrel - Death 2 (Dif. 2)", 88871347, 2), + LocationData("Venemous Spire Barrel - Nothing 1", 88871348, 1), + LocationData("Venemous Spire Barrel - Nothing 2", 88871349, 1), + LocationData("Venemous Spire Barrel - Nothing 3", 88871350, 1), + LocationData("Venemous Spire Barrel - Key 15", 88871351, 1), + LocationData("Venemous Spire Barrel - Gold 5", 88871352, 1), + LocationData("Venemous Spire Chest - Fire Shield 1", 88871353, 1), + LocationData("Venemous Spire Barrel - Key 16", 88871354, 1), + LocationData("Venemous Spire Barrel - Nothing 4", 88871355, 1), + LocationData("Venemous Spire Barrel - Meat 2 (Dif. 4)", 88871356, 4), + LocationData("Venemous Spire Barrel - Key 17 (Dif. 4)", 88871357, 4), + LocationData("Venemous Spire Barrel - Death 3 (Dif. 4)", 88871358, 4), + LocationData("Venemous Spire Chest - Potion 3 (Dif. 4)", 88871359, 4), + LocationData("Venemous Spire Chest - Gold 4", 88871360, 1), + LocationData("Venemous Spire Barrel - Key 18", 88871361, 1), + LocationData("Venemous Spire Chest - Gold 5 (Dif. 2)", 88871362, 2), + LocationData("Venemous Spire Barrel - Key 19", 88871363, 1), +] + +toxic_air_ship: typing.List[LocationData] = [ + LocationData("Toxic Air Ship - Acid Breath", 88870417, 1), + LocationData("Toxic Air Ship - Scroll", 88870418, 1), + LocationData("Toxic Air Ship - Key 1", 88870419, 1), + LocationData("Toxic Air Ship - Runestone", 88870420, 1), + LocationData("Toxic Air Ship - Phoenix Familiar", 88870421, 1), + LocationData("Toxic Air Ship - Key 2", 88870422, 1), + LocationData("Toxic Air Ship - Key 3 (Dif. 2)", 88870423, 2), + LocationData("Toxic Air Ship - Key 4 (Dif. 4)", 88870424, 4), + LocationData("Toxic Air Ship - Key 5 (Dif. 2)", 88870425, 2), + LocationData("Toxic Air Ship - Key 6", 88870426, 1), + LocationData("Toxic Air Ship - Poison Fruit 1 (Dif. 2)", 88870427, 2), + LocationData("Toxic Air Ship - Death (Dif. 2)", 88870428, 2), + LocationData("Toxic Air Ship - Bananas (Dif. 4)", 88870429, 4), + LocationData("Toxic Air Ship - Key 7", 88870430, 1), + LocationData("Toxic Air Ship - Key 8 (Dif. 2)", 88870431, 2), + LocationData("Toxic Air Ship - Key 9 (Dif. 3)", 88870432, 3), + LocationData("Toxic Air Ship - Poison Fruit 2 (Dif. 3)", 88870433, 3), + LocationData("Toxic Air Ship - Key 10", 88870434, 1), + LocationData("Toxic Air Ship - Fire Shield (Dif. 2)", 88870435, 2), + LocationData("Toxic Air Ship Barrel - Key 1 (Dif. 4)", 8887140, 4), + LocationData("Toxic Air Ship Barrel - Key 2 (Dif. 3)", 8887141, 3), + LocationData("Toxic Air Ship Chest - Meat 1 (Dif. 4)", 8887142, 4), + LocationData("Toxic Air Ship Barrel - Potion 1", 8887143, 1), + LocationData("Toxic Air Ship Chest - Meat 2 (Dif. 2)", 8887144, 2), + LocationData("Toxic Air Ship Barrel - Scroll 1", 8887145, 1), + LocationData("Toxic Air Ship Chest - Poison Fruit 1", 8887146, 1), + LocationData("Toxic Air Ship Barrel - Fruit 1", 8887147, 1), + LocationData("Toxic Air Ship Barrel - Key 3 (Dif. 2)", 8887148, 2), + LocationData("Toxic Air Ship Chest - Growth 1 (Dif. 4)", 8887149, 4), + LocationData("Toxic Air Ship Dark Chest - Random 1 (Dif. 2)", 88871410, 2), + LocationData("Toxic Air Ship Chest - Scroll 1", 88871411, 1), + LocationData("Toxic Air Ship Chest - Acid Amulet 1 (Dif. 2)", 88871412, 2), + LocationData("Toxic Air Ship Chest - Gold 1 (Dif. 3)", 88871413, 3), + LocationData("Toxic Air Ship Chest - Meat 3", 88871414, 1), + LocationData("Toxic Air Ship Chest - Potion 1 (Dif. 3)", 88871415, 3), + LocationData("Toxic Air Ship Barrel - Key 4", 88871416, 1), + LocationData("Toxic Air Ship Barrel - Death 1 (Dif. 3)", 88871417, 3), + LocationData("Toxic Air Ship Chest - Gold 2 (Dif. 2)", 88871418, 2), + LocationData("Toxic Air Ship Chest - 3-Way Shot 1", 88871419, 1), + LocationData("Toxic Air Ship Barrel - Death 2 (Dif. 4)", 88871420, 4), + LocationData("Toxic Air Ship Barrel - Key 5", 88871421, 1), + LocationData("Toxic Air Ship Barrel - Gold 1 (Dif. 4)", 88871422, 4), + LocationData("Toxic Air Ship Barrel - Nothing 1", 88871423, 1), + LocationData("Toxic Air Ship Barrel - Potion 2 (Dif. 2)", 88871424, 2), + LocationData("Toxic Air Ship Barrel - Key 6", 88871425, 1), + LocationData("Toxic Air Ship Barrel - Key 7", 88871426, 1), + LocationData("Toxic Air Ship Barrel - Death 3", 88871427, 1), + LocationData("Toxic Air Ship Barrel - Nothing 2", 88871428, 1), + LocationData("Toxic Air Ship Barrel - Key 8", 88871429, 1), + LocationData("Toxic Air Ship Barrel - Gold 2 (Dif. 2)", 88871430, 2), + LocationData("Toxic Air Ship Barrel - Key 9 (Dif. 3)", 88871431, 3), + LocationData("Toxic Air Ship Barrel - Acid Breath 1 (Dif. 3)", 88871432, 3), + LocationData("Toxic Air Ship Chest - Gold 3 (Dif. 4)", 88871433, 4), + LocationData("Toxic Air Ship Chest - Gold 4 (Dif. 3)", 88871434, 3), + LocationData("Toxic Air Ship Chest - Death 1", 88871435, 1), + LocationData("Toxic Air Ship Barrel - Gold 3", 88871436, 1), + LocationData("Toxic Air Ship Barrel - Key 10 (Dif. 2)", 88871437, 2), + LocationData("Toxic Air Ship Barrel - Key 11 (Dif. 4)", 88871438, 4), + LocationData("Toxic Air Ship Chest - Poison Fruit 2 (Dif. 2)", 88871439, 2), + LocationData("Toxic Air Ship Chest - Gold 5", 88871440, 1), + LocationData("Toxic Air Ship Barrel - Scroll 2", 88871441, 1), + LocationData("Toxic Air Ship Barrel - Meat 1 (Dif. 3)", 88871442, 3), + LocationData("Toxic Air Ship Barrel - Potion 3 (Dif. 4)", 88871443, 4), +] + +plague_fiend: typing.List[LocationData] = [ + LocationData("Vat of the Plague Fiend - Plague Fiend Mirror Shard", 88870609, 1), +] + +arctic_docks: typing.List[LocationData] = [ + LocationData("Arctic Docks - Light Potion 1", 88870436, 1), + LocationData("Arctic Docks - Scroll", 88870437, 1), + LocationData("Arctic Docks - Light Potion 2 (Dif. 2)", 88870438, 2), + LocationData("Arctic Docks - Small Pile of Gold (Dif. 2)", 88870439, 2), + LocationData("Arctic Docks - Fire Shield", 88870440, 1), + LocationData("Arctic Docks - Key 1", 88870441, 1), + LocationData("Arctic Docks - Meat", 88870442, 1), + LocationData("Arctic Docks - Light Potion 3 (Dif. 2)", 88870443, 2), + LocationData("Arctic Docks - Runestone", 88870444, 1), + LocationData("Arctic Docks - Key 2", 88870445, 1), + LocationData("Arctic Docks - Key 3 (Dif. 3)", 88870446, 3), + LocationData("Arctic Docks - Key 4", 88870447, 1), + LocationData("Arctic Docks - Key 5 (Dif. 2)", 88870448, 2), + LocationData("Arctic Docks - Key 6", 88870449, 1), + LocationData("Arctic Docks - Key 7", 88870450, 1), + LocationData("Arctic Docks - Key 8", 88870451, 1), + LocationData("Arctic Docks - Key 9", 88870452, 1), + LocationData("Arctic Docks - Key 10", 88870453, 1), + LocationData("Arctic Docks - Key 11", 88870454, 1), + LocationData("Arctic Docks - Key 12", 88870455, 1), + LocationData("Arctic Docks - Key 13 (Dif. 2)", 88870456, 2), + LocationData("Arctic Docks - Key 14 (Dif. 4)", 88870457, 4), + LocationData("Arctic Docks - Key 15", 88870458, 1), + LocationData("Arctic Docks - Key 16", 88870459, 1), + LocationData("Arctic Docks - Key 17", 88870460, 1), + LocationData("Arctic Docks - Speed Boots", 88870461, 1), + LocationData("Arctic Docks Barrel - Key 1", 8887160, 1), + LocationData("Arctic Docks Barrel - Phoenix Familiar 1", 8887161, 1), + LocationData("Arctic Docks Barrel - Fruit 1 (Dif. 2)", 8887162, 2), + LocationData("Arctic Docks Barrel - Potion 1 (Dif. 3)", 8887163, 3), + LocationData("Arctic Docks Barrel - Meat 1 (Dif. 3)", 8887164, 3), + LocationData("Arctic Docks Barrel - Fruit 2 (Dif. 2)", 8887165, 2), + LocationData("Arctic Docks Barrel - Fruit 3", 8887166, 1), + LocationData("Arctic Docks Barrel - Fruit 4 (Dif. 3)", 8887167, 3), + LocationData("Arctic Docks Barrel - Lightning Breath 1 (Dif. 2)", 8887168, 2), + LocationData("Arctic Docks Barrel - Fruit 5 (Dif. 4)", 8887169, 4), + LocationData("Arctic Docks Barrel - Meat 2 (Dif. 3)", 88871610, 3), + LocationData("Arctic Docks Barrel - Fruit 6 (Dif. 2)", 88871611, 2), + LocationData("Arctic Docks Barrel - Fruit 7", 88871612, 1), + LocationData("Arctic Docks Barrel - Meat 3", 88871613, 1), + LocationData("Arctic Docks Barrel - Key 2", 88871614, 1), + LocationData("Arctic Docks Barrel - Key 3", 88871615, 1), + LocationData("Arctic Docks Barrel - Key 4 (Dif. 2)", 88871616, 2), + LocationData("Arctic Docks Barrel - Key 5", 88871617, 1), + LocationData("Arctic Docks Barrel - Scroll 1", 88871618, 1), + LocationData("Arctic Docks Barrel - Key 6", 88871619, 1), + LocationData("Arctic Docks Barrel - Nothing 1", 88871620, 1), + LocationData("Arctic Docks Barrel - Rapid Fire 1 (Dif. 4)", 88871621, 4), + LocationData("Arctic Docks Chest - Meat 1", 88871622, 1), + LocationData("Arctic Docks Chest - Gold 1 (Dif. 2)", 88871623, 2), + LocationData("Arctic Docks Chest - Acid Amulet 1 (Dif. 4)", 88871624, 4), + LocationData("Arctic Docks Barrel - Fruit 8", 88871625, 1), + LocationData("Arctic Docks Barrel - Death 1 (Dif. 2)", 88871626, 2), + LocationData("Arctic Docks Barrel - Key 7 (Dif. 4)", 88871627, 4), + LocationData("Arctic Docks Barrel - Key 8 (Dif. 2)", 88871628, 2), + LocationData("Arctic Docks Barrel - Potion 2 (Dif. 4)", 88871629, 4), + LocationData("Arctic Docks Barrel - Scroll 2", 88871630, 1), + LocationData("Arctic Docks Barrel - Meat 4 (Dif. 4)", 88871631, 4), + LocationData("Arctic Docks Barrel - Nothing 2", 88871632, 1), + LocationData("Arctic Docks Barrel - Gold 1 (Dif. 4)", 88871633, 4), + LocationData("Arctic Docks Chest - Phoenix Familiar 1 (Dif. 3)", 88871634, 3), + LocationData("Arctic Docks Barrel - Key 9 (Dif. 3)", 88871635, 3), + LocationData("Arctic Docks Barrel - Key 10", 88871636, 1), + LocationData("Arctic Docks Barrel - Death 2 (Dif. 3)", 88871637, 3), + LocationData("Arctic Docks Barrel - Nothing 3", 88871638, 1), + LocationData("Arctic Docks Barrel - Nothing 4", 88871639, 1), + LocationData("Arctic Docks Barrel - Nothing 5", 88871640, 1), + LocationData("Arctic Docks Barrel - Nothing 6", 88871641, 1), + LocationData("Arctic Docks Barrel - Key 11 (Dif. 2)", 88871642, 2), + LocationData("Arctic Docks Chest - Reflective Shield 1 (Dif. 2)", 88871643, 2), + LocationData("Arctic Docks Barrel - Fruit 9 (Dif. 4)", 88871644, 4), + LocationData("Arctic Docks Chest - Gold 2", 88871645, 1), + LocationData("Arctic Docks Barrel - Scroll 3", 88871646, 1), + LocationData("Arctic Docks Barrel - Fruit 10 (Dif. 3)", 88871647, 3), + LocationData("Arctic Docks Barrel - Gold 2 (Dif. 3)", 88871648, 3), + LocationData("Arctic Docks Chest - Invisibility 1", 88871649, 1), + LocationData("Arctic Docks Chest - Potion 1", 88871650, 1), + LocationData("Arctic Docks Chest - Light Amulet 1 (Dif. 2)", 88871651, 2), + LocationData("Arctic Docks Chest - Speed Boots 1 (Dif. 3)", 88871652, 3), + LocationData("Arctic Docks Chest - Thunder Hammer 1 (Dif. 2)", 88871653, 2), + LocationData("Arctic Docks Chest - 3-Way Shot 1", 88871654, 1), + LocationData("Arctic Docks Chest - Rapid Fire 1", 88871655, 1), + LocationData("Arctic Docks Barrel - Gold 3", 88871656, 1), + LocationData("Arctic Docks Barrel - Key 12", 88871657, 1), + LocationData("Arctic Docks Chest - Death 1", 88871658, 1), + LocationData("Arctic Docks Barrel - Nothing 7", 88871659, 1), + LocationData("Arctic Docks Barrel - Nothing 8", 88871660, 1), + LocationData("Arctic Docks Barrel - Scroll 4", 88871661, 1), + LocationData("Arctic Docks Barrel - Gold 4 (Dif. 2)", 88871662, 2), + LocationData("Arctic Docks Barrel - Key 13", 88871663, 1), + LocationData("Arctic Docks Chest - Death 2", 88871664, 1), + LocationData("Arctic Docks Chest - Meat 2 (Dif. 2)", 88871665, 2), + LocationData("Arctic Docks Chest - Meat 3 (Dif. 4)", 88871666, 4), +] + +frozen_camp: typing.List[LocationData] = [ + LocationData("Frozen Camp - Light Potion 1", 88870462, 1), + LocationData("Frozen Camp - Full Barrel of Gold 1 (Dif. 4)", 88870463, 4), + LocationData("Frozen Camp - Half Barrel of Gold 1", 88870464, 1), + LocationData("Frozen Camp - Scroll", 88870465, 1), + LocationData("Frozen Camp - Light Potion 2 (Dif. 2)", 88870466, 2), + LocationData("Frozen Camp - Light Potion 3", 88870467, 1), + LocationData("Frozen Camp - Large Pile of Gold 1 (Dif. 2)", 88870468, 2), + LocationData("Frozen Camp - Half Barrel of Gold 2 (Dif. 3)", 88870469, 3), + LocationData("Frozen Camp - Small Pile of Gold 1", 88870470, 1), + LocationData("Frozen Camp - Key 1 (Dif. 4)", 88870471, 4), + LocationData("Frozen Camp - Lightning Breath (Dif. 3)", 88870472, 3), + LocationData("Frozen Camp - Levitate (Dif. 2)", 88870473, 2), + LocationData("Frozen Camp - Key 2", 88870474, 1), + LocationData("Frozen Camp - Key 3 (Dif. 3)", 88870475, 3), + LocationData("Frozen Camp - Speed Boots", 88870476, 1), + LocationData("Frozen Camp - Key 4 (Dif. 3)", 88870477, 3), + LocationData("Frozen Camp - Key 5", 88870478, 1), + LocationData("Frozen Camp - Bananas (Dif. 4)", 88870479, 4), + LocationData("Frozen Camp - Pineapple", 88870480, 1), + LocationData("Frozen Camp - Light Potion 4 (Dif. 4)", 88870481, 4), + LocationData("Frozen Camp - Key 6 (Dif. 2)", 88870482, 2), + LocationData("Frozen Camp - Fruit 1 (Dif. 2)", 88870483, 2), + LocationData("Frozen Camp - Very Small Pile of Gold 1 (Dif. 2)", 88870484, 2), + LocationData("Frozen Camp - Very Small Pile of Gold 2", 88870485, 1), + LocationData("Frozen Camp - Drumstick (Dif. 4)", 88870486, 4), + LocationData("Frozen Camp - Very Small Pile of Gold 3 (Dif. 2)", 88870487, 2), + LocationData("Frozen Camp - Small Pile of Gold 2 (Dif. 2)", 88870488, 2), + LocationData("Frozen Camp - Very Small Pile of Gold 4 (Dif. 2)", 88870489, 2), + LocationData("Frozen Camp - Large Pile of Gold 2 (Dif. 3)", 88870490, 3), + LocationData("Frozen Camp - Growth", 88870491, 1), + LocationData("Frozen Camp - Runestone", 88870492, 1), + LocationData("Frozen Camp - Fruit 2", 88870493, 1), + LocationData("Frozen Camp - Phoenix Familiar", 88870494, 1), + LocationData("Frozen Camp - Death", 88870495, 1), + LocationData("Frozen Camp - Key 7", 88870496, 1), + LocationData("Frozen Camp - Key 8 (Dif. 4)", 88870497, 4), + LocationData("Frozen Camp Barrel - Scroll 1", 8887170, 1), + LocationData("Frozen Camp Barrel - Meat 1", 8887171, 1), + LocationData("Frozen Camp Barrel - Key 1", 8887172, 1), + LocationData("Frozen Camp Barrel - Key 2 (Dif. 2)", 8887173, 2), + LocationData("Frozen Camp Barrel - Key 3", 8887174, 1), + LocationData("Frozen Camp Barrel - Key 4", 8887175, 1), + LocationData("Frozen Camp Barrel - Key 5 (Dif. 2)", 8887176, 2), + LocationData("Frozen Camp Chest - Gold 1", 8887177, 1), + LocationData("Frozen Camp Barrel - Key 6", 8887178, 1), + LocationData("Frozen Camp Chest - Meat 1 (Dif. 2)", 8887179, 2), + LocationData("Frozen Camp Barrel - Scroll 2", 88871710, 1), + LocationData("Frozen Camp Barrel - Nothing 1", 88871711, 1), + LocationData("Frozen Camp Barrel - Nothing 2", 88871712, 1), + LocationData("Frozen Camp Barrel - Key 7 (Dif. 2)", 88871713, 2), + LocationData("Frozen Camp Barrel - Key 8", 88871714, 1), + LocationData("Frozen Camp Dark Chest - Random 1 (Dif. 2)", 88871715, 2), + LocationData("Frozen Camp Chest - Death 1", 88871716, 1), + LocationData("Frozen Camp Barrel - Key 9 (Dif. 2)", 88871717, 2), + LocationData("Frozen Camp Barrel - Key 10", 88871718, 1), + LocationData("Frozen Camp Barrel - Rapid Fire 1 (Dif. 2)", 88871719, 2), + LocationData("Frozen Camp Chest - Gold 2", 88871720, 1), + LocationData("Frozen Camp Barrel - Key 11 (Dif. 2)", 88871721, 2), + LocationData("Frozen Camp Barrel - Key 12", 88871722, 1), + LocationData("Frozen Camp Barrel - Key 13", 88871723, 1), + LocationData("Frozen Camp Barrel - Key 14", 88871724, 1), + LocationData("Frozen Camp Barrel - Key 15 (Dif. 3)", 88871725, 3), + LocationData("Frozen Camp Barrel - Key 16 (Dif. 2)", 88871726, 2), + LocationData("Frozen Camp Chest - Gold 3 (Dif. 3)", 88871727, 3), + LocationData("Frozen Camp Chest - Gold 4 (Dif. 2)", 88871728, 2), + LocationData("Frozen Camp Chest - Gold 5 (Dif. 4)", 88871729, 4), + LocationData("Frozen Camp Chest - Gold 6", 88871730, 1), + LocationData("Frozen Camp Chest - Gold 7", 88871731, 1), + LocationData("Frozen Camp Barrel - Key 17", 88871732, 1), + LocationData("Frozen Camp Chest - Super Shot 1", 88871733, 1), + LocationData("Frozen Camp Chest - Invisibility 1 (Dif. 2)", 88871734, 2), + LocationData("Frozen Camp Barrel - Nothing 3", 88871735, 1), + LocationData("Frozen Camp Chest - Gold 8", 88871736, 1), + LocationData("Frozen Camp Chest - Meat 2 (Dif. 3)", 88871737, 3), + LocationData("Frozen Camp Barrel - Gold 1 (Dif. 3)", 88871738, 3), + LocationData("Frozen Camp Chest - Scroll 1", 88871739, 1), + LocationData("Frozen Camp Barrel - 3-Way Shot 1 (Dif. 3)", 88871740, 3), + LocationData("Frozen Camp Barrel - Fruit 1 (Dif. 3)", 88871741, 3), + LocationData("Frozen Camp Barrel - Shrink 1 (Dif. 2)", 88871742, 2), + LocationData("Frozen Camp Barrel - Key 18", 88871743, 1), + LocationData("Frozen Camp Barrel - Speed Boots 1 (Dif. 4)", 88871744, 4), + LocationData("Frozen Camp Chest - 5-Way Shot 1 (Dif. 4)", 88871745, 4), + LocationData("Frozen Camp Barrel - Fruit 2 (Dif. 4)", 88871746, 4), + LocationData("Frozen Camp Barrel - Key 19 (Dif. 4)", 88871747, 4), + LocationData("Frozen Camp Barrel - Nothing 4", 88871748, 1), + LocationData("Frozen Camp Barrel - Nothing 5", 88871749, 1), + LocationData("Frozen Camp Barrel - Growth 1 (Dif. 4)", 88871750, 4), + LocationData("Frozen Camp Barrel - Potion 1", 88871751, 1), + LocationData("Frozen Camp Chest - Fruit 1 (Dif. 2)", 88871752, 2), + LocationData("Frozen Camp Barrel - Scroll 3", 88871753, 1), + LocationData("Frozen Camp Barrel - Key 20", 88871754, 1), + LocationData("Frozen Camp Barrel - Potion 2 (Dif. 3)", 88871755, 3), + LocationData("Frozen Camp Barrel - Gold 2 (Dif. 4)", 88871756, 4), + LocationData("Frozen Camp Barrel - Key 21 (Dif. 3)", 88871757, 3), + LocationData("Frozen Camp Barrel - Reflective Shield 1", 88871758, 1), + LocationData("Frozen Camp Chest - Thunder Hammer 1 (Dif. 2)", 88871759, 2), + LocationData("Frozen Camp Chest - Lightning Amulet 1", 88871760, 1), + LocationData("Frozen Camp Chest - Meat 3 (Dif. 2)", 88871761, 2), + LocationData("Frozen Camp Barrel - Potion 3 (Dif. 2)", 88871762, 2), + LocationData("Frozen Camp Barrel - Scroll 4", 88871763, 1), + LocationData("Frozen Camp Barrel - Key 22 (Dif. 2)", 88871764, 2), + LocationData("Frozen Camp Chest - Gold 9 (Dif. 4)", 88871765, 4), + LocationData("Frozen Camp Barrel - Nothing 6", 88871766, 1), + LocationData("Frozen Camp Barrel - Fruit 3 (Dif. 3)", 88871767, 3), + LocationData("Frozen Camp Chest - Acid Amulet 1", 88871768, 1), + LocationData("Frozen Camp Barrel - Gold 3 (Dif. 3)", 88871769, 3), + LocationData("Frozen Camp Barrel - Key 23", 88871770, 1), + LocationData("Frozen Camp Chest - Gold 10 (Dif. 2)", 88871771, 2), + LocationData("Frozen Camp Chest - Meat 4", 88871772, 1), + LocationData("Frozen Camp Chest - Potion 1 (Dif. 3)", 88871773, 3), + LocationData("Frozen Camp Chest - Gold 11", 88871774, 1), + LocationData("Frozen Camp Chest - Speed Boots 1 (Dif. 3)", 88871775, 3), + LocationData("Frozen Camp Chest - Invulnerability 1", 88871776, 1), + LocationData("Frozen Camp Barrel - Death 1 (Dif. 2)", 88871777, 2), +] + +crystal_mine: typing.List[LocationData] = [ + LocationData("Crystal Mine - Rapid Fire", 88870498, 1), + LocationData("Crystal Mine - Speed Boots", 88870499, 1), + LocationData("Crystal Mine - Key 1 (Dif. 2)", 88870500, 2), + LocationData("Crystal Mine - Key 2 (Dif. 4)", 88870501, 4), + LocationData("Crystal Mine - Key 3 (Dif. 3)", 88870502, 3), + LocationData("Crystal Mine - Lightning Breath", 88870503, 1), + LocationData("Crystal Mine - Key 4 (Dif. 2)", 88870504, 2), + LocationData("Crystal Mine - Key 5", 88870505, 1), + LocationData("Crystal Mine - Lightning Shield", 88870506, 1), + LocationData("Crystal Mine - Bananas", 88870507, 1), + LocationData("Crystal Mine - Key 6", 88870508, 1), + LocationData("Crystal Mine - Key 7", 88870509, 1), + LocationData("Crystal Mine - Runestone", 88870510, 1), + LocationData("Crystal Mine - Shrink (Dif. 3)", 88870511, 3), + LocationData("Crystal Mine Barrel - Reflective Shot 1 (Dif. 3)", 8887180, 3), + LocationData("Crystal Mine Barrel - Speed Boots 1 (Dif. 4)", 8887181, 4), + LocationData("Crystal Mine Barrel - Meat 1 (Dif. 2)", 8887182, 2), + LocationData("Crystal Mine Barrel - Meat 2", 8887183, 1), + LocationData("Crystal Mine Barrel - Meat 3 (Dif. 3)", 8887184, 3), + LocationData("Crystal Mine Barrel - Meat 4 (Dif. 2)", 8887185, 2), + LocationData("Crystal Mine Barrel - Meat 5", 8887186, 1), + LocationData("Crystal Mine Barrel - Scroll 1", 8887187, 1), + LocationData("Crystal Mine Chest - Growth 1 (Dif. 2)", 8887188, 2), + LocationData("Crystal Mine Barrel - Key 1", 8887189, 1), + LocationData("Crystal Mine Barrel - Key 2", 88871810, 1), + LocationData("Crystal Mine Barrel - Key 3 (Dif. 2)", 88871811, 2), + LocationData("Crystal Mine Barrel - Key 4 (Dif. 2)", 88871812, 2), + LocationData("Crystal Mine Barrel - Key 5 (Dif. 4)", 88871813, 4), + LocationData("Crystal Mine Chest - Fruit 1", 88871814, 1), + LocationData("Crystal Mine Chest - Gold 1 (Dif. 3)", 88871815, 3), + LocationData("Crystal Mine Chest - Random 1 (Dif. 2)", 88871816, 2), + LocationData("Crystal Mine Barrel - Potion 1", 88871817, 1), + LocationData("Crystal Mine Chest - Meat 1 (Dif. 4)", 88871818, 4), + LocationData("Crystal Mine Barrel - Gold 1 (Dif. 2)", 88871819, 2), + LocationData("Crystal Mine Chest - Gold 2", 88871820, 1), + LocationData("Crystal Mine Barrel - Key 6", 88871821, 1), + LocationData("Crystal Mine Chest - Gold 3 (Dif. 2)", 88871822, 2), + LocationData("Crystal Mine Chest - Potion 1 (Dif. 4)", 88871823, 4), + LocationData("Crystal Mine Chest - Gold 4", 88871824, 1), + LocationData("Crystal Mine Chest - Gold 5", 88871825, 1), + LocationData("Crystal Mine Barrel - Fruit 1 (Dif. 2)", 88871826, 2), + LocationData("Crystal Mine Barrel - Key 7", 88871827, 1), + LocationData("Crystal Mine Chest - Meat 2", 88871828, 1), + LocationData("Crystal Mine Barrel - Gold 2 (Dif. 2)", 88871829, 2), + LocationData("Crystal Mine Barrel - Key 8", 88871830, 1), + LocationData("Crystal Mine Barrel - Death 1 (Dif. 3)", 88871831, 3), + LocationData("Crystal Mine Barrel - Key 9 (Dif. 2)", 88871832, 2), + LocationData("Crystal Mine Chest - Scroll 1", 88871833, 1), + LocationData("Crystal Mine Barrel - Nothing 1", 88871834, 1), + LocationData("Crystal Mine Barrel - Nothing 2", 88871835, 1), + LocationData("Crystal Mine Barrel - Potion 2 (Dif. 2)", 88871836, 2), + LocationData("Crystal Mine Barrel - Key 10", 88871837, 1), + LocationData("Crystal Mine Barrel - Gold 3 (Dif. 4)", 88871838, 4), + LocationData("Crystal Mine Chest - Rapid Fire 1 (Dif. 2)", 88871839, 2), + LocationData("Crystal Mine Barrel - Key 11 (Dif. 3)", 88871840, 3), + LocationData("Crystal Mine Barrel - Key 12", 88871841, 1), + LocationData("Crystal Mine Barrel - Nothing 3", 88871842, 1), + LocationData("Crystal Mine Barrel - Nothing 4", 88871843, 1), + LocationData("Crystal Mine Chest - Gold 6", 88871844, 1), + LocationData("Crystal Mine Chest - Fruit 2 (Dif. 3)", 88871845, 3), + LocationData("Crystal Mine Barrel - Key 13 (Dif. 2)", 88871846, 2), + LocationData("Crystal Mine Barrel - Fruit 2", 88871847, 1), + LocationData("Crystal Mine Barrel - Gold 4 (Dif. 3)", 88871848, 3), + LocationData("Crystal Mine Chest - Scroll 2", 88871849, 1), + LocationData("Crystal Mine Chest - Gold 7 (Dif. 2)", 88871850, 2), + LocationData("Crystal Mine Barrel - Potion 3 (Dif. 3)", 88871851, 3), + LocationData("Crystal Mine Chest - Fruit 3 (Dif. 2)", 88871852, 2), + LocationData("Crystal Mine Barrel - Gold 5 (Dif. 4)", 88871853, 4), + LocationData("Crystal Mine Barrel - Key 14", 88871854, 1), + LocationData("Crystal Mine Barrel - Potion 4", 88871855, 1), + LocationData("Crystal Mine Barrel - Key 15", 88871856, 1), + LocationData("Crystal Mine Chest - 3-Way Shot 1", 88871857, 1), + LocationData("Crystal Mine Barrel - Scroll 2", 88871858, 1), + LocationData("Crystal Mine Chest - Gold 8", 88871859, 1), + LocationData("Crystal Mine Barrel - Fire Amulet 1", 88871860, 1), + LocationData("Crystal Mine Barrel - Key 16", 88871861, 1), + LocationData("Crystal Mine Chest - Scroll 3", 88871862, 1), + LocationData("Crystal Mine Barrel - Gold 6 (Dif. 3)", 88871863, 3), + LocationData("Crystal Mine Barrel - Death 2 (Dif. 2)", 88871864, 2), + LocationData("Crystal Mine Barrel - Key 17", 88871865, 1), + LocationData("Crystal Mine Barrel - Potion 5", 88871866, 1), + LocationData("Crystal Mine Barrel - Fruit 3 (Dif. 4)", 88871867, 4), + LocationData("Crystal Mine Barrel - Gold 7 (Dif. 2)", 88871868, 2), +] + +erupting_fissure: typing.List[LocationData] = [ + LocationData("Erupting Fissure - Light Potion", 88870512, 1), + LocationData("Erupting Fissure - Growth", 88870513, 1), + LocationData("Erupting Fissure - Reflective Shield", 88870514, 1), + LocationData("Erupting Fissure - Key 1", 88870515, 1), + LocationData("Erupting Fissure - Key 2", 88870516, 1), + LocationData("Erupting Fissure - 3-Way Shot", 88870517, 1), + LocationData("Erupting Fissure - Key 3", 88870518, 1), + LocationData("Erupting Fissure - Key 4", 88870519, 1), + LocationData("Erupting Fissure - Key 5", 88870520, 1), + LocationData("Erupting Fissure - Death", 88870521, 1), + LocationData("Erupting Fissure - Key 6", 88870522, 1), + LocationData("Erupting Fissure - Key 7 (Dif. 2)", 88870523, 2), + LocationData("Erupting Fissure - Key 8", 88870524, 1), + LocationData("Erupting Fissure - Marker's Javelin", 88870525, 1), + LocationData("Erupting Fissure - Rapid Fire", 88870526, 1), + LocationData("Erupting Fissure - Speed Boots (Dif. 4)", 88870527, 4), + LocationData("Erupting Fissure - Key 9", 88870528, 1), + LocationData("Erupting Fissure - Key 10 (Dif. 2)", 88870529, 2), + LocationData("Erupting Fissure Barrel - Meat 1", 8887190, 1), + LocationData("Erupting Fissure Barrel - Potion 1 (Dif. 4)", 8887191, 4), + LocationData("Erupting Fissure Barrel - Thunder Hammer 1 (Dif. 2)", 8887192, 2), + LocationData("Erupting Fissure Barrel - Fruit 1 (Dif. 4)", 8887193, 4), + LocationData("Erupting Fissure Barrel - Scroll 1", 8887194, 1), + LocationData("Erupting Fissure Barrel - Meat 2 (Dif. 4)", 8887195, 4), + LocationData("Erupting Fissure Barrel - Fruit 2", 8887196, 1), + LocationData("Erupting Fissure Barrel - Meat 3 (Dif. 2)", 8887197, 2), + LocationData("Erupting Fissure Barrel - Scroll 2", 8887198, 1), + LocationData("Erupting Fissure Barrel - Scroll 3", 8887199, 1), + LocationData("Erupting Fissure Barrel - Fruit 3", 88871910, 1), + LocationData("Erupting Fissure Barrel - Meat 4 (Dif. 3)", 88871911, 3), + LocationData("Erupting Fissure Barrel - Fruit 4 (Dif. 2)", 88871912, 2), + LocationData("Erupting Fissure Barrel - Meat 5", 88871913, 1), + LocationData("Erupting Fissure Barrel - Scroll 4", 88871914, 1), + LocationData("Erupting Fissure Barrel - Key 1", 88871915, 1), + LocationData("Erupting Fissure Barrel - Key 2 (Dif. 2)", 88871916, 2), + LocationData("Erupting Fissure Barrel - Key 3 (Dif. 2)", 88871917, 2), + LocationData("Erupting Fissure Dark Chest - Random 1 (Dif. 2)", 88871918, 2), + LocationData("Erupting Fissure Barrel - Nothing 1", 88871919, 1), + LocationData("Erupting Fissure Barrel - Nothing 2", 88871920, 1), + LocationData("Erupting Fissure Barrel - Nothing 3", 88871921, 1), + LocationData("Erupting Fissure Barrel - Nothing 4", 88871922, 1), + LocationData("Erupting Fissure Barrel - Fruit 5 (Dif. 3)", 88871923, 3), + LocationData("Erupting Fissure Barrel - Acid Amulet 1 (Dif. 4)", 88871924, 4), + LocationData("Erupting Fissure Barrel - Nothing 5", 88871925, 1), + LocationData("Erupting Fissure Barrel - Nothing 6", 88871926, 1), + LocationData("Erupting Fissure Chest - Gold 1 (Dif. 2)", 88871927, 2), + LocationData("Erupting Fissure Barrel - Key 4 (Dif. 3)", 88871928, 3), + LocationData("Erupting Fissure Barrel - Key 5", 88871929, 1), + LocationData("Erupting Fissure Chest - Fire Breath 1 (Dif. 3)", 88871930, 3), + LocationData("Erupting Fissure Barrel - Key 6", 88871931, 1), + LocationData("Erupting Fissure Chest - Potion 1 (Dif. 3)", 88871932, 3), + LocationData("Erupting Fissure Barrel - Key 7 (Dif. 3)", 88871933, 3), + LocationData("Erupting Fissure Barrel - Key 8 (Dif. 4)", 88871934, 4), + LocationData("Erupting Fissure Barrel - Key 9 (Dif. 3)", 88871935, 3), + LocationData("Erupting Fissure Barrel - Fruit 6 (Dif. 2)", 88871936, 2), + LocationData("Erupting Fissure Barrel - Death 1", 88871937, 1), + LocationData("Erupting Fissure Chest - Gold 2 (Dif. 3)", 88871938, 3), + LocationData("Erupting Fissure Chest - Meat 1 (Dif. 2)", 88871939, 2), + LocationData("Erupting Fissure Barrel - Key 10 (Dif. 3)", 88871940, 3), + LocationData("Erupting Fissure Barrel - Key 11 (Dif. 2)", 88871941, 2), + LocationData("Erupting Fissure Barrel - Key 12", 88871942, 1), + LocationData("Erupting Fissure Chest - Gold 3", 88871943, 1), + LocationData("Erupting Fissure Barrel - Key 13", 88871944, 1), + LocationData("Erupting Fissure Barrel - Key 14", 88871945, 1), + LocationData("Erupting Fissure Barrel - Key 15", 88871946, 1), + LocationData("Erupting Fissure Barrel - Key 16", 88871947, 1), + LocationData("Erupting Fissure Barrel - Death 2 (Dif. 3)", 88871948, 3), + LocationData("Erupting Fissure Barrel - Key 17 (Dif. 3)", 88871949, 3), + LocationData("Erupting Fissure Barrel - Key 18 (Dif. 2)", 88871950, 2), + LocationData("Erupting Fissure Barrel - Key 19", 88871951, 1), + LocationData("Erupting Fissure Barrel - Key 20 (Dif. 2)", 88871952, 2), + LocationData("Erupting Fissure Chest - Gold 4 (Dif. 2)", 88871953, 2), + LocationData("Erupting Fissure Barrel - Scroll 5", 88871954, 1), + LocationData("Erupting Fissure Chest - Potion 2", 88871955, 1), + LocationData("Erupting Fissure Chest - Light Amulet 1 (Dif. 2)", 88871956, 2), + LocationData("Erupting Fissure Barrel - Gold 1 (Dif. 4)", 88871957, 4), + LocationData("Erupting Fissure Barrel - Key 21 (Dif. 2)", 88871958, 2), + LocationData("Erupting Fissure Barrel - Fruit 7 (Dif. 4)", 88871959, 4), + LocationData("Erupting Fissure Barrel - Fruit 8 (Dif. 3)", 88871960, 3), + LocationData("Erupting Fissure Barrel - Gold 2", 88871961, 1), + LocationData("Erupting Fissure Barrel - Key 22", 88871962, 1), + LocationData("Erupting Fissure Barrel - Gold 3 (Dif. 3)", 88871963, 3), + LocationData("Erupting Fissure Chest - Gold 5", 88871964, 1), + LocationData("Erupting Fissure Chest - Potion 3 (Dif. 2)", 88871965, 2), + LocationData("Erupting Fissure Dark Chest - Random 2 (Dif. 3)", 88871966, 3), + LocationData("Erupting Fissure Chest - Invulnerability 1 (Dif. 2)", 88871967, 2), + LocationData("Erupting Fissure Chest - Invisibility 1", 88871968, 1), + LocationData("Erupting Fissure Chest - Fruit 1", 88871969, 1), + LocationData("Erupting Fissure Chest - Gold 6 (Dif. 4)", 88871970, 4), + LocationData("Erupting Fissure Chest - Gold 7", 88871971, 1), + LocationData("Erupting Fissure Chest - Super Shot 1 (Dif. 3)", 88871972, 3), + LocationData("Erupting Fissure Chest - Timestop 1", 88871973, 1), + LocationData("Erupting Fissure Barrel - Potion 2", 88871974, 1), + LocationData("Erupting Fissure Barrel - Death 3 (Dif. 2)", 88871975, 2), + LocationData("Erupting Fissure Chest - Levitate 1", 88871976, 1), + LocationData("Erupting Fissure Chest - Gold 8 (Dif. 2)", 88871977, 2), +] + +yeti: typing.List[LocationData] = [LocationData("Yeti's Cavern - Yeti Mirror Shard", 88870610, 1)] + +desecrated_temple: typing.List[LocationData] = [ + LocationData("Desecrated Temple - Death 1", 88870530, 1), + LocationData("Desecrated Temple - Death 2 (Dif. 2)", 88870531, 2), + LocationData("Desecrated Temple - Speed Boots", 88870532, 1), + LocationData("Desecrated Temple - Reflective Shot", 88870533, 1), + LocationData("Desecrated Temple - Fire Amulet", 88870534, 1), + LocationData("Desecrated Temple - 3-Way Shot", 88870535, 1), + LocationData("Desecrated Temple Chest - Gold 1 (Dif. 2)", 8887200, 2), + LocationData("Desecrated Temple Chest - Gold 2 (Dif. 2)", 8887201, 2), + LocationData("Desecrated Temple Chest - Gold 3 (Dif. 2)", 8887202, 2), + LocationData("Desecrated Temple Chest - Gold 4 (Dif. 3)", 8887203, 3), + LocationData("Desecrated Temple Chest - Gold 5 (Dif. 4)", 8887204, 4), + LocationData("Desecrated Temple Chest - Gold 6", 8887205, 1), + LocationData("Desecrated Temple Chest - Gold 7 (Dif. 2)", 8887206, 2), + LocationData("Desecrated Temple Chest - Gold 8 (Dif. 2)", 8887207, 2), + LocationData("Desecrated Temple Chest - Gold 9", 8887208, 1), + LocationData("Desecrated Temple Chest - Gold 10", 8887209, 1), + LocationData("Desecrated Temple Chest - Gold 11 (Dif. 3)", 88872010, 3), + LocationData("Desecrated Temple Chest - Gold 12 (Dif. 2)", 88872011, 2), + LocationData("Desecrated Temple Chest - Gold 13 (Dif. 4)", 88872012, 4), + LocationData("Desecrated Temple Chest - Gold 14", 88872013, 1), + LocationData("Desecrated Temple Chest - Gold 15 (Dif. 4)", 88872014, 4), + LocationData("Desecrated Temple Dark Chest - Random 1 (Dif. 3)", 88872015, 3), + LocationData("Desecrated Temple Dark Chest - Random 2 (Dif. 2)", 88872016, 2), + LocationData("Desecrated Temple Chest - Gold 16 (Dif. 4)", 88872017, 4), + LocationData("Desecrated Temple Chest - Gold 17", 88872018, 1), + LocationData("Desecrated Temple Chest - Gold 18 (Dif. 2)", 88872019, 2), + LocationData("Desecrated Temple Chest - Gold 19 (Dif. 3)", 88872020, 3), + LocationData("Desecrated Temple Chest - Gold 20 (Dif. 3)", 88872021, 3), + LocationData("Desecrated Temple Chest - Gold 21", 88872022, 1), + LocationData("Desecrated Temple Chest - Gold 22", 88872023, 1), + LocationData("Desecrated Temple Chest - Gold 23 (Dif. 4)", 88872024, 4), + LocationData("Desecrated Temple Chest - Gold 24", 88872025, 1), + LocationData("Desecrated Temple Chest - Gold 25", 88872026, 1), + LocationData("Desecrated Temple Chest - Gold 26", 88872027, 1), + LocationData("Desecrated Temple Chest - Gold 27", 88872028, 1), + LocationData("Desecrated Temple Chest - Gold 28", 88872029, 1), + LocationData("Desecrated Temple Chest - Gold 29", 88872030, 1), + LocationData("Desecrated Temple Chest - Levitate 1 (Dif. 2)", 88872031, 2), + LocationData("Desecrated Temple Chest - Phoenix Familiar 1 (Dif. 3)", 88872032, 3), +] + +battle_trenches: typing.List[LocationData] = [ + LocationData("Battle Trenches - Fire Potion 1", 88870536, 1), + LocationData("Battle Trenches - Light Potion", 88870537, 1), + LocationData("Battle Trenches - Scroll 1", 88870538, 1), + LocationData("Battle Trenches - Fruit 1 (Dif. 2)", 88870539, 2), + LocationData("Battle Trenches - Key 1 (Dif. 3)", 88870540, 3), + LocationData("Battle Trenches - Invisibility (Dif. 2)", 88870541, 2), + LocationData("Battle Trenches - Speed Boots (Dif. 3)", 88870542, 3), + LocationData("Battle Trenches - Key 2", 88870543, 1), + LocationData("Battle Trenches - Key 3", 88870544, 1), + LocationData("Battle Trenches - Fruit 2", 88870545, 1), + LocationData("Battle Trenches - Levitate", 88870546, 1), + LocationData("Battle Trenches - Scroll 2", 88870547, 1), + LocationData("Battle Trenches - Key 4", 88870548, 1), + LocationData("Battle Trenches - Death (Dif. 2)", 88870549, 2), + LocationData("Battle Trenches - Bananas (Dif. 4)", 88870550, 4), + LocationData("Battle Trenches - Fruit 3", 88870551, 1), + LocationData("Battle Trenches - Key 5 (Dif. 3)", 88870552, 3), + LocationData("Battle Trenches - Key 6 (Dif. 2)", 88870553, 2), + LocationData("Battle Trenches - Fire Potion 2", 88870554, 1), + LocationData("Battle Trenches - Shrink", 88870555, 1), + LocationData("Battle Trenches - Fire Amulet", 88870556, 1), + LocationData("Battle Trenches - 3-Way Shot (Dif. 2)", 88870557, 2), + LocationData("Battle Trenches - Rapid Fire (Dif. 2)", 88870558, 2), + LocationData("Battle Trenches - Reflective Shot", 88870559, 1), + LocationData("Battle Trenches Barrel - 3-Way Shot 1", 8887210, 1), + LocationData("Battle Trenches Barrel - Potion 1 (Dif. 4)", 8887211, 4), + LocationData("Battle Trenches Barrel - Reflective Shot 1 (Dif. 4)", 8887212, 4), + LocationData("Battle Trenches Barrel - Levitate 1", 8887213, 1), + LocationData("Battle Trenches Barrel - Scroll 1", 8887214, 1), + LocationData("Battle Trenches Barrel - Gold 1 (Dif. 4)", 8887215, 4), + LocationData("Battle Trenches Barrel - Scroll 2", 8887216, 1), + LocationData("Battle Trenches Barrel - Scroll 3", 8887217, 1), + LocationData("Battle Trenches Barrel - Key 1 (Dif. 3)", 8887218, 3), + LocationData("Battle Trenches Barrel - Key 2", 8887219, 1), + LocationData("Battle Trenches Barrel - Key 3", 88872110, 1), + LocationData("Battle Trenches Barrel - Key 4", 88872111, 1), + LocationData("Battle Trenches Barrel - Key 5", 88872112, 1), + LocationData("Battle Trenches Barrel - Death 1", 88872113, 1), + LocationData("Battle Trenches Barrel - Gold 2", 88872114, 1), + LocationData("Battle Trenches Barrel - Fruit 1 (Dif. 3)", 88872115, 3), + LocationData("Battle Trenches Barrel - Key 6", 88872116, 1), + LocationData("Battle Trenches Barrel - Gold 3", 88872117, 1), + LocationData("Battle Trenches Barrel - Death 2 (Dif. 4)", 88872118, 4), + LocationData("Battle Trenches Barrel - Key 7 (Dif. 3)", 88872119, 3), + LocationData("Battle Trenches Barrel - Key 8 (Dif. 2)", 88872120, 2), + LocationData("Battle Trenches Barrel - Death 3 (Dif. 2)", 88872121, 2), + LocationData("Battle Trenches Chest - Potion 1 (Dif. 2)", 88872122, 2), + LocationData("Battle Trenches Chest - Gold 1 (Dif. 3)", 88872123, 3), + LocationData("Battle Trenches Chest - Meat 1", 88872124, 1), + LocationData("Battle Trenches Chest - Fire Breath 1 (Dif. 2)", 88872125, 2), + LocationData("Battle Trenches Barrel - Key 9 (Dif. 2)", 88872126, 2), + LocationData("Battle Trenches Barrel - Gold 4 (Dif. 2)", 88872127, 2), + LocationData("Battle Trenches Barrel - Key 10 (Dif. 2)", 88872128, 2), + LocationData("Battle Trenches Barrel - Key 11", 88872129, 1), + LocationData("Battle Trenches Barrel - Key 12 (Dif. 2)", 88872130, 2), + LocationData("Battle Trenches Chest - Potion 2", 88872131, 1), + LocationData("Battle Trenches Barrel - Gold 5 (Dif. 3)", 88872132, 3), + LocationData("Battle Trenches Barrel - Key 13", 88872133, 1), + LocationData("Battle Trenches Chest - Meat 2 (Dif. 2)", 88872134, 2), + LocationData("Battle Trenches Barrel - Thunder Hammer 1", 88872135, 1), + LocationData("Battle Trenches Chest - Gold 2", 88872136, 1), + LocationData("Battle Trenches Barrel - Key 14", 88872137, 1), + LocationData("Battle Trenches Barrel - Key 15 (Dif. 2)", 88872138, 2), + LocationData("Battle Trenches Chest - Meat 3", 88872139, 1), + LocationData("Battle Trenches Barrel - Key 16 (Dif. 3)", 88872140, 3), + LocationData("Battle Trenches Barrel - Death 4 (Dif. 3)", 88872141, 3), + LocationData("Battle Trenches Barrel - Meat 1 (Dif. 4)", 88872142, 4), + LocationData("Battle Trenches Barrel - Key 17", 88872143, 1), + LocationData("Battle Trenches Barrel - Gold 6 (Dif. 2)", 88872144, 2), + LocationData("Battle Trenches Chest - Phoenix Familiar 1", 88872145, 1), + LocationData("Battle Trenches Barrel - Key 18", 88872146, 1), + LocationData("Battle Trenches Chest - Speed Boots 1 (Dif. 4)", 88872147, 4), + LocationData("Battle Trenches Barrel - Gold 7 (Dif. 2)", 88872148, 2), + LocationData("Battle Trenches Barrel - Key 19 (Dif. 4)", 88872149, 4), + LocationData("Battle Trenches Chest - Potion 3 (Dif. 3)", 88872150, 3), + LocationData("Battle Trenches Chest - Gold 3", 88872151, 1), + LocationData("Battle Trenches Barrel - Key 20", 88872152, 1), + LocationData("Battle Trenches Barrel - Key 21", 88872153, 1), + LocationData("Battle Trenches Chest - Fire Amulet 1 (Dif. 3)", 88872154, 3), + LocationData("Battle Trenches Chest - Meat 4 (Dif. 3)", 88872155, 3), +] + +battle_towers: typing.List[LocationData] = [ + LocationData("Battle Towers - Meat (Dif. 3)", 88870560, 3), + LocationData("Battle Towers - Key 1", 88870561, 1), + LocationData("Battle Towers - Large Pile of Gold (Dif. 3)", 88870562, 3), + LocationData("Battle Towers - Small Pile of Gold (Dif. 2)", 88870563, 2), + LocationData("Battle Towers - Reflective Shot (Dif. 2)", 88870564, 2), + LocationData("Battle Towers - Key 2", 88870565, 1), + LocationData("Battle Towers - Soul Savior", 88870566, 1), + LocationData("Battle Towers Barrel - Fire Breath 1 (Dif. 4)", 8887220, 4), + LocationData("Battle Towers Barrel - Scroll 1", 8887221, 1), + LocationData("Battle Towers Chest - Invulnerability 1", 8887222, 1), + LocationData("Battle Towers Barrel - Thunder Hammer 1 (Dif. 2)", 8887223, 2), + LocationData("Battle Towers Barrel - Reflective Shield 1", 8887224, 1), + LocationData("Battle Towers Barrel - Potion 1 (Dif. 4)", 8887225, 4), + LocationData("Battle Towers Barrel - Key 1 (Dif. 2)", 8887226, 2), + LocationData("Battle Towers Barrel - Key 2 (Dif. 4)", 8887227, 4), + LocationData("Battle Towers Barrel - Potion 2 (Dif. 2)", 8887228, 2), + LocationData("Battle Towers Dark Chest - Random 1 (Dif. 3)", 8887229, 3), + LocationData("Battle Towers Barrel - Fruit 1", 88872210, 1), + LocationData("Battle Towers Barrel - Scroll 2", 88872211, 1), + LocationData("Battle Towers Barrel - Gold 1 (Dif. 4)", 88872212, 4), + LocationData("Battle Towers Barrel - Scroll 3", 88872213, 1), + LocationData("Battle Towers Barrel - Key 3", 88872214, 1), + LocationData("Battle Towers Barrel - Key 4", 88872215, 1), + LocationData("Battle Towers Barrel - Scroll 4", 88872216, 1), + LocationData("Battle Towers Barrel - Key 5", 88872217, 1), + LocationData("Battle Towers Barrel - Fruit 2 (Dif. 3)", 88872218, 3), + LocationData("Battle Towers Barrel - Gold 2", 88872219, 1), + LocationData("Battle Towers Barrel - Potion 3 (Dif. 3)", 88872220, 3), + LocationData("Battle Towers Barrel - Key 6 (Dif. 3)", 88872221, 3), + LocationData("Battle Towers Barrel - Meat 1 (Dif. 4)", 88872222, 4), + LocationData("Battle Towers Chest - Speed Boots 1 (Dif. 3)", 88872223, 3), + LocationData("Battle Towers Chest - Phoenix Familiar 1 (Dif. 2)", 88872224, 2), + LocationData("Battle Towers Chest - Fruit 1 (Dif. 4)", 88872225, 4), + LocationData("Battle Towers Chest - Gold 1", 88872226, 1), + LocationData("Battle Towers Barrel - Meat 2", 88872227, 1), + LocationData("Battle Towers Barrel - 3-Way Shot 1 (Dif. 3)", 88872228, 3), + LocationData("Battle Towers Barrel - Death 1 (Dif. 2)", 88872229, 2), + LocationData("Battle Towers Barrel - Gold 3", 88872230, 1), + LocationData("Battle Towers Barrel - Fruit 3 (Dif. 2)", 88872231, 2), + LocationData("Battle Towers Barrel - Key 7 (Dif. 3)", 88872232, 3), + LocationData("Battle Towers Barrel - Gold 4 (Dif. 3)", 88872233, 3), + LocationData("Battle Towers Barrel - Key 8", 88872234, 1), + LocationData("Battle Towers Barrel - Nothing 1", 88872235, 1), + LocationData("Battle Towers Chest - Potion 1", 88872236, 1), + LocationData("Battle Towers Barrel - Key 9 (Dif. 2)", 88872237, 2), + LocationData("Battle Towers Barrel - Rapid Fire 1", 88872238, 1), + LocationData("Battle Towers Chest - Fruit 2", 88872239, 1), + LocationData("Battle Towers Chest - Meat 1 (Dif. 2)", 88872240, 2), + LocationData("Battle Towers Chest - Meat 2", 88872241, 1), + LocationData("Battle Towers Chest - Fire Breath 1", 88872242, 1), +] + +infernal_fortress: typing.List[LocationData] = [ + LocationData("Infernal Fortress - Thunder Hammer", 88870567, 1), + LocationData("Infernal Fortress - Key 1 (Dif. 2)", 88870568, 2), + LocationData("Infernal Fortress - Runestone", 88870569, 1), + LocationData("Infernal Fortress - Drumstick (Dif. 4)", 88870570, 4), + LocationData("Infernal Fortress - Large Pile of Gold (Dif. 3)", 88870571, 3), + LocationData("Infernal Fortress - Very Small Pile of Gold 1", 88870572, 1), + LocationData("Infernal Fortress - Half Barrel of Gold 1 (Dif. 4)", 88870573, 4), + LocationData("Infernal Fortress - Light Potion", 88870574, 1), + LocationData("Infernal Fortress - Key 2 (Dif. 2)", 88870575, 2), + LocationData("Infernal Fortress - Death", 88870576, 1), + LocationData("Infernal Fortress - Fruit", 88870577, 1), + LocationData("Infernal Fortress - Small Pile of Gold (Dif. 2)", 88870578, 2), + LocationData("Infernal Fortress - Key 3", 88870579, 1), + LocationData("Infernal Fortress - Key 4", 88870580, 1), + LocationData("Infernal Fortress - Very Small Pile of Gold 2 (Dif. 2)", 88870581, 2), + LocationData("Infernal Fortress - Potion Pile (Dif. 2)", 88870582, 2), + LocationData("Infernal Fortress - Key 5", 88870583, 1), + LocationData("Infernal Fortress Barrel - Key 1 (Dif. 4)", 8887230, 4), + LocationData("Infernal Fortress Chest - Potion 1 (Dif. 4)", 8887231, 4), + LocationData("Infernal Fortress Barrel - Fruit 1 (Dif. 3)", 8887232, 3), + LocationData("Infernal Fortress Chest - Timestop 1", 8887233, 1), + LocationData("Infernal Fortress Barrel - Poison Fruit 1", 8887234, 1), + LocationData("Infernal Fortress Barrel - Meat 1 (Dif. 2)", 8887235, 2), + LocationData("Infernal Fortress Barrel - Key 2", 8887236, 1), + LocationData("Infernal Fortress Chest - Fire Shield 1 (Dif. 3)", 8887237, 3), + LocationData("Infernal Fortress Barrel - Scroll 1", 8887238, 1), + LocationData("Infernal Fortress Barrel - Levitate 1", 8887239, 1), + LocationData("Infernal Fortress Barrel - Scroll 2", 88872310, 1), + LocationData("Infernal Fortress Barrel - Scroll 3", 88872311, 1), + LocationData("Infernal Fortress Barrel - Meat 2", 88872312, 1), + LocationData("Infernal Fortress Barrel - Invisibility 1 (Dif. 2)", 88872313, 2), + LocationData("Infernal Fortress Dark Chest - Random 1", 88872314, 1), + LocationData("Infernal Fortress Barrel - Key 3", 88872315, 1), + LocationData("Infernal Fortress Barrel - Key 4", 88872316, 1), + LocationData("Infernal Fortress Barrel - Key 5", 88872317, 1), + LocationData("Infernal Fortress Barrel - Key 6 (Dif. 4)", 88872318, 4), + LocationData("Infernal Fortress Barrel - Key 7 (Dif. 3)", 88872319, 3), + LocationData("Infernal Fortress Barrel - Key 8 (Dif. 3)", 88872320, 3), + LocationData("Infernal Fortress Barrel - Key 9", 88872321, 1), + LocationData("Infernal Fortress Barrel - Key 10", 88872322, 1), + LocationData("Infernal Fortress Barrel - Key 11", 88872323, 1), + LocationData("Infernal Fortress Barrel - Key 12", 88872324, 1), + LocationData("Infernal Fortress Barrel - Key 13", 88872325, 1), + LocationData("Infernal Fortress Barrel - Key 14 (Dif. 3)", 88872326, 3), + LocationData("Infernal Fortress Chest - Speed Boots 1 (Dif. 2)", 88872327, 2), + LocationData("Infernal Fortress Barrel - Gold 1 (Dif. 3)", 88872328, 3), + LocationData("Infernal Fortress Barrel - Fruit 2 (Dif. 4)", 88872329, 4), + LocationData("Infernal Fortress Barrel - Key 15 (Dif. 2)", 88872330, 2), + LocationData("Infernal Fortress Chest - Gold 1 (Dif. 2)", 88872331, 2), + LocationData("Infernal Fortress Chest - Gold 2", 88872332, 1), + LocationData("Infernal Fortress Barrel - Fruit 3 (Dif. 2)", 88872333, 2), + LocationData("Infernal Fortress Chest - Light Amulet 1", 88872334, 1), + LocationData("Infernal Fortress Chest - Gold 3 (Dif. 4)", 88872335, 4), + LocationData("Infernal Fortress Chest - Gold 4 (Dif. 3)", 88872336, 3), + LocationData("Infernal Fortress Chest - Gold 5", 88872337, 1), + LocationData("Infernal Fortress Chest - Gold 6 (Dif. 2)", 88872338, 2), + LocationData("Infernal Fortress Chest - Gold 7 (Dif. 4)", 88872339, 4), + LocationData("Infernal Fortress Chest - Gold 8", 88872340, 1), + LocationData("Infernal Fortress Chest - Scroll 1", 88872341, 1), + LocationData("Infernal Fortress Barrel - Key 16 (Dif. 4)", 88872342, 4), + LocationData("Infernal Fortress Barrel - Death 1 (Dif. 3)", 88872343, 3), + LocationData("Infernal Fortress Barrel - Gold 2", 88872344, 1), + LocationData("Infernal Fortress Barrel - Reflective Shot 1 (Dif. 4)", 88872345, 4), + LocationData("Infernal Fortress Barrel - Gold 3 (Dif. 2)", 88872346, 2), + LocationData("Infernal Fortress Barrel - Fruit 4", 88872347, 1), + LocationData("Infernal Fortress Barrel - Potion 1 (Dif. 3)", 88872348, 3), + LocationData("Infernal Fortress Barrel - Key 17", 88872349, 1), + LocationData("Infernal Fortress Barrel - Death 2 (Dif. 2)", 88872350, 2), + LocationData("Infernal Fortress Barrel - Gold 4 (Dif. 3)", 88872351, 3), + LocationData("Infernal Fortress Chest - 3-Way Shot 1", 88872352, 1), + LocationData("Infernal Fortress Chest - Potion 2", 88872353, 1), + LocationData("Infernal Fortress Chest - Meat 1 (Dif. 3)", 88872354, 3), +] + +gates_of_the_underworld: typing.List[LocationData] = [ + LocationData("Gates of the Underworld - Potion Pile", 88870584, 1), + LocationData("Gates of the Underworld - Rapid Fire (Dif. 3)", 88870585, 3), + LocationData("Gates of the Underworld - Reflective Shield (Dif. 2)", 88870586, 2), + LocationData("Gates of the Underworld - Reflective Shot (Dif. 2)", 88870587, 2), + LocationData("Gates of the Underworld - Lightning Shield (Dif. 2)", 88870588, 2), + LocationData("Gates of the Underworld - Fire Shield (Dif. 3)", 88870589, 3), + LocationData("Gates of the Underworld - Acid Amulet (Dif. 4)", 88870590, 4), + LocationData("Gates of the Underworld - Light Amulet (Dif. 3)", 88870591, 3), + LocationData("Gates of the Underworld - Lightning Amulet (Dif. 2)", 88870592, 2), + LocationData("Gates of the Underworld - Fire Amulet", 88870593, 1), + LocationData("Gates of the Underworld - Growth", 88870594, 1), + LocationData("Gates of the Underworld - Fire Breath", 88870595, 1), + LocationData("Gates of the Underworld - Phoenix Familiar (Dif. 2)", 88870596, 2), + LocationData("Gates of the Underworld - Invulnerability (Dif. 4)", 88870597, 4), + LocationData("Gates of the Underworld - Acid Breath (Dif. 3)", 88870598, 3), + LocationData("Gates of the Underworld - 5-Way Shot", 88870599, 1), + LocationData("Gates of the Underworld - Thunder Hammer (Dif. 4)", 88870600, 4), + LocationData("Gates of the Underworld - Lightning Breath (Dif. 2)", 88870601, 2), + LocationData("Gates of the Underworld - Timestop", 88870602, 1), + LocationData("Gates of the Underworld - 3-Way Shot (Dif. 4)", 88870603, 4), + LocationData("Gates of the Underworld - Super Shot", 88870604, 1), + LocationData("Gates of the Underworld - Speed Boots (Dif. 3)", 88870605, 3), + LocationData("Gates of the Underworld - Shrink (Dif. 4)", 88870606, 4), +] + +all_locations: typing.List[LocationData] = ( + valley_of_fire + + dagger_peak + + cliffs_of_desolation + + lost_cave + + volcanic_cavern + + dragons_lair + + castle_courtyard + + dungeon_of_torment + + tower_armory + + castle_treasury + + chimeras_keep + + poisoned_fields + + haunted_cemetery + + venomous_spire + + toxic_air_ship + + arctic_docks + + frozen_camp + + crystal_mine + + erupting_fissure + + desecrated_temple + + battle_trenches + + battle_towers + + infernal_fortress + + gates_of_the_underworld + + plague_fiend + + yeti +) + + +location_table: typing.Dict[str, int] = {location.name: location.id for location in all_locations} + +location_data: typing.Dict[str, LocationData] = {location.name: location for location in all_locations} diff --git a/worlds/gl/Options.py b/worlds/gl/Options.py new file mode 100644 index 00000000000..c41240f553b --- /dev/null +++ b/worlds/gl/Options.py @@ -0,0 +1,231 @@ +from dataclasses import dataclass + +from Options import Choice, PerGameCommonOptions, StartInventoryPool, Toggle, Range + + +class PlayerCount(Range): + """ + Select how many players will be playing this world locally. + If 3 players will be active then change this to 3, etc. + """ + + display_name = "Local Players" + range_start = 1 + range_end = 4 + default = 1 + + +class ChestBarrels(Choice): + """ + Choose how you want Chests and Barrels to be randomized. + None: Neither Chests nor Barrels will be added as locations. + All Chests: Chests will be added as locations, Barrels will not. + All Barrels: Barrels will be added as locations, Chests will not. + All Both: Both Chests and Barrels will be added as locations. + """ + display_name = "Chests and Barrels" + option_none = 0 + option_all_chests = 1 + option_all_barrels = 2 + option_all_both = 3 + default = 3 + + +class Obelisks(Choice): + """ + Choose how you want Obelisks to be randomized. + None: Obelisks will be placed in their own locations. + All Obelisks: Obelisks will be shuffled into the item pool. + """ + + display_name = "Obelisks" + option_none = 0 + option_all_obelisks = 1 + default = 1 + + +class MirrorShards(Choice): + """ + Choose how you want Mirror Shards to be randomized. + None: Mirror Shards will be placed in their own locations. + All Shards: Mirror Shards will be shuffled into the item pool. + """ + + display_name = "Mirror Shards" + option_none = 0 + option_all_shards = 1 + default = 1 + + +class MaxDifficultyToggle(Toggle): + """ + Set all stages to have a maximum difficulty. + All locations with a difficulty higher than what is set will be excluded from the pool of locations. + Default max difficulty is 4. + """ + + display_name = "Change Max Difficulty" + + +class MaxDifficultyRange(Range): + """ + Select the difficulty value you want to be the maximum. + This does nothing if Change Max Difficulty is set to false. + This value has a minimum based on how many local players you have. + If you have 3 local players, this will be adjusted to be at least 3. + """ + + display_name = "Max Difficulty Value" + range_start = 1 + range_end = 4 + default = 4 + + +class InstantMaxDifficulty(Toggle): + """ + All stages will load with their max difficulty on the first run through. + By default, stages increase in difficulty by 1 every 5 player levels. + The starting level for each area increases gradually as you would progress in vanilla. + """ + + display_name = "Instant Max Difficulty" + + +class PermaSpeed(Toggle): + """ + You will be given speed boots with a permanent duration. + """ + + display_name = "Permanent Speed Boots" + + +class InfiniteKeys(Toggle): + """ + You will be given an absurd amount of keys. + """ + + display_name = "Infinite Keys" + + +class TrapsChoice(Choice): + """ + Choose what traps will be put in the item pool. + All Active: Both Death and Poison Fruit will be added to the item pool. + Only Death: Death will be added to the item pool. + Only Fruit: Poison Fruit will be added to the item pool. + None Active: No Traps will be added to the item pool. + """ + + display_name = "Active Traps" + option_all_active = 0 + option_only_death = 1 + option_only_fruit = 2 + option_none_active = 3 + default = 0 + + +class TrapsFrequency(Choice): + """ + Choose the frequency of traps added into the item pool + Normal: 75 of each selected trap are added into the item pool. + Large: 150 of each selected trap are added into the item pool. + Extreme: 375 of each selected trap are added into the item pool. + """ + + display_name = "Trap Frequency" + option_normal = 0 + option_large = 1 + option_extreme = 2 + default = 0 + + +class DeathLink(Toggle): + """ + When you die, everyone dies. Of course the reverse is true too. + """ + display_name = "Death Link" + + +class UnlockCharacterOne(Choice): + """ + Unlock a secret character for Player 1 from the start. + None: No secret characters will be unlocked. + Chosen Character: The selected character will be available from a new save. + """ + + option_none = 0 + option_minotaur = 1 + option_falconess = 2 + option_tigress = 3 + option_jackal = 4 + option_sumner = 5 + default = 0 + + +class UnlockCharacterTwo(Choice): + """ + Unlock a secret character for Player 2 from the start. + None: No secret characters will be unlocked. + Chosen Character: The selected character will be available from a new save. + """ + + option_none = 0 + option_minotaur = 1 + option_falconess = 2 + option_tigress = 3 + option_jackal = 4 + option_sumner = 5 + default = 0 + + +class UnlockCharacterThree(Choice): + """ + Unlock a secret character for Player 3 from the start. + None: No secret characters will be unlocked. + Chosen Character: The selected character will be available from a new save. + """ + + option_none = 0 + option_minotaur = 1 + option_falconess = 2 + option_tigress = 3 + option_jackal = 4 + option_sumner = 5 + default = 0 + + +class UnlockCharacterFour(Choice): + """ + Unlock a secret character for Player 4 from the start. + None: No secret characters will be unlocked. + Chosen Character: The selected character will be available from a new save. + """ + + option_none = 0 + option_minotaur = 1 + option_falconess = 2 + option_tigress = 3 + option_jackal = 4 + option_sumner = 5 + default = 0 + + +@dataclass +class GLOptions(PerGameCommonOptions): + start_inventory_from_pool: StartInventoryPool + local_players: PlayerCount + chests_barrels: ChestBarrels + obelisks: Obelisks + mirror_shards: MirrorShards + max_difficulty_toggle: MaxDifficultyToggle + max_difficulty_value: MaxDifficultyRange + instant_max: InstantMaxDifficulty + infinite_keys: InfiniteKeys + permanent_speed: PermaSpeed + traps_choice: TrapsChoice + traps_frequency: TrapsFrequency + death_link: DeathLink + unlock_character_one: UnlockCharacterOne + unlock_character_two: UnlockCharacterTwo + unlock_character_three: UnlockCharacterThree + unlock_character_four: UnlockCharacterFour diff --git a/worlds/gl/Regions.py b/worlds/gl/Regions.py new file mode 100644 index 00000000000..647ced17cd4 --- /dev/null +++ b/worlds/gl/Regions.py @@ -0,0 +1,207 @@ +import typing + +from BaseClasses import Entrance, Region + +from .Locations import ( + GLLocation, + arctic_docks, + battle_towers, + battle_trenches, + castle_courtyard, + castle_treasury, + chimeras_keep, + cliffs_of_desolation, + crystal_mine, + dagger_peak, + desecrated_temple, + dragons_lair, + dungeon_of_torment, + erupting_fissure, + frozen_camp, + gates_of_the_underworld, + haunted_cemetery, + infernal_fortress, + lost_cave, + plague_fiend, + poisoned_fields, + tower_armory, + toxic_air_ship, + valley_of_fire, + venomous_spire, + volcanic_cavern, + yeti, +) + +if typing.TYPE_CHECKING: + from . import GauntletLegendsWorld + + +def create_regions(world: "GauntletLegendsWorld"): + world.multiworld.regions.append(Region("Menu", world.player, world.multiworld)) + + create_region(world, "Valley of Fire", valley_of_fire) + + create_region(world, "Dagger Peak", dagger_peak) + + create_region(world, "Cliffs of Desolation", cliffs_of_desolation) + + create_region(world, "Lost Cave", lost_cave) + + create_region(world, "Volcanic Caverns", volcanic_cavern) + + create_region(world, "Dragon's Lair", dragons_lair) + + create_region(world, "Castle Courtyard", castle_courtyard) + + create_region(world, "Dungeon of Torment", dungeon_of_torment) + + create_region(world, "Tower Armory", tower_armory) + + create_region(world, "Castle Treasury", castle_treasury) + + create_region(world, "Chimera's Keep", chimeras_keep) + + create_region(world, "Poisonous Fields", poisoned_fields) + + create_region(world, "Haunted Cemetery", haunted_cemetery) + + create_region(world, "Venomous Spire", venomous_spire) + + create_region(world, "Toxic Air Ship", toxic_air_ship) + + create_region(world, "Vat of the Plague Fiend", plague_fiend) + + create_region(world, "Arctic Docks", arctic_docks) + + create_region(world, "Frozen Camp", frozen_camp) + + create_region(world, "Crystal Mine", crystal_mine) + + create_region(world, "Erupting Fissure", erupting_fissure) + + create_region(world, "Yeti", yeti) + + create_region(world, "Desecrated Temple", desecrated_temple) + + create_region(world, "Battle Trenches", battle_trenches) + + create_region(world, "Battle Towers", battle_towers) + + create_region(world, "Infernal Fortress", infernal_fortress) + + create_region(world, "Gates of the Underworld", gates_of_the_underworld) + + +def connect_regions(world: "GauntletLegendsWorld"): + names: typing.Dict[str, int] = {} + + connect(world, names, "Menu", "Valley of Fire") + connect(world, names, "Menu", "Dagger Peak") + connect(world, names, "Menu", "Cliffs of Desolation") + connect(world, names, "Menu", "Lost Cave") + connect(world, names, "Menu", "Volcanic Caverns") + connect(world, names, "Menu", "Dragon's Lair") + connect( + world, + names, + "Menu", + "Castle Courtyard", + lambda state: state.has("Mountain Obelisk 1", world.player) + and state.has("Mountain Obelisk 2", world.player) + and state.has("Mountain Obelisk 3", world.player), + ) + connect(world, names, "Castle Courtyard", "Dungeon of Torment") + connect(world, names, "Castle Courtyard", "Tower Armory") + connect(world, names, "Castle Courtyard", "Castle Treasury") + connect(world, names, "Castle Courtyard", "Chimera's Keep") + connect( + world, + names, + "Menu", + "Poisonous Fields", + lambda state: state.has("Castle Obelisk 1", world.player) + and state.has("Castle Obelisk 2", world.player), + ) + connect(world, names, "Poisonous Fields", "Haunted Cemetery") + connect(world, names, "Poisonous Fields", "Venomous Spire") + connect(world, names, "Poisonous Fields", "Toxic Air Ship") + connect(world, names, "Toxic Air Ship", "Vat of the Plague Fiend") + connect( + world, + names, + "Menu", + "Arctic Docks", + lambda state: state.has("Town Obelisk 1", world.player) + and state.has("Town Obelisk 2", world.player), + ) + connect(world, names, "Arctic Docks", "Frozen Camp") + connect(world, names, "Arctic Docks", "Crystal Mine") + connect(world, names, "Arctic Docks", "Erupting Fissure") + connect(world, names, "Erupting Fissure", "Yeti") + connect( + world, + names, + "Menu", + "Desecrated Temple", + lambda state: state.has("Dragon Mirror Shard", world.player) + and state.has("Chimera Mirror Shard", world.player) + and state.has("Plague Fiend Mirror Shard", world.player) + and state.has("Yeti Mirror Shard", world.player), + ) + connect(world, names, "Desecrated Temple", "Battle Trenches") + connect(world, names, "Desecrated Temple", "Battle Towers") + connect(world, names, "Desecrated Temple", "Infernal Fortress") + connect( + world, + names, + "Menu", + "Gates of the Underworld", + lambda state: state.has("Runestone 1", world.player) + and state.has("Runestone 2", world.player) + and state.has("Runestone 3", world.player) + and state.has("Runestone 4", world.player) + and state.has("Runestone 5", world.player) + and state.has("Runestone 6", world.player) + and state.has("Runestone 7", world.player) + and state.has("Runestone 8", world.player) + and state.has("Runestone 9", world.player) + and state.has("Runestone 10", world.player) + and state.has("Runestone 11", world.player) + and state.has("Runestone 12", world.player) + and state.has("Runestone 13", world.player), + ) + + +def create_region(world: "GauntletLegendsWorld", name, locations): + reg = Region(name, world.player, world.multiworld) + for location in locations: + if location.name not in world.disabled_locations: + loc = GLLocation(world.player, location.name, location.id, reg) + reg.locations.append(loc) + world.multiworld.regions.append(reg) + + +def connect( + world: "GauntletLegendsWorld", + used_names: typing.Dict[str, int], + source: str, + target: str, + rule: typing.Optional[typing.Callable] = None, +): + source_region = world.multiworld.get_region(source, world.player) + target_region = world.multiworld.get_region(target, world.player) + + if target not in used_names: + used_names[target] = 1 + name = target + else: + used_names[target] += 1 + name = target + (" " * used_names[target]) + + connection = Entrance(world.player, name, source_region) + + if rule: + connection.access_rule = rule + + source_region.exits.append(connection) + connection.connect(target_region) diff --git a/worlds/gl/Rom.py b/worlds/gl/Rom.py new file mode 100644 index 00000000000..e28821a312b --- /dev/null +++ b/worlds/gl/Rom.py @@ -0,0 +1,355 @@ +import io +import json +import os +import traceback +import typing +import zlib +from typing import Dict, List, Tuple + +import Utils +from BaseClasses import Location, ItemClassification +from settings import get_settings + +from worlds.Files import APPatchExtension, APProcedurePatch, APTokenMixin + +from .Arrays import item_dict, level_address, level_header, level_locations, level_size +from .Items import items_by_id, ItemData +from .Locations import location_data, GLLocation + +if typing.TYPE_CHECKING: + from . import GauntletLegendsWorld + + +def get_base_rom_as_bytes() -> bytes: + try: + with open(get_settings().gl_options.rom_file, "rb") as infile: + base_rom_bytes = bytes(infile.read()) + except Exception: + traceback.print_exc() + + return base_rom_bytes + + +def get_base_rom_path(file_name: str = "") -> str: + if not os.path.exists(file_name): + file_name = Utils.user_path(file_name) + return file_name + + +# Contains header info and raw data for level item positions and rotations. +class LevelData: + stream: io.BytesIO + header: bytearray + item_addr: int + spawner_addr: int + obj_addr: int + chest_addr: int + end_addr: int + end_addr2: int + end_addr3: int + portal_addr: int + items: List[bytearray] + spawners: List[bytearray] + objects: List[bytearray] + chests: List[bytearray] + end: bytes + items_replaced_by_obelisks: int = 0 + chests_replaced_by_obelisks: int = 0 + chests_replaced_by_items: int = 0 + obelisks_replaced_by_items: int = 0 + + def __init__(self): + self.items = [] + self.spawners = [] + self.objects = [] + self.chests = [] + self.end = b"" + + +class GLPatchExtension(APPatchExtension): + game = "Gauntlet Legends" + + # Patch max item stack values + @staticmethod + def patch_counts(caller: APProcedurePatch, rom: bytes) -> bytes: + stream = io.BytesIO(rom) + stream.seek(0x67E7E0) + data = io.BytesIO(zdec(stream.read(0x380))) + data.seek(0x1A, 0) + data.write(bytes([0xFF, 0xFF])) + data.seek(0x37, 0) + data.write(bytes([0xFF])) + data.seek(0xDF, 0) + data.write(bytes([0xFF])) + data.seek(0xFB, 0) + data.write(bytes([0xFF])) + data.seek(0x117, 0) + data.write(bytes([0xFF])) + data.seek(0x133, 0) + data.write(bytes([0xFF])) + data.seek(0x212, 0) + data.write(bytes([0xFF, 0xFF])) + for i in range(25): + data.seek(0x1A, 1) + data.write(bytes([0xFF, 0xFF])) + data.seek(0x53E, 0) + data.write(bytes([0xFF, 0xFF])) + data.seek(0x55A, 0) + data.write(bytes([0xFF, 0xFF])) + data.seek(0x576, 0) + data.write(bytes([0xFF, 0xFF])) + data.seek(0x506, 0) + data.write(bytes([0xFF, 0xFF])) + data.seek(0x522, 0) + data.write(bytes([0xFF, 0xFF])) + stream.seek(0x67E7E0, 0) + stream.write(zenc(data.getvalue())) + return stream.getvalue() + + # Decompress all levels, place all items in the levels. + @staticmethod + def patch_items(caller: APProcedurePatch, rom: bytes): + stream = io.BytesIO(rom) + options = json.loads(caller.get_file("options.json").decode("UTF-8")) + for i in range(len(level_locations)): + level: Dict[str, Tuple] = json.loads(caller.get_file(f"level_{i}.json").decode("utf-8")) + stream.seek(level_address[i], 0) + stream, data = get_level_data(stream, level_size[i], i) + for j, (location_name, item) in enumerate(level.items()): + if item[0] == 0: + continue + if "Mirror" in location_name: + continue + if "Obelisk" in location_name and "Obelisk" not in items_by_id.get(item[0], ItemData(0, "", ItemClassification.filler)).item_name: + try: + index = [index for index in range(len(data.objects)) if data.objects[index][8] == 0x26][0] + data.items += [ + bytearray(data.objects[index][0:6]) + + bytes(item_dict[item[0]] if item[1] == options["player"] else [0x27, 0x4]) + + bytes([0x0, 0x0, 0x0, 0x0]), + ] + del data.objects[index] + data.obelisks_replaced_by_items += 1 + except Exception as e: + print(item[0]) + print(e) + continue + if item[1] is not options["player"]: + if "Chest" in location_name or ( + "Barrel" in location_name and "Barrel of Gold" not in location_name + ): + data.chests[j - (len(data.items) + data.items_replaced_by_obelisks + data.chests_replaced_by_obelisks)][12:14] = [0x27, 0x4] + if "Chest" in location_name: + data.chests[j - (len(data.items) + data.items_replaced_by_obelisks + data.chests_replaced_by_obelisks)][9] = 0x1 + else: + data.items[j - data.items_replaced_by_obelisks][6:8] = [0x27, 0x4] + else: + if "Obelisk" in items_by_id[item[0]].item_name and "Obelisk" not in location_name: + if chest_barrel(location_name): + slice_ = bytearray(data.chests[j - (len(data.items) + data.items_replaced_by_obelisks + data.chests_replaced_by_obelisks)][0:6]) + else: + slice_ = bytearray(data.items[j - data.items_replaced_by_obelisks][0:6]) + data.objects += [ + slice_ + + bytearray( + [ + 0x0, 0x0, 0x26, 0x1, 0x0, + location_data[location_name].difficulty, + 0x0, 0x0, 0x0, + item[0] - 77780054, + 0x3F, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + ], + ), + ] + if chest_barrel(location_name): + del data.chests[j - (len(data.items) + data.items_replaced_by_obelisks + data.chests_replaced_by_obelisks)] + data.chests_replaced_by_obelisks += 1 + else: + del data.items[j - data.items_replaced_by_obelisks] + data.items_replaced_by_obelisks += 1 + elif (items_by_id[item[0]].progression == ItemClassification.useful or items_by_id[item[0]].progression == ItemClassification.progression) and chest_barrel(location_name): + chest_index = j - ( + len(data.items) + data.items_replaced_by_obelisks + data.chests_replaced_by_obelisks) + chest = data.chests[chest_index] + slice_ = bytearray(chest[0:6]) + data.items += [ + slice_ + + bytes(item_dict[item[0]] if item[1] == options["player"] else [0x27, 0x4]) + + bytes([chest[11], 0x0, 0x0, 0x0]), + ] + del data.chests[chest_index] + data.chests_replaced_by_items += 1 + else: + if chest_barrel(location_name): + data.chests[j - (len(data.items) + data.items_replaced_by_obelisks + data.chests_replaced_by_obelisks)][12:14] = item_dict[item[0]] + if "Chest" in location_name: + data.chests[j - (len(data.items) + data.items_replaced_by_obelisks + data.chests_replaced_by_obelisks)][9] = 0x2 + else: + data.items[j - data.items_replaced_by_obelisks][6:8] = item_dict[item[0]] + uncompressed = level_data_reformat(data) + compressed = zenc(uncompressed) + stream.seek(level_header[i] + 4, 0) + stream.write(len(compressed).to_bytes(4, byteorder="big")) + stream.write(len(uncompressed).to_bytes(4, byteorder="big")) + write_pos = 0xFA1000 + (0x1500 * i) + stream.write((write_pos - 0x636E0).to_bytes(4, byteorder="big")) + stream.seek(write_pos, 0) + stream.write(compressed) + return stream.getvalue() + + +class GLProcedurePatch(APProcedurePatch, APTokenMixin): + game = "Gauntlet Legends" + hash = "9cb963e8b71f18568f78ec1af120362e" + patch_file_ending = ".apgl" + result_file_ending = ".z64" + + procedure = [("patch_items", []), ("patch_counts", [])] + + @classmethod + def get_source_data(cls) -> bytes: + return get_base_rom_as_bytes() + + +# Write data on all placed items into json files. +# Also save options +def write_files(world: "GauntletLegendsWorld", patch: GLProcedurePatch) -> None: + options_dict = { + "seed": world.multiworld.seed, + "player": world.player, + } + patch.write_file("options.json", json.dumps(options_dict).encode("UTF-8")) + for i, level in enumerate(level_locations.values()): + locations: List[Location] = [] + for location in level: + if location.name in world.disabled_locations: + locations += [GLLocation(world.player, location.name, location.id)] + else: + locations += [world.get_location(location.name)] + patch.write_file(f"level_{i}.json", json.dumps(locations_to_dict(locations)).encode("UTF-8")) + + +def locations_to_dict(locations: List[Location]) -> Dict[str, Tuple]: + return {location.name: (location.item.code, location.item.player) if location.item is not None else (0, 0) for location in locations} + + +def patch_docks(data: LevelData) -> LevelData: + data.stream.seek(0x19AC, 0) + data.stream.write(bytes([0x3, 0x20, 0x0, 0x18, 0x1, 0x7C])) + data.stream.seek(0x74, 0) + data.stream.write(bytes([0x3, 0x0, 0x0, 0x1A, 0x1, 0x8])) + return data + + +def patch_camp(data: LevelData) -> LevelData: + data.stream.seek(0x1B74, 0) + data.stream.write(bytes([0xFE, 0xE9, 0x0, 0x3B, 0xFF, 0xDC, 0x0, 0x10])) + data.stream.seek(0x1B64, 0) + data.stream.write(bytes([0xFE, 0x84, 0x0, 0x3D, 0xFF, 0xE0, 0xFF, 0xF0])) + return data + +def patch_trenches(data: LevelData) -> LevelData: + data.stream.seek(0xD4, 0) + data.stream.write(bytes([0xFB, 0x29, 0x0, 0x82])) + return data + + +# Zlib decompression with wbits set to -15 +def zdec(data): + """ + Decompresses zlib archives used in Midway titles. + """ + decomp = zlib.decompressobj(-zlib.MAX_WBITS) + output = bytearray() + for i in range(0, len(data), 256): + output.extend(decomp.decompress(data[i : i + 256])) + output.extend(decomp.flush()) + return output + + +# Zlib compression with compression set to max and wbits set to -15 +def zenc(data): + """ + Headerless zlib encoding scheme used across games. + Note you get much better compression routing through gzip + and stripping off the header and CRC. + """ + compress = zlib.compressobj(zlib.Z_BEST_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) + output = bytearray() + for i in range(0, len(data), 256): + output.extend(compress.compress(data[i : i + 256])) + output.extend(compress.flush()) + return output + + +# Create a LevelData object from raw decompressed bytes of a level +def get_level_data(stream: io.BytesIO, size: int, level=0) -> (io.BytesIO, LevelData): + data = LevelData() + data.stream = io.BytesIO(zdec(stream.read(size))) + if level == 17: + data = patch_docks(data) + if level == 18: + data = patch_camp(data) + if level == 23: + data = patch_trenches(data) + data.header = data.stream.read(0x5C) + data.stream.seek(0) + data.item_addr = int.from_bytes(data.stream.read(4), "big") + data.spawner_addr = int.from_bytes(data.stream.read(4), "big") + data.stream.seek(4, 1) + data.obj_addr = int.from_bytes(data.stream.read(4), "big") + data.end_addr = int.from_bytes(data.stream.read(4), "big") + data.portal_addr = int.from_bytes(data.stream.read(4), "big") + data.chest_addr = int.from_bytes(data.stream.read(4), "big") + data.end_addr2 = int.from_bytes(data.stream.read(4), "big") + data.end_addr3 = int.from_bytes(data.stream.read(4), "big") + data.stream.seek(data.item_addr) + for i in range(data.stream.tell(), data.spawner_addr, 12): + data.stream.seek(i) + data.items += [bytearray(data.stream.read(12))] + for i in range(data.stream.tell(), data.obj_addr, 16): + data.stream.seek(i) + data.spawners += [bytearray(data.stream.read(16))] + for i in range(data.stream.tell(), data.chest_addr, 24): + data.stream.seek(i) + data.objects += [bytearray(data.stream.read(24))] + for i in range(data.stream.tell(), data.end_addr, 16): + data.stream.seek(i) + data.chests += [bytearray(data.stream.read(16))] + data.end = data.stream.read() + return stream, data + + +# Format a LevelData object back into a bytes object +# Format is header, items, spawners, objects, barrels/chests, then traps. +def level_data_reformat(data: LevelData) -> bytes: + stream = io.BytesIO() + obelisk_offset = 24 * (data.items_replaced_by_obelisks + data.chests_replaced_by_obelisks - data.obelisks_replaced_by_items) + item_offset = 12 * (data.chests_replaced_by_items + data.obelisks_replaced_by_items - data.items_replaced_by_obelisks) + chest_offset = 16 * (data.chests_replaced_by_obelisks + data.chests_replaced_by_items) + stream.write(int.to_bytes(0x5C, 4, "big")) + stream.write(int.to_bytes(data.spawner_addr + item_offset, 4, "big")) + stream.write(int.to_bytes(data.spawner_addr + item_offset, 4, "big")) + stream.write(int.to_bytes(data.obj_addr + item_offset, 4, "big")) + stream.write(int.to_bytes(data.end_addr + item_offset + obelisk_offset - chest_offset, 4, "big")) + stream.write(int.to_bytes(data.portal_addr + item_offset + obelisk_offset - chest_offset, 4, "big")) + stream.write(int.to_bytes(data.chest_addr + item_offset + obelisk_offset, 4, "big")) + stream.write(int.to_bytes(data.end_addr2 + item_offset + obelisk_offset - chest_offset, 4, "big")) + stream.write(int.to_bytes(data.end_addr3 + item_offset + obelisk_offset - chest_offset, 4, "big")) + stream.seek(1, 1) + stream.write(bytes([len(data.items)])) + stream.write(bytes([0x0, 0x0, 0x0])) + stream.write(bytes([len(data.spawners)])) + stream.write(bytes([0x0])) + stream.write(bytes([len(data.objects)])) + data.stream.seek(stream.tell()) + temp = bytearray(data.stream.read(48)) + temp[7] = len(data.chests) + stream.write(temp) + for item in data.items + data.spawners + data.objects + data.chests: + stream.write(bytes(item)) + stream.write(data.end) + return stream.getvalue() + +def chest_barrel(name: str): + return ("Chest" in name or ("Barrel" in name and "Barrel of Gold" not in name)) diff --git a/worlds/gl/Rules.py b/worlds/gl/Rules.py new file mode 100644 index 00000000000..f8126c2d997 --- /dev/null +++ b/worlds/gl/Rules.py @@ -0,0 +1,48 @@ +import typing + +from BaseClasses import ItemClassification +from worlds.generic.Rules import add_rule, forbid_item + +from .Arrays import difficulty_lambda, level_locations, no_obelisks +from .Items import item_list +from .Locations import all_locations, chimeras_keep, dragons_lair, gates_of_the_underworld + +if typing.TYPE_CHECKING: + from . import GauntletLegendsWorld + + +def prog_count(state, player, diff): + count = 0 + for i in range(1, 14): + if state.has(f"Runestone {i}", player): + count += 1 + return count >= diff + + +def set_rules(world: "GauntletLegendsWorld"): + obelisks = [item.item_name for item in item_list if "Obelisk" in item.item_name] + + for location in [ + location + for location in all_locations + if "Mirror" in location.name + or location in dragons_lair + or location in chimeras_keep + or location in gates_of_the_underworld + or location.id in no_obelisks + ] + [location for location in all_locations if "Obelisk" in location.name and world.options.obelisks == 1]: + for item in obelisks: + if location.name not in world.disabled_locations: + forbid_item(world.get_location(location.name), item, world.player) + + if not world.options.instant_max: + for level_id, locations in level_locations.items(): + for location in locations: + if location.difficulty > 1: + if location.name not in world.disabled_locations: + add_rule( + world.get_location(location.name), + lambda state, level_id_=level_id >> 4, difficulty=location.difficulty - 1: prog_count( + state, world.player, difficulty_lambda[level_id_][difficulty], + ), + ) diff --git a/worlds/gl/__init__.py b/worlds/gl/__init__.py new file mode 100644 index 00000000000..59dd66a96d4 --- /dev/null +++ b/worlds/gl/__init__.py @@ -0,0 +1,252 @@ +import os +import typing + +import settings +from BaseClasses import ItemClassification, Tutorial, Item, Location +from Fill import fast_fill +from typing import List + +from worlds.AutoWorld import WebWorld, World + +from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components, launch_subprocess +from .Arrays import item_dict +from .Items import GLItem, item_frequencies, item_list, item_table +from .Locations import LocationData, all_locations, location_table +from .Options import GLOptions +from .Regions import connect_regions, create_regions +from .Rom import GLProcedurePatch, write_files +from .Rules import set_rules + + +def launch_client(*args): + from .GauntletLegendsClient import launch + + launch_subprocess(launch, name="GauntletLegendsClient") + + +components.append( + Component( + "Gauntlet Legends Client", + "GauntletLegendsClient", + func=launch_client, + component_type=Type.CLIENT, + file_identifier=SuffixIdentifier(".apgl"), + ), +) + + +class GauntletLegendsWebWorld(WebWorld): + settings_page = "games/gl/info/en" + theme = "partyTime" + tutorials = [ + Tutorial( + tutorial_name="Setup Guide", + description="A guide to playing Gauntlet Legends", + language="English", + file_name="setup_en.md", + link="setup/en", + authors=["jamesbrq"], + ), + ] + + +class GLSettings(settings.Group): + class RomFile(settings.UserFilePath): + """File name of the GL US rom""" + + copy_to = "Gauntlet Legends (U) [!].z64" + description = "Gauntlet Legends ROM File" + + rom_file: RomFile = RomFile(RomFile.copy_to) + rom_start: bool = False + + +class GauntletLegendsWorld(World): + """ + Gauntlet Legends + """ + + game = "Gauntlet Legends" + web = GauntletLegendsWebWorld() + options_dataclass = GLOptions + options: GLOptions + settings: typing.ClassVar[GLSettings] + item_name_to_id = {name: data.code for name, data in item_table.items()} + location_name_to_id = {loc_data.name: loc_data.id for loc_data in all_locations} + required_client_version = (0, 5, 0) + crc32: str = None + death: List[Item] = [] + + disabled_locations: typing.List[LocationData] + + def generate_early(self) -> None: + self.options.max_difficulty_value.value = max(self.options.max_difficulty_value.value, self.options.local_players.value) + + def create_regions(self) -> None: + self.disabled_locations = [] + if self.options.chests_barrels == 0: + self.disabled_locations += [ + location.name + for location in all_locations + if "Chest" in location.name or ("Barrel" in location.name and "Barrel of Gold" not in location.name) + and location.name not in self.disabled_locations + ] + elif self.options.chests_barrels == 1: + self.disabled_locations += [ + location.name + for location in all_locations + if "Barrel" in location.name and "Barrel of Gold" not in location.name + and location.name not in self.disabled_locations + ] + elif self.options.chests_barrels == 2: + self.disabled_locations += [location.name for location in all_locations if "Chest" in location.name and location.name not in self.disabled_locations] + + if self.options.max_difficulty_toggle: + self.disabled_locations += [location.name for location in all_locations + if location.difficulty > self.options.max_difficulty_value + and location.name not in self.disabled_locations] + + create_regions(self) + connect_regions(self) + if not self.options.infinite_keys: + item = self.create_item("Key") + self.get_location("Valley of Fire - Key 1").place_locked_item(item) + self.get_location("Valley of Fire - Key 5").place_locked_item(item) + if self.options.obelisks == 0: + item = self.create_item("Mountain Obelisk 1") + self.get_location("Valley of Fire - Obelisk").place_locked_item(item) + item = self.create_item("Mountain Obelisk 2") + self.get_location("Dagger Peak - Obelisk").place_locked_item(item) + item = self.create_item("Mountain Obelisk 3") + self.get_location("Cliffs of Desolation - Obelisk").place_locked_item(item) + item = self.create_item("Castle Obelisk 1") + self.get_location("Castle Courtyard - Obelisk").place_locked_item(item) + item = self.create_item("Castle Obelisk 2") + self.get_location("Dungeon of Torment - Obelisk").place_locked_item(item) + item = self.create_item("Town Obelisk 1") + self.get_location("Poisoned Fields - Obelisk").place_locked_item(item) + item = self.create_item("Town Obelisk 2") + self.get_location("Haunted Cemetery - Obelisk").place_locked_item(item) + if self.options.mirror_shards == 0: + item = self.create_item("Dragon Mirror Shard") + self.get_location("Dragon's Lair - Dragon Mirror Shard").place_locked_item(item) + item = self.create_item("Chimera Mirror Shard") + self.get_location("Chimera's Keep - Chimera Mirror Shard").place_locked_item(item) + item = self.create_item("Plague Fiend Mirror Shard") + self.get_location("Vat of the Plague Fiend - Plague Fiend Mirror Shard").place_locked_item(item) + item = self.create_item("Yeti Mirror Shard") + self.get_location("Yeti's Cavern - Yeti Mirror Shard").place_locked_item(item) + + def fill_slot_data(self) -> dict: + dshard = self.get_location("Dragon's Lair - Dragon Mirror Shard").item + yshard = self.get_location("Yeti's Cavern - Yeti Mirror Shard").item + cshard = self.get_location("Chimera's Keep - Chimera Mirror Shard").item + fshard = self.get_location("Vat of the Plague Fiend - Plague Fiend Mirror Shard").item + shard_values = [ + item_dict[dshard.code] if dshard.player == self.player else [0x27, 0x4], + item_dict[yshard.code] if yshard.player == self.player else [0x27, 0x4], + item_dict[cshard.code] if cshard.player == self.player else [0x27, 0x4], + item_dict[fshard.code] if fshard.player == self.player else [0x27, 0x4], + ] + characters = [ + self.options.unlock_character_one.value, + self.options.unlock_character_two.value, + self.options.unlock_character_three.value, + self.options.unlock_character_four.value, + ] + chests_barrels = self.options.chests_barrels.value + return { + "player": self.player, + "players": self.options.local_players.value, + "shards": shard_values, + "chests": bool(chests_barrels == 3 or chests_barrels == 1), + "barrels": bool(chests_barrels == 3 or chests_barrels == 2), + "speed": self.options.permanent_speed.value, + "keys": self.options.infinite_keys.value, + "characters": characters, + "max": (self.options.max_difficulty_value.value if self.options.max_difficulty_toggle else 4), + "instant_max": self.options.instant_max.value, + "death_link": bool((self.options.death_link.value == 1)) + } + + def create_items(self) -> None: + # First add in all progression and useful items + required_items = [] + precollected = [item for item in item_list if item in self.multiworld.precollected_items[self.player]] + for item in item_list: + if item.progression != ItemClassification.filler and item.progression != ItemClassification.trap and item not in precollected: + if "Obelisk" in item.item_name and self.options.obelisks == 0: + continue + if "Mirror" in item.item_name and self.options.mirror_shards == 0: + continue + freq = item_frequencies.get(item.item_name, 1) + required_items += [item.item_name for _ in range(freq)] + + for item_name in required_items: + self.multiworld.itempool.append(self.create_item(item_name)) + + # Then, get a random amount of fillers until we have as many items as we have locations + filler_items = [] + trap_count = 0 + for item in item_list: + if item.progression == ItemClassification.filler or item.progression == ItemClassification.trap: + if "Key" in item.item_name and self.options.infinite_keys: + continue + if "Boots" in item.item_name and self.options.permanent_speed: + continue + if "Poison" in item.item_name and (self.options.traps_choice.value == self.options.traps_choice.option_only_death or self.options.traps_choice.value == self.options.traps_choice.option_none_active): + continue + if "Death" in item.item_name and (self.options.traps_choice.value == self.options.traps_choice.option_only_fruit or self.options.traps_choice.value == self.options.traps_choice.option_none_active): + continue + + freq = item_frequencies.get(item.item_name, 1) + (30 if self.options.infinite_keys else 0) + (5 if self.options.permanent_speed else 0) + + if self.options.traps_frequency.value == self.options.traps_frequency.option_large and item.progression == ItemClassification.trap: + freq *= 2 + elif self.options.traps_frequency.value == self.options.traps_frequency.option_extreme and item.progression == ItemClassification.trap: + freq *= 5 + + if item.item_name == "Invulnerability" or item.item_name == "Anti-Death Halo": + freq = item_frequencies.get(item.item_name, 1) + if item.item_name == "Anti-Death Halo" and (self.options.traps_choice.value == self.options.traps_choice.option_only_death or self.options.traps_choice.value == self.options.traps_choice.option_all_active) and self.options.traps_frequency.value == self.options.traps_frequency.option_extreme: + freq *= 2 + if item.item_name == "Death": + trap_count += freq + self.death = [] + for i in range(freq): + self.death += [self.create_item(item.item_name)] + else: + filler_items += [item.item_name for _ in range(freq)] + + remaining = len(all_locations) - len(required_items) - len(self.disabled_locations) - trap_count - (2 if not self.options.infinite_keys else 0) + if self.options.obelisks == 0: + remaining -= 7 + if self.options.mirror_shards == 0: + remaining -= 4 + for i in range(remaining): + filler_item_name = self.multiworld.random.choice(filler_items) + self.multiworld.itempool.append(self.create_item(filler_item_name)) + filler_items.remove(filler_item_name) + + def set_rules(self) -> None: + set_rules(self) + self.multiworld.completion_condition[self.player] = lambda state: state.can_reach( + "Gates of the Underworld", "Region", self.player, + ) + + def pre_fill(self) -> None: + locations = [location for location in self.multiworld.get_unfilled_locations(self.player)] + self.random.shuffle(locations) + fast_fill(self.multiworld, self.death, locations) + + def create_item(self, name: str) -> GLItem: + item = item_table[name] + return GLItem(item.item_name, item.progression, item.code, self.player) + + def generate_output(self, output_directory: str) -> None: + patch = GLProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player]) + write_files(self, patch) + rom_path = os.path.join( + output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}{patch.patch_file_ending}", + ) + patch.write(rom_path) diff --git a/worlds/gl/docs/en_Gauntlet Legends.md b/worlds/gl/docs/en_Gauntlet Legends.md new file mode 100644 index 00000000000..f7e7cf82442 --- /dev/null +++ b/worlds/gl/docs/en_Gauntlet Legends.md @@ -0,0 +1,46 @@ +# Gauntlet Legends + +## Where is the options page? + +The [player options page for this game](../player-options) contains all the options you need to configure and +export a config file. + +## What does randomization do to this game? + +Items which the player would normally acquire throughout the game have been moved around. Logic remains, so the game is +always able to be completed, but because of the item shuffle, the player may need to access certain areas before they +would in the vanilla game. + +## What is the goal of Gauntlet Legends when randomized? + +Defeat Skorne in the Underworld. This requires gathering all 13 Runestones to gain access to the Underworld, and being strong enough to beat Skorne. + +Along the way, players will need to activate Obelisks to unlock new areas for them to explore. +They may also need to collect Mirror Shards, which will unlock a 5th area to explore as well as the Cathedral level. + +## What items and locations can get shuffled? + +Locations in which items can be found: +- All in-stage items +- All Chests +- All Barrels +- All Runestones +- All Obelisks +- All Mirror Shards + +Items that can be placed into locations: +- Keys +- Potions +- All consumable items +- Runestones +- Obelisks +- Mirror Shards + +## What does another world's item look like in Gauntlet Legends? + +Items will show up as a purple coin, both as items and in chests or barrels. + +## When the player receives an item, what happens? + +Items will be placed directly into the players inventory. +If the player either leaves a stage midway or dies, the items they received mid-level will be given back to them in the lobby. diff --git a/worlds/gl/docs/setup_en.md b/worlds/gl/docs/setup_en.md new file mode 100644 index 00000000000..f77b3d38b38 --- /dev/null +++ b/worlds/gl/docs/setup_en.md @@ -0,0 +1,58 @@ +# Setup Guide for Gauntlet Legends in Archipelago + +## Required Software + +- Retroarch: [Standalone](https://www.retroarch.com/?page=platforms), [Steam](https://store.steampowered.com/app/1118310/RetroArch/) +- The built-in Gauntlet Legends Client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases) +- A US copy of Gauntlet Legends for the N64 + +## Configuring Retroarch + +### Enabling Network Commands + +You must go to Settings -> User Interface and turn the Show Advanced Settings to On. +Then in Settings -> Network, you must also turn On Network Commands. +Leave the port as the default. + +### Selecting a Core + +When selecting a core, make sure to select Mupen64-plus-next core. + +## Configuring your YAML file + +### What is a YAML file and why do I need one? + +Your YAML file contains a set of configuration options which provide the generator with information about how it should +generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy +an experience customized for their taste, and different players in the same multiworld can all have different options. + +### Where do I get a YAML file? + +You can customize your options by visiting the +[Gauntlet Legends Options Page](/games/Gauntlet%20Legends/player-options) + +## Joining a MultiWorld Game + +### Obtain your N64 patch file + +When you join a multiworld game, you will be asked to provide your YAML file to whoever is hosting. +The host generating does not need a copy of Gauntlet Legends to be able to generate with a Gauntlet Legends yaml. + +Once that is done, the host will provide you with either a link to download your data file, +or with a zip file containing everyone's data files. Your data file should have a `.apgl` extension. + + +Double-click on your `.apgl` file to start the ROM patch process. Once the process is finished, the client will be started automatically. + +### Connect to the Multiserver + +This game uses its own custom client, named Gauntlet Legends Client. +Retroarch is only emulator this client will accept. + +Once both the client and the emulator are started, you must connect them. Once your ROM is open in Retroach, +as long as the client is open they will be connected to eachother. + +To connect the client to the multiserver simply put `
:` on the textfield on top and press enter (if the +server uses password, type in the bottom textfield `/connect
: [password]`). + +The client will prompt you for your slot name, once you enter and submit it, you will be connected to the lobby.