From 2165253961e3c99ecae4c1c7c41112d9ae782ca9 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Tue, 13 Feb 2024 19:55:19 -0500 Subject: [PATCH 001/166] Lingo: Detach Art Gallery Exit from Progressive Art Gallery (#2739) The final stage of Progressive Art Gallery opens up the four-way intersection between the Art Gallery, Orange Tower Fifth Floor, The Bearer, and Outside The Initiated. This is a very useful door, and it would be cool to be able to open it without having to get five progressive items. The original reason this was included in the progression was because getting into the back of Art Gallery early would cause sequence breaks. At this point, the way the client handles the Art Gallery has changed enough that it does not matter if the player can go through this door before getting all progressive art galleries. --- worlds/lingo/data/LL1.yaml | 2 +- worlds/lingo/test/TestProgressive.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index da78a5123d..6d74a3f0da 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -6193,6 +6193,7 @@ Exit: id: Tower Room Area Doors/Door_painting_exit include_reduce: True + item_name: Orange Tower Fifth Floor - Quadruple Intersection panels: - ONE ROAD MANY TURNS paintings: @@ -6212,7 +6213,6 @@ - Third Floor - Fourth Floor - Fifth Floor - - Exit Art Gallery (Second Floor): entrances: Art Gallery: diff --git a/worlds/lingo/test/TestProgressive.py b/worlds/lingo/test/TestProgressive.py index 8edc7ce6cc..0aaebe9319 100644 --- a/worlds/lingo/test/TestProgressive.py +++ b/worlds/lingo/test/TestProgressive.py @@ -142,7 +142,7 @@ class TestProgressiveArtGallery(LingoTestBase): self.assertTrue(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) - self.collect(progressive_gallery_room[4]) + self.collect_by_name("Orange Tower Fifth Floor - Quadruple Intersection") self.assertTrue(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) From 2167db5a881870e7eaf34b8eccbf98957a50cd0b Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Tue, 13 Feb 2024 19:56:24 -0500 Subject: [PATCH 002/166] Lingo: Split up Color Hunt and Champion's Rest (#2745) --- worlds/lingo/data/LL1.yaml | 107 ++++++++++++++++------------------- worlds/lingo/data/ids.yaml | 6 +- worlds/lingo/player_logic.py | 5 +- 3 files changed, 54 insertions(+), 64 deletions(-) diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index 6d74a3f0da..2e18766c01 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -1166,7 +1166,7 @@ group: Color Hunt Barriers skip_location: True panels: - - room: Champion's Rest + - room: Color Hunt panel: PURPLE Hallway Door: id: Red Blue Purple Room Area Doors/Door_room_2 @@ -1957,7 +1957,7 @@ group: Color Hunt Barriers skip_location: True panels: - - room: Champion's Rest + - room: Color Hunt panel: RED Rhyme Room Entrance: id: Double Room Area Doors/Door_room_entry_stairs2 @@ -1975,9 +1975,9 @@ - Color Arrow Room Doors/Door_orange_hider_1 - Color Arrow Room Doors/Door_orange_hider_2 - Color Arrow Room Doors/Door_orange_hider_3 - location_name: Color Hunt - RED and YELLOW - group: Champion's Rest - Color Barriers - item_name: Champion's Rest - Orange Barrier + location_name: Color Barriers - RED and YELLOW + group: Color Hunt Barriers + item_name: Color Hunt - Orange Barrier panels: - RED - room: Directional Gallery @@ -2382,7 +2382,7 @@ group: Color Hunt Barriers skip_location: True panels: - - room: Champion's Rest + - room: Color Hunt panel: GREEN paintings: - id: flower_painting_7 @@ -2893,14 +2893,14 @@ group: Color Hunt Barriers skip_location: True panels: - - room: Champion's Rest + - room: Color Hunt panel: BLUE Orange Barrier: id: Color Arrow Room Doors/Door_orange_3 group: Color Hunt Barriers skip_location: True panels: - - room: Champion's Rest + - room: Color Hunt panel: ORANGE Initiated Entrance: id: Red Blue Purple Room Area Doors/Door_locked_knocked @@ -2912,9 +2912,9 @@ # containing region. Green Barrier: id: Color Arrow Room Doors/Door_green_hider_1 - location_name: Color Hunt - BLUE and YELLOW - item_name: Champion's Rest - Green Barrier - group: Champion's Rest - Color Barriers + location_name: Color Barriers - BLUE and YELLOW + item_name: Color Hunt - Green Barrier + group: Color Hunt Barriers panels: - BLUE - room: Directional Gallery @@ -2924,9 +2924,9 @@ - Color Arrow Room Doors/Door_purple_hider_1 - Color Arrow Room Doors/Door_purple_hider_2 - Color Arrow Room Doors/Door_purple_hider_3 - location_name: Color Hunt - RED and BLUE - item_name: Champion's Rest - Purple Barrier - group: Champion's Rest - Color Barriers + location_name: Color Barriers - RED and BLUE + item_name: Color Hunt - Purple Barrier + group: Color Hunt Barriers panels: - BLUE - room: Orange Tower Third Floor @@ -2936,7 +2936,7 @@ - Color Arrow Room Doors/Door_all_hider_1 - Color Arrow Room Doors/Door_all_hider_2 - Color Arrow Room Doors/Door_all_hider_3 - location_name: Color Hunt - GREEN, ORANGE and PURPLE + location_name: Color Barriers - GREEN, ORANGE and PURPLE item_name: Champion's Rest - Entrance panels: - ORANGE @@ -3176,8 +3176,8 @@ Outside The Bold: entrances: Color Hallways: True - Champion's Rest: - room: Champion's Rest + Color Hunt: + room: Color Hunt door: Shortcut to The Steady The Bearer: room: The Bearer @@ -4002,7 +4002,7 @@ group: Color Hunt Barriers skip_location: True panels: - - room: Champion's Rest + - room: Color Hunt panel: YELLOW paintings: - id: smile_painting_7 @@ -4020,12 +4020,15 @@ orientation: south - id: cherry_painting orientation: east - Champion's Rest: + Color Hunt: entrances: Outside The Bold: door: Shortcut to The Steady Orange Tower Fourth Floor: True # sunwarp Roof: True # through ceiling of sunwarp + Champion's Rest: + room: Outside The Initiated + door: Entrance panels: EXIT: id: Rock Room/Panel_red_red @@ -4066,41 +4069,6 @@ required_door: room: Orange Tower Third Floor door: Orange Barrier - YOU: - id: Color Arrow Room/Panel_you - required_door: - room: Outside The Initiated - door: Entrance - check: True - colors: gray - tag: forbid - ME: - id: Color Arrow Room/Panel_me - colors: gray - tag: forbid - required_door: - room: Outside The Initiated - door: Entrance - SECRET BLUE: - # Pretend this and the other two are white, because they are snipes. - # TODO: Extract them and randomize them? - id: Color Arrow Room/Panel_secret_blue - tag: forbid - required_door: - room: Outside The Initiated - door: Entrance - SECRET YELLOW: - id: Color Arrow Room/Panel_secret_yellow - tag: forbid - required_door: - room: Outside The Initiated - door: Entrance - SECRET RED: - id: Color Arrow Room/Panel_secret_red - tag: forbid - required_door: - room: Outside The Initiated - door: Entrance doors: Shortcut to The Steady: id: Rock Room Doors/Door_hint @@ -4115,12 +4083,35 @@ required_door: room: Outside The Initiated door: Entrance + Champion's Rest: + entrances: + Color Hunt: + room: Outside The Initiated + door: Entrance + panels: + YOU: + id: Color Arrow Room/Panel_you + check: True + colors: gray + tag: forbid + ME: + id: Color Arrow Room/Panel_me + colors: gray + tag: forbid + SECRET BLUE: + # Pretend this and the other two are white, because they are snipes. + # TODO: Extract them and randomize them? + id: Color Arrow Room/Panel_secret_blue + tag: forbid + SECRET YELLOW: + id: Color Arrow Room/Panel_secret_yellow + tag: forbid + SECRET RED: + id: Color Arrow Room/Panel_secret_red + tag: forbid + paintings: - id: colors_painting orientation: south - enter_only: True - required_door: - room: Outside The Initiated - door: Entrance The Bearer: entrances: Outside The Bold: diff --git a/worlds/lingo/data/ids.yaml b/worlds/lingo/data/ids.yaml index 2b9e7f3d8c..56c22ad1be 100644 --- a/worlds/lingo/data/ids.yaml +++ b/worlds/lingo/data/ids.yaml @@ -489,7 +489,7 @@ panels: WINDWARD: 444803 LIGHT: 444804 REWIND: 444805 - Champion's Rest: + Color Hunt: EXIT: 444806 HUES: 444807 RED: 444808 @@ -498,6 +498,7 @@ panels: GREEN: 444811 PURPLE: 444812 ORANGE: 444813 + Champion's Rest: YOU: 444814 ME: 444815 SECRET BLUE: 444816 @@ -1286,7 +1287,7 @@ doors: location: 445246 Yellow Barrier: item: 444538 - Champion's Rest: + Color Hunt: Shortcut to The Steady: item: 444539 location: 444806 @@ -1442,7 +1443,6 @@ door_groups: Fearless Doors: 444469 Backside Doors: 444473 Orange Tower First Floor - Shortcuts: 444484 - Champion's Rest - Color Barriers: 444489 Welcome Back Doors: 444492 Colorful Doors: 444498 Directional Gallery Doors: 444531 diff --git a/worlds/lingo/player_logic.py b/worlds/lingo/player_logic.py index f3efc2914c..d87aa5672c 100644 --- a/worlds/lingo/player_logic.py +++ b/worlds/lingo/player_logic.py @@ -196,9 +196,8 @@ class LingoPlayerLogic: ["Orange Tower Fourth Floor", "Hot Crusts Door"], ["Outside The Initiated", "Shortcut to Hub Room"], ["Orange Tower First Floor", "Shortcut to Hub Room"], ["Directional Gallery", "Shortcut to The Undeterred"], ["Orange Tower First Floor", "Salt Pepper Door"], ["Hub Room", "Crossroads Entrance"], - ["Champion's Rest", "Shortcut to The Steady"], ["The Bearer", "Shortcut to The Bold"], - ["Art Gallery", "Exit"], ["The Tenacious", "Shortcut to Hub Room"], - ["Outside The Agreeable", "Tenacious Entrance"] + ["Color Hunt", "Shortcut to The Steady"], ["The Bearer", "Shortcut to The Bold"], ["Art Gallery", "Exit"], + ["The Tenacious", "Shortcut to Hub Room"], ["Outside The Agreeable", "Tenacious Entrance"] ] pilgrimage_reqs = AccessRequirements() for door in fake_pilgrimage: From e5980ac5f5027c739883137c04ea5135d3d5ee2d Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Wed, 14 Feb 2024 22:56:21 +0100 Subject: [PATCH 003/166] Core: remove module level AutoWorld import (#2790) With BaseClasses running `worlds.__init__.py` and worlds importing `from BaseClasses`, this is likely to result in some extra code being run because of partial recursive imports. This now lazily loads `worlds` when needed, at which point `sys.modules` should be properly populated. --- BaseClasses.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 15470f82a0..4002800173 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -18,11 +18,14 @@ import NetUtils import Options import Utils +if typing.TYPE_CHECKING: + from worlds import AutoWorld + class Group(TypedDict, total=False): name: str game: str - world: auto_world + world: "AutoWorld.World" players: Set[int] item_pool: Set[str] replacement_items: Dict[int, Optional[str]] @@ -55,7 +58,7 @@ class MultiWorld(): plando_texts: List[Dict[str, str]] plando_items: List[List[Dict[str, Any]]] plando_connections: List - worlds: Dict[int, auto_world] + worlds: Dict[int, "AutoWorld.World"] groups: Dict[int, Group] regions: RegionManager itempool: List[Item] @@ -219,6 +222,8 @@ class MultiWorld(): def add_group(self, name: str, game: str, players: Set[int] = frozenset()) -> Tuple[int, Group]: """Create a group with name and return the assigned player ID and group. If a group of this name already exists, the set of players is extended instead of creating a new one.""" + from worlds import AutoWorld + for group_id, group in self.groups.items(): if group["name"] == name: group["players"] |= players @@ -253,6 +258,8 @@ class MultiWorld(): def set_options(self, args: Namespace) -> None: # TODO - remove this section once all worlds use options dataclasses + from worlds import AutoWorld + all_keys: Set[str] = {key for player in self.player_ids for key in AutoWorld.AutoWorldRegister.world_types[self.game[player]].options_dataclass.type_hints} for option_key in all_keys: @@ -270,6 +277,8 @@ class MultiWorld(): for option_key in options_dataclass.type_hints}) def set_item_links(self): + from worlds import AutoWorld + item_links = {} replacement_prio = [False, True, None] for player in self.player_ids: @@ -1327,6 +1336,8 @@ class Spoiler: get_path(state, multiworld.get_region('Inverted Big Bomb Shop', player)) def to_file(self, filename: str) -> None: + from worlds import AutoWorld + def write_option(option_key: str, option_obj: Options.AssembleOptions) -> None: res = getattr(self.multiworld.worlds[player].options, option_key) display_name = getattr(option_obj, "display_name", option_key) @@ -1450,8 +1461,3 @@ def get_seed(seed: Optional[int] = None) -> int: random.seed(None) return random.randint(0, pow(10, seeddigits) - 1) return seed - - -from worlds import AutoWorld - -auto_world = AutoWorld.World From f178d438b8f647d630b16588523729e2406113ba Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Wed, 14 Feb 2024 18:05:48 -0500 Subject: [PATCH 004/166] TUNIC: Fix duplicate entrance name in ER (#2818) --- worlds/tunic/er_rules.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index ee7cca4536..ad203e1e82 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -377,13 +377,13 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re # nmg: ice grapple through the big gold door, can do it both ways regions["Eastern Vault Fortress"].connect( connecting_region=regions["Eastern Vault Fortress Gold Door"], - name="Fortress Gold Door", + name="Fortress to Gold Door", rule=lambda state: state.has_all({"Activate Eastern Vault West Fuses", "Activate Eastern Vault East Fuse"}, player) or has_ice_grapple_logic(False, state, player, options, ability_unlocks)) regions["Eastern Vault Fortress Gold Door"].connect( connecting_region=regions["Eastern Vault Fortress"], - name="Fortress Gold Door", + name="Gold Door to Fortress", rule=lambda state: has_ice_grapple_logic(True, state, player, options, ability_unlocks)) regions["Fortress Grave Path"].connect( From 475e803500bcae5db41329d26bde7c35823fc994 Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Wed, 14 Feb 2024 15:23:05 -0800 Subject: [PATCH 005/166] Core: APPatch interface (#2808) define interface that has only the bare minimum required for `Patch.create_rom_file` --- Patch.py | 4 ++-- worlds/Files.py | 20 ++++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Patch.py b/Patch.py index 113d0658c6..0915457000 100644 --- a/Patch.py +++ b/Patch.py @@ -8,7 +8,7 @@ if __name__ == "__main__": import ModuleUpdate ModuleUpdate.update() -from worlds.Files import AutoPatchRegister, APDeltaPatch +from worlds.Files import AutoPatchRegister, APPatch class RomMeta(TypedDict): @@ -20,7 +20,7 @@ class RomMeta(TypedDict): def create_rom_file(patch_file: str) -> Tuple[RomMeta, str]: auto_handler = AutoPatchRegister.get_handler(patch_file) if auto_handler: - handler: APDeltaPatch = auto_handler(patch_file) + handler: APPatch = auto_handler(patch_file) target = os.path.splitext(patch_file)[0]+handler.result_file_ending handler.patch(target) return {"server": handler.server, diff --git a/worlds/Files.py b/worlds/Files.py index 52d3c7da1d..336a309093 100644 --- a/worlds/Files.py +++ b/worlds/Files.py @@ -1,5 +1,6 @@ from __future__ import annotations +import abc import json import zipfile import os @@ -15,7 +16,7 @@ del threading del os -class AutoPatchRegister(type): +class AutoPatchRegister(abc.ABCMeta): patch_types: ClassVar[Dict[str, AutoPatchRegister]] = {} file_endings: ClassVar[Dict[str, AutoPatchRegister]] = {} @@ -112,14 +113,25 @@ class APContainer: } -class APDeltaPatch(APContainer, metaclass=AutoPatchRegister): - """An APContainer that additionally has delta.bsdiff4 +class APPatch(APContainer, abc.ABC, metaclass=AutoPatchRegister): + """ + An abstract `APContainer` that defines the requirements for an object + to be used by the `Patch.create_rom_file` function. + """ + result_file_ending: str = ".sfc" + + @abc.abstractmethod + def patch(self, target: str) -> None: + """ create the output file with the file name `target` """ + + +class APDeltaPatch(APPatch): + """An APPatch that additionally has delta.bsdiff4 containing a delta patch to get the desired file, often a rom.""" hash: Optional[str] # base checksum of source file patch_file_ending: str = "" delta: Optional[bytes] = None - result_file_ending: str = ".sfc" source_data: bytes def __init__(self, *args: Any, patched_path: str = "", **kwargs: Any) -> None: From 2c38b9fd511dc6ab8b46ccc6434a9119093ae548 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 15 Feb 2024 15:03:10 -0500 Subject: [PATCH 006/166] Lingo: Various item/location renames (#2746) ## What is this fixing or adding? - Roof MASTERY panels are now technically in individual regions with more descriptive names, so they can be displayed better on the tracker. - Orange Tower Seventh Floor - Mastery has been renamed to simply Mastery. - The Optimistic is its own region now. - The misnamed CEILING in Room Room has been fixed. - The misnamed CHEESE in Challenge Room has been fixed. - The misnamed SOUND in Outside the Bold has been fixed. - "The Bearer - Shortcut to The Bold" is now "The Bearer - Entrance". - HUB ROOM - NEAR, FAR and the Warts Straw and Leaf Feel Areas have now been semantically combined into the "Symmetry Room". They are still logically three separate regions. - The FACTS chain in Challenge Room has been reindexed, and the full chain panel is now indicated as such. - The Room Room floors have been reindexed. - Panels in The Observant are now named by their questions, not answers. - Added a (1) subscript to several panels in Orange Tower Fourth Floor, Outside The Initiated, and The Seeker. The validate_config.rb script has also been updated to check that all items and locations have an ID. This change should not impact generation logic at all. It is just changing item and location names. --- worlds/lingo/data/LL1.yaml | 350 ++++++++++++++------------ worlds/lingo/data/ids.yaml | 124 ++++----- worlds/lingo/player_logic.py | 2 +- worlds/lingo/test/TestProgressive.py | 24 +- worlds/lingo/utils/validate_config.rb | 29 ++- 5 files changed, 300 insertions(+), 229 deletions(-) diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index 2e18766c01..23afb2b445 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -278,10 +278,10 @@ tag: forbid check: True achievement: The Seeker - BEAR: + BEAR (1): id: Heteronym Room/Panel_bear_bear tag: midwhite - MINE: + MINE (1): id: Heteronym Room/Panel_mine_mine tag: double midwhite subtag: left @@ -297,7 +297,7 @@ DOES: id: Heteronym Room/Panel_does_does tag: midwhite - MOBILE: + MOBILE (1): id: Heteronym Room/Panel_mobile_mobile tag: double midwhite subtag: left @@ -399,8 +399,7 @@ door: Crossroads Entrance The Tenacious: door: Tenacious Entrance - Warts Straw Area: - door: Symmetry Door + Near Far Area: True Hedge Maze: door: Shortcut to Hedge Maze Orange Tower First Floor: @@ -427,14 +426,6 @@ id: Palindrome Room/Panel_slaughter_laughter colors: red tag: midred - NEAR: - id: Symmetry Room/Panel_near_far - colors: black - tag: botblack - FAR: - id: Symmetry Room/Panel_far_near - colors: black - tag: botblack TRACE: id: Maze Room/Panel_trace_trace tag: midwhite @@ -477,14 +468,6 @@ group: Entrances to The Tenacious panels: - SLAUGHTER - Symmetry Door: - id: - - Symmetry Room Area Doors/Door_near_far - - Symmetry Room Area Doors/Door_far_near - group: Symmetry Doors - panels: - - NEAR - - FAR Shortcut to Hedge Maze: id: Maze Area Doors/Door_trace_trace group: Hedge Maze Doors @@ -548,7 +531,7 @@ id: Lingo Room/Panel_shortcut colors: yellow tag: midyellow - PILGRIMAGE: + PILGRIM: id: Lingo Room/Panel_pilgrim colors: blue tag: midblue @@ -569,7 +552,7 @@ Exit: event: True panels: - - PILGRIMAGE + - PILGRIM Pilgrim Room: entrances: The Seeker: @@ -755,7 +738,7 @@ panels: - TURN - room: Orange Tower Fourth Floor - panel: RUNT + panel: RUNT (1) Words Sword Door: id: - Shuffle Room Area Doors/Door_words_shuffle_3 @@ -962,11 +945,36 @@ - LEVEL (White) - RACECAR (White) - SOLOS (White) + Near Far Area: + entrances: + Hub Room: True + Warts Straw Area: + door: Door + panels: + NEAR: + id: Symmetry Room/Panel_near_far + colors: black + tag: botblack + FAR: + id: Symmetry Room/Panel_far_near + colors: black + tag: botblack + doors: + Door: + id: + - Symmetry Room Area Doors/Door_near_far + - Symmetry Room Area Doors/Door_far_near + group: Symmetry Doors + item_name: Symmetry Room - Near Far Door + location_name: Symmetry Room - NEAR, FAR + panels: + - NEAR + - FAR Warts Straw Area: entrances: - Hub Room: - room: Hub Room - door: Symmetry Door + Near Far Area: + room: Near Far Area + door: Door Leaf Feel Area: door: Door panels: @@ -984,6 +992,8 @@ - Symmetry Room Area Doors/Door_warts_straw - Symmetry Room Area Doors/Door_straw_warts group: Symmetry Doors + item_name: Symmetry Room - Warts Straw Door + location_name: Symmetry Room - WARTS, STRAW panels: - WARTS - STRAW @@ -1009,6 +1019,8 @@ - Symmetry Room Area Doors/Door_leaf_feel - Symmetry Room Area Doors/Door_feel_leaf group: Symmetry Doors + item_name: Symmetry Room - Leaf Feel Door + location_name: Symmetry Room - LEAF, FEEL panels: - LEAF - FEEL @@ -1120,7 +1132,7 @@ tag: forbid required_panel: - room: Outside The Bold - panel: MOUTH + panel: SOUND - room: Outside The Bold panel: YEAST - room: Outside The Bold @@ -1436,7 +1448,7 @@ entrances: The Perceptive: True panels: - NAPS: + SPAN: id: Naps Room/Panel_naps_span colors: black tag: midblack @@ -1462,7 +1474,7 @@ location_name: The Fearless - First Floor Puzzles group: Fearless Doors panels: - - NAPS + - SPAN - TEAM - TEEM - IMPATIENT @@ -1564,11 +1576,11 @@ required_door: door: Stairs achievement: The Observant - BACK: + FOUR (1): id: Look Room/Panel_four_back colors: green tag: forbid - SIDE: + FOUR (2): id: Look Room/Panel_four_side colors: green tag: forbid @@ -1578,87 +1590,87 @@ hunt: True required_door: door: Backside Door - STAIRS: + SIX: id: Look Room/Panel_six_stairs colors: green tag: forbid - WAYS: + FOUR (3): id: Look Room/Panel_four_ways colors: green tag: forbid - "ON": + TWO (1): id: Look Room/Panel_two_on colors: green tag: forbid - UP: + TWO (2): id: Look Room/Panel_two_up colors: green tag: forbid - SWIMS: + FIVE: id: Look Room/Panel_five_swims colors: green tag: forbid - UPSTAIRS: + BELOW (1): id: Look Room/Panel_eight_upstairs colors: green tag: forbid required_door: door: Stairs - TOIL: + BLUE: id: Look Room/Panel_blue_toil colors: green tag: forbid required_door: door: Stairs - STOP: + BELOW (2): id: Look Room/Panel_four_stop colors: green tag: forbid required_door: door: Stairs - TOP: + MINT (1): id: Look Room/Panel_aqua_top colors: green tag: forbid required_door: door: Stairs - HI: + ESACREWOL: id: Look Room/Panel_blue_hi colors: green tag: forbid required_door: door: Stairs - HI (2): + EULB: id: Look Room/Panel_blue_hi2 colors: green tag: forbid required_door: door: Stairs - "31": + NUMBERS (1): id: Look Room/Panel_numbers_31 colors: green tag: forbid required_door: door: Stairs - "52": + NUMBERS (2): id: Look Room/Panel_numbers_52 colors: green tag: forbid required_door: door: Stairs - OIL: + MINT (2): id: Look Room/Panel_aqua_oil colors: green tag: forbid required_door: door: Stairs - BACKSIDE (GREEN): + GREEN (1): id: Look Room/Panel_eight_backside colors: green tag: forbid required_door: door: Stairs - SIDEWAYS: + GREEN (2): id: Look Room/Panel_eight_sideways colors: green tag: forbid @@ -1669,13 +1681,13 @@ id: Maze Area Doors/Door_backside group: Backside Doors panels: - - BACK - - SIDE + - FOUR (1) + - FOUR (2) Stairs: id: Maze Area Doors/Door_stairs group: Observant Doors panels: - - STAIRS + - SIX The Incomparable: entrances: The Observant: True # Assuming that access to The Observant includes access to the right entrance @@ -2005,7 +2017,7 @@ Courtyard: True Roof: True # through the sunwarp panels: - RUNT: + RUNT (1): id: Shuffle Room/Panel_turn_runt2 colors: yellow tag: midyellow @@ -2219,6 +2231,7 @@ - Master Room Doors/Door_master_down - Master Room Doors/Door_master_down2 skip_location: True + item_name: Mastery panels: - THE MASTER Mastery Panels: @@ -2235,25 +2248,25 @@ panel: MASTERY - room: Hedge Maze panel: MASTERY (1) - - room: Roof - panel: MASTERY (1) - - room: Roof - panel: MASTERY (2) + - room: Behind A Smile + panel: MASTERY + - room: Sixteen Colorful Squares + panel: MASTERY - MASTERY - room: Hedge Maze panel: MASTERY (2) - - room: Roof - panel: MASTERY (3) - - room: Roof - panel: MASTERY (4) - - room: Roof - panel: MASTERY (5) + - room: Among Treetops + panel: MASTERY + - room: Horizon's Edge + panel: MASTERY + - room: Beneath The Lookout + panel: MASTERY - room: Elements Area panel: MASTERY - room: Pilgrim Antechamber panel: MASTERY - - room: Roof - panel: MASTERY (6) + - room: Rooftop Staircase + panel: MASTERY paintings: - id: map_painting2 orientation: north @@ -2265,52 +2278,75 @@ Crossroads: room: Crossroads door: Roof Access + Behind A Smile: + entrances: + Roof: True panels: - MASTERY (1): + MASTERY: id: Master Room/Panel_mastery_mastery6 tag: midwhite hunt: True required_door: room: Orange Tower Seventh Floor door: Mastery - MASTERY (2): - id: Master Room/Panel_mastery_mastery7 - tag: midwhite - hunt: True - required_door: - room: Orange Tower Seventh Floor - door: Mastery - MASTERY (3): - id: Master Room/Panel_mastery_mastery10 - tag: midwhite - hunt: True - required_door: - room: Orange Tower Seventh Floor - door: Mastery - MASTERY (4): - id: Master Room/Panel_mastery_mastery11 - tag: midwhite - hunt: True - required_door: - room: Orange Tower Seventh Floor - door: Mastery - MASTERY (5): - id: Master Room/Panel_mastery_mastery12 - tag: midwhite - hunt: True - required_door: - room: Orange Tower Seventh Floor - door: Mastery - MASTERY (6): - id: Master Room/Panel_mastery_mastery15 - tag: midwhite - hunt: True - required_door: - room: Orange Tower Seventh Floor - door: Mastery STAIRCASE: id: Open Areas/Panel_staircase tag: midwhite + Sixteen Colorful Squares: + entrances: + Roof: True + panels: + MASTERY: + id: Master Room/Panel_mastery_mastery7 + tag: midwhite + hunt: True + required_door: + room: Orange Tower Seventh Floor + door: Mastery + Among Treetops: + entrances: + Roof: True + panels: + MASTERY: + id: Master Room/Panel_mastery_mastery10 + tag: midwhite + hunt: True + required_door: + room: Orange Tower Seventh Floor + door: Mastery + Horizon's Edge: + entrances: + Roof: True + panels: + MASTERY: + id: Master Room/Panel_mastery_mastery11 + tag: midwhite + hunt: True + required_door: + room: Orange Tower Seventh Floor + door: Mastery + Beneath The Lookout: + entrances: + Roof: True + panels: + MASTERY: + id: Master Room/Panel_mastery_mastery12 + tag: midwhite + hunt: True + required_door: + room: Orange Tower Seventh Floor + door: Mastery + Rooftop Staircase: + entrances: + Roof: True + panels: + MASTERY: + id: Master Room/Panel_mastery_mastery15 + tag: midwhite + hunt: True + required_door: + room: Orange Tower Seventh Floor + door: Mastery Orange Tower Basement: entrances: Orange Tower Sixth Floor: @@ -2632,9 +2668,7 @@ tag: forbid doors: Progress Door: - id: - - Doorway Room Doors/Door_gray - - Doorway Room Doors/Door_gray2 # See comment below + id: Doorway Room Doors/Door_gray item_name: The Colorful - Gray Door location_name: The Colorful - Gray group: Colorful Doors @@ -2784,6 +2818,7 @@ door: Exit Eight Alcove: door: Eight Door + The Optimistic: True panels: SEVEN (1): id: Backside Room/Panel_seven_seven_5 @@ -2833,21 +2868,11 @@ id: Rhyme Room/Panel_locked_knocked colors: purple tag: midpurp - BACKSIDE: - id: Backside Room/Panel_backside_1 - tag: midwhite - The Optimistic: - id: Countdown Panels/Panel_optimistic_optimistic - check: True - tag: forbid - required_door: - door: Backsides - achievement: The Optimistic - PAST: + PAST (1): id: Shuffle Room/Panel_past_present colors: brown tag: botbrown - FUTURE: + FUTURE (1): id: Shuffle Room/Panel_future_present colors: - brown @@ -2944,17 +2969,6 @@ panel: GREEN - room: Outside The Agreeable panel: PURPLE - Backsides: - event: True - panels: - - room: The Observant - panel: BACKSIDE - - room: Yellow Backside Area - panel: BACKSIDE - - room: Directional Gallery - panel: BACKSIDE - - room: The Bearer - panel: BACKSIDE Eight Door: id: Red Blue Purple Room Area Doors/Door_a_strands2 skip_location: True @@ -3064,6 +3078,28 @@ id: Rhyme Room/Panel_bed_dead colors: purple tag: toppurp + The Optimistic: + entrances: + Outside The Initiated: True + panels: + BACKSIDE: + id: Backside Room/Panel_backside_1 + tag: midwhite + Achievement: + id: Countdown Panels/Panel_optimistic_optimistic + check: True + tag: forbid + required_panel: + - panel: BACKSIDE + - room: The Observant + panel: BACKSIDE + - room: Yellow Backside Area + panel: BACKSIDE + - room: Directional Gallery + panel: BACKSIDE + - room: The Bearer + panel: BACKSIDE + achievement: The Optimistic The Traveled: entrances: Hub Room: @@ -3164,7 +3200,7 @@ Outside The Undeterred: True Crossroads: True Hedge Maze: True - Outside The Initiated: True # backside + The Optimistic: True # backside Directional Gallery: True # backside Yellow Backside Area: True The Bearer: @@ -3181,7 +3217,7 @@ door: Shortcut to The Steady The Bearer: room: The Bearer - door: Shortcut to The Bold + door: Entrance Directional Gallery: # There is a painting warp here from the Directional Gallery, but it # only appears when the sixes are revealed. It could be its own item if @@ -3252,7 +3288,7 @@ tag: midwhite required_door: door: Stargazer Door - MOUTH: + SOUND: id: Cross Room/Panel_mouth_south colors: purple tag: midpurp @@ -3596,7 +3632,7 @@ id: Blue Room/Panel_bone_skeleton colors: blue tag: botblue - EYE: + EYE (1): id: Blue Room/Panel_mouth_face colors: blue tag: double botblue @@ -4115,7 +4151,7 @@ The Bearer: entrances: Outside The Bold: - door: Shortcut to The Bold + door: Entrance Orange Tower Fifth Floor: room: Art Gallery door: Exit @@ -4192,7 +4228,7 @@ - yellow tag: mid red yellow doors: - Shortcut to The Bold: + Entrance: id: Red Blue Purple Room Area Doors/Door_middle_middle panels: - MIDDLE @@ -4321,21 +4357,21 @@ door: Side Area Shortcut Roof: True panels: - SNOW: + SMILE: id: Cross Room/Panel_smile_lime colors: - red - yellow tag: mid yellow red - SMILE: + required_panel: + room: The Bearer (North) + panel: WARTS + SNOW: id: Cross Room/Panel_snow_won colors: - red - yellow tag: mid red yellow - required_panel: - room: The Bearer (North) - panel: WARTS doors: Side Area Shortcut: event: True @@ -4414,7 +4450,7 @@ - room: The Bearer (West) panel: SMILE - room: Outside The Bold - panel: MOUTH + panel: SOUND - room: Outside The Bold panel: YEAST - room: Outside The Bold @@ -6129,7 +6165,7 @@ id: Painting Room/Panel_our_four colors: blue tag: midblue - ONE ROAD MANY TURNS: + ORDER: id: Painting Room/Panel_order_onepathmanyturns tag: forbid colors: @@ -6186,7 +6222,7 @@ include_reduce: True item_name: Orange Tower Fifth Floor - Quadruple Intersection panels: - - ONE ROAD MANY TURNS + - ORDER paintings: - id: smile_painting_3 orientation: west @@ -6678,7 +6714,7 @@ - room: Rhyme Room (Target) panel: PISTOL - room: Rhyme Room (Target) - panel: QUARTZ + panel: GEM Rhyme Room (Target): entrances: Rhyme Room (Smiley): # one-way @@ -6706,7 +6742,7 @@ tag: syn rhyme subtag: top link: rhyme CRYSTAL - QUARTZ: + GEM: id: Double Room/Panel_crystal_syn colors: purple tag: syn rhyme @@ -6731,7 +6767,7 @@ group: Rhyme Room Doors panels: - PISTOL - - QUARTZ + - GEM - INNOVATIVE (Top) - INNOVATIVE (Bottom) paintings: @@ -6789,22 +6825,18 @@ id: Panel Room/Panel_room_floor_5 colors: gray tag: forbid - FLOOR (7): + FLOOR (6): id: Panel Room/Panel_room_floor_7 colors: gray tag: forbid - FLOOR (8): + FLOOR (7): id: Panel Room/Panel_room_floor_8 colors: gray tag: forbid - FLOOR (9): + FLOOR (8): id: Panel Room/Panel_room_floor_9 colors: gray tag: forbid - FLOOR (10): - id: Panel Room/Panel_room_floor_10 - colors: gray - tag: forbid CEILING (1): id: Panel Room/Panel_room_ceiling_1 colors: gray @@ -6825,6 +6857,10 @@ id: Panel Room/Panel_room_ceiling_5 colors: gray tag: forbid + CEILING (6): + id: Panel Room/Panel_room_floor_10 + colors: gray + tag: forbid WALL (1): id: Panel Room/Panel_room_wall_1 colors: gray @@ -7083,7 +7119,7 @@ id: Hangry Room/Panel_red_top_3 colors: red tag: topred - FLUMMOXED: + FLUSTERED: id: Hangry Room/Panel_red_top_4 colors: red tag: topred @@ -7586,7 +7622,7 @@ - black - blue tag: chain mid black blue - BREAD: + CHEESE: id: Challenge Room/Panel_bread_mold colors: brown tag: double botbrown @@ -7633,7 +7669,7 @@ id: Challenge Room/Panel_double_anagram_5 colors: yellow tag: midyellow - FACTS: + FACTS (Chain): id: Challenge Room/Panel_facts colors: - red @@ -7643,18 +7679,18 @@ id: Challenge Room/Panel_facts2 colors: red tag: forbid - FACTS (3): + FACTS (2): id: Challenge Room/Panel_facts3 tag: forbid - FACTS (4): + FACTS (3): id: Challenge Room/Panel_facts4 colors: blue tag: forbid - FACTS (5): + FACTS (4): id: Challenge Room/Panel_facts5 colors: blue tag: forbid - FACTS (6): + FACTS (5): id: Challenge Room/Panel_facts6 colors: blue tag: forbid diff --git a/worlds/lingo/data/ids.yaml b/worlds/lingo/data/ids.yaml index 56c22ad1be..4cad948555 100644 --- a/worlds/lingo/data/ids.yaml +++ b/worlds/lingo/data/ids.yaml @@ -31,12 +31,12 @@ panels: LIES: 444408 The Seeker: Achievement: 444409 - BEAR: 444410 - MINE: 444411 + BEAR (1): 444410 + MINE (1): 444411 MINE (2): 444412 BOW: 444413 DOES: 444414 - MOBILE: 444415 + MOBILE (1): 444415 MOBILE (2): 444416 DESERT: 444417 DESSERT: 444418 @@ -57,8 +57,6 @@ panels: Hub Room: ORDER: 444432 SLAUGHTER: 444433 - NEAR: 444434 - FAR: 444435 TRACE: 444436 RAT: 444437 OPEN: 444438 @@ -72,7 +70,7 @@ panels: EIGHT: 444445 Pilgrim Antechamber: HOT CRUST: 444446 - PILGRIMAGE: 444447 + PILGRIM: 444447 MASTERY: 444448 Pilgrim Room: THIS: 444449 @@ -123,6 +121,9 @@ panels: RACECAR (White): 444489 SOLOS (White): 444490 Achievement: 444491 + Near Far Area: + NEAR: 444434 + FAR: 444435 Warts Straw Area: WARTS: 444492 STRAW: 444493 @@ -187,7 +188,7 @@ panels: Achievement: 444546 GAZE: 444547 The Fearless (First Floor): - NAPS: 444548 + SPAN: 444548 TEAM: 444549 TEEM: 444550 IMPATIENT: 444551 @@ -208,25 +209,25 @@ panels: EVEN: 444564 The Observant: Achievement: 444565 - BACK: 444566 - SIDE: 444567 + FOUR (1): 444566 + FOUR (2): 444567 BACKSIDE: 444568 - STAIRS: 444569 - WAYS: 444570 - 'ON': 444571 - UP: 444572 - SWIMS: 444573 - UPSTAIRS: 444574 - TOIL: 444575 - STOP: 444576 - TOP: 444577 - HI: 444578 - HI (2): 444579 - '31': 444580 - '52': 444581 - OIL: 444582 - BACKSIDE (GREEN): 444583 - SIDEWAYS: 444584 + SIX: 444569 + FOUR (3): 444570 + TWO (1): 444571 + TWO (2): 444572 + FIVE: 444573 + BELOW (1): 444574 + BLUE: 444575 + BELOW (2): 444576 + MINT (1): 444577 + ESACREWOL: 444578 + EULB: 444579 + NUMBERS (1): 444580 + NUMBERS (2): 444581 + MINT (2): 444582 + GREEN (1): 444583 + GREEN (2): 444584 The Incomparable: Achievement: 444585 A (One): 444586 @@ -254,7 +255,7 @@ panels: RED: 444605 DEER + WREN: 444606 Orange Tower Fourth Floor: - RUNT: 444607 + RUNT (1): 444607 RUNT (2): 444608 LEARNS + UNSEW: 444609 HOT CRUSTS: 444610 @@ -279,14 +280,19 @@ panels: THE END: 444620 THE MASTER: 444621 MASTERY: 444622 - Roof: - MASTERY (1): 444623 - MASTERY (2): 444624 - MASTERY (3): 444625 - MASTERY (4): 444626 - MASTERY (5): 444627 - MASTERY (6): 444628 + Behind A Smile: + MASTERY: 444623 STAIRCASE: 444629 + Sixteen Colorful Squares: + MASTERY: 444624 + Among Treetops: + MASTERY: 444625 + Horizon's Edge: + MASTERY: 444626 + Beneath The Lookout: + MASTERY: 444627 + Rooftop Staircase: + MASTERY: 444628 Orange Tower Basement: MASTERY: 444630 THE LIBRARY: 444631 @@ -341,16 +347,17 @@ panels: ORANGE: 444663 UNCOVER: 444664 OXEN: 444665 - BACKSIDE: 444666 - The Optimistic: 444667 - PAST: 444668 - FUTURE: 444669 + PAST (1): 444668 + FUTURE (1): 444669 FUTURE (2): 444670 PAST (2): 444671 PRESENT: 444672 SMILE: 444673 ANGERED: 444674 VOTE: 444675 + The Optimistic: + BACKSIDE: 444666 + Achievement: 444667 The Initiated: Achievement: 444676 DAUGHTER: 444677 @@ -400,7 +407,7 @@ panels: ZEN: 444719 SON: 444720 STARGAZER: 444721 - MOUTH: 444722 + SOUND: 444722 YEAST: 444723 WET: 444724 The Bold: @@ -442,7 +449,7 @@ panels: The Undeterred: Achievement: 444759 BONE: 444760 - EYE: 444761 + EYE (1): 444761 MOUTH: 444762 IRIS: 444763 EYE (2): 444764 @@ -524,8 +531,8 @@ panels: TENT: 444832 BOWL: 444833 The Bearer (West): - SNOW: 444834 - SMILE: 444835 + SMILE: 444834 + SNOW: 444835 Bearer Side Area: SHORTCUT: 444836 POTS: 444837 @@ -720,7 +727,7 @@ panels: TRUSTWORTHY: 444978 FREE: 444979 OUR: 444980 - ONE ROAD MANY TURNS: 444981 + ORDER: 444981 Art Gallery (Second Floor): HOUSE: 444982 PATH: 444983 @@ -778,7 +785,7 @@ panels: WILD: 445028 KID: 445029 PISTOL: 445030 - QUARTZ: 445031 + GEM: 445031 INNOVATIVE (Top): 445032 INNOVATIVE (Bottom): 445033 Room Room: @@ -792,15 +799,15 @@ panels: FLOOR (3): 445041 FLOOR (4): 445042 FLOOR (5): 445043 - FLOOR (7): 445044 - FLOOR (8): 445045 - FLOOR (9): 445046 - FLOOR (10): 445047 + FLOOR (6): 445044 + FLOOR (7): 445045 + FLOOR (8): 445046 CEILING (1): 445048 CEILING (2): 445049 CEILING (3): 445050 CEILING (4): 445051 CEILING (5): 445052 + CEILING (6): 445047 WALL (1): 445053 WALL (2): 445054 WALL (3): 445055 @@ -848,7 +855,7 @@ panels: PANDEMIC (1): 445100 TRINITY: 445101 CHEMISTRY: 445102 - FLUMMOXED: 445103 + FLUSTERED: 445103 PANDEMIC (2): 445104 COUNTERCLOCKWISE: 445105 FEARLESS: 445106 @@ -934,7 +941,7 @@ panels: CORNER: 445182 STRAWBERRIES: 445183 GRUB: 445184 - BREAD: 445185 + CHEESE: 445185 COLOR: 445186 WRITER: 445187 '02759': 445188 @@ -945,12 +952,12 @@ panels: DUCK LOGO: 445193 AVIAN GREEN: 445194 FEVER TEAR: 445195 - FACTS: 445196 + FACTS (Chain): 445196 FACTS (1): 445197 - FACTS (3): 445198 - FACTS (4): 445199 - FACTS (5): 445200 - FACTS (6): 445201 + FACTS (2): 445198 + FACTS (3): 445199 + FACTS (4): 445200 + FACTS (5): 445201 LAPEL SHEEP: 445202 doors: Starting Room: @@ -980,9 +987,6 @@ doors: Tenacious Entrance: item: 444426 location: 444433 - Symmetry Door: - item: 444428 - location: 445204 Shortcut to Hedge Maze: item: 444430 location: 444436 @@ -1039,6 +1043,10 @@ doors: location: 445210 White Palindromes: location: 445211 + Near Far Area: + Door: + item: 444428 + location: 445204 Warts Straw Area: Door: item: 444451 @@ -1292,7 +1300,7 @@ doors: item: 444539 location: 444806 The Bearer: - Shortcut to The Bold: + Entrance: item: 444540 location: 444820 Backside Door: diff --git a/worlds/lingo/player_logic.py b/worlds/lingo/player_logic.py index d87aa5672c..b2e5f77df1 100644 --- a/worlds/lingo/player_logic.py +++ b/worlds/lingo/player_logic.py @@ -196,7 +196,7 @@ class LingoPlayerLogic: ["Orange Tower Fourth Floor", "Hot Crusts Door"], ["Outside The Initiated", "Shortcut to Hub Room"], ["Orange Tower First Floor", "Shortcut to Hub Room"], ["Directional Gallery", "Shortcut to The Undeterred"], ["Orange Tower First Floor", "Salt Pepper Door"], ["Hub Room", "Crossroads Entrance"], - ["Color Hunt", "Shortcut to The Steady"], ["The Bearer", "Shortcut to The Bold"], ["Art Gallery", "Exit"], + ["Color Hunt", "Shortcut to The Steady"], ["The Bearer", "Entrance"], ["Art Gallery", "Exit"], ["The Tenacious", "Shortcut to Hub Room"], ["Outside The Agreeable", "Tenacious Entrance"] ] pilgrimage_reqs = AccessRequirements() diff --git a/worlds/lingo/test/TestProgressive.py b/worlds/lingo/test/TestProgressive.py index 0aaebe9319..081d6743a5 100644 --- a/worlds/lingo/test/TestProgressive.py +++ b/worlds/lingo/test/TestProgressive.py @@ -96,7 +96,7 @@ class TestProgressiveArtGallery(LingoTestBase): self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect_by_name(["Second Room - Exit Door", "Crossroads - Tower Entrance", @@ -105,7 +105,7 @@ class TestProgressiveArtGallery(LingoTestBase): self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) progressive_gallery_room = self.get_items_by_name("Progressive Art Gallery") @@ -115,7 +115,7 @@ class TestProgressiveArtGallery(LingoTestBase): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect(progressive_gallery_room[1]) @@ -123,7 +123,7 @@ class TestProgressiveArtGallery(LingoTestBase): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect(progressive_gallery_room[2]) @@ -131,7 +131,7 @@ class TestProgressiveArtGallery(LingoTestBase): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect(progressive_gallery_room[3]) @@ -139,7 +139,7 @@ class TestProgressiveArtGallery(LingoTestBase): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertTrue(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertTrue(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect_by_name("Orange Tower Fifth Floor - Quadruple Intersection") @@ -147,7 +147,7 @@ class TestProgressiveArtGallery(LingoTestBase): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertTrue(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertTrue(self.can_reach_location("Art Gallery - ORDER")) self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) @@ -162,7 +162,7 @@ class TestNoDoorsArtGallery(LingoTestBase): self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect_by_name("Yellow") @@ -170,7 +170,7 @@ class TestNoDoorsArtGallery(LingoTestBase): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect_by_name("Brown") @@ -178,7 +178,7 @@ class TestNoDoorsArtGallery(LingoTestBase): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect_by_name("Blue") @@ -186,7 +186,7 @@ class TestNoDoorsArtGallery(LingoTestBase): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertFalse(self.can_reach_location("Art Gallery - ORDER")) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) self.collect_by_name(["Orange", "Gray"]) @@ -194,5 +194,5 @@ class TestNoDoorsArtGallery(LingoTestBase): self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player)) - self.assertTrue(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS")) + self.assertTrue(self.can_reach_location("Art Gallery - ORDER")) self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player)) diff --git a/worlds/lingo/utils/validate_config.rb b/worlds/lingo/utils/validate_config.rb index 3ac49dc220..96ed9fcd66 100644 --- a/worlds/lingo/utils/validate_config.rb +++ b/worlds/lingo/utils/validate_config.rb @@ -8,7 +8,8 @@ require 'set' require 'yaml' configpath = ARGV[0] -mappath = ARGV[1] +idspath = ARGV[1] +mappath = ARGV[2] panels = Set["Countdown Panels/Panel_1234567890_wanderlust"] doors = Set["Naps Room Doors/Door_hider_new1", "Tower Room Area Doors/Door_wanderer_entrance"] @@ -46,6 +47,8 @@ painting_directives = Set["id", "enter_only", "exit_only", "orientation", "requi non_counting = 0 +ids = YAML.load_file(idspath) + config = YAML.load_file(configpath) config.each do |room_name, room| configured_rooms.add(room_name) @@ -162,6 +165,10 @@ config.each do |room_name, room| unless bad_subdirectives.empty? then puts "#{room_name} - #{panel_name} :::: Panel has the following invalid subdirectives: #{bad_subdirectives.join(", ")}" end + + unless ids.include?("panels") and ids["panels"].include?(room_name) and ids["panels"][room_name].include?(panel_name) + puts "#{room_name} - #{panel_name} :::: Panel is missing a location ID" + end end (room["doors"] || {}).each do |door_name, door| @@ -229,6 +236,18 @@ config.each do |room_name, room| unless bad_subdirectives.empty? then puts "#{room_name} - #{door_name} :::: Door has the following invalid subdirectives: #{bad_subdirectives.join(", ")}" end + + unless door["skip_item"] or door["event"] + unless ids.include?("doors") and ids["doors"].include?(room_name) and ids["doors"][room_name].include?(door_name) and ids["doors"][room_name][door_name].include?("item") + puts "#{room_name} - #{door_name} :::: Door is missing an item ID" + end + end + + unless door["skip_location"] or door["event"] + unless ids.include?("doors") and ids["doors"].include?(room_name) and ids["doors"][room_name].include?(door_name) and ids["doors"][room_name][door_name].include?("location") + puts "#{room_name} - #{door_name} :::: Door is missing a location ID" + end + end end (room["paintings"] || []).each do |painting| @@ -281,6 +300,10 @@ config.each do |room_name, room| mentioned_doors.add("#{room_name} - #{door}") end end + + unless ids.include?("progression") and ids["progression"].include?(progression_name) + puts "#{room_name} - #{progression_name} :::: Progression is missing an item ID" + end end end @@ -303,6 +326,10 @@ door_groups.each do |group,num| if num == 1 then puts "Door group \"#{group}\" only has one door in it" end + + unless ids.include?("door_groups") and ids["door_groups"].include?(group) + puts "#{group} :::: Door group is missing an item ID" + end end slashed_rooms = configured_rooms.select do |room| From 057e3723256ed3398083526b53df7669224c51ba Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Thu, 15 Feb 2024 13:04:20 -0700 Subject: [PATCH 007/166] Pokemon Emerald: Shuffle initial TMs for diverse_balanced option (#2758) --- worlds/pokemon_emerald/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/worlds/pokemon_emerald/__init__.py b/worlds/pokemon_emerald/__init__.py index 5d50e0db96..7a7596c096 100644 --- a/worlds/pokemon_emerald/__init__.py +++ b/worlds/pokemon_emerald/__init__.py @@ -279,6 +279,7 @@ class PokemonEmeraldWorld(World): def refresh_tm_choices() -> None: fill_item_candidates_by_category["TM"] = all_tm_choices.copy() self.random.shuffle(fill_item_candidates_by_category["TM"]) + refresh_tm_choices() # Create items for item in default_itempool: From 9805bf92e4d66148fc7b240bed49f32213dc7c4a Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Thu, 15 Feb 2024 23:34:29 +0100 Subject: [PATCH 008/166] Core: fix comment that did more harm than good (#2826) --- worlds/AutoWorld.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index fdc50acc55..e8d48df58c 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -438,7 +438,7 @@ class World(metaclass=AutoWorldRegister): def get_pre_fill_items(self) -> List["Item"]: return [] - # following methods should not need to be overridden. + # these two methods can be extended for pseudo-items on state def collect(self, state: "CollectionState", item: "Item") -> bool: name = self.collect_item(state, item) if name: From 3869a25944ae0dc6a524b15c35cd8f280fc63a82 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Thu, 15 Feb 2024 16:49:52 -0600 Subject: [PATCH 009/166] Tests: assign the world to WorldTestBase, and a default player field (#2385) * Tests: assign the World to WorldTestBase and add a player field (because I like typing self.player far more than random 1's all over the place) * more accurate docstring for world and multiworld * use self.player within the class --- test/bases.py | 38 ++++++++++++++---------- worlds/messenger/test/__init__.py | 3 +- worlds/messenger/test/test_locations.py | 2 +- worlds/messenger/test/test_shop.py | 8 ++--- worlds/messenger/test/test_shop_chest.py | 14 ++++----- 5 files changed, 37 insertions(+), 28 deletions(-) diff --git a/test/bases.py b/test/bases.py index 7ce12cc7b7..2d4111d193 100644 --- a/test/bases.py +++ b/test/bases.py @@ -7,7 +7,7 @@ from argparse import Namespace from Generate import get_seed_name from test.general import gen_steps from worlds import AutoWorld -from worlds.AutoWorld import call_all +from worlds.AutoWorld import World, call_all from BaseClasses import Location, MultiWorld, CollectionState, ItemClassification, Item from worlds.alttp.Items import ItemFactory @@ -105,9 +105,15 @@ class TestBase(unittest.TestCase): class WorldTestBase(unittest.TestCase): options: typing.Dict[str, typing.Any] = {} + """Define options that should be used when setting up this TestBase.""" multiworld: MultiWorld + """The constructed MultiWorld instance after setup.""" + world: World + """The constructed World instance after setup.""" + player: typing.ClassVar[int] = 1 - game: typing.ClassVar[str] # define game name in subclass, example "Secret of Evermore" + game: typing.ClassVar[str] + """Define game name in subclass, example "Secret of Evermore".""" auto_construct: typing.ClassVar[bool] = True """ automatically set up a world for each test in this class """ memory_leak_tested: typing.ClassVar[bool] = False @@ -150,8 +156,8 @@ class WorldTestBase(unittest.TestCase): if not hasattr(self, "game"): raise NotImplementedError("didn't define game name") self.multiworld = MultiWorld(1) - self.multiworld.game[1] = self.game - self.multiworld.player_name = {1: "Tester"} + self.multiworld.game[self.player] = self.game + self.multiworld.player_name = {self.player: "Tester"} self.multiworld.set_seed(seed) self.multiworld.state = CollectionState(self.multiworld) random.seed(self.multiworld.seed) @@ -159,9 +165,10 @@ class WorldTestBase(unittest.TestCase): args = Namespace() for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].options_dataclass.type_hints.items(): setattr(args, name, { - 1: option.from_any(self.options.get(name, getattr(option, "default"))) + 1: option.from_any(self.options.get(name, option.default)) }) self.multiworld.set_options(args) + self.world = self.multiworld.worlds[self.player] for step in gen_steps: call_all(self.multiworld, step) @@ -220,19 +227,19 @@ class WorldTestBase(unittest.TestCase): def can_reach_location(self, location: str) -> bool: """Determines if the current state can reach the provided location name""" - return self.multiworld.state.can_reach(location, "Location", 1) + return self.multiworld.state.can_reach(location, "Location", self.player) def can_reach_entrance(self, entrance: str) -> bool: """Determines if the current state can reach the provided entrance name""" - return self.multiworld.state.can_reach(entrance, "Entrance", 1) + return self.multiworld.state.can_reach(entrance, "Entrance", self.player) def can_reach_region(self, region: str) -> bool: """Determines if the current state can reach the provided region name""" - return self.multiworld.state.can_reach(region, "Region", 1) + return self.multiworld.state.can_reach(region, "Region", self.player) def count(self, item_name: str) -> int: """Returns the amount of an item currently in state""" - return self.multiworld.state.count(item_name, 1) + return self.multiworld.state.count(item_name, self.player) def assertAccessDependency(self, locations: typing.List[str], @@ -246,10 +253,11 @@ class WorldTestBase(unittest.TestCase): self.collect_all_but(all_items, state) if only_check_listed: for location in locations: - self.assertFalse(state.can_reach(location, "Location", 1), f"{location} is reachable without {all_items}") + self.assertFalse(state.can_reach(location, "Location", self.player), + f"{location} is reachable without {all_items}") else: for location in self.multiworld.get_locations(): - loc_reachable = state.can_reach(location, "Location", 1) + loc_reachable = state.can_reach(location, "Location", self.player) self.assertEqual(loc_reachable, location.name not in locations, f"{location.name} is reachable without {all_items}" if loc_reachable else f"{location.name} is not reachable without {all_items}") @@ -258,7 +266,7 @@ class WorldTestBase(unittest.TestCase): for item in items: state.collect(item) for location in locations: - self.assertTrue(state.can_reach(location, "Location", 1), + self.assertTrue(state.can_reach(location, "Location", self.player), f"{location} not reachable with {item_names}") for item in items: state.remove(item) @@ -285,7 +293,7 @@ class WorldTestBase(unittest.TestCase): if not (self.run_default_tests and self.constructed): return with self.subTest("Game", game=self.game): - excluded = self.multiworld.worlds[1].options.exclude_locations.value + excluded = self.multiworld.worlds[self.player].options.exclude_locations.value state = self.multiworld.get_all_state(False) for location in self.multiworld.get_locations(): if location.name not in excluded: @@ -302,7 +310,7 @@ class WorldTestBase(unittest.TestCase): return with self.subTest("Game", game=self.game): state = CollectionState(self.multiworld) - locations = self.multiworld.get_reachable_locations(state, 1) + locations = self.multiworld.get_reachable_locations(state, self.player) self.assertGreater(len(locations), 0, "Need to be able to reach at least one location to get started.") @@ -328,7 +336,7 @@ class WorldTestBase(unittest.TestCase): for location in sphere: if location.item: state.collect(location.item, True, location) - return self.multiworld.has_beaten_game(state, 1) + return self.multiworld.has_beaten_game(state, self.player) with self.subTest("Game", game=self.game, seed=self.multiworld.seed): distribute_items_restrictive(self.multiworld) diff --git a/worlds/messenger/test/__init__.py b/worlds/messenger/test/__init__.py index 7ab1e11781..f3fcd4ae2d 100644 --- a/worlds/messenger/test/__init__.py +++ b/worlds/messenger/test/__init__.py @@ -1,6 +1,7 @@ from test.TestBase import WorldTestBase +from .. import MessengerWorld class MessengerTestBase(WorldTestBase): game = "The Messenger" - player: int = 1 + world: MessengerWorld diff --git a/worlds/messenger/test/test_locations.py b/worlds/messenger/test/test_locations.py index 0c330be4bd..627d58c290 100644 --- a/worlds/messenger/test/test_locations.py +++ b/worlds/messenger/test/test_locations.py @@ -12,5 +12,5 @@ class LocationsTest(MessengerTestBase): return False def test_locations_exist(self) -> None: - for location in self.multiworld.worlds[1].location_name_to_id: + for location in self.world.location_name_to_id: self.assertIsInstance(self.multiworld.get_location(location, self.player), MessengerLocation) diff --git a/worlds/messenger/test/test_shop.py b/worlds/messenger/test/test_shop.py index afb1b32b88..ee7e82d6cd 100644 --- a/worlds/messenger/test/test_shop.py +++ b/worlds/messenger/test/test_shop.py @@ -17,7 +17,7 @@ class ShopCostTest(MessengerTestBase): self.assertFalse(self.can_reach_location(loc)) def test_shop_prices(self) -> None: - prices: Dict[str, int] = self.multiworld.worlds[self.player].shop_prices + prices: Dict[str, int] = self.world.shop_prices for loc, price in prices.items(): with self.subTest("prices", loc=loc): self.assertLessEqual(price, self.multiworld.get_location(f"The Shop - {loc}", self.player).cost) @@ -51,7 +51,7 @@ class ShopCostMinTest(ShopCostTest): } def test_shop_rules(self) -> None: - if self.multiworld.worlds[self.player].total_shards: + if self.world.total_shards: super().test_shop_rules() else: for loc in SHOP_ITEMS: @@ -85,7 +85,7 @@ class PlandoTest(MessengerTestBase): with self.subTest("has cost", loc=loc): self.assertFalse(self.can_reach_location(loc)) - prices = self.multiworld.worlds[self.player].shop_prices + prices = self.world.shop_prices for loc, price in prices.items(): with self.subTest("prices", loc=loc): if loc == "Karuta Plates": @@ -98,7 +98,7 @@ class PlandoTest(MessengerTestBase): self.assertTrue(loc.replace("The Shop - ", "") in SHOP_ITEMS) self.assertEqual(len(prices), len(SHOP_ITEMS)) - figures = self.multiworld.worlds[self.player].figurine_prices + figures = self.world.figurine_prices for loc, price in figures.items(): with self.subTest("figure prices", loc=loc): if loc == "Barmath'azel Figurine": diff --git a/worlds/messenger/test/test_shop_chest.py b/worlds/messenger/test/test_shop_chest.py index a34fa0fb96..f2030c63de 100644 --- a/worlds/messenger/test/test_shop_chest.py +++ b/worlds/messenger/test/test_shop_chest.py @@ -41,8 +41,8 @@ class HalfSealsRequired(MessengerTestBase): def test_seals_amount(self) -> None: """Should have 45 power seals in the item pool and half that required""" self.assertEqual(self.multiworld.total_seals[self.player], 45) - self.assertEqual(self.multiworld.worlds[self.player].total_seals, 45) - self.assertEqual(self.multiworld.worlds[self.player].required_seals, 22) + self.assertEqual(self.world.total_seals, 45) + self.assertEqual(self.world.required_seals, 22) total_seals = [seal for seal in self.multiworld.itempool if seal.name == "Power Seal"] required_seals = [seal for seal in total_seals if seal.classification == ItemClassification.progression_skip_balancing] @@ -60,8 +60,8 @@ class ThirtyThirtySeals(MessengerTestBase): def test_seals_amount(self) -> None: """Should have 30 power seals in the pool and 33 percent of that required.""" self.assertEqual(self.multiworld.total_seals[self.player], 30) - self.assertEqual(self.multiworld.worlds[self.player].total_seals, 30) - self.assertEqual(self.multiworld.worlds[self.player].required_seals, 10) + self.assertEqual(self.world.total_seals, 30) + self.assertEqual(self.world.required_seals, 10) total_seals = [seal for seal in self.multiworld.itempool if seal.name == "Power Seal"] required_seals = [seal for seal in total_seals if seal.classification == ItemClassification.progression_skip_balancing] @@ -78,7 +78,7 @@ class MaxSealsNoShards(MessengerTestBase): def test_seals_amount(self) -> None: """Should set total seals to 70 since shards aren't shuffled.""" self.assertEqual(self.multiworld.total_seals[self.player], 85) - self.assertEqual(self.multiworld.worlds[self.player].total_seals, 70) + self.assertEqual(self.world.total_seals, 70) class MaxSealsWithShards(MessengerTestBase): @@ -91,8 +91,8 @@ class MaxSealsWithShards(MessengerTestBase): def test_seals_amount(self) -> None: """Should have 85 seals in the pool with all required and be a valid seed.""" self.assertEqual(self.multiworld.total_seals[self.player], 85) - self.assertEqual(self.multiworld.worlds[self.player].total_seals, 85) - self.assertEqual(self.multiworld.worlds[self.player].required_seals, 85) + self.assertEqual(self.world.total_seals, 85) + self.assertEqual(self.world.required_seals, 85) total_seals = [seal for seal in self.multiworld.itempool if seal.name == "Power Seal"] required_seals = [seal for seal in total_seals if seal.classification == ItemClassification.progression_skip_balancing] From 4d9202537ceda282468f49bdce6b948e7b5a6702 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 15 Feb 2024 18:19:54 -0500 Subject: [PATCH 010/166] Lingo: Fix non-progressive The Colorful (#2782) The Colorful did not actually properly split into individual doors when progressive colorful was off. This change refactors the code that handles special cases with progressive items to make things clearer (which is important because I will be introducing another one). --- worlds/lingo/items.py | 15 +-------------- worlds/lingo/player_logic.py | 31 ++++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/worlds/lingo/items.py b/worlds/lingo/items.py index 7b1a650561..9f8bf56155 100644 --- a/worlds/lingo/items.py +++ b/worlds/lingo/items.py @@ -24,14 +24,6 @@ class ItemData(NamedTuple): return world.options.shuffle_colors > 0 elif self.mode == "doors": return world.options.shuffle_doors != ShuffleDoors.option_none - elif self.mode == "orange tower": - # door shuffle is on and tower isn't progressive - return world.options.shuffle_doors != ShuffleDoors.option_none \ - and not world.options.progressive_orange_tower - elif self.mode == "the colorful": - # complex door shuffle is on and colorful isn't progressive - return world.options.shuffle_doors == ShuffleDoors.option_complex \ - and not world.options.progressive_colorful elif self.mode == "complex door": return world.options.shuffle_doors == ShuffleDoors.option_complex elif self.mode == "door group": @@ -72,12 +64,7 @@ def load_item_data(): door_groups.setdefault(door.group, []).extend(door.door_ids) if room_name in PROGRESSION_BY_ROOM and door_name in PROGRESSION_BY_ROOM[room_name]: - if room_name == "Orange Tower": - door_mode = "orange tower" - elif room_name == "The Colorful": - door_mode = "the colorful" - else: - door_mode = "special" + door_mode = "special" ALL_ITEM_TABLE[door.item_name] = \ ItemData(get_door_item_id(room_name, door_name), diff --git a/worlds/lingo/player_logic.py b/worlds/lingo/player_logic.py index b2e5f77df1..0ae303518c 100644 --- a/worlds/lingo/player_logic.py +++ b/worlds/lingo/player_logic.py @@ -1,3 +1,4 @@ +from enum import Enum from typing import Dict, List, NamedTuple, Optional, Set, Tuple, TYPE_CHECKING from .items import ALL_ITEM_TABLE @@ -36,6 +37,27 @@ class PlayerLocation(NamedTuple): access: AccessRequirements +class ProgressiveItemBehavior(Enum): + DISABLE = 1 + SPLIT = 2 + PROGRESSIVE = 3 + + +def should_split_progression(progression_name: str, world: "LingoWorld") -> ProgressiveItemBehavior: + if progression_name == "Progressive Orange Tower": + if world.options.progressive_orange_tower: + return ProgressiveItemBehavior.PROGRESSIVE + else: + return ProgressiveItemBehavior.SPLIT + elif progression_name == "Progressive Colorful": + if world.options.progressive_colorful: + return ProgressiveItemBehavior.PROGRESSIVE + else: + return ProgressiveItemBehavior.SPLIT + + return ProgressiveItemBehavior.PROGRESSIVE + + class LingoPlayerLogic: """ Defines logic after a player's options have been applied @@ -83,10 +105,13 @@ class LingoPlayerLogic: def handle_non_grouped_door(self, room_name: str, door_data: Door, world: "LingoWorld"): if room_name in PROGRESSION_BY_ROOM and door_data.name in PROGRESSION_BY_ROOM[room_name]: - if (room_name == "Orange Tower" and not world.options.progressive_orange_tower)\ - or (room_name == "The Colorful" and not world.options.progressive_colorful): + progression_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name + progression_handling = should_split_progression(progression_name, world) + + if progression_handling == ProgressiveItemBehavior.SPLIT: self.set_door_item(room_name, door_data.name, door_data.item_name) - else: + self.real_items.append(door_data.item_name) + elif progression_handling == ProgressiveItemBehavior.PROGRESSIVE: progressive_item_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name self.set_door_item(room_name, door_data.name, progressive_item_name) self.real_items.append(progressive_item_name) From 539307cf0b3f1fa00b178d09ceee438a8a286890 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Thu, 15 Feb 2024 23:03:51 -0500 Subject: [PATCH 011/166] TUNIC: Universal Tracker Support Update (#2786) Adds better support for the Universal Tracker (see its channel in the future game design section). This does absolutely nothing regarding standard gen, just adds some checks for an attribute that only exists when UT is being used. --- worlds/tunic/__init__.py | 72 +++++++++----------- worlds/tunic/er_data.py | 2 +- worlds/tunic/er_scripts.py | 136 ++++++++++++++++++++++++++++++++++--- 3 files changed, 159 insertions(+), 51 deletions(-) diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index d8311de856..7dfb5c09c6 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -59,6 +59,20 @@ class TunicWorld(World): er_portal_hints: Dict[int, str] def generate_early(self) -> None: + # Universal tracker stuff, shouldn't do anything in standard gen + if hasattr(self.multiworld, "re_gen_passthrough"): + if "TUNIC" in self.multiworld.re_gen_passthrough: + passthrough = self.multiworld.re_gen_passthrough["TUNIC"] + self.options.start_with_sword.value = passthrough["start_with_sword"] + self.options.keys_behind_bosses.value = passthrough["keys_behind_bosses"] + self.options.sword_progression.value = passthrough["sword_progression"] + self.options.ability_shuffling.value = passthrough["ability_shuffling"] + self.options.logic_rules.value = passthrough["logic_rules"] + self.options.lanternless.value = passthrough["lanternless"] + self.options.maskless.value = passthrough["maskless"] + self.options.hexagon_quest.value = passthrough["hexagon_quest"] + self.options.entrance_rando.value = passthrough["entrance_rando"] + if self.options.start_with_sword and "Sword" not in self.options.start_inventory: self.options.start_inventory.value["Sword"] = 1 @@ -150,10 +164,20 @@ class TunicWorld(World): self.tunic_portal_pairs = {} self.er_portal_hints = {} self.ability_unlocks = randomize_ability_unlocks(self.random, self.options) + + # stuff for universal tracker support, can be ignored for standard gen + if hasattr(self.multiworld, "re_gen_passthrough"): + if "TUNIC" in self.multiworld.re_gen_passthrough: + passthrough = self.multiworld.re_gen_passthrough["TUNIC"] + self.ability_unlocks["Pages 24-25 (Prayer)"] = passthrough["Hexagon Quest Prayer"] + self.ability_unlocks["Pages 42-43 (Holy Cross)"] = passthrough["Hexagon Quest Holy Cross"] + self.ability_unlocks["Pages 52-53 (Ice Rod)"] = passthrough["Hexagon Quest Ice Rod"] + if self.options.entrance_rando: portal_pairs, portal_hints = create_er_regions(self) for portal1, portal2 in portal_pairs.items(): self.tunic_portal_pairs[portal1.scene_destination()] = portal2.scene_destination() + self.er_portal_hints = portal_hints else: @@ -199,6 +223,9 @@ class TunicWorld(World): "ability_shuffling": self.options.ability_shuffling.value, "hexagon_quest": self.options.hexagon_quest.value, "fool_traps": self.options.fool_traps.value, + "logic_rules": self.options.logic_rules.value, + "lanternless": self.options.lanternless.value, + "maskless": self.options.maskless.value, "entrance_rando": self.options.entrance_rando.value, "Hexagon Quest Prayer": self.ability_unlocks["Pages 24-25 (Prayer)"], "Hexagon Quest Holy Cross": self.ability_unlocks["Pages 42-43 (Holy Cross)"], @@ -236,44 +263,7 @@ class TunicWorld(World): return slot_data # for the universal tracker, doesn't get called in standard gen - def interpret_slot_data(self, slot_data: Dict[str, Any]) -> None: - # bypassing random yaml settings - self.options.start_with_sword.value = slot_data["start_with_sword"] - self.options.keys_behind_bosses.value = slot_data["keys_behind_bosses"] - self.options.sword_progression.value = slot_data["sword_progression"] - self.options.ability_shuffling.value = slot_data["ability_shuffling"] - self.options.hexagon_quest.value = slot_data["hexagon_quest"] - self.ability_unlocks["Pages 24-25 (Prayer)"] = slot_data["Hexagon Quest Prayer"] - self.ability_unlocks["Pages 42-43 (Holy Cross)"] = slot_data["Hexagon Quest Holy Cross"] - self.ability_unlocks["Pages 52-53 (Ice Rod)"] = slot_data["Hexagon Quest Ice Rod"] - - # swapping entrances around so the mapping matches what was generated - if slot_data["entrance_rando"]: - from BaseClasses import Entrance - from .er_data import portal_mapping - entrance_dict: Dict[str, Entrance] = {entrance.name: entrance - for region in self.multiworld.get_regions(self.player) - for entrance in region.entrances} - slot_portals: Dict[str, str] = slot_data["Entrance Rando"] - for portal1, portal2 in slot_portals.items(): - portal_name1: str = "" - portal_name2: str = "" - entrance1 = None - entrance2 = None - for portal in portal_mapping: - if portal.scene_destination() == portal1: - portal_name1 = portal.name - if portal.scene_destination() == portal2: - portal_name2 = portal.name - - for entrance_name, entrance in entrance_dict.items(): - if entrance_name.startswith(portal_name1): - entrance1 = entrance - if entrance_name.startswith(portal_name2): - entrance2 = entrance - if entrance1 is None: - raise Exception("entrance1 not found, portal1 is " + portal1) - if entrance2 is None: - raise Exception("entrance2 not found, portal2 is " + portal2) - entrance1.connected_region = entrance2.parent_region - entrance2.connected_region = entrance1.parent_region + @staticmethod + def interpret_slot_data(slot_data: Dict[str, Any]) -> Dict[str, Any]: + # returning slot_data so it regens, giving it back in multiworld.re_gen_passthrough + return slot_data diff --git a/worlds/tunic/er_data.py b/worlds/tunic/er_data.py index 95d33d4aff..d76af11339 100644 --- a/worlds/tunic/er_data.py +++ b/worlds/tunic/er_data.py @@ -730,7 +730,7 @@ for p1, p2 in hallways_ur.items(): # the key is the region you have, the value is the regions you get for having that region # this is mostly so we don't have to do something overly complex to get this information -dependent_regions: Dict[Tuple[str, ...], List[str]] = { +dependent_regions_restricted: Dict[Tuple[str, ...], List[str]] = { ("Overworld", "Overworld Belltower", "Overworld Swamp Upper Entry", "Overworld Special Shop Entry", "Overworld West Garden Laurels Entry", "Overworld Southeast Cross Door", "Overworld Temple Door", "Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal"): diff --git a/worlds/tunic/er_scripts.py b/worlds/tunic/er_scripts.py index 4e28344b20..d2b854f5df 100644 --- a/worlds/tunic/er_scripts.py +++ b/worlds/tunic/er_scripts.py @@ -2,8 +2,9 @@ from typing import Dict, List, Set, Tuple, TYPE_CHECKING from BaseClasses import Region, ItemClassification, Item, Location from .locations import location_table from .er_data import Portal, tunic_er_regions, portal_mapping, hallway_helper, hallway_helper_ur, \ - dependent_regions, dependent_regions_nmg, dependent_regions_ur + dependent_regions_restricted, dependent_regions_nmg, dependent_regions_ur from .er_rules import set_er_region_rules +from worlds.generic import PlandoConnection if TYPE_CHECKING: from . import TunicWorld @@ -22,12 +23,17 @@ def create_er_regions(world: "TunicWorld") -> Tuple[Dict[Portal, Portal], Dict[i portal_pairs: Dict[Portal, Portal] = pair_portals(world) logic_rules = world.options.logic_rules + # output the entrances to the spoiler log here for convenience + for portal1, portal2 in portal_pairs.items(): + world.multiworld.spoiler.set_entrance(portal1.name, portal2.name, "both", world.player) + # check if a portal leads to a hallway. if it does, update the hint text accordingly def hint_helper(portal: Portal, hint_string: str = "") -> str: # start by setting it as the name of the portal, for the case we're not using the hallway helper if hint_string == "": hint_string = portal.name + # unrestricted has fewer hallways, like the well rail if logic_rules == "unrestricted": hallways = hallway_helper_ur else: @@ -69,6 +75,7 @@ def create_er_regions(world: "TunicWorld") -> Tuple[Dict[Portal, Portal], Dict[i return hint_string # create our regions, give them hint text if they're in a spot where it makes sense to + # we're limiting which ones get hints so that it still gets that ER feel with a little less BS for region_name, region_data in tunic_er_regions.items(): hint_text = "error" if region_data.hint == 1: @@ -90,7 +97,7 @@ def create_er_regions(world: "TunicWorld") -> Tuple[Dict[Portal, Portal], Dict[i break regions[region_name] = Region(region_name, world.player, world.multiworld, hint_text) elif region_data.hint == 3: - # only the west garden portal item for now + # west garden portal item is at a dead end in restricted, otherwise just in west garden if region_name == "West Garden Portal Item": if world.options.logic_rules: for portal1, portal2 in portal_pairs.items(): @@ -178,9 +185,17 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: portal_pairs: Dict[Portal, Portal] = {} dead_ends: List[Portal] = [] two_plus: List[Portal] = [] + plando_connections: List[PlandoConnection] = [] fixed_shop = False logic_rules = world.options.logic_rules.value + if not logic_rules: + dependent_regions = dependent_regions_restricted + elif logic_rules == 1: + dependent_regions = dependent_regions_nmg + else: + dependent_regions = dependent_regions_ur + # create separate lists for dead ends and non-dead ends if logic_rules: for portal in portal_mapping: @@ -200,8 +215,46 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: start_region = "Overworld" connected_regions.update(add_dependent_regions(start_region, logic_rules)) + # universal tracker support stuff, don't need to care about region dependency + if hasattr(world.multiworld, "re_gen_passthrough"): + if "TUNIC" in world.multiworld.re_gen_passthrough: + # universal tracker stuff, won't do anything in normal gen + for portal1, portal2 in world.multiworld.re_gen_passthrough["TUNIC"]["Entrance Rando"].items(): + portal_name1 = "" + portal_name2 = "" + + # skip this if 10 fairies laurels location is on, it can be handled normally + if portal1 == "Overworld Redux, Waterfall_" and portal2 == "Waterfall, Overworld Redux_" \ + and world.options.laurels_location == "10_fairies": + continue + + for portal in portal_mapping: + if portal.scene_destination() == portal1: + portal_name1 = portal.name + # connected_regions.update(add_dependent_regions(portal.region, logic_rules)) + if portal.scene_destination() == portal2: + portal_name2 = portal.name + # connected_regions.update(add_dependent_regions(portal.region, logic_rules)) + # shops have special handling + if not portal_name2 and portal2 == "Shop, Previous Region_": + portal_name2 = "Shop Portal" + plando_connections.append(PlandoConnection(portal_name1, portal_name2, "both")) + + if plando_connections: + portal_pairs, dependent_regions, dead_ends, two_plus = \ + create_plando_connections(plando_connections, dependent_regions, dead_ends, two_plus) + + # if we have plando connections, our connected regions may change somewhat + while True: + test1 = len(connected_regions) + for region in connected_regions.copy(): + connected_regions.update(add_dependent_regions(region, logic_rules)) + test2 = len(connected_regions) + if test1 == test2: + break + # need to plando fairy cave, or it could end up laurels locked - # fix this later to be random? probably not? + # fix this later to be random after adding some item logic to dependent regions if world.options.laurels_location == "10_fairies": portal1 = None portal2 = None @@ -217,7 +270,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: two_plus.remove(portal1) dead_ends.remove(portal2) - if world.options.fixed_shop: + if world.options.fixed_shop and not hasattr(world.multiworld, "re_gen_passthrough"): fixed_shop = True portal1 = None for portal in two_plus: @@ -283,6 +336,11 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: shop_count = 1 shop_scenes.add("Overworld Redux") + # for universal tracker, we want to skip shop gen + if hasattr(world.multiworld, "re_gen_passthrough"): + if "TUNIC" in world.multiworld.re_gen_passthrough: + shop_count = 0 + for i in range(shop_count): portal1 = None for portal in two_plus: @@ -311,10 +369,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: portal_pairs[portal1] = portal2 if len(two_plus) == 1: - raise Exception("two plus had an odd number of portals, investigate this") - - for portal1, portal2 in portal_pairs.items(): - world.multiworld.spoiler.set_entrance(portal1.name, portal2.name, "both", world.player) + raise Exception("two plus had an odd number of portals, investigate this. last portal is " + two_plus[0].name) return portal_pairs @@ -331,10 +386,11 @@ def create_randomized_entrances(portal_pairs: Dict[Portal, Portal], regions: Dic # loop through the static connections, return regions you can reach from this region +# todo: refactor to take region_name and dependent_regions def add_dependent_regions(region_name: str, logic_rules: int) -> Set[str]: region_set = set() if not logic_rules: - regions_to_add = dependent_regions + regions_to_add = dependent_regions_restricted elif logic_rules == 1: regions_to_add = dependent_regions_nmg else: @@ -451,3 +507,65 @@ def gate_before_switch(check_portal: Portal, two_plus: List[Portal]) -> bool: # false means you're good to place the portal return False + + +# this is for making the connections themselves +def create_plando_connections(plando_connections: List[PlandoConnection], + dependent_regions: Dict[Tuple[str, ...], List[str]], dead_ends: List[Portal], + two_plus: List[Portal]) \ + -> Tuple[Dict[Portal, Portal], Dict[Tuple[str, ...], List[str]], List[Portal], List[Portal]]: + + portal_pairs: Dict[Portal, Portal] = {} + shop_num = 1 + for connection in plando_connections: + p_entrance = connection.entrance + p_exit = connection.exit + + portal1 = None + portal2 = None + + # search two_plus for both at once + for portal in two_plus: + if p_entrance == portal.name: + portal1 = portal + if p_exit == portal.name: + portal2 = portal + + # search dead_ends individually since we can't really remove items from two_plus during the loop + if not portal1: + for portal in dead_ends: + if p_entrance == portal.name: + portal1 = portal + break + dead_ends.remove(portal1) + else: + two_plus.remove(portal1) + + if not portal2: + for portal in dead_ends: + if p_exit == portal.name: + portal2 = portal + break + if p_exit == "Shop Portal": + portal2 = Portal(name="Shop Portal", region=f"Shop Entrance {shop_num}", destination="Previous Region_") + shop_num += 1 + else: + dead_ends.remove(portal2) + else: + two_plus.remove(portal2) + + if not portal1: + raise Exception("could not find entrance named " + p_entrance + " for Tunic player's plando") + if not portal2: + raise Exception("could not find entrance named " + p_exit + " for Tunic player's plando") + + portal_pairs[portal1] = portal2 + + # update dependent regions based on the plando'd connections, to make sure the portals connect well, logically + for origins, destinations in dependent_regions.items(): + if portal1.region in origins: + destinations.append(portal2.region) + if portal2.region in origins: + destinations.append(portal1.region) + + return portal_pairs, dependent_regions, dead_ends, two_plus From 687af30d14d13aaf9791358d1f1f5cc9c562fb97 Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Fri, 16 Feb 2024 00:59:57 -0700 Subject: [PATCH 012/166] BizHawkClient: Use callbacks in connector script instead of else/ifs (#2784) --- data/lua/connector_bizhawk_generic.lua | 105 +++++++++++++++++++------ 1 file changed, 83 insertions(+), 22 deletions(-) diff --git a/data/lua/connector_bizhawk_generic.lua b/data/lua/connector_bizhawk_generic.lua index 47af6e003d..00021b241f 100644 --- a/data/lua/connector_bizhawk_generic.lua +++ b/data/lua/connector_bizhawk_generic.lua @@ -22,6 +22,10 @@ SOFTWARE. local SCRIPT_VERSION = 1 +-- Set to log incoming requests +-- Will cause lag due to large console output +local DEBUG = false + --[[ This script expects to receive JSON and will send JSON back. A message should be a list of 1 or more requests which will be executed in order. Each request @@ -271,10 +275,6 @@ local base64 = require("base64") local socket = require("socket") local json = require("json") --- Set to log incoming requests --- Will cause lag due to large console output -local DEBUG = false - local SOCKET_PORT_FIRST = 43055 local SOCKET_PORT_RANGE_SIZE = 5 local SOCKET_PORT_LAST = SOCKET_PORT_FIRST + SOCKET_PORT_RANGE_SIZE @@ -330,18 +330,28 @@ function unlock () client_socket:settimeout(0) end -function process_request (req) - local res = {} +request_handlers = { + ["PING"] = function (req) + local res = {} - if req["type"] == "PING" then res["type"] = "PONG" - elseif req["type"] == "SYSTEM" then + return res + end, + + ["SYSTEM"] = function (req) + local res = {} + res["type"] = "SYSTEM_RESPONSE" res["value"] = emu.getsystemid() - elseif req["type"] == "PREFERRED_CORES" then + return res + end, + + ["PREFERRED_CORES"] = function (req) + local res = {} local preferred_cores = client.getconfig().PreferredCores + res["type"] = "PREFERRED_CORES_RESPONSE" res["value"] = {} res["value"]["NES"] = preferred_cores.NES @@ -354,14 +364,21 @@ function process_request (req) res["value"]["PCECD"] = preferred_cores.PCECD res["value"]["SGX"] = preferred_cores.SGX - elseif req["type"] == "HASH" then + return res + end, + + ["HASH"] = function (req) + local res = {} + res["type"] = "HASH_RESPONSE" res["value"] = rom_hash - elseif req["type"] == "GUARD" then - res["type"] = "GUARD_RESPONSE" - local expected_data = base64.decode(req["expected_data"]) + return res + end, + ["GUARD"] = function (req) + local res = {} + local expected_data = base64.decode(req["expected_data"]) local actual_data = memory.read_bytes_as_array(req["address"], #expected_data, req["domain"]) local data_is_validated = true @@ -372,39 +389,83 @@ function process_request (req) end end + res["type"] = "GUARD_RESPONSE" res["value"] = data_is_validated res["address"] = req["address"] - elseif req["type"] == "LOCK" then + return res + end, + + ["LOCK"] = function (req) + local res = {} + res["type"] = "LOCKED" lock() - elseif req["type"] == "UNLOCK" then + return res + end, + + ["UNLOCK"] = function (req) + local res = {} + res["type"] = "UNLOCKED" unlock() - elseif req["type"] == "READ" then + return res + end, + + ["READ"] = function (req) + local res = {} + res["type"] = "READ_RESPONSE" res["value"] = base64.encode(memory.read_bytes_as_array(req["address"], req["size"], req["domain"])) - elseif req["type"] == "WRITE" then + return res + end, + + ["WRITE"] = function (req) + local res = {} + res["type"] = "WRITE_RESPONSE" memory.write_bytes_as_array(req["address"], base64.decode(req["value"]), req["domain"]) - elseif req["type"] == "DISPLAY_MESSAGE" then + return res + end, + + ["DISPLAY_MESSAGE"] = function (req) + local res = {} + res["type"] = "DISPLAY_MESSAGE_RESPONSE" message_queue:push(req["message"]) - elseif req["type"] == "SET_MESSAGE_INTERVAL" then + return res + end, + + ["SET_MESSAGE_INTERVAL"] = function (req) + local res = {} + res["type"] = "SET_MESSAGE_INTERVAL_RESPONSE" message_interval = req["value"] - else + return res + end, + + ["default"] = function (req) + local res = {} + res["type"] = "ERROR" res["err"] = "Unknown command: "..req["type"] - end - return res + return res + end, +} + +function process_request (req) + if request_handlers[req["type"]] then + return request_handlers[req["type"]](req) + else + return request_handlers["default"](req) + end end -- Receive data from AP client and send message back From 04b02f5a4a589b9f57a6e8671ad54aa774d56755 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Fri, 16 Feb 2024 17:24:25 -0500 Subject: [PATCH 013/166] TUNIC: Add aliases to LogicRules (#2825) --- worlds/tunic/options.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/worlds/tunic/options.py b/worlds/tunic/options.py index 1838941bc9..89ef27c367 100644 --- a/worlds/tunic/options.py +++ b/worlds/tunic/options.py @@ -36,8 +36,8 @@ class AbilityShuffling(Toggle): class LogicRules(Choice): """Set which logic rules to use for your world. Restricted: Standard logic, no glitches. - No Major Glitches: Ice grapples through doors, shooting the west bell, and boss quick kills are included in logic. - * Ice grappling through the Ziggurat door is not in logic since you will get stuck in there without Prayer + No Major Glitches: Sneaky Laurels zips, ice grapples through doors, shooting the west bell, and boss quick kills are included in logic. + * Ice grappling through the Ziggurat door is not in logic since you will get stuck in there without Prayer. Unrestricted: Logic in No Major Glitches, as well as ladder storage to get to certain places early. *Special Shop is not in logic without the Hero's Laurels due to soft lock potential. *Using Ladder Storage to get to individual chests is not in logic to avoid tedium. @@ -47,7 +47,9 @@ class LogicRules(Choice): display_name = "Logic Rules" option_restricted = 0 option_no_major_glitches = 1 + alias_nmg = 1 option_unrestricted = 2 + alias_ur = 2 default = 0 From e8249d1f727e9f6cad98c1e6be9f2b22736a1401 Mon Sep 17 00:00:00 2001 From: Silent <110704408+silent-destroyer@users.noreply.github.com> Date: Fri, 16 Feb 2024 17:25:20 -0500 Subject: [PATCH 014/166] TUNIC: Rename ability item (#2834) --- worlds/tunic/__init__.py | 4 ++-- worlds/tunic/er_rules.py | 4 ++-- worlds/tunic/items.py | 9 +++++---- worlds/tunic/options.py | 2 +- worlds/tunic/rules.py | 8 ++++---- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index 7dfb5c09c6..fb04570f22 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -171,7 +171,7 @@ class TunicWorld(World): passthrough = self.multiworld.re_gen_passthrough["TUNIC"] self.ability_unlocks["Pages 24-25 (Prayer)"] = passthrough["Hexagon Quest Prayer"] self.ability_unlocks["Pages 42-43 (Holy Cross)"] = passthrough["Hexagon Quest Holy Cross"] - self.ability_unlocks["Pages 52-53 (Ice Rod)"] = passthrough["Hexagon Quest Ice Rod"] + self.ability_unlocks["Pages 52-53 (Icebolt)"] = passthrough["Hexagon Quest Icebolt"] if self.options.entrance_rando: portal_pairs, portal_hints = create_er_regions(self) @@ -229,7 +229,7 @@ class TunicWorld(World): "entrance_rando": self.options.entrance_rando.value, "Hexagon Quest Prayer": self.ability_unlocks["Pages 24-25 (Prayer)"], "Hexagon Quest Holy Cross": self.ability_unlocks["Pages 42-43 (Holy Cross)"], - "Hexagon Quest Ice Rod": self.ability_unlocks["Pages 52-53 (Ice Rod)"], + "Hexagon Quest Icebolt": self.ability_unlocks["Pages 52-53 (Icebolt)"], "Hexagon Quest Goal": self.options.hexagon_goal.value, "Entrance Rando": self.tunic_portal_pairs } diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index ad203e1e82..ebc563c3da 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -16,7 +16,7 @@ fairies = "Fairy" coins = "Golden Coin" prayer = "Pages 24-25 (Prayer)" holy_cross = "Pages 42-43 (Holy Cross)" -ice_rod = "Pages 52-53 (Ice Rod)" +icebolt = "Pages 52-53 (Icebolt)" key = "Key" house_key = "Old House Key" vault_key = "Fortress Vault Key" @@ -884,7 +884,7 @@ def set_er_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) lambda state: state.has_all({grapple, laurels}, player)) set_rule(multiworld.get_location("East Forest - Ice Rod Grapple Chest", player), lambda state: ( state.has_all({grapple, ice_dagger, fire_wand}, player) and - has_ability(state, player, ice_rod, options, ability_unlocks))) + has_ability(state, player, icebolt, options, ability_unlocks))) # West Garden set_rule(multiworld.get_location("West Garden - [North] Across From Page Pickup", player), diff --git a/worlds/tunic/items.py b/worlds/tunic/items.py index 16608620c6..547a0ffb81 100644 --- a/worlds/tunic/items.py +++ b/worlds/tunic/items.py @@ -141,7 +141,7 @@ item_table: Dict[str, TunicItemData] = { "Pages 46-47": TunicItemData(ItemClassification.useful, 1, 125, "pages"), "Pages 48-49": TunicItemData(ItemClassification.useful, 1, 126, "pages"), "Pages 50-51": TunicItemData(ItemClassification.useful, 1, 127, "pages"), - "Pages 52-53 (Ice Rod)": TunicItemData(ItemClassification.progression, 1, 128, "pages"), + "Pages 52-53 (Icebolt)": TunicItemData(ItemClassification.progression, 1, 128, "pages"), "Pages 54-55": TunicItemData(ItemClassification.useful, 1, 129, "pages"), } @@ -176,7 +176,7 @@ slot_data_item_names = [ "Hero Relic - MP", "Pages 24-25 (Prayer)", "Pages 42-43 (Holy Cross)", - "Pages 52-53 (Ice Rod)", + "Pages 52-53 (Icebolt)", "Red Questagon", "Green Questagon", "Blue Questagon", @@ -204,10 +204,11 @@ extra_groups: Dict[str, Set[str]] = { "magic rod": {"Magic Wand"}, "holy cross": {"Pages 42-43 (Holy Cross)"}, "prayer": {"Pages 24-25 (Prayer)"}, - "ice rod": {"Pages 52-53 (Ice Rod)"}, + "icebolt": {"Pages 52-53 (Icebolt)"}, + "ice rod": {"Pages 52-53 (Icebolt)"}, "melee weapons": {"Stick", "Sword", "Sword Upgrade"}, "progressive sword": {"Sword Upgrade"}, - "abilities": {"Pages 24-25 (Prayer)", "Pages 42-43 (Holy Cross)", "Pages 52-53 (Ice Rod)"}, + "abilities": {"Pages 24-25 (Prayer)", "Pages 42-43 (Holy Cross)", "Pages 52-53 (Icebolt)"}, "questagons": {"Red Questagon", "Green Questagon", "Blue Questagon", "Gold Questagon"} } diff --git a/worlds/tunic/options.py b/worlds/tunic/options.py index 89ef27c367..f4790da367 100644 --- a/worlds/tunic/options.py +++ b/worlds/tunic/options.py @@ -23,7 +23,7 @@ class KeysBehindBosses(Toggle): class AbilityShuffling(Toggle): - """Locks the usage of Prayer, Holy Cross*, and Ice Rod until the relevant pages of the manual have been found. + """Locks the usage of Prayer, Holy Cross*, and the Icebolt combo until the relevant pages of the manual have been found. If playing Hexagon Quest, abilities are instead randomly unlocked after obtaining 25%, 50%, and 75% of the required Hexagon goal amount. *Certain Holy Cross usages are still allowed, such as the free bomb codes, the seeking spell, and other diff --git a/worlds/tunic/rules.py b/worlds/tunic/rules.py index df81335655..6e5639b4eb 100644 --- a/worlds/tunic/rules.py +++ b/worlds/tunic/rules.py @@ -16,7 +16,7 @@ fairies = "Fairy" coins = "Golden Coin" prayer = "Pages 24-25 (Prayer)" holy_cross = "Pages 42-43 (Holy Cross)" -ice_rod = "Pages 52-53 (Ice Rod)" +icebolt = "Pages 52-53 (Icebolt)" key = "Key" house_key = "Old House Key" vault_key = "Fortress Vault Key" @@ -33,7 +33,7 @@ def randomize_ability_unlocks(random: Random, options: TunicOptions) -> Dict[str hexagon_goal = options.hexagon_goal.value # Set ability unlocks to 25, 50, and 75% of goal amount ability_requirement = [hexagon_goal // 4, hexagon_goal // 2, hexagon_goal * 3 // 4] - abilities = [prayer, holy_cross, ice_rod] + abilities = [prayer, holy_cross, icebolt] random.shuffle(abilities) return dict(zip(abilities, ability_requirement)) @@ -65,7 +65,7 @@ def has_ice_grapple_logic(long_range: bool, state: CollectionState, player: int, return state.has_all({ice_dagger, grapple}, player) else: return state.has_all({ice_dagger, fire_wand, grapple}, player) and \ - has_ability(state, player, ice_rod, options, ability_unlocks) + has_ability(state, player, icebolt, options, ability_unlocks) def can_ladder_storage(state: CollectionState, player: int, options: TunicOptions) -> bool: @@ -251,7 +251,7 @@ def set_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) -> lambda state: state.has_all({grapple, laurels}, player)) set_rule(multiworld.get_location("East Forest - Ice Rod Grapple Chest", player), lambda state: state.has_all({grapple, ice_dagger, fire_wand}, player) - and has_ability(state, player, ice_rod, options, ability_unlocks)) + and has_ability(state, player, icebolt, options, ability_unlocks)) # West Garden set_rule(multiworld.get_location("West Garden - [North] Across From Page Pickup", player), From 523c7dbfada49c470e8130201f4d8494f088fc11 Mon Sep 17 00:00:00 2001 From: Nikola-Em <133994870+Nikola-Em@users.noreply.github.com> Date: Sat, 17 Feb 2024 05:50:51 +0000 Subject: [PATCH 015/166] Lingo: MASTERY (Room) not require "gray" (#2792) --- worlds/lingo/data/LL1.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index 23afb2b445..1a149f2db9 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -6966,10 +6966,12 @@ MASTERY: id: Master Room/Panel_mastery_mastery tag: midwhite - colors: gray required_door: room: Orange Tower Seventh Floor door: Mastery + required_panel: + room: Room Room + panel: WALL (2) doors: Excavation: event: True From 818b0a49e132d40ea205baf8dd556c3ab120433e Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Sat, 17 Feb 2024 17:52:50 -0700 Subject: [PATCH 016/166] Pokemon Emerald: Un-exclude locations that must contain progression (#2840) --- worlds/pokemon_emerald/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/worlds/pokemon_emerald/__init__.py b/worlds/pokemon_emerald/__init__.py index 7a7596c096..95e549a32e 100644 --- a/worlds/pokemon_emerald/__init__.py +++ b/worlds/pokemon_emerald/__init__.py @@ -330,6 +330,7 @@ class PokemonEmeraldWorld(World): for location in locations: if location.tags is not None and tag in location.tags: location.place_locked_item(self.create_event(self.item_id_to_name[location.default_item_code])) + location.progress_type = LocationProgressType.DEFAULT location.address = None if self.options.badges == RandomizeBadges.option_vanilla: @@ -366,6 +367,12 @@ class PokemonEmeraldWorld(World): } badge_items.sort(key=lambda item: badge_priority.get(item.name, 0)) + # Un-exclude badge locations, since we need to put progression items on them + for location in badge_locations: + location.progress_type = LocationProgressType.DEFAULT \ + if location.progress_type == LocationProgressType.EXCLUDED \ + else location.progress_type + collection_state = self.multiworld.get_all_state(False) if self.hm_shuffle_info is not None: for _, item in self.hm_shuffle_info: @@ -410,6 +417,12 @@ class PokemonEmeraldWorld(World): } hm_items.sort(key=lambda item: hm_priority.get(item.name, 0)) + # Un-exclude HM locations, since we need to put progression items on them + for location in hm_locations: + location.progress_type = LocationProgressType.DEFAULT \ + if location.progress_type == LocationProgressType.EXCLUDED \ + else location.progress_type + collection_state = self.multiworld.get_all_state(False) # In specific very constrained conditions, fill_restrictive may run From 933e5bacff5393c04f8ed49375e5ca97337bd122 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Tue, 20 Feb 2024 00:25:51 +0100 Subject: [PATCH 017/166] Core: update requirements (#2716) --- requirements.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index f604556809..e2ccb67c18 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,13 @@ -colorama>=0.4.5 +colorama>=0.4.6 websockets>=12.0 PyYAML>=6.0.1 jellyfish>=1.0.3 -jinja2>=3.1.2 +jinja2>=3.1.3 schema>=0.7.5 kivy>=2.3.0 bsdiff4>=1.2.4 -platformdirs>=4.0.0 +platformdirs>=4.1.0 certifi>=2023.11.17 -cython>=3.0.6 +cython>=3.0.8 cymem>=2.0.8 -orjson>=3.9.10 \ No newline at end of file +orjson>=3.9.10 From 7a86285807fe4a83ba6482268f4b1dac40dd35a1 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Mon, 19 Feb 2024 19:07:49 -0500 Subject: [PATCH 018/166] LttP: Bombless Start and Options/Shops overhaul (#2357) ## What is this fixing or adding? Adds Bombless Start option, along with proper bomb logic. This involves updating `can_kill_most_things` to include checking how many bombs can be held. Many places where the ability to kill enemies was assumed, now have logic. This fixes some possible existing logic issues, for example: Mini Moldorm cave checks currently are always in logic despite the fact that on expert enemy health it would require 12 bombs to kill each mini moldorm. Overhauls options, pulling them out of core and in particular making large changes to how the shop options work. Co-authored-by: espeon65536 <81029175+espeon65536@users.noreply.github.com> Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> Co-authored-by: Bondo <38083232+BadmoonzZ@users.noreply.github.com> Co-authored-by: espeon65536 Co-authored-by: Fabian Dill --- BaseClasses.py | 6 +- Generate.py | 118 ---- data/basepatch.bsdiff4 | Bin 114127 -> 114116 bytes setup.py | 2 - worlds/alttp/Bosses.py | 7 +- worlds/alttp/Dungeons.py | 4 +- worlds/alttp/EntranceRandomizer.py | 12 +- worlds/alttp/EntranceShuffle.py | 591 ++---------------- worlds/alttp/InvertedRegions.py | 15 +- worlds/alttp/ItemPool.py | 432 +++++++------ worlds/alttp/Items.py | 13 +- worlds/alttp/Options.py | 385 +++++++++++- worlds/alttp/Regions.py | 16 +- worlds/alttp/Rom.py | 136 ++-- worlds/alttp/Rules.py | 261 +++++--- worlds/alttp/Shops.py | 466 +++++--------- worlds/alttp/StateHelpers.py | 50 +- worlds/alttp/SubClasses.py | 3 + worlds/alttp/UnderworldGlitchRules.py | 8 +- worlds/alttp/__init__.py | 115 ++-- .../alttp/test/dungeons/TestAgahnimsTower.py | 8 +- worlds/alttp/test/dungeons/TestDarkPalace.py | 21 +- worlds/alttp/test/dungeons/TestDungeon.py | 2 + .../alttp/test/dungeons/TestEasternPalace.py | 4 +- worlds/alttp/test/dungeons/TestGanonsTower.py | 12 +- worlds/alttp/test/dungeons/TestIcePalace.py | 39 +- worlds/alttp/test/dungeons/TestMiseryMire.py | 7 +- worlds/alttp/test/dungeons/TestSkullWoods.py | 6 +- worlds/alttp/test/dungeons/TestSwampPalace.py | 3 +- worlds/alttp/test/dungeons/TestThievesTown.py | 9 +- worlds/alttp/test/dungeons/TestTowerOfHera.py | 4 +- worlds/alttp/test/inverted/TestInverted.py | 4 +- .../test/inverted/TestInvertedBombRules.py | 2 +- .../test/inverted/TestInvertedDarkWorld.py | 22 +- .../inverted/TestInvertedDeathMountain.py | 77 ++- .../test/inverted/TestInvertedLightWorld.py | 105 ++-- .../test/inverted/TestInvertedTurtleRock.py | 19 +- .../TestInvertedDarkWorld.py | 22 +- .../TestInvertedDeathMountain.py | 71 ++- .../TestInvertedLightWorld.py | 105 ++-- .../TestInvertedMinor.py | 6 +- .../TestInvertedTurtleRock.py | 18 +- .../alttp/test/inverted_owg/TestDarkWorld.py | 22 +- .../test/inverted_owg/TestDeathMountain.py | 18 +- .../alttp/test/inverted_owg/TestDungeons.py | 26 +- .../test/inverted_owg/TestInvertedOWG.py | 6 +- .../alttp/test/inverted_owg/TestLightWorld.py | 42 +- .../test/minor_glitches/TestDarkWorld.py | 64 +- .../test/minor_glitches/TestDeathMountain.py | 66 +- .../test/minor_glitches/TestLightWorld.py | 61 +- worlds/alttp/test/minor_glitches/TestMinor.py | 4 +- worlds/alttp/test/owg/TestDarkWorld.py | 78 +-- worlds/alttp/test/owg/TestDeathMountain.py | 84 +-- worlds/alttp/test/owg/TestDungeons.py | 19 +- worlds/alttp/test/owg/TestLightWorld.py | 63 +- worlds/alttp/test/owg/TestVanillaOWG.py | 4 +- worlds/alttp/test/vanilla/TestDarkWorld.py | 64 +- .../alttp/test/vanilla/TestDeathMountain.py | 61 +- worlds/alttp/test/vanilla/TestLightWorld.py | 60 +- worlds/alttp/test/vanilla/TestVanilla.py | 4 +- 60 files changed, 1926 insertions(+), 2026 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 4002800173..25e4e70741 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -159,11 +159,11 @@ class MultiWorld(): self.fix_trock_doors = self.AttributeProxy( lambda player: self.shuffle[player] != 'vanilla' or self.mode[player] == 'inverted') self.fix_skullwoods_exit = self.AttributeProxy( - lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']) + lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeons_simple']) self.fix_palaceofdarkness_exit = self.AttributeProxy( - lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']) + lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeons_simple']) self.fix_trock_exit = self.AttributeProxy( - lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']) + lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeons_simple']) for player in range(1, players + 1): def set_player_attr(attr, val): diff --git a/Generate.py b/Generate.py index e19a7a973f..fd4a5a7e19 100644 --- a/Generate.py +++ b/Generate.py @@ -315,20 +315,6 @@ def prefer_int(input_data: str) -> Union[str, int]: return input_data -goals = { - 'ganon': 'ganon', - 'crystals': 'crystals', - 'bosses': 'bosses', - 'pedestal': 'pedestal', - 'ganon_pedestal': 'ganonpedestal', - 'triforce_hunt': 'triforcehunt', - 'local_triforce_hunt': 'localtriforcehunt', - 'ganon_triforce_hunt': 'ganontriforcehunt', - 'local_ganon_triforce_hunt': 'localganontriforcehunt', - 'ice_rod_hunt': 'icerodhunt', -} - - def roll_percentage(percentage: Union[int, float]) -> bool: """Roll a percentage chance. percentage is expected to be in range [0, 100]""" @@ -357,15 +343,6 @@ def roll_meta_option(option_key, game: str, category_dict: Dict) -> Any: if options[option_key].supports_weighting: return get_choice(option_key, category_dict) return category_dict[option_key] - if game == "A Link to the Past": # TODO wow i hate this - if option_key in {"glitches_required", "dark_room_logic", "entrance_shuffle", "goals", "triforce_pieces_mode", - "triforce_pieces_percentage", "triforce_pieces_available", "triforce_pieces_extra", - "triforce_pieces_required", "shop_shuffle", "mode", "item_pool", "item_functionality", - "boss_shuffle", "enemy_damage", "enemy_health", "timer", "countdown_start_time", - "red_clock_time", "blue_clock_time", "green_clock_time", "dungeon_counters", "shuffle_prizes", - "misery_mire_medallion", "turtle_rock_medallion", "sprite_pool", "sprite", - "random_sprite_on_event"}: - return get_choice(option_key, category_dict) raise Exception(f"Error generating meta option {option_key} for {game}.") @@ -504,101 +481,6 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options): - if "dungeon_items" in weights and get_choice_legacy('dungeon_items', weights, "none") != "none": - raise Exception(f"dungeon_items key in A Link to the Past was removed, but is present in these weights as {get_choice_legacy('dungeon_items', weights, False)}.") - glitches_required = get_choice_legacy('glitches_required', weights) - if glitches_required not in [None, 'none', 'no_logic', 'overworld_glitches', 'hybrid_major_glitches', 'minor_glitches']: - logging.warning("Only NMG, OWG, HMG and No Logic supported") - glitches_required = 'none' - ret.logic = {None: 'noglitches', 'none': 'noglitches', 'no_logic': 'nologic', 'overworld_glitches': 'owglitches', - 'minor_glitches': 'minorglitches', 'hybrid_major_glitches': 'hybridglitches'}[ - glitches_required] - - ret.dark_room_logic = get_choice_legacy("dark_room_logic", weights, "lamp") - if not ret.dark_room_logic: # None/False - ret.dark_room_logic = "none" - if ret.dark_room_logic == "sconces": - ret.dark_room_logic = "torches" - if ret.dark_room_logic not in {"lamp", "torches", "none"}: - raise ValueError(f"Unknown Dark Room Logic: \"{ret.dark_room_logic}\"") - - entrance_shuffle = get_choice_legacy('entrance_shuffle', weights, 'vanilla') - if entrance_shuffle.startswith('none-'): - ret.shuffle = 'vanilla' - else: - ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla' - - goal = get_choice_legacy('goals', weights, 'ganon') - - ret.goal = goals[goal] - - - extra_pieces = get_choice_legacy('triforce_pieces_mode', weights, 'available') - - ret.triforce_pieces_required = LttPOptions.TriforcePieces.from_any(get_choice_legacy('triforce_pieces_required', weights, 20)) - - # sum a percentage to required - if extra_pieces == 'percentage': - percentage = max(100, float(get_choice_legacy('triforce_pieces_percentage', weights, 150))) / 100 - ret.triforce_pieces_available = int(round(ret.triforce_pieces_required * percentage, 0)) - # vanilla mode (specify how many pieces are) - elif extra_pieces == 'available': - ret.triforce_pieces_available = LttPOptions.TriforcePieces.from_any( - get_choice_legacy('triforce_pieces_available', weights, 30)) - # required pieces + fixed extra - elif extra_pieces == 'extra': - extra_pieces = max(0, int(get_choice_legacy('triforce_pieces_extra', weights, 10))) - ret.triforce_pieces_available = ret.triforce_pieces_required + extra_pieces - - # change minimum to required pieces to avoid problems - ret.triforce_pieces_available = min(max(ret.triforce_pieces_required, int(ret.triforce_pieces_available)), 90) - - ret.shop_shuffle = get_choice_legacy('shop_shuffle', weights, '') - if not ret.shop_shuffle: - ret.shop_shuffle = '' - - ret.mode = get_choice_legacy("mode", weights) - - ret.difficulty = get_choice_legacy('item_pool', weights) - - ret.item_functionality = get_choice_legacy('item_functionality', weights) - - - ret.enemy_damage = {None: 'default', - 'default': 'default', - 'shuffled': 'shuffled', - 'random': 'chaos', # to be removed - 'chaos': 'chaos', - }[get_choice_legacy('enemy_damage', weights)] - - ret.enemy_health = get_choice_legacy('enemy_health', weights) - - ret.timer = {'none': False, - None: False, - False: False, - 'timed': 'timed', - 'timed_ohko': 'timed-ohko', - 'ohko': 'ohko', - 'timed_countdown': 'timed-countdown', - 'display': 'display'}[get_choice_legacy('timer', weights, False)] - - ret.countdown_start_time = int(get_choice_legacy('countdown_start_time', weights, 10)) - ret.red_clock_time = int(get_choice_legacy('red_clock_time', weights, -2)) - ret.blue_clock_time = int(get_choice_legacy('blue_clock_time', weights, 2)) - ret.green_clock_time = int(get_choice_legacy('green_clock_time', weights, 4)) - - ret.dungeon_counters = get_choice_legacy('dungeon_counters', weights, 'default') - - ret.shuffle_prizes = get_choice_legacy('shuffle_prizes', weights, "g") - - ret.required_medallions = [get_choice_legacy("misery_mire_medallion", weights, "random"), - get_choice_legacy("turtle_rock_medallion", weights, "random")] - - for index, medallion in enumerate(ret.required_medallions): - ret.required_medallions[index] = {"ether": "Ether", "quake": "Quake", "bombos": "Bombos", "random": "random"} \ - .get(medallion.lower(), None) - if not ret.required_medallions[index]: - raise Exception(f"unknown Medallion {medallion} for {'misery mire' if index == 0 else 'turtle rock'}") ret.plando_texts = {} if PlandoOptions.texts in plando_options: diff --git a/data/basepatch.bsdiff4 b/data/basepatch.bsdiff4 index a578b248f57721cfc7e8c31da233bf732ed27db8..aa8f1375c522b724ae1944ebb6de113a7e592a35 100644 GIT binary patch literal 114116 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 wa4TS7uXZs4i)~=>>)= bool: def HelmasaurKingDefeatRule(state, player: int) -> bool: # TODO: technically possible with the hammer - return has_sword(state, player) or can_shoot_arrows(state, player) + return (can_use_bombs(state, player, 5) or state.has("Hammer", player)) and (has_sword(state, player) + or can_shoot_arrows(state, player)) def ArrghusDefeatRule(state, player: int) -> bool: @@ -143,7 +144,7 @@ def GanonDefeatRule(state, player: int) -> bool: can_hurt = has_beam_sword(state, player) common = can_hurt and has_fire_source(state, player) # silverless ganon may be needed in anything higher than no glitches - if state.multiworld.logic[player] != 'noglitches': + if state.multiworld.glitches_required[player] != 'no_glitches': # need to light torch a sufficient amount of times return common and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or ( state.has('Silver Bow', player) and can_shoot_arrows(state, player)) or diff --git a/worlds/alttp/Dungeons.py b/worlds/alttp/Dungeons.py index b456174f39..c886fce920 100644 --- a/worlds/alttp/Dungeons.py +++ b/worlds/alttp/Dungeons.py @@ -9,7 +9,7 @@ from Fill import fill_restrictive from .Bosses import BossFactory, Boss from .Items import ItemFactory from .Regions import lookup_boss_drops, key_drop_data -from .Options import smallkey_shuffle +from .Options import small_key_shuffle if typing.TYPE_CHECKING: from .SubClasses import ALttPLocation, ALttPItem @@ -66,7 +66,7 @@ def create_dungeons(world: "ALTTPWorld"): def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items): dungeon = Dungeon(name, dungeon_regions, big_key, - [] if multiworld.smallkey_shuffle[player] == smallkey_shuffle.option_universal else small_keys, + [] if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal else small_keys, dungeon_items, player) for item in dungeon.all_items: item.dungeon = dungeon diff --git a/worlds/alttp/EntranceRandomizer.py b/worlds/alttp/EntranceRandomizer.py index 47c36b6cde..37486a9cde 100644 --- a/worlds/alttp/EntranceRandomizer.py +++ b/worlds/alttp/EntranceRandomizer.py @@ -23,7 +23,7 @@ def parse_arguments(argv, no_defaults=False): multiargs, _ = parser.parse_known_args(argv) parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) - parser.add_argument('--logic', default=defval('noglitches'), const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'owglitches', 'hybridglitches', 'nologic'], + parser.add_argument('--logic', default=defval('no_glitches'), const='no_glitches', nargs='?', choices=['no_glitches', 'minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'], help='''\ Select Enforcement of Item Requirements. (default: %(default)s) No Glitches: @@ -49,7 +49,7 @@ def parse_arguments(argv, no_defaults=False): instead of a bunny. ''') parser.add_argument('--goal', default=defval('ganon'), const='ganon', nargs='?', - choices=['ganon', 'pedestal', 'bosses', 'triforcehunt', 'localtriforcehunt', 'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal'], + choices=['ganon', 'pedestal', 'bosses', 'triforce_hunt', 'local_triforce_hunt', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'crystals', 'ganon_pedestal'], help='''\ Select completion goal. (default: %(default)s) Ganon: Collect all crystals, beat Agahnim 2 then @@ -92,7 +92,7 @@ def parse_arguments(argv, no_defaults=False): Hard: Reduced functionality. Expert: Greatly reduced functionality. ''') - parser.add_argument('--timer', default=defval('none'), const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'], + parser.add_argument('--timer', default=defval('none'), const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed_ohko', 'ohko', 'timed_countdown'], help='''\ Select game timer setting. Affects available itempool. (default: %(default)s) None: No timer. @@ -151,7 +151,7 @@ def parse_arguments(argv, no_defaults=False): slightly biased to placing progression items with less restrictions. ''') - parser.add_argument('--shuffle', default=defval('vanilla'), const='vanilla', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple', 'dungeonscrossed'], + parser.add_argument('--shuffle', default=defval('vanilla'), const='vanilla', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed'], help='''\ Select Entrance Shuffling Algorithm. (default: %(default)s) Full: Mix cave and dungeon entrances freely while limiting @@ -178,9 +178,9 @@ def parse_arguments(argv, no_defaults=False): parser.add_argument('--open_pyramid', default=defval('auto'), help='''\ Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it. Depending on goal, you might still need to beat Agahnim 2 in order to beat ganon. - fast ganon goals are crystals, ganontriforcehunt, localganontriforcehunt, pedestalganon + fast ganon goals are crystals, ganon_triforce_hunt, local_ganon_triforce_hunt, pedestalganon auto - Only opens pyramid hole if the goal specifies a fast ganon, and entrance shuffle - is vanilla, dungeonssimple or dungeonsfull. + is vanilla, dungeons_simple or dungeons_full. goal - Opens pyramid hole if the goal specifies a fast ganon. yes - Always opens the pyramid hole. no - Never opens the pyramid hole. diff --git a/worlds/alttp/EntranceShuffle.py b/worlds/alttp/EntranceShuffle.py index 07bb587eeb..fceba86a73 100644 --- a/worlds/alttp/EntranceShuffle.py +++ b/worlds/alttp/EntranceShuffle.py @@ -21,17 +21,17 @@ def link_entrances(world, player): connect_simple(world, exitname, regionname, player) # if we do not shuffle, set default connections - if world.shuffle[player] == 'vanilla': + if world.entrance_shuffle[player] == 'vanilla': for exitname, regionname in default_connections: connect_simple(world, exitname, regionname, player) for exitname, regionname in default_dungeon_connections: connect_simple(world, exitname, regionname, player) - elif world.shuffle[player] == 'dungeonssimple': + elif world.entrance_shuffle[player] == 'dungeons_simple': for exitname, regionname in default_connections: connect_simple(world, exitname, regionname, player) simple_shuffle_dungeons(world, player) - elif world.shuffle[player] == 'dungeonsfull': + elif world.entrance_shuffle[player] == 'dungeons_full': for exitname, regionname in default_connections: connect_simple(world, exitname, regionname, player) @@ -63,9 +63,9 @@ def link_entrances(world, player): connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player) connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player) connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player) - elif world.shuffle[player] == 'dungeonscrossed': + elif world.entrance_shuffle[player] == 'dungeons_crossed': crossed_shuffle_dungeons(world, player) - elif world.shuffle[player] == 'simple': + elif world.entrance_shuffle[player] == 'simple': simple_shuffle_dungeons(world, player) old_man_entrances = list(Old_Man_Entrances) @@ -136,7 +136,7 @@ def link_entrances(world, player): # place remaining doors connect_doors(world, single_doors, door_targets, player) - elif world.shuffle[player] == 'restricted': + elif world.entrance_shuffle[player] == 'restricted': simple_shuffle_dungeons(world, player) lw_entrances = list(LW_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances) @@ -207,62 +207,8 @@ def link_entrances(world, player): # place remaining doors connect_doors(world, doors, door_targets, player) - elif world.shuffle[player] == 'restricted_legacy': - simple_shuffle_dungeons(world, player) - lw_entrances = list(LW_Entrances) - dw_entrances = list(DW_Entrances) - dw_must_exits = list(DW_Entrances_Must_Exit) - old_man_entrances = list(Old_Man_Entrances) - caves = list(Cave_Exits) - three_exit_caves = list(Cave_Three_Exits) - single_doors = list(Single_Cave_Doors) - bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors) - door_targets = list(Single_Cave_Targets) - - # only use two exit caves to do mandatory dw connections - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) - # add three exit doors to pool for remainder - caves.extend(three_exit_caves) - - # place old man, has limited options - # exit has to come from specific set of doors, the entrance is free to move about - world.random.shuffle(old_man_entrances) - old_man_exit = old_man_entrances.pop() - lw_entrances.extend(old_man_entrances) - world.random.shuffle(lw_entrances) - old_man_entrance = lw_entrances.pop() - connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) - connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) - - # place Old Man House in Light World - connect_caves(world, lw_entrances, [], Old_Man_House, player) - - # connect rest. There's 2 dw entrances remaining, so we will not run into parity issue placing caves - connect_caves(world, lw_entrances, dw_entrances, caves, player) - - # scramble holes - scramble_holes(world, player) - - # place blacksmith, has limited options - world.random.shuffle(blacksmith_doors) - blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) - bomb_shop_doors.extend(blacksmith_doors) - - # place dam and pyramid fairy, have limited options - world.random.shuffle(bomb_shop_doors) - bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) - single_doors.extend(bomb_shop_doors) - - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - - # place remaining doors - connect_doors(world, single_doors, door_targets, player) - elif world.shuffle[player] == 'full': + elif world.entrance_shuffle[player] == 'full': skull_woods_shuffle(world, player) lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances) @@ -368,7 +314,7 @@ def link_entrances(world, player): # place remaining doors connect_doors(world, doors, door_targets, player) - elif world.shuffle[player] == 'crossed': + elif world.entrance_shuffle[player] == 'crossed': skull_woods_shuffle(world, player) entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances + DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors) @@ -445,337 +391,8 @@ def link_entrances(world, player): # place remaining doors connect_doors(world, entrances, door_targets, player) - elif world.shuffle[player] == 'full_legacy': - skull_woods_shuffle(world, player) - lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances) - dw_entrances = list(DW_Entrances + DW_Dungeon_Entrances) - dw_must_exits = list(DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit) - lw_must_exits = list(LW_Dungeon_Entrances_Must_Exit) - old_man_entrances = list(Old_Man_Entrances + ['Tower of Hera']) - caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits) # don't need to consider three exit caves, have one exit caves to avoid parity issues - single_doors = list(Single_Cave_Doors) - bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors) - door_targets = list(Single_Cave_Targets) - - if world.mode[player] == 'standard': - # must connect front of hyrule castle to do escape - connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) - else: - caves.append(tuple(world.random.sample( - ['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'], 3))) - lw_entrances.append('Hyrule Castle Entrance (South)') - - if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) - else: - dw_entrances.append('Ganons Tower') - caves.append('Ganons Tower Exit') - - # we randomize which world requirements we fulfill first so we get better dungeon distribution - if world.random.randint(0, 1) == 0: - connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) - else: - connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player) - connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player) - if world.mode[player] == 'standard': - # rest of hyrule castle must be in light world - connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) - - # place old man, has limited options - # exit has to come from specific set of doors, the entrance is free to move about - old_man_entrances = [door for door in old_man_entrances if door in lw_entrances] - world.random.shuffle(old_man_entrances) - old_man_exit = old_man_entrances.pop() - lw_entrances.remove(old_man_exit) - - world.random.shuffle(lw_entrances) - old_man_entrance = lw_entrances.pop() - connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player) - connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player) - - # place Old Man House in Light World - connect_caves(world, lw_entrances, [], list(Old_Man_House), player) #need this to avoid badness with multiple seeds - - # now scramble the rest - connect_caves(world, lw_entrances, dw_entrances, caves, player) - - # scramble holes - scramble_holes(world, player) - - # place blacksmith, has limited options - world.random.shuffle(blacksmith_doors) - blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) - bomb_shop_doors.extend(blacksmith_doors) - - # place bomb shop, has limited options - world.random.shuffle(bomb_shop_doors) - bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) - single_doors.extend(bomb_shop_doors) - - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - - # place remaining doors - connect_doors(world, single_doors, door_targets, player) - elif world.shuffle[player] == 'madness_legacy': - # here lie dragons, connections are no longer two way - lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances) - dw_entrances = list(DW_Entrances + DW_Dungeon_Entrances) - dw_entrances_must_exits = list(DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit) - - lw_doors = list(LW_Entrances + LW_Dungeon_Entrances + LW_Dungeon_Entrances_Must_Exit) + ['Kakariko Well Cave', - 'Bat Cave Cave', - 'North Fairy Cave', - 'Sanctuary', - 'Lost Woods Hideout Stump', - 'Lumberjack Tree Cave'] + list( - Old_Man_Entrances) - dw_doors = list( - DW_Entrances + DW_Dungeon_Entrances + DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit) + [ - 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', - 'Skull Woods Second Section Door (West)'] - - world.random.shuffle(lw_doors) - world.random.shuffle(dw_doors) - - dw_entrances_must_exits.append('Skull Woods Second Section Door (West)') - dw_entrances.append('Skull Woods Second Section Door (East)') - dw_entrances.append('Skull Woods First Section Door') - - lw_entrances.extend( - ['Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', - 'Lumberjack Tree Cave']) - - lw_entrances_must_exits = list(LW_Dungeon_Entrances_Must_Exit) - - old_man_entrances = list(Old_Man_Entrances) + ['Tower of Hera'] - - mandatory_light_world = ['Old Man House Exit (Bottom)', 'Old Man House Exit (Top)'] - mandatory_dark_world = [] - caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits) - - # shuffle up holes - - lw_hole_entrances = ['Kakariko Well Drop', 'Bat Cave Drop', 'North Fairy Cave Drop', 'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave'] - dw_hole_entrances = ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'] - - hole_targets = [('Kakariko Well Exit', 'Kakariko Well (top)'), - ('Bat Cave Exit', 'Bat Cave (right)'), - ('North Fairy Cave Exit', 'North Fairy Cave'), - ('Lost Woods Hideout Exit', 'Lost Woods Hideout (top)'), - ('Lumberjack Tree Exit', 'Lumberjack Tree (top)'), - (('Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'), 'Skull Woods Second Section (Drop)')] - - if world.mode[player] == 'standard': - # cannot move uncle cave - connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) - connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player) - connect_entrance(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player) - else: - lw_hole_entrances.append('Hyrule Castle Secret Entrance Drop') - hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance')) - lw_doors.append('Hyrule Castle Secret Entrance Stairs') - lw_entrances.append('Hyrule Castle Secret Entrance Stairs') - - if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) - connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player) - connect_entrance(world, 'Pyramid Hole', 'Pyramid', player) - else: - dw_entrances.append('Ganons Tower') - caves.append('Ganons Tower Exit') - dw_hole_entrances.append('Pyramid Hole') - hole_targets.append(('Pyramid Exit', 'Pyramid')) - dw_entrances_must_exits.append('Pyramid Entrance') - dw_doors.extend(['Ganons Tower', 'Pyramid Entrance']) - - world.random.shuffle(lw_hole_entrances) - world.random.shuffle(dw_hole_entrances) - world.random.shuffle(hole_targets) - - # decide if skull woods first section should be in light or dark world - sw_light = world.random.randint(0, 1) == 0 - if sw_light: - sw_hole_pool = lw_hole_entrances - mandatory_light_world.append('Skull Woods First Section Exit') - else: - sw_hole_pool = dw_hole_entrances - mandatory_dark_world.append('Skull Woods First Section Exit') - for target in ['Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', - 'Skull Woods First Section (Top)']: - connect_entrance(world, sw_hole_pool.pop(), target, player) - - # sanctuary has to be in light world - connect_entrance(world, lw_hole_entrances.pop(), 'Sewer Drop', player) - mandatory_light_world.append('Sanctuary Exit') - - # fill up remaining holes - for hole in dw_hole_entrances: - exits, target = hole_targets.pop() - mandatory_dark_world.append(exits) - connect_entrance(world, hole, target, player) - - for hole in lw_hole_entrances: - exits, target = hole_targets.pop() - mandatory_light_world.append(exits) - connect_entrance(world, hole, target, player) - - # hyrule castle handling - if world.mode[player] == 'standard': - # must connect front of hyrule castle to do escape - connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) - connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player) - mandatory_light_world.append(('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) - else: - lw_doors.append('Hyrule Castle Entrance (South)') - lw_entrances.append('Hyrule Castle Entrance (South)') - caves.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) - - # now let's deal with mandatory reachable stuff - def extract_reachable_exit(cavelist): - world.random.shuffle(cavelist) - candidate = None - for cave in cavelist: - if isinstance(cave, tuple) and len(cave) > 1: - # special handling: TRock and Spectracle Rock cave have two entries that we should consider entrance only - # ToDo this should be handled in a more sensible manner - if cave[0] in ['Turtle Rock Exit (Front)', 'Spectacle Rock Cave Exit (Peak)'] and len(cave) == 2: - continue - candidate = cave - break - if candidate is None: - raise KeyError('No suitable cave.') - cavelist.remove(candidate) - return candidate - - def connect_reachable_exit(entrance, general, worldspecific, worldoors): - # select which one is the primary option - if world.random.randint(0, 1) == 0: - primary = general - secondary = worldspecific - else: - primary = worldspecific - secondary = general - - try: - cave = extract_reachable_exit(primary) - except KeyError: - cave = extract_reachable_exit(secondary) - - exit = cave[-1] - cave = cave[:-1] - connect_exit(world, exit, entrance, player) - connect_entrance(world, worldoors.pop(), exit, player) - # rest of cave now is forced to be in this world - worldspecific.append(cave) - - # we randomize which world requirements we fulfill first so we get better dungeon distribution - if world.random.randint(0, 1) == 0: - for entrance in lw_entrances_must_exits: - connect_reachable_exit(entrance, caves, mandatory_light_world, lw_doors) - for entrance in dw_entrances_must_exits: - connect_reachable_exit(entrance, caves, mandatory_dark_world, dw_doors) - else: - for entrance in dw_entrances_must_exits: - connect_reachable_exit(entrance, caves, mandatory_dark_world, dw_doors) - for entrance in lw_entrances_must_exits: - connect_reachable_exit(entrance, caves, mandatory_light_world, lw_doors) - - # place old man, has limited options - # exit has to come from specific set of doors, the entrance is free to move about - old_man_entrances = [entrance for entrance in old_man_entrances if entrance in lw_entrances] - world.random.shuffle(old_man_entrances) - old_man_exit = old_man_entrances.pop() - lw_entrances.remove(old_man_exit) - - connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit, player) - connect_entrance(world, lw_doors.pop(), 'Old Man Cave Exit (East)', player) - mandatory_light_world.append('Old Man Cave Exit (West)') - - # we connect up the mandatory associations we have found - for mandatory in mandatory_light_world: - if not isinstance(mandatory, tuple): - mandatory = (mandatory,) - for exit in mandatory: - # point out somewhere - connect_exit(world, exit, lw_entrances.pop(), player) - # point in from somewhere - connect_entrance(world, lw_doors.pop(), exit, player) - - for mandatory in mandatory_dark_world: - if not isinstance(mandatory, tuple): - mandatory = (mandatory,) - for exit in mandatory: - # point out somewhere - connect_exit(world, exit, dw_entrances.pop(), player) - # point in from somewhere - connect_entrance(world, dw_doors.pop(), exit, player) - - # handle remaining caves - while caves: - # connect highest exit count caves first, prevent issue where we have 2 or 3 exits accross worlds left to fill - cave_candidate = (None, 0) - for i, cave in enumerate(caves): - if isinstance(cave, str): - cave = (cave,) - if len(cave) > cave_candidate[1]: - cave_candidate = (i, len(cave)) - cave = caves.pop(cave_candidate[0]) - - place_lightworld = world.random.randint(0, 1) == 0 - if place_lightworld: - target_doors = lw_doors - target_entrances = lw_entrances - else: - target_doors = dw_doors - target_entrances = dw_entrances - - if isinstance(cave, str): - cave = (cave,) - - # check if we can still fit the cave into our target group - if len(target_doors) < len(cave): - if not place_lightworld: - target_doors = lw_doors - target_entrances = lw_entrances - else: - target_doors = dw_doors - target_entrances = dw_entrances - - for exit in cave: - connect_exit(world, exit, target_entrances.pop(), player) - connect_entrance(world, target_doors.pop(), exit, player) - - # handle simple doors - - single_doors = list(Single_Cave_Doors) - bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors) - door_targets = list(Single_Cave_Targets) - - # place blacksmith, has limited options - world.random.shuffle(blacksmith_doors) - blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) - bomb_shop_doors.extend(blacksmith_doors) - - # place dam and pyramid fairy, have limited options - world.random.shuffle(bomb_shop_doors) - bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) - single_doors.extend(bomb_shop_doors) - - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - - # place remaining doors - connect_doors(world, single_doors, door_targets, player) - elif world.shuffle[player] == 'insanity': + elif world.entrance_shuffle[player] == 'insanity': # beware ye who enter here entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave'] @@ -922,157 +539,15 @@ def link_entrances(world, player): # place remaining doors connect_doors(world, doors, door_targets, player) - elif world.shuffle[player] == 'insanity_legacy': - world.fix_fake_world[player] = False - # beware ye who enter here - entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave'] - entrances_must_exits = DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + LW_Dungeon_Entrances_Must_Exit + ['Skull Woods Second Section Door (West)'] - - doors = LW_Entrances + LW_Dungeon_Entrances + LW_Dungeon_Entrances_Must_Exit + ['Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave'] + Old_Man_Entrances +\ - DW_Entrances + DW_Dungeon_Entrances + DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] - - world.random.shuffle(doors) - - old_man_entrances = list(Old_Man_Entrances) + ['Tower of Hera'] - - caves = Cave_Exits + Dungeon_Exits + Cave_Three_Exits + ['Old Man House Exit (Bottom)', 'Old Man House Exit (Top)', 'Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)', - 'Kakariko Well Exit', 'Bat Cave Exit', 'North Fairy Cave Exit', 'Lost Woods Hideout Exit', 'Lumberjack Tree Exit', 'Sanctuary Exit'] - - # shuffle up holes - - hole_entrances = ['Kakariko Well Drop', 'Bat Cave Drop', 'North Fairy Cave Drop', 'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave', - 'Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'] - - hole_targets = ['Kakariko Well (top)', 'Bat Cave (right)', 'North Fairy Cave', 'Lost Woods Hideout (top)', 'Lumberjack Tree (top)', 'Sewer Drop', 'Skull Woods Second Section (Drop)', - 'Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Top)'] - - if world.mode[player] == 'standard': - # cannot move uncle cave - connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) - connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player) - connect_entrance(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player) - else: - hole_entrances.append('Hyrule Castle Secret Entrance Drop') - hole_targets.append('Hyrule Castle Secret Entrance') - doors.append('Hyrule Castle Secret Entrance Stairs') - entrances.append('Hyrule Castle Secret Entrance Stairs') - caves.append('Hyrule Castle Secret Entrance Exit') - - if not world.shuffle_ganon: - connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) - connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player) - connect_entrance(world, 'Pyramid Hole', 'Pyramid', player) - else: - entrances.append('Ganons Tower') - caves.extend(['Ganons Tower Exit', 'Pyramid Exit']) - hole_entrances.append('Pyramid Hole') - hole_targets.append('Pyramid') - entrances_must_exits.append('Pyramid Entrance') - doors.extend(['Ganons Tower', 'Pyramid Entrance']) - - world.random.shuffle(hole_entrances) - world.random.shuffle(hole_targets) - world.random.shuffle(entrances) - - # fill up holes - for hole in hole_entrances: - connect_entrance(world, hole, hole_targets.pop(), player) - - # hyrule castle handling - if world.mode[player] == 'standard': - # must connect front of hyrule castle to do escape - connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) - connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player) - caves.append(('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) - else: - doors.append('Hyrule Castle Entrance (South)') - entrances.append('Hyrule Castle Entrance (South)') - caves.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) - - # now let's deal with mandatory reachable stuff - def extract_reachable_exit(cavelist): - world.random.shuffle(cavelist) - candidate = None - for cave in cavelist: - if isinstance(cave, tuple) and len(cave) > 1: - # special handling: TRock has two entries that we should consider entrance only - # ToDo this should be handled in a more sensible manner - if cave[0] in ['Turtle Rock Exit (Front)', 'Spectacle Rock Cave Exit (Peak)'] and len(cave) == 2: - continue - candidate = cave - break - if candidate is None: - raise KeyError('No suitable cave.') - cavelist.remove(candidate) - return candidate - - def connect_reachable_exit(entrance, caves, doors): - cave = extract_reachable_exit(caves) - - exit = cave[-1] - cave = cave[:-1] - connect_exit(world, exit, entrance, player) - connect_entrance(world, doors.pop(), exit, player) - # rest of cave now is forced to be in this world - caves.append(cave) - - # connect mandatory exits - for entrance in entrances_must_exits: - connect_reachable_exit(entrance, caves, doors) - - # place old man, has limited options - # exit has to come from specific set of doors, the entrance is free to move about - old_man_entrances = [entrance for entrance in old_man_entrances if entrance in entrances] - world.random.shuffle(old_man_entrances) - old_man_exit = old_man_entrances.pop() - entrances.remove(old_man_exit) - - connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit, player) - connect_entrance(world, doors.pop(), 'Old Man Cave Exit (East)', player) - caves.append('Old Man Cave Exit (West)') - - # handle remaining caves - for cave in caves: - if isinstance(cave, str): - cave = (cave,) - - for exit in cave: - connect_exit(world, exit, entrances.pop(), player) - connect_entrance(world, doors.pop(), exit, player) - - # handle simple doors - - single_doors = list(Single_Cave_Doors) - bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors) - blacksmith_doors = list(Blacksmith_Single_Cave_Doors) - door_targets = list(Single_Cave_Targets) - - # place blacksmith, has limited options - world.random.shuffle(blacksmith_doors) - blacksmith_hut = blacksmith_doors.pop() - connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player) - bomb_shop_doors.extend(blacksmith_doors) - - # place dam and pyramid fairy, have limited options - world.random.shuffle(bomb_shop_doors) - bomb_shop = bomb_shop_doors.pop() - connect_entrance(world, bomb_shop, 'Big Bomb Shop', player) - single_doors.extend(bomb_shop_doors) - - # tavern back door cannot be shuffled yet - connect_doors(world, ['Tavern North'], ['Tavern'], player) - - # place remaining doors - connect_doors(world, single_doors, door_targets, player) else: raise NotImplementedError( - f'{world.shuffle[player]} Shuffling not supported yet. Player {world.get_player_name(player)}') + f'{world.entrance_shuffle[player]} Shuffling not supported yet. Player {world.get_player_name(player)}') - if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: + if world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: overworld_glitch_connections(world, player) # mandatory hybrid major glitches connections - if world.logic[player] in ['hybridglitches', 'nologic']: + if world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']: underworld_glitch_connections(world, player) # check for swamp palace fix @@ -1106,17 +581,17 @@ def link_inverted_entrances(world, player): connect_simple(world, exitname, regionname, player) # if we do not shuffle, set default connections - if world.shuffle[player] == 'vanilla': + if world.entrance_shuffle[player] == 'vanilla': for exitname, regionname in inverted_default_connections: connect_simple(world, exitname, regionname, player) for exitname, regionname in inverted_default_dungeon_connections: connect_simple(world, exitname, regionname, player) - elif world.shuffle[player] == 'dungeonssimple': + elif world.entrance_shuffle[player] == 'dungeons_simple': for exitname, regionname in inverted_default_connections: connect_simple(world, exitname, regionname, player) simple_shuffle_dungeons(world, player) - elif world.shuffle[player] == 'dungeonsfull': + elif world.entrance_shuffle[player] == 'dungeons_full': for exitname, regionname in inverted_default_connections: connect_simple(world, exitname, regionname, player) @@ -1171,9 +646,9 @@ def link_inverted_entrances(world, player): connect_mandatory_exits(world, lw_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player) connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player) - elif world.shuffle[player] == 'dungeonscrossed': + elif world.entrance_shuffle[player] == 'dungeons_crossed': inverted_crossed_shuffle_dungeons(world, player) - elif world.shuffle[player] == 'simple': + elif world.entrance_shuffle[player] == 'simple': simple_shuffle_dungeons(world, player) old_man_entrances = list(Inverted_Old_Man_Entrances) @@ -1270,7 +745,7 @@ def link_inverted_entrances(world, player): # place remaining doors connect_doors(world, single_doors, door_targets, player) - elif world.shuffle[player] == 'restricted': + elif world.entrance_shuffle[player] == 'restricted': simple_shuffle_dungeons(world, player) lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Single_Cave_Doors) @@ -1355,7 +830,7 @@ def link_inverted_entrances(world, player): doors = lw_entrances + dw_entrances # place remaining doors connect_doors(world, doors, door_targets, player) - elif world.shuffle[player] == 'full': + elif world.entrance_shuffle[player] == 'full': skull_woods_shuffle(world, player) lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors) @@ -1506,7 +981,7 @@ def link_inverted_entrances(world, player): # place remaining doors connect_doors(world, doors, door_targets, player) - elif world.shuffle[player] == 'crossed': + elif world.entrance_shuffle[player] == 'crossed': skull_woods_shuffle(world, player) entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors + Inverted_Old_Man_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_DW_Single_Cave_Doors) @@ -1617,7 +1092,7 @@ def link_inverted_entrances(world, player): # place remaining doors connect_doors(world, entrances, door_targets, player) - elif world.shuffle[player] == 'insanity': + elif world.entrance_shuffle[player] == 'insanity': # beware ye who enter here entrances = Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Entrance (South)'] @@ -1776,10 +1251,10 @@ def link_inverted_entrances(world, player): else: raise NotImplementedError('Shuffling not supported yet') - if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: + if world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: overworld_glitch_connections(world, player) # mandatory hybrid major glitches connections - if world.logic[player] in ['hybridglitches', 'nologic']: + if world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']: underworld_glitch_connections(world, player) # patch swamp drain @@ -1880,14 +1355,14 @@ def scramble_holes(world, player): hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance')) # do not shuffle sanctuary into pyramid hole unless shuffle is crossed - if world.shuffle[player] == 'crossed': + if world.entrance_shuffle[player] == 'crossed': hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) if world.shuffle_ganon: world.random.shuffle(hole_targets) exit, target = hole_targets.pop() connect_two_way(world, 'Pyramid Entrance', exit, player) connect_entrance(world, 'Pyramid Hole', target, player) - if world.shuffle[player] != 'crossed': + if world.entrance_shuffle[player] != 'crossed': hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) world.random.shuffle(hole_targets) @@ -1922,14 +1397,14 @@ def scramble_inverted_holes(world, player): hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance')) # do not shuffle sanctuary into pyramid hole unless shuffle is crossed - if world.shuffle[player] == 'crossed': + if world.entrance_shuffle[player] == 'crossed': hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) if world.shuffle_ganon: world.random.shuffle(hole_targets) exit, target = hole_targets.pop() connect_two_way(world, 'Inverted Pyramid Entrance', exit, player) connect_entrance(world, 'Inverted Pyramid Hole', target, player) - if world.shuffle[player] != 'crossed': + if world.entrance_shuffle[player] != 'crossed': hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) world.random.shuffle(hole_targets) @@ -1958,7 +1433,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): invalid_connections = Must_Exit_Invalid_Connections.copy() invalid_cave_connections = defaultdict(set) - if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: + if world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: from worlds.alttp import OverworldGlitchRules for entrance in OverworldGlitchRules.get_non_mandatory_exits(world.mode[player] == 'inverted'): invalid_connections[entrance] = set() @@ -3038,6 +2513,7 @@ mandatory_connections = [('Links House S&Q', 'Links House'), ('Sanctuary Push Door', 'Sanctuary'), ('Sewer Drop', 'Sewers'), ('Sewers Back Door', 'Sewers (Dark)'), + ('Sewers Secret Room', 'Sewers Secret Room'), ('Agahnim 1', 'Agahnim 1'), ('Flute Spot 1', 'Death Mountain'), ('Death Mountain Entrance Rock', 'Death Mountain Entrance'), @@ -3053,6 +2529,8 @@ mandatory_connections = [('Links House S&Q', 'Links House'), ('Spiral Cave Ledge Access', 'Spiral Cave Ledge'), ('Spiral Cave Ledge Drop', 'East Death Mountain (Bottom)'), ('Spiral Cave (top to bottom)', 'Spiral Cave (Bottom)'), + ('Hookshot Cave Bomb Wall (South)', 'Hookshot Cave (Upper)'), + ('Hookshot Cave Bomb Wall (North)', 'Hookshot Cave'), ('East Death Mountain (Top)', 'East Death Mountain (Top)'), ('Death Mountain (Top)', 'Death Mountain (Top)'), ('Death Mountain Drop', 'Death Mountain'), @@ -3227,6 +2705,7 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'), ('Sanctuary Push Door', 'Sanctuary'), ('Sewer Drop', 'Sewers'), ('Sewers Back Door', 'Sewers (Dark)'), + ('Sewers Secret Room', 'Sewers Secret Room'), ('Agahnim 1', 'Agahnim 1'), ('Death Mountain Entrance Rock', 'Death Mountain Entrance'), ('Death Mountain Entrance Drop', 'Light World'), @@ -3241,6 +2720,8 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'), ('Spiral Cave Ledge Access', 'Spiral Cave Ledge'), ('Spiral Cave Ledge Drop', 'East Death Mountain (Bottom)'), ('Spiral Cave (top to bottom)', 'Spiral Cave (Bottom)'), + ('Hookshot Cave Bomb Wall (South)', 'Hookshot Cave (Upper)'), + ('Hookshot Cave Bomb Wall (North)', 'Hookshot Cave'), ('East Death Mountain (Top)', 'East Death Mountain (Top)'), ('Death Mountain (Top)', 'Death Mountain (Top)'), ('Death Mountain Drop', 'Death Mountain'), @@ -3572,7 +3053,7 @@ default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'), ('Superbunny Cave Exit (Bottom)', 'Dark Death Mountain (East Bottom)'), ('Hookshot Cave Exit (South)', 'Dark Death Mountain (Top)'), ('Hookshot Cave Exit (North)', 'Death Mountain Floating Island (Dark World)'), - ('Hookshot Cave Back Entrance', 'Hookshot Cave'), + ('Hookshot Cave Back Entrance', 'Hookshot Cave (Upper)'), ('Mimic Cave', 'Mimic Cave'), ('Pyramid Hole', 'Pyramid'), @@ -3703,7 +3184,7 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing' ('Superbunny Cave (Bottom)', 'Superbunny Cave (Bottom)'), ('Superbunny Cave Exit (Bottom)', 'Dark Death Mountain (East Bottom)'), ('Hookshot Cave Exit (North)', 'Death Mountain Floating Island (Dark World)'), - ('Hookshot Cave Back Entrance', 'Hookshot Cave'), + ('Hookshot Cave Back Entrance', 'Hookshot Cave (Upper)'), ('Mimic Cave', 'Mimic Cave'), ('Inverted Pyramid Hole', 'Pyramid'), ('Inverted Links House', 'Inverted Links House'), diff --git a/worlds/alttp/InvertedRegions.py b/worlds/alttp/InvertedRegions.py index f89eebec33..2e30fde8cc 100644 --- a/worlds/alttp/InvertedRegions.py +++ b/worlds/alttp/InvertedRegions.py @@ -133,7 +133,7 @@ def create_inverted_regions(world, player): create_cave_region(world, player, 'Kakariko Gamble Game', 'a game of chance'), create_cave_region(world, player, 'Potion Shop', 'the potion shop', ['Potion Shop']), create_lw_region(world, player, 'Lake Hylia Island', ['Lake Hylia Island']), - create_cave_region(world, player, 'Capacity Upgrade', 'the queen of fairies'), + create_cave_region(world, player, 'Capacity Upgrade', 'the queen of fairies', ['Capacity Upgrade Shop']), create_cave_region(world, player, 'Two Brothers House', 'a connector', None, ['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)']), create_lw_region(world, player, 'Maze Race Ledge', ['Maze Race'], @@ -176,8 +176,9 @@ def create_inverted_regions(world, player): 'Throne Room']), create_dungeon_region(world, player, 'Sewer Drop', 'a drop\'s exit', None, ['Sewer Drop']), # This exists only to be referenced for access checks create_dungeon_region(world, player, 'Sewers (Dark)', 'a drop\'s exit', ['Sewers - Dark Cross', 'Sewers - Key Rat Key Drop'], ['Sewers Door']), - create_dungeon_region(world, player, 'Sewers', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle', - 'Sewers - Secret Room - Right'], ['Sanctuary Push Door', 'Sewers Back Door']), + create_dungeon_region(world, player, 'Sewers', 'a drop\'s exit', None, ['Sanctuary Push Door', 'Sewers Back Door', 'Sewers Secret Room']), + create_dungeon_region(world, player, 'Sewers Secret Room', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle', + 'Sewers - Secret Room - Right']), create_dungeon_region(world, player, 'Sanctuary', 'a drop\'s exit', ['Sanctuary'], ['Sanctuary Exit']), create_dungeon_region(world, player, 'Inverted Agahnims Tower', 'Castle Tower', ['Castle Tower - Room 03', 'Castle Tower - Dark Maze', 'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop'], ['Agahnim 1', 'Inverted Agahnims Tower Exit']), create_dungeon_region(world, player, 'Agahnim 1', 'Castle Tower', ['Agahnim 1'], None), @@ -346,7 +347,9 @@ def create_inverted_regions(world, player): create_cave_region(world, player, 'Hookshot Cave', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'], - ['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']), + ['Hookshot Cave Exit (South)', 'Hookshot Cave Bomb Wall (South)']), + create_cave_region(world, player, 'Hookshot Cave (Upper)', 'a connector', None, ['Hookshot Cave Exit (North)', + 'Hookshot Cave Bomb Wall (North)']), create_dw_region(world, player, 'Death Mountain Floating Island (Dark World)', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance']), create_cave_region(world, player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']), @@ -380,8 +383,8 @@ def create_inverted_regions(world, player): create_dungeon_region(world, player, 'Skull Woods Second Section', 'Skull Woods', ['Skull Woods - Big Key Chest', 'Skull Woods - West Lobby Pot Key'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']), create_dungeon_region(world, player, 'Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room', 'Skull Woods - Spike Corner Key Drop'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']), create_dungeon_region(world, player, 'Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']), - create_dungeon_region(world, player, 'Ice Palace (Entrance)', 'Ice Palace', ['Ice Palace - Jelly Key Drop'], ['Ice Palace (Second Section)', 'Ice Palace Exit']), - create_dungeon_region(world, player, 'Ice Palace (Second Section)', 'Ice Palace', ['Ice Palace - Conveyor Key Drop', 'Ice Palace - Compass Chest'], ['Ice Palace (Main)']), + create_dungeon_region(world, player, 'Ice Palace (Entrance)', 'Ice Palace', ['Ice Palace - Jelly Key Drop', 'Ice Palace - Compass Chest'], ['Ice Palace (Second Section)', 'Ice Palace Exit']), + create_dungeon_region(world, player, 'Ice Palace (Second Section)', 'Ice Palace', ['Ice Palace - Conveyor Key Drop'], ['Ice Palace (Main)']), create_dungeon_region(world, player, 'Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Freezor Chest', 'Ice Palace - Many Pots Pot Key', 'Ice Palace - Big Chest', 'Ice Palace - Iced T Room'], ['Ice Palace (East)', 'Ice Palace (Kholdstare)']), diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index 1c3f3e44f7..bb5bbaa61a 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -5,12 +5,12 @@ from BaseClasses import ItemClassification from Fill import FillError from .SubClasses import ALttPLocation, LTTPRegion, LTTPRegionType -from .Shops import TakeAny, total_shop_slots, set_up_shops, shuffle_shops, create_dynamic_shop_locations +from .Shops import TakeAny, total_shop_slots, set_up_shops, shop_table_by_location, ShopType from .Bosses import place_bosses from .Dungeons import get_dungeon_item_pool_player from .EntranceShuffle import connect_entrance -from .Items import ItemFactory, GetBeemizerItem -from .Options import smallkey_shuffle, compass_shuffle, bigkey_shuffle, map_shuffle, LTTPBosses +from .Items import ItemFactory, GetBeemizerItem, trap_replaceable, item_name_groups +from .Options import small_key_shuffle, compass_shuffle, big_key_shuffle, map_shuffle, TriforcePiecesMode from .StateHelpers import has_triforce_pieces, has_melee_weapon from .Regions import key_drop_data @@ -189,104 +189,62 @@ difficulties = { ), } -ice_rod_hunt_difficulties = dict() -for diff in {'easy', 'normal', 'hard', 'expert'}: - ice_rod_hunt_difficulties[diff] = Difficulty( - baseitems=['Nothing'] * 41, - bottles=['Nothing'] * 4, - bottle_count=difficulties[diff].bottle_count, - same_bottle=difficulties[diff].same_bottle, - progressiveshield=['Nothing'] * 3, - basicshield=['Nothing'] * 3, - progressivearmor=['Nothing'] * 2, - basicarmor=['Nothing'] * 2, - swordless=['Nothing'] * 4, - progressivemagic=['Nothing'] * 2, - basicmagic=['Nothing'] * 2, - progressivesword=['Nothing'] * 4, - basicsword=['Nothing'] * 4, - progressivebow=['Nothing'] * 2, - basicbow=['Nothing'] * 2, - timedohko=difficulties[diff].timedohko, - timedother=difficulties[diff].timedother, - progressiveglove=['Nothing'] * 2, - basicglove=['Nothing'] * 2, - alwaysitems=['Ice Rod'] + ['Nothing'] * 19, - legacyinsanity=['Nothing'] * 2, - universal_keys=['Nothing'] * 29, - extras=[['Nothing'] * 15, ['Nothing'] * 15, ['Nothing'] * 10, ['Nothing'] * 5, ['Nothing'] * 25], - progressive_sword_limit=difficulties[diff].progressive_sword_limit, - progressive_shield_limit=difficulties[diff].progressive_shield_limit, - progressive_armor_limit=difficulties[diff].progressive_armor_limit, - progressive_bow_limit=difficulties[diff].progressive_bow_limit, - progressive_bottle_limit=difficulties[diff].progressive_bottle_limit, - boss_heart_container_limit=difficulties[diff].boss_heart_container_limit, - heart_piece_limit=difficulties[diff].heart_piece_limit, - ) + +items_reduction_table = ( + ("Piece of Heart", "Boss Heart Container", 4, 1), + # the order of the upgrades is important + ("Arrow Upgrade (+5)", "Arrow Upgrade (+10)", 8, 4), + ("Arrow Upgrade (+5)", "Arrow Upgrade (+10)", 7, 4), + ("Arrow Upgrade (+5)", "Arrow Upgrade (+10)", 6, 3), + ("Arrow Upgrade (+10)", "Arrow Upgrade (70)", 4, 1), + ("Bomb Upgrade (+5)", "Bomb Upgrade (+10)", 8, 4), + ("Bomb Upgrade (+5)", "Bomb Upgrade (+10)", 7, 4), + ("Bomb Upgrade (+5)", "Bomb Upgrade (+10)", 6, 3), + ("Bomb Upgrade (+10)", "Bomb Upgrade (50)", 5, 1), + ("Bomb Upgrade (+10)", "Bomb Upgrade (50)", 4, 1), + ("Progressive Sword", 4), + ("Fighter Sword", 1), + ("Master Sword", 1), + ("Tempered Sword", 1), + ("Golden Sword", 1), + ("Progressive Shield", 3), + ("Blue Shield", 1), + ("Red Shield", 1), + ("Mirror Shield", 1), + ("Progressive Mail", 2), + ("Blue Mail", 1), + ("Red Mail", 1), + ("Progressive Bow", 2), + ("Bow", 1), + ("Silver Bow", 1), + ("Lamp", 1), + ("Bottles",) +) def generate_itempool(world): player = world.player multiworld = world.multiworld - if multiworld.difficulty[player] not in difficulties: - raise NotImplementedError(f"Diffulty {multiworld.difficulty[player]}") - if multiworld.goal[player] not in {'ganon', 'pedestal', 'bosses', 'triforcehunt', 'localtriforcehunt', 'icerodhunt', - 'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal'}: + if multiworld.item_pool[player].current_key not in difficulties: + raise NotImplementedError(f"Diffulty {multiworld.item_pool[player]}") + if multiworld.goal[player] not in ('ganon', 'pedestal', 'bosses', 'triforce_hunt', 'local_triforce_hunt', + 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'crystals', + 'ganon_pedestal'): raise NotImplementedError(f"Goal {multiworld.goal[player]} for player {player}") - if multiworld.mode[player] not in {'open', 'standard', 'inverted'}: + if multiworld.mode[player] not in ('open', 'standard', 'inverted'): raise NotImplementedError(f"Mode {multiworld.mode[player]} for player {player}") - if multiworld.timer[player] not in {False, 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'}: + if multiworld.timer[player] not in {False, 'display', 'timed', 'timed_ohko', 'ohko', 'timed_countdown'}: raise NotImplementedError(f"Timer {multiworld.mode[player]} for player {player}") - if multiworld.timer[player] in ['ohko', 'timed-ohko']: + if multiworld.timer[player] in ['ohko', 'timed_ohko']: multiworld.can_take_damage[player] = False - if multiworld.goal[player] in ['pedestal', 'triforcehunt', 'localtriforcehunt', 'icerodhunt']: + if multiworld.goal[player] in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']: multiworld.push_item(multiworld.get_location('Ganon', player), ItemFactory('Nothing', player), False) else: multiworld.push_item(multiworld.get_location('Ganon', player), ItemFactory('Triforce', player), False) - if multiworld.goal[player] == 'icerodhunt': - multiworld.progression_balancing[player].value = 0 - loc = multiworld.get_location('Turtle Rock - Boss', player) - multiworld.push_item(loc, ItemFactory('Triforce Piece', player), False) - multiworld.treasure_hunt_count[player] = 1 - if multiworld.boss_shuffle[player] != 'none': - if isinstance(multiworld.boss_shuffle[player].value, str) and 'turtle rock-' not in multiworld.boss_shuffle[player].value: - multiworld.boss_shuffle[player] = LTTPBosses.from_text(f'Turtle Rock-Trinexx;{multiworld.boss_shuffle[player].current_key}') - elif isinstance(multiworld.boss_shuffle[player].value, int): - multiworld.boss_shuffle[player] = LTTPBosses.from_text(f'Turtle Rock-Trinexx;{multiworld.boss_shuffle[player].current_key}') - else: - logging.warning(f'Cannot guarantee that Trinexx is the boss of Turtle Rock for player {player}') - loc.event = True - loc.locked = True - itemdiff = difficulties[multiworld.difficulty[player]] - itempool = [] - itempool.extend(itemdiff.alwaysitems) - itempool.remove('Ice Rod') - - itempool.extend(['Single Arrow', 'Sanctuary Heart Container']) - itempool.extend(['Boss Heart Container'] * itemdiff.boss_heart_container_limit) - itempool.extend(['Piece of Heart'] * itemdiff.heart_piece_limit) - itempool.extend(itemdiff.bottles) - itempool.extend(itemdiff.basicbow) - itempool.extend(itemdiff.basicarmor) - if not multiworld.swordless[player]: - itempool.extend(itemdiff.basicsword) - itempool.extend(itemdiff.basicmagic) - itempool.extend(itemdiff.basicglove) - itempool.extend(itemdiff.basicshield) - itempool.extend(itemdiff.legacyinsanity) - itempool.extend(['Rupees (300)'] * 34) - itempool.extend(['Bombs (10)'] * 5) - itempool.extend(['Arrows (10)'] * 7) - if multiworld.smallkey_shuffle[player] == smallkey_shuffle.option_universal: - itempool.extend(itemdiff.universal_keys) - - for item in itempool: - multiworld.push_precollected(ItemFactory(item, player)) - - if multiworld.goal[player] in ['triforcehunt', 'localtriforcehunt', 'icerodhunt']: + if multiworld.goal[player] in ['triforce_hunt', 'local_triforce_hunt']: region = multiworld.get_region('Light World', player) loc = ALttPLocation(player, "Murahdahla", parent=region) @@ -308,7 +266,8 @@ def generate_itempool(world): ('Missing Smith', 'Return Smith'), ('Floodgate', 'Open Floodgate'), ('Agahnim 1', 'Beat Agahnim 1'), - ('Flute Activation Spot', 'Activated Flute') + ('Flute Activation Spot', 'Activated Flute'), + ('Capacity Upgrade Shop', 'Capacity Upgrade Shop') ] for location_name, event_name in event_pairs: location = multiworld.get_location(location_name, player) @@ -340,17 +299,31 @@ def generate_itempool(world): if not found_sword: found_sword = True possible_weapons.append(item) - if item in ['Progressive Bow', 'Bow'] and not found_bow: + elif item in ['Progressive Bow', 'Bow'] and not found_bow: found_bow = True possible_weapons.append(item) - if item in ['Hammer', 'Bombs (10)', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']: + elif item in ['Hammer', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']: if item not in possible_weapons: possible_weapons.append(item) + elif (item == 'Bombs (10)' and (not multiworld.bombless_start[player]) and item not in + possible_weapons): + possible_weapons.append(item) + elif (item in ['Bomb Upgrade (+10)', 'Bomb Upgrade (50)'] and multiworld.bombless_start[player] and item + not in possible_weapons): + possible_weapons.append(item) + starting_weapon = multiworld.random.choice(possible_weapons) placed_items["Link's Uncle"] = starting_weapon pool.remove(starting_weapon) - if placed_items["Link's Uncle"] in ['Bow', 'Progressive Bow', 'Bombs (10)', 'Cane of Somaria', 'Cane of Byrna'] and multiworld.enemy_health[player] not in ['default', 'easy']: - multiworld.escape_assist[player].append('bombs') + if (placed_items["Link's Uncle"] in ['Bow', 'Progressive Bow', 'Bombs (10)', 'Bomb Upgrade (+10)', + 'Bomb Upgrade (50)', 'Cane of Somaria', 'Cane of Byrna'] and multiworld.enemy_health[player] not in ['default', 'easy']): + if multiworld.bombless_start[player] and "Bomb Upgrade" not in placed_items["Link's Uncle"]: + if 'Bow' in placed_items["Link's Uncle"]: + multiworld.escape_assist[player].append('arrows') + elif 'Cane' in placed_items["Link's Uncle"]: + multiworld.escape_assist[player].append('magic') + else: + multiworld.escape_assist[player].append('bombs') for (location, item) in placed_items.items(): multiworld.get_location(location, player).place_locked_item(ItemFactory(item, player)) @@ -377,7 +350,7 @@ def generate_itempool(world): for key_loc in key_drop_data: key_data = key_drop_data[key_loc] drop_item = ItemFactory(key_data[3], player) - if multiworld.goal[player] == 'icerodhunt' or not multiworld.key_drop_shuffle[player]: + if not multiworld.key_drop_shuffle[player]: if drop_item in dungeon_items: dungeon_items.remove(drop_item) else: @@ -391,88 +364,151 @@ def generate_itempool(world): world.dungeons[dungeon].small_keys.remove(drop_item) elif world.dungeons[dungeon].big_key is not None and world.dungeons[dungeon].big_key == drop_item: world.dungeons[dungeon].big_key = None - if not multiworld.key_drop_shuffle[player]: - # key drop item was removed from the pool because key drop shuffle is off - # and it will now place the removed key into its original location + loc = multiworld.get_location(key_loc, player) loc.place_locked_item(drop_item) loc.address = None - elif multiworld.goal[player] == 'icerodhunt': - # key drop item removed because of icerodhunt - multiworld.itempool.append(ItemFactory(GetBeemizerItem(world, player, 'Nothing'), player)) - multiworld.push_precollected(drop_item) - elif "Small" in key_data[3] and multiworld.smallkey_shuffle[player] == smallkey_shuffle.option_universal: + elif "Small" in key_data[3] and multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: # key drop shuffle and universal keys are on. Add universal keys in place of key drop keys. - multiworld.itempool.append(ItemFactory(GetBeemizerItem(world, player, 'Small Key (Universal)'), player)) + multiworld.itempool.append(ItemFactory(GetBeemizerItem(multiworld, player, 'Small Key (Universal)'), player)) dungeon_item_replacements = sum(difficulties[multiworld.difficulty[player]].extras, []) * 2 multiworld.random.shuffle(dungeon_item_replacements) - if multiworld.goal[player] == 'icerodhunt': - for item in dungeon_items: - multiworld.itempool.append(ItemFactory(GetBeemizerItem(multiworld, player, 'Nothing'), player)) + + for x in range(len(dungeon_items)-1, -1, -1): + item = dungeon_items[x] + if ((multiworld.small_key_shuffle[player] == small_key_shuffle.option_start_with and item.type == 'SmallKey') + or (multiworld.big_key_shuffle[player] == big_key_shuffle.option_start_with and item.type == 'BigKey') + or (multiworld.compass_shuffle[player] == compass_shuffle.option_start_with and item.type == 'Compass') + or (multiworld.map_shuffle[player] == map_shuffle.option_start_with and item.type == 'Map')): + dungeon_items.pop(x) multiworld.push_precollected(item) - else: - for x in range(len(dungeon_items)-1, -1, -1): - item = dungeon_items[x] - if ((multiworld.smallkey_shuffle[player] == smallkey_shuffle.option_start_with and item.type == 'SmallKey') - or (multiworld.bigkey_shuffle[player] == bigkey_shuffle.option_start_with and item.type == 'BigKey') - or (multiworld.compass_shuffle[player] == compass_shuffle.option_start_with and item.type == 'Compass') - or (multiworld.map_shuffle[player] == map_shuffle.option_start_with and item.type == 'Map')): - dungeon_items.pop(x) - multiworld.push_precollected(item) - multiworld.itempool.append(ItemFactory(dungeon_item_replacements.pop(), player)) - multiworld.itempool.extend([item for item in dungeon_items]) - # logic has some branches where having 4 hearts is one possible requirement (of several alternatives) - # rather than making all hearts/heart pieces progression items (which slows down generation considerably) - # We mark one random heart container as an advancement item (or 4 heart pieces in expert mode) - if multiworld.goal[player] != 'icerodhunt' and multiworld.difficulty[player] in ['easy', 'normal', 'hard'] and not (multiworld.custom and multiworld.customitemarray[30] == 0): - next(item for item in items if item.name == 'Boss Heart Container').classification = ItemClassification.progression - elif multiworld.goal[player] != 'icerodhunt' and multiworld.difficulty[player] in ['expert'] and not (multiworld.custom and multiworld.customitemarray[29] < 4): - adv_heart_pieces = (item for item in items if item.name == 'Piece of Heart') - for i in range(4): - next(adv_heart_pieces).classification = ItemClassification.progression + multiworld.itempool.append(ItemFactory(dungeon_item_replacements.pop(), player)) + multiworld.itempool.extend([item for item in dungeon_items]) - - progressionitems = [] - nonprogressionitems = [] - for item in items: - if item.advancement or item.type: - progressionitems.append(item) - else: - nonprogressionitems.append(GetBeemizerItem(multiworld, item.player, item)) - multiworld.random.shuffle(nonprogressionitems) - - if additional_triforce_pieces: - if additional_triforce_pieces > len(nonprogressionitems): - raise FillError(f"Not enough non-progression items to replace with Triforce pieces found for player " - f"{multiworld.get_player_name(player)}.") - progressionitems += [ItemFactory("Triforce Piece", player) for _ in range(additional_triforce_pieces)] - nonprogressionitems.sort(key=lambda item: int("Heart" in item.name)) # try to keep hearts in the pool - nonprogressionitems = nonprogressionitems[additional_triforce_pieces:] - multiworld.random.shuffle(nonprogressionitems) - - # shuffle medallions - if multiworld.required_medallions[player][0] == "random": - mm_medallion = multiworld.random.choice(['Ether', 'Quake', 'Bombos']) - else: - mm_medallion = multiworld.required_medallions[player][0] - if multiworld.required_medallions[player][1] == "random": - tr_medallion = multiworld.random.choice(['Ether', 'Quake', 'Bombos']) - else: - tr_medallion = multiworld.required_medallions[player][1] - multiworld.required_medallions[player] = (mm_medallion, tr_medallion) - - place_bosses(world) set_up_shops(multiworld, player) - if multiworld.shop_shuffle[player]: - shuffle_shops(multiworld, nonprogressionitems, player) + if multiworld.retro_bow[player]: + shop_items = 0 + shop_locations = [location for shop_locations in (shop.region.locations for shop in multiworld.shops if + shop.type == ShopType.Shop and shop.region.player == player) for location in shop_locations if + location.shop_slot is not None] + for location in shop_locations: + if location.shop.inventory[location.shop_slot]["item"] == "Single Arrow": + location.place_locked_item(ItemFactory("Single Arrow", player)) + else: + shop_items += 1 + else: + shop_items = min(multiworld.shop_item_slots[player], 30 if multiworld.include_witch_hut[player] else 27) - multiworld.itempool += progressionitems + nonprogressionitems + if multiworld.shuffle_capacity_upgrades[player]: + shop_items += 2 + chance_100 = int(multiworld.retro_bow[player]) * 0.25 + int( + multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal) * 0.5 + for _ in range(shop_items): + if multiworld.random.random() < chance_100: + items.append(ItemFactory(GetBeemizerItem(multiworld, player, "Rupees (100)"), player)) + else: + items.append(ItemFactory(GetBeemizerItem(multiworld, player, "Rupees (50)"), player)) + + multiworld.random.shuffle(items) + pool_count = len(items) + new_items = ["Triforce Piece" for _ in range(additional_triforce_pieces)] + if multiworld.shuffle_capacity_upgrades[player] or multiworld.bombless_start[player]: + progressive = multiworld.progressive[player] + progressive = multiworld.random.choice([True, False]) if progressive == 'grouped_random' else progressive == 'on' + if multiworld.shuffle_capacity_upgrades[player] == "on_combined": + new_items.append("Bomb Upgrade (50)") + elif multiworld.shuffle_capacity_upgrades[player] == "on": + new_items += ["Bomb Upgrade (+5)"] * 6 + new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)") + if multiworld.shuffle_capacity_upgrades[player] != "on_combined" and multiworld.bombless_start[player]: + new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)") + + if multiworld.shuffle_capacity_upgrades[player] and not multiworld.retro_bow[player]: + if multiworld.shuffle_capacity_upgrades[player] == "on_combined": + new_items += ["Arrow Upgrade (70)"] + else: + new_items += ["Arrow Upgrade (+5)"] * 6 + new_items.append("Arrow Upgrade (+5)" if progressive else "Arrow Upgrade (+10)") + + items += [ItemFactory(item, player) for item in new_items] + removed_filler = [] + + multiworld.random.shuffle(items) # Decide what gets tossed randomly. + + while len(items) > pool_count: + for i, item in enumerate(items): + if item.classification in (ItemClassification.filler, ItemClassification.trap): + removed_filler.append(items.pop(i)) + break + else: + # no more junk to remove, condense progressive items + def condense_items(items, small_item, big_item, rem, add): + small_item = ItemFactory(small_item, player) + # while (len(items) >= pool_count + rem - 1 # minus 1 to account for the replacement item + # and items.count(small_item) >= rem): + if items.count(small_item) >= rem: + for _ in range(rem): + items.remove(small_item) + removed_filler.append(ItemFactory(small_item.name, player)) + items += [ItemFactory(big_item, player) for _ in range(add)] + return True + return False + + def cut_item(items, item_to_cut, minimum_items): + item_to_cut = ItemFactory(item_to_cut, player) + if items.count(item_to_cut) > minimum_items: + items.remove(item_to_cut) + removed_filler.append(ItemFactory(item_to_cut.name, player)) + return True + return False + + while len(items) > pool_count: + items_were_cut = False + for reduce_item in items_reduction_table: + if len(items) <= pool_count: + break + if len(reduce_item) == 2: + items_were_cut = items_were_cut or cut_item(items, *reduce_item) + elif len(reduce_item) == 4: + items_were_cut = items_were_cut or condense_items(items, *reduce_item) + elif len(reduce_item) == 1: # Bottles + bottles = [item for item in items if item.name in item_name_groups["Bottles"]] + if len(bottles) > 4: + bottle = multiworld.random.choice(bottles) + items.remove(bottle) + removed_filler.append(bottle) + items_were_cut = True + assert items_were_cut, f"Failed to limit item pool size for player {player}" + if len(items) < pool_count: + items += removed_filler[len(items) - pool_count:] + + if multiworld.randomize_cost_types[player]: + # Heart and Arrow costs require all Heart Container/Pieces and Arrow Upgrades to be advancement items for logic + for item in items: + if (item.name in ("Boss Heart Container", "Sanctuary Heart Container", "Piece of Heart") + or "Arrow Upgrade" in item.name): + item.classification = ItemClassification.progression + else: + # Otherwise, logic has some branches where having 4 hearts is one possible requirement (of several alternatives) + # rather than making all hearts/heart pieces progression items (which slows down generation considerably) + # We mark one random heart container as an advancement item (or 4 heart pieces in expert mode) + if multiworld.item_pool[player] in ['easy', 'normal', 'hard'] and not (multiworld.custom and multiworld.customitemarray[30] == 0): + next(item for item in items if item.name == 'Boss Heart Container').classification = ItemClassification.progression + elif multiworld.item_pool[player] in ['expert'] and not (multiworld.custom and multiworld.customitemarray[29] < 4): + adv_heart_pieces = (item for item in items if item.name == 'Piece of Heart') + for i in range(4): + next(adv_heart_pieces).classification = ItemClassification.progression + + multiworld.required_medallions[player] = (multiworld.misery_mire_medallion[player].current_key.title(), + multiworld.turtle_rock_medallion[player].current_key.title()) + + place_bosses(world) + + multiworld.itempool += items if multiworld.retro_caves[player]: set_up_take_anys(multiworld, player) # depends on world.itempool to be set - # set_up_take_anys needs to run first - create_dynamic_shop_locations(multiworld, player) take_any_locations = { @@ -516,9 +552,14 @@ def set_up_take_anys(world, player): sword = world.random.choice(swords) world.itempool.remove(sword) world.itempool.append(ItemFactory('Rupees (20)', player)) - old_man_take_any.shop.add_inventory(0, sword.name, 0, 0, create_location=True) + old_man_take_any.shop.add_inventory(0, sword.name, 0, 0) + loc_name = "Old Man Sword Cave" + location = ALttPLocation(player, loc_name, shop_table_by_location[loc_name], parent=old_man_take_any) + location.shop_slot = 0 + old_man_take_any.locations.append(location) + location.place_locked_item(sword) else: - old_man_take_any.shop.add_inventory(0, 'Rupees (300)', 0, 0, create_location=True) + old_man_take_any.shop.add_inventory(0, 'Rupees (300)', 0, 0) for num in range(4): take_any = LTTPRegion("Take-Any #{}".format(num+1), LTTPRegionType.Cave, 'a cave of choice', player, world) @@ -532,18 +573,22 @@ def set_up_take_anys(world, player): take_any.shop = TakeAny(take_any, room_id, 0xE3, True, True, total_shop_slots + num + 1) world.shops.append(take_any.shop) take_any.shop.add_inventory(0, 'Blue Potion', 0, 0) - take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0, create_location=True) + take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0) + location = ALttPLocation(player, take_any.name, shop_table_by_location[take_any.name], parent=take_any) + location.shop_slot = 1 + take_any.locations.append(location) + location.place_locked_item(ItemFactory("Boss Heart Container", player)) def get_pool_core(world, player: int): - shuffle = world.shuffle[player] - difficulty = world.difficulty[player] - timer = world.timer[player] - goal = world.goal[player] - mode = world.mode[player] + shuffle = world.entrance_shuffle[player].current_key + difficulty = world.item_pool[player].current_key + timer = world.timer[player].current_key + goal = world.goal[player].current_key + mode = world.mode[player].current_key swordless = world.swordless[player] retro_bow = world.retro_bow[player] - logic = world.logic[player] + logic = world.glitches_required[player] pool = [] placed_items = {} @@ -552,7 +597,7 @@ def get_pool_core(world, player: int): treasure_hunt_count = None treasure_hunt_icon = None - diff = ice_rod_hunt_difficulties[difficulty] if goal == 'icerodhunt' else difficulties[difficulty] + diff = difficulties[difficulty] pool.extend(diff.alwaysitems) def place_item(loc, item): @@ -560,7 +605,7 @@ def get_pool_core(world, player: int): placed_items[loc] = item # provide boots to major glitch dependent seeds - if logic in {'owglitches', 'hybridglitches', 'nologic'} and world.glitch_boots[player] and goal != 'icerodhunt': + if logic in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.glitch_boots[player]: precollected_items.append('Pegasus Boots') pool.remove('Pegasus Boots') pool.append('Rupees (20)') @@ -611,7 +656,7 @@ def get_pool_core(world, player: int): if want_progressives(world.random): pool.extend(diff.progressivebow) world.worlds[player].has_progressive_bows = True - elif (swordless or logic == 'noglitches') and goal != 'icerodhunt': + elif (swordless or logic == 'no_glitches'): swordless_bows = ['Bow', 'Silver Bow'] if difficulty == "easy": swordless_bows *= 2 @@ -627,21 +672,32 @@ def get_pool_core(world, player: int): extraitems = total_items_to_place - len(pool) - len(placed_items) - if timer in ['timed', 'timed-countdown']: + if timer in ['timed', 'timed_countdown']: pool.extend(diff.timedother) extraitems -= len(diff.timedother) clock_mode = 'stopwatch' if timer == 'timed' else 'countdown' - elif timer == 'timed-ohko': + elif timer == 'timed_ohko': pool.extend(diff.timedohko) extraitems -= len(diff.timedohko) clock_mode = 'countdown-ohko' additional_pieces_to_place = 0 - if 'triforcehunt' in goal: - pieces_in_core = min(extraitems, world.triforce_pieces_available[player]) - additional_pieces_to_place = world.triforce_pieces_available[player] - pieces_in_core + if 'triforce_hunt' in goal: + + if world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_extra: + triforce_pieces = world.triforce_pieces_available[player].value + world.triforce_pieces_extra[player].value + elif world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_percentage: + percentage = float(max(100, world.triforce_pieces_percentage[player].value)) / 100 + triforce_pieces = int(round(world.triforce_pieces_required[player].value * percentage, 0)) + else: # available + triforce_pieces = world.triforce_pieces_available[player].value + + triforce_pieces = max(triforce_pieces, world.triforce_pieces_required[player].value) + + pieces_in_core = min(extraitems, triforce_pieces) + additional_pieces_to_place = triforce_pieces - pieces_in_core pool.extend(["Triforce Piece"] * pieces_in_core) extraitems -= pieces_in_core - treasure_hunt_count = world.triforce_pieces_required[player] + treasure_hunt_count = world.triforce_pieces_required[player].value treasure_hunt_icon = 'Triforce Piece' for extra in diff.extras: @@ -659,12 +715,12 @@ def get_pool_core(world, player: int): pool.remove("Rupees (20)") if retro_bow: - replace = {'Single Arrow', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)'} + replace = {'Single Arrow', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)', 'Arrow Upgrade (50)'} pool = ['Rupees (5)' if item in replace else item for item in pool] - if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal: + if world.small_key_shuffle[player] == small_key_shuffle.option_universal: pool.extend(diff.universal_keys) if mode == 'standard': - if world.key_drop_shuffle[player] and world.goal[player] != 'icerodhunt': + if world.key_drop_shuffle[player]: key_locations = ['Secret Passage', 'Hyrule Castle - Map Guard Key Drop'] key_location = world.random.choice(key_locations) key_locations.remove(key_location) @@ -688,8 +744,8 @@ def get_pool_core(world, player: int): def make_custom_item_pool(world, player): - shuffle = world.shuffle[player] - difficulty = world.difficulty[player] + shuffle = world.entrance_shuffle[player] + difficulty = world.item_pool[player] timer = world.timer[player] goal = world.goal[player] mode = world.mode[player] @@ -798,9 +854,9 @@ def make_custom_item_pool(world, player): treasure_hunt_count = world.triforce_pieces_required[player] treasure_hunt_icon = 'Triforce Piece' - if timer in ['display', 'timed', 'timed-countdown']: - clock_mode = 'countdown' if timer == 'timed-countdown' else 'stopwatch' - elif timer == 'timed-ohko': + if timer in ['display', 'timed', 'timed_countdown']: + clock_mode = 'countdown' if timer == 'timed_countdown' else 'stopwatch' + elif timer == 'timed_ohko': clock_mode = 'countdown-ohko' elif timer == 'ohko': clock_mode = 'ohko' @@ -810,7 +866,7 @@ def make_custom_item_pool(world, player): itemtotal = itemtotal + 1 if mode == 'standard': - if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal: + if world.small_key_shuffle[player] == small_key_shuffle.option_universal: key_location = world.random.choice( ['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross']) @@ -833,7 +889,7 @@ def make_custom_item_pool(world, player): pool.extend(['Magic Mirror'] * customitemarray[22]) pool.extend(['Moon Pearl'] * customitemarray[28]) - if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal: + if world.small_key_shuffle[player] == small_key_shuffle.option_universal: itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in universal Mode if world.key_drop_shuffle[player]: itemtotal = itemtotal - (len(key_drop_data) - 1) diff --git a/worlds/alttp/Items.py b/worlds/alttp/Items.py index 18f96b2ddb..8e513552ad 100644 --- a/worlds/alttp/Items.py +++ b/worlds/alttp/Items.py @@ -112,13 +112,15 @@ item_table = {'Bow': ItemData(IC.progression, None, 0x0B, 'You have\nchosen the\ 'Crystal 7': ItemData(IC.progression, 'Crystal', (0x08, 0x34, 0x64, 0x40, 0x7C, 0x06), None, None, None, None, None, None, "a blue crystal"), 'Single Arrow': ItemData(IC.filler, None, 0x43, 'a lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'), 'Arrows (10)': ItemData(IC.filler, None, 0x44, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack','stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again','ten arrows'), - 'Arrow Upgrade (+10)': ItemData(IC.filler, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), - 'Arrow Upgrade (+5)': ItemData(IC.filler, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), + 'Arrow Upgrade (+10)': ItemData(IC.useful, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), + 'Arrow Upgrade (+5)': ItemData(IC.useful, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), + 'Arrow Upgrade (70)': ItemData(IC.useful, None, 0x4D, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'), 'Single Bomb': ItemData(IC.filler, None, 0x27, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'), 'Bombs (3)': ItemData(IC.filler, None, 0x28, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'), 'Bombs (10)': ItemData(IC.filler, None, 0x31, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'), - 'Bomb Upgrade (+10)': ItemData(IC.filler, None, 0x52, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), - 'Bomb Upgrade (+5)': ItemData(IC.filler, None, 0x51, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), + 'Bomb Upgrade (+10)': ItemData(IC.progression, None, 0x52, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), + 'Bomb Upgrade (+5)': ItemData(IC.progression, None, 0x51, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), + 'Bomb Upgrade (50)': ItemData(IC.progression, None, 0x4C, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'), 'Blue Mail': ItemData(IC.useful, None, 0x22, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'the Blue Mail'), 'Red Mail': ItemData(IC.useful, None, 0x23, 'Now you\'re a\nred elf!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'the Red Mail'), 'Progressive Mail': ItemData(IC.useful, None, 0x60, 'time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'), @@ -222,6 +224,7 @@ item_table = {'Bow': ItemData(IC.progression, None, 0x0B, 'You have\nchosen the\ 'Return Smith': ItemData(IC.progression, 'Event', None, None, None, None, None, None, None, None), 'Pick Up Purple Chest': ItemData(IC.progression, 'Event', None, None, None, None, None, None, None, None), 'Open Floodgate': ItemData(IC.progression, 'Event', None, None, None, None, None, None, None, None), + 'Capacity Upgrade Shop': ItemData(IC.progression, 'Event', None, None, None, None, None, None, None, None), } item_init_table = {name: data.as_init_dict() for name, data in item_table.items()} @@ -287,5 +290,5 @@ progression_items = {name for name in everything if item_table[name].classification in {IC.progression, IC.progression_skip_balancing}} item_name_groups['Progression Items'] = progression_items item_name_groups['Non Progression Items'] = everything - progression_items - +item_name_groups['Upgrades'] = {name for name in everything if 'Upgrade' in name} trap_replaceable = item_name_groups['Rupees'] | {'Arrows (10)', 'Single Bomb', 'Bombs (3)', 'Bombs (10)', 'Nothing'} diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py index a89a9adb83..ed6af6dd67 100644 --- a/worlds/alttp/Options.py +++ b/worlds/alttp/Options.py @@ -1,10 +1,18 @@ import typing from BaseClasses import MultiWorld -from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, StartInventoryPool, PlandoBosses +from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, StartInventoryPool, PlandoBosses,\ + FreeText -class Logic(Choice): +class GlitchesRequired(Choice): + """Determine the logic required to complete the seed + None: No glitches required + Minor Glitches: Puts fake flipper, waterwalk, super bunny shenanigans, and etc into logic + Overworld Glitches: Assumes the player has knowledge of both overworld major glitches (boots clips, mirror clips) and minor glitches + Hybrid Major Glitches: In addition to overworld glitches, also requires underworld clips between dungeons. + No Logic: Your own items are placed with no regard to any logic; such as your Fire Rod can be on your Trinexx.""" + display_name = "Glitches Required" option_no_glitches = 0 option_minor_glitches = 1 option_overworld_glitches = 2 @@ -12,20 +20,122 @@ class Logic(Choice): option_no_logic = 4 alias_owg = 2 alias_hmg = 3 + alias_none = 0 -class Objective(Choice): - option_crystals = 0 - # option_pendants = 1 - option_triforce_pieces = 2 - option_pedestal = 3 - option_bingo = 4 +class DarkRoomLogic(Choice): + """Logic for unlit dark rooms. Lamp: require the Lamp for these rooms to be considered accessible. + Torches: in addition to lamp, allow the fire rod and presence of easily accessible torches for access. + None: all dark rooms are always considered doable, meaning this may force completion of rooms in complete darkness.""" + display_name = "Dark Room Logic" + option_lamp = 0 + option_torches = 1 + option_none = 2 + default = 0 class Goal(Choice): - option_kill_ganon = 0 - option_kill_ganon_and_gt_agahnim = 1 - option_hand_in = 2 + """Ganon: Climb GT, defeat Agahnim 2, and then kill Ganon + Crystals: Only killing Ganon is required. However, items may still be placed in GT + Bosses: Defeat the boss of all dungeons, including Agahnim's tower and GT (Aga 2) + Pedestal: Pull the Triforce from the Master Sword pedestal + Ganon Pedestal: Pull the Master Sword pedestal, then kill Ganon + Triforce Hunt: Collect Triforce pieces spread throughout the worlds, then turn them in to Murahadala in front of Hyrule Castle + Local Triforce Hunt: Collect Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle + Ganon Triforce Hunt: Collect Triforce pieces spread throughout the worlds, then kill Ganon + Local Ganon Triforce Hunt: Collect Triforce pieces spread throughout your world, then kill Ganon + Ice Rod Hunt: You start with everything except Ice Rod. Find the Ice rod, then kill Trinexx at Turtle rock.""" + display_name = "Goal" + default = 0 + option_ganon = 0 + option_crystals = 1 + option_bosses = 2 + option_pedestal = 3 + option_ganon_pedestal = 4 + option_triforce_hunt = 5 + option_local_triforce_hunt = 6 + option_ganon_triforce_hunt = 7 + option_local_ganon_triforce_hunt = 8 + + +class EntranceShuffle(Choice): + """Dungeons Simple: Shuffle just dungeons amongst each other, swapping dungeons entirely, so Hyrule Castle is always 1 dungeon. + Dungeons Full: Shuffle any dungeon entrance with any dungeon interior, so Hyrule Castle can be 4 different dungeons, but keep dungeons to a specific world. + Dungeons Crossed: like dungeons_full, but allow cross-world traversal through a dungeon. Warning: May force repeated dungeon traversal. + Simple: Entrances are grouped together before being randomized. Interiors with two entrances are grouped shuffled together with each other, + and Death Mountain entrances are shuffled only on Death Mountain. Dungeons are swapped entirely. + Restricted: Like Simple, but single entrance interiors, multi entrance interiors, and Death Mountain interior entrances are all shuffled with each other. + Full: Like Restricted, but all Dungeon entrances are shuffled with all non-Dungeon entrances. + Crossed: Like Full, but interiors with multiple entrances are no longer confined to the same world, which may allow crossing worlds. + Insanity: Like Crossed, but entrances and exits may be decoupled from each other, so that leaving through an exit may not return you to the entrance you entered from.""" + display_name = "Entrance Shuffle" + default = 0 + alias_none = 0 + option_vanilla = 0 + option_dungeons_simple = 1 + option_dungeons_full = 2 + option_dungeons_crossed = 3 + option_simple = 4 + option_restricted = 5 + option_full = 6 + option_crossed = 7 + option_insanity = 8 + alias_dungeonssimple = 1 + alias_dungeonsfull = 2 + alias_dungeonscrossed = 3 + + +class EntranceShuffleSeed(FreeText): + """You can specify a number to use as an entrance shuffle seed, or a group name. Everyone with the same group name + will get the same entrance shuffle result as long as their Entrance Shuffle, Mode, Retro Caves, and Glitches + Required options are the same.""" + default = "random" + display_name = "Entrance Shuffle Seed" + + +class TriforcePiecesMode(Choice): + """Determine how to calculate the extra available triforce pieces. + Extra: available = triforce_pieces_extra + triforce_pieces_required + Percentage: available = (triforce_pieces_percentage /100) * triforce_pieces_required + Available: available = triforce_pieces_available""" + display_name = "Triforce Pieces Mode" + default = 2 + option_extra = 0 + option_percentage = 1 + option_available = 2 + + +class TriforcePiecesPercentage(Range): + """Set to how many triforce pieces according to a percentage of the required ones, are available to collect in the world.""" + display_name = "Triforce Pieces Percentage" + range_start = 100 + range_end = 1000 + default = 150 + + +class TriforcePiecesAvailable(Range): + """Set to how many triforces pieces are available to collect in the world. Default is 30. Max is 90, Min is 1""" + display_name = "Triforce Pieces Available" + range_start = 1 + range_end = 90 + default = 30 + + +class TriforcePiecesRequired(Range): + """Set to how many out of X triforce pieces you need to win the game in a triforce hunt. + Default is 20. Max is 90, Min is 1.""" + display_name = "Triforce Pieces Required" + range_start = 1 + range_end = 90 + default = 20 + + +class TriforcePiecesExtra(Range): + """Set to how many extra triforces pieces are available to collect in the world.""" + display_name = "Triforce Pieces Extra" + range_start = 0 + range_end = 89 + default = 10 class OpenPyramid(Choice): @@ -44,10 +154,10 @@ class OpenPyramid(Choice): def to_bool(self, world: MultiWorld, player: int) -> bool: if self.value == self.option_goal: - return world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'} + return world.goal[player] in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} elif self.value == self.option_auto: - return world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'} \ - and (world.shuffle[player] in {'vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed'} or not + return world.goal[player] in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} \ + and (world.entrance_shuffle[player] in {'vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'} or not world.shuffle_ganon) elif self.value == self.option_open: return True @@ -76,13 +186,13 @@ class DungeonItem(Choice): return self.value in {1, 2, 3, 4} -class bigkey_shuffle(DungeonItem): +class big_key_shuffle(DungeonItem): """Big Key Placement""" item_name_group = "Big Keys" display_name = "Big Key Shuffle" -class smallkey_shuffle(DungeonItem): +class small_key_shuffle(DungeonItem): """Small Key Placement""" option_universal = 5 item_name_group = "Small Keys" @@ -107,6 +217,149 @@ class key_drop_shuffle(Toggle): display_name = "Key Drop Shuffle" + +class DungeonCounters(Choice): + """On: Always display amount of items checked in a dungeon. Pickup: Show when compass is picked up. + Default: Show when compass is picked up if the compass itself is shuffled. Off: Never show item count in dungeons.""" + display_name = "Dungeon Counters" + default = 1 + option_on = 0 + option_pickup = 1 + option_default = 2 + option_off = 4 + + +class Mode(Choice): + """Standard: Begin the game by rescuing Zelda from her cell and escorting her to the Sanctuary + Open: Begin the game from your choice of Link's House or the Sanctuary + Inverted: Begin in the Dark World. The Moon Pearl is required to avoid bunny-state in Light World, and the Light World game map is altered""" + option_standard = 0 + option_open = 1 + option_inverted = 2 + default = 1 + display_name = "Mode" + + +class ItemPool(Choice): + """Easy: Doubled upgrades, progressives, and etc. Normal: Item availability remains unchanged from vanilla game. + Hard: Reduced upgrade availability (max: 14 hearts, blue mail, tempered sword, fire shield, no silvers unless swordless). + Expert: Minimum upgrade availability (max: 8 hearts, green mail, master sword, fighter shield, no silvers unless swordless).""" + display_name = "Item Pool" + default = 1 + option_easy = 0 + option_normal = 1 + option_hard = 2 + option_expert = 3 + + +class ItemFunctionality(Choice): + """Easy: Allow Hammer to damage ganon, Allow Hammer tablet collection, Allow swordless medallion use everywhere. + Normal: Vanilla item functionality + Hard: Reduced helpfulness of items (potions less effective, can't catch faeries, cape uses double magic, byrna does not grant invulnerability, boomerangs do not stun, silvers disabled outside ganon) + Expert: Vastly reduces the helpfulness of items (potions barely effective, can't catch faeries, cape uses double magic, byrna does not grant invulnerability, boomerangs and hookshot do not stun, silvers disabled outside ganon)""" + display_name = "Item Functionality" + default = 1 + option_easy = 0 + option_normal = 1 + option_hard = 2 + option_expert = 3 + + +class EnemyHealth(Choice): + """Default: Vanilla enemy HP. Easy: Enemies have reduced health. Hard: Enemies have increased health. + Expert: Enemies have greatly increased health.""" + display_name = "Enemy Health" + default = 1 + option_easy = 0 + option_default = 1 + option_hard = 2 + option_expert = 3 + + +class EnemyDamage(Choice): + """Default: Vanilla enemy damage. Shuffled: 0 # Enemies deal 0 to 4 hearts and armor helps. + Chaos: Enemies deal 0 to 8 hearts and armor just reshuffles the damage.""" + display_name = "Enemy Damage" + default = 0 + option_default = 0 + option_shuffled = 2 + option_chaos = 3 + + +class ShufflePrizes(Choice): + """Shuffle "general" prize packs, as in enemy, tree pull, dig etc.; "bonk" prizes; or both.""" + display_name = "Shuffle Prizes" + default = 1 + option_off = 0 + option_general = 1 + option_bonk = 2 + option_both = 3 + + +class Medallion(Choice): + default = "random" + option_ether = 0 + option_bombos = 1 + option_quake = 2 + + +class MiseryMireMedallion(Medallion): + """Required medallion to open Misery Mire front entrance.""" + display_name = "Misery Mire Medallion" + + +class TurtleRockMedallion(Medallion): + """Required medallion to open Turtle Rock front entrance.""" + display_name = "Turtle Rock Medallion" + + +class Timer(Choice): + """None: No timer will be displayed. OHKO: Timer always at zero. Permanent OHKO. + Timed: Starts with clock at zero. Green clocks subtract 4 minutes (total 20). Blue clocks subtract 2 minutes (total 10). Red clocks add two minutes (total 10). Winner is the player with the lowest time at the end. + Timed OHKO: Starts the clock at ten minutes. Green clocks add five minutes (total 25). As long as the clock as at zero, Link will die in one hit. + Timed Countdown: Starts the clock with forty minutes. Same clocks as timed mode, but if the clock hits zero you lose. You can still keep playing, though. + Display: Displays a timer, but otherwise does not affect gameplay or the item pool.""" + display_name = "Timer" + option_none = 0 + option_timed = 1 + option_timed_ohko = 2 + option_ohko = 3 + option_timed_countdown = 4 + option_display = 5 + default = 0 + + +class CountdownStartTime(Range): + """For Timed OHKO and Timed Countdown timer modes, the amount of time in minutes to start with.""" + display_name = "Countdown Start Time" + range_start = 0 + range_end = 480 + default = 10 + + +class ClockTime(Range): + range_start = -60 + range_end = 60 + + +class RedClockTime(ClockTime): + """For all timer modes, the amount of time in minutes to gain or lose when picking up a red clock.""" + display_name = "Red Clock Time" + default = -2 + + +class BlueClockTime(ClockTime): + """For all timer modes, the amount of time in minutes to gain or lose when picking up a blue clock.""" + display_name = "Blue Clock Time" + default = 2 + + +class GreenClockTime(ClockTime): + """For all timer modes, the amount of time in minutes to gain or lose when picking up a green clock.""" + display_name = "Green Clock Time" + default = 4 + + class Crystals(Range): range_start = 0 range_end = 7 @@ -137,18 +390,52 @@ class ShopItemSlots(Range): range_end = 30 +class RandomizeShopInventories(Choice): + """Generate new default inventories for overworld/underworld shops, and unique shops; or each shop independently""" + display_name = "Randomize Shop Inventories" + default = 0 + option_default = 0 + option_randomize_by_shop_type = 1 + option_randomize_each = 2 + + +class ShuffleShopInventories(Toggle): + """Shuffle default inventories of the shops around""" + display_name = "Shuffle Shop Inventories" + + +class RandomizeShopPrices(Toggle): + """Randomize the prices of the items in shop inventories""" + display_name = "Randomize Shop Prices" + + +class RandomizeCostTypes(Toggle): + """Prices of the items in shop inventories may cost hearts, arrow, or bombs instead of rupees""" + display_name = "Randomize Cost Types" + + class ShopPriceModifier(Range): """Percentage modifier for shuffled item prices in shops""" - display_name = "Shop Price Cost Percent" + display_name = "Shop Price Modifier" range_start = 0 default = 100 range_end = 400 -class WorldState(Choice): - option_standard = 1 - option_open = 0 - option_inverted = 2 +class IncludeWitchHut(Toggle): + """Consider witch's hut like any other shop and shuffle/randomize it too""" + display_name = "Include Witch's Hut" + + +class ShuffleCapacityUpgrades(Choice): + """Shuffle capacity upgrades into the item pool (and allow them to traverse the multiworld). + On Combined will shuffle only a single bomb upgrade and arrow upgrade each which bring you to the maximum capacity.""" + display_name = "Shuffle Capacity Upgrades" + option_off = 0 + option_on = 1 + option_on_combined = 2 + alias_false = 0 + alias_true = 1 class LTTPBosses(PlandoBosses): @@ -236,6 +523,11 @@ class Swordless(Toggle): display_name = "Swordless" +class BomblessStart(Toggle): + """Start with a max of 0 bombs available, requiring Bomb Upgrade items in order to use bombs""" + display_name = "Bombless Start" + + # Might be a decent idea to split "Bow" into its own option with choices of # Defer to Progressive Option (default), Progressive, Non-Progressive, Bow + Silvers, Retro class RetroBow(Toggle): @@ -433,29 +725,66 @@ class AllowCollect(Toggle): alttp_options: typing.Dict[str, type(Option)] = { + "start_inventory_from_pool": StartInventoryPool, + "goal": Goal, + "mode": Mode, + "glitches_required": GlitchesRequired, + "dark_room_logic": DarkRoomLogic, + "open_pyramid": OpenPyramid, "crystals_needed_for_gt": CrystalsTower, "crystals_needed_for_ganon": CrystalsGanon, - "open_pyramid": OpenPyramid, - "bigkey_shuffle": bigkey_shuffle, - "smallkey_shuffle": smallkey_shuffle, + "triforce_pieces_mode": TriforcePiecesMode, + "triforce_pieces_percentage": TriforcePiecesPercentage, + "triforce_pieces_required": TriforcePiecesRequired, + "triforce_pieces_available": TriforcePiecesAvailable, + "triforce_pieces_extra": TriforcePiecesExtra, + "entrance_shuffle": EntranceShuffle, + "entrance_shuffle_seed": EntranceShuffleSeed, + "big_key_shuffle": big_key_shuffle, + "small_key_shuffle": small_key_shuffle, "key_drop_shuffle": key_drop_shuffle, "compass_shuffle": compass_shuffle, "map_shuffle": map_shuffle, + "restrict_dungeon_item_on_boss": RestrictBossItem, + "item_pool": ItemPool, + "item_functionality": ItemFunctionality, + "enemy_health": EnemyHealth, + "enemy_damage": EnemyDamage, "progressive": Progressive, "swordless": Swordless, + "dungeon_counters": DungeonCounters, "retro_bow": RetroBow, "retro_caves": RetroCaves, "hints": Hints, "scams": Scams, - "restrict_dungeon_item_on_boss": RestrictBossItem, "boss_shuffle": LTTPBosses, "pot_shuffle": PotShuffle, "enemy_shuffle": EnemyShuffle, "killable_thieves": KillableThieves, "bush_shuffle": BushShuffle, "shop_item_slots": ShopItemSlots, + "randomize_shop_inventories": RandomizeShopInventories, + "shuffle_shop_inventories": ShuffleShopInventories, + "include_witch_hut": IncludeWitchHut, + "randomize_shop_prices": RandomizeShopPrices, + "randomize_cost_types": RandomizeCostTypes, "shop_price_modifier": ShopPriceModifier, + "shuffle_capacity_upgrades": ShuffleCapacityUpgrades, + "bombless_start": BomblessStart, + "shuffle_prizes": ShufflePrizes, "tile_shuffle": TileShuffle, + "misery_mire_medallion": MiseryMireMedallion, + "turtle_rock_medallion": TurtleRockMedallion, + "glitch_boots": GlitchBoots, + "beemizer_total_chance": BeemizerTotalChance, + "beemizer_trap_chance": BeemizerTrapChance, + "timer": Timer, + "countdown_start_time": CountdownStartTime, + "red_clock_time": RedClockTime, + "blue_clock_time": BlueClockTime, + "green_clock_time": GreenClockTime, + "death_link": DeathLink, + "allow_collect": AllowCollect, "ow_palettes": OWPalette, "uw_palettes": UWPalette, "hud_palettes": HUDPalette, @@ -469,10 +798,4 @@ alttp_options: typing.Dict[str, type(Option)] = { "music": Music, "reduceflashing": ReduceFlashing, "triforcehud": TriforceHud, - "glitch_boots": GlitchBoots, - "beemizer_total_chance": BeemizerTotalChance, - "beemizer_trap_chance": BeemizerTrapChance, - "death_link": DeathLink, - "allow_collect": AllowCollect, - "start_inventory_from_pool": StartInventoryPool, } diff --git a/worlds/alttp/Regions.py b/worlds/alttp/Regions.py index 0cc8a3d6a7..dc3adb108a 100644 --- a/worlds/alttp/Regions.py +++ b/worlds/alttp/Regions.py @@ -94,7 +94,7 @@ def create_regions(world, player): create_cave_region(world, player, 'Kakariko Gamble Game', 'a game of chance'), create_cave_region(world, player, 'Potion Shop', 'the potion shop', ['Potion Shop']), create_lw_region(world, player, 'Lake Hylia Island', ['Lake Hylia Island']), - create_cave_region(world, player, 'Capacity Upgrade', 'the queen of fairies'), + create_cave_region(world, player, 'Capacity Upgrade', 'the queen of fairies', ['Capacity Upgrade Shop']), create_cave_region(world, player, 'Two Brothers House', 'a connector', None, ['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)']), create_lw_region(world, player, 'Maze Race Ledge', ['Maze Race'], ['Two Brothers House (West)']), create_cave_region(world, player, '50 Rupee Cave', 'a cave with some cash'), @@ -121,8 +121,9 @@ def create_regions(world, player): ['Hyrule Castle Exit (East)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (South)', 'Throne Room']), create_dungeon_region(world, player, 'Sewer Drop', 'a drop\'s exit', None, ['Sewer Drop']), # This exists only to be referenced for access checks create_dungeon_region(world, player, 'Sewers (Dark)', 'a drop\'s exit', ['Sewers - Dark Cross', 'Sewers - Key Rat Key Drop'], ['Sewers Door']), - create_dungeon_region(world, player, 'Sewers', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle', - 'Sewers - Secret Room - Right'], ['Sanctuary Push Door', 'Sewers Back Door']), + create_dungeon_region(world, player, 'Sewers', 'a drop\'s exit', None, ['Sanctuary Push Door', 'Sewers Back Door', 'Sewers Secret Room']), + create_dungeon_region(world, player, 'Sewers Secret Room', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle', + 'Sewers - Secret Room - Right']), create_dungeon_region(world, player, 'Sanctuary', 'a drop\'s exit', ['Sanctuary'], ['Sanctuary Exit']), create_dungeon_region(world, player, 'Agahnims Tower', 'Castle Tower', ['Castle Tower - Room 03', 'Castle Tower - Dark Maze', 'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop'], ['Agahnim 1', 'Agahnims Tower Exit']), create_dungeon_region(world, player, 'Agahnim 1', 'Castle Tower', ['Agahnim 1'], None), @@ -275,7 +276,9 @@ def create_regions(world, player): create_cave_region(world, player, 'Hookshot Cave', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'], - ['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']), + ['Hookshot Cave Exit (South)', 'Hookshot Cave Bomb Wall (South)']), + create_cave_region(world, player, 'Hookshot Cave (Upper)', 'a connector', None, ['Hookshot Cave Exit (North)', + 'Hookshot Cave Bomb Wall (North)']), create_dw_region(world, player, 'Death Mountain Floating Island (Dark World)', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance', 'Floating Island Mirror Spot']), create_lw_region(world, player, 'Death Mountain Floating Island (Light World)', ['Floating Island']), @@ -311,8 +314,8 @@ def create_regions(world, player): create_dungeon_region(world, player, 'Skull Woods Second Section', 'Skull Woods', ['Skull Woods - Big Key Chest', 'Skull Woods - West Lobby Pot Key'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']), create_dungeon_region(world, player, 'Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']), create_dungeon_region(world, player, 'Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Spike Corner Key Drop', 'Skull Woods - Boss', 'Skull Woods - Prize']), - create_dungeon_region(world, player, 'Ice Palace (Entrance)', 'Ice Palace', ['Ice Palace - Jelly Key Drop'], ['Ice Palace (Second Section)', 'Ice Palace Exit']), - create_dungeon_region(world, player, 'Ice Palace (Second Section)', 'Ice Palace', ['Ice Palace - Conveyor Key Drop', 'Ice Palace - Compass Chest'], ['Ice Palace (Main)']), + create_dungeon_region(world, player, 'Ice Palace (Entrance)', 'Ice Palace', ['Ice Palace - Jelly Key Drop', 'Ice Palace - Compass Chest'], ['Ice Palace (Second Section)', 'Ice Palace Exit']), + create_dungeon_region(world, player, 'Ice Palace (Second Section)', 'Ice Palace', ['Ice Palace - Conveyor Key Drop'], ['Ice Palace (Main)']), create_dungeon_region(world, player, 'Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Freezor Chest', 'Ice Palace - Many Pots Pot Key', 'Ice Palace - Big Chest', 'Ice Palace - Iced T Room'], ['Ice Palace (East)', 'Ice Palace (Kholdstare)']), @@ -735,6 +738,7 @@ location_table: typing.Dict[str, 'Missing Smith': (None, None, False, None), 'Dark Blacksmith Ruins': (None, None, False, None), 'Flute Activation Spot': (None, None, False, None), + 'Capacity Upgrade Shop': (None, None, False, None), 'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], None, True, 'Eastern Palace'), 'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], None, True, 'Desert Palace'), 'Tower of Hera - Prize': ( diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index b80cec578a..ff4947bb01 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -4,7 +4,7 @@ import Utils import worlds.Files LTTPJPN10HASH: str = "03a63945398191337e896e5771f77173" -RANDOMIZERBASEHASH: str = "9952c2a3ec1b421e408df0d20c8f0c7f" +RANDOMIZERBASEHASH: str = "35d010bc148e0ea0ee68e81e330223f1" ROM_PLAYER_LIMIT: int = 255 import io @@ -36,7 +36,7 @@ from .Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmith SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names from .Items import ItemFactory, item_table, item_name_groups, progression_items from .EntranceShuffle import door_addresses -from .Options import smallkey_shuffle +from .Options import small_key_shuffle try: from maseya import z3pr @@ -294,7 +294,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory): 'RandomizeBushEnemyChance': multiworld.bush_shuffle[player].value, 'RandomizeEnemyHealthRange': multiworld.enemy_health[player] != 'default', 'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[ - multiworld.enemy_health[player]], + multiworld.enemy_health[player].current_key], 'OHKO': False, 'RandomizeEnemyDamage': multiworld.enemy_damage[player] != 'default', 'AllowEnemyZeroDamage': True, @@ -858,13 +858,13 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): # Thanks to Zarby89 for originally finding these values # todo fix screen scrolling - if world.shuffle[player] not in {'insanity', 'insanity_legacy', 'madness_legacy'} and \ + if world.entrance_shuffle[player] != 'insanity' and \ exit.name in {'Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit', 'Skull Woods Final Section Exit', 'Ice Palace Exit', 'Misery Mire Exit', 'Palace of Darkness Exit', 'Swamp Palace Exit', 'Ganons Tower Exit', 'Desert Palace Exit (North)', 'Agahnims Tower Exit', 'Spiral Cave Exit (Top)', 'Superbunny Cave Exit (Bottom)', 'Turtle Rock Ledge Exit (East)'} and \ - (world.logic[player] not in ['hybridglitches', 'nologic'] or + (world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic'] or exit.name not in {'Palace of Darkness Exit', 'Tower of Hera Exit', 'Swamp Palace Exit'}): # For exits that connot be reached from another, no need to apply offset fixes. rom.write_int16(0x15DB5 + 2 * offset, link_y) # same as final else @@ -907,7 +907,9 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): if world.retro_caves[player]: # Old man cave and Take any caves will count towards collection rate. credits_total += 5 if world.shop_item_slots[player]: # Potion shop only counts towards collection rate if included in the shuffle. - credits_total += 30 if 'w' in world.shop_shuffle[player] else 27 + credits_total += 30 if world.include_witch_hut[player] else 27 + if world.shuffle_capacity_upgrades[player]: + credits_total += 2 rom.write_byte(0x187010, credits_total) # dynamic credits @@ -1059,7 +1061,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): # Set stun items rom.write_byte(0x180180, 0x03) # All standard items # Set overflow items for progressive equipment - if world.timer[player] in ['timed', 'timed-countdown', 'timed-ohko']: + if world.timer[player] in ['timed', 'timed_countdown', 'timed_ohko']: overflow_replacement = GREEN_CLOCK else: overflow_replacement = GREEN_TWENTY_RUPEES @@ -1079,7 +1081,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): difficulty.progressive_bow_limit, item_table[difficulty.basicbow[-1]].item_code]) if difficulty.progressive_bow_limit < 2 and ( - world.swordless[player] or world.logic[player] == 'noglitches'): + world.swordless[player] or world.glitches_required[player] == 'no_glitches'): rom.write_bytes(0x180098, [2, item_table["Silver Bow"].item_code]) rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup @@ -1095,7 +1097,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): prize_replacements[0xE1] = 0xDA # 5 Arrows -> Blue Rupee prize_replacements[0xE2] = 0xDB # 10 Arrows -> Red Rupee - if "g" in world.shuffle_prizes[player]: + if world.shuffle_prizes[player] in ("general", "both"): # shuffle prize packs prizes = [0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD8, 0xD8, 0xD9, 0xDA, 0xD9, 0xDA, 0xDB, 0xDA, 0xD9, 0xDA, 0xDA, 0xE0, 0xDF, 0xDF, 0xDA, 0xE0, 0xDF, 0xD8, 0xDF, @@ -1157,7 +1159,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): byte = int(rom.read_byte(address)) rom.write_byte(address, prize_replacements.get(byte, byte)) - if "b" in world.shuffle_prizes[player]: + if world.shuffle_prizes[player] in ("bonk", "both"): # set bonk prizes bonk_prizes = [0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xAC, 0xE3, 0xE3, 0xDA, 0xE3, 0xDA, 0xD8, 0xAC, 0xAC, 0xE3, 0xD8, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xDC, 0xDB, 0xE3, 0xDA, 0x79, 0x79, @@ -1274,7 +1276,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed gametype = 0x04 # item - if world.shuffle[player] != 'vanilla': + if world.entrance_shuffle[player] != 'vanilla': gametype |= 0x02 # entrance if enemized: gametype |= 0x01 # enemizer @@ -1312,7 +1314,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): equip[0x36C] = 0x18 equip[0x36D] = 0x18 equip[0x379] = 0x68 - starting_max_bombs = 10 + starting_max_bombs = 0 if world.bombless_start[player] else 10 starting_max_arrows = 30 startingstate = CollectionState(world) @@ -1430,8 +1432,8 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): 'Bottle (Fairy)': 6, 'Bottle (Bee)': 7, 'Bottle (Good Bee)': 8} rupees = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)': 50, 'Rupees (100)': 100, 'Rupees (300)': 300} - bomb_caps = {'Bomb Upgrade (+5)': 5, 'Bomb Upgrade (+10)': 10} - arrow_caps = {'Arrow Upgrade (+5)': 5, 'Arrow Upgrade (+10)': 10} + bomb_caps = {'Bomb Upgrade (+5)': 5, 'Bomb Upgrade (+10)': 10, 'Bomb Upgrade (50)': 50} + arrow_caps = {'Arrow Upgrade (+5)': 5, 'Arrow Upgrade (+10)': 10, 'Arrow Upgrade (70)': 70} bombs = {'Single Bomb': 1, 'Bombs (3)': 3, 'Bombs (10)': 10} arrows = {'Single Arrow': 1, 'Arrows (10)': 10} @@ -1498,7 +1500,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_byte(0x3A96D, 0xF0 if world.mode[ player] != 'inverted' else 0xD0) # Residual Portal: Normal (F0= Light Side, D0=Dark Side, 42 = both (Darth Vader)) rom.write_byte(0x3A9A7, 0xD0) # Residual Portal: Normal (D0= Light Side, F0=Dark Side, 42 = both (Darth Vader)) - if 'u' in world.shop_shuffle[player]: + if world.shuffle_capacity_upgrades[player]: rom.write_bytes(0x180080, [5, 10, 5, 10]) # values to fill for Capacity Upgrades (Bomb5, Bomb10, Arrow5, Arrow10) else: @@ -1509,11 +1511,11 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): (0x02 if 'bombs' in world.escape_assist[player] else 0x00) | (0x04 if 'magic' in world.escape_assist[player] else 0x00))) # Escape assist - if world.goal[player] in ['pedestal', 'triforcehunt', 'localtriforcehunt', 'icerodhunt']: + if world.goal[player] in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']: rom.write_byte(0x18003E, 0x01) # make ganon invincible - elif world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']: + elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: rom.write_byte(0x18003E, 0x05) # make ganon invincible until enough triforce pieces are collected - elif world.goal[player] in ['ganonpedestal']: + elif world.goal[player] in ['ganon_pedestal']: rom.write_byte(0x18003E, 0x06) elif world.goal[player] in ['bosses']: rom.write_byte(0x18003E, 0x02) # make ganon invincible until all bosses are beat @@ -1534,12 +1536,12 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): # c - enabled for inside compasses # s - enabled for inside small keys # block HC upstairs doors in rain state in standard mode - rom.write_byte(0x18008A, 0x01 if world.mode[player] == "standard" and world.shuffle[player] != 'vanilla' else 0x00) + rom.write_byte(0x18008A, 0x01 if world.mode[player] == "standard" and world.entrance_shuffle[player] != 'vanilla' else 0x00) - rom.write_byte(0x18016A, 0x10 | ((0x01 if world.smallkey_shuffle[player] else 0x00) + rom.write_byte(0x18016A, 0x10 | ((0x01 if world.small_key_shuffle[player] else 0x00) | (0x02 if world.compass_shuffle[player] else 0x00) | (0x04 if world.map_shuffle[player] else 0x00) - | (0x08 if world.bigkey_shuffle[ + | (0x08 if world.big_key_shuffle[ player] else 0x00))) # free roaming item text boxes rom.write_byte(0x18003B, 0x01 if world.map_shuffle[player] else 0x00) # maps showing crystals on overworld @@ -1561,9 +1563,9 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): # b - Big Key # a - Small Key # - rom.write_byte(0x180045, ((0x00 if (world.smallkey_shuffle[player] == smallkey_shuffle.option_original_dungeon or - world.smallkey_shuffle[player] == smallkey_shuffle.option_universal) else 0x01) - | (0x02 if world.bigkey_shuffle[player] else 0x00) + rom.write_byte(0x180045, ((0x00 if (world.small_key_shuffle[player] == small_key_shuffle.option_original_dungeon or + world.small_key_shuffle[player] == small_key_shuffle.option_universal) else 0x01) + | (0x02 if world.big_key_shuffle[player] else 0x00) | (0x04 if world.map_shuffle[player] else 0x00) | (0x08 if world.compass_shuffle[player] else 0x00))) # free roaming items in menu @@ -1595,8 +1597,8 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_int16(0x18017C, get_reveal_bytes('Crystal 5') | get_reveal_bytes('Crystal 6') if world.map_shuffle[ player] else 0x0000) # Bomb Shop Reveal - rom.write_byte(0x180172, 0x01 if world.smallkey_shuffle[ - player] == smallkey_shuffle.option_universal else 0x00) # universal keys + rom.write_byte(0x180172, 0x01 if world.small_key_shuffle[ + player] == small_key_shuffle.option_universal else 0x00) # universal keys rom.write_byte(0x18637E, 0x01 if world.retro_bow[player] else 0x00) # Skip quiver in item shops once bought rom.write_byte(0x180175, 0x01 if world.retro_bow[player] else 0x00) # rupee bow rom.write_byte(0x180176, 0x0A if world.retro_bow[player] else 0x00) # wood arrow cost @@ -1613,9 +1615,9 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_byte(0x180020, digging_game_rng) rom.write_byte(0xEFD95, digging_game_rng) rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills - rom.write_byte(0x1800A4, 0x01 if world.logic[player] != 'nologic' else 0x00) # enable POD EG fix - rom.write_byte(0x186383, 0x01 if world.glitch_triforce or world.logic[ - player] == 'nologic' else 0x00) # disable glitching to Triforce from Ganons Room + rom.write_byte(0x1800A4, 0x01 if world.glitches_required[player] != 'no_logic' else 0x00) # enable POD EG fix + rom.write_byte(0x186383, 0x01 if world.glitch_triforce or world.glitches_required[ + player] == 'no_logic' else 0x00) # disable glitching to Triforce from Ganons Room rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill # remove shield from uncle @@ -1660,8 +1662,8 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): 0x4F]) # allow smith into multi-entrance caves in appropriate shuffles - if world.shuffle[player] in ['restricted', 'full', 'crossed', 'insanity', 'madness'] or ( - world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'): + if world.entrance_shuffle[player] in ['restricted', 'full', 'crossed', 'insanity'] or ( + world.entrance_shuffle[player] == 'simple' and world.mode[player] == 'inverted'): rom.write_byte(0x18004C, 0x01) # set correct flag for hera basement item @@ -1758,8 +1760,8 @@ def write_custom_shops(rom, world, player): if item is None: break if world.shop_item_slots[player] or shop.type == ShopType.TakeAny: - count_shop = (shop.region.name != 'Potion Shop' or 'w' in world.shop_shuffle[player]) and \ - shop.region.name != 'Capacity Upgrade' + count_shop = (shop.region.name != 'Potion Shop' or world.include_witch_hut[player]) and \ + (shop.region.name != 'Capacity Upgrade' or world.shuffle_capacity_upgrades[player]) rom.write_byte(0x186560 + shop.sram_offset + slot, 1 if count_shop else 0) if item['item'] == 'Single Arrow' and item['player'] == 0: arrow_mask |= 1 << index @@ -2201,7 +2203,7 @@ def write_strings(rom, world, player): tt.removeUnwantedText() # Let's keep this guy's text accurate to the shuffle setting. - if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple', 'dungeonscrossed']: + if world.entrance_shuffle[player] in ['vanilla', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed']: tt['kakariko_flophouse_man_no_flippers'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.' tt['kakariko_flophouse_man'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.' @@ -2255,7 +2257,7 @@ def write_strings(rom, world, player): entrances_to_hint.update({'Inverted Ganons Tower': 'The sealed castle door'}) else: entrances_to_hint.update({'Ganons Tower': 'Ganon\'s Tower'}) - if world.shuffle[player] in ['simple', 'restricted', 'restricted_legacy']: + if world.entrance_shuffle[player] in ['simple', 'restricted']: for entrance in all_entrances: if entrance.name in entrances_to_hint: this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text( @@ -2265,9 +2267,9 @@ def write_strings(rom, world, player): break # Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones. entrances_to_hint.update(InconvenientOtherEntrances) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: + if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: hint_count = 0 - elif world.shuffle[player] in ['simple', 'restricted', 'restricted_legacy']: + elif world.entrance_shuffle[player] in ['simple', 'restricted']: hint_count = 2 else: hint_count = 4 @@ -2284,14 +2286,14 @@ def write_strings(rom, world, player): # Next we handle hints for randomly selected other entrances, # curating the selection intelligently based on shuffle. - if world.shuffle[player] not in ['simple', 'restricted', 'restricted_legacy']: + if world.entrance_shuffle[player] not in ['simple', 'restricted']: entrances_to_hint.update(ConnectorEntrances) entrances_to_hint.update(DungeonEntrances) if world.mode[player] == 'inverted': entrances_to_hint.update({'Inverted Agahnims Tower': 'The dark mountain tower'}) else: entrances_to_hint.update({'Agahnims Tower': 'The sealed castle door'}) - elif world.shuffle[player] == 'restricted': + elif world.entrance_shuffle[player] == 'restricted': entrances_to_hint.update(ConnectorEntrances) entrances_to_hint.update(OtherEntrances) if world.mode[player] == 'inverted': @@ -2301,15 +2303,15 @@ def write_strings(rom, world, player): else: entrances_to_hint.update({'Dark Sanctuary Hint': 'The dark sanctuary cave'}) entrances_to_hint.update({'Big Bomb Shop': 'The old bomb shop'}) - if world.shuffle[player] in ['insanity', 'madness_legacy', 'insanity_legacy']: + if world.entrance_shuffle[player] != 'insanity': entrances_to_hint.update(InsanityEntrances) if world.shuffle_ganon: if world.mode[player] == 'inverted': entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'}) else: entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'}) - hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', - 'dungeonscrossed'] else 0 + hint_count = 4 if world.entrance_shuffle[player] not in ['vanilla', 'dungeons_simple', 'dungeons_full', + 'dungeons_crossed'] else 0 for entrance in all_entrances: if entrance.name in entrances_to_hint: if hint_count: @@ -2323,11 +2325,11 @@ def write_strings(rom, world, player): # Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable. locations_to_hint = InconvenientLocations.copy() - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: + if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: locations_to_hint.extend(InconvenientVanillaLocations) local_random.shuffle(locations_to_hint) - hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', - 'dungeonscrossed'] else 5 + hint_count = 3 if world.entrance_shuffle[player] not in ['vanilla', 'dungeons_simple', 'dungeons_full', + 'dungeons_crossed'] else 5 for location in locations_to_hint[:hint_count]: if location == 'Swamp Left': if local_random.randint(0, 1): @@ -2381,16 +2383,16 @@ def write_strings(rom, world, player): # Lastly we write hints to show where certain interesting items are. items_to_hint = RelevantItems.copy() - if world.smallkey_shuffle[player].hints_useful: + if world.small_key_shuffle[player].hints_useful: items_to_hint |= item_name_groups["Small Keys"] - if world.bigkey_shuffle[player].hints_useful: + if world.big_key_shuffle[player].hints_useful: items_to_hint |= item_name_groups["Big Keys"] if world.hints[player] == "full": hint_count = len(hint_locations) # fill all remaining hint locations with Item hints. else: - hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', - 'dungeonscrossed'] else 8 + hint_count = 5 if world.entrance_shuffle[player] not in ['vanilla', 'dungeons_simple', 'dungeons_full', + 'dungeons_crossed'] else 8 hint_count = min(hint_count, len(items_to_hint), len(hint_locations)) if hint_count: locations = world.find_items_in_locations(items_to_hint, player, True) @@ -2417,7 +2419,7 @@ def write_strings(rom, world, player): tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint if world.worlds[player].has_progressive_bows and (world.difficulty_requirements[player].progressive_bow_limit >= 2 or ( - world.swordless[player] or world.logic[player] == 'noglitches')): + world.swordless[player] or world.glitches_required[player] == 'no_glitches')): prog_bow_locs = world.find_item_locations('Progressive Bow', player, True) world.per_slot_randoms[player].shuffle(prog_bow_locs) found_bow = False @@ -2448,7 +2450,7 @@ def write_strings(rom, world, player): if world.goal[player] == 'bosses': tt['sign_ganon'] = 'You need to kill all bosses, Ganon last.' - elif world.goal[player] == 'ganonpedestal': + elif world.goal[player] == 'ganon_pedestal': tt['sign_ganon'] = 'You need to pull the pedestal to defeat Ganon.' elif world.goal[player] == "ganon": if world.crystals_needed_for_ganon[player] == 1: @@ -2456,14 +2458,6 @@ def write_strings(rom, world, player): else: tt['sign_ganon'] = f'You need {world.crystals_needed_for_ganon[player]} crystals to beat Ganon and ' \ f'have beaten Agahnim atop Ganons Tower' - elif world.goal[player] == "icerodhunt": - tt['sign_ganon'] = 'Go find the Ice Rod and Kill Trinexx, then talk to Murahdahla... Ganon is invincible!' - tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Go kill Trinexx instead.' - tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' - tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \ - "invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \ - "hidden in a hollow tree. " \ - "If you bring me the Triforce piece from Turtle Rock, I can reassemble it." else: if world.crystals_needed_for_ganon[player] == 1: tt['sign_ganon'] = 'You need a crystal to beat Ganon.' @@ -2478,10 +2472,10 @@ def write_strings(rom, world, player): tt['sahasrahla_quest_have_master_sword'] = Sahasrahla2_texts[local_random.randint(0, len(Sahasrahla2_texts) - 1)] tt['blind_by_the_light'] = Blind_texts[local_random.randint(0, len(Blind_texts) - 1)] - if world.goal[player] in ['triforcehunt', 'localtriforcehunt', 'icerodhunt']: + if world.goal[player] in ['triforce_hunt', 'local_triforce_hunt']: tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.' tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' - if world.goal[player] == 'triforcehunt' and world.players > 1: + if world.goal[player] == 'triforce_hunt' and world.players > 1: tt['sign_ganon'] = 'Go find the Triforce pieces with your friends... Ganon is invincible!' else: tt['sign_ganon'] = 'Go find the Triforce pieces... Ganon is invincible!' @@ -2504,17 +2498,17 @@ def write_strings(rom, world, player): tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!' tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!' if world.treasure_hunt_count[player] > 1: - if world.goal[player] == 'ganontriforcehunt' and world.players > 1: + if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1: tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d with your friends to defeat Ganon.' % \ (world.treasure_hunt_count[player], world.triforce_pieces_available[player]) - elif world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']: + elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d to defeat Ganon.' % \ (world.treasure_hunt_count[player], world.triforce_pieces_available[player]) else: - if world.goal[player] == 'ganontriforcehunt' and world.players > 1: + if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1: tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d with your friends to defeat Ganon.' % \ (world.treasure_hunt_count[player], world.triforce_pieces_available[player]) - elif world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']: + elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d to defeat Ganon.' % \ (world.treasure_hunt_count[player], world.triforce_pieces_available[player]) @@ -2614,12 +2608,12 @@ def set_inverted_mode(world, player, rom): rom.write_byte(snes_to_pc(0x08D40C), 0xD0) # morph proof # the following bytes should only be written in vanilla # or they'll overwrite the randomizer's shuffles - if world.shuffle[player] == 'vanilla': + if world.entrance_shuffle[player] == 'vanilla': rom.write_byte(0xDBB73 + 0x23, 0x37) # switch AT and GT rom.write_byte(0xDBB73 + 0x36, 0x24) rom.write_int16(0x15AEE + 2 * 0x38, 0x00E0) rom.write_int16(0x15AEE + 2 * 0x25, 0x000C) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: + if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: rom.write_byte(0x15B8C, 0x6C) rom.write_byte(0xDBB73 + 0x00, 0x53) # switch bomb shop and links house rom.write_byte(0xDBB73 + 0x52, 0x01) @@ -2677,7 +2671,7 @@ def set_inverted_mode(world, player, rom): rom.write_int16(snes_to_pc(0x02D9A6), 0x005A) rom.write_byte(snes_to_pc(0x02D9B3), 0x12) # keep the old man spawn point at old man house unless shuffle is vanilla - if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple', 'dungeonscrossed']: + if world.entrance_shuffle[player] in ['vanilla', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed']: rom.write_bytes(snes_to_pc(0x308350), [0x00, 0x00, 0x01]) rom.write_int16(snes_to_pc(0x02D8DE), 0x00F1) rom.write_bytes(snes_to_pc(0x02D910), [0x1F, 0x1E, 0x1F, 0x1F, 0x03, 0x02, 0x03, 0x03]) @@ -2740,7 +2734,7 @@ def set_inverted_mode(world, player, rom): rom.write_int16s(snes_to_pc(0x1bb836), [0x001B, 0x001B, 0x001B]) rom.write_int16(snes_to_pc(0x308300), 0x0140) # new pyramid hole entrance rom.write_int16(snes_to_pc(0x308320), 0x001B) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: + if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: rom.write_byte(snes_to_pc(0x308340), 0x7B) rom.write_int16(snes_to_pc(0x1af504), 0x148B) rom.write_int16(snes_to_pc(0x1af50c), 0x149B) @@ -2777,10 +2771,10 @@ def set_inverted_mode(world, player, rom): rom.write_bytes(snes_to_pc(0x1BC85A), [0x50, 0x0F, 0x82]) rom.write_int16(0xDB96F + 2 * 0x35, 0x001B) # move pyramid exit door rom.write_int16(0xDBA71 + 2 * 0x35, 0x06A4) - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: + if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: rom.write_byte(0xDBB73 + 0x35, 0x36) rom.write_byte(snes_to_pc(0x09D436), 0xF3) # remove castle gate warp - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: + if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: rom.write_int16(0x15AEE + 2 * 0x37, 0x0010) # pyramid exit to new hc area rom.write_byte(0x15B8C + 0x37, 0x1B) rom.write_int16(0x15BDB + 2 * 0x37, 0x0418) diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index 98ab805b5c..17061842dd 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -9,25 +9,25 @@ from worlds.generic.Rules import (add_item_rule, add_rule, forbid_item, from . import OverworldGlitchRules from .Bosses import GanonDefeatRule from .Items import ItemFactory, item_name_groups, item_table, progression_items -from .Options import smallkey_shuffle +from .Options import small_key_shuffle from .OverworldGlitchRules import no_logic_rules, overworld_glitches_rules from .Regions import LTTPRegionType, location_table from .StateHelpers import (can_extend_magic, can_kill_most_things, can_lift_heavy_rocks, can_lift_rocks, can_melt_things, can_retrieve_tablet, can_shoot_arrows, has_beam_sword, has_crystals, - has_fire_source, has_hearts, + has_fire_source, has_hearts, has_melee_weapon, has_misery_mire_medallion, has_sword, has_turtle_rock_medallion, - has_triforce_pieces) + has_triforce_pieces, can_use_bombs, can_bomb_or_bonk) from .UnderworldGlitchRules import underworld_glitches_rules def set_rules(world): player = world.player world = world.multiworld - if world.logic[player] == 'nologic': + if world.glitches_required[player] == 'no_logic': if player == next(player_id for player_id in world.get_game_players("A Link to the Past") - if world.logic[player_id] == 'nologic'): # only warn one time + if world.glitches_required[player_id] == 'no_logic'): # only warn one time logging.info( 'WARNING! Seeds generated under this logic often require major glitches and may be impossible!') @@ -45,8 +45,8 @@ def set_rules(world): else: world.completion_condition[player] = lambda state: state.has('Triforce', player) - global_rules(world, player) dungeon_boss_rules(world, player) + global_rules(world, player) if world.mode[player] != 'inverted': default_rules(world, player) @@ -61,24 +61,24 @@ def set_rules(world): else: raise NotImplementedError(f'World state {world.mode[player]} is not implemented yet') - if world.logic[player] == 'noglitches': + if world.glitches_required[player] == 'no_glitches': no_glitches_rules(world, player) - elif world.logic[player] == 'owglitches': + elif world.glitches_required[player] == 'overworld_glitches': # Initially setting no_glitches_rules to set the baseline rules for some # entrances. The overworld_glitches_rules set is primarily additive. no_glitches_rules(world, player) fake_flipper_rules(world, player) overworld_glitches_rules(world, player) - elif world.logic[player] in ['hybridglitches', 'nologic']: + elif world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']: no_glitches_rules(world, player) fake_flipper_rules(world, player) overworld_glitches_rules(world, player) underworld_glitches_rules(world, player) - elif world.logic[player] == 'minorglitches': + elif world.glitches_required[player] == 'minor_glitches': no_glitches_rules(world, player) fake_flipper_rules(world, player) else: - raise NotImplementedError(f'Not implemented yet: Logic - {world.logic[player]}') + raise NotImplementedError(f'Not implemented yet: Logic - {world.glitches_required[player]}') if world.goal[player] == 'bosses': # require all bosses to beat ganon @@ -89,7 +89,7 @@ def set_rules(world): if world.mode[player] != 'inverted': set_big_bomb_rules(world, player) - if world.logic[player] in {'owglitches', 'hybridglitches', 'nologic'} and world.shuffle[player] not in {'insanity', 'insanity_legacy', 'madness'}: + if world.glitches_required[player] in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.entrance_shuffle[player] not in {'insanity', 'insanity_legacy', 'madness'}: path_to_courtyard = mirrorless_path_to_castle_courtyard(world, player) add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.multiworld.get_entrance('Dark Death Mountain Offset Mirror', player).can_reach(state) and all(rule(state) for rule in path_to_courtyard), 'or') else: @@ -97,18 +97,18 @@ def set_rules(world): # if swamp and dam have not been moved we require mirror for swamp palace # however there is mirrorless swamp in hybrid MG, so we don't necessarily want this. HMG handles this requirement itself. - if not world.swamp_patch_required[player] and world.logic[player] not in ['hybridglitches', 'nologic']: + if not world.swamp_patch_required[player] and world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']: add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player)) # GT Entrance may be required for Turtle Rock for OWG and < 7 required ganons_tower = world.get_entrance('Inverted Ganons Tower' if world.mode[player] == 'inverted' else 'Ganons Tower', player) - if world.crystals_needed_for_gt[player] == 7 and not (world.logic[player] in ['owglitches', 'hybridglitches', 'nologic'] and world.mode[player] != 'inverted'): + if world.crystals_needed_for_gt[player] == 7 and not (world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and world.mode[player] != 'inverted'): set_rule(ganons_tower, lambda state: False) set_trock_key_rules(world, player) set_rule(ganons_tower, lambda state: has_crystals(state, state.multiworld.crystals_needed_for_gt[player], player)) - if world.mode[player] != 'inverted' and world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: + if world.mode[player] != 'inverted' and world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.multiworld.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or') set_bunny_rules(world, player, world.mode[player] == 'inverted') @@ -139,6 +139,7 @@ def set_defeat_dungeon_boss_rule(location): add_rule(location, lambda state: location.parent_region.dungeon.boss.can_defeat(state)) + def set_always_allow(spot, rule): spot.always_allow = rule @@ -184,6 +185,7 @@ def dungeon_boss_rules(world, player): for location in boss_locations: set_defeat_dungeon_boss_rule(world.get_location(location, player)) + def global_rules(world, player): # ganon can only carry triforce add_item_rule(world.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player) @@ -213,14 +215,61 @@ def global_rules(world, player): set_rule(world.get_location('Ether Tablet', player), lambda state: can_retrieve_tablet(state, player)) set_rule(world.get_location('Master Sword Pedestal', player), lambda state: state.has('Red Pendant', player) and state.has('Blue Pendant', player) and state.has('Green Pendant', player)) - set_rule(world.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player) and state.can_reach('Blacksmiths Hut', 'Region', player)) # Can't S&Q with smith + set_rule(world.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player) and state.can_reach('Blacksmiths Hut', 'Region', player)) # Can't S&Q with smith set_rule(world.get_location('Blacksmith', player), lambda state: state.has('Return Smith', player)) set_rule(world.get_location('Magic Bat', player), lambda state: state.has('Magic Powder', player)) set_rule(world.get_location('Sick Kid', player), lambda state: state.has_group("Bottles", player)) set_rule(world.get_location('Library', player), lambda state: state.has('Pegasus Boots', player)) - set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player)) + + if world.enemy_shuffle[player]: + set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player) and + can_kill_most_things(state, player, 4)) + else: + set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player) + and ((state.multiworld.enemy_health[player] in ("easy", "default") and can_use_bombs(state, player, 4)) + or can_shoot_arrows(state, player) or state.has("Cane of Somaria", player) + or has_beam_sword(state, player))) + set_rule(world.get_location('Sahasrahla', player), lambda state: state.has('Green Pendant', player)) + set_rule(world.get_location('Aginah\'s Cave', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_location('Blind\'s Hideout - Top', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_location('Chicken House', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_location('Kakariko Well - Top', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_location('Graveyard Cave', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_location('Sahasrahla\'s Hut - Left', player), lambda state: can_bomb_or_bonk(state, player)) + set_rule(world.get_location('Sahasrahla\'s Hut - Middle', player), lambda state: can_bomb_or_bonk(state, player)) + set_rule(world.get_location('Sahasrahla\'s Hut - Right', player), lambda state: can_bomb_or_bonk(state, player)) + set_rule(world.get_location('Paradox Cave Lower - Left', player), lambda state: can_use_bombs(state, player) + or has_beam_sword(state, player) or can_shoot_arrows(state, player) + or state.has_any(["Fire Rod", "Cane of Somaria"], player)) + set_rule(world.get_location('Paradox Cave Lower - Right', player), lambda state: can_use_bombs(state, player) + or has_beam_sword(state, player) or can_shoot_arrows(state, player) + or state.has_any(["Fire Rod", "Cane of Somaria"], player)) + set_rule(world.get_location('Paradox Cave Lower - Far Right', player), lambda state: can_use_bombs(state, player) + or has_beam_sword(state, player) or can_shoot_arrows(state, player) + or state.has_any(["Fire Rod", "Cane of Somaria"], player)) + set_rule(world.get_location('Paradox Cave Lower - Middle', player), lambda state: can_use_bombs(state, player) + or has_beam_sword(state, player) or can_shoot_arrows(state, player) + or state.has_any(["Fire Rod", "Cane of Somaria"], player)) + set_rule(world.get_location('Paradox Cave Lower - Far Left', player), lambda state: can_use_bombs(state, player) + or has_beam_sword(state, player) or can_shoot_arrows(state, player) + or state.has_any(["Fire Rod", "Cane of Somaria"], player)) + set_rule(world.get_location('Paradox Cave Upper - Left', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_location('Paradox Cave Upper - Right', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_location('Mini Moldorm Cave - Far Left', player), lambda state: can_kill_most_things(state, player, 4)) + set_rule(world.get_location('Mini Moldorm Cave - Left', player), lambda state: can_kill_most_things(state, player, 4)) + set_rule(world.get_location('Mini Moldorm Cave - Far Right', player), lambda state: can_kill_most_things(state, player, 4)) + set_rule(world.get_location('Mini Moldorm Cave - Right', player), lambda state: can_kill_most_things(state, player, 4)) + set_rule(world.get_location('Mini Moldorm Cave - Generous Guy', player), lambda state: can_kill_most_things(state, player, 4)) + set_rule(world.get_location('Hype Cave - Bottom', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_location('Hype Cave - Middle Left', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_location('Hype Cave - Middle Right', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_location('Hype Cave - Top', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_entrance('Light World Death Mountain Shop', player), lambda state: can_use_bombs(state, player)) + + set_rule(world.get_entrance('Two Brothers House Exit (West)', player), lambda state: can_bomb_or_bonk(state, player)) + set_rule(world.get_entrance('Two Brothers House Exit (East)', player), lambda state: can_bomb_or_bonk(state, player)) set_rule(world.get_location('Spike Cave', player), lambda state: state.has('Hammer', player) and can_lift_rocks(state, player) and @@ -238,61 +287,81 @@ def global_rules(world, player): set_rule(world.get_entrance('Sewers Door', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) or ( - world.smallkey_shuffle[player] == smallkey_shuffle.option_universal and world.mode[ + world.small_key_shuffle[player] == small_key_shuffle.option_universal and world.mode[ player] == 'standard')) # standard universal small keys cannot access the shop set_rule(world.get_entrance('Sewers Back Door', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4)) + set_rule(world.get_entrance('Sewers Secret Room', player), lambda state: can_bomb_or_bonk(state, player)) + set_rule(world.get_entrance('Agahnim 1', player), lambda state: has_sword(state, player) and state._lttp_has_key('Small Key (Agahnims Tower)', player, 4)) - set_rule(world.get_location('Castle Tower - Room 03', player), lambda state: can_kill_most_things(state, player, 8)) + set_rule(world.get_location('Castle Tower - Room 03', player), lambda state: can_kill_most_things(state, player, 4)) set_rule(world.get_location('Castle Tower - Dark Maze', player), - lambda state: can_kill_most_things(state, player, 8) and state._lttp_has_key('Small Key (Agahnims Tower)', + lambda state: can_kill_most_things(state, player, 4) and state._lttp_has_key('Small Key (Agahnims Tower)', player)) set_rule(world.get_location('Castle Tower - Dark Archer Key Drop', player), - lambda state: can_kill_most_things(state, player, 8) and state._lttp_has_key('Small Key (Agahnims Tower)', + lambda state: can_kill_most_things(state, player, 4) and state._lttp_has_key('Small Key (Agahnims Tower)', player, 2)) set_rule(world.get_location('Castle Tower - Circle of Pots Key Drop', player), - lambda state: can_kill_most_things(state, player, 8) and state._lttp_has_key('Small Key (Agahnims Tower)', + lambda state: can_kill_most_things(state, player, 4) and state._lttp_has_key('Small Key (Agahnims Tower)', player, 3)) set_always_allow(world.get_location('Eastern Palace - Big Key Chest', player), lambda state, item: item.name == 'Big Key (Eastern Palace)' and item.player == player) set_rule(world.get_location('Eastern Palace - Big Key Chest', player), - lambda state: state._lttp_has_key('Small Key (Eastern Palace)', player, 2) or - ((location_item_name(state, 'Eastern Palace - Big Key Chest', player) == ('Big Key (Eastern Palace)', player) - and state.has('Small Key (Eastern Palace)', player)))) + lambda state: can_kill_most_things(state, player, 5) and (state._lttp_has_key('Small Key (Eastern Palace)', + player, 2) or ((location_item_name(state, 'Eastern Palace - Big Key Chest', player) + == ('Big Key (Eastern Palace)', player) and state.has('Small Key (Eastern Palace)', + player))))) set_rule(world.get_location('Eastern Palace - Dark Eyegore Key Drop', player), - lambda state: state.has('Big Key (Eastern Palace)', player)) + lambda state: state.has('Big Key (Eastern Palace)', player) and can_kill_most_things(state, player, 1)) set_rule(world.get_location('Eastern Palace - Big Chest', player), lambda state: state.has('Big Key (Eastern Palace)', player)) + # not bothering to check for can_kill_most_things in the rooms leading to boss, as if you can kill a boss you should + # be able to get through these rooms ep_boss = world.get_location('Eastern Palace - Boss', player) - set_rule(ep_boss, lambda state: state.has('Big Key (Eastern Palace)', player) and + add_rule(ep_boss, lambda state: state.has('Big Key (Eastern Palace)', player) and state._lttp_has_key('Small Key (Eastern Palace)', player, 2) and ep_boss.parent_region.dungeon.boss.can_defeat(state)) ep_prize = world.get_location('Eastern Palace - Prize', player) - set_rule(ep_prize, lambda state: state.has('Big Key (Eastern Palace)', player) and + add_rule(ep_prize, lambda state: state.has('Big Key (Eastern Palace)', player) and state._lttp_has_key('Small Key (Eastern Palace)', player, 2) and ep_prize.parent_region.dungeon.boss.can_defeat(state)) if not world.enemy_shuffle[player]: add_rule(ep_boss, lambda state: can_shoot_arrows(state, player)) add_rule(ep_prize, lambda state: can_shoot_arrows(state, player)) + # You can always kill the Stalfos' with the pots on easy/normal + if world.enemy_health[player] in ("hard", "expert") or world.enemy_shuffle[player]: + stalfos_rule = lambda state: can_kill_most_things(state, player, 4) + for location in ['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest', + 'Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop', + 'Eastern Palace - Big Key Chest', 'Eastern Palace - Boss', 'Eastern Palace - Prize']: + add_rule(world.get_location(location, player), stalfos_rule) + set_rule(world.get_location('Desert Palace - Big Chest', player), lambda state: state.has('Big Key (Desert Palace)', player)) set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has('Pegasus Boots', player)) set_rule(world.get_entrance('Desert Palace East Wing', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4)) - set_rule(world.get_location('Desert Palace - Big Key Chest', player), lambda state: can_kill_most_things(state, player)) - set_rule(world.get_location('Desert Palace - Beamos Hall Pot Key', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 2) and can_kill_most_things(state, player)) - set_rule(world.get_location('Desert Palace - Desert Tiles 2 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 3) and can_kill_most_things(state, player)) - set_rule(world.get_location('Desert Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state)) - set_rule(world.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state)) + set_rule(world.get_location('Desert Palace - Big Key Chest', player), lambda state: can_kill_most_things(state, player, 3)) + set_rule(world.get_location('Desert Palace - Beamos Hall Pot Key', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 2) and can_kill_most_things(state, player, 4)) + set_rule(world.get_location('Desert Palace - Desert Tiles 2 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 3) and can_kill_most_things(state, player, 4)) + add_rule(world.get_location('Desert Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state)) + add_rule(world.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state)) # logic patch to prevent placing a crystal in Desert that's required to reach the required keys - if not (world.smallkey_shuffle[player] and world.bigkey_shuffle[player]): + if not (world.small_key_shuffle[player] and world.big_key_shuffle[player]): add_rule(world.get_location('Desert Palace - Prize', player), lambda state: state.multiworld.get_region('Desert Palace Main (Outer)', player).can_reach(state)) set_rule(world.get_entrance('Tower of Hera Small Key Door', player), lambda state: state._lttp_has_key('Small Key (Tower of Hera)', player) or location_item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player)) set_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: state.has('Big Key (Tower of Hera)', player)) + if world.enemy_shuffle[player]: + add_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: can_kill_most_things(state, player, 3)) + else: + add_rule(world.get_entrance('Tower of Hera Big Key Door', player), + lambda state: (has_melee_weapon(state, player) or (state.has('Silver Bow', player) + and can_shoot_arrows(state, player)) or state.has("Cane of Byrna", player) + or state.has("Cane of Somaria", player))) set_rule(world.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player)) set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: has_fire_source(state, player)) if world.accessibility[player] != 'locations': @@ -300,9 +369,13 @@ def global_rules(world, player): set_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player)) set_rule(world.get_entrance('Swamp Palace Small Key Door', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player)) + set_rule(world.get_location('Swamp Palace - Map Chest', player), lambda state: can_use_bombs(state, player)) set_rule(world.get_location('Swamp Palace - Trench 1 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 2)) set_rule(world.get_entrance('Swamp Palace (Center)', player), lambda state: state.has('Hammer', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 3)) set_rule(world.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: state.has('Hookshot', player)) + if world.pot_shuffle[player]: + # it could move the key to the top right platform which can only be reached with bombs + add_rule(world.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: can_use_bombs(state, player)) set_rule(world.get_entrance('Swamp Palace (West)', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6) if state.has('Hookshot', player) else state._lttp_has_key('Small Key (Swamp Palace)', player, 4)) @@ -310,15 +383,18 @@ def global_rules(world, player): if world.accessibility[player] != 'locations': allow_self_locking_items(world.get_location('Swamp Palace - Big Chest', player), 'Big Key (Swamp Palace)') set_rule(world.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 5)) - if not world.smallkey_shuffle[player] and world.logic[player] not in ['hybridglitches', 'nologic']: + if not world.small_key_shuffle[player] and world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']: forbid_item(world.get_location('Swamp Palace - Entrance', player), 'Big Key (Swamp Palace)', player) set_rule(world.get_location('Swamp Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6)) set_rule(world.get_location('Swamp Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6)) + if world.pot_shuffle[player]: + # key can (and probably will) be moved behind bombable wall + set_rule(world.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: can_use_bombs(state, player)) set_rule(world.get_entrance('Thieves Town Big Key Door', player), lambda state: state.has('Big Key (Thieves Town)', player)) if world.worlds[player].dungeons["Thieves Town"].boss.enemizer_name == "Blind": - set_rule(world.get_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3)) + set_rule(world.get_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3) and can_use_bombs(state, player)) set_rule(world.get_location('Thieves\' Town - Big Chest', player), lambda state: (state._lttp_has_key('Small Key (Thieves Town)', player, 3)) and state.has('Hammer', player)) @@ -334,7 +410,7 @@ def global_rules(world, player): set_rule(world.get_entrance('Skull Woods First Section (Right) North Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) set_rule(world.get_entrance('Skull Woods First Section West Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) - set_rule(world.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player)) + set_rule(world.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) and can_use_bombs(state, player)) if world.accessibility[player] != 'locations': allow_self_locking_items(world.get_location('Skull Woods - Big Chest', player), 'Big Key (Skull Woods)') set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 4) and state.has('Fire Rod', player) and has_sword(state, player)) # sword required for curtain @@ -342,7 +418,13 @@ def global_rules(world, player): add_rule(world.get_location('Skull Woods - Boss', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) set_rule(world.get_location('Ice Palace - Jelly Key Drop', player), lambda state: can_melt_things(state, player)) - set_rule(world.get_entrance('Ice Palace (Second Section)', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player)) + set_rule(world.get_location('Ice Palace - Compass Chest', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player)) + set_rule(world.get_entrance('Ice Palace (Second Section)', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player) and can_use_bombs(state, player)) + if not world.enemy_shuffle[player]: + # Stalfos Knights can be killed by damaging them repeatedly with boomerang, swords, etc. if bombs are + # unavailable. If bombs are available, the pots can be thrown at them, so no other weapons are needed + add_rule(world.get_entrance('Ice Palace (Second Section)', player), lambda state: (can_use_bombs(state, player) + or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player) or has_sword(state, player) or state.has("Hammer", player))) set_rule(world.get_entrance('Ice Palace (Main)', player), lambda state: state._lttp_has_key('Small Key (Ice Palace)', player, 2)) set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player)) set_rule(world.get_entrance('Ice Palace (Kholdstare)', player), lambda state: can_lift_rocks(state, player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state._lttp_has_key('Small Key (Ice Palace)', player, 6) or (state.has('Cane of Somaria', player) and state._lttp_has_key('Small Key (Ice Palace)', player, 5)))) @@ -387,16 +469,21 @@ def global_rules(world, player): else state._lttp_has_key('Small Key (Misery Mire)', player, 6)) set_rule(world.get_location('Misery Mire - Compass Chest', player), lambda state: has_fire_source(state, player)) set_rule(world.get_location('Misery Mire - Big Key Chest', player), lambda state: has_fire_source(state, player)) - set_rule(world.get_entrance('Misery Mire (Vitreous)', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(world.get_entrance('Misery Mire (Vitreous)', player), lambda state: state.has('Cane of Somaria', player) and can_use_bombs(state, player)) set_rule(world.get_entrance('Turtle Rock Entrance Gap', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('Turtle Rock Entrance Gap Reverse', player), lambda state: state.has('Cane of Somaria', player)) + set_rule(world.get_location('Turtle Rock - Pokey 1 Key Drop', player), lambda state: can_kill_most_things(state, player, 5)) + set_rule(world.get_location('Turtle Rock - Pokey 2 Key Drop', player), lambda state: can_kill_most_things(state, player, 5)) set_rule(world.get_location('Turtle Rock - Compass Chest', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_location('Turtle Rock - Roller Room - Left', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) set_rule(world.get_location('Turtle Rock - Roller Room - Right', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) set_rule(world.get_location('Turtle Rock - Big Chest', player), lambda state: state.has('Big Key (Turtle Rock)', player) and (state.has('Cane of Somaria', player) or state.has('Hookshot', player))) set_rule(world.get_entrance('Turtle Rock (Big Chest) (North)', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player)) - set_rule(world.get_entrance('Turtle Rock Big Key Door', player), lambda state: state.has('Big Key (Turtle Rock)', player)) + set_rule(world.get_entrance('Turtle Rock Big Key Door', player), lambda state: state.has('Big Key (Turtle Rock)', player) and can_kill_most_things(state, player, 10)) + set_rule(world.get_entrance('Turtle Rock Ledge Exit (West)', player), lambda state: can_use_bombs(state, player) and can_kill_most_things(state, player, 10)) + set_rule(world.get_location('Turtle Rock - Chain Chomps', player), lambda state: can_use_bombs(state, player) or can_shoot_arrows(state, player) + or has_beam_sword(state, player) or state.has_any(["Blue Boomerang", "Red Boomerang", "Hookshot", "Cane of Somaria", "Fire Rod", "Ice Rod"], player)) set_rule(world.get_entrance('Turtle Rock (Dark Room) (North)', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('Turtle Rock (Dark Room) (South)', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) @@ -405,16 +492,22 @@ def global_rules(world, player): set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player)) set_rule(world.get_entrance('Turtle Rock (Trinexx)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6) and state.has('Big Key (Turtle Rock)', player) and state.has('Cane of Somaria', player)) - if not world.enemy_shuffle[player]: - set_rule(world.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_shoot_arrows(state, player)) + if world.enemy_shuffle[player]: + set_rule(world.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_kill_most_things(state, player, 3)) + else: + set_rule(world.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_shoot_arrows(state, player)) set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('Palace of Darkness Bridge Room', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 1)) # If we can reach any other small key door, we already have back door access to this area set_rule(world.get_entrance('Palace of Darkness Big Key Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) and state.has('Big Key (Palace of Darkness)', player) and can_shoot_arrows(state, player) and state.has('Hammer', player)) set_rule(world.get_entrance('Palace of Darkness (North)', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 4)) - set_rule(world.get_location('Palace of Darkness - Big Chest', player), lambda state: state.has('Big Key (Palace of Darkness)', player)) + set_rule(world.get_location('Palace of Darkness - Big Chest', player), lambda state: can_use_bombs(state, player) and state.has('Big Key (Palace of Darkness)', player)) + set_rule(world.get_location('Palace of Darkness - The Arena - Ledge', player), lambda state: can_use_bombs(state, player)) + if world.pot_shuffle[player]: + # chest switch may be up on ledge where bombs are required + set_rule(world.get_location('Palace of Darkness - Stalfos Basement', player), lambda state: can_use_bombs(state, player)) - set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or ( - location_item_name(state, 'Palace of Darkness - Big Key Chest', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 3))) + set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: can_use_bombs(state, player) and (state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or ( + location_item_name(state, 'Palace of Darkness - Big Key Chest', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 3)))) if world.accessibility[player] != 'locations': set_always_allow(world.get_location('Palace of Darkness - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5)) @@ -430,13 +523,9 @@ def global_rules(world, player): compass_room_chests = ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right', 'Ganons Tower - Conveyor Star Pits Pot Key'] back_chests = ['Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest', 'Ganons Tower - Big Key Room - Left', 'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Big Key Chest'] - set_rule(world.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has('Pegasus Boots', player)) set_rule(world.get_entrance('Ganons Tower (Tile Room)', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player))) - if world.pot_shuffle[player]: - # Pot Shuffle can move this check into the hookshot room - set_rule(world.get_location('Ganons Tower - Conveyor Cross Pot Key', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player))) set_rule(world.get_entrance('Ganons Tower (Map Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or ( location_item_name(state, 'Ganons Tower - Map Chest', player) in [('Big Key (Ganons Tower)', player)] and state._lttp_has_key('Small Key (Ganons Tower)', player, 6))) @@ -465,17 +554,17 @@ def global_rules(world, player): item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(back_chests, [player] * len(back_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5))) # Actual requirements for location in compass_room_chests: - set_rule(world.get_location(location, player), lambda state: state.has('Fire Rod', player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or ( + set_rule(world.get_location(location, player), lambda state: (can_use_bombs(state, player) or state.has("Cane of Somaria", player)) and state.has('Fire Rod', player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or ( item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(compass_room_chests, [player] * len(compass_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5)))) set_rule(world.get_location('Ganons Tower - Big Chest', player), lambda state: state.has('Big Key (Ganons Tower)', player)) set_rule(world.get_location('Ganons Tower - Big Key Room - Left', player), - lambda state: state.multiworld.get_location('Ganons Tower - Big Key Room - Left', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) + lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Room - Left', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) set_rule(world.get_location('Ganons Tower - Big Key Chest', player), - lambda state: state.multiworld.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) + lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) set_rule(world.get_location('Ganons Tower - Big Key Room - Right', player), - lambda state: state.multiworld.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) + lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) if world.enemy_shuffle[player]: set_rule(world.get_entrance('Ganons Tower Big Key Door', player), lambda state: state.has('Big Key (Ganons Tower)', player)) @@ -483,7 +572,8 @@ def global_rules(world, player): set_rule(world.get_entrance('Ganons Tower Big Key Door', player), lambda state: state.has('Big Key (Ganons Tower)', player) and can_shoot_arrows(state, player)) set_rule(world.get_entrance('Ganons Tower Torch Rooms', player), - lambda state: has_fire_source(state, player) and state.multiworld.get_entrance('Ganons Tower Torch Rooms', player).parent_region.dungeon.bosses['middle'].can_defeat(state)) + lambda state: can_kill_most_things(state, player, 8) and has_fire_source(state, player) and state.multiworld.get_entrance('Ganons Tower Torch Rooms', player).parent_region.dungeon.bosses['middle'].can_defeat(state)) + set_rule(world.get_location('Ganons Tower - Mini Helmasaur Key Drop', player), lambda state: can_kill_most_things(state, player, 1)) set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7)) set_rule(world.get_entrance('Ganons Tower Moldorm Door', player), @@ -493,9 +583,9 @@ def global_rules(world, player): set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player)) ganon = world.get_location('Ganon', player) set_rule(ganon, lambda state: GanonDefeatRule(state, player)) - if world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']: + if world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: add_rule(ganon, lambda state: has_triforce_pieces(state, player)) - elif world.goal[player] == 'ganonpedestal': + elif world.goal[player] == 'ganon_pedestal': add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player)) else: add_rule(ganon, lambda state: has_crystals(state, state.multiworld.crystals_needed_for_ganon[player], player)) @@ -507,6 +597,12 @@ def global_rules(world, player): def default_rules(world, player): """Default world rules when world state is not inverted.""" # overworld requirements + + set_rule(world.get_entrance('Light World Bomb Hut', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_entrance('Light Hype Fairy', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_entrance('Mini Moldorm Cave', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_entrance('Ice Rod Cave', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has('Pegasus Boots', player)) set_rule(world.get_entrance('Kings Grave Outer Rocks', player), lambda state: can_lift_heavy_rocks(state, player)) set_rule(world.get_entrance('Kings Grave Inner Rocks', player), lambda state: can_lift_heavy_rocks(state, player)) @@ -562,12 +658,12 @@ def default_rules(world, player): set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: (state.has('Moon Pearl', player) and state.has('Flippers', player) or state.has('Magic Mirror', player))) # Overworld Bunny Revival set_rule(world.get_location('Bombos Tablet', player), lambda state: can_retrieve_tablet(state, player)) set_rule(world.get_entrance('Dark Lake Hylia Drop (South)', player), lambda state: state.has('Moon Pearl', player) and state.has('Flippers', player)) # ToDo any fake flipper set up? - set_rule(world.get_entrance('Dark Lake Hylia Ledge Fairy', player), lambda state: state.has('Moon Pearl', player)) # bomb required + set_rule(world.get_entrance('Dark Lake Hylia Ledge Fairy', player), lambda state: state.has('Moon Pearl', player) and can_use_bombs(state, player)) set_rule(world.get_entrance('Dark Lake Hylia Ledge Spike Cave', player), lambda state: can_lift_rocks(state, player) and state.has('Moon Pearl', player)) set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has('Moon Pearl', player)) set_rule(world.get_entrance('Village of Outcasts Heavy Rock', player), lambda state: state.has('Moon Pearl', player) and can_lift_heavy_rocks(state, player)) - set_rule(world.get_entrance('Hype Cave', player), lambda state: state.has('Moon Pearl', player)) # bomb required - set_rule(world.get_entrance('Brewery', player), lambda state: state.has('Moon Pearl', player)) # bomb required + set_rule(world.get_entrance('Hype Cave', player), lambda state: state.has('Moon Pearl', player) and can_use_bombs(state, player)) + set_rule(world.get_entrance('Brewery', player), lambda state: state.has('Moon Pearl', player) and can_use_bombs(state, player)) set_rule(world.get_entrance('Thieves Town', player), lambda state: state.has('Moon Pearl', player)) # bunny cannot pull set_rule(world.get_entrance('Skull Woods First Section Hole (North)', player), lambda state: state.has('Moon Pearl', player)) # bunny cannot lift bush set_rule(world.get_entrance('Skull Woods Second Section Hole', player), lambda state: state.has('Moon Pearl', player)) # bunny cannot lift bush @@ -621,9 +717,9 @@ def inverted_rules(world, player): # overworld requirements set_rule(world.get_location('Maze Race', player), lambda state: state.has('Moon Pearl', player)) - set_rule(world.get_entrance('Mini Moldorm Cave', player), lambda state: state.has('Moon Pearl', player)) - set_rule(world.get_entrance('Ice Rod Cave', player), lambda state: state.has('Moon Pearl', player)) - set_rule(world.get_entrance('Light Hype Fairy', player), lambda state: state.has('Moon Pearl', player)) + set_rule(world.get_entrance('Mini Moldorm Cave', player), lambda state: state.has('Moon Pearl', player) and can_use_bombs(state, player)) + set_rule(world.get_entrance('Ice Rod Cave', player), lambda state: state.has('Moon Pearl', player) and can_use_bombs(state, player)) + set_rule(world.get_entrance('Light Hype Fairy', player), lambda state: state.has('Moon Pearl', player) and can_use_bombs(state, player)) set_rule(world.get_entrance('Potion Shop Pier', player), lambda state: state.has('Flippers', player) and state.has('Moon Pearl', player)) set_rule(world.get_entrance('Light World Pier', player), lambda state: state.has('Flippers', player) and state.has('Moon Pearl', player)) set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has('Pegasus Boots', player) and state.has('Moon Pearl', player)) @@ -669,7 +765,7 @@ def inverted_rules(world, player): set_rule(world.get_entrance('Bush Covered Lawn Outer Bushes', player), lambda state: state.has('Moon Pearl', player)) set_rule(world.get_entrance('Bomb Hut Inner Bushes', player), lambda state: state.has('Moon Pearl', player)) set_rule(world.get_entrance('Bomb Hut Outer Bushes', player), lambda state: state.has('Moon Pearl', player)) - set_rule(world.get_entrance('Light World Bomb Hut', player), lambda state: state.has('Moon Pearl', player)) # need bomb + set_rule(world.get_entrance('Light World Bomb Hut', player), lambda state: state.has('Moon Pearl', player) and can_use_bombs(state, player)) set_rule(world.get_entrance('North Fairy Cave Drop', player), lambda state: state.has('Moon Pearl', player)) set_rule(world.get_entrance('Lost Woods Hideout Drop', player), lambda state: state.has('Moon Pearl', player)) set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player) and (state.can_reach('Potion Shop Area', 'Region', player))) # new inverted region, need pearl for bushes or access to potion shop door/waterfall fairy @@ -715,6 +811,11 @@ def inverted_rules(world, player): set_rule(world.get_entrance('Bumper Cave Exit (Top)', player), lambda state: state.has('Cape', player)) set_rule(world.get_entrance('Bumper Cave Exit (Bottom)', player), lambda state: state.has('Cape', player) or state.has('Hookshot', player)) + set_rule(world.get_entrance('Hype Cave', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_entrance('Brewery', player), lambda state: can_use_bombs(state, player)) + set_rule(world.get_entrance('Dark Lake Hylia Ledge Fairy', player), lambda state: can_use_bombs(state, player)) + + set_rule(world.get_entrance('Skull Woods Final Section', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('Misery Mire', player), lambda state: has_sword(state, player) and has_misery_mire_medallion(state, player)) # sword required to cast magic (!) @@ -900,20 +1001,25 @@ def add_conditional_lamps(world, player): def open_rules(world, player): + + set_rule(world.get_location('Hyrule Castle - Map Guard Key Drop', player), + lambda state: can_kill_most_things(state, player, 1)) + def basement_key_rule(state): if location_item_name(state, 'Sewers - Key Rat Key Drop', player) == ("Small Key (Hyrule Castle)", player): return state._lttp_has_key("Small Key (Hyrule Castle)", player, 2) else: return state._lttp_has_key("Small Key (Hyrule Castle)", player, 3) - set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player), basement_key_rule) + set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player), + lambda state: basement_key_rule(state) and can_kill_most_things(state, player, 2)) set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), basement_key_rule) set_rule(world.get_location('Sewers - Key Rat Key Drop', player), - lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3)) + lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3) and can_kill_most_things(state, player, 1)) set_rule(world.get_location('Hyrule Castle - Big Key Drop', player), - lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4)) + lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) and can_kill_most_things(state, player, 1)) set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) and state.has('Big Key (Hyrule Castle)', player)) @@ -924,6 +1030,7 @@ def swordless_rules(world, player): set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player)) # no curtain set_rule(world.get_location('Ice Palace - Jelly Key Drop', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) + set_rule(world.get_location('Ice Palace - Compass Chest', player), lambda state: (state.has('Fire Rod', player) or state.has('Bombos', player)) and state._lttp_has_key('Small Key (Ice Palace)', player)) set_rule(world.get_entrance('Ice Palace (Second Section)', player), lambda state: (state.has('Fire Rod', player) or state.has('Bombos', player)) and state._lttp_has_key('Small Key (Ice Palace)', player)) set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop @@ -954,7 +1061,7 @@ def standard_rules(world, player): set_rule(world.get_entrance('Links House S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player)) set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player)) - if world.smallkey_shuffle[player] != smallkey_shuffle.option_universal: + if world.small_key_shuffle[player] != small_key_shuffle.option_universal: set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1)) set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), @@ -1062,15 +1169,15 @@ def set_trock_key_rules(world, player): return 6 # If TR is only accessible from the middle, the big key must be further restricted to prevent softlock potential - if not can_reach_front and not world.smallkey_shuffle[player]: + if not can_reach_front and not world.small_key_shuffle[player]: # Must not go in the Big Key Chest - only 1 other chest available and 2+ keys required for all other chests forbid_item(world.get_location('Turtle Rock - Big Key Chest', player), 'Big Key (Turtle Rock)', player) if not can_reach_big_chest: # Must not go in the Chain Chomps chest - only 2 other chests available and 3+ keys required for all other chests forbid_item(world.get_location('Turtle Rock - Chain Chomps', player), 'Big Key (Turtle Rock)', player) forbid_item(world.get_location('Turtle Rock - Pokey 2 Key Drop', player), 'Big Key (Turtle Rock)', player) - if world.accessibility[player] == 'locations' and world.goal[player] != 'icerodhunt': - if world.bigkey_shuffle[player] and can_reach_big_chest: + if world.accessibility[player] == 'locations': + if world.big_key_shuffle[player] and can_reach_big_chest: # Must not go in the dungeon - all 3 available chests (Chomps, Big Chest, Crystaroller) must be keys to access laser bridge, and the big key is required first for location in ['Turtle Rock - Chain Chomps', 'Turtle Rock - Compass Chest', 'Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop', @@ -1515,8 +1622,10 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool): # regions for the exits of multi-entrance caves/drops that bunny cannot pass # Note spiral cave and two brothers house are passable in superbunny state for glitch logic with extra requirements. - bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)', 'Turtle Rock (Entrance)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Skull Woods Second Section (Drop)', - 'Turtle Rock (Eye Bridge)', 'Sewers', 'Pyramid', 'Spiral Cave (Top)', 'Desert Palace Main (Inner)', 'Fairy Ascension Cave (Drop)'] + bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave', 'Skull Woods First Section (Right)', + 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)', 'Turtle Rock (Entrance)', 'Turtle Rock (Second Section)', + 'Turtle Rock (Big Chest)', 'Skull Woods Second Section (Drop)', 'Turtle Rock (Eye Bridge)', 'Sewers', 'Pyramid', + 'Spiral Cave (Top)', 'Desert Palace Main (Inner)', 'Fairy Ascension Cave (Drop)'] bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', @@ -1549,7 +1658,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool): def get_rule_to_add(region, location = None, connecting_entrance = None): # In OWG, a location can potentially be superbunny-mirror accessible or # bunny revival accessible. - if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic']: + if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic']: if region.name == 'Swamp Palace (Entrance)': # Need to 0hp revive - not in logic return lambda state: state.has('Moon Pearl', player) if region.name == 'Tower of Hera (Bottom)': # Need to hit the crystal switch @@ -1589,7 +1698,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool): seen.add(new_region) if not is_link(new_region): # For glitch rulesets, establish superbunny and revival rules. - if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic'] and entrance.name not in OverworldGlitchRules.get_invalid_bunny_revival_dungeons(): + if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and entrance.name not in OverworldGlitchRules.get_invalid_bunny_revival_dungeons(): if region.name in OverworldGlitchRules.get_sword_required_superbunny_mirror_regions(): possible_options.append(lambda state: path_to_access_rule(new_path, entrance) and state.has('Magic Mirror', player) and has_sword(state, player)) elif (region.name in OverworldGlitchRules.get_boots_required_superbunny_mirror_regions() @@ -1626,7 +1735,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool): # Add requirements for all locations that are actually in the dark world, except those available to the bunny, including dungeon revival for entrance in world.get_entrances(player): if is_bunny(entrance.connected_region): - if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic'] : + if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] : if entrance.connected_region.type == LTTPRegionType.Dungeon: if entrance.parent_region.type != LTTPRegionType.Dungeon and entrance.connected_region.name in OverworldGlitchRules.get_invalid_bunny_revival_dungeons(): add_rule(entrance, get_rule_to_add(entrance.connected_region, None, entrance)) @@ -1634,7 +1743,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool): if entrance.connected_region.name == 'Turtle Rock (Entrance)': add_rule(world.get_entrance('Turtle Rock Entrance Gap', player), get_rule_to_add(entrance.connected_region, None, entrance)) for location in entrance.connected_region.locations: - if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic'] and entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): + if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): continue if location.name in bunny_accessible_locations: continue diff --git a/worlds/alttp/Shops.py b/worlds/alttp/Shops.py index c0f2e2236e..64a385a185 100644 --- a/worlds/alttp/Shops.py +++ b/worlds/alttp/Shops.py @@ -5,11 +5,14 @@ import logging from Utils import int16_as_bytes +from worlds.generic.Rules import add_rule + +from BaseClasses import CollectionState from .SubClasses import ALttPLocation from .EntranceShuffle import door_addresses -from .Items import item_name_groups, item_table, ItemFactory, trap_replaceable, GetBeemizerItem -from .Options import smallkey_shuffle - +from .Items import item_name_groups +from .Options import small_key_shuffle, RandomizeShopInventories +from .StateHelpers import has_hearts, can_use_bombs, can_hold_arrows logger = logging.getLogger("Shops") @@ -36,9 +39,9 @@ class ShopPriceType(IntEnum): Item = 10 -class Shop(): +class Shop: slots: int = 3 # slot count is not dynamic in asm, however inventory can have None as empty slots - blacklist: Set[str] = set() # items that don't work, todo: actually check against this + blacklist: Set[str] = set() # items that don't work type = ShopType.Shop slot_names: Dict[int, str] = { 0: " Left", @@ -103,7 +106,7 @@ class Shop(): self.inventory = [None] * self.slots def add_inventory(self, slot: int, item: str, price: int, max: int = 0, - replacement: Optional[str] = None, replacement_price: int = 0, create_location: bool = False, + replacement: Optional[str] = None, replacement_price: int = 0, player: int = 0, price_type: int = ShopPriceType.Rupees, replacement_price_type: int = ShopPriceType.Rupees): self.inventory[slot] = { @@ -114,33 +117,23 @@ class Shop(): 'replacement': replacement, 'replacement_price': replacement_price, 'replacement_price_type': replacement_price_type, - 'create_location': create_location, 'player': player } def push_inventory(self, slot: int, item: str, price: int, max: int = 1, player: int = 0, price_type: int = ShopPriceType.Rupees): - if not self.inventory[slot]: - raise ValueError("Inventory can't be pushed back if it doesn't exist") - - if not self.can_push_inventory(slot): - logging.warning(f'Warning, there is already an item pushed into this slot.') self.inventory[slot] = { 'item': item, 'price': price, 'price_type': price_type, 'max': max, - 'replacement': self.inventory[slot]["item"], - 'replacement_price': self.inventory[slot]["price"], - 'replacement_price_type': self.inventory[slot]["price_type"], - 'create_location': self.inventory[slot]["create_location"], + 'replacement': self.inventory[slot]["item"] if self.inventory[slot] else None, + 'replacement_price': self.inventory[slot]["price"] if self.inventory[slot] else 0, + 'replacement_price_type': self.inventory[slot]["price_type"] if self.inventory[slot] else ShopPriceType.Rupees, 'player': player } - def can_push_inventory(self, slot: int): - return self.inventory[slot] and not self.inventory[slot]["replacement"] - class TakeAny(Shop): type = ShopType.TakeAny @@ -156,6 +149,10 @@ class UpgradeShop(Shop): # Potions break due to VRAM flags set in UpgradeShop. # Didn't check for more things breaking as not much else can be shuffled here currently blacklist = item_name_groups["Potions"] + slot_names: Dict[int, str] = { + 0: " Left", + 1: " Right" + } shop_class_mapping = {ShopType.UpgradeShop: UpgradeShop, @@ -163,191 +160,84 @@ shop_class_mapping = {ShopType.UpgradeShop: UpgradeShop, ShopType.TakeAny: TakeAny} -def FillDisabledShopSlots(world): - shop_slots: Set[ALttPLocation] = {location for shop_locations in (shop.region.locations for shop in world.shops) - for location in shop_locations - if location.shop_slot is not None and location.shop_slot_disabled} +def push_shop_inventories(multiworld): + shop_slots = [location for shop_locations in (shop.region.locations for shop in multiworld.shops if shop.type + != ShopType.TakeAny) for location in shop_locations if location.shop_slot is not None] + for location in shop_slots: - location.shop_slot_disabled = True - shop: Shop = location.parent_region.shop - location.item = ItemFactory(shop.inventory[location.shop_slot]['item'], location.player) - location.item_rule = lambda item: item.name == location.item.name and item.player == location.player - location.locked = True + item_name = location.item.name + # Retro Bow arrows will already have been pushed + if (not multiworld.retro_bow[location.player]) or ((item_name, location.item.player) + != ("Single Arrow", location.player)): + location.shop.push_inventory(location.shop_slot, item_name, location.shop_price, + 1, location.item.player if location.item.player != location.player else 0, + location.shop_price_type) + location.shop_price = location.shop.inventory[location.shop_slot]["price"] = min(location.shop_price, + get_price(multiworld, location.shop.inventory[location.shop_slot], location.player, + location.shop_price_type)[1]) -def ShopSlotFill(multiworld): - shop_slots: Set[ALttPLocation] = {location for shop_locations in - (shop.region.locations for shop in multiworld.shops if shop.type != ShopType.TakeAny) - for location in shop_locations if location.shop_slot is not None} - - removed = set() - for location in shop_slots: - shop: Shop = location.parent_region.shop - if not shop.can_push_inventory(location.shop_slot) or location.shop_slot_disabled: - location.shop_slot_disabled = True - removed.add(location) - - if removed: - shop_slots -= removed - - if shop_slots: - logger.info("Filling LttP Shop Slots") - del shop_slots - - from Fill import swap_location_item - # TODO: allow each game to register a blacklist to be used here? - blacklist_words = {"Rupee"} - blacklist_words = {item_name for item_name in item_table if any( - blacklist_word in item_name for blacklist_word in blacklist_words)} - blacklist_words.add("Bee") - - locations_per_sphere = [sorted(sphere, key=lambda location: (location.name, location.player)) - for sphere in multiworld.get_spheres()] - - # currently special care needs to be taken so that Shop.region.locations.item is identical to Shop.inventory - # Potentially create Locations as needed and make inventory the only source, to prevent divergence - cumu_weights = [] - shops_per_sphere = [] - candidates_per_sphere = [] - - # sort spheres into piles of valid candidates and shops - for sphere in locations_per_sphere: - current_shops_slots = [] - current_candidates = [] - shops_per_sphere.append(current_shops_slots) - candidates_per_sphere.append(current_candidates) - for location in sphere: - if isinstance(location, ALttPLocation) and location.shop_slot is not None: - if not location.shop_slot_disabled: - current_shops_slots.append(location) - elif not location.locked and location.item.name not in blacklist_words: - current_candidates.append(location) - if cumu_weights: - x = cumu_weights[-1] - else: - x = 0 - cumu_weights.append(len(current_candidates) + x) - - multiworld.random.shuffle(current_candidates) - - del locations_per_sphere - - for i, current_shop_slots in enumerate(shops_per_sphere): - if current_shop_slots: - # getting all candidates and shuffling them feels cpu expensive, there may be a better method - candidates = [(location, i) for i, candidates in enumerate(candidates_per_sphere[i:], start=i) - for location in candidates] - multiworld.random.shuffle(candidates) - for location in current_shop_slots: - shop: Shop = location.parent_region.shop - for index, (c, swapping_sphere_id) in enumerate(candidates): # chosen item locations - if c.item_rule(location.item) and location.item_rule(c.item): - swap_location_item(c, location, check_locked=False) - logger.debug(f"Swapping {c} into {location}:: {location.item}") - # remove candidate - candidates_per_sphere[swapping_sphere_id].remove(c) - candidates.pop(index) - break - - else: - # This *should* never happen. But let's fail safely just in case. - logger.warning("Ran out of ShopShuffle Item candidate locations.") - location.shop_slot_disabled = True - continue - - item_name = location.item.name - if location.item.game != "A Link to the Past": - if location.item.advancement: - price = multiworld.random.randrange(8, 56) - elif location.item.useful: - price = multiworld.random.randrange(4, 28) - else: - price = multiworld.random.randrange(2, 14) - elif any(x in item_name for x in - ['Compass', 'Map', 'Single Bomb', 'Single Arrow', 'Piece of Heart']): - price = multiworld.random.randrange(1, 7) - elif any(x in item_name for x in ['Arrow', 'Bomb', 'Clock']): - price = multiworld.random.randrange(2, 14) - elif any(x in item_name for x in ['Small Key', 'Heart']): - price = multiworld.random.randrange(4, 28) - else: - price = multiworld.random.randrange(8, 56) - - shop.push_inventory(location.shop_slot, item_name, - min(int(price * multiworld.shop_price_modifier[location.player] / 100) * 5, 9999), 1, - location.item.player if location.item.player != location.player else 0) - if 'P' in multiworld.shop_shuffle[location.player]: - price_to_funny_price(multiworld, shop.inventory[location.shop_slot], location.player) - - FillDisabledShopSlots(multiworld) - - -def create_shops(world, player: int): - option = world.shop_shuffle[player] +def create_shops(multiworld, player: int): player_shop_table = shop_table.copy() - if "w" in option: + if multiworld.include_witch_hut[player]: player_shop_table["Potion Shop"] = player_shop_table["Potion Shop"]._replace(locked=False) dynamic_shop_slots = total_dynamic_shop_slots + 3 else: dynamic_shop_slots = total_dynamic_shop_slots + if multiworld.shuffle_capacity_upgrades[player]: + player_shop_table["Capacity Upgrade"] = player_shop_table["Capacity Upgrade"]._replace(locked=False) - num_slots = min(dynamic_shop_slots, world.shop_item_slots[player]) + num_slots = min(dynamic_shop_slots, multiworld.shop_item_slots[player]) single_purchase_slots: List[bool] = [True] * num_slots + [False] * (dynamic_shop_slots - num_slots) - world.random.shuffle(single_purchase_slots) + multiworld.random.shuffle(single_purchase_slots) - if 'g' in option or 'f' in option: + if multiworld.randomize_shop_inventories[player]: default_shop_table = [i for l in [shop_generation_types[x] for x in ['arrows', 'bombs', 'potions', 'shields', 'bottle'] if - not world.retro_bow[player] or x != 'arrows'] for i in l] - new_basic_shop = world.random.sample(default_shop_table, k=3) - new_dark_shop = world.random.sample(default_shop_table, k=3) + not multiworld.retro_bow[player] or x != 'arrows'] for i in l] + new_basic_shop = multiworld.random.sample(default_shop_table, k=3) + new_dark_shop = multiworld.random.sample(default_shop_table, k=3) for name, shop in player_shop_table.items(): typ, shop_id, keeper, custom, locked, items, sram_offset = shop if not locked: - new_items = world.random.sample(default_shop_table, k=3) - if 'f' not in option: + new_items = multiworld.random.sample(default_shop_table, k=len(items)) + if multiworld.randomize_shop_inventories[player] == RandomizeShopInventories.option_randomize_by_shop_type: if items == _basic_shop_defaults: new_items = new_basic_shop elif items == _dark_world_shop_defaults: new_items = new_dark_shop - keeper = world.random.choice([0xA0, 0xC1, 0xFF]) + keeper = multiworld.random.choice([0xA0, 0xC1, 0xFF]) player_shop_table[name] = ShopData(typ, shop_id, keeper, custom, locked, new_items, sram_offset) - if world.mode[player] == "inverted": + if multiworld.mode[player] == "inverted": # make sure that blue potion is available in inverted, special case locked = None; lock when done. player_shop_table["Dark Lake Hylia Shop"] = \ player_shop_table["Dark Lake Hylia Shop"]._replace(items=_inverted_hylia_shop_defaults, locked=None) - chance_100 = int(world.retro_bow[player]) * 0.25 + int( - world.smallkey_shuffle[player] == smallkey_shuffle.option_universal) * 0.5 for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram_offset) in player_shop_table.items(): - region = world.get_region(region_name, player) + region = multiworld.get_region(region_name, player) shop: Shop = shop_class_mapping[type](region, room_id, shopkeeper, custom, locked, sram_offset) # special case: allow shop slots, but do not allow overwriting of base inventory behind them if locked is None: shop.locked = True region.shop = shop - world.shops.append(shop) + multiworld.shops.append(shop) for index, item in enumerate(inventory): shop.add_inventory(index, *item) - if not locked and num_slots: + if not locked and (num_slots or type == ShopType.UpgradeShop): slot_name = f"{region.name}{shop.slot_names[index]}" loc = ALttPLocation(player, slot_name, address=shop_table_by_location[slot_name], parent=region, hint_text="for sale") + loc.shop_price_type, loc.shop_price = get_price(multiworld, None, player) + loc.item_rule = lambda item, spot=loc: not any(i for i in price_blacklist[spot.shop_price_type] if i in item.name) + add_rule(loc, lambda state, spot=loc: shop_price_rules(state, player, spot)) + loc.shop = shop loc.shop_slot = index - loc.locked = True - if single_purchase_slots.pop(): - if world.goal[player] != 'icerodhunt': - if world.random.random() < chance_100: - additional_item = 'Rupees (100)' - else: - additional_item = 'Rupees (50)' - else: - additional_item = GetBeemizerItem(world, player, 'Nothing') - loc.item = ItemFactory(additional_item, player) - else: - loc.item = ItemFactory(GetBeemizerItem(world, player, 'Nothing'), player) + if ((not (multiworld.shuffle_capacity_upgrades[player] and type == ShopType.UpgradeShop)) + and not single_purchase_slots.pop()): loc.shop_slot_disabled = True - shop.region.locations.append(loc) + loc.locked = True + else: + shop.region.locations.append(loc) class ShopData(NamedTuple): @@ -387,9 +277,10 @@ total_dynamic_shop_slots = sum(3 for shopname, data in shop_table.items() if not SHOP_ID_START = 0x400000 shop_table_by_location_id = dict(enumerate( - (f"{name}{Shop.slot_names[num]}" for name, shop_data in - sorted(shop_table.items(), key=lambda item: item[1].sram_offset) - for num in range(3)), start=SHOP_ID_START)) + (f"{name}{UpgradeShop.slot_names[num]}" if shop_data.type == ShopType.UpgradeShop else + f"{name}{Shop.slot_names[num]}" for name, shop_data in sorted(shop_table.items(), + key=lambda item: item[1].sram_offset) + for num in range(2 if shop_data.type == ShopType.UpgradeShop else 3)), start=SHOP_ID_START)) shop_table_by_location_id[(SHOP_ID_START + total_shop_slots)] = "Old Man Sword Cave" shop_table_by_location_id[(SHOP_ID_START + total_shop_slots + 1)] = "Take-Any #1" @@ -409,114 +300,54 @@ shop_generation_types = { } -def set_up_shops(world, player: int): +def set_up_shops(multiworld, player: int): # TODO: move hard+ mode changes for shields here, utilizing the new shops - if world.retro_bow[player]: - rss = world.get_region('Red Shield Shop', player).shop + if multiworld.retro_bow[player]: + rss = multiworld.get_region('Red Shield Shop', player).shop replacement_items = [['Red Potion', 150], ['Green Potion', 75], ['Blue Potion', 200], ['Bombs (10)', 50], ['Blue Shield', 50], ['Small Heart', 10]] # Can't just replace the single arrow with 10 arrows as retro doesn't need them. - if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal: + if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: replacement_items.append(['Small Key (Universal)', 100]) - replacement_item = world.random.choice(replacement_items) + replacement_item = multiworld.random.choice(replacement_items) rss.add_inventory(2, 'Single Arrow', 80, 1, replacement_item[0], replacement_item[1]) rss.locked = True - if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal or world.retro_bow[player]: - for shop in world.random.sample([s for s in world.shops if - s.custom and not s.locked and s.type == ShopType.Shop and s.region.player == player], - 5): + if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal or multiworld.retro_bow[player]: + for shop in multiworld.random.sample([s for s in multiworld.shops if + s.custom and not s.locked and s.type == ShopType.Shop + and s.region.player == player], 5): shop.locked = True slots = [0, 1, 2] - world.random.shuffle(slots) + multiworld.random.shuffle(slots) slots = iter(slots) - if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal: + if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: shop.add_inventory(next(slots), 'Small Key (Universal)', 100) - if world.retro_bow[player]: + if multiworld.retro_bow[player]: shop.push_inventory(next(slots), 'Single Arrow', 80) - -def shuffle_shops(world, items, player: int): - option = world.shop_shuffle[player] - if 'u' in option: - progressive = world.progressive[player] - progressive = world.random.choice([True, False]) if progressive == 'grouped_random' else progressive == 'on' - progressive &= world.goal == 'icerodhunt' - new_items = ["Bomb Upgrade (+5)"] * 6 - new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)") - - if not world.retro_bow[player]: - new_items += ["Arrow Upgrade (+5)"] * 6 - new_items.append("Arrow Upgrade (+5)" if progressive else "Arrow Upgrade (+10)") - - world.random.shuffle(new_items) # Decide what gets tossed randomly if it can't insert everything. - - capacityshop: Optional[Shop] = None - for shop in world.shops: + if multiworld.shuffle_capacity_upgrades[player]: + for shop in multiworld.shops: if shop.type == ShopType.UpgradeShop and shop.region.player == player and \ shop.region.name == "Capacity Upgrade": shop.clear_inventory() - capacityshop = shop - if world.goal[player] != 'icerodhunt': - for i, item in enumerate(items): - if item.name in trap_replaceable: - items[i] = ItemFactory(new_items.pop(), player) - if not new_items: - break - else: - logging.warning( - f"Not all upgrades put into Player{player}' item pool. Putting remaining items in Capacity Upgrade shop instead.") - bombupgrades = sum(1 for item in new_items if 'Bomb Upgrade' in item) - arrowupgrades = sum(1 for item in new_items if 'Arrow Upgrade' in item) - slots = iter(range(2)) - if bombupgrades: - capacityshop.add_inventory(next(slots), 'Bomb Upgrade (+5)', 100, bombupgrades) - if arrowupgrades: - capacityshop.add_inventory(next(slots), 'Arrow Upgrade (+5)', 100, arrowupgrades) - else: - for item in new_items: - world.push_precollected(ItemFactory(item, player)) - - if any(setting in option for setting in 'ipP'): + if (multiworld.shuffle_shop_inventories[player] or multiworld.randomize_shop_prices[player] + or multiworld.randomize_cost_types[player]): shops = [] - upgrade_shops = [] total_inventory = [] - for shop in world.shops: + for shop in multiworld.shops: if shop.region.player == player: - if shop.type == ShopType.UpgradeShop: - upgrade_shops.append(shop) - elif shop.type == ShopType.Shop and not shop.locked: + if shop.type == ShopType.Shop and not shop.locked: shops.append(shop) total_inventory.extend(shop.inventory) - if 'p' in option: - def price_adjust(price: int) -> int: - # it is important that a base price of 0 always returns 0 as new price! - adjust = 2 if price < 100 else 5 - return int((price / adjust) * (0.5 + world.random.random() * 1.5)) * adjust + for item in total_inventory: + item["price_type"], item["price"] = get_price(multiworld, item, player) - def adjust_item(item): - if item: - item["price"] = price_adjust(item["price"]) - item['replacement_price'] = price_adjust(item["price"]) - - for item in total_inventory: - adjust_item(item) - for shop in upgrade_shops: - for item in shop.inventory: - adjust_item(item) - - if 'P' in option: - for item in total_inventory: - price_to_funny_price(world, item, player) - # Don't apply to upgrade shops - # Upgrade shop is only one place, and will generally be too easy to - # replenish hearts and bombs - - if 'i' in option: - world.random.shuffle(total_inventory) + if multiworld.shuffle_shop_inventories[player]: + multiworld.random.shuffle(total_inventory) i = 0 for shop in shops: @@ -539,16 +370,18 @@ price_blacklist = { } price_chart = { - ShopPriceType.Rupees: lambda p: p, - ShopPriceType.Hearts: lambda p: min(5, p // 5) * 8, # Each heart is 0x8 in memory, Max of 5 hearts (20 total??) - ShopPriceType.Magic: lambda p: min(15, p // 5) * 8, # Each pip is 0x8 in memory, Max of 15 pips (16 total...) - ShopPriceType.Bombs: lambda p: max(1, min(10, p // 5)), # 10 Bombs max - ShopPriceType.Arrows: lambda p: max(1, min(30, p // 5)), # 30 Arrows Max - ShopPriceType.HeartContainer: lambda p: 0x8, - ShopPriceType.BombUpgrade: lambda p: 0x1, - ShopPriceType.ArrowUpgrade: lambda p: 0x1, - ShopPriceType.Keys: lambda p: min(3, (p // 100) + 1), # Max of 3 keys for a price - ShopPriceType.Potion: lambda p: (p // 5) % 5, + ShopPriceType.Rupees: lambda p, d: p, + # Each heart is 0x8 in memory, Max of 19 hearts on easy/normal, 9 on hard, 7 on expert + ShopPriceType.Hearts: lambda p, d: max(8, min([19, 19, 9, 7][d], p // 14) * 8), + # Each pip is 0x8 in memory, Max of 15 pips (16 total) + ShopPriceType.Magic: lambda p, d: max(8, min(15, p // 18) * 8), + ShopPriceType.Bombs: lambda p, d: max(1, min(50, p // 5)), # 50 Bombs max + ShopPriceType.Arrows: lambda p, d: max(1, min(70, p // 4)), # 70 Arrows Max + ShopPriceType.HeartContainer: lambda p, d: 0x8, + ShopPriceType.BombUpgrade: lambda p, d: 0x1, + ShopPriceType.ArrowUpgrade: lambda p, d: 0x1, + ShopPriceType.Keys: lambda p, d: max(1, min(3, (p // 90) + 1)), # Max of 3 keys for a price + ShopPriceType.Potion: lambda p, d: (p // 5) % 5, } price_type_display_name = { @@ -557,6 +390,8 @@ price_type_display_name = { ShopPriceType.Bombs: "Bombs", ShopPriceType.Arrows: "Arrows", ShopPriceType.Keys: "Keys", + ShopPriceType.Item: "Item", + ShopPriceType.Magic: "Magic" } # price division @@ -565,57 +400,74 @@ price_rate_display = { ShopPriceType.Magic: 8, } -# prices with no? logic requirements -simple_price_types = [ - ShopPriceType.Rupees, - ShopPriceType.Hearts, - ShopPriceType.Bombs, - ShopPriceType.Arrows, - ShopPriceType.Keys -] + +def get_price_modifier(item): + if item.game == "A Link to the Past": + if any(x in item.name for x in + ['Compass', 'Map', 'Single Bomb', 'Single Arrow', 'Piece of Heart']): + return 0.125 + elif any(x in item.name for x in + ['Arrow', 'Bomb', 'Clock']) and item.name != "Bombos" and "(50)" not in item.name: + return 0.25 + elif any(x in item.name for x in ['Small Key', 'Heart']): + return 0.5 + else: + return 1 + if item.advancement: + return 1 + elif item.useful: + return 0.5 + else: + return 0.25 -def price_to_funny_price(world, item: dict, player: int): - """ - Converts a raw Rupee price into a special price type - """ +def get_price(multiworld, item, player: int, price_type=None): + """Converts a raw Rupee price into a special price type""" + + if price_type: + price_types = [price_type] + else: + price_types = [ShopPriceType.Rupees] # included as a chance to not change price + if multiworld.randomize_cost_types[player]: + price_types += [ + ShopPriceType.Hearts, + ShopPriceType.Bombs, + ShopPriceType.Magic, + ] + if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: + if item and item["item"] == "Small Key (Universal)": + price_types = [ShopPriceType.Rupees, ShopPriceType.Magic] # no logical requirements for repeatable keys + else: + price_types.append(ShopPriceType.Keys) + if multiworld.retro_bow[player]: + if item and item["item"] == "Single Arrow": + price_types = [ShopPriceType.Rupees, ShopPriceType.Magic] # no logical requirements for arrows + else: + price_types.append(ShopPriceType.Arrows) + diff = multiworld.item_pool[player].value if item: - price_types = [ - ShopPriceType.Rupees, # included as a chance to not change price type - ShopPriceType.Hearts, - ShopPriceType.Bombs, - ] - # don't pay in universal keys to get access to universal keys - if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal \ - and not "Small Key (Universal)" == item['replacement']: - price_types.append(ShopPriceType.Keys) - if not world.retro_bow[player]: - price_types.append(ShopPriceType.Arrows) - world.random.shuffle(price_types) + # This is for a shop's regular inventory, the item is already determined, and we will decide the price here + price = item["price"] + if multiworld.randomize_shop_prices[player]: + adjust = 2 if price < 100 else 5 + price = int((price / adjust) * (0.5 + multiworld.random.random() * 1.5)) * adjust + multiworld.random.shuffle(price_types) for p_type in price_types: - # Ignore rupee prices - if p_type == ShopPriceType.Rupees: - return if any(x in item['item'] for x in price_blacklist[p_type]): continue - else: - item['price'] = min(price_chart[p_type](item['price']), 255) - item['price_type'] = p_type - break + return p_type, price_chart[p_type](price, diff) + else: + # This is an AP location and the price will be adjusted after an item is shuffled into it + p_type = multiworld.random.choice(price_types) + return p_type, price_chart[p_type](min(int(multiworld.random.randint(8, 56) + * multiworld.shop_price_modifier[player] / 100) * 5, 9999), diff) -def create_dynamic_shop_locations(world, player): - for shop in world.shops: - if shop.region.player == player: - for i, item in enumerate(shop.inventory): - if item is None: - continue - if item['create_location']: - slot_name = f"{shop.region.name}{shop.slot_names[i]}" - loc = ALttPLocation(player, slot_name, - address=shop_table_by_location[slot_name], parent=shop.region) - loc.place_locked_item(ItemFactory(item['item'], player)) - if shop.type == ShopType.TakeAny: - loc.shop_slot_disabled = True - shop.region.locations.append(loc) - loc.shop_slot = i +def shop_price_rules(state: CollectionState, player: int, location: ALttPLocation): + if location.shop_price_type == ShopPriceType.Hearts: + return has_hearts(state, player, (location.shop_price / 8) + 1) + elif location.shop_price_type == ShopPriceType.Bombs: + return can_use_bombs(state, player, location.shop_price) + elif location.shop_price_type == ShopPriceType.Arrows: + return can_hold_arrows(state, player, location.shop_price) + return True diff --git a/worlds/alttp/StateHelpers.py b/worlds/alttp/StateHelpers.py index 38ce00ef45..4ed1b1caf2 100644 --- a/worlds/alttp/StateHelpers.py +++ b/worlds/alttp/StateHelpers.py @@ -10,7 +10,7 @@ def is_not_bunny(state: CollectionState, region: LTTPRegion, player: int) -> boo def can_bomb_clip(state: CollectionState, region: LTTPRegion, player: int) -> bool: - return is_not_bunny(state, region, player) and state.has('Pegasus Boots', player) + return can_use_bombs(state, player) and is_not_bunny(state, region, player) and state.has('Pegasus Boots', player) def can_buy_unlimited(state: CollectionState, item: str, player: int) -> bool: @@ -83,13 +83,47 @@ def can_extend_magic(state: CollectionState, player: int, smallmagic: int = 16, return basemagic >= smallmagic +def can_hold_arrows(state: CollectionState, player: int, quantity: int): + arrows = 30 + ((state.count("Arrow Upgrade (+5)", player) * 5) + (state.count("Arrow Upgrade (+10)", player) * 10) + + (state.count("Bomb Upgrade (50)", player) * 50)) + # Arrow Upgrade (+5) beyond the 6th gives +10 + arrows += max(0, ((state.count("Arrow Upgrade (+5)", player) - 6) * 10)) + return min(70, arrows) >= quantity + + +def can_use_bombs(state: CollectionState, player: int, quantity: int = 1) -> bool: + bombs = 0 if state.multiworld.bombless_start[player] else 10 + bombs += ((state.count("Bomb Upgrade (+5)", player) * 5) + (state.count("Bomb Upgrade (+10)", player) * 10) + + (state.count("Bomb Upgrade (50)", player) * 50)) + # Bomb Upgrade (+5) beyond the 6th gives +10 + bombs += max(0, ((state.count("Bomb Upgrade (+5)", player) - 6) * 10)) + if (not state.multiworld.shuffle_capacity_upgrades[player]) and state.has("Capacity Upgrade Shop", player): + bombs += 40 + return bombs >= min(quantity, 50) + + +def can_bomb_or_bonk(state: CollectionState, player: int) -> bool: + return state.has("Pegasus Boots", player) or can_use_bombs(state, player) + + def can_kill_most_things(state: CollectionState, player: int, enemies: int = 5) -> bool: - return (has_melee_weapon(state, player) - or state.has('Cane of Somaria', player) - or (state.has('Cane of Byrna', player) and (enemies < 6 or can_extend_magic(state, player))) - or can_shoot_arrows(state, player) - or state.has('Fire Rod', player) - or (state.has('Bombs (10)', player) and enemies < 6)) + if state.multiworld.enemy_shuffle[player]: + # I don't fully understand Enemizer's logic for placing enemies in spots where they need to be killable, if any. + # Just go with maximal requirements for now. + return (has_melee_weapon(state, player) + and state.has('Cane of Somaria', player) + and state.has('Cane of Byrna', player) and can_extend_magic(state, player) + and can_shoot_arrows(state, player) + and state.has('Fire Rod', player) + and can_use_bombs(state, player, enemies * 4)) + else: + return (has_melee_weapon(state, player) + or state.has('Cane of Somaria', player) + or (state.has('Cane of Byrna', player) and (enemies < 6 or can_extend_magic(state, player))) + or can_shoot_arrows(state, player) + or state.has('Fire Rod', player) + or (state.multiworld.enemy_health[player] in ("easy", "default") + and can_use_bombs(state, player, enemies * 4))) def can_get_good_bee(state: CollectionState, player: int) -> bool: @@ -159,4 +193,4 @@ def can_get_glitched_speed_dw(state: CollectionState, player: int) -> bool: rules = [state.has('Pegasus Boots', player), any([state.has('Hookshot', player), has_sword(state, player)])] if state.multiworld.mode[player] != 'inverted': rules.append(state.has('Moon Pearl', player)) - return all(rules) \ No newline at end of file + return all(rules) diff --git a/worlds/alttp/SubClasses.py b/worlds/alttp/SubClasses.py index 22eeebe181..769dcc1998 100644 --- a/worlds/alttp/SubClasses.py +++ b/worlds/alttp/SubClasses.py @@ -14,9 +14,12 @@ class ALttPLocation(Location): crystal: bool player_address: Optional[int] _hint_text: Optional[str] + shop: None shop_slot: Optional[int] = None """If given as integer, shop_slot is the shop's inventory index.""" shop_slot_disabled: bool = False + shop_price = 0 + shop_price_type = None parent_region: "LTTPRegion" def __init__(self, player: int, name: str, address: Optional[int] = None, crystal: bool = False, diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py index a6aefc7412..497d5de496 100644 --- a/worlds/alttp/UnderworldGlitchRules.py +++ b/worlds/alttp/UnderworldGlitchRules.py @@ -42,7 +42,7 @@ def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, du fix_fake_worlds = world.fix_fake_world[player] dungeon_entrance = [r for r in world.get_region(dungeon_region, player).entrances if r.name != clip.name][0] - if not fix_dungeon_exits: # vanilla, simple, restricted, dungeonssimple; should never have fake worlds fix + if not fix_dungeon_exits: # vanilla, simple, restricted, dungeons_simple; should never have fake worlds fix # Dungeons are only shuffled among themselves. We need to check SW, MM, and AT because they can't be reentered trivially. if dungeon_entrance.name == 'Skull Woods Final Section': set_rule(clip, lambda state: False) # entrance doesn't exist until you fire rod it from the other side @@ -52,12 +52,12 @@ def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, du add_rule(clip, lambda state: state.has('Cape', player) or has_beam_sword(state, player) or state.has('Beat Agahnim 1', player)) # kill/bypass barrier # Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally. add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state)) - elif not fix_fake_worlds: # full, dungeonsfull; fixed dungeon exits, but no fake worlds fix + elif not fix_fake_worlds: # full, dungeons_full; fixed dungeon exits, but no fake worlds fix # Entry requires the entrance's requirements plus a fake pearl, but you don't gain logical access to the surrounding region. add_rule(clip, lambda state: dungeon_entrance.access_rule(fake_pearl_state(state, player))) # exiting restriction add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state)) - # Otherwise, the shuffle type is crossed, dungeonscrossed, or insanity; all of these do not need additional rules on where we can go, + # Otherwise, the shuffle type is crossed, dungeons_crossed, or insanity; all of these do not need additional rules on where we can go, # since the clip links directly to the exterior region. @@ -93,7 +93,7 @@ def underworld_glitches_rules(world, player): # We need to be able to s+q to old man, then go to either Mire or Hera at either Hera or GT. # First we require a certain type of entrance shuffle, then build the rule from its pieces. if not world.swamp_patch_required[player]: - if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']: + if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: rule_map = { 'Misery Mire (Entrance)': (lambda state: True), 'Tower of Hera (Bottom)': (lambda state: state.can_reach('Tower of Hera Big Key Door', 'Entrance', player)) diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 3f380d0037..e1216010e2 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -13,14 +13,14 @@ from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_con from .InvertedRegions import create_inverted_regions, mark_dark_world_regions from .ItemPool import generate_itempool, difficulties from .Items import item_init_table, item_name_groups, item_table, GetBeemizerItem -from .Options import alttp_options, smallkey_shuffle +from .Options import alttp_options, small_key_shuffle from .Regions import lookup_name_to_id, create_regions, mark_light_world_regions, lookup_vanilla_location_to_entrance, \ is_main_entrance, key_drop_data from .Client import ALTTPSNIClient from .Rom import LocalRom, patch_rom, patch_race_rom, check_enemizer, patch_enemizer, apply_rom_settings, \ get_hash_string, get_base_rom_path, LttPDeltaPatch from .Rules import set_rules -from .Shops import create_shops, Shop, ShopSlotFill, ShopType, price_rate_display, price_type_display_name +from .Shops import create_shops, Shop, push_shop_inventories, ShopType, price_rate_display, price_type_display_name from .SubClasses import ALttPItem, LTTPRegionType from worlds.AutoWorld import World, WebWorld, LogicMixin from .StateHelpers import can_buy_unlimited @@ -213,7 +213,7 @@ class ALTTPWorld(World): item_name_to_id = {name: data.item_code for name, data in item_table.items() if type(data.item_code) == int} location_name_to_id = lookup_name_to_id - data_version = 8 + data_version = 9 required_client_version = (0, 4, 1) web = ALTTPWeb() @@ -290,33 +290,34 @@ class ALTTPWorld(World): self.pyramid_fairy_bottle_fill = self.random.choice(bottle_options) if multiworld.mode[player] == 'standard': - if multiworld.smallkey_shuffle[player]: - if (multiworld.smallkey_shuffle[player] not in - (smallkey_shuffle.option_universal, smallkey_shuffle.option_own_dungeons, - smallkey_shuffle.option_start_with)): + if multiworld.small_key_shuffle[player]: + if (multiworld.small_key_shuffle[player] not in + (small_key_shuffle.option_universal, small_key_shuffle.option_own_dungeons, + small_key_shuffle.option_start_with)): self.multiworld.local_early_items[self.player]["Small Key (Hyrule Castle)"] = 1 self.multiworld.local_items[self.player].value.add("Small Key (Hyrule Castle)") self.multiworld.non_local_items[self.player].value.discard("Small Key (Hyrule Castle)") - if multiworld.bigkey_shuffle[player]: + if multiworld.big_key_shuffle[player]: self.multiworld.local_items[self.player].value.add("Big Key (Hyrule Castle)") self.multiworld.non_local_items[self.player].value.discard("Big Key (Hyrule Castle)") # system for sharing ER layouts self.er_seed = str(multiworld.random.randint(0, 2 ** 64)) - if "-" in multiworld.shuffle[player]: - shuffle, seed = multiworld.shuffle[player].split("-", 1) - multiworld.shuffle[player] = shuffle + if multiworld.entrance_shuffle[player] != "vanilla" and multiworld.entrance_shuffle_seed[player] != "random": + shuffle = multiworld.entrance_shuffle[player].current_key if shuffle == "vanilla": self.er_seed = "vanilla" - elif seed.startswith("group-") or multiworld.is_race: + elif (not multiworld.entrance_shuffle_seed[player].value.isdigit()) or multiworld.is_race: self.er_seed = get_same_seed(multiworld, ( - shuffle, seed, multiworld.retro_caves[player], multiworld.mode[player], multiworld.logic[player])) + shuffle, multiworld.entrance_shuffle_seed[player].value, multiworld.retro_caves[player], multiworld.mode[player], + multiworld.glitches_required[player])) else: # not a race or group seed, use set seed as is. - self.er_seed = seed - elif multiworld.shuffle[player] == "vanilla": + self.er_seed = int(multiworld.entrance_shuffle_seed[player].value) + elif multiworld.entrance_shuffle[player] == "vanilla": self.er_seed = "vanilla" - for dungeon_item in ["smallkey_shuffle", "bigkey_shuffle", "compass_shuffle", "map_shuffle"]: + + for dungeon_item in ["small_key_shuffle", "big_key_shuffle", "compass_shuffle", "map_shuffle"]: option = getattr(multiworld, dungeon_item)[player] if option == "own_world": multiworld.local_items[player].value |= self.item_name_groups[option.item_name_group] @@ -329,10 +330,10 @@ class ALTTPWorld(World): if option == "original_dungeon": self.dungeon_specific_item_names |= self.item_name_groups[option.item_name_group] - multiworld.difficulty_requirements[player] = difficulties[multiworld.difficulty[player]] + multiworld.difficulty_requirements[player] = difficulties[multiworld.item_pool[player].current_key] # enforce pre-defined local items. - if multiworld.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]: + if multiworld.goal[player] in ["local_triforce_hunt", "local_ganon_triforce_hunt"]: multiworld.local_items[player].value.add('Triforce Piece') # Not possible to place crystals outside boss prizes yet (might as well make it consistent with pendants too). @@ -345,9 +346,6 @@ class ALTTPWorld(World): player = self.player world = self.multiworld - world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player], - world.triforce_pieces_required[player]) - if world.mode[player] != 'inverted': create_regions(world, player) else: @@ -355,8 +353,8 @@ class ALTTPWorld(World): create_shops(world, player) self.create_dungeons() - if world.logic[player] not in ["noglitches", "minorglitches"] and world.shuffle[player] in \ - {"vanilla", "dungeonssimple", "dungeonsfull", "simple", "restricted", "full"}: + if world.glitches_required[player] not in ["no_glitches", "minor_glitches"] and world.entrance_shuffle[player] in \ + {"vanilla", "dungeons_simple", "dungeons_full", "simple", "restricted", "full"}: world.fix_fake_world[player] = False # seeded entrance shuffle @@ -455,7 +453,7 @@ class ALTTPWorld(World): if state.has('Silver Bow', item.player): return elif state.has('Bow', item.player) and (self.multiworld.difficulty_requirements[item.player].progressive_bow_limit >= 2 - or self.multiworld.logic[item.player] == 'noglitches' + or self.multiworld.glitches_required[item.player] == 'no_glitches' or self.multiworld.swordless[item.player]): # modes where silver bow is always required for ganon return 'Silver Bow' elif self.multiworld.difficulty_requirements[item.player].progressive_bow_limit >= 1: @@ -499,9 +497,9 @@ class ALTTPWorld(World): break else: raise FillError('Unable to place dungeon prizes') - if world.mode[player] == 'standard' and world.smallkey_shuffle[player] \ - and world.smallkey_shuffle[player] != smallkey_shuffle.option_universal and \ - world.smallkey_shuffle[player] != smallkey_shuffle.option_own_dungeons: + if world.mode[player] == 'standard' and world.small_key_shuffle[player] \ + and world.small_key_shuffle[player] != small_key_shuffle.option_universal and \ + world.small_key_shuffle[player] != small_key_shuffle.option_own_dungeons: world.local_early_items[player]["Small Key (Hyrule Castle)"] = 1 @classmethod @@ -509,10 +507,9 @@ class ALTTPWorld(World): from .Dungeons import fill_dungeons_restrictive fill_dungeons_restrictive(world) - @classmethod def stage_post_fill(cls, world): - ShopSlotFill(world) + push_shop_inventories(world) @property def use_enemizer(self) -> bool: @@ -579,7 +576,7 @@ class ALTTPWorld(World): @classmethod def stage_extend_hint_information(cls, world, hint_data: typing.Dict[int, typing.Dict[int, str]]): er_hint_data = {player: {} for player in world.get_game_players("A Link to the Past") if - world.shuffle[player] != "vanilla" or world.retro_caves[player]} + world.entrance_shuffle[player] != "vanilla" or world.retro_caves[player]} for region in world.regions: if region.player in er_hint_data and region.locations: @@ -645,9 +642,9 @@ class ALTTPWorld(World): trash_counts = {} for player in world.get_game_players("A Link to the Past"): if not world.ganonstower_vanilla[player] or \ - world.logic[player] in {'owglitches', 'hybridglitches', "nologic"}: + world.glitches_required[player] in {'overworld_glitches', 'hybrid_major_glitches', "no_logic"}: pass - elif 'triforcehunt' in world.goal[player] and ('local' in world.goal[player] or world.players == 1): + elif 'triforce_hunt' in world.goal[player].current_key and ('local' in world.goal[player].current_key or world.players == 1): trash_counts[player] = world.random.randint(world.crystals_needed_for_gt[player] * 2, world.crystals_needed_for_gt[player] * 4) else: @@ -681,35 +678,6 @@ class ALTTPWorld(World): return variable return "Yes" if variable else "No" - spoiler_handle.write('Logic: %s\n' % self.multiworld.logic[self.player]) - spoiler_handle.write('Dark Room Logic: %s\n' % self.multiworld.dark_room_logic[self.player]) - spoiler_handle.write('Mode: %s\n' % self.multiworld.mode[self.player]) - spoiler_handle.write('Goal: %s\n' % self.multiworld.goal[self.player]) - if "triforce" in self.multiworld.goal[self.player]: # triforce hunt - spoiler_handle.write("Pieces available for Triforce: %s\n" % - self.multiworld.triforce_pieces_available[self.player]) - spoiler_handle.write("Pieces required for Triforce: %s\n" % - self.multiworld.triforce_pieces_required[self.player]) - spoiler_handle.write('Difficulty: %s\n' % self.multiworld.difficulty[self.player]) - spoiler_handle.write('Item Functionality: %s\n' % self.multiworld.item_functionality[self.player]) - spoiler_handle.write('Entrance Shuffle: %s\n' % self.multiworld.shuffle[self.player]) - if self.multiworld.shuffle[self.player] != "vanilla": - spoiler_handle.write('Entrance Shuffle Seed %s\n' % self.er_seed) - spoiler_handle.write('Shop inventory shuffle: %s\n' % - bool_to_text("i" in self.multiworld.shop_shuffle[self.player])) - spoiler_handle.write('Shop price shuffle: %s\n' % - bool_to_text("p" in self.multiworld.shop_shuffle[self.player])) - spoiler_handle.write('Shop upgrade shuffle: %s\n' % - bool_to_text("u" in self.multiworld.shop_shuffle[self.player])) - spoiler_handle.write('New Shop inventory: %s\n' % - bool_to_text("g" in self.multiworld.shop_shuffle[self.player] or - "f" in self.multiworld.shop_shuffle[self.player])) - spoiler_handle.write('Custom Potion Shop: %s\n' % - bool_to_text("w" in self.multiworld.shop_shuffle[self.player])) - spoiler_handle.write('Enemy health: %s\n' % self.multiworld.enemy_health[self.player]) - spoiler_handle.write('Enemy damage: %s\n' % self.multiworld.enemy_damage[self.player]) - spoiler_handle.write('Prize shuffle %s\n' % self.multiworld.shuffle_prizes[self.player]) - def write_spoiler(self, spoiler_handle: typing.TextIO) -> None: player_name = self.multiworld.get_player_name(self.player) spoiler_handle.write("\n\nMedallions:\n") @@ -783,7 +751,7 @@ class ALTTPWorld(World): if item["replacement"] is None: continue shop_data["item_{}".format(index)] +=\ - f", {item['replacement']} - {item['replacement_price']}" \ + f", {item['replacement']} - {item['replacement_price'] // price_rate_display.get(item['replacement_price_type'], 1)}" \ f" {price_type_display_name[item['replacement_price_type']]}" return shop_data @@ -796,10 +764,7 @@ class ALTTPWorld(World): item))) def get_filler_item_name(self) -> str: - if self.multiworld.goal[self.player] == "icerodhunt": - item = "Nothing" - else: - item = self.multiworld.random.choice(extras_list) + item = self.multiworld.random.choice(extras_list) return GetBeemizerItem(self.multiworld, self.player, item) def get_pre_fill_items(self): @@ -819,20 +784,20 @@ class ALTTPWorld(World): # for convenient auto-tracking of the generated settings and adjusting the tracker accordingly slot_options = ["crystals_needed_for_gt", "crystals_needed_for_ganon", "open_pyramid", - "bigkey_shuffle", "smallkey_shuffle", "compass_shuffle", "map_shuffle", + "big_key_shuffle", "small_key_shuffle", "compass_shuffle", "map_shuffle", "progressive", "swordless", "retro_bow", "retro_caves", "shop_item_slots", - "boss_shuffle", "pot_shuffle", "enemy_shuffle", "key_drop_shuffle"] + "boss_shuffle", "pot_shuffle", "enemy_shuffle", "key_drop_shuffle", "bombless_start", + "randomize_shop_inventories", "shuffle_shop_inventories", "shuffle_capacity_upgrades", + "entrance_shuffle", "dark_room_logic", "goal", "mode", + "triforce_pieces_mode", "triforce_pieces_percentage", "triforce_pieces_required", + "triforce_pieces_available", "triforce_pieces_extra", + ] slot_data = {option_name: getattr(self.multiworld, option_name)[self.player].value for option_name in slot_options} slot_data.update({ - 'mode': self.multiworld.mode[self.player], - 'goal': self.multiworld.goal[self.player], - 'dark_room_logic': self.multiworld.dark_room_logic[self.player], 'mm_medalion': self.multiworld.required_medallions[self.player][0], 'tr_medalion': self.multiworld.required_medallions[self.player][1], - 'shop_shuffle': self.multiworld.shop_shuffle[self.player], - 'entrance_shuffle': self.multiworld.shuffle[self.player], } ) return slot_data @@ -849,8 +814,8 @@ def get_same_seed(world, seed_def: tuple) -> str: class ALttPLogic(LogicMixin): def _lttp_has_key(self, item, player, count: int = 1): - if self.multiworld.logic[player] == 'nologic': + if self.multiworld.glitches_required[player] == 'no_logic': return True - if self.multiworld.smallkey_shuffle[player] == smallkey_shuffle.option_universal: + if self.multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: return can_buy_unlimited(self, 'Small Key (Universal)', player) return self.prog_items[player][item] >= count diff --git a/worlds/alttp/test/dungeons/TestAgahnimsTower.py b/worlds/alttp/test/dungeons/TestAgahnimsTower.py index 94e7854858..c44a92be1e 100644 --- a/worlds/alttp/test/dungeons/TestAgahnimsTower.py +++ b/worlds/alttp/test/dungeons/TestAgahnimsTower.py @@ -7,25 +7,25 @@ class TestAgahnimsTower(TestDungeon): self.starting_regions = ['Agahnims Tower'] self.run_tests([ ["Castle Tower - Room 03", False, []], - ["Castle Tower - Room 03", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], + ["Castle Tower - Room 03", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], ["Castle Tower - Room 03", True, ['Progressive Sword']], ["Castle Tower - Dark Maze", False, []], ["Castle Tower - Dark Maze", False, [], ['Small Key (Agahnims Tower)']], ["Castle Tower - Dark Maze", False, [], ['Lamp']], - ["Castle Tower - Dark Maze", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], + ["Castle Tower - Dark Maze", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], ["Castle Tower - Dark Maze", True, ['Progressive Sword', 'Small Key (Agahnims Tower)', 'Lamp']], ["Castle Tower - Dark Archer Key Drop", False, []], ["Castle Tower - Dark Archer Key Drop", False, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)']], ["Castle Tower - Dark Archer Key Drop", False, [], ['Lamp']], - ["Castle Tower - Dark Archer Key Drop", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], + ["Castle Tower - Dark Archer Key Drop", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], ["Castle Tower - Dark Archer Key Drop", True, ['Progressive Sword', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp']], ["Castle Tower - Circle of Pots Key Drop", False, []], ["Castle Tower - Circle of Pots Key Drop", False, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)']], ["Castle Tower - Circle of Pots Key Drop", False, [], ['Lamp']], - ["Castle Tower - Circle of Pots Key Drop", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], + ["Castle Tower - Circle of Pots Key Drop", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], ["Castle Tower - Circle of Pots Key Drop", True, ['Progressive Sword', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp']], ["Agahnim 1", False, []], diff --git a/worlds/alttp/test/dungeons/TestDarkPalace.py b/worlds/alttp/test/dungeons/TestDarkPalace.py index e3974e777d..3912fbd282 100644 --- a/worlds/alttp/test/dungeons/TestDarkPalace.py +++ b/worlds/alttp/test/dungeons/TestDarkPalace.py @@ -11,29 +11,37 @@ class TestDarkPalace(TestDungeon): ["Palace of Darkness - The Arena - Ledge", False, []], ["Palace of Darkness - The Arena - Ledge", False, [], ['Progressive Bow']], - ["Palace of Darkness - The Arena - Ledge", True, ['Progressive Bow']], + ["Palace of Darkness - The Arena - Ledge", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Palace of Darkness - The Arena - Ledge", True, ['Progressive Bow', 'Bomb Upgrade (+5)']], ["Palace of Darkness - Map Chest", False, []], ["Palace of Darkness - Map Chest", False, [], ['Progressive Bow']], - ["Palace of Darkness - Map Chest", True, ['Progressive Bow']], + ["Palace of Darkness - Map Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], + ["Palace of Darkness - Map Chest", True, ['Progressive Bow', 'Bomb Upgrade (+5)']], + ["Palace of Darkness - Map Chest", True, ['Progressive Bow', 'Pegasus Boots']], #Lower requirement for self-locking key #No lower requirement when bow/hammer is out of logic ["Palace of Darkness - Big Key Chest", False, []], ["Palace of Darkness - Big Key Chest", False, [key]*5, [key]], - ["Palace of Darkness - Big Key Chest", True, [key]*6], + ["Palace of Darkness - Big Key Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Palace of Darkness - Big Key Chest", True, [key]*6 + ['Bomb Upgrade (+5)']], ["Palace of Darkness - The Arena - Bridge", False, []], ["Palace of Darkness - The Arena - Bridge", False, [], [key, 'Progressive Bow']], ["Palace of Darkness - The Arena - Bridge", False, [], [key, 'Hammer']], + ["Palace of Darkness - The Arena - Bridge", False, [], [key, 'Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], ["Palace of Darkness - The Arena - Bridge", True, [key]], - ["Palace of Darkness - The Arena - Bridge", True, ['Progressive Bow', 'Hammer']], + ["Palace of Darkness - The Arena - Bridge", True, ['Progressive Bow', 'Hammer', 'Bomb Upgrade (+5)']], + ["Palace of Darkness - The Arena - Bridge", True, ['Progressive Bow', 'Hammer', 'Pegasus Boots']], ["Palace of Darkness - Stalfos Basement", False, []], ["Palace of Darkness - Stalfos Basement", False, [], [key, 'Progressive Bow']], ["Palace of Darkness - Stalfos Basement", False, [], [key, 'Hammer']], + ["Palace of Darkness - Stalfos Basement", False, [], [key, 'Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], ["Palace of Darkness - Stalfos Basement", True, [key]], - ["Palace of Darkness - Stalfos Basement", True, ['Progressive Bow', 'Hammer']], + ["Palace of Darkness - Stalfos Basement", True, ['Progressive Bow', 'Hammer', 'Bomb Upgrade (+5)']], + ["Palace of Darkness - Stalfos Basement", True, ['Progressive Bow', 'Hammer', 'Pegasus Boots']], ["Palace of Darkness - Compass Chest", False, []], ["Palace of Darkness - Compass Chest", False, [key]*3, [key]], @@ -67,8 +75,9 @@ class TestDarkPalace(TestDungeon): ["Palace of Darkness - Big Chest", False, []], ["Palace of Darkness - Big Chest", False, [], ['Lamp']], ["Palace of Darkness - Big Chest", False, [], ['Big Key (Palace of Darkness)']], + ["Palace of Darkness - Big Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], ["Palace of Darkness - Big Chest", False, [key]*5, [key]], - ["Palace of Darkness - Big Chest", True, ['Lamp', 'Big Key (Palace of Darkness)'] + [key]*6], + ["Palace of Darkness - Big Chest", True, ['Bomb Upgrade (+5)', 'Lamp', 'Big Key (Palace of Darkness)'] + [key]*6], ["Palace of Darkness - Boss", False, []], ["Palace of Darkness - Boss", False, [], ['Lamp']], diff --git a/worlds/alttp/test/dungeons/TestDungeon.py b/worlds/alttp/test/dungeons/TestDungeon.py index 8ca2791dcf..1f8288ace0 100644 --- a/worlds/alttp/test/dungeons/TestDungeon.py +++ b/worlds/alttp/test/dungeons/TestDungeon.py @@ -14,6 +14,8 @@ class TestDungeon(LTTPTestBase): self.starting_regions = [] # Where to start exploring self.remove_exits = [] # Block dungeon exits self.multiworld.difficulty_requirements[1] = difficulties['normal'] + self.multiworld.bombless_start[1].value = True + self.multiworld.shuffle_capacity_upgrades[1].value = True create_regions(self.multiworld, 1) self.multiworld.worlds[1].create_dungeons() create_shops(self.multiworld, 1) diff --git a/worlds/alttp/test/dungeons/TestEasternPalace.py b/worlds/alttp/test/dungeons/TestEasternPalace.py index 35c1b99283..c1a978343b 100644 --- a/worlds/alttp/test/dungeons/TestEasternPalace.py +++ b/worlds/alttp/test/dungeons/TestEasternPalace.py @@ -18,8 +18,8 @@ class TestEasternPalace(TestDungeon): ["Eastern Palace - Big Key Chest", False, []], ["Eastern Palace - Big Key Chest", False, [], ['Lamp']], - ["Eastern Palace - Big Key Chest", True, ['Lamp', 'Small Key (Eastern Palace)', 'Small Key (Eastern Palace)']], - ["Eastern Palace - Big Key Chest", True, ['Lamp', 'Big Key (Eastern Palace)']], + ["Eastern Palace - Big Key Chest", True, ['Lamp', 'Small Key (Eastern Palace)', 'Small Key (Eastern Palace)', 'Progressive Sword']], + ["Eastern Palace - Big Key Chest", True, ['Lamp', 'Big Key (Eastern Palace)', 'Progressive Sword']], #@todo: Advanced? ["Eastern Palace - Boss", False, []], diff --git a/worlds/alttp/test/dungeons/TestGanonsTower.py b/worlds/alttp/test/dungeons/TestGanonsTower.py index d22dc92b36..98bc6fa552 100644 --- a/worlds/alttp/test/dungeons/TestGanonsTower.py +++ b/worlds/alttp/test/dungeons/TestGanonsTower.py @@ -103,16 +103,16 @@ class TestGanonsTower(TestDungeon): ["Ganons Tower - Compass Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], ["Ganons Tower - Big Key Chest", False, []], - ["Ganons Tower - Big Key Chest", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], - ["Ganons Tower - Big Key Chest", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], ["Ganons Tower - Big Key Room - Left", False, []], - ["Ganons Tower - Big Key Room - Left", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], - ["Ganons Tower - Big Key Room - Left", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Big Key Room - Left", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Big Key Room - Left", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], ["Ganons Tower - Big Key Room - Right", False, []], - ["Ganons Tower - Big Key Room - Right", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], - ["Ganons Tower - Big Key Room - Right", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Big Key Room - Right", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Big Key Room - Right", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], ["Ganons Tower - Mini Helmasaur Room - Left", False, []], ["Ganons Tower - Mini Helmasaur Room - Left", False, [], ['Progressive Bow']], diff --git a/worlds/alttp/test/dungeons/TestIcePalace.py b/worlds/alttp/test/dungeons/TestIcePalace.py index edc9f1fbae..7a15c5c097 100644 --- a/worlds/alttp/test/dungeons/TestIcePalace.py +++ b/worlds/alttp/test/dungeons/TestIcePalace.py @@ -11,8 +11,9 @@ class TestIcePalace(TestDungeon): ["Ice Palace - Big Key Chest", False, [], ['Progressive Glove']], ["Ice Palace - Big Key Chest", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Big Key Chest", False, [], ['Fire Rod', 'Progressive Sword']], - ["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], - ["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Big Key Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Ice Palace - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], #@todo: Change from item randomizer - Right side key door is only in logic if big key is in there #["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], #["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], @@ -30,8 +31,9 @@ class TestIcePalace(TestDungeon): ["Ice Palace - Map Chest", False, [], ['Progressive Glove']], ["Ice Palace - Map Chest", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Map Chest", False, [], ['Fire Rod', 'Progressive Sword']], - ["Ice Palace - Map Chest", True, ['Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], - ["Ice Palace - Map Chest", True, ['Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Map Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Ice Palace - Map Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Map Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], #["Ice Palace - Map Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], #["Ice Palace - Map Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], #["Ice Palace - Map Chest", True, ['Progressive Glove', 'Cape', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], @@ -40,8 +42,9 @@ class TestIcePalace(TestDungeon): ["Ice Palace - Spike Room", False, []], ["Ice Palace - Spike Room", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Spike Room", False, [], ['Fire Rod', 'Progressive Sword']], - ["Ice Palace - Spike Room", True, ['Fire Rod', 'Hookshot', 'Small Key (Ice Palace)']], - ["Ice Palace - Spike Room", True, ['Bombos', 'Progressive Sword', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Spike Room", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Ice Palace - Spike Room", True, ['Bomb Upgrade (+5)', 'Fire Rod', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Spike Room", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword', 'Hookshot', 'Small Key (Ice Palace)']], #["Ice Palace - Spike Room", True, ['Cape', 'Fire Rod', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], #["Ice Palace - Spike Room", True, ['Cape', 'Bombos', 'Progressive Sword', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], #["Ice Palace - Spike Room", True, ['Cane of Byrna', 'Fire Rod', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], @@ -50,21 +53,24 @@ class TestIcePalace(TestDungeon): ["Ice Palace - Freezor Chest", False, []], ["Ice Palace - Freezor Chest", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Freezor Chest", False, [], ['Fire Rod', 'Progressive Sword']], - ["Ice Palace - Freezor Chest", True, ['Fire Rod']], - ["Ice Palace - Freezor Chest", True, ['Bombos', 'Progressive Sword']], + ["Ice Palace - Freezor Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Ice Palace - Freezor Chest", True, ['Bomb Upgrade (+5)', 'Fire Rod']], + ["Ice Palace - Freezor Chest", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword']], ["Ice Palace - Iced T Room", False, []], ["Ice Palace - Iced T Room", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Iced T Room", False, [], ['Fire Rod', 'Progressive Sword']], - ["Ice Palace - Iced T Room", True, ['Fire Rod']], - ["Ice Palace - Iced T Room", True, ['Bombos', 'Progressive Sword']], + ["Ice Palace - Iced T Room", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Ice Palace - Iced T Room", True, ['Bomb Upgrade (+5)', 'Fire Rod']], + ["Ice Palace - Iced T Room", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword']], ["Ice Palace - Big Chest", False, []], ["Ice Palace - Big Chest", False, [], ['Big Key (Ice Palace)']], ["Ice Palace - Big Chest", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Big Chest", False, [], ['Fire Rod', 'Progressive Sword']], - ["Ice Palace - Big Chest", True, ['Big Key (Ice Palace)', 'Fire Rod']], - ["Ice Palace - Big Chest", True, ['Big Key (Ice Palace)', 'Bombos', 'Progressive Sword']], + ["Ice Palace - Big Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Ice Palace - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Ice Palace)', 'Fire Rod']], + ["Ice Palace - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword']], ["Ice Palace - Boss", False, []], ["Ice Palace - Boss", False, [], ['Hammer']], @@ -72,9 +78,10 @@ class TestIcePalace(TestDungeon): ["Ice Palace - Boss", False, [], ['Big Key (Ice Palace)']], ["Ice Palace - Boss", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Boss", False, [], ['Fire Rod', 'Progressive Sword']], + ["Ice Palace - Boss", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], # need hookshot now to reach the right side for the 6th key - ["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], - ["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Hookshot']], - ["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], - ["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Hookshot']], + ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], + ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Hookshot']], + ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], + ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Hookshot']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/dungeons/TestMiseryMire.py b/worlds/alttp/test/dungeons/TestMiseryMire.py index ea5fb28845..6cbf42922f 100644 --- a/worlds/alttp/test/dungeons/TestMiseryMire.py +++ b/worlds/alttp/test/dungeons/TestMiseryMire.py @@ -78,7 +78,8 @@ class TestMiseryMire(TestDungeon): ["Misery Mire - Boss", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow']], ["Misery Mire - Boss", False, [], ['Big Key (Misery Mire)']], ["Misery Mire - Boss", False, [], ['Pegasus Boots', 'Hookshot']], - ["Misery Mire - Boss", True, ['Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Sword', 'Pegasus Boots']], - ["Misery Mire - Boss", True, ['Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Hammer', 'Pegasus Boots']], - ["Misery Mire - Boss", True, ['Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Bow', 'Pegasus Boots']], + ["Misery Mire - Boss", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Hammer', 'Pegasus Boots']], + ["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Bow', 'Pegasus Boots']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/dungeons/TestSkullWoods.py b/worlds/alttp/test/dungeons/TestSkullWoods.py index 7f97c4d2f8..55c8d2e29a 100644 --- a/worlds/alttp/test/dungeons/TestSkullWoods.py +++ b/worlds/alttp/test/dungeons/TestSkullWoods.py @@ -8,7 +8,8 @@ class TestSkullWoods(TestDungeon): self.run_tests([ ["Skull Woods - Big Chest", False, []], ["Skull Woods - Big Chest", False, [], ['Big Key (Skull Woods)']], - ["Skull Woods - Big Chest", True, ['Big Key (Skull Woods)']], + ["Skull Woods - Big Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Skull Woods - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Skull Woods)']], ["Skull Woods - Compass Chest", True, []], @@ -64,7 +65,8 @@ class TestSkullWoods(TestDungeon): self.run_tests([ ["Skull Woods - Big Chest", False, []], ["Skull Woods - Big Chest", False, [], ['Big Key (Skull Woods)']], - ["Skull Woods - Big Chest", True, ['Big Key (Skull Woods)']], + ["Skull Woods - Big Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Skull Woods - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Skull Woods)']], ["Skull Woods - Compass Chest", False, []], ["Skull Woods - Compass Chest", False, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)'], ['Small Key (Skull Woods)']], diff --git a/worlds/alttp/test/dungeons/TestSwampPalace.py b/worlds/alttp/test/dungeons/TestSwampPalace.py index 51440f6ccc..bddf40616f 100644 --- a/worlds/alttp/test/dungeons/TestSwampPalace.py +++ b/worlds/alttp/test/dungeons/TestSwampPalace.py @@ -30,7 +30,8 @@ class TestSwampPalace(TestDungeon): ["Swamp Palace - Map Chest", False, [], ['Flippers']], ["Swamp Palace - Map Chest", False, [], ['Open Floodgate']], ["Swamp Palace - Map Chest", False, [], ['Small Key (Swamp Palace)']], - ["Swamp Palace - Map Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers']], + ["Swamp Palace - Map Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Swamp Palace - Map Chest", True, ['Bomb Upgrade (+5)', 'Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers']], ["Swamp Palace - West Chest", False, []], ["Swamp Palace - West Chest", False, [], ['Flippers']], diff --git a/worlds/alttp/test/dungeons/TestThievesTown.py b/worlds/alttp/test/dungeons/TestThievesTown.py index 01f1570a25..752b530577 100644 --- a/worlds/alttp/test/dungeons/TestThievesTown.py +++ b/worlds/alttp/test/dungeons/TestThievesTown.py @@ -41,8 +41,9 @@ class TestThievesTown(TestDungeon): ["Thieves' Town - Boss", False, []], ["Thieves' Town - Boss", False, [], ['Big Key (Thieves Town)']], ["Thieves' Town - Boss", False, [], ['Hammer', 'Progressive Sword', 'Cane of Somaria', 'Cane of Byrna']], - ["Thieves' Town - Boss", True, ['Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Hammer']], - ["Thieves' Town - Boss", True, ['Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Progressive Sword']], - ["Thieves' Town - Boss", True, ['Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Somaria']], - ["Thieves' Town - Boss", True, ['Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Byrna']], + ["Thieves' Town - Boss", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Hammer']], + ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Progressive Sword']], + ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Somaria']], + ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Byrna']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/dungeons/TestTowerOfHera.py b/worlds/alttp/test/dungeons/TestTowerOfHera.py index 04685a66a8..3299e20291 100644 --- a/worlds/alttp/test/dungeons/TestTowerOfHera.py +++ b/worlds/alttp/test/dungeons/TestTowerOfHera.py @@ -18,11 +18,11 @@ class TestTowerOfHera(TestDungeon): ["Tower of Hera - Compass Chest", False, []], ["Tower of Hera - Compass Chest", False, [], ['Big Key (Tower of Hera)']], - ["Tower of Hera - Compass Chest", True, ['Big Key (Tower of Hera)']], + ["Tower of Hera - Compass Chest", True, ['Big Key (Tower of Hera)', 'Progressive Sword']], ["Tower of Hera - Big Chest", False, []], ["Tower of Hera - Big Chest", False, [], ['Big Key (Tower of Hera)']], - ["Tower of Hera - Big Chest", True, ['Big Key (Tower of Hera)']], + ["Tower of Hera - Big Chest", True, ['Big Key (Tower of Hera)', 'Progressive Sword']], ["Tower of Hera - Boss", False, []], ["Tower of Hera - Boss", False, [], ['Big Key (Tower of Hera)']], diff --git a/worlds/alttp/test/inverted/TestInverted.py b/worlds/alttp/test/inverted/TestInverted.py index f5608ba07b..f2c585e465 100644 --- a/worlds/alttp/test/inverted/TestInverted.py +++ b/worlds/alttp/test/inverted/TestInverted.py @@ -14,7 +14,9 @@ class TestInverted(TestBase, LTTPTestBase): def setUp(self): self.world_setup() self.multiworld.difficulty_requirements[1] = difficulties['normal'] - self.multiworld.mode[1] = "inverted" + self.multiworld.mode[1].value = 2 + self.multiworld.bombless_start[1].value = True + self.multiworld.shuffle_capacity_upgrades[1].value = True create_inverted_regions(self.multiworld, 1) self.multiworld.worlds[1].create_dungeons() create_shops(self.multiworld, 1) diff --git a/worlds/alttp/test/inverted/TestInvertedBombRules.py b/worlds/alttp/test/inverted/TestInvertedBombRules.py index d9eacb5ad9..83a25812c9 100644 --- a/worlds/alttp/test/inverted/TestInvertedBombRules.py +++ b/worlds/alttp/test/inverted/TestInvertedBombRules.py @@ -11,8 +11,8 @@ class TestInvertedBombRules(LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.mode[1] = "inverted" self.multiworld.difficulty_requirements[1] = difficulties['normal'] + self.multiworld.mode[1].value = 2 create_inverted_regions(self.multiworld, 1) self.multiworld.worlds[1].create_dungeons() diff --git a/worlds/alttp/test/inverted/TestInvertedDarkWorld.py b/worlds/alttp/test/inverted/TestInvertedDarkWorld.py index 710ee07f2b..16b837ee65 100644 --- a/worlds/alttp/test/inverted/TestInvertedDarkWorld.py +++ b/worlds/alttp/test/inverted/TestInvertedDarkWorld.py @@ -5,7 +5,8 @@ class TestInvertedDarkWorld(TestInverted): def testNorthWest(self): self.run_location_tests([ - ["Brewery", True, []], + ["Brewery", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Brewery", True, ['Bomb Upgrade (+5)']], ["C-Shaped House", True, []], @@ -77,15 +78,16 @@ class TestInvertedDarkWorld(TestInverted): def testSouth(self): self.run_location_tests([ - ["Hype Cave - Top", True, []], - - ["Hype Cave - Middle Right", True, []], - - ["Hype Cave - Middle Left", True, []], - - ["Hype Cave - Bottom", True, []], - - ["Hype Cave - Generous Guy", True, []], + ["Hype Cave - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)']], + ["Hype Cave - Middle Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)']], + ["Hype Cave - Middle Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)']], + ["Hype Cave - Bottom", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)']], + ["Hype Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)']], ["Stumpy", True, []], diff --git a/worlds/alttp/test/inverted/TestInvertedDeathMountain.py b/worlds/alttp/test/inverted/TestInvertedDeathMountain.py index aedec2a1da..605a9dc3f3 100644 --- a/worlds/alttp/test/inverted/TestInvertedDeathMountain.py +++ b/worlds/alttp/test/inverted/TestInvertedDeathMountain.py @@ -40,10 +40,12 @@ class TestInvertedDeathMountain(TestInverted): ["Paradox Cave Lower - Far Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Far Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], ["Paradox Cave Lower - Far Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], - ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], - ["Paradox Cave Lower - Far Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Cane of Somaria', 'Fire Rod']], + ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot', 'Moon Pearl', 'Bomb Upgrade (+5)']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl', 'Cane of Somaria']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Progressive Sword', 'Progressive Sword']], + ["Paradox Cave Lower - Far Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Fire Rod']], + ["Paradox Cave Lower - Far Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Progressive Bow']], ["Paradox Cave Lower - Left", False, []], ["Paradox Cave Lower - Left", False, [], ['Moon Pearl']], @@ -52,10 +54,12 @@ class TestInvertedDeathMountain(TestInverted): ["Paradox Cave Lower - Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], ["Paradox Cave Lower - Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], - ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], - ["Paradox Cave Lower - Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Paradox Cave Lower - Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Cane of Somaria', 'Fire Rod']], + ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot', 'Moon Pearl', 'Bomb Upgrade (+5)']], + ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl', 'Cane of Somaria']], + ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Progressive Sword', 'Progressive Sword']], + ["Paradox Cave Lower - Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Fire Rod']], + ["Paradox Cave Lower - Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Progressive Bow']], ["Paradox Cave Lower - Middle", False, []], ["Paradox Cave Lower - Middle", False, [], ['Moon Pearl']], @@ -64,10 +68,12 @@ class TestInvertedDeathMountain(TestInverted): ["Paradox Cave Lower - Middle", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Middle", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], ["Paradox Cave Lower - Middle", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], - ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], - ["Paradox Cave Lower - Middle", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Cane of Somaria', 'Fire Rod']], + ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot', 'Moon Pearl', 'Bomb Upgrade (+5)']], + ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl', 'Cane of Somaria']], + ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Progressive Sword', 'Progressive Sword']], + ["Paradox Cave Lower - Middle", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Fire Rod']], + ["Paradox Cave Lower - Middle", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Progressive Bow']], ["Paradox Cave Lower - Right", False, []], ["Paradox Cave Lower - Right", False, [], ['Moon Pearl']], @@ -76,10 +82,12 @@ class TestInvertedDeathMountain(TestInverted): ["Paradox Cave Lower - Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], ["Paradox Cave Lower - Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], - ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], - ["Paradox Cave Lower - Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Paradox Cave Lower - Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Cane of Somaria', 'Fire Rod']], + ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot', 'Moon Pearl', 'Bomb Upgrade (+5)']], + ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl', 'Cane of Somaria']], + ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Progressive Sword', 'Progressive Sword']], + ["Paradox Cave Lower - Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Fire Rod']], + ["Paradox Cave Lower - Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Progressive Bow']], ["Paradox Cave Lower - Far Right", False, []], ["Paradox Cave Lower - Far Right", False, [], ['Moon Pearl']], @@ -88,10 +96,12 @@ class TestInvertedDeathMountain(TestInverted): ["Paradox Cave Lower - Far Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Far Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], ["Paradox Cave Lower - Far Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], - ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], - ["Paradox Cave Lower - Far Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Cane of Somaria', 'Fire Rod']], + ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot', 'Moon Pearl', 'Bomb Upgrade (+5)']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl', 'Cane of Somaria']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Progressive Sword', 'Progressive Sword']], + ["Paradox Cave Lower - Far Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Fire Rod']], + ["Paradox Cave Lower - Far Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Progressive Bow']], ["Paradox Cave Upper - Left", False, []], ["Paradox Cave Upper - Left", False, [], ['Moon Pearl']], @@ -100,10 +110,11 @@ class TestInvertedDeathMountain(TestInverted): ["Paradox Cave Upper - Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], ["Paradox Cave Upper - Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], ["Paradox Cave Upper - Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], - ["Paradox Cave Upper - Left", True, ['Flute', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], - ["Paradox Cave Upper - Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Paradox Cave Upper - Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], ["Paradox Cave Upper - Right", False, []], ["Paradox Cave Upper - Right", False, [], ['Moon Pearl']], @@ -112,20 +123,22 @@ class TestInvertedDeathMountain(TestInverted): ["Paradox Cave Upper - Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], ["Paradox Cave Upper - Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], ["Paradox Cave Upper - Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], - ["Paradox Cave Upper - Right", True, ['Flute', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], - ["Paradox Cave Upper - Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Paradox Cave Upper - Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], ["Mimic Cave", False, []], ["Mimic Cave", False, [], ['Moon Pearl']], ["Mimic Cave", False, [], ['Hammer']], ["Mimic Cave", False, [], ['Progressive Glove', 'Flute']], ["Mimic Cave", False, [], ['Lamp', 'Flute']], - ["Mimic Cave", True, ['Flute', 'Moon Pearl', 'Hammer', 'Hookshot']], - ["Mimic Cave", True, ['Flute', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hammer']], - ["Mimic Cave", True, ['Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Hookshot']], - ["Mimic Cave", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer']], + ["Mimic Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Bow', 'Cane of Somaria', 'Progressive Sword']], + ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Flute', 'Moon Pearl', 'Hammer', 'Hookshot']], + ["Mimic Cave", True, ['Progressive Bow', 'Flute', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hammer']], + ["Mimic Cave", True, ['Cane of Somaria', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Hookshot']], + ["Mimic Cave", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer']], ["Ether Tablet", False, []], ["Ether Tablet", False, [], ['Moon Pearl']], diff --git a/worlds/alttp/test/inverted/TestInvertedLightWorld.py b/worlds/alttp/test/inverted/TestInvertedLightWorld.py index 9d4b9099da..77af093172 100644 --- a/worlds/alttp/test/inverted/TestInvertedLightWorld.py +++ b/worlds/alttp/test/inverted/TestInvertedLightWorld.py @@ -44,15 +44,17 @@ class TestInvertedLightWorld(TestInverted): ["Chicken House", False, []], ["Chicken House", False, [], ['Moon Pearl']], - ["Chicken House", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Chicken House", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Chicken House", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Chicken House", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Chicken House", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Chicken House", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Chicken House", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Kakariko Well - Top", False, []], ["Kakariko Well - Top", False, [], ['Moon Pearl']], - ["Kakariko Well - Top", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Kakariko Well - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Kakariko Well - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Kakariko Well - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Kakariko Well - Left", False, []], ["Kakariko Well - Left", False, [], ['Moon Pearl']], @@ -80,9 +82,10 @@ class TestInvertedLightWorld(TestInverted): ["Blind's Hideout - Top", False, []], ["Blind's Hideout - Top", False, [], ['Moon Pearl']], - ["Blind's Hideout - Top", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Blind's Hideout - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Blind's Hideout - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Blind's Hideout - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Blind's Hideout - Left", False, []], ["Blind's Hideout - Left", False, [], ['Moon Pearl']], @@ -161,9 +164,10 @@ class TestInvertedLightWorld(TestInverted): ["Maze Race", False, []], ["Maze Race", False, [], ['Moon Pearl']], - ["Maze Race", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Maze Race", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Maze Race", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Maze Race", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], + ["Maze Race", True, ['Pegasus Boots', 'Moon Pearl', 'Beat Agahnim 1']], + ["Maze Race", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Maze Race", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ]) def testSouthLightWorld(self): @@ -184,9 +188,10 @@ class TestInvertedLightWorld(TestInverted): ["Aginah's Cave", False, []], ["Aginah's Cave", False, [], ['Moon Pearl']], - ["Aginah's Cave", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Aginah's Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Aginah's Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Aginah's Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Aginah's Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Aginah's Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Aginah's Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Bombos Tablet", False, []], ["Bombos Tablet", False, ['Progressive Sword'], ['Progressive Sword']], @@ -212,39 +217,45 @@ class TestInvertedLightWorld(TestInverted): ["Mini Moldorm Cave - Far Left", False, []], ["Mini Moldorm Cave - Far Left", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Mini Moldorm Cave - Far Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Sword']], + ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Progressive Sword']], ["Mini Moldorm Cave - Left", False, []], ["Mini Moldorm Cave - Left", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Mini Moldorm Cave - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Sword']], + ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Progressive Sword']], ["Mini Moldorm Cave - Generous Guy", False, []], ["Mini Moldorm Cave - Generous Guy", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Mini Moldorm Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Sword']], + ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Progressive Sword']], ["Mini Moldorm Cave - Right", False, []], ["Mini Moldorm Cave - Right", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Mini Moldorm Cave - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Sword']], + ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Progressive Sword']], ["Mini Moldorm Cave - Far Right", False, []], ["Mini Moldorm Cave - Far Right", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Mini Moldorm Cave - Far Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Sword']], + ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Progressive Sword']], ["Ice Rod Cave", False, []], ["Ice Rod Cave", False, [], ['Moon Pearl']], - ["Ice Rod Cave", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Ice Rod Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Ice Rod Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Ice Rod Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Ice Rod Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Ice Rod Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Ice Rod Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ]) def testZoraArea(self): @@ -302,21 +313,24 @@ class TestInvertedLightWorld(TestInverted): ["Sahasrahla's Hut - Left", False, []], ["Sahasrahla's Hut - Left", False, [], ['Moon Pearl']], - ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sahasrahla's Hut - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], + ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Sahasrahla's Hut - Middle", False, []], ["Sahasrahla's Hut - Middle", False, [], ['Moon Pearl']], - ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sahasrahla's Hut - Middle", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], + ["Sahasrahla's Hut - Middle", True, ['Pegasus Boots', 'Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Middle", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sahasrahla's Hut - Middle", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Sahasrahla's Hut - Right", False, []], ["Sahasrahla's Hut - Right", False, [], ['Moon Pearl']], - ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sahasrahla's Hut - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], + ["Sahasrahla's Hut - Right", True, ['Pegasus Boots', 'Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sahasrahla's Hut - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Sahasrahla", False, []], ["Sahasrahla", False, [], ['Green Pendant']], @@ -346,9 +360,10 @@ class TestInvertedLightWorld(TestInverted): ["Graveyard Cave", False, []], ["Graveyard Cave", False, [], ['Moon Pearl']], - ["Graveyard Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Graveyard Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Graveyard Cave", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Graveyard Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], ["Potion Shop", False, []], ["Potion Shop", False, [], ['Mushroom']], diff --git a/worlds/alttp/test/inverted/TestInvertedTurtleRock.py b/worlds/alttp/test/inverted/TestInvertedTurtleRock.py index fe8979c1ef..f3698c90ff 100644 --- a/worlds/alttp/test/inverted/TestInvertedTurtleRock.py +++ b/worlds/alttp/test/inverted/TestInvertedTurtleRock.py @@ -21,10 +21,10 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Chain Chomps", False, ['Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Turtle Rock - Chain Chomps", True, ['Bomb Upgrade (+5)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']], - ["Turtle Rock - Chain Chomps", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']], + ["Turtle Rock - Chain Chomps", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Fire Rod']], ["Turtle Rock - Roller Room - Left", False, []], ["Turtle Rock - Roller Room - Left", False, [], ['Cane of Somaria']], @@ -54,8 +54,8 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Big Chest", False, [], ['Big Key (Turtle Rock)']], ["Turtle Rock - Big Chest", False, [], ['Magic Mirror', 'Cane of Somaria']], ["Turtle Rock - Big Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']], ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hookshot']], ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], @@ -68,10 +68,10 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Big Key Chest", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], # Mirror in from ledge, use left side entrance, have enough keys to get to the chest - ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Crystaroller Room", False, []], ["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], @@ -102,8 +102,11 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', 'Magic Mirror', 'Moon Pearl', 'Hookshot', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']] + ]) + + def testEyeBridge(self): for location in ["Turtle Rock - Eye Bridge - Top Right", "Turtle Rock - Eye Bridge - Top Left", "Turtle Rock - Eye Bridge - Bottom Right", "Turtle Rock - Eye Bridge - Bottom Left"]: diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedDarkWorld.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedDarkWorld.py index 69f5644897..dd4a74b6c4 100644 --- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedDarkWorld.py +++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedDarkWorld.py @@ -5,7 +5,8 @@ class TestInvertedDarkWorld(TestInvertedMinor): def testNorthWest(self): self.run_location_tests([ - ["Brewery", True, []], + ["Brewery", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Brewery", True, ['Bomb Upgrade (+5)']], ["C-Shaped House", True, []], @@ -67,15 +68,16 @@ class TestInvertedDarkWorld(TestInvertedMinor): def testSouth(self): self.run_location_tests([ - ["Hype Cave - Top", True, []], - - ["Hype Cave - Middle Right", True, []], - - ["Hype Cave - Middle Left", True, []], - - ["Hype Cave - Bottom", True, []], - - ["Hype Cave - Generous Guy", True, []], + ["Hype Cave - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)']], + ["Hype Cave - Middle Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)']], + ["Hype Cave - Middle Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)']], + ["Hype Cave - Bottom", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)']], + ["Hype Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)']], ["Stumpy", True, []], diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedDeathMountain.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedDeathMountain.py index c68a8e5f0c..c189d107d9 100644 --- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedDeathMountain.py +++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedDeathMountain.py @@ -40,10 +40,11 @@ class TestInvertedDeathMountain(TestInvertedMinor): ["Paradox Cave Lower - Far Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Far Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], ["Paradox Cave Lower - Far Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], - ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], - ["Paradox Cave Lower - Far Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod']], + ["Paradox Cave Lower - Far Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Bow', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Far Left", True, ['Cane of Somaria', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], ["Paradox Cave Lower - Left", False, []], ["Paradox Cave Lower - Left", False, [], ['Moon Pearl']], @@ -52,10 +53,11 @@ class TestInvertedDeathMountain(TestInvertedMinor): ["Paradox Cave Lower - Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], ["Paradox Cave Lower - Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], - ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], - ["Paradox Cave Lower - Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Paradox Cave Lower - Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod']], + ["Paradox Cave Lower - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Left", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Left", True, ['Progressive Bow', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Left", True, ['Cane of Somaria', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], ["Paradox Cave Lower - Middle", False, []], ["Paradox Cave Lower - Middle", False, [], ['Moon Pearl']], @@ -64,10 +66,11 @@ class TestInvertedDeathMountain(TestInvertedMinor): ["Paradox Cave Lower - Middle", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Middle", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], ["Paradox Cave Lower - Middle", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], - ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], - ["Paradox Cave Lower - Middle", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod']], + ["Paradox Cave Lower - Middle", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", True, ['Progressive Bow', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Middle", True, ['Cane of Somaria', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], ["Paradox Cave Lower - Right", False, []], ["Paradox Cave Lower - Right", False, [], ['Moon Pearl']], @@ -76,10 +79,11 @@ class TestInvertedDeathMountain(TestInvertedMinor): ["Paradox Cave Lower - Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], ["Paradox Cave Lower - Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], - ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], - ["Paradox Cave Lower - Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Paradox Cave Lower - Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod']], + ["Paradox Cave Lower - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Right", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Right", True, ['Progressive Bow', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Right", True, ['Cane of Somaria', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], ["Paradox Cave Lower - Far Right", False, []], ["Paradox Cave Lower - Far Right", False, [], ['Moon Pearl']], @@ -88,10 +92,11 @@ class TestInvertedDeathMountain(TestInvertedMinor): ["Paradox Cave Lower - Far Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Far Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], ["Paradox Cave Lower - Far Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], - ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], - ["Paradox Cave Lower - Far Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod']], + ["Paradox Cave Lower - Far Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Bow', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Lower - Far Right", True, ['Cane of Somaria', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], ["Paradox Cave Upper - Left", False, []], ["Paradox Cave Upper - Left", False, [], ['Moon Pearl']], @@ -100,10 +105,11 @@ class TestInvertedDeathMountain(TestInvertedMinor): ["Paradox Cave Upper - Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], ["Paradox Cave Upper - Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], ["Paradox Cave Upper - Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], - ["Paradox Cave Upper - Left", True, ['Flute', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], - ["Paradox Cave Upper - Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Paradox Cave Upper - Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)','Bomb Upgrade (50)']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], ["Paradox Cave Upper - Right", False, []], ["Paradox Cave Upper - Right", False, [], ['Moon Pearl']], @@ -112,20 +118,21 @@ class TestInvertedDeathMountain(TestInvertedMinor): ["Paradox Cave Upper - Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']], ["Paradox Cave Upper - Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']], ["Paradox Cave Upper - Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']], - ["Paradox Cave Upper - Right", True, ['Flute', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], - ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], - ["Paradox Cave Upper - Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Paradox Cave Upper - Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)','Bomb Upgrade (50)']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], ["Mimic Cave", False, []], ["Mimic Cave", False, [], ['Moon Pearl']], ["Mimic Cave", False, [], ['Hammer']], ["Mimic Cave", False, [], ['Progressive Glove', 'Flute']], ["Mimic Cave", False, [], ['Lamp', 'Flute']], - ["Mimic Cave", True, ['Flute', 'Moon Pearl', 'Hammer', 'Hookshot']], - ["Mimic Cave", True, ['Flute', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hammer']], - ["Mimic Cave", True, ['Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Hookshot']], - ["Mimic Cave", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer']], + ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Flute', 'Moon Pearl', 'Hammer', 'Hookshot']], + ["Mimic Cave", True, ['Progressive Sword', 'Progressive Sword', 'Flute', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hammer']], + ["Mimic Cave", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Hookshot']], + ["Mimic Cave", True, ['Cane of Somaria', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer']], ["Ether Tablet", False, []], ["Ether Tablet", False, [], ['Moon Pearl']], diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedLightWorld.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedLightWorld.py index 376e7b4bec..086c1c92b5 100644 --- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedLightWorld.py +++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedLightWorld.py @@ -43,16 +43,18 @@ class TestInvertedLightWorld(TestInvertedMinor): ["Chicken House", False, []], ["Chicken House", False, [], ['Moon Pearl']], - ["Chicken House", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Chicken House", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Chicken House", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Chicken House", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)','Bomb Upgrade (50)']], + ["Chicken House", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Chicken House", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Chicken House", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], # top can't be bombed as super bunny and needs Moon Pearl ["Kakariko Well - Top", False, []], ["Kakariko Well - Top", False, [], ['Moon Pearl']], - ["Kakariko Well - Top", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Kakariko Well - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Kakariko Well - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Kakariko Well - Top", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)','Bomb Upgrade (50)']], + ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Kakariko Well - Left", False, []], ["Kakariko Well - Left", True, ['Beat Agahnim 1']], @@ -76,9 +78,10 @@ class TestInvertedLightWorld(TestInvertedMinor): ["Blind's Hideout - Top", False, []], ["Blind's Hideout - Top", False, [], ['Moon Pearl', 'Magic Mirror']], - ["Blind's Hideout - Top", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Blind's Hideout - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Blind's Hideout - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Blind's Hideout - Top", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)','Bomb Upgrade (50)']], + ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Blind's Hideout - Left", False, []], ["Blind's Hideout - Left", False, [], ['Moon Pearl', 'Magic Mirror']], @@ -157,9 +160,10 @@ class TestInvertedLightWorld(TestInvertedMinor): ["Maze Race", False, []], ["Maze Race", False, [], ['Moon Pearl']], - ["Maze Race", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Maze Race", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Maze Race", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Maze Race", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)','Bomb Upgrade (50)', 'Pegasus Boots']], + ["Maze Race", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Maze Race", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Maze Race", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ]) def testSouthLightWorld(self): @@ -179,9 +183,10 @@ class TestInvertedLightWorld(TestInvertedMinor): ["Aginah's Cave", False, []], ["Aginah's Cave", False, [], ['Moon Pearl']], - ["Aginah's Cave", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Aginah's Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Aginah's Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Aginah's Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Aginah's Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Aginah's Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Aginah's Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Bombos Tablet", False, []], ["Bombos Tablet", False, ['Progressive Sword'], ['Progressive Sword']], @@ -209,39 +214,45 @@ class TestInvertedLightWorld(TestInvertedMinor): ["Mini Moldorm Cave - Far Left", False, []], ["Mini Moldorm Cave - Far Left", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Mini Moldorm Cave - Far Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Mini Moldorm Cave - Left", False, []], ["Mini Moldorm Cave - Left", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Mini Moldorm Cave - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Mini Moldorm Cave - Generous Guy", False, []], ["Mini Moldorm Cave - Generous Guy", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Mini Moldorm Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Mini Moldorm Cave - Right", False, []], ["Mini Moldorm Cave - Right", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Mini Moldorm Cave - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Mini Moldorm Cave - Far Right", False, []], ["Mini Moldorm Cave - Far Right", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Mini Moldorm Cave - Far Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Beat Agahnim 1']], + ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ["Ice Rod Cave", False, []], ["Ice Rod Cave", False, [], ['Moon Pearl']], - ["Ice Rod Cave", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Ice Rod Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Ice Rod Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Ice Rod Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Ice Rod Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Ice Rod Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Ice Rod Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], ]) def testZoraArea(self): @@ -297,25 +308,28 @@ class TestInvertedLightWorld(TestInvertedMinor): ["Sahasrahla's Hut - Left", False, []], ["Sahasrahla's Hut - Left", False, [], ['Moon Pearl', 'Magic Mirror']], - ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sahasrahla's Hut - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], + ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Left", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sahasrahla's Hut - Left", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], # super bunny bonk ["Sahasrahla's Hut - Left", True, ['Magic Mirror', 'Beat Agahnim 1', 'Pegasus Boots']], ["Sahasrahla's Hut - Middle", False, []], ["Sahasrahla's Hut - Middle", False, [], ['Moon Pearl', 'Magic Mirror']], - ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sahasrahla's Hut - Middle", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], + ["Sahasrahla's Hut - Middle", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Middle", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sahasrahla's Hut - Middle", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], # super bunny bonk ["Sahasrahla's Hut - Middle", True, ['Magic Mirror', 'Beat Agahnim 1', 'Pegasus Boots']], ["Sahasrahla's Hut - Right", False, []], ["Sahasrahla's Hut - Right", False, [], ['Moon Pearl', 'Magic Mirror']], - ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Beat Agahnim 1']], - ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Sahasrahla's Hut - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], + ["Sahasrahla's Hut - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Right", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Sahasrahla's Hut - Right", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], # super bunny bonk ["Sahasrahla's Hut - Right", True, ['Magic Mirror', 'Beat Agahnim 1', 'Pegasus Boots']], @@ -347,9 +361,10 @@ class TestInvertedLightWorld(TestInvertedMinor): ["Graveyard Cave", False, []], ["Graveyard Cave", False, [], ['Moon Pearl']], - ["Graveyard Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Graveyard Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Graveyard Cave", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Graveyard Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], ["Potion Shop", False, []], ["Potion Shop", False, [], ['Mushroom']], diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py index 33e5822981..0219332e07 100644 --- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py +++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py @@ -13,8 +13,10 @@ from worlds.alttp.test import LTTPTestBase class TestInvertedMinor(TestBase, LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.mode[1] = "inverted" - self.multiworld.logic[1] = "minorglitches" + self.multiworld.mode[1].value = 2 + self.multiworld.glitches_required[1] = "minor_glitches" + self.multiworld.bombless_start[1].value = True + self.multiworld.shuffle_capacity_upgrades[1].value = True self.multiworld.difficulty_requirements[1] = difficulties['normal'] create_inverted_regions(self.multiworld, 1) self.multiworld.worlds[1].create_dungeons() diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py index d7b5c9f797..3c75a2c368 100644 --- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py +++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py @@ -22,10 +22,10 @@ class TestInvertedTurtleRock(TestInvertedMinor): ["Turtle Rock - Chain Chomps", False, ['Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Turtle Rock - Chain Chomps", True, ['Bomb Upgrade (+5)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']], - ["Turtle Rock - Chain Chomps", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']], + ["Turtle Rock - Chain Chomps", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']], ["Turtle Rock - Roller Room - Left", False, []], ["Turtle Rock - Roller Room - Left", False, [], ['Cane of Somaria']], @@ -55,8 +55,8 @@ class TestInvertedTurtleRock(TestInvertedMinor): ["Turtle Rock - Big Chest", False, [], ['Big Key (Turtle Rock)']], ["Turtle Rock - Big Chest", False, [], ['Magic Mirror', 'Cane of Somaria']], ["Turtle Rock - Big Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']], ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hookshot']], ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], @@ -69,10 +69,10 @@ class TestInvertedTurtleRock(TestInvertedMinor): ["Turtle Rock - Big Key Chest", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], # Mirror in from ledge, use left side entrance, have enough keys to get to the chest - ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Crystaroller Room", False, []], ["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], @@ -98,7 +98,7 @@ class TestInvertedTurtleRock(TestInvertedMinor): ["Turtle Rock - Boss", False, [], ['Big Key (Turtle Rock)']], ["Turtle Rock - Boss", False, [], ['Magic Mirror', 'Lamp']], ["Turtle Rock - Boss", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], - ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], diff --git a/worlds/alttp/test/inverted_owg/TestDarkWorld.py b/worlds/alttp/test/inverted_owg/TestDarkWorld.py index e7e720d2b8..8fb234f0b5 100644 --- a/worlds/alttp/test/inverted_owg/TestDarkWorld.py +++ b/worlds/alttp/test/inverted_owg/TestDarkWorld.py @@ -5,15 +5,16 @@ class TestDarkWorld(TestInvertedOWG): def testSouthDarkWorld(self): self.run_location_tests([ - ["Hype Cave - Top", True, []], - - ["Hype Cave - Middle Right", True, []], - - ["Hype Cave - Middle Left", True, []], - - ["Hype Cave - Bottom", True, []], - - ["Hype Cave - Generous Guy", True, []], + ["Hype Cave - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Middle Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Middle Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Bottom", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)']], ["Stumpy", True, []], @@ -22,7 +23,8 @@ class TestDarkWorld(TestInvertedOWG): def testWestDarkWorld(self): self.run_location_tests([ - ["Brewery", True, []], + ["Brewery", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Brewery", True, ['Bomb Upgrade (+5)']], ["C-Shaped House", True, []], diff --git a/worlds/alttp/test/inverted_owg/TestDeathMountain.py b/worlds/alttp/test/inverted_owg/TestDeathMountain.py index 79796a7aeb..b509643d0c 100644 --- a/worlds/alttp/test/inverted_owg/TestDeathMountain.py +++ b/worlds/alttp/test/inverted_owg/TestDeathMountain.py @@ -24,36 +24,38 @@ class TestDeathMountain(TestInvertedOWG): ["Paradox Cave Lower - Far Left", False, []], ["Paradox Cave Lower - Far Left", False, [], ['Moon Pearl']], - ["Paradox Cave Lower - Far Left", True, ['Moon Pearl', 'Pegasus Boots']], + ["Paradox Cave Lower - Far Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], ["Paradox Cave Lower - Left", False, []], ["Paradox Cave Lower - Left", False, [], ['Moon Pearl']], - ["Paradox Cave Lower - Left", True, ['Moon Pearl', 'Pegasus Boots']], + ["Paradox Cave Lower - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], ["Paradox Cave Lower - Middle", False, []], ["Paradox Cave Lower - Middle", False, [], ['Moon Pearl']], - ["Paradox Cave Lower - Middle", True, ['Moon Pearl', 'Pegasus Boots']], + ["Paradox Cave Lower - Middle", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], ["Paradox Cave Lower - Right", False, []], ["Paradox Cave Lower - Right", False, [], ['Moon Pearl']], - ["Paradox Cave Lower - Right", True, ['Moon Pearl', 'Pegasus Boots']], + ["Paradox Cave Lower - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], ["Paradox Cave Lower - Far Right", False, []], ["Paradox Cave Lower - Far Right", False, [], ['Moon Pearl']], - ["Paradox Cave Lower - Far Right", True, ['Moon Pearl', 'Pegasus Boots']], + ["Paradox Cave Lower - Far Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], ["Paradox Cave Upper - Left", False, []], ["Paradox Cave Upper - Left", False, [], ['Moon Pearl']], - ["Paradox Cave Upper - Left", True, ['Moon Pearl', 'Pegasus Boots']], + ["Paradox Cave Upper - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], ["Paradox Cave Upper - Right", False, []], ["Paradox Cave Upper - Right", False, [], ['Moon Pearl']], - ["Paradox Cave Upper - Right", True, ['Moon Pearl', 'Pegasus Boots']], + ["Paradox Cave Upper - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], ["Mimic Cave", False, []], ["Mimic Cave", False, [], ['Moon Pearl']], ["Mimic Cave", False, [], ['Hammer']], - ["Mimic Cave", True, ['Moon Pearl', 'Hammer', 'Pegasus Boots']], + ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Hammer', 'Pegasus Boots']], ["Ether Tablet", False, []], ["Ether Tablet", False, ['Progressive Sword'], ['Progressive Sword']], diff --git a/worlds/alttp/test/inverted_owg/TestDungeons.py b/worlds/alttp/test/inverted_owg/TestDungeons.py index f5d07544aa..0d8445895e 100644 --- a/worlds/alttp/test/inverted_owg/TestDungeons.py +++ b/worlds/alttp/test/inverted_owg/TestDungeons.py @@ -13,16 +13,16 @@ class TestDungeons(TestInvertedOWG): ["Sanctuary", False, []], ["Sanctuary", False, ['Beat Agahnim 1']], ["Sanctuary", True, ['Magic Mirror', 'Beat Agahnim 1']], - ["Sanctuary", True, ['Lamp', 'Beat Agahnim 1', 'Small Key (Hyrule Castle)']], + ["Sanctuary", True, ['Progressive Sword', 'Lamp', 'Beat Agahnim 1', 'Small Key (Hyrule Castle)']], ["Sanctuary", True, ['Moon Pearl', 'Pegasus Boots']], ["Sanctuary", True, ['Magic Mirror', 'Pegasus Boots']], ["Sewers - Secret Room - Left", False, []], ["Sewers - Secret Room - Left", True, ['Moon Pearl', 'Progressive Glove', 'Pegasus Boots']], - ["Sewers - Secret Room - Left", True, ['Moon Pearl', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)']], - ["Sewers - Secret Room - Left", True, - ['Magic Mirror', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)']], - ["Sewers - Secret Room - Left", True, ['Beat Agahnim 1', 'Lamp', 'Small Key (Hyrule Castle)']], + ["Sewers - Secret Room - Left", True, ['Progressive Sword', 'Moon Pearl', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)']], + ["Sewers - Secret Room - Left", True, ['Progressive Sword', 'Magic Mirror', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)']], + ["Sewers - Secret Room - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Beat Agahnim 1', 'Lamp', 'Small Key (Hyrule Castle)']], + ["Sewers - Secret Room - Left", True, ['Bomb Upgrade (+10)', 'Beat Agahnim 1', 'Lamp', 'Small Key (Hyrule Castle)']], ["Eastern Palace - Compass Chest", False, []], ["Eastern Palace - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots']], @@ -45,7 +45,7 @@ class TestDungeons(TestInvertedOWG): ["Tower of Hera - Basement Cage", True, ['Pegasus Boots', 'Moon Pearl']], ["Castle Tower - Room 03", False, []], - ["Castle Tower - Room 03", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], + ["Castle Tower - Room 03", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], ["Castle Tower - Room 03", True, ['Pegasus Boots', 'Progressive Sword']], ["Castle Tower - Room 03", True, ['Pegasus Boots', 'Progressive Bow']], @@ -62,7 +62,8 @@ class TestDungeons(TestInvertedOWG): ["Skull Woods - Big Chest", False, []], ["Skull Woods - Big Chest", False, [], ['Big Key (Skull Woods)']], - ["Skull Woods - Big Chest", True, ['Big Key (Skull Woods)']], + ["Skull Woods - Big Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Skull Woods - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Skull Woods)']], ["Skull Woods - Big Key Chest", True, []], @@ -89,7 +90,16 @@ class TestDungeons(TestInvertedOWG): ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Quake', 'Progressive Sword', 'Cane of Somaria']], ["Turtle Rock - Chain Chomps", False, []], - ["Turtle Rock - Chain Chomps", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl']], + ["Turtle Rock - Chain Chomps", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Hookshot', 'Progressive Sword', 'Progressive Bow', 'Blue Boomerang', 'Red Boomerang', 'Cane of Somaria', 'Fire Rod', 'Ice Rod']], + ["Turtle Rock - Chain Chomps", True, ['Bomb Upgrade (+5)', 'Pegasus Boots', 'Magic Mirror', 'Moon Pearl']], + ["Turtle Rock - Chain Chomps", True, ['Hookshot', 'Pegasus Boots']], + ["Turtle Rock - Chain Chomps", True, ['Progressive Bow', 'Pegasus Boots', 'Magic Mirror', 'Moon Pearl']], + ["Turtle Rock - Chain Chomps", True, ['Blue Boomerang', 'Pegasus Boots', 'Magic Mirror', 'Moon Pearl']], + ["Turtle Rock - Chain Chomps", True, ['Red Boomerang', 'Pegasus Boots', 'Magic Mirror', 'Moon Pearl']], + ["Turtle Rock - Chain Chomps", True, ['Cane of Somaria', 'Pegasus Boots', 'Magic Mirror', 'Moon Pearl']], + ["Turtle Rock - Chain Chomps", True, ['Fire Rod', 'Pegasus Boots', 'Magic Mirror', 'Moon Pearl']], + ["Turtle Rock - Chain Chomps", True, ['Ice Rod', 'Pegasus Boots', 'Magic Mirror', 'Moon Pearl']], + ["Turtle Rock - Chain Chomps", True, ['Progressive Sword', 'Progressive Sword', 'Pegasus Boots']], ["Turtle Rock - Crystaroller Room", False, []], ["Turtle Rock - Crystaroller Room", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl', 'Big Key (Turtle Rock)']], diff --git a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py index a4e84fce9b..849f06098a 100644 --- a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py +++ b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py @@ -13,8 +13,10 @@ from worlds.alttp.test import LTTPTestBase class TestInvertedOWG(TestBase, LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.logic[1] = "owglitches" - self.multiworld.mode[1] = "inverted" + self.multiworld.glitches_required[1] = "overworld_glitches" + self.multiworld.mode[1].value = 2 + self.multiworld.bombless_start[1].value = True + self.multiworld.shuffle_capacity_upgrades[1].value = True self.multiworld.difficulty_requirements[1] = difficulties['normal'] create_inverted_regions(self.multiworld, 1) self.multiworld.worlds[1].create_dungeons() diff --git a/worlds/alttp/test/inverted_owg/TestLightWorld.py b/worlds/alttp/test/inverted_owg/TestLightWorld.py index de92b4ef85..bd18259bec 100644 --- a/worlds/alttp/test/inverted_owg/TestLightWorld.py +++ b/worlds/alttp/test/inverted_owg/TestLightWorld.py @@ -40,40 +40,46 @@ class TestLightWorld(TestInvertedOWG): ["Chicken House", False, []], ["Chicken House", False, [], ['Moon Pearl']], - ["Chicken House", True, ['Moon Pearl', 'Pegasus Boots']], + ["Chicken House", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Chicken House", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], ["Aginah's Cave", False, []], ["Aginah's Cave", False, [], ['Moon Pearl']], - ["Aginah's Cave", True, ['Moon Pearl', 'Pegasus Boots']], + ["Aginah's Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Aginah's Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], ["Sahasrahla's Hut - Left", False, []], ["Sahasrahla's Hut - Left", False, [], ['Moon Pearl', 'Magic Mirror']], ["Sahasrahla's Hut - Left", False, [], ['Moon Pearl', 'Pegasus Boots']], + ["Sahasrahla's Hut - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Pegasus Boots']], ["Sahasrahla's Hut - Left", True, ['Magic Mirror', 'Pegasus Boots']], ##todo: Damage boost superbunny not in logic #["Sahasrahla's Hut - Left", True, ['Beat Agahnim 1', 'Pegasus Boots']], - ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], ["Sahasrahla's Hut - Middle", False, []], ["Sahasrahla's Hut - Middle", False, [], ['Moon Pearl', 'Magic Mirror']], ["Sahasrahla's Hut - Middle", False, [], ['Moon Pearl', 'Pegasus Boots']], + ["Sahasrahla's Hut - Middle", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Pegasus Boots']], ["Sahasrahla's Hut - Middle", True, ['Magic Mirror', 'Pegasus Boots']], #["Sahasrahla's Hut - Middle", True, ['Beat Agahnim 1', 'Pegasus Boots']], - ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Middle", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], ["Sahasrahla's Hut - Right", False, []], ["Sahasrahla's Hut - Right", False, [], ['Moon Pearl', 'Magic Mirror']], ["Sahasrahla's Hut - Right", False, [], ['Moon Pearl', 'Pegasus Boots']], + ["Sahasrahla's Hut - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Pegasus Boots']], ["Sahasrahla's Hut - Right", True, ['Magic Mirror', 'Pegasus Boots']], #["Sahasrahla's Hut - Right", True, ['Beat Agahnim 1', 'Pegasus Boots']], - ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Beat Agahnim 1']], + ["Sahasrahla's Hut - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']], ["Kakariko Well - Top", False, []], ["Kakariko Well - Top", False, [], ['Moon Pearl']], - ["Kakariko Well - Top", True, ['Moon Pearl', 'Pegasus Boots']], + ["Kakariko Well - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], ["Kakariko Well - Left", False, []], ["Kakariko Well - Left", True, ['Moon Pearl', 'Pegasus Boots']], @@ -101,7 +107,8 @@ class TestLightWorld(TestInvertedOWG): ["Blind's Hideout - Top", False, []], ["Blind's Hideout - Top", False, [], ['Moon Pearl']], - ["Blind's Hideout - Top", True, ['Moon Pearl', 'Pegasus Boots']], + ["Blind's Hideout - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], ["Blind's Hideout - Left", False, []], ["Blind's Hideout - Left", False, [], ['Moon Pearl', 'Magic Mirror']], @@ -134,27 +141,33 @@ class TestLightWorld(TestInvertedOWG): ["Mini Moldorm Cave - Far Left", False, []], ["Mini Moldorm Cave - Far Left", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Pegasus Boots']], + ["Mini Moldorm Cave - Far Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Pegasus Boots']], ["Mini Moldorm Cave - Left", False, []], ["Mini Moldorm Cave - Left", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Pegasus Boots']], + ["Mini Moldorm Cave - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Pegasus Boots']], ["Mini Moldorm Cave - Right", False, []], ["Mini Moldorm Cave - Right", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Pegasus Boots']], + ["Mini Moldorm Cave - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Pegasus Boots']], ["Mini Moldorm Cave - Far Right", False, []], ["Mini Moldorm Cave - Far Right", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Pegasus Boots']], + ["Mini Moldorm Cave - Far Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Pegasus Boots']], ["Mini Moldorm Cave - Generous Guy", False, []], ["Mini Moldorm Cave - Generous Guy", False, [], ['Moon Pearl']], - ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Pegasus Boots']], + ["Mini Moldorm Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Pegasus Boots']], ["Ice Rod Cave", False, []], ["Ice Rod Cave", False, [], ['Moon Pearl']], - ["Ice Rod Cave", True, ['Moon Pearl', 'Pegasus Boots']], + ["Ice Rod Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Ice Rod Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], #I don't think so #["Ice Rod Cave", True, ['Magic Mirror', 'Pegasus Boots', 'BigRedBomb']], #["Ice Rod Cave", True, ['Magic Mirror', 'Beat Agahnim 1', 'BigRedBomb']], @@ -236,7 +249,8 @@ class TestLightWorld(TestInvertedOWG): ["Graveyard Cave", False, []], ["Graveyard Cave", False, [], ['Moon Pearl']], - ["Graveyard Cave", True, ['Moon Pearl', 'Pegasus Boots']], + ["Graveyard Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], ["Checkerboard Cave", False, []], ["Checkerboard Cave", False, [], ['Progressive Glove']], diff --git a/worlds/alttp/test/minor_glitches/TestDarkWorld.py b/worlds/alttp/test/minor_glitches/TestDarkWorld.py index 3a6f97254c..9b0e43ea94 100644 --- a/worlds/alttp/test/minor_glitches/TestDarkWorld.py +++ b/worlds/alttp/test/minor_glitches/TestDarkWorld.py @@ -7,43 +7,48 @@ class TestDarkWorld(TestMinor): self.run_location_tests([ ["Hype Cave - Top", False, []], ["Hype Cave - Top", False, [], ['Moon Pearl']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Hype Cave - Middle Right", False, []], ["Hype Cave - Middle Right", False, [], ['Moon Pearl']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Middle Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Hype Cave - Middle Left", False, []], ["Hype Cave - Middle Left", False, [], ['Moon Pearl']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Middle Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Hype Cave - Bottom", False, []], ["Hype Cave - Bottom", False, [], ['Moon Pearl']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Bottom", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Hype Cave - Generous Guy", False, []], ["Hype Cave - Generous Guy", False, [], ['Moon Pearl']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Stumpy", False, []], ["Stumpy", False, [], ['Moon Pearl']], @@ -66,10 +71,11 @@ class TestDarkWorld(TestMinor): self.run_location_tests([ ["Brewery", False, []], ["Brewery", False, [], ['Moon Pearl']], - ["Brewery", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Brewery", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Brewery", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Brewery", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Brewery", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["C-Shaped House", False, []], ["C-Shaped House", False, [], ['Moon Pearl']], diff --git a/worlds/alttp/test/minor_glitches/TestDeathMountain.py b/worlds/alttp/test/minor_glitches/TestDeathMountain.py index 2603aaeb7b..4446ee7e8f 100644 --- a/worlds/alttp/test/minor_glitches/TestDeathMountain.py +++ b/worlds/alttp/test/minor_glitches/TestDeathMountain.py @@ -48,7 +48,8 @@ class TestDeathMountain(TestMinor): ["Mimic Cave", False, [], ['Moon Pearl']], ["Mimic Cave", False, [], ['Cane of Somaria']], ["Mimic Cave", False, ['Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], - ["Mimic Cave", True, ['Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Mimic Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Spiral Cave", False, []], ["Spiral Cave", False, [], ['Progressive Glove', 'Flute']], @@ -73,10 +74,11 @@ class TestDeathMountain(TestMinor): ["Paradox Cave Lower - Far Left", False, ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Far Left", False, ['Flute', 'Magic Mirror']], ["Paradox Cave Lower - Far Left", False, ['Flute', 'Hammer']], - ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], - ["Paradox Cave Lower - Far Left", True, ['Flute', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Far Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']], + ["Paradox Cave Lower - Far Left", True, ['Cane of Somaria', 'Flute', 'Hookshot']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Far Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']], ["Paradox Cave Lower - Left", False, []], ["Paradox Cave Lower - Left", False, [], ['Progressive Glove', 'Flute']], @@ -87,10 +89,11 @@ class TestDeathMountain(TestMinor): ["Paradox Cave Lower - Left", False, ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Left", False, ['Flute', 'Magic Mirror']], ["Paradox Cave Lower - Left", False, ['Flute', 'Hammer']], - ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], - ["Paradox Cave Lower - Left", True, ['Flute', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']], + ["Paradox Cave Lower - Left", True, ['Cane of Somaria', 'Flute', 'Hookshot']], + ["Paradox Cave Lower - Left", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Left", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']], ["Paradox Cave Lower - Middle", False, []], ["Paradox Cave Lower - Middle", False, [], ['Progressive Glove', 'Flute']], @@ -101,10 +104,11 @@ class TestDeathMountain(TestMinor): ["Paradox Cave Lower - Middle", False, ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Middle", False, ['Flute', 'Magic Mirror']], ["Paradox Cave Lower - Middle", False, ['Flute', 'Hammer']], - ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], - ["Paradox Cave Lower - Middle", True, ['Flute', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Middle", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']], + ["Paradox Cave Lower - Middle", True, ['Cane of Somaria', 'Flute', 'Hookshot']], + ["Paradox Cave Lower - Middle", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Middle", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Middle", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']], ["Paradox Cave Lower - Right", False, []], ["Paradox Cave Lower - Right", False, [], ['Progressive Glove', 'Flute']], @@ -115,10 +119,11 @@ class TestDeathMountain(TestMinor): ["Paradox Cave Lower - Right", False, ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Right", False, ['Flute', 'Magic Mirror']], ["Paradox Cave Lower - Right", False, ['Flute', 'Hammer']], - ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], - ["Paradox Cave Lower - Right", True, ['Flute', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']], + ["Paradox Cave Lower - Right", True, ['Cane of Somaria', 'Flute', 'Hookshot']], + ["Paradox Cave Lower - Right", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Right", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']], ["Paradox Cave Lower - Far Right", False, []], ["Paradox Cave Lower - Far Right", False, [], ['Progressive Glove', 'Flute']], @@ -129,10 +134,11 @@ class TestDeathMountain(TestMinor): ["Paradox Cave Lower - Far Right", False, ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Far Right", False, ['Flute', 'Magic Mirror']], ["Paradox Cave Lower - Far Right", False, ['Flute', 'Hammer']], - ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], - ["Paradox Cave Lower - Far Right", True, ['Flute', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Far Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']], + ["Paradox Cave Lower - Far Right", True, ['Cane of Somaria', 'Flute', 'Hookshot']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Far Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']], ["Paradox Cave Upper - Left", False, []], ["Paradox Cave Upper - Left", False, [], ['Progressive Glove', 'Flute']], @@ -143,10 +149,11 @@ class TestDeathMountain(TestMinor): ["Paradox Cave Upper - Left", False, ['Progressive Glove', 'Hookshot']], ["Paradox Cave Upper - Left", False, ['Flute', 'Magic Mirror']], ["Paradox Cave Upper - Left", False, ['Flute', 'Hammer']], - ["Paradox Cave Upper - Left", True, ['Flute', 'Hookshot']], - ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], - ["Paradox Cave Upper - Left", True, ['Flute', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Upper - Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']], ["Paradox Cave Upper - Right", False, []], ["Paradox Cave Upper - Right", False, [], ['Progressive Glove', 'Flute']], @@ -157,10 +164,11 @@ class TestDeathMountain(TestMinor): ["Paradox Cave Upper - Right", False, ['Progressive Glove', 'Hookshot']], ["Paradox Cave Upper - Right", False, ['Flute', 'Magic Mirror']], ["Paradox Cave Upper - Right", False, ['Flute', 'Hammer']], - ["Paradox Cave Upper - Right", True, ['Flute', 'Hookshot']], - ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], - ["Paradox Cave Upper - Right", True, ['Flute', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Upper - Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']], ]) def testWestDarkWorldDeathMountain(self): diff --git a/worlds/alttp/test/minor_glitches/TestLightWorld.py b/worlds/alttp/test/minor_glitches/TestLightWorld.py index bdfdc23496..017f2d64a8 100644 --- a/worlds/alttp/test/minor_glitches/TestLightWorld.py +++ b/worlds/alttp/test/minor_glitches/TestLightWorld.py @@ -29,17 +29,21 @@ class TestLightWorld(TestMinor): ["Kakariko Tavern", True, []], - ["Chicken House", True, []], + ["Chicken House", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Chicken House", True, ['Bomb Upgrade (+5)']], - ["Aginah's Cave", True, []], + ["Aginah's Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Aginah's Cave", True, ['Bomb Upgrade (+5)']], - ["Sahasrahla's Hut - Left", True, []], + ["Sahasrahla's Hut - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], + ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)']], + ["Sahasrahla's Hut - Middle", True, ['Pegasus Boots']], + ["Sahasrahla's Hut - Middle", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], + ["Sahasrahla's Hut - Right", True, ['Bomb Upgrade (+5)']], + ["Sahasrahla's Hut - Right", True, ['Pegasus Boots']], - ["Sahasrahla's Hut - Middle", True, []], - - ["Sahasrahla's Hut - Right", True, []], - - ["Kakariko Well - Top", True, []], + ["Kakariko Well - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)']], ["Kakariko Well - Left", True, []], @@ -49,7 +53,8 @@ class TestLightWorld(TestMinor): ["Kakariko Well - Bottom", True, []], - ["Blind's Hideout - Top", True, []], + ["Blind's Hideout - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)']], ["Blind's Hideout - Left", True, []], @@ -63,15 +68,19 @@ class TestLightWorld(TestMinor): ["Bonk Rock Cave", False, [], ['Pegasus Boots']], ["Bonk Rock Cave", True, ['Pegasus Boots']], - ["Mini Moldorm Cave - Far Left", True, []], + ["Mini Moldorm Cave - Far Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], + ["Mini Moldorm Cave - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], + ["Mini Moldorm Cave - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], + ["Mini Moldorm Cave - Far Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], + ["Mini Moldorm Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], - ["Mini Moldorm Cave - Left", True, []], - - ["Mini Moldorm Cave - Right", True, []], - - ["Mini Moldorm Cave - Far Right", True, []], - - ["Ice Rod Cave", True, []], + ["Ice Rod Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Ice Rod Cave", True, ['Bomb Upgrade (+5)']], ["Bottle Merchant", True, []], @@ -131,11 +140,12 @@ class TestLightWorld(TestMinor): ["Graveyard Cave", False, []], ["Graveyard Cave", False, [], ['Magic Mirror']], ["Graveyard Cave", False, [], ['Moon Pearl']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Hammer', 'Hookshot']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Graveyard Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Hammer', 'Hookshot']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Checkerboard Cave", False, []], ["Checkerboard Cave", False, [], ['Progressive Glove']], @@ -143,8 +153,6 @@ class TestLightWorld(TestMinor): ["Checkerboard Cave", False, [], ['Magic Mirror']], ["Checkerboard Cave", True, ['Flute', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], - ["Mini Moldorm Cave - Generous Guy", True, []], - ["Library", False, []], ["Library", False, [], ['Pegasus Boots']], ["Library", True, ['Pegasus Boots']], @@ -155,7 +163,10 @@ class TestLightWorld(TestMinor): ["Potion Shop", False, [], ['Mushroom']], ["Potion Shop", True, ['Mushroom']], - ["Maze Race", True, []], + ["Maze Race", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Magic Mirror', 'Pegasus Boots']], + ["Maze Race", True, ['Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Maze Race", True, ['Bomb Upgrade (+5)']], + ["Maze Race", True, ['Pegasus Boots']], ["Desert Ledge", False, []], ["Desert Ledge", False, [], ['Book of Mudora', 'Flute']], diff --git a/worlds/alttp/test/minor_glitches/TestMinor.py b/worlds/alttp/test/minor_glitches/TestMinor.py index d5cfd3095b..c7de74d3a6 100644 --- a/worlds/alttp/test/minor_glitches/TestMinor.py +++ b/worlds/alttp/test/minor_glitches/TestMinor.py @@ -10,7 +10,9 @@ from worlds.alttp.test import LTTPTestBase class TestMinor(TestBase, LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.logic[1] = "minorglitches" + self.multiworld.glitches_required[1] = "minor_glitches" + self.multiworld.bombless_start[1].value = True + self.multiworld.shuffle_capacity_upgrades[1].value = True self.multiworld.difficulty_requirements[1] = difficulties['normal'] self.multiworld.worlds[1].er_seed = 0 self.multiworld.worlds[1].create_regions() diff --git a/worlds/alttp/test/owg/TestDarkWorld.py b/worlds/alttp/test/owg/TestDarkWorld.py index 93324656bd..c671f6485c 100644 --- a/worlds/alttp/test/owg/TestDarkWorld.py +++ b/worlds/alttp/test/owg/TestDarkWorld.py @@ -7,48 +7,53 @@ class TestDarkWorld(TestVanillaOWG): self.run_location_tests([ ["Hype Cave - Top", False, []], ["Hype Cave - Top", False, [], ['Moon Pearl']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Pegasus Boots']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Hype Cave - Middle Right", False, []], ["Hype Cave - Middle Right", False, [], ['Moon Pearl']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Pegasus Boots']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Middle Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Hype Cave - Middle Left", False, []], ["Hype Cave - Middle Left", False, [], ['Moon Pearl']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Pegasus Boots']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Middle Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Hype Cave - Bottom", False, []], ["Hype Cave - Bottom", False, [], ['Moon Pearl']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Pegasus Boots']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Bottom", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Hype Cave - Generous Guy", False, []], ["Hype Cave - Generous Guy", False, [], ['Moon Pearl']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Pegasus Boots']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Stumpy", False, []], ["Stumpy", False, [], ['Moon Pearl']], @@ -129,13 +134,14 @@ class TestDarkWorld(TestVanillaOWG): self.run_location_tests([ ["Brewery", False, []], ["Brewery", False, [], ['Moon Pearl']], + ["Brewery", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], ["Brewery", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot', 'Progressive Glove']], - ["Brewery", True, ['Moon Pearl', 'Pegasus Boots']], - ["Brewery", True, ['Moon Pearl', 'Flute', 'Magic Mirror']], - ["Brewery", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Brewery", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Brewery", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Brewery", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']], + ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Flute', 'Magic Mirror']], + ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["C-Shaped House", False, []], ["C-Shaped House", False, [], ['Moon Pearl', 'Magic Mirror']], diff --git a/worlds/alttp/test/owg/TestDeathMountain.py b/worlds/alttp/test/owg/TestDeathMountain.py index 41031c65c5..0933b2881e 100644 --- a/worlds/alttp/test/owg/TestDeathMountain.py +++ b/worlds/alttp/test/owg/TestDeathMountain.py @@ -48,9 +48,10 @@ class TestDeathMountain(TestVanillaOWG): ["Mimic Cave", False, [], ['Hammer']], ["Mimic Cave", False, [], ['Pegasus Boots', 'Flute', 'Lamp']], ["Mimic Cave", False, [], ['Pegasus Boots', 'Flute', 'Progressive Glove']], - ["Mimic Cave", True, ['Magic Mirror', 'Hammer', 'Pegasus Boots']], - ["Mimic Cave", True, ['Magic Mirror', 'Hammer', 'Progressive Glove', 'Lamp']], - ["Mimic Cave", True, ['Magic Mirror', 'Hammer', 'Flute']], + ["Mimic Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Hookshot', 'Hammer']], + ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Magic Mirror', 'Hammer', 'Pegasus Boots']], + ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Magic Mirror', 'Hammer', 'Progressive Glove', 'Lamp']], + ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Magic Mirror', 'Hammer', 'Flute']], ["Spiral Cave", False, []], ["Spiral Cave", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']], @@ -64,65 +65,72 @@ class TestDeathMountain(TestVanillaOWG): ["Paradox Cave Lower - Far Left", False, []], ["Paradox Cave Lower - Far Left", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']], ["Paradox Cave Lower - Far Left", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot']], - ["Paradox Cave Lower - Far Left", True, ['Pegasus Boots']], - ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']], - ["Paradox Cave Lower - Far Left", True, ['Flute', 'Magic Mirror']], + ["Paradox Cave Lower - Far Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']], + ["Paradox Cave Lower - Far Left", True, ['Fire Rod', 'Pegasus Boots']], + ["Paradox Cave Lower - Far Left", True, ['Cane of Somaria', 'Flute', 'Hookshot']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Paradox Cave Lower - Far Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror']], ["Paradox Cave Lower - Left", False, []], ["Paradox Cave Lower - Left", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']], ["Paradox Cave Lower - Left", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot']], - ["Paradox Cave Lower - Left", True, ['Pegasus Boots']], - ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']], - ["Paradox Cave Lower - Left", True, ['Flute', 'Magic Mirror']], + ["Paradox Cave Lower - Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']], + ["Paradox Cave Lower - Left", True, ['Fire Rod', 'Pegasus Boots']], + ["Paradox Cave Lower - Left", True, ['Cane of Somaria', 'Flute', 'Hookshot']], + ["Paradox Cave Lower - Left", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Left", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Paradox Cave Lower - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror']], ["Paradox Cave Lower - Middle", False, []], ["Paradox Cave Lower - Middle", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']], ["Paradox Cave Lower - Middle", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot']], - ["Paradox Cave Lower - Middle", True, ['Pegasus Boots']], - ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']], - ["Paradox Cave Lower - Middle", True, ['Flute', 'Magic Mirror']], + ["Paradox Cave Lower - Middle", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']], + ["Paradox Cave Lower - Middle", True, ['Fire Rod', 'Pegasus Boots']], + ["Paradox Cave Lower - Middle", True, ['Cane of Somaria', 'Flute', 'Hookshot']], + ["Paradox Cave Lower - Middle", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Middle", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Paradox Cave Lower - Middle", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror']], ["Paradox Cave Lower - Right", False, []], ["Paradox Cave Lower - Right", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']], ["Paradox Cave Lower - Right", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot']], - ["Paradox Cave Lower - Right", True, ['Pegasus Boots']], - ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']], - ["Paradox Cave Lower - Right", True, ['Flute', 'Magic Mirror']], + ["Paradox Cave Lower - Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']], + ["Paradox Cave Lower - Right", True, ['Fire Rod', 'Pegasus Boots']], + ["Paradox Cave Lower - Right", True, ['Cane of Somaria', 'Flute', 'Hookshot']], + ["Paradox Cave Lower - Right", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Right", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Paradox Cave Lower - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror']], ["Paradox Cave Lower - Far Right", False, []], ["Paradox Cave Lower - Far Right", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']], ["Paradox Cave Lower - Far Right", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot']], - ["Paradox Cave Lower - Far Right", True, ['Pegasus Boots']], - ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']], - ["Paradox Cave Lower - Far Right", True, ['Flute', 'Magic Mirror']], + ["Paradox Cave Lower - Far Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']], + ["Paradox Cave Lower - Far Right", True, ['Fire Rod', 'Pegasus Boots']], + ["Paradox Cave Lower - Far Right", True, ['Cane of Somaria', 'Flute', 'Hookshot']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Paradox Cave Lower - Far Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror']], ["Paradox Cave Upper - Left", False, []], ["Paradox Cave Upper - Left", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']], ["Paradox Cave Upper - Left", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot']], - ["Paradox Cave Upper - Left", True, ['Pegasus Boots']], - ["Paradox Cave Upper - Left", True, ['Flute', 'Hookshot']], - ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']], - ["Paradox Cave Upper - Left", True, ['Flute', 'Magic Mirror']], + ["Paradox Cave Upper - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Pegasus Boots']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror']], ["Paradox Cave Upper - Right", False, []], ["Paradox Cave Upper - Right", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']], ["Paradox Cave Upper - Right", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot']], - ["Paradox Cave Upper - Right", True, ['Pegasus Boots']], - ["Paradox Cave Upper - Right", True, ['Flute', 'Hookshot']], - ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']], - ["Paradox Cave Upper - Right", True, ['Flute', 'Magic Mirror']], + ["Paradox Cave Upper - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Pegasus Boots']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Magic Mirror']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror']], ]) def testWestDarkWorldDeathMountain(self): diff --git a/worlds/alttp/test/owg/TestDungeons.py b/worlds/alttp/test/owg/TestDungeons.py index 4f87896967..f4688b7a35 100644 --- a/worlds/alttp/test/owg/TestDungeons.py +++ b/worlds/alttp/test/owg/TestDungeons.py @@ -6,13 +6,14 @@ class TestDungeons(TestVanillaOWG): def testFirstDungeonChests(self): self.run_location_tests([ ["Hyrule Castle - Map Chest", True, []], - ["Hyrule Castle - Map Guard Key Drop", True, []], + ["Hyrule Castle - Map Guard Key Drop", False, []], + ["Hyrule Castle - Map Guard Key Drop", True, ['Progressive Sword']], ["Sanctuary", True, []], ["Sewers - Secret Room - Left", False, []], - ["Sewers - Secret Room - Left", True, ['Progressive Glove']], - ["Sewers - Secret Room - Left", True, ['Lamp', 'Small Key (Hyrule Castle)']], + ["Sewers - Secret Room - Left", True, ['Pegasus Boots', 'Progressive Glove']], + ["Sewers - Secret Room - Left", True, ['Progressive Sword', 'Bomb Upgrade (+5)', 'Lamp', 'Small Key (Hyrule Castle)']], ["Eastern Palace - Compass Chest", True, []], @@ -41,10 +42,9 @@ class TestDungeons(TestVanillaOWG): ["Castle Tower - Room 03", False, []], ["Castle Tower - Room 03", False, ['Progressive Sword'], ['Progressive Sword', 'Cape', 'Beat Agahnim 1']], - ["Castle Tower - Room 03", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], ["Castle Tower - Room 03", True, ['Progressive Sword', 'Progressive Sword']], - ["Castle Tower - Room 03", True, ['Cape', 'Progressive Bow']], - ["Castle Tower - Room 03", True, ['Beat Agahnim 1', 'Fire Rod']], + ["Castle Tower - Room 03", True, ['Progressive Sword', 'Cape']], + ["Castle Tower - Room 03", True, ['Progressive Sword', 'Beat Agahnim 1']], ["Palace of Darkness - Shooter Room", False, []], ["Palace of Darkness - Shooter Room", False, [], ['Moon Pearl']], @@ -69,9 +69,10 @@ class TestDungeons(TestVanillaOWG): ["Skull Woods - Big Chest", False, []], ["Skull Woods - Big Chest", False, [], ['Big Key (Skull Woods)']], + ["Skull Woods - Big Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], #todo: Bomb Jump in logic? #["Skull Woods - Big Chest", True, ['Magic Mirror', 'Pegasus Boots', 'Big Key (Skull Woods)']], - ["Skull Woods - Big Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Big Key (Skull Woods)']], + ["Skull Woods - Big Chest", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots', 'Big Key (Skull Woods)']], ["Skull Woods - Big Key Chest", False, []], ["Skull Woods - Big Key Chest", True, ['Magic Mirror', 'Pegasus Boots']], @@ -111,8 +112,8 @@ class TestDungeons(TestVanillaOWG): ["Turtle Rock - Chain Chomps", False, []], #todo: does clip require sword? #["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Pegasus Boots']], - ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Pegasus Boots', 'Progressive Sword']], - ["Turtle Rock - Chain Chomps", True, ['Pegasus Boots', 'Magic Mirror']], + ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Pegasus Boots', 'Progressive Sword', 'Progressive Sword']], + ["Turtle Rock - Chain Chomps", True, ['Pegasus Boots', 'Magic Mirror', 'Progressive Bow']], ["Turtle Rock - Crystaroller Room", False, []], ["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)']], diff --git a/worlds/alttp/test/owg/TestLightWorld.py b/worlds/alttp/test/owg/TestLightWorld.py index f3f1ba0c27..84342a33c8 100644 --- a/worlds/alttp/test/owg/TestLightWorld.py +++ b/worlds/alttp/test/owg/TestLightWorld.py @@ -25,17 +25,21 @@ class TestLightWorld(TestVanillaOWG): ["Kakariko Tavern", True, []], - ["Chicken House", True, []], + ["Chicken House", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Chicken House", True, ['Bomb Upgrade (+5)']], - ["Aginah's Cave", True, []], + ["Aginah's Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Aginah's Cave", True, ['Bomb Upgrade (+5)']], - ["Sahasrahla's Hut - Left", True, []], + ["Sahasrahla's Hut - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], + ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)']], + ["Sahasrahla's Hut - Middle", True, ['Pegasus Boots']], + ["Sahasrahla's Hut - Middle", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], + ["Sahasrahla's Hut - Right", True, ['Bomb Upgrade (+5)']], + ["Sahasrahla's Hut - Right", True, ['Pegasus Boots']], - ["Sahasrahla's Hut - Middle", True, []], - - ["Sahasrahla's Hut - Right", True, []], - - ["Kakariko Well - Top", True, []], + ["Kakariko Well - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)']], ["Kakariko Well - Left", True, []], @@ -45,7 +49,8 @@ class TestLightWorld(TestVanillaOWG): ["Kakariko Well - Bottom", True, []], - ["Blind's Hideout - Top", True, []], + ["Blind's Hideout - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)']], ["Blind's Hideout - Left", True, []], @@ -59,15 +64,19 @@ class TestLightWorld(TestVanillaOWG): ["Bonk Rock Cave", False, [], ['Pegasus Boots']], ["Bonk Rock Cave", True, ['Pegasus Boots']], - ["Mini Moldorm Cave - Far Left", True, []], + ["Mini Moldorm Cave - Far Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], + ["Mini Moldorm Cave - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], + ["Mini Moldorm Cave - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], + ["Mini Moldorm Cave - Far Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], + ["Mini Moldorm Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], - ["Mini Moldorm Cave - Left", True, []], - - ["Mini Moldorm Cave - Right", True, []], - - ["Mini Moldorm Cave - Far Right", True, []], - - ["Ice Rod Cave", True, []], + ["Ice Rod Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Ice Rod Cave", True, ['Bomb Upgrade (+5)']], ["Bottle Merchant", True, []], @@ -126,12 +135,13 @@ class TestLightWorld(TestVanillaOWG): ["Graveyard Cave", False, []], ["Graveyard Cave", False, [], ['Pegasus Boots', 'Magic Mirror']], ["Graveyard Cave", False, [], ['Pegasus Boots', 'Moon Pearl']], - ["Graveyard Cave", True, ['Pegasus Boots']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Hammer', 'Hookshot']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Graveyard Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Pegasus Boots']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Hammer', 'Hookshot']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Checkerboard Cave", False, []], ["Checkerboard Cave", False, [], ['Progressive Glove']], @@ -140,8 +150,6 @@ class TestLightWorld(TestVanillaOWG): ["Checkerboard Cave", True, ['Pegasus Boots', 'Progressive Glove']], ["Checkerboard Cave", True, ['Flute', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], - ["Mini Moldorm Cave - Generous Guy", True, []], - ["Library", False, []], ["Library", False, [], ['Pegasus Boots']], ["Library", True, ['Pegasus Boots']], @@ -152,7 +160,10 @@ class TestLightWorld(TestVanillaOWG): ["Potion Shop", False, [], ['Mushroom']], ["Potion Shop", True, ['Mushroom']], - ["Maze Race", True, []], + ["Maze Race", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Magic Mirror', 'Pegasus Boots']], + ["Maze Race", True, ['Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Maze Race", True, ['Bomb Upgrade (+5)']], + ["Maze Race", True, ['Pegasus Boots']], ["Desert Ledge", False, []], ["Desert Ledge", False, [], ['Pegasus Boots', 'Book of Mudora', 'Flute']], diff --git a/worlds/alttp/test/owg/TestVanillaOWG.py b/worlds/alttp/test/owg/TestVanillaOWG.py index 37b0b6ccb8..1f8f2707ed 100644 --- a/worlds/alttp/test/owg/TestVanillaOWG.py +++ b/worlds/alttp/test/owg/TestVanillaOWG.py @@ -11,7 +11,9 @@ class TestVanillaOWG(TestBase, LTTPTestBase): def setUp(self): self.world_setup() self.multiworld.difficulty_requirements[1] = difficulties['normal'] - self.multiworld.logic[1] = "owglitches" + self.multiworld.glitches_required[1] = "overworld_glitches" + self.multiworld.bombless_start[1].value = True + self.multiworld.shuffle_capacity_upgrades[1].value = True self.multiworld.worlds[1].er_seed = 0 self.multiworld.worlds[1].create_regions() self.multiworld.worlds[1].create_items() diff --git a/worlds/alttp/test/vanilla/TestDarkWorld.py b/worlds/alttp/test/vanilla/TestDarkWorld.py index ecb3e55830..8ff09c527d 100644 --- a/worlds/alttp/test/vanilla/TestDarkWorld.py +++ b/worlds/alttp/test/vanilla/TestDarkWorld.py @@ -7,43 +7,48 @@ class TestDarkWorld(TestVanilla): self.run_location_tests([ ["Hype Cave - Top", False, []], ["Hype Cave - Top", False, [], ['Moon Pearl']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Hype Cave - Middle Right", False, []], ["Hype Cave - Middle Right", False, [], ['Moon Pearl']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Middle Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Hype Cave - Middle Left", False, []], ["Hype Cave - Middle Left", False, [], ['Moon Pearl']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Middle Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Hype Cave - Bottom", False, []], ["Hype Cave - Bottom", False, [], ['Moon Pearl']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Bottom", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Hype Cave - Generous Guy", False, []], ["Hype Cave - Generous Guy", False, [], ['Moon Pearl']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Hype Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Stumpy", False, []], ["Stumpy", False, [], ['Moon Pearl']], @@ -66,10 +71,11 @@ class TestDarkWorld(TestVanilla): self.run_location_tests([ ["Brewery", False, []], ["Brewery", False, [], ['Moon Pearl']], - ["Brewery", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']], - ["Brewery", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']], - ["Brewery", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Brewery", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Brewery", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']], + ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']], + ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["C-Shaped House", False, []], ["C-Shaped House", False, [], ['Moon Pearl']], diff --git a/worlds/alttp/test/vanilla/TestDeathMountain.py b/worlds/alttp/test/vanilla/TestDeathMountain.py index ecb3831f6a..d77f1a8dd2 100644 --- a/worlds/alttp/test/vanilla/TestDeathMountain.py +++ b/worlds/alttp/test/vanilla/TestDeathMountain.py @@ -48,7 +48,8 @@ class TestDeathMountain(TestVanilla): ["Mimic Cave", False, [], ['Moon Pearl']], ["Mimic Cave", False, [], ['Cane of Somaria']], ["Mimic Cave", False, ['Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], - ["Mimic Cave", True, ['Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Mimic Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Spiral Cave", False, []], ["Spiral Cave", False, [], ['Progressive Glove', 'Flute']], @@ -73,10 +74,10 @@ class TestDeathMountain(TestVanilla): ["Paradox Cave Lower - Far Left", False, ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Far Left", False, ['Flute', 'Magic Mirror']], ["Paradox Cave Lower - Far Left", False, ['Flute', 'Hammer']], - ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], - ["Paradox Cave Lower - Far Left", True, ['Flute', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot', 'Cane of Somaria']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Bomb Upgrade (+5)']], + ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer', 'Progressive Sword', 'Progressive Sword']], + ["Paradox Cave Lower - Far Left", True, ['Flute', 'Magic Mirror', 'Hammer', 'Fire Rod']], ["Paradox Cave Lower - Left", False, []], ["Paradox Cave Lower - Left", False, [], ['Progressive Glove', 'Flute']], @@ -87,10 +88,10 @@ class TestDeathMountain(TestVanilla): ["Paradox Cave Lower - Left", False, ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Left", False, ['Flute', 'Magic Mirror']], ["Paradox Cave Lower - Left", False, ['Flute', 'Hammer']], - ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], - ["Paradox Cave Lower - Left", True, ['Flute', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot', 'Cane of Somaria']], + ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Bomb Upgrade (+5)']], + ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer', 'Progressive Sword', 'Progressive Sword']], + ["Paradox Cave Lower - Left", True, ['Flute', 'Magic Mirror', 'Hammer', 'Fire Rod']], ["Paradox Cave Lower - Middle", False, []], ["Paradox Cave Lower - Middle", False, [], ['Progressive Glove', 'Flute']], @@ -101,10 +102,10 @@ class TestDeathMountain(TestVanilla): ["Paradox Cave Lower - Middle", False, ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Middle", False, ['Flute', 'Magic Mirror']], ["Paradox Cave Lower - Middle", False, ['Flute', 'Hammer']], - ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], - ["Paradox Cave Lower - Middle", True, ['Flute', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot', 'Cane of Somaria']], + ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Bomb Upgrade (+5)']], + ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer', 'Progressive Sword', 'Progressive Sword']], + ["Paradox Cave Lower - Middle", True, ['Flute', 'Magic Mirror', 'Hammer', 'Fire Rod']], ["Paradox Cave Lower - Right", False, []], ["Paradox Cave Lower - Right", False, [], ['Progressive Glove', 'Flute']], @@ -115,10 +116,10 @@ class TestDeathMountain(TestVanilla): ["Paradox Cave Lower - Right", False, ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Right", False, ['Flute', 'Magic Mirror']], ["Paradox Cave Lower - Right", False, ['Flute', 'Hammer']], - ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], - ["Paradox Cave Lower - Right", True, ['Flute', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot', 'Cane of Somaria']], + ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Bomb Upgrade (+5)']], + ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer', 'Progressive Sword', 'Progressive Sword']], + ["Paradox Cave Lower - Right", True, ['Flute', 'Magic Mirror', 'Hammer', 'Fire Rod']], ["Paradox Cave Lower - Far Right", False, []], ["Paradox Cave Lower - Far Right", False, [], ['Progressive Glove', 'Flute']], @@ -129,10 +130,10 @@ class TestDeathMountain(TestVanilla): ["Paradox Cave Lower - Far Right", False, ['Progressive Glove', 'Hookshot']], ["Paradox Cave Lower - Far Right", False, ['Flute', 'Magic Mirror']], ["Paradox Cave Lower - Far Right", False, ['Flute', 'Hammer']], - ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot']], - ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], - ["Paradox Cave Lower - Far Right", True, ['Flute', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot', 'Cane of Somaria']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Bomb Upgrade (+5)']], + ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer', 'Progressive Sword', 'Progressive Sword']], + ["Paradox Cave Lower - Far Right", True, ['Flute', 'Magic Mirror', 'Hammer', 'Fire Rod']], ["Paradox Cave Upper - Left", False, []], ["Paradox Cave Upper - Left", False, [], ['Progressive Glove', 'Flute']], @@ -143,10 +144,11 @@ class TestDeathMountain(TestVanilla): ["Paradox Cave Upper - Left", False, ['Progressive Glove', 'Hookshot']], ["Paradox Cave Upper - Left", False, ['Flute', 'Magic Mirror']], ["Paradox Cave Upper - Left", False, ['Flute', 'Hammer']], - ["Paradox Cave Upper - Left", True, ['Flute', 'Hookshot']], - ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], - ["Paradox Cave Upper - Left", True, ['Flute', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Upper - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']], ["Paradox Cave Upper - Right", False, []], ["Paradox Cave Upper - Right", False, [], ['Progressive Glove', 'Flute']], @@ -157,10 +159,11 @@ class TestDeathMountain(TestVanilla): ["Paradox Cave Upper - Right", False, ['Progressive Glove', 'Hookshot']], ["Paradox Cave Upper - Right", False, ['Flute', 'Magic Mirror']], ["Paradox Cave Upper - Right", False, ['Flute', 'Hammer']], - ["Paradox Cave Upper - Right", True, ['Flute', 'Hookshot']], - ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']], - ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], - ["Paradox Cave Upper - Right", True, ['Flute', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Upper - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']], + ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']], ]) def testWestDarkWorldDeathMountain(self): diff --git a/worlds/alttp/test/vanilla/TestLightWorld.py b/worlds/alttp/test/vanilla/TestLightWorld.py index 977e807290..6d9284aba0 100644 --- a/worlds/alttp/test/vanilla/TestLightWorld.py +++ b/worlds/alttp/test/vanilla/TestLightWorld.py @@ -29,17 +29,21 @@ class TestLightWorld(TestVanilla): ["Kakariko Tavern", True, []], - ["Chicken House", True, []], + ["Chicken House", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Chicken House", True, ['Bomb Upgrade (+5)']], - ["Aginah's Cave", True, []], + ["Aginah's Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Aginah's Cave", True, ['Bomb Upgrade (+5)']], - ["Sahasrahla's Hut - Left", True, []], + ["Sahasrahla's Hut - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], + ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)']], + ["Sahasrahla's Hut - Middle", True, ['Pegasus Boots']], + ["Sahasrahla's Hut - Middle", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']], + ["Sahasrahla's Hut - Right", True, ['Bomb Upgrade (+5)']], + ["Sahasrahla's Hut - Right", True, ['Pegasus Boots']], - ["Sahasrahla's Hut - Middle", True, []], - - ["Sahasrahla's Hut - Right", True, []], - - ["Kakariko Well - Top", True, []], + ["Kakariko Well - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)']], ["Kakariko Well - Left", True, []], @@ -49,7 +53,8 @@ class TestLightWorld(TestVanilla): ["Kakariko Well - Bottom", True, []], - ["Blind's Hideout - Top", True, []], + ["Blind's Hideout - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)']], ["Blind's Hideout - Left", True, []], @@ -63,15 +68,19 @@ class TestLightWorld(TestVanilla): ["Bonk Rock Cave", False, [], ['Pegasus Boots']], ["Bonk Rock Cave", True, ['Pegasus Boots']], - ["Mini Moldorm Cave - Far Left", True, []], + ["Mini Moldorm Cave - Far Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], + ["Mini Moldorm Cave - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], + ["Mini Moldorm Cave - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], + ["Mini Moldorm Cave - Far Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], + ["Mini Moldorm Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Progressive Sword']], - ["Mini Moldorm Cave - Left", True, []], - - ["Mini Moldorm Cave - Right", True, []], - - ["Mini Moldorm Cave - Far Right", True, []], - - ["Ice Rod Cave", True, []], + ["Ice Rod Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Ice Rod Cave", True, ['Bomb Upgrade (+5)']], ["Bottle Merchant", True, []], @@ -136,11 +145,12 @@ class TestLightWorld(TestVanilla): ["Graveyard Cave", False, []], ["Graveyard Cave", False, [], ['Magic Mirror']], ["Graveyard Cave", False, [], ['Moon Pearl']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Hammer', 'Hookshot']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], - ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], + ["Graveyard Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Hammer', 'Hookshot']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']], + ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Flippers', 'Hookshot']], ["Checkerboard Cave", False, []], ["Checkerboard Cave", False, [], ['Progressive Glove']], @@ -148,7 +158,6 @@ class TestLightWorld(TestVanilla): ["Checkerboard Cave", False, [], ['Magic Mirror']], ["Checkerboard Cave", True, ['Flute', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], - ["Mini Moldorm Cave - Generous Guy", True, []], ["Library", False, []], ["Library", False, [], ['Pegasus Boots']], @@ -160,7 +169,10 @@ class TestLightWorld(TestVanilla): ["Potion Shop", False, [], ['Mushroom']], ["Potion Shop", True, ['Mushroom']], - ["Maze Race", True, []], + ["Maze Race", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Magic Mirror', 'Pegasus Boots']], + ["Maze Race", True, ['Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']], + ["Maze Race", True, ['Bomb Upgrade (+5)']], + ["Maze Race", True, ['Pegasus Boots']], ["Desert Ledge", False, []], ["Desert Ledge", False, [], ['Book of Mudora', 'Flute']], diff --git a/worlds/alttp/test/vanilla/TestVanilla.py b/worlds/alttp/test/vanilla/TestVanilla.py index 3c983e9850..3f4fbad8c2 100644 --- a/worlds/alttp/test/vanilla/TestVanilla.py +++ b/worlds/alttp/test/vanilla/TestVanilla.py @@ -9,8 +9,10 @@ from worlds.alttp.test import LTTPTestBase class TestVanilla(TestBase, LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.logic[1] = "noglitches" + self.multiworld.glitches_required[1] = "no_glitches" self.multiworld.difficulty_requirements[1] = difficulties['normal'] + self.multiworld.bombless_start[1].value = True + self.multiworld.shuffle_capacity_upgrades[1].value = True self.multiworld.worlds[1].er_seed = 0 self.multiworld.worlds[1].create_regions() self.multiworld.worlds[1].create_items() From 38cc90efd08ee802d08f7aba76a33d2da44db304 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Tue, 20 Feb 2024 08:07:33 +0100 Subject: [PATCH 019/166] TextClient: fix logging not always showing up (#2846) --- CommonClient.py | 1 + 1 file changed, 1 insertion(+) diff --git a/CommonClient.py b/CommonClient.py index 736cf4922f..c75ca3fd80 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -941,4 +941,5 @@ def run_as_textclient(): if __name__ == '__main__': + logging.getLogger().setLevel(logging.INFO) # force log-level to work around log level resetting to WARNING run_as_textclient() From 7fc159c8819a2e0b61c2cfe3824f485e6939b45b Mon Sep 17 00:00:00 2001 From: BootsinSoots <102177943+BootsinSoots@users.noreply.github.com> Date: Tue, 20 Feb 2024 11:22:32 -0500 Subject: [PATCH 020/166] Docs: Make all guide titles say Guide, for my sanity (and the webhost) (#2304) --- worlds/alttp/__init__.py | 6 +++--- worlds/bumpstik/__init__.py | 2 +- worlds/checksfinder/__init__.py | 2 +- worlds/dark_souls_3/__init__.py | 2 +- worlds/dlcquest/__init__.py | 2 +- worlds/factorio/__init__.py | 2 +- worlds/meritous/__init__.py | 2 +- worlds/messenger/__init__.py | 2 +- worlds/minecraft/__init__.py | 2 +- worlds/oot/__init__.py | 2 +- worlds/overcooked2/__init__.py | 2 +- worlds/pokemon_rb/__init__.py | 2 +- worlds/tloz/__init__.py | 2 +- worlds/undertale/__init__.py | 2 +- 14 files changed, 16 insertions(+), 16 deletions(-) diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index e1216010e2..7a2664b3f4 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -42,7 +42,7 @@ class ALTTPSettings(settings.Group): class ALTTPWeb(WebWorld): setup_en = Tutorial( - "Multiworld Setup Tutorial", + "Multiworld Setup Guide", "A guide to setting up the Archipelago ALttP Software on your computer. This guide covers single-player, multiworld, and related software.", "English", "multiworld_en.md", @@ -78,7 +78,7 @@ class ALTTPWeb(WebWorld): ) msu = Tutorial( - "MSU-1 Setup Tutorial", + "MSU-1 Setup Guide", "A guide to setting up MSU-1, which allows for custom in-game music.", "English", "msu1_en.md", @@ -105,7 +105,7 @@ class ALTTPWeb(WebWorld): ) plando = Tutorial( - "Plando Tutorial", + "Plando Guide", "A guide to creating Multiworld Plandos with LTTP", "English", "plando_en.md", diff --git a/worlds/bumpstik/__init__.py b/worlds/bumpstik/__init__.py index c4e65d07b6..d93b25cda5 100644 --- a/worlds/bumpstik/__init__.py +++ b/worlds/bumpstik/__init__.py @@ -14,7 +14,7 @@ from worlds.generic.Rules import forbid_item class BumpStikWeb(WebWorld): tutorials = [Tutorial( - "Bumper Stickers Setup Tutorial", + "Bumper Stickers Setup Guide", "A guide to setting up the Archipelago Bumper Stickers software on your computer.", "English", "setup_en.md", diff --git a/worlds/checksfinder/__init__.py b/worlds/checksfinder/__init__.py index 621e8f5c37..b70c65bb08 100644 --- a/worlds/checksfinder/__init__.py +++ b/worlds/checksfinder/__init__.py @@ -10,7 +10,7 @@ client_version = 7 class ChecksFinderWeb(WebWorld): tutorials = [Tutorial( - "Multiworld Setup Tutorial", + "Multiworld Setup Guide", "A guide to setting up the Archipelago ChecksFinder software on your computer. This guide covers " "single-player, multiworld, and related software.", "English", diff --git a/worlds/dark_souls_3/__init__.py b/worlds/dark_souls_3/__init__.py index 7ee6c2a641..6efe4e4bc9 100644 --- a/worlds/dark_souls_3/__init__.py +++ b/worlds/dark_souls_3/__init__.py @@ -15,7 +15,7 @@ from .Options import RandomizeWeaponLevelOption, PoolTypeOption, EarlySmallLothr class DarkSouls3Web(WebWorld): bug_report_page = "https://github.com/Marechal-L/Dark-Souls-III-Archipelago-client/issues" setup_en = Tutorial( - "Multiworld Setup Tutorial", + "Multiworld Setup Guide", "A guide to setting up the Archipelago Dark Souls III randomizer on your computer.", "English", "setup_en.md", diff --git a/worlds/dlcquest/__init__.py b/worlds/dlcquest/__init__.py index ca7a0157cb..db55b1903b 100644 --- a/worlds/dlcquest/__init__.py +++ b/worlds/dlcquest/__init__.py @@ -14,7 +14,7 @@ client_version = 0 class DLCqwebworld(WebWorld): setup_en = Tutorial( - "Multiworld Setup Tutorial", + "Multiworld Setup Guide", "A guide to setting up the Archipelago DLCQuest game on your computer.", "English", "setup_en.md", diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py index 17f3163e90..3b74757384 100644 --- a/worlds/factorio/__init__.py +++ b/worlds/factorio/__init__.py @@ -53,7 +53,7 @@ If this file does exist, then it will be used. class FactorioWeb(WebWorld): tutorials = [Tutorial( - "Multiworld Setup Tutorial", + "Multiworld Setup Guide", "A guide to setting up the Archipelago Factorio software on your computer.", "English", "setup_en.md", diff --git a/worlds/meritous/__init__.py b/worlds/meritous/__init__.py index 1bf1bfc0f2..fd12734be9 100644 --- a/worlds/meritous/__init__.py +++ b/worlds/meritous/__init__.py @@ -17,7 +17,7 @@ client_version = 1 class MeritousWeb(WebWorld): tutorials = [Tutorial( - "Meritous Setup Tutorial", + "Meritous Setup Guide", "A guide to setting up the Archipelago Meritous software on your computer.", "English", "setup_en.md", diff --git a/worlds/messenger/__init__.py b/worlds/messenger/__init__.py index b0d031905c..f4a28729f1 100644 --- a/worlds/messenger/__init__.py +++ b/worlds/messenger/__init__.py @@ -17,7 +17,7 @@ class MessengerWeb(WebWorld): bug_report_page = "https://github.com/alwaysintreble/TheMessengerRandomizerModAP/issues" tut_en = Tutorial( - "Multiworld Setup Tutorial", + "Multiworld Setup Guide", "A guide to setting up The Messenger randomizer on your computer.", "English", "setup_en.md", diff --git a/worlds/minecraft/__init__.py b/worlds/minecraft/__init__.py index 187f1fdf19..343b9bad19 100644 --- a/worlds/minecraft/__init__.py +++ b/worlds/minecraft/__init__.py @@ -37,7 +37,7 @@ class MinecraftWebWorld(WebWorld): bug_report_page = "https://github.com/KonoTyran/Minecraft_AP_Randomizer/issues/new?assignees=&labels=bug&template=bug_report.yaml&title=%5BBug%5D%3A+Brief+Description+of+bug+here" setup = Tutorial( - "Multiworld Setup Tutorial", + "Multiworld Setup Guide", "A guide to setting up the Archipelago Minecraft software on your computer. This guide covers" "single-player, multiworld, and related software.", "English", diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py index eb9c41f0b0..2f06500e81 100644 --- a/worlds/oot/__init__.py +++ b/worlds/oot/__init__.py @@ -92,7 +92,7 @@ class OOTSettings(settings.Group): class OOTWeb(WebWorld): setup = Tutorial( - "Multiworld Setup Tutorial", + "Multiworld Setup Guide", "A guide to setting up the Archipelago Ocarina of Time software on your computer.", "English", "setup_en.md", diff --git a/worlds/overcooked2/__init__.py b/worlds/overcooked2/__init__.py index 0451f32bdd..24ac175ceb 100644 --- a/worlds/overcooked2/__init__.py +++ b/worlds/overcooked2/__init__.py @@ -16,7 +16,7 @@ class Overcooked2Web(WebWorld): bug_report_page = "https://github.com/toasterparty/oc2-modding/issues" setup_en = Tutorial( - "Multiworld Setup Tutorial", + "Multiworld Setup Guide", "A guide to setting up the Overcooked! 2 randomizer on your computer.", "English", "setup_en.md", diff --git a/worlds/pokemon_rb/__init__.py b/worlds/pokemon_rb/__init__.py index 169ff1d59f..56502f5029 100644 --- a/worlds/pokemon_rb/__init__.py +++ b/worlds/pokemon_rb/__init__.py @@ -46,7 +46,7 @@ class PokemonSettings(settings.Group): class PokemonWebWorld(WebWorld): setup_en = Tutorial( "Multiworld Setup Guide", - "A guide to playing Pokemon Red and Blue with Archipelago.", + "A guide to playing Pokémon Red and Blue with Archipelago.", "English", "setup_en.md", "setup/en", diff --git a/worlds/tloz/__init__.py b/worlds/tloz/__init__.py index 259bfe2047..27230654b8 100644 --- a/worlds/tloz/__init__.py +++ b/worlds/tloz/__init__.py @@ -45,7 +45,7 @@ class TLoZSettings(settings.Group): class TLoZWeb(WebWorld): theme = "stone" setup = Tutorial( - "Multiworld Setup Tutorial", + "Multiworld Setup Guide", "A guide to setting up The Legend of Zelda for Archipelago on your computer.", "English", "multiworld_en.md", diff --git a/worlds/undertale/__init__.py b/worlds/undertale/__init__.py index 9e784a4a59..0694456a6b 100644 --- a/worlds/undertale/__init__.py +++ b/worlds/undertale/__init__.py @@ -29,7 +29,7 @@ def data_path(file_name: str): class UndertaleWeb(WebWorld): tutorials = [Tutorial( - "Multiworld Setup Tutorial", + "Multiworld Setup Guide", "A guide to setting up the Archipelago Undertale software on your computer. This guide covers " "single-player, multiworld, and related software.", "English", From 17c73916b70c2a7cf103f8ee0522ec83d5f5b0a9 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Wed, 21 Feb 2024 08:53:54 +0100 Subject: [PATCH 021/166] Speedups: no cinit, no pickling (#2851) * Speedups: remove unnecessary cinit This was meant for (memory) safety, but cython docs clearly state that this is done automatically. The code generated for cinit with args is what triggers a 'possible null deref' in clang's static analyzer, so by removing cinit, we can now use static analysis. * Speedups: disable pickling ... ... of LocationStore and internal classes. This reduces code size and avoids accidentally pickling them. --- _speedups.pyx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/_speedups.pyx b/_speedups.pyx index 9bf25cce29..b4167ec5aa 100644 --- a/_speedups.pyx +++ b/_speedups.pyx @@ -48,6 +48,7 @@ cdef struct IndexEntry: size_t count +@cython.auto_pickle(False) cdef class LocationStore: """Compact store for locations and their items in a MultiServer""" # The original implementation uses Dict[int, Dict[int, Tuple(int, int, int]] @@ -78,18 +79,6 @@ cdef class LocationStore: size += sizeof(self._raw_proxies[0]) * self.sender_index_size return size - def __cinit__(self, locations_dict: Dict[int, Dict[int, Sequence[int]]]) -> None: - self._mem = None - self._keys = None - self._items = None - self._proxies = None - self._len = 0 - self.entries = NULL - self.entry_count = 0 - self.sender_index = NULL - self.sender_index_size = 0 - self._raw_proxies = NULL - def __init__(self, locations_dict: Dict[int, Dict[int, Sequence[int]]]) -> None: self._mem = Pool() cdef object key @@ -281,6 +270,7 @@ cdef class LocationStore: entry.location not in checked]) +@cython.auto_pickle(False) @cython.internal # unsafe. disable direct import cdef class PlayerLocationProxy: cdef LocationStore _store From ffdcb91a13860111b4e467e2436bb9fcb8964483 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Wed, 21 Feb 2024 02:51:22 -0600 Subject: [PATCH 022/166] CI: add missing core files to "affects: core" labelling (#2824) * add missing files * Change to wildcard --- .github/labeler.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index c582902836..2743104f41 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -27,4 +27,5 @@ - '!WebHostLib/**' - any-glob-to-any-file: # exceptions to the above rules of "stuff that isn't core" - 'worlds/generic/**/*.py' + - 'worlds/*.py' - 'CommonClient.py' From 9f0d736aed464e945e4205289c86babaec3f1b73 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Thu, 22 Feb 2024 03:44:03 -0500 Subject: [PATCH 023/166] Generate: Fix sphere calculation debug message (#2788) --- BaseClasses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 25e4e70741..36d0bc267a 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1272,12 +1272,12 @@ class Spoiler: for location in sphere: state.collect(location.item, True, location) - required_locations -= sphere - collection_spheres.append(sphere) logging.debug('Calculated final sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(required_locations)) + + required_locations -= sphere if not sphere: raise RuntimeError(f'Not all required items reachable. Unreachable locations: {required_locations}') From f8981a463873ac4ea6093385427199cf58c089d0 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu, 22 Feb 2024 09:49:02 +0100 Subject: [PATCH 024/166] Docs: Better description for LocationScouts (#2674) * Better description for LocationScouts * Update network protocol.md * typo * Update docs/network protocol.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Update docs/network protocol.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Update docs/network protocol.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Update docs/network protocol.md Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- docs/network protocol.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/network protocol.md b/docs/network protocol.md index 338db55299..c6d6cf6887 100644 --- a/docs/network protocol.md +++ b/docs/network protocol.md @@ -327,7 +327,11 @@ Sent to server to inform it of locations that the client has checked. Used to in | locations | list\[int\] | The ids of the locations checked by the client. May contain any number of checks, even ones sent before; duplicates do not cause issues with the Archipelago server. | ### LocationScouts -Sent to the server to inform it of locations the client has seen, but not checked. Useful in cases in which the item may appear in the game world, such as 'ledge items' in A Link to the Past. The server will always respond with a [LocationInfo](#LocationInfo) packet with the items located in the scouted location. +Sent to the server to retrieve the items that are on a specified list of locations. The server will respond with a [LocationInfo](#LocationInfo) packet containing the items located in the scouted locations. +Fully remote clients without a patch file may use this to "place" items onto their in-game locations, most commonly to display their names or item classifications before/upon pickup. + +LocationScouts can also be used to inform the server of locations the client has seen, but not checked. This creates a hint as if the player had run `!hint_location` on a location, but without deducting hint points. +This is useful in cases where an item appears in the game world, such as 'ledge items' in _A Link to the Past_. To do this, set the `create_as_hint` parameter to a non-zero value. #### Arguments | Name | Type | Notes | From b18641091f3090306cb2dc463ac1b43387f377b1 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:56:53 -0500 Subject: [PATCH 025/166] LTTP: Thieves' Town Big Chest fix (#2853) --- worlds/alttp/Rules.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index 17061842dd..a87bfd5b0c 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -397,9 +397,9 @@ def global_rules(world, player): set_rule(world.get_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3) and can_use_bombs(state, player)) set_rule(world.get_location('Thieves\' Town - Big Chest', player), - lambda state: (state._lttp_has_key('Small Key (Thieves Town)', player, 3)) and state.has('Hammer', player)) + lambda state: ((state._lttp_has_key('Small Key (Thieves Town)', player, 3)) or (location_item_name(state, 'Thieves\' Town - Big Chest', player) == ("Small Key (Thieves Town)", player)) and state._lttp_has_key('Small Key (Thieves Town)', player, 2)) and state.has('Hammer', player)) if world.accessibility[player] != 'locations': - allow_self_locking_items(world.get_location('Thieves\' Town - Big Chest', player), 'Small Key (Thieves Town)') + set_always_allow(world.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player) set_rule(world.get_location('Thieves\' Town - Attic', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3)) set_rule(world.get_location('Thieves\' Town - Spike Switch Pot Key', player), From afa5ce4afe63abf1dfccb6c311f85392b7f131d1 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Fri, 23 Feb 2024 10:11:00 +0100 Subject: [PATCH 026/166] CI: add static analysis for native code / cython (#2852) * CI: add static analysis for native code / cython * CI: scan-build: also run for requirements.txt --- .github/workflows/scan-build.yml | 65 ++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 .github/workflows/scan-build.yml diff --git a/.github/workflows/scan-build.yml b/.github/workflows/scan-build.yml new file mode 100644 index 0000000000..5234d862b4 --- /dev/null +++ b/.github/workflows/scan-build.yml @@ -0,0 +1,65 @@ +name: Native Code Static Analysis + +on: + push: + paths: + - '**.c' + - '**.cc' + - '**.cpp' + - '**.cxx' + - '**.h' + - '**.hh' + - '**.hpp' + - '**.pyx' + - 'setup.py' + - 'requirements.txt' + - '.github/workflows/scan-build.yml' + pull_request: + paths: + - '**.c' + - '**.cc' + - '**.cpp' + - '**.cxx' + - '**.h' + - '**.hh' + - '**.hpp' + - '**.pyx' + - 'setup.py' + - 'requirements.txt' + - '.github/workflows/scan-build.yml' + +jobs: + scan-build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install newer Clang + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x ./llvm.sh + sudo ./llvm.sh 17 + - name: Install scan-build command + run: | + sudo apt install clang-tools-17 + - name: Get a recent python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m venv venv + source venv/bin/activate + python -m pip install --upgrade pip -r requirements.txt + - name: scan-build + run: | + source venv/bin/activate + scan-build-17 --status-bugs -o scan-build-reports -disable-checker deadcode.DeadStores python setup.py build -y + - name: Store report + if: failure() + uses: actions/upload-artifact@v4 + with: + name: scan-build-reports + path: scan-build-reports From 96163c640830545fed9b5a133b6989273e8d3a30 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 23 Feb 2024 10:32:14 +0100 Subject: [PATCH 027/166] Core: provide convenience getters on World class (#2827) --- worlds/AutoWorld.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index e8d48df58c..b282c7deb8 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -15,7 +15,7 @@ from BaseClasses import CollectionState if TYPE_CHECKING: import random - from BaseClasses import MultiWorld, Item, Location, Tutorial + from BaseClasses import MultiWorld, Item, Location, Tutorial, Region, Entrance from . import GamesPackage from settings import Group @@ -458,6 +458,16 @@ class World(metaclass=AutoWorldRegister): def create_filler(self) -> "Item": return self.create_item(self.get_filler_item_name()) + # convenience methods + def get_location(self, location_name: str) -> "Location": + return self.multiworld.get_location(location_name, self.player) + + def get_entrance(self, entrance_name: str) -> "Entrance": + return self.multiworld.get_entrance(entrance_name, self.player) + + def get_region(self, region_name: str) -> "Region": + return self.multiworld.get_region(region_name, self.player) + @classmethod def get_data_package_data(cls) -> "GamesPackage": sorted_item_name_groups = { From 6bf4a94537511afba4e8386361af59e31e315105 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Fri, 23 Feb 2024 13:41:59 -0500 Subject: [PATCH 028/166] TUNIC: Use push_precollected for start_with_sword (#2857) --- worlds/tunic/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index fb04570f22..b10ccd43af 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -73,9 +73,6 @@ class TunicWorld(World): self.options.hexagon_quest.value = passthrough["hexagon_quest"] self.options.entrance_rando.value = passthrough["entrance_rando"] - if self.options.start_with_sword and "Sword" not in self.options.start_inventory: - self.options.start_inventory.value["Sword"] = 1 - def create_item(self, name: str) -> TunicItem: item_data = item_table[name] return TunicItem(name, item_data.classification, self.item_name_to_id[name], self.player) @@ -94,6 +91,9 @@ class TunicWorld(World): items_to_create["Fool Trap"] += items_to_create[money_fool] items_to_create[money_fool] = 0 + if self.options.start_with_sword: + self.multiworld.push_precollected(self.create_item("Sword")) + if sword_progression: items_to_create["Stick"] = 0 items_to_create["Sword"] = 0 From 57fcd57a851c7b403ce15206163836c56c28c407 Mon Sep 17 00:00:00 2001 From: Ixrec Date: Sat, 24 Feb 2024 16:01:54 +0000 Subject: [PATCH 029/166] Docs: Clarify which kinds of options actually support "random" (#2845) * Clarify which kinds of options actually support "random" The current phrasing of this sentence made me expect "random" to work even on my OptionsDict option. After asking `#archipelago-dev` and checking the `Options.py` code, it's become clear that many option types don't (and can't) support "random". This is my best guess at a more correct wording. * add a sentence about from_text overrides based on black-silver's suggestion --- docs/options api.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/options api.md b/docs/options api.md index bfab0096bb..1141528991 100644 --- a/docs/options api.md +++ b/docs/options api.md @@ -24,8 +24,11 @@ display as `Value1` on the webhost. (i.e. `alias_value_1 = option_value1`) which will allow users to use either `value_1` or `value1` in their yaml files, and both will resolve as `value1`. This should be used when changing options around, i.e. changing a Toggle to a Choice, and defining `alias_true = option_full`. -- All options support `random` as a generic option. `random` chooses from any of the available values for that option, -and is reserved by AP. You can set this as your default value, but you cannot define your own `option_random`. +- All options with a fixed set of possible values (i.e. those which inherit from `Toggle`, `(Text)Choice` or +`(Named/Special)Range`) support `random` as a generic option. `random` chooses from any of the available values for that +option, and is reserved by AP. You can set this as your default value, but you cannot define your own `option_random`. +However, you can override `from_text` and handle `text == "random"` to customize its behavior or +implement it for additional option types. As an example, suppose we want an option that lets the user start their game with a sword in their inventory, an option to let the player choose the difficulty, and an option to choose how much health the final boss has. Let's create our From 86a7ac466e99240b5c9c33e701009d431e3fc33e Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Sat, 24 Feb 2024 21:45:23 -0600 Subject: [PATCH 030/166] Core: remove bad hardcoded behavior around plando_connections (#2170) --- Generate.py | 35 ++++++++++---------------------- worlds/generic/docs/plando_en.md | 8 ++++---- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/Generate.py b/Generate.py index fd4a5a7e19..725a7e9fec 100644 --- a/Generate.py +++ b/Generate.py @@ -462,20 +462,18 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b handle_option(ret, game_weights, option_key, option, plando_options) if PlandoOptions.items in plando_options: ret.plando_items = game_weights.get("plando_items", []) - if ret.game == "Minecraft" or ret.game == "Ocarina of Time": - # bad hardcoded behavior to make this work for now - ret.plando_connections = [] - if PlandoOptions.connections in plando_options: - options = game_weights.get("plando_connections", []) - for placement in options: - if roll_percentage(get_choice("percentage", placement, 100)): - ret.plando_connections.append(PlandoConnection( - get_choice("entrance", placement), - get_choice("exit", placement), - get_choice("direction", placement) - )) - elif ret.game == "A Link to the Past": + if ret.game == "A Link to the Past": roll_alttp_settings(ret, game_weights, plando_options) + if PlandoOptions.connections in plando_options: + ret.plando_connections = [] + options = game_weights.get("plando_connections", []) + for placement in options: + if roll_percentage(get_choice("percentage", placement, 100)): + ret.plando_connections.append(PlandoConnection( + get_choice("entrance", placement), + get_choice("exit", placement), + get_choice("direction", placement, "both") + )) return ret @@ -494,17 +492,6 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options): raise Exception(f"No text target \"{at}\" found.") ret.plando_texts[at] = str(get_choice_legacy("text", placement)) - ret.plando_connections = [] - if PlandoOptions.connections in plando_options: - options = weights.get("plando_connections", []) - for placement in options: - if roll_percentage(get_choice_legacy("percentage", placement, 100)): - ret.plando_connections.append(PlandoConnection( - get_choice_legacy("entrance", placement), - get_choice_legacy("exit", placement), - get_choice_legacy("direction", placement, "both") - )) - ret.sprite_pool = weights.get('sprite_pool', []) ret.sprite = get_choice_legacy('sprite', weights, "Link") if 'random_sprite_on_event' in weights: diff --git a/worlds/generic/docs/plando_en.md b/worlds/generic/docs/plando_en.md index 2d40f45195..9d8e6befe8 100644 --- a/worlds/generic/docs/plando_en.md +++ b/worlds/generic/docs/plando_en.md @@ -171,16 +171,16 @@ relevant guide: [A Link to the Past Plando Guide](/tutorial/A%20Link%20to%20the% ## Connections Plando -This is currently only supported by Minecraft and A Link to the Past. As the way that these games interact with their -connections is different, I will only explain the basics here, while more specifics for A Link to the Past connection -plando can be found in its plando guide. +This is currently only supported by a few games, including A Link to the Past, Minecraft, and Ocarina of Time. As the way that these games interact with their +connections is different, only the basics are explained here. More specific information for connection plando in A Link to the Past can be found in +its [plando guide](/tutorial/A%20Link%20to%20the%20Past/plando/en#connections). * The options for connections are `percentage`, `entrance`, `exit`, and `direction`. Each of these options supports subweights. * `percentage` is the percentage chance for this connection from 0 to 100 and defaults to 100. * Every connection has an `entrance` and an `exit`. These can be unlinked like in A Link to the Past insanity entrance shuffle. -* `direction` can be `both`, `entrance`, or `exit` and determines in which direction this connection will operate. +* `direction` can be `both`, `entrance`, or `exit` and determines in which direction this connection will operate. `direction` defaults to `both`. [A Link to the Past connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/EntranceShuffle.py#L3852) From 8f7b0ee489711c8d67d825e2073a2840c421bbc2 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Sun, 25 Feb 2024 14:56:27 -0600 Subject: [PATCH 031/166] Core: don't allow region, location, or entrance with duplicate names (#2453) --- BaseClasses.py | 8 ++++++++ test/general/test_helpers.py | 4 ++-- worlds/mmbn3/__init__.py | 5 +---- worlds/overcooked2/__init__.py | 8 +------- worlds/ror2/regions.py | 6 +----- worlds/ror2/rules.py | 19 +++++++++++-------- worlds/ror2/test/test_limbo_goal.py | 4 ++-- worlds/ror2/test/test_mithrix_goal.py | 10 +++++----- worlds/ror2/test/test_voidling_goal.py | 6 +++--- worlds/sm64ex/Regions.py | 9 +-------- worlds/timespinner/Regions.py | 8 +------- 11 files changed, 36 insertions(+), 51 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 36d0bc267a..f418945351 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -110,10 +110,14 @@ class MultiWorld(): return self def append(self, region: Region): + assert region.name not in self.region_cache[region.player], \ + f"{region.name} already exists in region cache." self.region_cache[region.player][region.name] = region def extend(self, regions: Iterable[Region]): for region in regions: + assert region.name not in self.region_cache[region.player], \ + f"{region.name} already exists in region cache." self.region_cache[region.player][region.name] = region def add_group(self, new_id: int): @@ -877,6 +881,8 @@ class Region: del(self.region_manager.location_cache[location.player][location.name]) def insert(self, index: int, value: Location) -> None: + assert value.name not in self.region_manager.location_cache[value.player], \ + f"{value.name} already exists in the location cache." self._list.insert(index, value) self.region_manager.location_cache[value.player][value.name] = value @@ -887,6 +893,8 @@ class Region: del(self.region_manager.entrance_cache[entrance.player][entrance.name]) def insert(self, index: int, value: Entrance) -> None: + assert value.name not in self.region_manager.entrance_cache[value.player], \ + f"{value.name} already exists in the entrance cache." self._list.insert(index, value) self.region_manager.entrance_cache[value.player][value.name] = value diff --git a/test/general/test_helpers.py b/test/general/test_helpers.py index 83b56b3438..be84739756 100644 --- a/test/general/test_helpers.py +++ b/test/general/test_helpers.py @@ -29,8 +29,8 @@ class TestHelpers(unittest.TestCase): "event_loc": None, }, "TestRegion2": { - "loc_1": 321, - "loc_2": 654, + "loc_3": 321, + "loc_4": 654, } } diff --git a/worlds/mmbn3/__init__.py b/worlds/mmbn3/__init__.py index acf258a730..762bfd11ae 100644 --- a/worlds/mmbn3/__init__.py +++ b/worlds/mmbn3/__init__.py @@ -100,9 +100,7 @@ class MMBN3World(World): for region_info in regions: region = name_to_region[region_info.name] for connection in region_info.connections: - connection_region = name_to_region[connection] - entrance = Entrance(self.player, connection, region) - entrance.connect(connection_region) + entrance = region.connect(name_to_region[connection]) # ACDC Pending with Start Randomizer # if connection == RegionName.ACDC_Overworld: @@ -141,7 +139,6 @@ class MMBN3World(World): if connection == RegionName.WWW_Island: entrance.access_rule = lambda state:\ state.has(ItemName.Progressive_Undernet_Rank, self.player, 8) - region.exits.append(entrance) def create_items(self) -> None: # First add in all progression and useful items diff --git a/worlds/overcooked2/__init__.py b/worlds/overcooked2/__init__.py index 24ac175ceb..da0e189089 100644 --- a/worlds/overcooked2/__init__.py +++ b/worlds/overcooked2/__init__.py @@ -90,13 +90,7 @@ class Overcooked2World(World): def connect_regions(self, source: str, target: str, rule: Optional[Callable[[CollectionState], bool]] = None): sourceRegion = self.multiworld.get_region(source, self.player) targetRegion = self.multiworld.get_region(target, self.player) - - connection = Entrance(self.player, '', sourceRegion) - if rule: - connection.access_rule = rule - - sourceRegion.exits.append(connection) - connection.connect(targetRegion) + sourceRegion.connect(targetRegion, rule=rule) def add_level_location( self, diff --git a/worlds/ror2/regions.py b/worlds/ror2/regions.py index 13b229da92..199fdccf80 100644 --- a/worlds/ror2/regions.py +++ b/worlds/ror2/regions.py @@ -140,11 +140,7 @@ def create_explore_region(multiworld: MultiWorld, player: int, name: str, data: def create_connections_in_regions(multiworld: MultiWorld, player: int, name: str, data: RoRRegionData) -> None: region = multiworld.get_region(name, player) if data.region_exits: - for region_exit in data.region_exits: - r_exit_stage = Entrance(player, region_exit, region) - exit_region = multiworld.get_region(region_exit, player) - r_exit_stage.connect(exit_region) - region.exits.append(r_exit_stage) + region.add_exits(data.region_exits) def create_classic_regions(ror2_world: "RiskOfRainWorld") -> None: diff --git a/worlds/ror2/rules.py b/worlds/ror2/rules.py index 442e6c0002..b4d5fe68b8 100644 --- a/worlds/ror2/rules.py +++ b/worlds/ror2/rules.py @@ -9,14 +9,16 @@ if TYPE_CHECKING: # Rule to see if it has access to the previous stage -def has_entrance_access_rule(multiworld: MultiWorld, stage: str, entrance: str, player: int) -> None: - multiworld.get_entrance(entrance, player).access_rule = \ - lambda state: state.has(entrance, player) and state.has(stage, player) +def has_entrance_access_rule(multiworld: MultiWorld, stage: str, region: str, player: int) -> None: + rule = lambda state: state.has(region, player) and state.has(stage, player) + for entrance in multiworld.get_region(region, player).entrances: + entrance.access_rule = rule -def has_all_items(multiworld: MultiWorld, items: Set[str], entrance: str, player: int) -> None: - multiworld.get_entrance(entrance, player).access_rule = \ - lambda state: state.has_all(items, player) and state.has(entrance, player) +def has_all_items(multiworld: MultiWorld, items: Set[str], region: str, player: int) -> None: + rule = lambda state: state.has_all(items, player) and state.has(region, player) + for entrance in multiworld.get_region(region, player).entrances: + entrance.access_rule = rule # Checks to see if chest/shrine are accessible @@ -45,8 +47,9 @@ def check_location(state, environment: str, player: int, item_number: int, item_ def get_stage_event(multiworld: MultiWorld, player: int, stage_number: int) -> None: if stage_number == 4: return - multiworld.get_entrance(f"OrderedStage_{stage_number + 1}", player).access_rule = \ - lambda state: state.has(f"Stage {stage_number + 1}", player) + rule = lambda state: state.has(f"Stage {stage_number + 1}", player) + for entrance in multiworld.get_region(f"OrderedStage_{stage_number + 1}", player).entrances: + entrance.access_rule = rule def set_rules(ror2_world: "RiskOfRainWorld") -> None: diff --git a/worlds/ror2/test/test_limbo_goal.py b/worlds/ror2/test/test_limbo_goal.py index f8757a9176..9be9cca120 100644 --- a/worlds/ror2/test/test_limbo_goal.py +++ b/worlds/ror2/test/test_limbo_goal.py @@ -8,8 +8,8 @@ class LimboGoalTest(RoR2TestBase): def test_limbo(self) -> None: self.collect_all_but(["Hidden Realm: A Moment, Whole", "Victory"]) - self.assertFalse(self.can_reach_entrance("Hidden Realm: A Moment, Whole")) + self.assertFalse(self.can_reach_region("Hidden Realm: A Moment, Whole")) self.assertBeatable(False) self.collect_by_name("Hidden Realm: A Moment, Whole") - self.assertTrue(self.can_reach_entrance("Hidden Realm: A Moment, Whole")) + self.assertTrue(self.can_reach_region("Hidden Realm: A Moment, Whole")) self.assertBeatable(True) diff --git a/worlds/ror2/test/test_mithrix_goal.py b/worlds/ror2/test/test_mithrix_goal.py index 7ed9a2cd73..03b8231178 100644 --- a/worlds/ror2/test/test_mithrix_goal.py +++ b/worlds/ror2/test/test_mithrix_goal.py @@ -8,18 +8,18 @@ class MithrixGoalTest(RoR2TestBase): def test_mithrix(self) -> None: self.collect_all_but(["Commencement", "Victory"]) - self.assertFalse(self.can_reach_entrance("Commencement")) + self.assertFalse(self.can_reach_region("Commencement")) self.assertBeatable(False) self.collect_by_name("Commencement") - self.assertTrue(self.can_reach_entrance("Commencement")) + self.assertTrue(self.can_reach_region("Commencement")) self.assertBeatable(True) def test_stage5(self) -> None: self.collect_all_but(["Stage 4", "Sky Meadow", "Victory"]) - self.assertFalse(self.can_reach_entrance("Sky Meadow")) + self.assertFalse(self.can_reach_region("Sky Meadow")) self.assertBeatable(False) self.collect_by_name("Sky Meadow") - self.assertFalse(self.can_reach_entrance("Sky Meadow")) + self.assertFalse(self.can_reach_region("Sky Meadow")) self.collect_by_name("Stage 4") - self.assertTrue(self.can_reach_entrance("Sky Meadow")) + self.assertTrue(self.can_reach_region("Sky Meadow")) self.assertBeatable(True) diff --git a/worlds/ror2/test/test_voidling_goal.py b/worlds/ror2/test/test_voidling_goal.py index a7520a5c5f..77d1349f10 100644 --- a/worlds/ror2/test/test_voidling_goal.py +++ b/worlds/ror2/test/test_voidling_goal.py @@ -9,17 +9,17 @@ class VoidlingGoalTest(RoR2TestBase): def test_planetarium(self) -> None: self.collect_all_but(["The Planetarium", "Victory"]) - self.assertFalse(self.can_reach_entrance("The Planetarium")) + self.assertFalse(self.can_reach_region("The Planetarium")) self.assertBeatable(False) self.collect_by_name("The Planetarium") - self.assertTrue(self.can_reach_entrance("The Planetarium")) + self.assertTrue(self.can_reach_region("The Planetarium")) self.assertBeatable(True) def test_void_locus_to_victory(self) -> None: self.collect_all_but(["Void Locus", "Commencement"]) self.assertFalse(self.can_reach_location("Victory")) self.collect_by_name("Void Locus") - self.assertTrue(self.can_reach_entrance("Victory")) + self.assertTrue(self.can_reach_location("Victory")) def test_commencement_to_victory(self) -> None: self.collect_all_but(["Void Locus", "Commencement"]) diff --git a/worlds/sm64ex/Regions.py b/worlds/sm64ex/Regions.py index c04b862fa7..8c2d32e401 100644 --- a/worlds/sm64ex/Regions.py +++ b/worlds/sm64ex/Regions.py @@ -200,7 +200,6 @@ def create_regions(world: MultiWorld, player: int): create_locs(thi_large_top, "THI: 100 Coins") regFloor3 = create_region("Third Floor", player, world) - world.regions.append(regFloor3) regTTC = create_region("Tick Tock Clock", player, world) create_locs(regTTC, "TTC: Stop Time for Red Coins") @@ -230,13 +229,7 @@ def create_regions(world: MultiWorld, player: int): def connect_regions(world: MultiWorld, player: int, source: str, target: str, rule=None): sourceRegion = world.get_region(source, player) targetRegion = world.get_region(target, player) - - connection = Entrance(player, '', sourceRegion) - if rule: - connection.access_rule = rule - - sourceRegion.exits.append(connection) - connection.connect(targetRegion) + sourceRegion.connect(targetRegion, rule=rule) def create_region(name: str, player: int, world: MultiWorld) -> Region: diff --git a/worlds/timespinner/Regions.py b/worlds/timespinner/Regions.py index fc75356429..f80babc0e6 100644 --- a/worlds/timespinner/Regions.py +++ b/worlds/timespinner/Regions.py @@ -247,13 +247,7 @@ def connect(world: MultiWorld, player: int, source: str, target: str, sourceRegion = world.get_region(source, player) targetRegion = world.get_region(target, player) - - connection = Entrance(player, "", sourceRegion) - - if rule: - connection.access_rule = rule - sourceRegion.exits.append(connection) - connection.connect(targetRegion) + sourceRegion.connect(targetRegion, rule=rule) def split_location_datas_per_region(locations: List[LocationData]) -> Dict[str, List[LocationData]]: From 46fc8df36e5e0fe4b31f78b860e7d41352a7d3bd Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Sun, 25 Feb 2024 16:27:19 -0500 Subject: [PATCH 032/166] TUNIC: Fix for incorrect Zig 3 ER rule (#2849) * Fix for incorrect ER rule in zig 3 * Add nmg logic to this same connection --- worlds/tunic/er_rules.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index ebc563c3da..2f9604c103 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -502,9 +502,13 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re rule=lambda state: state.has(laurels, player) or (has_sword(state, player) and has_ability(state, player, prayer, options, ability_unlocks))) # unrestricted: use ladder storage to get to the front, get hit by one of the many enemies + # nmg: can ice grapple on the voidlings to the double admin fight, still need to pray at the fuse regions["Rooted Ziggurat Lower Back"].connect( connecting_region=regions["Rooted Ziggurat Lower Front"], - rule=lambda state: state.has(laurels, player) or can_ladder_storage(state, player, options)) + rule=lambda state: ((state.has(laurels, player) or + has_ice_grapple_logic(True, state, player, options, ability_unlocks)) and + has_ability(state, player, prayer, options, ability_unlocks) + and has_sword(state, player)) or can_ladder_storage(state, player, options)) regions["Rooted Ziggurat Lower Back"].connect( connecting_region=regions["Rooted Ziggurat Portal Room Entrance"], From 5c05ab1527960374ae5c8dd61fa69fa4af0f15f3 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Sun, 25 Feb 2024 16:28:33 -0500 Subject: [PATCH 033/166] LTTP: KDS Default on (#2850) --- worlds/alttp/Options.py | 6 +- worlds/alttp/Rules.py | 6 +- .../alttp/test/dungeons/TestAgahnimsTower.py | 8 +-- .../alttp/test/dungeons/TestDesertPalace.py | 28 ++++----- .../alttp/test/dungeons/TestEasternPalace.py | 4 +- worlds/alttp/test/dungeons/TestGanonsTower.py | 58 +++++++++---------- worlds/alttp/test/dungeons/TestIcePalace.py | 36 ++++++------ worlds/alttp/test/dungeons/TestMiseryMire.py | 34 +++++------ worlds/alttp/test/dungeons/TestSkullWoods.py | 4 +- worlds/alttp/test/dungeons/TestSwampPalace.py | 18 +++--- worlds/alttp/test/dungeons/TestThievesTown.py | 15 ++--- .../test/inverted/TestInvertedTurtleRock.py | 54 ++++++++--------- .../TestInvertedTurtleRock.py | 54 ++++++++--------- .../alttp/test/inverted_owg/TestDungeons.py | 19 +++--- .../test/minor_glitches/TestDeathMountain.py | 2 +- worlds/alttp/test/owg/TestDungeons.py | 18 +++--- .../alttp/test/vanilla/TestDeathMountain.py | 2 +- 17 files changed, 176 insertions(+), 190 deletions(-) diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py index ed6af6dd67..8cc5d32608 100644 --- a/worlds/alttp/Options.py +++ b/worlds/alttp/Options.py @@ -43,8 +43,7 @@ class Goal(Choice): Triforce Hunt: Collect Triforce pieces spread throughout the worlds, then turn them in to Murahadala in front of Hyrule Castle Local Triforce Hunt: Collect Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle Ganon Triforce Hunt: Collect Triforce pieces spread throughout the worlds, then kill Ganon - Local Ganon Triforce Hunt: Collect Triforce pieces spread throughout your world, then kill Ganon - Ice Rod Hunt: You start with everything except Ice Rod. Find the Ice rod, then kill Trinexx at Turtle rock.""" + Local Ganon Triforce Hunt: Collect Triforce pieces spread throughout your world, then kill Ganon""" display_name = "Goal" default = 0 option_ganon = 0 @@ -211,13 +210,12 @@ class map_shuffle(DungeonItem): display_name = "Map Shuffle" -class key_drop_shuffle(Toggle): +class key_drop_shuffle(DefaultOnToggle): """Shuffle keys found in pots and dropped from killed enemies, respects the small key and big key shuffle options.""" display_name = "Key Drop Shuffle" - class DungeonCounters(Choice): """On: Always display amount of items checked in a dungeon. Pickup: Show when compass is picked up. Default: Show when compass is picked up if the compass itself is shuffled. Off: Never show item count in dungeons.""" diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index a87bfd5b0c..b86a793fb9 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -420,11 +420,7 @@ def global_rules(world, player): set_rule(world.get_location('Ice Palace - Jelly Key Drop', player), lambda state: can_melt_things(state, player)) set_rule(world.get_location('Ice Palace - Compass Chest', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player)) set_rule(world.get_entrance('Ice Palace (Second Section)', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player) and can_use_bombs(state, player)) - if not world.enemy_shuffle[player]: - # Stalfos Knights can be killed by damaging them repeatedly with boomerang, swords, etc. if bombs are - # unavailable. If bombs are available, the pots can be thrown at them, so no other weapons are needed - add_rule(world.get_entrance('Ice Palace (Second Section)', player), lambda state: (can_use_bombs(state, player) - or state.has('Blue Boomerang', player) or state.has('Red Boomerang', player) or has_sword(state, player) or state.has("Hammer", player))) + set_rule(world.get_entrance('Ice Palace (Main)', player), lambda state: state._lttp_has_key('Small Key (Ice Palace)', player, 2)) set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player)) set_rule(world.get_entrance('Ice Palace (Kholdstare)', player), lambda state: can_lift_rocks(state, player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state._lttp_has_key('Small Key (Ice Palace)', player, 6) or (state.has('Cane of Somaria', player) and state._lttp_has_key('Small Key (Ice Palace)', player, 5)))) diff --git a/worlds/alttp/test/dungeons/TestAgahnimsTower.py b/worlds/alttp/test/dungeons/TestAgahnimsTower.py index c44a92be1e..93c3f60463 100644 --- a/worlds/alttp/test/dungeons/TestAgahnimsTower.py +++ b/worlds/alttp/test/dungeons/TestAgahnimsTower.py @@ -23,14 +23,14 @@ class TestAgahnimsTower(TestDungeon): ["Castle Tower - Dark Archer Key Drop", True, ['Progressive Sword', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp']], ["Castle Tower - Circle of Pots Key Drop", False, []], - ["Castle Tower - Circle of Pots Key Drop", False, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)']], + ["Castle Tower - Circle of Pots Key Drop", False, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)']], ["Castle Tower - Circle of Pots Key Drop", False, [], ['Lamp']], ["Castle Tower - Circle of Pots Key Drop", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']], - ["Castle Tower - Circle of Pots Key Drop", True, ['Progressive Sword', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp']], + ["Castle Tower - Circle of Pots Key Drop", True, ['Progressive Sword', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp']], ["Agahnim 1", False, []], - ["Agahnim 1", False, ['Small Key (Agahnims Tower)'], ['Small Key (Agahnims Tower)']], + ["Agahnim 1", False, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)']], ["Agahnim 1", False, [], ['Progressive Sword']], ["Agahnim 1", False, [], ['Lamp']], - ["Agahnim 1", True, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp', 'Progressive Sword']], + ["Agahnim 1", True, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp', 'Progressive Sword']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/dungeons/TestDesertPalace.py b/worlds/alttp/test/dungeons/TestDesertPalace.py index 2d19513911..58e441f945 100644 --- a/worlds/alttp/test/dungeons/TestDesertPalace.py +++ b/worlds/alttp/test/dungeons/TestDesertPalace.py @@ -19,35 +19,35 @@ class TestDesertPalace(TestDungeon): ["Desert Palace - Compass Chest", False, []], ["Desert Palace - Compass Chest", False, [], ['Small Key (Desert Palace)']], ["Desert Palace - Compass Chest", False, ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']], - ["Desert Palace - Compass Chest", False, ['Small Key (Desert Palace)']], - ["Desert Palace - Compass Chest", True, ['Progressive Sword', 'Small Key (Desert Palace)']], + ["Desert Palace - Compass Chest", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)']], + ["Desert Palace - Compass Chest", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)']], ["Desert Palace - Big Key Chest", False, []], ["Desert Palace - Big Key Chest", False, [], ['Small Key (Desert Palace)']], ["Desert Palace - Big Key Chest", False, ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']], - ["Desert Palace - Big Key Chest", False, ['Small Key (Desert Palace)']], - ["Desert Palace - Big Key Chest", True, ['Progressive Sword', 'Small Key (Desert Palace)']], + ["Desert Palace - Big Key Chest", False, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)']], + ["Desert Palace - Big Key Chest", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)']], ["Desert Palace - Desert Tiles 1 Pot Key", True, []], ["Desert Palace - Beamos Hall Pot Key", False, []], - ["Desert Palace - Beamos Hall Pot Key", False, [], ['Small Key (Desert Palace)']], + ["Desert Palace - Beamos Hall Pot Key", False, ['Small Key (Desert Palace)']], ["Desert Palace - Beamos Hall Pot Key", False, ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']], - ["Desert Palace - Beamos Hall Pot Key", True, ['Small Key (Desert Palace)', 'Progressive Sword']], + ["Desert Palace - Beamos Hall Pot Key", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Progressive Sword']], ["Desert Palace - Desert Tiles 2 Pot Key", False, []], - ["Desert Palace - Desert Tiles 2 Pot Key", False, ['Small Key (Desert Palace)']], + ["Desert Palace - Desert Tiles 2 Pot Key", False, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)']], ["Desert Palace - Desert Tiles 2 Pot Key", False, ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']], - ["Desert Palace - Desert Tiles 2 Pot Key", True, ['Small Key (Desert Palace)', 'Progressive Sword']], + ["Desert Palace - Desert Tiles 2 Pot Key", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Progressive Sword']], ["Desert Palace - Boss", False, []], - ["Desert Palace - Boss", False, [], ['Small Key (Desert Palace)']], + ["Desert Palace - Boss", False, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)']], ["Desert Palace - Boss", False, [], ['Big Key (Desert Palace)']], ["Desert Palace - Boss", False, [], ['Lamp', 'Fire Rod']], ["Desert Palace - Boss", False, [], ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']], - ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Fire Rod']], - ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Progressive Sword']], - ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Hammer']], - ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Cane of Somaria']], - ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Cane of Byrna']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Fire Rod']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Progressive Sword']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Hammer']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Cane of Somaria']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Cane of Byrna']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/dungeons/TestEasternPalace.py b/worlds/alttp/test/dungeons/TestEasternPalace.py index c1a978343b..ee8b7a1624 100644 --- a/worlds/alttp/test/dungeons/TestEasternPalace.py +++ b/worlds/alttp/test/dungeons/TestEasternPalace.py @@ -19,12 +19,12 @@ class TestEasternPalace(TestDungeon): ["Eastern Palace - Big Key Chest", False, []], ["Eastern Palace - Big Key Chest", False, [], ['Lamp']], ["Eastern Palace - Big Key Chest", True, ['Lamp', 'Small Key (Eastern Palace)', 'Small Key (Eastern Palace)', 'Progressive Sword']], - ["Eastern Palace - Big Key Chest", True, ['Lamp', 'Big Key (Eastern Palace)', 'Progressive Sword']], #@todo: Advanced? ["Eastern Palace - Boss", False, []], ["Eastern Palace - Boss", False, [], ['Lamp']], ["Eastern Palace - Boss", False, [], ['Progressive Bow']], ["Eastern Palace - Boss", False, [], ['Big Key (Eastern Palace)']], - ["Eastern Palace - Boss", True, ['Lamp', 'Progressive Bow', 'Big Key (Eastern Palace)']] + ["Eastern Palace - Boss", False, ['Small Key (Eastern Palace)', 'Small Key (Eastern Palace)']], + ["Eastern Palace - Boss", True, ['Lamp', 'Small Key (Eastern Palace)', 'Small Key (Eastern Palace)', 'Progressive Bow', 'Big Key (Eastern Palace)']] ]) diff --git a/worlds/alttp/test/dungeons/TestGanonsTower.py b/worlds/alttp/test/dungeons/TestGanonsTower.py index 98bc6fa552..1e70f580de 100644 --- a/worlds/alttp/test/dungeons/TestGanonsTower.py +++ b/worlds/alttp/test/dungeons/TestGanonsTower.py @@ -33,50 +33,46 @@ class TestGanonsTower(TestDungeon): ["Ganons Tower - Randomizer Room - Top Left", False, []], ["Ganons Tower - Randomizer Room - Top Left", False, [], ['Hammer']], ["Ganons Tower - Randomizer Room - Top Left", False, [], ['Hookshot']], - ["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Fire Rod', 'Cane of Somaria']], - ["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], ["Ganons Tower - Randomizer Room - Top Right", False, []], ["Ganons Tower - Randomizer Room - Top Right", False, [], ['Hammer']], ["Ganons Tower - Randomizer Room - Top Right", False, [], ['Hookshot']], - ["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Fire Rod', 'Cane of Somaria']], - ["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], ["Ganons Tower - Randomizer Room - Bottom Left", False, []], ["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Hammer']], ["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Hookshot']], - ["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Fire Rod', 'Cane of Somaria']], - ["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], ["Ganons Tower - Randomizer Room - Bottom Right", False, []], ["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Hammer']], ["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Hookshot']], - ["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Fire Rod', 'Cane of Somaria']], - ["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], ["Ganons Tower - Firesnake Room", False, []], ["Ganons Tower - Firesnake Room", False, [], ['Hammer']], ["Ganons Tower - Firesnake Room", False, [], ['Hookshot']], - ["Ganons Tower - Firesnake Room", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Firesnake Room", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], ["Ganons Tower - Map Chest", False, []], ["Ganons Tower - Map Chest", False, [], ['Hammer']], ["Ganons Tower - Map Chest", False, [], ['Hookshot', 'Pegasus Boots']], - ["Ganons Tower - Map Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], - ["Ganons Tower - Map Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hammer', 'Pegasus Boots']], + ["Ganons Tower - Map Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Map Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hammer', 'Pegasus Boots']], ["Ganons Tower - Big Chest", False, []], ["Ganons Tower - Big Chest", False, [], ['Big Key (Ganons Tower)']], - ["Ganons Tower - Big Chest", True, ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], - ["Ganons Tower - Big Chest", True, ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Big Chest", True, ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Big Chest", True, ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], ["Ganons Tower - Hope Room - Left", True, []], ["Ganons Tower - Hope Room - Right", True, []], ["Ganons Tower - Bob's Chest", False, []], - ["Ganons Tower - Bob's Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], - ["Ganons Tower - Bob's Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Bob's Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Bob's Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], ["Ganons Tower - Tile Room", False, []], ["Ganons Tower - Tile Room", False, [], ['Cane of Somaria']], @@ -85,34 +81,34 @@ class TestGanonsTower(TestDungeon): ["Ganons Tower - Compass Room - Top Left", False, []], ["Ganons Tower - Compass Room - Top Left", False, [], ['Cane of Somaria']], ["Ganons Tower - Compass Room - Top Left", False, [], ['Fire Rod']], - ["Ganons Tower - Compass Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], + ["Ganons Tower - Compass Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], ["Ganons Tower - Compass Room - Top Right", False, []], ["Ganons Tower - Compass Room - Top Right", False, [], ['Cane of Somaria']], ["Ganons Tower - Compass Room - Top Right", False, [], ['Fire Rod']], - ["Ganons Tower - Compass Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], + ["Ganons Tower - Compass Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], ["Ganons Tower - Compass Room - Bottom Left", False, []], ["Ganons Tower - Compass Room - Bottom Left", False, [], ['Cane of Somaria']], ["Ganons Tower - Compass Room - Bottom Left", False, [], ['Fire Rod']], - ["Ganons Tower - Compass Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], + ["Ganons Tower - Compass Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], ["Ganons Tower - Compass Room - Bottom Right", False, []], ["Ganons Tower - Compass Room - Bottom Right", False, [], ['Cane of Somaria']], ["Ganons Tower - Compass Room - Bottom Right", False, [], ['Fire Rod']], - ["Ganons Tower - Compass Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], + ["Ganons Tower - Compass Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']], ["Ganons Tower - Big Key Chest", False, []], - ["Ganons Tower - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], - ["Ganons Tower - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], ["Ganons Tower - Big Key Room - Left", False, []], - ["Ganons Tower - Big Key Room - Left", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], - ["Ganons Tower - Big Key Room - Left", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Big Key Room - Left", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Big Key Room - Left", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], ["Ganons Tower - Big Key Room - Right", False, []], - ["Ganons Tower - Big Key Room - Right", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], - ["Ganons Tower - Big Key Room - Right", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], + ["Ganons Tower - Big Key Room - Right", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']], + ["Ganons Tower - Big Key Room - Right", True, ['Bomb Upgrade (+5)', 'Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']], ["Ganons Tower - Mini Helmasaur Room - Left", False, []], ["Ganons Tower - Mini Helmasaur Room - Left", False, [], ['Progressive Bow']], @@ -132,8 +128,8 @@ class TestGanonsTower(TestDungeon): ["Ganons Tower - Pre-Moldorm Chest", False, [], ['Progressive Bow']], ["Ganons Tower - Pre-Moldorm Chest", False, [], ['Big Key (Ganons Tower)']], ["Ganons Tower - Pre-Moldorm Chest", False, [], ['Lamp', 'Fire Rod']], - ["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp']], - ["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod']], + ["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp']], + ["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod']], ["Ganons Tower - Validation Chest", False, []], ["Ganons Tower - Validation Chest", False, [], ['Hookshot']], @@ -141,8 +137,8 @@ class TestGanonsTower(TestDungeon): ["Ganons Tower - Validation Chest", False, [], ['Big Key (Ganons Tower)']], ["Ganons Tower - Validation Chest", False, [], ['Lamp', 'Fire Rod']], ["Ganons Tower - Validation Chest", False, [], ['Progressive Sword', 'Hammer']], - ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Progressive Sword']], - ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Progressive Sword']], - ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Hammer']], - ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Hammer']], + ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Progressive Sword']], + ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Progressive Sword']], + ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Hammer']], + ["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Hammer']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/dungeons/TestIcePalace.py b/worlds/alttp/test/dungeons/TestIcePalace.py index 7a15c5c097..8686315873 100644 --- a/worlds/alttp/test/dungeons/TestIcePalace.py +++ b/worlds/alttp/test/dungeons/TestIcePalace.py @@ -12,8 +12,8 @@ class TestIcePalace(TestDungeon): ["Ice Palace - Big Key Chest", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Big Key Chest", False, [], ['Fire Rod', 'Progressive Sword']], ["Ice Palace - Big Key Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Ice Palace - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], - ["Ice Palace - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + ["Ice Palace - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], #@todo: Change from item randomizer - Right side key door is only in logic if big key is in there #["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], #["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], @@ -23,8 +23,8 @@ class TestIcePalace(TestDungeon): ["Ice Palace - Compass Chest", False, []], ["Ice Palace - Compass Chest", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Compass Chest", False, [], ['Fire Rod', 'Progressive Sword']], - ["Ice Palace - Compass Chest", True, ['Fire Rod']], - ["Ice Palace - Compass Chest", True, ['Bombos', 'Progressive Sword']], + ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Fire Rod']], + ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Bombos', 'Progressive Sword']], ["Ice Palace - Map Chest", False, []], ["Ice Palace - Map Chest", False, [], ['Hammer']], @@ -32,8 +32,8 @@ class TestIcePalace(TestDungeon): ["Ice Palace - Map Chest", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Map Chest", False, [], ['Fire Rod', 'Progressive Sword']], ["Ice Palace - Map Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Ice Palace - Map Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], - ["Ice Palace - Map Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Map Chest", True, ['Small Key (Ice Palace)', 'Bomb Upgrade (+5)', 'Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Map Chest", True, ['Small Key (Ice Palace)', 'Bomb Upgrade (+5)', 'Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']], #["Ice Palace - Map Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], #["Ice Palace - Map Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], #["Ice Palace - Map Chest", True, ['Progressive Glove', 'Cape', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], @@ -43,8 +43,8 @@ class TestIcePalace(TestDungeon): ["Ice Palace - Spike Room", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Spike Room", False, [], ['Fire Rod', 'Progressive Sword']], ["Ice Palace - Spike Room", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Ice Palace - Spike Room", True, ['Bomb Upgrade (+5)', 'Fire Rod', 'Hookshot', 'Small Key (Ice Palace)']], - ["Ice Palace - Spike Room", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword', 'Hookshot', 'Small Key (Ice Palace)']], + ["Ice Palace - Spike Room", True, ['Bomb Upgrade (+5)', 'Fire Rod', 'Hookshot', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + ["Ice Palace - Spike Room", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword', 'Hookshot', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], #["Ice Palace - Spike Room", True, ['Cape', 'Fire Rod', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], #["Ice Palace - Spike Room", True, ['Cape', 'Bombos', 'Progressive Sword', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], #["Ice Palace - Spike Room", True, ['Cane of Byrna', 'Fire Rod', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], @@ -54,23 +54,23 @@ class TestIcePalace(TestDungeon): ["Ice Palace - Freezor Chest", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Freezor Chest", False, [], ['Fire Rod', 'Progressive Sword']], ["Ice Palace - Freezor Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Ice Palace - Freezor Chest", True, ['Bomb Upgrade (+5)', 'Fire Rod']], - ["Ice Palace - Freezor Chest", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword']], + ["Ice Palace - Freezor Chest", True, ['Bomb Upgrade (+5)', 'Fire Rod', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + ["Ice Palace - Freezor Chest", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], ["Ice Palace - Iced T Room", False, []], ["Ice Palace - Iced T Room", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Iced T Room", False, [], ['Fire Rod', 'Progressive Sword']], ["Ice Palace - Iced T Room", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Ice Palace - Iced T Room", True, ['Bomb Upgrade (+5)', 'Fire Rod']], - ["Ice Palace - Iced T Room", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword']], + ["Ice Palace - Iced T Room", True, ['Bomb Upgrade (+5)', 'Fire Rod', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], + ["Ice Palace - Iced T Room", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']], ["Ice Palace - Big Chest", False, []], ["Ice Palace - Big Chest", False, [], ['Big Key (Ice Palace)']], ["Ice Palace - Big Chest", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Big Chest", False, [], ['Fire Rod', 'Progressive Sword']], ["Ice Palace - Big Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Ice Palace - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Ice Palace)', 'Fire Rod']], - ["Ice Palace - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword']], + ["Ice Palace - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Fire Rod']], + ["Ice Palace - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Bombos', 'Progressive Sword']], ["Ice Palace - Boss", False, []], ["Ice Palace - Boss", False, [], ['Hammer']], @@ -80,8 +80,8 @@ class TestIcePalace(TestDungeon): ["Ice Palace - Boss", False, [], ['Fire Rod', 'Progressive Sword']], ["Ice Palace - Boss", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], # need hookshot now to reach the right side for the 6th key - ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], - ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Hookshot']], - ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], - ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Hookshot']], + ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], + ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], + ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], + ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/dungeons/TestMiseryMire.py b/worlds/alttp/test/dungeons/TestMiseryMire.py index 6cbf42922f..ca74e9365e 100644 --- a/worlds/alttp/test/dungeons/TestMiseryMire.py +++ b/worlds/alttp/test/dungeons/TestMiseryMire.py @@ -32,36 +32,32 @@ class TestMiseryMire(TestDungeon): ["Misery Mire - Main Lobby", False, []], ["Misery Mire - Main Lobby", False, [], ['Pegasus Boots', 'Hookshot']], ["Misery Mire - Main Lobby", False, [], ['Small Key (Misery Mire)', 'Big Key (Misery Mire)']], - ["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Hookshot', 'Progressive Sword']], - ["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Pegasus Boots', 'Progressive Sword']], - ["Misery Mire - Main Lobby", True, ['Big Key (Misery Mire)', 'Hookshot', 'Progressive Sword']], - ["Misery Mire - Main Lobby", True, ['Big Key (Misery Mire)', 'Pegasus Boots', 'Progressive Sword']], + ["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Hookshot', 'Progressive Sword']], + ["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Pegasus Boots', 'Progressive Sword']], ["Misery Mire - Big Key Chest", False, []], ["Misery Mire - Big Key Chest", False, [], ['Fire Rod', 'Lamp']], ["Misery Mire - Big Key Chest", False, [], ['Pegasus Boots', 'Hookshot']], - ["Misery Mire - Big Key Chest", False, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)'], ['Small Key (Misery Mire)']], - ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Pegasus Boots']], - ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Hookshot']], - ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Pegasus Boots']], - ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Hookshot']], + ["Misery Mire - Big Key Chest", False, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)']], + ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Hookshot']], + ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Hookshot']], ["Misery Mire - Compass Chest", False, []], ["Misery Mire - Compass Chest", False, [], ['Fire Rod', 'Lamp']], ["Misery Mire - Compass Chest", False, [], ['Pegasus Boots', 'Hookshot']], - ["Misery Mire - Compass Chest", False, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)'], ['Small Key (Misery Mire)']], - ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Pegasus Boots']], - ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Hookshot']], - ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Pegasus Boots']], - ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Hookshot']], + ["Misery Mire - Compass Chest", False, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)']], + ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Hookshot']], + ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Hookshot']], ["Misery Mire - Map Chest", False, []], - ["Misery Mire - Map Chest", False, [], ['Small Key (Misery Mire)', 'Big Key (Misery Mire)']], + ["Misery Mire - Map Chest", False, [], ['Small Key (Misery Mire)']], ["Misery Mire - Map Chest", False, [], ['Pegasus Boots', 'Hookshot']], - ["Misery Mire - Map Chest", True, ['Small Key (Misery Mire)', 'Progressive Sword', 'Pegasus Boots']], - ["Misery Mire - Map Chest", True, ['Small Key (Misery Mire)', 'Progressive Sword', 'Hookshot']], - ["Misery Mire - Map Chest", True, ['Big Key (Misery Mire)', 'Progressive Sword', 'Pegasus Boots']], - ["Misery Mire - Map Chest", True, ['Big Key (Misery Mire)', 'Progressive Sword', 'Hookshot']], + ["Misery Mire - Map Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Progressive Sword', 'Pegasus Boots']], + ["Misery Mire - Map Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Progressive Sword', 'Hookshot']], ["Misery Mire - Spike Chest", False, []], ["Misery Mire - Spike Chest", False, [], ['Pegasus Boots', 'Hookshot']], diff --git a/worlds/alttp/test/dungeons/TestSkullWoods.py b/worlds/alttp/test/dungeons/TestSkullWoods.py index 55c8d2e29a..7650e785c8 100644 --- a/worlds/alttp/test/dungeons/TestSkullWoods.py +++ b/worlds/alttp/test/dungeons/TestSkullWoods.py @@ -96,6 +96,6 @@ class TestSkullWoods(TestDungeon): ["Skull Woods - Boss", False, []], ["Skull Woods - Boss", False, [], ['Fire Rod']], ["Skull Woods - Boss", False, [], ['Progressive Sword']], - ["Skull Woods - Boss", False, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)'], ['Small Key (Skull Woods)']], - ["Skull Woods - Boss", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Fire Rod', 'Progressive Sword']], + ["Skull Woods - Boss", False, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)'], ['Small Key (Skull Woods)']], + ["Skull Woods - Boss", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Fire Rod', 'Progressive Sword']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/dungeons/TestSwampPalace.py b/worlds/alttp/test/dungeons/TestSwampPalace.py index bddf40616f..fb0672a5a9 100644 --- a/worlds/alttp/test/dungeons/TestSwampPalace.py +++ b/worlds/alttp/test/dungeons/TestSwampPalace.py @@ -16,15 +16,15 @@ class TestSwampPalace(TestDungeon): ["Swamp Palace - Big Chest", False, [], ['Open Floodgate']], ["Swamp Palace - Big Chest", False, [], ['Hammer']], ["Swamp Palace - Big Chest", False, [], ['Big Key (Swamp Palace)']], - ["Swamp Palace - Big Chest", False, [], ['Small Key (Swamp Palace)']], - ["Swamp Palace - Big Chest", True, ['Open Floodgate', 'Big Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']], + ["Swamp Palace - Big Chest", False, [], ['Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)']], + ["Swamp Palace - Big Chest", True, ['Open Floodgate', 'Big Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']], ["Swamp Palace - Big Key Chest", False, []], ["Swamp Palace - Big Key Chest", False, [], ['Flippers']], ["Swamp Palace - Big Key Chest", False, [], ['Open Floodgate']], ["Swamp Palace - Big Key Chest", False, [], ['Hammer']], ["Swamp Palace - Big Key Chest", False, [], ['Small Key (Swamp Palace)']], - ["Swamp Palace - Big Key Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']], + ["Swamp Palace - Big Key Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']], ["Swamp Palace - Map Chest", False, []], ["Swamp Palace - Map Chest", False, [], ['Flippers']], @@ -38,14 +38,14 @@ class TestSwampPalace(TestDungeon): ["Swamp Palace - West Chest", False, [], ['Open Floodgate']], ["Swamp Palace - West Chest", False, [], ['Hammer']], ["Swamp Palace - West Chest", False, [], ['Small Key (Swamp Palace)']], - ["Swamp Palace - West Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']], + ["Swamp Palace - West Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']], ["Swamp Palace - Compass Chest", False, []], ["Swamp Palace - Compass Chest", False, [], ['Flippers']], ["Swamp Palace - Compass Chest", False, [], ['Open Floodgate']], ["Swamp Palace - Compass Chest", False, [], ['Hammer']], ["Swamp Palace - Compass Chest", False, [], ['Small Key (Swamp Palace)']], - ["Swamp Palace - Compass Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']], + ["Swamp Palace - Compass Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']], ["Swamp Palace - Flooded Room - Left", False, []], ["Swamp Palace - Flooded Room - Left", False, [], ['Flippers']], @@ -53,7 +53,7 @@ class TestSwampPalace(TestDungeon): ["Swamp Palace - Flooded Room - Left", False, [], ['Hammer']], ["Swamp Palace - Flooded Room - Left", False, [], ['Hookshot']], ["Swamp Palace - Flooded Room - Left", False, [], ['Small Key (Swamp Palace)']], - ["Swamp Palace - Flooded Room - Left", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], + ["Swamp Palace - Flooded Room - Left", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], ["Swamp Palace - Flooded Room - Right", False, []], ["Swamp Palace - Flooded Room - Right", False, [], ['Flippers']], @@ -61,7 +61,7 @@ class TestSwampPalace(TestDungeon): ["Swamp Palace - Flooded Room - Right", False, [], ['Hammer']], ["Swamp Palace - Flooded Room - Right", False, [], ['Hookshot']], ["Swamp Palace - Flooded Room - Right", False, [], ['Small Key (Swamp Palace)']], - ["Swamp Palace - Flooded Room - Right", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], + ["Swamp Palace - Flooded Room - Right", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], ["Swamp Palace - Waterfall Room", False, []], ["Swamp Palace - Waterfall Room", False, [], ['Flippers']], @@ -69,7 +69,7 @@ class TestSwampPalace(TestDungeon): ["Swamp Palace - Waterfall Room", False, [], ['Hammer']], ["Swamp Palace - Waterfall Room", False, [], ['Hookshot']], ["Swamp Palace - Waterfall Room", False, [], ['Small Key (Swamp Palace)']], - ["Swamp Palace - Waterfall Room", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], + ["Swamp Palace - Waterfall Room", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], ["Swamp Palace - Boss", False, []], ["Swamp Palace - Boss", False, [], ['Flippers']], @@ -77,5 +77,5 @@ class TestSwampPalace(TestDungeon): ["Swamp Palace - Boss", False, [], ['Hammer']], ["Swamp Palace - Boss", False, [], ['Hookshot']], ["Swamp Palace - Boss", False, [], ['Small Key (Swamp Palace)']], - ["Swamp Palace - Boss", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], + ["Swamp Palace - Boss", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/dungeons/TestThievesTown.py b/worlds/alttp/test/dungeons/TestThievesTown.py index 752b530577..342823c910 100644 --- a/worlds/alttp/test/dungeons/TestThievesTown.py +++ b/worlds/alttp/test/dungeons/TestThievesTown.py @@ -21,18 +21,19 @@ class TestThievesTown(TestDungeon): ["Thieves' Town - Spike Switch Pot Key", False, []], ["Thieves' Town - Spike Switch Pot Key", False, [], ['Big Key (Thieves Town)']], - ["Thieves' Town - Spike Switch Pot Key", True, ['Big Key (Thieves Town)']], + ["Thieves' Town - Spike Switch Pot Key", False, [], ['Small Key (Thieves Town)']], + ["Thieves' Town - Spike Switch Pot Key", True, ['Big Key (Thieves Town)', 'Small Key (Thieves Town)']], ["Thieves' Town - Attic", False, []], ["Thieves' Town - Attic", False, [], ['Big Key (Thieves Town)']], ["Thieves' Town - Attic", False, [], ['Small Key (Thieves Town)']], - ["Thieves' Town - Attic", True, ['Big Key (Thieves Town)', 'Small Key (Thieves Town)']], + ["Thieves' Town - Attic", True, ['Big Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)']], ["Thieves' Town - Big Chest", False, []], ["Thieves' Town - Big Chest", False, [], ['Big Key (Thieves Town)']], ["Thieves' Town - Big Chest", False, [], ['Small Key (Thieves Town)']], ["Thieves' Town - Big Chest", False, [], ['Hammer']], - ["Thieves' Town - Big Chest", True, ['Hammer', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)']], + ["Thieves' Town - Big Chest", True, ['Hammer', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)']], ["Thieves' Town - Blind's Cell", False, []], ["Thieves' Town - Blind's Cell", False, [], ['Big Key (Thieves Town)']], @@ -42,8 +43,8 @@ class TestThievesTown(TestDungeon): ["Thieves' Town - Boss", False, [], ['Big Key (Thieves Town)']], ["Thieves' Town - Boss", False, [], ['Hammer', 'Progressive Sword', 'Cane of Somaria', 'Cane of Byrna']], ["Thieves' Town - Boss", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Hammer']], - ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Progressive Sword']], - ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Somaria']], - ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Byrna']], + ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Hammer']], + ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Progressive Sword']], + ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Somaria']], + ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Byrna']], ]) \ No newline at end of file diff --git a/worlds/alttp/test/inverted/TestInvertedTurtleRock.py b/worlds/alttp/test/inverted/TestInvertedTurtleRock.py index f3698c90ff..db3084b02a 100644 --- a/worlds/alttp/test/inverted/TestInvertedTurtleRock.py +++ b/worlds/alttp/test/inverted/TestInvertedTurtleRock.py @@ -11,16 +11,16 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Compass Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']], ["Turtle Rock - Compass Chest", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria']], ["Turtle Rock - Compass Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']], - ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Compass Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", False, []], ["Turtle Rock - Chain Chomps", False, [], ['Magic Mirror', 'Cane of Somaria']], ["Turtle Rock - Chain Chomps", False, ['Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", True, ['Bomb Upgrade (+5)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']], @@ -33,10 +33,10 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Roller Room - Left", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']], ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria']], ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']], - ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Roller Room - Left", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Roller Room - Right", False, []], ["Turtle Rock - Roller Room - Right", False, [], ['Cane of Somaria']], @@ -45,17 +45,17 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Roller Room - Right", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']], ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria']], ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']], - ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Roller Room - Right", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Big Chest", False, []], ["Turtle Rock - Big Chest", False, [], ['Big Key (Turtle Rock)']], ["Turtle Rock - Big Chest", False, [], ['Magic Mirror', 'Cane of Somaria']], ["Turtle Rock - Big Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']], ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hookshot']], ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], @@ -66,12 +66,12 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Big Key Chest", False, []], ["Turtle Rock - Big Key Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], ["Turtle Rock - Big Key Chest", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], # Mirror in from ledge, use left side entrance, have enough keys to get to the chest - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Crystaroller Room", False, []], ["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], @@ -80,7 +80,7 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Crystaroller Room", False, [], ['Magic Mirror', 'Cane of Somaria']], ["Turtle Rock - Crystaroller Room", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']], @@ -98,10 +98,10 @@ class TestInvertedTurtleRock(TestInverted): ["Turtle Rock - Boss", False, [], ['Magic Mirror', 'Lamp']], ["Turtle Rock - Boss", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], - ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], - ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], - ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], - ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', 'Magic Mirror', 'Moon Pearl', 'Hookshot', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']] + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', 'Magic Mirror', 'Moon Pearl', 'Hookshot', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']] ]) @@ -118,11 +118,11 @@ class TestInvertedTurtleRock(TestInverted): [location, False, [], ['Magic Mirror', 'Lamp']], [location, False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], [location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']], - [location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], + [location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], [location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']], - [location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], + [location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], [location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - [location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + [location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], # Mirroring into Eye Bridge does not require Cane of Somaria [location, True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']], diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py index 3c75a2c368..a416e1b35d 100644 --- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py +++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py @@ -11,17 +11,17 @@ class TestInvertedTurtleRock(TestInvertedMinor): ["Turtle Rock - Compass Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']], ["Turtle Rock - Compass Chest", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria']], ["Turtle Rock - Compass Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']], - ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Compass Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", False, []], ["Turtle Rock - Chain Chomps", False, [], ['Magic Mirror', 'Cane of Somaria']], # Item rando only needs 1 key. ER needs to consider the case when the back is accessible, but not the middle (key wasted on Trinexx door) ["Turtle Rock - Chain Chomps", False, ['Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", True, ['Bomb Upgrade (+5)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], ["Turtle Rock - Chain Chomps", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']], @@ -34,10 +34,10 @@ class TestInvertedTurtleRock(TestInvertedMinor): ["Turtle Rock - Roller Room - Left", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']], ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria']], ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']], - ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Roller Room - Left", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Roller Room - Right", False, []], ["Turtle Rock - Roller Room - Right", False, [], ['Cane of Somaria']], @@ -46,17 +46,17 @@ class TestInvertedTurtleRock(TestInvertedMinor): ["Turtle Rock - Roller Room - Right", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']], ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria']], ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']], - ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Roller Room - Right", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Big Chest", False, []], ["Turtle Rock - Big Chest", False, [], ['Big Key (Turtle Rock)']], ["Turtle Rock - Big Chest", False, [], ['Magic Mirror', 'Cane of Somaria']], ["Turtle Rock - Big Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']], ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hookshot']], ["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], @@ -67,12 +67,12 @@ class TestInvertedTurtleRock(TestInvertedMinor): ["Turtle Rock - Big Key Chest", False, []], ["Turtle Rock - Big Key Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], ["Turtle Rock - Big Key Chest", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], # Mirror in from ledge, use left side entrance, have enough keys to get to the chest - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Big Key Chest", True, ['Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Big Key Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Crystaroller Room", False, []], ["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']], @@ -81,7 +81,7 @@ class TestInvertedTurtleRock(TestInvertedMinor): ["Turtle Rock - Crystaroller Room", False, [], ['Magic Mirror', 'Cane of Somaria']], ["Turtle Rock - Crystaroller Room", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']], ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']], ["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']], @@ -99,10 +99,10 @@ class TestInvertedTurtleRock(TestInvertedMinor): ["Turtle Rock - Boss", False, [], ['Magic Mirror', 'Lamp']], ["Turtle Rock - Boss", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], - ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], - ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], - ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], - ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', 'Magic Mirror', 'Moon Pearl', 'Hookshot', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']] + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']], + ["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', 'Magic Mirror', 'Moon Pearl', 'Hookshot', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']] ]) @@ -117,11 +117,11 @@ class TestInvertedTurtleRock(TestInvertedMinor): [location, False, [], ['Magic Mirror', 'Lamp']], [location, False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']], [location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']], - [location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], + [location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']], [location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']], - [location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], + [location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']], [location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], - [location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], + [location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']], # Mirroring into Eye Bridge does not require Cane of Somaria [location, True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']], diff --git a/worlds/alttp/test/inverted_owg/TestDungeons.py b/worlds/alttp/test/inverted_owg/TestDungeons.py index 0d8445895e..53b12bdf89 100644 --- a/worlds/alttp/test/inverted_owg/TestDungeons.py +++ b/worlds/alttp/test/inverted_owg/TestDungeons.py @@ -13,16 +13,15 @@ class TestDungeons(TestInvertedOWG): ["Sanctuary", False, []], ["Sanctuary", False, ['Beat Agahnim 1']], ["Sanctuary", True, ['Magic Mirror', 'Beat Agahnim 1']], - ["Sanctuary", True, ['Progressive Sword', 'Lamp', 'Beat Agahnim 1', 'Small Key (Hyrule Castle)']], + ["Sanctuary", True, ['Lamp', 'Beat Agahnim 1', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']], ["Sanctuary", True, ['Moon Pearl', 'Pegasus Boots']], ["Sanctuary", True, ['Magic Mirror', 'Pegasus Boots']], ["Sewers - Secret Room - Left", False, []], ["Sewers - Secret Room - Left", True, ['Moon Pearl', 'Progressive Glove', 'Pegasus Boots']], - ["Sewers - Secret Room - Left", True, ['Progressive Sword', 'Moon Pearl', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)']], - ["Sewers - Secret Room - Left", True, ['Progressive Sword', 'Magic Mirror', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)']], - ["Sewers - Secret Room - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Beat Agahnim 1', 'Lamp', 'Small Key (Hyrule Castle)']], - ["Sewers - Secret Room - Left", True, ['Bomb Upgrade (+10)', 'Beat Agahnim 1', 'Lamp', 'Small Key (Hyrule Castle)']], + ["Sewers - Secret Room - Left", True, ['Moon Pearl', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']], + ["Sewers - Secret Room - Left", True, ['Magic Mirror', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']], + ["Sewers - Secret Room - Left", True, ['Bomb Upgrade (+5)', 'Beat Agahnim 1', 'Lamp', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']], ["Eastern Palace - Compass Chest", False, []], ["Eastern Palace - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots']], @@ -37,8 +36,8 @@ class TestDungeons(TestInvertedOWG): ["Desert Palace - Boss", False, [], ['Small Key (Desert Palace)']], ["Desert Palace - Boss", False, [], ['Big Key (Desert Palace)']], ["Desert Palace - Boss", False, [], ['Lamp', 'Fire Rod']], - ["Desert Palace - Boss", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Lamp']], - ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Fire Rod']], + ["Desert Palace - Boss", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Lamp']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Fire Rod']], ["Tower of Hera - Basement Cage", False, []], ["Tower of Hera - Basement Cage", False, [], ['Moon Pearl']], @@ -76,8 +75,8 @@ class TestDungeons(TestInvertedOWG): ["Ice Palace - Compass Chest", False, []], ["Ice Palace - Compass Chest", False, [], ['Fire Rod', 'Bombos', 'Progressive Sword']], # Qirn Jump - ["Ice Palace - Compass Chest", True, ['Fire Rod']], - ["Ice Palace - Compass Chest", True, ['Bombos', 'Progressive Sword']], + ["Ice Palace - Compass Chest", True, ['Fire Rod', 'Small Key (Ice Palace)']], + ["Ice Palace - Compass Chest", True, ['Bombos', 'Progressive Sword', 'Small Key (Ice Palace)']], ["Misery Mire - Bridge Chest", False, []], ["Misery Mire - Bridge Chest", False, [], ['Ether']], @@ -86,7 +85,7 @@ class TestDungeons(TestInvertedOWG): ["Turtle Rock - Compass Chest", False, []], ["Turtle Rock - Compass Chest", False, [], ['Cane of Somaria']], - ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Quake', 'Progressive Sword', 'Cane of Somaria']], ["Turtle Rock - Chain Chomps", False, []], diff --git a/worlds/alttp/test/minor_glitches/TestDeathMountain.py b/worlds/alttp/test/minor_glitches/TestDeathMountain.py index 4446ee7e8f..7d7589d2f7 100644 --- a/worlds/alttp/test/minor_glitches/TestDeathMountain.py +++ b/worlds/alttp/test/minor_glitches/TestDeathMountain.py @@ -49,7 +49,7 @@ class TestDeathMountain(TestMinor): ["Mimic Cave", False, [], ['Cane of Somaria']], ["Mimic Cave", False, ['Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], ["Mimic Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Spiral Cave", False, []], ["Spiral Cave", False, [], ['Progressive Glove', 'Flute']], diff --git a/worlds/alttp/test/owg/TestDungeons.py b/worlds/alttp/test/owg/TestDungeons.py index f4688b7a35..e43e18d16c 100644 --- a/worlds/alttp/test/owg/TestDungeons.py +++ b/worlds/alttp/test/owg/TestDungeons.py @@ -13,7 +13,7 @@ class TestDungeons(TestVanillaOWG): ["Sewers - Secret Room - Left", False, []], ["Sewers - Secret Room - Left", True, ['Pegasus Boots', 'Progressive Glove']], - ["Sewers - Secret Room - Left", True, ['Progressive Sword', 'Bomb Upgrade (+5)', 'Lamp', 'Small Key (Hyrule Castle)']], + ["Sewers - Secret Room - Left", True, ['Bomb Upgrade (+5)', 'Lamp', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']], ["Eastern Palace - Compass Chest", True, []], @@ -26,8 +26,8 @@ class TestDungeons(TestVanillaOWG): ["Desert Palace - Boss", False, [], ['Small Key (Desert Palace)']], ["Desert Palace - Boss", False, [], ['Big Key (Desert Palace)']], ["Desert Palace - Boss", False, [], ['Lamp', 'Fire Rod']], - ["Desert Palace - Boss", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Pegasus Boots', 'Lamp', 'Big Key (Desert Palace)']], - ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Pegasus Boots', 'Fire Rod', 'Big Key (Desert Palace)']], + ["Desert Palace - Boss", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Pegasus Boots', 'Lamp', 'Big Key (Desert Palace)']], + ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Pegasus Boots', 'Fire Rod', 'Big Key (Desert Palace)']], ["Tower of Hera - Basement Cage", False, []], ["Tower of Hera - Basement Cage", False, [], ['Pegasus Boots', "Flute", "Progressive Glove"]], @@ -90,10 +90,10 @@ class TestDungeons(TestVanillaOWG): ["Ice Palace - Compass Chest", False, []], ["Ice Palace - Compass Chest", False, [], ['Fire Rod', 'Bombos']], ["Ice Palace - Compass Chest", False, [], ['Fire Rod', 'Progressive Sword']], - ["Ice Palace - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Flippers', 'Fire Rod']], - ["Ice Palace - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Flippers', 'Bombos', 'Progressive Sword']], - ["Ice Palace - Compass Chest", True, ['Progressive Glove', 'Progressive Glove', 'Fire Rod']], - ["Ice Palace - Compass Chest", True, ['Progressive Glove', 'Progressive Glove', 'Bombos', 'Progressive Sword']], + ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Moon Pearl', 'Pegasus Boots', 'Flippers', 'Fire Rod']], + ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Moon Pearl', 'Pegasus Boots', 'Flippers', 'Bombos', 'Progressive Sword']], + ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Progressive Glove', 'Progressive Glove', 'Fire Rod']], + ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Progressive Glove', 'Progressive Glove', 'Bombos', 'Progressive Sword']], ["Misery Mire - Bridge Chest", False, []], ["Misery Mire - Bridge Chest", False, [], ['Moon Pearl']], @@ -105,9 +105,9 @@ class TestDungeons(TestVanillaOWG): ["Turtle Rock - Compass Chest", False, [], ['Cane of Somaria']], #todo: does clip require sword? #["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], - ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Sword']], + ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Sword']], ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Cane of Somaria', 'Progressive Sword', 'Quake']], - ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Turtle Rock - Chain Chomps", False, []], #todo: does clip require sword? diff --git a/worlds/alttp/test/vanilla/TestDeathMountain.py b/worlds/alttp/test/vanilla/TestDeathMountain.py index d77f1a8dd2..a559d8869c 100644 --- a/worlds/alttp/test/vanilla/TestDeathMountain.py +++ b/worlds/alttp/test/vanilla/TestDeathMountain.py @@ -49,7 +49,7 @@ class TestDeathMountain(TestVanilla): ["Mimic Cave", False, [], ['Cane of Somaria']], ["Mimic Cave", False, ['Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']], ["Mimic Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']], - ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], + ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']], ["Spiral Cave", False, []], ["Spiral Cave", False, [], ['Progressive Glove', 'Flute']], From 738a9ebb7d5a04f916f80114bb81a27bff1a9b2c Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Mon, 26 Feb 2024 02:30:20 -0500 Subject: [PATCH 034/166] TUNIC: Misc Logic Changes, Additions (#2856) * Add nmg boss scav kill * Add boss quick kills * Fix name of orb * Remove getting into zig with ice grapple * Remove connection from quarry to zig * Add a few missing dependent regions * Separate the atoll statue and portal pad so that it doesn't assume you can get from one to the other without prayer --- worlds/tunic/er_data.py | 27 ++++++++++++++++++++------- worlds/tunic/er_rules.py | 12 ++++++++++-- worlds/tunic/regions.py | 2 +- worlds/tunic/rules.py | 5 ++--- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/worlds/tunic/er_data.py b/worlds/tunic/er_data.py index d76af11339..7678d77fe0 100644 --- a/worlds/tunic/er_data.py +++ b/worlds/tunic/er_data.py @@ -211,7 +211,7 @@ portal_mapping: List[Portal] = [ destination="Shop_"), Portal(name="Atoll to Far Shore", region="Ruined Atoll Portal", destination="Transit_teleporter_atoll"), - Portal(name="Atoll Statue Teleporter", region="Ruined Atoll Portal", + Portal(name="Atoll Statue Teleporter", region="Ruined Atoll Statue", destination="Library Exterior_"), Portal(name="Frog Stairs Eye Entrance", region="Ruined Atoll", destination="Frog Stairs_eye"), @@ -600,6 +600,7 @@ tunic_er_regions: Dict[str, RegionInfo] = { "Ruined Atoll Lower Entry Area": RegionInfo("Atoll Redux"), "Ruined Atoll Frog Mouth": RegionInfo("Atoll Redux"), "Ruined Atoll Portal": RegionInfo("Atoll Redux"), + "Ruined Atoll Statue": RegionInfo("Atoll Redux"), "Frog's Domain Entry": RegionInfo("Frog Stairs"), "Frog's Domain": RegionInfo("frog cave main", hint=Hint.region), "Frog's Domain Back": RegionInfo("frog cave main", hint=Hint.scene), @@ -749,6 +750,8 @@ dependent_regions_restricted: Dict[Tuple[str, ...], List[str]] = { ["Forest Belltower Main", "Forest Belltower Lower"], ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"): ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"], + ("Guard House 1 East", "Guard House 1 West"): + ["Guard House 1 East", "Guard House 1 West"], ("Forest Grave Path Main", "Forest Grave Path Upper"): ["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"], ("Forest Grave Path by Grave", "Forest Hero's Grave"): @@ -762,8 +765,10 @@ dependent_regions_restricted: Dict[Tuple[str, ...], List[str]] = { ("West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave"): ["West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave"], ("West Garden Portal", "West Garden Portal Item"): ["West Garden Portal", "West Garden Portal Item"], - ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"): - ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"], + ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", + "Ruined Atoll Statue"): + ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", + "Ruined Atoll Statue"], ("Frog's Domain",): ["Frog's Domain", "Frog's Domain Back"], ("Library Exterior Ladder", "Library Exterior Tree"): @@ -842,6 +847,8 @@ dependent_regions_nmg: Dict[Tuple[str, ...], List[str]] = { ["Forest Belltower Main", "Forest Belltower Lower"], ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"): ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"], + ("Guard House 1 East", "Guard House 1 West"): + ["Guard House 1 East", "Guard House 1 West"], ("Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"): ["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"], ("Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back"): @@ -854,8 +861,10 @@ dependent_regions_nmg: Dict[Tuple[str, ...], List[str]] = { "West Garden Portal", "West Garden Portal Item"): ["West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave", "West Garden Portal", "West Garden Portal Item"], - ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"): - ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"], + ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", + "Ruined Atoll Statue"): + ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", + "Ruined Atoll Statue"], ("Frog's Domain",): ["Frog's Domain", "Frog's Domain Back"], ("Library Exterior Ladder", "Library Exterior Tree"): @@ -934,6 +943,8 @@ dependent_regions_ur: Dict[Tuple[str, ...], List[str]] = { ["Forest Belltower Main", "Forest Belltower Lower"], ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"): ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"], + ("Guard House 1 East", "Guard House 1 West"): + ["Guard House 1 East", "Guard House 1 West"], # can use laurels, ice grapple, or ladder storage to traverse ("Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"): ["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"], @@ -948,8 +959,10 @@ dependent_regions_ur: Dict[Tuple[str, ...], List[str]] = { "West Garden Portal", "West Garden Portal Item"): ["West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave", "West Garden Portal", "West Garden Portal Item"], - ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"): - ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"], + ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", + "Ruined Atoll Statue"): + ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", + "Ruined Atoll Statue"], ("Frog's Domain",): ["Frog's Domain", "Frog's Domain Back"], ("Library Exterior Ladder", "Library Exterior Tree"): diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index 2f9604c103..a7d0543c3f 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -295,6 +295,12 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re regions["Ruined Atoll Portal"].connect( connecting_region=regions["Ruined Atoll"]) + regions["Ruined Atoll"].connect( + connecting_region=regions["Ruined Atoll Statue"], + rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks)) + regions["Ruined Atoll Statue"].connect( + connecting_region=regions["Ruined Atoll"]) + regions["Frog's Domain"].connect( connecting_region=regions["Frog's Domain Back"], rule=lambda state: state.has(grapple, player)) @@ -944,10 +950,12 @@ def set_er_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) # Bosses set_rule(multiworld.get_location("Fortress Arena - Siege Engine/Vault Key Pickup", player), lambda state: has_sword(state, player)) + # nmg - kill Librarian with a lure, or gun I guess set_rule(multiworld.get_location("Librarian - Hexagon Green", player), - lambda state: has_sword(state, player)) + lambda state: has_sword(state, player) or options.logic_rules) + # nmg - kill boss scav with orb + firecracker, or similar set_rule(multiworld.get_location("Rooted Ziggurat Lower - Hexagon Blue", player), - lambda state: has_sword(state, player)) + lambda state: has_sword(state, player) or (state.has(grapple, player) and options.logic_rules)) # Swamp set_rule(multiworld.get_location("Cathedral Gauntlet - Gauntlet Reward", player), diff --git a/worlds/tunic/regions.py b/worlds/tunic/regions.py index 5d5248f210..70204c6397 100644 --- a/worlds/tunic/regions.py +++ b/worlds/tunic/regions.py @@ -16,7 +16,7 @@ tunic_regions: Dict[str, Set[str]] = { "Eastern Vault Fortress": {"Beneath the Vault"}, "Beneath the Vault": {"Eastern Vault Fortress"}, "Quarry Back": {"Quarry"}, - "Quarry": {"Lower Quarry", "Rooted Ziggurat"}, + "Quarry": {"Lower Quarry"}, "Lower Quarry": {"Rooted Ziggurat"}, "Rooted Ziggurat": set(), "Swamp": {"Cathedral"}, diff --git a/worlds/tunic/rules.py b/worlds/tunic/rules.py index 6e5639b4eb..b3dd0b6832 100644 --- a/worlds/tunic/rules.py +++ b/worlds/tunic/rules.py @@ -131,8 +131,6 @@ def set_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) -> No lambda state: has_mask(state, player, options) multiworld.get_entrance("Lower Quarry -> Rooted Ziggurat", player).access_rule = \ lambda state: state.has(grapple, player) and has_ability(state, player, prayer, options, ability_unlocks) - multiworld.get_entrance("Quarry -> Rooted Ziggurat", player).access_rule = \ - lambda state: has_ice_grapple_logic(False, state, player, options, ability_unlocks) multiworld.get_entrance("Swamp -> Cathedral", player).access_rule = \ lambda state: state.has(laurels, player) and has_ability(state, player, prayer, options, ability_unlocks) \ or has_ice_grapple_logic(False, state, player, options, ability_unlocks) @@ -312,8 +310,9 @@ def set_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) -> lambda state: state.has(laurels, player)) set_rule(multiworld.get_location("Quarry - [West] Upper Area Bombable Wall", player), lambda state: has_mask(state, player, options)) + # nmg - kill boss scav with orb + firecracker, or similar set_rule(multiworld.get_location("Rooted Ziggurat Lower - Hexagon Blue", player), - lambda state: has_sword(state, player)) + lambda state: has_sword(state, player) or (state.has(grapple, player) and options.logic_rules)) # Swamp set_rule(multiworld.get_location("Cathedral Gauntlet - Gauntlet Reward", player), From c126418f35e95ccc1d22015baf79fe4f5c4a8aca Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Tue, 27 Feb 2024 08:44:34 +0100 Subject: [PATCH 035/166] Utils: YAML goes brrrt (#2868) Also tests to validate we dont break the API. --- Utils.py | 8 ++--- test/utils/test_yaml.py | 68 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 test/utils/test_yaml.py diff --git a/Utils.py b/Utils.py index 8b91226bed..da2d837ad3 100644 --- a/Utils.py +++ b/Utils.py @@ -19,14 +19,12 @@ import warnings from argparse import Namespace from settings import Settings, get_settings from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union -from yaml import load, load_all, dump, SafeLoader +from yaml import load, load_all, dump try: - from yaml import CLoader as UnsafeLoader - from yaml import CDumper as Dumper + from yaml import CLoader as UnsafeLoader, CSafeLoader as SafeLoader, CDumper as Dumper except ImportError: - from yaml import Loader as UnsafeLoader - from yaml import Dumper + from yaml import Loader as UnsafeLoader, SafeLoader, Dumper if typing.TYPE_CHECKING: import tkinter diff --git a/test/utils/test_yaml.py b/test/utils/test_yaml.py new file mode 100644 index 0000000000..4e23857eb0 --- /dev/null +++ b/test/utils/test_yaml.py @@ -0,0 +1,68 @@ +# Tests that yaml wrappers in Utils.py do what they should + +import unittest +from typing import cast, Any, ClassVar, Dict + +from Utils import dump, Dumper # type: ignore[attr-defined] +from Utils import parse_yaml, parse_yamls, unsafe_parse_yaml + + +class AClass: + def __eq__(self, other: Any) -> bool: + return isinstance(other, self.__class__) + + +class TestYaml(unittest.TestCase): + safe_data: ClassVar[Dict[str, Any]] = { + "a": [1, 2, 3], + "b": None, + "c": True, + } + unsafe_data: ClassVar[Dict[str, Any]] = { + "a": AClass() + } + + @property + def safe_str(self) -> str: + return cast(str, dump(self.safe_data, Dumper=Dumper)) + + @property + def unsafe_str(self) -> str: + return cast(str, dump(self.unsafe_data, Dumper=Dumper)) + + def assertIsNonEmptyString(self, string: str) -> None: + self.assertTrue(string) + self.assertIsInstance(string, str) + + def test_dump(self) -> None: + self.assertIsNonEmptyString(self.safe_str) + self.assertIsNonEmptyString(self.unsafe_str) + + def test_safe_parse(self) -> None: + self.assertEqual(self.safe_data, parse_yaml(self.safe_str)) + with self.assertRaises(Exception): + parse_yaml(self.unsafe_str) + with self.assertRaises(Exception): + parse_yaml("1\n---\n2\n") + + def test_unsafe_parse(self) -> None: + self.assertEqual(self.safe_data, unsafe_parse_yaml(self.safe_str)) + self.assertEqual(self.unsafe_data, unsafe_parse_yaml(self.unsafe_str)) + with self.assertRaises(Exception): + unsafe_parse_yaml("1\n---\n2\n") + + def test_multi_parse(self) -> None: + self.assertEqual(self.safe_data, next(parse_yamls(self.safe_str))) + with self.assertRaises(Exception): + next(parse_yamls(self.unsafe_str)) + self.assertEqual(2, len(list(parse_yamls("1\n---\n2\n")))) + + def test_unique_key(self) -> None: + s = """ + a: 1 + a: 2 + """ + with self.assertRaises(Exception): + parse_yaml(s) + with self.assertRaises(Exception): + next(parse_yamls(s)) From 59a6e4a1b59881ddfff7590ec75e5c927b248b60 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Wed, 28 Feb 2024 04:44:22 +0100 Subject: [PATCH 036/166] The Witness: New hint type ("area hints") (#2494) This new type of "area hint" will instead give you general information about one of the named geographical areas in your world. Example: ``` There are 4 progression items in the "Quarry" region. Of them, 2 are for other players. Also, one of them is a laser for this world. ``` This also renames some of the locations in the game to better fit into an "area", such as the "River Obelisk" being renamed to the "Mountainside Obelisk". --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Co-authored-by: Scipio Wright --- worlds/witness/WitnessItems.txt | 12 +- worlds/witness/WitnessLogic.txt | 348 ++++++++----- worlds/witness/WitnessLogicExpert.txt | 348 ++++++++----- worlds/witness/WitnessLogicVanilla.txt | 348 ++++++++----- worlds/witness/__init__.py | 35 +- worlds/witness/hints.py | 485 ++++++++++++++---- worlds/witness/locations.py | 68 +-- worlds/witness/options.py | 15 +- worlds/witness/presets.py | 3 + worlds/witness/regions.py | 5 +- .../Door_Shuffle/Complex_Door_Panels.txt | 4 +- .../settings/Door_Shuffle/Complex_Doors.txt | 16 +- .../settings/Door_Shuffle/Simple_Doors.txt | 10 +- .../settings/Door_Shuffle/Simple_Panels.txt | 2 +- .../witness/settings/EP_Shuffle/EP_Sides.txt | 12 +- worlds/witness/settings/Exclusions/Vaults.txt | 6 +- .../settings/Postgame/Mountain_Lower.txt | 6 +- worlds/witness/static_logic.py | 25 +- worlds/witness/utils.py | 15 + 19 files changed, 1152 insertions(+), 611 deletions(-) diff --git a/worlds/witness/WitnessItems.txt b/worlds/witness/WitnessItems.txt index e17464a092..6f63eccc95 100644 --- a/worlds/witness/WitnessItems.txt +++ b/worlds/witness/WitnessItems.txt @@ -39,8 +39,8 @@ Jokes: Doors: 1100 - Glass Factory Entry (Panel) - 0x01A54 -1101 - Tutorial Outpost Entry (Panel) - 0x0A171 -1102 - Tutorial Outpost Exit (Panel) - 0x04CA4 +1101 - Outside Tutorial Outpost Entry (Panel) - 0x0A171 +1102 - Outside Tutorial Outpost Exit (Panel) - 0x04CA4 1105 - Symmetry Island Lower (Panel) - 0x000B0 1107 - Symmetry Island Upper (Panel) - 0x1C349 1108 - Desert Surface 3 Control (Panel) - 0x09FA0 @@ -168,9 +168,9 @@ Doors: 1750 - Theater Entry (Door) - 0x17F88 1753 - Theater Exit Left (Door) - 0x0A16D 1756 - Theater Exit Right (Door) - 0x3CCDF -1759 - Jungle Bamboo Laser Shortcut (Door) - 0x3873B +1759 - Jungle Laser Shortcut (Door) - 0x3873B 1760 - Jungle Popup Wall (Door) - 0x1475B -1762 - River Monastery Garden Shortcut (Door) - 0x0CF2A +1762 - Jungle Monastery Garden Shortcut (Door) - 0x0CF2A 1765 - Bunker Entry (Door) - 0x0C2A4 1768 - Bunker Tinted Glass Door - 0x17C79 1771 - Bunker UV Room Entry (Door) - 0x0C2A3 @@ -195,7 +195,7 @@ Doors: 1828 - Mountain Floor 2 Exit (Door) - 0x09EDD 1831 - Mountain Floor 2 Staircase Far (Door) - 0x09E07 1834 - Mountain Bottom Floor Giant Puzzle Exit (Door) - 0x09F89 -1840 - Mountain Bottom Floor Final Room Entry (Door) - 0x0C141 +1840 - Mountain Bottom Floor Pillars Room Entry (Door) - 0x0C141 1843 - Mountain Bottom Floor Rock (Door) - 0x17F33 1846 - Caves Entry (Door) - 0x2D77D 1849 - Caves Pillar Door - 0x019A5 @@ -247,7 +247,7 @@ Doors: 2035 - Mountain & Caves Control Panels - 0x09ED8,0x09E86,0x09E39,0x09EEB,0x335AB,0x335AC,0x3369D 2100 - Symmetry Island Panels - 0x1C349,0x000B0 -2101 - Tutorial Outpost Panels - 0x0A171,0x04CA4 +2101 - Outside Tutorial Outpost Panels - 0x0A171,0x04CA4 2105 - Desert Panels - 0x09FAA,0x1C2DF,0x1831E,0x1C260,0x1831C,0x1C2F3,0x1831D,0x1C2B1,0x1831B,0x0C339,0x0A249,0x0A015,0x09FA0,0x09F86 2110 - Quarry Outside Panels - 0x17C09,0x09E57,0x17CC4 2115 - Quarry Stoneworks Panels - 0x01E5A,0x01E59,0x03678,0x03676,0x03679,0x03675 diff --git a/worlds/witness/WitnessLogic.txt b/worlds/witness/WitnessLogic.txt index ec0922bec6..e3bacfb4b0 100644 --- a/worlds/witness/WitnessLogic.txt +++ b/worlds/witness/WitnessLogic.txt @@ -1,12 +1,14 @@ +==Tutorial (Inside)== + Menu (Menu) - Entry - True: Entry (Entry): -First Hallway (First Hallway) - Entry - True - First Hallway Room - 0x00064: +Tutorial First Hallway (Tutorial First Hallway) - Entry - True - Tutorial First Hallway Room - 0x00064: 158000 - 0x00064 (Straight) - True - True 159510 - 0x01848 (EP) - 0x00064 - True -First Hallway Room (First Hallway) - Tutorial - 0x00182: +Tutorial First Hallway Room (Tutorial First Hallway) - Tutorial - 0x00182: 158001 - 0x00182 (Bend) - True - True Tutorial (Tutorial) - Outside Tutorial - 0x03629: @@ -23,6 +25,8 @@ Tutorial (Tutorial) - Outside Tutorial - 0x03629: 159513 - 0x33600 (Patio Flowers EP) - 0x0C373 - True 159517 - 0x3352F (Gate EP) - 0x03505 - True +==Tutorial (Outside)== + Outside Tutorial (Outside Tutorial) - Outside Tutorial Path To Outpost - 0x03BA2 - Outside Tutorial Vault - 0x033D0: 158650 - 0x033D4 (Vault Panel) - True - Dots & Black/White Squares Door - 0x033D0 (Vault Door) - 0x033D4 @@ -58,9 +62,23 @@ Outside Tutorial Outpost (Outside Tutorial) - Outside Tutorial - 0x04CA3: Door - 0x04CA3 (Outpost Exit) - 0x04CA4 158600 - 0x17CFB (Discard) - True - Triangles +Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307: +158071 - 0x00143 (Apple Tree 1) - True - True +158072 - 0x0003B (Apple Tree 2) - 0x00143 - True +158073 - 0x00055 (Apple Tree 3) - 0x0003B - True +Door - 0x03307 (First Gate) - 0x00055 + +Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313: +158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True +158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True +Door - 0x03313 (Second Gate) - 0x032FF + +Orchard End (Orchard): + Main Island (Main Island) - Outside Tutorial - True: 159801 - 0xFFD00 (Reached Independently) - True - True -159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True + +==Glass Factory== Outside Glass Factory (Glass Factory) - Main Island - True - Inside Glass Factory - 0x01A29: 158027 - 0x01A54 (Entry Panel) - True - Symmetry @@ -85,6 +103,8 @@ Door - 0x0D7ED (Back Wall) - 0x0005C Inside Glass Factory Behind Back Wall (Glass Factory) - The Ocean - 0x17CC8: 158039 - 0x17CC8 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat +==Symmetry Island== + Outside Symmetry Island (Symmetry Island) - Main Island - True - Symmetry Island Lower - 0x17F3E: 158040 - 0x000B0 (Lower Panel) - 0x0343A - Dots Door - 0x17F3E (Lower) - 0x000B0 @@ -128,20 +148,17 @@ Symmetry Island Upper (Symmetry Island): Laser - 0x00509 (Laser) - 0x0360D 159001 - 0x03367 (Glass Factory Black Line EP) - True - True -Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307: -158071 - 0x00143 (Apple Tree 1) - True - True -158072 - 0x0003B (Apple Tree 2) - 0x00143 - True -158073 - 0x00055 (Apple Tree 3) - 0x0003B - True -Door - 0x03307 (First Gate) - 0x00055 +==Desert== -Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313: -158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True -158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True -Door - 0x03313 (Second Gate) - 0x032FF +Desert Obelisk (Desert) - Entry - True: +159700 - 0xFFE00 (Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True +159701 - 0xFFE01 (Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True +159702 - 0xFFE02 (Obelisk Side 3) - 0x3351D - True +159703 - 0xFFE03 (Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True +159704 - 0xFFE04 (Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True +159709 - 0x00359 (Obelisk) - True - True -Orchard End (Orchard): - -Desert Outside (Desert) - Main Island - True - Desert Floodlight Room - 0x09FEE - Desert Vault - 0x03444: +Desert Outside (Desert) - Main Island - True - Desert Light Room - 0x09FEE - Desert Vault - 0x03444: 158652 - 0x0CC7B (Vault Panel) - True - Dots & Shapers & Rotated Shapers & Negative Shapers & Full Dots Door - 0x03444 (Vault Door) - 0x0CC7B 158602 - 0x17CE7 (Discard) - True - Triangles @@ -172,14 +189,14 @@ Laser - 0x012FB (Laser) - 0x03608 Desert Vault (Desert): 158653 - 0x0339E (Vault Box) - True - True -Desert Floodlight Room (Desert) - Desert Pond Room - 0x0C2C3: +Desert Light Room (Desert) - Desert Pond Room - 0x0C2C3: 158087 - 0x09FAA (Light Control) - True - True 158088 - 0x00422 (Light Room 1) - 0x09FAA - True 158089 - 0x006E3 (Light Room 2) - 0x09FAA - True 158090 - 0x0A02D (Light Room 3) - 0x09FAA & 0x00422 & 0x006E3 - True Door - 0x0C2C3 (Pond Room Entry) - 0x0A02D -Desert Pond Room (Desert) - Desert Water Levels Room - 0x0A24B: +Desert Pond Room (Desert) - Desert Flood Room - 0x0A24B: 158091 - 0x00C72 (Pond Room 1) - True - True 158092 - 0x0129D (Pond Room 2) - 0x00C72 - True 158093 - 0x008BB (Pond Room 3) - 0x0129D - True @@ -190,7 +207,7 @@ Door - 0x0A24B (Flood Room Entry) - 0x0A249 159043 - 0x0A14C (Pond Room Near Reflection EP) - True - True 159044 - 0x0A14D (Pond Room Far Reflection EP) - True - True -Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316: +Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316: 158097 - 0x1C2DF (Reduce Water Level Far Left) - True - True 158098 - 0x1831E (Reduce Water Level Far Right) - True - True 158099 - 0x1C260 (Reduce Water Level Near Left) - True - True @@ -208,7 +225,7 @@ Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316: Door - 0x0C316 (Elevator Room Entry) - 0x18076 159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True -Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x01317: +Desert Elevator Room (Desert) - Desert Behind Elevator - 0x01317: 158111 - 0x17C31 (Elevator Room Transparent) - True - True 158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True 158114 - 0x0A015 (Elevator Room Hexagonal Control) - 0x17C31 - True @@ -218,9 +235,19 @@ Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x0131 159035 - 0x037BB (Elevator EP) - 0x01317 - True Door - 0x01317 (Elevator) - 0x03608 -Desert Lowest Level Inbetween Shortcuts (Desert): +Desert Behind Elevator (Desert): -Outside Quarry (Quarry) - Main Island - True - Quarry Between Entrys - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01: +==Quarry== + +Quarry Obelisk (Quarry) - Entry - True: +159740 - 0xFFE40 (Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True +159741 - 0xFFE41 (Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True +159742 - 0xFFE42 (Obelisk Side 3) - 0x289CF & 0x289D1 - True +159743 - 0xFFE43 (Obelisk Side 4) - 0x33692 - True +159744 - 0xFFE44 (Obelisk Side 5) - 0x03E77 & 0x03E7C - True +159749 - 0x22073 (Obelisk) - True - True + +Outside Quarry (Quarry) - Main Island - True - Quarry Between Entry Doors - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01: 158118 - 0x09E57 (Entry 1 Panel) - True - Black/White Squares 158603 - 0x17CF0 (Discard) - True - Triangles 158702 - 0x03612 (Laser Panel) - 0x0A3D0 & 0x0367C - Eraser & Shapers @@ -236,7 +263,7 @@ Quarry Elevator (Quarry) - Outside Quarry - 0x17CC4 - Quarry - 0x17CC4: 158120 - 0x17CC4 (Elevator Control) - 0x0367C - Dots & Eraser 159403 - 0x17CB9 (Railroad EP) - 0x17CC4 - True -Quarry Between Entrys (Quarry) - Quarry - 0x17C07: +Quarry Between Entry Doors (Quarry) - Quarry - 0x17C07: 158119 - 0x17C09 (Entry 2 Panel) - True - Shapers Door - 0x17C07 (Entry 2) - 0x17C09 @@ -322,6 +349,8 @@ Door - 0x3865F (Second Barrier) - 0x38663 158169 - 0x0A3D0 (Back Second Row 3) - 0x0A3CC - Stars & Eraser & Shapers 159401 - 0x005F6 (Hook EP) - 0x275FA & 0x03852 & 0x3865F - True +==Shadows== + Shadows (Shadows) - Main Island - True - Shadows Ledge - 0x19B24 - Shadows Laser Room - 0x194B2 | 0x19665: 158170 - 0x334DB (Door Timer Outside) - True - True Door - 0x19B24 (Timed Door) - 0x334DB | 0x334DC @@ -361,19 +390,18 @@ Shadows Laser Room (Shadows): 158703 - 0x19650 (Laser Panel) - 0x194B2 & 0x19665 - True Laser - 0x181B3 (Laser) - 0x19650 -Treehouse Beach (Treehouse Beach) - Main Island - True: -159200 - 0x0053D (Rock Shadow EP) - True - True -159201 - 0x0053E (Sand Shadow EP) - True - True -159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True +==Keep== -Keep (Keep) - Main Island - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC: +Outside Keep (Keep) - Main Island - True: +159430 - 0x03E77 (Red Flowers EP) - True - True +159431 - 0x03E7C (Purple Flowers EP) - True - True + +Keep (Keep) - Outside Keep - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC: 158193 - 0x00139 (Hedge Maze 1) - True - True 158197 - 0x0A3A8 (Reset Pressure Plates 1) - True - True 158198 - 0x033EA (Pressure Plates 1) - 0x0A3A8 - Dots Door - 0x01954 (Hedge Maze 1 Exit) - 0x00139 Door - 0x01BEC (Pressure Plates 1 Exit) - 0x033EA -159430 - 0x03E77 (Red Flowers EP) - True - True -159431 - 0x03E7C (Purple Flowers EP) - True - True Keep 2nd Maze (Keep) - Keep - 0x018CE - Keep 3rd Maze - 0x019D8: Door - 0x018CE (Hedge Maze 2 Shortcut) - 0x00139 @@ -408,6 +436,22 @@ Door - 0x01D40 (Pressure Plates 4 Exit) - 0x01D3F 158205 - 0x09E49 (Shadows Shortcut Panel) - True - True Door - 0x09E3D (Shadows Shortcut) - 0x09E49 +Keep Tower (Keep) - Keep - 0x04F8F: +158206 - 0x0361B (Tower Shortcut Panel) - True - True +Door - 0x04F8F (Tower Shortcut) - 0x0361B +158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True +158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Black/White Squares & Colored Squares & Stars & Stars + Same Colored Symbol & Dots +Laser - 0x014BB (Laser) - 0x0360E | 0x03317 +159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True +159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True +159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True +159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True +159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True +159250 - 0x28AE9 (Path EP) - True - True +159251 - 0x3348F (Hedges EP) - True - True + +==Shipwreck== + Shipwreck (Shipwreck) - Keep 3rd Pressure Plate - True - Shipwreck Vault - 0x17BB4: 158654 - 0x00AFB (Vault Panel) - True - Symmetry & Sound Dots & Colored Dots Door - 0x17BB4 (Vault Door) - 0x00AFB @@ -423,19 +467,16 @@ Door - 0x17BB4 (Vault Door) - 0x00AFB Shipwreck Vault (Shipwreck): 158655 - 0x03535 (Vault Box) - True - True -Keep Tower (Keep) - Keep - 0x04F8F: -158206 - 0x0361B (Tower Shortcut Panel) - True - True -Door - 0x04F8F (Tower Shortcut) - 0x0361B -158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True -158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Black/White Squares & Colored Squares & Stars & Stars + Same Colored Symbol & Dots -Laser - 0x014BB (Laser) - 0x0360E | 0x03317 -159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True -159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True -159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True -159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True -159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True -159250 - 0x28AE9 (Path EP) - True - True -159251 - 0x3348F (Hedges EP) - True - True +==Monastery== + +Monastery Obelisk (Monastery) - Entry - True: +159710 - 0xFFE10 (Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True +159711 - 0xFFE11 (Obelisk Side 2) - 0x03AC5 - True +159712 - 0xFFE12 (Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True +159713 - 0xFFE13 (Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True +159714 - 0xFFE14 (Obelisk Side 5) - 0x03E01 - True +159715 - 0xFFE15 (Obelisk Side 6) - 0x289F4 & 0x289F5 - True +159719 - 0x00263 (Obelisk) - True - True Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128 & 0x0C153 - Monastery Garden - 0x03750: 158207 - 0x03713 (Laser Shortcut Panel) - True - True @@ -457,6 +498,9 @@ Laser - 0x17C65 (Laser) - 0x17CA4 159137 - 0x03DAC (Facade Left Stairs EP) - True - True 159138 - 0x03DAD (Facade Right Stairs EP) - True - True 159140 - 0x03E01 (Grass Stairs EP) - True - True +159120 - 0x03BE2 (Garden Left EP) - 0x03750 - True +159121 - 0x03BE3 (Garden Right EP) - True - True +159122 - 0x0A409 (Wall EP) - True - True Inside Monastery (Monastery): 158213 - 0x09D9B (Shutters Control) - True - Dots @@ -470,7 +514,18 @@ Inside Monastery (Monastery): Monastery Garden (Monastery): -Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - RGB House - 0x28A61 - Windmill Interior - 0x1845B - Town Inside Cargo Box - 0x0A0C9: +==Town== + +Town Obelisk (Town) - Entry - True: +159750 - 0xFFE50 (Obelisk Side 1) - 0x035C7 - True +159751 - 0xFFE51 (Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True +159752 - 0xFFE52 (Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True +159753 - 0xFFE53 (Obelisk Side 4) - 0x28B30 & 0x035C9 - True +159754 - 0xFFE54 (Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True +159755 - 0xFFE55 (Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True +159759 - 0x0A16C (Obelisk) - True - True + +Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - Town RGB House - 0x28A61 - Town Inside Cargo Box - 0x0A0C9 - Outside Windmill - True: 158218 - 0x0A054 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat 158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Black/White Squares & Shapers Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8 @@ -491,11 +546,6 @@ Door - 0x28A61 (RGB House Entry) - 0x28998 Door - 0x03BB0 (Church Entry) - 0x28A0D 158228 - 0x28A79 (Maze Panel) - True - True Door - 0x28AA2 (Maze Stairs) - 0x28A79 -158241 - 0x17F5F (Windmill Entry Panel) - True - Dots -Door - 0x1845B (Windmill Entry) - 0x17F5F -159010 - 0x037B6 (Windmill First Blade EP) - 0x17D02 - True -159011 - 0x037B2 (Windmill Second Blade EP) - 0x17D02 - True -159012 - 0x000F7 (Windmill Third Blade EP) - 0x17D02 - True 159540 - 0x03335 (Tower Underside Third EP) - True - True 159541 - 0x03412 (Tower Underside Fourth EP) - True - True 159542 - 0x038A6 (Tower Underside First EP) - True - True @@ -528,20 +578,26 @@ Town Church (Town): 158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True 159553 - 0x03BD1 (Black Line Church EP) - True - True -RGB House (Town) - RGB Room - 0x2897B: +Town RGB House (Town RGB House) - Town RGB House Upstairs - 0x2897B: 158242 - 0x034E4 (Sound Room Left) - True - True 158243 - 0x034E3 (Sound Room Right) - True - Sound Dots -Door - 0x2897B (RGB House Stairs) - 0x034E4 & 0x034E3 +Door - 0x2897B (Stairs) - 0x034E4 & 0x034E3 -RGB Room (Town): +Town RGB House Upstairs (Town RGB House Upstairs): 158244 - 0x334D8 (RGB Control) - True - Rotated Shapers & Colored Squares -158245 - 0x03C0C (RGB Room Left) - 0x334D8 - Colored Squares & Black/White Squares -158246 - 0x03C08 (RGB Room Right) - 0x334D8 - Stars +158245 - 0x03C0C (Left) - 0x334D8 - Colored Squares & Black/White Squares +158246 - 0x03C08 (Right) - 0x334D8 - Stars -Town Tower (Town Tower) - Town - True - Town Tower Top - 0x27798 & 0x27799 & 0x2779A & 0x2779C: -Door - 0x27798 (Second Door) - 0x28ACC -Door - 0x2779C (Third Door) - 0x28AD9 +Town Tower Bottom (Town Tower) - Town - True - Town Tower After First Door - 0x27799: Door - 0x27799 (First Door) - 0x28A69 + +Town Tower After First Door (Town Tower) - Town Tower After Second Door - 0x27798: +Door - 0x27798 (Second Door) - 0x28ACC + +Town Tower After Second Door (Town Tower) - Town Tower After Third Door - 0x2779C: +Door - 0x2779C (Third Door) - 0x28AD9 + +Town Tower After Third Door (Town Tower) - Town Tower Top - 0x2779A: Door - 0x2779A (Fourth Door) - 0x28B39 Town Tower Top (Town): @@ -550,6 +606,15 @@ Laser - 0x032F9 (Laser) - 0x032F5 159422 - 0x33692 (Brown Bridge EP) - True - True 159551 - 0x03BCE (Black Line Tower EP) - True - True +==Windmill & Theater== + +Outside Windmill (Windmill) - Windmill Interior - 0x1845B: +159010 - 0x037B6 (First Blade EP) - 0x17D02 - True +159011 - 0x037B2 (Second Blade EP) - 0x17D02 - True +159012 - 0x000F7 (Third Blade EP) - 0x17D02 - True +158241 - 0x17F5F (Entry Panel) - True - Dots +Door - 0x1845B (Entry) - 0x17F5F + Windmill Interior (Windmill) - Theater - 0x17F88: 158247 - 0x17D02 (Turn Control) - True - Dots 158248 - 0x17F89 (Theater Entry Panel) - True - Black/White Squares @@ -573,6 +638,8 @@ Door - 0x3CCDF (Exit Right) - 0x33AB2 159556 - 0x33A2A (Door EP) - 0x03553 - True 159558 - 0x33B06 (Church EP) - 0x0354E - True +==Jungle== + Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF: 158251 - 0x17CDF (Shore Boat Spawn) - True - Boat 158609 - 0x17F9B (Discard) - True - Triangles @@ -604,19 +671,18 @@ Door - 0x3873B (Laser Shortcut) - 0x337FA 159350 - 0x035CB (Bamboo CCW EP) - True - True 159351 - 0x035CF (Bamboo CW EP) - True - True -Outside Jungle River (River) - Main Island - True - Monastery Garden - 0x0CF2A - River Vault - 0x15287: +Outside Jungle River (Jungle) - Main Island - True - Monastery Garden - 0x0CF2A - Jungle Vault - 0x15287: 158267 - 0x17CAA (Monastery Garden Shortcut Panel) - True - True Door - 0x0CF2A (Monastery Garden Shortcut) - 0x17CAA 158663 - 0x15ADD (Vault Panel) - True - Black/White Squares & Dots Door - 0x15287 (Vault Door) - 0x15ADD 159110 - 0x03AC5 (Green Leaf Moss EP) - True - True -159120 - 0x03BE2 (Monastery Garden Left EP) - 0x03750 - True -159121 - 0x03BE3 (Monastery Garden Right EP) - True - True -159122 - 0x0A409 (Monastery Wall EP) - True - True -River Vault (River): +Jungle Vault (Jungle): 158664 - 0x03702 (Vault Box) - True - True +==Bunker== + Outside Bunker (Bunker) - Main Island - True - Bunker - 0x0C2A4: 158268 - 0x17C2E (Entry Panel) - True - Black/White Squares Door - 0x0C2A4 (Entry) - 0x17C2E @@ -650,9 +716,11 @@ Door - 0x0A08D (Elevator Room Entry) - 0x17E67 Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay: 159311 - 0x035F5 (Tinted Door EP) - 0x17C79 - True -Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079: +Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Cyan Room - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079: 158286 - 0x0A079 (Elevator Control) - True - Colored Squares & Black/White Squares +Bunker Cyan Room (Bunker) - Bunker Elevator - TrueOneWay: + Bunker Green Room (Bunker) - Bunker Elevator - TrueOneWay: 159310 - 0x000D3 (Green Room Flowers EP) - True - True @@ -660,6 +728,8 @@ Bunker Laser Platform (Bunker) - Bunker Elevator - TrueOneWay: 158710 - 0x09DE0 (Laser Panel) - True - True Laser - 0x0C2B2 (Laser) - 0x09DE0 +==Swamp== + Outside Swamp (Swamp) - Swamp Entry Area - 0x00C1C - Main Island - True: 158287 - 0x0056E (Entry Panel) - True - Shapers Door - 0x00C1C (Entry) - 0x0056E @@ -774,13 +844,29 @@ Laser - 0x00BF6 (Laser) - 0x03615 158342 - 0x17C02 (Laser Shortcut Right Panel) - 0x17C05 - Shapers & Negative Shapers & Rotated Shapers Door - 0x2D880 (Laser Shortcut) - 0x17C02 -Treehouse Entry Area (Treehouse) - Treehouse Between Doors - 0x0C309 - The Ocean - 0x17C95: +==Treehouse== + +Treehouse Obelisk (Treehouse) - Entry - True: +159720 - 0xFFE20 (Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True +159721 - 0xFFE21 (Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True +159722 - 0xFFE22 (Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True +159723 - 0xFFE23 (Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True +159724 - 0xFFE24 (Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True +159725 - 0xFFE25 (Obelisk Side 6) - 0x28AE9 & 0x3348F - True +159729 - 0x00097 (Obelisk) - True - True + +Treehouse Beach (Treehouse Beach) - Main Island - True: +159200 - 0x0053D (Rock Shadow EP) - True - True +159201 - 0x0053E (Sand Shadow EP) - True - True +159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True + +Treehouse Entry Area (Treehouse) - Treehouse Between Entry Doors - 0x0C309 - The Ocean - 0x17C95: 158343 - 0x17C95 (Boat Spawn) - True - Boat 158344 - 0x0288C (First Door Panel) - True - Stars Door - 0x0C309 (First Door) - 0x0288C 159210 - 0x33721 (Buoy EP) - 0x17C95 - True -Treehouse Between Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310: +Treehouse Between Entry Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310: 158345 - 0x02886 (Second Door Panel) - True - Stars Door - 0x0C310 (Second Door) - 0x02886 @@ -809,7 +895,7 @@ Treehouse First Purple Bridge (Treehouse) - Treehouse Second Purple Bridge - 0x1 158360 - 0x17D2D (First Purple Bridge 4) - 0x17CE4 - Stars & Dots 158361 - 0x17D6C (First Purple Bridge 5) - 0x17D2D - Stars & Dots -Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2: +Treehouse Right Orange Bridge (Treehouse) - Treehouse Drawbridge Platform - 0x17DA2: 158391 - 0x17D88 (Right Orange Bridge 1) - True - Stars 158392 - 0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars 158393 - 0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars @@ -823,7 +909,7 @@ Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2: 158401 - 0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars 158402 - 0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars -Treehouse Bridge Platform (Treehouse) - Main Island - 0x0C32D: +Treehouse Drawbridge Platform (Treehouse) - Main Island - 0x0C32D: 158404 - 0x037FF (Drawbridge Panel) - True - Stars Door - 0x0C32D (Drawbridge) - 0x037FF @@ -882,7 +968,19 @@ Treehouse Laser Room (Treehouse): 158403 - 0x17CBC (Laser House Door Timer Inside) - True - True Laser - 0x028A4 (Laser) - 0x03613 +==Mountain (Outside)== + +Mountainside Obelisk (Mountainside) - Entry - True: +159730 - 0xFFE30 (Obelisk Side 1) - 0x001A3 & 0x335AE - True +159731 - 0xFFE31 (Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True +159732 - 0xFFE32 (Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True +159733 - 0xFFE33 (Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True +159734 - 0xFFE34 (Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True +159735 - 0xFFE35 (Obelisk Side 6) - 0x035CB & 0x035CF - True +159739 - 0x00367 (Obelisk) - True - True + Mountainside (Mountainside) - Main Island - True - Mountaintop - True - Mountainside Vault - 0x00085: +159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True 158612 - 0x17C42 (Discard) - True - Triangles 158665 - 0x002A6 (Vault Panel) - True - Symmetry & Colored Dots & Black/White Squares & Dots Door - 0x00085 (Vault Door) - 0x002A6 @@ -893,7 +991,7 @@ Door - 0x00085 (Vault Door) - 0x002A6 Mountainside Vault (Mountainside): 158666 - 0x03542 (Vault Box) - True - True -Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34: +Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34: 158405 - 0x0042D (River Shape) - True - True 158406 - 0x09F7F (Box Short) - 7 Lasers + Redirect - True 158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Stars & Black/White Squares & Stars + Same Colored Symbol @@ -903,10 +1001,12 @@ Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34: 159324 - 0x336C8 (Arch White Right EP) - True - True 159326 - 0x3369A (Arch White Left EP) - True - True -Mountain Top Layer (Mountain Floor 1) - Mountain Top Layer Bridge - 0x09E39: +==Mountain (Inside)== + +Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39: 158408 - 0x09E39 (Light Bridge Controller) - True - Black/White Squares & Colored Squares & Eraser -Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - TrueOneWay: +Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay: 158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots 158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Dots 158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers & Dots @@ -925,10 +1025,10 @@ Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - True 158424 - 0x09EAD (Trash Pillar 1) - True - Black/White Squares & Shapers 158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Black/White Squares & Shapers -Mountain Top Layer At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: +Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B -Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Blue Bridge - 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 At Door - 0x09ED8 & 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: 158426 - 0x09FD3 (Near Row 1) - True - Stars & Colored Squares & Stars + Same Colored Symbol 158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Stars & Colored Squares & Stars + Same Colored Symbol 158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Colored Squares & Stars + Same Colored Symbol @@ -936,8 +1036,6 @@ Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Colored Squares & Symmetry & Colored Dots Door - 0x09FFB (Staircase Near) - 0x09FD8 -Mountain Floor 2 Blue Bridge (Mountain Floor 2) - Mountain Floor 2 Beyond Bridge - TrueOneWay - Mountain Floor 2 At Door - 0x09ED8: - Mountain Floor 2 At Door (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD: Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86 @@ -959,10 +1057,10 @@ Mountain Floor 2 Light Bridge Room Far (Mountain Floor 2): Mountain Floor 2 Elevator Room (Mountain Floor 2) - Mountain Floor 2 Elevator - TrueOneWay: 158613 - 0x17F93 (Elevator Discard) - True - Triangles -Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Third Layer - 0x09EEB: +Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Floor 3 - 0x09EEB: 158439 - 0x09EEB (Elevator Control Panel) - True - Dots -Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 3 (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay: 158440 - 0x09FC1 (Giant Puzzle Bottom Left) - True - Shapers & Eraser 158441 - 0x09F8E (Giant Puzzle Bottom Right) - True - Shapers & Eraser 158442 - 0x09F01 (Giant Puzzle Top Right) - True - Rotated Shapers @@ -972,13 +1070,32 @@ Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueO 159314 - 0x09D5E (Blue Bridge EP) - 0x09E86 & 0x09ED8 - True Door - 0x09F89 (Exit) - 0x09FDA -Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Final Room - 0x0C141: +Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Mountain Bottom Floor Pillars Room - 0x0C141: 158614 - 0x17FA2 (Discard) - 0xFFF00 - Triangles -158445 - 0x01983 (Final Room Entry Left) - True - Shapers & Stars -158446 - 0x01987 (Final Room Entry Right) - True - Colored Squares & Dots -Door - 0x0C141 (Final Room Entry) - 0x01983 & 0x01987 +158445 - 0x01983 (Pillars Room Entry Left) - True - Shapers & Stars +158446 - 0x01987 (Pillars Room Entry Right) - True - Colored Squares & Dots +Door - 0x0C141 (Pillars Room Entry) - 0x01983 & 0x01987 Door - 0x17F33 (Rock Open) - 0x17FA2 | 0x334E1 +Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB & 0x33961: +158522 - 0x0383A (Right Pillar 1) - True - Stars +158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots +158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots +158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry +158526 - 0x0383D (Left Pillar 1) - True - Dots +158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares +158528 - 0x03859 (Left Pillar 3) - 0x0383F - Shapers +158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry + +Elevator (Mountain Bottom Floor): +158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True +158531 - 0x3D9A7 (Elevator Door Close Right) - True - True +158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True +158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True +158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True +158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True +158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True + Mountain Pink Bridge EP (Mountain Floor 2): 159312 - 0x09D63 (Pink Bridge EP) - 0x09E39 - True @@ -987,7 +1104,9 @@ Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D: Door - 0x2D77D (Caves Entry) - 0x00FF8 158448 - 0x334E1 (Rock Control) - True - True -Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Path to Challenge - 0x019A5: +==Caves== + +Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5: 158451 - 0x335AB (Elevator Inside Control) - True - Dots & Black/White Squares 158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Black/White Squares 158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Black/White Squares & Dots @@ -1042,10 +1161,12 @@ Door - 0x2D73F (Mountain Shortcut Door) - 0x021D7 Door - 0x2D859 (Swamp Shortcut Door) - 0x17CF2 159341 - 0x3397C (Skylight EP) - True - True -Path to Challenge (Caves) - Challenge - 0x0A19A: +Caves Path to Challenge (Caves) - Challenge - 0x0A19A: 158477 - 0x0A16E (Challenge Entry Panel) - True - Stars & Shapers & Stars + Same Colored Symbol Door - 0x0A19A (Challenge Entry) - 0x0A16E +==Challenge== + Challenge (Challenge) - Tunnels - 0x0348A - Challenge Vault - 0x04D75: 158499 - 0x0A332 (Start Timer) - 11 Lasers - True 158500 - 0x0088E (Small Basic) - 0x0A332 - True @@ -1074,7 +1195,9 @@ Door - 0x0348A (Tunnels Entry) - 0x039B4 Challenge Vault (Challenge): 158667 - 0x0356B (Vault Box) - 0x1C31A & 0x1C319 - True -Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Lowest Level Inbetween Shortcuts - 0x27263 - Town - 0x09E87: +==Tunnels== + +Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Behind Elevator - 0x27263 - Town - 0x09E87: 158668 - 0x2FAF6 (Vault Box) - True - True 158519 - 0x27732 (Theater Shortcut Panel) - True - True Door - 0x27739 (Theater Shortcut) - 0x27732 @@ -1084,24 +1207,7 @@ Door - 0x27263 (Desert Shortcut) - 0x2773D Door - 0x09E87 (Town Shortcut) - 0x09E85 159557 - 0x33A20 (Theater Flowers EP) - 0x03553 & Theater to Tunnels - True -Final Room (Mountain Final Room) - Elevator - 0x339BB & 0x33961: -158522 - 0x0383A (Right Pillar 1) - True - Stars -158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots -158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots -158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry -158526 - 0x0383D (Left Pillar 1) - True - Dots -158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares -158528 - 0x03859 (Left Pillar 3) - 0x0383F - Shapers -158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry - -Elevator (Mountain Final Room): -158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True -158531 - 0x3D9A7 (Elevator Door Close Right) - True - True -158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True -158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True -158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True -158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True -158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True +==Boat== The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Treehouse Entry Area - TrueOneWay - Quarry Boathouse Behind Staircase - TrueOneWay - Inside Glass Factory Behind Back Wall - TrueOneWay: 159042 - 0x22106 (Desert EP) - True - True @@ -1114,45 +1220,3 @@ The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Tre 159521 - 0x33879 (Tutorial Reflection EP) - True - True 159522 - 0x03C19 (Tutorial Moss EP) - True - True 159531 - 0x035C9 (Cargo Box EP) - 0x0A0C9 - True - -Obelisks (EPs) - Entry - True: -159700 - 0xFFE00 (Desert Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True -159701 - 0xFFE01 (Desert Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True -159702 - 0xFFE02 (Desert Obelisk Side 3) - 0x3351D - True -159703 - 0xFFE03 (Desert Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True -159704 - 0xFFE04 (Desert Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True -159709 - 0x00359 (Desert Obelisk) - True - True -159710 - 0xFFE10 (Monastery Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True -159711 - 0xFFE11 (Monastery Obelisk Side 2) - 0x03AC5 - True -159712 - 0xFFE12 (Monastery Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True -159713 - 0xFFE13 (Monastery Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True -159714 - 0xFFE14 (Monastery Obelisk Side 5) - 0x03E01 - True -159715 - 0xFFE15 (Monastery Obelisk Side 6) - 0x289F4 & 0x289F5 - True -159719 - 0x00263 (Monastery Obelisk) - True - True -159720 - 0xFFE20 (Treehouse Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True -159721 - 0xFFE21 (Treehouse Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True -159722 - 0xFFE22 (Treehouse Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True -159723 - 0xFFE23 (Treehouse Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True -159724 - 0xFFE24 (Treehouse Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True -159725 - 0xFFE25 (Treehouse Obelisk Side 6) - 0x28AE9 & 0x3348F - True -159729 - 0x00097 (Treehouse Obelisk) - True - True -159730 - 0xFFE30 (River Obelisk Side 1) - 0x001A3 & 0x335AE - True -159731 - 0xFFE31 (River Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True -159732 - 0xFFE32 (River Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True -159733 - 0xFFE33 (River Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True -159734 - 0xFFE34 (River Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True -159735 - 0xFFE35 (River Obelisk Side 6) - 0x035CB & 0x035CF - True -159739 - 0x00367 (River Obelisk) - True - True -159740 - 0xFFE40 (Quarry Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True -159741 - 0xFFE41 (Quarry Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True -159742 - 0xFFE42 (Quarry Obelisk Side 3) - 0x289CF & 0x289D1 - True -159743 - 0xFFE43 (Quarry Obelisk Side 4) - 0x33692 - True -159744 - 0xFFE44 (Quarry Obelisk Side 5) - 0x03E77 & 0x03E7C - True -159749 - 0x22073 (Quarry Obelisk) - True - True -159750 - 0xFFE50 (Town Obelisk Side 1) - 0x035C7 - True -159751 - 0xFFE51 (Town Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True -159752 - 0xFFE52 (Town Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True -159753 - 0xFFE53 (Town Obelisk Side 4) - 0x28B30 & 0x035C9 - True -159754 - 0xFFE54 (Town Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True -159755 - 0xFFE55 (Town Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True -159759 - 0x0A16C (Town Obelisk) - True - True diff --git a/worlds/witness/WitnessLogicExpert.txt b/worlds/witness/WitnessLogicExpert.txt index 056ae145c4..b01d5551ec 100644 --- a/worlds/witness/WitnessLogicExpert.txt +++ b/worlds/witness/WitnessLogicExpert.txt @@ -1,12 +1,14 @@ +==Tutorial (Inside)== + Menu (Menu) - Entry - True: Entry (Entry): -First Hallway (First Hallway) - Entry - True - First Hallway Room - 0x00064: +Tutorial First Hallway (Tutorial First Hallway) - Entry - True - Tutorial First Hallway Room - 0x00064: 158000 - 0x00064 (Straight) - True - True 159510 - 0x01848 (EP) - 0x00064 - True -First Hallway Room (First Hallway) - Tutorial - 0x00182: +Tutorial First Hallway Room (Tutorial First Hallway) - Tutorial - 0x00182: 158001 - 0x00182 (Bend) - True - True Tutorial (Tutorial) - Outside Tutorial - True: @@ -23,6 +25,8 @@ Tutorial (Tutorial) - Outside Tutorial - True: 159513 - 0x33600 (Patio Flowers EP) - 0x0C373 - True 159517 - 0x3352F (Gate EP) - 0x03505 - True +==Tutorial (Outside)== + Outside Tutorial (Outside Tutorial) - Outside Tutorial Path To Outpost - 0x03BA2 - Outside Tutorial Vault - 0x033D0: 158650 - 0x033D4 (Vault Panel) - True - Dots & Full Dots & Squares & Black/White Squares Door - 0x033D0 (Vault Door) - 0x033D4 @@ -58,9 +62,23 @@ Outside Tutorial Outpost (Outside Tutorial) - Outside Tutorial - 0x04CA3: Door - 0x04CA3 (Outpost Exit) - 0x04CA4 158600 - 0x17CFB (Discard) - True - Arrows +Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307: +158071 - 0x00143 (Apple Tree 1) - True - True +158072 - 0x0003B (Apple Tree 2) - 0x00143 - True +158073 - 0x00055 (Apple Tree 3) - 0x0003B - True +Door - 0x03307 (First Gate) - 0x00055 + +Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313: +158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True +158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True +Door - 0x03313 (Second Gate) - 0x032FF + +Orchard End (Orchard): + Main Island (Main Island) - Outside Tutorial - True: 159801 - 0xFFD00 (Reached Independently) - True - True -159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True + +==Glass Factory== Outside Glass Factory (Glass Factory) - Main Island - True - Inside Glass Factory - 0x01A29: 158027 - 0x01A54 (Entry Panel) - True - Symmetry @@ -85,6 +103,8 @@ Door - 0x0D7ED (Back Wall) - 0x0005C Inside Glass Factory Behind Back Wall (Glass Factory) - The Ocean - 0x17CC8: 158039 - 0x17CC8 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat +==Symmetry Island== + Outside Symmetry Island (Symmetry Island) - Main Island - True - Symmetry Island Lower - 0x17F3E: 158040 - 0x000B0 (Lower Panel) - 0x0343A - Triangles Door - 0x17F3E (Lower) - 0x000B0 @@ -128,20 +148,17 @@ Symmetry Island Upper (Symmetry Island): Laser - 0x00509 (Laser) - 0x0360D 159001 - 0x03367 (Glass Factory Black Line EP) - True - True -Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307: -158071 - 0x00143 (Apple Tree 1) - True - True -158072 - 0x0003B (Apple Tree 2) - 0x00143 - True -158073 - 0x00055 (Apple Tree 3) - 0x0003B - True -Door - 0x03307 (First Gate) - 0x00055 +==Desert== -Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313: -158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True -158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True -Door - 0x03313 (Second Gate) - 0x032FF +Desert Obelisk (Desert) - Entry - True: +159700 - 0xFFE00 (Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True +159701 - 0xFFE01 (Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True +159702 - 0xFFE02 (Obelisk Side 3) - 0x3351D - True +159703 - 0xFFE03 (Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True +159704 - 0xFFE04 (Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True +159709 - 0x00359 (Obelisk) - True - True -Orchard End (Orchard): - -Desert Outside (Desert) - Main Island - True - Desert Floodlight Room - 0x09FEE - Desert Vault - 0x03444: +Desert Outside (Desert) - Main Island - True - Desert Light Room - 0x09FEE - Desert Vault - 0x03444: 158652 - 0x0CC7B (Vault Panel) - True - Dots & Full Dots & Stars & Stars + Same Colored Symbol & Eraser & Triangles & Shapers & Negative Shapers & Colored Squares Door - 0x03444 (Vault Door) - 0x0CC7B 158602 - 0x17CE7 (Discard) - True - Arrows @@ -172,14 +189,14 @@ Laser - 0x012FB (Laser) - 0x03608 Desert Vault (Desert): 158653 - 0x0339E (Vault Box) - True - True -Desert Floodlight Room (Desert) - Desert Pond Room - 0x0C2C3: +Desert Light Room (Desert) - Desert Pond Room - 0x0C2C3: 158087 - 0x09FAA (Light Control) - True - True 158088 - 0x00422 (Light Room 1) - 0x09FAA - True 158089 - 0x006E3 (Light Room 2) - 0x09FAA - True 158090 - 0x0A02D (Light Room 3) - 0x09FAA & 0x00422 & 0x006E3 - True Door - 0x0C2C3 (Pond Room Entry) - 0x0A02D -Desert Pond Room (Desert) - Desert Water Levels Room - 0x0A24B: +Desert Pond Room (Desert) - Desert Flood Room - 0x0A24B: 158091 - 0x00C72 (Pond Room 1) - True - True 158092 - 0x0129D (Pond Room 2) - 0x00C72 - True 158093 - 0x008BB (Pond Room 3) - 0x0129D - True @@ -190,7 +207,7 @@ Door - 0x0A24B (Flood Room Entry) - 0x0A249 159043 - 0x0A14C (Pond Room Near Reflection EP) - True - True 159044 - 0x0A14D (Pond Room Far Reflection EP) - True - True -Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316: +Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316: 158097 - 0x1C2DF (Reduce Water Level Far Left) - True - True 158098 - 0x1831E (Reduce Water Level Far Right) - True - True 158099 - 0x1C260 (Reduce Water Level Near Left) - True - True @@ -208,7 +225,7 @@ Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316: Door - 0x0C316 (Elevator Room Entry) - 0x18076 159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True -Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x01317: +Desert Elevator Room (Desert) - Desert Behind Elevator - 0x01317: 158111 - 0x17C31 (Elevator Room Transparent) - True - True 158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True 158114 - 0x0A015 (Elevator Room Hexagonal Control) - 0x17C31 - True @@ -218,9 +235,19 @@ Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x0131 159035 - 0x037BB (Elevator EP) - 0x01317 - True Door - 0x01317 (Elevator) - 0x03608 -Desert Lowest Level Inbetween Shortcuts (Desert): +Desert Behind Elevator (Desert): -Outside Quarry (Quarry) - Main Island - True - Quarry Between Entrys - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01: +==Quarry== + +Quarry Obelisk (Quarry) - Entry - True: +159740 - 0xFFE40 (Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True +159741 - 0xFFE41 (Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True +159742 - 0xFFE42 (Obelisk Side 3) - 0x289CF & 0x289D1 - True +159743 - 0xFFE43 (Obelisk Side 4) - 0x33692 - True +159744 - 0xFFE44 (Obelisk Side 5) - 0x03E77 & 0x03E7C - True +159749 - 0x22073 (Obelisk) - True - True + +Outside Quarry (Quarry) - Main Island - True - Quarry Between Entry Doors - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01: 158118 - 0x09E57 (Entry 1 Panel) - True - Squares & Black/White Squares & Triangles 158603 - 0x17CF0 (Discard) - True - Arrows 158702 - 0x03612 (Laser Panel) - 0x0A3D0 & 0x0367C - Eraser & Triangles & Stars & Stars + Same Colored Symbol @@ -236,7 +263,7 @@ Quarry Elevator (Quarry) - Outside Quarry - 0x17CC4 - Quarry - 0x17CC4: 158120 - 0x17CC4 (Elevator Control) - 0x0367C - Dots & Eraser 159403 - 0x17CB9 (Railroad EP) - 0x17CC4 - True -Quarry Between Entrys (Quarry) - Quarry - 0x17C07: +Quarry Between Entry Doors (Quarry) - Quarry - 0x17C07: 158119 - 0x17C09 (Entry 2 Panel) - True - Shapers & Triangles Door - 0x17C07 (Entry 2) - 0x17C09 @@ -322,6 +349,8 @@ Door - 0x3865F (Second Barrier) - 0x38663 158169 - 0x0A3D0 (Back Second Row 3) - 0x0A3CC - Stars & Eraser & Shapers & Negative Shapers & Stars + Same Colored Symbol 159401 - 0x005F6 (Hook EP) - 0x275FA & 0x03852 & 0x3865F - True +==Shadows== + Shadows (Shadows) - Main Island - True - Shadows Ledge - 0x19B24 - Shadows Laser Room - 0x194B2 | 0x19665: 158170 - 0x334DB (Door Timer Outside) - True - True Door - 0x19B24 (Timed Door) - 0x334DB | 0x334DC @@ -361,19 +390,18 @@ Shadows Laser Room (Shadows): 158703 - 0x19650 (Laser Panel) - 0x194B2 & 0x19665 - True Laser - 0x181B3 (Laser) - 0x19650 -Treehouse Beach (Treehouse Beach) - Main Island - True: -159200 - 0x0053D (Rock Shadow EP) - True - True -159201 - 0x0053E (Sand Shadow EP) - True - True -159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True +==Keep== -Keep (Keep) - Main Island - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC: +Outside Keep (Keep) - Main Island - True: +159430 - 0x03E77 (Red Flowers EP) - True - True +159431 - 0x03E7C (Purple Flowers EP) - True - True + +Keep (Keep) - Outside Keep - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC: 158193 - 0x00139 (Hedge Maze 1) - True - True 158197 - 0x0A3A8 (Reset Pressure Plates 1) - True - True 158198 - 0x033EA (Pressure Plates 1) - 0x0A3A8 - Colored Squares & Triangles & Stars & Stars + Same Colored Symbol Door - 0x01954 (Hedge Maze 1 Exit) - 0x00139 Door - 0x01BEC (Pressure Plates 1 Exit) - 0x033EA -159430 - 0x03E77 (Red Flowers EP) - True - True -159431 - 0x03E7C (Purple Flowers EP) - True - True Keep 2nd Maze (Keep) - Keep - 0x018CE - Keep 3rd Maze - 0x019D8: Door - 0x018CE (Hedge Maze 2 Shortcut) - 0x00139 @@ -408,6 +436,22 @@ Door - 0x01D40 (Pressure Plates 4 Exit) - 0x01D3F 158205 - 0x09E49 (Shadows Shortcut Panel) - True - True Door - 0x09E3D (Shadows Shortcut) - 0x09E49 +Keep Tower (Keep) - Keep - 0x04F8F: +158206 - 0x0361B (Tower Shortcut Panel) - True - True +Door - 0x04F8F (Tower Shortcut) - 0x0361B +158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True +158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Rotated Shapers & Triangles & Stars & Stars + Same Colored Symbol & Colored Squares & Black/White Squares +Laser - 0x014BB (Laser) - 0x0360E | 0x03317 +159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True +159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True +159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True +159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True +159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True +159250 - 0x28AE9 (Path EP) - True - True +159251 - 0x3348F (Hedges EP) - True - True + +==Shipwreck== + Shipwreck (Shipwreck) - Keep 3rd Pressure Plate - True - Shipwreck Vault - 0x17BB4: 158654 - 0x00AFB (Vault Panel) - True - Symmetry & Sound Dots & Colored Dots Door - 0x17BB4 (Vault Door) - 0x00AFB @@ -423,19 +467,16 @@ Door - 0x17BB4 (Vault Door) - 0x00AFB Shipwreck Vault (Shipwreck): 158655 - 0x03535 (Vault Box) - True - True -Keep Tower (Keep) - Keep - 0x04F8F: -158206 - 0x0361B (Tower Shortcut Panel) - True - True -Door - 0x04F8F (Tower Shortcut) - 0x0361B -158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True -158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Rotated Shapers & Triangles & Stars & Stars + Same Colored Symbol & Colored Squares & Black/White Squares -Laser - 0x014BB (Laser) - 0x0360E | 0x03317 -159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True -159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True -159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True -159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True -159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True -159250 - 0x28AE9 (Path EP) - True - True -159251 - 0x3348F (Hedges EP) - True - True +==Monastery== + +Monastery Obelisk (Monastery) - Entry - True: +159710 - 0xFFE10 (Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True +159711 - 0xFFE11 (Obelisk Side 2) - 0x03AC5 - True +159712 - 0xFFE12 (Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True +159713 - 0xFFE13 (Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True +159714 - 0xFFE14 (Obelisk Side 5) - 0x03E01 - True +159715 - 0xFFE15 (Obelisk Side 6) - 0x289F4 & 0x289F5 - True +159719 - 0x00263 (Obelisk) - True - True Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128 & 0x0C153 - Monastery Garden - 0x03750: 158207 - 0x03713 (Laser Shortcut Panel) - True - True @@ -457,6 +498,9 @@ Laser - 0x17C65 (Laser) - 0x17CA4 159137 - 0x03DAC (Facade Left Stairs EP) - True - True 159138 - 0x03DAD (Facade Right Stairs EP) - True - True 159140 - 0x03E01 (Grass Stairs EP) - True - True +159120 - 0x03BE2 (Garden Left EP) - 0x03750 - True +159121 - 0x03BE3 (Garden Right EP) - True - True +159122 - 0x0A409 (Wall EP) - True - True Inside Monastery (Monastery): 158213 - 0x09D9B (Shutters Control) - True - Dots @@ -470,7 +514,18 @@ Inside Monastery (Monastery): Monastery Garden (Monastery): -Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - RGB House - 0x28A61 - Windmill Interior - 0x1845B - Town Inside Cargo Box - 0x0A0C9: +==Town== + +Town Obelisk (Town) - Entry - True: +159750 - 0xFFE50 (Obelisk Side 1) - 0x035C7 - True +159751 - 0xFFE51 (Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True +159752 - 0xFFE52 (Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True +159753 - 0xFFE53 (Obelisk Side 4) - 0x28B30 & 0x035C9 - True +159754 - 0xFFE54 (Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True +159755 - 0xFFE55 (Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True +159759 - 0x0A16C (Obelisk) - True - True + +Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - Town RGB House - 0x28A61 - Town Inside Cargo Box - 0x0A0C9 - Outside Windmill - True: 158218 - 0x0A054 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat 158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Squares & Black/White Squares & Shapers & Triangles Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8 @@ -491,11 +546,6 @@ Door - 0x28A61 (RGB House Entry) - 0x28A0D Door - 0x03BB0 (Church Entry) - 0x03C08 158228 - 0x28A79 (Maze Panel) - True - True Door - 0x28AA2 (Maze Stairs) - 0x28A79 -158241 - 0x17F5F (Windmill Entry Panel) - True - Dots -Door - 0x1845B (Windmill Entry) - 0x17F5F -159010 - 0x037B6 (Windmill First Blade EP) - 0x17D02 - True -159011 - 0x037B2 (Windmill Second Blade EP) - 0x17D02 - True -159012 - 0x000F7 (Windmill Third Blade EP) - 0x17D02 - True 159540 - 0x03335 (Tower Underside Third EP) - True - True 159541 - 0x03412 (Tower Underside Fourth EP) - True - True 159542 - 0x038A6 (Tower Underside First EP) - True - True @@ -528,20 +578,26 @@ Town Church (Town): 158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True 159553 - 0x03BD1 (Black Line Church EP) - True - True -RGB House (Town) - RGB Room - 0x2897B: +Town RGB House (Town RGB House) - Town RGB House Upstairs - 0x2897B: 158242 - 0x034E4 (Sound Room Left) - True - True 158243 - 0x034E3 (Sound Room Right) - True - Sound Dots -Door - 0x2897B (RGB House Stairs) - 0x034E4 & 0x034E3 +Door - 0x2897B (Stairs) - 0x034E4 & 0x034E3 -RGB Room (Town): +Town RGB House Upstairs (Town RGB House Upstairs): 158244 - 0x334D8 (RGB Control) - True - Rotated Shapers & Squares & Colored Squares & Triangles -158245 - 0x03C0C (RGB Room Left) - 0x334D8 - Squares & Colored Squares & Black/White Squares & Eraser -158246 - 0x03C08 (RGB Room Right) - 0x334D8 & 0x03C0C - Symmetry & Dots & Colored Dots & Triangles +158245 - 0x03C0C (Left) - 0x334D8 - Squares & Colored Squares & Black/White Squares & Eraser +158246 - 0x03C08 (Right) - 0x334D8 & 0x03C0C - Symmetry & Dots & Colored Dots & Triangles -Town Tower (Town Tower) - Town - True - Town Tower Top - 0x27798 & 0x27799 & 0x2779A & 0x2779C: -Door - 0x27798 (Second Door) - 0x28ACC -Door - 0x2779C (Third Door) - 0x28AD9 +Town Tower Bottom (Town Tower) - Town - True - Town Tower After First Door - 0x27799: Door - 0x27799 (First Door) - 0x28A69 + +Town Tower After First Door (Town Tower) - Town Tower After Second Door - 0x27798: +Door - 0x27798 (Second Door) - 0x28ACC + +Town Tower After Second Door (Town Tower) - Town Tower After Third Door - 0x2779C: +Door - 0x2779C (Third Door) - 0x28AD9 + +Town Tower After Third Door (Town Tower) - Town Tower Top - 0x2779A: Door - 0x2779A (Fourth Door) - 0x28B39 Town Tower Top (Town): @@ -550,6 +606,15 @@ Laser - 0x032F9 (Laser) - 0x032F5 159422 - 0x33692 (Brown Bridge EP) - True - True 159551 - 0x03BCE (Black Line Tower EP) - True - True +==Windmill & Theater== + +Outside Windmill (Windmill) - Windmill Interior - 0x1845B: +159010 - 0x037B6 (First Blade EP) - 0x17D02 - True +159011 - 0x037B2 (Second Blade EP) - 0x17D02 - True +159012 - 0x000F7 (Third Blade EP) - 0x17D02 - True +158241 - 0x17F5F (Entry Panel) - True - Dots +Door - 0x1845B (Entry) - 0x17F5F + Windmill Interior (Windmill) - Theater - 0x17F88: 158247 - 0x17D02 (Turn Control) - True - Dots 158248 - 0x17F89 (Theater Entry Panel) - True - Squares & Black/White Squares & Eraser & Triangles @@ -573,6 +638,8 @@ Door - 0x3CCDF (Exit Right) - 0x33AB2 159556 - 0x33A2A (Door EP) - 0x03553 - True 159558 - 0x33B06 (Church EP) - 0x0354E - True +==Jungle== + Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF: 158251 - 0x17CDF (Shore Boat Spawn) - True - Boat 158609 - 0x17F9B (Discard) - True - Arrows @@ -604,19 +671,18 @@ Door - 0x3873B (Laser Shortcut) - 0x337FA 159350 - 0x035CB (Bamboo CCW EP) - True - True 159351 - 0x035CF (Bamboo CW EP) - True - True -Outside Jungle River (River) - Main Island - True - Monastery Garden - 0x0CF2A - River Vault - 0x15287: +Outside Jungle River (Jungle) - Main Island - True - Monastery Garden - 0x0CF2A - Jungle Vault - 0x15287: 158267 - 0x17CAA (Monastery Garden Shortcut Panel) - True - True Door - 0x0CF2A (Monastery Garden Shortcut) - 0x17CAA 158663 - 0x15ADD (Vault Panel) - True - Black/White Squares & Dots Door - 0x15287 (Vault Door) - 0x15ADD 159110 - 0x03AC5 (Green Leaf Moss EP) - True - True -159120 - 0x03BE2 (Monastery Garden Left EP) - 0x03750 - True -159121 - 0x03BE3 (Monastery Garden Right EP) - True - True -159122 - 0x0A409 (Monastery Wall EP) - True - True -River Vault (River): +Jungle Vault (Jungle): 158664 - 0x03702 (Vault Box) - True - True +==Bunker== + Outside Bunker (Bunker) - Main Island - True - Bunker - 0x0C2A4: 158268 - 0x17C2E (Entry Panel) - True - Squares & Black/White Squares Door - 0x0C2A4 (Entry) - 0x17C2E @@ -650,9 +716,11 @@ Door - 0x0A08D (Elevator Room Entry) - 0x17E67 Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay: 159311 - 0x035F5 (Tinted Door EP) - 0x17C79 - True -Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079: +Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Cyan Room - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079: 158286 - 0x0A079 (Elevator Control) - True - Colored Squares & Black/White Squares +Bunker Cyan Room (Bunker) - Bunker Elevator - TrueOneWay: + Bunker Green Room (Bunker) - Bunker Elevator - TrueOneWay: 159310 - 0x000D3 (Green Room Flowers EP) - True - True @@ -660,6 +728,8 @@ Bunker Laser Platform (Bunker) - Bunker Elevator - TrueOneWay: 158710 - 0x09DE0 (Laser Panel) - True - True Laser - 0x0C2B2 (Laser) - 0x09DE0 +==Swamp== + Outside Swamp (Swamp) - Swamp Entry Area - 0x00C1C - Main Island - True: 158287 - 0x0056E (Entry Panel) - True - Rotated Shapers & Black/White Squares & Triangles Door - 0x00C1C (Entry) - 0x0056E @@ -774,13 +844,29 @@ Laser - 0x00BF6 (Laser) - 0x03615 158342 - 0x17C02 (Laser Shortcut Right Panel) - 0x17C05 - Shapers & Negative Shapers & Stars & Stars + Same Colored Symbol Door - 0x2D880 (Laser Shortcut) - 0x17C02 -Treehouse Entry Area (Treehouse) - Treehouse Between Doors - 0x0C309 - The Ocean - 0x17C95: +==Treehouse== + +Treehouse Obelisk (Treehouse) - Entry - True: +159720 - 0xFFE20 (Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True +159721 - 0xFFE21 (Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True +159722 - 0xFFE22 (Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True +159723 - 0xFFE23 (Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True +159724 - 0xFFE24 (Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True +159725 - 0xFFE25 (Obelisk Side 6) - 0x28AE9 & 0x3348F - True +159729 - 0x00097 (Obelisk) - True - True + +Treehouse Beach (Treehouse Beach) - Main Island - True: +159200 - 0x0053D (Rock Shadow EP) - True - True +159201 - 0x0053E (Sand Shadow EP) - True - True +159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True + +Treehouse Entry Area (Treehouse) - Treehouse Between Entry Doors - 0x0C309 - The Ocean - 0x17C95: 158343 - 0x17C95 (Boat Spawn) - True - Boat 158344 - 0x0288C (First Door Panel) - True - Stars & Stars + Same Colored Symbol & Triangles Door - 0x0C309 (First Door) - 0x0288C 159210 - 0x33721 (Buoy EP) - 0x17C95 - True -Treehouse Between Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310: +Treehouse Between Entry Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310: 158345 - 0x02886 (Second Door Panel) - True - Stars & Stars + Same Colored Symbol & Triangles Door - 0x0C310 (Second Door) - 0x02886 @@ -809,7 +895,7 @@ Treehouse First Purple Bridge (Treehouse) - Treehouse Second Purple Bridge - 0x1 158360 - 0x17D2D (First Purple Bridge 4) - 0x17CE4 - Stars & Dots & Full Dots 158361 - 0x17D6C (First Purple Bridge 5) - 0x17D2D - Stars & Dots & Full Dots -Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2: +Treehouse Right Orange Bridge (Treehouse) - Treehouse Drawbridge Platform - 0x17DA2: 158391 - 0x17D88 (Right Orange Bridge 1) - True - Stars & Stars + Same Colored Symbol & Triangles 158392 - 0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars & Stars + Same Colored Symbol & Triangles 158393 - 0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars & Stars + Same Colored Symbol & Triangles @@ -823,7 +909,7 @@ Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2: 158401 - 0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars & Stars + Same Colored Symbol & Triangles 158402 - 0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars & Stars + Same Colored Symbol & Triangles -Treehouse Bridge Platform (Treehouse) - Main Island - 0x0C32D: +Treehouse Drawbridge Platform (Treehouse) - Main Island - 0x0C32D: 158404 - 0x037FF (Drawbridge Panel) - True - Stars Door - 0x0C32D (Drawbridge) - 0x037FF @@ -882,7 +968,19 @@ Treehouse Laser Room (Treehouse): 158403 - 0x17CBC (Laser House Door Timer Inside) - True - True Laser - 0x028A4 (Laser) - 0x03613 +==Mountain (Outside)== + +Mountainside Obelisk (Mountainside) - Entry - True: +159730 - 0xFFE30 (Obelisk Side 1) - 0x001A3 & 0x335AE - True +159731 - 0xFFE31 (Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True +159732 - 0xFFE32 (Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True +159733 - 0xFFE33 (Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True +159734 - 0xFFE34 (Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True +159735 - 0xFFE35 (Obelisk Side 6) - 0x035CB & 0x035CF - True +159739 - 0x00367 (Obelisk) - True - True + Mountainside (Mountainside) - Main Island - True - Mountaintop - True - Mountainside Vault - 0x00085: +159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True 158612 - 0x17C42 (Discard) - True - Arrows 158665 - 0x002A6 (Vault Panel) - True - Symmetry & Colored Squares & Triangles & Stars & Stars + Same Colored Symbol Door - 0x00085 (Vault Door) - 0x002A6 @@ -893,7 +991,7 @@ Door - 0x00085 (Vault Door) - 0x002A6 Mountainside Vault (Mountainside): 158666 - 0x03542 (Vault Box) - True - True -Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34: +Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34: 158405 - 0x0042D (River Shape) - True - True 158406 - 0x09F7F (Box Short) - 7 Lasers + Redirect - True 158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Stars & Black/White Squares & Stars + Same Colored Symbol & Triangles @@ -903,10 +1001,12 @@ Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34: 159324 - 0x336C8 (Arch White Right EP) - True - True 159326 - 0x3369A (Arch White Left EP) - True - True -Mountain Top Layer (Mountain Floor 1) - Mountain Top Layer Bridge - 0x09E39: +==Mountain (Inside)== + +Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39: 158408 - 0x09E39 (Light Bridge Controller) - True - Eraser & Triangles -Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - TrueOneWay: +Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay: 158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots & Stars & Stars + Same Colored Symbol 158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Triangles 158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers & Stars & Stars + Same Colored Symbol @@ -925,10 +1025,10 @@ Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - True 158424 - 0x09EAD (Trash Pillar 1) - True - Rotated Shapers & Stars 158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Rotated Shapers & Triangles -Mountain Top Layer At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: +Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B -Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Blue Bridge - 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 At Door - 0x09ED8 & 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: 158426 - 0x09FD3 (Near Row 1) - True - Stars & Colored Squares & Stars + Same Colored Symbol 158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Stars & Triangles & Stars + Same Colored Symbol 158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol @@ -936,8 +1036,6 @@ Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Stars & Stars + Same Colored Symbol & Rotated Shapers & Eraser Door - 0x09FFB (Staircase Near) - 0x09FD8 -Mountain Floor 2 Blue Bridge (Mountain Floor 2) - Mountain Floor 2 Beyond Bridge - TrueOneWay - Mountain Floor 2 At Door - 0x09ED8: - Mountain Floor 2 At Door (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD: Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86 @@ -959,10 +1057,10 @@ Mountain Floor 2 Light Bridge Room Far (Mountain Floor 2): Mountain Floor 2 Elevator Room (Mountain Floor 2) - Mountain Floor 2 Elevator - TrueOneWay: 158613 - 0x17F93 (Elevator Discard) - True - Arrows -Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Third Layer - 0x09EEB: +Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Floor 3 - 0x09EEB: 158439 - 0x09EEB (Elevator Control Panel) - True - Dots -Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 3 (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay: 158440 - 0x09FC1 (Giant Puzzle Bottom Left) - True - Shapers & Eraser & Negative Shapers 158441 - 0x09F8E (Giant Puzzle Bottom Right) - True - Shapers & Eraser & Negative Shapers 158442 - 0x09F01 (Giant Puzzle Top Right) - True - Shapers & Eraser & Negative Shapers @@ -972,13 +1070,32 @@ Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueO 159314 - 0x09D5E (Blue Bridge EP) - 0x09E86 & 0x09ED8 - True Door - 0x09F89 (Exit) - 0x09FDA -Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Final Room - 0x0C141: +Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Mountain Bottom Floor Pillars Room - 0x0C141: 158614 - 0x17FA2 (Discard) - 0xFFF00 - Arrows -158445 - 0x01983 (Final Room Entry Left) - True - Shapers & Stars -158446 - 0x01987 (Final Room Entry Right) - True - Squares & Colored Squares & Dots -Door - 0x0C141 (Final Room Entry) - 0x01983 & 0x01987 +158445 - 0x01983 (Pillars Room Entry Left) - True - Shapers & Stars +158446 - 0x01987 (Pillars Room Entry Right) - True - Squares & Colored Squares & Dots +Door - 0x0C141 (Pillars Room Entry) - 0x01983 & 0x01987 Door - 0x17F33 (Rock Open) - 0x17FA2 | 0x334E1 +Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB & 0x33961: +158522 - 0x0383A (Right Pillar 1) - True - Stars & Eraser & Triangles & Stars + Same Colored Symbol +158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Dots & Full Dots & Triangles & Symmetry +158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Shapers & Stars & Negative Shapers & Stars + Same Colored Symbol & Symmetry +158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Eraser & Symmetry & Stars & Stars + Same Colored Symbol & Negative Shapers & Shapers +158526 - 0x0383D (Left Pillar 1) - True - Stars & Black/White Squares & Stars + Same Colored Symbol +158527 - 0x0383F (Left Pillar 2) - 0x0383D - Triangles & Symmetry +158528 - 0x03859 (Left Pillar 3) - 0x0383F - Symmetry & Shapers & Black/White Squares +158529 - 0x339BB (Left Pillar 4) - 0x03859 - Symmetry & Black/White Squares & Stars & Stars + Same Colored Symbol & Triangles & Colored Dots + +Elevator (Mountain Bottom Floor): +158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True +158531 - 0x3D9A7 (Elevator Door Close Right) - True - True +158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True +158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True +158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True +158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True +158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True + Mountain Pink Bridge EP (Mountain Floor 2): 159312 - 0x09D63 (Pink Bridge EP) - 0x09E39 - True @@ -987,7 +1104,9 @@ Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D: Door - 0x2D77D (Caves Entry) - 0x00FF8 158448 - 0x334E1 (Rock Control) - True - True -Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Path to Challenge - 0x019A5: +==Caves== + +Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5: 158451 - 0x335AB (Elevator Inside Control) - True - Dots & Squares & Black/White Squares 158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Squares & Black/White Squares 158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Squares & Black/White Squares & Dots @@ -1042,10 +1161,12 @@ Door - 0x2D73F (Mountain Shortcut Door) - 0x021D7 Door - 0x2D859 (Swamp Shortcut Door) - 0x17CF2 159341 - 0x3397C (Skylight EP) - True - True -Path to Challenge (Caves) - Challenge - 0x0A19A: +Caves Path to Challenge (Caves) - Challenge - 0x0A19A: 158477 - 0x0A16E (Challenge Entry Panel) - True - Stars & Arrows & Stars + Same Colored Symbol Door - 0x0A19A (Challenge Entry) - 0x0A16E +==Challenge== + Challenge (Challenge) - Tunnels - 0x0348A - Challenge Vault - 0x04D75: 158499 - 0x0A332 (Start Timer) - 11 Lasers - True 158500 - 0x0088E (Small Basic) - 0x0A332 - True @@ -1074,7 +1195,9 @@ Door - 0x0348A (Tunnels Entry) - 0x039B4 Challenge Vault (Challenge): 158667 - 0x0356B (Vault Box) - 0x1C31A & 0x1C319 - True -Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Lowest Level Inbetween Shortcuts - 0x27263 - Town - 0x09E87: +==Tunnels== + +Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Behind Elevator - 0x27263 - Town - 0x09E87: 158668 - 0x2FAF6 (Vault Box) - True - True 158519 - 0x27732 (Theater Shortcut Panel) - True - True Door - 0x27739 (Theater Shortcut) - 0x27732 @@ -1084,24 +1207,7 @@ Door - 0x27263 (Desert Shortcut) - 0x2773D Door - 0x09E87 (Town Shortcut) - 0x09E85 159557 - 0x33A20 (Theater Flowers EP) - 0x03553 & Theater to Tunnels - True -Final Room (Mountain Final Room) - Elevator - 0x339BB & 0x33961: -158522 - 0x0383A (Right Pillar 1) - True - Stars & Eraser & Triangles & Stars + Same Colored Symbol -158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Dots & Full Dots & Triangles & Symmetry -158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Shapers & Stars & Negative Shapers & Stars + Same Colored Symbol & Symmetry -158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Eraser & Symmetry & Stars & Stars + Same Colored Symbol & Negative Shapers & Shapers -158526 - 0x0383D (Left Pillar 1) - True - Stars & Black/White Squares & Stars + Same Colored Symbol -158527 - 0x0383F (Left Pillar 2) - 0x0383D - Triangles & Symmetry -158528 - 0x03859 (Left Pillar 3) - 0x0383F - Symmetry & Shapers & Black/White Squares -158529 - 0x339BB (Left Pillar 4) - 0x03859 - Symmetry & Black/White Squares & Stars & Stars + Same Colored Symbol & Triangles & Colored Dots - -Elevator (Mountain Final Room): -158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True -158531 - 0x3D9A7 (Elevator Door Close Right) - True - True -158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True -158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True -158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True -158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True -158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True +==Boat== The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Treehouse Entry Area - TrueOneWay - Quarry Boathouse Behind Staircase - TrueOneWay - Inside Glass Factory Behind Back Wall - TrueOneWay: 159042 - 0x22106 (Desert EP) - True - True @@ -1114,45 +1220,3 @@ The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Tre 159521 - 0x33879 (Tutorial Reflection EP) - True - True 159522 - 0x03C19 (Tutorial Moss EP) - True - True 159531 - 0x035C9 (Cargo Box EP) - 0x0A0C9 - True - -Obelisks (EPs) - Entry - True: -159700 - 0xFFE00 (Desert Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True -159701 - 0xFFE01 (Desert Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True -159702 - 0xFFE02 (Desert Obelisk Side 3) - 0x3351D - True -159703 - 0xFFE03 (Desert Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True -159704 - 0xFFE04 (Desert Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True -159709 - 0x00359 (Desert Obelisk) - True - True -159710 - 0xFFE10 (Monastery Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True -159711 - 0xFFE11 (Monastery Obelisk Side 2) - 0x03AC5 - True -159712 - 0xFFE12 (Monastery Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True -159713 - 0xFFE13 (Monastery Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True -159714 - 0xFFE14 (Monastery Obelisk Side 5) - 0x03E01 - True -159715 - 0xFFE15 (Monastery Obelisk Side 6) - 0x289F4 & 0x289F5 - True -159719 - 0x00263 (Monastery Obelisk) - True - True -159720 - 0xFFE20 (Treehouse Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True -159721 - 0xFFE21 (Treehouse Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True -159722 - 0xFFE22 (Treehouse Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True -159723 - 0xFFE23 (Treehouse Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True -159724 - 0xFFE24 (Treehouse Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True -159725 - 0xFFE25 (Treehouse Obelisk Side 6) - 0x28AE9 & 0x3348F - True -159729 - 0x00097 (Treehouse Obelisk) - True - True -159730 - 0xFFE30 (River Obelisk Side 1) - 0x001A3 & 0x335AE - True -159731 - 0xFFE31 (River Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True -159732 - 0xFFE32 (River Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True -159733 - 0xFFE33 (River Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True -159734 - 0xFFE34 (River Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True -159735 - 0xFFE35 (River Obelisk Side 6) - 0x035CB & 0x035CF - True -159739 - 0x00367 (River Obelisk) - True - True -159740 - 0xFFE40 (Quarry Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True -159741 - 0xFFE41 (Quarry Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True -159742 - 0xFFE42 (Quarry Obelisk Side 3) - 0x289CF & 0x289D1 - True -159743 - 0xFFE43 (Quarry Obelisk Side 4) - 0x33692 - True -159744 - 0xFFE44 (Quarry Obelisk Side 5) - 0x03E77 & 0x03E7C - True -159749 - 0x22073 (Quarry Obelisk) - True - True -159750 - 0xFFE50 (Town Obelisk Side 1) - 0x035C7 - True -159751 - 0xFFE51 (Town Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True -159752 - 0xFFE52 (Town Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True -159753 - 0xFFE53 (Town Obelisk Side 4) - 0x28B30 & 0x035C9 - True -159754 - 0xFFE54 (Town Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True -159755 - 0xFFE55 (Town Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True -159759 - 0x0A16C (Town Obelisk) - True - True diff --git a/worlds/witness/WitnessLogicVanilla.txt b/worlds/witness/WitnessLogicVanilla.txt index 71af12f76d..62c38d4124 100644 --- a/worlds/witness/WitnessLogicVanilla.txt +++ b/worlds/witness/WitnessLogicVanilla.txt @@ -1,12 +1,14 @@ +==Tutorial (Inside)== + Menu (Menu) - Entry - True: Entry (Entry): -First Hallway (First Hallway) - Entry - True - First Hallway Room - 0x00064: +Tutorial First Hallway (Tutorial First Hallway) - Entry - True - Tutorial First Hallway Room - 0x00064: 158000 - 0x00064 (Straight) - True - True 159510 - 0x01848 (EP) - 0x00064 - True -First Hallway Room (First Hallway) - Tutorial - 0x00182: +Tutorial First Hallway Room (Tutorial First Hallway) - Tutorial - 0x00182: 158001 - 0x00182 (Bend) - True - True Tutorial (Tutorial) - Outside Tutorial - 0x03629: @@ -23,6 +25,8 @@ Tutorial (Tutorial) - Outside Tutorial - 0x03629: 159513 - 0x33600 (Patio Flowers EP) - 0x0C373 - True 159517 - 0x3352F (Gate EP) - 0x03505 - True +==Tutorial (Outside)== + Outside Tutorial (Outside Tutorial) - Outside Tutorial Path To Outpost - 0x03BA2 - Outside Tutorial Vault - 0x033D0: 158650 - 0x033D4 (Vault Panel) - True - Dots & Black/White Squares Door - 0x033D0 (Vault Door) - 0x033D4 @@ -58,9 +62,23 @@ Outside Tutorial Outpost (Outside Tutorial) - Outside Tutorial - 0x04CA3: Door - 0x04CA3 (Outpost Exit) - 0x04CA4 158600 - 0x17CFB (Discard) - True - Triangles +Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307: +158071 - 0x00143 (Apple Tree 1) - True - True +158072 - 0x0003B (Apple Tree 2) - 0x00143 - True +158073 - 0x00055 (Apple Tree 3) - 0x0003B - True +Door - 0x03307 (First Gate) - 0x00055 + +Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313: +158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True +158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True +Door - 0x03313 (Second Gate) - 0x032FF + +Orchard End (Orchard): + Main Island (Main Island) - Outside Tutorial - True: 159801 - 0xFFD00 (Reached Independently) - True - True -159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True + +==Glass Factory== Outside Glass Factory (Glass Factory) - Main Island - True - Inside Glass Factory - 0x01A29: 158027 - 0x01A54 (Entry Panel) - True - Symmetry @@ -85,6 +103,8 @@ Door - 0x0D7ED (Back Wall) - 0x0005C Inside Glass Factory Behind Back Wall (Glass Factory) - The Ocean - 0x17CC8: 158039 - 0x17CC8 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat +==Symmetry Island== + Outside Symmetry Island (Symmetry Island) - Main Island - True - Symmetry Island Lower - 0x17F3E: 158040 - 0x000B0 (Lower Panel) - 0x0343A - Dots Door - 0x17F3E (Lower) - 0x000B0 @@ -128,20 +148,17 @@ Symmetry Island Upper (Symmetry Island): Laser - 0x00509 (Laser) - 0x0360D 159001 - 0x03367 (Glass Factory Black Line EP) - True - True -Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307: -158071 - 0x00143 (Apple Tree 1) - True - True -158072 - 0x0003B (Apple Tree 2) - 0x00143 - True -158073 - 0x00055 (Apple Tree 3) - 0x0003B - True -Door - 0x03307 (First Gate) - 0x00055 +==Desert== -Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313: -158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True -158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True -Door - 0x03313 (Second Gate) - 0x032FF +Desert Obelisk (Desert) - Entry - True: +159700 - 0xFFE00 (Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True +159701 - 0xFFE01 (Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True +159702 - 0xFFE02 (Obelisk Side 3) - 0x3351D - True +159703 - 0xFFE03 (Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True +159704 - 0xFFE04 (Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True +159709 - 0x00359 (Obelisk) - True - True -Orchard End (Orchard): - -Desert Outside (Desert) - Main Island - True - Desert Floodlight Room - 0x09FEE - Desert Vault - 0x03444: +Desert Outside (Desert) - Main Island - True - Desert Light Room - 0x09FEE - Desert Vault - 0x03444: 158652 - 0x0CC7B (Vault Panel) - True - Dots & Shapers & Rotated Shapers & Negative Shapers & Full Dots Door - 0x03444 (Vault Door) - 0x0CC7B 158602 - 0x17CE7 (Discard) - True - Triangles @@ -172,14 +189,14 @@ Laser - 0x012FB (Laser) - 0x03608 Desert Vault (Desert): 158653 - 0x0339E (Vault Box) - True - True -Desert Floodlight Room (Desert) - Desert Pond Room - 0x0C2C3: +Desert Light Room (Desert) - Desert Pond Room - 0x0C2C3: 158087 - 0x09FAA (Light Control) - True - True 158088 - 0x00422 (Light Room 1) - 0x09FAA - True 158089 - 0x006E3 (Light Room 2) - 0x09FAA - True 158090 - 0x0A02D (Light Room 3) - 0x09FAA & 0x00422 & 0x006E3 - True Door - 0x0C2C3 (Pond Room Entry) - 0x0A02D -Desert Pond Room (Desert) - Desert Water Levels Room - 0x0A24B: +Desert Pond Room (Desert) - Desert Flood Room - 0x0A24B: 158091 - 0x00C72 (Pond Room 1) - True - True 158092 - 0x0129D (Pond Room 2) - 0x00C72 - True 158093 - 0x008BB (Pond Room 3) - 0x0129D - True @@ -190,7 +207,7 @@ Door - 0x0A24B (Flood Room Entry) - 0x0A249 159043 - 0x0A14C (Pond Room Near Reflection EP) - True - True 159044 - 0x0A14D (Pond Room Far Reflection EP) - True - True -Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316: +Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316: 158097 - 0x1C2DF (Reduce Water Level Far Left) - True - True 158098 - 0x1831E (Reduce Water Level Far Right) - True - True 158099 - 0x1C260 (Reduce Water Level Near Left) - True - True @@ -208,7 +225,7 @@ Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316: Door - 0x0C316 (Elevator Room Entry) - 0x18076 159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True -Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x01317: +Desert Elevator Room (Desert) - Desert Behind Elevator - 0x01317: 158111 - 0x17C31 (Elevator Room Transparent) - True - True 158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True 158114 - 0x0A015 (Elevator Room Hexagonal Control) - 0x17C31 - True @@ -218,9 +235,19 @@ Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x0131 159035 - 0x037BB (Elevator EP) - 0x01317 - True Door - 0x01317 (Elevator) - 0x03608 -Desert Lowest Level Inbetween Shortcuts (Desert): +Desert Behind Elevator (Desert): -Outside Quarry (Quarry) - Main Island - True - Quarry Between Entrys - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01: +==Quarry== + +Quarry Obelisk (Quarry) - Entry - True: +159740 - 0xFFE40 (Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True +159741 - 0xFFE41 (Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True +159742 - 0xFFE42 (Obelisk Side 3) - 0x289CF & 0x289D1 - True +159743 - 0xFFE43 (Obelisk Side 4) - 0x33692 - True +159744 - 0xFFE44 (Obelisk Side 5) - 0x03E77 & 0x03E7C - True +159749 - 0x22073 (Obelisk) - True - True + +Outside Quarry (Quarry) - Main Island - True - Quarry Between Entry Doors - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01: 158118 - 0x09E57 (Entry 1 Panel) - True - Black/White Squares 158603 - 0x17CF0 (Discard) - True - Triangles 158702 - 0x03612 (Laser Panel) - 0x0A3D0 & 0x0367C - Eraser & Shapers @@ -236,7 +263,7 @@ Quarry Elevator (Quarry) - Outside Quarry - 0x17CC4 - Quarry - 0x17CC4: 158120 - 0x17CC4 (Elevator Control) - 0x0367C - Dots & Eraser 159403 - 0x17CB9 (Railroad EP) - 0x17CC4 - True -Quarry Between Entrys (Quarry) - Quarry - 0x17C07: +Quarry Between Entry Doors (Quarry) - Quarry - 0x17C07: 158119 - 0x17C09 (Entry 2 Panel) - True - Shapers Door - 0x17C07 (Entry 2) - 0x17C09 @@ -322,6 +349,8 @@ Door - 0x3865F (Second Barrier) - 0x38663 158169 - 0x0A3D0 (Back Second Row 3) - 0x0A3CC - Stars & Eraser & Shapers 159401 - 0x005F6 (Hook EP) - 0x275FA & 0x03852 & 0x3865F - True +==Shadows== + Shadows (Shadows) - Main Island - True - Shadows Ledge - 0x19B24 - Shadows Laser Room - 0x194B2 | 0x19665: 158170 - 0x334DB (Door Timer Outside) - True - True Door - 0x19B24 (Timed Door) - 0x334DB | 0x334DC @@ -361,19 +390,18 @@ Shadows Laser Room (Shadows): 158703 - 0x19650 (Laser Panel) - 0x194B2 & 0x19665 - True Laser - 0x181B3 (Laser) - 0x19650 -Treehouse Beach (Treehouse Beach) - Main Island - True: -159200 - 0x0053D (Rock Shadow EP) - True - True -159201 - 0x0053E (Sand Shadow EP) - True - True -159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True +==Keep== -Keep (Keep) - Main Island - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC: +Outside Keep (Keep) - Main Island - True: +159430 - 0x03E77 (Red Flowers EP) - True - True +159431 - 0x03E7C (Purple Flowers EP) - True - True + +Keep (Keep) - Outside Keep - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC: 158193 - 0x00139 (Hedge Maze 1) - True - True 158197 - 0x0A3A8 (Reset Pressure Plates 1) - True - True 158198 - 0x033EA (Pressure Plates 1) - 0x0A3A8 - Dots Door - 0x01954 (Hedge Maze 1 Exit) - 0x00139 Door - 0x01BEC (Pressure Plates 1 Exit) - 0x033EA -159430 - 0x03E77 (Red Flowers EP) - True - True -159431 - 0x03E7C (Purple Flowers EP) - True - True Keep 2nd Maze (Keep) - Keep - 0x018CE - Keep 3rd Maze - 0x019D8: Door - 0x018CE (Hedge Maze 2 Shortcut) - 0x00139 @@ -408,6 +436,22 @@ Door - 0x01D40 (Pressure Plates 4 Exit) - 0x01D3F 158205 - 0x09E49 (Shadows Shortcut Panel) - True - True Door - 0x09E3D (Shadows Shortcut) - 0x09E49 +Keep Tower (Keep) - Keep - 0x04F8F: +158206 - 0x0361B (Tower Shortcut Panel) - True - True +Door - 0x04F8F (Tower Shortcut) - 0x0361B +158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True +158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Dots & Shapers & Black/White Squares & Rotated Shapers +Laser - 0x014BB (Laser) - 0x0360E | 0x03317 +159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True +159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 & 0x01BEA - True +159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True +159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True +159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True +159250 - 0x28AE9 (Path EP) - True - True +159251 - 0x3348F (Hedges EP) - True - True + +==Shipwreck== + Shipwreck (Shipwreck) - Keep 3rd Pressure Plate - True - Shipwreck Vault - 0x17BB4: 158654 - 0x00AFB (Vault Panel) - True - Symmetry & Sound Dots & Colored Dots Door - 0x17BB4 (Vault Door) - 0x00AFB @@ -423,19 +467,16 @@ Door - 0x17BB4 (Vault Door) - 0x00AFB Shipwreck Vault (Shipwreck): 158655 - 0x03535 (Vault Box) - True - True -Keep Tower (Keep) - Keep - 0x04F8F: -158206 - 0x0361B (Tower Shortcut Panel) - True - True -Door - 0x04F8F (Tower Shortcut) - 0x0361B -158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True -158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Dots & Shapers & Black/White Squares & Rotated Shapers -Laser - 0x014BB (Laser) - 0x0360E | 0x03317 -159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True -159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 & 0x01BEA - True -159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True -159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True -159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True -159250 - 0x28AE9 (Path EP) - True - True -159251 - 0x3348F (Hedges EP) - True - True +==Monastery== + +Monastery Obelisk (Monastery) - Entry - True: +159710 - 0xFFE10 (Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True +159711 - 0xFFE11 (Obelisk Side 2) - 0x03AC5 - True +159712 - 0xFFE12 (Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True +159713 - 0xFFE13 (Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True +159714 - 0xFFE14 (Obelisk Side 5) - 0x03E01 - True +159715 - 0xFFE15 (Obelisk Side 6) - 0x289F4 & 0x289F5 - True +159719 - 0x00263 (Obelisk) - True - True Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128 & 0x0C153 - Monastery Garden - 0x03750: 158207 - 0x03713 (Laser Shortcut Panel) - True - True @@ -457,6 +498,9 @@ Laser - 0x17C65 (Laser) - 0x17CA4 159137 - 0x03DAC (Facade Left Stairs EP) - True - True 159138 - 0x03DAD (Facade Right Stairs EP) - True - True 159140 - 0x03E01 (Grass Stairs EP) - True - True +159120 - 0x03BE2 (Garden Left EP) - 0x03750 - True +159121 - 0x03BE3 (Garden Right EP) - True - True +159122 - 0x0A409 (Wall EP) - True - True Inside Monastery (Monastery): 158213 - 0x09D9B (Shutters Control) - True - Dots @@ -470,7 +514,18 @@ Inside Monastery (Monastery): Monastery Garden (Monastery): -Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - RGB House - 0x28A61 - Windmill Interior - 0x1845B - Town Inside Cargo Box - 0x0A0C9: +==Town== + +Town Obelisk (Town) - Entry - True: +159750 - 0xFFE50 (Obelisk Side 1) - 0x035C7 - True +159751 - 0xFFE51 (Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True +159752 - 0xFFE52 (Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True +159753 - 0xFFE53 (Obelisk Side 4) - 0x28B30 & 0x035C9 - True +159754 - 0xFFE54 (Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True +159755 - 0xFFE55 (Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True +159759 - 0x0A16C (Obelisk) - True - True + +Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - Town RGB House - 0x28A61 - Town Inside Cargo Box - 0x0A0C9 - Outside Windmill - True: 158218 - 0x0A054 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat 158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Black/White Squares & Shapers Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8 @@ -491,11 +546,6 @@ Door - 0x28A61 (RGB House Entry) - 0x28998 Door - 0x03BB0 (Church Entry) - 0x28A0D 158228 - 0x28A79 (Maze Panel) - True - True Door - 0x28AA2 (Maze Stairs) - 0x28A79 -158241 - 0x17F5F (Windmill Entry Panel) - True - Dots -Door - 0x1845B (Windmill Entry) - 0x17F5F -159010 - 0x037B6 (Windmill First Blade EP) - 0x17D02 - True -159011 - 0x037B2 (Windmill Second Blade EP) - 0x17D02 - True -159012 - 0x000F7 (Windmill Third Blade EP) - 0x17D02 - True 159540 - 0x03335 (Tower Underside Third EP) - True - True 159541 - 0x03412 (Tower Underside Fourth EP) - True - True 159542 - 0x038A6 (Tower Underside First EP) - True - True @@ -528,20 +578,26 @@ Town Church (Town): 158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True 159553 - 0x03BD1 (Black Line Church EP) - True - True -RGB House (Town) - RGB Room - 0x2897B: +Town RGB House (Town RGB House) - Town RGB House Upstairs - 0x2897B: 158242 - 0x034E4 (Sound Room Left) - True - True 158243 - 0x034E3 (Sound Room Right) - True - Sound Dots -Door - 0x2897B (RGB House Stairs) - 0x034E4 & 0x034E3 +Door - 0x2897B (Stairs) - 0x034E4 & 0x034E3 -RGB Room (Town): +Town RGB House Upstairs (Town RGB House Upstairs): 158244 - 0x334D8 (RGB Control) - True - Rotated Shapers & Colored Squares -158245 - 0x03C0C (RGB Room Left) - 0x334D8 - Colored Squares & Black/White Squares -158246 - 0x03C08 (RGB Room Right) - 0x334D8 - Stars +158245 - 0x03C0C (Left) - 0x334D8 - Colored Squares & Black/White Squares +158246 - 0x03C08 (Right) - 0x334D8 - Stars -Town Tower (Town Tower) - Town - True - Town Tower Top - 0x27798 & 0x27799 & 0x2779A & 0x2779C: -Door - 0x27798 (Second Door) - 0x28ACC -Door - 0x2779C (Third Door) - 0x28AD9 +Town Tower Bottom (Town Tower) - Town - True - Town Tower After First Door - 0x27799: Door - 0x27799 (First Door) - 0x28A69 + +Town Tower After First Door (Town Tower) - Town Tower After Second Door - 0x27798: +Door - 0x27798 (Second Door) - 0x28ACC + +Town Tower After Second Door (Town Tower) - Town Tower After Third Door - 0x2779C: +Door - 0x2779C (Third Door) - 0x28AD9 + +Town Tower After Third Door (Town Tower) - Town Tower Top - 0x2779A: Door - 0x2779A (Fourth Door) - 0x28B39 Town Tower Top (Town): @@ -550,6 +606,15 @@ Laser - 0x032F9 (Laser) - 0x032F5 159422 - 0x33692 (Brown Bridge EP) - True - True 159551 - 0x03BCE (Black Line Tower EP) - True - True +==Windmill & Theater== + +Outside Windmill (Windmill) - Windmill Interior - 0x1845B: +159010 - 0x037B6 (First Blade EP) - 0x17D02 - True +159011 - 0x037B2 (Second Blade EP) - 0x17D02 - True +159012 - 0x000F7 (Third Blade EP) - 0x17D02 - True +158241 - 0x17F5F (Entry Panel) - True - Dots +Door - 0x1845B (Entry) - 0x17F5F + Windmill Interior (Windmill) - Theater - 0x17F88: 158247 - 0x17D02 (Turn Control) - True - Dots 158248 - 0x17F89 (Theater Entry Panel) - True - Black/White Squares @@ -573,6 +638,8 @@ Door - 0x3CCDF (Exit Right) - 0x33AB2 159556 - 0x33A2A (Door EP) - 0x03553 - True 159558 - 0x33B06 (Church EP) - 0x0354E - True +==Jungle== + Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF: 158251 - 0x17CDF (Shore Boat Spawn) - True - Boat 158609 - 0x17F9B (Discard) - True - Triangles @@ -604,19 +671,18 @@ Door - 0x3873B (Laser Shortcut) - 0x337FA 159350 - 0x035CB (Bamboo CCW EP) - True - True 159351 - 0x035CF (Bamboo CW EP) - True - True -Outside Jungle River (River) - Main Island - True - Monastery Garden - 0x0CF2A - River Vault - 0x15287: +Outside Jungle River (Jungle) - Main Island - True - Monastery Garden - 0x0CF2A - Jungle Vault - 0x15287: 158267 - 0x17CAA (Monastery Garden Shortcut Panel) - True - True Door - 0x0CF2A (Monastery Garden Shortcut) - 0x17CAA 158663 - 0x15ADD (Vault Panel) - True - Black/White Squares & Dots Door - 0x15287 (Vault Door) - 0x15ADD 159110 - 0x03AC5 (Green Leaf Moss EP) - True - True -159120 - 0x03BE2 (Monastery Garden Left EP) - 0x03750 - True -159121 - 0x03BE3 (Monastery Garden Right EP) - True - True -159122 - 0x0A409 (Monastery Wall EP) - True - True -River Vault (River): +Jungle Vault (Jungle): 158664 - 0x03702 (Vault Box) - True - True +==Bunker== + Outside Bunker (Bunker) - Main Island - True - Bunker - 0x0C2A4: 158268 - 0x17C2E (Entry Panel) - True - Black/White Squares Door - 0x0C2A4 (Entry) - 0x17C2E @@ -650,9 +716,11 @@ Door - 0x0A08D (Elevator Room Entry) - 0x17E67 Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay: 159311 - 0x035F5 (Tinted Door EP) - 0x17C79 - True -Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079: +Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Cyan Room - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079: 158286 - 0x0A079 (Elevator Control) - True - Colored Squares & Black/White Squares +Bunker Cyan Room (Bunker) - Bunker Elevator - TrueOneWay: + Bunker Green Room (Bunker) - Bunker Elevator - TrueOneWay: 159310 - 0x000D3 (Green Room Flowers EP) - True - True @@ -660,6 +728,8 @@ Bunker Laser Platform (Bunker) - Bunker Elevator - TrueOneWay: 158710 - 0x09DE0 (Laser Panel) - True - True Laser - 0x0C2B2 (Laser) - 0x09DE0 +==Swamp== + Outside Swamp (Swamp) - Swamp Entry Area - 0x00C1C - Main Island - True: 158287 - 0x0056E (Entry Panel) - True - Shapers Door - 0x00C1C (Entry) - 0x0056E @@ -774,13 +844,29 @@ Laser - 0x00BF6 (Laser) - 0x03615 158342 - 0x17C02 (Laser Shortcut Right Panel) - 0x17C05 - Shapers & Negative Shapers & Rotated Shapers Door - 0x2D880 (Laser Shortcut) - 0x17C02 -Treehouse Entry Area (Treehouse) - Treehouse Between Doors - 0x0C309 - The Ocean - 0x17C95: +==Treehouse== + +Treehouse Obelisk (Treehouse) - Entry - True: +159720 - 0xFFE20 (Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True +159721 - 0xFFE21 (Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True +159722 - 0xFFE22 (Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True +159723 - 0xFFE23 (Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True +159724 - 0xFFE24 (Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True +159725 - 0xFFE25 (Obelisk Side 6) - 0x28AE9 & 0x3348F - True +159729 - 0x00097 (Obelisk) - True - True + +Treehouse Beach (Treehouse Beach) - Main Island - True: +159200 - 0x0053D (Rock Shadow EP) - True - True +159201 - 0x0053E (Sand Shadow EP) - True - True +159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True + +Treehouse Entry Area (Treehouse) - Treehouse Between Entry Doors - 0x0C309 - The Ocean - 0x17C95: 158343 - 0x17C95 (Boat Spawn) - True - Boat 158344 - 0x0288C (First Door Panel) - True - Stars Door - 0x0C309 (First Door) - 0x0288C 159210 - 0x33721 (Buoy EP) - 0x17C95 - True -Treehouse Between Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310: +Treehouse Between Entry Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310: 158345 - 0x02886 (Second Door Panel) - True - Stars Door - 0x0C310 (Second Door) - 0x02886 @@ -809,7 +895,7 @@ Treehouse First Purple Bridge (Treehouse) - Treehouse Second Purple Bridge - 0x1 158360 - 0x17D2D (First Purple Bridge 4) - 0x17CE4 - Stars & Dots 158361 - 0x17D6C (First Purple Bridge 5) - 0x17D2D - Stars & Dots -Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2: +Treehouse Right Orange Bridge (Treehouse) - Treehouse Drawbridge Platform - 0x17DA2: 158391 - 0x17D88 (Right Orange Bridge 1) - True - Stars 158392 - 0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars 158393 - 0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars @@ -823,7 +909,7 @@ Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2: 158401 - 0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars 158402 - 0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars -Treehouse Bridge Platform (Treehouse) - Main Island - 0x0C32D: +Treehouse Drawbridge Platform (Treehouse) - Main Island - 0x0C32D: 158404 - 0x037FF (Drawbridge Panel) - True - Stars Door - 0x0C32D (Drawbridge) - 0x037FF @@ -882,7 +968,19 @@ Treehouse Laser Room (Treehouse): 158403 - 0x17CBC (Laser House Door Timer Inside) - True - True Laser - 0x028A4 (Laser) - 0x03613 +==Mountain (Outside)== + +Mountainside Obelisk (Mountainside) - Entry - True: +159730 - 0xFFE30 (Obelisk Side 1) - 0x001A3 & 0x335AE - True +159731 - 0xFFE31 (Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True +159732 - 0xFFE32 (Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True +159733 - 0xFFE33 (Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True +159734 - 0xFFE34 (Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True +159735 - 0xFFE35 (Obelisk Side 6) - 0x035CB & 0x035CF - True +159739 - 0x00367 (Obelisk) - True - True + Mountainside (Mountainside) - Main Island - True - Mountaintop - True - Mountainside Vault - 0x00085: +159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True 158612 - 0x17C42 (Discard) - True - Triangles 158665 - 0x002A6 (Vault Panel) - True - Symmetry & Colored Dots & Black/White Squares Door - 0x00085 (Vault Door) - 0x002A6 @@ -893,7 +991,7 @@ Door - 0x00085 (Vault Door) - 0x002A6 Mountainside Vault (Mountainside): 158666 - 0x03542 (Vault Box) - True - True -Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34: +Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34: 158405 - 0x0042D (River Shape) - True - True 158406 - 0x09F7F (Box Short) - 7 Lasers + Redirect - True 158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Black/White Squares @@ -903,10 +1001,12 @@ Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34: 159324 - 0x336C8 (Arch White Right EP) - True - True 159326 - 0x3369A (Arch White Left EP) - True - True -Mountain Top Layer (Mountain Floor 1) - Mountain Top Layer Bridge - 0x09E39: +==Mountain (Inside)== + +Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39: 158408 - 0x09E39 (Light Bridge Controller) - True - Black/White Squares & Rotated Shapers -Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - TrueOneWay: +Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay: 158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots 158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Dots 158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers @@ -925,10 +1025,10 @@ Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - True 158424 - 0x09EAD (Trash Pillar 1) - True - Black/White Squares & Shapers 158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Black/White Squares & Shapers -Mountain Top Layer At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: +Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B -Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Blue Bridge - 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 At Door - 0x09ED8 & 0x09E86 - Mountain Pink Bridge EP - TrueOneWay: 158426 - 0x09FD3 (Near Row 1) - True - Colored Squares 158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Colored Squares & Dots 158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Colored Squares & Stars + Same Colored Symbol @@ -936,8 +1036,6 @@ Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Colored Squares Door - 0x09FFB (Staircase Near) - 0x09FD8 -Mountain Floor 2 Blue Bridge (Mountain Floor 2) - Mountain Floor 2 Beyond Bridge - TrueOneWay - Mountain Floor 2 At Door - 0x09ED8: - Mountain Floor 2 At Door (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD: Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86 @@ -959,10 +1057,10 @@ Mountain Floor 2 Light Bridge Room Far (Mountain Floor 2): Mountain Floor 2 Elevator Room (Mountain Floor 2) - Mountain Floor 2 Elevator - TrueOneWay: 158613 - 0x17F93 (Elevator Discard) - True - Triangles -Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Third Layer - 0x09EEB: +Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Floor 3 - 0x09EEB: 158439 - 0x09EEB (Elevator Control Panel) - True - Dots -Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay: +Mountain Floor 3 (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay: 158440 - 0x09FC1 (Giant Puzzle Bottom Left) - True - Shapers & Eraser 158441 - 0x09F8E (Giant Puzzle Bottom Right) - True - Rotated Shapers & Eraser 158442 - 0x09F01 (Giant Puzzle Top Right) - True - Shapers & Eraser @@ -972,13 +1070,32 @@ Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueO 159314 - 0x09D5E (Blue Bridge EP) - 0x09E86 & 0x09ED8 - True Door - 0x09F89 (Exit) - 0x09FDA -Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Final Room - 0x0C141: +Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Mountain Bottom Floor Pillars Room - 0x0C141: 158614 - 0x17FA2 (Discard) - 0xFFF00 - Triangles -158445 - 0x01983 (Final Room Entry Left) - True - Shapers & Stars -158446 - 0x01987 (Final Room Entry Right) - True - Colored Squares & Dots -Door - 0x0C141 (Final Room Entry) - 0x01983 & 0x01987 +158445 - 0x01983 (Pillars Room Entry Left) - True - Shapers & Stars +158446 - 0x01987 (Pillars Room Entry Right) - True - Colored Squares & Dots +Door - 0x0C141 (Pillars Room Entry) - 0x01983 & 0x01987 Door - 0x17F33 (Rock Open) - 0x17FA2 | 0x334E1 +Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB & 0x33961: +158522 - 0x0383A (Right Pillar 1) - True - Stars +158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots +158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots +158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry +158526 - 0x0383D (Left Pillar 1) - True - Dots & Full Dots +158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares +158528 - 0x03859 (Left Pillar 3) - 0x0383F - Shapers +158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry + +Elevator (Mountain Bottom Floor): +158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True +158531 - 0x3D9A7 (Elevator Door Close Right) - True - True +158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True +158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True +158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True +158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True +158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True + Mountain Pink Bridge EP (Mountain Floor 2): 159312 - 0x09D63 (Pink Bridge EP) - 0x09E39 - True @@ -987,7 +1104,9 @@ Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D: Door - 0x2D77D (Caves Entry) - 0x00FF8 158448 - 0x334E1 (Rock Control) - True - True -Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Path to Challenge - 0x019A5: +==Caves== + +Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5: 158451 - 0x335AB (Elevator Inside Control) - True - Dots & Black/White Squares 158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Black/White Squares 158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Black/White Squares & Dots @@ -1042,10 +1161,12 @@ Door - 0x2D73F (Mountain Shortcut Door) - 0x021D7 Door - 0x2D859 (Swamp Shortcut Door) - 0x17CF2 159341 - 0x3397C (Skylight EP) - True - True -Path to Challenge (Caves) - Challenge - 0x0A19A: +Caves Path to Challenge (Caves) - Challenge - 0x0A19A: 158477 - 0x0A16E (Challenge Entry Panel) - True - Stars & Shapers & Stars + Same Colored Symbol Door - 0x0A19A (Challenge Entry) - 0x0A16E +==Challenge== + Challenge (Challenge) - Tunnels - 0x0348A - Challenge Vault - 0x04D75: 158499 - 0x0A332 (Start Timer) - 11 Lasers - True 158500 - 0x0088E (Small Basic) - 0x0A332 - True @@ -1074,7 +1195,9 @@ Door - 0x0348A (Tunnels Entry) - 0x039B4 Challenge Vault (Challenge): 158667 - 0x0356B (Vault Box) - 0x1C31A & 0x1C319 - True -Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Lowest Level Inbetween Shortcuts - 0x27263 - Town - 0x09E87: +==Tunnels== + +Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Behind Elevator - 0x27263 - Town - 0x09E87: 158668 - 0x2FAF6 (Vault Box) - True - True 158519 - 0x27732 (Theater Shortcut Panel) - True - True Door - 0x27739 (Theater Shortcut) - 0x27732 @@ -1084,24 +1207,7 @@ Door - 0x27263 (Desert Shortcut) - 0x2773D Door - 0x09E87 (Town Shortcut) - 0x09E85 159557 - 0x33A20 (Theater Flowers EP) - 0x03553 & Theater to Tunnels - True -Final Room (Mountain Final Room) - Elevator - 0x339BB & 0x33961: -158522 - 0x0383A (Right Pillar 1) - True - Stars -158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots -158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots -158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry -158526 - 0x0383D (Left Pillar 1) - True - Dots & Full Dots -158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares -158528 - 0x03859 (Left Pillar 3) - 0x0383F - Shapers -158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry - -Elevator (Mountain Final Room): -158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True -158531 - 0x3D9A7 (Elevator Door Close Right) - True - True -158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True -158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True -158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True -158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True -158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True +==Boat== The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Treehouse Entry Area - TrueOneWay - Quarry Boathouse Behind Staircase - TrueOneWay - Inside Glass Factory Behind Back Wall - TrueOneWay: 159042 - 0x22106 (Desert EP) - True - True @@ -1114,45 +1220,3 @@ The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Tre 159521 - 0x33879 (Tutorial Reflection EP) - True - True 159522 - 0x03C19 (Tutorial Moss EP) - True - True 159531 - 0x035C9 (Cargo Box EP) - 0x0A0C9 - True - -Obelisks (EPs) - Entry - True: -159700 - 0xFFE00 (Desert Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True -159701 - 0xFFE01 (Desert Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True -159702 - 0xFFE02 (Desert Obelisk Side 3) - 0x3351D - True -159703 - 0xFFE03 (Desert Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True -159704 - 0xFFE04 (Desert Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True -159709 - 0x00359 (Desert Obelisk) - True - True -159710 - 0xFFE10 (Monastery Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True -159711 - 0xFFE11 (Monastery Obelisk Side 2) - 0x03AC5 - True -159712 - 0xFFE12 (Monastery Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True -159713 - 0xFFE13 (Monastery Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True -159714 - 0xFFE14 (Monastery Obelisk Side 5) - 0x03E01 - True -159715 - 0xFFE15 (Monastery Obelisk Side 6) - 0x289F4 & 0x289F5 - True -159719 - 0x00263 (Monastery Obelisk) - True - True -159720 - 0xFFE20 (Treehouse Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True -159721 - 0xFFE21 (Treehouse Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True -159722 - 0xFFE22 (Treehouse Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True -159723 - 0xFFE23 (Treehouse Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True -159724 - 0xFFE24 (Treehouse Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True -159725 - 0xFFE25 (Treehouse Obelisk Side 6) - 0x28AE9 & 0x3348F - True -159729 - 0x00097 (Treehouse Obelisk) - True - True -159730 - 0xFFE30 (River Obelisk Side 1) - 0x001A3 & 0x335AE - True -159731 - 0xFFE31 (River Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True -159732 - 0xFFE32 (River Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True -159733 - 0xFFE33 (River Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True -159734 - 0xFFE34 (River Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True -159735 - 0xFFE35 (River Obelisk Side 6) - 0x035CB & 0x035CF - True -159739 - 0x00367 (River Obelisk) - True - True -159740 - 0xFFE40 (Quarry Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True -159741 - 0xFFE41 (Quarry Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True -159742 - 0xFFE42 (Quarry Obelisk Side 3) - 0x289CF & 0x289D1 - True -159743 - 0xFFE43 (Quarry Obelisk Side 4) - 0x33692 - True -159744 - 0xFFE44 (Quarry Obelisk Side 5) - 0x03E77 & 0x03E7C - True -159749 - 0x22073 (Quarry Obelisk) - True - True -159750 - 0xFFE50 (Town Obelisk Side 1) - 0x035C7 - True -159751 - 0xFFE51 (Town Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True -159752 - 0xFFE52 (Town Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True -159753 - 0xFFE53 (Town Obelisk Side 4) - 0x28B30 & 0x035C9 - True -159754 - 0xFFE54 (Town Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True -159755 - 0xFFE55 (Town Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True -159759 - 0x0A16C (Town Obelisk) - True - True diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index d99aab5cff..c38898b33d 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -2,16 +2,17 @@ Archipelago init file for The Witness """ import dataclasses -from typing import Dict, Optional +from typing import Dict, Optional from BaseClasses import Region, Location, MultiWorld, Item, Entrance, Tutorial, CollectionState from Options import PerGameCommonOptions, Toggle from .presets import witness_option_presets -from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \ - get_priority_hint_items, make_hints, generate_joke_hints from worlds.AutoWorld import World, WebWorld from .player_logic import WitnessPlayerLogic from .static_logic import StaticWitnessLogic +from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \ + get_priority_hint_items, make_always_and_priority_hints, generate_joke_hints, make_area_hints, get_hintable_areas, \ + make_extra_location_hints, create_all_hints from .locations import WitnessPlayerLocations, StaticWitnessLocations from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems, ItemData from .regions import WitnessRegions @@ -57,6 +58,7 @@ class WitnessWorld(World): } location_name_to_id = StaticWitnessLocations.ALL_LOCATIONS_TO_ID item_name_groups = StaticWitnessItems.item_groups + location_name_groups = StaticWitnessLocations.AREA_LOCATION_GROUPS required_client_version = (0, 4, 4) @@ -191,8 +193,8 @@ class WitnessWorld(World): # Then, add checks in order until the required amount of sphere 1 checks is met. extra_checks = [ - ("First Hallway Room", "First Hallway Bend"), - ("First Hallway", "First Hallway Straight"), + ("Tutorial First Hallway Room", "Tutorial First Hallway Bend"), + ("Tutorial First Hallway", "Tutorial First Hallway Straight"), ("Desert Outside", "Desert Surface 1"), ("Desert Outside", "Desert Surface 2"), ] @@ -277,26 +279,35 @@ class WitnessWorld(World): hint_amount = self.options.hint_amount.value credits_hint = ( - "This Randomizer is brought to you by", - "NewSoupVi, Jarno, blastron,", - "jbzdarkid, sigma144, IHNN, oddGarrett, Exempt-Medic.", -1 + "This Randomizer is brought to you by\n" + "NewSoupVi, Jarno, blastron,\n", + "jbzdarkid, sigma144, IHNN, oddGarrett, Exempt-Medic.", -1, -1 ) audio_logs = get_audio_logs().copy() if hint_amount: - generated_hints = make_hints(self, hint_amount, self.own_itempool) + area_hints = round(self.options.area_hint_percentage / 100 * hint_amount) + + generated_hints = create_all_hints(self, hint_amount, area_hints) self.random.shuffle(audio_logs) duplicates = min(3, len(audio_logs) // hint_amount) - for _ in range(0, hint_amount): - hint = generated_hints.pop(0) + for hint in generated_hints: + location = hint.location + area_amount = hint.area_amount + + # None if junk hint, address if location hint, area string if area hint + arg_1 = location.address if location else (hint.area if hint.area else None) + + # self.player if junk hint, player if location hint, progression amount if area hint + arg_2 = area_amount if area_amount is not None else (location.player if location else self.player) for _ in range(0, duplicates): audio_log = audio_logs.pop() - self.log_ids_to_hints[int(audio_log, 16)] = hint + self.log_ids_to_hints[int(audio_log, 16)] = (hint.wording, arg_1, arg_2) if audio_logs: audio_log = audio_logs.pop() diff --git a/worlds/witness/hints.py b/worlds/witness/hints.py index 0354660b5e..545aef2216 100644 --- a/worlds/witness/hints.py +++ b/worlds/witness/hints.py @@ -1,6 +1,9 @@ -from typing import Tuple, List, TYPE_CHECKING - -from BaseClasses import Item +import logging +from dataclasses import dataclass +from typing import Tuple, List, TYPE_CHECKING, Set, Dict, Optional +from BaseClasses import Item, ItemClassification, Location, LocationProgressType, CollectionState +from . import StaticWitnessLogic +from .utils import weighted_sample if TYPE_CHECKING: from . import WitnessWorld @@ -164,6 +167,27 @@ joke_hints = [ ] +@dataclass +class WitnessLocationHint: + location: Location + hint_came_from_location: bool + + # If a hint gets added to a set twice, but once as an item hint and once as a location hint, those are the same + def __hash__(self): + return hash(self.location) + + def __eq__(self, other): + return self.location == other.location + + +@dataclass +class WitnessWordedHint: + wording: str + location: Optional[Location] = None + area: Optional[str] = None + area_amount: Optional[int] = None + + def get_always_hint_items(world: "WitnessWorld") -> List[str]: always = [ "Boat", @@ -182,7 +206,7 @@ def get_always_hint_items(world: "WitnessWorld") -> List[str]: always.append("Triangles") if wincon == "elevator": - always += ["Mountain Bottom Floor Final Room Entry (Door)", "Mountain Bottom Floor Doors"] + always += ["Mountain Bottom Floor Pillars Room Entry (Door)", "Mountain Bottom Floor Doors"] if wincon == "challenge": always += ["Challenge Entry (Panel)", "Caves Panels"] @@ -200,12 +224,14 @@ def get_always_hint_locations(world: "WitnessWorld") -> List[str]: ] # Add Obelisk Sides that contain EPs that are meant to be hinted, if they are necessary to complete the Obelisk Side - if world.options.EP_difficulty == "eclipse": + if "0x339B6" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES: always.append("Town Obelisk Side 6") # Eclipse EP - if world.options.EP_difficulty != "normal": + if "0x3388F" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES: always.append("Treehouse Obelisk Side 4") # Couch EP - always.append("River Obelisk Side 1") # Cloud Cycle EP. Needs to be changed to "Mountainside Obelisk" soon + + if "0x335AE" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES: + always.append("Mountainside Obelisk Side 1") # Cloud Cycle EP. return always @@ -263,10 +289,12 @@ def get_priority_hint_items(world: "WitnessWorld") -> List[str]: def get_priority_hint_locations(world: "WitnessWorld") -> List[str]: priority = [ + "Tutorial Patio Floor", + "Tutorial Patio Flowers EP", "Swamp Purple Underwater", "Shipwreck Vault Box", - "Town RGB Room Left", - "Town RGB Room Right", + "Town RGB House Upstairs Left", + "Town RGB House Upstairs Right", "Treehouse Green Bridge 7", "Treehouse Green Bridge Discard", "Shipwreck Discard", @@ -279,14 +307,38 @@ def get_priority_hint_locations(world: "WitnessWorld") -> List[str]: ] # Add Obelisk Sides that contain EPs that are meant to be hinted, if they are necessary to complete the Obelisk Side - if world.options.EP_difficulty != "normal": + if "0x33A20" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES: priority.append("Town Obelisk Side 6") # Theater Flowers EP + + if "0x28B29" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES: priority.append("Treehouse Obelisk Side 4") # Shipwreck Green EP + if "0x33600" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES: + priority.append("Town Obelisk Side 2") # Tutorial Patio Flowers EP. + return priority -def make_hint_from_item(world: "WitnessWorld", item_name: str, own_itempool: List[Item]): +def word_direct_hint(world: "WitnessWorld", hint: WitnessLocationHint): + location_name = hint.location.name + if hint.location.player != world.player: + location_name += " (" + world.multiworld.get_player_name(hint.location.player) + ")" + + item = hint.location.item + item_name = item.name + if item.player != world.player: + item_name += " (" + world.multiworld.get_player_name(item.player) + ")" + + if hint.hint_came_from_location: + hint_text = f"{location_name} contains {item_name}." + else: + hint_text = f"{item_name} can be found at {location_name}." + + return WitnessWordedHint(hint_text, hint.location) + + +def hint_from_item(world: "WitnessWorld", item_name: str, own_itempool: List[Item]) -> Optional[WitnessLocationHint]: + locations = [item.location for item in own_itempool if item.name == item_name and item.location] if not locations: @@ -298,28 +350,39 @@ def make_hint_from_item(world: "WitnessWorld", item_name: str, own_itempool: Lis if location_obj.player != world.player: location_name += " (" + world.multiworld.get_player_name(location_obj.player) + ")" - return location_name, item_name, location_obj.address if (location_obj.player == world.player) else -1 + return WitnessLocationHint(location_obj, False) -def make_hint_from_location(world: "WitnessWorld", location: str): +def hint_from_location(world: "WitnessWorld", location: str) -> Optional[WitnessLocationHint]: location_obj = world.multiworld.get_location(location, world.player) item_obj = world.multiworld.get_location(location, world.player).item item_name = item_obj.name if item_obj.player != world.player: item_name += " (" + world.multiworld.get_player_name(item_obj.player) + ")" - return location, item_name, location_obj.address if (location_obj.player == world.player) else -1 + return WitnessLocationHint(location_obj, True) -def make_hints(world: "WitnessWorld", hint_amount: int, own_itempool: List[Item]): - hints = list() +def get_items_and_locations_in_random_order(world: "WitnessWorld", own_itempool: List[Item]): + prog_items_in_this_world = sorted( + item.name for item in own_itempool + if item.advancement and item.code and item.location + ) + locations_in_this_world = sorted( + location.name for location in world.multiworld.get_locations(world.player) + if location.address and location.progress_type != LocationProgressType.EXCLUDED + ) - prog_items_in_this_world = { - item.name for item in own_itempool if item.advancement and item.code and item.location - } - loc_in_this_world = { - location.name for location in world.multiworld.get_locations(world.player) if location.address - } + world.random.shuffle(prog_items_in_this_world) + world.random.shuffle(locations_in_this_world) + + return prog_items_in_this_world, locations_in_this_world + + +def make_always_and_priority_hints(world: "WitnessWorld", own_itempool: List[Item], + already_hinted_locations: Set[Location] + ) -> Tuple[List[WitnessLocationHint], List[WitnessLocationHint]]: + prog_items_in_this_world, loc_in_this_world = get_items_and_locations_in_random_order(world, own_itempool) always_locations = [ location for location in get_always_hint_locations(world) @@ -338,105 +401,323 @@ def make_hints(world: "WitnessWorld", hint_amount: int, own_itempool: List[Item] if item in prog_items_in_this_world ] - always_hint_pairs = dict() + # Get always and priority location/item hints + always_location_hints = {hint_from_location(world, location) for location in always_locations} + always_item_hints = {hint_from_item(world, item, own_itempool) for item in always_items} + priority_location_hints = {hint_from_location(world, location) for location in priority_locations} + priority_item_hints = {hint_from_item(world, item, own_itempool) for item in priority_items} - for item in always_items: - hint_pair = make_hint_from_item(world, item, own_itempool) + # Combine the sets. This will get rid of duplicates + always_hints_set = always_item_hints | always_location_hints + priority_hints_set = priority_item_hints | priority_location_hints - if not hint_pair or hint_pair[2] == 158007: # Tutorial Gate Open - continue + # Make sure priority hints doesn't contain any hints that are already always hints. + priority_hints_set -= always_hints_set - always_hint_pairs[hint_pair[0]] = (hint_pair[1], True, hint_pair[2]) + always_generator = [hint for hint in always_hints_set if hint and hint.location not in already_hinted_locations] + priority_generator = [hint for hint in priority_hints_set if hint and hint.location not in already_hinted_locations] - for location in always_locations: - hint_pair = make_hint_from_location(world, location) - always_hint_pairs[hint_pair[0]] = (hint_pair[1], False, hint_pair[2]) + # Convert both hint types to list and then shuffle. Also, get rid of None and Tutorial Gate Open. + always_hints = sorted(always_generator, key=lambda h: h.location) + priority_hints = sorted(priority_generator, key=lambda h: h.location) + world.random.shuffle(always_hints) + world.random.shuffle(priority_hints) - priority_hint_pairs = dict() + return always_hints, priority_hints - for item in priority_items: - hint_pair = make_hint_from_item(world, item, own_itempool) - if not hint_pair or hint_pair[2] == 158007: # Tutorial Gate Open - continue +def make_extra_location_hints(world: "WitnessWorld", hint_amount: int, own_itempool: List[Item], + already_hinted_locations: Set[Location], hints_to_use_first: List[WitnessLocationHint], + unhinted_locations_for_hinted_areas: Dict[str, Set[Location]]) -> List[WitnessWordedHint]: + prog_items_in_this_world, locations_in_this_world = get_items_and_locations_in_random_order(world, own_itempool) - priority_hint_pairs[hint_pair[0]] = (hint_pair[1], True, hint_pair[2]) + next_random_hint_is_location = world.random.randrange(0, 2) - for location in priority_locations: - hint_pair = make_hint_from_location(world, location) - priority_hint_pairs[hint_pair[0]] = (hint_pair[1], False, hint_pair[2]) + hints = [] - already_hinted_locations = set() - - for loc, item in always_hint_pairs.items(): - if loc in already_hinted_locations: - continue - - if item[1]: - hints.append((f"{item[0]} can be found at {loc}.", item[2])) - else: - hints.append((f"{loc} contains {item[0]}.", item[2])) - - already_hinted_locations.add(loc) - - world.random.shuffle(hints) # shuffle always hint order in case of low hint amount - - remaining_hints = hint_amount - len(hints) - priority_hint_amount = int(max(0.0, min(len(priority_hint_pairs) / 2, remaining_hints / 2))) - - prog_items_in_this_world = sorted(prog_items_in_this_world) - locations_in_this_world = sorted(loc_in_this_world) - - world.random.shuffle(prog_items_in_this_world) - world.random.shuffle(locations_in_this_world) - - priority_hint_list = list(priority_hint_pairs.items()) - world.random.shuffle(priority_hint_list) - for _ in range(0, priority_hint_amount): - next_priority_hint = priority_hint_list.pop() - loc = next_priority_hint[0] - item = next_priority_hint[1] - - if loc in already_hinted_locations: - continue - - if item[1]: - hints.append((f"{item[0]} can be found at {loc}.", item[2])) - else: - hints.append((f"{loc} contains {item[0]}.", item[2])) - - already_hinted_locations.add(loc) - - next_random_hint_is_item = world.random.randrange(0, 2) + # This is a way to reverse a Dict[a,List[b]] to a Dict[b,a] + area_reverse_lookup = {v: k for k, l in unhinted_locations_for_hinted_areas.items() for v in l} while len(hints) < hint_amount: - if next_random_hint_is_item: - if not prog_items_in_this_world: - next_random_hint_is_item = not next_random_hint_is_item - continue + if not prog_items_in_this_world and not locations_in_this_world and not hints_to_use_first: + player_name = world.multiworld.get_player_name(world.player) + logging.warning(f"Ran out of items/locations to hint for player {player_name}.") + break - hint = make_hint_from_item(world, prog_items_in_this_world.pop(), own_itempool) - - if not hint or hint[0] in already_hinted_locations: - continue - - hints.append((f"{hint[1]} can be found at {hint[0]}.", hint[2])) - - already_hinted_locations.add(hint[0]) + if hints_to_use_first: + location_hint = hints_to_use_first.pop() + elif next_random_hint_is_location and locations_in_this_world: + location_hint = hint_from_location(world, locations_in_this_world.pop()) + elif not next_random_hint_is_location and prog_items_in_this_world: + location_hint = hint_from_item(world, prog_items_in_this_world.pop(), own_itempool) + # The list that the hint was supposed to be taken from was empty. + # Try the other list, which has to still have something, as otherwise, all lists would be empty, + # which would have triggered the guard condition above. else: - hint = make_hint_from_location(world, locations_in_this_world.pop()) + next_random_hint_is_location = not next_random_hint_is_location + continue - if hint[0] in already_hinted_locations: + if not location_hint or location_hint.location in already_hinted_locations: + continue + + # Don't hint locations in areas that are almost fully hinted out already + if location_hint.location in area_reverse_lookup: + area = area_reverse_lookup[location_hint.location] + if len(unhinted_locations_for_hinted_areas[area]) == 1: continue + del area_reverse_lookup[location_hint.location] + unhinted_locations_for_hinted_areas[area] -= {location_hint.location} - hints.append((f"{hint[0]} contains {hint[1]}.", hint[2])) + hints.append(word_direct_hint(world, location_hint)) + already_hinted_locations.add(location_hint.location) - already_hinted_locations.add(hint[0]) - - next_random_hint_is_item = not next_random_hint_is_item + next_random_hint_is_location = not next_random_hint_is_location return hints -def generate_joke_hints(world: "WitnessWorld", amount: int) -> List[Tuple[str, int]]: - return [(x, -1) for x in world.random.sample(joke_hints, amount)] +def generate_joke_hints(world: "WitnessWorld", amount: int) -> List[Tuple[str, int, int]]: + return [(x, -1, -1) for x in world.random.sample(joke_hints, amount)] + + +def choose_areas(world: "WitnessWorld", amount: int, locations_per_area: Dict[str, List[Location]], + already_hinted_locations: Set[Location]) -> Tuple[List[str], Dict[str, Set[Location]]]: + """ + Choose areas to hint. + This takes into account that some areas may already have had items hinted in them through location hints. + When this happens, they are made less likely to receive an area hint. + """ + + unhinted_locations_per_area = dict() + unhinted_location_percentage_per_area = dict() + + for area_name, locations in locations_per_area.items(): + not_yet_hinted_locations = sum(location not in already_hinted_locations for location in locations) + unhinted_locations_per_area[area_name] = {loc for loc in locations if loc not in already_hinted_locations} + unhinted_location_percentage_per_area[area_name] = not_yet_hinted_locations / len(locations) + + items_per_area = {area_name: [location.item for location in locations] + for area_name, locations in locations_per_area.items()} + + areas = sorted(area for area in items_per_area if unhinted_location_percentage_per_area[area]) + weights = [unhinted_location_percentage_per_area[area] for area in areas] + + amount = min(amount, len(weights)) + + hinted_areas = weighted_sample(world.random, areas, weights, amount) + + return hinted_areas, unhinted_locations_per_area + + +def get_hintable_areas(world: "WitnessWorld") -> Tuple[Dict[str, List[Location]], Dict[str, List[Item]]]: + potential_areas = list(StaticWitnessLogic.ALL_AREAS_BY_NAME.keys()) + + locations_per_area = dict() + items_per_area = dict() + + for area in potential_areas: + regions = [ + world.regio.created_regions[region] + for region in StaticWitnessLogic.ALL_AREAS_BY_NAME[area]["regions"] + if region in world.regio.created_regions + ] + locations = [location for region in regions for location in region.get_locations() if location.address] + + if locations: + locations_per_area[area] = locations + items_per_area[area] = [location.item for location in locations] + + return locations_per_area, items_per_area + + +def word_area_hint(world: "WitnessWorld", hinted_area: str, corresponding_items: List[Item]) -> Tuple[str, int]: + """ + Word the hint for an area using natural sounding language. + This takes into account how much progression there is, how much of it is local/non-local, and whether there are + any local lasers to be found in this area. + """ + + local_progression = sum(item.player == world.player and item.advancement for item in corresponding_items) + non_local_progression = sum(item.player != world.player and item.advancement for item in corresponding_items) + + laser_names = {"Symmetry Laser", "Desert Laser", "Quarry Laser", "Shadows Laser", "Town Laser", "Monastery Laser", + "Jungle Laser", "Bunker Laser", "Swamp Laser", "Treehouse Laser", "Keep Laser", } + + local_lasers = sum( + item.player == world.player and item.name in laser_names + for item in corresponding_items + ) + + total_progression = non_local_progression + local_progression + + player_count = world.multiworld.players + + area_progression_word = "Both" if total_progression == 2 else "All" + + if not total_progression: + hint_string = f"In the {hinted_area} area, you will find no progression items." + + elif total_progression == 1: + hint_string = f"In the {hinted_area} area, you will find 1 progression item." + + if player_count > 1: + if local_lasers: + hint_string += "\nThis item is a laser for this world." + elif non_local_progression: + other_player_str = "the other player" if player_count == 2 else "another player" + hint_string += f"\nThis item is for {other_player_str}." + else: + hint_string += "\nThis item is for this world." + else: + if local_lasers: + hint_string += "\nThis item is a laser." + + else: + hint_string = f"In the {hinted_area} area, you will find {total_progression} progression items." + + if local_lasers == total_progression: + sentence_end = (" for this world." if player_count > 1 else ".") + hint_string += f"\nAll of them are lasers" + sentence_end + + elif player_count > 1: + if local_progression and non_local_progression: + if non_local_progression == 1: + other_player_str = "the other player" if player_count == 2 else "another player" + hint_string += f"\nOne of them is for {other_player_str}." + else: + other_player_str = "the other player" if player_count == 2 else "other players" + hint_string += f"\n{non_local_progression} of them are for {other_player_str}." + elif non_local_progression: + other_players_str = "the other player" if player_count == 2 else "other players" + hint_string += f"\n{area_progression_word} of them are for {other_players_str}." + elif local_progression: + hint_string += f"\n{area_progression_word} of them are for this world." + + if local_lasers == 1: + if not non_local_progression: + hint_string += "\nAlso, one of them is a laser." + else: + hint_string += "\nAlso, one of them is a laser for this world." + elif local_lasers: + if not non_local_progression: + hint_string += f"\nAlso, {local_lasers} of them are lasers." + else: + hint_string += f"\nAlso, {local_lasers} of them are lasers for this world." + + else: + if local_lasers == 1: + hint_string += "\nOne of them is a laser." + elif local_lasers: + hint_string += f"\n{local_lasers} of them are lasers." + + return hint_string, total_progression + + +def make_area_hints(world: "WitnessWorld", amount: int, already_hinted_locations: Set[Location] + ) -> Tuple[List[WitnessWordedHint], Dict[str, Set[Location]]]: + locs_per_area, items_per_area = get_hintable_areas(world) + + hinted_areas, unhinted_locations_per_area = choose_areas(world, amount, locs_per_area, already_hinted_locations) + + hints = [] + + for hinted_area in hinted_areas: + hint_string, prog_amount = word_area_hint(world, hinted_area, items_per_area[hinted_area]) + + hints.append(WitnessWordedHint(hint_string, None, f"hinted_area:{hinted_area}", prog_amount)) + + if len(hinted_areas) < amount: + player_name = world.multiworld.get_player_name(world.player) + logging.warning(f"Was not able to make {amount} area hints for player {player_name}. " + f"Made {len(hinted_areas)} instead, and filled the rest with random location hints.") + + return hints, unhinted_locations_per_area + + +def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int) -> List[WitnessWordedHint]: + generated_hints: List[WitnessWordedHint] = [] + + state = CollectionState(world.multiworld) + + # Keep track of already hinted locations. Consider early Tutorial as "already hinted" + + already_hinted_locations = { + loc for loc in world.multiworld.get_reachable_locations(state, world.player) + if loc.address and StaticWitnessLogic.ENTITIES_BY_NAME[loc.name]["area"]["name"] == "Tutorial (Inside)" + } + + intended_location_hints = hint_amount - area_hints + + # First, make always and priority hints. + + always_hints, priority_hints = make_always_and_priority_hints( + world, world.own_itempool, already_hinted_locations + ) + + generated_always_hints = len(always_hints) + possible_priority_hints = len(priority_hints) + + # Make as many always hints as possible + always_hints_to_use = min(intended_location_hints, generated_always_hints) + + # Make up to half of the rest of the location hints priority hints, using up to half of the possibly priority hints + remaining_location_hints = intended_location_hints - always_hints_to_use + priority_hints_to_use = int(max(0.0, min(possible_priority_hints / 2, remaining_location_hints / 2))) + + for _ in range(always_hints_to_use): + location_hint = always_hints.pop() + generated_hints.append(word_direct_hint(world, location_hint)) + already_hinted_locations.add(location_hint.location) + + for _ in range(priority_hints_to_use): + location_hint = priority_hints.pop() + generated_hints.append(word_direct_hint(world, location_hint)) + already_hinted_locations.add(location_hint.location) + + location_hints_created_in_round_1 = len(generated_hints) + + unhinted_locations_per_area: Dict[str, Set[Location]] = dict() + + # Then, make area hints. + if area_hints: + generated_area_hints, unhinted_locations_per_area = make_area_hints(world, area_hints, already_hinted_locations) + generated_hints += generated_area_hints + + # If we don't have enough hints yet, recalculate always and priority hints, then fill with random hints + if len(generated_hints) < hint_amount: + remaining_needed_location_hints = hint_amount - len(generated_hints) + + # Save old values for used always and priority hints for later calculations + amt_of_used_always_hints = always_hints_to_use + amt_of_used_priority_hints = priority_hints_to_use + + # Recalculate how many always hints and priority hints are supposed to be used + intended_location_hints = remaining_needed_location_hints + location_hints_created_in_round_1 + + always_hints_to_use = min(intended_location_hints, generated_always_hints) + priority_hints_to_use = int(max(0.0, min(possible_priority_hints / 2, remaining_location_hints / 2))) + + # If we now need more always hints and priority hints than we thought previously, make some more. + more_always_hints = always_hints_to_use - amt_of_used_always_hints + more_priority_hints = priority_hints_to_use - amt_of_used_priority_hints + + extra_always_and_priority_hints: List[WitnessLocationHint] = [] + + for _ in range(more_always_hints): + extra_always_and_priority_hints.append(always_hints.pop()) + + for _ in range(more_priority_hints): + extra_always_and_priority_hints.append(priority_hints.pop()) + + generated_hints += make_extra_location_hints( + world, hint_amount - len(generated_hints), world.own_itempool, already_hinted_locations, + extra_always_and_priority_hints, unhinted_locations_per_area + ) + + # If we still don't have enough for whatever reason, throw a warning, proceed with the lower amount + if len(generated_hints) != hint_amount: + player_name = world.multiworld.get_player_name(world.player) + logging.warning(f"Couldn't generate {hint_amount} hints for player {player_name}. " + f"Generated {len(generated_hints)} instead.") + + return generated_hints diff --git a/worlds/witness/locations.py b/worlds/witness/locations.py index 781cc4e25d..d38cf90258 100644 --- a/worlds/witness/locations.py +++ b/worlds/witness/locations.py @@ -110,13 +110,13 @@ class StaticWitnessLocations: "Town Red Rooftop 5", "Town Wooden Roof Lower Row 5", "Town Wooden Rooftop", - "Town Windmill Entry Panel", + "Windmill Entry Panel", "Town RGB House Entry Panel", "Town Laser Panel", - "Town RGB Room Left", - "Town RGB Room Right", - "Town Sound Room Right", + "Town RGB House Upstairs Left", + "Town RGB House Upstairs Right", + "Town RGB House Sound Room Right", "Windmill Theater Entry Panel", "Theater Exit Left Panel", @@ -134,8 +134,8 @@ class StaticWitnessLocations: "Jungle Popup Wall 6", "Jungle Laser Panel", - "River Vault Box", - "River Monastery Garden Shortcut Panel", + "Jungle Vault Box", + "Jungle Monastery Garden Shortcut Panel", "Bunker Entry Panel", "Bunker Intro Left 5", @@ -177,7 +177,7 @@ class StaticWitnessLocations: "Mountainside Vault Box", "Mountaintop River Shape", - "First Hallway EP", + "Tutorial First Hallway EP", "Tutorial Cloud EP", "Tutorial Patio Flowers EP", "Tutorial Gate EP", @@ -185,7 +185,7 @@ class StaticWitnessLocations: "Outside Tutorial Town Sewer EP", "Outside Tutorial Path EP", "Outside Tutorial Tractor EP", - "Main Island Thundercloud EP", + "Mountainside Thundercloud EP", "Glass Factory Vase EP", "Symmetry Island Glass Factory Black Line Reflection EP", "Symmetry Island Glass Factory Black Line EP", @@ -242,9 +242,9 @@ class StaticWitnessLocations: "Monastery Left Shutter EP", "Monastery Middle Shutter EP", "Monastery Right Shutter EP", - "Town Windmill First Blade EP", - "Town Windmill Second Blade EP", - "Town Windmill Third Blade EP", + "Windmill First Blade EP", + "Windmill Second Blade EP", + "Windmill Third Blade EP", "Town Tower Underside Third EP", "Town Tower Underside Fourth EP", "Town Tower Underside First EP", @@ -268,10 +268,10 @@ class StaticWitnessLocations: "Jungle Tree Halo EP", "Jungle Bamboo CCW EP", "Jungle Bamboo CW EP", - "River Green Leaf Moss EP", - "River Monastery Garden Left EP", - "River Monastery Garden Right EP", - "River Monastery Wall EP", + "Jungle Green Leaf Moss EP", + "Monastery Garden Left EP", + "Monastery Garden Right EP", + "Monastery Wall EP", "Bunker Tinted Door EP", "Bunker Green Room Flowers EP", "Swamp Purple Sand Middle EP", @@ -330,12 +330,12 @@ class StaticWitnessLocations: "Treehouse Obelisk Side 4", "Treehouse Obelisk Side 5", "Treehouse Obelisk Side 6", - "River Obelisk Side 1", - "River Obelisk Side 2", - "River Obelisk Side 3", - "River Obelisk Side 4", - "River Obelisk Side 5", - "River Obelisk Side 6", + "Mountainside Obelisk Side 1", + "Mountainside Obelisk Side 2", + "Mountainside Obelisk Side 3", + "Mountainside Obelisk Side 4", + "Mountainside Obelisk Side 5", + "Mountainside Obelisk Side 6", "Quarry Obelisk Side 1", "Quarry Obelisk Side 2", "Quarry Obelisk Side 3", @@ -407,13 +407,13 @@ class StaticWitnessLocations: "Mountain Floor 2 Elevator Discard", "Mountain Bottom Floor Giant Puzzle", - "Mountain Bottom Floor Final Room Entry Left", - "Mountain Bottom Floor Final Room Entry Right", + "Mountain Bottom Floor Pillars Room Entry Left", + "Mountain Bottom Floor Pillars Room Entry Right", "Mountain Bottom Floor Caves Entry Panel", - "Mountain Final Room Left Pillar 4", - "Mountain Final Room Right Pillar 4", + "Mountain Bottom Floor Left Pillar 4", + "Mountain Bottom Floor Right Pillar 4", "Challenge Vault Box", "Theater Challenge Video", @@ -438,12 +438,12 @@ class StaticWitnessLocations: "Treehouse Obelisk Side 4", "Treehouse Obelisk Side 5", "Treehouse Obelisk Side 6", - "River Obelisk Side 1", - "River Obelisk Side 2", - "River Obelisk Side 3", - "River Obelisk Side 4", - "River Obelisk Side 5", - "River Obelisk Side 6", + "Mountainside Obelisk Side 1", + "Mountainside Obelisk Side 2", + "Mountainside Obelisk Side 3", + "Mountainside Obelisk Side 4", + "Mountainside Obelisk Side 5", + "Mountainside Obelisk Side 6", "Quarry Obelisk Side 1", "Quarry Obelisk Side 2", "Quarry Obelisk Side 3", @@ -459,6 +459,8 @@ class StaticWitnessLocations: ALL_LOCATIONS_TO_ID = dict() + AREA_LOCATION_GROUPS = dict() + @staticmethod def get_id(chex: str): """ @@ -491,6 +493,10 @@ class StaticWitnessLocations: for key, item in all_loc_to_id.items(): self.ALL_LOCATIONS_TO_ID[key] = item + for loc in all_loc_to_id: + area = StaticWitnessLogic.ENTITIES_BY_NAME[loc]["area"]["name"] + self.AREA_LOCATION_GROUPS.setdefault(area, []).append(loc) + class WitnessPlayerLocations: """ diff --git a/worlds/witness/options.py b/worlds/witness/options.py index ac1f2bc828..68a4ac7fc2 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -187,7 +187,19 @@ class HintAmount(Range): display_name = "Hints on Audio Logs" range_start = 0 range_end = 49 - default = 10 + default = 12 + + +class AreaHintPercentage(Range): + """There are two types of hints for The Witness. + "Location hints" hint one location in your world / containing an item for your world. + "Area hints" will tell you some general info about the items you can find in one of the + main geographic areas on the island. + Use this option to specify how many of your hints you want to be area hints. The rest will be location hints.""" + display_name = "Area Hint Percentage" + range_start = 0 + range_end = 100 + default = 33 class DeathLink(Toggle): @@ -227,5 +239,6 @@ class TheWitnessOptions(PerGameCommonOptions): trap_percentage: TrapPercentage puzzle_skip_amount: PuzzleSkipAmount hint_amount: HintAmount + area_hint_percentage: AreaHintPercentage death_link: DeathLink death_link_amnesty: DeathLinkAmnesty diff --git a/worlds/witness/presets.py b/worlds/witness/presets.py index 1fee1a7968..3f02de550b 100644 --- a/worlds/witness/presets.py +++ b/worlds/witness/presets.py @@ -32,6 +32,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "trap_percentage": TrapPercentage.default, "puzzle_skip_amount": PuzzleSkipAmount.default, "hint_amount": HintAmount.default, + "area_hint_percentage": AreaHintPercentage.default, "death_link": DeathLink.default, }, @@ -64,6 +65,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "trap_percentage": TrapPercentage.default, "puzzle_skip_amount": 15, "hint_amount": HintAmount.default, + "area_hint_percentage": AreaHintPercentage.default, "death_link": DeathLink.default, }, @@ -96,6 +98,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "trap_percentage": TrapPercentage.default, "puzzle_skip_amount": 15, "hint_amount": HintAmount.default, + "area_hint_percentage": AreaHintPercentage.default, "death_link": DeathLink.default, }, } diff --git a/worlds/witness/regions.py b/worlds/witness/regions.py index 3a1a1781b7..350017c694 100644 --- a/worlds/witness/regions.py +++ b/worlds/witness/regions.py @@ -129,9 +129,9 @@ class WitnessRegions: regions_to_check.add(target.name) reachable_regions.add(target.name) - final_regions_list = [v for k, v in regions_by_name.items() if k in reachable_regions] + self.created_regions = {k: v for k, v in regions_by_name.items() if k in reachable_regions} - world.multiworld.regions += final_regions_list + world.multiworld.regions += self.created_regions.values() def __init__(self, locat: WitnessPlayerLocations, world: "WitnessWorld"): difficulty = world.options.puzzle_randomization @@ -145,3 +145,4 @@ class WitnessRegions: self.locat = locat self.created_entrances: Dict[Tuple[str, str], List[Entrance]] = KeyedDefaultDict(lambda _: []) + self.created_regions: Dict[str, Region] = dict() diff --git a/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt b/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt index 4724039620..70223bd749 100644 --- a/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt +++ b/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt @@ -1,7 +1,7 @@ Items: Glass Factory Entry (Panel) -Tutorial Outpost Entry (Panel) -Tutorial Outpost Exit (Panel) +Outside Tutorial Outpost Entry (Panel) +Outside Tutorial Outpost Exit (Panel) Symmetry Island Lower (Panel) Symmetry Island Upper (Panel) Desert Light Room Entry (Panel) diff --git a/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt b/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt index 2f2b321710..87ec69f59c 100644 --- a/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt +++ b/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt @@ -58,9 +58,9 @@ Town Tower Third (Door) Theater Entry (Door) Theater Exit Left (Door) Theater Exit Right (Door) -Jungle Bamboo Laser Shortcut (Door) +Jungle Laser Shortcut (Door) Jungle Popup Wall (Door) -River Monastery Garden Shortcut (Door) +Jungle Monastery Garden Shortcut (Door) Bunker Entry (Door) Bunker Tinted Glass Door Bunker UV Room Entry (Door) @@ -85,7 +85,7 @@ Mountain Floor 2 Staircase Near (Door) Mountain Floor 2 Exit (Door) Mountain Floor 2 Staircase Far (Door) Mountain Bottom Floor Giant Puzzle Exit (Door) -Mountain Bottom Floor Final Room Entry (Door) +Mountain Bottom Floor Pillars Room Entry (Door) Mountain Bottom Floor Rock (Door) Caves Entry (Door) Caves Pillar Door @@ -143,8 +143,8 @@ Town Wooden Roof Lower Row 5 Town RGB House Entry Panel Town Church Entry Panel Town Maze Panel -Town Windmill Entry Panel -Town Sound Room Right +Windmill Entry Panel +Town RGB House Sound Room Right Town Red Rooftop 5 Town Church Lattice Town Tall Hexagonal @@ -154,7 +154,7 @@ Theater Exit Left Panel Theater Exit Right Panel Jungle Laser Shortcut Panel Jungle Popup Wall Control -River Monastery Garden Shortcut Panel +Jungle Monastery Garden Shortcut Panel Bunker Entry Panel Bunker Tinted Glass Door Panel Bunker Glass Room 3 @@ -186,8 +186,8 @@ Mountain Floor 2 Light Bridge Controller Near Mountain Floor 2 Light Bridge Controller Far Mountain Floor 2 Far Row 6 Mountain Bottom Floor Giant Puzzle -Mountain Bottom Floor Final Room Entry Left -Mountain Bottom Floor Final Room Entry Right +Mountain Bottom Floor Pillars Room Entry Left +Mountain Bottom Floor Pillars Room Entry Right Mountain Bottom Floor Discard Mountain Bottom Floor Rock Control Mountain Bottom Floor Caves Entry Panel diff --git a/worlds/witness/settings/Door_Shuffle/Simple_Doors.txt b/worlds/witness/settings/Door_Shuffle/Simple_Doors.txt index 91a7132ec1..2059f43af6 100644 --- a/worlds/witness/settings/Door_Shuffle/Simple_Doors.txt +++ b/worlds/witness/settings/Door_Shuffle/Simple_Doors.txt @@ -76,8 +76,8 @@ Town Wooden Roof Lower Row 5 Town RGB House Entry Panel Town Church Entry Panel Town Maze Panel -Town Windmill Entry Panel -Town Sound Room Right +Windmill Entry Panel +Town RGB House Sound Room Right Town Red Rooftop 5 Town Church Lattice Town Tall Hexagonal @@ -87,7 +87,7 @@ Theater Exit Left Panel Theater Exit Right Panel Jungle Laser Shortcut Panel Jungle Popup Wall Control -River Monastery Garden Shortcut Panel +Jungle Monastery Garden Shortcut Panel Bunker Entry Panel Bunker Tinted Glass Door Panel Bunker Glass Room 3 @@ -119,8 +119,8 @@ Mountain Floor 2 Light Bridge Controller Near Mountain Floor 2 Light Bridge Controller Far Mountain Floor 2 Far Row 6 Mountain Bottom Floor Giant Puzzle -Mountain Bottom Floor Final Room Entry Left -Mountain Bottom Floor Final Room Entry Right +Mountain Bottom Floor Pillars Room Entry Left +Mountain Bottom Floor Pillars Room Entry Right Mountain Bottom Floor Discard Mountain Bottom Floor Rock Control Mountain Bottom Floor Caves Entry Panel diff --git a/worlds/witness/settings/Door_Shuffle/Simple_Panels.txt b/worlds/witness/settings/Door_Shuffle/Simple_Panels.txt index 42258bca1a..23501d20d3 100644 --- a/worlds/witness/settings/Door_Shuffle/Simple_Panels.txt +++ b/worlds/witness/settings/Door_Shuffle/Simple_Panels.txt @@ -1,6 +1,6 @@ Items: Symmetry Island Panels -Tutorial Outpost Panels +Outside Tutorial Outpost Panels Desert Panels Quarry Outside Panels Quarry Stoneworks Panels diff --git a/worlds/witness/settings/EP_Shuffle/EP_Sides.txt b/worlds/witness/settings/EP_Shuffle/EP_Sides.txt index 82ab633295..d561ffdc18 100644 --- a/worlds/witness/settings/EP_Shuffle/EP_Sides.txt +++ b/worlds/witness/settings/EP_Shuffle/EP_Sides.txt @@ -16,12 +16,12 @@ Added Locations: 0xFFE23 (Treehouse Obelisk Side 4) 0xFFE24 (Treehouse Obelisk Side 5) 0xFFE25 (Treehouse Obelisk Side 6) -0xFFE30 (River Obelisk Side 1) -0xFFE31 (River Obelisk Side 2) -0xFFE32 (River Obelisk Side 3) -0xFFE33 (River Obelisk Side 4) -0xFFE34 (River Obelisk Side 5) -0xFFE35 (River Obelisk Side 6) +0xFFE30 (Mountainside Obelisk Side 1) +0xFFE31 (Mountainside Obelisk Side 2) +0xFFE32 (Mountainside Obelisk Side 3) +0xFFE33 (Mountainside Obelisk Side 4) +0xFFE34 (Mountainside Obelisk Side 5) +0xFFE35 (Mountainside Obelisk Side 6) 0xFFE40 (Quarry Obelisk Side 1) 0xFFE41 (Quarry Obelisk Side 2) 0xFFE42 (Quarry Obelisk Side 3) diff --git a/worlds/witness/settings/Exclusions/Vaults.txt b/worlds/witness/settings/Exclusions/Vaults.txt index f23a131833..d9e5d28cd6 100644 --- a/worlds/witness/settings/Exclusions/Vaults.txt +++ b/worlds/witness/settings/Exclusions/Vaults.txt @@ -8,9 +8,9 @@ Disabled Locations: 0x00AFB (Shipwreck Vault) 0x03535 (Shipwreck Vault Box) 0x17BB4 (Shipwreck Vault Door) -0x15ADD (River Vault) -0x03702 (River Vault Box) -0x15287 (River Vault Door) +0x15ADD (Jungle Vault) +0x03702 (Jungle Vault Box) +0x15287 (Jungle Vault Door) 0x002A6 (Mountainside Vault) 0x03542 (Mountainside Vault Box) 0x00085 (Mountainside Vault Door) diff --git a/worlds/witness/settings/Postgame/Mountain_Lower.txt b/worlds/witness/settings/Postgame/Mountain_Lower.txt index 354e3feb82..aecddec5ad 100644 --- a/worlds/witness/settings/Postgame/Mountain_Lower.txt +++ b/worlds/witness/settings/Postgame/Mountain_Lower.txt @@ -7,9 +7,9 @@ Disabled Locations: 0x09EFF (Giant Puzzle Top Left) 0x09FDA (Giant Puzzle) 0x09F89 (Exit Door) -0x01983 (Final Room Entry Left) -0x01987 (Final Room Entry Right) -0x0C141 (Final Room Entry Door) +0x01983 (Pillars Room Entry Left) +0x01987 (Pillars Room Entry Right) +0x0C141 (Pillars Room Entry Door) 0x0383A (Right Pillar 1) 0x09E56 (Right Pillar 2) 0x09E5A (Right Pillar 3) diff --git a/worlds/witness/static_logic.py b/worlds/witness/static_logic.py index 0e8d649af6..5a3e8b1b58 100644 --- a/worlds/witness/static_logic.py +++ b/worlds/witness/static_logic.py @@ -56,6 +56,11 @@ class StaticWitnessLogicObj: """ current_region = dict() + current_area = { + "name": "Misc", + "regions": [], + } + self.ALL_AREAS_BY_NAME["Misc"] = current_area for line in lines: if line == "" or line[0] == "#": @@ -67,6 +72,16 @@ class StaticWitnessLogicObj: region_name = current_region["name"] self.ALL_REGIONS_BY_NAME[region_name] = current_region self.STATIC_CONNECTIONS_BY_REGION_NAME[region_name] = new_region_and_connections[1] + current_area["regions"].append(region_name) + continue + + if line[0] == "=": + area_name = line[2:-2] + current_area = { + "name": area_name, + "regions": [], + } + self.ALL_AREAS_BY_NAME[area_name] = current_area continue line_split = line.split(" - ") @@ -88,7 +103,8 @@ class StaticWitnessLogicObj: "entity_hex": entity_hex, "region": None, "id": None, - "entityType": location_id + "entityType": location_id, + "area": current_area, } self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex] @@ -120,7 +136,6 @@ class StaticWitnessLogicObj: location_type = "Laser" elif "Obelisk Side" in entity_name: location_type = "Obelisk Side" - full_entity_name = entity_name elif "EP" in entity_name: location_type = "EP" else: @@ -151,7 +166,8 @@ class StaticWitnessLogicObj: "entity_hex": entity_hex, "region": current_region, "id": int(location_id), - "entityType": location_type + "entityType": location_type, + "area": current_area, } self.ENTITY_ID_TO_NAME[entity_hex] = full_entity_name @@ -167,6 +183,7 @@ class StaticWitnessLogicObj: # All regions with a list of panels in them and the connections to other regions, before logic adjustments self.ALL_REGIONS_BY_NAME = dict() + self.ALL_AREAS_BY_NAME = dict() self.STATIC_CONNECTIONS_BY_REGION_NAME = dict() self.ENTITIES_BY_HEX = dict() @@ -188,6 +205,7 @@ class StaticWitnessLogic: _progressive_lookup: Dict[str, str] = {} ALL_REGIONS_BY_NAME = dict() + ALL_AREAS_BY_NAME = dict() STATIC_CONNECTIONS_BY_REGION_NAME = dict() OBELISK_SIDE_ID_TO_EP_HEXES = dict() @@ -265,6 +283,7 @@ class StaticWitnessLogic: self.parse_items() self.ALL_REGIONS_BY_NAME.update(self.sigma_normal.ALL_REGIONS_BY_NAME) + self.ALL_AREAS_BY_NAME.update(self.sigma_normal.ALL_AREAS_BY_NAME) self.STATIC_CONNECTIONS_BY_REGION_NAME.update(self.sigma_normal.STATIC_CONNECTIONS_BY_REGION_NAME) self.ENTITIES_BY_HEX.update(self.sigma_normal.ENTITIES_BY_HEX) diff --git a/worlds/witness/utils.py b/worlds/witness/utils.py index fbb670fd08..b1f1b6d831 100644 --- a/worlds/witness/utils.py +++ b/worlds/witness/utils.py @@ -2,6 +2,21 @@ from functools import lru_cache from math import floor from typing import List, Collection, FrozenSet, Tuple, Dict, Any, Set from pkgutil import get_data +from random import random + + +def weighted_sample(world_random: random, population: List, weights: List[float], k: int): + positions = range(len(population)) + indices = [] + while True: + needed = k - len(indices) + if not needed: + break + for i in world_random.choices(positions, weights, k=needed): + if weights[i]: + weights[i] = 0.0 + indices.append(i) + return [population[i] for i in indices] def build_weighted_int_list(inputs: Collection[float], total: int) -> List[int]: From 36cee91a2c620316c3da1b4233d499b8d4e2a6dc Mon Sep 17 00:00:00 2001 From: PoryGone <98504756+PoryGone@users.noreply.github.com> Date: Tue, 27 Feb 2024 22:53:13 -0500 Subject: [PATCH 037/166] DKC3: Long-overdue World code cleanup (#2820) Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com> --- worlds/dkc3/Locations.py | 7 +- worlds/dkc3/Options.py | 39 ++-- worlds/dkc3/Regions.py | 375 ++++++++++++++++++++------------------- worlds/dkc3/Rom.py | 56 +++--- worlds/dkc3/Rules.py | 33 ++-- worlds/dkc3/__init__.py | 57 +++--- 6 files changed, 287 insertions(+), 280 deletions(-) diff --git a/worlds/dkc3/Locations.py b/worlds/dkc3/Locations.py index e8d5409b15..6d8833872b 100644 --- a/worlds/dkc3/Locations.py +++ b/worlds/dkc3/Locations.py @@ -2,6 +2,7 @@ import typing from BaseClasses import Location from .Names import LocationName +from worlds.AutoWorld import World class DKC3Location(Location): @@ -321,13 +322,13 @@ all_locations = { location_table = {} -def setup_locations(world, player: int): +def setup_locations(world: World): location_table = {**level_location_table, **boss_location_table, **secret_cave_location_table} - if False:#world.include_trade_sequence[player].value: + if False:#world.options.include_trade_sequence: location_table.update({**brothers_bear_location_table}) - if world.kongsanity[player].value: + if world.options.kongsanity: location_table.update({**kong_location_table}) return location_table diff --git a/worlds/dkc3/Options.py b/worlds/dkc3/Options.py index 7c0f532cfc..06be30cf15 100644 --- a/worlds/dkc3/Options.py +++ b/worlds/dkc3/Options.py @@ -1,6 +1,7 @@ +from dataclasses import dataclass import typing -from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList +from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList, PerGameCommonOptions class Goal(Choice): @@ -158,21 +159,21 @@ class StartingLifeCount(Range): default = 5 -dkc3_options: typing.Dict[str, type(Option)] = { - #"death_link": DeathLink, # Disabled - "goal": Goal, - #"include_trade_sequence": IncludeTradeSequence, # Disabled - "dk_coins_for_gyrocopter": DKCoinsForGyrocopter, - "krematoa_bonus_coin_cost": KrematoaBonusCoinCost, - "percentage_of_extra_bonus_coins": PercentageOfExtraBonusCoins, - "number_of_banana_birds": NumberOfBananaBirds, - "percentage_of_banana_birds": PercentageOfBananaBirds, - "kongsanity": KONGsanity, - "level_shuffle": LevelShuffle, - "difficulty": Difficulty, - "autosave": Autosave, - "merry": MERRY, - "music_shuffle": MusicShuffle, - "kong_palette_swap": KongPaletteSwap, - "starting_life_count": StartingLifeCount, -} +@dataclass +class DKC3Options(PerGameCommonOptions): + #death_link: DeathLink # Disabled + goal: Goal + #include_trade_sequence: IncludeTradeSequence # Disabled + dk_coins_for_gyrocopter: DKCoinsForGyrocopter + krematoa_bonus_coin_cost: KrematoaBonusCoinCost + percentage_of_extra_bonus_coins: PercentageOfExtraBonusCoins + number_of_banana_birds: NumberOfBananaBirds + percentage_of_banana_birds: PercentageOfBananaBirds + kongsanity: KONGsanity + level_shuffle: LevelShuffle + difficulty: Difficulty + autosave: Autosave + merry: MERRY + music_shuffle: MusicShuffle + kong_palette_swap: KongPaletteSwap + starting_life_count: StartingLifeCount diff --git a/worlds/dkc3/Regions.py b/worlds/dkc3/Regions.py index ca6545ca14..ae505b78d8 100644 --- a/worlds/dkc3/Regions.py +++ b/worlds/dkc3/Regions.py @@ -4,38 +4,39 @@ from BaseClasses import MultiWorld, Region, Entrance from .Items import DKC3Item from .Locations import DKC3Location from .Names import LocationName, ItemName +from worlds.AutoWorld import World -def create_regions(world, player: int, active_locations): - menu_region = create_region(world, player, active_locations, 'Menu', None) +def create_regions(world: World, active_locations): + menu_region = create_region(world, active_locations, 'Menu', None) overworld_1_region_locations = {} - if world.goal[player] != "knautilus": + if world.options.goal != "knautilus": overworld_1_region_locations.update({LocationName.banana_bird_mother: []}) - overworld_1_region = create_region(world, player, active_locations, LocationName.overworld_1_region, + overworld_1_region = create_region(world, active_locations, LocationName.overworld_1_region, overworld_1_region_locations) overworld_2_region_locations = {} - overworld_2_region = create_region(world, player, active_locations, LocationName.overworld_2_region, + overworld_2_region = create_region(world, active_locations, LocationName.overworld_2_region, overworld_2_region_locations) overworld_3_region_locations = {} - overworld_3_region = create_region(world, player, active_locations, LocationName.overworld_3_region, + overworld_3_region = create_region(world, active_locations, LocationName.overworld_3_region, overworld_3_region_locations) overworld_4_region_locations = {} - overworld_4_region = create_region(world, player, active_locations, LocationName.overworld_4_region, + overworld_4_region = create_region(world, active_locations, LocationName.overworld_4_region, overworld_4_region_locations) - lake_orangatanga_region = create_region(world, player, active_locations, LocationName.lake_orangatanga_region, None) - kremwood_forest_region = create_region(world, player, active_locations, LocationName.kremwood_forest_region, None) - cotton_top_cove_region = create_region(world, player, active_locations, LocationName.cotton_top_cove_region, None) - mekanos_region = create_region(world, player, active_locations, LocationName.mekanos_region, None) - k3_region = create_region(world, player, active_locations, LocationName.k3_region, None) - razor_ridge_region = create_region(world, player, active_locations, LocationName.razor_ridge_region, None) - kaos_kore_region = create_region(world, player, active_locations, LocationName.kaos_kore_region, None) - krematoa_region = create_region(world, player, active_locations, LocationName.krematoa_region, None) + lake_orangatanga_region = create_region(world, active_locations, LocationName.lake_orangatanga_region, None) + kremwood_forest_region = create_region(world, active_locations, LocationName.kremwood_forest_region, None) + cotton_top_cove_region = create_region(world, active_locations, LocationName.cotton_top_cove_region, None) + mekanos_region = create_region(world, active_locations, LocationName.mekanos_region, None) + k3_region = create_region(world, active_locations, LocationName.k3_region, None) + razor_ridge_region = create_region(world, active_locations, LocationName.razor_ridge_region, None) + kaos_kore_region = create_region(world, active_locations, LocationName.kaos_kore_region, None) + krematoa_region = create_region(world, active_locations, LocationName.krematoa_region, None) lakeside_limbo_region_locations = { @@ -44,9 +45,9 @@ def create_regions(world, player: int, active_locations): LocationName.lakeside_limbo_bonus_2 : [0x657, 3], LocationName.lakeside_limbo_dk : [0x657, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: lakeside_limbo_region_locations[LocationName.lakeside_limbo_kong] = [] - lakeside_limbo_region = create_region(world, player, active_locations, LocationName.lakeside_limbo_region, + lakeside_limbo_region = create_region(world, active_locations, LocationName.lakeside_limbo_region, lakeside_limbo_region_locations) doorstop_dash_region_locations = { @@ -55,9 +56,9 @@ def create_regions(world, player: int, active_locations): LocationName.doorstop_dash_bonus_2 : [0x65A, 3], LocationName.doorstop_dash_dk : [0x65A, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: doorstop_dash_region_locations[LocationName.doorstop_dash_kong] = [] - doorstop_dash_region = create_region(world, player, active_locations, LocationName.doorstop_dash_region, + doorstop_dash_region = create_region(world, active_locations, LocationName.doorstop_dash_region, doorstop_dash_region_locations) tidal_trouble_region_locations = { @@ -66,9 +67,9 @@ def create_regions(world, player: int, active_locations): LocationName.tidal_trouble_bonus_2 : [0x659, 3], LocationName.tidal_trouble_dk : [0x659, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: tidal_trouble_region_locations[LocationName.tidal_trouble_kong] = [] - tidal_trouble_region = create_region(world, player, active_locations, LocationName.tidal_trouble_region, + tidal_trouble_region = create_region(world, active_locations, LocationName.tidal_trouble_region, tidal_trouble_region_locations) skiddas_row_region_locations = { @@ -77,9 +78,9 @@ def create_regions(world, player: int, active_locations): LocationName.skiddas_row_bonus_2 : [0x65D, 3], LocationName.skiddas_row_dk : [0x65D, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: skiddas_row_region_locations[LocationName.skiddas_row_kong] = [] - skiddas_row_region = create_region(world, player, active_locations, LocationName.skiddas_row_region, + skiddas_row_region = create_region(world, active_locations, LocationName.skiddas_row_region, skiddas_row_region_locations) murky_mill_region_locations = { @@ -88,9 +89,9 @@ def create_regions(world, player: int, active_locations): LocationName.murky_mill_bonus_2 : [0x65C, 3], LocationName.murky_mill_dk : [0x65C, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: murky_mill_region_locations[LocationName.murky_mill_kong] = [] - murky_mill_region = create_region(world, player, active_locations, LocationName.murky_mill_region, + murky_mill_region = create_region(world, active_locations, LocationName.murky_mill_region, murky_mill_region_locations) barrel_shield_bust_up_region_locations = { @@ -99,9 +100,9 @@ def create_regions(world, player: int, active_locations): LocationName.barrel_shield_bust_up_bonus_2 : [0x662, 3], LocationName.barrel_shield_bust_up_dk : [0x662, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: barrel_shield_bust_up_region_locations[LocationName.barrel_shield_bust_up_kong] = [] - barrel_shield_bust_up_region = create_region(world, player, active_locations, + barrel_shield_bust_up_region = create_region(world, active_locations, LocationName.barrel_shield_bust_up_region, barrel_shield_bust_up_region_locations) @@ -111,9 +112,9 @@ def create_regions(world, player: int, active_locations): LocationName.riverside_race_bonus_2 : [0x664, 3], LocationName.riverside_race_dk : [0x664, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: riverside_race_region_locations[LocationName.riverside_race_kong] = [] - riverside_race_region = create_region(world, player, active_locations, LocationName.riverside_race_region, + riverside_race_region = create_region(world, active_locations, LocationName.riverside_race_region, riverside_race_region_locations) squeals_on_wheels_region_locations = { @@ -122,9 +123,9 @@ def create_regions(world, player: int, active_locations): LocationName.squeals_on_wheels_bonus_2 : [0x65B, 3], LocationName.squeals_on_wheels_dk : [0x65B, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: squeals_on_wheels_region_locations[LocationName.squeals_on_wheels_kong] = [] - squeals_on_wheels_region = create_region(world, player, active_locations, LocationName.squeals_on_wheels_region, + squeals_on_wheels_region = create_region(world, active_locations, LocationName.squeals_on_wheels_region, squeals_on_wheels_region_locations) springin_spiders_region_locations = { @@ -133,9 +134,9 @@ def create_regions(world, player: int, active_locations): LocationName.springin_spiders_bonus_2 : [0x661, 3], LocationName.springin_spiders_dk : [0x661, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: springin_spiders_region_locations[LocationName.springin_spiders_kong] = [] - springin_spiders_region = create_region(world, player, active_locations, LocationName.springin_spiders_region, + springin_spiders_region = create_region(world, active_locations, LocationName.springin_spiders_region, springin_spiders_region_locations) bobbing_barrel_brawl_region_locations = { @@ -144,9 +145,9 @@ def create_regions(world, player: int, active_locations): LocationName.bobbing_barrel_brawl_bonus_2 : [0x666, 3], LocationName.bobbing_barrel_brawl_dk : [0x666, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: bobbing_barrel_brawl_region_locations[LocationName.bobbing_barrel_brawl_kong] = [] - bobbing_barrel_brawl_region = create_region(world, player, active_locations, + bobbing_barrel_brawl_region = create_region(world, active_locations, LocationName.bobbing_barrel_brawl_region, bobbing_barrel_brawl_region_locations) @@ -156,9 +157,9 @@ def create_regions(world, player: int, active_locations): LocationName.bazzas_blockade_bonus_2 : [0x667, 3], LocationName.bazzas_blockade_dk : [0x667, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: bazzas_blockade_region_locations[LocationName.bazzas_blockade_kong] = [] - bazzas_blockade_region = create_region(world, player, active_locations, LocationName.bazzas_blockade_region, + bazzas_blockade_region = create_region(world, active_locations, LocationName.bazzas_blockade_region, bazzas_blockade_region_locations) rocket_barrel_ride_region_locations = { @@ -167,9 +168,9 @@ def create_regions(world, player: int, active_locations): LocationName.rocket_barrel_ride_bonus_2 : [0x66A, 3], LocationName.rocket_barrel_ride_dk : [0x66A, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: rocket_barrel_ride_region_locations[LocationName.rocket_barrel_ride_kong] = [] - rocket_barrel_ride_region = create_region(world, player, active_locations, LocationName.rocket_barrel_ride_region, + rocket_barrel_ride_region = create_region(world, active_locations, LocationName.rocket_barrel_ride_region, rocket_barrel_ride_region_locations) kreeping_klasps_region_locations = { @@ -178,9 +179,9 @@ def create_regions(world, player: int, active_locations): LocationName.kreeping_klasps_bonus_2 : [0x658, 3], LocationName.kreeping_klasps_dk : [0x658, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: kreeping_klasps_region_locations[LocationName.kreeping_klasps_kong] = [] - kreeping_klasps_region = create_region(world, player, active_locations, LocationName.kreeping_klasps_region, + kreeping_klasps_region = create_region(world, active_locations, LocationName.kreeping_klasps_region, kreeping_klasps_region_locations) tracker_barrel_trek_region_locations = { @@ -189,9 +190,9 @@ def create_regions(world, player: int, active_locations): LocationName.tracker_barrel_trek_bonus_2 : [0x66B, 3], LocationName.tracker_barrel_trek_dk : [0x66B, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: tracker_barrel_trek_region_locations[LocationName.tracker_barrel_trek_kong] = [] - tracker_barrel_trek_region = create_region(world, player, active_locations, LocationName.tracker_barrel_trek_region, + tracker_barrel_trek_region = create_region(world, active_locations, LocationName.tracker_barrel_trek_region, tracker_barrel_trek_region_locations) fish_food_frenzy_region_locations = { @@ -200,9 +201,9 @@ def create_regions(world, player: int, active_locations): LocationName.fish_food_frenzy_bonus_2 : [0x668, 3], LocationName.fish_food_frenzy_dk : [0x668, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: fish_food_frenzy_region_locations[LocationName.fish_food_frenzy_kong] = [] - fish_food_frenzy_region = create_region(world, player, active_locations, LocationName.fish_food_frenzy_region, + fish_food_frenzy_region = create_region(world, active_locations, LocationName.fish_food_frenzy_region, fish_food_frenzy_region_locations) fire_ball_frenzy_region_locations = { @@ -211,9 +212,9 @@ def create_regions(world, player: int, active_locations): LocationName.fire_ball_frenzy_bonus_2 : [0x66D, 3], LocationName.fire_ball_frenzy_dk : [0x66D, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: fire_ball_frenzy_region_locations[LocationName.fire_ball_frenzy_kong] = [] - fire_ball_frenzy_region = create_region(world, player, active_locations, LocationName.fire_ball_frenzy_region, + fire_ball_frenzy_region = create_region(world, active_locations, LocationName.fire_ball_frenzy_region, fire_ball_frenzy_region_locations) demolition_drain_pipe_region_locations = { @@ -222,9 +223,9 @@ def create_regions(world, player: int, active_locations): LocationName.demolition_drain_pipe_bonus_2 : [0x672, 3], LocationName.demolition_drain_pipe_dk : [0x672, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: demolition_drain_pipe_region_locations[LocationName.demolition_drain_pipe_kong] = [] - demolition_drain_pipe_region = create_region(world, player, active_locations, + demolition_drain_pipe_region = create_region(world, active_locations, LocationName.demolition_drain_pipe_region, demolition_drain_pipe_region_locations) @@ -234,9 +235,9 @@ def create_regions(world, player: int, active_locations): LocationName.ripsaw_rage_bonus_2 : [0x660, 3], LocationName.ripsaw_rage_dk : [0x660, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: ripsaw_rage_region_locations[LocationName.ripsaw_rage_kong] = [] - ripsaw_rage_region = create_region(world, player, active_locations, LocationName.ripsaw_rage_region, + ripsaw_rage_region = create_region(world, active_locations, LocationName.ripsaw_rage_region, ripsaw_rage_region_locations) blazing_bazookas_region_locations = { @@ -245,9 +246,9 @@ def create_regions(world, player: int, active_locations): LocationName.blazing_bazookas_bonus_2 : [0x66E, 3], LocationName.blazing_bazookas_dk : [0x66E, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: blazing_bazookas_region_locations[LocationName.blazing_bazookas_kong] = [] - blazing_bazookas_region = create_region(world, player, active_locations, LocationName.blazing_bazookas_region, + blazing_bazookas_region = create_region(world, active_locations, LocationName.blazing_bazookas_region, blazing_bazookas_region_locations) low_g_labyrinth_region_locations = { @@ -256,9 +257,9 @@ def create_regions(world, player: int, active_locations): LocationName.low_g_labyrinth_bonus_2 : [0x670, 3], LocationName.low_g_labyrinth_dk : [0x670, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: low_g_labyrinth_region_locations[LocationName.low_g_labyrinth_kong] = [] - low_g_labyrinth_region = create_region(world, player, active_locations, LocationName.low_g_labyrinth_region, + low_g_labyrinth_region = create_region(world, active_locations, LocationName.low_g_labyrinth_region, low_g_labyrinth_region_locations) krevice_kreepers_region_locations = { @@ -267,9 +268,9 @@ def create_regions(world, player: int, active_locations): LocationName.krevice_kreepers_bonus_2 : [0x673, 3], LocationName.krevice_kreepers_dk : [0x673, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: krevice_kreepers_region_locations[LocationName.krevice_kreepers_kong] = [] - krevice_kreepers_region = create_region(world, player, active_locations, LocationName.krevice_kreepers_region, + krevice_kreepers_region = create_region(world, active_locations, LocationName.krevice_kreepers_region, krevice_kreepers_region_locations) tearaway_toboggan_region_locations = { @@ -278,9 +279,9 @@ def create_regions(world, player: int, active_locations): LocationName.tearaway_toboggan_bonus_2 : [0x65F, 3], LocationName.tearaway_toboggan_dk : [0x65F, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: tearaway_toboggan_region_locations[LocationName.tearaway_toboggan_kong] = [] - tearaway_toboggan_region = create_region(world, player, active_locations, LocationName.tearaway_toboggan_region, + tearaway_toboggan_region = create_region(world, active_locations, LocationName.tearaway_toboggan_region, tearaway_toboggan_region_locations) barrel_drop_bounce_region_locations = { @@ -289,9 +290,9 @@ def create_regions(world, player: int, active_locations): LocationName.barrel_drop_bounce_bonus_2 : [0x66C, 3], LocationName.barrel_drop_bounce_dk : [0x66C, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: barrel_drop_bounce_region_locations[LocationName.barrel_drop_bounce_kong] = [] - barrel_drop_bounce_region = create_region(world, player, active_locations, LocationName.barrel_drop_bounce_region, + barrel_drop_bounce_region = create_region(world, active_locations, LocationName.barrel_drop_bounce_region, barrel_drop_bounce_region_locations) krack_shot_kroc_region_locations = { @@ -300,9 +301,9 @@ def create_regions(world, player: int, active_locations): LocationName.krack_shot_kroc_bonus_2 : [0x66F, 3], LocationName.krack_shot_kroc_dk : [0x66F, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: krack_shot_kroc_region_locations[LocationName.krack_shot_kroc_kong] = [] - krack_shot_kroc_region = create_region(world, player, active_locations, LocationName.krack_shot_kroc_region, + krack_shot_kroc_region = create_region(world, active_locations, LocationName.krack_shot_kroc_region, krack_shot_kroc_region_locations) lemguin_lunge_region_locations = { @@ -311,9 +312,9 @@ def create_regions(world, player: int, active_locations): LocationName.lemguin_lunge_bonus_2 : [0x65E, 3], LocationName.lemguin_lunge_dk : [0x65E, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: lemguin_lunge_region_locations[LocationName.lemguin_lunge_kong] = [] - lemguin_lunge_region = create_region(world, player, active_locations, LocationName.lemguin_lunge_region, + lemguin_lunge_region = create_region(world, active_locations, LocationName.lemguin_lunge_region, lemguin_lunge_region_locations) buzzer_barrage_region_locations = { @@ -322,9 +323,9 @@ def create_regions(world, player: int, active_locations): LocationName.buzzer_barrage_bonus_2 : [0x676, 3], LocationName.buzzer_barrage_dk : [0x676, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: buzzer_barrage_region_locations[LocationName.buzzer_barrage_kong] = [] - buzzer_barrage_region = create_region(world, player, active_locations, LocationName.buzzer_barrage_region, + buzzer_barrage_region = create_region(world, active_locations, LocationName.buzzer_barrage_region, buzzer_barrage_region_locations) kong_fused_cliffs_region_locations = { @@ -333,9 +334,9 @@ def create_regions(world, player: int, active_locations): LocationName.kong_fused_cliffs_bonus_2 : [0x674, 3], LocationName.kong_fused_cliffs_dk : [0x674, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: kong_fused_cliffs_region_locations[LocationName.kong_fused_cliffs_kong] = [] - kong_fused_cliffs_region = create_region(world, player, active_locations, LocationName.kong_fused_cliffs_region, + kong_fused_cliffs_region = create_region(world, active_locations, LocationName.kong_fused_cliffs_region, kong_fused_cliffs_region_locations) floodlit_fish_region_locations = { @@ -344,9 +345,9 @@ def create_regions(world, player: int, active_locations): LocationName.floodlit_fish_bonus_2 : [0x669, 3], LocationName.floodlit_fish_dk : [0x669, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: floodlit_fish_region_locations[LocationName.floodlit_fish_kong] = [] - floodlit_fish_region = create_region(world, player, active_locations, LocationName.floodlit_fish_region, + floodlit_fish_region = create_region(world, active_locations, LocationName.floodlit_fish_region, floodlit_fish_region_locations) pothole_panic_region_locations = { @@ -355,9 +356,9 @@ def create_regions(world, player: int, active_locations): LocationName.pothole_panic_bonus_2 : [0x677, 3], LocationName.pothole_panic_dk : [0x677, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: pothole_panic_region_locations[LocationName.pothole_panic_kong] = [] - pothole_panic_region = create_region(world, player, active_locations, LocationName.pothole_panic_region, + pothole_panic_region = create_region(world, active_locations, LocationName.pothole_panic_region, pothole_panic_region_locations) ropey_rumpus_region_locations = { @@ -366,9 +367,9 @@ def create_regions(world, player: int, active_locations): LocationName.ropey_rumpus_bonus_2 : [0x675, 3], LocationName.ropey_rumpus_dk : [0x675, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: ropey_rumpus_region_locations[LocationName.ropey_rumpus_kong] = [] - ropey_rumpus_region = create_region(world, player, active_locations, LocationName.ropey_rumpus_region, + ropey_rumpus_region = create_region(world, active_locations, LocationName.ropey_rumpus_region, ropey_rumpus_region_locations) konveyor_rope_clash_region_locations = { @@ -377,9 +378,9 @@ def create_regions(world, player: int, active_locations): LocationName.konveyor_rope_clash_bonus_2 : [0x657, 3], LocationName.konveyor_rope_clash_dk : [0x657, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: konveyor_rope_clash_region_locations[LocationName.konveyor_rope_clash_kong] = [] - konveyor_rope_clash_region = create_region(world, player, active_locations, LocationName.konveyor_rope_clash_region, + konveyor_rope_clash_region = create_region(world, active_locations, LocationName.konveyor_rope_clash_region, konveyor_rope_clash_region_locations) creepy_caverns_region_locations = { @@ -388,9 +389,9 @@ def create_regions(world, player: int, active_locations): LocationName.creepy_caverns_bonus_2 : [0x678, 3], LocationName.creepy_caverns_dk : [0x678, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: creepy_caverns_region_locations[LocationName.creepy_caverns_kong] = [] - creepy_caverns_region = create_region(world, player, active_locations, LocationName.creepy_caverns_region, + creepy_caverns_region = create_region(world, active_locations, LocationName.creepy_caverns_region, creepy_caverns_region_locations) lightning_lookout_region_locations = { @@ -399,9 +400,9 @@ def create_regions(world, player: int, active_locations): LocationName.lightning_lookout_bonus_2 : [0x665, 3], LocationName.lightning_lookout_dk : [0x665, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: lightning_lookout_region_locations[LocationName.lightning_lookout_kong] = [] - lightning_lookout_region = create_region(world, player, active_locations, LocationName.lightning_lookout_region, + lightning_lookout_region = create_region(world, active_locations, LocationName.lightning_lookout_region, lightning_lookout_region_locations) koindozer_klamber_region_locations = { @@ -410,9 +411,9 @@ def create_regions(world, player: int, active_locations): LocationName.koindozer_klamber_bonus_2 : [0x679, 3], LocationName.koindozer_klamber_dk : [0x679, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: koindozer_klamber_region_locations[LocationName.koindozer_klamber_kong] = [] - koindozer_klamber_region = create_region(world, player, active_locations, LocationName.koindozer_klamber_region, + koindozer_klamber_region = create_region(world, active_locations, LocationName.koindozer_klamber_region, koindozer_klamber_region_locations) poisonous_pipeline_region_locations = { @@ -421,9 +422,9 @@ def create_regions(world, player: int, active_locations): LocationName.poisonous_pipeline_bonus_2 : [0x671, 3], LocationName.poisonous_pipeline_dk : [0x671, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: poisonous_pipeline_region_locations[LocationName.poisonous_pipeline_kong] = [] - poisonous_pipeline_region = create_region(world, player, active_locations, LocationName.poisonous_pipeline_region, + poisonous_pipeline_region = create_region(world, active_locations, LocationName.poisonous_pipeline_region, poisonous_pipeline_region_locations) stampede_sprint_region_locations = { @@ -433,9 +434,9 @@ def create_regions(world, player: int, active_locations): LocationName.stampede_sprint_bonus_3 : [0x67B, 4], LocationName.stampede_sprint_dk : [0x67B, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: stampede_sprint_region_locations[LocationName.stampede_sprint_kong] = [] - stampede_sprint_region = create_region(world, player, active_locations, LocationName.stampede_sprint_region, + stampede_sprint_region = create_region(world, active_locations, LocationName.stampede_sprint_region, stampede_sprint_region_locations) criss_cross_cliffs_region_locations = { @@ -444,9 +445,9 @@ def create_regions(world, player: int, active_locations): LocationName.criss_cross_cliffs_bonus_2 : [0x67C, 3], LocationName.criss_cross_cliffs_dk : [0x67C, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: criss_cross_cliffs_region_locations[LocationName.criss_cross_cliffs_kong] = [] - criss_cross_cliffs_region = create_region(world, player, active_locations, LocationName.criss_cross_cliffs_region, + criss_cross_cliffs_region = create_region(world, active_locations, LocationName.criss_cross_cliffs_region, criss_cross_cliffs_region_locations) tyrant_twin_tussle_region_locations = { @@ -456,9 +457,9 @@ def create_regions(world, player: int, active_locations): LocationName.tyrant_twin_tussle_bonus_3 : [0x67D, 4], LocationName.tyrant_twin_tussle_dk : [0x67D, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: tyrant_twin_tussle_region_locations[LocationName.tyrant_twin_tussle_kong] = [] - tyrant_twin_tussle_region = create_region(world, player, active_locations, LocationName.tyrant_twin_tussle_region, + tyrant_twin_tussle_region = create_region(world, active_locations, LocationName.tyrant_twin_tussle_region, tyrant_twin_tussle_region_locations) swoopy_salvo_region_locations = { @@ -468,147 +469,147 @@ def create_regions(world, player: int, active_locations): LocationName.swoopy_salvo_bonus_3 : [0x663, 4], LocationName.swoopy_salvo_dk : [0x663, 5], } - if world.kongsanity[player]: + if world.options.kongsanity: swoopy_salvo_region_locations[LocationName.swoopy_salvo_kong] = [] - swoopy_salvo_region = create_region(world, player, active_locations, LocationName.swoopy_salvo_region, + swoopy_salvo_region = create_region(world, active_locations, LocationName.swoopy_salvo_region, swoopy_salvo_region_locations) rocket_rush_region_locations = { LocationName.rocket_rush_flag : [0x67E, 1], LocationName.rocket_rush_dk : [0x67E, 5], } - rocket_rush_region = create_region(world, player, active_locations, LocationName.rocket_rush_region, + rocket_rush_region = create_region(world, active_locations, LocationName.rocket_rush_region, rocket_rush_region_locations) belchas_barn_region_locations = { LocationName.belchas_barn: [0x64F, 1], } - belchas_barn_region = create_region(world, player, active_locations, LocationName.belchas_barn_region, + belchas_barn_region = create_region(world, active_locations, LocationName.belchas_barn_region, belchas_barn_region_locations) arichs_ambush_region_locations = { LocationName.arichs_ambush: [0x650, 1], } - arichs_ambush_region = create_region(world, player, active_locations, LocationName.arichs_ambush_region, + arichs_ambush_region = create_region(world, active_locations, LocationName.arichs_ambush_region, arichs_ambush_region_locations) squirts_showdown_region_locations = { LocationName.squirts_showdown: [0x651, 1], } - squirts_showdown_region = create_region(world, player, active_locations, LocationName.squirts_showdown_region, + squirts_showdown_region = create_region(world, active_locations, LocationName.squirts_showdown_region, squirts_showdown_region_locations) kaos_karnage_region_locations = { LocationName.kaos_karnage: [0x652, 1], } - kaos_karnage_region = create_region(world, player, active_locations, LocationName.kaos_karnage_region, + kaos_karnage_region = create_region(world, active_locations, LocationName.kaos_karnage_region, kaos_karnage_region_locations) bleaks_house_region_locations = { LocationName.bleaks_house: [0x653, 1], } - bleaks_house_region = create_region(world, player, active_locations, LocationName.bleaks_house_region, + bleaks_house_region = create_region(world, active_locations, LocationName.bleaks_house_region, bleaks_house_region_locations) barboss_barrier_region_locations = { LocationName.barboss_barrier: [0x654, 1], } - barboss_barrier_region = create_region(world, player, active_locations, LocationName.barboss_barrier_region, + barboss_barrier_region = create_region(world, active_locations, LocationName.barboss_barrier_region, barboss_barrier_region_locations) kastle_kaos_region_locations = { LocationName.kastle_kaos: [0x655, 1], } - kastle_kaos_region = create_region(world, player, active_locations, LocationName.kastle_kaos_region, + kastle_kaos_region = create_region(world, active_locations, LocationName.kastle_kaos_region, kastle_kaos_region_locations) knautilus_region_locations = { LocationName.knautilus: [0x656, 1], } - knautilus_region = create_region(world, player, active_locations, LocationName.knautilus_region, + knautilus_region = create_region(world, active_locations, LocationName.knautilus_region, knautilus_region_locations) belchas_burrow_region_locations = { LocationName.belchas_burrow: [0x647, 1], } - belchas_burrow_region = create_region(world, player, active_locations, LocationName.belchas_burrow_region, + belchas_burrow_region = create_region(world, active_locations, LocationName.belchas_burrow_region, belchas_burrow_region_locations) kong_cave_region_locations = { LocationName.kong_cave: [0x645, 1], } - kong_cave_region = create_region(world, player, active_locations, LocationName.kong_cave_region, + kong_cave_region = create_region(world, active_locations, LocationName.kong_cave_region, kong_cave_region_locations) undercover_cove_region_locations = { LocationName.undercover_cove: [0x644, 1], } - undercover_cove_region = create_region(world, player, active_locations, LocationName.undercover_cove_region, + undercover_cove_region = create_region(world, active_locations, LocationName.undercover_cove_region, undercover_cove_region_locations) ks_cache_region_locations = { LocationName.ks_cache: [0x642, 1], } - ks_cache_region = create_region(world, player, active_locations, LocationName.ks_cache_region, + ks_cache_region = create_region(world, active_locations, LocationName.ks_cache_region, ks_cache_region_locations) hill_top_hoard_region_locations = { LocationName.hill_top_hoard: [0x643, 1], } - hill_top_hoard_region = create_region(world, player, active_locations, LocationName.hill_top_hoard_region, + hill_top_hoard_region = create_region(world, active_locations, LocationName.hill_top_hoard_region, hill_top_hoard_region_locations) bounty_beach_region_locations = { LocationName.bounty_beach: [0x646, 1], } - bounty_beach_region = create_region(world, player, active_locations, LocationName.bounty_beach_region, + bounty_beach_region = create_region(world, active_locations, LocationName.bounty_beach_region, bounty_beach_region_locations) smugglers_cove_region_locations = { LocationName.smugglers_cove: [0x648, 1], } - smugglers_cove_region = create_region(world, player, active_locations, LocationName.smugglers_cove_region, + smugglers_cove_region = create_region(world, active_locations, LocationName.smugglers_cove_region, smugglers_cove_region_locations) arichs_hoard_region_locations = { LocationName.arichs_hoard: [0x649, 1], } - arichs_hoard_region = create_region(world, player, active_locations, LocationName.arichs_hoard_region, + arichs_hoard_region = create_region(world, active_locations, LocationName.arichs_hoard_region, arichs_hoard_region_locations) bounty_bay_region_locations = { LocationName.bounty_bay: [0x64A, 1], } - bounty_bay_region = create_region(world, player, active_locations, LocationName.bounty_bay_region, + bounty_bay_region = create_region(world, active_locations, LocationName.bounty_bay_region, bounty_bay_region_locations) sky_high_secret_region_locations = {} - if False:#world.include_trade_sequence[player]: + if False:#world.options.include_trade_sequence: sky_high_secret_region_locations[LocationName.sky_high_secret] = [0x64B, 1] - sky_high_secret_region = create_region(world, player, active_locations, LocationName.sky_high_secret_region, + sky_high_secret_region = create_region(world, active_locations, LocationName.sky_high_secret_region, sky_high_secret_region_locations) glacial_grotto_region_locations = { LocationName.glacial_grotto: [0x64C, 1], } - glacial_grotto_region = create_region(world, player, active_locations, LocationName.glacial_grotto_region, + glacial_grotto_region = create_region(world, active_locations, LocationName.glacial_grotto_region, glacial_grotto_region_locations) cifftop_cache_region_locations = {} - if False:#world.include_trade_sequence[player]: + if False:#world.options.include_trade_sequence: cifftop_cache_region_locations[LocationName.cifftop_cache] = [0x64D, 1] - cifftop_cache_region = create_region(world, player, active_locations, LocationName.cifftop_cache_region, + cifftop_cache_region = create_region(world, active_locations, LocationName.cifftop_cache_region, cifftop_cache_region_locations) sewer_stockpile_region_locations = { LocationName.sewer_stockpile: [0x64E, 1], } - sewer_stockpile_region = create_region(world, player, active_locations, LocationName.sewer_stockpile_region, + sewer_stockpile_region = create_region(world, active_locations, LocationName.sewer_stockpile_region, sewer_stockpile_region_locations) # Set up the regions correctly. - world.regions += [ + world.multiworld.regions += [ menu_region, overworld_1_region, overworld_2_region, @@ -693,7 +694,7 @@ def create_regions(world, player: int, active_locations): blue_region_locations = {} blizzard_region_locations = {} - if False:#world.include_trade_sequence[player]: + if False:#world.options.include_trade_sequence: bazaar_region_locations.update({ LocationName.bazaars_general_store_1: [0x615, 2, True], LocationName.bazaars_general_store_2: [0x615, 3, True], @@ -713,19 +714,19 @@ def create_regions(world, player: int, active_locations): blizzard_region_locations[LocationName.blizzards_basecamp] = [0x625, 4, True] - bazaar_region = create_region(world, player, active_locations, LocationName.bazaar_region, bazaar_region_locations) - bramble_region = create_region(world, player, active_locations, LocationName.bramble_region, + bazaar_region = create_region(world, active_locations, LocationName.bazaar_region, bazaar_region_locations) + bramble_region = create_region(world, active_locations, LocationName.bramble_region, bramble_region_locations) - flower_spot_region = create_region(world, player, active_locations, LocationName.flower_spot_region, + flower_spot_region = create_region(world, active_locations, LocationName.flower_spot_region, flower_spot_region_locations) - barter_region = create_region(world, player, active_locations, LocationName.barter_region, barter_region_locations) - barnacle_region = create_region(world, player, active_locations, LocationName.barnacle_region, + barter_region = create_region(world, active_locations, LocationName.barter_region, barter_region_locations) + barnacle_region = create_region(world, active_locations, LocationName.barnacle_region, barnacle_region_locations) - blue_region = create_region(world, player, active_locations, LocationName.blue_region, blue_region_locations) - blizzard_region = create_region(world, player, active_locations, LocationName.blizzard_region, + blue_region = create_region(world, active_locations, LocationName.blue_region, blue_region_locations) + blizzard_region = create_region(world, active_locations, LocationName.blizzard_region, blizzard_region_locations) - world.regions += [ + world.multiworld.regions += [ bazaar_region, bramble_region, flower_spot_region, @@ -736,41 +737,41 @@ def create_regions(world, player: int, active_locations): ] -def connect_regions(world, player, level_list): +def connect_regions(world: World, level_list): names: typing.Dict[str, int] = {} # Overworld - connect(world, player, names, 'Menu', LocationName.overworld_1_region) - connect(world, player, names, LocationName.overworld_1_region, LocationName.overworld_2_region, - lambda state: (state.has(ItemName.progressive_boat, player, 1))) - connect(world, player, names, LocationName.overworld_2_region, LocationName.overworld_3_region, - lambda state: (state.has(ItemName.progressive_boat, player, 3))) - connect(world, player, names, LocationName.overworld_1_region, LocationName.overworld_4_region, - lambda state: (state.has(ItemName.dk_coin, player, world.dk_coins_for_gyrocopter[player].value) and - state.has(ItemName.progressive_boat, player, 3))) + connect(world, world.player, names, 'Menu', LocationName.overworld_1_region) + connect(world, world.player, names, LocationName.overworld_1_region, LocationName.overworld_2_region, + lambda state: (state.has(ItemName.progressive_boat, world.player, 1))) + connect(world, world.player, names, LocationName.overworld_2_region, LocationName.overworld_3_region, + lambda state: (state.has(ItemName.progressive_boat, world.player, 3))) + connect(world, world.player, names, LocationName.overworld_1_region, LocationName.overworld_4_region, + lambda state: (state.has(ItemName.dk_coin, world.player, world.options.dk_coins_for_gyrocopter.value) and + state.has(ItemName.progressive_boat, world.player, 3))) # World Connections - connect(world, player, names, LocationName.overworld_1_region, LocationName.lake_orangatanga_region) - connect(world, player, names, LocationName.overworld_1_region, LocationName.kremwood_forest_region) - connect(world, player, names, LocationName.overworld_1_region, LocationName.bounty_beach_region) - connect(world, player, names, LocationName.overworld_1_region, LocationName.bazaar_region) + connect(world, world.player, names, LocationName.overworld_1_region, LocationName.lake_orangatanga_region) + connect(world, world.player, names, LocationName.overworld_1_region, LocationName.kremwood_forest_region) + connect(world, world.player, names, LocationName.overworld_1_region, LocationName.bounty_beach_region) + connect(world, world.player, names, LocationName.overworld_1_region, LocationName.bazaar_region) - connect(world, player, names, LocationName.overworld_2_region, LocationName.cotton_top_cove_region) - connect(world, player, names, LocationName.overworld_2_region, LocationName.mekanos_region) - connect(world, player, names, LocationName.overworld_2_region, LocationName.kong_cave_region) - connect(world, player, names, LocationName.overworld_2_region, LocationName.bramble_region) + connect(world, world.player, names, LocationName.overworld_2_region, LocationName.cotton_top_cove_region) + connect(world, world.player, names, LocationName.overworld_2_region, LocationName.mekanos_region) + connect(world, world.player, names, LocationName.overworld_2_region, LocationName.kong_cave_region) + connect(world, world.player, names, LocationName.overworld_2_region, LocationName.bramble_region) - connect(world, player, names, LocationName.overworld_3_region, LocationName.k3_region) - connect(world, player, names, LocationName.overworld_3_region, LocationName.razor_ridge_region) - connect(world, player, names, LocationName.overworld_3_region, LocationName.kaos_kore_region) - connect(world, player, names, LocationName.overworld_3_region, LocationName.krematoa_region) - connect(world, player, names, LocationName.overworld_3_region, LocationName.undercover_cove_region) - connect(world, player, names, LocationName.overworld_3_region, LocationName.flower_spot_region) - connect(world, player, names, LocationName.overworld_3_region, LocationName.barter_region) + connect(world, world.player, names, LocationName.overworld_3_region, LocationName.k3_region) + connect(world, world.player, names, LocationName.overworld_3_region, LocationName.razor_ridge_region) + connect(world, world.player, names, LocationName.overworld_3_region, LocationName.kaos_kore_region) + connect(world, world.player, names, LocationName.overworld_3_region, LocationName.krematoa_region) + connect(world, world.player, names, LocationName.overworld_3_region, LocationName.undercover_cove_region) + connect(world, world.player, names, LocationName.overworld_3_region, LocationName.flower_spot_region) + connect(world, world.player, names, LocationName.overworld_3_region, LocationName.barter_region) - connect(world, player, names, LocationName.overworld_4_region, LocationName.belchas_burrow_region) - connect(world, player, names, LocationName.overworld_4_region, LocationName.ks_cache_region) - connect(world, player, names, LocationName.overworld_4_region, LocationName.hill_top_hoard_region) + connect(world, world.player, names, LocationName.overworld_4_region, LocationName.belchas_burrow_region) + connect(world, world.player, names, LocationName.overworld_4_region, LocationName.ks_cache_region) + connect(world, world.player, names, LocationName.overworld_4_region, LocationName.hill_top_hoard_region) # Lake Orangatanga Connections @@ -786,7 +787,7 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(lake_orangatanga_levels)): - connect(world, player, names, LocationName.lake_orangatanga_region, lake_orangatanga_levels[i]) + connect(world, world.player, names, LocationName.lake_orangatanga_region, lake_orangatanga_levels[i]) # Kremwood Forest Connections kremwood_forest_levels = [ @@ -800,10 +801,10 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(kremwood_forest_levels) - 1): - connect(world, player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[i]) + connect(world, world.player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[i]) - connect(world, player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[-1], - lambda state: (state.can_reach(LocationName.riverside_race_flag, "Location", player))) + connect(world, world.player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[-1], + lambda state: (state.can_reach(LocationName.riverside_race_flag, "Location", world.player))) # Cotton-Top Cove Connections cotton_top_cove_levels = [ @@ -818,7 +819,7 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(cotton_top_cove_levels)): - connect(world, player, names, LocationName.cotton_top_cove_region, cotton_top_cove_levels[i]) + connect(world, world.player, names, LocationName.cotton_top_cove_region, cotton_top_cove_levels[i]) # Mekanos Connections mekanos_levels = [ @@ -831,14 +832,14 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(mekanos_levels)): - connect(world, player, names, LocationName.mekanos_region, mekanos_levels[i]) + connect(world, world.player, names, LocationName.mekanos_region, mekanos_levels[i]) - if False:#world.include_trade_sequence[player]: - connect(world, player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region, - lambda state: (state.has(ItemName.bowling_ball, player, 1))) + if False:#world.options.include_trade_sequence: + connect(world, world.player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region, + lambda state: (state.has(ItemName.bowling_ball, world.player, 1))) else: - connect(world, player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region, - lambda state: (state.can_reach(LocationName.bleaks_house, "Location", player))) + connect(world, world.player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region, + lambda state: (state.can_reach(LocationName.bleaks_house, "Location", world.player))) # K3 Connections k3_levels = [ @@ -853,7 +854,7 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(k3_levels)): - connect(world, player, names, LocationName.k3_region, k3_levels[i]) + connect(world, world.player, names, LocationName.k3_region, k3_levels[i]) # Razor Ridge Connections razor_ridge_levels = [ @@ -866,13 +867,13 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(razor_ridge_levels)): - connect(world, player, names, LocationName.razor_ridge_region, razor_ridge_levels[i]) + connect(world, world.player, names, LocationName.razor_ridge_region, razor_ridge_levels[i]) - if False:#world.include_trade_sequence[player]: - connect(world, player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region, - lambda state: (state.has(ItemName.wrench, player, 1))) + if False:#world.options.include_trade_sequence: + connect(world, world.player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region, + lambda state: (state.has(ItemName.wrench, world.player, 1))) else: - connect(world, player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region) + connect(world, world.player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region) # KAOS Kore Connections kaos_kore_levels = [ @@ -885,7 +886,7 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(kaos_kore_levels)): - connect(world, player, names, LocationName.kaos_kore_region, kaos_kore_levels[i]) + connect(world, world.player, names, LocationName.kaos_kore_region, kaos_kore_levels[i]) # Krematoa Connections krematoa_levels = [ @@ -897,22 +898,22 @@ def connect_regions(world, player, level_list): ] for i in range(0, len(krematoa_levels)): - connect(world, player, names, LocationName.krematoa_region, krematoa_levels[i], - lambda state, i=i: (state.has(ItemName.bonus_coin, player, world.krematoa_bonus_coin_cost[player].value * (i+1)))) + connect(world, world.player, names, LocationName.krematoa_region, krematoa_levels[i], + lambda state, i=i: (state.has(ItemName.bonus_coin, world.player, world.options.krematoa_bonus_coin_cost.value * (i+1)))) - if world.goal[player] == "knautilus": - connect(world, player, names, LocationName.kaos_kore_region, LocationName.knautilus_region) - connect(world, player, names, LocationName.krematoa_region, LocationName.kastle_kaos_region, - lambda state: (state.has(ItemName.krematoa_cog, player, 5))) + if world.options.goal == "knautilus": + connect(world, world.player, names, LocationName.kaos_kore_region, LocationName.knautilus_region) + connect(world, world.player, names, LocationName.krematoa_region, LocationName.kastle_kaos_region, + lambda state: (state.has(ItemName.krematoa_cog, world.player, 5))) else: - connect(world, player, names, LocationName.kaos_kore_region, LocationName.kastle_kaos_region) - connect(world, player, names, LocationName.krematoa_region, LocationName.knautilus_region, - lambda state: (state.has(ItemName.krematoa_cog, player, 5))) + connect(world, world.player, names, LocationName.kaos_kore_region, LocationName.kastle_kaos_region) + connect(world, world.player, names, LocationName.krematoa_region, LocationName.knautilus_region, + lambda state: (state.has(ItemName.krematoa_cog, world.player, 5))) -def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None): +def create_region(world: World, active_locations, name: str, locations=None): # Shamelessly stolen from the ROR2 definition - ret = Region(name, player, world) + ret = Region(name, world.player, world.multiworld) if locations: for locationName, locationData in locations.items(): loc_id = active_locations.get(locationName, 0) @@ -921,16 +922,16 @@ def create_region(world: MultiWorld, player: int, active_locations, name: str, l loc_bit = locationData[1] if (len(locationData) > 1) else 0 loc_invert = locationData[2] if (len(locationData) > 2) else False - location = DKC3Location(player, locationName, loc_id, ret, loc_byte, loc_bit, loc_invert) + location = DKC3Location(world.player, locationName, loc_id, ret, loc_byte, loc_bit, loc_invert) ret.locations.append(location) return ret -def connect(world: MultiWorld, player: int, used_names: typing.Dict[str, int], source: str, target: str, +def connect(world: World, player: int, used_names: typing.Dict[str, int], source: str, target: str, rule: typing.Optional[typing.Callable] = None): - source_region = world.get_region(source, player) - target_region = world.get_region(target, player) + source_region = world.multiworld.get_region(source, player) + target_region = world.multiworld.get_region(target, player) if target not in used_names: used_names[target] = 1 diff --git a/worlds/dkc3/Rom.py b/worlds/dkc3/Rom.py index 4255a0a382..efe8033d0f 100644 --- a/worlds/dkc3/Rom.py +++ b/worlds/dkc3/Rom.py @@ -1,5 +1,6 @@ import Utils from Utils import read_snes_rom +from worlds.AutoWorld import World from worlds.Files import APDeltaPatch from .Locations import lookup_id_to_name, all_locations from .Levels import level_list, level_dict @@ -475,11 +476,10 @@ class LocalRom(object): -def patch_rom(world, rom, player, active_level_list): - local_random = world.per_slot_randoms[player] +def patch_rom(world: World, rom: LocalRom, active_level_list): # Boomer Costs - bonus_coin_cost = world.krematoa_bonus_coin_cost[player] + bonus_coin_cost = world.options.krematoa_bonus_coin_cost inverted_bonus_coin_cost = 0x100 - bonus_coin_cost rom.write_byte(0x3498B9, inverted_bonus_coin_cost) rom.write_byte(0x3498BA, inverted_bonus_coin_cost) @@ -491,7 +491,7 @@ def patch_rom(world, rom, player, active_level_list): rom.write_byte(0x349862, bonus_coin_cost) # Gyrocopter Costs - dk_coin_cost = world.dk_coins_for_gyrocopter[player] + dk_coin_cost = world.options.dk_coins_for_gyrocopter rom.write_byte(0x3484A6, dk_coin_cost) rom.write_byte(0x3484D5, dk_coin_cost) rom.write_byte(0x3484D7, 0x90) @@ -508,8 +508,8 @@ def patch_rom(world, rom, player, active_level_list): rom.write_bytes(0x34ACD0, bytearray([0xEA, 0xEA])) # Banana Bird Costs - if world.goal[player] == "banana_bird_hunt": - banana_bird_cost = math.floor(world.number_of_banana_birds[player] * world.percentage_of_banana_birds[player] / 100.0) + if world.options.goal == "banana_bird_hunt": + banana_bird_cost = math.floor(world.options.number_of_banana_birds * world.options.percentage_of_banana_birds / 100.0) rom.write_byte(0x34AB85, banana_bird_cost) rom.write_byte(0x329FD8, banana_bird_cost) rom.write_byte(0x32A025, banana_bird_cost) @@ -528,65 +528,65 @@ def patch_rom(world, rom, player, active_level_list): # Palette Swap rom.write_byte(0x3B96A5, 0xD0) - if world.kong_palette_swap[player] == "default": + if world.options.kong_palette_swap == "default": rom.write_byte(0x3B96A9, 0x00) rom.write_byte(0x3B96A8, 0x00) - elif world.kong_palette_swap[player] == "purple": + elif world.options.kong_palette_swap == "purple": rom.write_byte(0x3B96A9, 0x00) rom.write_byte(0x3B96A8, 0x3C) - elif world.kong_palette_swap[player] == "spooky": + elif world.options.kong_palette_swap == "spooky": rom.write_byte(0x3B96A9, 0x00) rom.write_byte(0x3B96A8, 0xA0) - elif world.kong_palette_swap[player] == "dark": + elif world.options.kong_palette_swap == "dark": rom.write_byte(0x3B96A9, 0x05) rom.write_byte(0x3B96A8, 0xA0) - elif world.kong_palette_swap[player] == "chocolate": + elif world.options.kong_palette_swap == "chocolate": rom.write_byte(0x3B96A9, 0x1D) rom.write_byte(0x3B96A8, 0xA0) - elif world.kong_palette_swap[player] == "shadow": + elif world.options.kong_palette_swap == "shadow": rom.write_byte(0x3B96A9, 0x45) rom.write_byte(0x3B96A8, 0xA0) - elif world.kong_palette_swap[player] == "red_gold": + elif world.options.kong_palette_swap == "red_gold": rom.write_byte(0x3B96A9, 0x5D) rom.write_byte(0x3B96A8, 0xA0) - elif world.kong_palette_swap[player] == "gbc": + elif world.options.kong_palette_swap == "gbc": rom.write_byte(0x3B96A9, 0x20) rom.write_byte(0x3B96A8, 0x3C) - elif world.kong_palette_swap[player] == "halloween": + elif world.options.kong_palette_swap == "halloween": rom.write_byte(0x3B96A9, 0x70) rom.write_byte(0x3B96A8, 0x3C) - if world.music_shuffle[player]: + if world.options.music_shuffle: for address in music_rom_data: - rand_song = local_random.choice(level_music_ids) + rand_song = world.random.choice(level_music_ids) rom.write_byte(address, rand_song) # Starting Lives - rom.write_byte(0x9130, world.starting_life_count[player].value) - rom.write_byte(0x913B, world.starting_life_count[player].value) + rom.write_byte(0x9130, world.options.starting_life_count.value) + rom.write_byte(0x913B, world.options.starting_life_count.value) # Cheat options cheat_bytes = [0x00, 0x00] - if world.merry[player]: + if world.options.merry: cheat_bytes[0] |= 0x01 - if world.autosave[player]: + if world.options.autosave: cheat_bytes[0] |= 0x02 - if world.difficulty[player] == "tufst": + if world.options.difficulty == "tufst": cheat_bytes[0] |= 0x80 cheat_bytes[1] |= 0x80 - elif world.difficulty[player] == "hardr": + elif world.options.difficulty == "hardr": cheat_bytes[0] |= 0x00 cheat_bytes[1] |= 0x00 - elif world.difficulty[player] == "norml": + elif world.options.difficulty == "norml": cheat_bytes[1] |= 0x40 rom.write_bytes(0x8303, bytearray(cheat_bytes)) # Handle Level Shuffle Here - if world.level_shuffle[player]: + if world.options.level_shuffle: for i in range(len(active_level_list)): rom.write_byte(level_dict[level_list[i]].nameIDAddress, level_dict[active_level_list[i]].nameID) rom.write_byte(level_dict[level_list[i]].levelIDAddress, level_dict[active_level_list[i]].levelID) @@ -611,7 +611,7 @@ def patch_rom(world, rom, player, active_level_list): rom.write_byte(0x34C213, (0x32 + level_dict[active_level_list[25]].levelID)) rom.write_byte(0x34C21B, (0x32 + level_dict[active_level_list[26]].levelID)) - if world.goal[player] == "knautilus": + if world.options.goal == "knautilus": # Swap Kastle KAOS and Knautilus rom.write_byte(0x34D4E1, 0xC2) rom.write_byte(0x34D4E2, 0x24) @@ -621,7 +621,7 @@ def patch_rom(world, rom, player, active_level_list): rom.write_byte(0x32F339, 0x55) # Handle KONGsanity Here - if world.kongsanity[player]: + if world.options.kongsanity: # Arich's Hoard KONGsanity fix rom.write_bytes(0x34BA8C, bytearray([0xEA, 0xEA])) @@ -668,7 +668,7 @@ def patch_rom(world, rom, player, active_level_list): rom.write_bytes(0x32A5EE, bytearray([0x00, 0x03, 0x50, 0x4F, 0x52, 0x59, 0x47, 0x4F, 0x4E, 0xC5])) # "PORYGONE" from Utils import __version__ - rom.name = bytearray(f'D3{__version__.replace(".", "")[0:3]}_{player}_{world.seed:11}\0', 'utf8')[:21] + rom.name = bytearray(f'D3{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', 'utf8')[:21] rom.name.extend([0] * (21 - len(rom.name))) rom.write_bytes(0x7FC0, rom.name) diff --git a/worlds/dkc3/Rules.py b/worlds/dkc3/Rules.py index dc90eefd13..cc45e4ef3a 100644 --- a/worlds/dkc3/Rules.py +++ b/worlds/dkc3/Rules.py @@ -1,32 +1,31 @@ import math -from BaseClasses import MultiWorld from .Names import LocationName, ItemName -from worlds.AutoWorld import LogicMixin +from worlds.AutoWorld import LogicMixin, World from worlds.generic.Rules import add_rule, set_rule -def set_rules(world: MultiWorld, player: int): +def set_rules(world: World): - if False:#world.include_trade_sequence[player]: - add_rule(world.get_location(LocationName.barnacles_island, player), - lambda state: state.has(ItemName.shell, player)) + if False:#world.options.include_trade_sequence: + add_rule(world.multiworld.get_location(LocationName.barnacles_island, world.player), + lambda state: state.has(ItemName.shell, world.player)) - add_rule(world.get_location(LocationName.blues_beach_hut, player), - lambda state: state.has(ItemName.present, player)) + add_rule(world.multiworld.get_location(LocationName.blues_beach_hut, world.player), + lambda state: state.has(ItemName.present, world.player)) - add_rule(world.get_location(LocationName.brambles_bungalow, player), - lambda state: state.has(ItemName.flower, player)) + add_rule(world.multiworld.get_location(LocationName.brambles_bungalow, world.player), + lambda state: state.has(ItemName.flower, world.player)) - add_rule(world.get_location(LocationName.barters_swap_shop, player), - lambda state: state.has(ItemName.mirror, player)) + add_rule(world.multiworld.get_location(LocationName.barters_swap_shop, world.player), + lambda state: state.has(ItemName.mirror, world.player)) - if world.goal[player] != "knautilus": + if world.options.goal != "knautilus": required_banana_birds = math.floor( - world.number_of_banana_birds[player].value * (world.percentage_of_banana_birds[player].value / 100.0)) + world.options.number_of_banana_birds.value * (world.options.percentage_of_banana_birds.value / 100.0)) - add_rule(world.get_location(LocationName.banana_bird_mother, player), - lambda state: state.has(ItemName.banana_bird, player, required_banana_birds)) + add_rule(world.multiworld.get_location(LocationName.banana_bird_mother, world.player), + lambda state: state.has(ItemName.banana_bird, world.player, required_banana_birds)) - world.completion_condition[player] = lambda state: state.has(ItemName.victory, player) + world.multiworld.completion_condition[world.player] = lambda state: state.has(ItemName.victory, world.player) diff --git a/worlds/dkc3/__init__.py b/worlds/dkc3/__init__.py index 462e1416d9..dfb42bd04c 100644 --- a/worlds/dkc3/__init__.py +++ b/worlds/dkc3/__init__.py @@ -1,3 +1,4 @@ +import dataclasses import os import typing import math @@ -5,9 +6,10 @@ import threading import settings from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification +from Options import PerGameCommonOptions from .Items import DKC3Item, ItemData, item_table, inventory_table, junk_table from .Locations import DKC3Location, all_locations, setup_locations -from .Options import dkc3_options +from .Options import DKC3Options from .Regions import create_regions, connect_regions from .Levels import level_list from .Rules import set_rules @@ -50,8 +52,11 @@ class DKC3World(World): mystery of why Donkey Kong and Diddy disappeared while on vacation. """ game: str = "Donkey Kong Country 3" - option_definitions = dkc3_options settings: typing.ClassVar[DK3Settings] + + options_dataclass = DKC3Options + options: DKC3Options + topology_present = False data_version = 2 #hint_blacklist = {LocationName.rocket_rush_flag} @@ -74,24 +79,25 @@ class DKC3World(World): def _get_slot_data(self): return { - #"death_link": self.world.death_link[self.player].value, + #"death_link": self.options.death_link.value, "active_levels": self.active_level_list, } def fill_slot_data(self) -> dict: slot_data = self._get_slot_data() - for option_name in dkc3_options: - option = getattr(self.multiworld, option_name)[self.player] + for option_name in (attr.name for attr in dataclasses.fields(DKC3Options) + if attr not in dataclasses.fields(PerGameCommonOptions)): + option = getattr(self.options, option_name) slot_data[option_name] = option.value return slot_data def create_regions(self): - location_table = setup_locations(self.multiworld, self.player) - create_regions(self.multiworld, self.player, location_table) + location_table = setup_locations(self) + create_regions(self, location_table) # Not generate basic - self.topology_present = self.multiworld.level_shuffle[self.player].value + self.topology_present = self.options.level_shuffle.value itempool: typing.List[DKC3Item] = [] # Levels @@ -103,12 +109,12 @@ class DKC3World(World): number_of_cogs = 4 self.multiworld.get_location(LocationName.rocket_rush_flag, self.player).place_locked_item(self.create_item(ItemName.krematoa_cog)) number_of_bosses = 8 - if self.multiworld.goal[self.player] == "knautilus": + if self.options.goal == "knautilus": self.multiworld.get_location(LocationName.kastle_kaos, self.player).place_locked_item(self.create_item(ItemName.victory)) number_of_bosses = 7 else: self.multiworld.get_location(LocationName.banana_bird_mother, self.player).place_locked_item(self.create_item(ItemName.victory)) - number_of_banana_birds = self.multiworld.number_of_banana_birds[self.player] + number_of_banana_birds = self.options.number_of_banana_birds # Bosses total_required_locations += number_of_bosses @@ -116,15 +122,15 @@ class DKC3World(World): # Secret Caves total_required_locations += 13 - if self.multiworld.kongsanity[self.player]: + if self.options.kongsanity: total_required_locations += 39 ## Brothers Bear - if False:#self.world.include_trade_sequence[self.player]: + if False:#self.options.include_trade_sequence: total_required_locations += 10 - number_of_bonus_coins = (self.multiworld.krematoa_bonus_coin_cost[self.player] * 5) - number_of_bonus_coins += math.ceil((85 - number_of_bonus_coins) * self.multiworld.percentage_of_extra_bonus_coins[self.player] / 100) + number_of_bonus_coins = (self.options.krematoa_bonus_coin_cost * 5) + number_of_bonus_coins += math.ceil((85 - number_of_bonus_coins) * self.options.percentage_of_extra_bonus_coins / 100) itempool += [self.create_item(ItemName.bonus_coin) for _ in range(number_of_bonus_coins)] itempool += [self.create_item(ItemName.dk_coin) for _ in range(41)] @@ -142,20 +148,17 @@ class DKC3World(World): self.active_level_list = level_list.copy() - if self.multiworld.level_shuffle[self.player]: - self.multiworld.random.shuffle(self.active_level_list) + if self.options.level_shuffle: + self.random.shuffle(self.active_level_list) - connect_regions(self.multiworld, self.player, self.active_level_list) + connect_regions(self, self.active_level_list) self.multiworld.itempool += itempool def generate_output(self, output_directory: str): try: - world = self.multiworld - player = self.player - rom = LocalRom(get_base_rom_path()) - patch_rom(self.multiworld, rom, self.player, self.active_level_list) + patch_rom(self, rom, self.active_level_list) self.active_level_list.append(LocationName.rocket_rush_region) @@ -163,15 +166,15 @@ class DKC3World(World): rom.write_to_file(rompath) self.rom_name = rom.name - patch = DKC3DeltaPatch(os.path.splitext(rompath)[0]+DKC3DeltaPatch.patch_file_ending, player=player, - player_name=world.player_name[player], patched_path=rompath) + patch = DKC3DeltaPatch(os.path.splitext(rompath)[0]+DKC3DeltaPatch.patch_file_ending, player=self.player, + player_name=self.multiworld.player_name[self.player], patched_path=rompath) patch.write() except: raise finally: + self.rom_name_available_event.set() # make sure threading continues and errors are collected if os.path.exists(rompath): os.unlink(rompath) - self.rom_name_available_event.set() # make sure threading continues and errors are collected def modify_multidata(self, multidata: dict): import base64 @@ -183,6 +186,7 @@ class DKC3World(World): new_name = base64.b64encode(bytes(self.rom_name)).decode() multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]] + def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]): if self.topology_present: world_names = [ LocationName.lake_orangatanga_region, @@ -200,7 +204,8 @@ class DKC3World(World): level_region = self.multiworld.get_region(self.active_level_list[world_index * 5 + level_index], self.player) for location in level_region.locations: er_hint_data[location.address] = world_names[world_index] - multidata['er_hint_data'][self.player] = er_hint_data + + hint_data[self.player] = er_hint_data def create_item(self, name: str, force_non_progression=False) -> Item: data = item_table[name] @@ -220,4 +225,4 @@ class DKC3World(World): return self.multiworld.random.choice(list(junk_table.keys())) def set_rules(self): - set_rules(self.multiworld, self.player) + set_rules(self) From a659036e959694b419ffd6d0ab178eb3252e18f3 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Wed, 28 Feb 2024 19:55:55 +0100 Subject: [PATCH 038/166] Docs: mention that IDs for items and locations can overlap (#2854) * Docs: mention that IDs for items and locations can overlap * Update docs/world api.md Co-authored-by: Ixrec --------- Co-authored-by: Ixrec --- docs/world api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/world api.md b/docs/world api.md index 72a67bca9d..fd8e0988e5 100644 --- a/docs/world api.md +++ b/docs/world api.md @@ -170,6 +170,7 @@ could also be progress in a research tree, or even something more abstract like Each location has a `name` and an `address` (hereafter referred to as an `id`), is placed in a Region, has access rules, and has a classification. The name needs to be unique within each game and must not be numeric (must contain least 1 letter or symbol). The ID needs to be unique across all games, and is best kept in the same range as the item IDs. +Locations and items can share IDs, so typically a game's locations and items start at the same ID. World-specific IDs must be in the range 1 to 253-1; IDs ≤ 0 are global and reserved. From 7ebd5d3891b62650f41406619b135e128e533efa Mon Sep 17 00:00:00 2001 From: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> Date: Wed, 28 Feb 2024 19:26:52 -0500 Subject: [PATCH 039/166] DS3: Modified theme and warning color for accessibility (#2312) --- worlds/dark_souls_3/__init__.py | 1 + worlds/dark_souls_3/docs/setup_en.md | 2 +- worlds/dark_souls_3/docs/setup_fr.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/worlds/dark_souls_3/__init__.py b/worlds/dark_souls_3/__init__.py index 6efe4e4bc9..b4c231cdea 100644 --- a/worlds/dark_souls_3/__init__.py +++ b/worlds/dark_souls_3/__init__.py @@ -14,6 +14,7 @@ from .Options import RandomizeWeaponLevelOption, PoolTypeOption, EarlySmallLothr class DarkSouls3Web(WebWorld): bug_report_page = "https://github.com/Marechal-L/Dark-Souls-III-Archipelago-client/issues" + theme = "stone" setup_en = Tutorial( "Multiworld Setup Guide", "A guide to setting up the Archipelago Dark Souls III randomizer on your computer.", diff --git a/worlds/dark_souls_3/docs/setup_en.md b/worlds/dark_souls_3/docs/setup_en.md index 7a3ca4e9bd..72c665af95 100644 --- a/worlds/dark_souls_3/docs/setup_en.md +++ b/worlds/dark_souls_3/docs/setup_en.md @@ -11,7 +11,7 @@ ## General Concept - + **This mod can ban you permanently from the FromSoftware servers if used online.** The Dark Souls III AP Client is a dinput8.dll triggered when launching Dark Souls III. This .dll file will launch a command diff --git a/worlds/dark_souls_3/docs/setup_fr.md b/worlds/dark_souls_3/docs/setup_fr.md index 6ad86c4aff..769d331bb9 100644 --- a/worlds/dark_souls_3/docs/setup_fr.md +++ b/worlds/dark_souls_3/docs/setup_fr.md @@ -12,7 +12,7 @@ permettant de lire des informations de la partie et écrire des commandes pour i ## Procédures d'installation - + **Il y a des risques de bannissement permanent des serveurs FromSoftware si ce mod est utilisé en ligne.** Ce client a été testé sur la version Steam officielle du jeu (v1.15/1.35), peu importe les DLCs actuellement installés. From 3bc2c44ac3dd5f5c547b148c0cd92d9b0b37bbd4 Mon Sep 17 00:00:00 2001 From: Hisu Date: Wed, 28 Feb 2024 21:54:54 -0300 Subject: [PATCH 040/166] Docs: Add Spanish Guide for Pokemon Emerald (#2696) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Docs: Add Spanish Guide for Pokemon Emerald * Docs: Add Spanish Guide for Pokémon Emerald * Docs: Add Spanish Guide for Pokemon Emerald * Docs: Add Spanish Guide for Pokemon Emerald * Docs: Add Spanish Guide for Pokemon Emerald --- worlds/pokemon_emerald/__init__.py | 12 +++- worlds/pokemon_emerald/docs/setup_es.md | 74 +++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 worlds/pokemon_emerald/docs/setup_es.md diff --git a/worlds/pokemon_emerald/__init__.py b/worlds/pokemon_emerald/__init__.py index 95e549a32e..4d40dd1966 100644 --- a/worlds/pokemon_emerald/__init__.py +++ b/worlds/pokemon_emerald/__init__.py @@ -36,6 +36,7 @@ class PokemonEmeraldWebWorld(WebWorld): Webhost info for Pokemon Emerald """ theme = "ocean" + setup_en = Tutorial( "Multiworld Setup Guide", "A guide to playing Pokémon Emerald with Archipelago.", @@ -45,7 +46,16 @@ class PokemonEmeraldWebWorld(WebWorld): ["Zunawe"] ) - tutorials = [setup_en] + setup_es = Tutorial( + "Guía de configuración para Multiworld", + "Una guía para jugar Pokémon Emerald en Archipelago", + "Español", + "setup_es.md", + "setup/es", + ["nachocua"] + ) + + tutorials = [setup_en, setup_es] class PokemonEmeraldSettings(settings.Group): diff --git a/worlds/pokemon_emerald/docs/setup_es.md b/worlds/pokemon_emerald/docs/setup_es.md new file mode 100644 index 0000000000..65a74a9ddc --- /dev/null +++ b/worlds/pokemon_emerald/docs/setup_es.md @@ -0,0 +1,74 @@ +# Guía de Configuración para Pokémon Emerald + +## Software Requerido + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) +- Una ROM de Pokémon Emerald en Inglés. La comunidad de Archipelago no puede proveerla. +- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) 2.7 o posterior + +### Configuración de BizHawk + +Una vez que hayas instalado BizHawk, abre `EmuHawk.exe` y cambia las siguientes configuraciones: + +- Si estás usando BizHawk 2.7 o 2.8, ve a `Config > Customize`. En la pestaña Advanced, cambia el Lua Core de +`NLua+KopiLua` a `Lua+LuaInterface`, luego reinicia EmuHawk. (Si estás usando BizHawk 2.9, puedes saltar este paso.) +- En `Config > Customize`, activa la opción "Run in background" para prevenir desconexiones del cliente mientras +la aplicación activa no sea EmuHawk. +- Abre el archivo `.gba` en EmuHawk y luego ve a `Config > Controllers…` para configurar los controles. Si no puedes +hacer clic en `Controllers…`, debes abrir cualquier ROM `.gba` primeramente. +- Considera limpiar tus macros y atajos en `Config > Hotkeys…` si no quieres usarlas de manera intencional. Para +limpiarlas, selecciona el atajo y presiona la tecla Esc. + +## Software Opcional + +- [Pokémon Emerald AP Tracker](https://github.com/AliceMousie/emerald-ap-tracker/releases/latest), para usar con +[PopTracker](https://github.com/black-sliver/PopTracker/releases) + +## Generando y Parcheando el Juego + +1. Crea tu archivo de configuración (YAML). Puedes hacerlo en +[Página de Opciones de Pokémon Emerald](../../../games/Pokemon%20Emerald/player-options). +2. Sigue las instrucciones generales de Archipelago para [Generar un juego] +(../../Archipelago/setup/en#generating-a-game). Esto generará un archivo de salida (output file) para ti. Tu archivo +de parche tendrá la extensión de archivo`.apemerald`. +3. Abre `ArchipelagoLauncher.exe` +4. Selecciona "Open Patch" en el lado derecho y elige tu archivo de parcheo. +5. Si esta es la primera vez que vas a parchear, se te pedirá que selecciones la ROM sin parchear. +6. Un archivo parcheado con extensión `.gba` será creado en el mismo lugar que el archivo de parcheo. +7. La primera vez que abras un archivo parcheado con el BizHawk Client, se te preguntará donde está localizado +`EmuHawk.exe` en tu instalación de BizHawk. + +Si estás jugando una seed Single-Player y no te interesa el auto-tracking o las pistas, puedes parar aquí, cierra el +cliente, y carga la ROM ya parcheada en cualquier emulador. Pero para partidas multi-worlds y para otras +implementaciones de Archipelago, continúa usando BizHawk como tu emulador + +## Conectando con el Servidor + +Por defecto, al abrir un archivo parcheado, se harán de manera automática 1-5 pasos. Aun así, ten en cuenta lo +siguiente en caso de que debas cerrar y volver a abrir la ventana en mitad de la partida por algún motivo. + +1. Pokémon Emerald usa el Archipelago BizHawk Client. Si el cliente no se encuentra abierto al abrir la rom +parcheada, puedes volver a abrirlo desde el Archipelago Launcher. +2. Asegúrate que EmuHawk está corriendo la ROM parcheada. +3. En EmuHawk, ve a `Tools > Lua Console`. Debes tener esta ventana abierta mientras juegas. +4. En la ventana de Lua Console, ve a `Script > Open Script…`. +5. Ve a la carpeta donde está instalado Archipelago y abre `data/lua/connector_bizhawk_generic.lua`. +6. El emulador y el cliente eventualmente se conectarán uno con el otro. La ventana de BizHawk Client indicará que te +has conectado y reconocerá Pokémon Emerald. +7. Para conectar el cliente con el servidor, ingresa la dirección y el puerto de la sala (ej. `archipelago.gg:38281`) +en el campo de texto que se encuentra en la parte superior del cliente y haz click en Connect. + +Ahora deberías poder enviar y recibir ítems. Debes seguir estos pasos cada vez que quieras reconectarte. Es seguro +jugar de manera offline; se sincronizará todo cuando te vuelvas a conectar. + +## Tracking Automático + +Pokémon Emerald tiene un Map Tracker completamente funcional que soporta auto-tracking. + +1. Descarga [Pokémon Emerald AP Tracker](https://github.com/AliceMousie/emerald-ap-tracker/releases/latest) y +[PopTracker](https://github.com/black-sliver/PopTracker/releases). +2. Coloca la carpeta del Tracker en la carpeta packs/ dentro de la carpeta de instalación del PopTracker. +3. Abre PopTracker, y carga el Pack de Pokémon Emerald Map Tracker. +4. Para utilizar el auto-tracking, haz click en el símbolo "AP" que se encuentra en la parte superior. +5. Entra la dirección del Servidor de Archipelago (la misma a la que te conectaste para jugar), nombre del jugador, y +contraseña (deja vacío este campo en caso de no utilizar contraseña). From 5e06a75bf2b24174488ab99f7cd743aa1db94a2b Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Wed, 28 Feb 2024 17:22:42 -0800 Subject: [PATCH 041/166] Core: typing: return type of `fill_slot_data` to `Mapping` (#2876) * Core: typing: return type of `fill_slot_data` to `Mapping` type checker be like: "Wait a minute! If you give this mutable dict to those sussy sketchbags, they might mutate it and invalidate your more specific typing!" Note that this doesn't mean the return value needs to be immutable. It just means the caller won't mutate it (which matches current `Main.py` implementation). I've seen some talk of introducing ownership to the type system. https://discuss.python.org/t/we-may-need-better-specification-for-existing-and-future-refinement-types-in-the-type-system/43955/5 Then maybe I could say: "Do whatever you want with it, because I'm giving up ownership." But that doesn't exist in the type system currently. * in docs too * docs talk less about type and more about json * keep `dict` to be safe with .net client and json --- docs/world api.md | 5 +++-- worlds/AutoWorld.py | 14 ++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/world api.md b/docs/world api.md index fd8e0988e5..f82ef40a98 100644 --- a/docs/world api.md +++ b/docs/world api.md @@ -738,8 +738,9 @@ def generate_output(self, output_directory: str) -> None: If the game client needs to know information about the generated seed, a preferred method of transferring the data is through the slot data. This is filled with the `fill_slot_data` method of your world by returning -a `Dict[str, Any]`, but, to not waste resources, should be limited to data that is absolutely necessary. Slot data is -sent to your client once it has successfully [connected](network%20protocol.md#connected). +a `dict` with `str` keys that can be serialized with json. +But, to not waste resources, it should be limited to data that is absolutely necessary. Slot data is sent to your client +once it has successfully [connected](network%20protocol.md#connected). If you need to know information about locations in your world, instead of propagating the slot data, it is preferable to use [LocationScouts](network%20protocol.md#locationscouts), since that data already exists on the server. The most common usage of slot data is sending option results that the client needs to be aware of. diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index b282c7deb8..dd0f46f6a6 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -7,8 +7,8 @@ import re import sys import time from dataclasses import make_dataclass -from typing import Any, Callable, ClassVar, Dict, Set, Tuple, FrozenSet, List, Optional, TYPE_CHECKING, TextIO, Type, \ - Union +from typing import (Any, Callable, ClassVar, Dict, FrozenSet, List, Mapping, + Optional, Set, TextIO, Tuple, TYPE_CHECKING, Type, Union) from Options import PerGameCommonOptions from BaseClasses import CollectionState @@ -365,13 +365,19 @@ class World(metaclass=AutoWorldRegister): If you need any last-second randomization, use self.random instead.""" pass - def fill_slot_data(self) -> Dict[str, Any]: # json of WebHostLib.models.Slot - """Fill in the `slot_data` field in the `Connected` network package. + def fill_slot_data(self) -> Mapping[str, Any]: # json of WebHostLib.models.Slot + """What is returned from this function will be in the `slot_data` field + in the `Connected` network package. + It should be a `dict` with `str` keys, and should be serializable with json. + This is a way the generator can give custom data to the client. The client will receive this as JSON in the `Connected` response. The generation does not wait for `generate_output` to complete before calling this. `threading.Event` can be used if you need to wait for something from `generate_output`.""" + # The reason for the `Mapping` type annotation, rather than `dict` + # is so that type checkers won't worry about the mutability of `dict`, + # so you can have more specific typing in your world implementation. return {} def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): From 184dedfa699451f885d433b7670327e94e12cb07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dana=C3=ABl=20V?= <104455676+ReverM@users.noreply.github.com> Date: Wed, 28 Feb 2024 20:30:28 -0500 Subject: [PATCH 042/166] Core: Default YAML header updates (#2723) * Cleaning up (#4) Cleanup * Adressed change about spaces no longer being replaced to underscores. Added a "that" to remove an ambiguity * Update data/options.yaml Combined the two sentences into one, per suggestion Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> --------- Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> --- data/options.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/options.yaml b/data/options.yaml index b9bacaa0d1..30bd328f99 100644 --- a/data/options.yaml +++ b/data/options.yaml @@ -17,10 +17,10 @@ # A. This is a .yaml file. You are allowed to use most characters. # To test if your yaml is valid or not, you can use this website: # http://www.yamllint.com/ -# You can also verify your Archipelago settings are valid at this site: +# You can also verify that your Archipelago options are valid at this site: # https://archipelago.gg/check -# Your name in-game. Spaces will be replaced with underscores and there is a 16-character limit. +# Your name in-game, limited to 16 characters. # {player} will be replaced with the player's slot number. # {PLAYER} will be replaced with the player's slot number, if that slot number is greater than 1. # {number} will be replaced with the counter value of the name. From e60a2636cd30e02aa281a6e9c7f6346a51bd8883 Mon Sep 17 00:00:00 2001 From: Jarno Date: Thu, 29 Feb 2024 02:40:59 +0100 Subject: [PATCH 043/166] Docs: Fixed broken ClientStatus hyperlink in network protocol.md (#2844) --- docs/network protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/network protocol.md b/docs/network protocol.md index c6d6cf6887..9f2c07883b 100644 --- a/docs/network protocol.md +++ b/docs/network protocol.md @@ -345,7 +345,7 @@ Sent to the server to update on the sender's status. Examples include readiness #### Arguments | Name | Type | Notes | | ---- | ---- | ----- | -| status | ClientStatus\[int\] | One of [Client States](#Client-States). Send as int. Follow the link for more information. | +| status | ClientStatus\[int\] | One of [Client States](#ClientStatus). Send as int. Follow the link for more information. | ### Say Basic chat command which sends text to the server to be distributed to other clients. From 7a85ee7ed106a551a15b2c6f6cc888e9bfd635e0 Mon Sep 17 00:00:00 2001 From: Trevor L <80716066+TRPG0@users.noreply.github.com> Date: Wed, 28 Feb 2024 18:56:20 -0700 Subject: [PATCH 044/166] Blasphemous: Remove poptracker pack from setup guide (#2759) --- worlds/blasphemous/docs/setup_en.md | 1 - 1 file changed, 1 deletion(-) diff --git a/worlds/blasphemous/docs/setup_en.md b/worlds/blasphemous/docs/setup_en.md index cc238a492e..070d1ca496 100644 --- a/worlds/blasphemous/docs/setup_en.md +++ b/worlds/blasphemous/docs/setup_en.md @@ -15,7 +15,6 @@ Optional: - Quick Prie Dieu warp mod: [GitHub](https://github.com/BadMagic100/Blasphemous-PrieWarp) - Boots of Pleading mod: [GitHub](https://github.com/BrandenEK/Blasphemous-Boots-of-Pleading) - Double Jump mod: [GitHub](https://github.com/BrandenEK/Blasphemous-Double-Jump) -- PopTracker pack: [GitHub](https://github.com/sassyvania/Blasphemous-Randomizer-Maptracker) ## Mod Installer (Recommended) From 564ec8c32e96f33c6acfd27cd8c64025973fdbb0 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu, 29 Feb 2024 07:40:08 +0100 Subject: [PATCH 045/166] The Witness: Allow specifying custom trap weights (#2835) * Trap weights * Slightly change the way the option works * Wording one more time * Non optional to bring in line with Ixrec's implementation * Be clear that it's not an absolute amount, but a weight * E x c l a m a t i o n p o i n t * Update worlds/witness/items.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * Wait I can just do this now lol --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/witness/__init__.py | 4 ---- worlds/witness/items.py | 12 +++++++++--- worlds/witness/locations.py | 3 +++ worlds/witness/options.py | 26 +++++++++++++++++++++++++- worlds/witness/static_logic.py | 3 +++ 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index c38898b33d..e985dde353 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -44,10 +44,6 @@ class WitnessWorld(World): """ game = "The Witness" topology_present = False - - StaticWitnessLogic() - StaticWitnessLocations() - StaticWitnessItems() web = WitnessWebWorld() options_dataclass = TheWitnessOptions diff --git a/worlds/witness/items.py b/worlds/witness/items.py index 41bc3c1bb8..6802fd2a21 100644 --- a/worlds/witness/items.py +++ b/worlds/witness/items.py @@ -176,9 +176,14 @@ class WitnessPlayerItems: # Read trap configuration data. trap_weight = self._world.options.trap_percentage / 100 - filler_weight = 1 - trap_weight + trap_items = self._world.options.trap_weights.value + + if not sum(trap_items.values()): + trap_weight = 0 # Add filler items to the list. + filler_weight = 1 - trap_weight + filler_items: Dict[str, float] filler_items = {name: data.definition.weight if isinstance(data.definition, WeightedItemDefinition) else 1 for (name, data) in self.item_data.items() if data.definition.category is ItemCategory.FILLER} @@ -187,8 +192,6 @@ class WitnessPlayerItems: # Add trap items. if trap_weight > 0: - trap_items = {name: data.definition.weight if isinstance(data.definition, WeightedItemDefinition) else 1 - for (name, data) in self.item_data.items() if data.definition.category is ItemCategory.TRAP} filler_items.update({name: base_weight * trap_weight / sum(trap_items.values()) for name, base_weight in trap_items.items() if base_weight > 0}) @@ -267,3 +270,6 @@ class WitnessPlayerItems: output[item.ap_code] = [StaticWitnessItems.item_data[child_item].ap_code for child_item in item.definition.child_item_names] return output + + +StaticWitnessItems() diff --git a/worlds/witness/locations.py b/worlds/witness/locations.py index d38cf90258..cd6d71f469 100644 --- a/worlds/witness/locations.py +++ b/worlds/witness/locations.py @@ -569,3 +569,6 @@ class WitnessPlayerLocations: entity_hex = StaticWitnessLogic.ENTITIES_BY_NAME[entity_name]["entity_hex"] self.CHECK_LOCATION_TABLE[entity_hex] = entity_name self.CHECK_PANELHEX_TO_ID[entity_hex] = StaticWitnessLocations.get_id(entity_hex) + + +StaticWitnessLocations() diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 68a4ac7fc2..18aa76d95a 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -1,5 +1,10 @@ from dataclasses import dataclass -from Options import Toggle, DefaultOnToggle, Range, Choice, PerGameCommonOptions + +from schema import Schema, And, Optional + +from Options import Toggle, DefaultOnToggle, Range, Choice, PerGameCommonOptions, OptionDict + +from worlds.witness.static_logic import WeightedItemDefinition, ItemCategory, StaticWitnessLogic class DisableNonRandomizedPuzzles(Toggle): @@ -172,6 +177,24 @@ class TrapPercentage(Range): default = 20 +class TrapWeights(OptionDict): + """Specify the weights determining how many copies of each trap item will be in your itempool. + If you don't want a specific type of trap, you can set the weight for it to 0 (Do not delete the entry outright!). + If you set all trap weights to 0, you will get no traps, bypassing the "Trap Percentage" option.""" + + display_name = "Trap Weights" + schema = Schema({ + trap_name: And(int, lambda n: n >= 0) + for trap_name, item_definition in StaticWitnessLogic.all_items.items() + if isinstance(item_definition, WeightedItemDefinition) and item_definition.category is ItemCategory.TRAP + }) + default = { + trap_name: item_definition.weight + for trap_name, item_definition in StaticWitnessLogic.all_items.items() + if isinstance(item_definition, WeightedItemDefinition) and item_definition.category is ItemCategory.TRAP + } + + class PuzzleSkipAmount(Range): """Adds this number of Puzzle Skips into the pool, if there is room. Puzzle Skips let you skip one panel. Works on most panels in the game - The only big exception is The Challenge.""" @@ -237,6 +260,7 @@ class TheWitnessOptions(PerGameCommonOptions): early_caves: EarlyCaves elevators_come_to_you: ElevatorsComeToYou trap_percentage: TrapPercentage + trap_weights: TrapWeights puzzle_skip_amount: PuzzleSkipAmount hint_amount: HintAmount area_hint_percentage: AreaHintPercentage diff --git a/worlds/witness/static_logic.py b/worlds/witness/static_logic.py index 5a3e8b1b58..3efab4915e 100644 --- a/worlds/witness/static_logic.py +++ b/worlds/witness/static_logic.py @@ -295,3 +295,6 @@ class StaticWitnessLogic: self.EP_TO_OBELISK_SIDE.update(self.sigma_normal.EP_TO_OBELISK_SIDE) self.ENTITY_ID_TO_NAME.update(self.sigma_normal.ENTITY_ID_TO_NAME) + + +StaticWitnessLogic() From 983da12a03aba795ced47cd547470132085c3940 Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Thu, 29 Feb 2024 12:42:13 -0700 Subject: [PATCH 046/166] Pokemon Emerald: Add exhaustive list of ROM changes (#2801) --- worlds/pokemon_emerald/docs/rom changes.md | 75 ++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 worlds/pokemon_emerald/docs/rom changes.md diff --git a/worlds/pokemon_emerald/docs/rom changes.md b/worlds/pokemon_emerald/docs/rom changes.md new file mode 100644 index 0000000000..9b189d08e7 --- /dev/null +++ b/worlds/pokemon_emerald/docs/rom changes.md @@ -0,0 +1,75 @@ +## QoL + +- The catch tutorial and cutscenes during your first visit to Petalburg are skipped +- The match call tutorial after you leave Devon Corp is skipped +- Cycling and running is allowed in every map (some exceptions like Fortree and Pacifidlog) +- When you run out of Repel steps, you'll be prompted to use another one if you have more in your bag +- Text is always rendered in its entirety on the first frame (instant text) +- With an option set, text will advance if A is held +- The message explaining that the trainer is about to send out a new pokemon is shortened to fit on two lines so that +you can still read the species when deciding whether to change pokemon +- The Pokemon Center Nurse dialogue is entirely removed except for the final text box +- When receiving TMs and HMs, the move that it teaches is consistently displayed in the "received item" message (by +default, certain ways of receiving items would only display the TM/HM number) +- The Pokedex starts in national mode +- The Oldale Pokemart sells Poke Balls at the start of the game +- Pauses during battles (e.g. the ~1 second pause at the start of a turn before an opponent uses a potion) are shorter +by 62.5% +- The sliding animation for trainers and wild pokemon at the start of a battle runs at double speed. +- Bag space was greatly expanded (there is room for one stack of every unique item in every pocket, plus a little bit +extra for some pockets) + - Save data format was changed as a result of this. Shrank some unused space and removed some multiplayer phrases from + the save data. + - Pretty much any code that checks for bag space is ignored or bypassed (this sounds dangerous, but with expanded bag + space you should pretty much never have a full bag unless you're trying to fill it up, and skipping those checks + greatly simplifies detecting when items are picked up) +- Pokemon are never disobedient +- When moving in the overworld, set the input priority based on the most recently pressed direction rather than by some +predetermined priority +- Shoal cave changes state every time you reload the map and is no longer tied to the RTC +- Increased safari zone steps from 500 to 50000 +- Trainers will not approach the player if the blind trainers option is set +- Changed trade evolutions to be possible without trading: + - Politoed: Use King's Rock in bag menu + - Alakazam: Level 37 + - Machamp: Level 37 + - Golem: Level 37 + - Slowking: Use King's Rock in bag menu + - Gengar: Level 37 + - Steelix: Use Metal Coat in bag menu + - Kingdra: Use Dragon Scale in bag menu + - Scizor: Use Metal Coat in bag menu + - Porygon2: Use Up-Grade in bag menu + - Milotic: Level 30 + - Huntail: Use Deep Sea Tooth in bag menu + - Gorebyss: Use Deep Sea Scale in bag menu + +## Game State Changes/Softlock Prevention + +- Mr. Briney never disappears or stops letting you use his ferry +- Prevent the player from flying or surfing until they have received the Pokedex +- The S.S. Tidal will be available at all times if you have the option enabled +- Some NPCs or tiles are removed on the creation of a new save file based on player options +- Ensured that every species has some damaging move by level 5 +- Route 115 may have strength boulders between the beach and cave entrance based on player options +- The Petalburg Gym is set up based on your player options rather than after the first 4 gyms +- The E4 guards will actually check all your badges (or gyms beaten based on your options) instead of just the Feather +Badge +- Steven cuts the conversation short in Granite Cave if you don't have the Letter +- Dock checks that you have the Devon Goods before asking you to deliver them (and thus opening the museum) +- Rydel gives you both bikes at the same time +- The man in Pacifidlog who gives you Frustration and Return will give you both at the same time, does not check +friendship first, and no longer has any behavior related to the RTC +- The woman who gives you the Soothe Bell in Slateport does not check friendship +- When trading the Scanner with Captain Stern, you will receive both the Deep Sea Tooth and Deep Sea Scale + +## Misc + +- You can no longer try to switch bikes in the bike shop +- The Seashore House only rewards you with 1 Soda Pop instead of 6 +- Many small changes that make it possible to swap single battles to double battles + - Includes some safeguards against two trainers seeing you and initiating a battle while one or both of them are + "single trainer double battles" +- Game now properly waits on vblank instead of spinning in a while loop +- Misc small changes to text for consistency +- Many bugfixes to the vanilla game code From f17ff156692b10678f9ce3e14c19f0f53510a271 Mon Sep 17 00:00:00 2001 From: zig-for Date: Sat, 2 Mar 2024 21:28:26 -0800 Subject: [PATCH 047/166] LADX: fix modifying item pool in pre_fill (#2060) --- worlds/ladx/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/worlds/ladx/__init__.py b/worlds/ladx/__init__.py index 181cc05322..6742dffd30 100644 --- a/worlds/ladx/__init__.py +++ b/worlds/ladx/__init__.py @@ -276,6 +276,11 @@ class LinksAwakeningWorld(World): # Properly fill locations within dungeon location.dungeon = r.dungeon_index + # For now, special case first item + FORCE_START_ITEM = True + if FORCE_START_ITEM: + self.force_start_item() + def force_start_item(self): start_loc = self.multiworld.get_location("Tarin's Gift (Mabe Village)", self.player) if not start_loc.item: @@ -287,17 +292,12 @@ class LinksAwakeningWorld(World): start_item = self.multiworld.itempool.pop(index) start_loc.place_locked_item(start_item) - def get_pre_fill_items(self): return self.pre_fill_items def pre_fill(self) -> None: allowed_locations_by_item = {} - # For now, special case first item - FORCE_START_ITEM = True - if FORCE_START_ITEM: - self.force_start_item() # Set up filter rules From ad3ffde7851d4410526fc08132dbfa5c1bb665ec Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Sun, 3 Mar 2024 00:31:22 -0500 Subject: [PATCH 048/166] FFMQ: Remove debug print statements (#2882) --- worlds/ffmq/Regions.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/worlds/ffmq/Regions.py b/worlds/ffmq/Regions.py index 61f70864c0..8b83c88e72 100644 --- a/worlds/ffmq/Regions.py +++ b/worlds/ffmq/Regions.py @@ -220,15 +220,12 @@ def stage_set_rules(multiworld): for player in no_enemies_players: for location in vendor_locations: if multiworld.accessibility[player] == "locations": - print("exclude") multiworld.get_location(location, player).progress_type = LocationProgressType.EXCLUDED else: - print("unreachable") multiworld.get_location(location, player).access_rule = lambda state: False else: # There are not enough junk items to fill non-minimal players' vendors. Just set an item rule not allowing - # advancement items so that useful items can be placed. - print("no advancement") + # advancement items so that useful items can be placed for player in no_enemies_players: for location in vendor_locations: multiworld.get_location(location, player).item_rule = lambda item: not item.advancement From 01cf60f48df5b5d298ff7e9781434cff16ae1a24 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Sat, 2 Mar 2024 23:32:58 -0600 Subject: [PATCH 049/166] Launcher: make launcher scrollable (#2881) --- Launcher.py | 32 ++++++++++++++++---------------- kvui.py | 13 +++++++++++++ 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/Launcher.py b/Launcher.py index 9e184bf108..8909579583 100644 --- a/Launcher.py +++ b/Launcher.py @@ -161,7 +161,7 @@ def launch(exe, in_terminal=False): def run_gui(): - from kvui import App, ContainerLayout, GridLayout, Button, Label + from kvui import App, ContainerLayout, GridLayout, Button, Label, ScrollBox, Widget from kivy.uix.image import AsyncImage from kivy.uix.relativelayout import RelativeLayout @@ -185,11 +185,16 @@ def run_gui(): self.container = ContainerLayout() self.grid = GridLayout(cols=2) self.container.add_widget(self.grid) - self.grid.add_widget(Label(text="General")) - self.grid.add_widget(Label(text="Clients")) - button_layout = self.grid # make buttons fill the window + self.grid.add_widget(Label(text="General", size_hint_y=None, height=40)) + self.grid.add_widget(Label(text="Clients", size_hint_y=None, height=40)) + tool_layout = ScrollBox() + tool_layout.layout.orientation = "vertical" + self.grid.add_widget(tool_layout) + client_layout = ScrollBox() + client_layout.layout.orientation = "vertical" + self.grid.add_widget(client_layout) - def build_button(component: Component): + def build_button(component: Component) -> Widget: """ Builds a button widget for a given component. @@ -200,31 +205,26 @@ def run_gui(): None. The button is added to the parent grid layout. """ - button = Button(text=component.display_name) + button = Button(text=component.display_name, size_hint_y=None, height=40) button.component = component button.bind(on_release=self.component_action) if component.icon != "icon": image = AsyncImage(source=icon_paths[component.icon], size=(38, 38), size_hint=(None, 1), pos=(5, 0)) - box_layout = RelativeLayout() + box_layout = RelativeLayout(size_hint_y=None, height=40) box_layout.add_widget(button) box_layout.add_widget(image) - button_layout.add_widget(box_layout) - else: - button_layout.add_widget(button) + return box_layout + return button for (tool, client) in itertools.zip_longest(itertools.chain( self._tools.items(), self._miscs.items(), self._adjusters.items()), self._clients.items()): # column 1 if tool: - build_button(tool[1]) - else: - button_layout.add_widget(Label()) + tool_layout.layout.add_widget(build_button(tool[1])) # column 2 if client: - build_button(client[1]) - else: - button_layout.add_widget(Label()) + client_layout.layout.add_widget(build_button(client[1])) return self.container diff --git a/kvui.py b/kvui.py index 22e179d5be..5e1b0fc030 100644 --- a/kvui.py +++ b/kvui.py @@ -38,11 +38,13 @@ from kivy.clock import Clock from kivy.factory import Factory from kivy.properties import BooleanProperty, ObjectProperty from kivy.metrics import dp +from kivy.effects.scroll import ScrollEffect from kivy.uix.widget import Widget from kivy.uix.button import Button from kivy.uix.gridlayout import GridLayout from kivy.uix.layout import Layout from kivy.uix.textinput import TextInput +from kivy.uix.scrollview import ScrollView from kivy.uix.recycleview import RecycleView from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem from kivy.uix.boxlayout import BoxLayout @@ -118,6 +120,17 @@ class ServerToolTip(ToolTip): pass +class ScrollBox(ScrollView): + layout: BoxLayout + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.layout = BoxLayout(size_hint_y=None) + self.layout.bind(minimum_height=self.layout.setter("height")) + self.add_widget(self.layout) + self.effect_cls = ScrollEffect + + class HovererableLabel(HoverBehavior, Label): pass From b65a3b7464a503a614dcf0726fe0863540bc18f1 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 3 Mar 2024 06:33:48 +0100 Subject: [PATCH 050/166] Subnautica: cleanup (#2828) --- worlds/subnautica/__init__.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/worlds/subnautica/__init__.py b/worlds/subnautica/__init__.py index de4f4e33dc..e9341ec3b9 100644 --- a/worlds/subnautica/__init__.py +++ b/worlds/subnautica/__init__.py @@ -115,7 +115,7 @@ class SubnauticaWorld(World): for i in range(item.count): subnautica_item = self.create_item(item.name) if item.name == "Neptune Launch Platform": - self.multiworld.get_location("Aurora - Captain Data Terminal", self.player).place_locked_item( + self.get_location("Aurora - Captain Data Terminal").place_locked_item( subnautica_item) else: pool.append(subnautica_item) @@ -128,7 +128,7 @@ class SubnauticaWorld(World): pool.append(self.create_item(name)) extras -= group_amount - for item_name in self.multiworld.random.sample( + for item_name in self.random.sample( # list of high-count important fragments as priority filler [ "Cyclops Engine Fragment", @@ -175,18 +175,6 @@ class SubnauticaWorld(World): item_table[item_id].classification, item_id, player=self.player) - def create_region(self, name: str, region_locations=None, exits=None): - ret = Region(name, self.player, self.multiworld) - if region_locations: - for location in region_locations: - loc_id = self.location_name_to_id.get(location, None) - location = SubnauticaLocation(self.player, location, loc_id, ret) - ret.locations.append(location) - if exits: - for region_exit in exits: - ret.exits.append(Entrance(self.player, region_exit, ret)) - return ret - def get_filler_item_name(self) -> str: return item_table[self.multiworld.random.choice(items_by_type[ItemType.resource])].name From 2c5b2e07590b926f55f6c817a3971b2b8c86154b Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 3 Mar 2024 06:34:48 +0100 Subject: [PATCH 051/166] MultiServer: make !hint without further arguments only reply to the instigating player (#2339) --- MultiServer.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/MultiServer.py b/MultiServer.py index 15ed22d715..62dab3298e 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -656,7 +656,8 @@ class Context: else: return self.player_names[team, slot] - def notify_hints(self, team: int, hints: typing.List[NetUtils.Hint], only_new: bool = False): + def notify_hints(self, team: int, hints: typing.List[NetUtils.Hint], only_new: bool = False, + recipients: typing.Sequence[int] = None): """Send and remember hints.""" if only_new: hints = [hint for hint in hints if hint not in self.hints[team, hint.finding_player]] @@ -685,12 +686,13 @@ class Context: for slot in new_hint_events: self.on_new_hint(team, slot) for slot, hint_data in concerns.items(): - clients = self.clients[team].get(slot) - if not clients: - continue - client_hints = [datum[1] for datum in sorted(hint_data, key=lambda x: x[0].finding_player == slot)] - for client in clients: - async_start(self.send_msgs(client, client_hints)) + if recipients is None or slot in recipients: + clients = self.clients[team].get(slot) + if not clients: + continue + client_hints = [datum[1] for datum in sorted(hint_data, key=lambda x: x[0].finding_player == slot)] + for client in clients: + async_start(self.send_msgs(client, client_hints)) # "events" @@ -1429,9 +1431,13 @@ class ClientMessageProcessor(CommonCommandProcessor): hints = {hint.re_check(self.ctx, self.client.team) for hint in self.ctx.hints[self.client.team, self.client.slot]} self.ctx.hints[self.client.team, self.client.slot] = hints - self.ctx.notify_hints(self.client.team, list(hints)) + self.ctx.notify_hints(self.client.team, list(hints), recipients=(self.client.slot,)) self.output(f"A hint costs {self.ctx.get_hint_cost(self.client.slot)} points. " f"You have {points_available} points.") + if hints and Utils.version_tuple < (0, 5, 0): + self.output("It was recently changed, so that the above hints are only shown to you. " + "If you meant to alert another player of an above hint, " + "please let them know of the content or to run !hint themselves.") return True elif input_text.isnumeric(): From b8bf67a1664c55eb690e1128dd69021f324b5033 Mon Sep 17 00:00:00 2001 From: wildham <64616385+wildham0@users.noreply.github.com> Date: Sun, 3 Mar 2024 00:43:45 -0500 Subject: [PATCH 052/166] FF1: Update Location Names (#2838) --- worlds/ff1/data/locations.json | 490 ++++++++++++++++----------------- 1 file changed, 245 insertions(+), 245 deletions(-) diff --git a/worlds/ff1/data/locations.json b/worlds/ff1/data/locations.json index 9771d51de0..2f465a7897 100644 --- a/worlds/ff1/data/locations.json +++ b/worlds/ff1/data/locations.json @@ -1,253 +1,253 @@ { - "Coneria1": 257, - "Coneria2": 258, - "ConeriaMajor": 259, - "Coneria4": 260, - "Coneria5": 261, - "Coneria6": 262, - "MatoyasCave1": 299, - "MatoyasCave3": 301, - "MatoyasCave2": 300, - "NorthwestCastle1": 273, - "NorthwestCastle3": 275, - "NorthwestCastle2": 274, - "ToFTopLeft1": 263, - "ToFBottomLeft": 265, - "ToFTopLeft2": 264, - "ToFRevisited6": 509, - "ToFRevisited4": 507, - "ToFRMasmune": 504, - "ToFRevisited5": 508, - "ToFRevisited3": 506, - "ToFRevisited2": 505, - "ToFRevisited7": 510, - "ToFTopRight1": 267, - "ToFTopRight2": 268, - "ToFBottomRight": 266, - "IceCave15": 377, - "IceCave16": 378, - "IceCave9": 371, - "IceCave11": 373, - "IceCave10": 372, - "IceCave12": 374, - "IceCave13": 375, - "IceCave14": 376, - "IceCave1": 363, - "IceCave2": 364, - "IceCave3": 365, - "IceCave4": 366, - "IceCave5": 367, - "IceCaveMajor": 370, - "IceCave7": 369, - "IceCave6": 368, - "Elfland1": 269, - "Elfland2": 270, - "Elfland3": 271, - "Elfland4": 272, - "Ordeals5": 383, - "Ordeals6": 384, - "Ordeals7": 385, - "Ordeals1": 379, - "Ordeals2": 380, - "Ordeals3": 381, - "Ordeals4": 382, - "OrdealsMajor": 387, - "Ordeals8": 386, - "SeaShrine7": 411, - "SeaShrine8": 412, - "SeaShrine9": 413, - "SeaShrine10": 414, - "SeaShrine1": 405, - "SeaShrine2": 406, - "SeaShrine3": 407, - "SeaShrine4": 408, - "SeaShrine5": 409, - "SeaShrine6": 410, - "SeaShrine13": 417, - "SeaShrine14": 418, - "SeaShrine11": 415, - "SeaShrine15": 419, - "SeaShrine16": 420, - "SeaShrineLocked": 421, - "SeaShrine18": 422, - "SeaShrine19": 423, - "SeaShrine20": 424, - "SeaShrine23": 427, - "SeaShrine21": 425, - "SeaShrine22": 426, - "SeaShrine24": 428, - "SeaShrine26": 430, - "SeaShrine28": 432, - "SeaShrine25": 429, - "SeaShrine30": 434, - "SeaShrine31": 435, - "SeaShrine27": 431, - "SeaShrine29": 433, - "SeaShrineMajor": 436, - "SeaShrine12": 416, - "DwarfCave3": 291, - "DwarfCave4": 292, - "DwarfCave6": 294, - "DwarfCave7": 295, - "DwarfCave5": 293, - "DwarfCave8": 296, - "DwarfCave9": 297, - "DwarfCave10": 298, - "DwarfCave1": 289, - "DwarfCave2": 290, - "Waterfall1": 437, - "Waterfall2": 438, - "Waterfall3": 439, - "Waterfall4": 440, - "Waterfall5": 441, - "Waterfall6": 442, - "MirageTower5": 456, - "MirageTower16": 467, - "MirageTower17": 468, - "MirageTower15": 466, - "MirageTower18": 469, - "MirageTower14": 465, - "SkyPalace1": 470, - "SkyPalace2": 471, - "SkyPalace3": 472, - "SkyPalace4": 473, - "SkyPalace18": 487, - "SkyPalace19": 488, - "SkyPalace16": 485, - "SkyPalaceMajor": 489, - "SkyPalace17": 486, - "SkyPalace22": 491, - "SkyPalace21": 490, - "SkyPalace23": 492, - "SkyPalace24": 493, - "SkyPalace31": 500, - "SkyPalace32": 501, - "SkyPalace33": 502, - "SkyPalace34": 503, - "SkyPalace29": 498, - "SkyPalace26": 495, - "SkyPalace25": 494, - "SkyPalace28": 497, - "SkyPalace27": 496, - "SkyPalace30": 499, - "SkyPalace14": 483, - "SkyPalace11": 480, - "SkyPalace12": 481, - "SkyPalace13": 482, - "SkyPalace15": 484, - "SkyPalace10": 479, - "SkyPalace5": 474, - "SkyPalace6": 475, - "SkyPalace7": 476, - "SkyPalace8": 477, - "SkyPalace9": 478, - "MirageTower9": 460, - "MirageTower13": 464, - "MirageTower10": 461, - "MirageTower12": 463, - "MirageTower11": 462, - "MirageTower1": 452, - "MirageTower2": 453, - "MirageTower4": 455, - "MirageTower3": 454, - "MirageTower8": 459, - "MirageTower7": 458, - "MirageTower6": 457, - "Volcano30": 359, - "Volcano32": 361, - "Volcano31": 360, - "Volcano28": 357, - "Volcano29": 358, - "Volcano21": 350, - "Volcano20": 349, - "Volcano24": 353, - "Volcano19": 348, - "Volcano25": 354, - "VolcanoMajor": 362, - "Volcano26": 355, - "Volcano27": 356, - "Volcano22": 351, - "Volcano23": 352, - "Volcano1": 330, - "Volcano9": 338, - "Volcano2": 331, - "Volcano10": 339, - "Volcano3": 332, - "Volcano8": 337, - "Volcano4": 333, - "Volcano13": 342, - "Volcano11": 340, - "Volcano7": 336, - "Volcano6": 335, - "Volcano5": 334, - "Volcano14": 343, - "Volcano12": 341, - "Volcano15": 344, - "Volcano18": 347, - "Volcano17": 346, - "Volcano16": 345, - "MarshCave6": 281, - "MarshCave5": 280, - "MarshCave7": 282, - "MarshCave8": 283, - "MarshCave10": 285, - "MarshCave2": 277, - "MarshCave11": 286, - "MarshCave3": 278, - "MarshCaveMajor": 284, - "MarshCave12": 287, - "MarshCave4": 279, - "MarshCave1": 276, - "MarshCave13": 288, - "TitansTunnel1": 326, - "TitansTunnel2": 327, - "TitansTunnel3": 328, - "TitansTunnel4": 329, - "EarthCave1": 302, - "EarthCave2": 303, - "EarthCave5": 306, - "EarthCave3": 304, - "EarthCave4": 305, - "EarthCave9": 310, - "EarthCave10": 311, - "EarthCave11": 312, - "EarthCave6": 307, - "EarthCave7": 308, - "EarthCave12": 313, - "EarthCaveMajor": 317, - "EarthCave19": 320, - "EarthCave17": 318, - "EarthCave18": 319, - "EarthCave20": 321, - "EarthCave24": 325, - "EarthCave21": 322, - "EarthCave22": 323, - "EarthCave23": 324, - "EarthCave13": 314, - "EarthCave15": 316, - "EarthCave14": 315, - "EarthCave8": 309, - "Cardia11": 398, - "Cardia9": 396, - "Cardia10": 397, - "Cardia6": 393, - "Cardia8": 395, - "Cardia7": 394, - "Cardia13": 400, - "Cardia12": 399, - "Cardia4": 391, - "Cardia5": 392, - "Cardia3": 390, - "Cardia1": 388, - "Cardia2": 389, - "CaravanShop": 767, + "Matoya's Cave - Chest 1": 299, + "Matoya's Cave - Chest 2": 301, + "Matoya's Cave - Chest 3": 300, + "Dwarf Cave - Entrance 1": 289, + "Dwarf Cave - Entrance 2": 290, + "Dwarf Cave - Treasury 1": 291, + "Dwarf Cave - Treasury 2": 292, + "Dwarf Cave - Treasury 3": 295, + "Dwarf Cave - Treasury 4": 293, + "Dwarf Cave - Treasury 5": 294, + "Dwarf Cave - Treasury 6": 296, + "Dwarf Cave - Treasury 7": 297, + "Dwarf Cave - Treasury 8": 298, + "Coneria Castle - Treasury 1": 257, + "Coneria Castle - Treasury 2": 258, + "Coneria Castle - Treasury 3": 260, + "Coneria Castle - Treasury 4": 261, + "Coneria Castle - Treasury 5": 262, + "Coneria Castle - Treasury Major": 259, + "Elf Castle - Treasury 1": 269, + "Elf Castle - Treasury 2": 270, + "Elf Castle - Treasury 3": 271, + "Elf Castle - Treasury 4": 272, + "Northwest Castle - Treasury 1": 273, + "Northwest Castle - Treasury 2": 275, + "Northwest Castle - Treasury 3": 274, + "Titan's Tunnel - Chest 1": 327, + "Titan's Tunnel - Chest 2": 328, + "Titan's Tunnel - Chest 3": 329, + "Titan's Tunnel - Major": 326, + "Cardia Grass Island - Entrance": 398, + "Cardia Grass Island - Duo Room 1": 396, + "Cardia Grass Island - Duo Rooom 2": 397, + "Cardia Swamp Island - Chest 1": 393, + "Cardia Swamp Island - Chest 2": 395, + "Cardia Swamp Island - Chest 3": 394, + "Cardia Forest Island - Entrance 1": 389, + "Cardia Forest Island - Entrance 2": 388, + "Cardia Forest Island - Entrance 3": 390, + "Cardia Forest Island - Incentive 1": 400, + "Cardia Forest Island - Incentive 2": 399, + "Cardia Forest Island - Incentive 3": 392, + "Cardia Forest Island - Incentive Major": 391, + "Temple of Fiends - Unlocked Single": 265, + "Temple of Fiends - Unlocked Duo 1": 263, + "Temple of Fiends - Unlocked Duo 2": 264, + "Temple of Fiends - Locked Single": 266, + "Temple of Fiends - Locked Duo 1": 267, + "Temple of Fiends - Locked Duo 2": 268, + "Marsh Cave Top (B1) - Single": 283, + "Marsh Cave Top (B1) - Corner": 282, + "Marsh Cave Top (B1) - Duo 1": 281, + "Marsh Cave Top (B1) - Duo 2": 280, + "Marsh Cave Bottom (B2) - Distant": 276, + "Marsh Cave Bottom (B2) - Tetris-Z First": 277, + "Marsh Cave Bottom (B2) - Tetris-Z Middle 1": 278, + "Marsh Cave Bottom (B2) - Tetris-Z Middle 2": 285, + "Marsh Cave Bottom (B2) - Tetris-Z Incentive": 284, + "Marsh Cave Bottom (B2) - Tetris-Z Last": 279, + "Marsh Cave Bottom (B2) - Locked Corner": 286, + "Marsh Cave Bottom (B2) - Locked Middle": 287, + "Marsh Cave Bottom (B2) - Locked Incentive": 288, + "Earth Cave Giant's Floor (B1) - Single": 306, + "Earth Cave Giant's Floor (B1) - Appendix 1": 302, + "Earth Cave Giant's Floor (B1) - Appendix 2": 303, + "Earth Cave Giant's Floor (B1) - Side Path 1": 304, + "Earth Cave Giant's Floor (B1) - Side Path 2": 305, + "Earth Cave (B2) - Side Room 1": 307, + "Earth Cave (B2) - Side Room 2": 308, + "Earth Cave (B2) - Side Room 3": 309, + "Earth Cave (B2) - Guarded 1": 310, + "Earth Cave (B2) - Guarded 2": 311, + "Earth Cave (B2) - Guarded 3": 312, + "Earth Cave Vampire Floor (B3) - Side Room": 315, + "Earth Cave Vampire Floor (B3) - TFC": 316, + "Earth Cave Vampire Floor (B3) - Asher Trunk": 314, + "Earth Cave Vampire Floor (B3) - Vampire's Closet": 313, + "Earth Cave Vampire Floor (B3) - Incentive": 317, + "Earth Cave Rod Locked Floor (B4) - Armory 1": 321, + "Earth Cave Rod Locked Floor (B4) - Armory 2": 322, + "Earth Cave Rod Locked Floor (B4) - Armory 3": 325, + "Earth Cave Rod Locked Floor (B4) - Armory 4": 323, + "Earth Cave Rod Locked Floor (B4) - Armory 5": 324, + "Earth Cave Rod Locked Floor (B4) - Lich's Closet 1": 318, + "Earth Cave Rod Locked Floor (B4) - Lich's Closet 2": 319, + "Earth Cave Rod Locked Floor (B4) - Lich's Closet 3": 320, + "Gurgu Volcano Armory Floor (B2) - Guarded": 346, + "Gurgu Volcano Armory Floor (B2) - Center": 347, + "Gurgu Volcano Armory Floor (B2) - Hairpins": 344, + "Gurgu Volcano Armory Floor (B2) - Shortpins": 345, + "Gurgu Volcano Armory Floor (B2) - Vertpins 1": 342, + "Gurgu Volcano Armory Floor (B2) - Vertpins 2": 343, + "Gurgu Volcano Armory Floor (B2) - Armory 1": 338, + "Gurgu Volcano Armory Floor (B2) - Armory 2": 330, + "Gurgu Volcano Armory Floor (B2) - Armory 3": 331, + "Gurgu Volcano Armory Floor (B2) - Armory 4": 337, + "Gurgu Volcano Armory Floor (B2) - Armory 5": 335, + "Gurgu Volcano Armory Floor (B2) - Armory 6": 332, + "Gurgu Volcano Armory Floor (B2) - Armory 7": 333, + "Gurgu Volcano Armory Floor (B2) - Armory 8": 334, + "Gurgu Volcano Armory Floor (B2) - Armory 9": 341, + "Gurgu Volcano Armory Floor (B2) - Armory 10": 336, + "Gurgu Volcano Armory Floor (B2) - Armory 11": 340, + "Gurgu Volcano Armory Floor (B2) - Armory 12": 339, + "Gurgu Volcano Agama Floor (B4) - Entrance 1": 349, + "Gurgu Volcano Agama Floor (B4) - Entrance 2": 348, + "Gurgu Volcano Agama Floor (B4) - First Greed": 350, + "Gurgu Volcano Agama Floor (B4) - Worm Room 1": 361, + "Gurgu Volcano Agama Floor (B4) - Worm Room 2": 359, + "Gurgu Volcano Agama Floor (B4) - Worm Room 3": 360, + "Gurgu Volcano Agama Floor (B4) - Worm Room 4": 357, + "Gurgu Volcano Agama Floor (B4) - Worm Room 5": 358, + "Gurgu Volcano Agama Floor (B4) - Second Greed 1": 353, + "Gurgu Volcano Agama Floor (B4) - Second Greed 2": 354, + "Gurgu Volcano Agama Floor (B4) - Side Room 1": 355, + "Gurgu Volcano Agama Floor (B4) - Side Room 2": 356, + "Gurgu Volcano Agama Floor (B4) - Grind Room 1": 351, + "Gurgu Volcano Agama Floor (B4) - Grind Room 2": 352, + "Gurgu Volcano Kary Floor (B5) - Incentive": 362, + "Ice Cave Incentive Floor (B2) - Chest 1": 368, + "Ice Cave Incentive Floor (B2) - Chest 2": 369, + "Ice Cave Incentive Floor (B2) - Major": 370, + "Ice Cave Bottom (B3) - IceD Room 1": 377, + "Ice Cave Bottom (B3) - IceD Room 2": 378, + "Ice Cave Bottom (B3) - Six-Pack 1": 371, + "Ice Cave Bottom (B3) - Six-Pack 2": 372, + "Ice Cave Bottom (B3) - Six-Pack 3": 375, + "Ice Cave Bottom (B3) - Six-Pack 4": 373, + "Ice Cave Bottom (B3) - Six-Pack 5": 374, + "Ice Cave Bottom (B3) - Six-Pack 6": 376, + "Ice Cave Exit Floor (B1) - Greeds Checks 1": 363, + "Ice Cave Exit Floor (B1) - Greeds Checks 2": 364, + "Ice Cave Exit Floor (B1) - Drop Room 1": 365, + "Ice Cave Exit Floor (B1) - Drop Room 2": 366, + "Ice Cave Exit Floor (B1) - Drop Room 3": 367, + "Castle of Ordeals Top Floor (3F) - Single": 386, + "Castle of Ordeals Top Floor (3F) - Three-Pack 1": 383, + "Castle of Ordeals Top Floor (3F) - Three-Pack 2": 384, + "Castle of Ordeals Top Floor (3F) - Three-Pack 3": 385, + "Castle of Ordeals Top Floor (3F) - Four-Pack 1": 379, + "Castle of Ordeals Top Floor (3F) - Four-Pack 2": 380, + "Castle of Ordeals Top Floor (3F) - Four-Pack 3": 381, + "Castle of Ordeals Top Floor (3F) - Four-Pack 4": 382, + "Castle of Ordeals Top Floor (3F) - Incentive": 387, + "Sea Shrine Split Floor (B3) - Kraken Side": 415, + "Sea Shrine Split Floor (B3) - Mermaid Side": 416, + "Sea Shrine TFC Floor (B2) - TFC": 421, + "Sea Shrine TFC Floor (B2) - TFC North": 420, + "Sea Shrine TFC Floor (B2) - Side Corner": 419, + "Sea Shrine TFC Floor (B2) - First Greed": 422, + "Sea Shrine TFC Floor (B2) - Second Greed": 423, + "Sea Shrine Mermaids (B1) - Passby": 427, + "Sea Shrine Mermaids (B1) - Bubbles 1": 428, + "Sea Shrine Mermaids (B1) - Bubbles 2": 429, + "Sea Shrine Mermaids (B1) - Incentive 1": 434, + "Sea Shrine Mermaids (B1) - Incentive 2": 435, + "Sea Shrine Mermaids (B1) - Incentive Major": 436, + "Sea Shrine Mermaids (B1) - Entrance 1": 424, + "Sea Shrine Mermaids (B1) - Entrance 2": 425, + "Sea Shrine Mermaids (B1) - Entrance 3": 426, + "Sea Shrine Mermaids (B1) - Four-Corner First": 430, + "Sea Shrine Mermaids (B1) - Four-Corner Second": 431, + "Sea Shrine Mermaids (B1) - Four-Corner Third": 432, + "Sea Shrine Mermaids (B1) - Four-Corner Fourth": 433, + "Sea Shrine Greed Floor (B3) - Chest 1": 418, + "Sea Shrine Greed Floor (B3) - Chest 2": 417, + "Sea Shrine Sharknado Floor (B4) - Dengbait 1": 409, + "Sea Shrine Sharknado Floor (B4) - Dengbait 2": 410, + "Sea Shrine Sharknado Floor (B4) - Side Corner 1": 411, + "Sea Shrine Sharknado Floor (B4) - Side Corner 2": 412, + "Sea Shrine Sharknado Floor (B4) - Side Corner 3": 413, + "Sea Shrine Sharknado Floor (B4) - Exit": 414, + "Sea Shrine Sharknado Floor (B4) - Greed Room 1": 405, + "Sea Shrine Sharknado Floor (B4) - Greed Room 2": 406, + "Sea Shrine Sharknado Floor (B4) - Greed Room 3": 407, + "Sea Shrine Sharknado Floor (B4) - Greed Room 4": 408, + "Waterfall Cave - Chest 1": 437, + "Waterfall Cave - Chest 2": 438, + "Waterfall Cave - Chest 3": 439, + "Waterfall Cave - Chest 4": 440, + "Waterfall Cave - Chest 5": 441, + "Waterfall Cave - Chest 6": 442, + "Mirage Tower (1F) - Chest 1": 456, + "Mirage Tower (1F) - Chest 2": 452, + "Mirage Tower (1F) - Chest 3": 453, + "Mirage Tower (1F) - Chest 4": 455, + "Mirage Tower (1F) - Chest 5": 454, + "Mirage Tower (1F) - Chest 6": 459, + "Mirage Tower (1F) - Chest 7": 457, + "Mirage Tower (1F) - Chest 8": 458, + "Mirage Tower (2F) - Lesser 1": 469, + "Mirage Tower (2F) - Lesser 2": 468, + "Mirage Tower (2F) - Lesser 3": 467, + "Mirage Tower (2F) - Lesser 4": 466, + "Mirage Tower (2F) - Lesser 5": 465, + "Mirage Tower (2F) - Greater 1": 460, + "Mirage Tower (2F) - Greater 2": 461, + "Mirage Tower (2F) - Greater 3": 462, + "Mirage Tower (2F) - Greater 4": 463, + "Mirage Tower (2F) - Greater 5": 464, + "Sky Fortress Plus (1F) - Solo": 479, + "Sky Fortress Plus (1F) - Five-Pack 1": 474, + "Sky Fortress Plus (1F) - Five-Pack 2": 475, + "Sky Fortress Plus (1F) - Five-Pack 3": 476, + "Sky Fortress Plus (1F) - Five-Pack 4": 477, + "Sky Fortress Plus (1F) - Five-Pack 5": 478, + "Sky Fortress Plus (1F) - Four-Pack 1": 470, + "Sky Fortress Plus (1F) - Four-Pack 2": 471, + "Sky Fortress Plus (1F) - Four-Pack 3": 472, + "Sky Fortress Plus (1F) - Four-Pack 4": 473, + "Sky Fortress Spider (2F) - Cheap Room 1": 485, + "Sky Fortress Spider (2F) - Cheap Room 2": 486, + "Sky Fortress Spider (2F) - Vault 1": 487, + "Sky Fortress Spider (2F) - Vault 2": 488, + "Sky Fortress Spider (2F) - Incentive": 489, + "Sky Fortress Spider (2F) - Gauntlet Room": 483, + "Sky Fortress Spider (2F) - Ribbon Room 1": 482, + "Sky Fortress Spider (2F) - Ribbon Room 2": 484, + "Sky Fortress Spider (2F) - Wardrobe 1": 480, + "Sky Fortress Spider (2F) - Wardrobe 2": 481, + "Sky Fortress Provides (3F) - Six-Pack 1": 498, + "Sky Fortress Provides (3F) - Six-Pack 2": 495, + "Sky Fortress Provides (3F) - Six-Pack 3": 494, + "Sky Fortress Provides (3F) - Six-Pack 4": 497, + "Sky Fortress Provides (3F) - Six-Pack 5": 496, + "Sky Fortress Provides (3F) - Six-Pack 6": 499, + "Sky Fortress Provides (3F) - CC's Gambit 1": 500, + "Sky Fortress Provides (3F) - CC's Gambit 2": 501, + "Sky Fortress Provides (3F) - CC's Gambit 3": 502, + "Sky Fortress Provides (3F) - CC's Gambit 4": 503, + "Sky Fortress Provides (3F) - Greed 1": 491, + "Sky Fortress Provides (3F) - Greed 2": 490, + "Sky Fortress Provides (3F) - Greed 3": 492, + "Sky Fortress Provides (3F) - Greed 4": 493, + "Temple of Fiends Revisited (3F) - Validation 1": 509, + "Temple of Fiends Revisited (3F) - Validation 2": 510, + "Temple of Fiends Revisited Kary Floor (6F) - Greed Checks 1": 507, + "Temple of Fiends Revisited Kary Floor (6F) - Greed Checks 2": 508, + "Temple of Fiends Revisited Kary Floor (6F) - Katana Chest": 506, + "Temple of Fiends Revisited Kary Floor (6F) - Vault": 505, + "Temple of Fiends Revisited Tiamat Floor (8F) - Masamune Chest": 504, + "Shop Item": 767, "King": 513, - "Princess2": 530, + "Princess": 530, "Matoya": 522, "Astos": 519, "Bikke": 516, - "CanoeSage": 533, - "ElfPrince": 518, + "Canoe Sage": 533, + "Elf Prince": 518, "Nerrick": 520, "Smith": 521, "CubeBot": 529, From b2f30d5fd001ec7a37ad56c3c989015a991a6089 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 3 Mar 2024 02:20:37 -0500 Subject: [PATCH 053/166] Lingo: Add a third location to Starting Room (#2839) Despite earlier efforts, there were still rare fill errors when door shuffle and color shuffle were on and early color hallways was off, because sphere 1 was too small. This turns "Starting Room - HI" back into a location, which should give the algorithm more room. The "forced good item" pool has been reconsidered. The problem with the specific item that caused the recent failure (Welcome Back - Shortcut to Starting Room) is that it only provided one location when color shuffle was on, which is a net of zero considering that the GOOD LUCK check was forced. It will no longer show up as a good item unless color shuffle is off. On an opposite vein, Rhyme Room Doors will now show up even if color shuffle is on, because it gives color hallways access by itself. A good item will only be forced onto GOOD LUCK now if there is more than one player. --- worlds/lingo/data/LL1.yaml | 1 + worlds/lingo/player_logic.py | 38 +++++++++++++++++++--------- worlds/lingo/test/TestDoors.py | 10 -------- worlds/lingo/test/TestOrangeTower.py | 4 --- worlds/lingo/test/TestProgressive.py | 6 ----- worlds/lingo/test/__init__.py | 9 ------- 6 files changed, 27 insertions(+), 41 deletions(-) diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index 1a149f2db9..f72e63c142 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -112,6 +112,7 @@ HI: id: Entry Room/Panel_hi_hi tag: midwhite + check: True HIDDEN: id: Entry Room/Panel_hidden_hidden tag: midwhite diff --git a/worlds/lingo/player_logic.py b/worlds/lingo/player_logic.py index 0ae303518c..3a6eedfe0a 100644 --- a/worlds/lingo/player_logic.py +++ b/worlds/lingo/player_logic.py @@ -248,30 +248,44 @@ class LingoPlayerLogic: "kind of logic error.") if door_shuffle != ShuffleDoors.option_none and location_classification != LocationClassification.insanity \ - and not early_color_hallways: - # If shuffle doors is on, force a useful item onto the HI panel. This may not necessarily get you out of BK, - # but the goal is to allow you to reach at least one more check. The non-painting ones are hardcoded right - # now. We only allow the entrance to the Pilgrim Room if color shuffle is off, because otherwise there are - # no extra checks in there. We only include the entrance to the Rhyme Room when color shuffle is off and - # door shuffle is on simple, because otherwise there are no extra checks in there. + and not early_color_hallways and world.multiworld.players > 1: + # Under the combination of door shuffle, normal location checks, and no early color hallways, sphere 1 is + # only three checks. In a multiplayer situation, this can be frustrating for the player because they are + # more likely to be stuck in the starting room for a long time. To remedy this, we will force a useful item + # onto the GOOD LUCK check under these circumstances. The goal is to expand sphere 1 to at least four + # checks (and likely more than that). + # + # Note: A very low LEVEL 2 requirement would naturally expand sphere 1 to four checks, but this is a very + # uncommon configuration, so we will ignore it and force a good item anyway. + + # Starting Room - Back Right Door gives access to OPEN and DEAD END. + # Starting Room - Exit Door gives access to OPEN and TRACE. good_item_options: List[str] = ["Starting Room - Back Right Door", "Second Room - Exit Door"] if not color_shuffle: + # HOT CRUST and THIS. good_item_options.append("Pilgrim Room - Sun Painting") + if door_shuffle == ShuffleDoors.option_simple: + # WELCOME BACK, CLOCKWISE, and DRAWL + RUNS. + good_item_options.append("Welcome Back Doors") + else: + # WELCOME BACK and CLOCKWISE. + good_item_options.append("Welcome Back Area - Shortcut to Starting Room") + if door_shuffle == ShuffleDoors.option_simple: - good_item_options += ["Welcome Back Doors"] - - if not color_shuffle: - good_item_options.append("Rhyme Room Doors") - else: - good_item_options += ["Welcome Back Area - Shortcut to Starting Room"] + # Color hallways access (NOTE: reconsider when sunwarp shuffling exists). + good_item_options.append("Rhyme Room Doors") + # When painting shuffle is off, most Starting Room paintings give color hallways access. The Wondrous's + # painting does not, but it gives access to SHRINK and WELCOME BACK. for painting_obj in PAINTINGS_BY_ROOM["Starting Room"]: if not painting_obj.enter_only or painting_obj.required_door is None: continue # If painting shuffle is on, we only want to consider paintings that actually go somewhere. + # + # NOTE: This does not guarantee that there will be any checks on the other side. if painting_shuffle and painting_obj.id not in self.painting_mapping.keys(): continue diff --git a/worlds/lingo/test/TestDoors.py b/worlds/lingo/test/TestDoors.py index 49a0f9c490..f496c5f578 100644 --- a/worlds/lingo/test/TestDoors.py +++ b/worlds/lingo/test/TestDoors.py @@ -8,8 +8,6 @@ class TestRequiredRoomLogic(LingoTestBase): } def test_pilgrim_first(self) -> None: - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("The Seeker", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Pilgrim Antechamber", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player)) @@ -30,8 +28,6 @@ class TestRequiredRoomLogic(LingoTestBase): self.assertTrue(self.can_reach_location("The Seeker - Achievement")) def test_hidden_first(self) -> None: - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("The Seeker", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player)) self.assertFalse(self.can_reach_location("The Seeker - Achievement")) @@ -59,8 +55,6 @@ class TestRequiredDoorLogic(LingoTestBase): } def test_through_rhyme(self) -> None: - self.remove_forced_good_item() - self.assertFalse(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall")) self.collect_by_name("Starting Room - Rhyme Room Entrance") @@ -70,8 +64,6 @@ class TestRequiredDoorLogic(LingoTestBase): self.assertTrue(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall")) def test_through_hidden(self) -> None: - self.remove_forced_good_item() - self.assertFalse(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall")) self.collect_by_name("Starting Room - Rhyme Room Entrance") @@ -91,8 +83,6 @@ class TestSimpleDoors(LingoTestBase): } def test_requirement(self): - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("Outside The Wanderer", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) diff --git a/worlds/lingo/test/TestOrangeTower.py b/worlds/lingo/test/TestOrangeTower.py index 9170de108a..7b0c3bb525 100644 --- a/worlds/lingo/test/TestOrangeTower.py +++ b/worlds/lingo/test/TestOrangeTower.py @@ -8,8 +8,6 @@ class TestProgressiveOrangeTower(LingoTestBase): } def test_from_welcome_back(self) -> None: - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) @@ -85,8 +83,6 @@ class TestProgressiveOrangeTower(LingoTestBase): self.assertTrue(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player)) def test_from_hub_room(self) -> None: - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player)) diff --git a/worlds/lingo/test/TestProgressive.py b/worlds/lingo/test/TestProgressive.py index 081d6743a5..e79fd6bc90 100644 --- a/worlds/lingo/test/TestProgressive.py +++ b/worlds/lingo/test/TestProgressive.py @@ -7,8 +7,6 @@ class TestComplexProgressiveHallwayRoom(LingoTestBase): } def test_item(self): - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player)) @@ -60,8 +58,6 @@ class TestSimpleHallwayRoom(LingoTestBase): } def test_item(self): - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player)) @@ -90,8 +86,6 @@ class TestProgressiveArtGallery(LingoTestBase): } def test_item(self): - self.remove_forced_good_item() - self.assertFalse(self.multiworld.state.can_reach("Art Gallery", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player)) self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player)) diff --git a/worlds/lingo/test/__init__.py b/worlds/lingo/test/__init__.py index 7ff456d8fc..a4196de110 100644 --- a/worlds/lingo/test/__init__.py +++ b/worlds/lingo/test/__init__.py @@ -6,12 +6,3 @@ from test.bases import WorldTestBase class LingoTestBase(WorldTestBase): game = "Lingo" player: ClassVar[int] = 1 - - def world_setup(self, *args, **kwargs): - super().world_setup(*args, **kwargs) - - def remove_forced_good_item(self): - location = self.multiworld.get_location("Second Room - Good Luck", self.player) - self.remove(location.item) - self.multiworld.itempool.append(location.item) - self.multiworld.state.events.add(location) From 526eb090891c9b0fed6b9fadeee2d02bf8d763c1 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Sun, 3 Mar 2024 07:11:44 -0600 Subject: [PATCH 054/166] Options: add a DeathLinkMixin dataclass to easily standardize death_link (#2355) * Options: add a DeathLinkOption dataclass to easily standardize death_link * rename to DeathLinkMixin * Update worlds/messenger/options.py --- Options.py | 5 +++++ worlds/messenger/options.py | 6 ++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Options.py b/Options.py index 2e3927aae3..5fe4087132 100644 --- a/Options.py +++ b/Options.py @@ -1110,6 +1110,11 @@ class PerGameCommonOptions(CommonOptions): item_links: ItemLinks +@dataclass +class DeathLinkMixin: + death_link: DeathLink + + def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True): import os diff --git a/worlds/messenger/options.py b/worlds/messenger/options.py index 1da544bee7..6984e21547 100644 --- a/worlds/messenger/options.py +++ b/worlds/messenger/options.py @@ -3,7 +3,7 @@ from typing import Dict from schema import And, Optional, Or, Schema -from Options import Accessibility, Choice, DeathLink, DefaultOnToggle, OptionDict, PerGameCommonOptions, Range, \ +from Options import Accessibility, Choice, DeathLinkMixin, DefaultOnToggle, OptionDict, PerGameCommonOptions, Range, \ StartInventoryPool, Toggle @@ -133,7 +133,7 @@ class PlannedShopPrices(OptionDict): @dataclass -class MessengerOptions(PerGameCommonOptions): +class MessengerOptions(DeathLinkMixin, PerGameCommonOptions): accessibility: MessengerAccessibility start_inventory: StartInventoryPool logic_level: Logic @@ -146,5 +146,3 @@ class MessengerOptions(PerGameCommonOptions): percent_seals_required: RequiredSeals shop_price: ShopPrices shop_price_plan: PlannedShopPrices - death_link: DeathLink - From ef37ee81f9f3bbe501c4e52ed955e4cb7cb6aa43 Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Sun, 3 Mar 2024 07:23:02 -0800 Subject: [PATCH 055/166] Zillion: apworld-compatible package data (#2860) * Zillion: apworld-compatible module data * fixed `World` import --- setup.py | 1 - typings/kivy/graphics/texture.pyi | 2 +- worlds/zillion/__init__.py | 2 +- worlds/zillion/client.py | 24 ++++++++++++++++++------ worlds/zillion/config.py | 3 --- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index 272e6de0be..3f9a7f0ba6 100644 --- a/setup.py +++ b/setup.py @@ -80,7 +80,6 @@ non_apworlds: set = { "Super Mario 64", "VVVVVV", "Wargroove", - "Zillion", } # LogicMixin is broken before 3.10 import revamp diff --git a/typings/kivy/graphics/texture.pyi b/typings/kivy/graphics/texture.pyi index 19e03aad69..ca643b1cad 100644 --- a/typings/kivy/graphics/texture.pyi +++ b/typings/kivy/graphics/texture.pyi @@ -10,4 +10,4 @@ class FillType_Drawable: class Texture: - pass + size: FillType_Vec diff --git a/worlds/zillion/__init__.py b/worlds/zillion/__init__.py index d30bef1444..d7e653bb80 100644 --- a/worlds/zillion/__init__.py +++ b/worlds/zillion/__init__.py @@ -25,7 +25,7 @@ from zilliandomizer.logic_components.items import RESCUE, items as zz_items, Ite from zilliandomizer.logic_components.locations import Location as ZzLocation, Req from zilliandomizer.options import Chars -from ..AutoWorld import World, WebWorld +from worlds.AutoWorld import World, WebWorld class ZillionSettings(settings.Group): diff --git a/worlds/zillion/client.py b/worlds/zillion/client.py index b10507aaf8..1a85b9df25 100644 --- a/worlds/zillion/client.py +++ b/worlds/zillion/client.py @@ -1,5 +1,7 @@ import asyncio import base64 +import io +import pkgutil import platform from typing import Any, ClassVar, Coroutine, Dict, List, Optional, Protocol, Tuple, cast @@ -17,7 +19,7 @@ from zilliandomizer.options import Chars from zilliandomizer.patch import RescueInfo from .id_maps import loc_name_to_id, make_id_to_others -from .config import base_id, zillion_map +from .config import base_id class ZillionCommandProcessor(ClientCommandProcessor): @@ -138,7 +140,9 @@ class ZillionContext(CommonContext): from kvui import GameManager from kivy.core.text import Label as CoreLabel from kivy.graphics import Ellipse, Color, Rectangle + from kivy.graphics.texture import Texture from kivy.uix.layout import Layout + from kivy.uix.image import CoreImage from kivy.uix.widget import Widget class ZillionManager(GameManager): @@ -150,12 +154,21 @@ class ZillionContext(CommonContext): class MapPanel(Widget): MAP_WIDTH: ClassVar[int] = 281 - _number_textures: List[Any] = [] + map_background: CoreImage + _number_textures: List[Texture] = [] rooms: List[List[int]] = [] def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) + FILE_NAME = "empty-zillion-map-row-col-labels-281.png" + image_file_data = pkgutil.get_data(__name__, FILE_NAME) + if not image_file_data: + raise FileNotFoundError(f"{__name__=} {FILE_NAME=}") + data = io.BytesIO(image_file_data) + self.map_background = CoreImage(data, ext="png") + assert self.map_background.texture.size[0] == ZillionManager.MapPanel.MAP_WIDTH + self.rooms = [[0 for _ in range(8)] for _ in range(16)] self._make_numbers() @@ -176,10 +189,9 @@ class ZillionContext(CommonContext): with self.canvas: Color(1, 1, 1, 1) - Rectangle(source=zillion_map, + Rectangle(texture=self.map_background.texture, pos=self.pos, - size=(ZillionManager.MapPanel.MAP_WIDTH, - int(ZillionManager.MapPanel.MAP_WIDTH * 1.456))) # aspect ratio of that image + size=self.map_background.texture.size) for y in range(16): for x in range(8): num = self.rooms[15 - y][x] @@ -194,7 +206,7 @@ class ZillionContext(CommonContext): def build(self) -> Layout: container = super().build() - self.map_widget = ZillionManager.MapPanel(size_hint_x=None, width=0) + self.map_widget = ZillionManager.MapPanel(size_hint_x=None, width=ZillionManager.MapPanel.MAP_WIDTH) self.main_area_container.add_widget(self.map_widget) return container diff --git a/worlds/zillion/config.py b/worlds/zillion/config.py index ca02f9a99f..e08c4f4278 100644 --- a/worlds/zillion/config.py +++ b/worlds/zillion/config.py @@ -1,4 +1 @@ -import os - base_id = 8675309 -zillion_map = os.path.join(os.path.dirname(__file__), "empty-zillion-map-row-col-labels-281.png") From 57d1fe6d799caa2a848dd7ca377fe1c6a04829c3 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Sun, 3 Mar 2024 17:00:32 +0100 Subject: [PATCH 056/166] Docs: add note for stage_assert_generate to settings api (#2885) --- docs/settings api.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/settings api.md b/docs/settings api.md index f9cbe5e021..41023879ad 100644 --- a/docs/settings api.md +++ b/docs/settings api.md @@ -121,6 +121,10 @@ Path to a single file. Automatically resolves as user_path: Source folder or AP install path on Windows. ~/Archipelago for the AppImage. Will open a file browser if the file is missing when in GUI mode. +If the file is used in the world's `generate_output`, make sure to add a `stage_assert_generate` that checks if the +file is available, otherwise generation may fail at the very end. +See also [world api.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md#generation). + #### class method validate(cls, path: str) Override this and raise ValueError if validation fails. From d124df72e4624d0c159667c6a272901ee8b504d3 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Sun, 3 Mar 2024 10:25:21 -0600 Subject: [PATCH 057/166] Core: add specific can_reach helpers to CollectionState (#2867) --- BaseClasses.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index f418945351..2be9a9820d 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -717,14 +717,23 @@ class CollectionState(): assert isinstance(player, int), "can_reach: player is required if spot is str" # try to resolve a name if resolution_hint == 'Location': - spot = self.multiworld.get_location(spot, player) + return self.can_reach_location(spot, player) elif resolution_hint == 'Entrance': - spot = self.multiworld.get_entrance(spot, player) + return self.can_reach_entrance(spot, player) else: # default to Region - spot = self.multiworld.get_region(spot, player) + return self.can_reach_region(spot, player) return spot.can_reach(self) + def can_reach_location(self, spot: str, player: int) -> bool: + return self.multiworld.get_location(spot, player).can_reach(self) + + def can_reach_entrance(self, spot: str, player: int) -> bool: + return self.multiworld.get_entrance(spot, player).can_reach(self) + + def can_reach_region(self, spot: str, player: int) -> bool: + return self.multiworld.get_region(spot, player).can_reach(self) + def sweep_for_events(self, key_only: bool = False, locations: Optional[Iterable[Location]] = None) -> None: if locations is None: locations = self.multiworld.get_filled_locations() From 519dffdb7371c8f7769124713de8861de59881dd Mon Sep 17 00:00:00 2001 From: t3hf1gm3nt <59876300+t3hf1gm3nt@users.noreply.github.com> Date: Sun, 3 Mar 2024 11:59:31 -0500 Subject: [PATCH 058/166] TLOZ: Fix Logic for Gleeok guarded locations (#2734) Turns out you can't kill Gleeok with bombs or a candle as I happened to find out in a community async. While I'll be fine, a rare combination of settings could put all 4 possible weapons (the three levels of sword and the Magical Rod) to kill Gleeoks behind killing Gleeoks. This fix should prevent that from happening. Note: Even though there are technically 5 weapons that can kill Gleeok in the pool because at the moment we have an extra copy of the base Sword, I want to future-proof this incase we make changes to the item pool later. Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --- worlds/tloz/Locations.py | 4 ++++ worlds/tloz/Rules.py | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/worlds/tloz/Locations.py b/worlds/tloz/Locations.py index 3e46c43833..5b30357c94 100644 --- a/worlds/tloz/Locations.py +++ b/worlds/tloz/Locations.py @@ -105,6 +105,10 @@ food_locations = [ "Level 7 Bomb Drop (Dodongos)", "Level 7 Rupee Drop (Goriyas North)" ] +gleeok_locations = [ + "Level 4 Boss", "Level 4 Triforce", "Level 8 Boss", "Level 8 Triforce" +] + floor_location_game_offsets_early = { "Level 1 Item (Bow)": 0x7F, "Level 1 Item (Boomerang)": 0x44, diff --git a/worlds/tloz/Rules.py b/worlds/tloz/Rules.py index b94002f25d..f8b21bff71 100644 --- a/worlds/tloz/Rules.py +++ b/worlds/tloz/Rules.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING from worlds.generic.Rules import add_rule -from .Locations import food_locations, shop_locations +from .Locations import food_locations, shop_locations, gleeok_locations from .ItemPool import dangerous_weapon_locations from .Options import StartingPosition @@ -80,6 +80,10 @@ def set_rules(tloz_world: "TLoZWorld"): add_rule(world.get_location(location, player), lambda state: state.has("Food", player)) + for location in gleeok_locations: + add_rule(world.get_location(location, player), + lambda state: state.has_group("swords", player) or state.has("Magical Rod", player)) + add_rule(world.get_location("Level 8 Item (Magical Key)", player), lambda state: state.has("Bow", player) and state.has_group("arrows", player)) if options.ExpandedPool: From 4e31e51d7aa181c742102047665d43aaae960dc5 Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Sun, 3 Mar 2024 11:09:06 -0800 Subject: [PATCH 059/166] Core: clarify error message when reading an `APContainer` (#2887) --- worlds/Files.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/worlds/Files.py b/worlds/Files.py index 336a309093..dbeb54cfde 100644 --- a/worlds/Files.py +++ b/worlds/Files.py @@ -41,6 +41,13 @@ class AutoPatchRegister(abc.ABCMeta): current_patch_version: int = 5 +class InvalidDataError(Exception): + """ + Since games can override `read_contents` in APContainer, + this is to report problems in that process. + """ + + class APContainer: """A zipfile containing at least archipelago.json""" version: int = current_patch_version @@ -89,7 +96,15 @@ class APContainer: with zipfile.ZipFile(zip_file, "r") as zf: if file: self.path = zf.filename - self.read_contents(zf) + try: + self.read_contents(zf) + except Exception as e: + message = "" + if len(e.args): + arg0 = e.args[0] + if isinstance(e.args[0], str): + message = f"{arg0} - " + raise InvalidDataError(f"{message}This might be the incorrect world version for this file") from e def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None: with opened_zipfile.open("archipelago.json", "r") as f: From 113c54f9bea7138946b9bcc9b583a34254414b4f Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Sun, 3 Mar 2024 13:10:14 -0800 Subject: [PATCH 060/166] Zillion: remove rom requirement for generation (#2875) * in the middle of work towards no rom for generation (not working) * no rom needed for Zillion generation * revert core changes --- worlds/zillion/__init__.py | 113 +++++++++++--------------------- worlds/zillion/client.py | 3 +- worlds/zillion/gen_data.py | 35 ++++++++++ worlds/zillion/id_maps.py | 75 +++++++++++++++++++-- worlds/zillion/patch.py | 57 ++++++++++++++-- worlds/zillion/requirements.txt | 2 +- 6 files changed, 200 insertions(+), 85 deletions(-) create mode 100644 worlds/zillion/gen_data.py diff --git a/worlds/zillion/__init__.py b/worlds/zillion/__init__.py index d7e653bb80..b4e382e097 100644 --- a/worlds/zillion/__init__.py +++ b/worlds/zillion/__init__.py @@ -4,20 +4,22 @@ import functools import settings import threading import typing -from typing import Any, Dict, List, Set, Tuple, Optional, cast +from typing import Any, Dict, List, Set, Tuple, Optional import os import logging from BaseClasses import ItemClassification, LocationProgressType, \ MultiWorld, Item, CollectionState, Entrance, Tutorial + +from .gen_data import GenData from .logic import cs_to_zz_locs from .region import ZillionLocation, ZillionRegion from .options import ZillionOptions, validate -from .id_maps import item_name_to_id as _item_name_to_id, \ +from .id_maps import ZillionSlotInfo, get_slot_info, item_name_to_id as _item_name_to_id, \ loc_name_to_id as _loc_name_to_id, make_id_to_others, \ zz_reg_name_to_reg_name, base_id from .item import ZillionItem -from .patch import ZillionDeltaPatch, get_base_rom_path +from .patch import ZillionPatch from zilliandomizer.randomizer import Randomizer as ZzRandomizer from zilliandomizer.system import System @@ -33,8 +35,8 @@ class ZillionSettings(settings.Group): """File name of the Zillion US rom""" description = "Zillion US ROM File" copy_to = "Zillion (UE) [!].sms" - assert ZillionDeltaPatch.hash - md5s = [ZillionDeltaPatch.hash] + assert ZillionPatch.hash + md5s = [ZillionPatch.hash] class RomStart(str): """ @@ -134,14 +136,6 @@ class ZillionWorld(World): _id_to_name, _id_to_zz_id, id_to_zz_item = make_id_to_others(start_char) self.id_to_zz_item = id_to_zz_item - @classmethod - def stage_assert_generate(cls, multiworld: MultiWorld) -> None: - """Checks that a game is capable of generating, usually checks for some base file like a ROM. - Not run for unittests since they don't produce output""" - rom_file = get_base_rom_path() - if not os.path.exists(rom_file): - raise FileNotFoundError(rom_file) - def generate_early(self) -> None: if not hasattr(self.multiworld, "zillion_logic_cache"): setattr(self.multiworld, "zillion_logic_cache", {}) @@ -311,7 +305,9 @@ class ZillionWorld(World): if sc != to_stay: group_players.remove(p) assert "world" in group - cast(ZillionWorld, group["world"])._make_item_maps(to_stay) + group_world = group["world"] + assert isinstance(group_world, ZillionWorld) + group_world._make_item_maps(to_stay) def post_fill(self) -> None: """Optional Method that is called after regular fill. Can be used to do adjustments before output generation. @@ -319,27 +315,28 @@ class ZillionWorld(World): self.zz_system.post_fill() - def finalize_item_locations(self) -> None: + def finalize_item_locations(self) -> GenData: """ sync zilliandomizer item locations with AP item locations + + return the data needed to generate output """ - rom_dir_name = os.path.dirname(get_base_rom_path()) - self.zz_system.make_patcher(rom_dir_name) - assert self.zz_system.randomizer and self.zz_system.patcher, "generate_early hasn't been called" - zz_options = self.zz_system.randomizer.options + + assert self.zz_system.randomizer, "generate_early hasn't been called" # debug_zz_loc_ids: Dict[str, int] = {} empty = zz_items[4] multi_item = empty # a different patcher method differentiates empty from ap multi item multi_items: Dict[str, Tuple[str, str]] = {} # zz_loc_name to (item_name, player_name) - for loc in self.multiworld.get_locations(self.player): - z_loc = cast(ZillionLocation, loc) + for z_loc in self.multiworld.get_locations(self.player): + assert isinstance(z_loc, ZillionLocation) # debug_zz_loc_ids[z_loc.zz_loc.name] = id(z_loc.zz_loc) if z_loc.item is None: self.logger.warn("generate_output location has no item - is that ok?") z_loc.zz_loc.item = empty elif z_loc.item.player == self.player: - z_item = cast(ZillionItem, z_loc.item) + z_item = z_loc.item + assert isinstance(z_item, ZillionItem) z_loc.zz_loc.item = z_item.zz_item else: # another player's item # print(f"put multi item in {z_loc.zz_loc.name}") @@ -368,47 +365,32 @@ class ZillionWorld(World): f"in world {self.player} didn't get an item" ) - zz_patcher = self.zz_system.patcher - - zz_patcher.write_locations(self.zz_system.randomizer.regions, - zz_options.start_char, - self.zz_system.randomizer.loc_name_2_pretty) - self.slot_data_ready.set() - rm = self.zz_system.resource_managers - assert rm, "missing resource_managers from generate_early" - zz_patcher.all_fixes_and_options(zz_options, rm) - zz_patcher.set_external_item_interface(zz_options.start_char, zz_options.max_level) - zz_patcher.set_multiworld_items(multi_items) game_id = self.multiworld.player_name[self.player].encode() + b'\x00' + self.multiworld.seed_name[-6:].encode() - zz_patcher.set_rom_to_ram_data(game_id) + + return GenData(multi_items, self.zz_system.get_game(), game_id) def generate_output(self, output_directory: str) -> None: - """This method gets called from a threadpool, do not use world.random here. - If you need any last-second randomization, use MultiWorld.per_slot_randoms[slot] instead.""" - self.finalize_item_locations() - - assert self.zz_system.patcher, "didn't get patcher from finalize_item_locations" - # original_rom_bytes = self.zz_patcher.rom - patched_rom_bytes = self.zz_system.patcher.get_patched_bytes() + """This method gets called from a threadpool, do not use multiworld.random here. + If you need any last-second randomization, use self.random instead.""" + try: + gen_data = self.finalize_item_locations() + except BaseException: + raise + finally: + self.slot_data_ready.set() out_file_base = self.multiworld.get_out_file_name_base(self.player) - filename = os.path.join( - output_directory, - f'{out_file_base}{ZillionDeltaPatch.result_file_ending}' - ) - with open(filename, "wb") as binary_file: - binary_file.write(patched_rom_bytes) - patch = ZillionDeltaPatch( - os.path.splitext(filename)[0] + ZillionDeltaPatch.patch_file_ending, - player=self.player, - player_name=self.multiworld.player_name[self.player], - patched_path=filename - ) + patch_file_name = os.path.join(output_directory, f"{out_file_base}{ZillionPatch.patch_file_ending}") + patch = ZillionPatch(patch_file_name, + player=self.player, + player_name=self.multiworld.player_name[self.player], + gen_data_str=gen_data.to_json()) patch.write() - os.remove(filename) - def fill_slot_data(self) -> Dict[str, Any]: # json of WebHostLib.models.Slot + self.logger.debug(f"Zillion player {self.player} finished generate_output") + + def fill_slot_data(self) -> ZillionSlotInfo: # json of WebHostLib.models.Slot """Fill in the `slot_data` field in the `Connected` network package. This is a way the generator can give custom data to the client. The client will receive this as JSON in the `Connected` response.""" @@ -418,25 +400,10 @@ class ZillionWorld(World): # TODO: tell client which canisters are keywords # so it can open and get those when restoring doors - assert self.zz_system.randomizer, "didn't get randomizer from generate_early" - - rescues: Dict[str, Any] = {} self.slot_data_ready.wait() - zz_patcher = self.zz_system.patcher - assert zz_patcher, "didn't get patcher from generate_output" - for i in (0, 1): - if i in zz_patcher.rescue_locations: - ri = zz_patcher.rescue_locations[i] - rescues[str(i)] = { - "start_char": ri.start_char, - "room_code": ri.room_code, - "mask": ri.mask - } - return { - "start_char": self.zz_system.randomizer.options.start_char, - "rescues": rescues, - "loc_mem_to_id": zz_patcher.loc_memory_to_loc_id - } + assert self.zz_system.randomizer, "didn't get randomizer from generate_early" + game = self.zz_system.get_game() + return get_slot_info(game.regions, game.char_order[0], game.loc_name_2_pretty) # def modify_multidata(self, multidata: Dict[str, Any]) -> None: # """For deeper modification of server multidata.""" diff --git a/worlds/zillion/client.py b/worlds/zillion/client.py index 1a85b9df25..5c2e114530 100644 --- a/worlds/zillion/client.py +++ b/worlds/zillion/client.py @@ -12,11 +12,10 @@ from Utils import async_start import colorama -from zilliandomizer.zri.memory import Memory +from zilliandomizer.zri.memory import Memory, RescueInfo from zilliandomizer.zri import events from zilliandomizer.utils.loc_name_maps import id_to_loc from zilliandomizer.options import Chars -from zilliandomizer.patch import RescueInfo from .id_maps import loc_name_to_id, make_id_to_others from .config import base_id diff --git a/worlds/zillion/gen_data.py b/worlds/zillion/gen_data.py new file mode 100644 index 0000000000..aa24ff8961 --- /dev/null +++ b/worlds/zillion/gen_data.py @@ -0,0 +1,35 @@ +from dataclasses import dataclass +import json +from typing import Dict, Tuple + +from zilliandomizer.game import Game as ZzGame + + +@dataclass +class GenData: + """ data passed from generation to patcher """ + + multi_items: Dict[str, Tuple[str, str]] + """ zz_loc_name to (item_name, player_name) """ + zz_game: ZzGame + game_id: bytes + """ the byte string used to detect the rom """ + + def to_json(self) -> str: + """ serialized data from generation needed to patch rom """ + jsonable = { + "multi_items": self.multi_items, + "zz_game": self.zz_game.to_jsonable(), + "game_id": list(self.game_id) + } + return json.dumps(jsonable) + + @staticmethod + def from_json(gen_data_str: str) -> "GenData": + """ the reverse of `to_json` """ + from_json = json.loads(gen_data_str) + return GenData( + from_json["multi_items"], + ZzGame.from_jsonable(from_json["zz_game"]), + bytes(from_json["game_id"]) + ) diff --git a/worlds/zillion/id_maps.py b/worlds/zillion/id_maps.py index bc9caeeece..32d71fc79b 100644 --- a/worlds/zillion/id_maps.py +++ b/worlds/zillion/id_maps.py @@ -1,10 +1,22 @@ -from typing import Dict, Tuple -from zilliandomizer.logic_components.items import Item as ZzItem, \ - item_name_to_id as zz_item_name_to_zz_id, items as zz_items, \ - item_name_to_item as zz_item_name_to_zz_item +from collections import defaultdict +from typing import Dict, Iterable, Mapping, Tuple, TypedDict + +from zilliandomizer.logic_components.items import ( + Item as ZzItem, + KEYWORD, + NORMAL, + RESCUE, + item_name_to_id as zz_item_name_to_zz_id, + items as zz_items, + item_name_to_item as zz_item_name_to_zz_item, +) +from zilliandomizer.logic_components.regions import RegionData +from zilliandomizer.low_resources.item_rooms import item_room_codes from zilliandomizer.options import Chars from zilliandomizer.utils.loc_name_maps import loc_to_id as pretty_loc_name_to_id -from zilliandomizer.utils import parse_reg_name +from zilliandomizer.utils import parse_loc_name, parse_reg_name +from zilliandomizer.zri.memory import RescueInfo + from .config import base_id as base_id item_name_to_id = { @@ -91,3 +103,56 @@ def zz_reg_name_to_reg_name(zz_reg_name: str) -> str: end = zz_reg_name[5:] return f"{make_room_name(row, col)} {end.upper()}" return zz_reg_name + + +class ClientRescue(TypedDict): + start_char: Chars + room_code: int + mask: int + + +class ZillionSlotInfo(TypedDict): + start_char: Chars + rescues: Dict[str, ClientRescue] + loc_mem_to_id: Dict[int, int] + """ memory location of canister to Archipelago location id number """ + + +def get_slot_info(regions: Iterable[RegionData], + start_char: Chars, + loc_name_to_pretty: Mapping[str, str]) -> ZillionSlotInfo: + items_placed_in_map_index: Dict[int, int] = defaultdict(int) + rescue_locations: Dict[int, RescueInfo] = {} + loc_memory_to_loc_id: Dict[int, int] = {} + for region in regions: + for loc in region.locations: + assert loc.item, ("There should be an item placed in every location before " + f"writing slot info. {loc.name} is missing item.") + if loc.item.code in {KEYWORD, NORMAL, RESCUE}: + row, col, _y, _x = parse_loc_name(loc.name) + map_index = row * 8 + col + item_no = items_placed_in_map_index[map_index] + room_code = item_room_codes[map_index] + + r = room_code + m = 1 << item_no + if loc.item.code == RESCUE: + rescue_locations[loc.item.id] = RescueInfo(start_char, r, m) + loc_memory = (r << 7) | m + loc_memory_to_loc_id[loc_memory] = pretty_loc_name_to_id[loc_name_to_pretty[loc.name]] + items_placed_in_map_index[map_index] += 1 + + rescues: Dict[str, ClientRescue] = {} + for i in (0, 1): + if i in rescue_locations: + ri = rescue_locations[i] + rescues[str(i)] = { + "start_char": ri.start_char, + "room_code": ri.room_code, + "mask": ri.mask + } + return { + "start_char": start_char, + "rescues": rescues, + "loc_mem_to_id": loc_memory_to_loc_id + } diff --git a/worlds/zillion/patch.py b/worlds/zillion/patch.py index 148caac9fb..dcbb85bcfc 100644 --- a/worlds/zillion/patch.py +++ b/worlds/zillion/patch.py @@ -1,22 +1,53 @@ -from typing import BinaryIO, Optional, cast -import Utils -from worlds.Files import APDeltaPatch import os +from typing import Any, BinaryIO, Optional, cast +import zipfile + +from typing_extensions import override + +import Utils +from worlds.Files import APPatch + +from zilliandomizer.patch import Patcher + +from .gen_data import GenData USHASH = 'd4bf9e7bcf9a48da53785d2ae7bc4270' -class ZillionDeltaPatch(APDeltaPatch): +class ZillionPatch(APPatch): hash = USHASH game = "Zillion" patch_file_ending = ".apzl" result_file_ending = ".sms" + gen_data_str: str + """ JSON encoded """ + + def __init__(self, *args: Any, gen_data_str: str = "", **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.gen_data_str = gen_data_str + @classmethod def get_source_data(cls) -> bytes: with open(get_base_rom_path(), "rb") as stream: return read_rom(stream) + @override + def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None: + super().write_contents(opened_zipfile) + opened_zipfile.writestr("gen_data.json", + self.gen_data_str, + compress_type=zipfile.ZIP_DEFLATED) + + @override + def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None: + super().read_contents(opened_zipfile) + self.gen_data_str = opened_zipfile.read("gen_data.json").decode() + + def patch(self, target: str) -> None: + self.read() + write_rom_from_gen_data(self.gen_data_str, target) + def get_base_rom_path(file_name: Optional[str] = None) -> str: options = Utils.get_options() @@ -32,3 +63,21 @@ def read_rom(stream: BinaryIO) -> bytes: data = stream.read() # I'm not aware of any sms header. return data + + +def write_rom_from_gen_data(gen_data_str: str, output_rom_file_name: str) -> None: + """ take the output of `GenData.to_json`, and create rom from it """ + gen_data = GenData.from_json(gen_data_str) + + base_rom_path = get_base_rom_path() + zz_patcher = Patcher(base_rom_path) + + zz_patcher.write_locations(gen_data.zz_game.regions, gen_data.zz_game.char_order[0]) + zz_patcher.all_fixes_and_options(gen_data.zz_game) + zz_patcher.set_external_item_interface(gen_data.zz_game.char_order[0], gen_data.zz_game.options.max_level) + zz_patcher.set_multiworld_items(gen_data.multi_items) + zz_patcher.set_rom_to_ram_data(gen_data.game_id) + + patched_rom_bytes = zz_patcher.get_patched_bytes() + with open(output_rom_file_name, "wb") as binary_file: + binary_file.write(patched_rom_bytes) diff --git a/worlds/zillion/requirements.txt b/worlds/zillion/requirements.txt index c8944925ac..3a784846a8 100644 --- a/worlds/zillion/requirements.txt +++ b/worlds/zillion/requirements.txt @@ -1,2 +1,2 @@ -zilliandomizer @ git+https://github.com/beauxq/zilliandomizer@ae00a4b186be897c7cfaf429a0e0ff83c4ecf28c#0.6.0 +zilliandomizer @ git+https://github.com/beauxq/zilliandomizer@b36a23b5a138c78732ac8efb5b5ca8b0be07dcff#0.7.0 typing-extensions>=4.7, <5 From 37a871eab1b58afc7abd10d40dd1f4a917670b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Bolduc?= <16137441+Jouramie@users.noreply.github.com> Date: Sun, 3 Mar 2024 16:30:51 -0500 Subject: [PATCH 061/166] Core: Allow common collections in OptionSet and OptionList constructors (#2874) * allow common collection in set and list option constructors * allow any iterable of strings * add return None --------- Co-authored-by: beauxq --- Options.py | 25 +++++++++++++------------ Utils.py | 11 +++++++++++ requirements.txt | 1 + 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/Options.py b/Options.py index 5fe4087132..139dc0a0bb 100644 --- a/Options.py +++ b/Options.py @@ -1,19 +1,18 @@ from __future__ import annotations import abc -import logging -from copy import deepcopy -from dataclasses import dataclass import functools +import logging import math import numbers import random import typing from copy import deepcopy +from dataclasses import dataclass from schema import And, Optional, Or, Schema -from Utils import get_fuzzy_results +from Utils import get_fuzzy_results, is_iterable_of_str if typing.TYPE_CHECKING: from BaseClasses import PlandoOptions @@ -59,6 +58,7 @@ class AssembleOptions(abc.ABCMeta): def verify(self, *args, **kwargs) -> None: for f in verifiers: f(self, *args, **kwargs) + attrs["verify"] = verify else: assert verifiers, "class Option is supposed to implement def verify" @@ -183,6 +183,7 @@ class FreeText(Option[str]): class NumericOption(Option[int], numbers.Integral, abc.ABC): default = 0 + # note: some of the `typing.Any`` here is a result of unresolved issue in python standards # `int` is not a `numbers.Integral` according to the official typestubs # (even though isinstance(5, numbers.Integral) == True) @@ -598,7 +599,7 @@ class PlandoBosses(TextChoice, metaclass=BossMeta): if isinstance(self.value, int): return from BaseClasses import PlandoOptions - if not(PlandoOptions.bosses & plando_options): + if not (PlandoOptions.bosses & plando_options): # plando is disabled but plando options were given so pull the option and change it to an int option = self.value.split(";")[-1] self.value = self.options[option] @@ -765,7 +766,7 @@ class VerifyKeys(metaclass=FreezeValidKeys): value: typing.Any @classmethod - def verify_keys(cls, data: typing.List[str]): + def verify_keys(cls, data: typing.Iterable[str]) -> None: if cls.valid_keys: data = set(data) dataset = set(word.casefold() for word in data) if cls.valid_keys_casefold else set(data) @@ -843,11 +844,11 @@ class OptionList(Option[typing.List[typing.Any]], VerifyKeys): # If only unique entries are needed and input order of elements does not matter, OptionSet should be used instead. # Not a docstring so it doesn't get grabbed by the options system. - default: typing.List[typing.Any] = [] + default: typing.Union[typing.List[typing.Any], typing.Tuple[typing.Any, ...]] = () supports_weighting = False - def __init__(self, value: typing.List[typing.Any]): - self.value = deepcopy(value) + def __init__(self, value: typing.Iterable[str]): + self.value = list(deepcopy(value)) super(OptionList, self).__init__() @classmethod @@ -856,7 +857,7 @@ class OptionList(Option[typing.List[typing.Any]], VerifyKeys): @classmethod def from_any(cls, data: typing.Any): - if type(data) == list: + if is_iterable_of_str(data): cls.verify_keys(data) return cls(data) return cls.from_text(str(data)) @@ -882,7 +883,7 @@ class OptionSet(Option[typing.Set[str]], VerifyKeys): @classmethod def from_any(cls, data: typing.Any): - if isinstance(data, (list, set, frozenset)): + if is_iterable_of_str(data): cls.verify_keys(data) return cls(data) return cls.from_text(str(data)) @@ -932,7 +933,7 @@ class OptionsMetaProperty(type): bases: typing.Tuple[type, ...], attrs: typing.Dict[str, typing.Any]) -> "OptionsMetaProperty": for attr_type in attrs.values(): - assert not isinstance(attr_type, AssembleOptions),\ + assert not isinstance(attr_type, AssembleOptions), \ f"Options for {name} should be type hinted on the class, not assigned" return super().__new__(mcs, name, bases, attrs) diff --git a/Utils.py b/Utils.py index da2d837ad3..cea6405a38 100644 --- a/Utils.py +++ b/Utils.py @@ -19,6 +19,7 @@ import warnings from argparse import Namespace from settings import Settings, get_settings from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union +from typing_extensions import TypeGuard from yaml import load, load_all, dump try: @@ -966,3 +967,13 @@ class RepeatableChain: def __len__(self): return sum(len(iterable) for iterable in self.iterable) + + +def is_iterable_of_str(obj: object) -> TypeGuard[typing.Iterable[str]]: + """ but not a `str` (because technically, `str` is `Iterable[str]`) """ + if isinstance(obj, str): + return False + if not isinstance(obj, typing.Iterable): + return False + obj_it: typing.Iterable[object] = obj + return all(isinstance(v, str) for v in obj_it) diff --git a/requirements.txt b/requirements.txt index e2ccb67c18..9531e3058e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ certifi>=2023.11.17 cython>=3.0.8 cymem>=2.0.8 orjson>=3.9.10 +typing-extensions>=4.7.0 From a70b94fd62f6dea76592e6df93deda8645c34d4c Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Sun, 3 Mar 2024 19:52:03 -0500 Subject: [PATCH 062/166] LTTP: Open Pyramid and Shop Prog Balancing Bug Fixes (#2890) --- worlds/alttp/Options.py | 4 ++-- worlds/alttp/Shops.py | 9 ++++++--- worlds/alttp/__init__.py | 8 ++++++-- worlds/alttp/test/options/TestOpenPyramid.py | 2 +- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py index 8cc5d32608..afd5295545 100644 --- a/worlds/alttp/Options.py +++ b/worlds/alttp/Options.py @@ -153,9 +153,9 @@ class OpenPyramid(Choice): def to_bool(self, world: MultiWorld, player: int) -> bool: if self.value == self.option_goal: - return world.goal[player] in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} + return world.goal[player].current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} elif self.value == self.option_auto: - return world.goal[player] in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} \ + return world.goal[player].current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} \ and (world.entrance_shuffle[player] in {'vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'} or not world.shuffle_ganon) elif self.value == self.option_open: diff --git a/worlds/alttp/Shops.py b/worlds/alttp/Shops.py index 64a385a185..1d548d8fdb 100644 --- a/worlds/alttp/Shops.py +++ b/worlds/alttp/Shops.py @@ -176,6 +176,9 @@ def push_shop_inventories(multiworld): get_price(multiworld, location.shop.inventory[location.shop_slot], location.player, location.shop_price_type)[1]) + for world in multiworld.get_game_worlds("A Link to the Past"): + world.pushed_shop_inventories.set() + def create_shops(multiworld, player: int): @@ -451,15 +454,15 @@ def get_price(multiworld, item, player: int, price_type=None): if multiworld.randomize_shop_prices[player]: adjust = 2 if price < 100 else 5 price = int((price / adjust) * (0.5 + multiworld.random.random() * 1.5)) * adjust - multiworld.random.shuffle(price_types) + multiworld.per_slot_randoms[player].shuffle(price_types) for p_type in price_types: if any(x in item['item'] for x in price_blacklist[p_type]): continue return p_type, price_chart[p_type](price, diff) else: # This is an AP location and the price will be adjusted after an item is shuffled into it - p_type = multiworld.random.choice(price_types) - return p_type, price_chart[p_type](min(int(multiworld.random.randint(8, 56) + p_type = multiworld.per_slot_randoms[player].choice(price_types) + return p_type, price_chart[p_type](min(int(multiworld.per_slot_randoms[player].randint(8, 56) * multiworld.shop_price_modifier[player] / 100) * 5, 9999), diff) diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 7a2664b3f4..a7ade61c9e 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -256,6 +256,7 @@ class ALTTPWorld(World): self.dungeon_local_item_names = set() self.dungeon_specific_item_names = set() self.rom_name_available_event = threading.Event() + self.pushed_shop_inventories = threading.Event() self.has_progressive_bows = False self.dungeons = {} self.waterfall_fairy_bottle_fill = "Bottle" @@ -508,8 +509,8 @@ class ALTTPWorld(World): fill_dungeons_restrictive(world) @classmethod - def stage_post_fill(cls, world): - push_shop_inventories(world) + def stage_generate_output(cls, multiworld, output_directory): + push_shop_inventories(multiworld) @property def use_enemizer(self) -> bool: @@ -523,6 +524,9 @@ class ALTTPWorld(World): def generate_output(self, output_directory: str): multiworld = self.multiworld player = self.player + + self.pushed_shop_inventories.wait() + try: use_enemizer = self.use_enemizer diff --git a/worlds/alttp/test/options/TestOpenPyramid.py b/worlds/alttp/test/options/TestOpenPyramid.py index c66eb2ee98..895ecb95a9 100644 --- a/worlds/alttp/test/options/TestOpenPyramid.py +++ b/worlds/alttp/test/options/TestOpenPyramid.py @@ -23,7 +23,7 @@ class GoalPyramidTest(PyramidTestBase): } def testCrystalsGoalAccess(self): - self.multiworld.goal[1] = "crystals" + self.multiworld.goal[1].value = 1 # crystals self.assertFalse(self.can_reach_entrance("Pyramid Hole")) self.collect_by_name(["Hammer", "Progressive Glove", "Moon Pearl"]) self.assertTrue(self.can_reach_entrance("Pyramid Hole")) From ecec931e9f40838cee5a735313fe2219b4038977 Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Sun, 3 Mar 2024 23:26:52 -0800 Subject: [PATCH 063/166] Core: fix (typing) mistake in PR #2887 (#2891) I made this variable for more compatible and safer type narrowing, and then I didn't use if for the type narrowing. --- worlds/Files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/Files.py b/worlds/Files.py index dbeb54cfde..f46b9cba7a 100644 --- a/worlds/Files.py +++ b/worlds/Files.py @@ -102,7 +102,7 @@ class APContainer: message = "" if len(e.args): arg0 = e.args[0] - if isinstance(e.args[0], str): + if isinstance(arg0, str): message = f"{arg0} - " raise InvalidDataError(f"{message}This might be the incorrect world version for this file") from e From b9d561ae25187f0b8abfa926ec1dd7c5f4563480 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Mon, 4 Mar 2024 21:55:46 -0500 Subject: [PATCH 064/166] Core: Update generic.Rules.py (#2896) --- worlds/generic/Rules.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/generic/Rules.py b/worlds/generic/Rules.py index ac5e1aa507..c434351e94 100644 --- a/worlds/generic/Rules.py +++ b/worlds/generic/Rules.py @@ -41,12 +41,12 @@ def locality_rules(world: MultiWorld): forbid_data[sender][receiver].update(items) for receiving_player in world.player_ids: - local_items: typing.Set[str] = world.local_items[receiving_player].value + local_items: typing.Set[str] = world.worlds[receiving_player].options.local_items.value if local_items: for sending_player in world.player_ids: if receiving_player != sending_player: forbid(sending_player, receiving_player, local_items) - non_local_items: typing.Set[str] = world.non_local_items[receiving_player].value + non_local_items: typing.Set[str] = world.worlds[receiving_player].options.non_local_items.value if non_local_items: forbid(receiving_player, receiving_player, non_local_items) From 12cc93082557e939b119f3ee2ea449b7f829b7e8 Mon Sep 17 00:00:00 2001 From: Justus Lind Date: Tue, 5 Mar 2024 18:33:15 +1000 Subject: [PATCH 065/166] Muse Dash: Add Muse Dash 4.1.0 songs (#2878) --- worlds/musedash/MuseDashCollection.py | 9 +++++++++ worlds/musedash/MuseDashData.txt | 10 +++++++++- worlds/musedash/test/TestCollection.py | 7 +------ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/worlds/musedash/MuseDashCollection.py b/worlds/musedash/MuseDashCollection.py index cc4cc71ce3..20bb8deceb 100644 --- a/worlds/musedash/MuseDashCollection.py +++ b/worlds/musedash/MuseDashCollection.py @@ -37,6 +37,12 @@ class MuseDashCollections: "PeroPero in the Universe", "umpopoff" ] + + REMOVED_SONGS = [ + "CHAOS Glitch", + "FM 17314 SUGAR RADIO", + "Yume Ou Mono Yo Secret" + ] album_items: Dict[str, AlbumData] = {} album_locations: Dict[str, int] = {} @@ -130,6 +136,9 @@ class MuseDashCollections: for songKey, songData in self.song_items.items(): if not self.song_matches_dlc_filter(songData, dlc_songs): continue + + if songKey in self.REMOVED_SONGS: + continue if streamer_mode_active and not songData.streamer_mode: continue diff --git a/worlds/musedash/MuseDashData.txt b/worlds/musedash/MuseDashData.txt index ce5929bfd0..620c1968bd 100644 --- a/worlds/musedash/MuseDashData.txt +++ b/worlds/musedash/MuseDashData.txt @@ -529,4 +529,12 @@ Dance of the Corpses|70-5|Rin Len's Mirrorland|False|2|5|8| Bitter Choco Decoration|70-6|Rin Len's Mirrorland|False|3|6|9| Dance Robot Dance|70-7|Rin Len's Mirrorland|False|4|7|10| Sweet Devil|70-8|Rin Len's Mirrorland|False|5|7|9| -Someday'z Coming|70-9|Rin Len's Mirrorland|False|5|7|9| \ No newline at end of file +Someday'z Coming|70-9|Rin Len's Mirrorland|False|5|7|9| +Yume Ou Mono Yo Secret|0-53|Default Music|True|6|8|10| +Yume Ou Mono Yo|0-54|Default Music|True|1|4|0| +Sweet Dream VIVINOS|71-0|Valentine Stage|False|1|4|7| +Ruler Of My Heart VIVINOS|71-1|Valentine Stage|False|2|4|6| +Reality Show|71-2|Valentine Stage|False|5|7|10| +SIG feat.Tobokegao|71-3|Valentine Stage|True|3|6|8| +Rose Love|71-4|Valentine Stage|True|2|4|7| +Euphoria|71-5|Valentine Stage|True|1|3|6| \ No newline at end of file diff --git a/worlds/musedash/test/TestCollection.py b/worlds/musedash/test/TestCollection.py index f9422388ae..48cb69e403 100644 --- a/worlds/musedash/test/TestCollection.py +++ b/worlds/musedash/test/TestCollection.py @@ -3,11 +3,6 @@ from ..MuseDashCollection import MuseDashCollections class CollectionsTest(unittest.TestCase): - REMOVED_SONGS = [ - "CHAOS Glitch", - "FM 17314 SUGAR RADIO", - ] - def test_all_names_are_ascii(self) -> None: bad_names = list() collection = MuseDashCollections() @@ -58,5 +53,5 @@ class CollectionsTest(unittest.TestCase): collection = MuseDashCollections() songs = collection.get_songs_with_settings({x for x in collection.DLC}, False, 0, 12) - for song_name in self.REMOVED_SONGS: + for song_name in collection.REMOVED_SONGS: self.assertNotIn(song_name, songs, f"Song '{song_name}' wasn't removed correctly.") From 26ee9fe05cefea24c907a9a71941846dcf6db6bd Mon Sep 17 00:00:00 2001 From: Remy Jette Date: Tue, 5 Mar 2024 00:36:18 -0800 Subject: [PATCH 066/166] Pokemon RB: Fix exceptions raised by /bank (#2836) * If the user tried to run `/bank` with no arguments to see the current value while disconnected, previously it threw an exception `KeyError: 'EnergyLinkNone'`. Now it informs the user that they must be connected and in-game, like `/bank deposit` and `/bank withdraw` do. I'm also open to adding another `if` branch to make `/bank` only check for `ctx.server` instead of combining it with the other bank commands, to allow connecting to check the bank before the game save is loaded. If that's preferred let me know. * If the user tried to run `/bank` or `/bank deposit` when the EnergyLink hadn't been used yet, they would get a `TypeError` exception. Trying `/bank withdraw` would give no output and would crash the lua connector script. Now it treats a `None` EnergyLink as `0` and works properly. --- worlds/pokemon_rb/client.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/worlds/pokemon_rb/client.py b/worlds/pokemon_rb/client.py index 9e2689bccc..8ed21443e0 100644 --- a/worlds/pokemon_rb/client.py +++ b/worlds/pokemon_rb/client.py @@ -206,7 +206,7 @@ class PokemonRBClient(BizHawkClient): money = int(original_money.hex()) if self.banking_command > money: logger.warning(f"You do not have ${self.banking_command} to deposit!") - elif (-self.banking_command * BANK_EXCHANGE_RATE) > ctx.stored_data[f"EnergyLink{ctx.team}"]: + elif (-self.banking_command * BANK_EXCHANGE_RATE) > (ctx.stored_data[f"EnergyLink{ctx.team}"] or 0): logger.warning("Not enough money in the EnergyLink storage!") else: if self.banking_command + money > 999999: @@ -258,11 +258,12 @@ def cmd_bank(self, cmd: str = "", amount: str = ""): if self.ctx.game != "Pokemon Red and Blue": logger.warning("This command can only be used while playing Pokémon Red and Blue") return - if not cmd: - logger.info(f"Money available: {int(self.ctx.stored_data[f'EnergyLink{self.ctx.team}'] / BANK_EXCHANGE_RATE)}") - return - elif (not self.ctx.server) or self.ctx.server.socket.closed or not self.ctx.client_handler.game_state: + if (not self.ctx.server) or self.ctx.server.socket.closed or not self.ctx.client_handler.game_state: logger.info(f"Must be connected to server and in game.") + return + elif not cmd: + logger.info(f"Money available: {int((self.ctx.stored_data[f'EnergyLink{self.ctx.team}'] or 0) / BANK_EXCHANGE_RATE)}") + return elif not amount: logger.warning("You must specify an amount.") elif cmd == "withdraw": From bcbb06d78d36efcef268e6a460bcf88423a347e7 Mon Sep 17 00:00:00 2001 From: Nicholas Brochu Date: Tue, 5 Mar 2024 10:46:09 -0500 Subject: [PATCH 067/166] Fix usage of `__new__` for `SpecialRange` compatibility fallback (#2513) --- Options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Options.py b/Options.py index 139dc0a0bb..ff8ad11c5a 100644 --- a/Options.py +++ b/Options.py @@ -728,7 +728,7 @@ class SpecialRange(NamedRange): "Consider switching to NamedRange, which supports all use-cases of SpecialRange, and more. In " "NamedRange, range_start specifies the lower end of the regular range, while special values can be " "placed anywhere (below, inside, or above the regular range).") - return super().__new__(cls, value) + return super().__new__(cls) @classmethod def weighted_range(cls, text) -> Range: From 7384bbdf23d380ad7da3b0d16a01c2743654af98 Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Tue, 5 Mar 2024 08:48:37 -0700 Subject: [PATCH 068/166] BizHawkClient: Add README (#2689) Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> --- worlds/_bizhawk/README.md | 279 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 worlds/_bizhawk/README.md diff --git a/worlds/_bizhawk/README.md b/worlds/_bizhawk/README.md new file mode 100644 index 0000000000..ddc70c3dd7 --- /dev/null +++ b/worlds/_bizhawk/README.md @@ -0,0 +1,279 @@ +# BizHawk Client + +`BizHawkClient` is an abstract base class for a client that can access the memory of a ROM running in BizHawk. It does +the legwork of connecting Python to a Lua connector script, letting you focus on the loop of checking locations and +making on-the-fly modifications based on updates from the server. It also provides the same experience to users across +multiple games that use it, and was built in response to a growing number of similar but separate bespoke game clients +which are/were largely exclusive to BizHawk anyway. + +It's similar to `SNIClient`, but where `SNIClient` is designed to work for specifically SNES games across different +emulators/hardware, `BizHawkClient` is designed to work for specifically BizHawk across the different systems BizHawk +supports. + +The idea is that `BizHawkClient` connects to and communicates with a Lua script running in BizHawk. It provides an API +that will call BizHawk functions for you to do things like read and write memory. And on an interval, control will be +handed to a function you write for your game (`game_watcher`) which should interact with the game's memory to check what +locations have been checked, give the player items, detect and send deathlinks, etc... + +Table of Contents: +- [Connector Requests](#connector-requests) + - [Requests that depend on other requests](#requests-that-depend-on-other-requests) +- [Implementing a Client](#implementing-a-client) + - [Example](#example) +- [Tips](#tips) + +## Connector Requests + +Communication with BizHawk is done through `connector_bizhawk_generic.lua`. The client sends requests to the Lua script +via sockets; the Lua script processes the request and sends the corresponding responses. + +The Lua script includes its own documentation, but you probably don't need to worry about the specifics. Instead, you'll +be using the functions in `worlds/_bizhawk/__init__.py`. If you do need more control over the specific requests being +sent or their order, you can still use `send_requests` to directly communicate with the connector script. + +It's not necessary to use the UI or client context if you only want to interact with the connector script. You can +import and use just `worlds/_bizhawk/__init__.py`, which only depends on default modules. + +Here's a list of the included classes and functions. I would highly recommend looking at the actual function signatures +and docstrings to learn more about each function. + +``` +class ConnectionStatus +class BizHawkContext + +class NotConnectedError +class RequestFailedError +class ConnectorError +class SyncError + +async def read(ctx, read_list) -> list[bytes] +async def write(ctx, write_list) -> None: +async def guarded_read(ctx, read_list, guard_list) -> (list[bytes] | None) +async def guarded_write(ctx, write_list, guard_list) -> bool + +async def lock(ctx) -> None +async def unlock(ctx) -> None + +async def get_hash(ctx) -> str +async def get_system(ctx) -> str +async def get_cores(ctx) -> dict[str, str] +async def ping(ctx) -> None + +async def display_message(ctx, message: str) -> None +async def set_message_interval(ctx, value: float) -> None + +async def connect(ctx) -> bool +def disconnect(ctx) -> None + +async def get_script_version(ctx) -> int +async def send_requests(ctx, req_list) -> list[dict[str, Any]] +``` + +`send_requests` is what actually communicates with the connector, and any functions like `guarded_read` will build the +requests and then call `send_requests` for you. You can call `send_requests` yourself for more direct control, but make +sure to read the docs in `connector_bizhawk_generic.lua`. + +A bundle of requests sent by `send_requests` will all be executed on the same frame, and by extension, so will any +helper that calls `send_requests`. For example, if you were to call `read` with 3 items on your `read_list`, all 3 +addresses will be read on the same frame and then sent back. + +It also means that, by default, the only way to run multiple requests on the same frame is for them to be included in +the same `send_requests` call. As soon as the connector finishes responding to a list of requests, it will advance the +frame before checking for the next batch. + +### Requests that depend on other requests + +The fact that you have to wait at least a frame to act on any response may raise concerns. For example, Pokemon +Emerald's save data is at a dynamic location in memory; it moves around when you load a new map. There is a static +variable that holds the address of the save data, so we want to read the static variable to get the save address, and +then use that address in a `write` to send the player an item. But between the `read` that tells us the address of the +save data and the `write` to save data itself, an arbitrary number of frames have been executed, and the player may have +loaded a new map, meaning we've written data to who knows where. + +There are two solutions to this problem. + +1. Use `guarded_write` instead of `write`. We can include a guard against the address changing, and the script will only +perform the write if the data in memory matches what's in the guard. In the below example, `write_result` will be `True` +if the guard validated and the data was written, and `False` if the guard failed to validate. + +```py +# Get the address of the save data +read_result: bytes = (await _bizhawk.read(ctx, [(0x3001111, 4, "System Bus")]))[0] +save_data_address = int.from_bytes(read_result, "little") + +# Write to `save_data_address` if it hasn't changed +write_result: bool = await _bizhawk.guarded_write( + ctx, + [(save_data_address, [0xAA, 0xBB], "System Bus")], + [(0x3001111, read_result, "System Bus")] +) + +if write_result: + # The data at 0x3001111 was still the same value as + # what was returned from the first `_bizhawk.read`, + # so the data was written. + ... +else: + # The data at 0x3001111 has changed since the + # first `_bizhawk.read`, so the data was not written. + ... +``` + +2. Use `lock` and `unlock` (discouraged if not necessary). When you call `lock`, you tell the emulator to stop advancing +frames and just process requests until it receives an unlock request. This means you can lock, read the address, write +the data, and then unlock on a single frame. **However**, this is _slow_. If you can't get in and get out quickly +enough, players will notice a stutter in the emulation. + +```py +# Pause emulation +await _bizhawk.lock(ctx) + +# Get the address of the save data +read_result: bytes = (await _bizhawk.read(ctx, [(0x3001111, 4, "System Bus")]))[0] +save_data_address = int.from_bytes(read_result, "little") + +# Write to `save_data_address` +await _bizhawk.write(ctx, [(save_data_address, [0xAA, 0xBB], "System Bus")]) + +# Resume emulation +await _bizhawk.unlock(ctx) +``` + +You should always use `guarded_read` and `guarded_write` instead of locking the emulator if possible. It may be +unreliable, but that's by design. Most of the time you should have no problem giving up and retrying. Data that is +volatile but only changes occasionally is the perfect use case. + +If data is almost guaranteed to change between frames, locking may be the better solution. You can lower the time spent +locked by using `send_requests` directly to include as many requests alongside the `LOCK` and `UNLOCK` requests as +possible. But in general it's probably worth doing some extra asm hacking and designing to make guards work instead. + +## Implementing a Client + +`BizHawkClient` itself is built on `CommonClient` and inspired heavily by `SNIClient`. Your world's client should +inherit from `BizHawkClient` in `worlds/_bizhawk/client.py`. It must implement `validate_rom` and `game_watcher`, and +must define values for `system` and `game`. + +As with the functions and classes in the previous section, I would highly recommend looking at the types and docstrings +of the code itself. + +`game` should be the same value you use for your world definition. + +`system` can either be a string or a tuple of strings. This is the system (or systems) that your client is intended to +handle games on (SNES, GBA, etc.). It's used to prevent validators from running on unknown systems and crashing. The +actual abbreviation corresponds to whatever BizHawk returns from `emu.getsystemid()`. + +`patch_suffix` is an optional `ClassVar` meant to specify the file extensions you want to register. It can be a string +or tuple of strings. When a player clicks "Open Patch" in a launcher, the suffix(es) will be whitelisted in the file +select dialog and they will be associated with BizHawkClient. This does not affect whether the user's computer will +associate the file extension with Archipelago. + +`validate_rom` is called to figure out whether a given ROM belongs to your client. It will only be called when a ROM is +running on a system you specified in your `system` class variable. In most cases, that will be a single system and you +can be sure that you're not about to try to read from nonexistent domains or out of bounds. If you decide to claim this +ROM as yours, this is where you should do setup for things like `items_handling`. + +`game_watcher` is the "main loop" of your client where you should be checking memory and sending new items to the ROM. +`BizHawkClient` will make sure that your `game_watcher` only runs when your client has validated the ROM, and will do +its best to make sure you're connected to the connector script before calling your watcher. It runs this loop either +immediately once it receives a message from the server, or a specified amount of time after the last iteration of the +loop finished. + +`validate_rom`, `game_watcher`, and other methods will be passed an instance of `BizHawkClientContext`, which is a +subclass of `CommonContext`. It additionally includes `slot_data` (if you are connected and asked for slot data), +`bizhawk_ctx` (the instance of `BizHawkContext` that you should be giving to functions like `guarded_read`), and +`watcher_timeout` (the amount of time in seconds between iterations of the game watcher loop). + +### Example + +A very simple client might look like this. All addresses here are made up; you should instead be using addresses that +make sense for your specific ROM. The `validate_rom` here tries to read the name of the ROM. If it gets the value it +wanted, it sets a couple values on `ctx` and returns `True`. The `game_watcher` reads some data from memory and acts on +it by sending messages to AP. You should be smarter than this example, which will send `LocationChecks` messages even if +there's nothing new since the last loop. + +```py +from typing import TYPE_CHECKING + +from NetUtils import ClientStatus + +import worlds._bizhawk as bizhawk +from worlds._bizhawk.client import BizHawkClient + +if TYPE_CHECKING: + from worlds._bizhawk.context import BizHawkClientContext + + +class MyGameClient(BizHawkClient): + game = "My Game" + system = "GBA" + patch_suffix = ".apextension" + + async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: + try: + # Check ROM name/patch version + rom_name = ((await bizhawk.read(ctx.bizhawk_ctx, [(0x100, 6, "ROM")]))[0]).decode("ascii") + if rom_name != "MYGAME": + return False # Not a MYGAME ROM + except bizhawk.RequestFailedError: + return False # Not able to get a response, say no for now + + # This is a MYGAME ROM + ctx.game = self.game + ctx.items_handling = 0b001 + ctx.want_slot_data = True + + return True + + async def game_watcher(self, ctx: "BizHawkClientContext") -> None: + try: + # Read save data + save_data = await bizhawk.read( + ctx.bizhawk_ctx, + [(0x3000100, 20, "System Bus")] + )[0] + + # Check locations + if save_data[2] & 0x04: + await ctx.send_msgs([{ + "cmd": "LocationChecks", + "locations": [23] + }]) + + # Send game clear + if not ctx.finished_game and (save_data[5] & 0x01): + await ctx.send_msgs([{ + "cmd": "StatusUpdate", + "status": ClientStatus.CLIENT_GOAL + }]) + + except bizhawk.RequestFailedError: + # The connector didn't respond. Exit handler and return to main loop to reconnect + pass +``` + +### Tips + +- Make sure your client gets imported when your world is imported. You probably don't need to actually use anything in +your `client.py` elsewhere, but you still have to import the file for your client to register itself. +- When it comes to performance, there are two directions to optimize: + 1. If you need to execute multiple commands on the same frame, do as little work as possible. Only read and write necessary data, + and if you have to use locks, unlock as soon as it's okay to advance frames. This is probably the obvious one. + 2. Multiple things that don't have to happen on the same frame should be split up if they're likely to be slow. + Remember, the game watcher runs only a few times per second. Extra function calls on the client aren't that big of a + deal; the player will not notice if your `game_watcher` is slow. But the emulator has to be done with any given set of + commands in 1/60th of a second to avoid hiccups (faster still if your players use speedup). Too many reads of too much + data at the same time is more likely to cause a bad user experience. +- Your `game_watcher` will be called regardless of the status of the client's connection to the server. Double-check the +server connection before trying to interact with it. +- By default, the player will be asked to provide their slot name after connecting to the server and validating, and +that input will be used to authenticate with the `Connect` command. You can override `set_auth` in your own client to +set it automatically based on data in the ROM or on your client instance. +- You can override `on_package` in your client to watch raw packages, but don't forget you also have access to a +subclass of `CommonContext` and its API. +- You can import `BizHawkClientContext` for type hints using `typing.TYPE_CHECKING`. Importing it without conditions at +the top of the file will probably cause a circular dependency. +- Your game's system may have multiple usable cores in BizHawk. You can use `get_cores` to try to determine which one is +currently loaded (it's the best we can do). Some cores may differ in the names of memory domains. It's good to check all +the available cores to find differences before your users do. +- The connector script includes a DEBUG variable that you can use to log requests/responses. (Be aware that as the log +grows in size in BizHawk, it begins to stutter while trying to print it.) From ce43c5258975b4338c53c942be00f5895e6d84bd Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:49:34 +0100 Subject: [PATCH 069/166] Doc: fix typo in commands_en.md (#2765) --- worlds/generic/docs/commands_en.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worlds/generic/docs/commands_en.md b/worlds/generic/docs/commands_en.md index 3e7c0bd4bd..fe12f10ee3 100644 --- a/worlds/generic/docs/commands_en.md +++ b/worlds/generic/docs/commands_en.md @@ -42,9 +42,9 @@ including the exclamation point. ### Collect/Release - `!collect` Grants you all the remaining items for your world by collecting them from all games. Typically used after -goal completion. -- `!release` Releases all items contained in your world to other worlds. Typically, done automatically by the sever, but -can be configured to allow/require manual usage of this command. + goal completion. +- `!release` Releases all items contained in your world to other worlds. Typically, done automatically by the server, + but can be configured to allow/require manual usage of this command. ### Cheats - `!getitem ` Cheats an item to the currently connected slot, if it is enabled in the server. From 45a15004a48d99c79939ed8bcdb28abf093562eb Mon Sep 17 00:00:00 2001 From: Silent <110704408+silent-destroyer@users.noreply.github.com> Date: Tue, 5 Mar 2024 10:54:18 -0500 Subject: [PATCH 070/166] TUNIC: Update setup guide and game page docs (#2832) --- worlds/tunic/docs/en_TUNIC.md | 16 +++++++++----- worlds/tunic/docs/setup_en.md | 41 +++++++++++++++++++---------------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/worlds/tunic/docs/en_TUNIC.md b/worlds/tunic/docs/en_TUNIC.md index e957f9eafa..1204f2ef4c 100644 --- a/worlds/tunic/docs/en_TUNIC.md +++ b/worlds/tunic/docs/en_TUNIC.md @@ -14,13 +14,15 @@ It is recommended that you achieve both endings in the vanilla game before playi In the TUNIC Randomizer, every item in the game is randomized. All chests, key item pickups, instruction manual pages, hero relics, and other unique items are shuffled.
-Ability shuffling is an option available from the options page to shuffle certain abilities (prayer, holy cross, and the ice rod combo), +Ability shuffling is an option available from the options page to shuffle certain abilities (prayer, holy cross, and the icebolt combo), preventing them from being used until they are unlocked.
+Entrances can also be randomized, shuffling the connections between every door, teleporter, etc. in the game. + Enemy randomization and other options are also available and can be turned on in the client mod. ## What is the goal of TUNIC when randomized? -The standard goal is the same as the vanilla game, which is to find the three hexagon keys, at which point you may either Take Your +The standard goal is the same as the vanilla game. Find the three hexagon keys, then Take Your Rightful Place or seek another path and Share Your Wisdom. Alternatively, Hexagon Quest is a mode that shuffles a certain number of Gold Questagons into the item pool, with the goal @@ -44,12 +46,16 @@ There is a [tracker pack](https://github.com/SapphireSapphic/TunicTracker/releas There is also a [standalone item tracker](https://github.com/radicoon/tunic-rando-tracker/releases/latest), which tracks what items you have received. It is great for adding an item overlay to streaming setups. This item tracker was created by Radicoon. +There is an [entrance tracker](https://scipiowright.gitlab.io/tunic-tracker/) for the entrance randomizer. This is a manual tracker that runs in your browser. This tracker was created by ScipioWright, and is a fork of the Pokémon Tracker by [Sergi "Sekii" Santana](https://gitlab.com/Sekii/pokemon-tracker). + +You can also use the Universal Tracker (by Faris and qwint) to find a complete list of what checks are in logic with your current items. You can find it on the Archipelago Discord, in its post in the future-game-design channel. This tracker is an extension of the regular Archipelago Text Client. + ## What should I know regarding logic? - Nighttime is not considered in logic. Every check in the game is obtainable during the day. - The Cathedral is accessible during the day by using the Hero's Laurels to reach the Overworld fuse near the Swamp entrance. - The Secret Legend chest at the Cathedral can be obtained during the day by opening the Holy Cross door from the outside. -For Entrance Rando specifically: +For the Entrance Randomizer: - Activating a fuse to turn on a yellow teleporter pad also activates its counterpart in the Far Shore. - The West Garden fuse can be activated from below. - You can pray at the tree at the exterior of the Library. @@ -58,7 +64,7 @@ For Entrance Rando specifically: - The elevator in Cathedral is immediately usable without activating the fuse. Activating the fuse does nothing. ## What item groups are there? -Bombs, consumables (non-bomb ones), weapons, melee weapons (stick and sword), keys, hexagons, offerings, hero relics, cards, golden treasures, money, pages, and abilities (the three ability pages). There are also a few groups being used for singular items: laurels, orb, dagger, magic rod, holy cross, prayer, ice rod, and progressive sword. +Bombs, consumables (non-bomb ones), weapons, melee weapons (stick and sword), keys, hexagons, offerings, hero relics, cards, golden treasures, money, pages, and abilities (the three ability pages). There are also a few groups being used for singular items: laurels, orb, dagger, magic rod, holy cross, prayer, icebolt, and progressive sword. ## What location groups are there? -Holy cross (for all holy cross checks), fairies (for the two fairy checks), well (for the coin well checks), and shop. Additionally, for checks that do not fall into the above categories, the name of the region is the name of the location group. +Holy cross (for all holy cross checks), fairies (for the two fairy checks), well (for the coin well checks), and shop. Additionally, for checks that do not fall into the above categories, the name of the region is the name of the location group. \ No newline at end of file diff --git a/worlds/tunic/docs/setup_en.md b/worlds/tunic/docs/setup_en.md index 3c13331fe5..5ec41e8d52 100644 --- a/worlds/tunic/docs/setup_en.md +++ b/worlds/tunic/docs/setup_en.md @@ -1,16 +1,17 @@ # TUNIC Setup Guide -## Installation - -### Required Software +## Required Software - [TUNIC](https://tunicgame.com/) for PC (Steam Deck also supported) -- [BepInEx](https://builds.bepinex.dev/projects/bepinex_be/572/BepInEx_UnityIL2CPP_x64_9c2b17f_6.0.0-be.572.zip) -- [TUNIC Randomizer Archipelago Mod](https://github.com/silent-destroyer/tunic-randomizer-archipelago/releases/latest) +- [BepInEx (Unity IL2CPP)](https://github.com/BepInEx/BepInEx/releases/tag/v6.0.0-pre.1) +- [TUNIC Randomizer Mod](https://github.com/silent-destroyer/tunic-randomizer/releases/latest) -### Optional Software +## Optional Software - [TUNIC Randomizer Map Tracker](https://github.com/SapphireSapphic/TunicTracker/releases/latest) (For use with EmoTracker/PopTracker) - [TUNIC Randomizer Item Auto-tracker](https://github.com/radicoon/tunic-rando-tracker/releases/latest) +- [Archipelago Text Client](https://github.com/ArchipelagoMW/Archipelago/releases/latest) + +## Installation ### Find Your Relevant Game Directories @@ -24,29 +25,30 @@ Find your TUNIC game installation directory: ### Install BepInEx -BepInEx is a general purpose framework for modding Unity games, and is used by the TUNIC Randomizer. +BepInEx is a general purpose framework for modding Unity games, and is used to run the TUNIC Randomizer. -Download [BepInEx](https://builds.bepinex.dev/projects/bepinex_be/572/BepInEx_UnityIL2CPP_x64_9c2b17f_6.0.0-be.572.zip). +Download [BepInEx](https://github.com/BepInEx/BepInEx/releases/download/v6.0.0-pre.1/BepInEx_UnityIL2CPP_x64_6.0.0-pre.1.zip). If playing on Steam Deck, follow this [guide to set up BepInEx via Proton](https://docs.bepinex.dev/articles/advanced/proton_wine.html). Extract the contents of the BepInEx .zip file into your TUNIC game directory:
- **Steam**: Steam\steamapps\common\TUNIC
- **PC Game Pass**: XboxGames\Tunic\Content
-- **Other platforms**: Place into the same folder that the Tunic_Data/Secret Legend_Data folder is found. +- **Other platforms**: Place into the same folder that the Tunic_Data or Secret Legend_Data folder is found. Launch the game once and close it to finish the BepInEx installation. -### Install The TUNIC Randomizer Archipelago Client Mod +### Install The TUNIC Randomizer Mod -Download the latest release of the [TUNIC Randomizer Archipelago Mod](https://github.com/silent-destroyer/tunic-randomizer-archipelago/releases/latest). +Download the latest release of the [TUNIC Randomizer Mod](https://github.com/silent-destroyer/tunic-randomizer/releases/latest). -The downloaded .zip will contain a folder called `Tunic Archipelago`. +Extract the contents of the downloaded .zip file, and find the folder labeled `Tunic Randomizer`. -Copy the `Tunic Archipelago` folder into `BepInEx/plugins` in your TUNIC game installation directory. -The filepath to the mod should look like `BepInEx/plugins/Tunic Archipelago/TunicArchipelago.dll`
+Copy the `Tunic Randomizer` folder into `BepInEx/plugins` in your TUNIC game installation directory. -Launch the game, and if everything was installed correctly you should see `Randomizer + Archipelago Mod Ver. x.y.z` in the top left corner of the title screen! +The filepath to the mod should look like `BepInEx/plugins/Tunic Randomizer/TunicRandomizer.dll`
+ +Launch the game, and if everything was installed correctly you should see `Randomizer Mod Ver. x.y.z` in the top left corner of the title screen! ## Configure Archipelago Options @@ -55,11 +57,12 @@ Launch the game, and if everything was installed correctly you should see `Rando Visit the [TUNIC options page](/games/Tunic/player-options) to generate a YAML with your selected options. ### Configure Your Mod Settings -Launch the game and click the button labeled `Open AP Config` on the Title Screen. -In the menu that opens, fill in *Player*, *Hostname*, *Port*, and *Password* (if required) with the correct information for your room. +Launch the game, and using the menu on the Title Screen select `Archipelago` under `Randomizer Mode`. -Once you've input your information, click on Close. If everything was configured properly, you should see `Status: Connected!` and your chosen game options will be shown under `World Settings`. +Click the button labeled `Edit AP Config`, and fill in *Player*, *Hostname*, *Port*, and *Password* (if required) with the correct information for your room. + +Once you've input your information, click the `Close` button. If everything was configured properly, you should see `Status: Connected!` and your chosen game options will be shown under `World Settings`. An error message will display if the game fails to connect to the server. -Be sure to also look at the in-game options menu for a variety of additional settings, such as enemy randomization! +Be sure to also look at the in-game options menu for a variety of additional settings, such as enemy randomization! \ No newline at end of file From af4172f32fa29c364f1bc62eefbee80df601643c Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:55:12 +0100 Subject: [PATCH 071/166] Docs: Add review expectations to contributing.md (#2843) Co-authored-by: BadMagic100 Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- docs/contributing.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/contributing.md b/docs/contributing.md index 9b5f93e198..e7f3516712 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -17,7 +17,17 @@ It is recommended that automated github actions are turned on in your fork to ha You can turn them on here: ![Github actions example](./img/github-actions-example.png) -Other than these requests, we tend to judge code on a case by case basis. +* **When reviewing PRs, please leave a message about what was done.** +We don't have full test coverage, so manual testing can help. +For code changes that could affect multiple worlds or that could have changes in unexpected code paths, manual testing +or checking if all code paths are covered by automated tests is desired. The original author may not have been able +to test all possibly affected worlds, or didn't know it would affect another world. In such cases, it is helpful to +state which games or settings were rolled, if any. +Please also tell us if you looked at code, just did functional testing, did both, or did neither. +If testing the PR depends on other PRs, please state what you merged into what for testing. +We cannot determine what "LGTM" means without additional context, so that should not be the norm. + +Other than these requests, we tend to judge code on a case-by-case basis. For contribution to the website, please refer to the [WebHost README](/WebHostLib/README.md). From 644f75978d879cd490576ee7d852abcd9d2c4c20 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:59:55 -0600 Subject: [PATCH 072/166] Kirby's Dream Land 3: Implement New Game (#2119) Co-authored-by: Alchav <59858495+Alchav@users.noreply.github.com> Co-authored-by: Aaron Wagener Co-authored-by: Doug Hoskisson Co-authored-by: Fabian Dill --- README.md | 1 + docs/CODEOWNERS | 3 + inno_setup.iss | 5 + worlds/LauncherComponents.py | 2 +- worlds/generic/docs/plando_en.md | 38 +- worlds/kdl3/Aesthetics.py | 434 +++++++ worlds/kdl3/Client.py | 417 +++++++ worlds/kdl3/ClientAddrs.py | 816 ++++++++++++ worlds/kdl3/Compression.py | 57 + worlds/kdl3/Gifting.py | 282 +++++ worlds/kdl3/Items.py | 105 ++ worlds/kdl3/Locations.py | 940 ++++++++++++++ worlds/kdl3/Names/AnimalFriendSpawns.py | 199 +++ worlds/kdl3/Names/EnemyAbilities.py | 822 ++++++++++++ worlds/kdl3/Names/LocationName.py | 928 ++++++++++++++ worlds/kdl3/Names/__init__.py | 0 worlds/kdl3/Options.py | 432 +++++++ worlds/kdl3/Presets.py | 56 + worlds/kdl3/Regions.py | 231 ++++ worlds/kdl3/Rom.py | 577 +++++++++ worlds/kdl3/Room.py | 95 ++ worlds/kdl3/Rules.py | 332 +++++ worlds/kdl3/__init__.py | 350 ++++++ worlds/kdl3/data/APConsumable.bsdiff4 | Bin 0 -> 585 bytes worlds/kdl3/data/APHeartStar.bsdiff4 | Bin 0 -> 603 bytes worlds/kdl3/data/APPauseIcons.dat | Bin 0 -> 448 bytes worlds/kdl3/data/APStars.bsdiff4 | Bin 0 -> 250 bytes worlds/kdl3/data/Rooms.json | 1 + worlds/kdl3/data/kdl3_basepatch.bsdiff4 | Bin 0 -> 2411 bytes worlds/kdl3/docs/en_Kirby's Dream Land 3.md | 38 + worlds/kdl3/docs/setup_en.md | 148 +++ worlds/kdl3/src/kdl3_basepatch.asm | 1237 +++++++++++++++++++ worlds/kdl3/test/__init__.py | 37 + worlds/kdl3/test/test_goal.py | 64 + worlds/kdl3/test/test_locations.py | 68 + worlds/kdl3/test/test_shuffles.py | 245 ++++ 36 files changed, 8956 insertions(+), 4 deletions(-) create mode 100644 worlds/kdl3/Aesthetics.py create mode 100644 worlds/kdl3/Client.py create mode 100644 worlds/kdl3/ClientAddrs.py create mode 100644 worlds/kdl3/Compression.py create mode 100644 worlds/kdl3/Gifting.py create mode 100644 worlds/kdl3/Items.py create mode 100644 worlds/kdl3/Locations.py create mode 100644 worlds/kdl3/Names/AnimalFriendSpawns.py create mode 100644 worlds/kdl3/Names/EnemyAbilities.py create mode 100644 worlds/kdl3/Names/LocationName.py create mode 100644 worlds/kdl3/Names/__init__.py create mode 100644 worlds/kdl3/Options.py create mode 100644 worlds/kdl3/Presets.py create mode 100644 worlds/kdl3/Regions.py create mode 100644 worlds/kdl3/Rom.py create mode 100644 worlds/kdl3/Room.py create mode 100644 worlds/kdl3/Rules.py create mode 100644 worlds/kdl3/__init__.py create mode 100644 worlds/kdl3/data/APConsumable.bsdiff4 create mode 100644 worlds/kdl3/data/APHeartStar.bsdiff4 create mode 100644 worlds/kdl3/data/APPauseIcons.dat create mode 100644 worlds/kdl3/data/APStars.bsdiff4 create mode 100644 worlds/kdl3/data/Rooms.json create mode 100644 worlds/kdl3/data/kdl3_basepatch.bsdiff4 create mode 100644 worlds/kdl3/docs/en_Kirby's Dream Land 3.md create mode 100644 worlds/kdl3/docs/setup_en.md create mode 100644 worlds/kdl3/src/kdl3_basepatch.asm create mode 100644 worlds/kdl3/test/__init__.py create mode 100644 worlds/kdl3/test/test_goal.py create mode 100644 worlds/kdl3/test/test_locations.py create mode 100644 worlds/kdl3/test/test_shuffles.py diff --git a/README.md b/README.md index ce2130ce8e..4a3c53548c 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Currently, the following games are supported: * Landstalker: The Treasures of King Nole * Final Fantasy Mystic Quest * TUNIC +* Kirby's Dream Land 3 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 95c0baea3a..6ec3802ede 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -67,6 +67,9 @@ # Hylics 2 /worlds/hylics2/ @TRPG0 +# Kirby's Dream Land 3 +/worlds/kdl3/ @Silvris + # Kingdom Hearts 2 /worlds/kh2/ @JaredWeakStrike diff --git a/inno_setup.iss b/inno_setup.iss index b122cdc00b..c1b634292f 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -131,6 +131,11 @@ Root: HKCR; Subkey: "{#MyAppName}l2acpatch"; ValueData: "Arc Root: HKCR; Subkey: "{#MyAppName}l2acpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}l2acpatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: ".apkdl3"; ValueData: "{#MyAppName}kdl3patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}kdl3patch"; ValueData: "Archipelago Kirby's Dream Land 3 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}kdl3patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}kdl3patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni + Root: HKCR; Subkey: ".apmc"; ValueData: "{#MyAppName}mcdata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}mcdata"; ValueData: "Archipelago Minecraft Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}mcdata\DefaultIcon"; ValueData: "{app}\ArchipelagoMinecraftClient.exe,0"; ValueType: string; ValueName: ""; diff --git a/worlds/LauncherComponents.py b/worlds/LauncherComponents.py index 03c89b75ff..7814aac5ae 100644 --- a/worlds/LauncherComponents.py +++ b/worlds/LauncherComponents.py @@ -88,7 +88,7 @@ components: List[Component] = [ # SNI Component('SNI Client', 'SNIClient', file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3', '.apdkc3', - '.apsmw', '.apl2ac')), + '.apsmw', '.apl2ac', '.apkdl3')), Component('Links Awakening DX Client', 'LinksAwakeningClient', file_identifier=SuffixIdentifier('.apladx')), Component('LttP Adjuster', 'LttPAdjuster'), diff --git a/worlds/generic/docs/plando_en.md b/worlds/generic/docs/plando_en.md index 9d8e6befe8..d6a09cf4e6 100644 --- a/worlds/generic/docs/plando_en.md +++ b/worlds/generic/docs/plando_en.md @@ -161,8 +161,40 @@ into any locations within the game slots named BobsSlaytheSpire and BobsRogueLeg ## Boss Plando -As this is currently only supported by A Link to the Past, instead of finding an explanation here, please refer to the -relevant guide: [A Link to the Past Plando Guide](/tutorial/A%20Link%20to%20the%20Past/plando/en) +This is currently only supported by A Link to the Past and Kirby's Dream Land 3. Boss plando allows a player to place a +given boss within an arena. More specific information for boss plando in A Link to the Past can be found in +its [plando guide](/tutorial/A%20Link%20to%20the%20Past/plando/en). + +Boss plando takes in a list of instructions for placing bosses, separated by a semicolon `;`. +There are three types of placement: direct, full, and shuffle. +* Direct placement takes both an arena and a boss, and places the boss into that arena. + * `Eastern Palace-Trinexx` +* Full placement will take a boss, and place it into as many remaining arenas as possible. + * `King Dedede` +* Shuffle will fill any remaining arenas using a given boss shuffle option, typically to be used as the last instruction. + * `full` + +### Examples + +```yaml +A Link to the Past: + boss_shuffle: + # Basic boss shuffle, but prevent Trinexx from being outside Turtle Rock + Turtle Rock-Trinexx;basic: 1 + # Place as many Arrghus as possible, then let the rest be random + Arrghus;chaos: 1 + +Kirby's Dream Land 3: + boss_shuffle: + # Ensure Iceberg's boss will be King Dedede, but randomize the rest + Iceberg-King Dedede;full: 1 + # Have all bosses be Whispy Woods + Whispy Woods: 1 + # Ensure Ripple Field's boss is Pon & Con, but let the method others + # are placed with be random + Ripple Field-Pon & Con;random: 1 +``` + ## Text Plando @@ -184,7 +216,7 @@ its [plando guide](/tutorial/A%20Link%20to%20the%20Past/plando/en#connections). [A Link to the Past connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/EntranceShuffle.py#L3852) -[Minecraft connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/minecraft/Regions.py#L62) +[Minecraft connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/minecraft/data/regions.json#L18****) ### Examples diff --git a/worlds/kdl3/Aesthetics.py b/worlds/kdl3/Aesthetics.py new file mode 100644 index 0000000000..8c7363908f --- /dev/null +++ b/worlds/kdl3/Aesthetics.py @@ -0,0 +1,434 @@ +import struct +from .Options import KirbyFlavorPreset, GooeyFlavorPreset + +kirby_flavor_presets = { + 1: { + "1": "B50029", + "2": "FF91C6", + "3": "B0123B", + "4": "630F0F", + "5": "D60052", + "6": "DE4873", + "7": "D07880", + "8": "000000", + "9": "F770A5", + "10": "E01784", + "11": "CA4C74", + "12": "A7443F", + "13": "FF1784", + "14": "FFA1DE", + "15": "B03830", + }, + 2: { + "1": "C70057", + "2": "FF3554", + "3": "AA0040", + "4": "C02D47", + "5": "E02068", + "6": "C2183F", + "7": "D03F80", + "8": "872939", + "9": "E82B47", + "10": "E80067", + "11": "D52F40", + "12": "9F1C33", + "13": "FD187F", + "14": "F85068", + "15": "D2386F", + }, + 3: { + "1": "5858e2", + "2": "e6e6fa", + "3": "bcbcf2", + "4": "8484e6", + "5": "2929ec", + "6": "b5b5f0", + "7": "847bd6", + "8": "3232d6", + "9": "d6d6ef", + "10": "4a52ef", + "11": "c6c6e6", + "12": "4343ad", + "13": "6767ff", + "14": "f6f6fd", + "15": "3139b6", + }, + 4: { + "1": "B01810", + "2": "F0E08D", + "3": "C8A060", + "4": "A87043", + "5": "E03700", + "6": "EFC063", + "7": "D07818", + "8": "A8501C", + "9": "E8D070", + "10": "E2501E", + "11": "E8C55C", + "12": "B08833", + "13": "E8783B", + "14": "F8F8A5", + "15": "B03800", + }, + 5: { + "1": "9F4410", + "2": "88F27B", + "3": "57A044", + "4": "227029", + "5": "C75418", + "6": "57BA23", + "7": "1C6B00", + "8": "2D6823", + "9": "3FD744", + "10": "E06C16", + "11": "54C053", + "12": "1A541E", + "13": "F06B10", + "14": "98F89A", + "15": "B05830", + }, + 6: { + "1": "7C1060", + "2": "CA8AE8", + "3": "8250A5", + "4": "604B7B", + "5": "A52068", + "6": "8D64B8", + "7": "B73B80", + "8": "672D9A", + "9": "BA82D5", + "10": "B55098", + "11": "9F5CCF", + "12": "632B74", + "13": "CF78B5", + "14": "DA98F8", + "15": "8D3863", + }, + 7: { + "1": "6F1410", + "2": "C2735C", + "3": "5C351C", + "4": "875440", + "5": "9F2F0C", + "6": "874C3B", + "7": "88534C", + "8": "4C1E00", + "9": "B06458", + "10": "921C16", + "11": "9F5C54", + "12": "5B3125", + "13": "C01A14", + "14": "CF785B", + "15": "6B3125", + }, + 8: { + "1": "a6a6a6", + "2": "e6e6e6", + "3": "bcbcbc", + "4": "848484", + "5": "909090", + "6": "b5b5b5", + "7": "848484", + "8": "646464", + "9": "d6d6d6", + "10": "525252", + "11": "c6c6c6", + "12": "737373", + "13": "949494", + "14": "f6f6f6", + "15": "545454", + }, + 9: { + "1": "400000", + "2": "6B6B6B", + "3": "2B2B2B", + "4": "181818", + "5": "640000", + "6": "3D3D3D", + "7": "878787", + "8": "020202", + "9": "606060", + "10": "980000", + "11": "505050", + "12": "474747", + "13": "C80000", + "14": "808080", + "15": "AF0000", + }, + 10: { + "1": "2B4B10", + "2": "EF8A9D", + "3": "C84F6B", + "4": "B74F54", + "5": "126018", + "6": "D85F6F", + "7": "D06870", + "8": "A24858", + "9": "E77B8D", + "10": "168025", + "11": "DF5C68", + "12": "9D4353", + "13": "48953F", + "14": "F897AD", + "15": "B03830", + }, + 11: { + "1": "7B290C", + "2": "FF9A00", + "3": "B05C1C", + "4": "8F3F0E", + "5": "D23B0C", + "6": "E08200", + "7": "D05800", + "8": "8A2B16", + "9": "EF970A", + "10": "E24800", + "11": "E58F00", + "12": "A03700", + "13": "ED3B00", + "14": "FFAF27", + "15": "A84700", + }, + 12: { + "1": "AFA810", + "2": "4FF29D", + "3": "2BA04C", + "4": "007043", + "5": "C7C218", + "6": "33BA5F", + "7": "006B40", + "8": "2D6823", + "9": "1CD773", + "10": "E0CF16", + "11": "2DC06C", + "12": "00543F", + "13": "F0F010", + "14": "43F8B2", + "15": "B0A230", + }, + 13: { + "1": "7C73B0", + "2": "CACAE7", + "3": "7B7BA8", + "4": "5F5FA7", + "5": "B57EDC", + "6": "8585C5", + "7": "5B5B82", + "8": "474796", + "9": "B2B2D8", + "10": "B790EF", + "11": "9898C2", + "12": "6B6BB7", + "13": "CDADFA", + "14": "E6E6FA", + "15": "976FBD", + }, +} + +gooey_flavor_presets = { + 1: { + "1": "CD539D", + "2": "D270AD", + "3": "F27CBF", + "4": "FF91C6", + "5": "FFA1DE", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 2: { + "1": "161600", + "2": "592910", + "3": "5A3118", + "4": "AB3918", + "5": "EB3918", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 3: { + "1": "001616", + "2": "102959", + "3": "18315A", + "4": "1839AB", + "5": "1839EB", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 4: { + "1": "C8A031", + "2": "C5BD38", + "3": "D2CD48", + "4": "E2E040", + "5": "EAE2A0", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 5: { + "1": "54A208", + "2": "5CB021", + "3": "6CB206", + "4": "8AC54C", + "5": "8DD554", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 6: { + "1": "3D083D", + "2": "4B024B", + "3": "4C104C", + "4": "5F0A5F", + "5": "9F1D9F", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 7: { + "1": "270C08", + "2": "481C10", + "3": "581E10", + "4": "5B2712", + "5": "743316", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 8: { + "1": "7F7F7F", + "2": "909090", + "3": "9D9D9D", + "4": "BFBFBF", + "5": "D2D2D2", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 9: { + "1": "141414", + "2": "2D2D2D", + "3": "404040", + "4": "585858", + "5": "7F7F7F", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 10: { + "1": "954353", + "2": "AF4F68", + "3": "CD6073", + "4": "E06774", + "5": "E587A2", + "6": "17AF10", + "7": "4FE748", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 11: { + "1": "CF4700", + "2": "D85C08", + "3": "E26C04", + "4": "EA7B16", + "5": "EF8506", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 12: { + "1": "1C4708", + "2": "105B1C", + "3": "186827", + "4": "187C3B", + "5": "188831", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, + 13: { + "1": "501E70", + "2": "673B87", + "3": "7848A7", + "4": "9067C7", + "5": "B57EDC", + "6": "B51810", + "7": "EF524A", + "8": "D6C6C6", + "9": "FFFFFF", + }, +} + +kirby_target_palettes = { + 0x64646: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 1), + 0x64846: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 1), + 0x1E007E: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 1), + 0x1E009C: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 0.5), + 0x1E00F6: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 1), + 0x1E0114: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 0.5), + 0x1E0216: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 1), + 0x1E0234: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 0.5), + 0x1E0486: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 1), + 0x1E04A4: (["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"], 0, 0.5), +} + +gooey_target_palettes = { + 0x604C2: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x64592: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x64692: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x64892: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x1E02CA: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x1E0342: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x1E05A6: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x1E05B8: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 0.5), + 0x1E0636: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1), + 0x1E065A: (["1", "2", "3", "4", "5", "6", "7", "8", "9"], 0, 1.5), +} + + +def get_kirby_palette(world): + palette = world.options.kirby_flavor_preset.value + if palette == KirbyFlavorPreset.option_custom: + return world.options.kirby_flavor.value + return kirby_flavor_presets.get(palette, None) + + +def get_gooey_palette(world): + palette = world.options.gooey_flavor_preset.value + if palette == GooeyFlavorPreset.option_custom: + return world.options.gooey_flavor.value + return gooey_flavor_presets.get(palette, None) + + +def rgb888_to_bgr555(red, green, blue) -> bytes: + red = red >> 3 + green = green >> 3 + blue = blue >> 3 + outcol = (blue << 10) + (green << 5) + red + return struct.pack("H", outcol) + + +def get_palette_bytes(palette, target, offset, factor): + output_data = bytearray() + for color in target: + hexcol = palette[color] + if hexcol.startswith("#"): + hexcol = hexcol.replace("#", "") + colint = int(hexcol, 16) + col = ((colint & 0xFF0000) >> 16, (colint & 0xFF00) >> 8, colint & 0xFF) + col = tuple(int(int(factor*x) + offset) for x in col) + byte_data = rgb888_to_bgr555(col[0], col[1], col[2]) + output_data.extend(bytearray(byte_data)) + return output_data diff --git a/worlds/kdl3/Client.py b/worlds/kdl3/Client.py new file mode 100644 index 0000000000..3fef042900 --- /dev/null +++ b/worlds/kdl3/Client.py @@ -0,0 +1,417 @@ +import logging +import struct +import time +import typing +import uuid +from struct import unpack, pack +from collections import defaultdict +import random + +from MultiServer import mark_raw +from NetUtils import ClientStatus, color +from Utils import async_start +from worlds.AutoSNIClient import SNIClient +from .Locations import boss_locations +from .Gifting import kdl3_gifting_options, kdl3_trap_gifts, kdl3_gifts, update_object, pop_object, initialize_giftboxes +from .ClientAddrs import consumable_addrs, star_addrs +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from SNIClient import SNIClientCommandProcessor + +snes_logger = logging.getLogger("SNES") + +# FXPAK Pro protocol memory mapping used by SNI +ROM_START = 0x000000 +SRAM_1_START = 0xE00000 + +# KDL3 +KDL3_HALKEN = SRAM_1_START + 0x80F0 +KDL3_NINTEN = SRAM_1_START + 0x8FF0 +KDL3_ROMNAME = SRAM_1_START + 0x8100 +KDL3_DEATH_LINK_ADDR = SRAM_1_START + 0x9010 +KDL3_GOAL_ADDR = SRAM_1_START + 0x9012 +KDL3_CONSUMABLE_FLAG = SRAM_1_START + 0x9018 +KDL3_STARS_FLAG = SRAM_1_START + 0x901A +KDL3_GIFTING_FLAG = SRAM_1_START + 0x901C +KDL3_LEVEL_ADDR = SRAM_1_START + 0x9020 +KDL3_IS_DEMO = SRAM_1_START + 0x5AD5 +KDL3_GAME_STATE = SRAM_1_START + 0x36D0 +KDL3_GAME_SAVE = SRAM_1_START + 0x3617 +KDL3_LIFE_COUNT = SRAM_1_START + 0x39CF +KDL3_KIRBY_HP = SRAM_1_START + 0x39D1 +KDL3_BOSS_HP = SRAM_1_START + 0x39D5 +KDL3_STAR_COUNT = SRAM_1_START + 0x39D7 +KDL3_LIFE_VISUAL = SRAM_1_START + 0x39E3 +KDL3_HEART_STARS = SRAM_1_START + 0x53A7 +KDL3_WORLD_UNLOCK = SRAM_1_START + 0x53CB +KDL3_LEVEL_UNLOCK = SRAM_1_START + 0x53CD +KDL3_CURRENT_WORLD = SRAM_1_START + 0x53CF +KDL3_CURRENT_LEVEL = SRAM_1_START + 0x53D3 +KDL3_BOSS_STATUS = SRAM_1_START + 0x53D5 +KDL3_INVINCIBILITY_TIMER = SRAM_1_START + 0x54B1 +KDL3_MG5_STATUS = SRAM_1_START + 0x5EE4 +KDL3_BOSS_BUTCH_STATUS = SRAM_1_START + 0x5EEA +KDL3_JUMPING_STATUS = SRAM_1_START + 0x5EF0 +KDL3_CURRENT_BGM = SRAM_1_START + 0x733E +KDL3_SOUND_FX = SRAM_1_START + 0x7F62 +KDL3_ANIMAL_FRIENDS = SRAM_1_START + 0x8000 +KDL3_ABILITY_ARRAY = SRAM_1_START + 0x8020 +KDL3_RECV_COUNT = SRAM_1_START + 0x8050 +KDL3_HEART_STAR_COUNT = SRAM_1_START + 0x8070 +KDL3_GOOEY_TRAP = SRAM_1_START + 0x8080 +KDL3_SLOWNESS_TRAP = SRAM_1_START + 0x8082 +KDL3_ABILITY_TRAP = SRAM_1_START + 0x8084 +KDL3_GIFTING_SEND = SRAM_1_START + 0x8086 +KDL3_COMPLETED_STAGES = SRAM_1_START + 0x8200 +KDL3_CONSUMABLES = SRAM_1_START + 0xA000 +KDL3_STARS = SRAM_1_START + 0xB000 +KDL3_ITEM_QUEUE = SRAM_1_START + 0xC000 + +deathlink_messages = defaultdict(lambda: " was defeated.", { + 0x0200: " was bonked by apples from Whispy Woods.", + 0x0201: " was out-maneuvered by Acro.", + 0x0202: " was out-numbered by Pon & Con.", + 0x0203: " was defeated by Ado's powerful paintings.", + 0x0204: " was clobbered by King Dedede.", + 0x0205: " lost their battle against Dark Matter." +}) + + +@mark_raw +def cmd_gift(self: "SNIClientCommandProcessor"): + """Toggles gifting for the current game.""" + if not getattr(self.ctx, "gifting", None): + self.ctx.gifting = True + else: + self.ctx.gifting = not self.ctx.gifting + self.output(f"Gifting set to {self.ctx.gifting}") + async_start(update_object(self.ctx, f"Giftboxes;{self.ctx.team}", { + f"{self.ctx.slot}": + { + "IsOpen": self.ctx.gifting, + **kdl3_gifting_options + } + })) + + +class KDL3SNIClient(SNIClient): + game = "Kirby's Dream Land 3" + levels = None + consumables = None + stars = None + item_queue: typing.List = [] + initialize_gifting = False + giftbox_key: str = "" + motherbox_key: str = "" + client_random: random.Random = random.Random() + + async def deathlink_kill_player(self, ctx) -> None: + from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read + game_state = await snes_read(ctx, KDL3_GAME_STATE, 1) + if game_state[0] == 0xFF: + return # despite how funny it is, don't try to kill Kirby in a menu + + current_stage = await snes_read(ctx, KDL3_CURRENT_LEVEL, 1) + if current_stage[0] == 0x7: # boss stage + boss_hp = await snes_read(ctx, KDL3_BOSS_HP, 1) + if boss_hp[0] == 0: + return # receiving a deathlink after defeating a boss has softlock potential + + current_hp = await snes_read(ctx, KDL3_KIRBY_HP, 1) + if current_hp[0] == 0: + return # don't kill Kirby while he's already dead + snes_buffered_write(ctx, KDL3_KIRBY_HP, bytes([0x00])) + + await snes_flush_writes(ctx) + + ctx.death_state = DeathState.dead + ctx.last_death_link = time.time() + + async def validate_rom(self, ctx) -> bool: + from SNIClient import snes_read + rom_name = await snes_read(ctx, KDL3_ROMNAME, 0x15) + if rom_name is None or rom_name == bytes([0] * 0x15) or rom_name[:4] != b"KDL3": + if "gift" in ctx.command_processor.commands: + ctx.command_processor.commands.pop("gift") + return False + + ctx.game = self.game + ctx.rom = rom_name + ctx.items_handling = 0b111 # always remote items + ctx.allow_collect = True + if "gift" not in ctx.command_processor.commands: + ctx.command_processor.commands["gift"] = cmd_gift + + death_link = await snes_read(ctx, KDL3_DEATH_LINK_ADDR, 1) + if death_link: + await ctx.update_death_link(bool(death_link[0] & 0b1)) + return True + + async def pop_item(self, ctx, in_stage): + from SNIClient import snes_buffered_write, snes_read + if len(self.item_queue) > 0: + item = self.item_queue.pop() + if not in_stage and item & 0xC0: + # can't handle this item right now, send it to the back and return to handle the rest + self.item_queue.append(item) + return + ingame_queue = list(unpack("HHHHHHHH", await snes_read(ctx, KDL3_ITEM_QUEUE, 16))) + for i in range(len(ingame_queue)): + if ingame_queue[i] == 0x00: + ingame_queue[i] = item + snes_buffered_write(ctx, KDL3_ITEM_QUEUE, pack("HHHHHHHH", *ingame_queue)) + break + else: + self.item_queue.append(item) # no more slots, get it next go around + + async def pop_gift(self, ctx): + if ctx.stored_data[self.giftbox_key]: + from SNIClient import snes_read, snes_buffered_write + key, gift = ctx.stored_data[self.giftbox_key].popitem() + await pop_object(ctx, self.giftbox_key, key) + # first, special cases + traits = [trait["Trait"] for trait in gift["Traits"]] + if "Candy" in traits or "Invincible" in traits: + # apply invincibility candy + self.item_queue.append(0x43) + elif "Tomato" in traits or "tomato" in gift["ItemName"].lower(): + # apply maxim tomato + # only want tomatos here, no other vegetable is that good + self.item_queue.append(0x42) + elif "Life" in traits: + # Apply 1-Up + self.item_queue.append(0x41) + elif "Currency" in traits or "Star" in traits: + value = gift["ItemValue"] + if value >= 50000: + self.item_queue.append(0x46) + elif value >= 30000: + self.item_queue.append(0x45) + else: + self.item_queue.append(0x44) + elif "Trap" in traits: + # find the best trap to apply + if "Goo" in traits or "Gel" in traits: + self.item_queue.append(0x80) + elif "Slow" in traits or "Slowness" in traits: + self.item_queue.append(0x81) + elif "Eject" in traits or "Removal" in traits: + self.item_queue.append(0x82) + else: + # just deal damage to Kirby + kirby_hp = struct.unpack("H", await snes_read(ctx, KDL3_KIRBY_HP, 2))[0] + snes_buffered_write(ctx, KDL3_KIRBY_HP, struct.pack("H", max(kirby_hp - 1, 0))) + else: + # check if it's tasty + if any(x in traits for x in ["Consumable", "Food", "Drink", "Heal", "Health"]): + # it's tasty!, use quality to decide how much to heal + quality = max((trait["Quality"] for trait in gift["Traits"] + if trait["Trait"] in ["Consumable", "Food", "Drink", "Heal", "Health"])) + quality = min(10, quality * 2) + else: + # it's not really edible, but he'll eat it anyway + quality = self.client_random.choices(range(0, 2), {0: 75, 1: 25})[0] + kirby_hp = await snes_read(ctx, KDL3_KIRBY_HP, 1) + gooey_hp = await snes_read(ctx, KDL3_KIRBY_HP + 2, 1) + snes_buffered_write(ctx, KDL3_SOUND_FX, bytes([0x26])) + if gooey_hp[0] > 0x00: + snes_buffered_write(ctx, KDL3_KIRBY_HP, struct.pack("H", min(kirby_hp[0] + quality // 2, 8))) + snes_buffered_write(ctx, KDL3_KIRBY_HP + 2, struct.pack("H", min(gooey_hp[0] + quality // 2, 8))) + else: + snes_buffered_write(ctx, KDL3_KIRBY_HP, struct.pack("H", min(kirby_hp[0] + quality, 10))) + + async def pick_gift_recipient(self, ctx, gift): + if gift != 4: + gift_base = kdl3_gifts[gift] + else: + gift_base = kdl3_trap_gifts[self.client_random.randint(0, 3)] + most_applicable = -1 + most_applicable_slot = ctx.slot + for slot, info in ctx.stored_data[self.motherbox_key].items(): + if int(slot) == ctx.slot and len(ctx.stored_data[self.motherbox_key]) > 1: + continue + desire = len(set(info["DesiredTraits"]).intersection([trait["Trait"] for trait in gift_base["Traits"]])) + if desire > most_applicable: + most_applicable = desire + most_applicable_slot = int(slot) + elif most_applicable_slot == ctx.slot and info["AcceptsAnyGift"]: + # only send to ourselves if no one else will take it + most_applicable_slot = int(slot) + # print(most_applicable, most_applicable_slot) + item_uuid = uuid.uuid4().hex + item = { + **gift_base, + "ID": item_uuid, + "Sender": ctx.player_names[ctx.slot], + "Receiver": ctx.player_names[most_applicable_slot], + "SenderTeam": ctx.team, + "ReceiverTeam": ctx.team, # for the moment + "IsRefund": False + } + # print(item) + await update_object(ctx, f"Giftbox;{ctx.team};{most_applicable_slot}", { + item_uuid: item, + }) + + async def game_watcher(self, ctx) -> None: + try: + from SNIClient import snes_buffered_write, snes_flush_writes, snes_read + rom = await snes_read(ctx, KDL3_ROMNAME, 0x15) + if rom != ctx.rom: + ctx.rom = None + halken = await snes_read(ctx, KDL3_HALKEN, 6) + if halken != b"halken": + return + ninten = await snes_read(ctx, KDL3_NINTEN, 6) + if ninten != b"ninten": + return + if not ctx.slot: + return + if not self.initialize_gifting: + self.giftbox_key = f"Giftbox;{ctx.team};{ctx.slot}" + self.motherbox_key = f"Giftboxes;{ctx.team}" + enable_gifting = await snes_read(ctx, KDL3_GIFTING_FLAG, 0x01) + await initialize_giftboxes(ctx, self.giftbox_key, self.motherbox_key, bool(enable_gifting[0])) + self.initialize_gifting = True + # can't check debug anymore, without going and copying the value. might be important later. + if self.levels is None: + self.levels = dict() + for i in range(5): + level_data = await snes_read(ctx, KDL3_LEVEL_ADDR + (14 * i), 14) + self.levels[i] = unpack("HHHHHHH", level_data) + + if self.consumables is None: + consumables = await snes_read(ctx, KDL3_CONSUMABLE_FLAG, 1) + self.consumables = consumables[0] == 0x01 + if self.stars is None: + stars = await snes_read(ctx, KDL3_STARS_FLAG, 1) + self.stars = stars[0] == 0x01 + is_demo = await snes_read(ctx, KDL3_IS_DEMO, 1) + # 1 - recording a demo, 2 - playing back recorded, 3+ is a demo + if is_demo[0] > 0x00: + return + current_save = await snes_read(ctx, KDL3_GAME_SAVE, 1) + goal = await snes_read(ctx, KDL3_GOAL_ADDR, 1) + boss_butch_status = await snes_read(ctx, KDL3_BOSS_BUTCH_STATUS + (current_save[0] * 2), 1) + mg5_status = await snes_read(ctx, KDL3_MG5_STATUS + (current_save[0] * 2), 1) + jumping_status = await snes_read(ctx, KDL3_JUMPING_STATUS + (current_save[0] * 2), 1) + if boss_butch_status[0] == 0xFF: + return # save file is not created, ignore + if (goal[0] == 0x00 and boss_butch_status[0] == 0x01) \ + or (goal[0] == 0x01 and boss_butch_status[0] == 0x03) \ + or (goal[0] == 0x02 and mg5_status[0] == 0x03) \ + or (goal[0] == 0x03 and jumping_status[0] == 0x03): + await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) + ctx.finished_game = True + current_bgm = await snes_read(ctx, KDL3_CURRENT_BGM, 1) + if current_bgm[0] in (0x00, 0x21, 0x22, 0x23, 0x25, 0x2A, 0x2B): + return # null, title screen, opening, save select, true and false endings + game_state = await snes_read(ctx, KDL3_GAME_STATE, 1) + current_hp = await snes_read(ctx, KDL3_KIRBY_HP, 1) + if "DeathLink" in ctx.tags and game_state[0] == 0x00 and ctx.last_death_link + 1 < time.time(): + currently_dead = current_hp[0] == 0x00 + await ctx.handle_deathlink_state(currently_dead) + + recv_count = await snes_read(ctx, KDL3_RECV_COUNT, 2) + recv_amount = unpack("H", recv_count)[0] + if recv_amount < len(ctx.items_received): + item = ctx.items_received[recv_amount] + recv_amount += 1 + logging.info('Received %s from %s (%s) (%d/%d in list)' % ( + color(ctx.item_names[item.item], 'red', 'bold'), + color(ctx.player_names[item.player], 'yellow'), + ctx.location_names[item.location], recv_amount, len(ctx.items_received))) + + snes_buffered_write(ctx, KDL3_RECV_COUNT, pack("H", recv_amount)) + item_idx = item.item & 0x00000F + if item.item & 0x000070 == 0: + self.item_queue.append(item_idx | 0x10) + elif item.item & 0x000010 > 0: + self.item_queue.append(item_idx | 0x20) + elif item.item & 0x000020 > 0: + # Positive + self.item_queue.append(item_idx | 0x40) + elif item.item & 0x000040 > 0: + self.item_queue.append(item_idx | 0x80) + + # handle gifts here + gifting_status = await snes_read(ctx, KDL3_GIFTING_FLAG, 0x01) + if hasattr(ctx, "gifting") and ctx.gifting: + if gifting_status[0]: + gift = await snes_read(ctx, KDL3_GIFTING_SEND, 0x01) + if gift[0]: + # we have a gift to send + await self.pick_gift_recipient(ctx, gift[0]) + snes_buffered_write(ctx, KDL3_GIFTING_SEND, bytes([0x00])) + else: + snes_buffered_write(ctx, KDL3_GIFTING_FLAG, bytes([0x01])) + else: + if gifting_status[0]: + snes_buffered_write(ctx, KDL3_GIFTING_FLAG, bytes([0x00])) + + await snes_flush_writes(ctx) + + new_checks = [] + # level completion status + world_unlocks = await snes_read(ctx, KDL3_WORLD_UNLOCK, 1) + if world_unlocks[0] > 0x06: + return # save is not loaded, ignore + stages_raw = await snes_read(ctx, KDL3_COMPLETED_STAGES, 60) + stages = struct.unpack("HHHHHHHHHHHHHHHHHHHHHHHHHHHHHH", stages_raw) + for i in range(30): + loc_id = 0x770000 + i + 1 + if stages[i] == 1 and loc_id not in ctx.checked_locations: + new_checks.append(loc_id) + elif loc_id in ctx.checked_locations: + snes_buffered_write(ctx, KDL3_COMPLETED_STAGES + (i * 2), struct.pack("H", 1)) + + # heart star status + heart_stars = await snes_read(ctx, KDL3_HEART_STARS, 35) + for i in range(5): + start_ind = i * 7 + for j in range(1, 7): + level_ind = start_ind + j - 1 + loc_id = 0x770100 + (6 * i) + j + if heart_stars[level_ind] and loc_id not in ctx.checked_locations: + new_checks.append(loc_id) + elif loc_id in ctx.checked_locations: + snes_buffered_write(ctx, KDL3_HEART_STARS + level_ind, bytes([0x01])) + if self.consumables: + consumables = await snes_read(ctx, KDL3_CONSUMABLES, 1920) + for consumable in consumable_addrs: + # TODO: see if this can be sped up in any way + loc_id = 0x770300 + consumable + if loc_id not in ctx.checked_locations and consumables[consumable_addrs[consumable]] == 0x01: + new_checks.append(loc_id) + if self.stars: + stars = await snes_read(ctx, KDL3_STARS, 1920) + for star in star_addrs: + if star not in ctx.checked_locations and stars[star_addrs[star]] == 0x01: + new_checks.append(star) + + if game_state[0] != 0xFF: + await self.pop_gift(ctx) + await self.pop_item(ctx, game_state[0] != 0xFF) + await snes_flush_writes(ctx) + + # boss status + boss_flag_bytes = await snes_read(ctx, KDL3_BOSS_STATUS, 2) + boss_flag = unpack("H", boss_flag_bytes)[0] + for bitmask, boss in zip(range(1, 11, 2), boss_locations.keys()): + if boss_flag & (1 << bitmask) > 0 and boss not in ctx.checked_locations: + new_checks.append(boss) + + for new_check_id in new_checks: + ctx.locations_checked.add(new_check_id) + location = ctx.location_names[new_check_id] + snes_logger.info( + f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') + await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}]) + except Exception as ex: + # we crashed, so print log and clean up + snes_logger.error("", exc_info=ex) + if "gift" in ctx.command_processor.commands: + ctx.command_processor.commands.pop("gift") + ctx.rom = None + ctx.game = None diff --git a/worlds/kdl3/ClientAddrs.py b/worlds/kdl3/ClientAddrs.py new file mode 100644 index 0000000000..1f5475741b --- /dev/null +++ b/worlds/kdl3/ClientAddrs.py @@ -0,0 +1,816 @@ +consumable_addrs = { + 0: 14, + 1: 15, + 2: 84, + 3: 138, + 4: 139, + 5: 204, + 6: 214, + 7: 215, + 8: 224, + 9: 330, + 10: 353, + 11: 458, + 12: 459, + 13: 522, + 14: 525, + 15: 605, + 16: 606, + 17: 630, + 18: 671, + 19: 672, + 20: 693, + 21: 791, + 22: 851, + 23: 883, + 24: 971, + 25: 985, + 26: 986, + 27: 1024, + 28: 1035, + 29: 1036, + 30: 1038, + 31: 1039, + 32: 1170, + 33: 1171, + 34: 1377, + 35: 1378, + 36: 1413, + 37: 1494, + 38: 1666, + 39: 1808, + 40: 1809, + 41: 1816, + 42: 1856, + 43: 1857, +} + +star_addrs = { + 0x770401: 0, + 0x770402: 1, + 0x770403: 2, + 0x770404: 3, + 0x770405: 4, + 0x770406: 5, + 0x770407: 7, + 0x770408: 8, + 0x770409: 9, + 0x77040a: 10, + 0x77040b: 11, + 0x77040c: 12, + 0x77040d: 13, + 0x77040e: 16, + 0x77040f: 17, + 0x770410: 19, + 0x770411: 20, + 0x770412: 21, + 0x770413: 22, + 0x770414: 23, + 0x770415: 24, + 0x770416: 25, + 0x770417: 26, + 0x770418: 65, + 0x770419: 66, + 0x77041a: 67, + 0x77041b: 68, + 0x77041c: 69, + 0x77041d: 70, + 0x77041e: 71, + 0x77041f: 72, + 0x770420: 73, + 0x770421: 74, + 0x770422: 76, + 0x770423: 77, + 0x770424: 78, + 0x770425: 79, + 0x770426: 80, + 0x770427: 81, + 0x770428: 82, + 0x770429: 83, + 0x77042a: 85, + 0x77042b: 86, + 0x77042c: 87, + 0x77042d: 128, + 0x77042e: 129, + 0x77042f: 130, + 0x770430: 131, + 0x770431: 132, + 0x770432: 133, + 0x770433: 134, + 0x770434: 135, + 0x770435: 136, + 0x770436: 137, + 0x770437: 140, + 0x770438: 141, + 0x770439: 142, + 0x77043a: 143, + 0x77043b: 144, + 0x77043c: 145, + 0x77043d: 146, + 0x77043e: 147, + 0x77043f: 148, + 0x770440: 149, + 0x770441: 150, + 0x770442: 151, + 0x770443: 152, + 0x770444: 153, + 0x770445: 154, + 0x770446: 155, + 0x770447: 156, + 0x770448: 157, + 0x770449: 158, + 0x77044a: 159, + 0x77044b: 160, + 0x77044c: 192, + 0x77044d: 193, + 0x77044e: 194, + 0x77044f: 195, + 0x770450: 197, + 0x770451: 198, + 0x770452: 199, + 0x770453: 200, + 0x770454: 201, + 0x770455: 203, + 0x770456: 205, + 0x770457: 206, + 0x770458: 207, + 0x770459: 208, + 0x77045a: 209, + 0x77045b: 210, + 0x77045c: 211, + 0x77045d: 212, + 0x77045e: 213, + 0x77045f: 216, + 0x770460: 217, + 0x770461: 218, + 0x770462: 219, + 0x770463: 220, + 0x770464: 221, + 0x770465: 222, + 0x770466: 225, + 0x770467: 227, + 0x770468: 228, + 0x770469: 229, + 0x77046a: 230, + 0x77046b: 231, + 0x77046c: 232, + 0x77046d: 233, + 0x77046e: 234, + 0x77046f: 235, + 0x770470: 236, + 0x770471: 257, + 0x770472: 258, + 0x770473: 259, + 0x770474: 260, + 0x770475: 261, + 0x770476: 262, + 0x770477: 263, + 0x770478: 264, + 0x770479: 265, + 0x77047a: 266, + 0x77047b: 267, + 0x77047c: 268, + 0x77047d: 270, + 0x77047e: 271, + 0x77047f: 272, + 0x770480: 273, + 0x770481: 275, + 0x770482: 276, + 0x770483: 277, + 0x770484: 278, + 0x770485: 279, + 0x770486: 280, + 0x770487: 281, + 0x770488: 282, + 0x770489: 283, + 0x77048a: 284, + 0x77048b: 285, + 0x77048c: 286, + 0x77048d: 287, + 0x77048e: 321, + 0x77048f: 322, + 0x770490: 323, + 0x770491: 324, + 0x770492: 325, + 0x770493: 326, + 0x770494: 327, + 0x770495: 328, + 0x770496: 329, + 0x770497: 332, + 0x770498: 334, + 0x770499: 335, + 0x77049a: 336, + 0x77049b: 337, + 0x77049c: 340, + 0x77049d: 341, + 0x77049e: 342, + 0x77049f: 343, + 0x7704a0: 345, + 0x7704a1: 346, + 0x7704a2: 347, + 0x7704a3: 348, + 0x7704a4: 349, + 0x7704a5: 350, + 0x7704a6: 351, + 0x7704a7: 354, + 0x7704a8: 355, + 0x7704a9: 356, + 0x7704aa: 357, + 0x7704ab: 384, + 0x7704ac: 385, + 0x7704ad: 386, + 0x7704ae: 387, + 0x7704af: 388, + 0x7704b0: 389, + 0x7704b1: 391, + 0x7704b2: 392, + 0x7704b3: 393, + 0x7704b4: 394, + 0x7704b5: 396, + 0x7704b6: 397, + 0x7704b7: 398, + 0x7704b8: 399, + 0x7704b9: 400, + 0x7704ba: 401, + 0x7704bb: 402, + 0x7704bc: 403, + 0x7704bd: 404, + 0x7704be: 449, + 0x7704bf: 450, + 0x7704c0: 451, + 0x7704c1: 453, + 0x7704c2: 454, + 0x7704c3: 455, + 0x7704c4: 456, + 0x7704c5: 457, + 0x7704c6: 460, + 0x7704c7: 461, + 0x7704c8: 462, + 0x7704c9: 463, + 0x7704ca: 464, + 0x7704cb: 465, + 0x7704cc: 466, + 0x7704cd: 467, + 0x7704ce: 468, + 0x7704cf: 513, + 0x7704d0: 514, + 0x7704d1: 515, + 0x7704d2: 516, + 0x7704d3: 517, + 0x7704d4: 518, + 0x7704d5: 519, + 0x7704d6: 520, + 0x7704d7: 521, + 0x7704d8: 523, + 0x7704d9: 524, + 0x7704da: 527, + 0x7704db: 528, + 0x7704dc: 529, + 0x7704dd: 531, + 0x7704de: 532, + 0x7704df: 533, + 0x7704e0: 534, + 0x7704e1: 535, + 0x7704e2: 536, + 0x7704e3: 537, + 0x7704e4: 576, + 0x7704e5: 577, + 0x7704e6: 578, + 0x7704e7: 579, + 0x7704e8: 580, + 0x7704e9: 582, + 0x7704ea: 583, + 0x7704eb: 584, + 0x7704ec: 585, + 0x7704ed: 586, + 0x7704ee: 587, + 0x7704ef: 588, + 0x7704f0: 589, + 0x7704f1: 590, + 0x7704f2: 591, + 0x7704f3: 592, + 0x7704f4: 593, + 0x7704f5: 594, + 0x7704f6: 595, + 0x7704f7: 596, + 0x7704f8: 597, + 0x7704f9: 598, + 0x7704fa: 599, + 0x7704fb: 600, + 0x7704fc: 601, + 0x7704fd: 602, + 0x7704fe: 603, + 0x7704ff: 604, + 0x770500: 607, + 0x770501: 608, + 0x770502: 609, + 0x770503: 610, + 0x770504: 611, + 0x770505: 612, + 0x770506: 613, + 0x770507: 614, + 0x770508: 615, + 0x770509: 616, + 0x77050a: 617, + 0x77050b: 618, + 0x77050c: 619, + 0x77050d: 620, + 0x77050e: 621, + 0x77050f: 622, + 0x770510: 623, + 0x770511: 624, + 0x770512: 625, + 0x770513: 626, + 0x770514: 627, + 0x770515: 628, + 0x770516: 629, + 0x770517: 640, + 0x770518: 641, + 0x770519: 642, + 0x77051a: 643, + 0x77051b: 644, + 0x77051c: 645, + 0x77051d: 646, + 0x77051e: 647, + 0x77051f: 648, + 0x770520: 649, + 0x770521: 650, + 0x770522: 651, + 0x770523: 652, + 0x770524: 653, + 0x770525: 654, + 0x770526: 655, + 0x770527: 656, + 0x770528: 657, + 0x770529: 658, + 0x77052a: 659, + 0x77052b: 660, + 0x77052c: 661, + 0x77052d: 662, + 0x77052e: 663, + 0x77052f: 664, + 0x770530: 665, + 0x770531: 666, + 0x770532: 667, + 0x770533: 668, + 0x770534: 669, + 0x770535: 670, + 0x770536: 674, + 0x770537: 675, + 0x770538: 676, + 0x770539: 677, + 0x77053a: 678, + 0x77053b: 679, + 0x77053c: 680, + 0x77053d: 681, + 0x77053e: 682, + 0x77053f: 683, + 0x770540: 684, + 0x770541: 686, + 0x770542: 687, + 0x770543: 688, + 0x770544: 689, + 0x770545: 690, + 0x770546: 691, + 0x770547: 692, + 0x770548: 694, + 0x770549: 695, + 0x77054a: 704, + 0x77054b: 705, + 0x77054c: 706, + 0x77054d: 707, + 0x77054e: 708, + 0x77054f: 709, + 0x770550: 710, + 0x770551: 711, + 0x770552: 712, + 0x770553: 713, + 0x770554: 714, + 0x770555: 715, + 0x770556: 716, + 0x770557: 717, + 0x770558: 718, + 0x770559: 719, + 0x77055a: 720, + 0x77055b: 721, + 0x77055c: 722, + 0x77055d: 723, + 0x77055e: 724, + 0x77055f: 725, + 0x770560: 726, + 0x770561: 769, + 0x770562: 770, + 0x770563: 771, + 0x770564: 772, + 0x770565: 773, + 0x770566: 774, + 0x770567: 775, + 0x770568: 776, + 0x770569: 777, + 0x77056a: 778, + 0x77056b: 779, + 0x77056c: 780, + 0x77056d: 781, + 0x77056e: 782, + 0x77056f: 783, + 0x770570: 784, + 0x770571: 785, + 0x770572: 786, + 0x770573: 787, + 0x770574: 788, + 0x770575: 789, + 0x770576: 790, + 0x770577: 832, + 0x770578: 833, + 0x770579: 834, + 0x77057a: 835, + 0x77057b: 836, + 0x77057c: 837, + 0x77057d: 838, + 0x77057e: 839, + 0x77057f: 840, + 0x770580: 841, + 0x770581: 842, + 0x770582: 843, + 0x770583: 844, + 0x770584: 845, + 0x770585: 846, + 0x770586: 847, + 0x770587: 848, + 0x770588: 849, + 0x770589: 850, + 0x77058a: 854, + 0x77058b: 855, + 0x77058c: 856, + 0x77058d: 857, + 0x77058e: 858, + 0x77058f: 859, + 0x770590: 860, + 0x770591: 861, + 0x770592: 862, + 0x770593: 863, + 0x770594: 864, + 0x770595: 865, + 0x770596: 866, + 0x770597: 867, + 0x770598: 868, + 0x770599: 869, + 0x77059a: 870, + 0x77059b: 871, + 0x77059c: 872, + 0x77059d: 873, + 0x77059e: 874, + 0x77059f: 875, + 0x7705a0: 876, + 0x7705a1: 877, + 0x7705a2: 878, + 0x7705a3: 879, + 0x7705a4: 880, + 0x7705a5: 881, + 0x7705a6: 882, + 0x7705a7: 896, + 0x7705a8: 897, + 0x7705a9: 898, + 0x7705aa: 899, + 0x7705ab: 900, + 0x7705ac: 901, + 0x7705ad: 902, + 0x7705ae: 903, + 0x7705af: 904, + 0x7705b0: 905, + 0x7705b1: 960, + 0x7705b2: 961, + 0x7705b3: 962, + 0x7705b4: 963, + 0x7705b5: 964, + 0x7705b6: 965, + 0x7705b7: 966, + 0x7705b8: 967, + 0x7705b9: 968, + 0x7705ba: 969, + 0x7705bb: 970, + 0x7705bc: 972, + 0x7705bd: 973, + 0x7705be: 974, + 0x7705bf: 975, + 0x7705c0: 977, + 0x7705c1: 978, + 0x7705c2: 979, + 0x7705c3: 980, + 0x7705c4: 981, + 0x7705c5: 982, + 0x7705c6: 983, + 0x7705c7: 984, + 0x7705c8: 1025, + 0x7705c9: 1026, + 0x7705ca: 1027, + 0x7705cb: 1028, + 0x7705cc: 1029, + 0x7705cd: 1030, + 0x7705ce: 1031, + 0x7705cf: 1032, + 0x7705d0: 1033, + 0x7705d1: 1034, + 0x7705d2: 1037, + 0x7705d3: 1040, + 0x7705d4: 1041, + 0x7705d5: 1042, + 0x7705d6: 1043, + 0x7705d7: 1044, + 0x7705d8: 1045, + 0x7705d9: 1046, + 0x7705da: 1049, + 0x7705db: 1050, + 0x7705dc: 1051, + 0x7705dd: 1052, + 0x7705de: 1053, + 0x7705df: 1054, + 0x7705e0: 1055, + 0x7705e1: 1056, + 0x7705e2: 1057, + 0x7705e3: 1058, + 0x7705e4: 1059, + 0x7705e5: 1060, + 0x7705e6: 1061, + 0x7705e7: 1062, + 0x7705e8: 1063, + 0x7705e9: 1064, + 0x7705ea: 1065, + 0x7705eb: 1066, + 0x7705ec: 1067, + 0x7705ed: 1068, + 0x7705ee: 1069, + 0x7705ef: 1070, + 0x7705f0: 1152, + 0x7705f1: 1154, + 0x7705f2: 1155, + 0x7705f3: 1156, + 0x7705f4: 1157, + 0x7705f5: 1158, + 0x7705f6: 1159, + 0x7705f7: 1160, + 0x7705f8: 1161, + 0x7705f9: 1162, + 0x7705fa: 1163, + 0x7705fb: 1164, + 0x7705fc: 1165, + 0x7705fd: 1166, + 0x7705fe: 1167, + 0x7705ff: 1168, + 0x770600: 1169, + 0x770601: 1173, + 0x770602: 1174, + 0x770603: 1175, + 0x770604: 1176, + 0x770605: 1177, + 0x770606: 1178, + 0x770607: 1216, + 0x770608: 1217, + 0x770609: 1218, + 0x77060a: 1219, + 0x77060b: 1220, + 0x77060c: 1221, + 0x77060d: 1222, + 0x77060e: 1223, + 0x77060f: 1224, + 0x770610: 1225, + 0x770611: 1226, + 0x770612: 1227, + 0x770613: 1228, + 0x770614: 1229, + 0x770615: 1230, + 0x770616: 1231, + 0x770617: 1232, + 0x770618: 1233, + 0x770619: 1234, + 0x77061a: 1235, + 0x77061b: 1236, + 0x77061c: 1237, + 0x77061d: 1238, + 0x77061e: 1239, + 0x77061f: 1240, + 0x770620: 1241, + 0x770621: 1242, + 0x770622: 1243, + 0x770623: 1244, + 0x770624: 1245, + 0x770625: 1246, + 0x770626: 1247, + 0x770627: 1248, + 0x770628: 1249, + 0x770629: 1250, + 0x77062a: 1251, + 0x77062b: 1252, + 0x77062c: 1253, + 0x77062d: 1254, + 0x77062e: 1255, + 0x77062f: 1256, + 0x770630: 1257, + 0x770631: 1258, + 0x770632: 1259, + 0x770633: 1260, + 0x770634: 1261, + 0x770635: 1262, + 0x770636: 1263, + 0x770637: 1264, + 0x770638: 1265, + 0x770639: 1266, + 0x77063a: 1267, + 0x77063b: 1268, + 0x77063c: 1269, + 0x77063d: 1280, + 0x77063e: 1281, + 0x77063f: 1282, + 0x770640: 1283, + 0x770641: 1284, + 0x770642: 1285, + 0x770643: 1286, + 0x770644: 1289, + 0x770645: 1290, + 0x770646: 1291, + 0x770647: 1292, + 0x770648: 1293, + 0x770649: 1294, + 0x77064a: 1295, + 0x77064b: 1296, + 0x77064c: 1297, + 0x77064d: 1298, + 0x77064e: 1299, + 0x77064f: 1300, + 0x770650: 1301, + 0x770651: 1302, + 0x770652: 1303, + 0x770653: 1344, + 0x770654: 1345, + 0x770655: 1346, + 0x770656: 1347, + 0x770657: 1348, + 0x770658: 1349, + 0x770659: 1350, + 0x77065a: 1351, + 0x77065b: 1352, + 0x77065c: 1354, + 0x77065d: 1355, + 0x77065e: 1356, + 0x77065f: 1357, + 0x770660: 1358, + 0x770661: 1359, + 0x770662: 1360, + 0x770663: 1361, + 0x770664: 1362, + 0x770665: 1363, + 0x770666: 1365, + 0x770667: 1366, + 0x770668: 1367, + 0x770669: 1368, + 0x77066a: 1369, + 0x77066b: 1370, + 0x77066c: 1371, + 0x77066d: 1372, + 0x77066e: 1374, + 0x77066f: 1375, + 0x770670: 1376, + 0x770671: 1379, + 0x770672: 1380, + 0x770673: 1381, + 0x770674: 1382, + 0x770675: 1383, + 0x770676: 1384, + 0x770677: 1385, + 0x770678: 1386, + 0x770679: 1387, + 0x77067a: 1388, + 0x77067b: 1389, + 0x77067c: 1390, + 0x77067d: 1391, + 0x77067e: 1392, + 0x77067f: 1393, + 0x770680: 1394, + 0x770681: 1395, + 0x770682: 1396, + 0x770683: 1397, + 0x770684: 1398, + 0x770685: 1408, + 0x770686: 1409, + 0x770687: 1410, + 0x770688: 1411, + 0x770689: 1412, + 0x77068a: 1414, + 0x77068b: 1472, + 0x77068c: 1473, + 0x77068d: 1474, + 0x77068e: 1475, + 0x77068f: 1476, + 0x770690: 1477, + 0x770691: 1478, + 0x770692: 1479, + 0x770693: 1480, + 0x770694: 1481, + 0x770695: 1482, + 0x770696: 1483, + 0x770697: 1484, + 0x770698: 1486, + 0x770699: 1487, + 0x77069a: 1488, + 0x77069b: 1489, + 0x77069c: 1490, + 0x77069d: 1491, + 0x77069e: 1495, + 0x77069f: 1496, + 0x7706a0: 1497, + 0x7706a1: 1498, + 0x7706a2: 1499, + 0x7706a3: 1500, + 0x7706a4: 1501, + 0x7706a5: 1502, + 0x7706a6: 1503, + 0x7706a7: 1504, + 0x7706a8: 1505, + 0x7706a9: 1506, + 0x7706aa: 1507, + 0x7706ab: 1508, + 0x7706ac: 1536, + 0x7706ad: 1537, + 0x7706ae: 1538, + 0x7706af: 1539, + 0x7706b0: 1540, + 0x7706b1: 1541, + 0x7706b2: 1600, + 0x7706b3: 1601, + 0x7706b4: 1602, + 0x7706b5: 1603, + 0x7706b6: 1604, + 0x7706b7: 1605, + 0x7706b8: 1606, + 0x7706b9: 1607, + 0x7706ba: 1612, + 0x7706bb: 1613, + 0x7706bc: 1614, + 0x7706bd: 1615, + 0x7706be: 1616, + 0x7706bf: 1617, + 0x7706c0: 1618, + 0x7706c1: 1619, + 0x7706c2: 1620, + 0x7706c3: 1621, + 0x7706c4: 1622, + 0x7706c5: 1664, + 0x7706c6: 1665, + 0x7706c7: 1667, + 0x7706c8: 1668, + 0x7706c9: 1670, + 0x7706ca: 1671, + 0x7706cb: 1672, + 0x7706cc: 1673, + 0x7706cd: 1674, + 0x7706ce: 1675, + 0x7706cf: 1676, + 0x7706d0: 1677, + 0x7706d1: 1678, + 0x7706d2: 1679, + 0x7706d3: 1680, + 0x7706d4: 1681, + 0x7706d5: 1682, + 0x7706d6: 1683, + 0x7706d7: 1684, + 0x7706d8: 1685, + 0x7706d9: 1686, + 0x7706da: 1730, + 0x7706db: 1732, + 0x7706dc: 1734, + 0x7706dd: 1792, + 0x7706de: 1793, + 0x7706df: 1794, + 0x7706e0: 1795, + 0x7706e1: 1796, + 0x7706e2: 1797, + 0x7706e3: 1798, + 0x7706e4: 1799, + 0x7706e5: 1800, + 0x7706e6: 1801, + 0x7706e7: 1802, + 0x7706e8: 1803, + 0x7706e9: 1804, + 0x7706ea: 1805, + 0x7706eb: 1810, + 0x7706ec: 1811, + 0x7706ed: 1812, + 0x7706ee: 1813, + 0x7706ef: 1814, + 0x7706f0: 1815, + 0x7706f1: 1817, + 0x7706f2: 1818, + 0x7706f3: 1819, + 0x7706f4: 1820, + 0x7706f5: 1821, + 0x7706f6: 1822, + 0x7706f7: 1823, + 0x7706f8: 1824, + 0x7706f9: 1825, + 0x7706fa: 1826, + 0x7706fb: 1827, + 0x7706fc: 1828, + 0x7706fd: 1831, + 0x7706fe: 1832, + 0x7706ff: 1858, +} diff --git a/worlds/kdl3/Compression.py b/worlds/kdl3/Compression.py new file mode 100644 index 0000000000..ec5461fbec --- /dev/null +++ b/worlds/kdl3/Compression.py @@ -0,0 +1,57 @@ +def hal_decompress(comp: bytes) -> bytes: + """ + HAL decompression based on exhal by devinacker + https://github.com/devinacker/exhal + """ + inpos = 0 + + inval = 0 + output = bytearray() + while inval != 0xFF: + remaining = 65536 - inpos + if remaining < 1: + return bytes() + inval = comp[inpos] + inpos += 1 + if inval == 0xFF: + break + if (inval & 0xE0) == 0xE0: + command = (inval >> 2) & 0x07 + length = (((inval & 0x03) << 8) | comp[inpos]) + 1 + inpos += 1 + else: + command = inval >> 5 + length = (inval & 0x1F) + 1 + if (command == 2 and ((len(output) + 2*length) > 65536)) or (len(output) + length) > 65536: + return bytes() + if command == 0: + output.extend(comp[inpos:inpos+length]) + inpos += length + elif command == 1: + output.extend([comp[inpos] for _ in range(length)]) + inpos += 1 + elif command == 2: + output.extend([comp[x] for _ in range(length) for x in (inpos, inpos+1)]) + inpos += 2 + elif command == 3: + output.extend([comp[inpos] + i for i in range(length)]) + inpos += 1 + elif command == 4 or command == 7: + offset = (comp[inpos] << 8) | comp[inpos + 1] + if (offset + length) > 65536: + return bytes() + output.extend(output[offset:offset+length]) + inpos += 2 + elif command == 5: + offset = (comp[inpos] << 8) | comp[inpos + 1] + if (offset + length) > 65536: + return bytes() + output.extend([int('{:08b}'.format(x)[::-1], 2) for x in output[offset:offset+length]]) + inpos += 2 + elif command == 6: + offset = (comp[inpos] << 8) | comp[inpos + 1] + if offset < length - 1: + return bytes() + output.extend([output[offset - x] for x in range(length)]) + inpos += 2 + return bytes(output) diff --git a/worlds/kdl3/Gifting.py b/worlds/kdl3/Gifting.py new file mode 100644 index 0000000000..8ccba7ec1a --- /dev/null +++ b/worlds/kdl3/Gifting.py @@ -0,0 +1,282 @@ +# Small subfile to handle gifting info such as desired traits and giftbox management +import typing + + +async def update_object(ctx, key: str, value: typing.Dict): + await ctx.send_msgs([ + { + "cmd": "Set", + "key": key, + "default": {}, + "want_reply": False, + "operations": [ + {"operation": "update", "value": value} + ] + } + ]) + + +async def pop_object(ctx, key: str, value: str): + await ctx.send_msgs([ + { + "cmd": "Set", + "key": key, + "default": {}, + "want_reply": False, + "operations": [ + {"operation": "pop", "value": value} + ] + } + ]) + + +async def initialize_giftboxes(ctx, giftbox_key: str, motherbox_key: str, is_open: bool): + ctx.set_notify(motherbox_key, giftbox_key) + await update_object(ctx, f"Giftboxes;{ctx.team}", {f"{ctx.slot}": + { + "IsOpen": is_open, + **kdl3_gifting_options + }}) + ctx.gifting = is_open + + +kdl3_gifting_options = { + "AcceptsAnyGift": True, + "DesiredTraits": [ + "Consumable", "Food", "Drink", "Candy", "Tomato", + "Invincible", "Life", "Heal", "Health", "Trap", + "Goo", "Gel", "Slow", "Slowness", "Eject", "Removal" + ], + "MinimumGiftVersion": 2, +} + +kdl3_gifts = { + 1: { + "ItemName": "1-Up", + "Amount": 1, + "ItemValue": 400000, + "Traits": [ + { + "Trait": "Consumable", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Life", + "Quality": 1, + "Duration": 1 + } + ] + }, + 2: { + "ItemName": "Maxim Tomato", + "Amount": 1, + "ItemValue": 500000, + "Traits": [ + { + "Trait": "Consumable", + "Quality": 5, + "Duration": 1, + }, + { + "Trait": "Heal", + "Quality": 5, + "Duration": 1, + }, + { + "Trait": "Food", + "Quality": 5, + "Duration": 1, + }, + { + "Trait": "Tomato", + "Quality": 5, + "Duration": 1, + }, + { + "Trait": "Vegetable", + "Quality": 5, + "Duration": 1, + } + ] + }, + 3: { + "ItemName": "Energy Drink", + "Amount": 1, + "ItemValue": 100000, + "Traits": [ + { + "Trait": "Consumable", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Heal", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Drink", + "Quality": 1, + "Duration": 1, + }, + ] + }, + 5: { + "ItemName": "Small Star Piece", + "Amount": 1, + "ItemValue": 10000, + "Traits": [ + { + "Trait": "Currency", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Money", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Star", + "Quality": 1, + "Duration": 1 + } + ] + }, + 6: { + "ItemName": "Medium Star Piece", + "Amount": 1, + "ItemValue": 30000, + "Traits": [ + { + "Trait": "Currency", + "Quality": 3, + "Duration": 1, + }, + { + "Trait": "Money", + "Quality": 3, + "Duration": 1, + }, + { + "Trait": "Star", + "Quality": 3, + "Duration": 1 + } + ] + }, + 7: { + "ItemName": "Large Star Piece", + "Amount": 1, + "ItemValue": 50000, + "Traits": [ + { + "Trait": "Currency", + "Quality": 5, + "Duration": 1, + }, + { + "Trait": "Money", + "Quality": 5, + "Duration": 1, + }, + { + "Trait": "Star", + "Quality": 5, + "Duration": 1 + } + ] + }, +} + +kdl3_trap_gifts = { + 0: { + "ItemName": "Gooey Bag", + "Amount": 1, + "ItemValue": 10000, + "Traits": [ + { + "Trait": "Trap", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Goo", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Gel", + "Quality": 1, + "Duration": 1 + } + ] + }, + 1: { + "ItemName": "Slowness", + "Amount": 1, + "ItemValue": 10000, + "Traits": [ + { + "Trait": "Trap", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Slow", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Slowness", + "Quality": 1, + "Duration": 1 + } + ] + }, + 2: { + "ItemName": "Eject Ability", + "Amount": 1, + "ItemValue": 10000, + "Traits": [ + { + "Trait": "Trap", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Eject", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Removal", + "Quality": 1, + "Duration": 1 + } + ] + }, + 3: { + "ItemName": "Bad Meal", + "Amount": 1, + "ItemValue": 10000, + "Traits": [ + { + "Trait": "Trap", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Damage", + "Quality": 1, + "Duration": 1, + }, + { + "Trait": "Food", + "Quality": 1, + "Duration": 1 + } + ] + }, +} diff --git a/worlds/kdl3/Items.py b/worlds/kdl3/Items.py new file mode 100644 index 0000000000..66c7f8fee3 --- /dev/null +++ b/worlds/kdl3/Items.py @@ -0,0 +1,105 @@ +from BaseClasses import Item +import typing + + +class ItemData(typing.NamedTuple): + code: typing.Optional[int] + progression: bool + skip_balancing: bool = False + trap: bool = False + + +class KDL3Item(Item): + game = "Kirby's Dream Land 3" + + +copy_ability_table = { + "Burning": ItemData(0x770001, True), + "Stone": ItemData(0x770002, True), + "Ice": ItemData(0x770003, True), + "Needle": ItemData(0x770004, True), + "Clean": ItemData(0x770005, True), + "Parasol": ItemData(0x770006, True), + "Spark": ItemData(0x770007, True), + "Cutter": ItemData(0x770008, True) +} + +animal_friend_table = { + "Rick": ItemData(0x770010, True), + "Kine": ItemData(0x770011, True), + "Coo": ItemData(0x770012, True), + "Nago": ItemData(0x770013, True), + "ChuChu": ItemData(0x770014, True), + "Pitch": ItemData(0x770015, True) +} + +animal_friend_spawn_table = { + "Rick Spawn": ItemData(None, True), + "Kine Spawn": ItemData(None, True), + "Coo Spawn": ItemData(None, True), + "Nago Spawn": ItemData(None, True), + "ChuChu Spawn": ItemData(None, True), + "Pitch Spawn": ItemData(None, True) +} + +copy_ability_access_table = { + "No Ability": ItemData(None, False), + "Burning Ability": ItemData(None, True), + "Stone Ability": ItemData(None, True), + "Ice Ability": ItemData(None, True), + "Needle Ability": ItemData(None, True), + "Clean Ability": ItemData(None, True), + "Parasol Ability": ItemData(None, True), + "Spark Ability": ItemData(None, True), + "Cutter Ability": ItemData(None, True), +} + +misc_item_table = { + "Heart Star": ItemData(0x770020, True, True), + "1-Up": ItemData(0x770021, False), + "Maxim Tomato": ItemData(0x770022, False), + "Invincible Candy": ItemData(0x770023, False), + "Little Star": ItemData(0x770024, False), + "Medium Star": ItemData(0x770025, False), + "Big Star": ItemData(0x770026, False), +} + +trap_item_table = { + "Gooey Bag": ItemData(0x770040, False, False, True), + "Slowness": ItemData(0x770041, False, False, True), + "Eject Ability": ItemData(0x770042, False, False, True) +} + +filler_item_weights = { + "1-Up": 4, + "Maxim Tomato": 2, + "Invincible Candy": 2 +} + +star_item_weights = { + "Little Star": 4, + "Medium Star": 2, + "Big Star": 1 +} + +total_filler_weights = { + **filler_item_weights, + **star_item_weights +} + + +item_table = { + **copy_ability_table, + **copy_ability_access_table, + **animal_friend_table, + **animal_friend_spawn_table, + **misc_item_table, + **trap_item_table +} + +item_names = { + "Copy Ability": set(copy_ability_table), + "Animal Friend": set(animal_friend_table), +} + +lookup_name_to_id: typing.Dict[str, int] = {item_name: data.code for item_name, data in item_table.items() if data.code} diff --git a/worlds/kdl3/Locations.py b/worlds/kdl3/Locations.py new file mode 100644 index 0000000000..4d039a1349 --- /dev/null +++ b/worlds/kdl3/Locations.py @@ -0,0 +1,940 @@ +import typing +from BaseClasses import Location, Region +from .Names import LocationName + +if typing.TYPE_CHECKING: + from .Room import KDL3Room + + +class KDL3Location(Location): + game: str = "Kirby's Dream Land 3" + room: typing.Optional["KDL3Room"] = None + + def __init__(self, player: int, name: str, address: typing.Optional[int], parent: typing.Union[Region, None]): + super().__init__(player, name, address, parent) + if not address: + self.show_in_spoiler = False + + +stage_locations = { + 0x770001: LocationName.grass_land_1, + 0x770002: LocationName.grass_land_2, + 0x770003: LocationName.grass_land_3, + 0x770004: LocationName.grass_land_4, + 0x770005: LocationName.grass_land_5, + 0x770006: LocationName.grass_land_6, + 0x770007: LocationName.ripple_field_1, + 0x770008: LocationName.ripple_field_2, + 0x770009: LocationName.ripple_field_3, + 0x77000A: LocationName.ripple_field_4, + 0x77000B: LocationName.ripple_field_5, + 0x77000C: LocationName.ripple_field_6, + 0x77000D: LocationName.sand_canyon_1, + 0x77000E: LocationName.sand_canyon_2, + 0x77000F: LocationName.sand_canyon_3, + 0x770010: LocationName.sand_canyon_4, + 0x770011: LocationName.sand_canyon_5, + 0x770012: LocationName.sand_canyon_6, + 0x770013: LocationName.cloudy_park_1, + 0x770014: LocationName.cloudy_park_2, + 0x770015: LocationName.cloudy_park_3, + 0x770016: LocationName.cloudy_park_4, + 0x770017: LocationName.cloudy_park_5, + 0x770018: LocationName.cloudy_park_6, + 0x770019: LocationName.iceberg_1, + 0x77001A: LocationName.iceberg_2, + 0x77001B: LocationName.iceberg_3, + 0x77001C: LocationName.iceberg_4, + 0x77001D: LocationName.iceberg_5, + 0x77001E: LocationName.iceberg_6, +} + +heart_star_locations = { + 0x770101: LocationName.grass_land_tulip, + 0x770102: LocationName.grass_land_muchi, + 0x770103: LocationName.grass_land_pitcherman, + 0x770104: LocationName.grass_land_chao, + 0x770105: LocationName.grass_land_mine, + 0x770106: LocationName.grass_land_pierre, + 0x770107: LocationName.ripple_field_kamuribana, + 0x770108: LocationName.ripple_field_bakasa, + 0x770109: LocationName.ripple_field_elieel, + 0x77010A: LocationName.ripple_field_toad, + 0x77010B: LocationName.ripple_field_mama_pitch, + 0x77010C: LocationName.ripple_field_hb002, + 0x77010D: LocationName.sand_canyon_mushrooms, + 0x77010E: LocationName.sand_canyon_auntie, + 0x77010F: LocationName.sand_canyon_caramello, + 0x770110: LocationName.sand_canyon_hikari, + 0x770111: LocationName.sand_canyon_nyupun, + 0x770112: LocationName.sand_canyon_rob, + 0x770113: LocationName.cloudy_park_hibanamodoki, + 0x770114: LocationName.cloudy_park_piyokeko, + 0x770115: LocationName.cloudy_park_mrball, + 0x770116: LocationName.cloudy_park_mikarin, + 0x770117: LocationName.cloudy_park_pick, + 0x770118: LocationName.cloudy_park_hb007, + 0x770119: LocationName.iceberg_kogoesou, + 0x77011A: LocationName.iceberg_samus, + 0x77011B: LocationName.iceberg_kawasaki, + 0x77011C: LocationName.iceberg_name, + 0x77011D: LocationName.iceberg_shiro, + 0x77011E: LocationName.iceberg_angel, +} + +boss_locations = { + 0x770200: LocationName.grass_land_whispy, + 0x770201: LocationName.ripple_field_acro, + 0x770202: LocationName.sand_canyon_poncon, + 0x770203: LocationName.cloudy_park_ado, + 0x770204: LocationName.iceberg_dedede, +} + +consumable_locations = { + 0x770300: LocationName.grass_land_1_u1, + 0x770301: LocationName.grass_land_1_m1, + 0x770302: LocationName.grass_land_2_u1, + 0x770303: LocationName.grass_land_3_u1, + 0x770304: LocationName.grass_land_3_m1, + 0x770305: LocationName.grass_land_4_m1, + 0x770306: LocationName.grass_land_4_u1, + 0x770307: LocationName.grass_land_4_m2, + 0x770308: LocationName.grass_land_4_m3, + 0x770309: LocationName.grass_land_6_u1, + 0x77030A: LocationName.grass_land_6_u2, + 0x77030B: LocationName.ripple_field_2_u1, + 0x77030C: LocationName.ripple_field_2_m1, + 0x77030D: LocationName.ripple_field_3_m1, + 0x77030E: LocationName.ripple_field_3_u1, + 0x77030F: LocationName.ripple_field_4_m2, + 0x770310: LocationName.ripple_field_4_u1, + 0x770311: LocationName.ripple_field_4_m1, + 0x770312: LocationName.ripple_field_5_u1, + 0x770313: LocationName.ripple_field_5_m2, + 0x770314: LocationName.ripple_field_5_m1, + 0x770315: LocationName.sand_canyon_1_u1, + 0x770316: LocationName.sand_canyon_2_u1, + 0x770317: LocationName.sand_canyon_2_m1, + 0x770318: LocationName.sand_canyon_4_m1, + 0x770319: LocationName.sand_canyon_4_u1, + 0x77031A: LocationName.sand_canyon_4_m2, + 0x77031B: LocationName.sand_canyon_5_u1, + 0x77031C: LocationName.sand_canyon_5_u3, + 0x77031D: LocationName.sand_canyon_5_m1, + 0x77031E: LocationName.sand_canyon_5_u4, + 0x77031F: LocationName.sand_canyon_5_u2, + 0x770320: LocationName.cloudy_park_1_m1, + 0x770321: LocationName.cloudy_park_1_u1, + 0x770322: LocationName.cloudy_park_4_u1, + 0x770323: LocationName.cloudy_park_4_m1, + 0x770324: LocationName.cloudy_park_5_m1, + 0x770325: LocationName.cloudy_park_6_u1, + 0x770326: LocationName.iceberg_3_m1, + 0x770327: LocationName.iceberg_5_u1, + 0x770328: LocationName.iceberg_5_u2, + 0x770329: LocationName.iceberg_5_u3, + 0x77032A: LocationName.iceberg_6_m1, + 0x77032B: LocationName.iceberg_6_u1, +} + +level_consumables = { + 1: [0, 1], + 2: [2], + 3: [3, 4], + 4: [5, 6, 7, 8], + 6: [9, 10], + 8: [11, 12], + 9: [13, 14], + 10: [15, 16, 17], + 11: [18, 19, 20], + 13: [21], + 14: [22, 23], + 16: [24, 25, 26], + 17: [27, 28, 29, 30, 31], + 19: [32, 33], + 22: [34, 35], + 23: [36], + 24: [37], + 27: [38], + 29: [39, 40, 41], + 30: [42, 43], +} + +star_locations = { + 0x770401: LocationName.grass_land_1_s1, + 0x770402: LocationName.grass_land_1_s2, + 0x770403: LocationName.grass_land_1_s3, + 0x770404: LocationName.grass_land_1_s4, + 0x770405: LocationName.grass_land_1_s5, + 0x770406: LocationName.grass_land_1_s6, + 0x770407: LocationName.grass_land_1_s7, + 0x770408: LocationName.grass_land_1_s8, + 0x770409: LocationName.grass_land_1_s9, + 0x77040a: LocationName.grass_land_1_s10, + 0x77040b: LocationName.grass_land_1_s11, + 0x77040c: LocationName.grass_land_1_s12, + 0x77040d: LocationName.grass_land_1_s13, + 0x77040e: LocationName.grass_land_1_s14, + 0x77040f: LocationName.grass_land_1_s15, + 0x770410: LocationName.grass_land_1_s16, + 0x770411: LocationName.grass_land_1_s17, + 0x770412: LocationName.grass_land_1_s18, + 0x770413: LocationName.grass_land_1_s19, + 0x770414: LocationName.grass_land_1_s20, + 0x770415: LocationName.grass_land_1_s21, + 0x770416: LocationName.grass_land_1_s22, + 0x770417: LocationName.grass_land_1_s23, + 0x770418: LocationName.grass_land_2_s1, + 0x770419: LocationName.grass_land_2_s2, + 0x77041a: LocationName.grass_land_2_s3, + 0x77041b: LocationName.grass_land_2_s4, + 0x77041c: LocationName.grass_land_2_s5, + 0x77041d: LocationName.grass_land_2_s6, + 0x77041e: LocationName.grass_land_2_s7, + 0x77041f: LocationName.grass_land_2_s8, + 0x770420: LocationName.grass_land_2_s9, + 0x770421: LocationName.grass_land_2_s10, + 0x770422: LocationName.grass_land_2_s11, + 0x770423: LocationName.grass_land_2_s12, + 0x770424: LocationName.grass_land_2_s13, + 0x770425: LocationName.grass_land_2_s14, + 0x770426: LocationName.grass_land_2_s15, + 0x770427: LocationName.grass_land_2_s16, + 0x770428: LocationName.grass_land_2_s17, + 0x770429: LocationName.grass_land_2_s18, + 0x77042a: LocationName.grass_land_2_s19, + 0x77042b: LocationName.grass_land_2_s20, + 0x77042c: LocationName.grass_land_2_s21, + 0x77042d: LocationName.grass_land_3_s1, + 0x77042e: LocationName.grass_land_3_s2, + 0x77042f: LocationName.grass_land_3_s3, + 0x770430: LocationName.grass_land_3_s4, + 0x770431: LocationName.grass_land_3_s5, + 0x770432: LocationName.grass_land_3_s6, + 0x770433: LocationName.grass_land_3_s7, + 0x770434: LocationName.grass_land_3_s8, + 0x770435: LocationName.grass_land_3_s9, + 0x770436: LocationName.grass_land_3_s10, + 0x770437: LocationName.grass_land_3_s11, + 0x770438: LocationName.grass_land_3_s12, + 0x770439: LocationName.grass_land_3_s13, + 0x77043a: LocationName.grass_land_3_s14, + 0x77043b: LocationName.grass_land_3_s15, + 0x77043c: LocationName.grass_land_3_s16, + 0x77043d: LocationName.grass_land_3_s17, + 0x77043e: LocationName.grass_land_3_s18, + 0x77043f: LocationName.grass_land_3_s19, + 0x770440: LocationName.grass_land_3_s20, + 0x770441: LocationName.grass_land_3_s21, + 0x770442: LocationName.grass_land_3_s22, + 0x770443: LocationName.grass_land_3_s23, + 0x770444: LocationName.grass_land_3_s24, + 0x770445: LocationName.grass_land_3_s25, + 0x770446: LocationName.grass_land_3_s26, + 0x770447: LocationName.grass_land_3_s27, + 0x770448: LocationName.grass_land_3_s28, + 0x770449: LocationName.grass_land_3_s29, + 0x77044a: LocationName.grass_land_3_s30, + 0x77044b: LocationName.grass_land_3_s31, + 0x77044c: LocationName.grass_land_4_s1, + 0x77044d: LocationName.grass_land_4_s2, + 0x77044e: LocationName.grass_land_4_s3, + 0x77044f: LocationName.grass_land_4_s4, + 0x770450: LocationName.grass_land_4_s5, + 0x770451: LocationName.grass_land_4_s6, + 0x770452: LocationName.grass_land_4_s7, + 0x770453: LocationName.grass_land_4_s8, + 0x770454: LocationName.grass_land_4_s9, + 0x770455: LocationName.grass_land_4_s10, + 0x770456: LocationName.grass_land_4_s11, + 0x770457: LocationName.grass_land_4_s12, + 0x770458: LocationName.grass_land_4_s13, + 0x770459: LocationName.grass_land_4_s14, + 0x77045a: LocationName.grass_land_4_s15, + 0x77045b: LocationName.grass_land_4_s16, + 0x77045c: LocationName.grass_land_4_s17, + 0x77045d: LocationName.grass_land_4_s18, + 0x77045e: LocationName.grass_land_4_s19, + 0x77045f: LocationName.grass_land_4_s20, + 0x770460: LocationName.grass_land_4_s21, + 0x770461: LocationName.grass_land_4_s22, + 0x770462: LocationName.grass_land_4_s23, + 0x770463: LocationName.grass_land_4_s24, + 0x770464: LocationName.grass_land_4_s25, + 0x770465: LocationName.grass_land_4_s26, + 0x770466: LocationName.grass_land_4_s27, + 0x770467: LocationName.grass_land_4_s28, + 0x770468: LocationName.grass_land_4_s29, + 0x770469: LocationName.grass_land_4_s30, + 0x77046a: LocationName.grass_land_4_s31, + 0x77046b: LocationName.grass_land_4_s32, + 0x77046c: LocationName.grass_land_4_s33, + 0x77046d: LocationName.grass_land_4_s34, + 0x77046e: LocationName.grass_land_4_s35, + 0x77046f: LocationName.grass_land_4_s36, + 0x770470: LocationName.grass_land_4_s37, + 0x770471: LocationName.grass_land_5_s1, + 0x770472: LocationName.grass_land_5_s2, + 0x770473: LocationName.grass_land_5_s3, + 0x770474: LocationName.grass_land_5_s4, + 0x770475: LocationName.grass_land_5_s5, + 0x770476: LocationName.grass_land_5_s6, + 0x770477: LocationName.grass_land_5_s7, + 0x770478: LocationName.grass_land_5_s8, + 0x770479: LocationName.grass_land_5_s9, + 0x77047a: LocationName.grass_land_5_s10, + 0x77047b: LocationName.grass_land_5_s11, + 0x77047c: LocationName.grass_land_5_s12, + 0x77047d: LocationName.grass_land_5_s13, + 0x77047e: LocationName.grass_land_5_s14, + 0x77047f: LocationName.grass_land_5_s15, + 0x770480: LocationName.grass_land_5_s16, + 0x770481: LocationName.grass_land_5_s17, + 0x770482: LocationName.grass_land_5_s18, + 0x770483: LocationName.grass_land_5_s19, + 0x770484: LocationName.grass_land_5_s20, + 0x770485: LocationName.grass_land_5_s21, + 0x770486: LocationName.grass_land_5_s22, + 0x770487: LocationName.grass_land_5_s23, + 0x770488: LocationName.grass_land_5_s24, + 0x770489: LocationName.grass_land_5_s25, + 0x77048a: LocationName.grass_land_5_s26, + 0x77048b: LocationName.grass_land_5_s27, + 0x77048c: LocationName.grass_land_5_s28, + 0x77048d: LocationName.grass_land_5_s29, + 0x77048e: LocationName.grass_land_6_s1, + 0x77048f: LocationName.grass_land_6_s2, + 0x770490: LocationName.grass_land_6_s3, + 0x770491: LocationName.grass_land_6_s4, + 0x770492: LocationName.grass_land_6_s5, + 0x770493: LocationName.grass_land_6_s6, + 0x770494: LocationName.grass_land_6_s7, + 0x770495: LocationName.grass_land_6_s8, + 0x770496: LocationName.grass_land_6_s9, + 0x770497: LocationName.grass_land_6_s10, + 0x770498: LocationName.grass_land_6_s11, + 0x770499: LocationName.grass_land_6_s12, + 0x77049a: LocationName.grass_land_6_s13, + 0x77049b: LocationName.grass_land_6_s14, + 0x77049c: LocationName.grass_land_6_s15, + 0x77049d: LocationName.grass_land_6_s16, + 0x77049e: LocationName.grass_land_6_s17, + 0x77049f: LocationName.grass_land_6_s18, + 0x7704a0: LocationName.grass_land_6_s19, + 0x7704a1: LocationName.grass_land_6_s20, + 0x7704a2: LocationName.grass_land_6_s21, + 0x7704a3: LocationName.grass_land_6_s22, + 0x7704a4: LocationName.grass_land_6_s23, + 0x7704a5: LocationName.grass_land_6_s24, + 0x7704a6: LocationName.grass_land_6_s25, + 0x7704a7: LocationName.grass_land_6_s26, + 0x7704a8: LocationName.grass_land_6_s27, + 0x7704a9: LocationName.grass_land_6_s28, + 0x7704aa: LocationName.grass_land_6_s29, + 0x7704ab: LocationName.ripple_field_1_s1, + 0x7704ac: LocationName.ripple_field_1_s2, + 0x7704ad: LocationName.ripple_field_1_s3, + 0x7704ae: LocationName.ripple_field_1_s4, + 0x7704af: LocationName.ripple_field_1_s5, + 0x7704b0: LocationName.ripple_field_1_s6, + 0x7704b1: LocationName.ripple_field_1_s7, + 0x7704b2: LocationName.ripple_field_1_s8, + 0x7704b3: LocationName.ripple_field_1_s9, + 0x7704b4: LocationName.ripple_field_1_s10, + 0x7704b5: LocationName.ripple_field_1_s11, + 0x7704b6: LocationName.ripple_field_1_s12, + 0x7704b7: LocationName.ripple_field_1_s13, + 0x7704b8: LocationName.ripple_field_1_s14, + 0x7704b9: LocationName.ripple_field_1_s15, + 0x7704ba: LocationName.ripple_field_1_s16, + 0x7704bb: LocationName.ripple_field_1_s17, + 0x7704bc: LocationName.ripple_field_1_s18, + 0x7704bd: LocationName.ripple_field_1_s19, + 0x7704be: LocationName.ripple_field_2_s1, + 0x7704bf: LocationName.ripple_field_2_s2, + 0x7704c0: LocationName.ripple_field_2_s3, + 0x7704c1: LocationName.ripple_field_2_s4, + 0x7704c2: LocationName.ripple_field_2_s5, + 0x7704c3: LocationName.ripple_field_2_s6, + 0x7704c4: LocationName.ripple_field_2_s7, + 0x7704c5: LocationName.ripple_field_2_s8, + 0x7704c6: LocationName.ripple_field_2_s9, + 0x7704c7: LocationName.ripple_field_2_s10, + 0x7704c8: LocationName.ripple_field_2_s11, + 0x7704c9: LocationName.ripple_field_2_s12, + 0x7704ca: LocationName.ripple_field_2_s13, + 0x7704cb: LocationName.ripple_field_2_s14, + 0x7704cc: LocationName.ripple_field_2_s15, + 0x7704cd: LocationName.ripple_field_2_s16, + 0x7704ce: LocationName.ripple_field_2_s17, + 0x7704cf: LocationName.ripple_field_3_s1, + 0x7704d0: LocationName.ripple_field_3_s2, + 0x7704d1: LocationName.ripple_field_3_s3, + 0x7704d2: LocationName.ripple_field_3_s4, + 0x7704d3: LocationName.ripple_field_3_s5, + 0x7704d4: LocationName.ripple_field_3_s6, + 0x7704d5: LocationName.ripple_field_3_s7, + 0x7704d6: LocationName.ripple_field_3_s8, + 0x7704d7: LocationName.ripple_field_3_s9, + 0x7704d8: LocationName.ripple_field_3_s10, + 0x7704d9: LocationName.ripple_field_3_s11, + 0x7704da: LocationName.ripple_field_3_s12, + 0x7704db: LocationName.ripple_field_3_s13, + 0x7704dc: LocationName.ripple_field_3_s14, + 0x7704dd: LocationName.ripple_field_3_s15, + 0x7704de: LocationName.ripple_field_3_s16, + 0x7704df: LocationName.ripple_field_3_s17, + 0x7704e0: LocationName.ripple_field_3_s18, + 0x7704e1: LocationName.ripple_field_3_s19, + 0x7704e2: LocationName.ripple_field_3_s20, + 0x7704e3: LocationName.ripple_field_3_s21, + 0x7704e4: LocationName.ripple_field_4_s1, + 0x7704e5: LocationName.ripple_field_4_s2, + 0x7704e6: LocationName.ripple_field_4_s3, + 0x7704e7: LocationName.ripple_field_4_s4, + 0x7704e8: LocationName.ripple_field_4_s5, + 0x7704e9: LocationName.ripple_field_4_s6, + 0x7704ea: LocationName.ripple_field_4_s7, + 0x7704eb: LocationName.ripple_field_4_s8, + 0x7704ec: LocationName.ripple_field_4_s9, + 0x7704ed: LocationName.ripple_field_4_s10, + 0x7704ee: LocationName.ripple_field_4_s11, + 0x7704ef: LocationName.ripple_field_4_s12, + 0x7704f0: LocationName.ripple_field_4_s13, + 0x7704f1: LocationName.ripple_field_4_s14, + 0x7704f2: LocationName.ripple_field_4_s15, + 0x7704f3: LocationName.ripple_field_4_s16, + 0x7704f4: LocationName.ripple_field_4_s17, + 0x7704f5: LocationName.ripple_field_4_s18, + 0x7704f6: LocationName.ripple_field_4_s19, + 0x7704f7: LocationName.ripple_field_4_s20, + 0x7704f8: LocationName.ripple_field_4_s21, + 0x7704f9: LocationName.ripple_field_4_s22, + 0x7704fa: LocationName.ripple_field_4_s23, + 0x7704fb: LocationName.ripple_field_4_s24, + 0x7704fc: LocationName.ripple_field_4_s25, + 0x7704fd: LocationName.ripple_field_4_s26, + 0x7704fe: LocationName.ripple_field_4_s27, + 0x7704ff: LocationName.ripple_field_4_s28, + 0x770500: LocationName.ripple_field_4_s29, + 0x770501: LocationName.ripple_field_4_s30, + 0x770502: LocationName.ripple_field_4_s31, + 0x770503: LocationName.ripple_field_4_s32, + 0x770504: LocationName.ripple_field_4_s33, + 0x770505: LocationName.ripple_field_4_s34, + 0x770506: LocationName.ripple_field_4_s35, + 0x770507: LocationName.ripple_field_4_s36, + 0x770508: LocationName.ripple_field_4_s37, + 0x770509: LocationName.ripple_field_4_s38, + 0x77050a: LocationName.ripple_field_4_s39, + 0x77050b: LocationName.ripple_field_4_s40, + 0x77050c: LocationName.ripple_field_4_s41, + 0x77050d: LocationName.ripple_field_4_s42, + 0x77050e: LocationName.ripple_field_4_s43, + 0x77050f: LocationName.ripple_field_4_s44, + 0x770510: LocationName.ripple_field_4_s45, + 0x770511: LocationName.ripple_field_4_s46, + 0x770512: LocationName.ripple_field_4_s47, + 0x770513: LocationName.ripple_field_4_s48, + 0x770514: LocationName.ripple_field_4_s49, + 0x770515: LocationName.ripple_field_4_s50, + 0x770516: LocationName.ripple_field_4_s51, + 0x770517: LocationName.ripple_field_5_s1, + 0x770518: LocationName.ripple_field_5_s2, + 0x770519: LocationName.ripple_field_5_s3, + 0x77051a: LocationName.ripple_field_5_s4, + 0x77051b: LocationName.ripple_field_5_s5, + 0x77051c: LocationName.ripple_field_5_s6, + 0x77051d: LocationName.ripple_field_5_s7, + 0x77051e: LocationName.ripple_field_5_s8, + 0x77051f: LocationName.ripple_field_5_s9, + 0x770520: LocationName.ripple_field_5_s10, + 0x770521: LocationName.ripple_field_5_s11, + 0x770522: LocationName.ripple_field_5_s12, + 0x770523: LocationName.ripple_field_5_s13, + 0x770524: LocationName.ripple_field_5_s14, + 0x770525: LocationName.ripple_field_5_s15, + 0x770526: LocationName.ripple_field_5_s16, + 0x770527: LocationName.ripple_field_5_s17, + 0x770528: LocationName.ripple_field_5_s18, + 0x770529: LocationName.ripple_field_5_s19, + 0x77052a: LocationName.ripple_field_5_s20, + 0x77052b: LocationName.ripple_field_5_s21, + 0x77052c: LocationName.ripple_field_5_s22, + 0x77052d: LocationName.ripple_field_5_s23, + 0x77052e: LocationName.ripple_field_5_s24, + 0x77052f: LocationName.ripple_field_5_s25, + 0x770530: LocationName.ripple_field_5_s26, + 0x770531: LocationName.ripple_field_5_s27, + 0x770532: LocationName.ripple_field_5_s28, + 0x770533: LocationName.ripple_field_5_s29, + 0x770534: LocationName.ripple_field_5_s30, + 0x770535: LocationName.ripple_field_5_s31, + 0x770536: LocationName.ripple_field_5_s32, + 0x770537: LocationName.ripple_field_5_s33, + 0x770538: LocationName.ripple_field_5_s34, + 0x770539: LocationName.ripple_field_5_s35, + 0x77053a: LocationName.ripple_field_5_s36, + 0x77053b: LocationName.ripple_field_5_s37, + 0x77053c: LocationName.ripple_field_5_s38, + 0x77053d: LocationName.ripple_field_5_s39, + 0x77053e: LocationName.ripple_field_5_s40, + 0x77053f: LocationName.ripple_field_5_s41, + 0x770540: LocationName.ripple_field_5_s42, + 0x770541: LocationName.ripple_field_5_s43, + 0x770542: LocationName.ripple_field_5_s44, + 0x770543: LocationName.ripple_field_5_s45, + 0x770544: LocationName.ripple_field_5_s46, + 0x770545: LocationName.ripple_field_5_s47, + 0x770546: LocationName.ripple_field_5_s48, + 0x770547: LocationName.ripple_field_5_s49, + 0x770548: LocationName.ripple_field_5_s50, + 0x770549: LocationName.ripple_field_5_s51, + 0x77054a: LocationName.ripple_field_6_s1, + 0x77054b: LocationName.ripple_field_6_s2, + 0x77054c: LocationName.ripple_field_6_s3, + 0x77054d: LocationName.ripple_field_6_s4, + 0x77054e: LocationName.ripple_field_6_s5, + 0x77054f: LocationName.ripple_field_6_s6, + 0x770550: LocationName.ripple_field_6_s7, + 0x770551: LocationName.ripple_field_6_s8, + 0x770552: LocationName.ripple_field_6_s9, + 0x770553: LocationName.ripple_field_6_s10, + 0x770554: LocationName.ripple_field_6_s11, + 0x770555: LocationName.ripple_field_6_s12, + 0x770556: LocationName.ripple_field_6_s13, + 0x770557: LocationName.ripple_field_6_s14, + 0x770558: LocationName.ripple_field_6_s15, + 0x770559: LocationName.ripple_field_6_s16, + 0x77055a: LocationName.ripple_field_6_s17, + 0x77055b: LocationName.ripple_field_6_s18, + 0x77055c: LocationName.ripple_field_6_s19, + 0x77055d: LocationName.ripple_field_6_s20, + 0x77055e: LocationName.ripple_field_6_s21, + 0x77055f: LocationName.ripple_field_6_s22, + 0x770560: LocationName.ripple_field_6_s23, + 0x770561: LocationName.sand_canyon_1_s1, + 0x770562: LocationName.sand_canyon_1_s2, + 0x770563: LocationName.sand_canyon_1_s3, + 0x770564: LocationName.sand_canyon_1_s4, + 0x770565: LocationName.sand_canyon_1_s5, + 0x770566: LocationName.sand_canyon_1_s6, + 0x770567: LocationName.sand_canyon_1_s7, + 0x770568: LocationName.sand_canyon_1_s8, + 0x770569: LocationName.sand_canyon_1_s9, + 0x77056a: LocationName.sand_canyon_1_s10, + 0x77056b: LocationName.sand_canyon_1_s11, + 0x77056c: LocationName.sand_canyon_1_s12, + 0x77056d: LocationName.sand_canyon_1_s13, + 0x77056e: LocationName.sand_canyon_1_s14, + 0x77056f: LocationName.sand_canyon_1_s15, + 0x770570: LocationName.sand_canyon_1_s16, + 0x770571: LocationName.sand_canyon_1_s17, + 0x770572: LocationName.sand_canyon_1_s18, + 0x770573: LocationName.sand_canyon_1_s19, + 0x770574: LocationName.sand_canyon_1_s20, + 0x770575: LocationName.sand_canyon_1_s21, + 0x770576: LocationName.sand_canyon_1_s22, + 0x770577: LocationName.sand_canyon_2_s1, + 0x770578: LocationName.sand_canyon_2_s2, + 0x770579: LocationName.sand_canyon_2_s3, + 0x77057a: LocationName.sand_canyon_2_s4, + 0x77057b: LocationName.sand_canyon_2_s5, + 0x77057c: LocationName.sand_canyon_2_s6, + 0x77057d: LocationName.sand_canyon_2_s7, + 0x77057e: LocationName.sand_canyon_2_s8, + 0x77057f: LocationName.sand_canyon_2_s9, + 0x770580: LocationName.sand_canyon_2_s10, + 0x770581: LocationName.sand_canyon_2_s11, + 0x770582: LocationName.sand_canyon_2_s12, + 0x770583: LocationName.sand_canyon_2_s13, + 0x770584: LocationName.sand_canyon_2_s14, + 0x770585: LocationName.sand_canyon_2_s15, + 0x770586: LocationName.sand_canyon_2_s16, + 0x770587: LocationName.sand_canyon_2_s17, + 0x770588: LocationName.sand_canyon_2_s18, + 0x770589: LocationName.sand_canyon_2_s19, + 0x77058a: LocationName.sand_canyon_2_s20, + 0x77058b: LocationName.sand_canyon_2_s21, + 0x77058c: LocationName.sand_canyon_2_s22, + 0x77058d: LocationName.sand_canyon_2_s23, + 0x77058e: LocationName.sand_canyon_2_s24, + 0x77058f: LocationName.sand_canyon_2_s25, + 0x770590: LocationName.sand_canyon_2_s26, + 0x770591: LocationName.sand_canyon_2_s27, + 0x770592: LocationName.sand_canyon_2_s28, + 0x770593: LocationName.sand_canyon_2_s29, + 0x770594: LocationName.sand_canyon_2_s30, + 0x770595: LocationName.sand_canyon_2_s31, + 0x770596: LocationName.sand_canyon_2_s32, + 0x770597: LocationName.sand_canyon_2_s33, + 0x770598: LocationName.sand_canyon_2_s34, + 0x770599: LocationName.sand_canyon_2_s35, + 0x77059a: LocationName.sand_canyon_2_s36, + 0x77059b: LocationName.sand_canyon_2_s37, + 0x77059c: LocationName.sand_canyon_2_s38, + 0x77059d: LocationName.sand_canyon_2_s39, + 0x77059e: LocationName.sand_canyon_2_s40, + 0x77059f: LocationName.sand_canyon_2_s41, + 0x7705a0: LocationName.sand_canyon_2_s42, + 0x7705a1: LocationName.sand_canyon_2_s43, + 0x7705a2: LocationName.sand_canyon_2_s44, + 0x7705a3: LocationName.sand_canyon_2_s45, + 0x7705a4: LocationName.sand_canyon_2_s46, + 0x7705a5: LocationName.sand_canyon_2_s47, + 0x7705a6: LocationName.sand_canyon_2_s48, + 0x7705a7: LocationName.sand_canyon_3_s1, + 0x7705a8: LocationName.sand_canyon_3_s2, + 0x7705a9: LocationName.sand_canyon_3_s3, + 0x7705aa: LocationName.sand_canyon_3_s4, + 0x7705ab: LocationName.sand_canyon_3_s5, + 0x7705ac: LocationName.sand_canyon_3_s6, + 0x7705ad: LocationName.sand_canyon_3_s7, + 0x7705ae: LocationName.sand_canyon_3_s8, + 0x7705af: LocationName.sand_canyon_3_s9, + 0x7705b0: LocationName.sand_canyon_3_s10, + 0x7705b1: LocationName.sand_canyon_4_s1, + 0x7705b2: LocationName.sand_canyon_4_s2, + 0x7705b3: LocationName.sand_canyon_4_s3, + 0x7705b4: LocationName.sand_canyon_4_s4, + 0x7705b5: LocationName.sand_canyon_4_s5, + 0x7705b6: LocationName.sand_canyon_4_s6, + 0x7705b7: LocationName.sand_canyon_4_s7, + 0x7705b8: LocationName.sand_canyon_4_s8, + 0x7705b9: LocationName.sand_canyon_4_s9, + 0x7705ba: LocationName.sand_canyon_4_s10, + 0x7705bb: LocationName.sand_canyon_4_s11, + 0x7705bc: LocationName.sand_canyon_4_s12, + 0x7705bd: LocationName.sand_canyon_4_s13, + 0x7705be: LocationName.sand_canyon_4_s14, + 0x7705bf: LocationName.sand_canyon_4_s15, + 0x7705c0: LocationName.sand_canyon_4_s16, + 0x7705c1: LocationName.sand_canyon_4_s17, + 0x7705c2: LocationName.sand_canyon_4_s18, + 0x7705c3: LocationName.sand_canyon_4_s19, + 0x7705c4: LocationName.sand_canyon_4_s20, + 0x7705c5: LocationName.sand_canyon_4_s21, + 0x7705c6: LocationName.sand_canyon_4_s22, + 0x7705c7: LocationName.sand_canyon_4_s23, + 0x7705c8: LocationName.sand_canyon_5_s1, + 0x7705c9: LocationName.sand_canyon_5_s2, + 0x7705ca: LocationName.sand_canyon_5_s3, + 0x7705cb: LocationName.sand_canyon_5_s4, + 0x7705cc: LocationName.sand_canyon_5_s5, + 0x7705cd: LocationName.sand_canyon_5_s6, + 0x7705ce: LocationName.sand_canyon_5_s7, + 0x7705cf: LocationName.sand_canyon_5_s8, + 0x7705d0: LocationName.sand_canyon_5_s9, + 0x7705d1: LocationName.sand_canyon_5_s10, + 0x7705d2: LocationName.sand_canyon_5_s11, + 0x7705d3: LocationName.sand_canyon_5_s12, + 0x7705d4: LocationName.sand_canyon_5_s13, + 0x7705d5: LocationName.sand_canyon_5_s14, + 0x7705d6: LocationName.sand_canyon_5_s15, + 0x7705d7: LocationName.sand_canyon_5_s16, + 0x7705d8: LocationName.sand_canyon_5_s17, + 0x7705d9: LocationName.sand_canyon_5_s18, + 0x7705da: LocationName.sand_canyon_5_s19, + 0x7705db: LocationName.sand_canyon_5_s20, + 0x7705dc: LocationName.sand_canyon_5_s21, + 0x7705dd: LocationName.sand_canyon_5_s22, + 0x7705de: LocationName.sand_canyon_5_s23, + 0x7705df: LocationName.sand_canyon_5_s24, + 0x7705e0: LocationName.sand_canyon_5_s25, + 0x7705e1: LocationName.sand_canyon_5_s26, + 0x7705e2: LocationName.sand_canyon_5_s27, + 0x7705e3: LocationName.sand_canyon_5_s28, + 0x7705e4: LocationName.sand_canyon_5_s29, + 0x7705e5: LocationName.sand_canyon_5_s30, + 0x7705e6: LocationName.sand_canyon_5_s31, + 0x7705e7: LocationName.sand_canyon_5_s32, + 0x7705e8: LocationName.sand_canyon_5_s33, + 0x7705e9: LocationName.sand_canyon_5_s34, + 0x7705ea: LocationName.sand_canyon_5_s35, + 0x7705eb: LocationName.sand_canyon_5_s36, + 0x7705ec: LocationName.sand_canyon_5_s37, + 0x7705ed: LocationName.sand_canyon_5_s38, + 0x7705ee: LocationName.sand_canyon_5_s39, + 0x7705ef: LocationName.sand_canyon_5_s40, + 0x7705f0: LocationName.cloudy_park_1_s1, + 0x7705f1: LocationName.cloudy_park_1_s2, + 0x7705f2: LocationName.cloudy_park_1_s3, + 0x7705f3: LocationName.cloudy_park_1_s4, + 0x7705f4: LocationName.cloudy_park_1_s5, + 0x7705f5: LocationName.cloudy_park_1_s6, + 0x7705f6: LocationName.cloudy_park_1_s7, + 0x7705f7: LocationName.cloudy_park_1_s8, + 0x7705f8: LocationName.cloudy_park_1_s9, + 0x7705f9: LocationName.cloudy_park_1_s10, + 0x7705fa: LocationName.cloudy_park_1_s11, + 0x7705fb: LocationName.cloudy_park_1_s12, + 0x7705fc: LocationName.cloudy_park_1_s13, + 0x7705fd: LocationName.cloudy_park_1_s14, + 0x7705fe: LocationName.cloudy_park_1_s15, + 0x7705ff: LocationName.cloudy_park_1_s16, + 0x770600: LocationName.cloudy_park_1_s17, + 0x770601: LocationName.cloudy_park_1_s18, + 0x770602: LocationName.cloudy_park_1_s19, + 0x770603: LocationName.cloudy_park_1_s20, + 0x770604: LocationName.cloudy_park_1_s21, + 0x770605: LocationName.cloudy_park_1_s22, + 0x770606: LocationName.cloudy_park_1_s23, + 0x770607: LocationName.cloudy_park_2_s1, + 0x770608: LocationName.cloudy_park_2_s2, + 0x770609: LocationName.cloudy_park_2_s3, + 0x77060a: LocationName.cloudy_park_2_s4, + 0x77060b: LocationName.cloudy_park_2_s5, + 0x77060c: LocationName.cloudy_park_2_s6, + 0x77060d: LocationName.cloudy_park_2_s7, + 0x77060e: LocationName.cloudy_park_2_s8, + 0x77060f: LocationName.cloudy_park_2_s9, + 0x770610: LocationName.cloudy_park_2_s10, + 0x770611: LocationName.cloudy_park_2_s11, + 0x770612: LocationName.cloudy_park_2_s12, + 0x770613: LocationName.cloudy_park_2_s13, + 0x770614: LocationName.cloudy_park_2_s14, + 0x770615: LocationName.cloudy_park_2_s15, + 0x770616: LocationName.cloudy_park_2_s16, + 0x770617: LocationName.cloudy_park_2_s17, + 0x770618: LocationName.cloudy_park_2_s18, + 0x770619: LocationName.cloudy_park_2_s19, + 0x77061a: LocationName.cloudy_park_2_s20, + 0x77061b: LocationName.cloudy_park_2_s21, + 0x77061c: LocationName.cloudy_park_2_s22, + 0x77061d: LocationName.cloudy_park_2_s23, + 0x77061e: LocationName.cloudy_park_2_s24, + 0x77061f: LocationName.cloudy_park_2_s25, + 0x770620: LocationName.cloudy_park_2_s26, + 0x770621: LocationName.cloudy_park_2_s27, + 0x770622: LocationName.cloudy_park_2_s28, + 0x770623: LocationName.cloudy_park_2_s29, + 0x770624: LocationName.cloudy_park_2_s30, + 0x770625: LocationName.cloudy_park_2_s31, + 0x770626: LocationName.cloudy_park_2_s32, + 0x770627: LocationName.cloudy_park_2_s33, + 0x770628: LocationName.cloudy_park_2_s34, + 0x770629: LocationName.cloudy_park_2_s35, + 0x77062a: LocationName.cloudy_park_2_s36, + 0x77062b: LocationName.cloudy_park_2_s37, + 0x77062c: LocationName.cloudy_park_2_s38, + 0x77062d: LocationName.cloudy_park_2_s39, + 0x77062e: LocationName.cloudy_park_2_s40, + 0x77062f: LocationName.cloudy_park_2_s41, + 0x770630: LocationName.cloudy_park_2_s42, + 0x770631: LocationName.cloudy_park_2_s43, + 0x770632: LocationName.cloudy_park_2_s44, + 0x770633: LocationName.cloudy_park_2_s45, + 0x770634: LocationName.cloudy_park_2_s46, + 0x770635: LocationName.cloudy_park_2_s47, + 0x770636: LocationName.cloudy_park_2_s48, + 0x770637: LocationName.cloudy_park_2_s49, + 0x770638: LocationName.cloudy_park_2_s50, + 0x770639: LocationName.cloudy_park_2_s51, + 0x77063a: LocationName.cloudy_park_2_s52, + 0x77063b: LocationName.cloudy_park_2_s53, + 0x77063c: LocationName.cloudy_park_2_s54, + 0x77063d: LocationName.cloudy_park_3_s1, + 0x77063e: LocationName.cloudy_park_3_s2, + 0x77063f: LocationName.cloudy_park_3_s3, + 0x770640: LocationName.cloudy_park_3_s4, + 0x770641: LocationName.cloudy_park_3_s5, + 0x770642: LocationName.cloudy_park_3_s6, + 0x770643: LocationName.cloudy_park_3_s7, + 0x770644: LocationName.cloudy_park_3_s8, + 0x770645: LocationName.cloudy_park_3_s9, + 0x770646: LocationName.cloudy_park_3_s10, + 0x770647: LocationName.cloudy_park_3_s11, + 0x770648: LocationName.cloudy_park_3_s12, + 0x770649: LocationName.cloudy_park_3_s13, + 0x77064a: LocationName.cloudy_park_3_s14, + 0x77064b: LocationName.cloudy_park_3_s15, + 0x77064c: LocationName.cloudy_park_3_s16, + 0x77064d: LocationName.cloudy_park_3_s17, + 0x77064e: LocationName.cloudy_park_3_s18, + 0x77064f: LocationName.cloudy_park_3_s19, + 0x770650: LocationName.cloudy_park_3_s20, + 0x770651: LocationName.cloudy_park_3_s21, + 0x770652: LocationName.cloudy_park_3_s22, + 0x770653: LocationName.cloudy_park_4_s1, + 0x770654: LocationName.cloudy_park_4_s2, + 0x770655: LocationName.cloudy_park_4_s3, + 0x770656: LocationName.cloudy_park_4_s4, + 0x770657: LocationName.cloudy_park_4_s5, + 0x770658: LocationName.cloudy_park_4_s6, + 0x770659: LocationName.cloudy_park_4_s7, + 0x77065a: LocationName.cloudy_park_4_s8, + 0x77065b: LocationName.cloudy_park_4_s9, + 0x77065c: LocationName.cloudy_park_4_s10, + 0x77065d: LocationName.cloudy_park_4_s11, + 0x77065e: LocationName.cloudy_park_4_s12, + 0x77065f: LocationName.cloudy_park_4_s13, + 0x770660: LocationName.cloudy_park_4_s14, + 0x770661: LocationName.cloudy_park_4_s15, + 0x770662: LocationName.cloudy_park_4_s16, + 0x770663: LocationName.cloudy_park_4_s17, + 0x770664: LocationName.cloudy_park_4_s18, + 0x770665: LocationName.cloudy_park_4_s19, + 0x770666: LocationName.cloudy_park_4_s20, + 0x770667: LocationName.cloudy_park_4_s21, + 0x770668: LocationName.cloudy_park_4_s22, + 0x770669: LocationName.cloudy_park_4_s23, + 0x77066a: LocationName.cloudy_park_4_s24, + 0x77066b: LocationName.cloudy_park_4_s25, + 0x77066c: LocationName.cloudy_park_4_s26, + 0x77066d: LocationName.cloudy_park_4_s27, + 0x77066e: LocationName.cloudy_park_4_s28, + 0x77066f: LocationName.cloudy_park_4_s29, + 0x770670: LocationName.cloudy_park_4_s30, + 0x770671: LocationName.cloudy_park_4_s31, + 0x770672: LocationName.cloudy_park_4_s32, + 0x770673: LocationName.cloudy_park_4_s33, + 0x770674: LocationName.cloudy_park_4_s34, + 0x770675: LocationName.cloudy_park_4_s35, + 0x770676: LocationName.cloudy_park_4_s36, + 0x770677: LocationName.cloudy_park_4_s37, + 0x770678: LocationName.cloudy_park_4_s38, + 0x770679: LocationName.cloudy_park_4_s39, + 0x77067a: LocationName.cloudy_park_4_s40, + 0x77067b: LocationName.cloudy_park_4_s41, + 0x77067c: LocationName.cloudy_park_4_s42, + 0x77067d: LocationName.cloudy_park_4_s43, + 0x77067e: LocationName.cloudy_park_4_s44, + 0x77067f: LocationName.cloudy_park_4_s45, + 0x770680: LocationName.cloudy_park_4_s46, + 0x770681: LocationName.cloudy_park_4_s47, + 0x770682: LocationName.cloudy_park_4_s48, + 0x770683: LocationName.cloudy_park_4_s49, + 0x770684: LocationName.cloudy_park_4_s50, + 0x770685: LocationName.cloudy_park_5_s1, + 0x770686: LocationName.cloudy_park_5_s2, + 0x770687: LocationName.cloudy_park_5_s3, + 0x770688: LocationName.cloudy_park_5_s4, + 0x770689: LocationName.cloudy_park_5_s5, + 0x77068a: LocationName.cloudy_park_5_s6, + 0x77068b: LocationName.cloudy_park_6_s1, + 0x77068c: LocationName.cloudy_park_6_s2, + 0x77068d: LocationName.cloudy_park_6_s3, + 0x77068e: LocationName.cloudy_park_6_s4, + 0x77068f: LocationName.cloudy_park_6_s5, + 0x770690: LocationName.cloudy_park_6_s6, + 0x770691: LocationName.cloudy_park_6_s7, + 0x770692: LocationName.cloudy_park_6_s8, + 0x770693: LocationName.cloudy_park_6_s9, + 0x770694: LocationName.cloudy_park_6_s10, + 0x770695: LocationName.cloudy_park_6_s11, + 0x770696: LocationName.cloudy_park_6_s12, + 0x770697: LocationName.cloudy_park_6_s13, + 0x770698: LocationName.cloudy_park_6_s14, + 0x770699: LocationName.cloudy_park_6_s15, + 0x77069a: LocationName.cloudy_park_6_s16, + 0x77069b: LocationName.cloudy_park_6_s17, + 0x77069c: LocationName.cloudy_park_6_s18, + 0x77069d: LocationName.cloudy_park_6_s19, + 0x77069e: LocationName.cloudy_park_6_s20, + 0x77069f: LocationName.cloudy_park_6_s21, + 0x7706a0: LocationName.cloudy_park_6_s22, + 0x7706a1: LocationName.cloudy_park_6_s23, + 0x7706a2: LocationName.cloudy_park_6_s24, + 0x7706a3: LocationName.cloudy_park_6_s25, + 0x7706a4: LocationName.cloudy_park_6_s26, + 0x7706a5: LocationName.cloudy_park_6_s27, + 0x7706a6: LocationName.cloudy_park_6_s28, + 0x7706a7: LocationName.cloudy_park_6_s29, + 0x7706a8: LocationName.cloudy_park_6_s30, + 0x7706a9: LocationName.cloudy_park_6_s31, + 0x7706aa: LocationName.cloudy_park_6_s32, + 0x7706ab: LocationName.cloudy_park_6_s33, + 0x7706ac: LocationName.iceberg_1_s1, + 0x7706ad: LocationName.iceberg_1_s2, + 0x7706ae: LocationName.iceberg_1_s3, + 0x7706af: LocationName.iceberg_1_s4, + 0x7706b0: LocationName.iceberg_1_s5, + 0x7706b1: LocationName.iceberg_1_s6, + 0x7706b2: LocationName.iceberg_2_s1, + 0x7706b3: LocationName.iceberg_2_s2, + 0x7706b4: LocationName.iceberg_2_s3, + 0x7706b5: LocationName.iceberg_2_s4, + 0x7706b6: LocationName.iceberg_2_s5, + 0x7706b7: LocationName.iceberg_2_s6, + 0x7706b8: LocationName.iceberg_2_s7, + 0x7706b9: LocationName.iceberg_2_s8, + 0x7706ba: LocationName.iceberg_2_s9, + 0x7706bb: LocationName.iceberg_2_s10, + 0x7706bc: LocationName.iceberg_2_s11, + 0x7706bd: LocationName.iceberg_2_s12, + 0x7706be: LocationName.iceberg_2_s13, + 0x7706bf: LocationName.iceberg_2_s14, + 0x7706c0: LocationName.iceberg_2_s15, + 0x7706c1: LocationName.iceberg_2_s16, + 0x7706c2: LocationName.iceberg_2_s17, + 0x7706c3: LocationName.iceberg_2_s18, + 0x7706c4: LocationName.iceberg_2_s19, + 0x7706c5: LocationName.iceberg_3_s1, + 0x7706c6: LocationName.iceberg_3_s2, + 0x7706c7: LocationName.iceberg_3_s3, + 0x7706c8: LocationName.iceberg_3_s4, + 0x7706c9: LocationName.iceberg_3_s5, + 0x7706ca: LocationName.iceberg_3_s6, + 0x7706cb: LocationName.iceberg_3_s7, + 0x7706cc: LocationName.iceberg_3_s8, + 0x7706cd: LocationName.iceberg_3_s9, + 0x7706ce: LocationName.iceberg_3_s10, + 0x7706cf: LocationName.iceberg_3_s11, + 0x7706d0: LocationName.iceberg_3_s12, + 0x7706d1: LocationName.iceberg_3_s13, + 0x7706d2: LocationName.iceberg_3_s14, + 0x7706d3: LocationName.iceberg_3_s15, + 0x7706d4: LocationName.iceberg_3_s16, + 0x7706d5: LocationName.iceberg_3_s17, + 0x7706d6: LocationName.iceberg_3_s18, + 0x7706d7: LocationName.iceberg_3_s19, + 0x7706d8: LocationName.iceberg_3_s20, + 0x7706d9: LocationName.iceberg_3_s21, + 0x7706da: LocationName.iceberg_4_s1, + 0x7706db: LocationName.iceberg_4_s2, + 0x7706dc: LocationName.iceberg_4_s3, + 0x7706dd: LocationName.iceberg_5_s1, + 0x7706de: LocationName.iceberg_5_s2, + 0x7706df: LocationName.iceberg_5_s3, + 0x7706e0: LocationName.iceberg_5_s4, + 0x7706e1: LocationName.iceberg_5_s5, + 0x7706e2: LocationName.iceberg_5_s6, + 0x7706e3: LocationName.iceberg_5_s7, + 0x7706e4: LocationName.iceberg_5_s8, + 0x7706e5: LocationName.iceberg_5_s9, + 0x7706e6: LocationName.iceberg_5_s10, + 0x7706e7: LocationName.iceberg_5_s11, + 0x7706e8: LocationName.iceberg_5_s12, + 0x7706e9: LocationName.iceberg_5_s13, + 0x7706ea: LocationName.iceberg_5_s14, + 0x7706eb: LocationName.iceberg_5_s15, + 0x7706ec: LocationName.iceberg_5_s16, + 0x7706ed: LocationName.iceberg_5_s17, + 0x7706ee: LocationName.iceberg_5_s18, + 0x7706ef: LocationName.iceberg_5_s19, + 0x7706f0: LocationName.iceberg_5_s20, + 0x7706f1: LocationName.iceberg_5_s21, + 0x7706f2: LocationName.iceberg_5_s22, + 0x7706f3: LocationName.iceberg_5_s23, + 0x7706f4: LocationName.iceberg_5_s24, + 0x7706f5: LocationName.iceberg_5_s25, + 0x7706f6: LocationName.iceberg_5_s26, + 0x7706f7: LocationName.iceberg_5_s27, + 0x7706f8: LocationName.iceberg_5_s28, + 0x7706f9: LocationName.iceberg_5_s29, + 0x7706fa: LocationName.iceberg_5_s30, + 0x7706fb: LocationName.iceberg_5_s31, + 0x7706fc: LocationName.iceberg_5_s32, + 0x7706fd: LocationName.iceberg_5_s33, + 0x7706fe: LocationName.iceberg_5_s34, + 0x7706ff: LocationName.iceberg_6_s1, + +} + +location_table = { + **stage_locations, + **heart_star_locations, + **boss_locations, + **consumable_locations, + **star_locations +} diff --git a/worlds/kdl3/Names/AnimalFriendSpawns.py b/worlds/kdl3/Names/AnimalFriendSpawns.py new file mode 100644 index 0000000000..4520cf1438 --- /dev/null +++ b/worlds/kdl3/Names/AnimalFriendSpawns.py @@ -0,0 +1,199 @@ +grass_land_1_a1 = "Grass Land 1 - Animal 1" # Nago +grass_land_1_a2 = "Grass Land 1 - Animal 2" # Rick +grass_land_2_a1 = "Grass Land 2 - Animal 1" # ChuChu +grass_land_2_a2 = "Grass Land 2 - Animal 2" # Pitch +grass_land_3_a1 = "Grass Land 3 - Animal 1" # Kine +grass_land_3_a2 = "Grass Land 3 - Animal 2" # Coo +grass_land_4_a1 = "Grass Land 4 - Animal 1" # ChuChu +grass_land_4_a2 = "Grass Land 4 - Animal 2" # Nago +grass_land_5_a1 = "Grass Land 5 - Animal 1" # Coo +grass_land_5_a2 = "Grass Land 5 - Animal 2" # Kine +grass_land_5_a3 = "Grass Land 5 - Animal 3" # Nago +grass_land_5_a4 = "Grass Land 5 - Animal 4" # Rick +grass_land_6_a1 = "Grass Land 6 - Animal 1" # Rick +grass_land_6_a2 = "Grass Land 6 - Animal 2" # ChuChu +grass_land_6_a3 = "Grass Land 6 - Animal 3" # Nago +grass_land_6_a4 = "Grass Land 6 - Animal 4" # Coo +ripple_field_1_a1 = "Ripple Field 1 - Animal 1" # Pitch +ripple_field_1_a2 = "Ripple Field 1 - Animal 2" # Nago +ripple_field_2_a1 = "Ripple Field 2 - Animal 1" # Kine +ripple_field_2_a2 = "Ripple Field 2 - Animal 2" # ChuChu +ripple_field_2_a3 = "Ripple Field 2 - Animal 3" # Rick +ripple_field_2_a4 = "Ripple Field 2 - Animal 4" # Coo +ripple_field_3_a1 = "Ripple Field 3 - Animal 1" # Kine +ripple_field_3_a2 = "Ripple Field 3 - Animal 2" # Rick +ripple_field_4_a1 = "Ripple Field 4 - Animal 1" # ChuChu +ripple_field_4_a2 = "Ripple Field 4 - Animal 2" # Kine +ripple_field_4_a3 = "Ripple Field 4 - Animal 3" # Nago +ripple_field_5_a1 = "Ripple Field 5 - Animal 1" # Kine +ripple_field_5_a2 = "Ripple Field 5 - Animal 2" # Pitch +ripple_field_6_a1 = "Ripple Field 6 - Animal 1" # Nago +ripple_field_6_a2 = "Ripple Field 6 - Animal 2" # Pitch +ripple_field_6_a3 = "Ripple Field 6 - Animal 3" # Rick +ripple_field_6_a4 = "Ripple Field 6 - Animal 4" # Coo +sand_canyon_1_a1 = "Sand Canyon 1 - Animal 1" # Rick +sand_canyon_1_a2 = "Sand Canyon 1 - Animal 2" # Pitch +sand_canyon_2_a1 = "Sand Canyon 2 - Animal 1" # ChuChu +sand_canyon_2_a2 = "Sand Canyon 2 - Animal 2" # Coo +sand_canyon_3_a1 = "Sand Canyon 3 - Animal 1" # Pitch +sand_canyon_3_a2 = "Sand Canyon 3 - Animal 2" # Coo +sand_canyon_3_a3 = "Sand Canyon 3 - Animal 3" # ChuChu +sand_canyon_4_a1 = "Sand Canyon 4 - Animal 1" # Rick +sand_canyon_4_a2 = "Sand Canyon 4 - Animal 2" # Pitch +sand_canyon_4_a3 = "Sand Canyon 4 - Animal 3" # Nago +sand_canyon_5_a1 = "Sand Canyon 5 - Animal 1" # Rick +sand_canyon_5_a2 = "Sand Canyon 5 - Animal 2" # ChuChu +sand_canyon_6_a1 = "Sand Canyon 6 - Animal 1" # Coo +sand_canyon_6_a2 = "Sand Canyon 6 - Animal 2" # Kine +sand_canyon_6_a3 = "Sand Canyon 6 - Animal 3" # Rick +sand_canyon_6_a4 = "Sand Canyon 6 - Animal 4" # ChuChu +sand_canyon_6_a5 = "Sand Canyon 6 - Animal 5" # Nago +sand_canyon_6_a6 = "Sand Canyon 6 - Animal 6" # Pitch +cloudy_park_1_a1 = "Cloudy Park 1 - Animal 1" # Rick +cloudy_park_1_a2 = "Cloudy Park 1 - Animal 2" # Nago +cloudy_park_1_a3 = "Cloudy Park 1 - Animal 3" # Coo +cloudy_park_1_a4 = "Cloudy Park 1 - Animal 4" # Kine +cloudy_park_1_a5 = "Cloudy Park 1 - Animal 5" # ChuChu +cloudy_park_1_a6 = "Cloudy Park 1 - Animal 6" # Pitch +cloudy_park_2_a1 = "Cloudy Park 2 - Animal 1" # Nago +cloudy_park_2_a2 = "Cloudy Park 2 - Animal 2" # Pitch +cloudy_park_2_a3 = "Cloudy Park 2 - Animal 3" # ChuChu +cloudy_park_3_a1 = "Cloudy Park 3 - Animal 1" # Kine +cloudy_park_3_a2 = "Cloudy Park 3 - Animal 2" # Rick +cloudy_park_3_a3 = "Cloudy Park 3 - Animal 3" # ChuChu +cloudy_park_4_a1 = "Cloudy Park 4 - Animal 1" # Coo +cloudy_park_4_a2 = "Cloudy Park 4 - Animal 2" # ChuChu +cloudy_park_5_a1 = "Cloudy Park 5 - Animal 1" # Rick +cloudy_park_5_a2 = "Cloudy Park 5 - Animal 2" # Coo +cloudy_park_6_a1 = "Cloudy Park 6 - Animal 1" # Nago +cloudy_park_6_a2 = "Cloudy Park 6 - Animal 2" # Coo +cloudy_park_6_a3 = "Cloudy Park 6 - Animal 3" # Rick +iceberg_1_a1 = "Iceberg 1 - Animal 1" # Pitch +iceberg_1_a2 = "Iceberg 1 - Animal 2" # Rick +iceberg_2_a1 = "Iceberg 2 - Animal 1" # Nago +iceberg_2_a2 = "Iceberg 2 - Animal 2" # Pitch +iceberg_3_a1 = "Iceberg 3 - Animal 1" # Pitch +iceberg_3_a2 = "Iceberg 3 - Animal 2" # Coo +iceberg_3_a3 = "Iceberg 3 - Animal 3" # Nago +iceberg_3_a4 = "Iceberg 3 - Animal 4" # Rick +iceberg_3_a5 = "Iceberg 3 - Animal 5" # Kine +iceberg_4_a1 = "Iceberg 4 - Animal 1" # ChuChu +iceberg_4_a2 = "Iceberg 4 - Animal 2" # Coo +iceberg_4_a3 = "Iceberg 4 - Animal 3" # Pitch +iceberg_4_a4 = "Iceberg 4 - Animal 4" # Coo +iceberg_4_a5 = "Iceberg 4 - Animal 5" # Rick +iceberg_5_a1 = "Iceberg 5 - Animal 1" # Kine +iceberg_5_a2 = "Iceberg 5 - Animal 2" # Rick +iceberg_5_a3 = "Iceberg 5 - Animal 3" # Pitch +iceberg_5_a4 = "Iceberg 5 - Animal 4" # ChuChu +iceberg_5_a5 = "Iceberg 5 - Animal 5" # Kine +iceberg_5_a6 = "Iceberg 5 - Animal 6" # Coo +iceberg_5_a7 = "Iceberg 5 - Animal 7" # Rick +iceberg_5_a8 = "Iceberg 5 - Animal 8" # ChuChu +iceberg_6_a1 = "Iceberg 6 - Animal 1" # Rick +iceberg_6_a2 = "Iceberg 6 - Animal 2" # Coo +iceberg_6_a3 = "Iceberg 6 - Animal 3" # Nago +iceberg_6_a4 = "Iceberg 6 - Animal 4" # Kine +iceberg_6_a5 = "Iceberg 6 - Animal 5" # ChuChu +iceberg_6_a6 = "Iceberg 6 - Animal 6" # Nago + +animal_friend_spawns = { + grass_land_1_a1: "Nago Spawn", + grass_land_1_a2: "Rick Spawn", + grass_land_2_a1: "ChuChu Spawn", + grass_land_2_a2: "Pitch Spawn", + grass_land_3_a1: "Kine Spawn", + grass_land_3_a2: "Coo Spawn", + grass_land_4_a1: "ChuChu Spawn", + grass_land_4_a2: "Nago Spawn", + grass_land_5_a1: "Coo Spawn", + grass_land_5_a2: "Kine Spawn", + grass_land_5_a3: "Nago Spawn", + grass_land_5_a4: "Rick Spawn", + grass_land_6_a1: "Rick Spawn", + grass_land_6_a2: "ChuChu Spawn", + grass_land_6_a3: "Nago Spawn", + grass_land_6_a4: "Coo Spawn", + ripple_field_1_a1: "Pitch Spawn", + ripple_field_1_a2: "Nago Spawn", + ripple_field_2_a1: "Kine Spawn", + ripple_field_2_a2: "ChuChu Spawn", + ripple_field_2_a3: "Rick Spawn", + ripple_field_2_a4: "Coo Spawn", + ripple_field_3_a1: "Kine Spawn", + ripple_field_3_a2: "Rick Spawn", + ripple_field_4_a1: "ChuChu Spawn", + ripple_field_4_a2: "Kine Spawn", + ripple_field_4_a3: "Nago Spawn", + ripple_field_5_a1: "Kine Spawn", + ripple_field_5_a2: "Pitch Spawn", + ripple_field_6_a1: "Nago Spawn", + ripple_field_6_a2: "Pitch Spawn", + ripple_field_6_a3: "Rick Spawn", + ripple_field_6_a4: "Coo Spawn", + sand_canyon_1_a1: "Rick Spawn", + sand_canyon_1_a2: "Pitch Spawn", + sand_canyon_2_a1: "ChuChu Spawn", + sand_canyon_2_a2: "Coo Spawn", + sand_canyon_3_a1: "Pitch Spawn", + sand_canyon_3_a2: "Coo Spawn", + sand_canyon_3_a3: "ChuChu Spawn", + sand_canyon_4_a1: "Rick Spawn", + sand_canyon_4_a2: "Pitch Spawn", + sand_canyon_4_a3: "Nago Spawn", + sand_canyon_5_a1: "Rick Spawn", + sand_canyon_5_a2: "ChuChu Spawn", + sand_canyon_6_a1: "Coo Spawn", + sand_canyon_6_a2: "Kine Spawn", + sand_canyon_6_a3: "Rick Spawn", + sand_canyon_6_a4: "ChuChu Spawn", + sand_canyon_6_a5: "Nago Spawn", + sand_canyon_6_a6: "Pitch Spawn", + cloudy_park_1_a1: "Rick Spawn", + cloudy_park_1_a2: "Nago Spawn", + cloudy_park_1_a3: "Coo Spawn", + cloudy_park_1_a4: "Kine Spawn", + cloudy_park_1_a5: "ChuChu Spawn", + cloudy_park_1_a6: "Pitch Spawn", + cloudy_park_2_a1: "Nago Spawn", + cloudy_park_2_a2: "Pitch Spawn", + cloudy_park_2_a3: "ChuChu Spawn", + cloudy_park_3_a1: "Kine Spawn", + cloudy_park_3_a2: "Rick Spawn", + cloudy_park_3_a3: "ChuChu Spawn", + cloudy_park_4_a1: "Coo Spawn", + cloudy_park_4_a2: "ChuChu Spawn", + cloudy_park_5_a1: "Rick Spawn", + cloudy_park_5_a2: "Coo Spawn", + cloudy_park_6_a1: "Nago Spawn", + cloudy_park_6_a2: "Coo Spawn", + cloudy_park_6_a3: "Rick Spawn", + iceberg_1_a1: "Pitch Spawn", + iceberg_1_a2: "Rick Spawn", + iceberg_2_a1: "Nago Spawn", + iceberg_2_a2: "Pitch Spawn", + iceberg_3_a1: "Pitch Spawn", + iceberg_3_a2: "Coo Spawn", + iceberg_3_a3: "Nago Spawn", + iceberg_3_a4: "Rick Spawn", + iceberg_3_a5: "Kine Spawn", + iceberg_4_a1: "ChuChu Spawn", + iceberg_4_a2: "Coo Spawn", + iceberg_4_a3: "Pitch Spawn", + iceberg_4_a4: "Coo Spawn", + iceberg_4_a5: "Rick Spawn", + iceberg_5_a1: "Kine Spawn", + iceberg_5_a2: "Rick Spawn", + iceberg_5_a3: "Pitch Spawn", + iceberg_5_a4: "ChuChu Spawn", + iceberg_5_a5: "Kine Spawn", + iceberg_5_a6: "Coo Spawn", + iceberg_5_a7: "Rick Spawn", + iceberg_5_a8: "ChuChu Spawn", + iceberg_6_a1: "Rick Spawn", + iceberg_6_a2: "Coo Spawn", + iceberg_6_a3: "Nago Spawn", + iceberg_6_a4: "Kine Spawn", + iceberg_6_a5: "ChuChu Spawn", + iceberg_6_a6: "Nago Spawn", +} diff --git a/worlds/kdl3/Names/EnemyAbilities.py b/worlds/kdl3/Names/EnemyAbilities.py new file mode 100644 index 0000000000..016e3033ab --- /dev/null +++ b/worlds/kdl3/Names/EnemyAbilities.py @@ -0,0 +1,822 @@ +from typing import List, Tuple, Set + +Grass_Land_1_E1 = "Grass Land 1 - Enemy 1 (Waddle Dee)" +Grass_Land_1_E2 = "Grass Land 1 - Enemy 2 (Sir Kibble)" +Grass_Land_1_E3 = "Grass Land 1 - Enemy 3 (Cappy)" +Grass_Land_1_E4 = "Grass Land 1 - Enemy 4 (Sparky)" +Grass_Land_1_E5 = "Grass Land 1 - Enemy 5 (Bronto Burt)" +Grass_Land_1_E6 = "Grass Land 1 - Enemy 6 (Sasuke)" +Grass_Land_1_E7 = "Grass Land 1 - Enemy 7 (Poppy Bros Jr.)" +Grass_Land_2_E1 = "Grass Land 2 - Enemy 1 (Rocky)" +Grass_Land_2_E2 = "Grass Land 2 - Enemy 2 (KeKe)" +Grass_Land_2_E3 = "Grass Land 2 - Enemy 3 (Bobo)" +Grass_Land_2_E4 = "Grass Land 2 - Enemy 4 (Poppy Bros Jr.)" +Grass_Land_2_E5 = "Grass Land 2 - Enemy 5 (Waddle Dee)" +Grass_Land_2_E6 = "Grass Land 2 - Enemy 6 (Popon Ball)" +Grass_Land_2_E7 = "Grass Land 2 - Enemy 7 (Bouncy)" +Grass_Land_2_E8 = "Grass Land 2 - Enemy 8 (Tick)" +Grass_Land_2_E9 = "Grass Land 2 - Enemy 9 (Bronto Burt)" +Grass_Land_2_E10 = "Grass Land 2 - Enemy 10 (Nruff)" +Grass_Land_3_E1 = "Grass Land 3 - Enemy 1 (Sparky)" +Grass_Land_3_E2 = "Grass Land 3 - Enemy 2 (Rocky)" +Grass_Land_3_E3 = "Grass Land 3 - Enemy 3 (Nruff)" +Grass_Land_3_E4 = "Grass Land 3 - Enemy 4 (Bouncy)" +Grass_Land_4_E1 = "Grass Land 4 - Enemy 1 (Loud)" +Grass_Land_4_E2 = "Grass Land 4 - Enemy 2 (Babut)" +Grass_Land_4_E3 = "Grass Land 4 - Enemy 3 (Rocky)" +Grass_Land_4_E4 = "Grass Land 4 - Enemy 4 (Kapar)" +Grass_Land_4_E5 = "Grass Land 4 - Enemy 5 (Glunk)" +Grass_Land_4_E6 = "Grass Land 4 - Enemy 6 (Oro)" +Grass_Land_4_E7 = "Grass Land 4 - Enemy 7 (Peran)" +Grass_Land_5_E1 = "Grass Land 5 - Enemy 1 (Propeller)" +Grass_Land_5_E2 = "Grass Land 5 - Enemy 2 (Broom Hatter)" +Grass_Land_5_E3 = "Grass Land 5 - Enemy 3 (Bouncy)" +Grass_Land_5_E4 = "Grass Land 5 - Enemy 4 (Sir Kibble)" +Grass_Land_5_E5 = "Grass Land 5 - Enemy 5 (Waddle Dee)" +Grass_Land_5_E6 = "Grass Land 5 - Enemy 6 (Sasuke)" +Grass_Land_5_E7 = "Grass Land 5 - Enemy 7 (Nruff)" +Grass_Land_5_E8 = "Grass Land 5 - Enemy 8 (Tick)" +Grass_Land_6_E1 = "Grass Land 6 - Enemy 1 (Como)" +Grass_Land_6_E2 = "Grass Land 6 - Enemy 2 (Togezo)" +Grass_Land_6_E3 = "Grass Land 6 - Enemy 3 (Bronto Burt)" +Grass_Land_6_E4 = "Grass Land 6 - Enemy 4 (Cappy)" +Grass_Land_6_E5 = "Grass Land 6 - Enemy 5 (Bobo)" +Grass_Land_6_E6 = "Grass Land 6 - Enemy 6 (Mariel)" +Grass_Land_6_E7 = "Grass Land 6 - Enemy 7 (Yaban)" +Grass_Land_6_E8 = "Grass Land 6 - Enemy 8 (Broom Hatter)" +Grass_Land_6_E9 = "Grass Land 6 - Enemy 9 (Apolo)" +Grass_Land_6_E10 = "Grass Land 6 - Enemy 10 (Sasuke)" +Grass_Land_6_E11 = "Grass Land 6 - Enemy 11 (Rocky)" +Ripple_Field_1_E1 = "Ripple Field 1 - Enemy 1 (Waddle Dee)" +Ripple_Field_1_E2 = "Ripple Field 1 - Enemy 2 (Glunk)" +Ripple_Field_1_E3 = "Ripple Field 1 - Enemy 3 (Broom Hatter)" +Ripple_Field_1_E4 = "Ripple Field 1 - Enemy 4 (Cappy)" +Ripple_Field_1_E5 = "Ripple Field 1 - Enemy 5 (Bronto Burt)" +Ripple_Field_1_E6 = "Ripple Field 1 - Enemy 6 (Rocky)" +Ripple_Field_1_E7 = "Ripple Field 1 - Enemy 7 (Poppy Bros Jr.)" +Ripple_Field_1_E8 = "Ripple Field 1 - Enemy 8 (Bobin)" +Ripple_Field_2_E1 = "Ripple Field 2 - Enemy 1 (Togezo)" +Ripple_Field_2_E2 = "Ripple Field 2 - Enemy 2 (Coconut)" +Ripple_Field_2_E3 = "Ripple Field 2 - Enemy 3 (Blipper)" +Ripple_Field_2_E4 = "Ripple Field 2 - Enemy 4 (Sasuke)" +Ripple_Field_2_E5 = "Ripple Field 2 - Enemy 5 (Kany)" +Ripple_Field_2_E6 = "Ripple Field 2 - Enemy 6 (Glunk)" +Ripple_Field_3_E1 = "Ripple Field 3 - Enemy 1 (Raft Waddle Dee)" +Ripple_Field_3_E2 = "Ripple Field 3 - Enemy 2 (Kapar)" +Ripple_Field_3_E3 = "Ripple Field 3 - Enemy 3 (Blipper)" +Ripple_Field_3_E4 = "Ripple Field 3 - Enemy 4 (Sparky)" +Ripple_Field_3_E5 = "Ripple Field 3 - Enemy 5 (Glunk)" +Ripple_Field_3_E6 = "Ripple Field 3 - Enemy 6 (Joe)" +Ripple_Field_3_E7 = "Ripple Field 3 - Enemy 7 (Bobo)" +Ripple_Field_4_E1 = "Ripple Field 4 - Enemy 1 (Bukiset (Stone))" +Ripple_Field_4_E2 = "Ripple Field 4 - Enemy 2 (Bukiset (Needle))" +Ripple_Field_4_E3 = "Ripple Field 4 - Enemy 3 (Bukiset (Clean))" +Ripple_Field_4_E4 = "Ripple Field 4 - Enemy 4 (Bukiset (Parasol))" +Ripple_Field_4_E5 = "Ripple Field 4 - Enemy 5 (Mony)" +Ripple_Field_4_E6 = "Ripple Field 4 - Enemy 6 (Bukiset (Burning))" +Ripple_Field_4_E7 = "Ripple Field 4 - Enemy 7 (Bobin)" +Ripple_Field_4_E8 = "Ripple Field 4 - Enemy 8 (Blipper)" +Ripple_Field_4_E9 = "Ripple Field 4 - Enemy 9 (Como)" +Ripple_Field_4_E10 = "Ripple Field 4 - Enemy 10 (Oro)" +Ripple_Field_4_E11 = "Ripple Field 4 - Enemy 11 (Gansan)" +Ripple_Field_4_E12 = "Ripple Field 4 - Enemy 12 (Waddle Dee)" +Ripple_Field_4_E13 = "Ripple Field 4 - Enemy 13 (Kapar)" +Ripple_Field_4_E14 = "Ripple Field 4 - Enemy 14 (Squishy)" +Ripple_Field_4_E15 = "Ripple Field 4 - Enemy 15 (Nidoo)" +Ripple_Field_5_E1 = "Ripple Field 5 - Enemy 1 (Glunk)" +Ripple_Field_5_E2 = "Ripple Field 5 - Enemy 2 (Joe)" +Ripple_Field_5_E3 = "Ripple Field 5 - Enemy 3 (Bobin)" +Ripple_Field_5_E4 = "Ripple Field 5 - Enemy 4 (Mony)" +Ripple_Field_5_E5 = "Ripple Field 5 - Enemy 5 (Squishy)" +Ripple_Field_5_E6 = "Ripple Field 5 - Enemy 6 (Yaban)" +Ripple_Field_5_E7 = "Ripple Field 5 - Enemy 7 (Broom Hatter)" +Ripple_Field_5_E8 = "Ripple Field 5 - Enemy 8 (Bouncy)" +Ripple_Field_5_E9 = "Ripple Field 5 - Enemy 9 (Sparky)" +Ripple_Field_5_E10 = "Ripple Field 5 - Enemy 10 (Rocky)" +Ripple_Field_5_E11 = "Ripple Field 5 - Enemy 11 (Babut)" +Ripple_Field_5_E12 = "Ripple Field 5 - Enemy 12 (Galbo)" +Ripple_Field_6_E1 = "Ripple Field 6 - Enemy 1 (Kany)" +Ripple_Field_6_E2 = "Ripple Field 6 - Enemy 2 (KeKe)" +Ripple_Field_6_E3 = "Ripple Field 6 - Enemy 3 (Kapar)" +Ripple_Field_6_E4 = "Ripple Field 6 - Enemy 4 (Rocky)" +Ripple_Field_6_E5 = "Ripple Field 6 - Enemy 5 (Poppy Bros Jr.)" +Ripple_Field_6_E6 = "Ripple Field 6 - Enemy 6 (Propeller)" +Ripple_Field_6_E7 = "Ripple Field 6 - Enemy 7 (Coconut)" +Ripple_Field_6_E8 = "Ripple Field 6 - Enemy 8 (Sasuke)" +Ripple_Field_6_E9 = "Ripple Field 6 - Enemy 9 (Nruff)" +Sand_Canyon_1_E1 = "Sand Canyon 1 - Enemy 1 (Bronto Burt)" +Sand_Canyon_1_E2 = "Sand Canyon 1 - Enemy 2 (Galbo)" +Sand_Canyon_1_E3 = "Sand Canyon 1 - Enemy 3 (Oro)" +Sand_Canyon_1_E4 = "Sand Canyon 1 - Enemy 4 (Sparky)" +Sand_Canyon_1_E5 = "Sand Canyon 1 - Enemy 5 (Propeller)" +Sand_Canyon_1_E6 = "Sand Canyon 1 - Enemy 6 (Gansan)" +Sand_Canyon_1_E7 = "Sand Canyon 1 - Enemy 7 (Babut)" +Sand_Canyon_1_E8 = "Sand Canyon 1 - Enemy 8 (Loud)" +Sand_Canyon_1_E9 = "Sand Canyon 1 - Enemy 9 (Dogon)" +Sand_Canyon_1_E10 = "Sand Canyon 1 - Enemy 10 (Bouncy)" +Sand_Canyon_1_E11 = "Sand Canyon 1 - Enemy 11 (Pteran)" +Sand_Canyon_1_E12 = "Sand Canyon 1 - Enemy 12 (Polof)" +Sand_Canyon_2_E1 = "Sand Canyon 2 - Enemy 1 (KeKe)" +Sand_Canyon_2_E2 = "Sand Canyon 2 - Enemy 2 (Doka)" +Sand_Canyon_2_E3 = "Sand Canyon 2 - Enemy 3 (Boten)" +Sand_Canyon_2_E4 = "Sand Canyon 2 - Enemy 4 (Propeller)" +Sand_Canyon_2_E5 = "Sand Canyon 2 - Enemy 5 (Waddle Dee)" +Sand_Canyon_2_E6 = "Sand Canyon 2 - Enemy 6 (Sparky)" +Sand_Canyon_2_E7 = "Sand Canyon 2 - Enemy 7 (Sasuke)" +Sand_Canyon_2_E8 = "Sand Canyon 2 - Enemy 8 (Como)" +Sand_Canyon_2_E9 = "Sand Canyon 2 - Enemy 9 (Bukiset (Ice))" +Sand_Canyon_2_E10 = "Sand Canyon 2 - Enemy 10 (Bukiset (Needle))" +Sand_Canyon_2_E11 = "Sand Canyon 2 - Enemy 11 (Bukiset (Clean))" +Sand_Canyon_2_E12 = "Sand Canyon 2 - Enemy 12 (Bukiset (Parasol))" +Sand_Canyon_2_E13 = "Sand Canyon 2 - Enemy 13 (Bukiset (Spark))" +Sand_Canyon_2_E14 = "Sand Canyon 2 - Enemy 14 (Bukiset (Cutter))" +Sand_Canyon_2_E15 = "Sand Canyon 2 - Enemy 15 (Nidoo)" +Sand_Canyon_2_E16 = "Sand Canyon 2 - Enemy 16 (Mariel)" +Sand_Canyon_2_E17 = "Sand Canyon 2 - Enemy 17 (Yaban)" +Sand_Canyon_2_E18 = "Sand Canyon 2 - Enemy 18 (Wapod)" +Sand_Canyon_2_E19 = "Sand Canyon 2 - Enemy 19 (Squishy)" +Sand_Canyon_2_E20 = "Sand Canyon 2 - Enemy 20 (Pteran)" +Sand_Canyon_3_E1 = "Sand Canyon 3 - Enemy 1 (Sir Kibble)" +Sand_Canyon_3_E2 = "Sand Canyon 3 - Enemy 2 (Broom Hatter)" +Sand_Canyon_3_E3 = "Sand Canyon 3 - Enemy 3 (Rocky)" +Sand_Canyon_3_E4 = "Sand Canyon 3 - Enemy 4 (Gabon)" +Sand_Canyon_3_E5 = "Sand Canyon 3 - Enemy 5 (Kany)" +Sand_Canyon_3_E6 = "Sand Canyon 3 - Enemy 6 (Galbo)" +Sand_Canyon_3_E7 = "Sand Canyon 3 - Enemy 7 (Propeller)" +Sand_Canyon_3_E8 = "Sand Canyon 3 - Enemy 8 (Sasuke)" +Sand_Canyon_3_E9 = "Sand Canyon 3 - Enemy 9 (Wapod)" +Sand_Canyon_3_E10 = "Sand Canyon 3 - Enemy 10 (Bobo)" +Sand_Canyon_3_E11 = "Sand Canyon 3 - Enemy 11 (Babut)" +Sand_Canyon_3_E12 = "Sand Canyon 3 - Enemy 12 (Magoo)" +Sand_Canyon_4_E1 = "Sand Canyon 4 - Enemy 1 (Popon Ball)" +Sand_Canyon_4_E2 = "Sand Canyon 4 - Enemy 2 (Mariel)" +Sand_Canyon_4_E3 = "Sand Canyon 4 - Enemy 3 (Chilly)" +Sand_Canyon_4_E4 = "Sand Canyon 4 - Enemy 4 (Tick)" +Sand_Canyon_4_E5 = "Sand Canyon 4 - Enemy 5 (Bronto Burt)" +Sand_Canyon_4_E6 = "Sand Canyon 4 - Enemy 6 (Babut)" +Sand_Canyon_4_E7 = "Sand Canyon 4 - Enemy 7 (Bobin)" +Sand_Canyon_4_E8 = "Sand Canyon 4 - Enemy 8 (Joe)" +Sand_Canyon_4_E9 = "Sand Canyon 4 - Enemy 9 (Mony)" +Sand_Canyon_4_E10 = "Sand Canyon 4 - Enemy 10 (Blipper)" +Sand_Canyon_4_E11 = "Sand Canyon 4 - Enemy 11 (Togezo)" +Sand_Canyon_4_E12 = "Sand Canyon 4 - Enemy 12 (Rocky)" +Sand_Canyon_4_E13 = "Sand Canyon 4 - Enemy 13 (Bobo)" +Sand_Canyon_5_E1 = "Sand Canyon 5 - Enemy 1 (Wapod)" +Sand_Canyon_5_E2 = "Sand Canyon 5 - Enemy 2 (Dogon)" +Sand_Canyon_5_E3 = "Sand Canyon 5 - Enemy 3 (Tick)" +Sand_Canyon_5_E4 = "Sand Canyon 5 - Enemy 4 (Rocky)" +Sand_Canyon_5_E5 = "Sand Canyon 5 - Enemy 5 (Bobo)" +Sand_Canyon_5_E6 = "Sand Canyon 5 - Enemy 6 (Chilly)" +Sand_Canyon_5_E7 = "Sand Canyon 5 - Enemy 7 (Sparky)" +Sand_Canyon_5_E8 = "Sand Canyon 5 - Enemy 8 (Togezo)" +Sand_Canyon_5_E9 = "Sand Canyon 5 - Enemy 9 (Bronto Burt)" +Sand_Canyon_5_E10 = "Sand Canyon 5 - Enemy 10 (Sasuke)" +Sand_Canyon_5_E11 = "Sand Canyon 5 - Enemy 11 (Oro)" +Sand_Canyon_5_E12 = "Sand Canyon 5 - Enemy 12 (Galbo)" +Sand_Canyon_5_E13 = "Sand Canyon 5 - Enemy 13 (Nidoo)" +Sand_Canyon_5_E14 = "Sand Canyon 5 - Enemy 14 (Propeller)" +Sand_Canyon_5_E15 = "Sand Canyon 5 - Enemy 15 (Sir Kibble)" +Sand_Canyon_5_E16 = "Sand Canyon 5 - Enemy 16 (KeKe)" +Sand_Canyon_5_E17 = "Sand Canyon 5 - Enemy 17 (Kabu)" +Sand_Canyon_6_E1 = "Sand Canyon 6 - Enemy 1 (Sparky)" +Sand_Canyon_6_E2 = "Sand Canyon 6 - Enemy 2 (Doka)" +Sand_Canyon_6_E3 = "Sand Canyon 6 - Enemy 3 (Cappy)" +Sand_Canyon_6_E4 = "Sand Canyon 6 - Enemy 4 (Pteran)" +Sand_Canyon_6_E5 = "Sand Canyon 6 - Enemy 5 (Bukiset (Parasol))" +Sand_Canyon_6_E6 = "Sand Canyon 6 - Enemy 6 (Bukiset (Cutter))" +Sand_Canyon_6_E7 = "Sand Canyon 6 - Enemy 7 (Bukiset (Clean))" +Sand_Canyon_6_E8 = "Sand Canyon 6 - Enemy 8 (Bukiset (Spark))" +Sand_Canyon_6_E9 = "Sand Canyon 6 - Enemy 9 (Bukiset (Ice))" +Sand_Canyon_6_E10 = "Sand Canyon 6 - Enemy 10 (Bukiset (Needle))" +Sand_Canyon_6_E11 = "Sand Canyon 6 - Enemy 11 (Bukiset (Burning))" +Sand_Canyon_6_E12 = "Sand Canyon 6 - Enemy 12 (Bukiset (Stone))" +Sand_Canyon_6_E13 = "Sand Canyon 6 - Enemy 13 (Nidoo)" +Cloudy_Park_1_E1 = "Cloudy Park 1 - Enemy 1 (Waddle Dee)" +Cloudy_Park_1_E2 = "Cloudy Park 1 - Enemy 2 (KeKe)" +Cloudy_Park_1_E3 = "Cloudy Park 1 - Enemy 3 (Cappy)" +Cloudy_Park_1_E4 = "Cloudy Park 1 - Enemy 4 (Yaban)" +Cloudy_Park_1_E5 = "Cloudy Park 1 - Enemy 5 (Togezo)" +Cloudy_Park_1_E6 = "Cloudy Park 1 - Enemy 6 (Galbo)" +Cloudy_Park_1_E7 = "Cloudy Park 1 - Enemy 7 (Sparky)" +Cloudy_Park_1_E8 = "Cloudy Park 1 - Enemy 8 (Como)" +Cloudy_Park_1_E9 = "Cloudy Park 1 - Enemy 9 (Bronto Burt)" +Cloudy_Park_1_E10 = "Cloudy Park 1 - Enemy 10 (Gabon)" +Cloudy_Park_1_E11 = "Cloudy Park 1 - Enemy 11 (Sir Kibble)" +Cloudy_Park_1_E12 = "Cloudy Park 1 - Enemy 12 (Mariel)" +Cloudy_Park_1_E13 = "Cloudy Park 1 - Enemy 13 (Nruff)" +Cloudy_Park_2_E1 = "Cloudy Park 2 - Enemy 1 (Chilly)" +Cloudy_Park_2_E2 = "Cloudy Park 2 - Enemy 2 (Sasuke)" +Cloudy_Park_2_E3 = "Cloudy Park 2 - Enemy 3 (Waddle Dee)" +Cloudy_Park_2_E4 = "Cloudy Park 2 - Enemy 4 (Sparky)" +Cloudy_Park_2_E5 = "Cloudy Park 2 - Enemy 5 (Broom Hatter)" +Cloudy_Park_2_E6 = "Cloudy Park 2 - Enemy 6 (Sir Kibble)" +Cloudy_Park_2_E7 = "Cloudy Park 2 - Enemy 7 (Pteran)" +Cloudy_Park_2_E8 = "Cloudy Park 2 - Enemy 8 (Propeller)" +Cloudy_Park_2_E9 = "Cloudy Park 2 - Enemy 9 (Dogon)" +Cloudy_Park_2_E10 = "Cloudy Park 2 - Enemy 10 (Togezo)" +Cloudy_Park_2_E11 = "Cloudy Park 2 - Enemy 11 (Oro)" +Cloudy_Park_2_E12 = "Cloudy Park 2 - Enemy 12 (Bronto Burt)" +Cloudy_Park_2_E13 = "Cloudy Park 2 - Enemy 13 (Rocky)" +Cloudy_Park_2_E14 = "Cloudy Park 2 - Enemy 14 (Galbo)" +Cloudy_Park_2_E15 = "Cloudy Park 2 - Enemy 15 (Kapar)" +Cloudy_Park_3_E1 = "Cloudy Park 3 - Enemy 1 (Bronto Burt)" +Cloudy_Park_3_E2 = "Cloudy Park 3 - Enemy 2 (Mopoo)" +Cloudy_Park_3_E3 = "Cloudy Park 3 - Enemy 3 (Poppy Bros Jr.)" +Cloudy_Park_3_E4 = "Cloudy Park 3 - Enemy 4 (Como)" +Cloudy_Park_3_E5 = "Cloudy Park 3 - Enemy 5 (Glunk)" +Cloudy_Park_3_E6 = "Cloudy Park 3 - Enemy 6 (Bobin)" +Cloudy_Park_3_E7 = "Cloudy Park 3 - Enemy 7 (Loud)" +Cloudy_Park_3_E8 = "Cloudy Park 3 - Enemy 8 (Kapar)" +Cloudy_Park_3_E9 = "Cloudy Park 3 - Enemy 9 (Galbo)" +Cloudy_Park_3_E10 = "Cloudy Park 3 - Enemy 10 (Batamon)" +Cloudy_Park_3_E11 = "Cloudy Park 3 - Enemy 11 (Bouncy)" +Cloudy_Park_4_E1 = "Cloudy Park 4 - Enemy 1 (Gabon)" +Cloudy_Park_4_E2 = "Cloudy Park 4 - Enemy 2 (Como)" +Cloudy_Park_4_E3 = "Cloudy Park 4 - Enemy 3 (Wapod)" +Cloudy_Park_4_E4 = "Cloudy Park 4 - Enemy 4 (Cappy)" +Cloudy_Park_4_E5 = "Cloudy Park 4 - Enemy 5 (Sparky)" +Cloudy_Park_4_E6 = "Cloudy Park 4 - Enemy 6 (Togezo)" +Cloudy_Park_4_E7 = "Cloudy Park 4 - Enemy 7 (Bronto Burt)" +Cloudy_Park_4_E8 = "Cloudy Park 4 - Enemy 8 (KeKe)" +Cloudy_Park_4_E9 = "Cloudy Park 4 - Enemy 9 (Bouncy)" +Cloudy_Park_4_E10 = "Cloudy Park 4 - Enemy 10 (Sir Kibble)" +Cloudy_Park_4_E11 = "Cloudy Park 4 - Enemy 11 (Mariel)" +Cloudy_Park_4_E12 = "Cloudy Park 4 - Enemy 12 (Kabu)" +Cloudy_Park_4_E13 = "Cloudy Park 4 - Enemy 13 (Wappa)" +Cloudy_Park_5_E1 = "Cloudy Park 5 - Enemy 1 (Yaban)" +Cloudy_Park_5_E2 = "Cloudy Park 5 - Enemy 2 (Sir Kibble)" +Cloudy_Park_5_E3 = "Cloudy Park 5 - Enemy 3 (Cappy)" +Cloudy_Park_5_E4 = "Cloudy Park 5 - Enemy 4 (Wappa)" +Cloudy_Park_5_E5 = "Cloudy Park 5 - Enemy 5 (Galbo)" +Cloudy_Park_5_E6 = "Cloudy Park 5 - Enemy 6 (Bronto Burt)" +Cloudy_Park_5_E7 = "Cloudy Park 5 - Enemy 7 (KeKe)" +Cloudy_Park_5_E8 = "Cloudy Park 5 - Enemy 8 (Propeller)" +Cloudy_Park_5_E9 = "Cloudy Park 5 - Enemy 9 (Klinko)" +Cloudy_Park_5_E10 = "Cloudy Park 5 - Enemy 10 (Wapod)" +Cloudy_Park_5_E11 = "Cloudy Park 5 - Enemy 11 (Pteran)" +Cloudy_Park_6_E1 = "Cloudy Park 6 - Enemy 1 (Madoo)" +Cloudy_Park_6_E2 = "Cloudy Park 6 - Enemy 2 (Tick)" +Cloudy_Park_6_E3 = "Cloudy Park 6 - Enemy 3 (Como)" +Cloudy_Park_6_E4 = "Cloudy Park 6 - Enemy 4 (Waddle Dee Drawing)" +Cloudy_Park_6_E5 = "Cloudy Park 6 - Enemy 5 (Bronto Burt Drawing)" +Cloudy_Park_6_E6 = "Cloudy Park 6 - Enemy 6 (Bouncy Drawing)" +Cloudy_Park_6_E7 = "Cloudy Park 6 - Enemy 7 (Propeller)" +Cloudy_Park_6_E8 = "Cloudy Park 6 - Enemy 8 (Mopoo)" +Cloudy_Park_6_E9 = "Cloudy Park 6 - Enemy 9 (Bukiset (Burning))" +Cloudy_Park_6_E10 = "Cloudy Park 6 - Enemy 10 (Bukiset (Ice))" +Cloudy_Park_6_E11 = "Cloudy Park 6 - Enemy 11 (Bukiset (Needle))" +Cloudy_Park_6_E12 = "Cloudy Park 6 - Enemy 12 (Bukiset (Clean))" +Cloudy_Park_6_E13 = "Cloudy Park 6 - Enemy 13 (Bukiset (Cutter))" +Iceberg_1_E1 = "Iceberg 1 - Enemy 1 (Waddle Dee)" +Iceberg_1_E2 = "Iceberg 1 - Enemy 2 (Klinko)" +Iceberg_1_E3 = "Iceberg 1 - Enemy 3 (KeKe)" +Iceberg_1_E4 = "Iceberg 1 - Enemy 4 (Como)" +Iceberg_1_E5 = "Iceberg 1 - Enemy 5 (Galbo)" +Iceberg_1_E6 = "Iceberg 1 - Enemy 6 (Rocky)" +Iceberg_1_E7 = "Iceberg 1 - Enemy 7 (Kapar)" +Iceberg_1_E8 = "Iceberg 1 - Enemy 8 (Mopoo)" +Iceberg_1_E9 = "Iceberg 1 - Enemy 9 (Babut)" +Iceberg_1_E10 = "Iceberg 1 - Enemy 10 (Wappa)" +Iceberg_1_E11 = "Iceberg 1 - Enemy 11 (Bronto Burt)" +Iceberg_1_E12 = "Iceberg 1 - Enemy 12 (Chilly)" +Iceberg_1_E13 = "Iceberg 1 - Enemy 13 (Poppy Bros Jr.)" +Iceberg_2_E1 = "Iceberg 2 - Enemy 1 (Gabon)" +Iceberg_2_E2 = "Iceberg 2 - Enemy 2 (Nruff)" +Iceberg_2_E3 = "Iceberg 2 - Enemy 3 (Waddle Dee)" +Iceberg_2_E4 = "Iceberg 2 - Enemy 4 (Chilly)" +Iceberg_2_E5 = "Iceberg 2 - Enemy 5 (Pteran)" +Iceberg_2_E6 = "Iceberg 2 - Enemy 6 (Glunk)" +Iceberg_2_E7 = "Iceberg 2 - Enemy 7 (Galbo)" +Iceberg_2_E8 = "Iceberg 2 - Enemy 8 (Babut)" +Iceberg_2_E9 = "Iceberg 2 - Enemy 9 (Magoo)" +Iceberg_2_E10 = "Iceberg 2 - Enemy 10 (Propeller)" +Iceberg_2_E11 = "Iceberg 2 - Enemy 11 (Nidoo)" +Iceberg_2_E12 = "Iceberg 2 - Enemy 12 (Oro)" +Iceberg_2_E13 = "Iceberg 2 - Enemy 13 (Klinko)" +Iceberg_2_E14 = "Iceberg 2 - Enemy 14 (Bronto Burt)" +Iceberg_3_E1 = "Iceberg 3 - Enemy 1 (Corori)" +Iceberg_3_E2 = "Iceberg 3 - Enemy 2 (Bouncy)" +Iceberg_3_E3 = "Iceberg 3 - Enemy 3 (Chilly)" +Iceberg_3_E4 = "Iceberg 3 - Enemy 4 (Pteran)" +Iceberg_3_E5 = "Iceberg 3 - Enemy 5 (Raft Waddle Dee)" +Iceberg_3_E6 = "Iceberg 3 - Enemy 6 (Kapar)" +Iceberg_3_E7 = "Iceberg 3 - Enemy 7 (Blipper)" +Iceberg_3_E8 = "Iceberg 3 - Enemy 8 (Wapod)" +Iceberg_3_E9 = "Iceberg 3 - Enemy 9 (Glunk)" +Iceberg_3_E10 = "Iceberg 3 - Enemy 10 (Icicle)" +Iceberg_4_E1 = "Iceberg 4 - Enemy 1 (Bronto Burt)" +Iceberg_4_E2 = "Iceberg 4 - Enemy 2 (Galbo)" +Iceberg_4_E3 = "Iceberg 4 - Enemy 3 (Klinko)" +Iceberg_4_E4 = "Iceberg 4 - Enemy 4 (Chilly)" +Iceberg_4_E5 = "Iceberg 4 - Enemy 5 (Babut)" +Iceberg_4_E6 = "Iceberg 4 - Enemy 6 (Wappa)" +Iceberg_4_E7 = "Iceberg 4 - Enemy 7 (Icicle)" +Iceberg_4_E8 = "Iceberg 4 - Enemy 8 (Corori)" +Iceberg_4_E9 = "Iceberg 4 - Enemy 9 (Gabon)" +Iceberg_4_E10 = "Iceberg 4 - Enemy 10 (Kabu)" +Iceberg_4_E11 = "Iceberg 4 - Enemy 11 (Broom Hatter)" +Iceberg_4_E12 = "Iceberg 4 - Enemy 12 (Sasuke)" +Iceberg_4_E13 = "Iceberg 4 - Enemy 13 (Nruff)" +Iceberg_5_E1 = "Iceberg 5 - Enemy 1 (Bukiset (Burning))" +Iceberg_5_E2 = "Iceberg 5 - Enemy 2 (Bukiset (Stone))" +Iceberg_5_E3 = "Iceberg 5 - Enemy 3 (Bukiset (Ice))" +Iceberg_5_E4 = "Iceberg 5 - Enemy 4 (Bukiset (Needle))" +Iceberg_5_E5 = "Iceberg 5 - Enemy 5 (Bukiset (Clean))" +Iceberg_5_E6 = "Iceberg 5 - Enemy 6 (Bukiset (Parasol))" +Iceberg_5_E7 = "Iceberg 5 - Enemy 7 (Bukiset (Spark))" +Iceberg_5_E8 = "Iceberg 5 - Enemy 8 (Bukiset (Cutter))" +Iceberg_5_E9 = "Iceberg 5 - Enemy 9 (Glunk)" +Iceberg_5_E10 = "Iceberg 5 - Enemy 10 (Wapod)" +Iceberg_5_E11 = "Iceberg 5 - Enemy 11 (Tick)" +Iceberg_5_E12 = "Iceberg 5 - Enemy 12 (Madoo)" +Iceberg_5_E13 = "Iceberg 5 - Enemy 13 (Yaban)" +Iceberg_5_E14 = "Iceberg 5 - Enemy 14 (Propeller)" +Iceberg_5_E15 = "Iceberg 5 - Enemy 15 (Mariel)" +Iceberg_5_E16 = "Iceberg 5 - Enemy 16 (Pteran)" +Iceberg_5_E17 = "Iceberg 5 - Enemy 17 (Galbo)" +Iceberg_5_E18 = "Iceberg 5 - Enemy 18 (KeKe)" +Iceberg_5_E19 = "Iceberg 5 - Enemy 19 (Nidoo)" +Iceberg_5_E20 = "Iceberg 5 - Enemy 20 (Waddle Dee Drawing)" +Iceberg_5_E21 = "Iceberg 5 - Enemy 21 (Bronto Burt Drawing)" +Iceberg_5_E22 = "Iceberg 5 - Enemy 22 (Bouncy Drawing)" +Iceberg_5_E23 = "Iceberg 5 - Enemy 23 (Joe)" +Iceberg_5_E24 = "Iceberg 5 - Enemy 24 (Kapar)" +Iceberg_5_E25 = "Iceberg 5 - Enemy 25 (Gansan)" +Iceberg_5_E26 = "Iceberg 5 - Enemy 26 (Sasuke)" +Iceberg_5_E27 = "Iceberg 5 - Enemy 27 (Togezo)" +Iceberg_5_E28 = "Iceberg 5 - Enemy 28 (Sparky)" +Iceberg_5_E29 = "Iceberg 5 - Enemy 29 (Bobin)" +Iceberg_5_E30 = "Iceberg 5 - Enemy 30 (Chilly)" +Iceberg_5_E31 = "Iceberg 5 - Enemy 31 (Peran)" +Iceberg_6_E1 = "Iceberg 6 - Enemy 1 (Nruff)" +Iceberg_6_E2 = "Iceberg 6 - Enemy 2 (Nidoo)" +Iceberg_6_E3 = "Iceberg 6 - Enemy 3 (Sparky)" +Iceberg_6_E4 = "Iceberg 6 - Enemy 4 (Sir Kibble)" +Grass_Land_4_M1 = "Grass Land 4 - Miniboss 1 (Boboo)" +Ripple_Field_4_M1 = "Ripple Field 4 - Miniboss 1 (Captain Stitch)" +Sand_Canyon_4_M1 = "Sand Canyon 4 - Miniboss 1 (Haboki)" +Cloudy_Park_4_M1 = "Cloudy Park 4 - Miniboss 1 (Jumper Shoot)" +Iceberg_4_M1 = "Iceberg 4 - Miniboss 1 (Yuki)" +Iceberg_6_M1 = "Iceberg 6 - Miniboss 1 (Blocky)" +Iceberg_6_M2 = "Iceberg 6 - Miniboss 2 (Jumper Shoot)" +Iceberg_6_M3 = "Iceberg 6 - Miniboss 3 (Yuki)" +Iceberg_6_M4 = "Iceberg 6 - Miniboss 4 (Haboki)" +Iceberg_6_M5 = "Iceberg 6 - Miniboss 5 (Boboo)" +Iceberg_6_M6 = "Iceberg 6 - Miniboss 6 (Captain Stitch)" + + +enemy_mapping = { + Grass_Land_1_E1: "Waddle Dee", + Grass_Land_1_E2: "Sir Kibble", + Grass_Land_1_E3: "Cappy", + Grass_Land_1_E4: "Sparky", + Grass_Land_1_E5: "Bronto Burt", + Grass_Land_1_E6: "Sasuke", + Grass_Land_1_E7: "Poppy Bros Jr.", + Grass_Land_2_E1: "Rocky", + Grass_Land_2_E2: "KeKe", + Grass_Land_2_E3: "Bobo", + Grass_Land_2_E4: "Poppy Bros Jr.", + Grass_Land_2_E5: "Waddle Dee", + Grass_Land_2_E6: "Popon Ball", + Grass_Land_2_E7: "Bouncy", + Grass_Land_2_E8: "Tick", + Grass_Land_2_E9: "Bronto Burt", + Grass_Land_2_E10: "Nruff", + Grass_Land_3_E1: "Sparky", + Grass_Land_3_E2: "Rocky", + Grass_Land_3_E3: "Nruff", + Grass_Land_3_E4: "Bouncy", + Grass_Land_4_E1: "Loud", + Grass_Land_4_E2: "Babut", + Grass_Land_4_E3: "Rocky", + Grass_Land_4_E4: "Kapar", + Grass_Land_4_E5: "Glunk", + Grass_Land_4_E6: "Oro", + Grass_Land_4_E7: "Peran", + Grass_Land_5_E1: "Propeller", + Grass_Land_5_E2: "Broom Hatter", + Grass_Land_5_E3: "Bouncy", + Grass_Land_5_E4: "Sir Kibble", + Grass_Land_5_E5: "Waddle Dee", + Grass_Land_5_E6: "Sasuke", + Grass_Land_5_E7: "Nruff", + Grass_Land_5_E8: "Tick", + Grass_Land_6_E1: "Como", + Grass_Land_6_E2: "Togezo", + Grass_Land_6_E3: "Bronto Burt", + Grass_Land_6_E4: "Cappy", + Grass_Land_6_E5: "Bobo", + Grass_Land_6_E6: "Mariel", + Grass_Land_6_E7: "Yaban", + Grass_Land_6_E8: "Broom Hatter", + Grass_Land_6_E9: "Apolo", + Grass_Land_6_E10: "Sasuke", + Grass_Land_6_E11: "Rocky", + Ripple_Field_1_E1: "Waddle Dee", + Ripple_Field_1_E2: "Glunk", + Ripple_Field_1_E3: "Broom Hatter", + Ripple_Field_1_E4: "Cappy", + Ripple_Field_1_E5: "Bronto Burt", + Ripple_Field_1_E6: "Rocky", + Ripple_Field_1_E7: "Poppy Bros Jr.", + Ripple_Field_1_E8: "Bobin", + Ripple_Field_2_E1: "Togezo", + Ripple_Field_2_E2: "Coconut", + Ripple_Field_2_E3: "Blipper", + Ripple_Field_2_E4: "Sasuke", + Ripple_Field_2_E5: "Kany", + Ripple_Field_2_E6: "Glunk", + Ripple_Field_3_E1: "Raft Waddle Dee", + Ripple_Field_3_E2: "Kapar", + Ripple_Field_3_E3: "Blipper", + Ripple_Field_3_E4: "Sparky", + Ripple_Field_3_E5: "Glunk", + Ripple_Field_3_E6: "Joe", + Ripple_Field_3_E7: "Bobo", + Ripple_Field_4_E1: "Bukiset (Stone)", + Ripple_Field_4_E2: "Bukiset (Needle)", + Ripple_Field_4_E3: "Bukiset (Clean)", + Ripple_Field_4_E4: "Bukiset (Parasol)", + Ripple_Field_4_E5: "Mony", + Ripple_Field_4_E6: "Bukiset (Burning)", + Ripple_Field_4_E7: "Bobin", + Ripple_Field_4_E8: "Blipper", + Ripple_Field_4_E9: "Como", + Ripple_Field_4_E10: "Oro", + Ripple_Field_4_E11: "Gansan", + Ripple_Field_4_E12: "Waddle Dee", + Ripple_Field_4_E13: "Kapar", + Ripple_Field_4_E14: "Squishy", + Ripple_Field_4_E15: "Nidoo", + Ripple_Field_5_E1: "Glunk", + Ripple_Field_5_E2: "Joe", + Ripple_Field_5_E3: "Bobin", + Ripple_Field_5_E4: "Mony", + Ripple_Field_5_E5: "Squishy", + Ripple_Field_5_E6: "Yaban", + Ripple_Field_5_E7: "Broom Hatter", + Ripple_Field_5_E8: "Bouncy", + Ripple_Field_5_E9: "Sparky", + Ripple_Field_5_E10: "Rocky", + Ripple_Field_5_E11: "Babut", + Ripple_Field_5_E12: "Galbo", + Ripple_Field_6_E1: "Kany", + Ripple_Field_6_E2: "KeKe", + Ripple_Field_6_E3: "Kapar", + Ripple_Field_6_E4: "Rocky", + Ripple_Field_6_E5: "Poppy Bros Jr.", + Ripple_Field_6_E6: "Propeller", + Ripple_Field_6_E7: "Coconut", + Ripple_Field_6_E8: "Sasuke", + Ripple_Field_6_E9: "Nruff", + Sand_Canyon_1_E1: "Bronto Burt", + Sand_Canyon_1_E2: "Galbo", + Sand_Canyon_1_E3: "Oro", + Sand_Canyon_1_E4: "Sparky", + Sand_Canyon_1_E5: "Propeller", + Sand_Canyon_1_E6: "Gansan", + Sand_Canyon_1_E7: "Babut", + Sand_Canyon_1_E8: "Loud", + Sand_Canyon_1_E9: "Dogon", + Sand_Canyon_1_E10: "Bouncy", + Sand_Canyon_1_E11: "Pteran", + Sand_Canyon_1_E12: "Polof", + Sand_Canyon_2_E1: "KeKe", + Sand_Canyon_2_E2: "Doka", + Sand_Canyon_2_E3: "Boten", + Sand_Canyon_2_E4: "Propeller", + Sand_Canyon_2_E5: "Waddle Dee", + Sand_Canyon_2_E6: "Sparky", + Sand_Canyon_2_E7: "Sasuke", + Sand_Canyon_2_E8: "Como", + Sand_Canyon_2_E9: "Bukiset (Ice)", + Sand_Canyon_2_E10: "Bukiset (Needle)", + Sand_Canyon_2_E11: "Bukiset (Clean)", + Sand_Canyon_2_E12: "Bukiset (Parasol)", + Sand_Canyon_2_E13: "Bukiset (Spark)", + Sand_Canyon_2_E14: "Bukiset (Cutter)", + Sand_Canyon_2_E15: "Nidoo", + Sand_Canyon_2_E16: "Mariel", + Sand_Canyon_2_E17: "Yaban", + Sand_Canyon_2_E18: "Wapod", + Sand_Canyon_2_E19: "Squishy", + Sand_Canyon_2_E20: "Pteran", + Sand_Canyon_3_E1: "Sir Kibble", + Sand_Canyon_3_E2: "Broom Hatter", + Sand_Canyon_3_E3: "Rocky", + Sand_Canyon_3_E4: "Gabon", + Sand_Canyon_3_E5: "Kany", + Sand_Canyon_3_E6: "Galbo", + Sand_Canyon_3_E7: "Propeller", + Sand_Canyon_3_E8: "Sasuke", + Sand_Canyon_3_E9: "Wapod", + Sand_Canyon_3_E10: "Bobo", + Sand_Canyon_3_E11: "Babut", + Sand_Canyon_3_E12: "Magoo", + Sand_Canyon_4_E1: "Popon Ball", + Sand_Canyon_4_E2: "Mariel", + Sand_Canyon_4_E3: "Chilly", + Sand_Canyon_4_E4: "Tick", + Sand_Canyon_4_E5: "Bronto Burt", + Sand_Canyon_4_E6: "Babut", + Sand_Canyon_4_E7: "Bobin", + Sand_Canyon_4_E8: "Joe", + Sand_Canyon_4_E9: "Mony", + Sand_Canyon_4_E10: "Blipper", + Sand_Canyon_4_E11: "Togezo", + Sand_Canyon_4_E12: "Rocky", + Sand_Canyon_4_E13: "Bobo", + Sand_Canyon_5_E1: "Wapod", + Sand_Canyon_5_E2: "Dogon", + Sand_Canyon_5_E3: "Tick", + Sand_Canyon_5_E4: "Rocky", + Sand_Canyon_5_E5: "Bobo", + Sand_Canyon_5_E6: "Chilly", + Sand_Canyon_5_E7: "Sparky", + Sand_Canyon_5_E8: "Togezo", + Sand_Canyon_5_E9: "Bronto Burt", + Sand_Canyon_5_E10: "Sasuke", + Sand_Canyon_5_E11: "Oro", + Sand_Canyon_5_E12: "Galbo", + Sand_Canyon_5_E13: "Nidoo", + Sand_Canyon_5_E14: "Propeller", + Sand_Canyon_5_E15: "Sir Kibble", + Sand_Canyon_5_E16: "KeKe", + Sand_Canyon_5_E17: "Kabu", + Sand_Canyon_6_E1: "Sparky", + Sand_Canyon_6_E2: "Doka", + Sand_Canyon_6_E3: "Cappy", + Sand_Canyon_6_E4: "Pteran", + Sand_Canyon_6_E5: "Bukiset (Parasol)", + Sand_Canyon_6_E6: "Bukiset (Cutter)", + Sand_Canyon_6_E7: "Bukiset (Clean)", + Sand_Canyon_6_E8: "Bukiset (Spark)", + Sand_Canyon_6_E9: "Bukiset (Ice)", + Sand_Canyon_6_E10: "Bukiset (Needle)", + Sand_Canyon_6_E11: "Bukiset (Burning)", + Sand_Canyon_6_E12: "Bukiset (Stone)", + Sand_Canyon_6_E13: "Nidoo", + Cloudy_Park_1_E1: "Waddle Dee", + Cloudy_Park_1_E2: "KeKe", + Cloudy_Park_1_E3: "Cappy", + Cloudy_Park_1_E4: "Yaban", + Cloudy_Park_1_E5: "Togezo", + Cloudy_Park_1_E6: "Galbo", + Cloudy_Park_1_E7: "Sparky", + Cloudy_Park_1_E8: "Como", + Cloudy_Park_1_E9: "Bronto Burt", + Cloudy_Park_1_E10: "Gabon", + Cloudy_Park_1_E11: "Sir Kibble", + Cloudy_Park_1_E12: "Mariel", + Cloudy_Park_1_E13: "Nruff", + Cloudy_Park_2_E1: "Chilly", + Cloudy_Park_2_E2: "Sasuke", + Cloudy_Park_2_E3: "Waddle Dee", + Cloudy_Park_2_E4: "Sparky", + Cloudy_Park_2_E5: "Broom Hatter", + Cloudy_Park_2_E6: "Sir Kibble", + Cloudy_Park_2_E7: "Pteran", + Cloudy_Park_2_E8: "Propeller", + Cloudy_Park_2_E9: "Dogon", + Cloudy_Park_2_E10: "Togezo", + Cloudy_Park_2_E11: "Oro", + Cloudy_Park_2_E12: "Bronto Burt", + Cloudy_Park_2_E13: "Rocky", + Cloudy_Park_2_E14: "Galbo", + Cloudy_Park_2_E15: "Kapar", + Cloudy_Park_3_E1: "Bronto Burt", + Cloudy_Park_3_E2: "Mopoo", + Cloudy_Park_3_E3: "Poppy Bros Jr.", + Cloudy_Park_3_E4: "Como", + Cloudy_Park_3_E5: "Glunk", + Cloudy_Park_3_E6: "Bobin", + Cloudy_Park_3_E7: "Loud", + Cloudy_Park_3_E8: "Kapar", + Cloudy_Park_3_E9: "Galbo", + Cloudy_Park_3_E10: "Batamon", + Cloudy_Park_3_E11: "Bouncy", + Cloudy_Park_4_E1: "Gabon", + Cloudy_Park_4_E2: "Como", + Cloudy_Park_4_E3: "Wapod", + Cloudy_Park_4_E4: "Cappy", + Cloudy_Park_4_E5: "Sparky", + Cloudy_Park_4_E6: "Togezo", + Cloudy_Park_4_E7: "Bronto Burt", + Cloudy_Park_4_E8: "KeKe", + Cloudy_Park_4_E9: "Bouncy", + Cloudy_Park_4_E10: "Sir Kibble", + Cloudy_Park_4_E11: "Mariel", + Cloudy_Park_4_E12: "Kabu", + Cloudy_Park_4_E13: "Wappa", + Cloudy_Park_5_E1: "Yaban", + Cloudy_Park_5_E2: "Sir Kibble", + Cloudy_Park_5_E3: "Cappy", + Cloudy_Park_5_E4: "Wappa", + Cloudy_Park_5_E5: "Galbo", + Cloudy_Park_5_E6: "Bronto Burt", + Cloudy_Park_5_E7: "KeKe", + Cloudy_Park_5_E8: "Propeller", + Cloudy_Park_5_E9: "Klinko", + Cloudy_Park_5_E10: "Wapod", + Cloudy_Park_5_E11: "Pteran", + Cloudy_Park_6_E1: "Madoo", + Cloudy_Park_6_E2: "Tick", + Cloudy_Park_6_E3: "Como", + Cloudy_Park_6_E4: "Waddle Dee Drawing", + Cloudy_Park_6_E5: "Bronto Burt Drawing", + Cloudy_Park_6_E6: "Bouncy Drawing", + Cloudy_Park_6_E7: "Propeller", + Cloudy_Park_6_E8: "Mopoo", + Cloudy_Park_6_E9: "Bukiset (Burning)", + Cloudy_Park_6_E10: "Bukiset (Ice)", + Cloudy_Park_6_E11: "Bukiset (Needle)", + Cloudy_Park_6_E12: "Bukiset (Clean)", + Cloudy_Park_6_E13: "Bukiset (Cutter)", + Iceberg_1_E1: "Waddle Dee", + Iceberg_1_E2: "Klinko", + Iceberg_1_E3: "KeKe", + Iceberg_1_E4: "Como", + Iceberg_1_E5: "Galbo", + Iceberg_1_E6: "Rocky", + Iceberg_1_E7: "Kapar", + Iceberg_1_E8: "Mopoo", + Iceberg_1_E9: "Babut", + Iceberg_1_E10: "Wappa", + Iceberg_1_E11: "Bronto Burt", + Iceberg_1_E12: "Chilly", + Iceberg_1_E13: "Poppy Bros Jr.", + Iceberg_2_E1: "Gabon", + Iceberg_2_E2: "Nruff", + Iceberg_2_E3: "Waddle Dee", + Iceberg_2_E4: "Chilly", + Iceberg_2_E5: "Pteran", + Iceberg_2_E6: "Glunk", + Iceberg_2_E7: "Galbo", + Iceberg_2_E8: "Babut", + Iceberg_2_E9: "Magoo", + Iceberg_2_E10: "Propeller", + Iceberg_2_E11: "Nidoo", + Iceberg_2_E12: "Oro", + Iceberg_2_E13: "Klinko", + Iceberg_2_E14: "Bronto Burt", + Iceberg_3_E1: "Corori", + Iceberg_3_E2: "Bouncy", + Iceberg_3_E3: "Chilly", + Iceberg_3_E4: "Pteran", + Iceberg_3_E5: "Raft Waddle Dee", + Iceberg_3_E6: "Kapar", + Iceberg_3_E7: "Blipper", + Iceberg_3_E8: "Wapod", + Iceberg_3_E9: "Glunk", + Iceberg_3_E10: "Icicle", + Iceberg_4_E1: "Bronto Burt", + Iceberg_4_E2: "Galbo", + Iceberg_4_E3: "Klinko", + Iceberg_4_E4: "Chilly", + Iceberg_4_E5: "Babut", + Iceberg_4_E6: "Wappa", + Iceberg_4_E7: "Icicle", + Iceberg_4_E8: "Corori", + Iceberg_4_E9: "Gabon", + Iceberg_4_E10: "Kabu", + Iceberg_4_E11: "Broom Hatter", + Iceberg_4_E12: "Sasuke", + Iceberg_4_E13: "Nruff", + Iceberg_5_E1: "Bukiset (Burning)", + Iceberg_5_E2: "Bukiset (Stone)", + Iceberg_5_E3: "Bukiset (Ice)", + Iceberg_5_E4: "Bukiset (Needle)", + Iceberg_5_E5: "Bukiset (Clean)", + Iceberg_5_E6: "Bukiset (Parasol)", + Iceberg_5_E7: "Bukiset (Spark)", + Iceberg_5_E8: "Bukiset (Cutter)", + Iceberg_5_E9: "Glunk", + Iceberg_5_E10: "Wapod", + Iceberg_5_E11: "Tick", + Iceberg_5_E12: "Madoo", + Iceberg_5_E13: "Yaban", + Iceberg_5_E14: "Propeller", + Iceberg_5_E15: "Mariel", + Iceberg_5_E16: "Pteran", + Iceberg_5_E17: "Galbo", + Iceberg_5_E18: "KeKe", + Iceberg_5_E19: "Nidoo", + Iceberg_5_E20: "Waddle Dee Drawing", + Iceberg_5_E21: "Bronto Burt Drawing", + Iceberg_5_E22: "Bouncy Drawing", + Iceberg_5_E23: "Joe", + Iceberg_5_E24: "Kapar", + Iceberg_5_E25: "Gansan", + Iceberg_5_E26: "Sasuke", + Iceberg_5_E27: "Togezo", + Iceberg_5_E28: "Sparky", + Iceberg_5_E29: "Bobin", + Iceberg_5_E30: "Chilly", + Iceberg_5_E31: "Peran", + Iceberg_6_E1: "Nruff", + Iceberg_6_E2: "Nidoo", + Iceberg_6_E3: "Sparky", + Iceberg_6_E4: "Sir Kibble", + Grass_Land_4_M1: "Boboo", + Ripple_Field_4_M1: "Captain Stitch", + Sand_Canyon_4_M1: "Haboki", + Cloudy_Park_4_M1: "Jumper Shoot", + Iceberg_4_M1: "Yuki", + Iceberg_6_M1: "Blocky", + Iceberg_6_M2: "Jumper Shoot", + Iceberg_6_M3: "Yuki", + Iceberg_6_M4: "Haboki", + Iceberg_6_M5: "Boboo", + Iceberg_6_M6: "Captain Stitch", + +} + +vanilla_enemies = {'Waddle Dee': 'No Ability', + 'Bronto Burt': 'No Ability', + 'Rocky': 'Stone Ability', + 'Bobo': 'Burning Ability', + 'Chilly': 'Ice Ability', + 'Poppy Bros Jr.': 'No Ability', + 'Sparky': 'Spark Ability', + 'Polof': 'No Ability', + 'Broom Hatter': 'Clean Ability', + 'Cappy': 'No Ability', + 'Bouncy': 'No Ability', + 'Nruff': 'No Ability', + 'Glunk': 'No Ability', + 'Togezo': 'Needle Ability', + 'Kabu': 'No Ability', + 'Mony': 'No Ability', + 'Blipper': 'No Ability', + 'Squishy': 'No Ability', + 'Gabon': 'No Ability', + 'Oro': 'No Ability', + 'Galbo': 'Burning Ability', + 'Sir Kibble': 'Cutter Ability', + 'Nidoo': 'No Ability', + 'Kany': 'No Ability', + 'Sasuke': 'Parasol Ability', + 'Yaban': 'No Ability', + 'Boten': 'Needle Ability', + 'Coconut': 'No Ability', + 'Doka': 'No Ability', + 'Icicle': 'No Ability', + 'Pteran': 'No Ability', + 'Loud': 'No Ability', + 'Como': 'No Ability', + 'Klinko': 'Parasol Ability', + 'Babut': 'No Ability', + 'Wappa': 'Ice Ability', + 'Mariel': 'No Ability', + 'Tick': 'Needle Ability', + 'Apolo': 'No Ability', + 'Popon Ball': 'No Ability', + 'KeKe': 'Clean Ability', + 'Magoo': 'Burning Ability', + 'Raft Waddle Dee': 'No Ability', + 'Madoo': 'No Ability', + 'Corori': 'No Ability', + 'Kapar': 'Cutter Ability', + 'Batamon': 'No Ability', + 'Peran': 'No Ability', + 'Bobin': 'Spark Ability', + 'Mopoo': 'No Ability', + 'Gansan': 'Stone Ability', + 'Bukiset (Burning)': 'Burning Ability', + 'Bukiset (Stone)': 'Stone Ability', + 'Bukiset (Ice)': 'Ice Ability', + 'Bukiset (Needle)': 'Needle Ability', + 'Bukiset (Clean)': 'Clean Ability', + 'Bukiset (Parasol)': 'Parasol Ability', + 'Bukiset (Spark)': 'Spark Ability', + 'Bukiset (Cutter)': 'Cutter Ability', + 'Waddle Dee Drawing': 'No Ability', + 'Bronto Burt Drawing': 'No Ability', + 'Bouncy Drawing': 'No Ability', + 'Kabu (Dekabu)': 'No Ability', + 'Wapod': 'No Ability', + 'Propeller': 'No Ability', + 'Dogon': 'No Ability', + 'Joe': 'No Ability', + 'Captain Stitch': 'Needle Ability', + 'Yuki': 'Ice Ability', + 'Blocky': 'Stone Ability', + 'Jumper Shoot': 'Parasol Ability', + 'Boboo': 'Burning Ability', + 'Haboki': 'Clean Ability', + } + +enemy_restrictive: List[Tuple[List[str], List[str]]] = [ + # abilities, enemies, set_all (False to set any) + (["Burning Ability", "Stone Ability"], ["Rocky", "Sparky", "Babut", "Squishy", ]), # Ribbon Field 5 - 7 + # Sand Canyon 6 + (["Parasol Ability", "Cutter Ability"], ['Bukiset (Parasol)', 'Bukiset (Cutter)']), + (["Spark Ability", "Clean Ability"], ['Bukiset (Spark)', 'Bukiset (Clean)']), + (["Ice Ability", "Needle Ability"], ['Bukiset (Ice)', 'Bukiset (Needle)']), + (["Stone Ability", "Burning Ability"], ['Bukiset (Stone)', 'Bukiset (Burning)']), + (["Stone Ability"], ['Bukiset (Burning)', 'Bukiset (Stone)', 'Bukiset (Ice)', 'Bukiset (Needle)', + 'Bukiset (Clean)', 'Bukiset (Spark)', 'Bukiset (Parasol)', 'Bukiset (Cutter)']), + (["Parasol Ability"], ['Bukiset (Burning)', 'Bukiset (Stone)', 'Bukiset (Ice)', 'Bukiset (Needle)', + 'Bukiset (Clean)', 'Bukiset (Spark)', 'Bukiset (Parasol)', 'Bukiset (Cutter)']), +] diff --git a/worlds/kdl3/Names/LocationName.py b/worlds/kdl3/Names/LocationName.py new file mode 100644 index 0000000000..59a0a1d690 --- /dev/null +++ b/worlds/kdl3/Names/LocationName.py @@ -0,0 +1,928 @@ +# Level 1 +grass_land_1 = "Grass Land 1 - Complete" +grass_land_2 = "Grass Land 2 - Complete" +grass_land_3 = "Grass Land 3 - Complete" +grass_land_4 = "Grass Land 4 - Complete" +grass_land_5 = "Grass Land 5 - Complete" +grass_land_6 = "Grass Land 6 - Complete" +grass_land_tulip = "Grass Land 1 - Tulip" +grass_land_muchi = "Grass Land 2 - Muchimuchi" +grass_land_pitcherman = "Grass Land 3 - Pitcherman" +grass_land_chao = "Grass Land 4 - Chao & Goku" +grass_land_mine = "Grass Land 5 - Mine" +grass_land_pierre = "Grass Land 6 - Pierre" +grass_land_whispy = "Grass Land - Boss (Whispy Woods) Purified" + +# Level 2 +ripple_field_1 = "Ripple Field 1 - Complete" +ripple_field_2 = "Ripple Field 2 - Complete" +ripple_field_3 = "Ripple Field 3 - Complete" +ripple_field_4 = "Ripple Field 4 - Complete" +ripple_field_5 = "Ripple Field 5 - Complete" +ripple_field_6 = "Ripple Field 6 - Complete" +ripple_field_kamuribana = "Ripple Field 1 - Kamuribana" +ripple_field_bakasa = "Ripple Field 2 - Bakasa" +ripple_field_elieel = "Ripple Field 3 - Elieel" +ripple_field_toad = "Ripple Field 4 - Toad & Little Toad" +ripple_field_mama_pitch = "Ripple Field 5 - Mama Pitch" +ripple_field_hb002 = "Ripple Field 6 - HB-002" +ripple_field_acro = "Ripple Field - Boss (Acro) Purified" + +# Level 3 +sand_canyon_1 = "Sand Canyon 1 - Complete" +sand_canyon_2 = "Sand Canyon 2 - Complete" +sand_canyon_3 = "Sand Canyon 3 - Complete" +sand_canyon_4 = "Sand Canyon 4 - Complete" +sand_canyon_5 = "Sand Canyon 5 - Complete" +sand_canyon_6 = "Sand Canyon 6 - Complete" +sand_canyon_mushrooms = "Sand Canyon 1 - Geromuzudake" +sand_canyon_auntie = "Sand Canyon 2 - Auntie" +sand_canyon_caramello = "Sand Canyon 3 - Caramello" +sand_canyon_hikari = "Sand Canyon 4 - Donbe & Hikari" +sand_canyon_nyupun = "Sand Canyon 5 - Nyupun" +sand_canyon_rob = "Sand Canyon 6 - Professor Hector & R.O.B" +sand_canyon_poncon = "Sand Canyon - Boss (Pon & Con) Purified" + +# Level 4 +cloudy_park_1 = "Cloudy Park 1 - Complete" +cloudy_park_2 = "Cloudy Park 2 - Complete" +cloudy_park_3 = "Cloudy Park 3 - Complete" +cloudy_park_4 = "Cloudy Park 4 - Complete" +cloudy_park_5 = "Cloudy Park 5 - Complete" +cloudy_park_6 = "Cloudy Park 6 - Complete" +cloudy_park_hibanamodoki = "Cloudy Park 1 - Hibanamodoki" +cloudy_park_piyokeko = "Cloudy Park 2 - Piyo & Keko" +cloudy_park_mrball = "Cloudy Park 3 - Mr. Ball" +cloudy_park_mikarin = "Cloudy Park 4 - Mikarin & Kagami Mocchi" +cloudy_park_pick = "Cloudy Park 5 - Pick" +cloudy_park_hb007 = "Cloudy Park 6 - HB-007" +cloudy_park_ado = "Cloudy Park - Boss (Ado) Purified" + +# Level 5 +iceberg_1 = "Iceberg 1 - Complete" +iceberg_2 = "Iceberg 2 - Complete" +iceberg_3 = "Iceberg 3 - Complete" +iceberg_4 = "Iceberg 4 - Complete" +iceberg_5 = "Iceberg 5 - Complete" +iceberg_6 = "Iceberg 6 - Complete" +iceberg_kogoesou = "Iceberg 1 - Kogoesou" +iceberg_samus = "Iceberg 2 - Samus" +iceberg_kawasaki = "Iceberg 3 - Chef Kawasaki" +iceberg_name = "Iceberg 4 - Name" +iceberg_shiro = "Iceberg 5 - Shiro" +iceberg_angel = "Iceberg 6 - Angel" +iceberg_dedede = "Iceberg - Boss (Dedede) Purified" + +# Level 6 +hyper_zone = "Hyper Zone - Zero" + +# Extras +boss_butch = "Boss Butch" +mg5_p = "Minigame 5 - Perfect" +jumping_clear = "Jumping - Target Score Reached" + +# 1-Ups +grass_land_1_u1 = "Grass Land 1 - 1-Up (Parasol)" # Parasol +grass_land_2_u1 = "Grass Land 2 - 1-Up (Needle)" # Needle +grass_land_3_u1 = "Grass Land 3 - 1-Up (Climb)" # None +grass_land_4_u1 = "Grass Land 4 - 1-Up (Gordo)" # None +grass_land_6_u1 = "Grass Land 6 - 1-Up (Tower)" # None +grass_land_6_u2 = "Grass Land 6 - 1-Up (Falling)" # None +ripple_field_2_u1 = "Ripple Field 2 - 1-Up (Currents)" # Kine +ripple_field_3_u1 = "Ripple Field 3 - 1-Up (Cutter/Spark)" # Cutter or Spark +ripple_field_4_u1 = "Ripple Field 4 - 1-Up (Stone)" # Stone +ripple_field_5_u1 = "Ripple Field 5 - 1-Up (Currents)" # Kine, Burning, Stone +sand_canyon_1_u1 = "Sand Canyon 1 - 1-Up (Polof)" # None +sand_canyon_2_u1 = "Sand Canyon 2 - 1-Up (Enclave)" # None +sand_canyon_4_u1 = "Sand Canyon 4 - 1-Up (Clean)" # Clean +sand_canyon_5_u1 = "Sand Canyon 5 - 1-Up (Falling Block)" # None +sand_canyon_5_u2 = "Sand Canyon 5 - 1-Up (Ice 1)" # Ice +sand_canyon_5_u3 = "Sand Canyon 5 - 1-Up (Ice 2)" # Ice +sand_canyon_5_u4 = "Sand Canyon 5 - 1-Up (Ice 3)" # Ice +cloudy_park_1_u1 = "Cloudy Park 1 - 1-Up (Shotzo)" # None +cloudy_park_4_u1 = "Cloudy Park 4 - 1-Up (Windy)" # Coo +cloudy_park_6_u1 = "Cloudy Park 6 - 1-Up (Cutter)" # Cutter +iceberg_5_u1 = "Iceberg 5 - 1-Up (Boulder)" # None +iceberg_5_u2 = "Iceberg 5 - 1-Up (Floor)" # None +iceberg_5_u3 = "Iceberg 5 - 1-Up (Peloo)" # None, just let yourself get eaten by the Peloo +iceberg_6_u1 = "Iceberg 6 - 1-Up (Middle)" # None + +# Maxim Tomatoes +grass_land_1_m1 = "Grass Land 1 - Maxim Tomato (Spark)" # Spark +grass_land_3_m1 = "Grass Land 3 - Maxim Tomato (Climb)" # None +grass_land_4_m1 = "Grass Land 4 - Maxim Tomato (Zebon Right)" # None +grass_land_4_m2 = "Grass Land 4 - Maxim Tomato (Gordo)" # None +grass_land_4_m3 = "Grass Land 4 - Maxim Tomato (Cliff)" # None +ripple_field_2_m1 = "Ripple Field 2 - Maxim Tomato (Currents)" # Kine +ripple_field_3_m1 = "Ripple Field 3 - Maxim Tomato (Cove)" # None +ripple_field_4_m1 = "Ripple Field 4 - Maxim Tomato (Dark)" # None (maybe Spark?) +ripple_field_4_m2 = "Ripple Field 4 - Maxim Tomato (Stone)" # Stone +ripple_field_5_m1 = "Ripple Field 5 - Maxim Tomato (Exit)" # Kine +ripple_field_5_m2 = "Ripple Field 5 - Maxim Tomato (Currents)" # Kine, Burning, Stone +sand_canyon_2_m1 = "Sand Canyon 2 - Maxim Tomato (Underwater)" # None +sand_canyon_4_m1 = "Sand Canyon 4 - Maxim Tomato (Pacto)" # None +sand_canyon_4_m2 = "Sand Canyon 4 - Maxim Tomato (Needle)" # Needle +sand_canyon_5_m1 = "Sand Canyon 5 - Maxim Tomato (Pit)" # None +cloudy_park_1_m1 = "Cloudy Park 1 - Maxim Tomato (Mariel)" # None +cloudy_park_4_m1 = "Cloudy Park 4 - Maxim Tomato (Windy)" # Coo +cloudy_park_5_m1 = "Cloudy Park 5 - Maxim Tomato (Pillars)" # None +iceberg_3_m1 = "Iceberg 3 - Maxim Tomato (Ceiling)" # None +iceberg_6_m1 = "Iceberg 6 - Maxim Tomato (Left)" # None + +# Level Names +level_names = { + "Grass Land": 1, + "Ripple Field": 2, + "Sand Canyon": 3, + "Cloudy Park": 4, + "Iceberg": 5, +} + +level_names_inverse = { + level_names[level]: level for level in level_names +} + +# Boss Names +boss_names = { + "Whispy Woods": 0x770200, + "Acro": 0x770201, + "Pon & Con": 0x770202, + "Ado": 0x770203, + "King Dedede": 0x770204 +} + +# Goal Mapping +goals = { + 0: hyper_zone, + 1: boss_butch, + 2: mg5_p, + 3: jumping_clear +} + +grass_land_1_s1 = "Grass Land 1 - Star 1" +grass_land_1_s2 = "Grass Land 1 - Star 2" +grass_land_1_s3 = "Grass Land 1 - Star 3" +grass_land_1_s4 = "Grass Land 1 - Star 4" +grass_land_1_s5 = "Grass Land 1 - Star 5" +grass_land_1_s6 = "Grass Land 1 - Star 6" +grass_land_1_s7 = "Grass Land 1 - Star 7" +grass_land_1_s8 = "Grass Land 1 - Star 8" +grass_land_1_s9 = "Grass Land 1 - Star 9" +grass_land_1_s10 = "Grass Land 1 - Star 10" +grass_land_1_s11 = "Grass Land 1 - Star 11" +grass_land_1_s12 = "Grass Land 1 - Star 12" +grass_land_1_s13 = "Grass Land 1 - Star 13" +grass_land_1_s14 = "Grass Land 1 - Star 14" +grass_land_1_s15 = "Grass Land 1 - Star 15" +grass_land_1_s16 = "Grass Land 1 - Star 16" +grass_land_1_s17 = "Grass Land 1 - Star 17" +grass_land_1_s18 = "Grass Land 1 - Star 18" +grass_land_1_s19 = "Grass Land 1 - Star 19" +grass_land_1_s20 = "Grass Land 1 - Star 20" +grass_land_1_s21 = "Grass Land 1 - Star 21" +grass_land_1_s22 = "Grass Land 1 - Star 22" +grass_land_1_s23 = "Grass Land 1 - Star 23" +grass_land_2_s1 = "Grass Land 2 - Star 1" +grass_land_2_s2 = "Grass Land 2 - Star 2" +grass_land_2_s3 = "Grass Land 2 - Star 3" +grass_land_2_s4 = "Grass Land 2 - Star 4" +grass_land_2_s5 = "Grass Land 2 - Star 5" +grass_land_2_s6 = "Grass Land 2 - Star 6" +grass_land_2_s7 = "Grass Land 2 - Star 7" +grass_land_2_s8 = "Grass Land 2 - Star 8" +grass_land_2_s9 = "Grass Land 2 - Star 9" +grass_land_2_s10 = "Grass Land 2 - Star 10" +grass_land_2_s11 = "Grass Land 2 - Star 11" +grass_land_2_s12 = "Grass Land 2 - Star 12" +grass_land_2_s13 = "Grass Land 2 - Star 13" +grass_land_2_s14 = "Grass Land 2 - Star 14" +grass_land_2_s15 = "Grass Land 2 - Star 15" +grass_land_2_s16 = "Grass Land 2 - Star 16" +grass_land_2_s17 = "Grass Land 2 - Star 17" +grass_land_2_s18 = "Grass Land 2 - Star 18" +grass_land_2_s19 = "Grass Land 2 - Star 19" +grass_land_2_s20 = "Grass Land 2 - Star 20" +grass_land_2_s21 = "Grass Land 2 - Star 21" +grass_land_3_s1 = "Grass Land 3 - Star 1" +grass_land_3_s2 = "Grass Land 3 - Star 2" +grass_land_3_s3 = "Grass Land 3 - Star 3" +grass_land_3_s4 = "Grass Land 3 - Star 4" +grass_land_3_s5 = "Grass Land 3 - Star 5" +grass_land_3_s6 = "Grass Land 3 - Star 6" +grass_land_3_s7 = "Grass Land 3 - Star 7" +grass_land_3_s8 = "Grass Land 3 - Star 8" +grass_land_3_s9 = "Grass Land 3 - Star 9" +grass_land_3_s10 = "Grass Land 3 - Star 10" +grass_land_3_s11 = "Grass Land 3 - Star 11" +grass_land_3_s12 = "Grass Land 3 - Star 12" +grass_land_3_s13 = "Grass Land 3 - Star 13" +grass_land_3_s14 = "Grass Land 3 - Star 14" +grass_land_3_s15 = "Grass Land 3 - Star 15" +grass_land_3_s16 = "Grass Land 3 - Star 16" +grass_land_3_s17 = "Grass Land 3 - Star 17" +grass_land_3_s18 = "Grass Land 3 - Star 18" +grass_land_3_s19 = "Grass Land 3 - Star 19" +grass_land_3_s20 = "Grass Land 3 - Star 20" +grass_land_3_s21 = "Grass Land 3 - Star 21" +grass_land_3_s22 = "Grass Land 3 - Star 22" +grass_land_3_s23 = "Grass Land 3 - Star 23" +grass_land_3_s24 = "Grass Land 3 - Star 24" +grass_land_3_s25 = "Grass Land 3 - Star 25" +grass_land_3_s26 = "Grass Land 3 - Star 26" +grass_land_3_s27 = "Grass Land 3 - Star 27" +grass_land_3_s28 = "Grass Land 3 - Star 28" +grass_land_3_s29 = "Grass Land 3 - Star 29" +grass_land_3_s30 = "Grass Land 3 - Star 30" +grass_land_3_s31 = "Grass Land 3 - Star 31" +grass_land_4_s1 = "Grass Land 4 - Star 1" +grass_land_4_s2 = "Grass Land 4 - Star 2" +grass_land_4_s3 = "Grass Land 4 - Star 3" +grass_land_4_s4 = "Grass Land 4 - Star 4" +grass_land_4_s5 = "Grass Land 4 - Star 5" +grass_land_4_s6 = "Grass Land 4 - Star 6" +grass_land_4_s7 = "Grass Land 4 - Star 7" +grass_land_4_s8 = "Grass Land 4 - Star 8" +grass_land_4_s9 = "Grass Land 4 - Star 9" +grass_land_4_s10 = "Grass Land 4 - Star 10" +grass_land_4_s11 = "Grass Land 4 - Star 11" +grass_land_4_s12 = "Grass Land 4 - Star 12" +grass_land_4_s13 = "Grass Land 4 - Star 13" +grass_land_4_s14 = "Grass Land 4 - Star 14" +grass_land_4_s15 = "Grass Land 4 - Star 15" +grass_land_4_s16 = "Grass Land 4 - Star 16" +grass_land_4_s17 = "Grass Land 4 - Star 17" +grass_land_4_s18 = "Grass Land 4 - Star 18" +grass_land_4_s19 = "Grass Land 4 - Star 19" +grass_land_4_s20 = "Grass Land 4 - Star 20" +grass_land_4_s21 = "Grass Land 4 - Star 21" +grass_land_4_s22 = "Grass Land 4 - Star 22" +grass_land_4_s23 = "Grass Land 4 - Star 23" +grass_land_4_s24 = "Grass Land 4 - Star 24" +grass_land_4_s25 = "Grass Land 4 - Star 25" +grass_land_4_s26 = "Grass Land 4 - Star 26" +grass_land_4_s27 = "Grass Land 4 - Star 27" +grass_land_4_s28 = "Grass Land 4 - Star 28" +grass_land_4_s29 = "Grass Land 4 - Star 29" +grass_land_4_s30 = "Grass Land 4 - Star 30" +grass_land_4_s31 = "Grass Land 4 - Star 31" +grass_land_4_s32 = "Grass Land 4 - Star 32" +grass_land_4_s33 = "Grass Land 4 - Star 33" +grass_land_4_s34 = "Grass Land 4 - Star 34" +grass_land_4_s35 = "Grass Land 4 - Star 35" +grass_land_4_s36 = "Grass Land 4 - Star 36" +grass_land_4_s37 = "Grass Land 4 - Star 37" +grass_land_5_s1 = "Grass Land 5 - Star 1" +grass_land_5_s2 = "Grass Land 5 - Star 2" +grass_land_5_s3 = "Grass Land 5 - Star 3" +grass_land_5_s4 = "Grass Land 5 - Star 4" +grass_land_5_s5 = "Grass Land 5 - Star 5" +grass_land_5_s6 = "Grass Land 5 - Star 6" +grass_land_5_s7 = "Grass Land 5 - Star 7" +grass_land_5_s8 = "Grass Land 5 - Star 8" +grass_land_5_s9 = "Grass Land 5 - Star 9" +grass_land_5_s10 = "Grass Land 5 - Star 10" +grass_land_5_s11 = "Grass Land 5 - Star 11" +grass_land_5_s12 = "Grass Land 5 - Star 12" +grass_land_5_s13 = "Grass Land 5 - Star 13" +grass_land_5_s14 = "Grass Land 5 - Star 14" +grass_land_5_s15 = "Grass Land 5 - Star 15" +grass_land_5_s16 = "Grass Land 5 - Star 16" +grass_land_5_s17 = "Grass Land 5 - Star 17" +grass_land_5_s18 = "Grass Land 5 - Star 18" +grass_land_5_s19 = "Grass Land 5 - Star 19" +grass_land_5_s20 = "Grass Land 5 - Star 20" +grass_land_5_s21 = "Grass Land 5 - Star 21" +grass_land_5_s22 = "Grass Land 5 - Star 22" +grass_land_5_s23 = "Grass Land 5 - Star 23" +grass_land_5_s24 = "Grass Land 5 - Star 24" +grass_land_5_s25 = "Grass Land 5 - Star 25" +grass_land_5_s26 = "Grass Land 5 - Star 26" +grass_land_5_s27 = "Grass Land 5 - Star 27" +grass_land_5_s28 = "Grass Land 5 - Star 28" +grass_land_5_s29 = "Grass Land 5 - Star 29" +grass_land_6_s1 = "Grass Land 6 - Star 1" +grass_land_6_s2 = "Grass Land 6 - Star 2" +grass_land_6_s3 = "Grass Land 6 - Star 3" +grass_land_6_s4 = "Grass Land 6 - Star 4" +grass_land_6_s5 = "Grass Land 6 - Star 5" +grass_land_6_s6 = "Grass Land 6 - Star 6" +grass_land_6_s7 = "Grass Land 6 - Star 7" +grass_land_6_s8 = "Grass Land 6 - Star 8" +grass_land_6_s9 = "Grass Land 6 - Star 9" +grass_land_6_s10 = "Grass Land 6 - Star 10" +grass_land_6_s11 = "Grass Land 6 - Star 11" +grass_land_6_s12 = "Grass Land 6 - Star 12" +grass_land_6_s13 = "Grass Land 6 - Star 13" +grass_land_6_s14 = "Grass Land 6 - Star 14" +grass_land_6_s15 = "Grass Land 6 - Star 15" +grass_land_6_s16 = "Grass Land 6 - Star 16" +grass_land_6_s17 = "Grass Land 6 - Star 17" +grass_land_6_s18 = "Grass Land 6 - Star 18" +grass_land_6_s19 = "Grass Land 6 - Star 19" +grass_land_6_s20 = "Grass Land 6 - Star 20" +grass_land_6_s21 = "Grass Land 6 - Star 21" +grass_land_6_s22 = "Grass Land 6 - Star 22" +grass_land_6_s23 = "Grass Land 6 - Star 23" +grass_land_6_s24 = "Grass Land 6 - Star 24" +grass_land_6_s25 = "Grass Land 6 - Star 25" +grass_land_6_s26 = "Grass Land 6 - Star 26" +grass_land_6_s27 = "Grass Land 6 - Star 27" +grass_land_6_s28 = "Grass Land 6 - Star 28" +grass_land_6_s29 = "Grass Land 6 - Star 29" +ripple_field_1_s1 = "Ripple Field 1 - Star 1" +ripple_field_1_s2 = "Ripple Field 1 - Star 2" +ripple_field_1_s3 = "Ripple Field 1 - Star 3" +ripple_field_1_s4 = "Ripple Field 1 - Star 4" +ripple_field_1_s5 = "Ripple Field 1 - Star 5" +ripple_field_1_s6 = "Ripple Field 1 - Star 6" +ripple_field_1_s7 = "Ripple Field 1 - Star 7" +ripple_field_1_s8 = "Ripple Field 1 - Star 8" +ripple_field_1_s9 = "Ripple Field 1 - Star 9" +ripple_field_1_s10 = "Ripple Field 1 - Star 10" +ripple_field_1_s11 = "Ripple Field 1 - Star 11" +ripple_field_1_s12 = "Ripple Field 1 - Star 12" +ripple_field_1_s13 = "Ripple Field 1 - Star 13" +ripple_field_1_s14 = "Ripple Field 1 - Star 14" +ripple_field_1_s15 = "Ripple Field 1 - Star 15" +ripple_field_1_s16 = "Ripple Field 1 - Star 16" +ripple_field_1_s17 = "Ripple Field 1 - Star 17" +ripple_field_1_s18 = "Ripple Field 1 - Star 18" +ripple_field_1_s19 = "Ripple Field 1 - Star 19" +ripple_field_2_s1 = "Ripple Field 2 - Star 1" +ripple_field_2_s2 = "Ripple Field 2 - Star 2" +ripple_field_2_s3 = "Ripple Field 2 - Star 3" +ripple_field_2_s4 = "Ripple Field 2 - Star 4" +ripple_field_2_s5 = "Ripple Field 2 - Star 5" +ripple_field_2_s6 = "Ripple Field 2 - Star 6" +ripple_field_2_s7 = "Ripple Field 2 - Star 7" +ripple_field_2_s8 = "Ripple Field 2 - Star 8" +ripple_field_2_s9 = "Ripple Field 2 - Star 9" +ripple_field_2_s10 = "Ripple Field 2 - Star 10" +ripple_field_2_s11 = "Ripple Field 2 - Star 11" +ripple_field_2_s12 = "Ripple Field 2 - Star 12" +ripple_field_2_s13 = "Ripple Field 2 - Star 13" +ripple_field_2_s14 = "Ripple Field 2 - Star 14" +ripple_field_2_s15 = "Ripple Field 2 - Star 15" +ripple_field_2_s16 = "Ripple Field 2 - Star 16" +ripple_field_2_s17 = "Ripple Field 2 - Star 17" +ripple_field_3_s1 = "Ripple Field 3 - Star 1" +ripple_field_3_s2 = "Ripple Field 3 - Star 2" +ripple_field_3_s3 = "Ripple Field 3 - Star 3" +ripple_field_3_s4 = "Ripple Field 3 - Star 4" +ripple_field_3_s5 = "Ripple Field 3 - Star 5" +ripple_field_3_s6 = "Ripple Field 3 - Star 6" +ripple_field_3_s7 = "Ripple Field 3 - Star 7" +ripple_field_3_s8 = "Ripple Field 3 - Star 8" +ripple_field_3_s9 = "Ripple Field 3 - Star 9" +ripple_field_3_s10 = "Ripple Field 3 - Star 10" +ripple_field_3_s11 = "Ripple Field 3 - Star 11" +ripple_field_3_s12 = "Ripple Field 3 - Star 12" +ripple_field_3_s13 = "Ripple Field 3 - Star 13" +ripple_field_3_s14 = "Ripple Field 3 - Star 14" +ripple_field_3_s15 = "Ripple Field 3 - Star 15" +ripple_field_3_s16 = "Ripple Field 3 - Star 16" +ripple_field_3_s17 = "Ripple Field 3 - Star 17" +ripple_field_3_s18 = "Ripple Field 3 - Star 18" +ripple_field_3_s19 = "Ripple Field 3 - Star 19" +ripple_field_3_s20 = "Ripple Field 3 - Star 20" +ripple_field_3_s21 = "Ripple Field 3 - Star 21" +ripple_field_4_s1 = "Ripple Field 4 - Star 1" +ripple_field_4_s2 = "Ripple Field 4 - Star 2" +ripple_field_4_s3 = "Ripple Field 4 - Star 3" +ripple_field_4_s4 = "Ripple Field 4 - Star 4" +ripple_field_4_s5 = "Ripple Field 4 - Star 5" +ripple_field_4_s6 = "Ripple Field 4 - Star 6" +ripple_field_4_s7 = "Ripple Field 4 - Star 7" +ripple_field_4_s8 = "Ripple Field 4 - Star 8" +ripple_field_4_s9 = "Ripple Field 4 - Star 9" +ripple_field_4_s10 = "Ripple Field 4 - Star 10" +ripple_field_4_s11 = "Ripple Field 4 - Star 11" +ripple_field_4_s12 = "Ripple Field 4 - Star 12" +ripple_field_4_s13 = "Ripple Field 4 - Star 13" +ripple_field_4_s14 = "Ripple Field 4 - Star 14" +ripple_field_4_s15 = "Ripple Field 4 - Star 15" +ripple_field_4_s16 = "Ripple Field 4 - Star 16" +ripple_field_4_s17 = "Ripple Field 4 - Star 17" +ripple_field_4_s18 = "Ripple Field 4 - Star 18" +ripple_field_4_s19 = "Ripple Field 4 - Star 19" +ripple_field_4_s20 = "Ripple Field 4 - Star 20" +ripple_field_4_s21 = "Ripple Field 4 - Star 21" +ripple_field_4_s22 = "Ripple Field 4 - Star 22" +ripple_field_4_s23 = "Ripple Field 4 - Star 23" +ripple_field_4_s24 = "Ripple Field 4 - Star 24" +ripple_field_4_s25 = "Ripple Field 4 - Star 25" +ripple_field_4_s26 = "Ripple Field 4 - Star 26" +ripple_field_4_s27 = "Ripple Field 4 - Star 27" +ripple_field_4_s28 = "Ripple Field 4 - Star 28" +ripple_field_4_s29 = "Ripple Field 4 - Star 29" +ripple_field_4_s30 = "Ripple Field 4 - Star 30" +ripple_field_4_s31 = "Ripple Field 4 - Star 31" +ripple_field_4_s32 = "Ripple Field 4 - Star 32" +ripple_field_4_s33 = "Ripple Field 4 - Star 33" +ripple_field_4_s34 = "Ripple Field 4 - Star 34" +ripple_field_4_s35 = "Ripple Field 4 - Star 35" +ripple_field_4_s36 = "Ripple Field 4 - Star 36" +ripple_field_4_s37 = "Ripple Field 4 - Star 37" +ripple_field_4_s38 = "Ripple Field 4 - Star 38" +ripple_field_4_s39 = "Ripple Field 4 - Star 39" +ripple_field_4_s40 = "Ripple Field 4 - Star 40" +ripple_field_4_s41 = "Ripple Field 4 - Star 41" +ripple_field_4_s42 = "Ripple Field 4 - Star 42" +ripple_field_4_s43 = "Ripple Field 4 - Star 43" +ripple_field_4_s44 = "Ripple Field 4 - Star 44" +ripple_field_4_s45 = "Ripple Field 4 - Star 45" +ripple_field_4_s46 = "Ripple Field 4 - Star 46" +ripple_field_4_s47 = "Ripple Field 4 - Star 47" +ripple_field_4_s48 = "Ripple Field 4 - Star 48" +ripple_field_4_s49 = "Ripple Field 4 - Star 49" +ripple_field_4_s50 = "Ripple Field 4 - Star 50" +ripple_field_4_s51 = "Ripple Field 4 - Star 51" +ripple_field_5_s1 = "Ripple Field 5 - Star 1" +ripple_field_5_s2 = "Ripple Field 5 - Star 2" +ripple_field_5_s3 = "Ripple Field 5 - Star 3" +ripple_field_5_s4 = "Ripple Field 5 - Star 4" +ripple_field_5_s5 = "Ripple Field 5 - Star 5" +ripple_field_5_s6 = "Ripple Field 5 - Star 6" +ripple_field_5_s7 = "Ripple Field 5 - Star 7" +ripple_field_5_s8 = "Ripple Field 5 - Star 8" +ripple_field_5_s9 = "Ripple Field 5 - Star 9" +ripple_field_5_s10 = "Ripple Field 5 - Star 10" +ripple_field_5_s11 = "Ripple Field 5 - Star 11" +ripple_field_5_s12 = "Ripple Field 5 - Star 12" +ripple_field_5_s13 = "Ripple Field 5 - Star 13" +ripple_field_5_s14 = "Ripple Field 5 - Star 14" +ripple_field_5_s15 = "Ripple Field 5 - Star 15" +ripple_field_5_s16 = "Ripple Field 5 - Star 16" +ripple_field_5_s17 = "Ripple Field 5 - Star 17" +ripple_field_5_s18 = "Ripple Field 5 - Star 18" +ripple_field_5_s19 = "Ripple Field 5 - Star 19" +ripple_field_5_s20 = "Ripple Field 5 - Star 20" +ripple_field_5_s21 = "Ripple Field 5 - Star 21" +ripple_field_5_s22 = "Ripple Field 5 - Star 22" +ripple_field_5_s23 = "Ripple Field 5 - Star 23" +ripple_field_5_s24 = "Ripple Field 5 - Star 24" +ripple_field_5_s25 = "Ripple Field 5 - Star 25" +ripple_field_5_s26 = "Ripple Field 5 - Star 26" +ripple_field_5_s27 = "Ripple Field 5 - Star 27" +ripple_field_5_s28 = "Ripple Field 5 - Star 28" +ripple_field_5_s29 = "Ripple Field 5 - Star 29" +ripple_field_5_s30 = "Ripple Field 5 - Star 30" +ripple_field_5_s31 = "Ripple Field 5 - Star 31" +ripple_field_5_s32 = "Ripple Field 5 - Star 32" +ripple_field_5_s33 = "Ripple Field 5 - Star 33" +ripple_field_5_s34 = "Ripple Field 5 - Star 34" +ripple_field_5_s35 = "Ripple Field 5 - Star 35" +ripple_field_5_s36 = "Ripple Field 5 - Star 36" +ripple_field_5_s37 = "Ripple Field 5 - Star 37" +ripple_field_5_s38 = "Ripple Field 5 - Star 38" +ripple_field_5_s39 = "Ripple Field 5 - Star 39" +ripple_field_5_s40 = "Ripple Field 5 - Star 40" +ripple_field_5_s41 = "Ripple Field 5 - Star 41" +ripple_field_5_s42 = "Ripple Field 5 - Star 42" +ripple_field_5_s43 = "Ripple Field 5 - Star 43" +ripple_field_5_s44 = "Ripple Field 5 - Star 44" +ripple_field_5_s45 = "Ripple Field 5 - Star 45" +ripple_field_5_s46 = "Ripple Field 5 - Star 46" +ripple_field_5_s47 = "Ripple Field 5 - Star 47" +ripple_field_5_s48 = "Ripple Field 5 - Star 48" +ripple_field_5_s49 = "Ripple Field 5 - Star 49" +ripple_field_5_s50 = "Ripple Field 5 - Star 50" +ripple_field_5_s51 = "Ripple Field 5 - Star 51" +ripple_field_6_s1 = "Ripple Field 6 - Star 1" +ripple_field_6_s2 = "Ripple Field 6 - Star 2" +ripple_field_6_s3 = "Ripple Field 6 - Star 3" +ripple_field_6_s4 = "Ripple Field 6 - Star 4" +ripple_field_6_s5 = "Ripple Field 6 - Star 5" +ripple_field_6_s6 = "Ripple Field 6 - Star 6" +ripple_field_6_s7 = "Ripple Field 6 - Star 7" +ripple_field_6_s8 = "Ripple Field 6 - Star 8" +ripple_field_6_s9 = "Ripple Field 6 - Star 9" +ripple_field_6_s10 = "Ripple Field 6 - Star 10" +ripple_field_6_s11 = "Ripple Field 6 - Star 11" +ripple_field_6_s12 = "Ripple Field 6 - Star 12" +ripple_field_6_s13 = "Ripple Field 6 - Star 13" +ripple_field_6_s14 = "Ripple Field 6 - Star 14" +ripple_field_6_s15 = "Ripple Field 6 - Star 15" +ripple_field_6_s16 = "Ripple Field 6 - Star 16" +ripple_field_6_s17 = "Ripple Field 6 - Star 17" +ripple_field_6_s18 = "Ripple Field 6 - Star 18" +ripple_field_6_s19 = "Ripple Field 6 - Star 19" +ripple_field_6_s20 = "Ripple Field 6 - Star 20" +ripple_field_6_s21 = "Ripple Field 6 - Star 21" +ripple_field_6_s22 = "Ripple Field 6 - Star 22" +ripple_field_6_s23 = "Ripple Field 6 - Star 23" +sand_canyon_1_s1 = "Sand Canyon 1 - Star 1" +sand_canyon_1_s2 = "Sand Canyon 1 - Star 2" +sand_canyon_1_s3 = "Sand Canyon 1 - Star 3" +sand_canyon_1_s4 = "Sand Canyon 1 - Star 4" +sand_canyon_1_s5 = "Sand Canyon 1 - Star 5" +sand_canyon_1_s6 = "Sand Canyon 1 - Star 6" +sand_canyon_1_s7 = "Sand Canyon 1 - Star 7" +sand_canyon_1_s8 = "Sand Canyon 1 - Star 8" +sand_canyon_1_s9 = "Sand Canyon 1 - Star 9" +sand_canyon_1_s10 = "Sand Canyon 1 - Star 10" +sand_canyon_1_s11 = "Sand Canyon 1 - Star 11" +sand_canyon_1_s12 = "Sand Canyon 1 - Star 12" +sand_canyon_1_s13 = "Sand Canyon 1 - Star 13" +sand_canyon_1_s14 = "Sand Canyon 1 - Star 14" +sand_canyon_1_s15 = "Sand Canyon 1 - Star 15" +sand_canyon_1_s16 = "Sand Canyon 1 - Star 16" +sand_canyon_1_s17 = "Sand Canyon 1 - Star 17" +sand_canyon_1_s18 = "Sand Canyon 1 - Star 18" +sand_canyon_1_s19 = "Sand Canyon 1 - Star 19" +sand_canyon_1_s20 = "Sand Canyon 1 - Star 20" +sand_canyon_1_s21 = "Sand Canyon 1 - Star 21" +sand_canyon_1_s22 = "Sand Canyon 1 - Star 22" +sand_canyon_2_s1 = "Sand Canyon 2 - Star 1" +sand_canyon_2_s2 = "Sand Canyon 2 - Star 2" +sand_canyon_2_s3 = "Sand Canyon 2 - Star 3" +sand_canyon_2_s4 = "Sand Canyon 2 - Star 4" +sand_canyon_2_s5 = "Sand Canyon 2 - Star 5" +sand_canyon_2_s6 = "Sand Canyon 2 - Star 6" +sand_canyon_2_s7 = "Sand Canyon 2 - Star 7" +sand_canyon_2_s8 = "Sand Canyon 2 - Star 8" +sand_canyon_2_s9 = "Sand Canyon 2 - Star 9" +sand_canyon_2_s10 = "Sand Canyon 2 - Star 10" +sand_canyon_2_s11 = "Sand Canyon 2 - Star 11" +sand_canyon_2_s12 = "Sand Canyon 2 - Star 12" +sand_canyon_2_s13 = "Sand Canyon 2 - Star 13" +sand_canyon_2_s14 = "Sand Canyon 2 - Star 14" +sand_canyon_2_s15 = "Sand Canyon 2 - Star 15" +sand_canyon_2_s16 = "Sand Canyon 2 - Star 16" +sand_canyon_2_s17 = "Sand Canyon 2 - Star 17" +sand_canyon_2_s18 = "Sand Canyon 2 - Star 18" +sand_canyon_2_s19 = "Sand Canyon 2 - Star 19" +sand_canyon_2_s20 = "Sand Canyon 2 - Star 20" +sand_canyon_2_s21 = "Sand Canyon 2 - Star 21" +sand_canyon_2_s22 = "Sand Canyon 2 - Star 22" +sand_canyon_2_s23 = "Sand Canyon 2 - Star 23" +sand_canyon_2_s24 = "Sand Canyon 2 - Star 24" +sand_canyon_2_s25 = "Sand Canyon 2 - Star 25" +sand_canyon_2_s26 = "Sand Canyon 2 - Star 26" +sand_canyon_2_s27 = "Sand Canyon 2 - Star 27" +sand_canyon_2_s28 = "Sand Canyon 2 - Star 28" +sand_canyon_2_s29 = "Sand Canyon 2 - Star 29" +sand_canyon_2_s30 = "Sand Canyon 2 - Star 30" +sand_canyon_2_s31 = "Sand Canyon 2 - Star 31" +sand_canyon_2_s32 = "Sand Canyon 2 - Star 32" +sand_canyon_2_s33 = "Sand Canyon 2 - Star 33" +sand_canyon_2_s34 = "Sand Canyon 2 - Star 34" +sand_canyon_2_s35 = "Sand Canyon 2 - Star 35" +sand_canyon_2_s36 = "Sand Canyon 2 - Star 36" +sand_canyon_2_s37 = "Sand Canyon 2 - Star 37" +sand_canyon_2_s38 = "Sand Canyon 2 - Star 38" +sand_canyon_2_s39 = "Sand Canyon 2 - Star 39" +sand_canyon_2_s40 = "Sand Canyon 2 - Star 40" +sand_canyon_2_s41 = "Sand Canyon 2 - Star 41" +sand_canyon_2_s42 = "Sand Canyon 2 - Star 42" +sand_canyon_2_s43 = "Sand Canyon 2 - Star 43" +sand_canyon_2_s44 = "Sand Canyon 2 - Star 44" +sand_canyon_2_s45 = "Sand Canyon 2 - Star 45" +sand_canyon_2_s46 = "Sand Canyon 2 - Star 46" +sand_canyon_2_s47 = "Sand Canyon 2 - Star 47" +sand_canyon_2_s48 = "Sand Canyon 2 - Star 48" +sand_canyon_3_s1 = "Sand Canyon 3 - Star 1" +sand_canyon_3_s2 = "Sand Canyon 3 - Star 2" +sand_canyon_3_s3 = "Sand Canyon 3 - Star 3" +sand_canyon_3_s4 = "Sand Canyon 3 - Star 4" +sand_canyon_3_s5 = "Sand Canyon 3 - Star 5" +sand_canyon_3_s6 = "Sand Canyon 3 - Star 6" +sand_canyon_3_s7 = "Sand Canyon 3 - Star 7" +sand_canyon_3_s8 = "Sand Canyon 3 - Star 8" +sand_canyon_3_s9 = "Sand Canyon 3 - Star 9" +sand_canyon_3_s10 = "Sand Canyon 3 - Star 10" +sand_canyon_4_s1 = "Sand Canyon 4 - Star 1" +sand_canyon_4_s2 = "Sand Canyon 4 - Star 2" +sand_canyon_4_s3 = "Sand Canyon 4 - Star 3" +sand_canyon_4_s4 = "Sand Canyon 4 - Star 4" +sand_canyon_4_s5 = "Sand Canyon 4 - Star 5" +sand_canyon_4_s6 = "Sand Canyon 4 - Star 6" +sand_canyon_4_s7 = "Sand Canyon 4 - Star 7" +sand_canyon_4_s8 = "Sand Canyon 4 - Star 8" +sand_canyon_4_s9 = "Sand Canyon 4 - Star 9" +sand_canyon_4_s10 = "Sand Canyon 4 - Star 10" +sand_canyon_4_s11 = "Sand Canyon 4 - Star 11" +sand_canyon_4_s12 = "Sand Canyon 4 - Star 12" +sand_canyon_4_s13 = "Sand Canyon 4 - Star 13" +sand_canyon_4_s14 = "Sand Canyon 4 - Star 14" +sand_canyon_4_s15 = "Sand Canyon 4 - Star 15" +sand_canyon_4_s16 = "Sand Canyon 4 - Star 16" +sand_canyon_4_s17 = "Sand Canyon 4 - Star 17" +sand_canyon_4_s18 = "Sand Canyon 4 - Star 18" +sand_canyon_4_s19 = "Sand Canyon 4 - Star 19" +sand_canyon_4_s20 = "Sand Canyon 4 - Star 20" +sand_canyon_4_s21 = "Sand Canyon 4 - Star 21" +sand_canyon_4_s22 = "Sand Canyon 4 - Star 22" +sand_canyon_4_s23 = "Sand Canyon 4 - Star 23" +sand_canyon_5_s1 = "Sand Canyon 5 - Star 1" +sand_canyon_5_s2 = "Sand Canyon 5 - Star 2" +sand_canyon_5_s3 = "Sand Canyon 5 - Star 3" +sand_canyon_5_s4 = "Sand Canyon 5 - Star 4" +sand_canyon_5_s5 = "Sand Canyon 5 - Star 5" +sand_canyon_5_s6 = "Sand Canyon 5 - Star 6" +sand_canyon_5_s7 = "Sand Canyon 5 - Star 7" +sand_canyon_5_s8 = "Sand Canyon 5 - Star 8" +sand_canyon_5_s9 = "Sand Canyon 5 - Star 9" +sand_canyon_5_s10 = "Sand Canyon 5 - Star 10" +sand_canyon_5_s11 = "Sand Canyon 5 - Star 11" +sand_canyon_5_s12 = "Sand Canyon 5 - Star 12" +sand_canyon_5_s13 = "Sand Canyon 5 - Star 13" +sand_canyon_5_s14 = "Sand Canyon 5 - Star 14" +sand_canyon_5_s15 = "Sand Canyon 5 - Star 15" +sand_canyon_5_s16 = "Sand Canyon 5 - Star 16" +sand_canyon_5_s17 = "Sand Canyon 5 - Star 17" +sand_canyon_5_s18 = "Sand Canyon 5 - Star 18" +sand_canyon_5_s19 = "Sand Canyon 5 - Star 19" +sand_canyon_5_s20 = "Sand Canyon 5 - Star 20" +sand_canyon_5_s21 = "Sand Canyon 5 - Star 21" +sand_canyon_5_s22 = "Sand Canyon 5 - Star 22" +sand_canyon_5_s23 = "Sand Canyon 5 - Star 23" +sand_canyon_5_s24 = "Sand Canyon 5 - Star 24" +sand_canyon_5_s25 = "Sand Canyon 5 - Star 25" +sand_canyon_5_s26 = "Sand Canyon 5 - Star 26" +sand_canyon_5_s27 = "Sand Canyon 5 - Star 27" +sand_canyon_5_s28 = "Sand Canyon 5 - Star 28" +sand_canyon_5_s29 = "Sand Canyon 5 - Star 29" +sand_canyon_5_s30 = "Sand Canyon 5 - Star 30" +sand_canyon_5_s31 = "Sand Canyon 5 - Star 31" +sand_canyon_5_s32 = "Sand Canyon 5 - Star 32" +sand_canyon_5_s33 = "Sand Canyon 5 - Star 33" +sand_canyon_5_s34 = "Sand Canyon 5 - Star 34" +sand_canyon_5_s35 = "Sand Canyon 5 - Star 35" +sand_canyon_5_s36 = "Sand Canyon 5 - Star 36" +sand_canyon_5_s37 = "Sand Canyon 5 - Star 37" +sand_canyon_5_s38 = "Sand Canyon 5 - Star 38" +sand_canyon_5_s39 = "Sand Canyon 5 - Star 39" +sand_canyon_5_s40 = "Sand Canyon 5 - Star 40" +cloudy_park_1_s1 = "Cloudy Park 1 - Star 1" +cloudy_park_1_s2 = "Cloudy Park 1 - Star 2" +cloudy_park_1_s3 = "Cloudy Park 1 - Star 3" +cloudy_park_1_s4 = "Cloudy Park 1 - Star 4" +cloudy_park_1_s5 = "Cloudy Park 1 - Star 5" +cloudy_park_1_s6 = "Cloudy Park 1 - Star 6" +cloudy_park_1_s7 = "Cloudy Park 1 - Star 7" +cloudy_park_1_s8 = "Cloudy Park 1 - Star 8" +cloudy_park_1_s9 = "Cloudy Park 1 - Star 9" +cloudy_park_1_s10 = "Cloudy Park 1 - Star 10" +cloudy_park_1_s11 = "Cloudy Park 1 - Star 11" +cloudy_park_1_s12 = "Cloudy Park 1 - Star 12" +cloudy_park_1_s13 = "Cloudy Park 1 - Star 13" +cloudy_park_1_s14 = "Cloudy Park 1 - Star 14" +cloudy_park_1_s15 = "Cloudy Park 1 - Star 15" +cloudy_park_1_s16 = "Cloudy Park 1 - Star 16" +cloudy_park_1_s17 = "Cloudy Park 1 - Star 17" +cloudy_park_1_s18 = "Cloudy Park 1 - Star 18" +cloudy_park_1_s19 = "Cloudy Park 1 - Star 19" +cloudy_park_1_s20 = "Cloudy Park 1 - Star 20" +cloudy_park_1_s21 = "Cloudy Park 1 - Star 21" +cloudy_park_1_s22 = "Cloudy Park 1 - Star 22" +cloudy_park_1_s23 = "Cloudy Park 1 - Star 23" +cloudy_park_2_s1 = "Cloudy Park 2 - Star 1" +cloudy_park_2_s2 = "Cloudy Park 2 - Star 2" +cloudy_park_2_s3 = "Cloudy Park 2 - Star 3" +cloudy_park_2_s4 = "Cloudy Park 2 - Star 4" +cloudy_park_2_s5 = "Cloudy Park 2 - Star 5" +cloudy_park_2_s6 = "Cloudy Park 2 - Star 6" +cloudy_park_2_s7 = "Cloudy Park 2 - Star 7" +cloudy_park_2_s8 = "Cloudy Park 2 - Star 8" +cloudy_park_2_s9 = "Cloudy Park 2 - Star 9" +cloudy_park_2_s10 = "Cloudy Park 2 - Star 10" +cloudy_park_2_s11 = "Cloudy Park 2 - Star 11" +cloudy_park_2_s12 = "Cloudy Park 2 - Star 12" +cloudy_park_2_s13 = "Cloudy Park 2 - Star 13" +cloudy_park_2_s14 = "Cloudy Park 2 - Star 14" +cloudy_park_2_s15 = "Cloudy Park 2 - Star 15" +cloudy_park_2_s16 = "Cloudy Park 2 - Star 16" +cloudy_park_2_s17 = "Cloudy Park 2 - Star 17" +cloudy_park_2_s18 = "Cloudy Park 2 - Star 18" +cloudy_park_2_s19 = "Cloudy Park 2 - Star 19" +cloudy_park_2_s20 = "Cloudy Park 2 - Star 20" +cloudy_park_2_s21 = "Cloudy Park 2 - Star 21" +cloudy_park_2_s22 = "Cloudy Park 2 - Star 22" +cloudy_park_2_s23 = "Cloudy Park 2 - Star 23" +cloudy_park_2_s24 = "Cloudy Park 2 - Star 24" +cloudy_park_2_s25 = "Cloudy Park 2 - Star 25" +cloudy_park_2_s26 = "Cloudy Park 2 - Star 26" +cloudy_park_2_s27 = "Cloudy Park 2 - Star 27" +cloudy_park_2_s28 = "Cloudy Park 2 - Star 28" +cloudy_park_2_s29 = "Cloudy Park 2 - Star 29" +cloudy_park_2_s30 = "Cloudy Park 2 - Star 30" +cloudy_park_2_s31 = "Cloudy Park 2 - Star 31" +cloudy_park_2_s32 = "Cloudy Park 2 - Star 32" +cloudy_park_2_s33 = "Cloudy Park 2 - Star 33" +cloudy_park_2_s34 = "Cloudy Park 2 - Star 34" +cloudy_park_2_s35 = "Cloudy Park 2 - Star 35" +cloudy_park_2_s36 = "Cloudy Park 2 - Star 36" +cloudy_park_2_s37 = "Cloudy Park 2 - Star 37" +cloudy_park_2_s38 = "Cloudy Park 2 - Star 38" +cloudy_park_2_s39 = "Cloudy Park 2 - Star 39" +cloudy_park_2_s40 = "Cloudy Park 2 - Star 40" +cloudy_park_2_s41 = "Cloudy Park 2 - Star 41" +cloudy_park_2_s42 = "Cloudy Park 2 - Star 42" +cloudy_park_2_s43 = "Cloudy Park 2 - Star 43" +cloudy_park_2_s44 = "Cloudy Park 2 - Star 44" +cloudy_park_2_s45 = "Cloudy Park 2 - Star 45" +cloudy_park_2_s46 = "Cloudy Park 2 - Star 46" +cloudy_park_2_s47 = "Cloudy Park 2 - Star 47" +cloudy_park_2_s48 = "Cloudy Park 2 - Star 48" +cloudy_park_2_s49 = "Cloudy Park 2 - Star 49" +cloudy_park_2_s50 = "Cloudy Park 2 - Star 50" +cloudy_park_2_s51 = "Cloudy Park 2 - Star 51" +cloudy_park_2_s52 = "Cloudy Park 2 - Star 52" +cloudy_park_2_s53 = "Cloudy Park 2 - Star 53" +cloudy_park_2_s54 = "Cloudy Park 2 - Star 54" +cloudy_park_3_s1 = "Cloudy Park 3 - Star 1" +cloudy_park_3_s2 = "Cloudy Park 3 - Star 2" +cloudy_park_3_s3 = "Cloudy Park 3 - Star 3" +cloudy_park_3_s4 = "Cloudy Park 3 - Star 4" +cloudy_park_3_s5 = "Cloudy Park 3 - Star 5" +cloudy_park_3_s6 = "Cloudy Park 3 - Star 6" +cloudy_park_3_s7 = "Cloudy Park 3 - Star 7" +cloudy_park_3_s8 = "Cloudy Park 3 - Star 8" +cloudy_park_3_s9 = "Cloudy Park 3 - Star 9" +cloudy_park_3_s10 = "Cloudy Park 3 - Star 10" +cloudy_park_3_s11 = "Cloudy Park 3 - Star 11" +cloudy_park_3_s12 = "Cloudy Park 3 - Star 12" +cloudy_park_3_s13 = "Cloudy Park 3 - Star 13" +cloudy_park_3_s14 = "Cloudy Park 3 - Star 14" +cloudy_park_3_s15 = "Cloudy Park 3 - Star 15" +cloudy_park_3_s16 = "Cloudy Park 3 - Star 16" +cloudy_park_3_s17 = "Cloudy Park 3 - Star 17" +cloudy_park_3_s18 = "Cloudy Park 3 - Star 18" +cloudy_park_3_s19 = "Cloudy Park 3 - Star 19" +cloudy_park_3_s20 = "Cloudy Park 3 - Star 20" +cloudy_park_3_s21 = "Cloudy Park 3 - Star 21" +cloudy_park_3_s22 = "Cloudy Park 3 - Star 22" +cloudy_park_4_s1 = "Cloudy Park 4 - Star 1" +cloudy_park_4_s2 = "Cloudy Park 4 - Star 2" +cloudy_park_4_s3 = "Cloudy Park 4 - Star 3" +cloudy_park_4_s4 = "Cloudy Park 4 - Star 4" +cloudy_park_4_s5 = "Cloudy Park 4 - Star 5" +cloudy_park_4_s6 = "Cloudy Park 4 - Star 6" +cloudy_park_4_s7 = "Cloudy Park 4 - Star 7" +cloudy_park_4_s8 = "Cloudy Park 4 - Star 8" +cloudy_park_4_s9 = "Cloudy Park 4 - Star 9" +cloudy_park_4_s10 = "Cloudy Park 4 - Star 10" +cloudy_park_4_s11 = "Cloudy Park 4 - Star 11" +cloudy_park_4_s12 = "Cloudy Park 4 - Star 12" +cloudy_park_4_s13 = "Cloudy Park 4 - Star 13" +cloudy_park_4_s14 = "Cloudy Park 4 - Star 14" +cloudy_park_4_s15 = "Cloudy Park 4 - Star 15" +cloudy_park_4_s16 = "Cloudy Park 4 - Star 16" +cloudy_park_4_s17 = "Cloudy Park 4 - Star 17" +cloudy_park_4_s18 = "Cloudy Park 4 - Star 18" +cloudy_park_4_s19 = "Cloudy Park 4 - Star 19" +cloudy_park_4_s20 = "Cloudy Park 4 - Star 20" +cloudy_park_4_s21 = "Cloudy Park 4 - Star 21" +cloudy_park_4_s22 = "Cloudy Park 4 - Star 22" +cloudy_park_4_s23 = "Cloudy Park 4 - Star 23" +cloudy_park_4_s24 = "Cloudy Park 4 - Star 24" +cloudy_park_4_s25 = "Cloudy Park 4 - Star 25" +cloudy_park_4_s26 = "Cloudy Park 4 - Star 26" +cloudy_park_4_s27 = "Cloudy Park 4 - Star 27" +cloudy_park_4_s28 = "Cloudy Park 4 - Star 28" +cloudy_park_4_s29 = "Cloudy Park 4 - Star 29" +cloudy_park_4_s30 = "Cloudy Park 4 - Star 30" +cloudy_park_4_s31 = "Cloudy Park 4 - Star 31" +cloudy_park_4_s32 = "Cloudy Park 4 - Star 32" +cloudy_park_4_s33 = "Cloudy Park 4 - Star 33" +cloudy_park_4_s34 = "Cloudy Park 4 - Star 34" +cloudy_park_4_s35 = "Cloudy Park 4 - Star 35" +cloudy_park_4_s36 = "Cloudy Park 4 - Star 36" +cloudy_park_4_s37 = "Cloudy Park 4 - Star 37" +cloudy_park_4_s38 = "Cloudy Park 4 - Star 38" +cloudy_park_4_s39 = "Cloudy Park 4 - Star 39" +cloudy_park_4_s40 = "Cloudy Park 4 - Star 40" +cloudy_park_4_s41 = "Cloudy Park 4 - Star 41" +cloudy_park_4_s42 = "Cloudy Park 4 - Star 42" +cloudy_park_4_s43 = "Cloudy Park 4 - Star 43" +cloudy_park_4_s44 = "Cloudy Park 4 - Star 44" +cloudy_park_4_s45 = "Cloudy Park 4 - Star 45" +cloudy_park_4_s46 = "Cloudy Park 4 - Star 46" +cloudy_park_4_s47 = "Cloudy Park 4 - Star 47" +cloudy_park_4_s48 = "Cloudy Park 4 - Star 48" +cloudy_park_4_s49 = "Cloudy Park 4 - Star 49" +cloudy_park_4_s50 = "Cloudy Park 4 - Star 50" +cloudy_park_5_s1 = "Cloudy Park 5 - Star 1" +cloudy_park_5_s2 = "Cloudy Park 5 - Star 2" +cloudy_park_5_s3 = "Cloudy Park 5 - Star 3" +cloudy_park_5_s4 = "Cloudy Park 5 - Star 4" +cloudy_park_5_s5 = "Cloudy Park 5 - Star 5" +cloudy_park_5_s6 = "Cloudy Park 5 - Star 6" +cloudy_park_6_s1 = "Cloudy Park 6 - Star 1" +cloudy_park_6_s2 = "Cloudy Park 6 - Star 2" +cloudy_park_6_s3 = "Cloudy Park 6 - Star 3" +cloudy_park_6_s4 = "Cloudy Park 6 - Star 4" +cloudy_park_6_s5 = "Cloudy Park 6 - Star 5" +cloudy_park_6_s6 = "Cloudy Park 6 - Star 6" +cloudy_park_6_s7 = "Cloudy Park 6 - Star 7" +cloudy_park_6_s8 = "Cloudy Park 6 - Star 8" +cloudy_park_6_s9 = "Cloudy Park 6 - Star 9" +cloudy_park_6_s10 = "Cloudy Park 6 - Star 10" +cloudy_park_6_s11 = "Cloudy Park 6 - Star 11" +cloudy_park_6_s12 = "Cloudy Park 6 - Star 12" +cloudy_park_6_s13 = "Cloudy Park 6 - Star 13" +cloudy_park_6_s14 = "Cloudy Park 6 - Star 14" +cloudy_park_6_s15 = "Cloudy Park 6 - Star 15" +cloudy_park_6_s16 = "Cloudy Park 6 - Star 16" +cloudy_park_6_s17 = "Cloudy Park 6 - Star 17" +cloudy_park_6_s18 = "Cloudy Park 6 - Star 18" +cloudy_park_6_s19 = "Cloudy Park 6 - Star 19" +cloudy_park_6_s20 = "Cloudy Park 6 - Star 20" +cloudy_park_6_s21 = "Cloudy Park 6 - Star 21" +cloudy_park_6_s22 = "Cloudy Park 6 - Star 22" +cloudy_park_6_s23 = "Cloudy Park 6 - Star 23" +cloudy_park_6_s24 = "Cloudy Park 6 - Star 24" +cloudy_park_6_s25 = "Cloudy Park 6 - Star 25" +cloudy_park_6_s26 = "Cloudy Park 6 - Star 26" +cloudy_park_6_s27 = "Cloudy Park 6 - Star 27" +cloudy_park_6_s28 = "Cloudy Park 6 - Star 28" +cloudy_park_6_s29 = "Cloudy Park 6 - Star 29" +cloudy_park_6_s30 = "Cloudy Park 6 - Star 30" +cloudy_park_6_s31 = "Cloudy Park 6 - Star 31" +cloudy_park_6_s32 = "Cloudy Park 6 - Star 32" +cloudy_park_6_s33 = "Cloudy Park 6 - Star 33" +iceberg_1_s1 = "Iceberg 1 - Star 1" +iceberg_1_s2 = "Iceberg 1 - Star 2" +iceberg_1_s3 = "Iceberg 1 - Star 3" +iceberg_1_s4 = "Iceberg 1 - Star 4" +iceberg_1_s5 = "Iceberg 1 - Star 5" +iceberg_1_s6 = "Iceberg 1 - Star 6" +iceberg_2_s1 = "Iceberg 2 - Star 1" +iceberg_2_s2 = "Iceberg 2 - Star 2" +iceberg_2_s3 = "Iceberg 2 - Star 3" +iceberg_2_s4 = "Iceberg 2 - Star 4" +iceberg_2_s5 = "Iceberg 2 - Star 5" +iceberg_2_s6 = "Iceberg 2 - Star 6" +iceberg_2_s7 = "Iceberg 2 - Star 7" +iceberg_2_s8 = "Iceberg 2 - Star 8" +iceberg_2_s9 = "Iceberg 2 - Star 9" +iceberg_2_s10 = "Iceberg 2 - Star 10" +iceberg_2_s11 = "Iceberg 2 - Star 11" +iceberg_2_s12 = "Iceberg 2 - Star 12" +iceberg_2_s13 = "Iceberg 2 - Star 13" +iceberg_2_s14 = "Iceberg 2 - Star 14" +iceberg_2_s15 = "Iceberg 2 - Star 15" +iceberg_2_s16 = "Iceberg 2 - Star 16" +iceberg_2_s17 = "Iceberg 2 - Star 17" +iceberg_2_s18 = "Iceberg 2 - Star 18" +iceberg_2_s19 = "Iceberg 2 - Star 19" +iceberg_3_s1 = "Iceberg 3 - Star 1" +iceberg_3_s2 = "Iceberg 3 - Star 2" +iceberg_3_s3 = "Iceberg 3 - Star 3" +iceberg_3_s4 = "Iceberg 3 - Star 4" +iceberg_3_s5 = "Iceberg 3 - Star 5" +iceberg_3_s6 = "Iceberg 3 - Star 6" +iceberg_3_s7 = "Iceberg 3 - Star 7" +iceberg_3_s8 = "Iceberg 3 - Star 8" +iceberg_3_s9 = "Iceberg 3 - Star 9" +iceberg_3_s10 = "Iceberg 3 - Star 10" +iceberg_3_s11 = "Iceberg 3 - Star 11" +iceberg_3_s12 = "Iceberg 3 - Star 12" +iceberg_3_s13 = "Iceberg 3 - Star 13" +iceberg_3_s14 = "Iceberg 3 - Star 14" +iceberg_3_s15 = "Iceberg 3 - Star 15" +iceberg_3_s16 = "Iceberg 3 - Star 16" +iceberg_3_s17 = "Iceberg 3 - Star 17" +iceberg_3_s18 = "Iceberg 3 - Star 18" +iceberg_3_s19 = "Iceberg 3 - Star 19" +iceberg_3_s20 = "Iceberg 3 - Star 20" +iceberg_3_s21 = "Iceberg 3 - Star 21" +iceberg_4_s1 = "Iceberg 4 - Star 1" +iceberg_4_s2 = "Iceberg 4 - Star 2" +iceberg_4_s3 = "Iceberg 4 - Star 3" +iceberg_5_s1 = "Iceberg 5 - Star 1" +iceberg_5_s2 = "Iceberg 5 - Star 2" +iceberg_5_s3 = "Iceberg 5 - Star 3" +iceberg_5_s4 = "Iceberg 5 - Star 4" +iceberg_5_s5 = "Iceberg 5 - Star 5" +iceberg_5_s6 = "Iceberg 5 - Star 6" +iceberg_5_s7 = "Iceberg 5 - Star 7" +iceberg_5_s8 = "Iceberg 5 - Star 8" +iceberg_5_s9 = "Iceberg 5 - Star 9" +iceberg_5_s10 = "Iceberg 5 - Star 10" +iceberg_5_s11 = "Iceberg 5 - Star 11" +iceberg_5_s12 = "Iceberg 5 - Star 12" +iceberg_5_s13 = "Iceberg 5 - Star 13" +iceberg_5_s14 = "Iceberg 5 - Star 14" +iceberg_5_s15 = "Iceberg 5 - Star 15" +iceberg_5_s16 = "Iceberg 5 - Star 16" +iceberg_5_s17 = "Iceberg 5 - Star 17" +iceberg_5_s18 = "Iceberg 5 - Star 18" +iceberg_5_s19 = "Iceberg 5 - Star 19" +iceberg_5_s20 = "Iceberg 5 - Star 20" +iceberg_5_s21 = "Iceberg 5 - Star 21" +iceberg_5_s22 = "Iceberg 5 - Star 22" +iceberg_5_s23 = "Iceberg 5 - Star 23" +iceberg_5_s24 = "Iceberg 5 - Star 24" +iceberg_5_s25 = "Iceberg 5 - Star 25" +iceberg_5_s26 = "Iceberg 5 - Star 26" +iceberg_5_s27 = "Iceberg 5 - Star 27" +iceberg_5_s28 = "Iceberg 5 - Star 28" +iceberg_5_s29 = "Iceberg 5 - Star 29" +iceberg_5_s30 = "Iceberg 5 - Star 30" +iceberg_5_s31 = "Iceberg 5 - Star 31" +iceberg_5_s32 = "Iceberg 5 - Star 32" +iceberg_5_s33 = "Iceberg 5 - Star 33" +iceberg_5_s34 = "Iceberg 5 - Star 34" +iceberg_6_s1 = "Iceberg 6 - Star 1" diff --git a/worlds/kdl3/Names/__init__.py b/worlds/kdl3/Names/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/worlds/kdl3/Options.py b/worlds/kdl3/Options.py new file mode 100644 index 0000000000..336bd33bc5 --- /dev/null +++ b/worlds/kdl3/Options.py @@ -0,0 +1,432 @@ +import random +from dataclasses import dataclass + +from Options import DeathLink, Choice, Toggle, OptionDict, Range, PlandoBosses, DefaultOnToggle, \ + PerGameCommonOptions +from .Names import LocationName + + +class Goal(Choice): + """ + Zero: collect the Heart Stars, and defeat Zero in the Hyper Zone. + Boss Butch: collect the Heart Stars, and then complete the boss rematches in the Boss Butch mode. + MG5: collect the Heart Stars, and then complete a perfect run through the minigame gauntlet within the Super MG5 + Jumping: collect the Heart Stars, and then reach a designated score within the Jumping sub-game + """ + display_name = "Goal" + option_zero = 0 + option_boss_butch = 1 + option_MG5 = 2 + option_jumping = 3 + default = 0 + + @classmethod + def get_option_name(cls, value: int) -> str: + if value == 2: + return cls.name_lookup[value].upper() + return super().get_option_name(value) + +class GoalSpeed(Choice): + """ + Normal: the goal is unlocked after purifying the five bosses + Fast: the goal is unlocked after acquiring the target number of Heart Stars + """ + display_name = "Goal Speed" + option_normal = 0 + option_fast = 1 + + +class TotalHeartStars(Range): + """ + Maximum number of heart stars to include in the pool of items. + """ + display_name = "Max Heart Stars" + range_start = 5 # set to 5 so strict bosses does not degrade + range_end = 50 # 30 default locations + 30 stage clears + 5 bosses - 14 progression items = 51, so round down + default = 30 + + +class HeartStarsRequired(Range): + """ + Percentage of heart stars required to purify the five bosses and reach Zero. + Each boss will require a differing amount of heart stars to purify. + """ + display_name = "Required Heart Stars" + range_start = 1 + range_end = 100 + default = 50 + + +class LevelShuffle(Choice): + """ + None: No stage shuffling. + Same World: shuffles stages around their world. + Pattern: shuffles stages according to the stage pattern (stage 3 will always be a minigame stage, etc.) + Shuffled: shuffles stages across all worlds. + """ + display_name = "Stage Shuffle" + option_none = 0 + option_same_world = 1 + option_pattern = 2 + option_shuffled = 3 + default = 0 + + +class BossShuffle(PlandoBosses): + """ + None: Bosses will remain in their vanilla locations + Shuffled: Bosses will be shuffled amongst each other + Full: Bosses will be randomized + Singularity: All (non-Zero) bosses will be replaced with a single boss + Supports plando placement. + """ + bosses = frozenset(LocationName.boss_names.keys()) + + locations = frozenset(LocationName.level_names.keys()) + + duplicate_bosses = True + + @classmethod + def can_place_boss(cls, boss: str, location: str) -> bool: + # Kirby has no logic about requiring bosses in specific locations (since we load in their stage) + return True + + display_name = "Boss Shuffle" + option_none = 0 + option_shuffled = 1 + option_full = 2 + option_singularity = 3 + + +class BossShuffleAllowBB(Choice): + """ + Allow Boss Butch variants of bosses in Boss Shuffle. + Enabled: any boss placed will have a 50% chance of being the Boss Butch variant, including bosses not present + Enforced: all bosses will be their Boss Butch variant. + Boss Butch boss changes are only visual. + """ + display_name = "Allow Boss Butch Bosses" + option_disabled = 0 + option_enabled = 1 + option_enforced = 2 + default = 0 + + +class AnimalRandomization(Choice): + """ + Disabled: all animal positions will be vanilla. + Shuffled: all animal positions will be shuffled amongst each other. + Full: random animals will be placed across the levels. At least one of each animal is guaranteed. + """ + display_name = "Animal Randomization" + option_disabled = 0 + option_shuffled = 1 + option_full = 2 + default = 0 + + +class CopyAbilityRandomization(Choice): + """ + Disabled: enemies give regular copy abilities and health. + Enabled: all enemies will have the copy ability received from them randomized. + Enabled Plus Minus: enemies (except minibosses) can additionally give you anywhere from +2 health to -1 health when eaten. + """ + display_name = "Copy Ability Randomization" + option_disabled = 0 + option_enabled = 1 + option_enabled_plus_minus = 2 + + +class StrictBosses(DefaultOnToggle): + """ + If enabled, one will not be able to move onto the next world until the previous world's boss has been purified. + """ + display_name = "Strict Bosses" + + +class OpenWorld(DefaultOnToggle): + """ + If enabled, all 6 stages will be unlocked upon entering a world for the first time. A certain amount of stages + will need to be completed in order to unlock the bosses + """ + display_name = "Open World" + + +class OpenWorldBossRequirement(Range): + """ + The amount of stages completed needed to unlock the boss of a world when Open World is turned on. + """ + display_name = "Open World Boss Requirement" + range_start = 1 + range_end = 6 + default = 3 + + +class BossRequirementRandom(Toggle): + """ + If enabled, boss purification will require a random amount of Heart Stars. Depending on options, this may have + boss purification unlock in a random order. + """ + display_name = "Randomize Purification Requirement" + + +class JumpingTarget(Range): + """ + The required score needed to complete the Jumping minigame. + """ + display_name = "Jumping Target Score" + range_start = 1 + range_end = 25 + default = 10 + + +class GameLanguage(Choice): + """ + The language that the game should display. This does not have to match the given rom. + """ + display_name = "Game Language" + option_japanese = 0 + option_english = 1 + default = 1 + + +class FillerPercentage(Range): + """ + Percentage of non-required Heart Stars to be converted to filler items (1-Ups, Maxim Tomatoes, Invincibility Candy). + """ + display_name = "Filler Percentage" + range_start = 0 + range_end = 100 + default = 50 + + +class TrapPercentage(Range): + """ + Percentage of filler items to be converted to trap items (Gooey Bags, Slowness, Eject Ability). + """ + display_name = "Trap Percentage" + range_start = 0 + range_end = 100 + default = 50 + + +class GooeyTrapPercentage(Range): + """ + Chance that any given trap is a Gooey Bag (spawns Gooey when you receive it). + """ + display_name = "Gooey Trap Percentage" + range_start = 0 + range_end = 100 + default = 50 + + +class SlowTrapPercentage(Range): + """ + Chance that any given trap is Slowness (halves your max speed for 15 seconds when you receive it). + """ + display_name = "Slowness Trap Percentage" + range_start = 0 + range_end = 100 + default = 50 + + +class AbilityTrapPercentage(Range): + """ + Chance that any given trap is an Eject Ability (ejects your ability when you receive it). + """ + display_name = "Ability Trap Percentage" + range_start = 0 + range_end = 100 + default = 50 + + +class ConsumableChecks(Toggle): + """ + When enabled, adds all 1-Ups and Maxim Tomatoes as possible locations. + """ + display_name = "Consumable-sanity" + + +class StarChecks(Toggle): + """ + When enabled, every star in a given stage will become a check. + Will increase the possible filler pool to include 1/3/5 stars. + """ + display_name = "Starsanity" + + +class KirbyFlavorPreset(Choice): + """ + The color of Kirby, from a list of presets. + """ + display_name = "Kirby Flavor" + option_default = 0 + option_bubblegum = 1 + option_cherry = 2 + option_blueberry = 3 + option_lemon = 4 + option_kiwi = 5 + option_grape = 6 + option_chocolate = 7 + option_marshmallow = 8 + option_licorice = 9 + option_watermelon = 10 + option_orange = 11 + option_lime = 12 + option_lavender = 13 + option_custom = 14 + default = 0 + + @classmethod + def from_text(cls, text: str) -> Choice: + text = text.lower() + if text == "random": + choice_list = list(cls.name_lookup) + choice_list.remove(14) + return cls(random.choice(choice_list)) + return super().from_text(text) + + +class KirbyFlavor(OptionDict): + """ + A custom color for Kirby. To use a custom color, set the preset to Custom and then define a dict of keys from "1" to + "15", with their values being an HTML hex color. + """ + default = { + "1": "B01810", + "2": "F0E0E8", + "3": "C8A0A8", + "4": "A87070", + "5": "E02018", + "6": "F0A0B8", + "7": "D07880", + "8": "A85048", + "9": "E8D0D0", + "10": "E85048", + "11": "D0C0C0", + "12": "B08888", + "13": "E87880", + "14": "F8F8F8", + "15": "B03830", + } + + +class GooeyFlavorPreset(Choice): + """ + The color of Gooey, from a list of presets. + """ + display_name = "Gooey Flavor" + option_default = 0 + option_bubblegum = 1 + option_cherry = 2 + option_blueberry = 3 + option_lemon = 4 + option_kiwi = 5 + option_grape = 6 + option_chocolate = 7 + option_marshmallow = 8 + option_licorice = 9 + option_watermelon = 10 + option_orange = 11 + option_lime = 12 + option_lavender = 13 + option_custom = 14 + default = 0 + + @classmethod + def from_text(cls, text: str) -> Choice: + text = text.lower() + if text == "random": + choice_list = list(cls.name_lookup) + choice_list.remove(14) + return cls(random.choice(choice_list)) + return super().from_text(text) + + +class GooeyFlavor(OptionDict): + """ + A custom color for Gooey. To use a custom color, set the preset to Custom and then define a dict of keys from "1" to + "15", with their values being an HTML hex color. + """ + default = { + "1": "000808", + "2": "102838", + "3": "183048", + "4": "183878", + "5": "1838A0", + "6": "B01810", + "7": "E85048", + "8": "D0C0C0", + "9": "F8F8F8", + } + + +class MusicShuffle(Choice): + """ + None: default music will play + Shuffled: music will be shuffled amongst each other + Full: random music will play in each room + Note that certain songs will not be chosen in shuffled or full + """ + display_name = "Music Randomization" + option_none = 0 + option_shuffled = 1 + option_full = 2 + default = 0 + + +class VirtualConsoleChanges(Choice): + """ + Adds the ability to enable 2 of the Virtual Console changes. + Flash Reduction: reduces the flashing during the Zero battle. + Color Changes: changes the color of the background within the Zero Boss Butch rematch. + """ + display_name = "Virtual Console Changes" + option_none = 0 + option_flash_reduction = 1 + option_color_changes = 2 + option_both = 3 + default = 1 + + +class Gifting(Toggle): + """ + When enabled, the goal game item will be sent to other compatible games as a gift, + and you can receive gifts from other players. This can be enabled during gameplay + using the client. + """ + display_name = "Gifting" + + +@dataclass +class KDL3Options(PerGameCommonOptions): + death_link: DeathLink + game_language: GameLanguage + goal: Goal + goal_speed: GoalSpeed + total_heart_stars: TotalHeartStars + heart_stars_required: HeartStarsRequired + filler_percentage: FillerPercentage + trap_percentage: TrapPercentage + gooey_trap_weight: GooeyTrapPercentage + slow_trap_weight: SlowTrapPercentage + ability_trap_weight: AbilityTrapPercentage + jumping_target: JumpingTarget + stage_shuffle: LevelShuffle + boss_shuffle: BossShuffle + allow_bb: BossShuffleAllowBB + animal_randomization: AnimalRandomization + copy_ability_randomization: CopyAbilityRandomization + strict_bosses: StrictBosses + open_world: OpenWorld + ow_boss_requirement: OpenWorldBossRequirement + boss_requirement_random: BossRequirementRandom + consumables: ConsumableChecks + starsanity: StarChecks + gifting: Gifting + kirby_flavor_preset: KirbyFlavorPreset + kirby_flavor: KirbyFlavor + gooey_flavor_preset: GooeyFlavorPreset + gooey_flavor: GooeyFlavor + music_shuffle: MusicShuffle + virtual_console: VirtualConsoleChanges diff --git a/worlds/kdl3/Presets.py b/worlds/kdl3/Presets.py new file mode 100644 index 0000000000..d3a7146ded --- /dev/null +++ b/worlds/kdl3/Presets.py @@ -0,0 +1,56 @@ +from typing import Dict, Any + +all_random = { + "progression_balancing": "random", + "accessibility": "random", + "death_link": "random", + "game_language": "random", + "goal": "random", + "goal_speed": "random", + "total_heart_stars": "random", + "heart_stars_required": "random", + "filler_percentage": "random", + "trap_percentage": "random", + "gooey_trap_weight": "random", + "slow_trap_weight": "random", + "ability_trap_weight": "random", + "jumping_target": "random", + "stage_shuffle": "random", + "boss_shuffle": "random", + "allow_bb": "random", + "animal_randomization": "random", + "copy_ability_randomization": "random", + "strict_bosses": "random", + "open_world": "random", + "ow_boss_requirement": "random", + "boss_requirement_random": "random", + "consumables": "random", + "kirby_flavor_preset": "random", + "gooey_flavor_preset": "random", + "music_shuffle": "random", +} + +beginner = { + "goal": "zero", + "goal_speed": "normal", + "total_heart_stars": 50, + "heart_stars_required": 30, + "filler_percentage": 25, + "trap_percentage": 0, + "gooey_trap_weight": "random", + "slow_trap_weight": "random", + "ability_trap_weight": "random", + "jumping_target": 5, + "stage_shuffle": "pattern", + "boss_shuffle": "shuffled", + "allow_bb": "random", + "strict_bosses": True, + "open_world": True, + "ow_boss_requirement": 3, +} + + +kdl3_options_presets: Dict[str, Dict[str, Any]] = { + "All Random": all_random, + "Beginner": beginner, +} diff --git a/worlds/kdl3/Regions.py b/worlds/kdl3/Regions.py new file mode 100644 index 0000000000..ed0d865866 --- /dev/null +++ b/worlds/kdl3/Regions.py @@ -0,0 +1,231 @@ +import orjson +import os +import typing +from pkgutil import get_data + +from BaseClasses import Region +from worlds.generic.Rules import add_item_rule +from .Locations import KDL3Location +from .Names import LocationName +from .Options import BossShuffle +from .Room import KDL3Room + +if typing.TYPE_CHECKING: + from . import KDL3World + +default_levels = { + 1: [0x770001, 0x770002, 0x770003, 0x770004, 0x770005, 0x770006, 0x770200], + 2: [0x770007, 0x770008, 0x770009, 0x77000A, 0x77000B, 0x77000C, 0x770201], + 3: [0x77000D, 0x77000E, 0x77000F, 0x770010, 0x770011, 0x770012, 0x770202], + 4: [0x770013, 0x770014, 0x770015, 0x770016, 0x770017, 0x770018, 0x770203], + 5: [0x770019, 0x77001A, 0x77001B, 0x77001C, 0x77001D, 0x77001E, 0x770204], +} + +first_stage_blacklist = { + # We want to confirm that the first stage can be completed without any items + 0x77000B, # 2-5 needs Kine + 0x770011, # 3-5 needs Cutter + 0x77001C, # 5-4 needs Burning +} + + +def generate_valid_level(level, stage, possible_stages, slot_random): + new_stage = slot_random.choice(possible_stages) + if level == 1 and stage == 0 and new_stage in first_stage_blacklist: + return generate_valid_level(level, stage, possible_stages, slot_random) + else: + return new_stage + + +def generate_rooms(world: "KDL3World", door_shuffle: bool, level_regions: typing.Dict[int, Region]): + level_names = {LocationName.level_names[level]: level for level in LocationName.level_names} + room_data = orjson.loads(get_data(__name__, os.path.join("data", "Rooms.json"))) + rooms: typing.Dict[str, KDL3Room] = dict() + for room_entry in room_data: + room = KDL3Room(room_entry["name"], world.player, world.multiworld, None, room_entry["level"], + room_entry["stage"], room_entry["room"], room_entry["pointer"], room_entry["music"], + room_entry["default_exits"], room_entry["animal_pointers"], room_entry["enemies"], + room_entry["entity_load"], room_entry["consumables"], room_entry["consumables_pointer"]) + room.add_locations({location: world.location_name_to_id[location] if location in world.location_name_to_id else + None for location in room_entry["locations"] + if (not any(x in location for x in ["1-Up", "Maxim"]) or + world.options.consumables.value) and ("Star" not in location + or world.options.starsanity.value)}, + KDL3Location) + rooms[room.name] = room + for location in room.locations: + if "Animal" in location.name: + add_item_rule(location, lambda item: item.name in { + "Rick Spawn", "Kine Spawn", "Coo Spawn", "Nago Spawn", "ChuChu Spawn", "Pitch Spawn" + }) + world.rooms = list(rooms.values()) + world.multiworld.regions.extend(world.rooms) + + first_rooms: typing.Dict[int, KDL3Room] = dict() + if door_shuffle: + # first, we need to generate the notable edge cases + # 5-6 is the first, being the most restrictive + # half of its rooms are required to be vanilla, but can be in different orders + # the room before it *must* contain the copy ability required to unlock the room's goal + + raise NotImplementedError() + else: + for name, room in rooms.items(): + if room.room == 0: + if room.stage == 7: + first_rooms[0x770200 + room.level - 1] = room + else: + first_rooms[0x770000 + ((room.level - 1) * 6) + room.stage] = room + exits = dict() + for def_exit in room.default_exits: + target = f"{level_names[room.level]} {room.stage} - {def_exit['room']}" + access_rule = tuple(def_exit["access_rule"]) + exits[target] = lambda state, rule=access_rule: state.has_all(rule, world.player) + room.add_exits( + exits.keys(), + exits + ) + if world.options.open_world: + if any("Complete" in location.name for location in room.locations): + room.add_locations({f"{level_names[room.level]} {room.stage} - Stage Completion": None}, + KDL3Location) + + for level in world.player_levels: + for stage in range(6): + proper_stage = world.player_levels[level][stage] + stage_name = world.multiworld.get_location(world.location_id_to_name[proper_stage], + world.player).name.replace(" - Complete", "") + stage_regions = [rooms[room] for room in rooms if stage_name in rooms[room].name] + for region in stage_regions: + region.level = level + region.stage = stage + if world.options.open_world or stage == 0: + level_regions[level].add_exits([first_rooms[proper_stage].name]) + else: + world.multiworld.get_location(world.location_id_to_name[world.player_levels[level][stage-1]], + world.player).parent_region.add_exits([first_rooms[proper_stage].name]) + level_regions[level].add_exits([first_rooms[0x770200 + level - 1].name]) + + +def generate_valid_levels(world: "KDL3World", enforce_world: bool, enforce_pattern: bool) -> dict: + levels: typing.Dict[int, typing.List[typing.Optional[int]]] = { + 1: [None] * 7, + 2: [None] * 7, + 3: [None] * 7, + 4: [None] * 7, + 5: [None] * 7, + } + + possible_stages = [default_levels[level][stage] for level in default_levels for stage in range(6)] + if world.multiworld.plando_connections[world.player]: + for connection in world.multiworld.plando_connections[world.player]: + try: + entrance_world, entrance_stage = connection.entrance.rsplit(" ", 1) + stage_world, stage_stage = connection.exit.rsplit(" ", 1) + new_stage = default_levels[LocationName.level_names[stage_world.strip()]][int(stage_stage) - 1] + levels[LocationName.level_names[entrance_world.strip()]][int(entrance_stage) - 1] = new_stage + possible_stages.remove(new_stage) + + except Exception: + raise Exception( + f"Invalid connection: {connection.entrance} =>" + f" {connection.exit} for player {world.player} ({world.player_name})") + + for level in range(1, 6): + for stage in range(6): + # Randomize bosses separately + try: + if levels[level][stage] is None: + stage_candidates = [candidate for candidate in possible_stages + if (enforce_world and candidate in default_levels[level]) + or (enforce_pattern and ((candidate - 1) & 0x00FFFF) % 6 == stage) + or (enforce_pattern == enforce_world) + ] + new_stage = generate_valid_level(level, stage, stage_candidates, + world.random) + possible_stages.remove(new_stage) + levels[level][stage] = new_stage + except Exception: + raise Exception(f"Failed to find valid stage for {level}-{stage}. Remaining Stages:{possible_stages}") + + # now handle bosses + boss_shuffle: typing.Union[int, str] = world.options.boss_shuffle.value + plando_bosses = [] + if isinstance(boss_shuffle, str): + # boss plando + options = boss_shuffle.split(";") + boss_shuffle = BossShuffle.options[options.pop()] + for option in options: + if "-" in option: + loc, boss = option.split("-") + loc = loc.title() + boss = boss.title() + levels[LocationName.level_names[loc]][6] = LocationName.boss_names[boss] + plando_bosses.append(LocationName.boss_names[boss]) + else: + option = option.title() + for level in levels: + if levels[level][6] is None: + levels[level][6] = LocationName.boss_names[option] + plando_bosses.append(LocationName.boss_names[option]) + + if boss_shuffle > 0: + if boss_shuffle == BossShuffle.option_full: + possible_bosses = [default_levels[world.random.randint(1, 5)][6] + for _ in range(5 - len(plando_bosses))] + elif boss_shuffle == BossShuffle.option_singularity: + boss = world.random.randint(1, 5) + possible_bosses = [default_levels[boss][6] for _ in range(5 - len(plando_bosses))] + else: + possible_bosses = [default_levels[level][6] for level in default_levels + if default_levels[level][6] not in plando_bosses] + for level in levels: + if levels[level][6] is None: + boss = world.random.choice(possible_bosses) + levels[level][6] = boss + possible_bosses.remove(boss) + else: + for level in levels: + if levels[level][6] is None: + levels[level][6] = default_levels[level][6] + + for level in levels: + for stage in range(7): + assert levels[level][stage] is not None, "Level tried to be sent with a None stage, incorrect plando?" + + return levels + + +def create_levels(world: "KDL3World") -> None: + menu = Region("Menu", world.player, world.multiworld) + level1 = Region("Grass Land", world.player, world.multiworld) + level2 = Region("Ripple Field", world.player, world.multiworld) + level3 = Region("Sand Canyon", world.player, world.multiworld) + level4 = Region("Cloudy Park", world.player, world.multiworld) + level5 = Region("Iceberg", world.player, world.multiworld) + level6 = Region("Hyper Zone", world.player, world.multiworld) + levels = { + 1: level1, + 2: level2, + 3: level3, + 4: level4, + 5: level5, + } + level_shuffle = world.options.stage_shuffle.value + if level_shuffle != 0: + world.player_levels = generate_valid_levels( + world, + level_shuffle == 1, + level_shuffle == 2) + + generate_rooms(world, False, levels) + + level6.add_locations({LocationName.goals[world.options.goal]: None}, KDL3Location) + + menu.connect(level1, "Start Game") + level1.connect(level2, "To Level 2") + level2.connect(level3, "To Level 3") + level3.connect(level4, "To Level 4") + level4.connect(level5, "To Level 5") + menu.connect(level6, "To Level 6") # put the connection on menu, since you can reach it before level 5 on fast goal + world.multiworld.regions += [menu, level1, level2, level3, level4, level5, level6] diff --git a/worlds/kdl3/Rom.py b/worlds/kdl3/Rom.py new file mode 100644 index 0000000000..5a846ab8be --- /dev/null +++ b/worlds/kdl3/Rom.py @@ -0,0 +1,577 @@ +import typing +from pkgutil import get_data + +import Utils +from typing import Optional, TYPE_CHECKING +import hashlib +import os +import struct + +import settings +from worlds.Files import APDeltaPatch +from .Aesthetics import get_palette_bytes, kirby_target_palettes, get_kirby_palette, gooey_target_palettes, \ + get_gooey_palette +from .Compression import hal_decompress +import bsdiff4 + +if TYPE_CHECKING: + from . import KDL3World + +KDL3UHASH = "201e7658f6194458a3869dde36bf8ec2" +KDL3JHASH = "b2f2d004ea640c3db66df958fce122b2" + +level_pointers = { + 0x770001: 0x0084, + 0x770002: 0x009C, + 0x770003: 0x00B8, + 0x770004: 0x00D8, + 0x770005: 0x0104, + 0x770006: 0x0124, + 0x770007: 0x014C, + 0x770008: 0x0170, + 0x770009: 0x0190, + 0x77000A: 0x01B0, + 0x77000B: 0x01E8, + 0x77000C: 0x0218, + 0x77000D: 0x024C, + 0x77000E: 0x0270, + 0x77000F: 0x02A0, + 0x770010: 0x02C4, + 0x770011: 0x02EC, + 0x770012: 0x0314, + 0x770013: 0x03CC, + 0x770014: 0x0404, + 0x770015: 0x042C, + 0x770016: 0x044C, + 0x770017: 0x0478, + 0x770018: 0x049C, + 0x770019: 0x04E4, + 0x77001A: 0x0504, + 0x77001B: 0x0530, + 0x77001C: 0x0554, + 0x77001D: 0x05A8, + 0x77001E: 0x0640, + 0x770200: 0x0148, + 0x770201: 0x0248, + 0x770202: 0x03C8, + 0x770203: 0x04E0, + 0x770204: 0x06A4, + 0x770205: 0x06A8, +} + +bb_bosses = { + 0x770200: 0xED85F1, + 0x770201: 0xF01360, + 0x770202: 0xEDA3DF, + 0x770203: 0xEDC2B9, + 0x770204: 0xED7C3F, + 0x770205: 0xEC29D2, +} + +level_sprites = { + 0x19B2C6: 1827, + 0x1A195C: 1584, + 0x19F6F3: 1679, + 0x19DC8B: 1717, + 0x197900: 1872 +} + +stage_tiles = { + 0: [ + 0, 1, 2, + 16, 17, 18, + 32, 33, 34, + 48, 49, 50 + ], + 1: [ + 3, 4, 5, + 19, 20, 21, + 35, 36, 37, + 51, 52, 53 + ], + 2: [ + 6, 7, 8, + 22, 23, 24, + 38, 39, 40, + 54, 55, 56 + ], + 3: [ + 9, 10, 11, + 25, 26, 27, + 41, 42, 43, + 57, 58, 59, + ], + 4: [ + 12, 13, 64, + 28, 29, 65, + 44, 45, 66, + 60, 61, 67 + ], + 5: [ + 14, 15, 68, + 30, 31, 69, + 46, 47, 70, + 62, 63, 71 + ] +} + +heart_star_address = 0x2D0000 +heart_star_size = 456 +consumable_address = 0x2F91DD +consumable_size = 698 + +stage_palettes = [0x60964, 0x60B64, 0x60D64, 0x60F64, 0x61164] + +music_choices = [ + 2, # Boss 1 + 3, # Boss 2 (Unused) + 4, # Boss 3 (Miniboss) + 7, # Dedede + 9, # Event 2 (used once) + 10, # Field 1 + 11, # Field 2 + 12, # Field 3 + 13, # Field 4 + 14, # Field 5 + 15, # Field 6 + 16, # Field 7 + 17, # Field 8 + 18, # Field 9 + 19, # Field 10 + 20, # Field 11 + 21, # Field 12 (Gourmet Race) + 23, # Dark Matter in the Hyper Zone + 24, # Zero + 25, # Level 1 + 26, # Level 2 + 27, # Level 4 + 28, # Level 3 + 29, # Heart Star Failed + 30, # Level 5 + 31, # Minigame + 38, # Animal Friend 1 + 39, # Animal Friend 2 + 40, # Animal Friend 3 +] +# extra room pointers we don't want to track other than for music +room_pointers = [ + 3079990, # Zero + 2983409, # BB Whispy + 3150688, # BB Acro + 2991071, # BB PonCon + 2998969, # BB Ado + 2980927, # BB Dedede + 2894290 # BB Zero +] + +enemy_remap = { + "Waddle Dee": 0, + "Bronto Burt": 2, + "Rocky": 3, + "Bobo": 5, + "Chilly": 6, + "Poppy Bros Jr.": 7, + "Sparky": 8, + "Polof": 9, + "Broom Hatter": 11, + "Cappy": 12, + "Bouncy": 13, + "Nruff": 15, + "Glunk": 16, + "Togezo": 18, + "Kabu": 19, + "Mony": 20, + "Blipper": 21, + "Squishy": 22, + "Gabon": 24, + "Oro": 25, + "Galbo": 26, + "Sir Kibble": 27, + "Nidoo": 28, + "Kany": 29, + "Sasuke": 30, + "Yaban": 32, + "Boten": 33, + "Coconut": 34, + "Doka": 35, + "Icicle": 36, + "Pteran": 39, + "Loud": 40, + "Como": 41, + "Klinko": 42, + "Babut": 43, + "Wappa": 44, + "Mariel": 45, + "Tick": 48, + "Apolo": 49, + "Popon Ball": 50, + "KeKe": 51, + "Magoo": 53, + "Raft Waddle Dee": 57, + "Madoo": 58, + "Corori": 60, + "Kapar": 67, + "Batamon": 68, + "Peran": 72, + "Bobin": 73, + "Mopoo": 74, + "Gansan": 75, + "Bukiset (Burning)": 76, + "Bukiset (Stone)": 77, + "Bukiset (Ice)": 78, + "Bukiset (Needle)": 79, + "Bukiset (Clean)": 80, + "Bukiset (Parasol)": 81, + "Bukiset (Spark)": 82, + "Bukiset (Cutter)": 83, + "Waddle Dee Drawing": 84, + "Bronto Burt Drawing": 85, + "Bouncy Drawing": 86, + "Kabu (Dekabu)": 87, + "Wapod": 88, + "Propeller": 89, + "Dogon": 90, + "Joe": 91 +} + +miniboss_remap = { + "Captain Stitch": 0, + "Yuki": 1, + "Blocky": 2, + "Jumper Shoot": 3, + "Boboo": 4, + "Haboki": 5 +} + +ability_remap = { + "No Ability": 0, + "Burning Ability": 1, + "Stone Ability": 2, + "Ice Ability": 3, + "Needle Ability": 4, + "Clean Ability": 5, + "Parasol Ability": 6, + "Spark Ability": 7, + "Cutter Ability": 8, +} + + +class RomData: + def __init__(self, file: str, name: typing.Optional[str] = None): + self.file = bytearray() + self.read_from_file(file) + self.name = name + + def read_byte(self, offset: int): + return self.file[offset] + + def read_bytes(self, offset: int, length: int): + return self.file[offset:offset + length] + + def write_byte(self, offset: int, value: int): + self.file[offset] = value + + def write_bytes(self, offset: int, values: typing.Sequence) -> None: + self.file[offset:offset + len(values)] = values + + def write_to_file(self, file: str): + with open(file, 'wb') as outfile: + outfile.write(self.file) + + def read_from_file(self, file: str): + with open(file, 'rb') as stream: + self.file = bytearray(stream.read()) + + def apply_patch(self, patch: bytes): + self.file = bytearray(bsdiff4.patch(bytes(self.file), patch)) + + def write_crc(self): + crc = (sum(self.file[:0x7FDC] + self.file[0x7FE0:]) + 0x01FE) & 0xFFFF + inv = crc ^ 0xFFFF + self.write_bytes(0x7FDC, [inv & 0xFF, (inv >> 8) & 0xFF, crc & 0xFF, (crc >> 8) & 0xFF]) + + +def handle_level_sprites(stages, sprites, palettes): + palette_by_level = list() + for palette in palettes: + palette_by_level.extend(palette[10:16]) + for i in range(5): + for j in range(6): + palettes[i][10 + j] = palette_by_level[stages[i][j] - 1] + palettes[i] = [x for palette in palettes[i] for x in palette] + tiles_by_level = list() + for spritesheet in sprites: + decompressed = hal_decompress(spritesheet) + tiles = [decompressed[i:i + 32] for i in range(0, 2304, 32)] + tiles_by_level.extend([[tiles[x] for x in stage_tiles[stage]] for stage in stage_tiles]) + for world in range(5): + levels = [stages[world][x] - 1 for x in range(6)] + world_tiles: typing.List[typing.Optional[bytes]] = [None for _ in range(72)] + for i in range(6): + for x in range(12): + world_tiles[stage_tiles[i][x]] = tiles_by_level[levels[i]][x] + sprites[world] = list() + for tile in world_tiles: + sprites[world].extend(tile) + # insert our fake compression + sprites[world][0:0] = [0xe3, 0xff] + sprites[world][1026:1026] = [0xe3, 0xff] + sprites[world][2052:2052] = [0xe0, 0xff] + sprites[world].append(0xff) + return sprites, palettes + + +def write_heart_star_sprites(rom: RomData): + compressed = rom.read_bytes(heart_star_address, heart_star_size) + decompressed = hal_decompress(compressed) + patch = get_data(__name__, os.path.join("data", "APHeartStar.bsdiff4")) + patched = bytearray(bsdiff4.patch(decompressed, patch)) + rom.write_bytes(0x1AF7DF, patched) + patched[0:0] = [0xE3, 0xFF] + patched.append(0xFF) + rom.write_bytes(0x1CD000, patched) + rom.write_bytes(0x3F0EBF, [0x00, 0xD0, 0x39]) + + +def write_consumable_sprites(rom: RomData, consumables: bool, stars: bool): + compressed = rom.read_bytes(consumable_address, consumable_size) + decompressed = hal_decompress(compressed) + patched = bytearray(decompressed) + if consumables: + patch = get_data(__name__, os.path.join("data", "APConsumable.bsdiff4")) + patched = bytearray(bsdiff4.patch(bytes(patched), patch)) + if stars: + patch = get_data(__name__, os.path.join("data", "APStars.bsdiff4")) + patched = bytearray(bsdiff4.patch(bytes(patched), patch)) + patched[0:0] = [0xE3, 0xFF] + patched.append(0xFF) + rom.write_bytes(0x1CD500, patched) + rom.write_bytes(0x3F0DAE, [0x00, 0xD5, 0x39]) + + +class KDL3DeltaPatch(APDeltaPatch): + hash = [KDL3UHASH, KDL3JHASH] + game = "Kirby's Dream Land 3" + patch_file_ending = ".apkdl3" + + @classmethod + def get_source_data(cls) -> bytes: + return get_base_rom_bytes() + + def patch(self, target: str): + super().patch(target) + rom = RomData(target) + target_language = rom.read_byte(0x3C020) + rom.write_byte(0x7FD9, target_language) + write_heart_star_sprites(rom) + if rom.read_bytes(0x3D014, 1)[0] > 0: + stages = [struct.unpack("HHHHHHH", rom.read_bytes(0x3D020 + x * 14, 14)) for x in range(5)] + palettes = [rom.read_bytes(full_pal, 512) for full_pal in stage_palettes] + palettes = [[palette[i:i + 32] for i in range(0, 512, 32)] for palette in palettes] + sprites = [rom.read_bytes(offset, level_sprites[offset]) for offset in level_sprites] + sprites, palettes = handle_level_sprites(stages, sprites, palettes) + for addr, palette in zip(stage_palettes, palettes): + rom.write_bytes(addr, palette) + for addr, level_sprite in zip([0x1CA000, 0x1CA920, 0x1CB230, 0x1CBB40, 0x1CC450], sprites): + rom.write_bytes(addr, level_sprite) + rom.write_bytes(0x460A, [0x00, 0xA0, 0x39, 0x20, 0xA9, 0x39, 0x30, 0xB2, 0x39, 0x40, 0xBB, 0x39, + 0x50, 0xC4, 0x39]) + write_consumable_sprites(rom, rom.read_byte(0x3D018) > 0, rom.read_byte(0x3D01A) > 0) + rom_name = rom.read_bytes(0x3C000, 21) + rom.write_bytes(0x7FC0, rom_name) + rom.write_crc() + rom.write_to_file(target) + + +def patch_rom(world: "KDL3World", rom: RomData): + rom.apply_patch(get_data(__name__, os.path.join("data", "kdl3_basepatch.bsdiff4"))) + tiles = get_data(__name__, os.path.join("data", "APPauseIcons.dat")) + rom.write_bytes(0x3F000, tiles) + + # Write open world patch + if world.options.open_world: + rom.write_bytes(0x143C7, [0xAD, 0xC1, 0x5A, 0xCD, 0xC1, 0x5A, ]) + # changes the stage flag function to compare $5AC1 to $5AC1, + # always running the "new stage" function + # This has further checks present for bosses already, so we just + # need to handle regular stages + # write check for boss to be unlocked + + if world.options.consumables: + # reroute maxim tomatoes to use the 1-UP function, then null out the function + rom.write_bytes(0x3002F, [0x37, 0x00]) + rom.write_bytes(0x30037, [0xA9, 0x26, 0x00, # LDA #$0026 + 0x22, 0x27, 0xD9, 0x00, # JSL $00D927 + 0xA4, 0xD2, # LDY $D2 + 0x6B, # RTL + 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, # NOP #10 + ]) + + # stars handling is built into the rom, so no changes there + + rooms = world.rooms + if world.options.music_shuffle > 0: + if world.options.music_shuffle == 1: + shuffled_music = music_choices.copy() + world.random.shuffle(shuffled_music) + music_map = dict(zip(music_choices, shuffled_music)) + # Avoid putting star twinkle in the pool + music_map[5] = world.random.choice(music_choices) + # Heart Star music doesn't work on regular stages + music_map[8] = world.random.choice(music_choices) + for room in rooms: + room.music = music_map[room.music] + for room in room_pointers: + old_music = rom.read_byte(room + 2) + rom.write_byte(room + 2, music_map[old_music]) + for i in range(5): + # level themes + old_music = rom.read_byte(0x133F2 + i) + rom.write_byte(0x133F2 + i, music_map[old_music]) + # Zero + rom.write_byte(0x9AE79, music_map[0x18]) + # Heart Star success and fail + rom.write_byte(0x4A388, music_map[0x08]) + rom.write_byte(0x4A38D, music_map[0x1D]) + elif world.options.music_shuffle == 2: + for room in rooms: + room.music = world.random.choice(music_choices) + for room in room_pointers: + rom.write_byte(room + 2, world.random.choice(music_choices)) + for i in range(5): + # level themes + rom.write_byte(0x133F2 + i, world.random.choice(music_choices)) + # Zero + rom.write_byte(0x9AE79, world.random.choice(music_choices)) + # Heart Star success and fail + rom.write_byte(0x4A388, world.random.choice(music_choices)) + rom.write_byte(0x4A38D, world.random.choice(music_choices)) + + for room in rooms: + room.patch(rom) + + if world.options.virtual_console in [1, 3]: + # Flash Reduction + rom.write_byte(0x9AE68, 0x10) + rom.write_bytes(0x9AE8E, [0x08, 0x00, 0x22, 0x5D, 0xF7, 0x00, 0xA2, 0x08, ]) + rom.write_byte(0x9AEA1, 0x08) + rom.write_byte(0x9AEC9, 0x01) + rom.write_bytes(0x9AED2, [0xA9, 0x1F]) + rom.write_byte(0x9AEE1, 0x08) + + if world.options.virtual_console in [2, 3]: + # Hyper Zone BB colors + rom.write_bytes(0x2C5E16, [0xEE, 0x1B, 0x18, 0x5B, 0xD3, 0x4A, 0xF4, 0x3B, ]) + rom.write_bytes(0x2C8217, [0xFF, 0x1E, ]) + + # boss requirements + rom.write_bytes(0x3D000, struct.pack("HHHHH", world.boss_requirements[0], world.boss_requirements[1], + world.boss_requirements[2], world.boss_requirements[3], + world.boss_requirements[4])) + rom.write_bytes(0x3D00A, struct.pack("H", world.required_heart_stars if world.options.goal_speed == 1 else 0xFFFF)) + rom.write_byte(0x3D00C, world.options.goal_speed.value) + rom.write_byte(0x3D00E, world.options.open_world.value) + rom.write_byte(0x3D010, world.options.death_link.value) + rom.write_byte(0x3D012, world.options.goal.value) + rom.write_byte(0x3D014, world.options.stage_shuffle.value) + rom.write_byte(0x3D016, world.options.ow_boss_requirement.value) + rom.write_byte(0x3D018, world.options.consumables.value) + rom.write_byte(0x3D01A, world.options.starsanity.value) + rom.write_byte(0x3D01C, world.options.gifting.value if world.multiworld.players > 1 else 0) + rom.write_byte(0x3D01E, world.options.strict_bosses.value) + # don't write gifting for solo game, since there's no one to send anything to + + for level in world.player_levels: + for i in range(len(world.player_levels[level])): + rom.write_bytes(0x3F002E + ((level - 1) * 14) + (i * 2), + struct.pack("H", level_pointers[world.player_levels[level][i]])) + rom.write_bytes(0x3D020 + (level - 1) * 14 + (i * 2), + struct.pack("H", world.player_levels[level][i] & 0x00FFFF)) + if (i == 0) or (i > 0 and i % 6 != 0): + rom.write_bytes(0x3D080 + (level - 1) * 12 + (i * 2), + struct.pack("H", (world.player_levels[level][i] & 0x00FFFF) % 6)) + + for i in range(6): + if world.boss_butch_bosses[i]: + rom.write_bytes(0x3F0000 + (level_pointers[0x770200 + i]), struct.pack("I", bb_bosses[0x770200 + i])) + + # copy ability shuffle + if world.options.copy_ability_randomization.value > 0: + for enemy in world.copy_abilities: + if enemy in miniboss_remap: + rom.write_bytes(0xB417E + (miniboss_remap[enemy] << 1), + struct.pack("H", ability_remap[world.copy_abilities[enemy]])) + else: + rom.write_bytes(0xB3CAC + (enemy_remap[enemy] << 1), + struct.pack("H", ability_remap[world.copy_abilities[enemy]])) + # following only needs done on non-door rando + # incredibly lucky this follows the same order (including 5E == star block) + rom.write_byte(0x2F77EA, 0x5E + (ability_remap[world.copy_abilities["Sparky"]] << 1)) + rom.write_byte(0x2F7811, 0x5E + (ability_remap[world.copy_abilities["Sparky"]] << 1)) + rom.write_byte(0x2F9BC4, 0x5E + (ability_remap[world.copy_abilities["Blocky"]] << 1)) + rom.write_byte(0x2F9BEB, 0x5E + (ability_remap[world.copy_abilities["Blocky"]] << 1)) + rom.write_byte(0x2FAC06, 0x5E + (ability_remap[world.copy_abilities["Jumper Shoot"]] << 1)) + rom.write_byte(0x2FAC2D, 0x5E + (ability_remap[world.copy_abilities["Jumper Shoot"]] << 1)) + rom.write_byte(0x2F9E7B, 0x5E + (ability_remap[world.copy_abilities["Yuki"]] << 1)) + rom.write_byte(0x2F9EA2, 0x5E + (ability_remap[world.copy_abilities["Yuki"]] << 1)) + rom.write_byte(0x2FA951, 0x5E + (ability_remap[world.copy_abilities["Sir Kibble"]] << 1)) + rom.write_byte(0x2FA978, 0x5E + (ability_remap[world.copy_abilities["Sir Kibble"]] << 1)) + rom.write_byte(0x2FA132, 0x5E + (ability_remap[world.copy_abilities["Haboki"]] << 1)) + rom.write_byte(0x2FA159, 0x5E + (ability_remap[world.copy_abilities["Haboki"]] << 1)) + rom.write_byte(0x2FA3E8, 0x5E + (ability_remap[world.copy_abilities["Boboo"]] << 1)) + rom.write_byte(0x2FA40F, 0x5E + (ability_remap[world.copy_abilities["Boboo"]] << 1)) + rom.write_byte(0x2F90E2, 0x5E + (ability_remap[world.copy_abilities["Captain Stitch"]] << 1)) + rom.write_byte(0x2F9109, 0x5E + (ability_remap[world.copy_abilities["Captain Stitch"]] << 1)) + + if world.options.copy_ability_randomization == 2: + for enemy in enemy_remap: + # we just won't include it for minibosses + rom.write_bytes(0xB3E40 + (enemy_remap[enemy] << 1), struct.pack("h", world.random.randint(-1, 2))) + + # write jumping goal + rom.write_bytes(0x94F8, struct.pack("H", world.options.jumping_target)) + rom.write_bytes(0x944E, struct.pack("H", world.options.jumping_target)) + + from Utils import __version__ + rom.name = bytearray( + f'KDL3{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', 'utf8')[:21] + rom.name.extend([0] * (21 - len(rom.name))) + rom.write_bytes(0x3C000, rom.name) + rom.write_byte(0x3C020, world.options.game_language.value) + + # handle palette + if world.options.kirby_flavor_preset.value != 0: + for addr in kirby_target_palettes: + target = kirby_target_palettes[addr] + palette = get_kirby_palette(world) + rom.write_bytes(addr, get_palette_bytes(palette, target[0], target[1], target[2])) + + if world.options.gooey_flavor_preset.value != 0: + for addr in gooey_target_palettes: + target = gooey_target_palettes[addr] + palette = get_gooey_palette(world) + rom.write_bytes(addr, get_palette_bytes(palette, target[0], target[1], target[2])) + + +def get_base_rom_bytes() -> bytes: + rom_file: str = get_base_rom_path() + base_rom_bytes: Optional[bytes] = getattr(get_base_rom_bytes, "base_rom_bytes", None) + if not base_rom_bytes: + base_rom_bytes = bytes(Utils.read_snes_rom(open(rom_file, "rb"))) + + basemd5 = hashlib.md5() + basemd5.update(base_rom_bytes) + if basemd5.hexdigest() not in {KDL3UHASH, KDL3JHASH}: + raise Exception("Supplied Base Rom does not match known MD5 for US or JP release. " + "Get the correct game and version, then dump it") + get_base_rom_bytes.base_rom_bytes = base_rom_bytes + return base_rom_bytes + + +def get_base_rom_path(file_name: str = "") -> str: + options: settings.Settings = settings.get_settings() + if not file_name: + file_name = options["kdl3_options"]["rom_file"] + if not os.path.exists(file_name): + file_name = Utils.user_path(file_name) + return file_name diff --git a/worlds/kdl3/Room.py b/worlds/kdl3/Room.py new file mode 100644 index 0000000000..256955b924 --- /dev/null +++ b/worlds/kdl3/Room.py @@ -0,0 +1,95 @@ +import struct +import typing +from BaseClasses import Region, ItemClassification + +if typing.TYPE_CHECKING: + from .Rom import RomData + +animal_map = { + "Rick Spawn": 0, + "Kine Spawn": 1, + "Coo Spawn": 2, + "Nago Spawn": 3, + "ChuChu Spawn": 4, + "Pitch Spawn": 5 +} + + +class KDL3Room(Region): + pointer: int = 0 + level: int = 0 + stage: int = 0 + room: int = 0 + music: int = 0 + default_exits: typing.List[typing.Dict[str, typing.Union[int, typing.List[str]]]] + animal_pointers: typing.List[int] + enemies: typing.List[str] + entity_load: typing.List[typing.List[int]] + consumables: typing.List[typing.Dict[str, typing.Union[int, str]]] + + def __init__(self, name, player, multiworld, hint, level, stage, room, pointer, music, default_exits, + animal_pointers, enemies, entity_load, consumables, consumable_pointer): + super().__init__(name, player, multiworld, hint) + self.level = level + self.stage = stage + self.room = room + self.pointer = pointer + self.music = music + self.default_exits = default_exits + self.animal_pointers = animal_pointers + self.enemies = enemies + self.entity_load = entity_load + self.consumables = consumables + self.consumable_pointer = consumable_pointer + + def patch(self, rom: "RomData"): + rom.write_byte(self.pointer + 2, self.music) + animals = [x.item.name for x in self.locations if "Animal" in x.name] + if len(animals) > 0: + for current_animal, address in zip(animals, self.animal_pointers): + rom.write_byte(self.pointer + address + 7, animal_map[current_animal]) + if self.multiworld.worlds[self.player].options.consumables: + load_len = len(self.entity_load) + for consumable in self.consumables: + location = next(x for x in self.locations if x.name == consumable["name"]) + assert location.item + is_progression = location.item.classification & ItemClassification.progression + if load_len == 8: + # edge case, there is exactly 1 room with 8 entities and only 1 consumable among them + if not (any(x in self.entity_load for x in [[0, 22], [1, 22]]) + and any(x in self.entity_load for x in [[2, 22], [3, 22]])): + replacement_target = self.entity_load.index( + next(x for x in self.entity_load if x in [[0, 22], [1, 22], [2, 22], [3, 22]])) + if is_progression: + vtype = 0 + else: + vtype = 2 + rom.write_byte(self.pointer + 88 + (replacement_target * 2), vtype) + self.entity_load[replacement_target] = [vtype, 22] + else: + if is_progression: + # we need to see if 1-ups are in our load list + if any(x not in self.entity_load for x in [[0, 22], [1, 22]]): + self.entity_load.append([0, 22]) + else: + if any(x not in self.entity_load for x in [[2, 22], [3, 22]]): + # edge case: if (1, 22) is in, we need to load (3, 22) instead + if [1, 22] in self.entity_load: + self.entity_load.append([3, 22]) + else: + self.entity_load.append([2, 22]) + if load_len < len(self.entity_load): + rom.write_bytes(self.pointer + 88 + (load_len * 2), bytes(self.entity_load[load_len])) + rom.write_bytes(self.pointer + 104 + (load_len * 2), + bytes(struct.pack("H", self.consumable_pointer))) + if is_progression: + if [1, 22] in self.entity_load: + vtype = 1 + else: + vtype = 0 + else: + if [3, 22] in self.entity_load: + vtype = 3 + else: + vtype = 2 + rom.write_byte(self.pointer + consumable["pointer"] + 7, vtype) diff --git a/worlds/kdl3/Rules.py b/worlds/kdl3/Rules.py new file mode 100644 index 0000000000..81ad8f1f1f --- /dev/null +++ b/worlds/kdl3/Rules.py @@ -0,0 +1,332 @@ +from worlds.generic.Rules import set_rule, add_rule +from .Names import LocationName, EnemyAbilities +from .Locations import location_table +from .Options import GoalSpeed +import typing + +if typing.TYPE_CHECKING: + from . import KDL3World + from BaseClasses import CollectionState + + +def can_reach_boss(state: "CollectionState", player: int, level: int, open_world: int, + ow_boss_req: int, player_levels: typing.Dict[int, typing.Dict[int, int]]): + if open_world: + return state.has(f"{LocationName.level_names_inverse[level]} - Stage Completion", player, ow_boss_req) + else: + return state.can_reach(location_table[player_levels[level][5]], "Location", player) + + +def can_reach_rick(state: "CollectionState", player: int) -> bool: + return state.has("Rick", player) and state.has("Rick Spawn", player) + + +def can_reach_kine(state: "CollectionState", player: int) -> bool: + return state.has("Kine", player) and state.has("Kine Spawn", player) + + +def can_reach_coo(state: "CollectionState", player: int) -> bool: + return state.has("Coo", player) and state.has("Coo Spawn", player) + + +def can_reach_nago(state: "CollectionState", player: int) -> bool: + return state.has("Nago", player) and state.has("Nago Spawn", player) + + +def can_reach_chuchu(state: "CollectionState", player: int) -> bool: + return state.has("ChuChu", player) and state.has("ChuChu Spawn", player) + + +def can_reach_pitch(state: "CollectionState", player: int) -> bool: + return state.has("Pitch", player) and state.has("Pitch Spawn", player) + + +def can_reach_burning(state: "CollectionState", player: int) -> bool: + return state.has("Burning", player) and state.has("Burning Ability", player) + + +def can_reach_stone(state: "CollectionState", player: int) -> bool: + return state.has("Stone", player) and state.has("Stone Ability", player) + + +def can_reach_ice(state: "CollectionState", player: int) -> bool: + return state.has("Ice", player) and state.has("Ice Ability", player) + + +def can_reach_needle(state: "CollectionState", player: int) -> bool: + return state.has("Needle", player) and state.has("Needle Ability", player) + + +def can_reach_clean(state: "CollectionState", player: int) -> bool: + return state.has("Clean", player) and state.has("Clean Ability", player) + + +def can_reach_parasol(state: "CollectionState", player: int) -> bool: + return state.has("Parasol", player) and state.has("Parasol Ability", player) + + +def can_reach_spark(state: "CollectionState", player: int) -> bool: + return state.has("Spark", player) and state.has("Spark Ability", player) + + +def can_reach_cutter(state: "CollectionState", player: int) -> bool: + return state.has("Cutter", player) and state.has("Cutter Ability", player) + + +ability_map: typing.Dict[str, typing.Callable[["CollectionState", int], bool]] = { + "No Ability": lambda state, player: True, + "Burning Ability": can_reach_burning, + "Stone Ability": can_reach_stone, + "Ice Ability": can_reach_ice, + "Needle Ability": can_reach_needle, + "Clean Ability": can_reach_clean, + "Parasol Ability": can_reach_parasol, + "Spark Ability": can_reach_spark, + "Cutter Ability": can_reach_cutter, +} + + +def can_assemble_rob(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]): + # check animal requirements + if not (can_reach_coo(state, player) and can_reach_kine(state, player)): + return False + for abilities, bukisets in EnemyAbilities.enemy_restrictive[1:5]: + iterator = iter(x for x in bukisets if copy_abilities[x] in abilities) + target_bukiset = next(iterator, None) + can_reach = False + while target_bukiset is not None: + can_reach = can_reach | ability_map[copy_abilities[target_bukiset]](state, player) + target_bukiset = next(iterator, None) + if not can_reach: + return False + # now the known needed abilities + return can_reach_parasol(state, player) and can_reach_stone(state, player) + + +def can_fix_angel_wings(state: "CollectionState", player: int, copy_abilities: typing.Dict[str, str]): + can_reach = True + for enemy in {"Sparky", "Blocky", "Jumper Shoot", "Yuki", "Sir Kibble", "Haboki", "Boboo", "Captain Stitch"}: + can_reach = can_reach & ability_map[copy_abilities[enemy]](state, player) + return can_reach + + +def set_rules(world: "KDL3World") -> None: + # Level 1 + set_rule(world.multiworld.get_location(LocationName.grass_land_muchi, world.player), + lambda state: can_reach_chuchu(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.grass_land_chao, world.player), + lambda state: can_reach_stone(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.grass_land_mine, world.player), + lambda state: can_reach_kine(state, world.player)) + + # Level 2 + set_rule(world.multiworld.get_location(LocationName.ripple_field_5, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_kamuribana, world.player), + lambda state: can_reach_pitch(state, world.player) and can_reach_clean(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_bakasa, world.player), + lambda state: can_reach_kine(state, world.player) and can_reach_parasol(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_toad, world.player), + lambda state: can_reach_needle(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_mama_pitch, world.player), + lambda state: (can_reach_pitch(state, world.player) and + can_reach_kine(state, world.player) and + can_reach_burning(state, world.player) and + can_reach_stone(state, world.player))) + + # Level 3 + set_rule(world.multiworld.get_location(LocationName.sand_canyon_5, world.player), + lambda state: can_reach_cutter(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_auntie, world.player), + lambda state: can_reach_clean(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_nyupun, world.player), + lambda state: can_reach_chuchu(state, world.player) and can_reach_cutter(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_rob, world.player), + lambda state: can_assemble_rob(state, world.player, world.copy_abilities) + ) + + # Level 4 + set_rule(world.multiworld.get_location(LocationName.cloudy_park_hibanamodoki, world.player), + lambda state: can_reach_coo(state, world.player) and can_reach_clean(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.cloudy_park_piyokeko, world.player), + lambda state: can_reach_needle(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.cloudy_park_mikarin, world.player), + lambda state: can_reach_coo(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.cloudy_park_pick, world.player), + lambda state: can_reach_rick(state, world.player)) + + # Level 5 + set_rule(world.multiworld.get_location(LocationName.iceberg_4, world.player), + lambda state: can_reach_burning(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.iceberg_kogoesou, world.player), + lambda state: can_reach_burning(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.iceberg_samus, world.player), + lambda state: can_reach_ice(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.iceberg_name, world.player), + lambda state: (can_reach_coo(state, world.player) and + can_reach_burning(state, world.player) and + can_reach_chuchu(state, world.player))) + # ChuChu is guaranteed here, but we use this for consistency + set_rule(world.multiworld.get_location(LocationName.iceberg_shiro, world.player), + lambda state: can_reach_nago(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.iceberg_angel, world.player), + lambda state: can_fix_angel_wings(state, world.player, world.copy_abilities)) + + # Consumables + if world.options.consumables: + set_rule(world.multiworld.get_location(LocationName.grass_land_1_u1, world.player), + lambda state: can_reach_parasol(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.grass_land_1_m1, world.player), + lambda state: can_reach_spark(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.grass_land_2_u1, world.player), + lambda state: can_reach_needle(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_2_u1, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_2_m1, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_3_u1, world.player), + lambda state: can_reach_cutter(state, world.player) or can_reach_spark(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_4_u1, world.player), + lambda state: can_reach_stone(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_4_m2, world.player), + lambda state: can_reach_stone(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_5_m1, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.ripple_field_5_u1, world.player), + lambda state: (can_reach_kine(state, world.player) and + can_reach_burning(state, world.player) and + can_reach_stone(state, world.player))) + set_rule(world.multiworld.get_location(LocationName.ripple_field_5_m2, world.player), + lambda state: (can_reach_kine(state, world.player) and + can_reach_burning(state, world.player) and + can_reach_stone(state, world.player))) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_4_u1, world.player), + lambda state: can_reach_clean(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_4_m2, world.player), + lambda state: can_reach_needle(state, world.player)) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u2, world.player), + lambda state: can_reach_ice(state, world.player) and + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u3, world.player), + lambda state: can_reach_ice(state, world.player) and + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u4, world.player), + lambda state: can_reach_ice(state, world.player) and + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + set_rule(world.multiworld.get_location(LocationName.cloudy_park_6_u1, world.player), + lambda state: can_reach_cutter(state, world.player)) + + if world.options.starsanity: + # ranges are our friend + for i in range(7, 11): + set_rule(world.multiworld.get_location(f"Grass Land 1 - Star {i}", world.player), + lambda state: can_reach_cutter(state, world.player)) + for i in range(11, 14): + set_rule(world.multiworld.get_location(f"Grass Land 1 - Star {i}", world.player), + lambda state: can_reach_parasol(state, world.player)) + for i in [1, 3, 4, 9, 10]: + set_rule(world.multiworld.get_location(f"Grass Land 2 - Star {i}", world.player), + lambda state: can_reach_stone(state, world.player)) + set_rule(world.multiworld.get_location("Grass Land 2 - Star 2", world.player), + lambda state: can_reach_burning(state, world.player)) + set_rule(world.multiworld.get_location("Ripple Field 2 - Star 17", world.player), + lambda state: can_reach_kine(state, world.player)) + for i in range(41, 43): + # any star past this point also needs kine, but so does the exit + set_rule(world.multiworld.get_location(f"Ripple Field 5 - Star {i}", world.player), + lambda state: can_reach_kine(state, world.player)) + for i in range(46, 49): + # also requires kine, but only for access from the prior room + set_rule(world.multiworld.get_location(f"Ripple Field 5 - Star {i}", world.player), + lambda state: can_reach_burning(state, world.player) and can_reach_stone(state, world.player)) + for i in range(12, 18): + set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player), + lambda state: can_reach_ice(state, world.player) and + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + for i in range(21, 23): + set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player), + lambda state: can_reach_chuchu(state, world.player)) + for r in [range(19, 21), range(23, 31)]: + for i in r: + set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player), + lambda state: can_reach_clean(state, world.player)) + for i in range(31, 41): + set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player), + lambda state: can_reach_burning(state, world.player)) + for r in [range(1, 31), range(44, 51)]: + for i in r: + set_rule(world.multiworld.get_location(f"Cloudy Park 4 - Star {i}", world.player), + lambda state: can_reach_clean(state, world.player)) + for i in [18, *list(range(20, 25))]: + set_rule(world.multiworld.get_location(f"Cloudy Park 6 - Star {i}", world.player), + lambda state: can_reach_ice(state, world.player)) + for i in [19, *list(range(25, 30))]: + set_rule(world.multiworld.get_location(f"Cloudy Park 6 - Star {i}", world.player), + lambda state: can_reach_ice(state, world.player)) + # copy ability access edge cases + # Kirby cannot eat enemies fully submerged in water. Vast majority of cases, the enemy can be brought to the surface + # and eaten by inhaling while falling on top of them + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_2_E3, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_3_E6, world.player), + lambda state: can_reach_kine(state, world.player)) + # Ripple Field 4 E5, E7, and E8 are doable, but too strict to leave in logic + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E5, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E7, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E8, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E1, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E2, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E3, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E4, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E7, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E8, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E9, world.player), + lambda state: can_reach_kine(state, world.player)) + set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E10, world.player), + lambda state: can_reach_kine(state, world.player)) + + for boss_flag, purification, i in zip(["Level 1 Boss - Purified", "Level 2 Boss - Purified", + "Level 3 Boss - Purified", "Level 4 Boss - Purified", + "Level 5 Boss - Purified"], + [LocationName.grass_land_whispy, LocationName.ripple_field_acro, + LocationName.sand_canyon_poncon, LocationName.cloudy_park_ado, + LocationName.iceberg_dedede], + range(1, 6)): + set_rule(world.multiworld.get_location(boss_flag, world.player), + lambda state, i=i: (state.has("Heart Star", world.player, world.boss_requirements[i - 1]) + and can_reach_boss(state, world.player, i, + world.options.open_world.value, + world.options.ow_boss_requirement.value, + world.player_levels))) + set_rule(world.multiworld.get_location(purification, world.player), + lambda state, i=i: (state.has("Heart Star", world.player, world.boss_requirements[i - 1]) + and can_reach_boss(state, world.player, i, + world.options.open_world.value, + world.options.ow_boss_requirement.value, + world.player_levels))) + + set_rule(world.multiworld.get_entrance("To Level 6", world.player), + lambda state: state.has("Heart Star", world.player, world.required_heart_stars)) + + for level in range(2, 6): + set_rule(world.multiworld.get_entrance(f"To Level {level}", world.player), + lambda state, i=level: state.has(f"Level {i - 1} Boss Defeated", world.player)) + + if world.options.strict_bosses: + for level in range(2, 6): + add_rule(world.multiworld.get_entrance(f"To Level {level}", world.player), + lambda state, i=level: state.has(f"Level {i - 1} Boss Purified", world.player)) + + if world.options.goal_speed == GoalSpeed.option_normal: + add_rule(world.multiworld.get_entrance("To Level 6", world.player), + lambda state: state.has_all(["Level 1 Boss Purified", "Level 2 Boss Purified", "Level 3 Boss Purified", + "Level 4 Boss Purified", "Level 5 Boss Purified"], world.player)) diff --git a/worlds/kdl3/__init__.py b/worlds/kdl3/__init__.py new file mode 100644 index 0000000000..66c9b17b84 --- /dev/null +++ b/worlds/kdl3/__init__.py @@ -0,0 +1,350 @@ +import logging +import typing + +from BaseClasses import Tutorial, ItemClassification, MultiWorld +from Fill import fill_restrictive +from Options import PerGameCommonOptions +from worlds.AutoWorld import World, WebWorld +from .Items import item_table, item_names, copy_ability_table, animal_friend_table, filler_item_weights, KDL3Item, \ + trap_item_table, copy_ability_access_table, star_item_weights, total_filler_weights +from .Locations import location_table, KDL3Location, level_consumables, consumable_locations, star_locations +from .Names.AnimalFriendSpawns import animal_friend_spawns +from .Names.EnemyAbilities import vanilla_enemies, enemy_mapping, enemy_restrictive +from .Regions import create_levels, default_levels +from .Options import KDL3Options +from .Presets import kdl3_options_presets +from .Names import LocationName +from .Room import KDL3Room +from .Rules import set_rules +from .Rom import KDL3DeltaPatch, get_base_rom_path, RomData, patch_rom, KDL3JHASH, KDL3UHASH +from .Client import KDL3SNIClient + +from typing import Dict, TextIO, Optional, List +import os +import math +import threading +import base64 +import settings + +logger = logging.getLogger("Kirby's Dream Land 3") + + +class KDL3Settings(settings.Group): + class RomFile(settings.SNESRomPath): + """File name of the KDL3 JP or EN rom""" + description = "Kirby's Dream Land 3 ROM File" + copy_to = "Kirby's Dream Land 3.sfc" + md5s = [KDL3JHASH, KDL3UHASH] + + rom_file: RomFile = RomFile(RomFile.copy_to) + + +class KDL3WebWorld(WebWorld): + theme = "partyTime" + tutorials = [ + + Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Kirby's Dream Land 3 randomizer connected to an Archipelago Multiworld.", + "English", + "setup_en.md", + "setup/en", + ["Silvris"] + ) + ] + options_presets = kdl3_options_presets + + +class KDL3World(World): + """ + Join Kirby and his Animal Friends on an adventure to collect Heart Stars and drive Dark Matter away from Dream Land! + """ + + game = "Kirby's Dream Land 3" + options_dataclass: typing.ClassVar[typing.Type[PerGameCommonOptions]] = KDL3Options + options: KDL3Options + item_name_to_id = {item: item_table[item].code for item in item_table} + location_name_to_id = {location_table[location]: location for location in location_table} + item_name_groups = item_names + web = KDL3WebWorld() + settings: typing.ClassVar[KDL3Settings] + + def __init__(self, multiworld: MultiWorld, player: int): + self.rom_name = None + self.rom_name_available_event = threading.Event() + super().__init__(multiworld, player) + self.copy_abilities: Dict[str, str] = vanilla_enemies.copy() + self.required_heart_stars: int = 0 # we fill this during create_items + self.boss_requirements: Dict[int, int] = dict() + self.player_levels = default_levels.copy() + self.stage_shuffle_enabled = False + self.boss_butch_bosses: List[Optional[bool]] = list() + self.rooms: Optional[List[KDL3Room]] = None + + @classmethod + def stage_assert_generate(cls, multiworld: MultiWorld) -> None: + rom_file: str = get_base_rom_path() + if not os.path.exists(rom_file): + raise FileNotFoundError(f"Could not find base ROM for {cls.game}: {rom_file}") + + create_regions = create_levels + + def create_item(self, name: str, force_non_progression=False) -> KDL3Item: + item = item_table[name] + classification = ItemClassification.filler + if item.progression and not force_non_progression: + classification = ItemClassification.progression_skip_balancing \ + if item.skip_balancing else ItemClassification.progression + elif item.trap: + classification = ItemClassification.trap + return KDL3Item(name, classification, item.code, self.player) + + def get_filler_item_name(self, include_stars=True) -> str: + if include_stars: + return self.random.choices(list(total_filler_weights.keys()), + weights=list(total_filler_weights.values()))[0] + return self.random.choices(list(filler_item_weights.keys()), + weights=list(filler_item_weights.values()))[0] + + def get_trap_item_name(self) -> str: + return self.random.choices(["Gooey Bag", "Slowness", "Eject Ability"], + weights=[self.options.gooey_trap_weight.value, + self.options.slow_trap_weight.value, + self.options.ability_trap_weight.value])[0] + + def get_restrictive_copy_ability_placement(self, copy_ability: str, enemies_to_set: typing.List[str], + level: int, stage: int): + valid_rooms = [room for room in self.rooms if (room.level < level) + or (room.level == level and room.stage < stage)] # leave out the stage in question to avoid edge + valid_enemies = set() + for room in valid_rooms: + valid_enemies.update(room.enemies) + placed_enemies = [enemy for enemy in valid_enemies if enemy not in enemies_to_set] + if any(self.copy_abilities[enemy] == copy_ability for enemy in placed_enemies): + return None # a valid enemy got placed by a more restrictive placement + return self.random.choice(sorted([enemy for enemy in valid_enemies if enemy not in placed_enemies])) + + def pre_fill(self) -> None: + if self.options.copy_ability_randomization: + # randomize copy abilities + valid_abilities = list(copy_ability_access_table.keys()) + enemies_to_set = list(self.copy_abilities.keys()) + # now for the edge cases + for abilities, enemies in enemy_restrictive: + available_enemies = list() + for enemy in enemies: + if enemy not in enemies_to_set: + if self.copy_abilities[enemy] in abilities: + break + else: + available_enemies.append(enemy) + else: + chosen_enemy = self.random.choice(available_enemies) + chosen_ability = self.random.choice(abilities) + self.copy_abilities[chosen_enemy] = chosen_ability + enemies_to_set.remove(chosen_enemy) + # two less restrictive ones, we need to ensure Cutter and Burning appear before their required stages + sand_canyon_5 = self.get_region("Sand Canyon 5 - 9") + # this is primarily for typing, but if this ever hits it's fine to crash + assert isinstance(sand_canyon_5, KDL3Room) + cutter_enemy = self.get_restrictive_copy_ability_placement("Cutter Ability", enemies_to_set, + sand_canyon_5.level, sand_canyon_5.stage) + if cutter_enemy: + self.copy_abilities[cutter_enemy] = "Cutter Ability" + enemies_to_set.remove(cutter_enemy) + iceberg_4 = self.get_region("Iceberg 4 - 7") + # this is primarily for typing, but if this ever hits it's fine to crash + assert isinstance(iceberg_4, KDL3Room) + burning_enemy = self.get_restrictive_copy_ability_placement("Burning Ability", enemies_to_set, + iceberg_4.level, iceberg_4.stage) + if burning_enemy: + self.copy_abilities[burning_enemy] = "Burning Ability" + enemies_to_set.remove(burning_enemy) + # place remaining + for enemy in enemies_to_set: + self.copy_abilities[enemy] = self.random.choice(valid_abilities) + for enemy in enemy_mapping: + self.multiworld.get_location(enemy, self.player) \ + .place_locked_item(self.create_item(self.copy_abilities[enemy_mapping[enemy]])) + # fill animals + if self.options.animal_randomization != 0: + spawns = [animal for animal in animal_friend_spawns.keys() if + animal not in ["Ripple Field 5 - Animal 2", "Sand Canyon 6 - Animal 1", "Iceberg 4 - Animal 1"]] + self.multiworld.get_location("Iceberg 4 - Animal 1", self.player) \ + .place_locked_item(self.create_item("ChuChu Spawn")) + # Not having ChuChu here makes the room impossible (since only she has vertical burning) + self.multiworld.get_location("Ripple Field 5 - Animal 2", self.player) \ + .place_locked_item(self.create_item("Pitch Spawn")) + guaranteed_animal = self.random.choice(["Kine Spawn", "Coo Spawn"]) + self.multiworld.get_location("Sand Canyon 6 - Animal 1", self.player) \ + .place_locked_item(self.create_item(guaranteed_animal)) + # Ripple Field 5 - Animal 2 needs to be Pitch to ensure accessibility on non-door rando + if self.options.animal_randomization == 1: + animal_pool = [animal_friend_spawns[spawn] for spawn in animal_friend_spawns + if spawn not in ["Ripple Field 5 - Animal 2", "Sand Canyon 6 - Animal 1", + "Iceberg 4 - Animal 1"]] + else: + animal_base = ["Rick Spawn", "Kine Spawn", "Coo Spawn", "Nago Spawn", "ChuChu Spawn", "Pitch Spawn"] + animal_pool = [self.random.choice(animal_base) + for _ in range(len(animal_friend_spawns) - 9)] + # have to guarantee one of each animal + animal_pool.extend(animal_base) + if guaranteed_animal == "Kine Spawn": + animal_pool.append("Coo Spawn") + else: + animal_pool.append("Kine Spawn") + locations = [self.multiworld.get_location(spawn, self.player) for spawn in spawns] + items = [self.create_item(animal) for animal in animal_pool] + allstate = self.multiworld.get_all_state(False) + fill_restrictive(self.multiworld, allstate, locations, items, True, True) + else: + animal_friends = animal_friend_spawns.copy() + for animal in animal_friends: + self.multiworld.get_location(animal, self.player) \ + .place_locked_item(self.create_item(animal_friends[animal])) + + def create_items(self) -> None: + itempool = [] + itempool.extend([self.create_item(name) for name in copy_ability_table]) + itempool.extend([self.create_item(name) for name in animal_friend_table]) + required_percentage = self.options.heart_stars_required / 100.0 + remaining_items = len(location_table) - len(itempool) + if not self.options.consumables: + remaining_items -= len(consumable_locations) + remaining_items -= len(star_locations) + if self.options.starsanity: + # star fill, keep consumable pool locked to consumable and fill 767 stars specifically + star_items = list(star_item_weights.keys()) + star_weights = list(star_item_weights.values()) + itempool.extend([self.create_item(item) for item in self.random.choices(star_items, weights=star_weights, + k=767)]) + total_heart_stars = self.options.total_heart_stars + # ensure at least 1 heart star required per world + required_heart_stars = max(int(total_heart_stars * required_percentage), 5) + filler_items = total_heart_stars - required_heart_stars + filler_amount = math.floor(filler_items * (self.options.filler_percentage / 100.0)) + trap_amount = math.floor(filler_amount * (self.options.trap_percentage / 100.0)) + filler_amount -= trap_amount + non_required_heart_stars = filler_items - filler_amount - trap_amount + self.required_heart_stars = required_heart_stars + # handle boss requirements here + requirements = [required_heart_stars] + quotient = required_heart_stars // 5 # since we set the last manually, we can afford imperfect rounding + if self.options.boss_requirement_random: + for i in range(1, 5): + if self.options.strict_bosses: + max_stars = quotient * i + else: + max_stars = required_heart_stars + requirements.insert(i, self.random.randint( + min(1, max_stars), max_stars)) + if self.options.strict_bosses: + requirements.sort() + else: + self.random.shuffle(requirements) + else: + for i in range(1, 5): + requirements.insert(i - 1, quotient * i) + self.boss_requirements = requirements + itempool.extend([self.create_item("Heart Star") for _ in range(required_heart_stars)]) + itempool.extend([self.create_item(self.get_filler_item_name(False)) + for _ in range(filler_amount + (remaining_items - total_heart_stars))]) + itempool.extend([self.create_item(self.get_trap_item_name()) + for _ in range(trap_amount)]) + itempool.extend([self.create_item("Heart Star", True) for _ in range(non_required_heart_stars)]) + self.multiworld.itempool += itempool + if self.options.open_world: + for level in self.player_levels: + for stage in range(0, 6): + self.multiworld.get_location(location_table[self.player_levels[level][stage]] + .replace("Complete", "Stage Completion"), self.player) \ + .place_locked_item(KDL3Item( + f"{LocationName.level_names_inverse[level]} - Stage Completion", + ItemClassification.progression, None, self.player)) + + set_rules = set_rules + + def generate_basic(self) -> None: + self.stage_shuffle_enabled = self.options.stage_shuffle > 0 + goal = self.options.goal + goal_location = self.multiworld.get_location(LocationName.goals[goal], self.player) + goal_location.place_locked_item(KDL3Item("Love-Love Rod", ItemClassification.progression, None, self.player)) + for level in range(1, 6): + self.multiworld.get_location(f"Level {level} Boss - Defeated", self.player) \ + .place_locked_item( + KDL3Item(f"Level {level} Boss Defeated", ItemClassification.progression, None, self.player)) + self.multiworld.get_location(f"Level {level} Boss - Purified", self.player) \ + .place_locked_item( + KDL3Item(f"Level {level} Boss Purified", ItemClassification.progression, None, self.player)) + self.multiworld.completion_condition[self.player] = lambda state: state.has("Love-Love Rod", self.player) + # this can technically be done at any point before generate_output + if self.options.allow_bb: + if self.options.allow_bb == self.options.allow_bb.option_enforced: + self.boss_butch_bosses = [True for _ in range(6)] + else: + self.boss_butch_bosses = [self.random.choice([True, False]) for _ in range(6)] + + def generate_output(self, output_directory: str): + rom_path = "" + try: + rom = RomData(get_base_rom_path()) + patch_rom(self, rom) + + rom_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc") + rom.write_to_file(rom_path) + self.rom_name = rom.name + + patch = KDL3DeltaPatch(os.path.splitext(rom_path)[0] + KDL3DeltaPatch.patch_file_ending, player=self.player, + player_name=self.multiworld.player_name[self.player], patched_path=rom_path) + patch.write() + except Exception: + raise + finally: + self.rom_name_available_event.set() # make sure threading continues and errors are collected + if os.path.exists(rom_path): + os.unlink(rom_path) + + def modify_multidata(self, multidata: dict): + # wait for self.rom_name to be available. + self.rom_name_available_event.wait() + rom_name = getattr(self, "rom_name", None) + # we skip in case of error, so that the original error in the output thread is the one that gets raised + if rom_name: + new_name = base64.b64encode(bytes(self.rom_name)).decode() + multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]] + + def write_spoiler(self, spoiler_handle: TextIO) -> None: + if self.stage_shuffle_enabled: + spoiler_handle.write(f"\nLevel Layout ({self.multiworld.get_player_name(self.player)}):\n") + for level in LocationName.level_names: + for stage, i in zip(self.player_levels[LocationName.level_names[level]], range(1, 7)): + spoiler_handle.write(f"{level} {i}: {location_table[stage].replace(' - Complete', '')}\n") + if self.options.animal_randomization: + spoiler_handle.write(f"\nAnimal Friends ({self.multiworld.get_player_name(self.player)}):\n") + for level in self.player_levels: + for stage in range(6): + rooms = [room for room in self.rooms if room.level == level and room.stage == stage] + animals = [] + for room in rooms: + animals.extend([location.item.name.replace(" Spawn", "") + for location in room.locations if "Animal" in location.name]) + spoiler_handle.write(f"{location_table[self.player_levels[level][stage]].replace(' - Complete','')}" + f": {', '.join(animals)}\n") + if self.options.copy_ability_randomization: + spoiler_handle.write(f"\nCopy Abilities ({self.multiworld.get_player_name(self.player)}):\n") + for enemy in self.copy_abilities: + spoiler_handle.write(f"{enemy}: {self.copy_abilities[enemy].replace('No Ability', 'None').replace(' Ability', '')}\n") + + def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): + if self.stage_shuffle_enabled: + regions = {LocationName.level_names[level]: level for level in LocationName.level_names} + level_hint_data = {} + for level in regions: + for stage in range(7): + stage_name = self.multiworld.get_location(self.location_id_to_name[self.player_levels[level][stage]], + self.player).name.replace(" - Complete", "") + stage_regions = [room for room in self.rooms if stage_name in room.name] + for region in stage_regions: + for location in [location for location in region.locations if location.address]: + level_hint_data[location.address] = f"{regions[level]} {stage + 1 if stage < 6 else 'Boss'}" + hint_data[self.player] = level_hint_data diff --git a/worlds/kdl3/data/APConsumable.bsdiff4 b/worlds/kdl3/data/APConsumable.bsdiff4 new file mode 100644 index 0000000000000000000000000000000000000000..e930a4e2b30ca2094f8bced437275ff9c89bec27 GIT binary patch literal 585 zcmZ;fqqXZvIz|GR`Xd zxMfr=>|8J_$#6;9WTvFFWj>5X2{UH6xe0bQFto88G+;T%;;WQ2Z*iurFXOVdnQU&e zGT0hB3>q#j6EMk;GD=~aWn}BdwxlO)tHTx3Qx2>wQ+T$P&U>83#UQ52K7Vk0>e0*0a8Nm^AH{i^a-mkq4f9cCE8fYV2m|xtMalHh1PKrA2*9ESy8_CSNTn zIQgz%yZF@853L2*^=7;lQ1*Ytq5J&$wY>LnXPx4e4(N)xOuRO6o0O2j$_59SpSBYp zyX?2pd8v`7WH*12!pZsd_FE)H=H;KQdh?xizO34b2ah@ox+EI*UMS;|)L6CFcGk&L zq8Gm}bx2*Vm&LyI($jm_y%y)RG(9_3utaL{m9#ZyY!$iqnGF`ChD69XOJ*&$|8PKs z;or6Q@m>voz1yEjzt27^nBrmaqB-uG#r``tnpC4+3mS+WxWMD2v?)(2_3nnP|H41| zT>2q4(Oj;lr?=F&GfB@Vyn6=g*9zAoN)PfE)e0Uqkm@>pF~l+WW_|Zgr8ycN)_pHu NHGtw867!&>0swph^2z`J literal 0 HcmV?d00001 diff --git a/worlds/kdl3/data/APHeartStar.bsdiff4 b/worlds/kdl3/data/APHeartStar.bsdiff4 new file mode 100644 index 0000000000000000000000000000000000000000..e442604f26009c5981c79d4cdd497dee2d6003d0 GIT binary patch literal 603 zcmZmL5CI1U0R{yIMUBiC z25e<~?OS3`iad03%==um2cdjcgFy)c1JD2e|38PMUJFSGzq`PJ-M@i>?a{`o0eKGs z6zhLDupg_?&fmaTu_EBAf0n8LMgL&6Hn(0WBMIkADVYlx7cMZ$oM6Ii()eU{@TrUf z23{b{U|_h|!5{^4oD_qpky^`9<6viJw^__rG8mYB86>5oB-t7m8041btnpzjI+V36 z`QfR!Q(K-FYCNjmGuLYoyW@gR#vjLpd9SKYW0>VQw{6lzjiwXfZx2jinV>YuL-OhE zssbTZi)>Y!q?IfJ#)5Jdj*_klMQ4u84mr(lCM0%8Gd0LIbjjvFON)E7JU|aRq zT`;8PkCn>gBr(yPBW4R7_W$LO_|hBZlIi`^OlnqqSyI|VuhK(FoeD;=Of4Vpd{S5= zt;^P7u=r=6km{ABTJcNDahEQwwEh*cG^NAf@T;1u&Kea9cv?@ydPQ2UsTF2s*_j;})-8WayNt+3cvE{&ktw!P|w` zOm1&=;ykp)OZet8$)s6%YBTT5xhh@Q^n~9bvf%K;*mqm^m2gO`l5=zw{69k^D<;mT eBv$yotM?yp>^o&K9J~OEeeoh!i+~BB_yGVyZ1K|o literal 0 HcmV?d00001 diff --git a/worlds/kdl3/data/APPauseIcons.dat b/worlds/kdl3/data/APPauseIcons.dat new file mode 100644 index 0000000000000000000000000000000000000000..e7773a0e6718d35360e9c250119c2bc329b50379 GIT binary patch literal 448 zcmZuts|v$F5UlHGMfAhU5lV=vs0tOKBBCF0BCH5ip(>OUAw;Z*sE7!qgz4PTxZB}a z_I79XvEqXPH40chLClo@hKOe`xG{4=j%SRFFJ@vS;{25r4KnO;MjufyoNgdqoG@ks zbu;E?DBh8RlIot{pp0Zk)MMUWdrNi8PT-vF+HaK)K-H;D@5ZL0%fgsQd5x5Q!9#W=E_g4?{ EKW{sg`v3p{ literal 0 HcmV?d00001 diff --git a/worlds/kdl3/data/APStars.bsdiff4 b/worlds/kdl3/data/APStars.bsdiff4 new file mode 100644 index 0000000000000000000000000000000000000000..c335bae9cadaa0297ecaac90e06568a2bfd37ebb GIT binary patch literal 250 zcmZnt=tvaEi(>HFS(r3y$2Sqj83Tf%$+#h(G{?0)wJP<_iP1 zGQRdLu_r|yIyt@Au-E{hc(IVuLk0%Mzuo^jSaokP2)G?!Z~$sI*WiVloVq8$f_IlAwjdNSf8~ZLi%@Q;a)a-mv!ar-iLuaFd z>crW#j5qIlzC9OeIO$2!M(-`}!%vH>cQx4g^1j@H>+AjqmFjPCJDj!LL?O;eu*V`| egZPW4e|k^43KlB5skj~jIZwRE)goX5$Zr4uwN>Z< literal 0 HcmV?d00001 diff --git a/worlds/kdl3/data/Rooms.json b/worlds/kdl3/data/Rooms.json new file mode 100644 index 0000000000..47fe76534c --- /dev/null +++ b/worlds/kdl3/data/Rooms.json @@ -0,0 +1 @@ +[{"name": "Grass Land 1 - 0", "level": 1, "stage": 1, "room": 0, "pointer": 3434257, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Sir Kibble", "Cappy"], "default_exits": [{"room": 1, "unkn1": 205, "unkn2": 8, "x": 72, "y": 200, "name": "Grass Land 1 - 0 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 87, "unkn2": 9, "x": 104, "y": 152, "name": "Grass Land 1 - 0 Exit 1", "access_rule": []}], "entity_load": [[0, 16], [1, 23], [0, 23], [14, 23], [27, 16], [12, 16], [4, 22]], "locations": ["Grass Land 1 - Enemy 1 (Waddle Dee)", "Grass Land 1 - Enemy 2 (Sir Kibble)", "Grass Land 1 - Enemy 3 (Cappy)", "Grass Land 1 - Star 1", "Grass Land 1 - Star 2", "Grass Land 1 - Star 3", "Grass Land 1 - Star 4", "Grass Land 1 - Star 5", "Grass Land 1 - Star 6", "Grass Land 1 - Star 7", "Grass Land 1 - Star 8", "Grass Land 1 - Star 9", "Grass Land 1 - Star 10"], "music": 20}, {"name": "Grass Land 1 - 1", "level": 1, "stage": 1, "room": 1, "pointer": 3368373, "animal_pointers": [], "consumables": [{"idx": 14, "pointer": 264, "x": 928, "y": 160, "etype": 22, "vtype": 0, "name": "Grass Land 1 - 1-Up (Parasol)"}, {"idx": 15, "pointer": 312, "x": 1456, "y": 176, "etype": 22, "vtype": 2, "name": "Grass Land 1 - Maxim Tomato (Spark)"}], "consumables_pointer": 304, "enemies": ["Sparky", "Bronto Burt", "Sasuke"], "default_exits": [{"room": 3, "unkn1": 143, "unkn2": 6, "x": 56, "y": 152, "name": "Grass Land 1 - 1 Exit 0", "access_rule": []}], "entity_load": [[2, 16], [30, 16], [12, 16], [14, 23], [8, 16], [0, 22], [2, 22]], "locations": ["Grass Land 1 - Enemy 4 (Sparky)", "Grass Land 1 - Enemy 5 (Bronto Burt)", "Grass Land 1 - Enemy 6 (Sasuke)", "Grass Land 1 - Star 11", "Grass Land 1 - Star 12", "Grass Land 1 - Star 13", "Grass Land 1 - Star 14", "Grass Land 1 - Star 15", "Grass Land 1 - 1-Up (Parasol)", "Grass Land 1 - Maxim Tomato (Spark)"], "music": 20}, {"name": "Grass Land 1 - 2", "level": 1, "stage": 1, "room": 2, "pointer": 2960650, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 5, "unkn2": 9, "x": 1416, "y": 152, "name": "Grass Land 1 - 2 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 1 - Animal 1", "Grass Land 1 - Animal 2"], "music": 38}, {"name": "Grass Land 1 - 3", "level": 1, "stage": 1, "room": 3, "pointer": 3478442, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Poppy Bros Jr."], "default_exits": [{"room": 4, "unkn1": 179, "unkn2": 9, "x": 56, "y": 152, "name": "Grass Land 1 - 3 Exit 0", "access_rule": []}], "entity_load": [[0, 19], [7, 16], [0, 23], [6, 22], [14, 23], [8, 16], [1, 23]], "locations": ["Grass Land 1 - Enemy 7 (Poppy Bros Jr.)", "Grass Land 1 - Star 16", "Grass Land 1 - Star 17", "Grass Land 1 - Star 18", "Grass Land 1 - Star 19", "Grass Land 1 - Star 20", "Grass Land 1 - Star 21", "Grass Land 1 - Star 22", "Grass Land 1 - Star 23"], "music": 20}, {"name": "Grass Land 1 - 4", "level": 1, "stage": 1, "room": 4, "pointer": 2978390, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 5, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Grass Land 1 - 4 Exit 0", "access_rule": []}], "entity_load": [[0, 19], [42, 19]], "locations": ["Grass Land 1 - Tulip"], "music": 8}, {"name": "Grass Land 1 - 5", "level": 1, "stage": 1, "room": 5, "pointer": 2890835, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Grass Land 1 - Complete"], "music": 5}, {"name": "Grass Land 2 - 0", "level": 1, "stage": 2, "room": 0, "pointer": 3293347, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Rocky", "KeKe", "Bobo", "Poppy Bros Jr."], "default_exits": [{"room": 1, "unkn1": 112, "unkn2": 9, "x": 72, "y": 152, "name": "Grass Land 2 - 0 Exit 0", "access_rule": []}], "entity_load": [[3, 16], [7, 16], [5, 16], [4, 22], [51, 16], [14, 23]], "locations": ["Grass Land 2 - Enemy 1 (Rocky)", "Grass Land 2 - Enemy 2 (KeKe)", "Grass Land 2 - Enemy 3 (Bobo)", "Grass Land 2 - Enemy 4 (Poppy Bros Jr.)", "Grass Land 2 - Star 1", "Grass Land 2 - Star 2", "Grass Land 2 - Star 3", "Grass Land 2 - Star 4", "Grass Land 2 - Star 5", "Grass Land 2 - Star 6", "Grass Land 2 - Star 7", "Grass Land 2 - Star 8", "Grass Land 2 - Star 9", "Grass Land 2 - Star 10"], "music": 11}, {"name": "Grass Land 2 - 1", "level": 1, "stage": 2, "room": 1, "pointer": 3059685, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 20, "unkn2": 9, "x": 56, "y": 136, "name": "Grass Land 2 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 2 - Animal 1", "Grass Land 2 - Animal 2"], "music": 39}, {"name": "Grass Land 2 - 2", "level": 1, "stage": 2, "room": 2, "pointer": 3432109, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Popon Ball", "Bouncy"], "default_exits": [{"room": 4, "unkn1": 133, "unkn2": 11, "x": 72, "y": 200, "name": "Grass Land 2 - 2 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 52, "unkn2": 12, "x": 56, "y": 152, "name": "Grass Land 2 - 2 Exit 1", "access_rule": []}], "entity_load": [[13, 16], [50, 16], [4, 22], [3, 16], [0, 16], [14, 23]], "locations": ["Grass Land 2 - Enemy 5 (Waddle Dee)", "Grass Land 2 - Enemy 6 (Popon Ball)", "Grass Land 2 - Enemy 7 (Bouncy)", "Grass Land 2 - Star 11", "Grass Land 2 - Star 12", "Grass Land 2 - Star 13"], "music": 11}, {"name": "Grass Land 2 - 3", "level": 1, "stage": 2, "room": 3, "pointer": 2970029, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 2, "unkn2": 9, "x": 840, "y": 168, "name": "Grass Land 2 - 3 Exit 0", "access_rule": []}], "entity_load": [[1, 19]], "locations": [], "music": 11}, {"name": "Grass Land 2 - 4", "level": 1, "stage": 2, "room": 4, "pointer": 3578022, "animal_pointers": [], "consumables": [{"idx": 20, "pointer": 272, "x": 992, "y": 192, "etype": 22, "vtype": 0, "name": "Grass Land 2 - 1-Up (Needle)"}], "consumables_pointer": 352, "enemies": ["Tick", "Bronto Burt", "Nruff"], "default_exits": [{"room": 5, "unkn1": 154, "unkn2": 12, "x": 72, "y": 152, "name": "Grass Land 2 - 4 Exit 0", "access_rule": []}], "entity_load": [[15, 16], [5, 16], [2, 16], [48, 16], [14, 23], [0, 22]], "locations": ["Grass Land 2 - Enemy 8 (Tick)", "Grass Land 2 - Enemy 9 (Bronto Burt)", "Grass Land 2 - Enemy 10 (Nruff)", "Grass Land 2 - Star 14", "Grass Land 2 - Star 15", "Grass Land 2 - Star 16", "Grass Land 2 - Star 17", "Grass Land 2 - Star 18", "Grass Land 2 - Star 19", "Grass Land 2 - Star 20", "Grass Land 2 - Star 21", "Grass Land 2 - 1-Up (Needle)"], "music": 11}, {"name": "Grass Land 2 - 5", "level": 1, "stage": 2, "room": 5, "pointer": 2966057, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Grass Land 2 - 5 Exit 0", "access_rule": []}], "entity_load": [[1, 19], [42, 19]], "locations": ["Grass Land 2 - Muchimuchi"], "music": 8}, {"name": "Grass Land 2 - 6", "level": 1, "stage": 2, "room": 6, "pointer": 2887461, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Grass Land 2 - Complete"], "music": 5}, {"name": "Grass Land 3 - 0", "level": 1, "stage": 3, "room": 0, "pointer": 3149707, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sparky", "Rocky", "Nruff"], "default_exits": [{"room": 1, "unkn1": 107, "unkn2": 7, "x": 72, "y": 840, "name": "Grass Land 3 - 0 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 46, "unkn2": 9, "x": 152, "y": 152, "name": "Grass Land 3 - 0 Exit 1", "access_rule": []}], "entity_load": [[3, 16], [14, 23], [15, 16], [0, 16], [8, 16]], "locations": ["Grass Land 3 - Enemy 1 (Sparky)", "Grass Land 3 - Enemy 2 (Rocky)", "Grass Land 3 - Enemy 3 (Nruff)", "Grass Land 3 - Star 1", "Grass Land 3 - Star 2", "Grass Land 3 - Star 3", "Grass Land 3 - Star 4", "Grass Land 3 - Star 5", "Grass Land 3 - Star 6", "Grass Land 3 - Star 7", "Grass Land 3 - Star 8", "Grass Land 3 - Star 9", "Grass Land 3 - Star 10"], "music": 19}, {"name": "Grass Land 3 - 1", "level": 1, "stage": 3, "room": 1, "pointer": 3204939, "animal_pointers": [], "consumables": [{"idx": 10, "pointer": 360, "x": 208, "y": 344, "etype": 22, "vtype": 0, "name": "Grass Land 3 - 1-Up (Climb)"}, {"idx": 11, "pointer": 376, "x": 224, "y": 568, "etype": 22, "vtype": 2, "name": "Grass Land 3 - Maxim Tomato (Climb)"}], "consumables_pointer": 128, "enemies": [], "default_exits": [{"room": 3, "unkn1": 9, "unkn2": 9, "x": 56, "y": 152, "name": "Grass Land 3 - 1 Exit 0", "access_rule": []}], "entity_load": [[0, 22], [6, 23], [2, 22], [5, 23], [14, 23], [1, 23], [0, 23], [31, 16]], "locations": ["Grass Land 3 - Star 11", "Grass Land 3 - Star 12", "Grass Land 3 - Star 13", "Grass Land 3 - Star 14", "Grass Land 3 - Star 15", "Grass Land 3 - Star 16", "Grass Land 3 - 1-Up (Climb)", "Grass Land 3 - Maxim Tomato (Climb)"], "music": 19}, {"name": "Grass Land 3 - 2", "level": 1, "stage": 3, "room": 2, "pointer": 3200066, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 13, "unkn2": 55, "x": 56, "y": 152, "name": "Grass Land 3 - 2 Exit 0", "access_rule": []}], "entity_load": [[15, 16], [0, 16], [14, 23]], "locations": ["Grass Land 3 - Star 17", "Grass Land 3 - Star 18", "Grass Land 3 - Star 19", "Grass Land 3 - Star 20", "Grass Land 3 - Star 21", "Grass Land 3 - Star 22", "Grass Land 3 - Star 23", "Grass Land 3 - Star 24", "Grass Land 3 - Star 25", "Grass Land 3 - Star 26"], "music": 19}, {"name": "Grass Land 3 - 3", "level": 1, "stage": 3, "room": 3, "pointer": 2959784, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 15, "unkn2": 9, "x": 56, "y": 120, "name": "Grass Land 3 - 3 Exit 0", "access_rule": []}], "entity_load": [[2, 19]], "locations": [], "music": 31}, {"name": "Grass Land 3 - 4", "level": 1, "stage": 3, "room": 4, "pointer": 2979121, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 8, "unkn2": 9, "x": 760, "y": 152, "name": "Grass Land 3 - 4 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 3 - Animal 1", "Grass Land 3 - Animal 2"], "music": 40}, {"name": "Grass Land 3 - 5", "level": 1, "stage": 3, "room": 5, "pointer": 2997811, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 15, "unkn2": 9, "x": 72, "y": 120, "name": "Grass Land 3 - 5 Exit 0", "access_rule": []}], "entity_load": [[2, 19]], "locations": [], "music": 8}, {"name": "Grass Land 3 - 6", "level": 1, "stage": 3, "room": 6, "pointer": 3084876, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bouncy"], "default_exits": [{"room": 5, "unkn1": 96, "unkn2": 9, "x": 40, "y": 152, "name": "Grass Land 3 - 6 Exit 0", "access_rule": []}], "entity_load": [[13, 16], [14, 16], [1, 23], [59, 16], [14, 23]], "locations": ["Grass Land 3 - Pitcherman", "Grass Land 3 - Enemy 4 (Bouncy)", "Grass Land 3 - Star 27", "Grass Land 3 - Star 28", "Grass Land 3 - Star 29", "Grass Land 3 - Star 30", "Grass Land 3 - Star 31"], "music": 19}, {"name": "Grass Land 3 - 7", "level": 1, "stage": 3, "room": 7, "pointer": 2891317, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Grass Land 3 - Complete"], "music": 5}, {"name": "Grass Land 4 - 0", "level": 1, "stage": 4, "room": 0, "pointer": 3471284, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Loud", "Babut", "Rocky"], "default_exits": [{"room": 1, "unkn1": 145, "unkn2": 13, "x": 72, "y": 136, "name": "Grass Land 4 - 0 Exit 0", "access_rule": []}], "entity_load": [[3, 16], [43, 16], [14, 23], [40, 16], [61, 16], [4, 22]], "locations": ["Grass Land 4 - Enemy 1 (Loud)", "Grass Land 4 - Enemy 2 (Babut)", "Grass Land 4 - Enemy 3 (Rocky)", "Grass Land 4 - Star 1", "Grass Land 4 - Star 2", "Grass Land 4 - Star 3", "Grass Land 4 - Star 4", "Grass Land 4 - Star 5", "Grass Land 4 - Star 6", "Grass Land 4 - Star 7", "Grass Land 4 - Star 8", "Grass Land 4 - Star 9"], "music": 10}, {"name": "Grass Land 4 - 1", "level": 1, "stage": 4, "room": 1, "pointer": 3436401, "animal_pointers": [], "consumables": [{"idx": 12, "pointer": 290, "x": 1008, "y": 144, "etype": 22, "vtype": 2, "name": "Grass Land 4 - Maxim Tomato (Zebon Right)"}], "consumables_pointer": 368, "enemies": ["Kapar"], "default_exits": [{"room": 5, "unkn1": 58, "unkn2": 5, "x": 184, "y": 312, "name": "Grass Land 4 - 1 Exit 0", "access_rule": []}, {"room": 9, "unkn1": 42, "unkn2": 18, "x": 168, "y": 88, "name": "Grass Land 4 - 1 Exit 1", "access_rule": []}], "entity_load": [[43, 16], [10, 23], [6, 22], [14, 23], [2, 22], [67, 16]], "locations": ["Grass Land 4 - Enemy 4 (Kapar)", "Grass Land 4 - Star 10", "Grass Land 4 - Maxim Tomato (Zebon Right)"], "music": 10}, {"name": "Grass Land 4 - 2", "level": 1, "stage": 4, "room": 2, "pointer": 3039401, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 14, "unkn2": 4, "x": 56, "y": 72, "name": "Grass Land 4 - 2 Exit 0", "access_rule": []}], "entity_load": [[4, 27]], "locations": [], "music": 9}, {"name": "Grass Land 4 - 3", "level": 1, "stage": 4, "room": 3, "pointer": 3722714, "animal_pointers": [], "consumables": [{"idx": 23, "pointer": 280, "x": 856, "y": 224, "etype": 22, "vtype": 2, "name": "Grass Land 4 - Maxim Tomato (Gordo)"}, {"idx": 22, "pointer": 480, "x": 1352, "y": 112, "etype": 22, "vtype": 0, "name": "Grass Land 4 - 1-Up (Gordo)"}], "consumables_pointer": 288, "enemies": ["Glunk", "Oro"], "default_exits": [{"room": 4, "unkn1": 95, "unkn2": 5, "x": 72, "y": 200, "name": "Grass Land 4 - 3 Exit 0", "access_rule": []}], "entity_load": [[1, 16], [55, 16], [16, 16], [25, 16], [14, 23], [0, 22], [2, 22], [4, 22]], "locations": ["Grass Land 4 - Enemy 5 (Glunk)", "Grass Land 4 - Enemy 6 (Oro)", "Grass Land 4 - Star 11", "Grass Land 4 - Star 12", "Grass Land 4 - Star 13", "Grass Land 4 - Star 14", "Grass Land 4 - Star 15", "Grass Land 4 - Star 16", "Grass Land 4 - Star 17", "Grass Land 4 - Star 18", "Grass Land 4 - Star 19", "Grass Land 4 - Star 20", "Grass Land 4 - Star 21", "Grass Land 4 - Star 22", "Grass Land 4 - Star 23", "Grass Land 4 - Star 24", "Grass Land 4 - Star 25", "Grass Land 4 - Star 26", "Grass Land 4 - Maxim Tomato (Gordo)", "Grass Land 4 - 1-Up (Gordo)"], "music": 10}, {"name": "Grass Land 4 - 4", "level": 1, "stage": 4, "room": 4, "pointer": 3304980, "animal_pointers": [], "consumables": [{"idx": 32, "pointer": 208, "x": 488, "y": 64, "etype": 22, "vtype": 2, "name": "Grass Land 4 - Maxim Tomato (Cliff)"}], "consumables_pointer": 160, "enemies": [], "default_exits": [{"room": 8, "unkn1": 94, "unkn2": 9, "x": 40, "y": 152, "name": "Grass Land 4 - 4 Exit 0", "access_rule": []}], "entity_load": [[43, 16], [2, 22], [54, 16], [1, 16], [40, 16], [14, 23]], "locations": ["Grass Land 4 - Star 27", "Grass Land 4 - Maxim Tomato (Cliff)"], "music": 10}, {"name": "Grass Land 4 - 5", "level": 1, "stage": 4, "room": 5, "pointer": 3498127, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Peran"], "default_exits": [{"room": 2, "unkn1": 61, "unkn2": 13, "x": 56, "y": 72, "name": "Grass Land 4 - 5 Exit 0", "access_rule": []}, {"room": 7, "unkn1": 61, "unkn2": 18, "x": 56, "y": 200, "name": "Grass Land 4 - 5 Exit 1", "access_rule": ["Stone", "Stone Ability"]}], "entity_load": [[72, 16], [43, 16], [4, 22], [14, 23], [10, 23], [3, 16]], "locations": ["Grass Land 4 - Enemy 7 (Peran)", "Grass Land 4 - Star 28", "Grass Land 4 - Star 29", "Grass Land 4 - Star 30", "Grass Land 4 - Star 31", "Grass Land 4 - Star 32", "Grass Land 4 - Star 33", "Grass Land 4 - Star 34", "Grass Land 4 - Star 35", "Grass Land 4 - Star 36", "Grass Land 4 - Star 37"], "music": 10}, {"name": "Grass Land 4 - 6", "level": 1, "stage": 4, "room": 6, "pointer": 3160191, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 3, "unkn1": 28, "unkn2": 4, "x": 72, "y": 376, "name": "Grass Land 4 - 6 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 28, "unkn2": 12, "x": 72, "y": 440, "name": "Grass Land 4 - 6 Exit 1", "access_rule": []}], "entity_load": [[3, 19], [6, 23]], "locations": [], "music": 10}, {"name": "Grass Land 4 - 7", "level": 1, "stage": 4, "room": 7, "pointer": 3035801, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 14, "unkn2": 12, "x": 56, "y": 200, "name": "Grass Land 4 - 7 Exit 0", "access_rule": []}], "entity_load": [[4, 27]], "locations": ["Grass Land 4 - Miniboss 1 (Boboo)"], "music": 4}, {"name": "Grass Land 4 - 8", "level": 1, "stage": 4, "room": 8, "pointer": 2989794, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Grass Land 4 - 8 Exit 0", "access_rule": []}], "entity_load": [[3, 19], [42, 19]], "locations": ["Grass Land 4 - Chao & Goku"], "music": 8}, {"name": "Grass Land 4 - 9", "level": 1, "stage": 4, "room": 9, "pointer": 3043518, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 9, "unkn2": 5, "x": 696, "y": 296, "name": "Grass Land 4 - 9 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 4 - Animal 1", "Grass Land 4 - Animal 2"], "music": 38}, {"name": "Grass Land 4 - 10", "level": 1, "stage": 4, "room": 10, "pointer": 2888425, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Grass Land 4 - Complete"], "music": 5}, {"name": "Grass Land 5 - 0", "level": 1, "stage": 5, "room": 0, "pointer": 3303565, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller", "Broom Hatter", "Bouncy"], "default_exits": [{"room": 1, "unkn1": 120, "unkn2": 9, "x": 56, "y": 152, "name": "Grass Land 5 - 0 Exit 0", "access_rule": []}], "entity_load": [[13, 16], [4, 22], [14, 23], [6, 23], [11, 16], [89, 16]], "locations": ["Grass Land 5 - Enemy 1 (Propeller)", "Grass Land 5 - Enemy 2 (Broom Hatter)", "Grass Land 5 - Enemy 3 (Bouncy)", "Grass Land 5 - Star 1", "Grass Land 5 - Star 2", "Grass Land 5 - Star 3", "Grass Land 5 - Star 4", "Grass Land 5 - Star 5", "Grass Land 5 - Star 6", "Grass Land 5 - Star 7", "Grass Land 5 - Star 8"], "music": 11}, {"name": "Grass Land 5 - 1", "level": 1, "stage": 5, "room": 1, "pointer": 3048718, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sir Kibble"], "default_exits": [{"room": 3, "unkn1": 18, "unkn2": 4, "x": 184, "y": 152, "name": "Grass Land 5 - 1 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 21, "unkn2": 4, "x": 184, "y": 152, "name": "Grass Land 5 - 1 Exit 1", "access_rule": []}, {"room": 2, "unkn1": 36, "unkn2": 9, "x": 56, "y": 88, "name": "Grass Land 5 - 1 Exit 2", "access_rule": []}], "entity_load": [[27, 16], [14, 23]], "locations": ["Grass Land 5 - Enemy 4 (Sir Kibble)", "Grass Land 5 - Star 9", "Grass Land 5 - Star 10"], "music": 11}, {"name": "Grass Land 5 - 2", "level": 1, "stage": 5, "room": 2, "pointer": 3327019, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Sasuke", "Nruff"], "default_exits": [{"room": 7, "unkn1": 121, "unkn2": 6, "x": 56, "y": 72, "name": "Grass Land 5 - 2 Exit 0", "access_rule": []}], "entity_load": [[0, 16], [14, 23], [4, 22], [30, 16], [15, 16], [1, 16]], "locations": ["Grass Land 5 - Enemy 5 (Waddle Dee)", "Grass Land 5 - Enemy 6 (Sasuke)", "Grass Land 5 - Enemy 7 (Nruff)", "Grass Land 5 - Star 11", "Grass Land 5 - Star 12", "Grass Land 5 - Star 13", "Grass Land 5 - Star 14", "Grass Land 5 - Star 15", "Grass Land 5 - Star 16"], "music": 11}, {"name": "Grass Land 5 - 3", "level": 1, "stage": 5, "room": 3, "pointer": 2966459, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 10, "unkn2": 9, "x": 312, "y": 72, "name": "Grass Land 5 - 3 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 5 - Animal 1", "Grass Land 5 - Animal 2"], "music": 38}, {"name": "Grass Land 5 - 4", "level": 1, "stage": 5, "room": 4, "pointer": 2973509, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 10, "unkn2": 9, "x": 360, "y": 72, "name": "Grass Land 5 - 4 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 5 - Animal 3", "Grass Land 5 - Animal 4"], "music": 38}, {"name": "Grass Land 5 - 5", "level": 1, "stage": 5, "room": 5, "pointer": 2962351, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Grass Land 5 - 5 Exit 0", "access_rule": []}], "entity_load": [[4, 19], [42, 19]], "locations": ["Grass Land 5 - Mine"], "music": 8}, {"name": "Grass Land 5 - 6", "level": 1, "stage": 5, "room": 6, "pointer": 2886738, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Grass Land 5 - Complete"], "music": 5}, {"name": "Grass Land 5 - 7", "level": 1, "stage": 5, "room": 7, "pointer": 3255423, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Tick"], "default_exits": [{"room": 5, "unkn1": 96, "unkn2": 9, "x": 56, "y": 152, "name": "Grass Land 5 - 7 Exit 0", "access_rule": []}], "entity_load": [[48, 16], [4, 22], [14, 23]], "locations": ["Grass Land 5 - Enemy 8 (Tick)", "Grass Land 5 - Star 17", "Grass Land 5 - Star 18", "Grass Land 5 - Star 19", "Grass Land 5 - Star 20", "Grass Land 5 - Star 21", "Grass Land 5 - Star 22", "Grass Land 5 - Star 23", "Grass Land 5 - Star 24", "Grass Land 5 - Star 25", "Grass Land 5 - Star 26", "Grass Land 5 - Star 27", "Grass Land 5 - Star 28", "Grass Land 5 - Star 29"], "music": 11}, {"name": "Grass Land 6 - 0", "level": 1, "stage": 6, "room": 0, "pointer": 3376872, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Como", "Togezo", "Bronto Burt", "Cappy"], "default_exits": [{"room": 6, "unkn1": 51, "unkn2": 9, "x": 216, "y": 152, "name": "Grass Land 6 - 0 Exit 0", "access_rule": []}, {"room": 1, "unkn1": 96, "unkn2": 9, "x": 216, "y": 1144, "name": "Grass Land 6 - 0 Exit 1", "access_rule": []}], "entity_load": [[12, 16], [18, 16], [2, 16], [41, 16], [4, 22], [14, 23]], "locations": ["Grass Land 6 - Enemy 1 (Como)", "Grass Land 6 - Enemy 2 (Togezo)", "Grass Land 6 - Enemy 3 (Bronto Burt)", "Grass Land 6 - Enemy 4 (Cappy)", "Grass Land 6 - Star 1", "Grass Land 6 - Star 2", "Grass Land 6 - Star 3", "Grass Land 6 - Star 4", "Grass Land 6 - Star 5", "Grass Land 6 - Star 6", "Grass Land 6 - Star 7", "Grass Land 6 - Star 8", "Grass Land 6 - Star 9"], "music": 20}, {"name": "Grass Land 6 - 1", "level": 1, "stage": 6, "room": 1, "pointer": 3395125, "animal_pointers": [], "consumables": [{"idx": 10, "pointer": 192, "x": 104, "y": 1144, "etype": 22, "vtype": 0, "name": "Grass Land 6 - 1-Up (Tower)"}], "consumables_pointer": 256, "enemies": ["Bobo", "Mariel"], "default_exits": [{"room": 2, "unkn1": 16, "unkn2": 5, "x": 72, "y": 88, "name": "Grass Land 6 - 1 Exit 0", "access_rule": []}], "entity_load": [[5, 16], [5, 19], [45, 16], [0, 22], [4, 22], [14, 23], [55, 16]], "locations": ["Grass Land 6 - Enemy 5 (Bobo)", "Grass Land 6 - Enemy 6 (Mariel)", "Grass Land 6 - Star 10", "Grass Land 6 - Star 11", "Grass Land 6 - Star 12", "Grass Land 6 - Star 13", "Grass Land 6 - Star 14", "Grass Land 6 - 1-Up (Tower)"], "music": 20}, {"name": "Grass Land 6 - 2", "level": 1, "stage": 6, "room": 2, "pointer": 3375177, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Yaban", "Broom Hatter"], "default_exits": [{"room": 3, "unkn1": 93, "unkn2": 6, "x": 200, "y": 152, "name": "Grass Land 6 - 2 Exit 0", "access_rule": []}, {"room": 7, "unkn1": 49, "unkn2": 7, "x": 216, "y": 104, "name": "Grass Land 6 - 2 Exit 1", "access_rule": []}], "entity_load": [[11, 16], [45, 16], [41, 16], [4, 22], [32, 16], [14, 23]], "locations": ["Grass Land 6 - Enemy 7 (Yaban)", "Grass Land 6 - Enemy 8 (Broom Hatter)", "Grass Land 6 - Star 15", "Grass Land 6 - Star 16"], "music": 20}, {"name": "Grass Land 6 - 3", "level": 1, "stage": 6, "room": 3, "pointer": 3322977, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Apolo", "Sasuke"], "default_exits": [{"room": 4, "unkn1": 12, "unkn2": 52, "x": 72, "y": 104, "name": "Grass Land 6 - 3 Exit 0", "access_rule": []}], "entity_load": [[41, 16], [49, 16], [30, 16], [14, 23], [4, 22]], "locations": ["Grass Land 6 - Enemy 9 (Apolo)", "Grass Land 6 - Enemy 10 (Sasuke)", "Grass Land 6 - Star 17", "Grass Land 6 - Star 18", "Grass Land 6 - Star 19", "Grass Land 6 - Star 20", "Grass Land 6 - Star 21", "Grass Land 6 - Star 22", "Grass Land 6 - Star 23", "Grass Land 6 - Star 24", "Grass Land 6 - Star 25"], "music": 20}, {"name": "Grass Land 6 - 4", "level": 1, "stage": 6, "room": 4, "pointer": 3490819, "animal_pointers": [], "consumables": [{"idx": 33, "pointer": 192, "x": 40, "y": 104, "etype": 22, "vtype": 1, "name": "Grass Land 6 - 1-Up (Falling)"}], "consumables_pointer": 176, "enemies": ["Rocky"], "default_exits": [{"room": 5, "unkn1": 145, "unkn2": 6, "x": 56, "y": 152, "name": "Grass Land 6 - 4 Exit 0", "access_rule": []}], "entity_load": [[5, 16], [4, 22], [49, 16], [61, 16], [3, 16], [1, 22], [14, 23]], "locations": ["Grass Land 6 - Enemy 11 (Rocky)", "Grass Land 6 - Star 26", "Grass Land 6 - Star 27", "Grass Land 6 - Star 28", "Grass Land 6 - Star 29", "Grass Land 6 - 1-Up (Falling)"], "music": 20}, {"name": "Grass Land 6 - 5", "level": 1, "stage": 6, "room": 5, "pointer": 3076769, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Grass Land 6 - 5 Exit 0", "access_rule": []}], "entity_load": [[5, 19], [42, 19]], "locations": ["Grass Land 6 - Pierre"], "music": 8}, {"name": "Grass Land 6 - 6", "level": 1, "stage": 6, "room": 6, "pointer": 3047576, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 12, "unkn2": 9, "x": 840, "y": 152, "name": "Grass Land 6 - 6 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 6 - Animal 1", "Grass Land 6 - Animal 2"], "music": 39}, {"name": "Grass Land 6 - 7", "level": 1, "stage": 6, "room": 7, "pointer": 3022909, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 12, "unkn2": 6, "x": 808, "y": 120, "name": "Grass Land 6 - 7 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Grass Land 6 - Animal 3", "Grass Land 6 - Animal 4"], "music": 38}, {"name": "Grass Land 6 - 8", "level": 1, "stage": 6, "room": 8, "pointer": 2884569, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Grass Land 6 - Complete"], "music": 5}, {"name": "Grass Land Boss - 0", "level": 1, "stage": 7, "room": 0, "pointer": 2984105, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[0, 18]], "locations": ["Grass Land - Boss (Whispy Woods) Purified", "Level 1 Boss - Defeated", "Level 1 Boss - Purified"], "music": 2}, {"name": "Ripple Field 1 - 0", "level": 2, "stage": 1, "room": 0, "pointer": 3279855, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Glunk", "Broom Hatter", "Cappy"], "default_exits": [{"room": 2, "unkn1": 102, "unkn2": 8, "x": 56, "y": 152, "name": "Ripple Field 1 - 0 Exit 0", "access_rule": []}], "entity_load": [[16, 16], [0, 16], [12, 16], [11, 16], [14, 23]], "locations": ["Ripple Field 1 - Enemy 1 (Waddle Dee)", "Ripple Field 1 - Enemy 2 (Glunk)", "Ripple Field 1 - Enemy 3 (Broom Hatter)", "Ripple Field 1 - Enemy 4 (Cappy)", "Ripple Field 1 - Star 1", "Ripple Field 1 - Star 2", "Ripple Field 1 - Star 3", "Ripple Field 1 - Star 4", "Ripple Field 1 - Star 5", "Ripple Field 1 - Star 6"], "music": 15}, {"name": "Ripple Field 1 - 1", "level": 2, "stage": 1, "room": 1, "pointer": 3588688, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bronto Burt", "Rocky", "Poppy Bros Jr."], "default_exits": [{"room": 3, "unkn1": 146, "unkn2": 11, "x": 40, "y": 232, "name": "Ripple Field 1 - 1 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 73, "unkn2": 16, "x": 200, "y": 184, "name": "Ripple Field 1 - 1 Exit 1", "access_rule": []}, {"room": 6, "unkn1": 108, "unkn2": 16, "x": 200, "y": 184, "name": "Ripple Field 1 - 1 Exit 2", "access_rule": []}, {"room": 7, "unkn1": 138, "unkn2": 16, "x": 200, "y": 184, "name": "Ripple Field 1 - 1 Exit 3", "access_rule": []}], "entity_load": [[11, 16], [2, 16], [3, 16], [7, 16]], "locations": ["Ripple Field 1 - Enemy 5 (Bronto Burt)", "Ripple Field 1 - Enemy 6 (Rocky)", "Ripple Field 1 - Enemy 7 (Poppy Bros Jr.)"], "music": 15}, {"name": "Ripple Field 1 - 2", "level": 2, "stage": 1, "room": 2, "pointer": 2955848, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 18, "unkn2": 9, "x": 56, "y": 168, "name": "Ripple Field 1 - 2 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 1 - Animal 1", "Ripple Field 1 - Animal 2"], "music": 40}, {"name": "Ripple Field 1 - 3", "level": 2, "stage": 1, "room": 3, "pointer": 3558828, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bobin"], "default_exits": [{"room": 4, "unkn1": 171, "unkn2": 5, "x": 40, "y": 152, "name": "Ripple Field 1 - 3 Exit 0", "access_rule": []}], "entity_load": [[73, 16], [6, 22], [14, 23], [4, 22], [0, 16], [10, 23]], "locations": ["Ripple Field 1 - Enemy 8 (Bobin)", "Ripple Field 1 - Star 7", "Ripple Field 1 - Star 8", "Ripple Field 1 - Star 9", "Ripple Field 1 - Star 10", "Ripple Field 1 - Star 11", "Ripple Field 1 - Star 12", "Ripple Field 1 - Star 13", "Ripple Field 1 - Star 14", "Ripple Field 1 - Star 15", "Ripple Field 1 - Star 16"], "music": 15}, {"name": "Ripple Field 1 - 4", "level": 2, "stage": 1, "room": 4, "pointer": 2974271, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Ripple Field 1 - 4 Exit 0", "access_rule": []}], "entity_load": [[7, 19], [42, 19]], "locations": ["Ripple Field 1 - Kamuribana"], "music": 8}, {"name": "Ripple Field 1 - 5", "level": 2, "stage": 1, "room": 5, "pointer": 3051513, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 11, "unkn2": 11, "x": 1192, "y": 264, "name": "Ripple Field 1 - 5 Exit 0", "access_rule": []}], "entity_load": [[7, 19], [14, 23]], "locations": ["Ripple Field 1 - Star 17", "Ripple Field 1 - Star 18"], "music": 15}, {"name": "Ripple Field 1 - 6", "level": 2, "stage": 1, "room": 6, "pointer": 3049838, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 11, "unkn2": 11, "x": 1752, "y": 264, "name": "Ripple Field 1 - 6 Exit 0", "access_rule": []}], "entity_load": [[7, 19], [14, 23]], "locations": ["Ripple Field 1 - Star 19"], "music": 15}, {"name": "Ripple Field 1 - 7", "level": 2, "stage": 1, "room": 7, "pointer": 3066407, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 11, "unkn2": 11, "x": 2232, "y": 264, "name": "Ripple Field 1 - 7 Exit 0", "access_rule": []}], "entity_load": [[7, 19]], "locations": [], "music": 15}, {"name": "Ripple Field 1 - 8", "level": 2, "stage": 1, "room": 8, "pointer": 2889148, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Ripple Field 1 - Complete"], "music": 5}, {"name": "Ripple Field 2 - 0", "level": 2, "stage": 2, "room": 0, "pointer": 3342336, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Togezo", "Coconut", "Blipper", "Sasuke"], "default_exits": [{"room": 1, "unkn1": 103, "unkn2": 15, "x": 56, "y": 104, "name": "Ripple Field 2 - 0 Exit 0", "access_rule": []}], "entity_load": [[5, 22], [34, 16], [30, 16], [21, 16], [14, 23], [4, 22], [18, 16]], "locations": ["Ripple Field 2 - Enemy 1 (Togezo)", "Ripple Field 2 - Enemy 2 (Coconut)", "Ripple Field 2 - Enemy 3 (Blipper)", "Ripple Field 2 - Enemy 4 (Sasuke)", "Ripple Field 2 - Star 1", "Ripple Field 2 - Star 2", "Ripple Field 2 - Star 3", "Ripple Field 2 - Star 4"], "music": 10}, {"name": "Ripple Field 2 - 1", "level": 2, "stage": 2, "room": 1, "pointer": 3084099, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 3, "unkn1": 23, "unkn2": 8, "x": 72, "y": 248, "name": "Ripple Field 2 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 2 - Animal 1", "Ripple Field 2 - Animal 2"], "music": 39}, {"name": "Ripple Field 2 - 2", "level": 2, "stage": 2, "room": 2, "pointer": 3451207, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Kany"], "default_exits": [{"room": 4, "unkn1": 31, "unkn2": 5, "x": 72, "y": 152, "name": "Ripple Field 2 - 2 Exit 0", "access_rule": []}, {"room": 6, "unkn1": 96, "unkn2": 6, "x": 56, "y": 152, "name": "Ripple Field 2 - 2 Exit 1", "access_rule": []}, {"room": 5, "unkn1": 56, "unkn2": 17, "x": 136, "y": 264, "name": "Ripple Field 2 - 2 Exit 2", "access_rule": []}], "entity_load": [[29, 16], [47, 16], [1, 16], [46, 16], [14, 23]], "locations": ["Ripple Field 2 - Enemy 5 (Kany)", "Ripple Field 2 - Star 5", "Ripple Field 2 - Star 6", "Ripple Field 2 - Star 7", "Ripple Field 2 - Star 8"], "music": 10}, {"name": "Ripple Field 2 - 3", "level": 2, "stage": 2, "room": 3, "pointer": 3674327, "animal_pointers": [], "consumables": [{"idx": 11, "pointer": 480, "x": 1384, "y": 200, "etype": 22, "vtype": 2, "name": "Ripple Field 2 - Maxim Tomato (Currents)"}, {"idx": 10, "pointer": 520, "x": 1456, "y": 200, "etype": 22, "vtype": 0, "name": "Ripple Field 2 - 1-Up (Currents)"}], "consumables_pointer": 128, "enemies": ["Glunk"], "default_exits": [{"room": 2, "unkn1": 134, "unkn2": 5, "x": 40, "y": 136, "name": "Ripple Field 2 - 3 Exit 0", "access_rule": []}], "entity_load": [[0, 22], [2, 22], [14, 23], [16, 16], [21, 16], [4, 22]], "locations": ["Ripple Field 2 - Enemy 6 (Glunk)", "Ripple Field 2 - Star 9", "Ripple Field 2 - Star 10", "Ripple Field 2 - Star 11", "Ripple Field 2 - Star 12", "Ripple Field 2 - Star 13", "Ripple Field 2 - Star 14", "Ripple Field 2 - Star 15", "Ripple Field 2 - Star 16", "Ripple Field 2 - Star 17", "Ripple Field 2 - Maxim Tomato (Currents)", "Ripple Field 2 - 1-Up (Currents)"], "music": 10}, {"name": "Ripple Field 2 - 4", "level": 2, "stage": 2, "room": 4, "pointer": 2972744, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 3, "unkn2": 9, "x": 520, "y": 88, "name": "Ripple Field 2 - 4 Exit 0", "access_rule": []}], "entity_load": [[8, 19]], "locations": [], "music": 10}, {"name": "Ripple Field 2 - 5", "level": 2, "stage": 2, "room": 5, "pointer": 3109710, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 16, "unkn2": 16, "x": 1048, "y": 280, "name": "Ripple Field 2 - 5 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 2 - Animal 3", "Ripple Field 2 - Animal 4"], "music": 38}, {"name": "Ripple Field 2 - 6", "level": 2, "stage": 2, "room": 6, "pointer": 2973127, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Ripple Field 2 - 6 Exit 0", "access_rule": []}], "entity_load": [[8, 19], [42, 19]], "locations": ["Ripple Field 2 - Bakasa"], "music": 8}, {"name": "Ripple Field 2 - 7", "level": 2, "stage": 2, "room": 7, "pointer": 2890353, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Ripple Field 2 - Complete"], "music": 5}, {"name": "Ripple Field 3 - 0", "level": 2, "stage": 3, "room": 0, "pointer": 3517254, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Raft Waddle Dee", "Kapar", "Blipper"], "default_exits": [{"room": 3, "unkn1": 105, "unkn2": 8, "x": 40, "y": 104, "name": "Ripple Field 3 - 0 Exit 0", "access_rule": []}], "entity_load": [[21, 16], [57, 16], [62, 16], [67, 16], [4, 22], [14, 23]], "locations": ["Ripple Field 3 - Enemy 1 (Raft Waddle Dee)", "Ripple Field 3 - Enemy 2 (Kapar)", "Ripple Field 3 - Enemy 3 (Blipper)", "Ripple Field 3 - Star 1", "Ripple Field 3 - Star 2", "Ripple Field 3 - Star 3", "Ripple Field 3 - Star 4"], "music": 18}, {"name": "Ripple Field 3 - 1", "level": 2, "stage": 3, "room": 1, "pointer": 3604480, "animal_pointers": [], "consumables": [{"idx": 10, "pointer": 320, "x": 832, "y": 152, "etype": 22, "vtype": 2, "name": "Ripple Field 3 - Maxim Tomato (Cove)"}, {"idx": 13, "pointer": 424, "x": 1128, "y": 384, "etype": 22, "vtype": 0, "name": "Ripple Field 3 - 1-Up (Cutter/Spark)"}], "consumables_pointer": 160, "enemies": ["Sparky", "Glunk", "Joe"], "default_exits": [{"room": 7, "unkn1": 80, "unkn2": 24, "x": 104, "y": 328, "name": "Ripple Field 3 - 1 Exit 0", "access_rule": []}], "entity_load": [[13, 23], [14, 23], [91, 16], [16, 16], [2, 22], [8, 16], [0, 22]], "locations": ["Ripple Field 3 - Enemy 4 (Sparky)", "Ripple Field 3 - Enemy 5 (Glunk)", "Ripple Field 3 - Enemy 6 (Joe)", "Ripple Field 3 - Star 5", "Ripple Field 3 - Star 6", "Ripple Field 3 - Star 7", "Ripple Field 3 - Star 8", "Ripple Field 3 - Star 9", "Ripple Field 3 - Star 10", "Ripple Field 3 - Star 11", "Ripple Field 3 - Maxim Tomato (Cove)", "Ripple Field 3 - 1-Up (Cutter/Spark)"], "music": 18}, {"name": "Ripple Field 3 - 2", "level": 2, "stage": 3, "room": 2, "pointer": 3715428, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bobo"], "default_exits": [{"room": 4, "unkn1": 118, "unkn2": 9, "x": 56, "y": 152, "name": "Ripple Field 3 - 2 Exit 0", "access_rule": []}], "entity_load": [[91, 16], [16, 16], [21, 16], [4, 22], [46, 16], [47, 16], [5, 16], [14, 23]], "locations": ["Ripple Field 3 - Enemy 7 (Bobo)", "Ripple Field 3 - Star 12", "Ripple Field 3 - Star 13", "Ripple Field 3 - Star 14", "Ripple Field 3 - Star 15", "Ripple Field 3 - Star 16", "Ripple Field 3 - Star 17", "Ripple Field 3 - Star 18", "Ripple Field 3 - Star 19", "Ripple Field 3 - Star 20", "Ripple Field 3 - Star 21"], "music": 18}, {"name": "Ripple Field 3 - 3", "level": 2, "stage": 3, "room": 3, "pointer": 3071919, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 15, "unkn2": 6, "x": 56, "y": 104, "name": "Ripple Field 3 - 3 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 3 - Animal 1", "Ripple Field 3 - Animal 2"], "music": 39}, {"name": "Ripple Field 3 - 4", "level": 2, "stage": 3, "room": 4, "pointer": 2970810, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Ripple Field 3 - 4 Exit 0", "access_rule": []}], "entity_load": [[9, 19]], "locations": ["Ripple Field 3 - Elieel"], "music": 8}, {"name": "Ripple Field 3 - 5", "level": 2, "stage": 3, "room": 5, "pointer": 2987502, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 15, "unkn2": 9, "x": 232, "y": 88, "name": "Ripple Field 3 - 5 Exit 0", "access_rule": []}], "entity_load": [[9, 19]], "locations": [], "music": 31}, {"name": "Ripple Field 3 - 6", "level": 2, "stage": 3, "room": 6, "pointer": 2888666, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Ripple Field 3 - Complete"], "music": 5}, {"name": "Ripple Field 3 - 7", "level": 2, "stage": 3, "room": 7, "pointer": 3161120, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 5, "unkn1": 3, "unkn2": 5, "x": 40, "y": 152, "name": "Ripple Field 3 - 7 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 11, "unkn2": 20, "x": 56, "y": 216, "name": "Ripple Field 3 - 7 Exit 1", "access_rule": []}], "entity_load": [[57, 16]], "locations": [], "music": 18}, {"name": "Ripple Field 4 - 0", "level": 2, "stage": 4, "room": 0, "pointer": 3082540, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Stone)", "Bukiset (Needle)", "Bukiset (Clean)", "Bukiset (Parasol)", "Mony"], "default_exits": [{"room": 6, "unkn1": 4, "unkn2": 16, "x": 72, "y": 232, "name": "Ripple Field 4 - 0 Exit 0", "access_rule": []}], "entity_load": [[14, 23], [79, 16], [80, 16], [81, 16], [4, 22], [20, 16], [77, 16]], "locations": ["Ripple Field 4 - Enemy 1 (Bukiset (Stone))", "Ripple Field 4 - Enemy 2 (Bukiset (Needle))", "Ripple Field 4 - Enemy 3 (Bukiset (Clean))", "Ripple Field 4 - Enemy 4 (Bukiset (Parasol))", "Ripple Field 4 - Enemy 5 (Mony)", "Ripple Field 4 - Star 1", "Ripple Field 4 - Star 2", "Ripple Field 4 - Star 3", "Ripple Field 4 - Star 4", "Ripple Field 4 - Star 5"], "music": 15}, {"name": "Ripple Field 4 - 1", "level": 2, "stage": 4, "room": 1, "pointer": 2964846, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 14, "unkn2": 8, "x": 72, "y": 88, "name": "Ripple Field 4 - 1 Exit 0", "access_rule": []}], "entity_load": [[0, 27]], "locations": ["Ripple Field 4 - Miniboss 1 (Captain Stitch)"], "music": 4}, {"name": "Ripple Field 4 - 2", "level": 2, "stage": 4, "room": 2, "pointer": 3018503, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Burning)"], "default_exits": [{"room": 11, "unkn1": 25, "unkn2": 5, "x": 56, "y": 88, "name": "Ripple Field 4 - 2 Exit 0", "access_rule": []}, {"room": 11, "unkn1": 25, "unkn2": 15, "x": 56, "y": 216, "name": "Ripple Field 4 - 2 Exit 1", "access_rule": []}], "entity_load": [[10, 19], [76, 16]], "locations": ["Ripple Field 4 - Enemy 6 (Bukiset (Burning))"], "music": 15}, {"name": "Ripple Field 4 - 3", "level": 2, "stage": 4, "room": 3, "pointer": 2988166, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Ripple Field 4 - 3 Exit 0", "access_rule": []}], "entity_load": [[10, 19], [42, 19]], "locations": ["Ripple Field 4 - Toad & Little Toad"], "music": 8}, {"name": "Ripple Field 4 - 4", "level": 2, "stage": 4, "room": 4, "pointer": 2885533, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Ripple Field 4 - Complete"], "music": 5}, {"name": "Ripple Field 4 - 5", "level": 2, "stage": 4, "room": 5, "pointer": 3042349, "animal_pointers": [222, 230, 238], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 5, "unkn2": 5, "x": 360, "y": 120, "name": "Ripple Field 4 - 5 Exit 0", "access_rule": []}, {"room": 6, "unkn1": 15, "unkn2": 5, "x": 488, "y": 120, "name": "Ripple Field 4 - 5 Exit 1", "access_rule": []}, {"room": 5, "unkn1": 16, "unkn2": 5, "x": 104, "y": 88, "name": "Ripple Field 4 - 5 Exit 2", "access_rule": []}, {"room": 6, "unkn1": 10, "unkn2": 11, "x": 440, "y": 216, "name": "Ripple Field 4 - 5 Exit 3", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 4 - Animal 1", "Ripple Field 4 - Animal 2", "Ripple Field 4 - Animal 3"], "music": 40}, {"name": "Ripple Field 4 - 6", "level": 2, "stage": 4, "room": 6, "pointer": 3234805, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bobin", "Blipper"], "default_exits": [{"room": 5, "unkn1": 21, "unkn2": 7, "x": 104, "y": 88, "name": "Ripple Field 4 - 6 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 31, "unkn2": 7, "x": 232, "y": 88, "name": "Ripple Field 4 - 6 Exit 1", "access_rule": []}, {"room": 5, "unkn1": 26, "unkn2": 13, "x": 184, "y": 184, "name": "Ripple Field 4 - 6 Exit 2", "access_rule": []}, {"room": 12, "unkn1": 48, "unkn2": 15, "x": 88, "y": 216, "name": "Ripple Field 4 - 6 Exit 3", "access_rule": []}], "entity_load": [[73, 16], [14, 23], [21, 16], [13, 23]], "locations": ["Ripple Field 4 - Enemy 7 (Bobin)", "Ripple Field 4 - Enemy 8 (Blipper)", "Ripple Field 4 - Star 6", "Ripple Field 4 - Star 7"], "music": 15}, {"name": "Ripple Field 4 - 7", "level": 2, "stage": 4, "room": 7, "pointer": 3155468, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 11, "unkn2": 6, "x": 104, "y": 136, "name": "Ripple Field 4 - 7 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 18, "unkn2": 9, "x": 72, "y": 248, "name": "Ripple Field 4 - 7 Exit 1", "access_rule": []}], "entity_load": [[14, 23], [1, 16], [0, 23]], "locations": ["Ripple Field 4 - Star 8", "Ripple Field 4 - Star 9", "Ripple Field 4 - Star 10", "Ripple Field 4 - Star 11", "Ripple Field 4 - Star 12", "Ripple Field 4 - Star 13", "Ripple Field 4 - Star 14", "Ripple Field 4 - Star 15", "Ripple Field 4 - Star 16", "Ripple Field 4 - Star 17", "Ripple Field 4 - Star 18", "Ripple Field 4 - Star 19", "Ripple Field 4 - Star 20", "Ripple Field 4 - Star 21"], "music": 15}, {"name": "Ripple Field 4 - 8", "level": 2, "stage": 4, "room": 8, "pointer": 3350031, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Como", "Oro"], "default_exits": [{"room": 7, "unkn1": 24, "unkn2": 22, "x": 184, "y": 440, "name": "Ripple Field 4 - 8 Exit 0", "access_rule": []}, {"room": 7, "unkn1": 34, "unkn2": 22, "x": 296, "y": 440, "name": "Ripple Field 4 - 8 Exit 1", "access_rule": []}, {"room": 9, "unkn1": 16, "unkn2": 72, "x": 168, "y": 152, "name": "Ripple Field 4 - 8 Exit 2", "access_rule": []}, {"room": 10, "unkn1": 23, "unkn2": 72, "x": 120, "y": 152, "name": "Ripple Field 4 - 8 Exit 3", "access_rule": []}], "entity_load": [[41, 16], [68, 16], [14, 23], [25, 16], [6, 23]], "locations": ["Ripple Field 4 - Enemy 9 (Como)", "Ripple Field 4 - Enemy 10 (Oro)", "Ripple Field 4 - Star 22", "Ripple Field 4 - Star 23", "Ripple Field 4 - Star 24", "Ripple Field 4 - Star 25", "Ripple Field 4 - Star 26", "Ripple Field 4 - Star 27", "Ripple Field 4 - Star 28"], "music": 15}, {"name": "Ripple Field 4 - 9", "level": 2, "stage": 4, "room": 9, "pointer": 3050397, "animal_pointers": [], "consumables": [{"idx": 29, "pointer": 200, "x": 88, "y": 200, "etype": 22, "vtype": 2, "name": "Ripple Field 4 - Maxim Tomato (Stone)"}], "consumables_pointer": 176, "enemies": ["Gansan"], "default_exits": [{"room": 8, "unkn1": 11, "unkn2": 9, "x": 264, "y": 1144, "name": "Ripple Field 4 - 9 Exit 0", "access_rule": []}], "entity_load": [[75, 16], [2, 22]], "locations": ["Ripple Field 4 - Enemy 11 (Gansan)", "Ripple Field 4 - Maxim Tomato (Stone)"], "music": 15}, {"name": "Ripple Field 4 - 10", "level": 2, "stage": 4, "room": 10, "pointer": 3052069, "animal_pointers": [], "consumables": [{"idx": 30, "pointer": 192, "x": 200, "y": 200, "etype": 22, "vtype": 0, "name": "Ripple Field 4 - 1-Up (Stone)"}], "consumables_pointer": 128, "enemies": [], "default_exits": [{"room": 8, "unkn1": 6, "unkn2": 9, "x": 376, "y": 1144, "name": "Ripple Field 4 - 10 Exit 0", "access_rule": []}], "entity_load": [[0, 22], [75, 16]], "locations": ["Ripple Field 4 - 1-Up (Stone)"], "music": 15}, {"name": "Ripple Field 4 - 11", "level": 2, "stage": 4, "room": 11, "pointer": 3386974, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Kapar", "Squishy"], "default_exits": [{"room": 3, "unkn1": 146, "unkn2": 13, "x": 72, "y": 152, "name": "Ripple Field 4 - 11 Exit 0", "access_rule": []}], "entity_load": [[22, 16], [67, 16], [14, 23], [62, 16], [0, 16]], "locations": ["Ripple Field 4 - Enemy 12 (Waddle Dee)", "Ripple Field 4 - Enemy 13 (Kapar)", "Ripple Field 4 - Enemy 14 (Squishy)", "Ripple Field 4 - Star 29", "Ripple Field 4 - Star 30", "Ripple Field 4 - Star 31", "Ripple Field 4 - Star 32", "Ripple Field 4 - Star 33", "Ripple Field 4 - Star 34", "Ripple Field 4 - Star 35", "Ripple Field 4 - Star 36", "Ripple Field 4 - Star 37", "Ripple Field 4 - Star 38", "Ripple Field 4 - Star 39", "Ripple Field 4 - Star 40", "Ripple Field 4 - Star 41", "Ripple Field 4 - Star 42", "Ripple Field 4 - Star 43"], "music": 15}, {"name": "Ripple Field 4 - 12", "level": 2, "stage": 4, "room": 12, "pointer": 3168339, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Nidoo"], "default_exits": [{"room": 8, "unkn1": 67, "unkn2": 7, "x": 88, "y": 1224, "name": "Ripple Field 4 - 12 Exit 0", "access_rule": []}, {"room": 13, "unkn1": 75, "unkn2": 7, "x": 88, "y": 136, "name": "Ripple Field 4 - 12 Exit 1", "access_rule": []}], "entity_load": [[59, 16], [13, 23], [28, 16]], "locations": ["Ripple Field 4 - Enemy 15 (Nidoo)"], "music": 15}, {"name": "Ripple Field 4 - 13", "level": 2, "stage": 4, "room": 13, "pointer": 2958478, "animal_pointers": [], "consumables": [{"idx": 54, "pointer": 264, "x": 216, "y": 136, "etype": 22, "vtype": 2, "name": "Ripple Field 4 - Maxim Tomato (Dark)"}], "consumables_pointer": 128, "enemies": [], "default_exits": [{"room": 12, "unkn1": 4, "unkn2": 8, "x": 1192, "y": 120, "name": "Ripple Field 4 - 13 Exit 0", "access_rule": []}], "entity_load": [[14, 23], [2, 22], [59, 16]], "locations": ["Ripple Field 4 - Star 44", "Ripple Field 4 - Star 45", "Ripple Field 4 - Star 46", "Ripple Field 4 - Star 47", "Ripple Field 4 - Star 48", "Ripple Field 4 - Star 49", "Ripple Field 4 - Star 50", "Ripple Field 4 - Star 51", "Ripple Field 4 - Maxim Tomato (Dark)"], "music": 15}, {"name": "Ripple Field 5 - 0", "level": 2, "stage": 5, "room": 0, "pointer": 3240369, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 9, "unkn2": 43, "x": 88, "y": 344, "name": "Ripple Field 5 - 0 Exit 0", "access_rule": []}], "entity_load": [[14, 23]], "locations": ["Ripple Field 5 - Star 1", "Ripple Field 5 - Star 2", "Ripple Field 5 - Star 3", "Ripple Field 5 - Star 4", "Ripple Field 5 - Star 5", "Ripple Field 5 - Star 6"], "music": 16}, {"name": "Ripple Field 5 - 1", "level": 2, "stage": 5, "room": 1, "pointer": 3547528, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 15, "unkn2": 4, "x": 184, "y": 344, "name": "Ripple Field 5 - 1 Exit 0", "access_rule": []}], "entity_load": [[1, 16], [14, 23]], "locations": ["Ripple Field 5 - Star 7", "Ripple Field 5 - Star 8", "Ripple Field 5 - Star 9", "Ripple Field 5 - Star 10", "Ripple Field 5 - Star 11", "Ripple Field 5 - Star 12", "Ripple Field 5 - Star 13", "Ripple Field 5 - Star 14", "Ripple Field 5 - Star 15", "Ripple Field 5 - Star 16", "Ripple Field 5 - Star 17"], "music": 16}, {"name": "Ripple Field 5 - 2", "level": 2, "stage": 5, "room": 2, "pointer": 3611327, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Glunk", "Joe"], "default_exits": [{"room": 4, "unkn1": 95, "unkn2": 21, "x": 56, "y": 184, "name": "Ripple Field 5 - 2 Exit 0", "access_rule": []}], "entity_load": [[91, 16], [16, 16], [14, 23]], "locations": ["Ripple Field 5 - Enemy 1 (Glunk)", "Ripple Field 5 - Enemy 2 (Joe)", "Ripple Field 5 - Star 18", "Ripple Field 5 - Star 19", "Ripple Field 5 - Star 20", "Ripple Field 5 - Star 21", "Ripple Field 5 - Star 22", "Ripple Field 5 - Star 23", "Ripple Field 5 - Star 24", "Ripple Field 5 - Star 25", "Ripple Field 5 - Star 26", "Ripple Field 5 - Star 27", "Ripple Field 5 - Star 28", "Ripple Field 5 - Star 29", "Ripple Field 5 - Star 30", "Ripple Field 5 - Star 31"], "music": 16}, {"name": "Ripple Field 5 - 3", "level": 2, "stage": 5, "room": 3, "pointer": 3926157, "animal_pointers": [], "consumables": [{"idx": 32, "pointer": 1258, "x": 1488, "y": 192, "etype": 22, "vtype": 2, "name": "Ripple Field 5 - Maxim Tomato (Currents)"}, {"idx": 31, "pointer": 1290, "x": 1520, "y": 192, "etype": 22, "vtype": 0, "name": "Ripple Field 5 - 1-Up (Currents)"}], "consumables_pointer": 128, "enemies": ["Bobin", "Mony", "Squishy"], "default_exits": [{"room": 8, "unkn1": 4, "unkn2": 38, "x": 152, "y": 152, "name": "Ripple Field 5 - 3 Exit 0", "access_rule": ["Kine", "Kine Spawn"]}, {"room": 1, "unkn1": 95, "unkn2": 38, "x": 248, "y": 1064, "name": "Ripple Field 5 - 3 Exit 1", "access_rule": []}], "entity_load": [[0, 22], [2, 22], [6, 22], [14, 23], [1, 16], [73, 16], [22, 16], [20, 16]], "locations": ["Ripple Field 5 - Enemy 3 (Bobin)", "Ripple Field 5 - Enemy 4 (Mony)", "Ripple Field 5 - Enemy 5 (Squishy)", "Ripple Field 5 - Star 32", "Ripple Field 5 - Star 33", "Ripple Field 5 - Star 34", "Ripple Field 5 - Star 35", "Ripple Field 5 - Star 36", "Ripple Field 5 - Star 37", "Ripple Field 5 - Maxim Tomato (Currents)", "Ripple Field 5 - 1-Up (Currents)"], "music": 16}, {"name": "Ripple Field 5 - 4", "level": 2, "stage": 5, "room": 4, "pointer": 3026639, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 5, "unkn1": 14, "unkn2": 4, "x": 232, "y": 152, "name": "Ripple Field 5 - 4 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 5 - Animal 1"], "music": 40}, {"name": "Ripple Field 5 - 5", "level": 2, "stage": 5, "room": 5, "pointer": 3207333, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 4, "unkn2": 9, "x": 56, "y": 72, "name": "Ripple Field 5 - 5 Exit 0", "access_rule": []}, {"room": 7, "unkn1": 24, "unkn2": 9, "x": 120, "y": 552, "name": "Ripple Field 5 - 5 Exit 1", "access_rule": ["Kine", "Kine Spawn"]}], "entity_load": [[14, 23]], "locations": ["Ripple Field 5 - Star 38", "Ripple Field 5 - Star 39", "Ripple Field 5 - Star 40", "Ripple Field 5 - Star 41", "Ripple Field 5 - Star 42"], "music": 16}, {"name": "Ripple Field 5 - 6", "level": 2, "stage": 5, "room": 6, "pointer": 3485896, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Yaban", "Broom Hatter", "Bouncy"], "default_exits": [{"room": 9, "unkn1": 121, "unkn2": 11, "x": 56, "y": 152, "name": "Ripple Field 5 - 6 Exit 0", "access_rule": []}], "entity_load": [[6, 22], [22, 16], [14, 23], [13, 16], [11, 16], [32, 16]], "locations": ["Ripple Field 5 - Enemy 6 (Yaban)", "Ripple Field 5 - Enemy 7 (Broom Hatter)", "Ripple Field 5 - Enemy 8 (Bouncy)", "Ripple Field 5 - Star 43", "Ripple Field 5 - Star 44", "Ripple Field 5 - Star 45"], "music": 16}, {"name": "Ripple Field 5 - 7", "level": 2, "stage": 5, "room": 7, "pointer": 3752698, "animal_pointers": [], "consumables": [{"idx": 53, "pointer": 418, "x": 1512, "y": 608, "etype": 22, "vtype": 2, "name": "Ripple Field 5 - Maxim Tomato (Exit)"}], "consumables_pointer": 352, "enemies": ["Sparky", "Rocky", "Babut"], "default_exits": [{"room": 10, "unkn1": 45, "unkn2": 31, "x": 152, "y": 152, "name": "Ripple Field 5 - 7 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 94, "unkn2": 40, "x": 88, "y": 200, "name": "Ripple Field 5 - 7 Exit 1", "access_rule": []}], "entity_load": [[3, 16], [43, 16], [8, 16], [22, 16], [14, 23], [2, 22]], "locations": ["Ripple Field 5 - Enemy 9 (Sparky)", "Ripple Field 5 - Enemy 10 (Rocky)", "Ripple Field 5 - Enemy 11 (Babut)", "Ripple Field 5 - Star 46", "Ripple Field 5 - Star 47", "Ripple Field 5 - Star 48", "Ripple Field 5 - Star 49", "Ripple Field 5 - Maxim Tomato (Exit)"], "music": 16}, {"name": "Ripple Field 5 - 8", "level": 2, "stage": 5, "room": 8, "pointer": 3044682, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 3, "unkn1": 8, "unkn2": 9, "x": 88, "y": 616, "name": "Ripple Field 5 - 8 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 5 - Animal 2"], "music": 39}, {"name": "Ripple Field 5 - 9", "level": 2, "stage": 5, "room": 9, "pointer": 2963193, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Ripple Field 5 - 9 Exit 0", "access_rule": []}], "entity_load": [[11, 19], [42, 19]], "locations": ["Ripple Field 5 - Mama Pitch"], "music": 8}, {"name": "Ripple Field 5 - 10", "level": 2, "stage": 5, "room": 10, "pointer": 3042934, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Galbo"], "default_exits": [{"room": 7, "unkn1": 8, "unkn2": 9, "x": 712, "y": 504, "name": "Ripple Field 5 - 10 Exit 0", "access_rule": []}], "entity_load": [[26, 16], [14, 23]], "locations": ["Ripple Field 5 - Enemy 12 (Galbo)", "Ripple Field 5 - Star 50", "Ripple Field 5 - Star 51"], "music": 16}, {"name": "Ripple Field 5 - 11", "level": 2, "stage": 5, "room": 11, "pointer": 2886256, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Ripple Field 5 - Complete"], "music": 5}, {"name": "Ripple Field 6 - 0", "level": 2, "stage": 6, "room": 0, "pointer": 2949576, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Kany"], "default_exits": [{"room": 1, "unkn1": 56, "unkn2": 7, "x": 40, "y": 152, "name": "Ripple Field 6 - 0 Exit 0", "access_rule": []}], "entity_load": [[29, 16], [13, 23]], "locations": ["Ripple Field 6 - Enemy 1 (Kany)"], "music": 15}, {"name": "Ripple Field 6 - 1", "level": 2, "stage": 6, "room": 1, "pointer": 2971200, "animal_pointers": [192, 200, 208], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 23, "unkn2": 9, "x": 56, "y": 264, "name": "Ripple Field 6 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 6 - Animal 1", "Ripple Field 6 - Animal 2", "Ripple Field 6 - Animal 3"], "music": 38}, {"name": "Ripple Field 6 - 2", "level": 2, "stage": 6, "room": 2, "pointer": 3637749, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 95, "unkn2": 9, "x": 104, "y": 872, "name": "Ripple Field 6 - 2 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 50, "unkn2": 22, "x": 184, "y": 88, "name": "Ripple Field 6 - 2 Exit 1", "access_rule": []}, {"room": 3, "unkn1": 45, "unkn2": 26, "x": 88, "y": 88, "name": "Ripple Field 6 - 2 Exit 2", "access_rule": []}, {"room": 3, "unkn1": 55, "unkn2": 26, "x": 248, "y": 88, "name": "Ripple Field 6 - 2 Exit 3", "access_rule": []}], "entity_load": [[52, 16], [13, 23]], "locations": [], "music": 15}, {"name": "Ripple Field 6 - 3", "level": 2, "stage": 6, "room": 3, "pointer": 3092564, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 4, "unkn2": 5, "x": 744, "y": 424, "name": "Ripple Field 6 - 3 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 16, "unkn2": 5, "x": 872, "y": 424, "name": "Ripple Field 6 - 3 Exit 1", "access_rule": []}], "entity_load": [], "locations": [], "music": 15}, {"name": "Ripple Field 6 - 4", "level": 2, "stage": 6, "room": 4, "pointer": 3133247, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 10, "unkn2": 5, "x": 824, "y": 360, "name": "Ripple Field 6 - 4 Exit 0", "access_rule": []}], "entity_load": [[12, 19]], "locations": [], "music": 15}, {"name": "Ripple Field 6 - 5", "level": 2, "stage": 6, "room": 5, "pointer": 3507762, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 76, "unkn2": 4, "x": 680, "y": 72, "name": "Ripple Field 6 - 5 Exit 0", "access_rule": []}, {"room": 6, "unkn1": 49, "unkn2": 6, "x": 440, "y": 104, "name": "Ripple Field 6 - 5 Exit 1", "access_rule": []}, {"room": 6, "unkn1": 49, "unkn2": 10, "x": 440, "y": 168, "name": "Ripple Field 6 - 5 Exit 2", "access_rule": []}, {"room": 2, "unkn1": 88, "unkn2": 10, "x": 104, "y": 152, "name": "Ripple Field 6 - 5 Exit 3", "access_rule": []}, {"room": 6, "unkn1": 22, "unkn2": 12, "x": 200, "y": 200, "name": "Ripple Field 6 - 5 Exit 4", "access_rule": []}, {"room": 6, "unkn1": 76, "unkn2": 12, "x": 680, "y": 200, "name": "Ripple Field 6 - 5 Exit 5", "access_rule": []}, {"room": 6, "unkn1": 76, "unkn2": 16, "x": 680, "y": 264, "name": "Ripple Field 6 - 5 Exit 6", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 15}, {"name": "Ripple Field 6 - 6", "level": 2, "stage": 6, "room": 6, "pointer": 3211264, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 5, "unkn1": 4, "unkn2": 10, "x": 72, "y": 168, "name": "Ripple Field 6 - 6 Exit 0", "access_rule": []}], "entity_load": [[6, 23]], "locations": [], "music": 15}, {"name": "Ripple Field 6 - 7", "level": 2, "stage": 6, "room": 7, "pointer": 3586039, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["KeKe", "Kapar", "Rocky", "Poppy Bros Jr."], "default_exits": [{"room": 5, "unkn1": 134, "unkn2": 16, "x": 72, "y": 168, "name": "Ripple Field 6 - 7 Exit 0", "access_rule": []}], "entity_load": [[14, 23], [3, 16], [67, 16], [51, 16], [7, 16]], "locations": ["Ripple Field 6 - Enemy 2 (KeKe)", "Ripple Field 6 - Enemy 3 (Kapar)", "Ripple Field 6 - Enemy 4 (Rocky)", "Ripple Field 6 - Enemy 5 (Poppy Bros Jr.)", "Ripple Field 6 - Star 1", "Ripple Field 6 - Star 2", "Ripple Field 6 - Star 3", "Ripple Field 6 - Star 4", "Ripple Field 6 - Star 5", "Ripple Field 6 - Star 6", "Ripple Field 6 - Star 7", "Ripple Field 6 - Star 8", "Ripple Field 6 - Star 9", "Ripple Field 6 - Star 10", "Ripple Field 6 - Star 11", "Ripple Field 6 - Star 12", "Ripple Field 6 - Star 13", "Ripple Field 6 - Star 14"], "music": 15}, {"name": "Ripple Field 6 - 8", "level": 2, "stage": 6, "room": 8, "pointer": 3621483, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller", "Coconut", "Sasuke", "Nruff"], "default_exits": [{"room": 10, "unkn1": 70, "unkn2": 11, "x": 56, "y": 152, "name": "Ripple Field 6 - 8 Exit 0", "access_rule": []}, {"room": 9, "unkn1": 26, "unkn2": 54, "x": 72, "y": 152, "name": "Ripple Field 6 - 8 Exit 1", "access_rule": []}], "entity_load": [[89, 16], [15, 16], [30, 16], [34, 16], [14, 23]], "locations": ["Ripple Field 6 - Enemy 6 (Propeller)", "Ripple Field 6 - Enemy 7 (Coconut)", "Ripple Field 6 - Enemy 8 (Sasuke)", "Ripple Field 6 - Enemy 9 (Nruff)", "Ripple Field 6 - Star 15", "Ripple Field 6 - Star 16", "Ripple Field 6 - Star 17", "Ripple Field 6 - Star 18", "Ripple Field 6 - Star 19", "Ripple Field 6 - Star 20", "Ripple Field 6 - Star 21", "Ripple Field 6 - Star 22", "Ripple Field 6 - Star 23"], "music": 15}, {"name": "Ripple Field 6 - 9", "level": 2, "stage": 6, "room": 9, "pointer": 2954523, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 3, "unkn2": 9, "x": 408, "y": 872, "name": "Ripple Field 6 - 9 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Ripple Field 6 - Animal 4"], "music": 39}, {"name": "Ripple Field 6 - 10", "level": 2, "stage": 6, "room": 10, "pointer": 3069438, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 15, "unkn2": 9, "x": 72, "y": 120, "name": "Ripple Field 6 - 10 Exit 0", "access_rule": []}], "entity_load": [[12, 19], [42, 19]], "locations": ["Ripple Field 6 - HB-002"], "music": 8}, {"name": "Ripple Field 6 - 11", "level": 2, "stage": 6, "room": 11, "pointer": 2886497, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Ripple Field 6 - Complete"], "music": 5}, {"name": "Ripple Field Boss - 0", "level": 2, "stage": 7, "room": 0, "pointer": 3157370, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[7, 18]], "locations": ["Ripple Field - Boss (Acro) Purified", "Level 2 Boss - Defeated", "Level 2 Boss - Purified"], "music": 2}, {"name": "Sand Canyon 1 - 0", "level": 3, "stage": 1, "room": 0, "pointer": 3524267, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bronto Burt", "Galbo"], "default_exits": [{"room": 5, "unkn1": 196, "unkn2": 7, "x": 72, "y": 152, "name": "Sand Canyon 1 - 0 Exit 0", "access_rule": []}], "entity_load": [[14, 19], [1, 16], [26, 16], [2, 16]], "locations": ["Sand Canyon 1 - Enemy 1 (Bronto Burt)", "Sand Canyon 1 - Enemy 2 (Galbo)"], "music": 13}, {"name": "Sand Canyon 1 - 1", "level": 3, "stage": 1, "room": 1, "pointer": 3163860, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 34, "unkn2": 5, "x": 104, "y": 408, "name": "Sand Canyon 1 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 1 - Animal 1", "Sand Canyon 1 - Animal 2"], "music": 38}, {"name": "Sand Canyon 1 - 2", "level": 3, "stage": 1, "room": 2, "pointer": 3512532, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Oro"], "default_exits": [{"room": 3, "unkn1": 73, "unkn2": 6, "x": 56, "y": 120, "name": "Sand Canyon 1 - 2 Exit 0", "access_rule": []}], "entity_load": [[14, 16], [26, 16], [25, 16], [4, 22], [14, 23], [1, 23], [0, 23], [4, 23]], "locations": ["Sand Canyon 1 - Enemy 3 (Oro)", "Sand Canyon 1 - Star 1", "Sand Canyon 1 - Star 2", "Sand Canyon 1 - Star 3", "Sand Canyon 1 - Star 4"], "music": 13}, {"name": "Sand Canyon 1 - 3", "level": 3, "stage": 1, "room": 3, "pointer": 3719146, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sparky", "Propeller", "Gansan", "Babut"], "default_exits": [{"room": 4, "unkn1": 25, "unkn2": 73, "x": 72, "y": 280, "name": "Sand Canyon 1 - 3 Exit 0", "access_rule": []}], "entity_load": [[89, 16], [75, 16], [43, 16], [8, 16]], "locations": ["Sand Canyon 1 - Enemy 4 (Sparky)", "Sand Canyon 1 - Enemy 5 (Propeller)", "Sand Canyon 1 - Enemy 6 (Gansan)", "Sand Canyon 1 - Enemy 7 (Babut)"], "music": 13}, {"name": "Sand Canyon 1 - 4", "level": 3, "stage": 1, "room": 4, "pointer": 3421212, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Loud", "Dogon", "Bouncy", "Pteran"], "default_exits": [{"room": 7, "unkn1": 196, "unkn2": 8, "x": 56, "y": 152, "name": "Sand Canyon 1 - 4 Exit 0", "access_rule": []}], "entity_load": [[90, 16], [13, 16], [40, 16], [14, 23], [39, 16]], "locations": ["Sand Canyon 1 - Enemy 8 (Loud)", "Sand Canyon 1 - Enemy 9 (Dogon)", "Sand Canyon 1 - Enemy 10 (Bouncy)", "Sand Canyon 1 - Enemy 11 (Pteran)", "Sand Canyon 1 - Star 5", "Sand Canyon 1 - Star 6", "Sand Canyon 1 - Star 7", "Sand Canyon 1 - Star 8", "Sand Canyon 1 - Star 9", "Sand Canyon 1 - Star 10"], "music": 13}, {"name": "Sand Canyon 1 - 5", "level": 3, "stage": 1, "room": 5, "pointer": 3203325, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Polof"], "default_exits": [{"room": 6, "unkn1": 32, "unkn2": 9, "x": 104, "y": 152, "name": "Sand Canyon 1 - 5 Exit 0", "access_rule": []}, {"room": 1, "unkn1": 46, "unkn2": 9, "x": 56, "y": 344, "name": "Sand Canyon 1 - 5 Exit 1", "access_rule": []}], "entity_load": [[9, 16]], "locations": ["Sand Canyon 1 - Enemy 12 (Polof)"], "music": 13}, {"name": "Sand Canyon 1 - 6", "level": 3, "stage": 1, "room": 6, "pointer": 3138524, "animal_pointers": [], "consumables": [{"idx": 23, "pointer": 248, "x": 168, "y": 104, "etype": 22, "vtype": 0, "name": "Sand Canyon 1 - 1-Up (Polof)"}], "consumables_pointer": 128, "enemies": [], "default_exits": [{"room": 5, "unkn1": 5, "unkn2": 9, "x": 536, "y": 152, "name": "Sand Canyon 1 - 6 Exit 0", "access_rule": []}], "entity_load": [[14, 23], [0, 22]], "locations": ["Sand Canyon 1 - Star 11", "Sand Canyon 1 - Star 12", "Sand Canyon 1 - Star 13", "Sand Canyon 1 - Star 14", "Sand Canyon 1 - Star 15", "Sand Canyon 1 - Star 16", "Sand Canyon 1 - Star 17", "Sand Canyon 1 - Star 18", "Sand Canyon 1 - Star 19", "Sand Canyon 1 - Star 20", "Sand Canyon 1 - Star 21", "Sand Canyon 1 - Star 22", "Sand Canyon 1 - 1-Up (Polof)"], "music": 13}, {"name": "Sand Canyon 1 - 7", "level": 3, "stage": 1, "room": 7, "pointer": 2988822, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 15, "unkn2": 9, "x": 72, "y": 120, "name": "Sand Canyon 1 - 7 Exit 0", "access_rule": []}], "entity_load": [[14, 19], [42, 19]], "locations": ["Sand Canyon 1 - Geromuzudake"], "music": 8}, {"name": "Sand Canyon 1 - 8", "level": 3, "stage": 1, "room": 8, "pointer": 2885292, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Sand Canyon 1 - Complete"], "music": 5}, {"name": "Sand Canyon 2 - 0", "level": 3, "stage": 2, "room": 0, "pointer": 3668370, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["KeKe", "Doka", "Boten"], "default_exits": [{"room": 1, "unkn1": 178, "unkn2": 8, "x": 184, "y": 104, "name": "Sand Canyon 2 - 0 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 244, "unkn2": 11, "x": 56, "y": 152, "name": "Sand Canyon 2 - 0 Exit 1", "access_rule": []}], "entity_load": [[35, 16], [33, 16], [14, 23], [51, 16], [47, 16], [46, 16]], "locations": ["Sand Canyon 2 - Enemy 1 (KeKe)", "Sand Canyon 2 - Enemy 2 (Doka)", "Sand Canyon 2 - Enemy 3 (Boten)", "Sand Canyon 2 - Star 1", "Sand Canyon 2 - Star 2", "Sand Canyon 2 - Star 3", "Sand Canyon 2 - Star 4", "Sand Canyon 2 - Star 5", "Sand Canyon 2 - Star 6", "Sand Canyon 2 - Star 7", "Sand Canyon 2 - Star 8", "Sand Canyon 2 - Star 9", "Sand Canyon 2 - Star 10", "Sand Canyon 2 - Star 11"], "music": 21}, {"name": "Sand Canyon 2 - 1", "level": 3, "stage": 2, "room": 1, "pointer": 2952738, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 10, "unkn2": 6, "x": 2872, "y": 136, "name": "Sand Canyon 2 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 2 - Animal 1", "Sand Canyon 2 - Animal 2"], "music": 40}, {"name": "Sand Canyon 2 - 2", "level": 3, "stage": 2, "room": 2, "pointer": 3531156, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller"], "default_exits": [{"room": 9, "unkn1": 45, "unkn2": 60, "x": 88, "y": 184, "name": "Sand Canyon 2 - 2 Exit 0", "access_rule": []}], "entity_load": [[6, 23], [89, 16], [14, 23]], "locations": ["Sand Canyon 2 - Enemy 4 (Propeller)", "Sand Canyon 2 - Star 12", "Sand Canyon 2 - Star 13", "Sand Canyon 2 - Star 14", "Sand Canyon 2 - Star 15", "Sand Canyon 2 - Star 16", "Sand Canyon 2 - Star 17"], "music": 21}, {"name": "Sand Canyon 2 - 3", "level": 3, "stage": 2, "room": 3, "pointer": 3263731, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Sparky", "Sasuke", "Como"], "default_exits": [{"room": 4, "unkn1": 63, "unkn2": 5, "x": 88, "y": 184, "name": "Sand Canyon 2 - 3 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 68, "unkn2": 5, "x": 184, "y": 184, "name": "Sand Canyon 2 - 3 Exit 1", "access_rule": []}, {"room": 4, "unkn1": 73, "unkn2": 5, "x": 248, "y": 184, "name": "Sand Canyon 2 - 3 Exit 2", "access_rule": []}, {"room": 5, "unkn1": 130, "unkn2": 9, "x": 72, "y": 312, "name": "Sand Canyon 2 - 3 Exit 3", "access_rule": []}], "entity_load": [[41, 16], [8, 16], [30, 16], [0, 16]], "locations": ["Sand Canyon 2 - Enemy 5 (Waddle Dee)", "Sand Canyon 2 - Enemy 6 (Sparky)", "Sand Canyon 2 - Enemy 7 (Sasuke)", "Sand Canyon 2 - Enemy 8 (Como)"], "music": 21}, {"name": "Sand Canyon 2 - 4", "level": 3, "stage": 2, "room": 4, "pointer": 3076300, "animal_pointers": [], "consumables": [{"idx": 19, "pointer": 228, "x": 168, "y": 72, "etype": 22, "vtype": 0, "name": "Sand Canyon 2 - 1-Up (Enclave)"}], "consumables_pointer": 128, "enemies": [], "default_exits": [{"room": 3, "unkn1": 5, "unkn2": 11, "x": 1016, "y": 88, "name": "Sand Canyon 2 - 4 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 10, "unkn2": 11, "x": 1112, "y": 88, "name": "Sand Canyon 2 - 4 Exit 1", "access_rule": []}, {"room": 3, "unkn1": 15, "unkn2": 11, "x": 1176, "y": 88, "name": "Sand Canyon 2 - 4 Exit 2", "access_rule": []}], "entity_load": [[14, 23], [0, 22], [4, 22]], "locations": ["Sand Canyon 2 - Star 18", "Sand Canyon 2 - Star 19", "Sand Canyon 2 - 1-Up (Enclave)"], "music": 21}, {"name": "Sand Canyon 2 - 5", "level": 3, "stage": 2, "room": 5, "pointer": 3302133, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Ice)", "Bukiset (Needle)", "Bukiset (Clean)", "Bukiset (Parasol)", "Bukiset (Spark)", "Bukiset (Cutter)"], "default_exits": [{"room": 6, "unkn1": 63, "unkn2": 15, "x": 120, "y": 216, "name": "Sand Canyon 2 - 5 Exit 0", "access_rule": []}, {"room": 8, "unkn1": 71, "unkn2": 18, "x": 152, "y": 136, "name": "Sand Canyon 2 - 5 Exit 1", "access_rule": []}, {"room": 2, "unkn1": 130, "unkn2": 19, "x": 72, "y": 952, "name": "Sand Canyon 2 - 5 Exit 2", "access_rule": []}, {"room": 7, "unkn1": 56, "unkn2": 23, "x": 88, "y": 216, "name": "Sand Canyon 2 - 5 Exit 3", "access_rule": []}], "entity_load": [[80, 16], [78, 16], [81, 16], [83, 16], [79, 16], [82, 16], [14, 23]], "locations": ["Sand Canyon 2 - Enemy 9 (Bukiset (Ice))", "Sand Canyon 2 - Enemy 10 (Bukiset (Needle))", "Sand Canyon 2 - Enemy 11 (Bukiset (Clean))", "Sand Canyon 2 - Enemy 12 (Bukiset (Parasol))", "Sand Canyon 2 - Enemy 13 (Bukiset (Spark))", "Sand Canyon 2 - Enemy 14 (Bukiset (Cutter))", "Sand Canyon 2 - Star 20", "Sand Canyon 2 - Star 21", "Sand Canyon 2 - Star 22", "Sand Canyon 2 - Star 23", "Sand Canyon 2 - Star 24", "Sand Canyon 2 - Star 25", "Sand Canyon 2 - Star 26", "Sand Canyon 2 - Star 27", "Sand Canyon 2 - Star 28", "Sand Canyon 2 - Star 29", "Sand Canyon 2 - Star 30", "Sand Canyon 2 - Star 31", "Sand Canyon 2 - Star 32", "Sand Canyon 2 - Star 33"], "music": 21}, {"name": "Sand Canyon 2 - 6", "level": 3, "stage": 2, "room": 6, "pointer": 3800612, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Nidoo"], "default_exits": [{"room": 5, "unkn1": 17, "unkn2": 13, "x": 1144, "y": 248, "name": "Sand Canyon 2 - 6 Exit 0", "access_rule": []}], "entity_load": [[28, 16], [14, 23]], "locations": ["Sand Canyon 2 - Enemy 15 (Nidoo)", "Sand Canyon 2 - Star 34"], "music": 21}, {"name": "Sand Canyon 2 - 7", "level": 3, "stage": 2, "room": 7, "pointer": 3073888, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 5, "unkn1": 15, "unkn2": 8, "x": 1016, "y": 296, "name": "Sand Canyon 2 - 7 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 5, "unkn2": 13, "x": 904, "y": 376, "name": "Sand Canyon 2 - 7 Exit 1", "access_rule": []}], "entity_load": [[15, 19]], "locations": [], "music": 21}, {"name": "Sand Canyon 2 - 8", "level": 3, "stage": 2, "room": 8, "pointer": 3061270, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Mariel"], "default_exits": [{"room": 5, "unkn1": 19, "unkn2": 13, "x": 1256, "y": 376, "name": "Sand Canyon 2 - 8 Exit 0", "access_rule": []}], "entity_load": [[45, 16], [14, 23]], "locations": ["Sand Canyon 2 - Enemy 16 (Mariel)", "Sand Canyon 2 - Star 35"], "music": 21}, {"name": "Sand Canyon 2 - 9", "level": 3, "stage": 2, "room": 9, "pointer": 3453286, "animal_pointers": [], "consumables": [{"idx": 51, "pointer": 240, "x": 1408, "y": 216, "etype": 22, "vtype": 2, "name": "Sand Canyon 2 - Maxim Tomato (Underwater)"}], "consumables_pointer": 128, "enemies": ["Yaban", "Wapod", "Squishy", "Pteran"], "default_exits": [{"room": 10, "unkn1": 246, "unkn2": 10, "x": 56, "y": 152, "name": "Sand Canyon 2 - 9 Exit 0", "access_rule": []}], "entity_load": [[14, 23], [22, 16], [2, 22], [39, 16], [88, 16], [32, 16]], "locations": ["Sand Canyon 2 - Enemy 17 (Yaban)", "Sand Canyon 2 - Enemy 18 (Wapod)", "Sand Canyon 2 - Enemy 19 (Squishy)", "Sand Canyon 2 - Enemy 20 (Pteran)", "Sand Canyon 2 - Star 36", "Sand Canyon 2 - Star 37", "Sand Canyon 2 - Star 38", "Sand Canyon 2 - Star 39", "Sand Canyon 2 - Star 40", "Sand Canyon 2 - Star 41", "Sand Canyon 2 - Star 42", "Sand Canyon 2 - Star 43", "Sand Canyon 2 - Star 44", "Sand Canyon 2 - Star 45", "Sand Canyon 2 - Star 46", "Sand Canyon 2 - Star 47", "Sand Canyon 2 - Star 48", "Sand Canyon 2 - Maxim Tomato (Underwater)"], "music": 21}, {"name": "Sand Canyon 2 - 10", "level": 3, "stage": 2, "room": 10, "pointer": 2994821, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Sand Canyon 2 - 10 Exit 0", "access_rule": []}], "entity_load": [[15, 19], [42, 19]], "locations": ["Sand Canyon 2 - Auntie"], "music": 8}, {"name": "Sand Canyon 2 - 11", "level": 3, "stage": 2, "room": 11, "pointer": 2891799, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Sand Canyon 2 - Complete"], "music": 5}, {"name": "Sand Canyon 3 - 0", "level": 3, "stage": 3, "room": 0, "pointer": 3544676, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sir Kibble", "Broom Hatter", "Rocky", "Gabon"], "default_exits": [{"room": 1, "unkn1": 6, "unkn2": 57, "x": 104, "y": 152, "name": "Sand Canyon 3 - 0 Exit 0", "access_rule": []}, {"room": 1, "unkn1": 11, "unkn2": 57, "x": 200, "y": 152, "name": "Sand Canyon 3 - 0 Exit 1", "access_rule": []}, {"room": 1, "unkn1": 16, "unkn2": 57, "x": 296, "y": 152, "name": "Sand Canyon 3 - 0 Exit 2", "access_rule": []}, {"room": 2, "unkn1": 23, "unkn2": 109, "x": 104, "y": 72, "name": "Sand Canyon 3 - 0 Exit 3", "access_rule": []}], "entity_load": [[24, 16], [3, 16], [11, 16], [27, 16]], "locations": ["Sand Canyon 3 - Enemy 1 (Sir Kibble)", "Sand Canyon 3 - Enemy 2 (Broom Hatter)", "Sand Canyon 3 - Enemy 3 (Rocky)", "Sand Canyon 3 - Enemy 4 (Gabon)"], "music": 16}, {"name": "Sand Canyon 3 - 1", "level": 3, "stage": 3, "room": 1, "pointer": 2965655, "animal_pointers": [212, 220, 228], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 5, "unkn2": 9, "x": 88, "y": 920, "name": "Sand Canyon 3 - 1 Exit 0", "access_rule": []}, {"room": 0, "unkn1": 11, "unkn2": 9, "x": 168, "y": 920, "name": "Sand Canyon 3 - 1 Exit 1", "access_rule": []}, {"room": 0, "unkn1": 17, "unkn2": 9, "x": 248, "y": 920, "name": "Sand Canyon 3 - 1 Exit 2", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 3 - Animal 1", "Sand Canyon 3 - Animal 2", "Sand Canyon 3 - Animal 3"], "music": 39}, {"name": "Sand Canyon 3 - 2", "level": 3, "stage": 3, "room": 2, "pointer": 3726270, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Kany"], "default_exits": [{"room": 3, "unkn1": 184, "unkn2": 20, "x": 104, "y": 232, "name": "Sand Canyon 3 - 2 Exit 0", "access_rule": []}], "entity_load": [[63, 16], [55, 16], [14, 23], [4, 23], [29, 16]], "locations": ["Sand Canyon 3 - Enemy 5 (Kany)", "Sand Canyon 3 - Star 1", "Sand Canyon 3 - Star 2", "Sand Canyon 3 - Star 3", "Sand Canyon 3 - Star 4", "Sand Canyon 3 - Star 5", "Sand Canyon 3 - Star 6"], "music": 16}, {"name": "Sand Canyon 3 - 3", "level": 3, "stage": 3, "room": 3, "pointer": 3416806, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Galbo"], "default_exits": [{"room": 8, "unkn1": 130, "unkn2": 14, "x": 40, "y": 152, "name": "Sand Canyon 3 - 3 Exit 0", "access_rule": []}], "entity_load": [[26, 16], [6, 23], [5, 23]], "locations": ["Sand Canyon 3 - Enemy 6 (Galbo)"], "music": 16}, {"name": "Sand Canyon 3 - 4", "level": 3, "stage": 3, "room": 4, "pointer": 3564419, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller", "Sasuke"], "default_exits": [{"room": 5, "unkn1": 135, "unkn2": 11, "x": 56, "y": 152, "name": "Sand Canyon 3 - 4 Exit 0", "access_rule": []}], "entity_load": [[68, 16], [30, 16], [14, 23], [89, 16], [24, 16]], "locations": ["Sand Canyon 3 - Enemy 7 (Propeller)", "Sand Canyon 3 - Enemy 8 (Sasuke)", "Sand Canyon 3 - Star 7", "Sand Canyon 3 - Star 8", "Sand Canyon 3 - Star 9", "Sand Canyon 3 - Star 10"], "music": 16}, {"name": "Sand Canyon 3 - 5", "level": 3, "stage": 3, "room": 5, "pointer": 2973891, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Sand Canyon 3 - 5 Exit 0", "access_rule": []}], "entity_load": [[16, 19]], "locations": ["Sand Canyon 3 - Caramello"], "music": 8}, {"name": "Sand Canyon 3 - 6", "level": 3, "stage": 3, "room": 6, "pointer": 3247969, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Wapod", "Bobo", "Babut", "Magoo"], "default_exits": [{"room": 4, "unkn1": 95, "unkn2": 8, "x": 88, "y": 424, "name": "Sand Canyon 3 - 6 Exit 0", "access_rule": []}], "entity_load": [[53, 16], [88, 16], [46, 16], [47, 16], [5, 16], [43, 16]], "locations": ["Sand Canyon 3 - Enemy 9 (Wapod)", "Sand Canyon 3 - Enemy 10 (Bobo)", "Sand Canyon 3 - Enemy 11 (Babut)", "Sand Canyon 3 - Enemy 12 (Magoo)"], "music": 16}, {"name": "Sand Canyon 3 - 7", "level": 3, "stage": 3, "room": 7, "pointer": 2887702, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Sand Canyon 3 - Complete"], "music": 5}, {"name": "Sand Canyon 3 - 8", "level": 3, "stage": 3, "room": 8, "pointer": 2968057, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 14, "unkn2": 9, "x": 104, "y": 120, "name": "Sand Canyon 3 - 8 Exit 0", "access_rule": []}], "entity_load": [[16, 19]], "locations": [], "music": 31}, {"name": "Sand Canyon 4 - 0", "level": 3, "stage": 4, "room": 0, "pointer": 3521954, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Popon Ball", "Mariel", "Chilly"], "default_exits": [{"room": 1, "unkn1": 95, "unkn2": 8, "x": 216, "y": 88, "name": "Sand Canyon 4 - 0 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 199, "unkn2": 15, "x": 104, "y": 88, "name": "Sand Canyon 4 - 0 Exit 1", "access_rule": []}], "entity_load": [[6, 16], [45, 16], [6, 23], [50, 16], [14, 23]], "locations": ["Sand Canyon 4 - Enemy 1 (Popon Ball)", "Sand Canyon 4 - Enemy 2 (Mariel)", "Sand Canyon 4 - Enemy 3 (Chilly)", "Sand Canyon 4 - Star 1", "Sand Canyon 4 - Star 2", "Sand Canyon 4 - Star 3", "Sand Canyon 4 - Star 4", "Sand Canyon 4 - Star 5", "Sand Canyon 4 - Star 6", "Sand Canyon 4 - Star 7", "Sand Canyon 4 - Star 8", "Sand Canyon 4 - Star 9", "Sand Canyon 4 - Star 10", "Sand Canyon 4 - Star 11"], "music": 18}, {"name": "Sand Canyon 4 - 1", "level": 3, "stage": 4, "room": 1, "pointer": 2956289, "animal_pointers": [192, 200, 208], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 12, "unkn2": 5, "x": 1544, "y": 136, "name": "Sand Canyon 4 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 4 - Animal 1", "Sand Canyon 4 - Animal 2", "Sand Canyon 4 - Animal 3"], "music": 39}, {"name": "Sand Canyon 4 - 2", "level": 3, "stage": 4, "room": 2, "pointer": 3505360, "animal_pointers": [], "consumables": [{"idx": 11, "pointer": 328, "x": 2024, "y": 72, "etype": 22, "vtype": 2, "name": "Sand Canyon 4 - Maxim Tomato (Pacto)"}], "consumables_pointer": 160, "enemies": ["Tick", "Bronto Burt", "Babut"], "default_exits": [{"room": 3, "unkn1": 149, "unkn2": 14, "x": 88, "y": 216, "name": "Sand Canyon 4 - 2 Exit 0", "access_rule": []}], "entity_load": [[62, 16], [2, 22], [43, 16], [2, 16], [48, 16]], "locations": ["Sand Canyon 4 - Enemy 4 (Tick)", "Sand Canyon 4 - Enemy 5 (Bronto Burt)", "Sand Canyon 4 - Enemy 6 (Babut)", "Sand Canyon 4 - Maxim Tomato (Pacto)"], "music": 18}, {"name": "Sand Canyon 4 - 3", "level": 3, "stage": 4, "room": 3, "pointer": 3412361, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bobin", "Joe", "Mony", "Blipper"], "default_exits": [{"room": 4, "unkn1": 115, "unkn2": 6, "x": 776, "y": 120, "name": "Sand Canyon 4 - 3 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 29, "unkn2": 8, "x": 72, "y": 88, "name": "Sand Canyon 4 - 3 Exit 1", "access_rule": []}, {"room": 5, "unkn1": 105, "unkn2": 14, "x": 104, "y": 216, "name": "Sand Canyon 4 - 3 Exit 2", "access_rule": []}, {"room": 4, "unkn1": 115, "unkn2": 20, "x": 776, "y": 248, "name": "Sand Canyon 4 - 3 Exit 3", "access_rule": []}, {"room": 4, "unkn1": 64, "unkn2": 21, "x": 408, "y": 232, "name": "Sand Canyon 4 - 3 Exit 4", "access_rule": []}], "entity_load": [[73, 16], [20, 16], [91, 16], [14, 23], [21, 16]], "locations": ["Sand Canyon 4 - Enemy 7 (Bobin)", "Sand Canyon 4 - Enemy 8 (Joe)", "Sand Canyon 4 - Enemy 9 (Mony)", "Sand Canyon 4 - Enemy 10 (Blipper)", "Sand Canyon 4 - Star 12", "Sand Canyon 4 - Star 13", "Sand Canyon 4 - Star 14", "Sand Canyon 4 - Star 15"], "music": 18}, {"name": "Sand Canyon 4 - 4", "level": 3, "stage": 4, "room": 4, "pointer": 3307804, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 60, "unkn2": 6, "x": 88, "y": 104, "name": "Sand Canyon 4 - 4 Exit 0", "access_rule": []}, {"room": 6, "unkn1": 60, "unkn2": 16, "x": 88, "y": 360, "name": "Sand Canyon 4 - 4 Exit 1", "access_rule": []}], "entity_load": [[21, 16], [20, 16], [91, 16], [73, 16]], "locations": [], "music": 18}, {"name": "Sand Canyon 4 - 5", "level": 3, "stage": 4, "room": 5, "pointer": 2955407, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 16, "unkn2": 13, "x": 88, "y": 232, "name": "Sand Canyon 4 - 5 Exit 0", "access_rule": []}], "entity_load": [[5, 27]], "locations": ["Sand Canyon 4 - Miniboss 1 (Haboki)"], "music": 4}, {"name": "Sand Canyon 4 - 6", "level": 3, "stage": 4, "room": 6, "pointer": 3094851, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 24, "unkn2": 6, "x": 56, "y": 56, "name": "Sand Canyon 4 - 6 Exit 0", "access_rule": []}, {"room": 7, "unkn1": 24, "unkn2": 14, "x": 56, "y": 104, "name": "Sand Canyon 4 - 6 Exit 1", "access_rule": []}, {"room": 7, "unkn1": 24, "unkn2": 22, "x": 56, "y": 152, "name": "Sand Canyon 4 - 6 Exit 2", "access_rule": []}], "entity_load": [[17, 19], [4, 23], [5, 23]], "locations": [], "music": 18}, {"name": "Sand Canyon 4 - 7", "level": 3, "stage": 4, "room": 7, "pointer": 3483428, "animal_pointers": [], "consumables": [{"idx": 25, "pointer": 200, "x": 344, "y": 144, "etype": 22, "vtype": 0, "name": "Sand Canyon 4 - 1-Up (Clean)"}, {"idx": 26, "pointer": 336, "x": 1656, "y": 144, "etype": 22, "vtype": 2, "name": "Sand Canyon 4 - Maxim Tomato (Needle)"}], "consumables_pointer": 128, "enemies": ["Togezo", "Rocky", "Bobo"], "default_exits": [{"room": 8, "unkn1": 266, "unkn2": 9, "x": 56, "y": 152, "name": "Sand Canyon 4 - 7 Exit 0", "access_rule": []}], "entity_load": [[6, 22], [3, 16], [14, 23], [5, 16], [2, 16], [18, 16], [0, 22], [2, 22]], "locations": ["Sand Canyon 4 - Enemy 11 (Togezo)", "Sand Canyon 4 - Enemy 12 (Rocky)", "Sand Canyon 4 - Enemy 13 (Bobo)", "Sand Canyon 4 - Star 16", "Sand Canyon 4 - Star 17", "Sand Canyon 4 - Star 18", "Sand Canyon 4 - Star 19", "Sand Canyon 4 - Star 20", "Sand Canyon 4 - Star 21", "Sand Canyon 4 - Star 22", "Sand Canyon 4 - Star 23", "Sand Canyon 4 - 1-Up (Clean)", "Sand Canyon 4 - Maxim Tomato (Needle)"], "music": 18}, {"name": "Sand Canyon 4 - 8", "level": 3, "stage": 4, "room": 8, "pointer": 3002086, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 9, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Sand Canyon 4 - 8 Exit 0", "access_rule": []}], "entity_load": [[17, 19], [42, 19]], "locations": ["Sand Canyon 4 - Donbe & Hikari"], "music": 8}, {"name": "Sand Canyon 4 - 9", "level": 3, "stage": 4, "room": 9, "pointer": 2885774, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Sand Canyon 4 - Complete"], "music": 5}, {"name": "Sand Canyon 5 - 0", "level": 3, "stage": 5, "room": 0, "pointer": 3662486, "animal_pointers": [], "consumables": [{"idx": 0, "pointer": 450, "x": 2256, "y": 232, "etype": 22, "vtype": 0, "name": "Sand Canyon 5 - 1-Up (Falling Block)"}], "consumables_pointer": 208, "enemies": ["Wapod", "Dogon", "Tick"], "default_exits": [{"room": 3, "unkn1": 151, "unkn2": 15, "x": 184, "y": 56, "name": "Sand Canyon 5 - 0 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 83, "unkn2": 16, "x": 72, "y": 120, "name": "Sand Canyon 5 - 0 Exit 1", "access_rule": []}], "entity_load": [[55, 16], [48, 16], [0, 22], [14, 23], [90, 16], [88, 16]], "locations": ["Sand Canyon 5 - Enemy 1 (Wapod)", "Sand Canyon 5 - Enemy 2 (Dogon)", "Sand Canyon 5 - Enemy 3 (Tick)", "Sand Canyon 5 - Star 1", "Sand Canyon 5 - Star 2", "Sand Canyon 5 - Star 3", "Sand Canyon 5 - Star 4", "Sand Canyon 5 - Star 5", "Sand Canyon 5 - Star 6", "Sand Canyon 5 - Star 7", "Sand Canyon 5 - Star 8", "Sand Canyon 5 - Star 9", "Sand Canyon 5 - Star 10", "Sand Canyon 5 - 1-Up (Falling Block)"], "music": 13}, {"name": "Sand Canyon 5 - 1", "level": 3, "stage": 5, "room": 1, "pointer": 3052622, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Rocky", "Bobo", "Chilly", "Sparky", "Togezo"], "default_exits": [{"room": 9, "unkn1": 6, "unkn2": 9, "x": 216, "y": 1128, "name": "Sand Canyon 5 - 1 Exit 0", "access_rule": []}], "entity_load": [[5, 16], [6, 16], [3, 16], [8, 16], [18, 16]], "locations": ["Sand Canyon 5 - Enemy 4 (Rocky)", "Sand Canyon 5 - Enemy 5 (Bobo)", "Sand Canyon 5 - Enemy 6 (Chilly)", "Sand Canyon 5 - Enemy 7 (Sparky)", "Sand Canyon 5 - Enemy 8 (Togezo)"], "music": 13}, {"name": "Sand Canyon 5 - 2", "level": 3, "stage": 5, "room": 2, "pointer": 3057544, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 3, "unkn2": 7, "x": 1320, "y": 264, "name": "Sand Canyon 5 - 2 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 5 - Animal 1"], "music": 40}, {"name": "Sand Canyon 5 - 3", "level": 3, "stage": 5, "room": 3, "pointer": 3419011, "animal_pointers": [], "consumables": [{"idx": 11, "pointer": 192, "x": 56, "y": 648, "etype": 22, "vtype": 0, "name": "Sand Canyon 5 - 1-Up (Ice 2)"}, {"idx": 14, "pointer": 200, "x": 56, "y": 664, "etype": 22, "vtype": 0, "name": "Sand Canyon 5 - 1-Up (Ice 3)"}, {"idx": 15, "pointer": 208, "x": 56, "y": 632, "etype": 22, "vtype": 0, "name": "Sand Canyon 5 - 1-Up (Ice 1)"}, {"idx": 12, "pointer": 368, "x": 320, "y": 264, "etype": 22, "vtype": 2, "name": "Sand Canyon 5 - Maxim Tomato (Pit)"}], "consumables_pointer": 304, "enemies": ["Bronto Burt", "Sasuke"], "default_exits": [{"room": 4, "unkn1": 12, "unkn2": 66, "x": 88, "y": 88, "name": "Sand Canyon 5 - 3 Exit 0", "access_rule": []}], "entity_load": [[6, 16], [30, 16], [2, 16], [0, 22], [2, 22], [14, 23]], "locations": ["Sand Canyon 5 - Enemy 9 (Bronto Burt)", "Sand Canyon 5 - Enemy 10 (Sasuke)", "Sand Canyon 5 - Star 11", "Sand Canyon 5 - Star 12", "Sand Canyon 5 - Star 13", "Sand Canyon 5 - Star 14", "Sand Canyon 5 - Star 15", "Sand Canyon 5 - Star 16", "Sand Canyon 5 - Star 17", "Sand Canyon 5 - 1-Up (Ice 2)", "Sand Canyon 5 - 1-Up (Ice 3)", "Sand Canyon 5 - 1-Up (Ice 1)", "Sand Canyon 5 - Maxim Tomato (Pit)"], "music": 13}, {"name": "Sand Canyon 5 - 4", "level": 3, "stage": 5, "room": 4, "pointer": 3644149, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Oro", "Galbo", "Nidoo"], "default_exits": [{"room": 5, "unkn1": 76, "unkn2": 9, "x": 88, "y": 120, "name": "Sand Canyon 5 - 4 Exit 0", "access_rule": []}, {"room": 9, "unkn1": 116, "unkn2": 14, "x": 200, "y": 1256, "name": "Sand Canyon 5 - 4 Exit 1", "access_rule": []}], "entity_load": [[54, 16], [26, 16], [28, 16], [25, 16], [14, 23]], "locations": ["Sand Canyon 5 - Enemy 11 (Oro)", "Sand Canyon 5 - Enemy 12 (Galbo)", "Sand Canyon 5 - Enemy 13 (Nidoo)", "Sand Canyon 5 - Star 18"], "music": 13}, {"name": "Sand Canyon 5 - 5", "level": 3, "stage": 5, "room": 5, "pointer": 3044100, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 4, "unkn2": 7, "x": 1240, "y": 152, "name": "Sand Canyon 5 - 5 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 5 - Animal 2"], "music": 39}, {"name": "Sand Canyon 5 - 6", "level": 3, "stage": 5, "room": 6, "pointer": 3373481, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller"], "default_exits": [{"room": 7, "unkn1": 25, "unkn2": 5, "x": 56, "y": 152, "name": "Sand Canyon 5 - 6 Exit 0", "access_rule": []}], "entity_load": [[2, 16], [31, 16], [89, 16], [6, 16]], "locations": ["Sand Canyon 5 - Enemy 14 (Propeller)"], "music": 13}, {"name": "Sand Canyon 5 - 7", "level": 3, "stage": 5, "room": 7, "pointer": 2964028, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Sand Canyon 5 - 7 Exit 0", "access_rule": []}], "entity_load": [[18, 19], [42, 19]], "locations": ["Sand Canyon 5 - Nyupun"], "music": 8}, {"name": "Sand Canyon 5 - 8", "level": 3, "stage": 5, "room": 8, "pointer": 2890112, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Sand Canyon 5 - Complete"], "music": 5}, {"name": "Sand Canyon 5 - 9", "level": 3, "stage": 5, "room": 9, "pointer": 3647264, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sir Kibble", "KeKe", "Kabu"], "default_exits": [{"room": 6, "unkn1": 12, "unkn2": 6, "x": 72, "y": 760, "name": "Sand Canyon 5 - 9 Exit 0", "access_rule": ["Cutter", "Cutter Ability"]}, {"room": 1, "unkn1": 12, "unkn2": 70, "x": 120, "y": 152, "name": "Sand Canyon 5 - 9 Exit 1", "access_rule": []}], "entity_load": [[27, 16], [61, 16], [4, 22], [14, 23], [51, 16], [19, 16]], "locations": ["Sand Canyon 5 - Enemy 15 (Sir Kibble)", "Sand Canyon 5 - Enemy 16 (KeKe)", "Sand Canyon 5 - Enemy 17 (Kabu)", "Sand Canyon 5 - Star 19", "Sand Canyon 5 - Star 20", "Sand Canyon 5 - Star 21", "Sand Canyon 5 - Star 22", "Sand Canyon 5 - Star 23", "Sand Canyon 5 - Star 24", "Sand Canyon 5 - Star 25", "Sand Canyon 5 - Star 26", "Sand Canyon 5 - Star 27", "Sand Canyon 5 - Star 28", "Sand Canyon 5 - Star 29", "Sand Canyon 5 - Star 30", "Sand Canyon 5 - Star 31", "Sand Canyon 5 - Star 32", "Sand Canyon 5 - Star 33", "Sand Canyon 5 - Star 34", "Sand Canyon 5 - Star 35", "Sand Canyon 5 - Star 36", "Sand Canyon 5 - Star 37", "Sand Canyon 5 - Star 38", "Sand Canyon 5 - Star 39", "Sand Canyon 5 - Star 40"], "music": 13}, {"name": "Sand Canyon 6 - 0", "level": 3, "stage": 6, "room": 0, "pointer": 3314761, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sparky", "Doka", "Cappy", "Pteran"], "default_exits": [{"room": 2, "unkn1": 132, "unkn2": 16, "x": 56, "y": 136, "name": "Sand Canyon 6 - 0 Exit 0", "access_rule": []}], "entity_load": [[8, 16], [35, 16], [39, 16], [12, 16]], "locations": ["Sand Canyon 6 - Enemy 1 (Sparky)", "Sand Canyon 6 - Enemy 2 (Doka)", "Sand Canyon 6 - Enemy 3 (Cappy)", "Sand Canyon 6 - Enemy 4 (Pteran)"], "music": 14}, {"name": "Sand Canyon 6 - 1", "level": 3, "stage": 6, "room": 1, "pointer": 2976913, "animal_pointers": [192, 200, 208], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 22, "unkn1": 24, "unkn2": 13, "x": 168, "y": 216, "name": "Sand Canyon 6 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 6 - Animal 1", "Sand Canyon 6 - Animal 2", "Sand Canyon 6 - Animal 3"], "music": 39}, {"name": "Sand Canyon 6 - 2", "level": 3, "stage": 6, "room": 2, "pointer": 2978757, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 55, "unkn2": 6, "x": 104, "y": 104, "name": "Sand Canyon 6 - 2 Exit 0", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 3", "level": 3, "stage": 6, "room": 3, "pointer": 3178082, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 3 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 3 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 3 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 3 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 3 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 3 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 3 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 3 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 4", "level": 3, "stage": 6, "room": 4, "pointer": 3181563, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 4 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 4 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 4 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 4 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 4 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 4 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 4 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 4 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 5", "level": 3, "stage": 6, "room": 5, "pointer": 3177209, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 5 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 5 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 5 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 5 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 5 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 5 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 5 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 5 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 6", "level": 3, "stage": 6, "room": 6, "pointer": 3183291, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 6 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 6 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 6 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 6 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 6 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 6 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 6 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 6 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 7", "level": 3, "stage": 6, "room": 7, "pointer": 3175453, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 7 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 7 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 7 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 7 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 7 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 7 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 7 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 7 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 8", "level": 3, "stage": 6, "room": 8, "pointer": 3179826, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 8 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 8 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 8 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 8 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 8 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 8 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 8 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 8 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 9", "level": 3, "stage": 6, "room": 9, "pointer": 3176334, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 9 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 9 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 9 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 9 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 9 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 9 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 9 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 9 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 10", "level": 3, "stage": 6, "room": 10, "pointer": 3178954, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 16, "unkn2": 4, "x": 184, "y": 712, "name": "Sand Canyon 6 - 10 Exit 0", "access_rule": []}, {"room": 24, "unkn1": 8, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 10 Exit 1", "access_rule": []}, {"room": 31, "unkn1": 24, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 10 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 16, "x": 600, "y": 104, "name": "Sand Canyon 6 - 10 Exit 3", "access_rule": []}, {"room": 12, "unkn1": 28, "unkn2": 16, "x": 88, "y": 136, "name": "Sand Canyon 6 - 10 Exit 4", "access_rule": []}, {"room": 14, "unkn1": 8, "unkn2": 24, "x": 552, "y": 152, "name": "Sand Canyon 6 - 10 Exit 5", "access_rule": []}, {"room": 19, "unkn1": 24, "unkn2": 24, "x": 104, "y": 152, "name": "Sand Canyon 6 - 10 Exit 6", "access_rule": []}, {"room": 38, "unkn1": 16, "unkn2": 28, "x": 168, "y": 88, "name": "Sand Canyon 6 - 10 Exit 7", "access_rule": []}], "entity_load": [[10, 23]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 11", "level": 3, "stage": 6, "room": 11, "pointer": 2989149, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 3, "unkn1": 37, "unkn2": 6, "x": 88, "y": 264, "name": "Sand Canyon 6 - 11 Exit 0", "access_rule": []}], "entity_load": [[59, 16]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 12", "level": 3, "stage": 6, "room": 12, "pointer": 3015947, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 4, "unkn2": 8, "x": 440, "y": 264, "name": "Sand Canyon 6 - 12 Exit 0", "access_rule": []}, {"room": 13, "unkn1": 86, "unkn2": 8, "x": 72, "y": 104, "name": "Sand Canyon 6 - 12 Exit 1", "access_rule": []}], "entity_load": [[64, 16], [59, 16], [55, 16]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 13", "level": 3, "stage": 6, "room": 13, "pointer": 2976539, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 23, "unkn1": 55, "unkn2": 8, "x": 56, "y": 152, "name": "Sand Canyon 6 - 13 Exit 0", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 14", "level": 3, "stage": 6, "room": 14, "pointer": 3030336, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 15, "unkn1": 5, "unkn2": 9, "x": 200, "y": 152, "name": "Sand Canyon 6 - 14 Exit 0", "access_rule": []}, {"room": 8, "unkn1": 35, "unkn2": 9, "x": 152, "y": 392, "name": "Sand Canyon 6 - 14 Exit 1", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 15", "level": 3, "stage": 6, "room": 15, "pointer": 2972361, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 14, "unkn1": 13, "unkn2": 9, "x": 104, "y": 152, "name": "Sand Canyon 6 - 15 Exit 0", "access_rule": []}], "entity_load": [[19, 19]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 16", "level": 3, "stage": 6, "room": 16, "pointer": 2953186, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 18, "unkn1": 10, "unkn2": 19, "x": 248, "y": 152, "name": "Sand Canyon 6 - 16 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 10, "unkn2": 44, "x": 264, "y": 88, "name": "Sand Canyon 6 - 16 Exit 1", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 17", "level": 3, "stage": 6, "room": 17, "pointer": 2953633, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 18, "unkn1": 10, "unkn2": 19, "x": 248, "y": 152, "name": "Sand Canyon 6 - 17 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 10, "unkn2": 44, "x": 264, "y": 88, "name": "Sand Canyon 6 - 17 Exit 1", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 18", "level": 3, "stage": 6, "room": 18, "pointer": 2991707, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 17, "unkn1": 14, "unkn2": 9, "x": 184, "y": 312, "name": "Sand Canyon 6 - 18 Exit 0", "access_rule": []}], "entity_load": [[19, 19]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 19", "level": 3, "stage": 6, "room": 19, "pointer": 2974651, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 5, "unkn2": 9, "x": 376, "y": 392, "name": "Sand Canyon 6 - 19 Exit 0", "access_rule": []}, {"room": 20, "unkn1": 35, "unkn2": 9, "x": 88, "y": 152, "name": "Sand Canyon 6 - 19 Exit 1", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 20", "level": 3, "stage": 6, "room": 20, "pointer": 2969638, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 19, "unkn1": 4, "unkn2": 9, "x": 552, "y": 152, "name": "Sand Canyon 6 - 20 Exit 0", "access_rule": []}], "entity_load": [[19, 19]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 21", "level": 3, "stage": 6, "room": 21, "pointer": 2976163, "animal_pointers": [192, 200, 208], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 22, "unkn1": 9, "unkn2": 13, "x": 296, "y": 216, "name": "Sand Canyon 6 - 21 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Sand Canyon 6 - Animal 4", "Sand Canyon 6 - Animal 5", "Sand Canyon 6 - Animal 6"], "music": 40}, {"name": "Sand Canyon 6 - 22", "level": 3, "stage": 6, "room": 22, "pointer": 2950938, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 38, "unkn1": 14, "unkn2": 8, "x": 168, "y": 376, "name": "Sand Canyon 6 - 22 Exit 0", "access_rule": []}, {"room": 1, "unkn1": 9, "unkn2": 13, "x": 376, "y": 216, "name": "Sand Canyon 6 - 22 Exit 1", "access_rule": []}, {"room": 21, "unkn1": 19, "unkn2": 13, "x": 168, "y": 216, "name": "Sand Canyon 6 - 22 Exit 2", "access_rule": []}], "entity_load": [[4, 22]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 23", "level": 3, "stage": 6, "room": 23, "pointer": 3311993, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 43, "unkn1": 145, "unkn2": 7, "x": 72, "y": 152, "name": "Sand Canyon 6 - 23 Exit 0", "access_rule": []}], "entity_load": [[14, 16]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 24", "level": 3, "stage": 6, "room": 24, "pointer": 3079078, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 39, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 24 Exit 0", "access_rule": []}, {"room": 25, "unkn1": 11, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 24 Exit 1", "access_rule": []}, {"room": 39, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 24 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 25", "level": 3, "stage": 6, "room": 25, "pointer": 3065898, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 26, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 25 Exit 0", "access_rule": []}, {"room": 40, "unkn1": 11, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 25 Exit 1", "access_rule": []}, {"room": 40, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 25 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 26", "level": 3, "stage": 6, "room": 26, "pointer": 3063347, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 41, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 26 Exit 0", "access_rule": []}, {"room": 41, "unkn1": 11, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 26 Exit 1", "access_rule": []}, {"room": 27, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 26 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 27", "level": 3, "stage": 6, "room": 27, "pointer": 3066916, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 28, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 27 Exit 0", "access_rule": []}, {"room": 42, "unkn1": 11, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 27 Exit 1", "access_rule": []}, {"room": 42, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 27 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 28", "level": 3, "stage": 6, "room": 28, "pointer": 3067425, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 29, "unkn1": 7, "unkn2": 8, "x": 120, "y": 344, "name": "Sand Canyon 6 - 28 Exit 0", "access_rule": []}, {"room": 29, "unkn1": 11, "unkn2": 8, "x": 184, "y": 344, "name": "Sand Canyon 6 - 28 Exit 1", "access_rule": []}, {"room": 29, "unkn1": 15, "unkn2": 8, "x": 248, "y": 344, "name": "Sand Canyon 6 - 28 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 29", "level": 3, "stage": 6, "room": 29, "pointer": 2950032, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 30, "unkn1": 11, "unkn2": 8, "x": 168, "y": 136, "name": "Sand Canyon 6 - 29 Exit 0", "access_rule": []}], "entity_load": [[19, 19]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 30", "level": 3, "stage": 6, "room": 30, "pointer": 2986500, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 10, "unkn2": 44, "x": 136, "y": 152, "name": "Sand Canyon 6 - 30 Exit 0", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 31", "level": 3, "stage": 6, "room": 31, "pointer": 3070930, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 32, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 31 Exit 0", "access_rule": []}, {"room": 36, "unkn1": 11, "unkn2": 8, "x": 168, "y": 648, "name": "Sand Canyon 6 - 31 Exit 1", "access_rule": []}, {"room": 32, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 31 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 32", "level": 3, "stage": 6, "room": 32, "pointer": 3054817, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Parasol)", "Bukiset (Cutter)"], "default_exits": [{"room": 33, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 32 Exit 0", "access_rule": []}, {"room": 36, "unkn1": 11, "unkn2": 8, "x": 168, "y": 536, "name": "Sand Canyon 6 - 32 Exit 1", "access_rule": []}, {"room": 33, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 32 Exit 2", "access_rule": []}], "entity_load": [[81, 16], [83, 16]], "locations": ["Sand Canyon 6 - Enemy 5 (Bukiset (Parasol))", "Sand Canyon 6 - Enemy 6 (Bukiset (Cutter))"], "music": 14}, {"name": "Sand Canyon 6 - 33", "level": 3, "stage": 6, "room": 33, "pointer": 3053173, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Clean)", "Bukiset (Spark)"], "default_exits": [{"room": 34, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 33 Exit 0", "access_rule": []}, {"room": 36, "unkn1": 11, "unkn2": 8, "x": 168, "y": 440, "name": "Sand Canyon 6 - 33 Exit 1", "access_rule": []}, {"room": 34, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 33 Exit 2", "access_rule": []}], "entity_load": [[80, 16], [82, 16]], "locations": ["Sand Canyon 6 - Enemy 7 (Bukiset (Clean))", "Sand Canyon 6 - Enemy 8 (Bukiset (Spark))"], "music": 14}, {"name": "Sand Canyon 6 - 34", "level": 3, "stage": 6, "room": 34, "pointer": 3053721, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Ice)", "Bukiset (Needle)"], "default_exits": [{"room": 35, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 34 Exit 0", "access_rule": []}, {"room": 36, "unkn1": 11, "unkn2": 8, "x": 168, "y": 344, "name": "Sand Canyon 6 - 34 Exit 1", "access_rule": []}, {"room": 35, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 34 Exit 2", "access_rule": []}], "entity_load": [[79, 16], [78, 16]], "locations": ["Sand Canyon 6 - Enemy 9 (Bukiset (Ice))", "Sand Canyon 6 - Enemy 10 (Bukiset (Needle))"], "music": 14}, {"name": "Sand Canyon 6 - 35", "level": 3, "stage": 6, "room": 35, "pointer": 3054269, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Burning)", "Bukiset (Stone)"], "default_exits": [{"room": 37, "unkn1": 7, "unkn2": 8, "x": 120, "y": 344, "name": "Sand Canyon 6 - 35 Exit 0", "access_rule": []}, {"room": 36, "unkn1": 11, "unkn2": 8, "x": 168, "y": 248, "name": "Sand Canyon 6 - 35 Exit 1", "access_rule": []}, {"room": 37, "unkn1": 15, "unkn2": 8, "x": 248, "y": 344, "name": "Sand Canyon 6 - 35 Exit 2", "access_rule": []}], "entity_load": [[77, 16], [76, 16]], "locations": ["Sand Canyon 6 - Enemy 11 (Bukiset (Burning))", "Sand Canyon 6 - Enemy 12 (Bukiset (Stone))"], "music": 14}, {"name": "Sand Canyon 6 - 36", "level": 3, "stage": 6, "room": 36, "pointer": 2986164, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 10, "unkn2": 44, "x": 392, "y": 152, "name": "Sand Canyon 6 - 36 Exit 0", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 37", "level": 3, "stage": 6, "room": 37, "pointer": 3074377, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 36, "unkn1": 11, "unkn2": 8, "x": 168, "y": 152, "name": "Sand Canyon 6 - 37 Exit 0", "access_rule": []}], "entity_load": [[19, 19]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 38", "level": 3, "stage": 6, "room": 38, "pointer": 2971589, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 9, "unkn1": 10, "unkn2": 5, "x": 264, "y": 440, "name": "Sand Canyon 6 - 38 Exit 0", "access_rule": []}, {"room": 22, "unkn1": 10, "unkn2": 23, "x": 248, "y": 136, "name": "Sand Canyon 6 - 38 Exit 1", "access_rule": []}], "entity_load": [[76, 16], [77, 16], [78, 16], [79, 16], [80, 16], [81, 16], [82, 16], [83, 16]], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 39", "level": 3, "stage": 6, "room": 39, "pointer": 3063858, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 40, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 39 Exit 0", "access_rule": []}, {"room": 40, "unkn1": 11, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 39 Exit 1", "access_rule": []}, {"room": 40, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 39 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 40", "level": 3, "stage": 6, "room": 40, "pointer": 3064368, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 41, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 40 Exit 0", "access_rule": []}, {"room": 41, "unkn1": 11, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 40 Exit 1", "access_rule": []}, {"room": 41, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 40 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 41", "level": 3, "stage": 6, "room": 41, "pointer": 3064878, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 42, "unkn1": 7, "unkn2": 8, "x": 120, "y": 328, "name": "Sand Canyon 6 - 41 Exit 0", "access_rule": []}, {"room": 42, "unkn1": 11, "unkn2": 8, "x": 184, "y": 328, "name": "Sand Canyon 6 - 41 Exit 1", "access_rule": []}, {"room": 42, "unkn1": 15, "unkn2": 8, "x": 248, "y": 328, "name": "Sand Canyon 6 - 41 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 14}, {"name": "Sand Canyon 6 - 42", "level": 3, "stage": 6, "room": 42, "pointer": 3068939, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Nidoo"], "default_exits": [{"room": 29, "unkn1": 7, "unkn2": 8, "x": 120, "y": 344, "name": "Sand Canyon 6 - 42 Exit 0", "access_rule": []}, {"room": 29, "unkn1": 15, "unkn2": 8, "x": 248, "y": 344, "name": "Sand Canyon 6 - 42 Exit 1", "access_rule": []}], "entity_load": [[28, 16]], "locations": ["Sand Canyon 6 - Enemy 13 (Nidoo)"], "music": 14}, {"name": "Sand Canyon 6 - 43", "level": 3, "stage": 6, "room": 43, "pointer": 2988495, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 44, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Sand Canyon 6 - 43 Exit 0", "access_rule": []}], "entity_load": [[19, 19], [42, 19]], "locations": ["Sand Canyon 6 - Professor Hector & R.O.B"], "music": 8}, {"name": "Sand Canyon 6 - 44", "level": 3, "stage": 6, "room": 44, "pointer": 2886979, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Sand Canyon 6 - Complete"], "music": 5}, {"name": "Sand Canyon Boss - 0", "level": 3, "stage": 7, "room": 0, "pointer": 2991389, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[6, 18]], "locations": ["Sand Canyon - Boss (Pon & Con) Purified", "Level 3 Boss - Defeated", "Level 3 Boss - Purified"], "music": 2}, {"name": "Cloudy Park 1 - 0", "level": 4, "stage": 1, "room": 0, "pointer": 3172795, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "KeKe", "Cappy"], "default_exits": [{"room": 1, "unkn1": 95, "unkn2": 5, "x": 56, "y": 152, "name": "Cloudy Park 1 - 0 Exit 0", "access_rule": []}], "entity_load": [[0, 16], [12, 16], [51, 16], [1, 23], [0, 23]], "locations": ["Cloudy Park 1 - Enemy 1 (Waddle Dee)", "Cloudy Park 1 - Enemy 2 (KeKe)", "Cloudy Park 1 - Enemy 3 (Cappy)"], "music": 17}, {"name": "Cloudy Park 1 - 1", "level": 4, "stage": 1, "room": 1, "pointer": 3078163, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 14, "unkn2": 8, "x": 88, "y": 424, "name": "Cloudy Park 1 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 1 - Animal 1"], "music": 39}, {"name": "Cloudy Park 1 - 2", "level": 4, "stage": 1, "room": 2, "pointer": 3285882, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Yaban", "Togezo"], "default_exits": [{"room": 3, "unkn1": 95, "unkn2": 8, "x": 40, "y": 104, "name": "Cloudy Park 1 - 2 Exit 0", "access_rule": []}], "entity_load": [[32, 16], [4, 16], [18, 16], [6, 23]], "locations": ["Cloudy Park 1 - Enemy 4 (Yaban)", "Cloudy Park 1 - Enemy 5 (Togezo)"], "music": 17}, {"name": "Cloudy Park 1 - 3", "level": 4, "stage": 1, "room": 3, "pointer": 3062831, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 15, "unkn2": 6, "x": 56, "y": 264, "name": "Cloudy Park 1 - 3 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 1 - Animal 2"], "music": 39}, {"name": "Cloudy Park 1 - 4", "level": 4, "stage": 1, "room": 4, "pointer": 3396729, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Galbo"], "default_exits": [{"room": 7, "unkn1": 92, "unkn2": 16, "x": 56, "y": 152, "name": "Cloudy Park 1 - 4 Exit 0", "access_rule": []}], "entity_load": [[54, 16], [0, 16], [14, 23], [68, 16], [26, 16], [4, 22]], "locations": ["Cloudy Park 1 - Enemy 6 (Galbo)", "Cloudy Park 1 - Star 1"], "music": 17}, {"name": "Cloudy Park 1 - 5", "level": 4, "stage": 1, "room": 5, "pointer": 3038805, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 6, "unkn2": 5, "x": 2344, "y": 232, "name": "Cloudy Park 1 - 5 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 1 - Animal 3", "Cloudy Park 1 - Animal 4"], "music": 39}, {"name": "Cloudy Park 1 - 6", "level": 4, "stage": 1, "room": 6, "pointer": 3535736, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sparky", "Como"], "default_exits": [{"room": 8, "unkn1": 5, "unkn2": 8, "x": 72, "y": 104, "name": "Cloudy Park 1 - 6 Exit 0", "access_rule": []}], "entity_load": [[54, 16], [14, 23], [0, 16], [41, 16], [8, 16]], "locations": ["Cloudy Park 1 - Enemy 7 (Sparky)", "Cloudy Park 1 - Enemy 8 (Como)", "Cloudy Park 1 - Star 2"], "music": 17}, {"name": "Cloudy Park 1 - 7", "level": 4, "stage": 1, "room": 7, "pointer": 2954080, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 14, "unkn2": 6, "x": 88, "y": 104, "name": "Cloudy Park 1 - 7 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 1 - Animal 5"], "music": 39}, {"name": "Cloudy Park 1 - 8", "level": 4, "stage": 1, "room": 8, "pointer": 3465407, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bronto Burt"], "default_exits": [{"room": 12, "unkn1": 181, "unkn2": 6, "x": 56, "y": 152, "name": "Cloudy Park 1 - 8 Exit 0", "access_rule": []}], "entity_load": [[21, 19], [2, 16], [51, 16], [1, 23], [14, 23]], "locations": ["Cloudy Park 1 - Enemy 9 (Bronto Burt)", "Cloudy Park 1 - Star 3", "Cloudy Park 1 - Star 4", "Cloudy Park 1 - Star 5", "Cloudy Park 1 - Star 6", "Cloudy Park 1 - Star 7", "Cloudy Park 1 - Star 8", "Cloudy Park 1 - Star 9", "Cloudy Park 1 - Star 10", "Cloudy Park 1 - Star 11"], "music": 17}, {"name": "Cloudy Park 1 - 9", "level": 4, "stage": 1, "room": 9, "pointer": 3078621, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 14, "unkn2": 9, "x": 56, "y": 264, "name": "Cloudy Park 1 - 9 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 1 - Animal 6"], "music": 39}, {"name": "Cloudy Park 1 - 10", "level": 4, "stage": 1, "room": 10, "pointer": 3281366, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Gabon", "Sir Kibble"], "default_exits": [{"room": 9, "unkn1": 99, "unkn2": 8, "x": 56, "y": 152, "name": "Cloudy Park 1 - 10 Exit 0", "access_rule": []}], "entity_load": [[1, 16], [24, 16], [27, 16], [4, 23]], "locations": ["Cloudy Park 1 - Enemy 10 (Gabon)", "Cloudy Park 1 - Enemy 11 (Sir Kibble)"], "music": 17}, {"name": "Cloudy Park 1 - 11", "level": 4, "stage": 1, "room": 11, "pointer": 3519608, "animal_pointers": [], "consumables": [{"idx": 19, "pointer": 192, "x": 216, "y": 600, "etype": 22, "vtype": 0, "name": "Cloudy Park 1 - 1-Up (Shotzo)"}, {"idx": 18, "pointer": 456, "x": 856, "y": 408, "etype": 22, "vtype": 2, "name": "Cloudy Park 1 - Maxim Tomato (Mariel)"}], "consumables_pointer": 352, "enemies": ["Mariel", "Nruff"], "default_exits": [{"room": 5, "unkn1": 64, "unkn2": 6, "x": 104, "y": 216, "name": "Cloudy Park 1 - 11 Exit 0", "access_rule": []}], "entity_load": [[14, 16], [27, 16], [15, 16], [45, 16], [14, 23], [2, 22], [0, 22], [6, 22]], "locations": ["Cloudy Park 1 - Enemy 12 (Mariel)", "Cloudy Park 1 - Enemy 13 (Nruff)", "Cloudy Park 1 - Star 12", "Cloudy Park 1 - Star 13", "Cloudy Park 1 - Star 14", "Cloudy Park 1 - Star 15", "Cloudy Park 1 - Star 16", "Cloudy Park 1 - Star 17", "Cloudy Park 1 - Star 18", "Cloudy Park 1 - Star 19", "Cloudy Park 1 - Star 20", "Cloudy Park 1 - Star 21", "Cloudy Park 1 - Star 22", "Cloudy Park 1 - Star 23", "Cloudy Park 1 - 1-Up (Shotzo)", "Cloudy Park 1 - Maxim Tomato (Mariel)"], "music": 17}, {"name": "Cloudy Park 1 - 12", "level": 4, "stage": 1, "room": 12, "pointer": 2958914, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 13, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Cloudy Park 1 - 12 Exit 0", "access_rule": []}], "entity_load": [[21, 19], [42, 19]], "locations": ["Cloudy Park 1 - Hibanamodoki"], "music": 8}, {"name": "Cloudy Park 1 - 13", "level": 4, "stage": 1, "room": 13, "pointer": 2886015, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Cloudy Park 1 - Complete"], "music": 5}, {"name": "Cloudy Park 2 - 0", "level": 4, "stage": 2, "room": 0, "pointer": 3142443, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Chilly", "Sasuke"], "default_exits": [{"room": 6, "unkn1": 96, "unkn2": 7, "x": 56, "y": 88, "name": "Cloudy Park 2 - 0 Exit 0", "access_rule": []}], "entity_load": [[23, 16], [7, 16], [30, 16], [6, 16], [14, 23]], "locations": ["Cloudy Park 2 - Enemy 1 (Chilly)", "Cloudy Park 2 - Enemy 2 (Sasuke)", "Cloudy Park 2 - Star 1", "Cloudy Park 2 - Star 2", "Cloudy Park 2 - Star 3", "Cloudy Park 2 - Star 4", "Cloudy Park 2 - Star 5", "Cloudy Park 2 - Star 6"], "music": 19}, {"name": "Cloudy Park 2 - 1", "level": 4, "stage": 2, "room": 1, "pointer": 3235925, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Sparky", "Broom Hatter"], "default_exits": [{"room": 2, "unkn1": 146, "unkn2": 5, "x": 88, "y": 88, "name": "Cloudy Park 2 - 1 Exit 0", "access_rule": []}], "entity_load": [[0, 16], [14, 23], [8, 16], [11, 16]], "locations": ["Cloudy Park 2 - Enemy 3 (Waddle Dee)", "Cloudy Park 2 - Enemy 4 (Sparky)", "Cloudy Park 2 - Enemy 5 (Broom Hatter)", "Cloudy Park 2 - Star 7", "Cloudy Park 2 - Star 8", "Cloudy Park 2 - Star 9", "Cloudy Park 2 - Star 10", "Cloudy Park 2 - Star 11", "Cloudy Park 2 - Star 12", "Cloudy Park 2 - Star 13", "Cloudy Park 2 - Star 14", "Cloudy Park 2 - Star 15"], "music": 19}, {"name": "Cloudy Park 2 - 2", "level": 4, "stage": 2, "room": 2, "pointer": 3297758, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sir Kibble", "Pteran"], "default_exits": [{"room": 3, "unkn1": 147, "unkn2": 8, "x": 72, "y": 136, "name": "Cloudy Park 2 - 2 Exit 0", "access_rule": []}], "entity_load": [[27, 16], [39, 16], [1, 23]], "locations": ["Cloudy Park 2 - Enemy 6 (Sir Kibble)", "Cloudy Park 2 - Enemy 7 (Pteran)"], "music": 19}, {"name": "Cloudy Park 2 - 3", "level": 4, "stage": 2, "room": 3, "pointer": 3341087, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller", "Dogon"], "default_exits": [{"room": 4, "unkn1": 145, "unkn2": 12, "x": 72, "y": 136, "name": "Cloudy Park 2 - 3 Exit 0", "access_rule": []}], "entity_load": [[4, 16], [14, 23], [90, 16], [89, 16]], "locations": ["Cloudy Park 2 - Enemy 8 (Propeller)", "Cloudy Park 2 - Enemy 9 (Dogon)", "Cloudy Park 2 - Star 16", "Cloudy Park 2 - Star 17", "Cloudy Park 2 - Star 18", "Cloudy Park 2 - Star 19", "Cloudy Park 2 - Star 20", "Cloudy Park 2 - Star 21", "Cloudy Park 2 - Star 22", "Cloudy Park 2 - Star 23", "Cloudy Park 2 - Star 24", "Cloudy Park 2 - Star 25", "Cloudy Park 2 - Star 26", "Cloudy Park 2 - Star 27", "Cloudy Park 2 - Star 28", "Cloudy Park 2 - Star 29", "Cloudy Park 2 - Star 30", "Cloudy Park 2 - Star 31", "Cloudy Park 2 - Star 32", "Cloudy Park 2 - Star 33", "Cloudy Park 2 - Star 34", "Cloudy Park 2 - Star 35", "Cloudy Park 2 - Star 36", "Cloudy Park 2 - Star 37"], "music": 19}, {"name": "Cloudy Park 2 - 4", "level": 4, "stage": 2, "room": 4, "pointer": 3457404, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Togezo", "Oro", "Bronto Burt", "Rocky"], "default_exits": [{"room": 5, "unkn1": 165, "unkn2": 5, "x": 72, "y": 104, "name": "Cloudy Park 2 - 4 Exit 0", "access_rule": []}], "entity_load": [[2, 16], [3, 16], [14, 23], [18, 16], [25, 16]], "locations": ["Cloudy Park 2 - Enemy 10 (Togezo)", "Cloudy Park 2 - Enemy 11 (Oro)", "Cloudy Park 2 - Enemy 12 (Bronto Burt)", "Cloudy Park 2 - Enemy 13 (Rocky)", "Cloudy Park 2 - Star 38", "Cloudy Park 2 - Star 39", "Cloudy Park 2 - Star 40", "Cloudy Park 2 - Star 41", "Cloudy Park 2 - Star 42", "Cloudy Park 2 - Star 43", "Cloudy Park 2 - Star 44", "Cloudy Park 2 - Star 45", "Cloudy Park 2 - Star 46", "Cloudy Park 2 - Star 47"], "music": 19}, {"name": "Cloudy Park 2 - 5", "level": 4, "stage": 2, "room": 5, "pointer": 3273878, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Galbo", "Kapar"], "default_exits": [{"room": 7, "unkn1": 96, "unkn2": 8, "x": 56, "y": 88, "name": "Cloudy Park 2 - 5 Exit 0", "access_rule": []}], "entity_load": [[14, 23], [62, 16], [26, 16], [67, 16]], "locations": ["Cloudy Park 2 - Enemy 14 (Galbo)", "Cloudy Park 2 - Enemy 15 (Kapar)", "Cloudy Park 2 - Star 48", "Cloudy Park 2 - Star 49", "Cloudy Park 2 - Star 50", "Cloudy Park 2 - Star 51", "Cloudy Park 2 - Star 52", "Cloudy Park 2 - Star 53", "Cloudy Park 2 - Star 54"], "music": 19}, {"name": "Cloudy Park 2 - 6", "level": 4, "stage": 2, "room": 6, "pointer": 2984453, "animal_pointers": [192, 200, 208], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 25, "unkn2": 5, "x": 72, "y": 152, "name": "Cloudy Park 2 - 6 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 2 - Animal 1", "Cloudy Park 2 - Animal 2", "Cloudy Park 2 - Animal 3"], "music": 38}, {"name": "Cloudy Park 2 - 7", "level": 4, "stage": 2, "room": 7, "pointer": 2985482, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 15, "unkn2": 5, "x": 40, "y": 88, "name": "Cloudy Park 2 - 7 Exit 0", "access_rule": []}], "entity_load": [[22, 19]], "locations": [], "music": 19}, {"name": "Cloudy Park 2 - 8", "level": 4, "stage": 2, "room": 8, "pointer": 2990753, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 9, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Cloudy Park 2 - 8 Exit 0", "access_rule": []}], "entity_load": [[22, 19], [42, 19]], "locations": ["Cloudy Park 2 - Piyo & Keko"], "music": 8}, {"name": "Cloudy Park 2 - 9", "level": 4, "stage": 2, "room": 9, "pointer": 2889630, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Cloudy Park 2 - Complete"], "music": 5}, {"name": "Cloudy Park 3 - 0", "level": 4, "stage": 3, "room": 0, "pointer": 3100859, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bronto Burt", "Mopoo", "Poppy Bros Jr."], "default_exits": [{"room": 2, "unkn1": 145, "unkn2": 8, "x": 56, "y": 136, "name": "Cloudy Park 3 - 0 Exit 0", "access_rule": []}], "entity_load": [[2, 16], [74, 16], [47, 16], [7, 16], [14, 23]], "locations": ["Cloudy Park 3 - Enemy 1 (Bronto Burt)", "Cloudy Park 3 - Enemy 2 (Mopoo)", "Cloudy Park 3 - Enemy 3 (Poppy Bros Jr.)", "Cloudy Park 3 - Star 1", "Cloudy Park 3 - Star 2", "Cloudy Park 3 - Star 3", "Cloudy Park 3 - Star 4", "Cloudy Park 3 - Star 5", "Cloudy Park 3 - Star 6", "Cloudy Park 3 - Star 7"], "music": 15}, {"name": "Cloudy Park 3 - 1", "level": 4, "stage": 3, "room": 1, "pointer": 3209719, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Como"], "default_exits": [{"room": 5, "unkn1": 13, "unkn2": 14, "x": 56, "y": 152, "name": "Cloudy Park 3 - 1 Exit 0", "access_rule": []}], "entity_load": [[10, 23], [31, 16], [4, 22], [41, 16], [14, 23]], "locations": ["Cloudy Park 3 - Enemy 4 (Como)", "Cloudy Park 3 - Star 8", "Cloudy Park 3 - Star 9"], "music": 15}, {"name": "Cloudy Park 3 - 2", "level": 4, "stage": 3, "room": 2, "pointer": 3216185, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Glunk", "Bobin", "Loud", "Kapar"], "default_exits": [{"room": 1, "unkn1": 145, "unkn2": 6, "x": 216, "y": 1064, "name": "Cloudy Park 3 - 2 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 24, "unkn2": 14, "x": 104, "y": 152, "name": "Cloudy Park 3 - 2 Exit 1", "access_rule": []}], "entity_load": [[67, 16], [40, 16], [73, 16], [14, 23], [16, 16]], "locations": ["Cloudy Park 3 - Enemy 5 (Glunk)", "Cloudy Park 3 - Enemy 6 (Bobin)", "Cloudy Park 3 - Enemy 7 (Loud)", "Cloudy Park 3 - Enemy 8 (Kapar)", "Cloudy Park 3 - Star 10", "Cloudy Park 3 - Star 11", "Cloudy Park 3 - Star 12", "Cloudy Park 3 - Star 13", "Cloudy Park 3 - Star 14", "Cloudy Park 3 - Star 15", "Cloudy Park 3 - Star 16"], "music": 15}, {"name": "Cloudy Park 3 - 3", "level": 4, "stage": 3, "room": 3, "pointer": 2994208, "animal_pointers": [192, 200, 208], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 5, "unkn2": 9, "x": 408, "y": 232, "name": "Cloudy Park 3 - 3 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 3 - Animal 1", "Cloudy Park 3 - Animal 2", "Cloudy Park 3 - Animal 3"], "music": 40}, {"name": "Cloudy Park 3 - 4", "level": 4, "stage": 3, "room": 4, "pointer": 3229151, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Galbo", "Batamon", "Bouncy"], "default_exits": [{"room": 6, "unkn1": 156, "unkn2": 6, "x": 56, "y": 152, "name": "Cloudy Park 3 - 4 Exit 0", "access_rule": []}, {"room": 6, "unkn1": 126, "unkn2": 9, "x": 56, "y": 152, "name": "Cloudy Park 3 - 4 Exit 1", "access_rule": []}], "entity_load": [[7, 16], [26, 16], [14, 23], [13, 16], [68, 16]], "locations": ["Cloudy Park 3 - Enemy 9 (Galbo)", "Cloudy Park 3 - Enemy 10 (Batamon)", "Cloudy Park 3 - Enemy 11 (Bouncy)", "Cloudy Park 3 - Star 17", "Cloudy Park 3 - Star 18", "Cloudy Park 3 - Star 19", "Cloudy Park 3 - Star 20", "Cloudy Park 3 - Star 21", "Cloudy Park 3 - Star 22"], "music": 15}, {"name": "Cloudy Park 3 - 5", "level": 4, "stage": 3, "room": 5, "pointer": 2969244, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 14, "unkn2": 9, "x": 88, "y": 152, "name": "Cloudy Park 3 - 5 Exit 0", "access_rule": []}], "entity_load": [[23, 19]], "locations": [], "music": 31}, {"name": "Cloudy Park 3 - 6", "level": 4, "stage": 3, "room": 6, "pointer": 4128530, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Cloudy Park 3 - 6 Exit 0", "access_rule": []}], "entity_load": [[23, 19]], "locations": ["Cloudy Park 3 - Mr. Ball"], "music": 8}, {"name": "Cloudy Park 3 - 7", "level": 4, "stage": 3, "room": 7, "pointer": 2885051, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Cloudy Park 3 - Complete"], "music": 5}, {"name": "Cloudy Park 4 - 0", "level": 4, "stage": 4, "room": 0, "pointer": 3072905, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 95, "unkn2": 9, "x": 88, "y": 152, "name": "Cloudy Park 4 - 0 Exit 0", "access_rule": []}], "entity_load": [[4, 23], [1, 23], [0, 23], [31, 16]], "locations": [], "music": 21}, {"name": "Cloudy Park 4 - 1", "level": 4, "stage": 4, "room": 1, "pointer": 3074863, "animal_pointers": [208, 224], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 21, "unkn2": 9, "x": 72, "y": 152, "name": "Cloudy Park 4 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 4 - Animal 1", "Cloudy Park 4 - Animal 2"], "music": 38}, {"name": "Cloudy Park 4 - 2", "level": 4, "stage": 4, "room": 2, "pointer": 3309209, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Gabon", "Como", "Wapod", "Cappy"], "default_exits": [{"room": 3, "unkn1": 145, "unkn2": 9, "x": 104, "y": 152, "name": "Cloudy Park 4 - 2 Exit 0", "access_rule": []}], "entity_load": [[88, 16], [24, 16], [12, 16], [14, 23], [41, 16], [4, 22]], "locations": ["Cloudy Park 4 - Enemy 1 (Gabon)", "Cloudy Park 4 - Enemy 2 (Como)", "Cloudy Park 4 - Enemy 3 (Wapod)", "Cloudy Park 4 - Enemy 4 (Cappy)", "Cloudy Park 4 - Star 1", "Cloudy Park 4 - Star 2", "Cloudy Park 4 - Star 3", "Cloudy Park 4 - Star 4", "Cloudy Park 4 - Star 5", "Cloudy Park 4 - Star 6", "Cloudy Park 4 - Star 7", "Cloudy Park 4 - Star 8", "Cloudy Park 4 - Star 9", "Cloudy Park 4 - Star 10", "Cloudy Park 4 - Star 11", "Cloudy Park 4 - Star 12"], "music": 21}, {"name": "Cloudy Park 4 - 3", "level": 4, "stage": 4, "room": 3, "pointer": 3296291, "animal_pointers": [], "consumables": [{"idx": 33, "pointer": 776, "x": 1880, "y": 152, "etype": 22, "vtype": 0, "name": "Cloudy Park 4 - 1-Up (Windy)"}, {"idx": 34, "pointer": 912, "x": 2160, "y": 152, "etype": 22, "vtype": 2, "name": "Cloudy Park 4 - Maxim Tomato (Windy)"}], "consumables_pointer": 304, "enemies": ["Sparky", "Togezo"], "default_exits": [{"room": 5, "unkn1": 144, "unkn2": 9, "x": 56, "y": 136, "name": "Cloudy Park 4 - 3 Exit 0", "access_rule": []}], "entity_load": [[18, 16], [8, 16], [31, 16], [14, 23], [4, 22], [4, 16], [0, 22], [2, 22]], "locations": ["Cloudy Park 4 - Enemy 5 (Sparky)", "Cloudy Park 4 - Enemy 6 (Togezo)", "Cloudy Park 4 - Star 13", "Cloudy Park 4 - Star 14", "Cloudy Park 4 - Star 15", "Cloudy Park 4 - Star 16", "Cloudy Park 4 - Star 17", "Cloudy Park 4 - Star 18", "Cloudy Park 4 - Star 19", "Cloudy Park 4 - Star 20", "Cloudy Park 4 - Star 21", "Cloudy Park 4 - Star 22", "Cloudy Park 4 - Star 23", "Cloudy Park 4 - Star 24", "Cloudy Park 4 - Star 25", "Cloudy Park 4 - Star 26", "Cloudy Park 4 - Star 27", "Cloudy Park 4 - Star 28", "Cloudy Park 4 - Star 29", "Cloudy Park 4 - Star 30", "Cloudy Park 4 - 1-Up (Windy)", "Cloudy Park 4 - Maxim Tomato (Windy)"], "music": 21}, {"name": "Cloudy Park 4 - 4", "level": 4, "stage": 4, "room": 4, "pointer": 3330996, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bronto Burt", "KeKe", "Bouncy"], "default_exits": [{"room": 7, "unkn1": 4, "unkn2": 15, "x": 72, "y": 152, "name": "Cloudy Park 4 - 4 Exit 0", "access_rule": []}], "entity_load": [[13, 16], [51, 16], [17, 16], [14, 23], [2, 16]], "locations": ["Cloudy Park 4 - Enemy 7 (Bronto Burt)", "Cloudy Park 4 - Enemy 8 (KeKe)", "Cloudy Park 4 - Enemy 9 (Bouncy)", "Cloudy Park 4 - Star 31", "Cloudy Park 4 - Star 32", "Cloudy Park 4 - Star 33", "Cloudy Park 4 - Star 34", "Cloudy Park 4 - Star 35", "Cloudy Park 4 - Star 36", "Cloudy Park 4 - Star 37", "Cloudy Park 4 - Star 38"], "music": 21}, {"name": "Cloudy Park 4 - 5", "level": 4, "stage": 4, "room": 5, "pointer": 3332300, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sir Kibble", "Mariel"], "default_exits": [{"room": 4, "unkn1": 21, "unkn2": 51, "x": 2328, "y": 120, "name": "Cloudy Park 4 - 5 Exit 0", "access_rule": []}], "entity_load": [[45, 16], [46, 16], [27, 16], [14, 23]], "locations": ["Cloudy Park 4 - Enemy 10 (Sir Kibble)", "Cloudy Park 4 - Enemy 11 (Mariel)", "Cloudy Park 4 - Star 39", "Cloudy Park 4 - Star 40", "Cloudy Park 4 - Star 41", "Cloudy Park 4 - Star 42", "Cloudy Park 4 - Star 43"], "music": 21}, {"name": "Cloudy Park 4 - 6", "level": 4, "stage": 4, "room": 6, "pointer": 3253310, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Kabu", "Wappa"], "default_exits": [{"room": 9, "unkn1": 3, "unkn2": 6, "x": 72, "y": 152, "name": "Cloudy Park 4 - 6 Exit 0", "access_rule": []}], "entity_load": [[44, 16], [31, 16], [19, 16], [14, 23]], "locations": ["Cloudy Park 4 - Enemy 12 (Kabu)", "Cloudy Park 4 - Enemy 13 (Wappa)", "Cloudy Park 4 - Star 44", "Cloudy Park 4 - Star 45", "Cloudy Park 4 - Star 46", "Cloudy Park 4 - Star 47", "Cloudy Park 4 - Star 48", "Cloudy Park 4 - Star 49", "Cloudy Park 4 - Star 50"], "music": 21}, {"name": "Cloudy Park 4 - 7", "level": 4, "stage": 4, "room": 7, "pointer": 2967658, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 12, "unkn2": 9, "x": 152, "y": 120, "name": "Cloudy Park 4 - 7 Exit 0", "access_rule": []}], "entity_load": [[3, 27]], "locations": ["Cloudy Park 4 - Miniboss 1 (Jumper Shoot)"], "music": 4}, {"name": "Cloudy Park 4 - 8", "level": 4, "stage": 4, "room": 8, "pointer": 2981644, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 13, "unkn2": 9, "x": 344, "y": 680, "name": "Cloudy Park 4 - 8 Exit 0", "access_rule": []}], "entity_load": [[24, 19]], "locations": [], "music": 21}, {"name": "Cloudy Park 4 - 9", "level": 4, "stage": 4, "room": 9, "pointer": 3008001, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 13, "unkn2": 9, "x": 72, "y": 120, "name": "Cloudy Park 4 - 9 Exit 0", "access_rule": []}], "entity_load": [[24, 19], [42, 19]], "locations": ["Cloudy Park 4 - Mikarin & Kagami Mocchi"], "music": 8}, {"name": "Cloudy Park 4 - 10", "level": 4, "stage": 4, "room": 10, "pointer": 2888184, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Cloudy Park 4 - Complete"], "music": 5}, {"name": "Cloudy Park 5 - 0", "level": 4, "stage": 5, "room": 0, "pointer": 3192630, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Yaban", "Sir Kibble", "Cappy", "Wappa"], "default_exits": [{"room": 1, "unkn1": 146, "unkn2": 6, "x": 168, "y": 616, "name": "Cloudy Park 5 - 0 Exit 0", "access_rule": []}], "entity_load": [[32, 16], [27, 16], [44, 16], [12, 16], [14, 23]], "locations": ["Cloudy Park 5 - Enemy 1 (Yaban)", "Cloudy Park 5 - Enemy 2 (Sir Kibble)", "Cloudy Park 5 - Enemy 3 (Cappy)", "Cloudy Park 5 - Enemy 4 (Wappa)", "Cloudy Park 5 - Star 1", "Cloudy Park 5 - Star 2"], "music": 17}, {"name": "Cloudy Park 5 - 1", "level": 4, "stage": 5, "room": 1, "pointer": 3050956, "animal_pointers": [], "consumables": [{"idx": 5, "pointer": 264, "x": 480, "y": 720, "etype": 22, "vtype": 2, "name": "Cloudy Park 5 - Maxim Tomato (Pillars)"}], "consumables_pointer": 288, "enemies": ["Galbo", "Bronto Burt", "KeKe"], "default_exits": [{"room": 2, "unkn1": 32, "unkn2": 9, "x": 72, "y": 152, "name": "Cloudy Park 5 - 1 Exit 0", "access_rule": []}], "entity_load": [[2, 16], [51, 16], [26, 16], [14, 23], [2, 22]], "locations": ["Cloudy Park 5 - Enemy 5 (Galbo)", "Cloudy Park 5 - Enemy 6 (Bronto Burt)", "Cloudy Park 5 - Enemy 7 (KeKe)", "Cloudy Park 5 - Star 3", "Cloudy Park 5 - Star 4", "Cloudy Park 5 - Star 5", "Cloudy Park 5 - Maxim Tomato (Pillars)"], "music": 17}, {"name": "Cloudy Park 5 - 2", "level": 4, "stage": 5, "room": 2, "pointer": 3604194, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 3, "unkn1": 17, "unkn2": 9, "x": 72, "y": 88, "name": "Cloudy Park 5 - 2 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 5 - Animal 1", "Cloudy Park 5 - Animal 2"], "music": 40}, {"name": "Cloudy Park 5 - 3", "level": 4, "stage": 5, "room": 3, "pointer": 3131242, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller", "Klinko"], "default_exits": [{"room": 4, "unkn1": 98, "unkn2": 5, "x": 72, "y": 120, "name": "Cloudy Park 5 - 3 Exit 0", "access_rule": []}], "entity_load": [[89, 16], [4, 16], [42, 16], [4, 23]], "locations": ["Cloudy Park 5 - Enemy 8 (Propeller)", "Cloudy Park 5 - Enemy 9 (Klinko)"], "music": 17}, {"name": "Cloudy Park 5 - 4", "level": 4, "stage": 5, "room": 4, "pointer": 2990116, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Wapod"], "default_exits": [{"room": 5, "unkn1": 23, "unkn2": 7, "x": 216, "y": 744, "name": "Cloudy Park 5 - 4 Exit 0", "access_rule": []}], "entity_load": [[88, 16]], "locations": ["Cloudy Park 5 - Enemy 10 (Wapod)"], "music": 17}, {"name": "Cloudy Park 5 - 5", "level": 4, "stage": 5, "room": 5, "pointer": 2975410, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 5, "unkn2": 4, "x": 104, "y": 296, "name": "Cloudy Park 5 - 5 Exit 0", "access_rule": []}], "entity_load": [[1, 16], [14, 23], [2, 16]], "locations": ["Cloudy Park 5 - Star 6"], "music": 17}, {"name": "Cloudy Park 5 - 6", "level": 4, "stage": 5, "room": 6, "pointer": 3173683, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Pteran"], "default_exits": [{"room": 7, "unkn1": 115, "unkn2": 6, "x": 56, "y": 136, "name": "Cloudy Park 5 - 6 Exit 0", "access_rule": []}], "entity_load": [[2, 16], [1, 23], [39, 16], [70, 16]], "locations": ["Cloudy Park 5 - Enemy 11 (Pteran)"], "music": 17}, {"name": "Cloudy Park 5 - 7", "level": 4, "stage": 5, "room": 7, "pointer": 2992340, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 14, "unkn2": 8, "x": 72, "y": 120, "name": "Cloudy Park 5 - 7 Exit 0", "access_rule": []}], "entity_load": [[25, 19], [42, 19]], "locations": ["Cloudy Park 5 - Pick"], "music": 8}, {"name": "Cloudy Park 5 - 8", "level": 4, "stage": 5, "room": 8, "pointer": 2891558, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Cloudy Park 5 - Complete"], "music": 5}, {"name": "Cloudy Park 6 - 0", "level": 4, "stage": 6, "room": 0, "pointer": 3269847, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 65, "unkn2": 9, "x": 88, "y": 152, "name": "Cloudy Park 6 - 0 Exit 0", "access_rule": []}], "entity_load": [[14, 23]], "locations": ["Cloudy Park 6 - Star 1"], "music": 11}, {"name": "Cloudy Park 6 - 1", "level": 4, "stage": 6, "room": 1, "pointer": 3252248, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 3, "unkn2": 25, "x": 72, "y": 72, "name": "Cloudy Park 6 - 1 Exit 0", "access_rule": []}], "entity_load": [[14, 16], [55, 16]], "locations": [], "music": 11}, {"name": "Cloudy Park 6 - 2", "level": 4, "stage": 6, "room": 2, "pointer": 3028494, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Madoo"], "default_exits": [{"room": 0, "unkn1": 4, "unkn2": 9, "x": 1032, "y": 152, "name": "Cloudy Park 6 - 2 Exit 0", "access_rule": []}, {"room": 1, "unkn1": 13, "unkn2": 9, "x": 56, "y": 72, "name": "Cloudy Park 6 - 2 Exit 1", "access_rule": []}], "entity_load": [[58, 16], [14, 23]], "locations": ["Cloudy Park 6 - Enemy 1 (Madoo)", "Cloudy Park 6 - Star 2", "Cloudy Park 6 - Star 3", "Cloudy Park 6 - Star 4", "Cloudy Park 6 - Star 5"], "music": 11}, {"name": "Cloudy Park 6 - 3", "level": 4, "stage": 6, "room": 3, "pointer": 3131911, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 4, "unkn2": 9, "x": 72, "y": 152, "name": "Cloudy Park 6 - 3 Exit 0", "access_rule": []}, {"room": 10, "unkn1": 8, "unkn2": 9, "x": 136, "y": 152, "name": "Cloudy Park 6 - 3 Exit 1", "access_rule": []}, {"room": 10, "unkn1": 12, "unkn2": 9, "x": 200, "y": 152, "name": "Cloudy Park 6 - 3 Exit 2", "access_rule": []}, {"room": 10, "unkn1": 16, "unkn2": 9, "x": 264, "y": 152, "name": "Cloudy Park 6 - 3 Exit 3", "access_rule": []}, {"room": 10, "unkn1": 20, "unkn2": 9, "x": 328, "y": 152, "name": "Cloudy Park 6 - 3 Exit 4", "access_rule": []}], "entity_load": [[58, 16]], "locations": [], "music": 11}, {"name": "Cloudy Park 6 - 4", "level": 4, "stage": 6, "room": 4, "pointer": 3115416, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Tick", "Como"], "default_exits": [{"room": 8, "unkn1": 20, "unkn2": 4, "x": 72, "y": 488, "name": "Cloudy Park 6 - 4 Exit 0", "access_rule": []}, {"room": 11, "unkn1": 4, "unkn2": 9, "x": 72, "y": 152, "name": "Cloudy Park 6 - 4 Exit 1", "access_rule": []}, {"room": 11, "unkn1": 8, "unkn2": 9, "x": 200, "y": 152, "name": "Cloudy Park 6 - 4 Exit 2", "access_rule": []}, {"room": 11, "unkn1": 12, "unkn2": 9, "x": 328, "y": 152, "name": "Cloudy Park 6 - 4 Exit 3", "access_rule": []}, {"room": 11, "unkn1": 16, "unkn2": 9, "x": 136, "y": 152, "name": "Cloudy Park 6 - 4 Exit 4", "access_rule": []}, {"room": 11, "unkn1": 20, "unkn2": 9, "x": 264, "y": 152, "name": "Cloudy Park 6 - 4 Exit 5", "access_rule": []}], "entity_load": [[55, 16], [48, 16], [41, 16], [14, 23]], "locations": ["Cloudy Park 6 - Enemy 2 (Tick)", "Cloudy Park 6 - Enemy 3 (Como)", "Cloudy Park 6 - Star 6", "Cloudy Park 6 - Star 7", "Cloudy Park 6 - Star 8"], "music": 11}, {"name": "Cloudy Park 6 - 5", "level": 4, "stage": 6, "room": 5, "pointer": 3245809, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee Drawing", "Bronto Burt Drawing", "Bouncy Drawing"], "default_exits": [{"room": 15, "unkn1": 65, "unkn2": 9, "x": 72, "y": 152, "name": "Cloudy Park 6 - 5 Exit 0", "access_rule": []}], "entity_load": [[84, 16], [85, 16], [86, 16], [14, 23], [4, 22]], "locations": ["Cloudy Park 6 - Enemy 4 (Waddle Dee Drawing)", "Cloudy Park 6 - Enemy 5 (Bronto Burt Drawing)", "Cloudy Park 6 - Enemy 6 (Bouncy Drawing)", "Cloudy Park 6 - Star 9", "Cloudy Park 6 - Star 10", "Cloudy Park 6 - Star 11", "Cloudy Park 6 - Star 12", "Cloudy Park 6 - Star 13"], "music": 11}, {"name": "Cloudy Park 6 - 6", "level": 4, "stage": 6, "room": 6, "pointer": 3237044, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller"], "default_exits": [{"room": 7, "unkn1": 56, "unkn2": 9, "x": 72, "y": 136, "name": "Cloudy Park 6 - 6 Exit 0", "access_rule": []}], "entity_load": [[89, 16]], "locations": ["Cloudy Park 6 - Enemy 7 (Propeller)"], "music": 11}, {"name": "Cloudy Park 6 - 7", "level": 4, "stage": 6, "room": 7, "pointer": 3262705, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Mopoo"], "default_exits": [{"room": 13, "unkn1": 12, "unkn2": 8, "x": 184, "y": 216, "name": "Cloudy Park 6 - 7 Exit 0", "access_rule": []}, {"room": 14, "unkn1": 57, "unkn2": 8, "x": 184, "y": 216, "name": "Cloudy Park 6 - 7 Exit 1", "access_rule": []}, {"room": 9, "unkn1": 64, "unkn2": 8, "x": 72, "y": 120, "name": "Cloudy Park 6 - 7 Exit 2", "access_rule": []}], "entity_load": [[74, 16], [14, 23]], "locations": ["Cloudy Park 6 - Enemy 8 (Mopoo)", "Cloudy Park 6 - Star 14", "Cloudy Park 6 - Star 15", "Cloudy Park 6 - Star 16", "Cloudy Park 6 - Star 17"], "music": 11}, {"name": "Cloudy Park 6 - 8", "level": 4, "stage": 6, "room": 8, "pointer": 3027259, "animal_pointers": [], "consumables": [{"idx": 22, "pointer": 312, "x": 224, "y": 256, "etype": 22, "vtype": 0, "name": "Cloudy Park 6 - 1-Up (Cutter)"}], "consumables_pointer": 304, "enemies": ["Bukiset (Burning)", "Bukiset (Ice)", "Bukiset (Needle)", "Bukiset (Clean)", "Bukiset (Cutter)"], "default_exits": [{"room": 12, "unkn1": 13, "unkn2": 4, "x": 88, "y": 152, "name": "Cloudy Park 6 - 8 Exit 0", "access_rule": []}], "entity_load": [[78, 16], [80, 16], [76, 16], [79, 16], [83, 16], [14, 23], [4, 22], [0, 22]], "locations": ["Cloudy Park 6 - Enemy 9 (Bukiset (Burning))", "Cloudy Park 6 - Enemy 10 (Bukiset (Ice))", "Cloudy Park 6 - Enemy 11 (Bukiset (Needle))", "Cloudy Park 6 - Enemy 12 (Bukiset (Clean))", "Cloudy Park 6 - Enemy 13 (Bukiset (Cutter))", "Cloudy Park 6 - Star 18", "Cloudy Park 6 - Star 19", "Cloudy Park 6 - Star 20", "Cloudy Park 6 - Star 21", "Cloudy Park 6 - Star 22", "Cloudy Park 6 - Star 23", "Cloudy Park 6 - Star 24", "Cloudy Park 6 - Star 25", "Cloudy Park 6 - Star 26", "Cloudy Park 6 - Star 27", "Cloudy Park 6 - Star 28", "Cloudy Park 6 - Star 29", "Cloudy Park 6 - 1-Up (Cutter)"], "music": 11}, {"name": "Cloudy Park 6 - 9", "level": 4, "stage": 6, "room": 9, "pointer": 3089504, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 35, "unkn2": 7, "x": 72, "y": 72, "name": "Cloudy Park 6 - 9 Exit 0", "access_rule": []}], "entity_load": [[41, 16]], "locations": [], "music": 11}, {"name": "Cloudy Park 6 - 10", "level": 4, "stage": 6, "room": 10, "pointer": 3132579, "animal_pointers": [242, 250, 258], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 20, "unkn2": 4, "x": 72, "y": 152, "name": "Cloudy Park 6 - 10 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 4, "unkn2": 9, "x": 88, "y": 152, "name": "Cloudy Park 6 - 10 Exit 1", "access_rule": []}, {"room": 3, "unkn1": 8, "unkn2": 9, "x": 136, "y": 152, "name": "Cloudy Park 6 - 10 Exit 2", "access_rule": []}, {"room": 3, "unkn1": 12, "unkn2": 9, "x": 200, "y": 152, "name": "Cloudy Park 6 - 10 Exit 3", "access_rule": []}, {"room": 3, "unkn1": 16, "unkn2": 9, "x": 264, "y": 152, "name": "Cloudy Park 6 - 10 Exit 4", "access_rule": []}, {"room": 3, "unkn1": 20, "unkn2": 9, "x": 328, "y": 152, "name": "Cloudy Park 6 - 10 Exit 5", "access_rule": []}], "entity_load": [], "locations": ["Cloudy Park 6 - Animal 1", "Cloudy Park 6 - Animal 2", "Cloudy Park 6 - Animal 3"], "music": 40}, {"name": "Cloudy Park 6 - 11", "level": 4, "stage": 6, "room": 11, "pointer": 3017866, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 4, "unkn2": 9, "x": 72, "y": 152, "name": "Cloudy Park 6 - 11 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 8, "unkn2": 9, "x": 264, "y": 152, "name": "Cloudy Park 6 - 11 Exit 1", "access_rule": []}, {"room": 4, "unkn1": 12, "unkn2": 9, "x": 136, "y": 152, "name": "Cloudy Park 6 - 11 Exit 2", "access_rule": []}, {"room": 4, "unkn1": 16, "unkn2": 9, "x": 328, "y": 152, "name": "Cloudy Park 6 - 11 Exit 3", "access_rule": []}, {"room": 4, "unkn1": 20, "unkn2": 9, "x": 200, "y": 152, "name": "Cloudy Park 6 - 11 Exit 4", "access_rule": []}], "entity_load": [[58, 16]], "locations": [], "music": 11}, {"name": "Cloudy Park 6 - 12", "level": 4, "stage": 6, "room": 12, "pointer": 3036404, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 4, "unkn2": 9, "x": 200, "y": 72, "name": "Cloudy Park 6 - 12 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 13, "unkn2": 9, "x": 88, "y": 152, "name": "Cloudy Park 6 - 12 Exit 1", "access_rule": []}], "entity_load": [[58, 16], [14, 23]], "locations": ["Cloudy Park 6 - Star 30", "Cloudy Park 6 - Star 31", "Cloudy Park 6 - Star 32", "Cloudy Park 6 - Star 33"], "music": 11}, {"name": "Cloudy Park 6 - 13", "level": 4, "stage": 6, "room": 13, "pointer": 2965251, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 10, "unkn2": 13, "x": 216, "y": 136, "name": "Cloudy Park 6 - 13 Exit 0", "access_rule": []}], "entity_load": [[26, 19]], "locations": [], "music": 11}, {"name": "Cloudy Park 6 - 14", "level": 4, "stage": 6, "room": 14, "pointer": 3077236, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 10, "unkn2": 13, "x": 936, "y": 136, "name": "Cloudy Park 6 - 14 Exit 0", "access_rule": []}], "entity_load": [], "locations": [], "music": 11}, {"name": "Cloudy Park 6 - 15", "level": 4, "stage": 6, "room": 15, "pointer": 3061794, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 13, "unkn2": 9, "x": 72, "y": 120, "name": "Cloudy Park 6 - 15 Exit 0", "access_rule": []}], "entity_load": [[26, 19], [42, 19]], "locations": ["Cloudy Park 6 - HB-007"], "music": 8}, {"name": "Cloudy Park 6 - 16", "level": 4, "stage": 6, "room": 16, "pointer": 2888907, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Cloudy Park 6 - Complete"], "music": 5}, {"name": "Cloudy Park Boss - 0", "level": 4, "stage": 7, "room": 0, "pointer": 2998682, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[3, 18]], "locations": ["Cloudy Park - Boss (Ado) Purified", "Level 4 Boss - Defeated", "Level 4 Boss - Purified"], "music": 2}, {"name": "Iceberg 1 - 0", "level": 5, "stage": 1, "room": 0, "pointer": 3363111, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Klinko", "KeKe"], "default_exits": [{"room": 1, "unkn1": 104, "unkn2": 8, "x": 312, "y": 1384, "name": "Iceberg 1 - 0 Exit 0", "access_rule": []}], "entity_load": [[42, 16], [0, 16], [51, 16]], "locations": ["Iceberg 1 - Enemy 1 (Waddle Dee)", "Iceberg 1 - Enemy 2 (Klinko)", "Iceberg 1 - Enemy 3 (KeKe)"], "music": 18}, {"name": "Iceberg 1 - 1", "level": 5, "stage": 1, "room": 1, "pointer": 3596524, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Como", "Galbo", "Rocky"], "default_exits": [{"room": 2, "unkn1": 20, "unkn2": 8, "x": 72, "y": 344, "name": "Iceberg 1 - 1 Exit 0", "access_rule": []}], "entity_load": [[3, 16], [41, 16], [26, 16], [14, 23], [4, 22], [6, 23]], "locations": ["Iceberg 1 - Enemy 4 (Como)", "Iceberg 1 - Enemy 5 (Galbo)", "Iceberg 1 - Enemy 6 (Rocky)", "Iceberg 1 - Star 1", "Iceberg 1 - Star 2", "Iceberg 1 - Star 3", "Iceberg 1 - Star 4", "Iceberg 1 - Star 5", "Iceberg 1 - Star 6"], "music": 18}, {"name": "Iceberg 1 - 2", "level": 5, "stage": 1, "room": 2, "pointer": 3288880, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Kapar"], "default_exits": [{"room": 3, "unkn1": 49, "unkn2": 9, "x": 184, "y": 152, "name": "Iceberg 1 - 2 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 94, "unkn2": 21, "x": 120, "y": 168, "name": "Iceberg 1 - 2 Exit 1", "access_rule": []}], "entity_load": [[28, 19], [46, 16], [47, 16], [17, 16], [67, 16]], "locations": ["Iceberg 1 - Enemy 7 (Kapar)"], "music": 18}, {"name": "Iceberg 1 - 3", "level": 5, "stage": 1, "room": 3, "pointer": 3068439, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 10, "unkn2": 9, "x": 808, "y": 152, "name": "Iceberg 1 - 3 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 1 - Animal 1", "Iceberg 1 - Animal 2"], "music": 38}, {"name": "Iceberg 1 - 4", "level": 5, "stage": 1, "room": 4, "pointer": 3233681, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Mopoo", "Babut", "Wappa"], "default_exits": [{"room": 6, "unkn1": 74, "unkn2": 4, "x": 56, "y": 152, "name": "Iceberg 1 - 4 Exit 0", "access_rule": []}], "entity_load": [[44, 16], [43, 16], [74, 16]], "locations": ["Iceberg 1 - Enemy 8 (Mopoo)", "Iceberg 1 - Enemy 9 (Babut)", "Iceberg 1 - Enemy 10 (Wappa)"], "music": 18}, {"name": "Iceberg 1 - 5", "level": 5, "stage": 1, "room": 5, "pointer": 3406133, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bronto Burt", "Chilly", "Poppy Bros Jr."], "default_exits": [{"room": 4, "unkn1": 196, "unkn2": 9, "x": 72, "y": 744, "name": "Iceberg 1 - 5 Exit 0", "access_rule": []}], "entity_load": [[6, 16], [7, 16], [2, 16], [0, 16]], "locations": ["Iceberg 1 - Enemy 11 (Bronto Burt)", "Iceberg 1 - Enemy 12 (Chilly)", "Iceberg 1 - Enemy 13 (Poppy Bros Jr.)"], "music": 18}, {"name": "Iceberg 1 - 6", "level": 5, "stage": 1, "room": 6, "pointer": 2985823, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Iceberg 1 - 6 Exit 0", "access_rule": []}], "entity_load": [[28, 19], [42, 19]], "locations": ["Iceberg 1 - Kogoesou"], "music": 8}, {"name": "Iceberg 1 - 7", "level": 5, "stage": 1, "room": 7, "pointer": 2892040, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Iceberg 1 - Complete"], "music": 5}, {"name": "Iceberg 2 - 0", "level": 5, "stage": 2, "room": 0, "pointer": 3106800, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Gabon", "Nruff"], "default_exits": [{"room": 1, "unkn1": 113, "unkn2": 36, "x": 88, "y": 152, "name": "Iceberg 2 - 0 Exit 0", "access_rule": []}], "entity_load": [[15, 16], [0, 16], [24, 16]], "locations": ["Iceberg 2 - Enemy 1 (Gabon)", "Iceberg 2 - Enemy 2 (Nruff)"], "music": 20}, {"name": "Iceberg 2 - 1", "level": 5, "stage": 2, "room": 1, "pointer": 3334841, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee", "Chilly", "Pteran"], "default_exits": [{"room": 2, "unkn1": 109, "unkn2": 20, "x": 88, "y": 72, "name": "Iceberg 2 - 1 Exit 0", "access_rule": []}], "entity_load": [[6, 16], [0, 16], [4, 16], [39, 16]], "locations": ["Iceberg 2 - Enemy 3 (Waddle Dee)", "Iceberg 2 - Enemy 4 (Chilly)", "Iceberg 2 - Enemy 5 (Pteran)"], "music": 20}, {"name": "Iceberg 2 - 2", "level": 5, "stage": 2, "room": 2, "pointer": 3473408, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Glunk", "Galbo", "Babut", "Magoo"], "default_exits": [{"room": 6, "unkn1": 102, "unkn2": 5, "x": 88, "y": 152, "name": "Iceberg 2 - 2 Exit 0", "access_rule": []}, {"room": 9, "unkn1": 24, "unkn2": 18, "x": 88, "y": 152, "name": "Iceberg 2 - 2 Exit 1", "access_rule": []}, {"room": 3, "unkn1": 37, "unkn2": 26, "x": 200, "y": 184, "name": "Iceberg 2 - 2 Exit 2", "access_rule": []}, {"room": 4, "unkn1": 55, "unkn2": 26, "x": 200, "y": 184, "name": "Iceberg 2 - 2 Exit 3", "access_rule": []}, {"room": 5, "unkn1": 73, "unkn2": 26, "x": 200, "y": 184, "name": "Iceberg 2 - 2 Exit 4", "access_rule": []}], "entity_load": [[53, 16], [26, 16], [43, 16], [14, 23], [16, 16]], "locations": ["Iceberg 2 - Enemy 6 (Glunk)", "Iceberg 2 - Enemy 7 (Galbo)", "Iceberg 2 - Enemy 8 (Babut)", "Iceberg 2 - Enemy 9 (Magoo)", "Iceberg 2 - Star 1", "Iceberg 2 - Star 2"], "music": 20}, {"name": "Iceberg 2 - 3", "level": 5, "stage": 2, "room": 3, "pointer": 3037006, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 11, "unkn2": 11, "x": 616, "y": 424, "name": "Iceberg 2 - 3 Exit 0", "access_rule": []}], "entity_load": [[29, 19], [14, 23]], "locations": ["Iceberg 2 - Star 3"], "music": 20}, {"name": "Iceberg 2 - 4", "level": 5, "stage": 2, "room": 4, "pointer": 3035198, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 11, "unkn2": 11, "x": 904, "y": 424, "name": "Iceberg 2 - 4 Exit 0", "access_rule": []}], "entity_load": [[29, 19], [14, 23]], "locations": ["Iceberg 2 - Star 4", "Iceberg 2 - Star 5"], "music": 20}, {"name": "Iceberg 2 - 5", "level": 5, "stage": 2, "room": 5, "pointer": 3128551, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 11, "unkn2": 11, "x": 1192, "y": 424, "name": "Iceberg 2 - 5 Exit 0", "access_rule": []}], "entity_load": [[29, 19], [14, 23]], "locations": ["Iceberg 2 - Star 6", "Iceberg 2 - Star 7", "Iceberg 2 - Star 8"], "music": 20}, {"name": "Iceberg 2 - 6", "level": 5, "stage": 2, "room": 6, "pointer": 3270857, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller", "Nidoo", "Oro"], "default_exits": [{"room": 7, "unkn1": 65, "unkn2": 9, "x": 88, "y": 152, "name": "Iceberg 2 - 6 Exit 0", "access_rule": []}], "entity_load": [[62, 16], [4, 22], [89, 16], [28, 16], [25, 16]], "locations": ["Iceberg 2 - Enemy 10 (Propeller)", "Iceberg 2 - Enemy 11 (Nidoo)", "Iceberg 2 - Enemy 12 (Oro)"], "music": 20}, {"name": "Iceberg 2 - 7", "level": 5, "stage": 2, "room": 7, "pointer": 3212501, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Klinko", "Bronto Burt"], "default_exits": [{"room": 8, "unkn1": 124, "unkn2": 8, "x": 72, "y": 152, "name": "Iceberg 2 - 7 Exit 0", "access_rule": []}], "entity_load": [[42, 16], [6, 16], [14, 23], [2, 16], [4, 23]], "locations": ["Iceberg 2 - Enemy 13 (Klinko)", "Iceberg 2 - Enemy 14 (Bronto Burt)", "Iceberg 2 - Star 9", "Iceberg 2 - Star 10", "Iceberg 2 - Star 11", "Iceberg 2 - Star 12", "Iceberg 2 - Star 13", "Iceberg 2 - Star 14", "Iceberg 2 - Star 15", "Iceberg 2 - Star 16", "Iceberg 2 - Star 17", "Iceberg 2 - Star 18", "Iceberg 2 - Star 19"], "music": 20}, {"name": "Iceberg 2 - 8", "level": 5, "stage": 2, "room": 8, "pointer": 2994515, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Iceberg 2 - 8 Exit 0", "access_rule": []}], "entity_load": [[29, 19], [42, 19]], "locations": ["Iceberg 2 - Samus"], "music": 8}, {"name": "Iceberg 2 - 9", "level": 5, "stage": 2, "room": 9, "pointer": 3058084, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 4, "unkn2": 9, "x": 408, "y": 296, "name": "Iceberg 2 - 9 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 2 - Animal 1", "Iceberg 2 - Animal 2"], "music": 39}, {"name": "Iceberg 2 - 10", "level": 5, "stage": 2, "room": 10, "pointer": 2887220, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Iceberg 2 - Complete"], "music": 5}, {"name": "Iceberg 3 - 0", "level": 5, "stage": 3, "room": 0, "pointer": 3455346, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Corori", "Bouncy", "Chilly", "Pteran"], "default_exits": [{"room": 1, "unkn1": 98, "unkn2": 6, "x": 200, "y": 232, "name": "Iceberg 3 - 0 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 217, "unkn2": 7, "x": 40, "y": 120, "name": "Iceberg 3 - 0 Exit 1", "access_rule": []}], "entity_load": [[60, 16], [6, 16], [39, 16], [13, 16], [14, 23]], "locations": ["Iceberg 3 - Enemy 1 (Corori)", "Iceberg 3 - Enemy 2 (Bouncy)", "Iceberg 3 - Enemy 3 (Chilly)", "Iceberg 3 - Enemy 4 (Pteran)", "Iceberg 3 - Star 1", "Iceberg 3 - Star 2"], "music": 14}, {"name": "Iceberg 3 - 1", "level": 5, "stage": 3, "room": 1, "pointer": 3019769, "animal_pointers": [192, 200, 208], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 0, "unkn1": 11, "unkn2": 14, "x": 1592, "y": 104, "name": "Iceberg 3 - 1 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 3 - Animal 1", "Iceberg 3 - Animal 2", "Iceberg 3 - Animal 3"], "music": 38}, {"name": "Iceberg 3 - 2", "level": 5, "stage": 3, "room": 2, "pointer": 3618121, "animal_pointers": [], "consumables": [{"idx": 2, "pointer": 352, "x": 1776, "y": 104, "etype": 22, "vtype": 2, "name": "Iceberg 3 - Maxim Tomato (Ceiling)"}], "consumables_pointer": 128, "enemies": ["Raft Waddle Dee", "Kapar", "Blipper"], "default_exits": [{"room": 3, "unkn1": 196, "unkn2": 11, "x": 72, "y": 152, "name": "Iceberg 3 - 2 Exit 0", "access_rule": []}], "entity_load": [[2, 22], [71, 16], [57, 16], [21, 16], [67, 16], [14, 23]], "locations": ["Iceberg 3 - Enemy 5 (Raft Waddle Dee)", "Iceberg 3 - Enemy 6 (Kapar)", "Iceberg 3 - Enemy 7 (Blipper)", "Iceberg 3 - Star 3", "Iceberg 3 - Maxim Tomato (Ceiling)"], "music": 14}, {"name": "Iceberg 3 - 3", "level": 5, "stage": 3, "room": 3, "pointer": 3650368, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Wapod"], "default_exits": [{"room": 5, "unkn1": 116, "unkn2": 11, "x": 40, "y": 152, "name": "Iceberg 3 - 3 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 63, "unkn2": 13, "x": 184, "y": 136, "name": "Iceberg 3 - 3 Exit 1", "access_rule": []}], "entity_load": [[1, 16], [14, 23], [4, 22], [6, 16], [88, 16]], "locations": ["Iceberg 3 - Enemy 8 (Wapod)", "Iceberg 3 - Star 4", "Iceberg 3 - Star 5", "Iceberg 3 - Star 6", "Iceberg 3 - Star 7", "Iceberg 3 - Star 8", "Iceberg 3 - Star 9", "Iceberg 3 - Star 10"], "music": 14}, {"name": "Iceberg 3 - 4", "level": 5, "stage": 3, "room": 4, "pointer": 3038208, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 3, "unkn1": 10, "unkn2": 8, "x": 1032, "y": 216, "name": "Iceberg 3 - 4 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 3 - Animal 4", "Iceberg 3 - Animal 5"], "music": 39}, {"name": "Iceberg 3 - 5", "level": 5, "stage": 3, "room": 5, "pointer": 3013938, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 15, "unkn2": 9, "x": 72, "y": 120, "name": "Iceberg 3 - 5 Exit 0", "access_rule": []}], "entity_load": [[30, 19]], "locations": [], "music": 31}, {"name": "Iceberg 3 - 6", "level": 5, "stage": 3, "room": 6, "pointer": 3624789, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Glunk", "Icicle"], "default_exits": [{"room": 7, "unkn1": 211, "unkn2": 7, "x": 40, "y": 152, "name": "Iceberg 3 - 6 Exit 0", "access_rule": []}], "entity_load": [[6, 16], [16, 16], [14, 23], [36, 16], [4, 22]], "locations": ["Iceberg 3 - Enemy 9 (Glunk)", "Iceberg 3 - Enemy 10 (Icicle)", "Iceberg 3 - Star 11", "Iceberg 3 - Star 12", "Iceberg 3 - Star 13", "Iceberg 3 - Star 14", "Iceberg 3 - Star 15", "Iceberg 3 - Star 16", "Iceberg 3 - Star 17", "Iceberg 3 - Star 18", "Iceberg 3 - Star 19", "Iceberg 3 - Star 20", "Iceberg 3 - Star 21"], "music": 14}, {"name": "Iceberg 3 - 7", "level": 5, "stage": 3, "room": 7, "pointer": 2989472, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 15, "unkn2": 9, "x": 72, "y": 120, "name": "Iceberg 3 - 7 Exit 0", "access_rule": []}], "entity_load": [[30, 19]], "locations": ["Iceberg 3 - Chef Kawasaki"], "music": 8}, {"name": "Iceberg 3 - 8", "level": 5, "stage": 3, "room": 8, "pointer": 2889871, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Iceberg 3 - Complete"], "music": 5}, {"name": "Iceberg 4 - 0", "level": 5, "stage": 4, "room": 0, "pointer": 3274879, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bronto Burt", "Galbo", "Klinko", "Chilly"], "default_exits": [{"room": 1, "unkn1": 111, "unkn2": 8, "x": 72, "y": 104, "name": "Iceberg 4 - 0 Exit 0", "access_rule": []}], "entity_load": [[42, 16], [6, 16], [2, 16], [26, 16]], "locations": ["Iceberg 4 - Enemy 1 (Bronto Burt)", "Iceberg 4 - Enemy 2 (Galbo)", "Iceberg 4 - Enemy 3 (Klinko)", "Iceberg 4 - Enemy 4 (Chilly)"], "music": 19}, {"name": "Iceberg 4 - 1", "level": 5, "stage": 4, "room": 1, "pointer": 3371780, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Babut", "Wappa"], "default_exits": [{"room": 2, "unkn1": 60, "unkn2": 36, "x": 216, "y": 88, "name": "Iceberg 4 - 1 Exit 0", "access_rule": []}], "entity_load": [[43, 16], [4, 22], [44, 16]], "locations": ["Iceberg 4 - Enemy 5 (Babut)", "Iceberg 4 - Enemy 6 (Wappa)"], "music": 19}, {"name": "Iceberg 4 - 2", "level": 5, "stage": 4, "room": 2, "pointer": 3284378, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 16, "unkn1": 8, "unkn2": 39, "x": 168, "y": 152, "name": "Iceberg 4 - 2 Exit 0", "access_rule": ["Burning", "Burning Ability"]}, {"room": 3, "unkn1": 13, "unkn2": 39, "x": 88, "y": 136, "name": "Iceberg 4 - 2 Exit 1", "access_rule": []}, {"room": 17, "unkn1": 18, "unkn2": 39, "x": 120, "y": 152, "name": "Iceberg 4 - 2 Exit 2", "access_rule": ["Burning", "Burning Ability"]}], "entity_load": [[26, 16]], "locations": [], "music": 19}, {"name": "Iceberg 4 - 3", "level": 5, "stage": 4, "room": 3, "pointer": 3162957, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 4, "unkn1": 44, "unkn2": 8, "x": 216, "y": 104, "name": "Iceberg 4 - 3 Exit 0", "access_rule": []}], "entity_load": [[69, 16]], "locations": [], "music": 19}, {"name": "Iceberg 4 - 4", "level": 5, "stage": 4, "room": 4, "pointer": 3261679, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Icicle"], "default_exits": [{"room": 5, "unkn1": 4, "unkn2": 42, "x": 104, "y": 840, "name": "Iceberg 4 - 4 Exit 0", "access_rule": []}], "entity_load": [[36, 16]], "locations": ["Iceberg 4 - Enemy 7 (Icicle)"], "music": 19}, {"name": "Iceberg 4 - 5", "level": 5, "stage": 4, "room": 5, "pointer": 3217398, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Corori"], "default_exits": [{"room": 6, "unkn1": 19, "unkn2": 5, "x": 72, "y": 120, "name": "Iceberg 4 - 5 Exit 0", "access_rule": []}], "entity_load": [[60, 16], [44, 16], [4, 22]], "locations": ["Iceberg 4 - Enemy 8 (Corori)"], "music": 19}, {"name": "Iceberg 4 - 6", "level": 5, "stage": 4, "room": 6, "pointer": 3108265, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 61, "unkn2": 7, "x": 456, "y": 72, "name": "Iceberg 4 - 6 Exit 0", "access_rule": []}], "entity_load": [[62, 16]], "locations": [], "music": 19}, {"name": "Iceberg 4 - 7", "level": 5, "stage": 4, "room": 7, "pointer": 3346202, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 8, "unkn1": 39, "unkn2": 6, "x": 168, "y": 104, "name": "Iceberg 4 - 7 Exit 0", "access_rule": []}, {"room": 13, "unkn1": 4, "unkn2": 21, "x": 88, "y": 168, "name": "Iceberg 4 - 7 Exit 1", "access_rule": ["Burning", "Burning Ability"]}, {"room": 13, "unkn1": 21, "unkn2": 21, "x": 280, "y": 168, "name": "Iceberg 4 - 7 Exit 2", "access_rule": ["Burning", "Burning Ability"]}], "entity_load": [[14, 23], [4, 22], [4, 23]], "locations": ["Iceberg 4 - Star 1", "Iceberg 4 - Star 2"], "music": 19}, {"name": "Iceberg 4 - 8", "level": 5, "stage": 4, "room": 8, "pointer": 3055911, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 10, "unkn2": 5, "x": 648, "y": 104, "name": "Iceberg 4 - 8 Exit 0", "access_rule": []}], "entity_load": [[26, 16]], "locations": [], "music": 19}, {"name": "Iceberg 4 - 9", "level": 5, "stage": 4, "room": 9, "pointer": 3056457, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 13, "unkn2": 9, "x": 136, "y": 136, "name": "Iceberg 4 - 9 Exit 0", "access_rule": []}], "entity_load": [[1, 27]], "locations": ["Iceberg 4 - Miniboss 1 (Yuki)"], "music": 4}, {"name": "Iceberg 4 - 10", "level": 5, "stage": 4, "room": 10, "pointer": 3257516, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 9, "unkn1": 8, "unkn2": 37, "x": 88, "y": 40, "name": "Iceberg 4 - 10 Exit 0", "access_rule": []}, {"room": 9, "unkn1": 15, "unkn2": 37, "x": 200, "y": 40, "name": "Iceberg 4 - 10 Exit 1", "access_rule": []}], "entity_load": [[31, 19]], "locations": [], "music": 19}, {"name": "Iceberg 4 - 11", "level": 5, "stage": 4, "room": 11, "pointer": 3083322, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Gabon"], "default_exits": [{"room": 12, "unkn1": 46, "unkn2": 8, "x": 88, "y": 120, "name": "Iceberg 4 - 11 Exit 0", "access_rule": []}], "entity_load": [[24, 16]], "locations": ["Iceberg 4 - Enemy 9 (Gabon)"], "music": 19}, {"name": "Iceberg 4 - 12", "level": 5, "stage": 4, "room": 12, "pointer": 3147724, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Kabu"], "default_exits": [{"room": 18, "unkn1": 64, "unkn2": 7, "x": 72, "y": 456, "name": "Iceberg 4 - 12 Exit 0", "access_rule": []}], "entity_load": [[19, 16], [61, 16]], "locations": ["Iceberg 4 - Enemy 10 (Kabu)"], "music": 19}, {"name": "Iceberg 4 - 13", "level": 5, "stage": 4, "room": 13, "pointer": 3370077, "animal_pointers": [232, 240, 248], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 15, "unkn1": 31, "unkn2": 4, "x": 216, "y": 120, "name": "Iceberg 4 - 13 Exit 0", "access_rule": []}, {"room": 10, "unkn1": 46, "unkn2": 10, "x": 72, "y": 88, "name": "Iceberg 4 - 13 Exit 1", "access_rule": []}, {"room": 10, "unkn1": 57, "unkn2": 10, "x": 312, "y": 88, "name": "Iceberg 4 - 13 Exit 2", "access_rule": []}, {"room": 14, "unkn1": 28, "unkn2": 21, "x": 152, "y": 136, "name": "Iceberg 4 - 13 Exit 3", "access_rule": []}, {"room": 14, "unkn1": 34, "unkn2": 21, "x": 280, "y": 136, "name": "Iceberg 4 - 13 Exit 4", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 4 - Animal 1", "Iceberg 4 - Animal 2", "Iceberg 4 - Animal 3"], "music": 19}, {"name": "Iceberg 4 - 14", "level": 5, "stage": 4, "room": 14, "pointer": 3057002, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Broom Hatter", "Sasuke"], "default_exits": [{"room": 15, "unkn1": 4, "unkn2": 8, "x": 88, "y": 360, "name": "Iceberg 4 - 14 Exit 0", "access_rule": []}, {"room": 13, "unkn1": 10, "unkn2": 8, "x": 440, "y": 344, "name": "Iceberg 4 - 14 Exit 1", "access_rule": []}, {"room": 13, "unkn1": 16, "unkn2": 8, "x": 568, "y": 344, "name": "Iceberg 4 - 14 Exit 2", "access_rule": []}, {"room": 15, "unkn1": 22, "unkn2": 8, "x": 344, "y": 360, "name": "Iceberg 4 - 14 Exit 3", "access_rule": []}], "entity_load": [[11, 16], [30, 16]], "locations": ["Iceberg 4 - Enemy 11 (Broom Hatter)", "Iceberg 4 - Enemy 12 (Sasuke)"], "music": 19}, {"name": "Iceberg 4 - 15", "level": 5, "stage": 4, "room": 15, "pointer": 3116124, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 13, "unkn1": 13, "unkn2": 6, "x": 520, "y": 72, "name": "Iceberg 4 - 15 Exit 0", "access_rule": []}, {"room": 14, "unkn1": 4, "unkn2": 22, "x": 88, "y": 136, "name": "Iceberg 4 - 15 Exit 1", "access_rule": []}, {"room": 14, "unkn1": 22, "unkn2": 22, "x": 344, "y": 136, "name": "Iceberg 4 - 15 Exit 2", "access_rule": []}], "entity_load": [[4, 22]], "locations": [], "music": 19}, {"name": "Iceberg 4 - 16", "level": 5, "stage": 4, "room": 16, "pointer": 3069937, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 11, "unkn2": 9, "x": 152, "y": 632, "name": "Iceberg 4 - 16 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 4 - Animal 4"], "music": 38}, {"name": "Iceberg 4 - 17", "level": 5, "stage": 4, "room": 17, "pointer": 3072413, "animal_pointers": [192], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 6, "unkn2": 9, "x": 280, "y": 632, "name": "Iceberg 4 - 17 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 4 - Animal 5"], "music": 38}, {"name": "Iceberg 4 - 18", "level": 5, "stage": 4, "room": 18, "pointer": 3404593, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Nruff"], "default_exits": [{"room": 19, "unkn1": 94, "unkn2": 12, "x": 72, "y": 152, "name": "Iceberg 4 - 18 Exit 0", "access_rule": []}], "entity_load": [[14, 23], [43, 16], [30, 16], [15, 16]], "locations": ["Iceberg 4 - Enemy 13 (Nruff)", "Iceberg 4 - Star 3"], "music": 19}, {"name": "Iceberg 4 - 19", "level": 5, "stage": 4, "room": 19, "pointer": 3075826, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 20, "unkn1": 14, "unkn2": 9, "x": 72, "y": 120, "name": "Iceberg 4 - 19 Exit 0", "access_rule": []}], "entity_load": [[31, 19], [42, 19]], "locations": ["Iceberg 4 - Name"], "music": 8}, {"name": "Iceberg 4 - 20", "level": 5, "stage": 4, "room": 20, "pointer": 2887943, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Iceberg 4 - Complete"], "music": 5}, {"name": "Iceberg 5 - 0", "level": 5, "stage": 5, "room": 0, "pointer": 3316135, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Bukiset (Burning)", "Bukiset (Stone)", "Bukiset (Ice)", "Bukiset (Needle)", "Bukiset (Clean)", "Bukiset (Parasol)", "Bukiset (Spark)", "Bukiset (Cutter)"], "default_exits": [{"room": 30, "unkn1": 75, "unkn2": 9, "x": 72, "y": 152, "name": "Iceberg 5 - 0 Exit 0", "access_rule": []}], "entity_load": [[79, 16], [76, 16], [81, 16], [78, 16], [77, 16], [82, 16], [80, 16], [83, 16]], "locations": ["Iceberg 5 - Enemy 1 (Bukiset (Burning))", "Iceberg 5 - Enemy 2 (Bukiset (Stone))", "Iceberg 5 - Enemy 3 (Bukiset (Ice))", "Iceberg 5 - Enemy 4 (Bukiset (Needle))", "Iceberg 5 - Enemy 5 (Bukiset (Clean))", "Iceberg 5 - Enemy 6 (Bukiset (Parasol))", "Iceberg 5 - Enemy 7 (Bukiset (Spark))", "Iceberg 5 - Enemy 8 (Bukiset (Cutter))"], "music": 16}, {"name": "Iceberg 5 - 1", "level": 5, "stage": 5, "room": 1, "pointer": 3037607, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 12, "unkn2": 6, "x": 72, "y": 104, "name": "Iceberg 5 - 1 Exit 0", "access_rule": []}], "entity_load": [[1, 16]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 2", "level": 5, "stage": 5, "room": 2, "pointer": 3103842, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Glunk"], "default_exits": [{"room": 25, "unkn1": 27, "unkn2": 6, "x": 72, "y": 200, "name": "Iceberg 5 - 2 Exit 0", "access_rule": []}], "entity_load": [[16, 16]], "locations": ["Iceberg 5 - Enemy 9 (Glunk)"], "music": 16}, {"name": "Iceberg 5 - 3", "level": 5, "stage": 5, "room": 3, "pointer": 3135899, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Wapod"], "default_exits": [{"room": 4, "unkn1": 20, "unkn2": 7, "x": 72, "y": 88, "name": "Iceberg 5 - 3 Exit 0", "access_rule": []}], "entity_load": [[88, 16], [14, 23]], "locations": ["Iceberg 5 - Enemy 10 (Wapod)", "Iceberg 5 - Star 1", "Iceberg 5 - Star 2"], "music": 16}, {"name": "Iceberg 5 - 4", "level": 5, "stage": 5, "room": 4, "pointer": 3180695, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Tick"], "default_exits": [{"room": 5, "unkn1": 26, "unkn2": 5, "x": 56, "y": 104, "name": "Iceberg 5 - 4 Exit 0", "access_rule": []}], "entity_load": [[48, 16], [14, 23]], "locations": ["Iceberg 5 - Enemy 11 (Tick)", "Iceberg 5 - Star 3", "Iceberg 5 - Star 4", "Iceberg 5 - Star 5", "Iceberg 5 - Star 6", "Iceberg 5 - Star 7", "Iceberg 5 - Star 8"], "music": 16}, {"name": "Iceberg 5 - 5", "level": 5, "stage": 5, "room": 5, "pointer": 3106064, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Madoo"], "default_exits": [{"room": 24, "unkn1": 14, "unkn2": 6, "x": 168, "y": 152, "name": "Iceberg 5 - 5 Exit 0", "access_rule": []}], "entity_load": [[58, 16], [14, 23], [4, 22]], "locations": ["Iceberg 5 - Enemy 12 (Madoo)", "Iceberg 5 - Star 9", "Iceberg 5 - Star 10", "Iceberg 5 - Star 11", "Iceberg 5 - Star 12", "Iceberg 5 - Star 13", "Iceberg 5 - Star 14"], "music": 16}, {"name": "Iceberg 5 - 6", "level": 5, "stage": 5, "room": 6, "pointer": 3276800, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 7, "unkn1": 59, "unkn2": 12, "x": 72, "y": 120, "name": "Iceberg 5 - 6 Exit 0", "access_rule": []}], "entity_load": [[55, 16]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 7", "level": 5, "stage": 5, "room": 7, "pointer": 3104585, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 26, "unkn1": 25, "unkn2": 7, "x": 72, "y": 136, "name": "Iceberg 5 - 7 Exit 0", "access_rule": []}], "entity_load": [[23, 16], [7, 16]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 8", "level": 5, "stage": 5, "room": 8, "pointer": 3195121, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 9, "unkn1": 35, "unkn2": 9, "x": 72, "y": 152, "name": "Iceberg 5 - 8 Exit 0", "access_rule": []}], "entity_load": [[4, 16]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 9", "level": 5, "stage": 5, "room": 9, "pointer": 3087198, "animal_pointers": [], "consumables": [{"idx": 16, "pointer": 200, "x": 256, "y": 88, "etype": 22, "vtype": 0, "name": "Iceberg 5 - 1-Up (Boulder)"}], "consumables_pointer": 128, "enemies": [], "default_exits": [{"room": 28, "unkn1": 20, "unkn2": 9, "x": 72, "y": 152, "name": "Iceberg 5 - 9 Exit 0", "access_rule": []}], "entity_load": [[0, 22], [54, 16]], "locations": ["Iceberg 5 - 1-Up (Boulder)"], "music": 16}, {"name": "Iceberg 5 - 10", "level": 5, "stage": 5, "room": 10, "pointer": 3321612, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 32, "unkn1": 45, "unkn2": 15, "x": 72, "y": 120, "name": "Iceberg 5 - 10 Exit 0", "access_rule": []}], "entity_load": [[14, 16]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 11", "level": 5, "stage": 5, "room": 11, "pointer": 3139178, "animal_pointers": [], "consumables": [{"idx": 17, "pointer": 192, "x": 152, "y": 168, "etype": 22, "vtype": 0, "name": "Iceberg 5 - 1-Up (Floor)"}], "consumables_pointer": 176, "enemies": ["Yaban"], "default_exits": [{"room": 12, "unkn1": 17, "unkn2": 7, "x": 72, "y": 120, "name": "Iceberg 5 - 11 Exit 0", "access_rule": []}], "entity_load": [[32, 16], [0, 22]], "locations": ["Iceberg 5 - Enemy 13 (Yaban)", "Iceberg 5 - 1-Up (Floor)"], "music": 16}, {"name": "Iceberg 5 - 12", "level": 5, "stage": 5, "room": 12, "pointer": 3118231, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Propeller"], "default_exits": [{"room": 13, "unkn1": 13, "unkn2": 7, "x": 72, "y": 104, "name": "Iceberg 5 - 12 Exit 0", "access_rule": []}], "entity_load": [[89, 16], [14, 23]], "locations": ["Iceberg 5 - Enemy 14 (Propeller)", "Iceberg 5 - Star 15", "Iceberg 5 - Star 16", "Iceberg 5 - Star 17", "Iceberg 5 - Star 18", "Iceberg 5 - Star 19", "Iceberg 5 - Star 20"], "music": 16}, {"name": "Iceberg 5 - 13", "level": 5, "stage": 5, "room": 13, "pointer": 3021658, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Mariel"], "default_exits": [{"room": 27, "unkn1": 16, "unkn2": 6, "x": 72, "y": 152, "name": "Iceberg 5 - 13 Exit 0", "access_rule": []}], "entity_load": [[45, 16]], "locations": ["Iceberg 5 - Enemy 15 (Mariel)"], "music": 16}, {"name": "Iceberg 5 - 14", "level": 5, "stage": 5, "room": 14, "pointer": 3025398, "animal_pointers": [], "consumables": [{"idx": 24, "pointer": 200, "x": 208, "y": 88, "etype": 22, "vtype": 0, "name": "Iceberg 5 - 1-Up (Peloo)"}], "consumables_pointer": 176, "enemies": [], "default_exits": [{"room": 15, "unkn1": 13, "unkn2": 9, "x": 72, "y": 216, "name": "Iceberg 5 - 14 Exit 0", "access_rule": []}], "entity_load": [[64, 16], [0, 22]], "locations": ["Iceberg 5 - 1-Up (Peloo)"], "music": 16}, {"name": "Iceberg 5 - 15", "level": 5, "stage": 5, "room": 15, "pointer": 3167445, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Pteran"], "default_exits": [{"room": 16, "unkn1": 13, "unkn2": 13, "x": 72, "y": 152, "name": "Iceberg 5 - 15 Exit 0", "access_rule": []}], "entity_load": [[39, 16]], "locations": ["Iceberg 5 - Enemy 16 (Pteran)"], "music": 16}, {"name": "Iceberg 5 - 16", "level": 5, "stage": 5, "room": 16, "pointer": 3033990, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 33, "unkn1": 36, "unkn2": 9, "x": 168, "y": 152, "name": "Iceberg 5 - 16 Exit 0", "access_rule": []}], "entity_load": [[68, 16]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 17", "level": 5, "stage": 5, "room": 17, "pointer": 3100111, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 20, "unkn1": 40, "unkn2": 7, "x": 72, "y": 200, "name": "Iceberg 5 - 17 Exit 0", "access_rule": []}], "entity_load": [[52, 16]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 18", "level": 5, "stage": 5, "room": 18, "pointer": 3030947, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Galbo"], "default_exits": [{"room": 17, "unkn1": 13, "unkn2": 7, "x": 72, "y": 120, "name": "Iceberg 5 - 18 Exit 0", "access_rule": []}], "entity_load": [[26, 16]], "locations": ["Iceberg 5 - Enemy 17 (Galbo)"], "music": 16}, {"name": "Iceberg 5 - 19", "level": 5, "stage": 5, "room": 19, "pointer": 3105326, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["KeKe"], "default_exits": [{"room": 21, "unkn1": 13, "unkn2": 4, "x": 72, "y": 152, "name": "Iceberg 5 - 19 Exit 0", "access_rule": []}], "entity_load": [[51, 16]], "locations": ["Iceberg 5 - Enemy 18 (KeKe)"], "music": 16}, {"name": "Iceberg 5 - 20", "level": 5, "stage": 5, "room": 20, "pointer": 3118928, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Nidoo"], "default_exits": [{"room": 19, "unkn1": 13, "unkn2": 6, "x": 72, "y": 264, "name": "Iceberg 5 - 20 Exit 0", "access_rule": []}], "entity_load": [[28, 16]], "locations": ["Iceberg 5 - Enemy 19 (Nidoo)"], "music": 16}, {"name": "Iceberg 5 - 21", "level": 5, "stage": 5, "room": 21, "pointer": 3202517, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Waddle Dee Drawing", "Bronto Burt Drawing", "Bouncy Drawing"], "default_exits": [{"room": 22, "unkn1": 29, "unkn2": 9, "x": 72, "y": 72, "name": "Iceberg 5 - 21 Exit 0", "access_rule": []}], "entity_load": [[84, 16], [85, 16], [86, 16]], "locations": ["Iceberg 5 - Enemy 20 (Waddle Dee Drawing)", "Iceberg 5 - Enemy 21 (Bronto Burt Drawing)", "Iceberg 5 - Enemy 22 (Bouncy Drawing)"], "music": 16}, {"name": "Iceberg 5 - 22", "level": 5, "stage": 5, "room": 22, "pointer": 3014656, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Joe"], "default_exits": [{"room": 23, "unkn1": 13, "unkn2": 4, "x": 72, "y": 104, "name": "Iceberg 5 - 22 Exit 0", "access_rule": []}], "entity_load": [[91, 16], [14, 23]], "locations": ["Iceberg 5 - Enemy 23 (Joe)", "Iceberg 5 - Star 21", "Iceberg 5 - Star 22", "Iceberg 5 - Star 23", "Iceberg 5 - Star 24", "Iceberg 5 - Star 25"], "music": 16}, {"name": "Iceberg 5 - 23", "level": 5, "stage": 5, "room": 23, "pointer": 3166550, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Kapar"], "default_exits": [{"room": 34, "unkn1": 27, "unkn2": 6, "x": 72, "y": 152, "name": "Iceberg 5 - 23 Exit 0", "access_rule": []}], "entity_load": [[67, 16]], "locations": ["Iceberg 5 - Enemy 24 (Kapar)"], "music": 16}, {"name": "Iceberg 5 - 24", "level": 5, "stage": 5, "room": 24, "pointer": 3029110, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Gansan"], "default_exits": [{"room": 31, "unkn1": 10, "unkn2": 4, "x": 72, "y": 88, "name": "Iceberg 5 - 24 Exit 0", "access_rule": []}], "entity_load": [[75, 16]], "locations": ["Iceberg 5 - Enemy 25 (Gansan)"], "music": 16}, {"name": "Iceberg 5 - 25", "level": 5, "stage": 5, "room": 25, "pointer": 3156420, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sasuke"], "default_exits": [{"room": 3, "unkn1": 25, "unkn2": 7, "x": 72, "y": 120, "name": "Iceberg 5 - 25 Exit 0", "access_rule": []}], "entity_load": [[30, 16]], "locations": ["Iceberg 5 - Enemy 26 (Sasuke)"], "music": 16}, {"name": "Iceberg 5 - 26", "level": 5, "stage": 5, "room": 26, "pointer": 3127877, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Togezo"], "default_exits": [{"room": 8, "unkn1": 24, "unkn2": 8, "x": 72, "y": 152, "name": "Iceberg 5 - 26 Exit 0", "access_rule": []}], "entity_load": [[18, 16]], "locations": ["Iceberg 5 - Enemy 27 (Togezo)"], "music": 16}, {"name": "Iceberg 5 - 27", "level": 5, "stage": 5, "room": 27, "pointer": 3256471, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sparky", "Bobin"], "default_exits": [{"room": 14, "unkn1": 26, "unkn2": 9, "x": 72, "y": 152, "name": "Iceberg 5 - 27 Exit 0", "access_rule": []}], "entity_load": [[8, 16], [73, 16]], "locations": ["Iceberg 5 - Enemy 28 (Sparky)", "Iceberg 5 - Enemy 29 (Bobin)"], "music": 16}, {"name": "Iceberg 5 - 28", "level": 5, "stage": 5, "room": 28, "pointer": 3029723, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Chilly"], "default_exits": [{"room": 10, "unkn1": 13, "unkn2": 9, "x": 72, "y": 248, "name": "Iceberg 5 - 28 Exit 0", "access_rule": []}], "entity_load": [[6, 16]], "locations": ["Iceberg 5 - Enemy 30 (Chilly)"], "music": 16}, {"name": "Iceberg 5 - 29", "level": 5, "stage": 5, "room": 29, "pointer": 3526568, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 36, "unkn1": 10, "unkn2": 6, "x": 72, "y": 152, "name": "Iceberg 5 - 29 Exit 0", "access_rule": []}], "entity_load": [[10, 23], [31, 16], [14, 23]], "locations": ["Iceberg 5 - Star 26", "Iceberg 5 - Star 27", "Iceberg 5 - Star 28", "Iceberg 5 - Star 29", "Iceberg 5 - Star 30", "Iceberg 5 - Star 31", "Iceberg 5 - Star 32"], "music": 16}, {"name": "Iceberg 5 - 30", "level": 5, "stage": 5, "room": 30, "pointer": 3022285, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 1, "unkn1": 13, "unkn2": 5, "x": 88, "y": 104, "name": "Iceberg 5 - 30 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 5 - Animal 1", "Iceberg 5 - Animal 2"], "music": 40}, {"name": "Iceberg 5 - 31", "level": 5, "stage": 5, "room": 31, "pointer": 3026019, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 13, "unkn2": 9, "x": 72, "y": 200, "name": "Iceberg 5 - 31 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 5 - Animal 3", "Iceberg 5 - Animal 4"], "music": 40}, {"name": "Iceberg 5 - 32", "level": 5, "stage": 5, "room": 32, "pointer": 3039996, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 13, "unkn2": 7, "x": 72, "y": 120, "name": "Iceberg 5 - 32 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 5 - Animal 5", "Iceberg 5 - Animal 6"], "music": 40}, {"name": "Iceberg 5 - 33", "level": 5, "stage": 5, "room": 33, "pointer": 3015302, "animal_pointers": [192, 200], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 18, "unkn1": 10, "unkn2": 6, "x": 72, "y": 152, "name": "Iceberg 5 - 33 Exit 0", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 5 - Animal 7", "Iceberg 5 - Animal 8"], "music": 40}, {"name": "Iceberg 5 - 34", "level": 5, "stage": 5, "room": 34, "pointer": 3185868, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Peran"], "default_exits": [{"room": 35, "unkn1": 35, "unkn2": 9, "x": 200, "y": 328, "name": "Iceberg 5 - 34 Exit 0", "access_rule": []}], "entity_load": [[72, 16], [4, 22], [14, 23]], "locations": ["Iceberg 5 - Enemy 31 (Peran)", "Iceberg 5 - Star 33", "Iceberg 5 - Star 34"], "music": 16}, {"name": "Iceberg 5 - 35", "level": 5, "stage": 5, "room": 35, "pointer": 3865635, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 29, "unkn1": 12, "unkn2": 7, "x": 168, "y": 1384, "name": "Iceberg 5 - 35 Exit 0", "access_rule": []}], "entity_load": [[17, 16], [4, 22]], "locations": [], "music": 16}, {"name": "Iceberg 5 - 36", "level": 5, "stage": 5, "room": 36, "pointer": 3024154, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 37, "unkn1": 13, "unkn2": 9, "x": 72, "y": 120, "name": "Iceberg 5 - 36 Exit 0", "access_rule": []}], "entity_load": [[32, 19], [42, 19]], "locations": ["Iceberg 5 - Shiro"], "music": 8}, {"name": "Iceberg 5 - 37", "level": 5, "stage": 5, "room": 37, "pointer": 2890594, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Iceberg 5 - Complete"], "music": 5}, {"name": "Iceberg 6 - 0", "level": 5, "stage": 6, "room": 0, "pointer": 3385305, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Nruff"], "default_exits": [{"room": 1, "unkn1": 6, "unkn2": 28, "x": 136, "y": 184, "name": "Iceberg 6 - 0 Exit 0", "access_rule": []}, {"room": 1, "unkn1": 12, "unkn2": 28, "x": 248, "y": 184, "name": "Iceberg 6 - 0 Exit 1", "access_rule": []}, {"room": 1, "unkn1": 18, "unkn2": 28, "x": 360, "y": 184, "name": "Iceberg 6 - 0 Exit 2", "access_rule": []}], "entity_load": [[15, 16]], "locations": ["Iceberg 6 - Enemy 1 (Nruff)"], "music": 12}, {"name": "Iceberg 6 - 1", "level": 5, "stage": 6, "room": 1, "pointer": 3197599, "animal_pointers": [212, 220, 228], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 2, "unkn1": 8, "unkn2": 5, "x": 152, "y": 184, "name": "Iceberg 6 - 1 Exit 0", "access_rule": []}, {"room": 2, "unkn1": 15, "unkn2": 5, "x": 248, "y": 184, "name": "Iceberg 6 - 1 Exit 1", "access_rule": []}, {"room": 2, "unkn1": 22, "unkn2": 5, "x": 344, "y": 184, "name": "Iceberg 6 - 1 Exit 2", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 6 - Animal 1", "Iceberg 6 - Animal 2", "Iceberg 6 - Animal 3"], "music": 12}, {"name": "Iceberg 6 - 2", "level": 5, "stage": 6, "room": 2, "pointer": 3097113, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 5, "unkn1": 9, "unkn2": 5, "x": 136, "y": 184, "name": "Iceberg 6 - 2 Exit 0", "access_rule": []}, {"room": 5, "unkn1": 15, "unkn2": 5, "x": 248, "y": 184, "name": "Iceberg 6 - 2 Exit 1", "access_rule": []}, {"room": 5, "unkn1": 21, "unkn2": 5, "x": 360, "y": 184, "name": "Iceberg 6 - 2 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 12}, {"name": "Iceberg 6 - 3", "level": 5, "stage": 6, "room": 3, "pointer": 3198422, "animal_pointers": [212, 220, 228], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 6, "unkn1": 8, "unkn2": 5, "x": 152, "y": 184, "name": "Iceberg 6 - 3 Exit 0", "access_rule": []}, {"room": 6, "unkn1": 15, "unkn2": 5, "x": 248, "y": 184, "name": "Iceberg 6 - 3 Exit 1", "access_rule": []}, {"room": 6, "unkn1": 22, "unkn2": 5, "x": 344, "y": 184, "name": "Iceberg 6 - 3 Exit 2", "access_rule": []}], "entity_load": [], "locations": ["Iceberg 6 - Animal 4", "Iceberg 6 - Animal 5", "Iceberg 6 - Animal 6"], "music": 12}, {"name": "Iceberg 6 - 4", "level": 5, "stage": 6, "room": 4, "pointer": 3210507, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 3, "unkn1": 9, "unkn2": 5, "x": 136, "y": 184, "name": "Iceberg 6 - 4 Exit 0", "access_rule": []}, {"room": 3, "unkn1": 15, "unkn2": 5, "x": 248, "y": 184, "name": "Iceberg 6 - 4 Exit 1", "access_rule": []}, {"room": 3, "unkn1": 21, "unkn2": 5, "x": 360, "y": 184, "name": "Iceberg 6 - 4 Exit 2", "access_rule": []}], "entity_load": [], "locations": [], "music": 12}, {"name": "Iceberg 6 - 5", "level": 5, "stage": 6, "room": 5, "pointer": 3196776, "animal_pointers": [], "consumables": [{"idx": 0, "pointer": 212, "x": 136, "y": 120, "etype": 22, "vtype": 2, "name": "Iceberg 6 - Maxim Tomato (Left)"}, {"idx": 1, "pointer": 220, "x": 248, "y": 120, "etype": 22, "vtype": 0, "name": "Iceberg 6 - 1-Up (Middle)"}], "consumables_pointer": 128, "enemies": [], "default_exits": [{"room": 4, "unkn1": 8, "unkn2": 5, "x": 152, "y": 184, "name": "Iceberg 6 - 5 Exit 0", "access_rule": []}, {"room": 4, "unkn1": 15, "unkn2": 5, "x": 248, "y": 184, "name": "Iceberg 6 - 5 Exit 1", "access_rule": []}, {"room": 4, "unkn1": 22, "unkn2": 5, "x": 344, "y": 184, "name": "Iceberg 6 - 5 Exit 2", "access_rule": []}], "entity_load": [[2, 22], [0, 22], [14, 23]], "locations": ["Iceberg 6 - Star 1", "Iceberg 6 - Maxim Tomato (Left)", "Iceberg 6 - 1-Up (Middle)"], "music": 12}, {"name": "Iceberg 6 - 6", "level": 5, "stage": 6, "room": 6, "pointer": 3208130, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Nidoo"], "default_exits": [{"room": 7, "unkn1": 9, "unkn2": 5, "x": 136, "y": 168, "name": "Iceberg 6 - 6 Exit 0", "access_rule": []}], "entity_load": [[28, 16]], "locations": ["Iceberg 6 - Enemy 2 (Nidoo)"], "music": 12}, {"name": "Iceberg 6 - 7", "level": 5, "stage": 6, "room": 7, "pointer": 3124478, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sparky"], "default_exits": [{"room": 8, "unkn1": 17, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 7 Exit 0", "access_rule": []}], "entity_load": [[8, 16]], "locations": ["Iceberg 6 - Enemy 3 (Sparky)"], "music": 12}, {"name": "Iceberg 6 - 8", "level": 5, "stage": 6, "room": 8, "pointer": 3110431, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 9, "unkn1": 7, "unkn2": 5, "x": 152, "y": 168, "name": "Iceberg 6 - 8 Exit 0", "access_rule": []}, {"room": 14, "unkn1": 14, "unkn2": 5, "x": 296, "y": 136, "name": "Iceberg 6 - 8 Exit 1", "access_rule": []}], "entity_load": [[4, 22], [33, 19]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 9", "level": 5, "stage": 6, "room": 9, "pointer": 3139832, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 10, "unkn1": 16, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 9 Exit 0", "access_rule": []}], "entity_load": [[2, 27]], "locations": ["Iceberg 6 - Miniboss 1 (Blocky)"], "music": 12}, {"name": "Iceberg 6 - 10", "level": 5, "stage": 6, "room": 10, "pointer": 3119624, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 11, "unkn1": 7, "unkn2": 5, "x": 152, "y": 168, "name": "Iceberg 6 - 10 Exit 0", "access_rule": []}], "entity_load": [[33, 19], [4, 22]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 11", "level": 5, "stage": 6, "room": 11, "pointer": 3141139, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 12, "unkn1": 16, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 11 Exit 0", "access_rule": []}], "entity_load": [[3, 27]], "locations": ["Iceberg 6 - Miniboss 2 (Jumper Shoot)"], "music": 12}, {"name": "Iceberg 6 - 12", "level": 5, "stage": 6, "room": 12, "pointer": 3123788, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 13, "unkn1": 7, "unkn2": 5, "x": 136, "y": 168, "name": "Iceberg 6 - 12 Exit 0", "access_rule": []}], "entity_load": [[33, 19], [4, 22]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 13", "level": 5, "stage": 6, "room": 13, "pointer": 3143741, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 14, "unkn1": 15, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 13 Exit 0", "access_rule": []}], "entity_load": [[1, 27]], "locations": ["Iceberg 6 - Miniboss 3 (Yuki)"], "music": 12}, {"name": "Iceberg 6 - 14", "level": 5, "stage": 6, "room": 14, "pointer": 3120319, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 15, "unkn1": 7, "unkn2": 5, "x": 136, "y": 168, "name": "Iceberg 6 - 14 Exit 0", "access_rule": []}], "entity_load": [[33, 19], [4, 22]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 15", "level": 5, "stage": 6, "room": 15, "pointer": 3135238, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": ["Sir Kibble"], "default_exits": [{"room": 16, "unkn1": 15, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 15 Exit 0", "access_rule": []}], "entity_load": [[27, 16]], "locations": ["Iceberg 6 - Enemy 4 (Sir Kibble)"], "music": 12}, {"name": "Iceberg 6 - 16", "level": 5, "stage": 6, "room": 16, "pointer": 3123096, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 17, "unkn1": 7, "unkn2": 5, "x": 136, "y": 168, "name": "Iceberg 6 - 16 Exit 0", "access_rule": []}], "entity_load": [[4, 22], [33, 19]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 17", "level": 5, "stage": 6, "room": 17, "pointer": 3144389, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 18, "unkn1": 15, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 17 Exit 0", "access_rule": []}], "entity_load": [[5, 27]], "locations": ["Iceberg 6 - Miniboss 4 (Haboki)"], "music": 12}, {"name": "Iceberg 6 - 18", "level": 5, "stage": 6, "room": 18, "pointer": 3121014, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 19, "unkn1": 7, "unkn2": 5, "x": 136, "y": 168, "name": "Iceberg 6 - 18 Exit 0", "access_rule": []}], "entity_load": [[33, 19], [4, 22]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 19", "level": 5, "stage": 6, "room": 19, "pointer": 3017228, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 20, "unkn1": 15, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 19 Exit 0", "access_rule": []}], "entity_load": [[4, 27]], "locations": ["Iceberg 6 - Miniboss 5 (Boboo)"], "music": 12}, {"name": "Iceberg 6 - 20", "level": 5, "stage": 6, "room": 20, "pointer": 3121709, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 21, "unkn1": 7, "unkn2": 5, "x": 136, "y": 168, "name": "Iceberg 6 - 20 Exit 0", "access_rule": []}], "entity_load": [[33, 19], [4, 22]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 21", "level": 5, "stage": 6, "room": 21, "pointer": 3145036, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 22, "unkn1": 15, "unkn2": 10, "x": 296, "y": 136, "name": "Iceberg 6 - 21 Exit 0", "access_rule": []}], "entity_load": [[0, 27]], "locations": ["Iceberg 6 - Miniboss 6 (Captain Stitch)"], "music": 12}, {"name": "Iceberg 6 - 22", "level": 5, "stage": 6, "room": 22, "pointer": 3116830, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 23, "unkn1": 7, "unkn2": 5, "x": 136, "y": 152, "name": "Iceberg 6 - 22 Exit 0", "access_rule": []}], "entity_load": [[33, 19], [4, 22]], "locations": [], "music": 12}, {"name": "Iceberg 6 - 23", "level": 5, "stage": 6, "room": 23, "pointer": 3045263, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [{"room": 24, "unkn1": 17, "unkn2": 9, "x": 72, "y": 120, "name": "Iceberg 6 - 23 Exit 0", "access_rule": []}], "entity_load": [[33, 19], [42, 19]], "locations": ["Iceberg 6 - Angel"], "music": 8}, {"name": "Iceberg 6 - 24", "level": 5, "stage": 6, "room": 24, "pointer": 2889389, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[38, 19]], "locations": ["Iceberg 6 - Complete"], "music": 5}, {"name": "Iceberg Boss - 0", "level": 5, "stage": 7, "room": 0, "pointer": 2980207, "animal_pointers": [], "consumables": [], "consumables_pointer": 0, "enemies": [], "default_exits": [], "entity_load": [[8, 18]], "locations": ["Iceberg - Boss (Dedede) Purified", "Level 5 Boss - Defeated", "Level 5 Boss - Purified"], "music": 7}] \ No newline at end of file diff --git a/worlds/kdl3/data/kdl3_basepatch.bsdiff4 b/worlds/kdl3/data/kdl3_basepatch.bsdiff4 new file mode 100644 index 0000000000000000000000000000000000000000..cd002121cd38dcb319ba2148ced46c9592c3905b GIT binary patch literal 2411 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 R2S2ZGPW5pM7JXgjzX0o>8Uz3U literal 0 HcmV?d00001 diff --git a/worlds/kdl3/docs/en_Kirby's Dream Land 3.md b/worlds/kdl3/docs/en_Kirby's Dream Land 3.md new file mode 100644 index 0000000000..c1e36fed54 --- /dev/null +++ b/worlds/kdl3/docs/en_Kirby's Dream Land 3.md @@ -0,0 +1,38 @@ +# Kirby's Dream Land 3 + +## Where is the settings page? + +The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +config file. + +## What does randomization do to this game? +Kirby will be unable to absorb Copy Abilities and meet up with his animal friends until they are sent to him. Items such +as Heart Stars, 1-Ups, and Invincibility Candy will be shuffled into the pool for Kirby to receive. + +## What is considered a location check in Kirby's Dream Land 3? +- Completing a stage for the first time +- Completing the given task of a stage and receiving a Heart Star +- Purifying a boss after acquiring a certain number of Heart Stars + (indicated by their portrait flashing in the level select) +- If enabled, 1-Ups and Maxim Tomatoes + +## When the player receives an item, what happens? +A sound effect will play, and Kirby will immediately receive the effects of that item, such as being able to receive Copy Abilities from enemies that +give said Copy Ability. Animal Friends will require leaving the room you are currently in before they will appear. + +## What is the goal of Kirby's Dream Land 3? +Under the Zero goal, players must collect enough Heart Stars to purify the five bosses and gain access to the Hyper Zone, +where Zero can be found and defeated. + +Under the Boss Butch goal, players must collect enough Heart Stars to purify the five bosses +and then complete the Boss Butch game mode accessible from the main menu. + +Under the MG5 goal, players must collect enough Heart Stars to purify the five bosses +and then perfect the Super MG5 game mode accessible from the main menu. + +Under the Jumping goal, players must collect enough Heart Stars to purify the five bosses +and then reach a target score in the Jumping game mode accessible from the main menu. + +## Why is EmuHawk resizing itself while I'm playing? +Kirby's Dream Land 3 changes the SNES's display resolution from 1x to 2x many times during gameplay (particularly in rooms with foreground effects). +To counter-act this resizing, set SNES -> Options -> "Always use double-size frame buffer". diff --git a/worlds/kdl3/docs/setup_en.md b/worlds/kdl3/docs/setup_en.md new file mode 100644 index 0000000000..a13a0f1a74 --- /dev/null +++ b/worlds/kdl3/docs/setup_en.md @@ -0,0 +1,148 @@ +# Kirby's Dream Land 3 Randomizer Setup Guide + +## Required Software + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). +- Hardware or software capable of loading and playing SNES ROM files + - An emulator capable of connecting to SNI with ROM access. Any one of the following will work: + - snes9x-emunwa from: [snes9x-emunwa Releases Page](https://github.com/Skarsnik/snes9x-emunwa/releases) + - snes9x-rr from: [snes9x-rr Releases Page](https://github.com/gocha/snes9x-rr/releases) + - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html) + - bsnes-plus-nwa from: [bsnes-plus GitHub](https://github.com/black-sliver/bsnes-plus) + - **RetroArch is currently incompatible with Kirby's Dream Land 3** + - Or SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other + compatible hardware. +- Your KDL3 ROM file, probably named either `Kirby's Dream Land 3 (USA).sfc` or `Hoshi no Kirby 3 (J).sfc` + +## Installation Procedures + +1. Download and install Archipelago from the link above, making sure to install the most recent version. + **The installer file is located in the assets section at the bottom of the version information**. + - During generation/patching, you will be asked to locate your base ROM file. This is your Kirby's Dream Land 3 ROM file. + +2. If you are using an emulator, you should assign your SNI-compatible emulator as your default program for launching ROM + files. + 1. Extract your emulator's folder to your Desktop, or somewhere you will remember. + 2. Right-click on a ROM file and select **Open with...** + 3. Check the box next to **Always use this app to open .sfc files** + 4. Scroll to the bottom of the list and click the grey text **Look for another App on this PC** + 5. Browse for your emulator's `.exe` file and click **Open**. This file should be located inside the folder you + extracted in step one. + +## Create a Config (.yaml) File + +### What is a config file and why do I need one? + +Your config 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 config 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. + +See the guide on setting up a basic YAML at the Archipelago setup +guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) + +### Where do I get a config file? + +The [Player Settings](/games/Kirby's%20Dream%20Land%203/player-settings) page on the website allows you to configure +your personal settings and export a config file from them. + +### Verifying your config file + +If you would like to validate your config file to make sure it works, you may do so on the +[YAML Validator](/mysterycheck) page. + +## Generating a Single-Player Game + +1. Navigate to the [Player Settings](/games/Kirby's%20Dream%20Land%203/player-settings) page, configure your options, + and click the "Generate Game" button. +2. You will be presented with a "Seed Info" page. +3. Click the "Create New Room" link. +4. You will be presented with a server page, from which you can download your patch file. +5. Double-click on your patch file, and SNIClient will launch automatically, create your ROM from the patch file, and + open your emulator for you. + +## Joining a MultiWorld Game + +### Obtain your patch file and create your ROM + +When you join a multiworld game, you will be asked to provide your config file to whoever is hosting. Once that is done, +the host will provide you with either a link to download your patch file, or with a zip file containing everyone's patch +files. Your patch file should have a `.apkdl3` extension. + +Put your patch file on your desktop or somewhere convenient, and double click it. This should automatically launch the +client, and will also create your ROM in the same place as your patch file. + +### Connect to the client + +#### With an emulator + +When the client launched automatically, SNI should have also automatically launched in the background. If this is its +first time launching, you may be prompted to allow it to communicate through the Windows Firewall. + +##### snes9x-rr + +1. Load your ROM file if it hasn't already been loaded. +2. Click on the File menu and hover on **Lua Scripting** +3. Click on **New Lua Script Window...** +4. In the new window, click **Browse...** +5. Select the connector lua file included with your client + - Look in the Archipelago folder for `/SNI/lua/Connector.lua` +6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of +the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install. + +##### BizHawk + +1. Ensure you have the BSNES core loaded. You may do this by clicking on the Tools menu in BizHawk and following these + menu options: + `Config --> Cores --> SNES --> BSNES` + Once you have changed the loaded core, you must restart BizHawk. +2. Load your ROM file if it hasn't already been loaded. +3. Click on the Tools menu and click on **Lua Console** +4. Click Script -> Open Script... +5. Select the `Connector.lua` file you downloaded above + - Look in the Archipelago folder for `/SNI/lua/Connector.lua` + +##### bsnes-plus-nwa and snes9x-nwa + +These should automatically connect to SNI. If this is the first time launching, you may be prompted to allow it to +communicate through the Windows Firewall. + +#### With hardware + +This guide assumes you have downloaded the correct firmware for your device. If you have not done so already, please do +this now. SD2SNES and FXPak Pro users may download the appropriate firmware +[here](https://github.com/RedGuyyyy/sd2snes/releases). Other hardware may find helpful information +[on this page](http://usb2snes.com/#supported-platforms). + +1. Close your emulator, which may have auto-launched. +2. Power on your device and load the ROM. + +### Connect to the Archipelago Server + +The patch file which launched your client should have automatically connected you to the AP Server. There are a few +reasons this may not happen however, including if the game is hosted on the website but was generated elsewhere. If the +client window shows "Server Status: Not Connected", simply ask the host for the address of the server, and copy/paste it +into the "Server" input field then press enter. + +The client will attempt to reconnect to the new server address, and should momentarily show "Server Status: Connected". + +### Play the game + +When the client shows both SNES Device and Server as connected, you're ready to begin playing. Congratulations on +successfully joining a multiworld game! You can execute various commands in your client. For more information regarding +these commands you can use `/help` for local client commands and `!help` for server commands. + +## Hosting a MultiWorld game + +The recommended way to host a game is to use our [hosting service](/generate). The process is relatively simple: + +1. Collect config files from your players. +2. Create a zip file containing your players' config files. +3. Upload that zip file to the website linked above. +4. Wait a moment while the seed is generated. +5. When the seed is generated, you will be redirected to a "Seed Info" page. +6. Click "Create New Room". This will take you to the server page. Provide the link to this page to your players, so + they may download their patch files from there. +7. Note that a link to a MultiWorld Tracker is at the top of the room page. The tracker shows the progress of all + players in the game. Any observers may also be given the link to this page. +8. Once all players have joined, you may begin playing. diff --git a/worlds/kdl3/src/kdl3_basepatch.asm b/worlds/kdl3/src/kdl3_basepatch.asm new file mode 100644 index 0000000000..e419d0632f --- /dev/null +++ b/worlds/kdl3/src/kdl3_basepatch.asm @@ -0,0 +1,1237 @@ +fullsa1rom + +!GAME_STATUS = $36D0 + +; SNES hardware registers +!VMADDL = $002116 +!VMDATAL = $002118 +!MDMAEN = $00420B +!DMAP0 = $004300 +!BBAD0 = $004301 +!A1T0L = $004302 +!A1B0 = $004304 +!DAS0L = $004305 + +org $008033 + JSL WriteBWRAM + NOP #5 + +org $00A245 +HSVPatch: + BRA .Jump + PHX + LDA $6CA0,X + TAX + LDA $6E22,X + JSR $A25B + PLX + INX + .Jump: + JSL HeartStarVisual + + +org $00A3FC + JSL NintenHalken + NOP + +org $00EAE4 + JSL MainLoopHook + NOP + +org $00F85E + JSL SpeedTrap + NOP #2 + +org $00FFC0 + db "KDL3_BASEPATCH_ARCHI" + +org $00FFD8 + db $06 + +org $018405 + JSL PauseMenu + +org $01AFC8 + JSL HeartStarCheck + NOP #13 + +org $01B013 + SEC ; Remove Dedede Bad Ending + +org $02B7B0 ; Zero unlock + LDA $80A0 + CMP #$0001 + +org $02C238 + LDA #$0006 + JSL OpenWorldUnlock + NOP #5 + +org $02C27C + JSL HeartStarSelectFix + NOP #2 + +org $02C317 + JSL LoadFont + +org $02C39D + JSL StageCompleteSet + NOP #2 + +org $02C3D9 + JSL StrictBosses + NOP #2 + +org $02C3F0 + JSL OpenWorldBossUnlock + NOP #6 + +org $02C463 + JSL NormalGoalSet + NOP #2 + +org $049CD7 + JSL AnimalFriendSpawn + +org $06801E + JSL ConsumableSet + +org $068518 + JSL CopyAbilityOverride + NOP #2 + +org $099F35 + JSL HeartStarCutsceneFix + +org $09A01F + JSL HeartStarGraphicFix + NOP #2 + db $B0 + +org $09A0AE + JSL HeartStarGraphicFix + NOP #2 + db $90 + +org $0A87E8 + JSL CopyAbilityAnimalOverride + NOP #2 + +org $12B238 + JSL FinalIcebergFix + NOP #10 + db $B0 + +org $14A3EB + LDA $07A2, Y + JSL StarsSet + NOP #3 + +org $15BC13 + JML GiftGiving + NOP + +org $0799A0 +CopyAbilityOverride: + LDA $54F3, Y + PHA + ASL + TAX + PLA + CMP $8020, X + NOP #2 + BEQ .StoreAbilityK + LDA #$0000 + .StoreAbilityK: + STA $54A9, Y + RTL + NOP #4 +CopyAbilityAnimalOverride: + PHA + ASL + TAY + PLA + CMP $8020, Y + NOP + BEQ .StoreAbilityA + LDA #$0000 + .StoreAbilityA: + STA $54A9, X + STA $39DF, X + RTL + +org $079A00 +HeartStarCheck: + TXA + CMP #$0000 ; is this level 1 + BEQ .PassToX + LSR + LSR + INC + .PassToX: + TAX + LDA $8070 ; heart stars + CLC + CMP $07D00A ;Compare to goal heart stars + BCC .CompareWorldHS ; we don't have enough + PHX + LDA #$0014 + STA $7F62 ; play sound fx 0x14 + LDA $07D012 ; goal + CMP #$0000 ; are we on zero goal? + BEQ .ZeroGoal ; we are + LDA #$0001 + LDX $3617 ; current save + STA $53DD, X ; boss butch + STA $53DF, X ; MG5 + STA $53E1, X ; Jumping + BRA .PullX + .ZeroGoal: + LDA #$0001 + STA $80A0 ; zero unlock address + .PullX: + PLX + .CompareWorldHS: + LDA $8070 ; current heart stars + CMP $07D000, X ; compare to world heart stars + BCS .ReturnTrue + CLC + RTL + .ReturnTrue: + SEC + RTL + +org $079A80 +OpenWorldUnlock: + PHX + LDX $900E ; Are we on open world? + BNE .Open ; Branch if we are + LDA #$0001 + .Open: + STA $5AC1 ;(cutscene) + STA $53CD ;(unlocked stages) + INC + STA $5AB9 ;(currently selectable stages) + CPX #$0001 + BNE .Return ; Return if we aren't on open world + LDA #$0001 + STA $5A9D + STA $5A9F + STA $5AA1 + STA $5AA3 + STA $5AA5 + .Return: + PLX + RTL + +org $079B00 +MainLoopHook: + STA $D4 + INC $3524 + JSL ParseItemQueue + LDA $7F62 ; sfx to be played + BEQ .Traps ; skip if 0 + JSL $00D927 ; play sfx + STZ $7F62 + .Traps: + LDA $36D0 + CMP #$FFFF ; are we in menus? + BEQ .Return ; return if we are + LDA $5541 ; gooey status + BPL .Slowness ; gooey is already spawned + LDA $8080 + CMP #$0000 ; did we get a gooey trap + BEQ .Slowness ; branch if we did not + JSL GooeySpawn + STZ $8080 + .Slowness: + LDA $8082 ; slowness + BEQ .Eject ; are we under the effects of a slowness trap + DEC + STA $8082 ; dec by 1 each frame + .Eject: + PHX + PHY + LDA $54A9 ; copy ability + BEQ .PullVars ; branch if we do not have a copy ability + LDA $8084 ; eject ability + BEQ .PullVars ; branch if we haven't received eject + LDA #$2000 ; select button press + STA $60C1 ; write to controller mirror + STZ $8084 + .PullVars: + PLY + PLX + .Return: + RTL + +org $079B80 +HeartStarGraphicFix: + LDA #$0000 + PHX + PHY + LDX $363F ; current level + LDY $3641 ; current stage + .LoopLevel: + CPX #$0000 + BEQ .LoopStage + INC #6 + DEX + BRA .LoopLevel ; return to loop head + .LoopStage: + CPY #$0000 + BEQ .EndLoop + INC + DEY + BRA .LoopStage ; return to loop head + .EndLoop + ASL + TAX + LDA $07D080, X ; table of original stage number + CMP #$0003 ; is the current stage a minigame stage? + BEQ .ReturnTrue ; branch if so + CLC + BRA .Return + .ReturnTrue: + SEC + .Return: + PLY + PLX + RTL + +org $079BF0 +ParseItemQueue: +; Local item queue parsing + NOP + LDX #$0000 + .LoopHead: + LDA $C000,X + BIT #$0010 + BNE .Ability + BIT #$0020 + BNE .Animal + BIT #$0040 + BNE .Positive + BIT #$0080 + BNE .Negative + .LoopCheck: + INX + INX + CPX #$000F + BCC .LoopHead + RTL + .Ability: + JSL .ApplyAbility + RTL + .Animal: + JSL .ApplyAnimal + RTL + .Positive: + LDY $36D0 + CPY #$FFFF + BEQ .LoopCheck + JSL .ApplyPositive + RTL + .Negative: + AND #$000F + ASL + TAY + LDA $8080,Y + BNE .LoopCheck + JSL .ApplyNegative + RTL + .ApplyAbility: + AND #$000F + PHA + ASL + TAY + PLA + STA $8020,Y + LDA #$0032 + BRA .PlaySFX + .ApplyAnimal: + AND #$000F + PHA + ASL + TAY + PLA + INC + STA $8000,Y + LDA #$0032 + BRA .PlaySFX + .PlaySFX: + STA $7F62 + STZ $C000,X + .Return: + RTL + .ApplyPositive: + LDY $36D0 + CPY #$FFFF + BEQ .Return + AND #$000F + BEQ .HeartStar + CMP #$0004 + BCS .StarBit + CMP #$0002 + BCS .Not1UP + LDA $39CF + INC + STA $39CF + STA $39E3 + LDA #$0033 + BRA .PlaySFX + .Not1UP: + CMP #$0003 + BEQ .Invincibility + LDA $39D3 + BEQ .JustKirby + LDA #$0008 + STA $39D1 + STA $39D3 + BRA .PlayPositive + .JustKirby: + LDA #$000A + STA $39D1 + BRA .PlayPositive + .Invincibility: + LDA #$0384 + STA $54B1 + BRA .PlayPositive + .HeartStar: + INC $8070 + LDA #$0016 + BRA .PlaySFX + .StarBit: + SEC + SBC #$0004 + ASL + INC + CLC + ADC $39D7 + ORA #$8000 + STA $39D7 + .PlayPositive: + LDA #$0026 + .PlaySFXLong + BRA .PlaySFX + .ApplyNegative: + CPY #$0005 + BCS .PlayNone + LDA $8080,Y + BNE .Return + LDA #$0384 + STA $8080,Y + LDA #$00A7 + BRA .PlaySFXLong + .PlayNone: + LDA #$0000 + BRA .PlaySFXLong + +org $079D00 +AnimalFriendSpawn: + PHA + CPX #$0002 ; is this an animal friend? + BNE .Return + XBA + PHA + ASL + TAY + PLA + INC + CMP $8000, Y ; do we have this animal friend + BEQ .Return ; we have this animal friend + INX + .Return: + PLY + LDA #$9999 + RTL + +org $079E00 +WriteBWRAM: + LDY #$6001 ;starting addr + LDA #$1FFE ;bytes to write + MVN $40, $40 ;copy $406000 from 406001 to 407FFE + LDX #$0000 + LDY #$0014 + .LoopHead: + LDA $8100, X ; rom header + CMP $07C000, X ; compare to real rom name + BNE .InitializeRAM ; area is uninitialized or corrupt, reset + INX + DEY + BMI .Return ; if Y is negative, rom header matches, valid bwram + BRA .LoopHead ; else continue loop + .InitializeRAM: + LDA #$0000 + STA $8000 ; initialize first byte that gets copied + LDX #$8000 + LDY #$8001 + LDA #$7FFD + MVN $40, $40 ; initialize 0x8000 onward + LDX #$D000 ; seed info 0x3D000 + LDY #$9000 ; target location + LDA #$1000 + MVN $40, $07 + LDX #$C000 ; ROM name + LDY #$8100 ; target + LDA #$0015 + MVN $40, $07 + .Return: + RTL + +org $079E80 +ConsumableSet: + PHA + PHX + PHY + AND #$00FF + PHA + LDX $53CF + LDY $53D3 + LDA #$0000 + DEY + .LoopLevel: + CPX #$0000 + BEQ .LoopStage + CLC + ADC #$0007 + DEX + BRA .LoopLevel ; return to loop head + .LoopStage: + CPY #$0000 + BEQ .EndLoop + INC + DEY + BRA .LoopStage ; return to loop head + .EndLoop: + ASL + TAX + LDA $07D020, X ; current stage + DEC + ASL #6 + TAX + PLA + .LoopHead: + CMP #$0000 + BEQ .ApplyCheck + INX + DEC + BRA .LoopHead ; return to loop head + .ApplyCheck: + LDA $A000, X ; consumables index + ORA #$0001 + STA $A000, X + PLY + PLX + PLA + XBA + AND #$00FF + RTL + +org $079F00 +NormalGoalSet: + PHX + LDA $07D012 + CMP #$0000 + BEQ .ZeroGoal + LDA #$0001 + LDX $3617 ; current save + STA $53DD, X ; Boss butch + STA $53DF, X ; MG5 + STA $53D1, X ; Jumping + BRA .Return + .ZeroGoal: + LDA #$0001 + STA $80A0 + .Return: + PLX + LDA #$0006 + STA $5AC1 ; cutscene + RTL + +org $079F80 +FinalIcebergFix: + PHX + PHY + LDA #$0000 + LDX $363F + LDY $3641 + .LoopLevel: + CPX #$0000 + BEQ .LoopStage + INC #7 + DEX + BRA .LoopLevel ; return to loop head + .LoopStage: + CPY #$0000 + BEQ .CheckStage + INC + DEY + BRA .LoopStage ; return to loop head + .CheckStage: + ASL + TAX + LDA $07D020, X + CMP #$001E + BEQ .ReturnTrue + CLC + BRA .Return + .ReturnTrue: + SEC + .Return: + PLY + PLX + RTL + +org $07A000 +StrictBosses: + PHX + LDA $901E ; Do we have strict bosses enabled? + BEQ .ReturnTrue ; Return True if we don't, always unlock the boss in this case + LDA $53CB ; unlocked level + CMP #$0005 ; have we unlocked level 5? + BCS .ReturnFalse ; we don't need to do anything if so + NOP #5 ;unsure when these got here + LDX $53CB + DEX + TXA + ASL + TAX + LDA $8070 ; current heart stars + CMP $07D000, X ; do we have enough HS to purify? + BCS .ReturnTrue ; branch if we do + .ReturnFalse: + SEC + BRA .Return + .ReturnTrue: + CLC + .Return: + PLX + LDA $53CD + RTL + +org $07A030 +NintenHalken: + LDX #$0005 + .Halken: + LDA $00A405, X ; loop head (halken) + STA $4080F0, X + DEX + BPL .Halken ; branch if more letters to copy + LDX #$0005 + .Ninten: + LDA $00A40B, X ; loop head (ninten) + STA $408FF0, X + DEX + BPL .Ninten ; branch if more letters to copy + REP #$20 + LDA #$0001 + RTL + +org $07A080 +StageCompleteSet: + PHX + LDA $5AC1 ; completed stage cutscene + BEQ .Return ; we have not completed a stage + LDA #$0000 + LDX $53CF ; current level + .LoopLevel: + CPX #$0000 + BEQ .StageStart + DEX + INC #7 + BRA .LoopLevel ; return to loop head + .StageStart: + LDX $53D3 ; current stage + CPX #$0007 ; is this a boss stage + BEQ .Return ; return if so + DEX + .LoopStage: + CPX #$0000 + BEQ .LoopEnd + INC + DEX + BRA .LoopStage ; return to loop head + .LoopEnd: + ASL + TAX + LDA $9020, X ; load the stage we completed + DEC + ASL + TAX + LDA #$0001 + ORA $8200, X + STA $8200, X + .Return: + PLX + LDA $53CF + CMP $53CB + RTL + +org $07A100 +OpenWorldBossUnlock: + PHX + PHY + LDA $900E ; Are we on open world? + BEQ .ReturnTrue ; Return true if we aren't, always unlock boss + LDA $53CD + CMP #$0006 + BNE .ReturnFalse ; return if we aren't on stage 6 + LDA $53CF + INC + CMP $53CB ; are we on the most unlocked level? + BNE .ReturnFalse ; return if we aren't + LDA #$0000 + LDX $53CF + .LoopLevel: + CPX #$0000 + BEQ .LoopStages + ADC #$0006 + DEX + BRA .LoopLevel ; return to loop head + .LoopStages: + ASL + TAX + LDA #$0000 + LDY #$0006 + PHY + PHX + .LoopStage: + PLX + LDY $9020, X ; get stage id + DEY + INX + INX + PHA + TYA + ASL + TAY + PLA + ADC $8200, Y ; add current stage value to total + PLY + DEY + PHY + PHX + CPY #$0000 + BNE .LoopStage ; return to loop head + PLX + PLY + SEC + SBC $9016 + BCC .ReturnFalse + .ReturnTrue + LDA $53CD + INC + STA $53CD + STA $5AC1 + BRA .Return + .ReturnFalse: + STZ $5AC1 + .Return: + PLY + PLX + RTL + +org $07A180 +GooeySpawn: + PHY + PHX + LDX #$0000 + LDY #$0000 + STA $5543 + LDA $1922,Y + STA $C0 + LDA $19A2,Y + STA $C2 + LDA #$0008 + STA $C4 + LDA #$0002 + STA $352A + LDA #$0003 + JSL $00F54F + STX $5541 + LDA #$FFFF + STA $0622,X + JSL $00BAEF + JSL $C4883C + LDX $39D1 + CPX #$0001 + BEQ .Return + LDA #$FFFF + CPX #$0002 + BEQ .Call + DEC + .Call: + JSL $C43C22 + .Return: + PLX + PLY + RTL + +org $07A200 +SpeedTrap: + PHX + LDX $8082 ; do we have slowness + BEQ .Apply ; branch if we do not + LSR + .Apply: + PLX + STA $1F22, Y ; player max speed + EOR #$FFFF + RTL + +org $07A280 +HeartStarVisual: + CPX #$0000 + BEQ .SkipInx + INX + .SkipInx + CPX $651E + BCC .Return + CPX #$0000 + BEQ .Return + LDA $4036D0 + AND #$00FF + BEQ .ReturnTrue + LDA $3000 + AND #$0200 + CMP #$0000 + BNE .ReturnTrue + PHY + LDA $3000 + TAY + CLC + ADC #$0020 + STA $3000 + LDA $408070 + LDX #$0000 + .LoopHead: + CMP #$000A + BCC .LoopEnd + SEC + SBC #$000A + INX + BRA .LoopHead + .LoopEnd: + PHX + TAX + PLA + ORA #$2500 + PHA + LDA #$2C70 + STA $0000, Y + PLA + INY + INY + STA $0000, Y + INY + INY + TXA + ORA #$2500 + PHA + LDA #$2C78 + STA $0000, Y + INY + INY + PLA + STA $0000, Y + INY + INY + JSL HeartStarVisual2 ; we ran out of room + PLY + .ReturnTrue: + SEC + .Return: + RTL + +org $07A300 +LoadFont: + JSL $00D29F ; play sfx + PHX + PHB + LDA #$0000 + PHA + PLB + PLB + LDA #$7000 + STA $2116 + LDX #$0000 + .LoopHead: + CPX #$0140 + BEQ .LoopEnd + LDA $D92F50, X + STA $2118 + INX + INX + BRA .LoopHead + .LoopEnd: + LDX #$0000 + .2LoopHead: + CPX #$0020 + BEQ .2LoopEnd + LDA $D92E10, X + STA $2118 + INX + INX + BRA .2LoopHead + .2LoopEnd: + PHY + LDA $07D012 + ASL + TAX + LDA $07E000, X + TAX + LDY #$0000 + .3LoopHead: + CPY #$0020 + BEQ .3LoopEnd + LDA $D93170, X + STA $2118 + INX + INX + INY + INY + BRA .3LoopHead + .3LoopEnd: + LDA $07D00C + ASL + TAX + LDA $07E010, X + TAX + LDY #$0000 + .4LoopHead: + CPY #$0020 + BEQ .4LoopEnd + LDA $D93170, X + STA $2118 + INX + INX + INY + INY + BRA .4LoopHead + .4LoopEnd: + PLY + PLB + PLX + RTL + +org $07A380 +HeartStarVisual2: + LDA #$2C80 + STA $0000, Y + INY + INY + LDA #$250A + STA $0000, Y + INY + INY + LDA $4053CF + ASL + TAX + .LoopHead: + LDA $409000, X + CMP #$FFFF + BNE .LoopEnd + DEX + DEX + BRA .LoopHead + .LoopEnd: + LDX #$0000 + .2LoopHead: + CMP #$000A + BCC .2LoopEnd + SEC + SBC #$000A + INX + BRA .2LoopHead ; return to loop head + .2LoopEnd: + PHX + TAX + PLA + ORA #$2500 + PHA + LDA #$2C88 + STA $0000, Y + PLA + INY + INY + STA $0000, Y + INY + INY + TXA + ORA #$2500 + PHA + LDA #$2C90 + STA $0000, Y + INY + INY + PLA + STA $0000, Y + INY + INY + LDA #$14D8 + STA $0000, Y + INY + INY + LDA #$250B + STA $0000, Y + INY + INY + LDA #$14E0 + STA $0000, Y + INY + INY + LDA #$250A + STA $0000, Y + INY + INY + LDA #$14E8 + STA $0000, Y + INY + INY + LDA #$250C + STA $0000, Y + INY + INY + LDA $3000 + SEC + SBC #$3040 + LSR + LSR + .3LoopHead: + CMP #$0004 + BCC .3LoopEnd + DEC #4 + BRA .3LoopHead ; return to loop head + .3LoopEnd: + STA $3240 + LDA #$0004 + SEC + SBC $3240 + TAX + LDA #$00FF + .4LoopHead: + CPX #$0000 + BEQ .4LoopEnd + LSR + LSR + DEX + BRA .4LoopHead + .4LoopEnd: + LDY $3002 + AND $0000, Y + STA $0000, Y + INY + LDA #$0000 + STA $0000, Y + INY + INY + STA $0000, Y + RTL + +org $07A480 +HeartStarSelectFix: + PHX + TXA + ASL + TAX + LDA $9020, X + DEC + TAX + .LoopHead: + CMP #$0006 + BMI .LoopEnd + INX + SEC + SBC #$0006 + BRA .LoopHead + .LoopEnd: + LDA $53A7, X + PLX + AND #$00FF + RTL + +org $07A500 +HeartStarCutsceneFix: + TAX + LDA $53D3 + DEC + STA $5AC3 + RTL + +org $07A510 +GiftGiving: + CMP #$0008 + .This: + BCS .This ; this intentionally safe-crashes the game if hit + PHX + LDX $901C + BEQ .Return + PLX + STA $8086 + LDA #$0026 + JML $CABC99 + .Return: + PLX + JML $CABC18 + +org $07A550 +PauseMenu: + JSL $00D29F + PHX + PHY + LDA #$3300 + STA !VMADDL + LDA #$0007 + STA !A1B0 + LDA #$F000 + STA !A1T0L + LDA #$01C0 + STA !DAS0L + SEP #$20 + LDA #$01 + STA !DMAP0 + LDA #$18 + STA !BBAD0 + LDA #$01 + STA !MDMAEN + REP #$20 + LDY #$0000 + .LoopHead: + INY ; loop head + CPY #$0009 + BPL .LoopEnd + TYA + ASL + TAX + LDA $8020, X + BEQ .LoopHead ; return to loop head + TYA + CLC + ADC #$31E2 + STA !VMADDL + LDA $07E020, X + STA !VMDATAL + BRA .LoopHead ; return to loop head + .LoopEnd: + LDY #$FFFF + .2LoopHead: + INY ; loop head + CPY #$0007 + BPL .2LoopEnd + TYA + ASL + TAX + LDA $8000, X + BEQ .2LoopHead ; return to loop head + TYA + CLC + ADC #$3203 + STA !VMADDL + LDA $07E040, X + STA !VMDATAL + BRA .2LoopHead ; return to loop head + .2LoopEnd: + PLY + PLX + RTL + +org $07A600 +StarsSet: + PHA + PHX + PHY + LDX $901A + BEQ .ApplyStar + AND #$00FF + PHA + LDX $53CF + LDY $53D3 + LDA #$0000 + DEY + .LoopLevel: + CPX #$0000 + BEQ .LoopStage + CLC + ADC #$0007 + DEX + BRA .LoopLevel + .LoopStage: + CPY #$0000 + BEQ .LoopEnd + INC + DEY + BRA .LoopStage + .LoopEnd: + ASL + TAX + LDA $07D020, X + DEC + ASL + ASL + ASL + ASL + ASL + ASL + TAX + PLA + .2LoopHead: + CMP #$0000 + BEQ .2LoopEnd + INX + DEC + BRA .2LoopHead + .2LoopEnd: + LDA $B000, X + ORA #$0001 + STA $B000, X + .Return: + PLY + PLX + PLA + XBA + AND #$00FF + RTL + .ApplyStar: + LDA $39D7 + INC + ORA #$8000 + STA $39D7 + BRA .Return + + +org $07C000 + db "KDL3_BASEPATCH_ARCHI" + +org $07E000 + db $20, $03 + db $20, $00 + db $80, $01 + db $20, $01 + db $00, $00 + db $00, $00 + db $00, $00 + db $00, $00 + db $A0, $01 + db $A0, $00 + +; Pause Icons + +org $07E020 + db $00, $0C + db $30, $09 + db $31, $09 + db $32, $09 + db $33, $09 + db $34, $09 + db $35, $09 + db $36, $09 + db $37, $09 + +org $07E040 + db $38, $05 + db $39, $05 + db $3A, $01 + db $3B, $05 + db $3C, $05 + db $3D, $05 \ No newline at end of file diff --git a/worlds/kdl3/test/__init__.py b/worlds/kdl3/test/__init__.py new file mode 100644 index 0000000000..11a17e63b7 --- /dev/null +++ b/worlds/kdl3/test/__init__.py @@ -0,0 +1,37 @@ +import typing +from argparse import Namespace + +from BaseClasses import MultiWorld, PlandoOptions, CollectionState +from test.TestBase import WorldTestBase +from test.general import gen_steps +from worlds import AutoWorld +from worlds.AutoWorld import call_all + + +class KDL3TestBase(WorldTestBase): + game = "Kirby's Dream Land 3" + + def world_setup(self, seed: typing.Optional[int] = None) -> None: + if type(self) is WorldTestBase or \ + (hasattr(WorldTestBase, self._testMethodName) + and not self.run_default_tests and + getattr(self, self._testMethodName).__code__ is + getattr(WorldTestBase, self._testMethodName, None).__code__): + return # setUp gets called for tests defined in the base class. We skip world_setup here. + if not hasattr(self, "game"): + raise NotImplementedError("didn't define game name") + self.multiworld = MultiWorld(1) + self.multiworld.game[1] = self.game + self.multiworld.player_name = {1: "Tester"} + self.multiworld.set_seed(seed) + self.multiworld.state = CollectionState(self.multiworld) + args = Namespace() + for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].options_dataclass.type_hints.items(): + setattr(args, name, { + 1: option.from_any(self.options.get(name, getattr(option, "default"))) + }) + self.multiworld.set_options(args) + self.multiworld.plando_options = PlandoOptions.connections + self.multiworld.plando_connections = self.options["plando_connections"] if "plando_connections" in self.options.keys() else [] + for step in gen_steps: + call_all(self.multiworld, step) diff --git a/worlds/kdl3/test/test_goal.py b/worlds/kdl3/test/test_goal.py new file mode 100644 index 0000000000..ce53642a97 --- /dev/null +++ b/worlds/kdl3/test/test_goal.py @@ -0,0 +1,64 @@ +from . import KDL3TestBase + + +class TestFastGoal(KDL3TestBase): + options = { + "open_world": False, + "goal_speed": "fast", + "total_heart_stars": 30, + "heart_stars_required": 50, + "filler_percentage": 0, + } + + def test_goal(self): + self.assertBeatable(False) + heart_stars = self.get_items_by_name("Heart Star") + self.collect(heart_stars[0:14]) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14:15]) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) + self.assertBeatable(True) + self.remove([self.get_item_by_name("Love-Love Rod")]) + self.collect_by_name("Kine") # Ensure a little more progress, but leave out cutter and burning + self.collect(heart_stars[15:]) + self.assertBeatable(True) + + +class TestNormalGoal(KDL3TestBase): + # TODO: open world tests + options = { + "open_world": False, + "goal_speed": "normal", + "total_heart_stars": 30, + "heart_stars_required": 50, + "filler_percentage": 0, + } + + def test_goal(self): + self.assertBeatable(False) + heart_stars = self.get_items_by_name("Heart Star") + self.collect(heart_stars[0:14]) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14:15]) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect_by_name(["Burning", "Cutter", "Kine"]) + self.assertBeatable(True) + self.remove([self.get_item_by_name("Love-Love Rod")]) + self.collect(heart_stars) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) + self.assertBeatable(True) + + def test_kine(self): + self.collect_by_name(["Cutter", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_cutter(self): + self.collect_by_name(["Kine", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_burning(self): + self.collect_by_name(["Cutter", "Kine", "Heart Star"]) + self.assertBeatable(False) diff --git a/worlds/kdl3/test/test_locations.py b/worlds/kdl3/test/test_locations.py new file mode 100644 index 0000000000..543f0d8392 --- /dev/null +++ b/worlds/kdl3/test/test_locations.py @@ -0,0 +1,68 @@ +from . import KDL3TestBase +from worlds.generic import PlandoConnection +from ..Names import LocationName +import typing + + +class TestLocations(KDL3TestBase): + options = { + "open_world": True, + "ow_boss_requirement": 1, + "strict_bosses": False + # these ensure we can always reach all stages physically + } + + def test_simple_heart_stars(self): + self.run_location_test(LocationName.grass_land_muchi, ["ChuChu"]) + self.run_location_test(LocationName.grass_land_chao, ["Stone"]) + self.run_location_test(LocationName.grass_land_mine, ["Kine"]) + self.run_location_test(LocationName.ripple_field_kamuribana, ["Pitch", "Clean"]) + self.run_location_test(LocationName.ripple_field_bakasa, ["Kine", "Parasol"]) + self.run_location_test(LocationName.ripple_field_toad, ["Needle"]) + self.run_location_test(LocationName.ripple_field_mama_pitch, ["Pitch", "Kine", "Burning", "Stone"]) + self.run_location_test(LocationName.sand_canyon_auntie, ["Clean"]) + self.run_location_test(LocationName.sand_canyon_nyupun, ["ChuChu", "Cutter"]) + self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Ice"]) + self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Ice"]), + self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Spark", "Needle"]), + self.run_location_test(LocationName.sand_canyon_rob, ["Stone", "Kine", "Coo", "Parasol", "Clean", "Needle"]), + self.run_location_test(LocationName.cloudy_park_hibanamodoki, ["Coo", "Clean"]) + self.run_location_test(LocationName.cloudy_park_piyokeko, ["Needle"]) + self.run_location_test(LocationName.cloudy_park_mikarin, ["Coo"]) + self.run_location_test(LocationName.cloudy_park_pick, ["Rick"]) + self.run_location_test(LocationName.iceberg_kogoesou, ["Burning"]) + self.run_location_test(LocationName.iceberg_samus, ["Ice"]) + self.run_location_test(LocationName.iceberg_name, ["Burning", "Coo", "ChuChu"]) + self.run_location_test(LocationName.iceberg_angel, ["Cutter", "Burning", "Spark", "Parasol", "Needle", "Clean", "Stone", "Ice"]) + + def run_location_test(self, location: str, itempool: typing.List[str]): + items = itempool.copy() + while len(itempool) > 0: + self.assertFalse(self.can_reach_location(location), str(self.multiworld.seed)) + self.collect_by_name(itempool.pop()) + self.assertTrue(self.can_reach_location(location), str(self.multiworld.seed)) + self.remove(self.get_items_by_name(items)) + + +class TestShiro(KDL3TestBase): + options = { + "open_world": False, + "plando_connections": [ + [], + [ + PlandoConnection("Grass Land 1", "Iceberg 5", "both"), + PlandoConnection("Grass Land 2", "Ripple Field 5", "both"), + PlandoConnection("Grass Land 3", "Grass Land 1", "both") + ]], + "stage_shuffle": "shuffled", + "plando_options": "connections" + } + + def test_shiro(self): + self.assertFalse(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed)) + self.collect_by_name("Nago") + self.assertFalse(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed)) + # despite Shiro only requiring Nago for logic, it cannot be in logic because our two accessible stages + # do not actually give the player access to Nago, thus we need Kine to pass 2-5 + self.collect_by_name("Kine") + self.assertTrue(self.can_reach_location("Iceberg 5 - Shiro"), str(self.multiworld.seed)) diff --git a/worlds/kdl3/test/test_shuffles.py b/worlds/kdl3/test/test_shuffles.py new file mode 100644 index 0000000000..d676b641b0 --- /dev/null +++ b/worlds/kdl3/test/test_shuffles.py @@ -0,0 +1,245 @@ +from typing import List, Tuple +from . import KDL3TestBase +from ..Room import KDL3Room + + +class TestCopyAbilityShuffle(KDL3TestBase): + options = { + "open_world": False, + "goal_speed": "normal", + "total_heart_stars": 30, + "heart_stars_required": 50, + "filler_percentage": 0, + "copy_ability_randomization": "enabled", + } + + def test_goal(self): + self.assertBeatable(False) + heart_stars = self.get_items_by_name("Heart Star") + self.collect(heart_stars[0:14]) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14:15]) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect_by_name(["Burning", "Cutter", "Kine"]) + self.assertBeatable(True) + self.remove([self.get_item_by_name("Love-Love Rod")]) + self.collect(heart_stars) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) + self.assertBeatable(True) + + def test_kine(self): + self.collect_by_name(["Cutter", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_cutter(self): + self.collect_by_name(["Kine", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_burning(self): + self.collect_by_name(["Cutter", "Kine", "Heart Star"]) + self.assertBeatable(False) + + def test_cutter_and_burning_reachable(self): + rooms = self.multiworld.worlds[1].rooms + copy_abilities = self.multiworld.worlds[1].copy_abilities + sand_canyon_5 = self.multiworld.get_region("Sand Canyon 5 - 9", 1) + assert isinstance(sand_canyon_5, KDL3Room) + valid_rooms = [room for room in rooms if (room.level < sand_canyon_5.level) + or (room.level == sand_canyon_5.level and room.stage < sand_canyon_5.stage)] + for room in valid_rooms: + if any(copy_abilities[enemy] == "Cutter Ability" for enemy in room.enemies): + break + else: + self.fail("Could not reach Cutter Ability before Sand Canyon 5!") + iceberg_4 = self.multiworld.get_region("Iceberg 4 - 7", 1) + assert isinstance(iceberg_4, KDL3Room) + valid_rooms = [room for room in rooms if (room.level < iceberg_4.level) + or (room.level == iceberg_4.level and room.stage < iceberg_4.stage)] + for room in valid_rooms: + if any(copy_abilities[enemy] == "Burning Ability" for enemy in room.enemies): + break + else: + self.fail("Could not reach Burning Ability before Iceberg 4!") + + def test_valid_abilities_for_ROB(self): + # there exists a subset of 4-7 abilities that will allow us access to ROB heart star on default settings + self.collect_by_name(["Heart Star", "Kine", "Coo"]) # we will guaranteed need Coo, Kine, and Heart Stars to reach + # first we need to identify our bukiset requirements + groups = [ + ({"Parasol Ability", "Cutter Ability"}, {'Bukiset (Parasol)', 'Bukiset (Cutter)'}), + ({"Spark Ability", "Clean Ability"}, {'Bukiset (Spark)', 'Bukiset (Clean)'}), + ({"Ice Ability", "Needle Ability"}, {'Bukiset (Ice)', 'Bukiset (Needle)'}), + ({"Stone Ability", "Burning Ability"}, {'Bukiset (Stone)', 'Bukiset (Burning)'}), + ] + copy_abilities = self.multiworld.worlds[1].copy_abilities + required_abilities: List[Tuple[str]] = [] + for abilities, bukisets in groups: + potential_abilities: List[str] = list() + for bukiset in bukisets: + if copy_abilities[bukiset] in abilities: + potential_abilities.append(copy_abilities[bukiset]) + required_abilities.append(tuple(potential_abilities)) + collected_abilities = list() + for group in required_abilities: + self.assertFalse(len(group) == 0, str(self.multiworld.seed)) + collected_abilities.append(group[0]) + self.collect_by_name([ability.replace(" Ability", "") for ability in collected_abilities]) + if "Parasol Ability" not in collected_abilities or "Stone Ability" not in collected_abilities: + # required for non-Bukiset related portions + self.collect_by_name(["Parasol", "Stone"]) + + if "Cutter Ability" not in collected_abilities: + # we can't actually reach 3-6 without Cutter + self.assertFalse(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"), str(self.multiworld.seed)) + self.collect_by_name(["Cutter"]) + + self.assertTrue(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"), + ''.join(str(self.multiworld.seed)).join(collected_abilities)) + + +class TestAnimalShuffle(KDL3TestBase): + options = { + "open_world": False, + "goal_speed": "normal", + "total_heart_stars": 30, + "heart_stars_required": 50, + "filler_percentage": 0, + "animal_randomization": "full", + } + + def test_goal(self): + self.assertBeatable(False) + heart_stars = self.get_items_by_name("Heart Star") + self.collect(heart_stars[0:14]) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14:15]) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect_by_name(["Burning", "Cutter", "Kine"]) + self.assertBeatable(True) + self.remove([self.get_item_by_name("Love-Love Rod")]) + self.collect(heart_stars) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) + self.assertBeatable(True) + + def test_kine(self): + self.collect_by_name(["Cutter", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_cutter(self): + self.collect_by_name(["Kine", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_burning(self): + self.collect_by_name(["Cutter", "Kine", "Heart Star"]) + self.assertBeatable(False) + + def test_locked_animals(self): + self.assertTrue(self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn") + self.assertTrue(self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn") + self.assertTrue(self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in {"Kine Spawn", "Coo Spawn"}) + + +class TestAllShuffle(KDL3TestBase): + options = { + "open_world": False, + "goal_speed": "normal", + "total_heart_stars": 30, + "heart_stars_required": 50, + "filler_percentage": 0, + "animal_randomization": "full", + "copy_ability_randomization": "enabled", + } + + def test_goal(self): + self.assertBeatable(False) + heart_stars = self.get_items_by_name("Heart Star") + self.collect(heart_stars[0:14]) + self.assertEqual(self.count("Heart Star"), 14, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect(heart_stars[14:15]) + self.assertEqual(self.count("Heart Star"), 15, str(self.multiworld.seed)) + self.assertBeatable(False) + self.collect_by_name(["Burning", "Cutter", "Kine"]) + self.assertBeatable(True) + self.remove([self.get_item_by_name("Love-Love Rod")]) + self.collect(heart_stars) + self.assertEqual(self.count("Heart Star"), 30, str(self.multiworld.seed)) + self.assertBeatable(True) + + def test_kine(self): + self.collect_by_name(["Cutter", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_cutter(self): + self.collect_by_name(["Kine", "Burning", "Heart Star"]) + self.assertBeatable(False) + + def test_burning(self): + self.collect_by_name(["Cutter", "Kine", "Heart Star"]) + self.assertBeatable(False) + + def test_locked_animals(self): + self.assertTrue(self.multiworld.get_location("Ripple Field 5 - Animal 2", 1).item.name == "Pitch Spawn") + self.assertTrue(self.multiworld.get_location("Iceberg 4 - Animal 1", 1).item.name == "ChuChu Spawn") + self.assertTrue(self.multiworld.get_location("Sand Canyon 6 - Animal 1", 1).item.name in {"Kine Spawn", "Coo Spawn"}) + + def test_cutter_and_burning_reachable(self): + rooms = self.multiworld.worlds[1].rooms + copy_abilities = self.multiworld.worlds[1].copy_abilities + sand_canyon_5 = self.multiworld.get_region("Sand Canyon 5 - 9", 1) + assert isinstance(sand_canyon_5, KDL3Room) + valid_rooms = [room for room in rooms if (room.level < sand_canyon_5.level) + or (room.level == sand_canyon_5.level and room.stage < sand_canyon_5.stage)] + for room in valid_rooms: + if any(copy_abilities[enemy] == "Cutter Ability" for enemy in room.enemies): + break + else: + self.fail("Could not reach Cutter Ability before Sand Canyon 5!") + iceberg_4 = self.multiworld.get_region("Iceberg 4 - 7", 1) + assert isinstance(iceberg_4, KDL3Room) + valid_rooms = [room for room in rooms if (room.level < iceberg_4.level) + or (room.level == iceberg_4.level and room.stage < iceberg_4.stage)] + for room in valid_rooms: + if any(copy_abilities[enemy] == "Burning Ability" for enemy in room.enemies): + break + else: + self.fail("Could not reach Burning Ability before Iceberg 4!") + + def test_valid_abilities_for_ROB(self): + # there exists a subset of 4-7 abilities that will allow us access to ROB heart star on default settings + self.collect_by_name(["Heart Star", "Kine", "Coo"]) # we will guaranteed need Coo, Kine, and Heart Stars to reach + # first we need to identify our bukiset requirements + groups = [ + ({"Parasol Ability", "Cutter Ability"}, {'Bukiset (Parasol)', 'Bukiset (Cutter)'}), + ({"Spark Ability", "Clean Ability"}, {'Bukiset (Spark)', 'Bukiset (Clean)'}), + ({"Ice Ability", "Needle Ability"}, {'Bukiset (Ice)', 'Bukiset (Needle)'}), + ({"Stone Ability", "Burning Ability"}, {'Bukiset (Stone)', 'Bukiset (Burning)'}), + ] + copy_abilities = self.multiworld.worlds[1].copy_abilities + required_abilities: List[Tuple[str]] = [] + for abilities, bukisets in groups: + potential_abilities: List[str] = list() + for bukiset in bukisets: + if copy_abilities[bukiset] in abilities: + potential_abilities.append(copy_abilities[bukiset]) + required_abilities.append(tuple(potential_abilities)) + collected_abilities = list() + for group in required_abilities: + self.assertFalse(len(group) == 0, str(self.multiworld.seed)) + collected_abilities.append(group[0]) + self.collect_by_name([ability.replace(" Ability", "") for ability in collected_abilities]) + if "Parasol Ability" not in collected_abilities or "Stone Ability" not in collected_abilities: + # required for non-Bukiset related portions + self.collect_by_name(["Parasol", "Stone"]) + + if "Cutter Ability" not in collected_abilities: + # we can't actually reach 3-6 without Cutter + self.assertFalse(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"), str(self.multiworld.seed)) + self.collect_by_name(["Cutter"]) + + self.assertTrue(self.can_reach_location("Sand Canyon 6 - Professor Hector & R.O.B"), + ''.join(str(self.multiworld.seed)).join(collected_abilities)) From 6926f384145c6fdac87ee1035212d78000c6a421 Mon Sep 17 00:00:00 2001 From: Seldom <38388947+Seldom-SE@users.noreply.github.com> Date: Tue, 5 Mar 2024 11:35:41 -0700 Subject: [PATCH 073/166] Terraria: Broken Hero Sword reqs mech bosses (#2879) --- worlds/terraria/Rules.dsv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/terraria/Rules.dsv b/worlds/terraria/Rules.dsv index b511db54de..43a21b4957 100644 --- a/worlds/terraria/Rules.dsv +++ b/worlds/terraria/Rules.dsv @@ -385,7 +385,7 @@ Armored Digger; Calamity | Location | Item; Temple Raider; Achievement; #Plantera; Lihzahrd Temple; ; #Plantera | (Plantera & Actuator) | @pickaxe(210) | (@calamity & Hardmode Anvil & Soul of Light & Soul of Night); Solar Eclipse; ; Lihzahrd Temple & Wall of Flesh; -Broken Hero Sword; ; (Solar Eclipse & Plantera) | (@calamity & #Calamitas Clone); +Broken Hero Sword; ; (Solar Eclipse & Plantera & @mech_boss(3)) | (@calamity & #Calamitas Clone); Terra Blade; ; Hardmode Anvil & True Night's Edge & True Excalibur & Broken Hero Sword & (~@calamity | Living Shard); Sword of the Hero; Achievement; Terra Blade; Kill the Sun; Achievement; Solar Eclipse; From bf60e905ec2a07a7291c05e7e927ee1c884e1b9b Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Tue, 5 Mar 2024 22:51:29 +0100 Subject: [PATCH 074/166] The Witness: Fix absolute world import (#2905) --- worlds/witness/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 18aa76d95a..3b3f3b2155 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -4,7 +4,7 @@ from schema import Schema, And, Optional from Options import Toggle, DefaultOnToggle, Range, Choice, PerGameCommonOptions, OptionDict -from worlds.witness.static_logic import WeightedItemDefinition, ItemCategory, StaticWitnessLogic +from .static_logic import WeightedItemDefinition, ItemCategory, StaticWitnessLogic class DisableNonRandomizedPuzzles(Toggle): From b147c5bf8a6a37d81fb3baca1b074f1512cdc15f Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Tue, 5 Mar 2024 22:53:00 +0100 Subject: [PATCH 075/166] The Witness: Bump required client version (#2897) Bump required client version from 0.4.4 to 0.4.5. The [client](https://github.com/NewSoupVi/The-Witness-Randomizer-for-Archipelago/releases/tag/v5.0.0p14) now connects with version 0.4.5. --- worlds/witness/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index e985dde353..c3f1192b92 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -56,7 +56,7 @@ class WitnessWorld(World): item_name_groups = StaticWitnessItems.item_groups location_name_groups = StaticWitnessLocations.AREA_LOCATION_GROUPS - required_client_version = (0, 4, 4) + required_client_version = (0, 4, 5) def __init__(self, multiworld: "MultiWorld", player: int): super().__init__(multiworld, player) From 4ddfb7ce8bc2f249b45e2ede375d0b6f3fe08a13 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Tue, 5 Mar 2024 22:54:02 +0100 Subject: [PATCH 076/166] The Witness: Laser Hints (#2895) --- worlds/witness/__init__.py | 46 ++++++++++++++++++++++---------------- worlds/witness/hints.py | 35 ++++++++++++++++++++++++++--- worlds/witness/options.py | 7 ++++++ worlds/witness/presets.py | 3 +++ 4 files changed, 69 insertions(+), 22 deletions(-) diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index c3f1192b92..bd877a16ef 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -3,22 +3,22 @@ Archipelago init file for The Witness """ import dataclasses -from typing import Dict, Optional +from typing import Dict, Optional, cast from BaseClasses import Region, Location, MultiWorld, Item, Entrance, Tutorial, CollectionState from Options import PerGameCommonOptions, Toggle from .presets import witness_option_presets from worlds.AutoWorld import World, WebWorld from .player_logic import WitnessPlayerLogic -from .static_logic import StaticWitnessLogic +from .static_logic import StaticWitnessLogic, ItemCategory, DoorItemDefinition from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \ get_priority_hint_items, make_always_and_priority_hints, generate_joke_hints, make_area_hints, get_hintable_areas, \ - make_extra_location_hints, create_all_hints + make_extra_location_hints, create_all_hints, make_laser_hints, make_compact_hint_data, CompactItemData from .locations import WitnessPlayerLocations, StaticWitnessLocations from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems, ItemData from .regions import WitnessRegions from .rules import set_rules from .options import TheWitnessOptions -from .utils import get_audio_logs +from .utils import get_audio_logs, get_laser_shuffle from logging import warning, error @@ -66,7 +66,8 @@ class WitnessWorld(World): self.items = None self.regio = None - self.log_ids_to_hints = None + self.log_ids_to_hints: Dict[int, CompactItemData] = dict() + self.laser_ids_to_hints: Dict[int, CompactItemData] = dict() self.items_placed_early = [] self.own_itempool = [] @@ -81,6 +82,7 @@ class WitnessWorld(World): 'symbols_not_in_the_game': self.items.get_symbol_ids_not_in_pool(), 'disabled_entities': [int(h, 16) for h in self.player_logic.COMPLETELY_DISABLED_ENTITIES], 'log_ids_to_hints': self.log_ids_to_hints, + 'laser_ids_to_hints': self.laser_ids_to_hints, 'progressive_item_lists': self.items.get_progressive_item_ids_in_pool(), 'obelisk_side_id_to_EPs': StaticWitnessLogic.OBELISK_SIDE_ID_TO_EP_HEXES, 'precompleted_puzzles': [int(h, 16) for h in self.player_logic.EXCLUDED_LOCATIONS], @@ -100,8 +102,6 @@ class WitnessWorld(World): ) self.regio: WitnessRegions = WitnessRegions(self.locat, self) - self.log_ids_to_hints = dict() - interacts_with_multiworld = ( self.options.shuffle_symbols or self.options.shuffle_doors or @@ -272,11 +272,25 @@ class WitnessWorld(World): self.options.local_items.value.add(item_name) def fill_slot_data(self) -> dict: + already_hinted_locations = set() + + # Laser hints + + if self.options.laser_hints: + laser_hints = make_laser_hints(self, StaticWitnessItems.item_groups["Lasers"]) + + for item_name, hint in laser_hints.items(): + item_def = cast(DoorItemDefinition, StaticWitnessLogic.all_items[item_name]) + self.laser_ids_to_hints[int(item_def.panel_id_hexes[0], 16)] = make_compact_hint_data(hint, self.player) + already_hinted_locations.add(hint.location) + + # Audio Log Hints + hint_amount = self.options.hint_amount.value credits_hint = ( "This Randomizer is brought to you by\n" - "NewSoupVi, Jarno, blastron,\n", + "NewSoupVi, Jarno, blastron,\n" "jbzdarkid, sigma144, IHNN, oddGarrett, Exempt-Medic.", -1, -1 ) @@ -285,25 +299,19 @@ class WitnessWorld(World): if hint_amount: area_hints = round(self.options.area_hint_percentage / 100 * hint_amount) - generated_hints = create_all_hints(self, hint_amount, area_hints) + generated_hints = create_all_hints(self, hint_amount, area_hints, already_hinted_locations) self.random.shuffle(audio_logs) duplicates = min(3, len(audio_logs) // hint_amount) for hint in generated_hints: - location = hint.location - area_amount = hint.area_amount - - # None if junk hint, address if location hint, area string if area hint - arg_1 = location.address if location else (hint.area if hint.area else None) - - # self.player if junk hint, player if location hint, progression amount if area hint - arg_2 = area_amount if area_amount is not None else (location.player if location else self.player) + hint = generated_hints.pop(0) + compact_hint_data = make_compact_hint_data(hint, self.player) for _ in range(0, duplicates): audio_log = audio_logs.pop() - self.log_ids_to_hints[int(audio_log, 16)] = (hint.wording, arg_1, arg_2) + self.log_ids_to_hints[int(audio_log, 16)] = compact_hint_data if audio_logs: audio_log = audio_logs.pop() @@ -315,7 +323,7 @@ class WitnessWorld(World): audio_log = audio_logs.pop() self.log_ids_to_hints[int(audio_log, 16)] = joke_hints.pop() - # generate hints done + # Options for the client & auto-tracker slot_data = self._get_slot_data() diff --git a/worlds/witness/hints.py b/worlds/witness/hints.py index 545aef2216..4b40ba32df 100644 --- a/worlds/witness/hints.py +++ b/worlds/witness/hints.py @@ -1,6 +1,6 @@ import logging from dataclasses import dataclass -from typing import Tuple, List, TYPE_CHECKING, Set, Dict, Optional +from typing import Tuple, List, TYPE_CHECKING, Set, Dict, Optional, Union from BaseClasses import Item, ItemClassification, Location, LocationProgressType, CollectionState from . import StaticWitnessLogic from .utils import weighted_sample @@ -8,6 +8,8 @@ from .utils import weighted_sample if TYPE_CHECKING: from . import WitnessWorld +CompactItemData = Tuple[str, Union[str, int], int] + joke_hints = [ "Quaternions break my brain", "Eclipse has nothing, but you should do it anyway.", @@ -634,14 +636,15 @@ def make_area_hints(world: "WitnessWorld", amount: int, already_hinted_locations return hints, unhinted_locations_per_area -def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int) -> List[WitnessWordedHint]: +def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int, + already_hinted_locations: Set[Location]) -> List[WitnessWordedHint]: generated_hints: List[WitnessWordedHint] = [] state = CollectionState(world.multiworld) # Keep track of already hinted locations. Consider early Tutorial as "already hinted" - already_hinted_locations = { + already_hinted_locations |= { loc for loc in world.multiworld.get_reachable_locations(state, world.player) if loc.address and StaticWitnessLogic.ENTITIES_BY_NAME[loc.name]["area"]["name"] == "Tutorial (Inside)" } @@ -721,3 +724,29 @@ def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int) - f"Generated {len(generated_hints)} instead.") return generated_hints + + +def make_compact_hint_data(hint: WitnessWordedHint, local_player_number: int) -> CompactItemData: + location = hint.location + area_amount = hint.area_amount + + # None if junk hint, address if location hint, area string if area hint + arg_1 = location.address if location else (hint.area if hint.area else None) + + # self.player if junk hint, player if location hint, progression amount if area hint + arg_2 = area_amount if area_amount is not None else (location.player if location else local_player_number) + + return hint.wording, arg_1, arg_2 + + +def make_laser_hints(world: "WitnessWorld", laser_names: List[str]) -> Dict[str, WitnessWordedHint]: + laser_hints_by_name = dict() + + for item_name in laser_names: + location_hint = hint_from_item(world, item_name, world.own_itempool) + if not location_hint: + continue + + laser_hints_by_name[item_name] = word_direct_hint(world, location_hint) + + return laser_hints_by_name diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 3b3f3b2155..a24896e1d0 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -225,6 +225,12 @@ class AreaHintPercentage(Range): default = 33 +class LaserHints(Toggle): + """If on, lasers will tell you where their items are if you walk close to them in-game. + Only applies if laser shuffle is enabled.""" + display_name = "Laser Hints" + + class DeathLink(Toggle): """If on: Whenever you fail a puzzle (with some exceptions), everyone who is also on Death Link dies. The effect of a "death" in The Witness is a Bonk Trap.""" @@ -264,5 +270,6 @@ class TheWitnessOptions(PerGameCommonOptions): puzzle_skip_amount: PuzzleSkipAmount hint_amount: HintAmount area_hint_percentage: AreaHintPercentage + laser_hints: LaserHints death_link: DeathLink death_link_amnesty: DeathLinkAmnesty diff --git a/worlds/witness/presets.py b/worlds/witness/presets.py index 3f02de550b..0f37fd50a3 100644 --- a/worlds/witness/presets.py +++ b/worlds/witness/presets.py @@ -33,6 +33,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "puzzle_skip_amount": PuzzleSkipAmount.default, "hint_amount": HintAmount.default, "area_hint_percentage": AreaHintPercentage.default, + "laser_hints": LaserHints.default, "death_link": DeathLink.default, }, @@ -66,6 +67,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "puzzle_skip_amount": 15, "hint_amount": HintAmount.default, "area_hint_percentage": AreaHintPercentage.default, + "laser_hints": LaserHints.default, "death_link": DeathLink.default, }, @@ -99,6 +101,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "puzzle_skip_amount": 15, "hint_amount": HintAmount.default, "area_hint_percentage": AreaHintPercentage.default, + "laser_hints": LaserHints.default, "death_link": DeathLink.default, }, } From 938beb34df65827998079cf4f8cc8121824317ff Mon Sep 17 00:00:00 2001 From: t3hf1gm3nt <59876300+t3hf1gm3nt@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:55:59 -0500 Subject: [PATCH 077/166] TLOZ: use proper rule for completion condition (#2872) Was pointed out that using `state.locations.checked` is not the best solution, even if it's for a completion condition and the Ganon event location would always have the Triforce of Power event item. So let's just check for the Triforce of Power instead. Thank you Zunawe for pointing it out and Silvris for providing the proper rule to use. --- worlds/tloz/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/tloz/__init__.py b/worlds/tloz/__init__.py index 27230654b8..b2f23ae2ca 100644 --- a/worlds/tloz/__init__.py +++ b/worlds/tloz/__init__.py @@ -180,7 +180,7 @@ class TLoZWorld(World): self.multiworld.get_location("Zelda", self.player).place_locked_item(self.create_event("Rescued Zelda!")) add_rule(self.multiworld.get_location("Zelda", self.player), - lambda state: ganon in state.locations_checked) + lambda state: state.has("Triforce of Power", self.player)) self.multiworld.completion_condition[self.player] = lambda state: state.has("Rescued Zelda!", self.player) def apply_base_patch(self, rom): From bfa9e7da006272065143ff501ae0a2bdb8501a48 Mon Sep 17 00:00:00 2001 From: Zach Parks Date: Tue, 5 Mar 2024 15:59:34 -0600 Subject: [PATCH 078/166] Generate: Trim slot names again after 16 character limitation slice. (#2906) --- Generate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Generate.py b/Generate.py index 725a7e9fec..ecdc81833a 100644 --- a/Generate.py +++ b/Generate.py @@ -302,7 +302,9 @@ def handle_name(name: str, player: int, name_counter: Counter): NUMBER=(number if number > 1 else ''), player=player, PLAYER=(player if player > 1 else ''))) - new_name = new_name.strip()[:16] + # Run .strip twice for edge case where after the initial .slice new_name has a leading whitespace. + # Could cause issues for some clients that cannot handle the additional whitespace. + new_name = new_name.strip()[:16].strip() if new_name == "Archipelago": raise Exception(f"You cannot name yourself \"{new_name}\"") return new_name From a5a1494a969fc032d4a167f36190c28f939a2d22 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Tue, 5 Mar 2024 17:01:45 -0500 Subject: [PATCH 079/166] Pokemon R/B: The Big Door Shuffle Update (#2861) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Perhaps most critically, adds the ability for the door shuffle code to catch door shuffle exceptions and try again. Will try up to 10 times. Should mean Door Shuffle does not need to be disallowed in the big async🤞 - Door Shuffle code has been made drastically faster by searching for the first dead end instead of sorting the whole list of entrances by whether they are dead ends. - Renames Full to Interiors, and adds a new Full door shuffle that shuffles interior-to-interior doors separately from exterior-to-interior doors. - Adds a new Decoupled door shuffle. - Warp Tile Shuffle now has 3 separate options, Vanilla, Shuffle, and Mixed. Shuffle shuffles the warp tiles among themselves, Mixed mixes them into the Door Shuffle pool. - Safari Zone connections are now shuffled on Full, Insanity, and Decoupled. - On Simple Door Shuffle, the Town Map is updated to show the new dungeon locations. The Town Map has been updated to show the locations of dungeons that previously were not shown unless you opened the map within them, and the Sea Cottage has been removed from it. - Adds Auto Level Scaling that chooses the level scaling mode based on the Door Shuffle choice. - Fixes issues with Flash and Fly move interventions (where it ensures an available Pokémon that can learn it is reachable depending on settings). - Fixes a possible generation crash with type chart randomization. - Should fix an issue where `stage_fill_hook` was able to remove the wrong item from the item pool resulting in a duplicated item reference existing. - Adds a stage_post_fill function which searches for Pokémon in order of spheres, setting all but the first advancement Pokémon event found to `useful` so that spoiler playthrough calculation skips them. In a solo game gen test, this cut gen time from 15 seconds to 10 seconds with same seed number. Difference is likely to be much more massive in larger multiworlds. --- worlds/pokemon_rb/__init__.py | 66 +-- worlds/pokemon_rb/basepatch_blue.bsdiff4 | Bin 45872 -> 46356 bytes worlds/pokemon_rb/basepatch_red.bsdiff4 | Bin 45839 -> 46344 bytes .../docs/en_Pokemon Red and Blue.md | 2 + worlds/pokemon_rb/level_scaling.py | 25 +- worlds/pokemon_rb/locations.py | 20 +- worlds/pokemon_rb/options.py | 64 +-- worlds/pokemon_rb/regions.py | 505 ++++++++++++------ worlds/pokemon_rb/rom.py | 25 +- worlds/pokemon_rb/rom_addresses.py | 330 ++++++------ 10 files changed, 621 insertions(+), 416 deletions(-) diff --git a/worlds/pokemon_rb/__init__.py b/worlds/pokemon_rb/__init__.py index 56502f5029..beb2010b58 100644 --- a/worlds/pokemon_rb/__init__.py +++ b/worlds/pokemon_rb/__init__.py @@ -195,11 +195,11 @@ class PokemonRedBlueWorld(World): normals -= subtract_amounts[2] while super_effectives + not_very_effectives + normals > 225 - immunities: r = self.multiworld.random.randint(0, 2) - if r == 0: + if r == 0 and super_effectives: super_effectives -= 1 - elif r == 1: + elif r == 1 and not_very_effectives: not_very_effectives -= 1 - else: + elif normals: normals -= 1 chart = [] for matchup_list, matchup_value in zip([immunities, normals, super_effectives, not_very_effectives], @@ -249,14 +249,18 @@ class PokemonRedBlueWorld(World): itempool = progitempool + usefulitempool + filleritempool multiworld.random.shuffle(itempool) unplaced_items = [] - for item in itempool: + for i, item in enumerate(itempool): if item.player == loc.player and loc.can_fill(multiworld.state, item, False): - if item in progitempool: - progitempool.remove(item) - elif item in usefulitempool: - usefulitempool.remove(item) - elif item in filleritempool: - filleritempool.remove(item) + if item.advancement: + pool = progitempool + elif item.useful: + pool = usefulitempool + else: + pool = filleritempool + for i, check_item in enumerate(pool): + if item is check_item: + pool.pop(i) + break if item.advancement: state = sweep_from_pool(multiworld.state, progitempool + unplaced_items) if (not item.advancement) or state.can_reach(loc, "Location", loc.player): @@ -416,16 +420,16 @@ class PokemonRedBlueWorld(World): self.multiworld.victory_road_condition[self.player]) > 7) or (self.multiworld.door_shuffle[self.player] not in ("off", "simple")))): intervene_move = "Cut" - elif ((not logic.can_learn_hm(test_state, "Flash", self.player)) and self.multiworld.dark_rock_tunnel_logic[self.player] - and (((self.multiworld.accessibility[self.player] != "minimal" or - (self.multiworld.trainersanity[self.player] or self.multiworld.extra_key_items[self.player])) or - self.multiworld.door_shuffle[self.player]))): + elif ((not logic.can_learn_hm(test_state, "Flash", self.player)) + and self.multiworld.dark_rock_tunnel_logic[self.player] + and (self.multiworld.accessibility[self.player] != "minimal" + or self.multiworld.door_shuffle[self.player])): intervene_move = "Flash" # If no Pokémon can learn Fly, then during door shuffle it would simply not treat the free fly maps # as reachable, and if on no door shuffle or simple, fly is simply never necessary. # We only intervene if a Pokémon is able to learn fly but none are reachable, as that would have been # considered in door shuffle. - elif ((not logic.can_learn_hm(test_state, "Fly", self.player)) and logic.can_learn_hm(test_state, "Fly", self.player) + elif ((not logic.can_learn_hm(test_state, "Fly", self.player)) and self.multiworld.door_shuffle[self.player] not in ("off", "simple") and [self.fly_map, self.town_map_fly_map] != ["Pallet Town", "Pallet Town"]): intervene_move = "Fly" @@ -554,23 +558,21 @@ class PokemonRedBlueWorld(World): else: raise Exception("Failed to remove corresponding item while deleting unreachable Dexsanity location") - - if self.multiworld.door_shuffle[self.player] == "decoupled": - swept_state = self.multiworld.state.copy() - swept_state.sweep_for_events(player=self.player) - locations = [location for location in - self.multiworld.get_reachable_locations(swept_state, self.player) if location.item is - None] - self.multiworld.random.shuffle(locations) - while len(locations) > 10: - location = locations.pop() - location.progress_type = LocationProgressType.EXCLUDED - - if self.multiworld.key_items_only[self.player]: - locations = [location for location in self.multiworld.get_unfilled_locations(self.player) if - location.progress_type == LocationProgressType.DEFAULT] - for location in locations: - location.progress_type = LocationProgressType.PRIORITY + @classmethod + def stage_post_fill(cls, multiworld): + # Convert all but one of each instance of a wild Pokemon to useful classification. + # This cuts down on time spent calculating the spoiler playthrough. + found_mons = set() + for sphere in multiworld.get_spheres(): + for location in sphere: + if (location.game == "Pokemon Red and Blue" and (location.item.name in poke_data.pokemon_data.keys() + or "Static " in location.item.name) + and location.item.advancement): + key = (location.player, location.item.name) + if key in found_mons: + location.item.classification = ItemClassification.useful + else: + found_mons.add(key) def create_regions(self): if (self.multiworld.old_man[self.player] == "vanilla" or diff --git a/worlds/pokemon_rb/basepatch_blue.bsdiff4 b/worlds/pokemon_rb/basepatch_blue.bsdiff4 index 5ccf4e9bbaf80f0167471e02dd64384b5bb0e622..0f65564a737be4e77c18cf94336ac9ba859345a9 100644 GIT binary patch literal 46356 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

%ff+)8u7 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 z zI0<{*>zFM!t(l1`B$QxJYCTX$f;r!lCo-Ek#A+j%!((=S0FX;O5vUFj z0l)x7)hrfdM*6`UIhNsi^Wt~6-i|Bp+`bBC|3k^=#uoVSMT)}daiiN~yv^i$Xeet6 zS=;2$XdI~tN>M%kez#a(5!srtk44-TygzEiKBWcO*r**rFjTX?Te)q}>lj)kSH-Ll13ZGrCl{5(+E-K}PBf_B)KQN#c#W$SWY;W-lxhS6&N zDR+coI9Mio+ucW%CYO`I6I;}rOFHF1n4prJ7%&aZ-ASX$dJh|1rXp3`1C9>HeyF9M z6Dg{6UI;M8vD3OZYs9ouwrB7qo8mBJ{aI+w8VroC;!~Xm%VwBDI{=VVjUj!W9rfep|TnkR6o>&zK{YC3Gm^|j%U)^ zR}h~-^g!q*;<6_)6#Eqa_h;$t=6*(B$cxCuCkAfiVT$-m*UFmv?vgZ~;%c+?B0U}I zhX*;gr^E-EJ{YdpM^{BIhgELjqBT5Q^^}gRG&NxEM$?2c^i)_wjXwyS)L0_w(d4HV z5mdvAu4LGe!{|*LawT~25k$nAAk#zLOEf#g#l^VK#l}LjkyK%t_{NT866sK(BcGPu z>*^~Db43bjH35fuB|!il4E?3|yLDC~0nA0Y`N&T7sk!!z>=bAo=y=6yibrEky*s)i zrJN53&#{0w8jt@b7D;iN%1E0MFW0rw=fS{UD0IzSZFolZrd*3Ku_JZ)2eur{O5_2=sP?+D|?rn_V zel{*naJ?fql{{Z{1c_#f3JM`gW2$&|Bju)}NZ}{}GTdA+IaLNiNXcla9YM=LaX6@g z2AC;zwUgdP*MBv{qdQXcq`T}Efn5!i{r2c5A=bR?B3wNY_aRvP4Z z?I&jScBB$v)Fp@U(}zgDkRA%dhY@-lKtl-WDK&d-1<;N3tBuW+L~$#EZg*~++doIM zMHuFy*aC3g&?)hKh7$PA`QP^D?wHQEBbj*J+yFWo!b`s^=Wc%jnRw|v*)S-2Wd87U zy|z2>s5gIDbc_oF>{I`_A~FdXzEcU1A2{84Ni}@1-3NIBrfFUdTi3o=a+f1#)7of zG5sGKDC6*3G!Y$c?2r0|&5bhwS%!rP5Cy*5mDtkk1NS$U`6M|MVFFP_MOjVq4-puA zPn~lLeuK{Mk-cvxrPAVUq6Otyioff|CzMmS_6J~_iDEQ1a!78f25%0dNWDXKE8Gc= z%6rW;zC<;oeM)$Ai`)LqmL|v=Za8(cH1u8;Pl*xb)KfeQ%>e!>{85r#`T;1=I1ZT> z|944&mwvLZ*TSJg(J@SLxygkrrvc>KHr2 zANe*by0-VISr5a?K*R)L*ZwU1ytZw^yo=}S;Pw8oSxjim-Rt&*kB;-1Ph(G;n3J{e zG1c)^rqyd%+1NQrm-p7rujxl?jxre?M{Wv~wMU^}(ofP>H-z|@mzw@Mjt(N8+xOG7 z$%i@bDd}Qg%Qr8FwyS&okFi{IJlXhD;4$G(=Cm}=eTO-Gbmov&iy?12(r<#N^}D0X z|K2}amHBx0e0sh2r6j(Mdvu*gEGKv3pZh(rOHrXzCk$q`32C!>EY+_XU7d}IrS&_? zNa5SfY{}a{&jnl>`qVyD^-nK}TC+3$nn33qWxjPA8R5BXK8SDcCoc{p-u<3lekshM zQ_iR1E(I%OgNF%CSZ7~bKHxWwB<$a6Sxe3b*L&nIi!^f{Q@JmPa}m=G(4{O7NJ zy)UtW8`AHvPzwI0XWq=tlXQrs0wC=Q00MD|C<#JILO^)H86yA!0KgM?Jo3jo2p1bJ znrZ$^B_6joCNsX74O8Wox3h15gAFLp997I*e(5g<>a`mYCGH3_dLrpiaS~h#dvmtf zIYv(%|2&k;ioqe8$s#V}6{z4DSD8F(I=WVZ@@=QcIN|;|J_5VSXZQ!&tq)6Rp+!Ki z7Z30W^AF;R3~kvszU83K_Z!?|IgC7Du%|Od+HkZK%B=iURi-SDXSSr#9}_UOePi+m z(w=sA`hD>tSUmH6mh;NZXG-bySx#SKR+k%`1?@w@XTE%mu6VDMjy&DO?Nvb6sBR~d zK76ZxyUcmoDfj_`*L4u68_Y96{j;m6Q!m%e z@oA~2$DWej`ewh^z##xh3;`FXX`P;AAt*j%-r#+HS@ACuPlLY9z3qK{srwdaC{C}9 z4BS#MAr?yt4jk1Ip?`ME3c>9S9bEcR)iZr;Ep6okCPhuj|K~S_`+EI-bbVhq2Op=e zU%539(zMf}e6@^U*XwQt;RgTbl(X}3KKaAzczqFC9E>zGzOw7gZIYy8byVuQ0VumG z$mGYvl6~%HU&fST77th(TAJ5--p7Kb3{Ax2!w62is|y3Tj;`9_tSn}!NGh_f~EHwNAJ=z_^s|wF=Ak0B9K{A)pvQ< zE;h^gV|=tf?R;Y@8F>Ywmb;2-*c3FzIpDza{vx*%(I@KC0PE=A-~&vou>eF zKhI;Z`aTJ22Qj3kpA5mDizle#2NF98tF)ka=;o}?fe6;`Y-~hebaup`+5FN#LX!pG zmD3Rgu27C+jFNBs6@P~wQ96SsiHFIr#l1J-20`g_@2go`>-H%Cdr6k~A#`pd5t2Or zmWJ%j8jFWaPWJIEK%^NtIRy6ic5H5Nl{$RvVhBqveUn*cyf+*~j}Ax1fdVi7NNqvf zOuxI)M1Fp%HF|o#m{J97V?Wk0n#g@AcGpI{dCW7=r}Np2Fc9*)7U7S5pJciD<@%AS zad!6=ulerq+g!QSmCVh<+BzvF%k8G{%KZXzx|y3TIm%zdyN1u9_NM)IwLcpe#aAzpeM9^8 z0qtv(FTjTo4iOOsQ@^)`LgV$-HhmS2H%WK*HycB1^!tx?9HV>Wm>fZ{Ys?XQe0nI zQ||2694qOz{4dPWb>xPfO`^VuXzMomrg!;u zEG6@HskZ%R+eTNrjSiZXk?Ou8n#b;f(*YOXq4qU>VPu=Igam*C# z8ySPGZl2{{>oZKe`eP4M!7oVJOi`|>B~NK{(v-uws4_|LS6EBvax;2&F*(9}m`$4~ z0CY3}!P99rhi5w@G|kmB@*OL-Bcm|n#rrF>D%!h_8Gk2pQ_SN~VERV6>SWRII32dY z-2fa>mMe)@b@?l0`s*nV>)osnd}7oww(Ryyb9orAav5*1vP~J57?a7!9*^I!S*+_r z2m+R#rn0x^+IA()lxIaJ=_~3_amH~+iu9Q_Vp8Y1ELpQJke8~kvieih`eN}_7uW{? zgU4Tp&3SYCUVNmn5TFDr%Q;fdoS!1tb3EH)A8*A*Z12~H$(b_4{^KR%Xxm*6{?*2L z8=FvT4jBo~Z;mY)99Hl#*w~OT2uMJP>lB0qt`raAebidm5!E>41V2`>gJnm z=lEmB^xV_mgR9y}BVxkMIQJc*-m_8FQr(wCy{GTnE_Xh%{AqRy)Aoweouq1leVFyR z2dA5j#&`hnzv%!H551wf=8xRy45Ah2mifkKsT|pf&6aP(Sw;~qj3HKVM+KfT;9Q!z z>m74`LQ1~$q0iBCTfOf1z=#x91u21cG)^HN7y|r>em!P{>Z-_jZ2SwhmA}nF2m}2U ztu)wWvzw7MQv8en0FWIk?qGy##7cA@%YXTBfrMZX!vg^9)1KUtz3jG+m)O_zIuw^L zj`M%S4=#_FkA8AM=nrA4l(P9fda`$i?l)sQ$oXxF{o9yJQ>OBV8``-w#}+zq#8J)uNxhvKO>i$ z6$`I34woS}zuJ2rsL}?bPr2xhfGQH^`n<^9#32ANKp{Sy@`2d<(nEUOsH9{Z4NaSL zun$|~SDuITw>elkDXtew8)^cL3=%UomeI7jBc zLsg)G{^>Z~w`Z-V?e{#_oDP-36p@DOp1v5t1BjSUN1Uljmad)G<+q`$2%%OQmT zi~#6Y`9l0aKX;@&hvbirN4B1B3vP-$HbavPS8Ibx1&@*l8I86%{#wWeSJ>xTWe8yk zy&M)sYF8X&1l(BF!ONZC94p#I6y)I+1{(``R%3tE-?h8K+yZL~P9F6Mx$rJM-`QZ+r-5f%B!tH-MOM?u(h1P6e0LfhU=#@O>>`TbX+ zx{?1^^7A95u?dKFgjKc0+BG~<1=$xS+>S9pK+PjK`8QG-U*J?=Bw4XXTa>ck+nTKn zFcHCuyO&fFCffg%I@Dlbrv@uyzl&B>gx+4KB>xaX!!IvAUd?Ob>Smfyg7yD7S+370 z05WF|=<&ng+DC6_@%2whhlB+NA)HYp)AImh@%$z`drkF1 z*;`=cYRxpr24o1IU2hl+QJY1jm1x}-r;PB46|8@_3XhfIBc)mxMNck{z zaJg=YO)C=JGWyAjFE(Bs`t`~2hDjdQCtT~#xyQ^C6Ta$0mjy;XL=PztmFw52$G@; zAL(LAb`vfo7@^OWEtGMjR>HzlO#8ditHKpXL;4k80A34dt^*ETAMW4U{3QHSrI$s+ zQ$_RDjp+J~os(tZ!`{|LT*R;=Wf2J=JdDgzlvZxQ5 zob`)r7oJMj912$RqJ|!6YH|B4!rsm~P}!5%sX} z`L$5I!n=aBv%5VcrOC5LE@C|WukX&v;?EZ4puFoxTG$~D{6DN+CWj?#u`q3Zm0~p7 zI}H>Ngf;Q{~hmkDq1_v%0!c0KDv{Q^fqU)-~z)QzkrGHn$|Y zE?o?$=s20pYY!kjWG}}m$}uQKnWikX%Noo}F^&!1Ej(PyF<|_=+o?j**1^Kq=rW2o z)KD87*#mi|!KRXh?<>?bt$u1Tv9M^lM!zK+!%nRbP-0TTBD$kk8dzQy(UK18Ld>2g zl0gx~5U(droo=!Pqdwj4pTc}9V^qe*#iLr%a^s&Rqe?)yNSuE7 z8q+T8L6NPX7OY_`vg|FxVNQZ3#&XwV0LJAT+iD=q%ZYn?ZALxbb%dBMmg`6x=7WjxeVabp)f>?EF35Mx$ElJb-enp^9)PM82U;@NYz$i6@rNp=y#O z;!h?!9=81Meb&r)kAhU7XT%Hezd$Gkqtf^shIs|3i8G491V2sGS5>jRVdS8?mC}xq zH3Vga<1`5|gi_UL()A1=Y^N;ds4rQ>Tg%l}s*!4i$xLkJu%6`0 zats25CKeG1XBVDwT{$noxpj{iDDsjZ$;HmogvQ?36c!2OnN{Z;)+KR;uydlNsbCqjo)V!rcK^W?noOu&2A(2rU(QzSAZpaf(Jv1f+Xo`{p5uO5-`NYm~ zXETt1L1KdTrQUU`1p*fMi|1F?bCOaeV6V1R5y+=}6#|aC(Dxho<1%ieP&Bm$mm?#t zh3;Wv^|;W4&L+YUbt{DqC7xr6k4T#>f^G94oE~Ku>&S3wRtMM3fc(4 z#n)2y^UaF=N+2>CD0#EBJCv&#+9=-z1d{U(g~`aoN#kReV@&=v3pik5g+Xwb%b?VtQ zoj|sRbFx%VJCVBW_gc-fO9pJNlu=H$(?masL$Bw?bF}w7Hotq`s8~Z>3nT$Vxm_{F zhSF#6Z+m&X82v z9693|(UlRL*@})v78)d`;Z(3F*(u(FP6GpSi->RyGa6_g0wvNh-gC&skL9up#fgsj zCI=Qwyl-d8`S}iNBazzWMz0{mV!4NxdV&D;N-fwWq*RlC3Rgpj2cR}(dAo}J-CLdF zVQ0?CQme~9c_X&ExP4YERodIU*|)Hlw=3$gVFe<0A`WAE>=!w@f5uiBO>|B52kR?g z_rHSjEJV;^pjHUTNW#>$jw7LQ3P9t~r9q|DYI;+XAq6IpxdQmV2{H=$pXr}OODLo{ z5Nh891Oh;cCL(<4njZ+@aK=Mel-hZSC1@jVVO)SQY)-B@tkLcypdmZBom4a9?c%K^ z0Jt<5BTLyZN+~~N_$3`6pj8Ofv#8jNdVA~%1$UI2co#ffDx&$btYq54BM1xj=;86? zfUXeGf-u5?Fn7n>Wk@6rj%sSN=jNl6Lw(9e(~oUd90C&zZ=|HiPFP~%M_}uCKUPce zh>2Lzs-QKDz7y58=MCW_d=dwQH7TGiXKt%kY=AUL*F-czJL0A@ZeLuN0N20I5lgN3 zGnvQ{M=E0MOf##Oc7m3{mYdEru49crMw9QUhbRu_okFuefFN7zGEPuLPLrMA+oSp9=GODcT^alBg zam}6rvB24KwyFaf$s=T|I9U~l1|bD+QdHsEU~PRZZq~N{hiT(^x`RMXbeBATdOSk%C~eIUnqk-F)XaG)2#al(RQ^B&wodKiqG zoSSy|PAICSDTg2FTJNy_?8Wo5{O5^5WOurX|H(f33NXiNW_e?3iYK7vNTDiJjeQPT z?Y19@J(w`{-?c0dYN5>0b(2J=o6LJ8!gz_u>Ez*!k0#)SZ z-RhMct*}?SHxF{Trh^ITsB#rdMa`}gpPVV7r+wI|`frurZ{RGsp|*9WP0!F`oUdN{;~x}^lgl(F+e&%&^iG@AE5@8xhvcsUZs{T?Xaa< zDK6#coA2)Pdl1@ctM$77oy?tog@jO2l(GfvwrtIk#@7tc6tmYNiy^$8DPlA)u%S{G z+K+#zDD_?SWIFbQ(%rjqYnX#L00Y1r>zkM*!c3f^karC(qDD4lB-I= zRhHB>AHVp}3*e`8UA=CZ>?-YbOuUOjAXzwDZWVfCJ~@CfN@S=E6`-KyZ|=R@$Ydeh zR?o#^6aNzOKY{P_z*0ywhd9F;I->`RwwT4;+mI6yZB(?xe-LFPmJO#**&HBX|GtB3 z$OCYAiH#a2J^ycdEO274h>{5J8>VyJW_W<%+XF+`ulA_ zYB}JcG2&Nn`3>%mN5A&u)wFdGB(Tg6%cyteVd!kCRI=ipkK zAV_a`we;=R+-B;t)Q}34v8gPsn%@^ymh<_rLxiRGVGUbcE;z z!kn$AFW_HrMGkjuPTNKlE_ZWMrhPh&&X=yp;83&$kAh6T(MD4ZEnEF0EE>m$tx&@8 zR|%b*(+mKsQa;%lxW{Tdsr|0tOm9|^NO-`-+46){!Rq+!QhuTUA5G8a>NQ%E^=thW z5NnT`-rb~So|kjclz^7EvONNsW8O53iDi5Zg2-3}OOudMIvTUuEl%!E2_%P!X?C~+ zvn(Q;CzqL8bo7ZR>Q;;$Hovga>f21E5yyy-p)EK7k{W%K;dJUT?OL?O@8&&St!Km9 z0cexD1F47&p-hH~KJH;~gd)EYy$UN`q`>+iHbYZ)rQ>b5yjH!|@AjvDJG0lVvtEt@ zAK@y4f$Uv+onX^$zrbO~XonXxW;2o-!@*`aLnH z`e-%t?Jv0X*L1=`qdpFhF~3k(r5gc4X+1uir(WEVB6LUqCQ@aM+61031@=>z`q7}9 z7bK8^p2B5mIS5MIE%uwZ=_DWPA&EXG&2CUWs2Y7K|9K=#LFbaP-Tih2`}jXe12)Yo z70+lP-tzMM?vFpm%$I0jet2fi7mnU<_x46B?H2X>=nzFZfTSr&YOEBI34!RI4#LxM z`Dg?OaUy?aS~_}j9uzGZ>dQDLn;@ z?USiS#8lwewJ3rC1QIqeKpejqDcy3c#gWP0Z#Qut_&t;`kXE`t{N!;sPVwwCf`P6o zRK7xNzHzB@$jH#()_%j%I?3h(ymT{nzs5DUdMQF~X#W$Re

^yJTP6oc_cjvKX=i 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 literal 45872 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 AWB>pF diff --git a/worlds/pokemon_rb/basepatch_red.bsdiff4 b/worlds/pokemon_rb/basepatch_red.bsdiff4 index 26d2eb0c2869e020058689cee8e18abbca5e3c04..826b7bf8b4e5ff0dd0926ca876c9930eebe4eefc 100644 GIT binary patch literal 46344 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 DjU7nf literal 45839 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 O;_gVN3K9a`DoQ|XZiR;c diff --git a/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md b/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md index b164d4b0fe..dc55aca0f0 100644 --- a/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md +++ b/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md @@ -41,6 +41,8 @@ and repeatable source of money. * You can disable and re-enable experience gains by talking to an aide in Oak's Lab. * You can reset static encounters (Poké Flute encounter, legendaries, and the trap Poké Ball battles in Power Plant) for any Pokémon you have defeated but not caught, by talking to an aide in Oak's Lab. +* Dungeons normally hidden on the Town Map are now present, and the "Sea Cottage" has been removed. This is to allow +Simple Door Shuffle to update the locations of all of the dungeons on the Town Map. ## What items and locations get shuffled? diff --git a/worlds/pokemon_rb/level_scaling.py b/worlds/pokemon_rb/level_scaling.py index 5f3dfc1acd..79cda39472 100644 --- a/worlds/pokemon_rb/level_scaling.py +++ b/worlds/pokemon_rb/level_scaling.py @@ -10,7 +10,9 @@ def level_scaling(multiworld): while locations: sphere = set() for world in multiworld.get_game_worlds("Pokemon Red and Blue"): - if multiworld.level_scaling[world.player] != "by_spheres_and_distance": + if (multiworld.level_scaling[world.player] != "by_spheres_and_distance" + and (multiworld.level_scaling[world.player] != "auto" or multiworld.door_shuffle[world.player] + in ("off", "simple"))): continue regions = {multiworld.get_region("Menu", world.player)} checked_regions = set() @@ -45,18 +47,18 @@ def level_scaling(multiworld): return True if (("Rock Tunnel 1F - Wild Pokemon" in location.name and any([multiworld.get_entrance(e, location.player).connected_region.can_reach(state) - for e in ['Rock Tunnel 1F-NE to Route 10-N', - 'Rock Tunnel 1F-NE to Rock Tunnel B1F-E', - 'Rock Tunnel 1F-NW to Rock Tunnel B1F-E', - 'Rock Tunnel 1F-NW to Rock Tunnel B1F-W', - 'Rock Tunnel 1F-S to Route 10-S', - 'Rock Tunnel 1F-S to Rock Tunnel B1F-W']])) or + for e in ['Rock Tunnel 1F-NE 1 to Route 10-N', + 'Rock Tunnel 1F-NE 2 to Rock Tunnel B1F-E 1', + 'Rock Tunnel 1F-NW 1 to Rock Tunnel B1F-E 2', + 'Rock Tunnel 1F-NW 2 to Rock Tunnel B1F-W 1', + 'Rock Tunnel 1F-S 1 to Route 10-S', + 'Rock Tunnel 1F-S 2 to Rock Tunnel B1F-W 2']])) or ("Rock Tunnel B1F - Wild Pokemon" in location.name and any([multiworld.get_entrance(e, location.player).connected_region.can_reach(state) - for e in ['Rock Tunnel B1F-E to Rock Tunnel 1F-NE', - 'Rock Tunnel B1F-E to Rock Tunnel 1F-NW', - 'Rock Tunnel B1F-W to Rock Tunnel 1F-NW', - 'Rock Tunnel B1F-W to Rock Tunnel 1F-S']]))): + for e in ['Rock Tunnel B1F-E 1 to Rock Tunnel 1F-NE 2', + 'Rock Tunnel B1F-E 2 to Rock Tunnel 1F-NW 1', + 'Rock Tunnel B1F-W 1 to Rock Tunnel 1F-NW 2', + 'Rock Tunnel B1F-W 2 to Rock Tunnel 1F-S 2']]))): # Even if checks in Rock Tunnel are out of logic due to lack of Flash, it is very easy to # wander in the dark and encounter wild Pokémon, even unintentionally while attempting to # leave the way you entered. We'll count the wild Pokémon as reachable as soon as the Rock @@ -135,4 +137,3 @@ def level_scaling(multiworld): sphere_objects[object].level = level_list_copy.pop(0) for world in multiworld.get_game_worlds("Pokemon Red and Blue"): world.finished_level_scaling.set() - diff --git a/worlds/pokemon_rb/locations.py b/worlds/pokemon_rb/locations.py index 3fff3b88c1..abaa58fcf9 100644 --- a/worlds/pokemon_rb/locations.py +++ b/worlds/pokemon_rb/locations.py @@ -1036,25 +1036,25 @@ location_data = [ type="Wild Encounter", level=12), LocationData("Mt Moon B2F-Wild", "Wild Pokemon - 10", "Clefairy", rom_addresses["Wild_MtMoonB2F"] + 19, None, event=True, type="Wild Encounter", level=12), - LocationData("Route 4-Grass", "Wild Pokemon - 1", "Rattata", rom_addresses["Wild_Route4"] + 1, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 1", "Rattata", rom_addresses["Wild_Route4"] + 1, None, event=True, type="Wild Encounter", level=10), - LocationData("Route 4-Grass", "Wild Pokemon - 2", "Spearow", rom_addresses["Wild_Route4"] + 3, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 2", "Spearow", rom_addresses["Wild_Route4"] + 3, None, event=True, type="Wild Encounter", level=10), - LocationData("Route 4-Grass", "Wild Pokemon - 3", "Rattata", rom_addresses["Wild_Route4"] + 5, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 3", "Rattata", rom_addresses["Wild_Route4"] + 5, None, event=True, type="Wild Encounter", level=8), - LocationData("Route 4-Grass", "Wild Pokemon - 4", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 7, None, + LocationData("Route 4-E", "Wild Pokemon - 4", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 7, None, event=True, type="Wild Encounter", level=6), - LocationData("Route 4-Grass", "Wild Pokemon - 5", "Spearow", rom_addresses["Wild_Route4"] + 9, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 5", "Spearow", rom_addresses["Wild_Route4"] + 9, None, event=True, type="Wild Encounter", level=8), - LocationData("Route 4-Grass", "Wild Pokemon - 6", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 11, None, + LocationData("Route 4-E", "Wild Pokemon - 6", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 11, None, event=True, type="Wild Encounter", level=10), - LocationData("Route 4-Grass", "Wild Pokemon - 7", "Rattata", rom_addresses["Wild_Route4"] + 13, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 7", "Rattata", rom_addresses["Wild_Route4"] + 13, None, event=True, type="Wild Encounter", level=12), - LocationData("Route 4-Grass", "Wild Pokemon - 8", "Spearow", rom_addresses["Wild_Route4"] + 15, None, event=True, + LocationData("Route 4-E", "Wild Pokemon - 8", "Spearow", rom_addresses["Wild_Route4"] + 15, None, event=True, type="Wild Encounter", level=12), - LocationData("Route 4-Grass", "Wild Pokemon - 9", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 17, None, + LocationData("Route 4-E", "Wild Pokemon - 9", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 17, None, event=True, type="Wild Encounter", level=8), - LocationData("Route 4-Grass", "Wild Pokemon - 10", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 19, None, + LocationData("Route 4-E", "Wild Pokemon - 10", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 19, None, event=True, type="Wild Encounter", level=12), LocationData("Route 24", "Wild Pokemon - 1", ["Weedle", "Caterpie"], rom_addresses["Wild_Route24"] + 1, None, event=True, type="Wild Encounter", level=7), diff --git a/worlds/pokemon_rb/options.py b/worlds/pokemon_rb/options.py index 8afe91b867..bd6515913a 100644 --- a/worlds/pokemon_rb/options.py +++ b/worlds/pokemon_rb/options.py @@ -228,7 +228,7 @@ class SplitCardKey(Choice): class AllElevatorsLocked(Toggle): """Adds requirements to the Celadon Department Store elevator and Silph Co elevators to have the Lift Key. - No logical implications normally, but may have a significant impact on Insanity Door Shuffle.""" + No logical implications normally, but may have a significant impact on some Door Shuffle options.""" display_name = "All Elevators Locked" default = 1 @@ -317,42 +317,42 @@ class TownMapFlyLocation(Toggle): class DoorShuffle(Choice): """Simple: entrances are randomized together in groups: Pokemarts, Gyms, single exit dungeons, dual exit dungeons, single exit misc interiors, dual exit misc interiors are all shuffled separately. Safari Zone is not shuffled. - Full: Any outdoor entrance may lead to any interior. - Insanity: All rooms in the game are shuffled.""" + On Simple only, the Town Map will be updated to show the new locations for each dungeon. + Interiors: Any outdoor entrance may lead to any interior, but intra-interior doors are not shuffled. Previously + named Full. + Full: Exterior to interior entrances are shuffled, and interior to interior doors are shuffled, separately. + Insanity: All doors in the game are shuffled. + Decoupled: Doors may be decoupled from each other, so that leaving through an exit may not return you to the + door you entered from.""" display_name = "Door Shuffle" option_off = 0 option_simple = 1 - option_full = 2 - option_insanity = 3 - # Disabled for now, has issues with elevators that need to be resolved - # option_decoupled = 4 + option_interiors = 2 + option_full = 3 + option_insanity = 4 + option_decoupled = 5 default = 0 - # remove assertions that blow up checks for decoupled - def __eq__(self, other): - if isinstance(other, self.__class__): - return other.value == self.value - elif isinstance(other, str): - return other == self.current_key - elif isinstance(other, int): - return other == self.value - elif isinstance(other, bool): - return other == bool(self.value) - else: - raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}") - -class WarpTileShuffle(Toggle): - """Shuffle the warp tiles in Silph Co and Sabrina's Gym among themselves, separately. - On Insanity, turning this off means they are mixed into the general door shuffle instead of only being shuffled - among themselves.""" +class WarpTileShuffle(Choice): + """Vanilla: The warp tiles in Silph Co and Sabrina's Gym are not changed. + Shuffle: The warp tile destinations are shuffled among themselves. + Mixed: The warp tiles are mixed into the pool of available doors for Full, Insanity, and Decoupled. Same as Shuffle + for any other door shuffle option.""" display_name = "Warp Tile Shuffle" default = 0 + option_vanilla = 0 + option_shuffle = 1 + option_mixed = 2 + alias_true = 1 + alias_on = 1 + alias_off = 0 + alias_false = 0 class RandomizeRockTunnel(Toggle): - """Randomize the layout of Rock Tunnel. - If Insanity Door Shuffle is on, this will cause only the main entrances to Rock Tunnel to be shuffled.""" + """Randomize the layout of Rock Tunnel. If Full, Insanity, or Decoupled Door Shuffle is on, this will cause only the + main entrances to Rock Tunnel to be shuffled.""" display_name = "Randomize Rock Tunnel" default = 0 @@ -401,15 +401,17 @@ class Stonesanity(Toggle): class LevelScaling(Choice): """Off: Encounters use vanilla game levels. By Spheres: Levels are scaled by access sphere. Areas reachable in later spheres will have higher levels. - Spheres and Distance: Levels are scaled by access spheres as well as distance from Pallet Town, measured by number - of internal region connections. This is a much more severe curving of levels and may lead to much less variation in - levels found in a particular map. However, it may make the higher door shuffle settings significantly more bearable, - as these options more often result in a smaller number of larger access spheres.""" + By Spheres and Distance: Levels are scaled by access spheres as well as distance from Pallet Town, measured by + number of internal region connections. This is a much more severe curving of levels and may lead to much less + variation in levels found in a particular map. However, it may make the higher door shuffle settings significantly + more bearable, as these options more often result in a smaller number of larger access spheres. + Auto: Scales by Spheres if Door Shuffle is off or on Simple, otherwise scales by Spheres and Distance""" display_name = "Level Scaling" option_off = 0 option_by_spheres = 1 option_by_spheres_and_distance = 2 - default = 1 + option_auto = 3 + default = 3 class ExpModifier(NamedRange): diff --git a/worlds/pokemon_rb/regions.py b/worlds/pokemon_rb/regions.py index 97e63c0557..afeb301c9b 100644 --- a/worlds/pokemon_rb/regions.py +++ b/worlds/pokemon_rb/regions.py @@ -256,6 +256,22 @@ map_ids = { "Indigo Plateau Agatha's Room": 0xF7, } +town_map_coords = { + "Route 2-SW": ("Viridian Forest South Gate to Route 2-SW", 2, 4, (3,), "Viridian Forest", 4), #ViridianForestName + "Route 2-NE": ("Diglett's Cave Route 2 to Route 2-NE", 3, 4, (48,), "Diglett's Cave", 5), #DiglettsCaveName + "Route 4-W": ("Mt Moon 1F to Route 4-W", 6, 2, (5,), "Mt Moon 1F", 8), #MountMoonName + "Cerulean City-Cave": ("Cerulean Cave 1F-SE to Cerulean City-Cave", 9, 1, (54,), "Cerulean Cave 1F", 11), #CeruleanCaveName + "Vermilion City-Dock": ("Vermilion Dock to Vermilion City-Dock", 9, 10, (19,), "S.S. Anne 1F", 17), #SSAnneName + "Route 10-N": ("Rock Tunnel 1F-NE 1 to Route 10-N", 14, 3, (13, 57), "Rock Tunnel Pokemon Center", 19), #RockTunnelName + "Lavender Town": ("Pokemon Tower 1F to Lavender Town", 15, 5, (27,), "Pokemon Tower 2F", 22), #PokemonTowerName + "Celadon Game Corner-Hidden Stairs": ("Rocket Hideout B1F to Celadon Game Corner-Hidden Stairs", 7, 5, (50,), "Rocket Hideout B1F", 26), #RocketHQName + "Saffron City-Silph": ("Silph Co 1F to Saffron City-Silph", 10, 5, (51, 58), "Silph Co 2F", 28), #SilphCoName + "Route 20-IE": ("Seafoam Islands 1F to Route 20-IE", 5, 15, (32,), "Seafoam Islands B1F", 40), #SeafoamIslandsName + "Cinnabar Island-M": ("Pokemon Mansion 1F to Cinnabar Island-M", 2, 15, (35, 52), "Pokemon Mansion 1F", 43), #PokemonMansionName + "Route 23-C": ("Victory Road 1F-S to Route 23-C", 0, 4, (20, 45, 49), "Victory Road 1F", 47), #VictoryRoadName + "Route 10-P": ("Power Plant to Route 10-P", 15, 4, (14,), "Power Plant", 49), #PowerPlantName +} + warp_data = {'Menu': [], 'Evolution': [], 'Old Rod Fishing': [], 'Good Rod Fishing': [], 'Fossil Level': [], 'Pokedex': [], 'Fossil': [], 'Celadon City': [ {'name': 'Celadon City to Celadon Department Store 1F W', 'address': 'Warps_CeladonCity', 'id': 0, @@ -461,15 +477,21 @@ warp_data = {'Menu': [], 'Evolution': [], 'Old Rod Fishing': [], 'Good Rod Fishi {'address': 'Warps_PokemonMansion3F', 'id': 0, 'to': {'map': 'Pokemon Mansion 2F', 'id': 1}}], 'Pokemon Mansion B1F': [ {'address': 'Warps_PokemonMansionB1F', 'id': 0, 'to': {'map': 'Pokemon Mansion 1F-SE', 'id': 5}}], - 'Rock Tunnel 1F-NE': [{'address': 'Warps_RockTunnel1F', 'id': 0, 'to': {'map': 'Route 10-N', 'id': 1}}, - {'address': 'Warps_RockTunnel1F', 'id': 4, - 'to': {'map': 'Rock Tunnel B1F-E', 'id': 0}}], 'Rock Tunnel 1F-NW': [ - {'address': 'Warps_RockTunnel1F', 'id': 5, 'to': {'map': 'Rock Tunnel B1F-E', 'id': 1}}, - {'address': 'Warps_RockTunnel1F', 'id': 6, 'to': {'map': 'Rock Tunnel B1F-W', 'id': 2}}], - 'Rock Tunnel 1F-S': [{'address': 'Warps_RockTunnel1F', 'id': 2, 'to': {'map': 'Route 10-S', 'id': 2}}, + 'Rock Tunnel 1F-NE 1': [{'address': 'Warps_RockTunnel1F', 'id': 0, 'to': {'map': 'Route 10-N', 'id': 1}}], + 'Rock Tunnel 1F-NE 2': + [{'address': 'Warps_RockTunnel1F', 'id': 4, + 'to': {'map': 'Rock Tunnel B1F-E 1', 'id': 0}}], 'Rock Tunnel 1F-NW 1': [ + {'address': 'Warps_RockTunnel1F', 'id': 5, 'to': {'map': 'Rock Tunnel B1F-E 2', 'id': 1}}], + 'Rock Tunnel 1F-NW 2': [ + {'address': 'Warps_RockTunnel1F', 'id': 6, 'to': {'map': 'Rock Tunnel B1F-W 1', 'id': 2}}], + 'Rock Tunnel 1F-S 1': [{'address': 'Warps_RockTunnel1F', 'id': 2, 'to': {'map': 'Route 10-S', 'id': 2}}], + 'Rock Tunnel 1F-S 2': [ {'address': 'Warps_RockTunnel1F', 'id': 7, - 'to': {'map': 'Rock Tunnel B1F-W', 'id': 3}}], 'Rock Tunnel 1F-Wild': [], - 'Rock Tunnel B1F-Wild': [], 'Seafoam Islands 1F': [ + 'to': {'map': 'Rock Tunnel B1F-W 2', 'id': 3}}], 'Rock Tunnel 1F-Wild': [], + 'Rock Tunnel B1F-Wild': [], + 'Rock Tunnel 1F-NE': [], 'Rock Tunnel 1F-NW': [], 'Rock Tunnel 1F-S': [], 'Rock Tunnel B1F-E': [], + 'Rock Tunnel B1F-W': [], + 'Seafoam Islands 1F': [ {'address': 'Warps_SeafoamIslands1F', 'id': (2, 3), 'to': {'map': 'Route 20-IE', 'id': 1}}, {'address': 'Warps_SeafoamIslands1F', 'id': 4, 'to': {'map': 'Seafoam Islands B1F', 'id': 1}}, {'address': 'Warps_SeafoamIslands1F', 'id': 5, 'to': {'map': 'Seafoam Islands B1F-NE', 'id': 6}}], @@ -569,12 +591,14 @@ warp_data = {'Menu': [], 'Evolution': [], 'Old Rod Fishing': [], 'Good Rod Fishi {'address': 'Warps_CeruleanCave2F', 'id': 3, 'to': {'map': 'Cerulean Cave 1F-N', 'id': 5}}], 'Cerulean Cave B1F': [ {'address': 'Warps_CeruleanCaveB1F', 'id': 0, 'to': {'map': 'Cerulean Cave 1F-NW', 'id': 8}}], - 'Cerulean Cave B1F-E': [], 'Rock Tunnel B1F-E': [ - {'address': 'Warps_RockTunnelB1F', 'id': 0, 'to': {'map': 'Rock Tunnel 1F-NE', 'id': 4}}, - {'address': 'Warps_RockTunnelB1F', 'id': 1, 'to': {'map': 'Rock Tunnel 1F-NW', 'id': 5}}], - 'Rock Tunnel B1F-W': [ - {'address': 'Warps_RockTunnelB1F', 'id': 2, 'to': {'map': 'Rock Tunnel 1F-NW', 'id': 6}}, - {'address': 'Warps_RockTunnelB1F', 'id': 3, 'to': {'map': 'Rock Tunnel 1F-S', 'id': 7}}], + 'Cerulean Cave B1F-E': [], 'Rock Tunnel B1F-E 1': [ + {'address': 'Warps_RockTunnelB1F', 'id': 0, 'to': {'map': 'Rock Tunnel 1F-NE 2', 'id': 4}}], + 'Rock Tunnel B1F-E 2': [ + {'address': 'Warps_RockTunnelB1F', 'id': 1, 'to': {'map': 'Rock Tunnel 1F-NW 1', 'id': 5}}], + 'Rock Tunnel B1F-W 1': [ + {'address': 'Warps_RockTunnelB1F', 'id': 2, 'to': {'map': 'Rock Tunnel 1F-NW 2', 'id': 6}}], + 'Rock Tunnel B1F-W 2': [ + {'address': 'Warps_RockTunnelB1F', 'id': 3, 'to': {'map': 'Rock Tunnel 1F-S 2', 'id': 7}}], 'Seafoam Islands B1F': [ {'address': 'Warps_SeafoamIslandsB1F', 'id': 0, 'to': {'map': 'Seafoam Islands B2F-NW', 'id': 0}}, {'address': 'Warps_SeafoamIslandsB1F', 'id': 1, 'to': {'map': 'Seafoam Islands 1F', 'id': 4}}, @@ -802,7 +826,7 @@ warp_data = {'Menu': [], 'Evolution': [], 'Old Rod Fishing': [], 'Good Rod Fishi 'Route 4-W': [{'address': 'Warps_Route4', 'id': 0, 'to': {'map': 'Route 4 Pokemon Center', 'id': 0}}, {'address': 'Warps_Route4', 'id': 1, 'to': {'map': 'Mt Moon 1F', 'id': 0}}], 'Route 4-C': [{'address': 'Warps_Route4', 'id': 2, 'to': {'map': 'Mt Moon B1F-NE', 'id': 7}}], - 'Route 4-E': [], 'Route 4-Lass': [], 'Route 4-Grass': [], + 'Route 4-Lass': [], 'Route 4-E': [], 'Route 5': [{'address': 'Warps_Route5', 'id': (1, 0), 'to': {'map': 'Route 5 Gate-N', 'id': (3, 2)}}, {'address': 'Warps_Route5', 'id': 3, 'to': {'map': 'Underground Path Route 5', 'id': 0}}, {'address': 'Warps_Route5', 'id': 4, 'to': {'map': 'Daycare', 'id': 0}}], 'Route 9': [], @@ -838,8 +862,8 @@ warp_data = {'Menu': [], 'Evolution': [], 'Old Rod Fishing': [], 'Good Rod Fishi {'address': 'Warps_Route8', 'id': 4, 'to': {'map': 'Underground Path Route 8', 'id': 0}}], 'Route 8-Grass': [], 'Route 10-N': [{'address': 'Warps_Route10', 'id': 0, 'to': {'map': 'Rock Tunnel Pokemon Center', 'id': 0}}, - {'address': 'Warps_Route10', 'id': 1, 'to': {'map': 'Rock Tunnel 1F-NE', 'id': 0}}], - 'Route 10-S': [{'address': 'Warps_Route10', 'id': 2, 'to': {'map': 'Rock Tunnel 1F-S', 'id': 2}}], + {'address': 'Warps_Route10', 'id': 1, 'to': {'map': 'Rock Tunnel 1F-NE 1', 'id': 0}}], + 'Route 10-S': [{'address': 'Warps_Route10', 'id': 2, 'to': {'map': 'Rock Tunnel 1F-S 1', 'id': 2}}], 'Route 10-P': [{'address': 'Warps_Route10', 'id': 3, 'to': {'map': 'Power Plant', 'id': 0}}], 'Route 10-C': [], 'Route 11': [{'address': 'Warps_Route11', 'id': 4, 'to': {'map': "Diglett's Cave Route 11", 'id': 0}}], @@ -1293,7 +1317,7 @@ def pair(a, b): return (f"{a} to {b}", f"{b} to {a}") -mandatory_connections = { +safari_zone_connections = { pair("Safari Zone Center-S", "Safari Zone Gate-N"), pair("Safari Zone East", "Safari Zone North"), pair("Safari Zone East", "Safari Zone Center-S"), @@ -1302,14 +1326,8 @@ mandatory_connections = { pair("Safari Zone North", "Safari Zone West-NW"), pair("Safari Zone West", "Safari Zone Center-NW"), } -insanity_mandatory_connections = { - # pair("Seafoam Islands B1F-NE", "Seafoam Islands 1F"), - # pair("Seafoam Islands 1F", "Seafoam Islands B1F"), - # pair("Seafoam Islands B2F-NW", "Seafoam Islands B1F"), - # pair("Seafoam Islands B3F-SE", "Seafoam Islands B2F-SE"), - # pair("Seafoam Islands B3F-NE", "Seafoam Islands B2F-NE"), - # pair("Seafoam Islands B4F", "Seafoam Islands B3F-NE"), - # pair("Seafoam Islands B4F", "Seafoam Islands B3F"), + +full_mandatory_connections = { pair("Player's House 1F", "Player's House 2F"), pair("Indigo Plateau Lorelei's Room", "Indigo Plateau Lobby-N"), pair("Indigo Plateau Bruno's Room", "Indigo Plateau Lorelei's Room"), @@ -1338,7 +1356,7 @@ safe_connecting_interior_dungeons = [ unsafe_connecting_interior_dungeons = [ ["Seafoam Islands 1F to Route 20-IE", "Seafoam Islands 1F-SE to Route 20-IW"], - ["Rock Tunnel 1F-NE to Route 10-N", "Rock Tunnel 1F-S to Route 10-S"], + ["Rock Tunnel 1F-NE 1 to Route 10-N", "Rock Tunnel 1F-S 1 to Route 10-S"], ["Victory Road 1F-S to Route 23-C", "Victory Road 2F-E to Route 23-N"], ] @@ -1357,7 +1375,7 @@ connecting_interior_dungeon_entrances = [ ["Route 2-NE to Diglett's Cave Route 2", "Route 11 to Diglett's Cave Route 11"], ['Route 20-IE to Seafoam Islands 1F', 'Route 20-IW to Seafoam Islands 1F-SE'], ['Route 4-W to Mt Moon 1F', 'Route 4-C to Mt Moon B1F-NE'], - ['Route 10-N to Rock Tunnel 1F-NE', 'Route 10-S to Rock Tunnel 1F-S'], + ['Route 10-N to Rock Tunnel 1F-NE 1', 'Route 10-S to Rock Tunnel 1F-S 1'], ['Route 23-C to Victory Road 1F-S', 'Route 23-N to Victory Road 2F-E'], ] @@ -1454,7 +1472,6 @@ mansion_stair_destinations = [ ] unreachable_outdoor_entrances = [ - "Route 4-C to Mt Moon B1F-NE", "Fuchsia City-Good Rod House Backyard to Fuchsia Good Rod House", "Cerulean City-Badge House Backyard to Cerulean Badge House", # TODO: This doesn't need to be forced if fly location is Pokemon League? @@ -1496,7 +1513,6 @@ def create_regions(self): start_inventory["Exp. All"] = 1 self.multiworld.push_precollected(self.create_item("Exp. All")) - # locations = [location for location in location_data if location.type in ("Item", "Trainer Parties")] self.item_pool = [] combined_traps = (self.multiworld.poison_trap_weight[self.player].value + self.multiworld.fire_trap_weight[self.player].value @@ -1556,7 +1572,6 @@ def create_regions(self): if event: location_object.place_locked_item(item) if location.type == "Trainer Parties": - # loc.item.classification = ItemClassification.filler location_object.party_data = deepcopy(location.party_data) else: self.item_pool.append(item) @@ -1566,7 +1581,7 @@ def create_regions(self): + [item.name for item in self.multiworld.precollected_items[self.player] if item.advancement] self.total_key_items = len( - # The stonesanity items are not checekd for here and instead just always added as the `+ 4` + # The stonesanity items are not checked for here and instead just always added as the `+ 4` # They will always exist, but if stonesanity is off, then only as events. # We don't want to just add 4 if stonesanity is off while still putting them in this list in case # the player puts stones in their start inventory, in which case they would be double-counted here. @@ -1619,16 +1634,15 @@ def create_regions(self): connect(multiworld, player, "Pewter City-E", "Route 3", lambda state: logic.route_3(state, player), one_way=True) connect(multiworld, player, "Route 3", "Pewter City-E", one_way=True) connect(multiworld, player, "Route 4-W", "Route 3") - connect(multiworld, player, "Route 24", "Cerulean City-Water", one_way=True) + connect(multiworld, player, "Route 24", "Cerulean City-Water", lambda state: logic.can_surf(state, player)) connect(multiworld, player, "Cerulean City-Water", "Route 4-Lass", lambda state: logic.can_surf(state, player), one_way=True) connect(multiworld, player, "Mt Moon B2F", "Mt Moon B2F-Wild", one_way=True) connect(multiworld, player, "Mt Moon B2F-NE", "Mt Moon B2F-Wild", one_way=True) connect(multiworld, player, "Mt Moon B2F-C", "Mt Moon B2F-Wild", one_way=True) - connect(multiworld, player, "Route 4-Lass", "Route 4-E", one_way=True) + connect(multiworld, player, "Route 4-Lass", "Route 4-C", one_way=True) connect(multiworld, player, "Route 4-C", "Route 4-E", one_way=True) - connect(multiworld, player, "Route 4-E", "Route 4-Grass", one_way=True) - connect(multiworld, player, "Route 4-Grass", "Cerulean City", one_way=True) - connect(multiworld, player, "Cerulean City", "Route 24", one_way=True) + connect(multiworld, player, "Route 4-E", "Cerulean City") + connect(multiworld, player, "Cerulean City", "Route 24") connect(multiworld, player, "Cerulean City", "Cerulean City-T", lambda state: state.has("Help Bill", player)) connect(multiworld, player, "Cerulean City-Outskirts", "Cerulean City", one_way=True) connect(multiworld, player, "Cerulean City", "Cerulean City-Outskirts", lambda state: logic.can_cut(state, player), one_way=True) @@ -1785,7 +1799,6 @@ def create_regions(self): connect(multiworld, player, "Seafoam Islands B3F-SE", "Seafoam Islands B3F-Wild", one_way=True) connect(multiworld, player, "Seafoam Islands B4F", "Seafoam Islands B4F-W", lambda state: logic.can_surf(state, player), one_way=True) connect(multiworld, player, "Seafoam Islands B4F-W", "Seafoam Islands B4F", one_way=True) - # This really shouldn't be necessary since if the boulders are reachable you can drop, but might as well be thorough connect(multiworld, player, "Seafoam Islands B3F", "Seafoam Islands B3F-SE", lambda state: logic.can_surf(state, player) and logic.can_strength(state, player) and state.has("Seafoam Exit Boulder", player, 6)) connect(multiworld, player, "Viridian City", "Viridian City-N", lambda state: state.has("Oak's Parcel", player) or state.multiworld.old_man[player].value == 2 or logic.can_cut(state, player)) connect(multiworld, player, "Route 11", "Route 11-C", lambda state: logic.can_strength(state, player) or not state.multiworld.extra_strength_boulders[player]) @@ -1804,6 +1817,16 @@ def create_regions(self): connect(multiworld, player, "Pokemon Mansion 2F-E", "Pokemon Mansion 2F-Wild", one_way=True) connect(multiworld, player, "Pokemon Mansion 1F-SE", "Pokemon Mansion 1F-Wild", one_way=True) connect(multiworld, player, "Pokemon Mansion 1F", "Pokemon Mansion 1F-Wild", one_way=True) + connect(multiworld, player, "Rock Tunnel 1F-S 1", "Rock Tunnel 1F-S", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-S 2", "Rock Tunnel 1F-S", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-NW 1", "Rock Tunnel 1F-NW", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-NW 2", "Rock Tunnel 1F-NW", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-NE 1", "Rock Tunnel 1F-NE", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel 1F-NE 2", "Rock Tunnel 1F-NE", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel B1F-W 1", "Rock Tunnel B1F-W", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel B1F-W 2", "Rock Tunnel B1F-W", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel B1F-E 1", "Rock Tunnel B1F-E", lambda state: logic.rock_tunnel(state, player)) + connect(multiworld, player, "Rock Tunnel B1F-E 2", "Rock Tunnel B1F-E", lambda state: logic.rock_tunnel(state, player)) connect(multiworld, player, "Rock Tunnel 1F-S", "Rock Tunnel 1F-Wild", lambda state: logic.rock_tunnel(state, player), one_way=True) connect(multiworld, player, "Rock Tunnel 1F-NW", "Rock Tunnel 1F-Wild", lambda state: logic.rock_tunnel(state, player), one_way=True) connect(multiworld, player, "Rock Tunnel 1F-NE", "Rock Tunnel 1F-Wild", lambda state: logic.rock_tunnel(state, player), one_way=True) @@ -1860,7 +1883,6 @@ def create_regions(self): logic.has_badges(state, self.multiworld.cerulean_cave_badges_condition[player].value, player) and logic.has_key_items(state, self.multiworld.cerulean_cave_key_items_condition[player].total, player) and logic.can_surf(state, player)) - # access to any part of a city will enable flying to the Pokemon Center connect(multiworld, player, "Cerulean City-Cave", "Cerulean City", lambda state: logic.can_fly(state, player), one_way=True) connect(multiworld, player, "Cerulean City-Badge House Backyard", "Cerulean City", lambda state: logic.can_fly(state, player), one_way=True) @@ -1876,7 +1898,6 @@ def create_regions(self): connect(multiworld, player, "Cinnabar Island-G", "Cinnabar Island", lambda state: logic.can_fly(state, player), one_way=True, name="Cinnabar Island-G to Cinnabar Island (Fly)") connect(multiworld, player, "Cinnabar Island-M", "Cinnabar Island", lambda state: logic.can_fly(state, player), one_way=True, name="Cinnabar Island-M to Cinnabar Island (Fly)") - # drops connect(multiworld, player, "Seafoam Islands 1F", "Seafoam Islands B1F", one_way=True, name="Seafoam Islands 1F to Seafoam Islands B1F (Drop)") connect(multiworld, player, "Seafoam Islands 1F", "Seafoam Islands B1F-NE", one_way=True, name="Seafoam Islands 1F to Seafoam Islands B1F-NE (Drop)") @@ -1904,14 +1925,50 @@ def create_regions(self): lambda state: logic.can_fly(state, player) and state.has("Town Map", player), one_way=True, name="Town Map Fly Location") + cache = multiworld.regions.entrance_cache[self.player].copy() + if multiworld.badgesanity[player] or multiworld.door_shuffle[player] in ("off", "simple"): + badges = None + badge_locs = None + else: + badges = [item for item in self.item_pool if "Badge" in item.name] + for badge in badges: + self.item_pool.remove(badge) + badge_locs = [multiworld.get_location(loc, player) for loc in [ + "Pewter Gym - Brock Prize", "Cerulean Gym - Misty Prize", "Vermilion Gym - Lt. Surge Prize", + "Celadon Gym - Erika Prize", "Fuchsia Gym - Koga Prize", "Saffron Gym - Sabrina Prize", + "Cinnabar Gym - Blaine Prize", "Viridian Gym - Giovanni Prize" + ]] + for attempt in range(10): + try: + door_shuffle(self, multiworld, player, badges, badge_locs) + except DoorShuffleException as e: + if attempt == 9: + raise e + for region in self.multiworld.get_regions(player): + for entrance in reversed(region.exits): + if isinstance(entrance, PokemonRBWarp): + region.exits.remove(entrance) + multiworld.regions.entrance_cache[self.player] = cache + if badge_locs: + for loc in badge_locs: + loc.item = None + loc.locked = False + else: + break + + +def door_shuffle(world, multiworld, player, badges, badge_locs): entrances = [] + full_interiors = [] for region_name, region_entrances in warp_data.items(): + region = multiworld.get_region(region_name, player) for entrance_data in region_entrances: - region = multiworld.get_region(region_name, player) shuffle = True - if not outdoor_map(region.name) and not outdoor_map(entrance_data['to']['map']) and \ - multiworld.door_shuffle[player] not in ("insanity", "decoupled"): - shuffle = False + interior = False + if not outdoor_map(region.name) and not outdoor_map(entrance_data['to']['map']): + if multiworld.door_shuffle[player] not in ("full", "insanity", "decoupled"): + shuffle = False + interior = True if multiworld.door_shuffle[player] == "simple": if sorted([entrance_data['to']['map'], region.name]) == ["Celadon Game Corner-Hidden Stairs", "Rocket Hideout B1F"]: @@ -1921,11 +1978,14 @@ def create_regions(self): if (multiworld.randomize_rock_tunnel[player] and "Rock Tunnel" in region.name and "Rock Tunnel" in entrance_data['to']['map']): shuffle = False - if (f"{region.name} to {entrance_data['to']['map']}" if "name" not in entrance_data else + elif (f"{region.name} to {entrance_data['to']['map']}" if "name" not in entrance_data else entrance_data["name"]) in silph_co_warps + saffron_gym_warps: - if multiworld.warp_tile_shuffle[player] or multiworld.door_shuffle[player] in ("insanity", - "decoupled"): + if multiworld.warp_tile_shuffle[player]: shuffle = True + if multiworld.warp_tile_shuffle[player] == "mixed" and multiworld.door_shuffle[player] == "full": + interior = True + else: + interior = False else: shuffle = False elif not multiworld.door_shuffle[player]: @@ -1935,33 +1995,49 @@ def create_regions(self): entrance_data else entrance_data["name"], region, entrance_data["id"], entrance_data["address"], entrance_data["flags"] if "flags" in entrance_data else "") - # if "Rock Tunnel" in region_name: - # entrance.access_rule = lambda state: logic.rock_tunnel(state, player) - entrances.append(entrance) + if interior and multiworld.door_shuffle[player] == "full": + full_interiors.append(entrance) + else: + entrances.append(entrance) region.exits.append(entrance) else: - # connect(multiworld, player, region.name, entrance_data['to']['map'], one_way=True) - if "Rock Tunnel" in region.name: - connect(multiworld, player, region.name, entrance_data["to"]["map"], - lambda state: logic.rock_tunnel(state, player), one_way=True) - else: - connect(multiworld, player, region.name, entrance_data["to"]["map"], one_way=True, - name=entrance_data["name"] if "name" in entrance_data else None) + connect(multiworld, player, region.name, entrance_data["to"]["map"], one_way=True, + name=entrance_data["name"] if "name" in entrance_data else None) forced_connections = set() + one_way_forced_connections = set() if multiworld.door_shuffle[player]: - forced_connections.update(mandatory_connections.copy()) + if multiworld.door_shuffle[player] in ("full", "insanity", "decoupled"): + safari_zone_doors = [door for pair in safari_zone_connections for door in pair] + safari_zone_doors.sort() + order = ["Center", "East", "North", "West"] + multiworld.random.shuffle(order) + usable_doors = ["Safari Zone Gate-N to Safari Zone Center-S"] + for section in order: + section_doors = [door for door in safari_zone_doors if door.startswith(f"Safari Zone {section}")] + connect_door_a = multiworld.random.choice(usable_doors) + connect_door_b = multiworld.random.choice(section_doors) + usable_doors.remove(connect_door_a) + section_doors.remove(connect_door_b) + forced_connections.add((connect_door_a, connect_door_b)) + usable_doors += section_doors + multiworld.random.shuffle(usable_doors) + while usable_doors: + forced_connections.add((usable_doors.pop(), usable_doors.pop())) + else: + forced_connections.update(safari_zone_connections) + usable_safe_rooms = safe_rooms.copy() if multiworld.door_shuffle[player] == "simple": forced_connections.update(simple_mandatory_connections) else: usable_safe_rooms += pokemarts - if self.multiworld.key_items_only[self.player]: + if multiworld.key_items_only[player]: usable_safe_rooms.remove("Viridian Pokemart to Viridian City") - if multiworld.door_shuffle[player] in ("insanity", "decoupled"): - forced_connections.update(insanity_mandatory_connections) + if multiworld.door_shuffle[player] in ("full", "insanity", "decoupled"): + forced_connections.update(full_mandatory_connections) r = multiworld.random.randint(0, 3) if r == 2: forced_connections.add(("Pokemon Mansion 1F-SE to Pokemon Mansion B1F", @@ -1969,6 +2045,9 @@ def create_regions(self): forced_connections.add(("Pokemon Mansion 2F to Pokemon Mansion 3F", multiworld.random.choice(mansion_stair_destinations + mansion_dead_ends + ["Pokemon Mansion B1F to Pokemon Mansion 1F-SE"]))) + if multiworld.door_shuffle[player] == "full": + forced_connections.add(("Pokemon Mansion 1F to Pokemon Mansion 2F", + "Pokemon Mansion 3F to Pokemon Mansion 2F")) elif r == 3: dead_end = multiworld.random.randint(0, 1) forced_connections.add(("Pokemon Mansion 3F-SE to Pokemon Mansion 2F-E", @@ -1987,7 +2066,8 @@ def create_regions(self): multiworld.random.choice(mansion_stair_destinations + ["Pokemon Mansion B1F to Pokemon Mansion 1F-SE"]))) - usable_safe_rooms += insanity_safe_rooms + if multiworld.door_shuffle[player] in ("insanity", "decoupled"): + usable_safe_rooms += insanity_safe_rooms safe_rooms_sample = multiworld.random.sample(usable_safe_rooms, 6) pallet_safe_room = safe_rooms_sample[-1] @@ -1995,16 +2075,28 @@ def create_regions(self): for a, b in zip(multiworld.random.sample(["Pallet Town to Player's House 1F", "Pallet Town to Oak's Lab", "Pallet Town to Rival's House"], 3), ["Oak's Lab to Pallet Town", "Player's House 1F to Pallet Town", pallet_safe_room]): - forced_connections.add((a, b)) + one_way_forced_connections.add((a, b)) + + if multiworld.door_shuffle[player] == "decoupled": + for a, b in zip(["Oak's Lab to Pallet Town", "Player's House 1F to Pallet Town", pallet_safe_room], + multiworld.random.sample(["Pallet Town to Player's House 1F", "Pallet Town to Oak's Lab", + "Pallet Town to Rival's House"], 3)): + one_way_forced_connections.add((a, b)) + for a, b in zip(safari_zone_houses, safe_rooms_sample): - forced_connections.add((a, b)) + one_way_forced_connections.add((a, b)) + if multiworld.door_shuffle[player] == "decoupled": + for a, b in zip(multiworld.random.sample(safe_rooms_sample[:-1], len(safe_rooms_sample) - 1), + safari_zone_houses): + one_way_forced_connections.add((a, b)) + if multiworld.door_shuffle[player] == "simple": # force Indigo Plateau Lobby to vanilla location on simple, otherwise shuffle with Pokemon Centers. for a, b in zip(multiworld.random.sample(pokemon_center_entrances[0:-1], 11), pokemon_centers[0:-1]): forced_connections.add((a, b)) forced_connections.add((pokemon_center_entrances[-1], pokemon_centers[-1])) forced_pokemarts = multiworld.random.sample(pokemart_entrances, 8) - if self.multiworld.key_items_only[self.player]: + if multiworld.key_items_only[player]: forced_pokemarts.sort(key=lambda i: i[0] != "Viridian Pokemart to Viridian City") for a, b in zip(forced_pokemarts, pokemarts): forced_connections.add((a, b)) @@ -2014,15 +2106,19 @@ def create_regions(self): # warping outside an entrance that isn't the Pokemon Center, just always put Pokemon Centers at Pokemon # Center entrances for a, b in zip(multiworld.random.sample(pokemon_center_entrances, 12), pokemon_centers): - forced_connections.add((a, b)) + one_way_forced_connections.add((a, b)) # Ensure a Pokemart is available at the beginning of the game if multiworld.key_items_only[player]: - forced_connections.add((multiworld.random.choice(initial_doors), "Viridian Pokemart to Viridian City")) - elif "Pokemart" not in pallet_safe_room: - forced_connections.add((multiworld.random.choice(initial_doors), multiworld.random.choice( - [mart for mart in pokemarts if mart not in safe_rooms_sample]))) + one_way_forced_connections.add((multiworld.random.choice(initial_doors), + "Viridian Pokemart to Viridian City")) - if multiworld.warp_tile_shuffle[player]: + elif "Pokemart" not in pallet_safe_room: + one_way_forced_connections.add((multiworld.random.choice(initial_doors), multiworld.random.choice( + [mart for mart in pokemarts if mart not in safe_rooms_sample]))) + + if multiworld.warp_tile_shuffle[player] == "shuffle" or (multiworld.warp_tile_shuffle[player] == "mixed" + and multiworld.door_shuffle[player] + in ("off", "simple", "interiors")): warps = multiworld.random.sample(silph_co_warps, len(silph_co_warps)) # The only warp tiles never reachable from the stairs/elevators are the two 7F-NW warps (where the rival is) # and the final 11F-W warp. As long as the two 7F-NW warps aren't connected to each other, everything should @@ -2055,13 +2151,38 @@ def create_regions(self): while warps: forced_connections.add((warps.pop(), warps.pop(),)) + dc_destinations = None + if multiworld.door_shuffle[player] == "decoupled": + dc_destinations = entrances.copy() + for pair in one_way_forced_connections: + entrance_a = multiworld.get_entrance(pair[0], player) + entrance_b = multiworld.get_entrance(pair[1], player) + entrance_a.connect(entrance_b) + entrances.remove(entrance_a) + dc_destinations.remove(entrance_b) + else: + forced_connections.update(one_way_forced_connections) + for pair in forced_connections: entrance_a = multiworld.get_entrance(pair[0], player) entrance_b = multiworld.get_entrance(pair[1], player) entrance_a.connect(entrance_b) entrance_b.connect(entrance_a) - entrances.remove(entrance_a) - entrances.remove(entrance_b) + if entrance_a in entrances: + entrances.remove(entrance_a) + elif entrance_a in full_interiors: + full_interiors.remove(entrance_a) + else: + raise DoorShuffleException("Attempted to force connection with entrance not in any entrance pool, likely because it tried to force an entrance to connect twice.") + if entrance_b in entrances: + entrances.remove(entrance_b) + elif entrance_b in full_interiors: + full_interiors.remove(entrance_b) + else: + raise DoorShuffleException("Attempted to force connection with entrance not in any entrance pool, likely because it tried to force an entrance to connect twice.") + if multiworld.door_shuffle[player] == "decoupled": + dc_destinations.remove(entrance_a) + dc_destinations.remove(entrance_b) if multiworld.door_shuffle[player] == "simple": def connect_connecting_interiors(interior_exits, exterior_entrances): @@ -2069,7 +2190,7 @@ def create_regions(self): for a, b in zip(interior, exterior): entrance_a = multiworld.get_entrance(a, player) if b is None: - #entrance_b = multiworld.get_entrance(entrances[0], player) + # entrance_b = multiworld.get_entrance(entrances[0], player) # should just be able to use the entrance_b from the previous link? pass else: @@ -2102,7 +2223,7 @@ def create_regions(self): single_entrance_dungeon_entrances = dungeon_entrances.copy() for i in range(2): - if True or not multiworld.random.randint(0, 2): + if not multiworld.random.randint(0, 2): placed_connecting_interior_dungeons.append(multi_purpose_dungeons[i]) interior_dungeon_entrances.append([multi_purpose_dungeon_entrances[i], None]) else: @@ -2185,7 +2306,7 @@ def create_regions(self): and interiors[0] in connecting_interiors[13:17] # Saffron Gate at Underground Path North South and interiors[13] in connecting_interiors[13:17] # Saffron Gate at Route 5 Saffron Gate and multi_purpose_dungeons[0] == placed_connecting_interior_dungeons[4] # Pokémon Mansion at Rock Tunnel, which is - and (not multiworld.tea[player]) # not traversable backwards + and (not multiworld.tea[player]) # not traversable backwards and multiworld.route_3_condition[player] == "defeat_brock" and multiworld.worlds[player].fly_map != "Cerulean City" and multiworld.worlds[player].town_map_fly_map != "Cerulean City"): @@ -2209,20 +2330,64 @@ def create_regions(self): entrance_b.connect(entrance_a) elif multiworld.door_shuffle[player]: if multiworld.door_shuffle[player] == "full": + multiworld.random.shuffle(full_interiors) + + def search_for_exit(entrance, region, checked_regions): + checked_regions.add(region) + for exit_candidate in region.exits: + if ((not exit_candidate.connected_region) + and exit_candidate in entrances and exit_candidate is not entrance): + return exit_candidate + for entrance_candidate in region.entrances: + if entrance_candidate.parent_region not in checked_regions: + found_exit = search_for_exit(entrance, entrance_candidate.parent_region, checked_regions) + if found_exit is not None: + return found_exit + return None + + while True: + for entrance_a in full_interiors: + if search_for_exit(entrance_a, entrance_a.parent_region, set()) is None: + for entrance_b in full_interiors: + if search_for_exit(entrance_b, entrance_b.parent_region, set()): + entrance_a.connect(entrance_b) + entrance_b.connect(entrance_a) + # Yes, it removes from full_interiors while iterating through it, but it immediately + # breaks out, from both loops. + full_interiors.remove(entrance_a) + full_interiors.remove(entrance_b) + break + else: + raise DoorShuffleException("No non-dead end interior sections found in Pokemon Red and Blue door shuffle.") + break + else: + break + + loop_out_interiors = [] + multiworld.random.shuffle(entrances) + for entrance in reversed(entrances): + if not outdoor_map(entrance.parent_region.name): + found_exit = search_for_exit(entrance, entrance.parent_region, set()) + if found_exit is None: + continue + loop_out_interiors.append([found_exit, entrance]) + entrances.remove(entrance) + + if len(loop_out_interiors) == 2: + break + + for entrance_a, entrance_b in zip(full_interiors[:len(full_interiors) // 2], + full_interiors[len(full_interiors) // 2:]): + entrance_a.connect(entrance_b) + entrance_b.connect(entrance_a) + + elif multiworld.door_shuffle[player] == "interiors": loop_out_interiors = [[multiworld.get_entrance(e[0], player), multiworld.get_entrance(e[1], player)] for e in multiworld.random.sample(unsafe_connecting_interior_dungeons + safe_connecting_interior_dungeons, 2)] entrances.remove(loop_out_interiors[0][1]) entrances.remove(loop_out_interiors[1][1]) if not multiworld.badgesanity[player]: - badges = [item for item in self.item_pool if "Badge" in item.name] - for badge in badges: - self.item_pool.remove(badge) - badge_locs = [] - for loc in ["Pewter Gym - Brock Prize", "Cerulean Gym - Misty Prize", "Vermilion Gym - Lt. Surge Prize", - "Celadon Gym - Erika Prize", "Fuchsia Gym - Koga Prize", "Saffron Gym - Sabrina Prize", - "Cinnabar Gym - Blaine Prize", "Viridian Gym - Giovanni Prize"]: - badge_locs.append(multiworld.get_location(loc, player)) multiworld.random.shuffle(badges) while badges[3].name == "Cascade Badge" and multiworld.badges_needed_for_hm_moves[player]: multiworld.random.shuffle(badges) @@ -2233,7 +2398,7 @@ def create_regions(self): for item, data in item_table.items(): if (data.id or item in poke_data.pokemon_data) and data.classification == ItemClassification.progression \ and ("Badge" not in item or multiworld.badgesanity[player]): - state.collect(self.create_item(item)) + state.collect(world.create_item(item)) multiworld.random.shuffle(entrances) reachable_entrances = [] @@ -2269,22 +2434,23 @@ def create_regions(self): "Defeat Viridian Gym Giovanni", ] - event_locations = self.multiworld.get_filled_locations(player) + event_locations = multiworld.get_filled_locations(player) - def adds_reachable_entrances(entrances_copy, item, dead_end_cache): - ret = dead_end_cache.get(item.name) - if (ret != None): - return ret + def adds_reachable_entrances(item): state_copy = state.copy() state_copy.collect(item, True) state.sweep_for_events(locations=event_locations) - ret = len([entrance for entrance in entrances_copy if entrance in reachable_entrances or - entrance.parent_region.can_reach(state_copy)]) > len(reachable_entrances) - dead_end_cache[item.name] = ret - return ret + new_reachable_entrances = len([entrance for entrance in entrances if entrance in reachable_entrances or + entrance.parent_region.can_reach(state_copy)]) + return new_reachable_entrances > len(reachable_entrances) - def dead_end(entrances_copy, e, dead_end_cache): + def dead_end(e): + if e.can_reach(state): + return True + elif multiworld.door_shuffle[player] == "decoupled": + # Any unreachable exit in decoupled is not a dead end + return False region = e.parent_region check_warps = set() checked_regions = {region} @@ -2292,93 +2458,105 @@ def create_regions(self): check_warps.remove(e) for location in region.locations: if location.item and location.item.name in relevant_events and \ - adds_reachable_entrances(entrances_copy, location.item, dead_end_cache): + adds_reachable_entrances(location.item): return False while check_warps: warp = check_warps.pop() warp = warp if warp not in reachable_entrances: - if "Rock Tunnel" not in warp.name or logic.rock_tunnel(state, player): - # confirm warp is in entrances list to ensure it's not a loop-out interior - if warp.connected_region is None and warp in entrances_copy: - return False - elif (isinstance(warp, PokemonRBWarp) and ("Rock Tunnel" not in warp.name or - logic.rock_tunnel(state, player))) or warp.access_rule(state): - if warp.connected_region and warp.connected_region not in checked_regions: - checked_regions.add(warp.connected_region) - check_warps.update(warp.connected_region.exits) - for location in warp.connected_region.locations: - if (location.item and location.item.name in relevant_events and - adds_reachable_entrances(entrances_copy, location.item, dead_end_cache)): - return False + # confirm warp is in entrances list to ensure it's not a loop-out interior + if warp.connected_region is None and warp in entrances: + return False + elif isinstance(warp, PokemonRBWarp) or warp.access_rule(state): + if warp.connected_region and warp.connected_region not in checked_regions: + checked_regions.add(warp.connected_region) + check_warps.update(warp.connected_region.exits) + for location in warp.connected_region.locations: + if (location.item and location.item.name in relevant_events and + adds_reachable_entrances(location.item)): + return False return True starting_entrances = len(entrances) - dc_connected = [] - rock_tunnel_entrances = [entrance for entrance in entrances if "Rock Tunnel" in entrance.name] - entrances = [entrance for entrance in entrances if entrance not in rock_tunnel_entrances] + while entrances: state.update_reachable_regions(player) state.sweep_for_events(locations=event_locations) - if rock_tunnel_entrances and logic.rock_tunnel(state, player): - entrances += rock_tunnel_entrances - rock_tunnel_entrances = None + multiworld.random.shuffle(entrances) + + if multiworld.door_shuffle[player] == "decoupled": + multiworld.random.shuffle(dc_destinations) + else: + entrances.sort(key=lambda e: e.name not in entrance_only) reachable_entrances = [entrance for entrance in entrances if entrance in reachable_entrances or entrance.parent_region.can_reach(state)] - assert reachable_entrances, \ - "Ran out of reachable entrances in Pokemon Red and Blue door shuffle" - multiworld.random.shuffle(entrances) - if multiworld.door_shuffle[player] == "decoupled" and len(entrances) == 1: - entrances += dc_connected - entrances[-1].connect(entrances[0]) - while len(entrances) > 1: - entrances.pop(0).connect(entrances[0]) - break - if multiworld.door_shuffle[player] == "full" or len(entrances) != len(reachable_entrances): - entrances.sort(key=lambda e: e.name not in entrance_only) - dead_end_cache = {} + entrances.sort(key=lambda e: e in reachable_entrances) + + if not reachable_entrances: + raise DoorShuffleException("Ran out of reachable entrances in Pokemon Red and Blue door shuffle") + + entrance_a = reachable_entrances.pop(0) + entrances.remove(entrance_a) + + is_outdoor_map = outdoor_map(entrance_a.parent_region.name) + + if multiworld.door_shuffle[player] in ("interiors", "full") or len(entrances) != len(reachable_entrances): + + find_dead_end = False + if (len(reachable_entrances) > + (1 if multiworld.door_shuffle[player] in ("insanity", "decoupled") else 8) and len(entrances) + <= (starting_entrances - 3)): + find_dead_end = True + + if (multiworld.door_shuffle[player] in ("interiors", "full") and len(entrances) < 48 + and not is_outdoor_map): + # Try to prevent a situation where the only remaining outdoor entrances are ones that cannot be + # reached except by connecting directly to it. + entrances.sort(key=lambda e: e.name not in unreachable_outdoor_entrances) + if entrances[0].name in unreachable_outdoor_entrances and len([entrance for entrance + in reachable_entrances if not outdoor_map(entrance.parent_region.name)]) > 1: + find_dead_end = True - # entrances list is empty while it's being sorted, must pass a copy to iterate through - entrances_copy = entrances.copy() if multiworld.door_shuffle[player] == "decoupled": - entrances.sort(key=lambda e: 1 if e.connected_region is not None else 2 if e not in - reachable_entrances else 0) - assert entrances[0].connected_region is None,\ - "Ran out of valid reachable entrances in Pokemon Red and Blue door shuffle" - elif len(reachable_entrances) > (1 if multiworld.door_shuffle[player] == "insanity" else 8) and len( - entrances) <= (starting_entrances - 3): - entrances.sort(key=lambda e: 0 if e in reachable_entrances else 2 if - dead_end(entrances_copy, e, dead_end_cache) else 1) + destinations = dc_destinations + elif multiworld.door_shuffle[player] in ("interiors", "full"): + destinations = [entrance for entrance in entrances if outdoor_map(entrance.parent_region.name) is + not is_outdoor_map] + if not destinations: + raise DoorShuffleException("Ran out of connectable destinations in Pokemon Red and Blue door shuffle") else: - entrances.sort(key=lambda e: 0 if e in reachable_entrances else 1 if - dead_end(entrances_copy, e, dead_end_cache) else 2) - if multiworld.door_shuffle[player] == "full": - outdoor = outdoor_map(entrances[0].parent_region.name) - if len(entrances) < 48 and not outdoor: - # Prevent a situation where the only remaining outdoor entrances are ones that cannot be reached - # except by connecting directly to it. - entrances.sort(key=lambda e: e.name in unreachable_outdoor_entrances) + destinations = entrances - entrances.sort(key=lambda e: outdoor_map(e.parent_region.name) != outdoor) - assert entrances[0] in reachable_entrances, \ - "Ran out of valid reachable entrances in Pokemon Red and Blue door shuffle" - if (multiworld.door_shuffle[player] == "decoupled" and len(reachable_entrances) > 8 and len(entrances) - <= (starting_entrances - 3)): - entrance_b = entrances.pop(1) + destinations.sort(key=lambda e: e == entrance_a) + for entrance in destinations: + if (dead_end(entrance) is find_dead_end and (multiworld.door_shuffle[player] != "decoupled" + or entrance.parent_region.name.split("-")[0] != + entrance_a.parent_region.name.split("-")[0])): + entrance_b = entrance + destinations.remove(entrance) + break + else: + entrance_b = destinations.pop(0) + + if multiworld.door_shuffle[player] in ("interiors", "full"): + # on Interiors/Full, the destinations variable does not point to the entrances list, so we need to + # remove from that list here. + entrances.remove(entrance_b) else: - entrance_b = entrances.pop() - entrance_a = entrances.pop(0) + # Everything is reachable. Just start connecting the rest of the doors at random. + if multiworld.door_shuffle[player] == "decoupled": + entrance_b = dc_destinations.pop(0) + else: + entrance_b = entrances.pop(0) + entrance_a.connect(entrance_b) - if multiworld.door_shuffle[player] == "decoupled": - entrances.append(entrance_b) - dc_connected.append(entrance_a) - else: + if multiworld.door_shuffle[player] != "decoupled": entrance_b.connect(entrance_a) - if multiworld.door_shuffle[player] == "full": + if multiworld.door_shuffle[player] in ("interiors", "full"): for pair in loop_out_interiors: pair[1].connected_region = pair[0].connected_region pair[1].parent_region.entrances.append(pair[0]) @@ -2443,11 +2621,18 @@ class PokemonRBWarp(Entrance): def access_rule(self, state): if self.connected_region is None: return False - if "Rock Tunnel" in self.parent_region.name or "Rock Tunnel" in self.connected_region.name: - return logic.rock_tunnel(state, self.player) + if "Elevator" in self.parent_region.name and ( + (state.multiworld.all_elevators_locked[self.player] + or "Rocket Hideout" in self.parent_region.name) + and not state.has("Lift Key", self.player)): + return False return True +class DoorShuffleException(Exception): + pass + + class PokemonRBRegion(Region): def __init__(self, name, player, multiworld): super().__init__(name, player, multiworld) diff --git a/worlds/pokemon_rb/rom.py b/worlds/pokemon_rb/rom.py index 81ab6648dd..b6c1221a29 100644 --- a/worlds/pokemon_rb/rom.py +++ b/worlds/pokemon_rb/rom.py @@ -9,9 +9,10 @@ from .items import item_table from .pokemon import set_mon_palettes from .rock_tunnel import randomize_rock_tunnel from .rom_addresses import rom_addresses -from .regions import PokemonRBWarp, map_ids +from .regions import PokemonRBWarp, map_ids, town_map_coords from . import poke_data + def write_quizzes(self, data, random): def get_quiz(q, a): @@ -204,19 +205,21 @@ def generate_output(self, output_directory: str): basemd5 = hashlib.md5() basemd5.update(data) - lab_loc = self.multiworld.get_entrance("Oak's Lab to Pallet Town", self.player).target + pallet_connections = {entrance: self.multiworld.get_entrance(f"Pallet Town to {entrance}", + self.player).connected_region.name for + entrance in ["Player's House 1F", "Oak's Lab", + "Rival's House"]} paths = None - if lab_loc == 0: # Player's House + if pallet_connections["Player's House 1F"] == "Oak's Lab": paths = ((0x00, 4, 0x80, 5, 0x40, 1, 0xE0, 1, 0xFF), (0x40, 2, 0x20, 5, 0x80, 5, 0xFF)) - elif lab_loc == 1: # Rival's House + elif pallet_connections["Rival's House"] == "Oak's Lab": paths = ((0x00, 4, 0xC0, 3, 0x40, 1, 0xE0, 1, 0xFF), (0x40, 2, 0x10, 3, 0x80, 5, 0xFF)) if paths: write_bytes(data, paths[0], rom_addresses["Path_Pallet_Oak"]) write_bytes(data, paths[1], rom_addresses["Path_Pallet_Player"]) - home_loc = self.multiworld.get_entrance("Player's House 1F to Pallet Town", self.player).target - if home_loc == 1: # Rival's House + if pallet_connections["Rival's House"] == "Player's House 1F": write_bytes(data, [0x2F, 0xC7, 0x06, 0x0D, 0x00, 0x01], rom_addresses["Pallet_Fly_Coords"]) - elif home_loc == 2: # Oak's Lab + elif pallet_connections["Oak's Lab"] == "Player's House 1F": write_bytes(data, [0x5F, 0xC7, 0x0C, 0x0C, 0x00, 0x00], rom_addresses["Pallet_Fly_Coords"]) for region in self.multiworld.get_regions(self.player): @@ -238,6 +241,14 @@ def generate_output(self, output_directory: str): data[address] = 0 if "Elevator" in connected_map_name else warp_to_ids[i] data[address + 1] = map_ids[connected_map_name] + if self.multiworld.door_shuffle[self.player] == "simple": + for (entrance, _, _, map_coords_entries, map_name, _) in town_map_coords.values(): + destination = self.multiworld.get_entrance(entrance, self.player).connected_region.name + (_, x, y, _, _, map_order_entry) = town_map_coords[destination] + for map_coord_entry in map_coords_entries: + data[rom_addresses["Town_Map_Coords"] + (map_coord_entry * 4) + 1] = (y << 4) | x + data[rom_addresses["Town_Map_Order"] + map_order_entry] = map_ids[map_name] + if not self.multiworld.key_items_only[self.player]: for i, gym_leader in enumerate(("Pewter Gym - Brock TM", "Cerulean Gym - Misty TM", "Vermilion Gym - Lt. Surge TM", "Celadon Gym - Erika TM", diff --git a/worlds/pokemon_rb/rom_addresses.py b/worlds/pokemon_rb/rom_addresses.py index ffb89a4dfc..e5c073971d 100644 --- a/worlds/pokemon_rb/rom_addresses.py +++ b/worlds/pokemon_rb/rom_addresses.py @@ -1,10 +1,10 @@ rom_addresses = { "Option_Encounter_Minimum_Steps": 0x3c1, - "Option_Pitch_Black_Rock_Tunnel": 0x75c, - "Option_Blind_Trainers": 0x30c7, - "Option_Trainersanity1": 0x3157, - "Option_Split_Card_Key": 0x3e10, - "Option_Fix_Combat_Bugs": 0x3e11, + "Option_Pitch_Black_Rock_Tunnel": 0x76a, + "Option_Blind_Trainers": 0x30d5, + "Option_Trainersanity1": 0x3165, + "Option_Split_Card_Key": 0x3e1e, + "Option_Fix_Combat_Bugs": 0x3e1f, "Option_Lose_Money": 0x40d4, "Base_Stats_Mew": 0x4260, "Title_Mon_First": 0x4373, @@ -131,49 +131,49 @@ rom_addresses = { "Starter2_K": 0x19611, "Starter3_K": 0x19619, "Event_Rocket_Thief": 0x19733, - "Option_Cerulean_Cave_Badges": 0x19857, - "Option_Cerulean_Cave_Key_Items": 0x1985e, - "Text_Cerulean_Cave_Badges": 0x198c3, - "Text_Cerulean_Cave_Key_Items": 0x198d1, - "Event_Stranded_Man": 0x19b28, - "Event_Rivals_Sister": 0x19cfb, - "Warps_BluesHouse": 0x19d51, - "Warps_VermilionTradeHouse": 0x19da8, - "Require_Pokedex_D": 0x19e3f, - "Option_Elite_Four_Key_Items": 0x19e89, - "Option_Elite_Four_Pokedex": 0x19e90, - "Option_Elite_Four_Badges": 0x19e97, - "Text_Elite_Four_Badges": 0x19f33, - "Text_Elite_Four_Key_Items": 0x19f3d, - "Text_Elite_Four_Pokedex": 0x19f50, - "Shop10": 0x1a004, - "Warps_IndigoPlateauLobby": 0x1a030, - "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_0_ITEM": 0x1a158, - "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_1_ITEM": 0x1a166, - "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_2_ITEM": 0x1a174, - "Event_SKC4F": 0x1a187, - "Warps_SilphCo4F": 0x1a209, - "Missable_Silph_Co_4F_Item_1": 0x1a249, - "Missable_Silph_Co_4F_Item_2": 0x1a250, - "Missable_Silph_Co_4F_Item_3": 0x1a257, - "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_0_ITEM": 0x1a3af, - "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_1_ITEM": 0x1a3bd, - "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_2_ITEM": 0x1a3cb, - "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_3_ITEM": 0x1a3d9, - "Event_SKC5F": 0x1a3ec, - "Warps_SilphCo5F": 0x1a496, - "Missable_Silph_Co_5F_Item_1": 0x1a4de, - "Missable_Silph_Co_5F_Item_2": 0x1a4e5, - "Missable_Silph_Co_5F_Item_3": 0x1a4ec, - "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_0_ITEM": 0x1a61c, - "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_1_ITEM": 0x1a62a, - "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_2_ITEM": 0x1a638, - "Event_SKC6F": 0x1a659, - "Warps_SilphCo6F": 0x1a737, - "Missable_Silph_Co_6F_Item_1": 0x1a787, - "Missable_Silph_Co_6F_Item_2": 0x1a78e, - "Path_Pallet_Oak": 0x1a914, - "Path_Pallet_Player": 0x1a921, + "Option_Cerulean_Cave_Badges": 0x19861, + "Option_Cerulean_Cave_Key_Items": 0x19868, + "Text_Cerulean_Cave_Badges": 0x198d7, + "Text_Cerulean_Cave_Key_Items": 0x198e5, + "Event_Stranded_Man": 0x19b3c, + "Event_Rivals_Sister": 0x19d0f, + "Warps_BluesHouse": 0x19d65, + "Warps_VermilionTradeHouse": 0x19dbc, + "Require_Pokedex_D": 0x19e53, + "Option_Elite_Four_Key_Items": 0x19e9d, + "Option_Elite_Four_Pokedex": 0x19ea4, + "Option_Elite_Four_Badges": 0x19eab, + "Text_Elite_Four_Badges": 0x19f47, + "Text_Elite_Four_Key_Items": 0x19f51, + "Text_Elite_Four_Pokedex": 0x19f64, + "Shop10": 0x1a018, + "Warps_IndigoPlateauLobby": 0x1a044, + "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_0_ITEM": 0x1a16c, + "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_1_ITEM": 0x1a17a, + "Trainersanity_EVENT_BEAT_SILPH_CO_4F_TRAINER_2_ITEM": 0x1a188, + "Event_SKC4F": 0x1a19b, + "Warps_SilphCo4F": 0x1a21d, + "Missable_Silph_Co_4F_Item_1": 0x1a25d, + "Missable_Silph_Co_4F_Item_2": 0x1a264, + "Missable_Silph_Co_4F_Item_3": 0x1a26b, + "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_0_ITEM": 0x1a3c3, + "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_1_ITEM": 0x1a3d1, + "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_2_ITEM": 0x1a3df, + "Trainersanity_EVENT_BEAT_SILPH_CO_5F_TRAINER_3_ITEM": 0x1a3ed, + "Event_SKC5F": 0x1a400, + "Warps_SilphCo5F": 0x1a4aa, + "Missable_Silph_Co_5F_Item_1": 0x1a4f2, + "Missable_Silph_Co_5F_Item_2": 0x1a4f9, + "Missable_Silph_Co_5F_Item_3": 0x1a500, + "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_0_ITEM": 0x1a630, + "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_1_ITEM": 0x1a63e, + "Trainersanity_EVENT_BEAT_SILPH_CO_6F_TRAINER_2_ITEM": 0x1a64c, + "Event_SKC6F": 0x1a66d, + "Warps_SilphCo6F": 0x1a74b, + "Missable_Silph_Co_6F_Item_1": 0x1a79b, + "Missable_Silph_Co_6F_Item_2": 0x1a7a2, + "Path_Pallet_Oak": 0x1a928, + "Path_Pallet_Player": 0x1a935, "Warps_CinnabarIsland": 0x1c026, "Warps_Route1": 0x1c0e9, "Option_Extra_Key_Items_B": 0x1ca46, @@ -1074,112 +1074,112 @@ rom_addresses = { "Missable_Route_25_Item": 0x5080b, "Warps_IndigoPlateau": 0x5093a, "Warps_SaffronCity": 0x509e0, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_0_ITEM": 0x50d63, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_1_ITEM": 0x50d71, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_2_ITEM": 0x50d7f, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_3_ITEM": 0x50d8d, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_4_ITEM": 0x50d9b, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_5_ITEM": 0x50da9, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_6_ITEM": 0x50db7, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_7_ITEM": 0x50dc5, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_8_ITEM": 0x50dd3, - "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_9_ITEM": 0x50de1, - "Starter2_B": 0x50ffe, - "Starter3_B": 0x51000, - "Starter1_B": 0x51002, - "Starter2_A": 0x5111d, - "Starter3_A": 0x5111f, - "Starter1_A": 0x51121, - "Option_Route23_Badges": 0x5126e, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_0_ITEM": 0x51384, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_1_ITEM": 0x51392, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_2_ITEM": 0x513a0, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_3_ITEM": 0x513ae, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_4_ITEM": 0x513bc, - "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_5_ITEM": 0x513ca, - "Event_Nugget_Bridge": 0x513e1, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_0_ITEM": 0x51569, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_1_ITEM": 0x51577, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_2_ITEM": 0x51585, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_3_ITEM": 0x51593, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_4_ITEM": 0x515a1, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_5_ITEM": 0x515af, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_6_ITEM": 0x515bd, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_7_ITEM": 0x515cb, - "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_8_ITEM": 0x515d9, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_0_ITEM": 0x51772, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_1_ITEM": 0x51780, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_2_ITEM": 0x5178e, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_3_ITEM": 0x5179c, - "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_4_ITEM": 0x517aa, - "Trainersanity_EVENT_BEAT_MOLTRES_ITEM": 0x517b8, - "Warps_VictoryRoad2F": 0x51855, - "Static_Encounter_Moltres": 0x5189f, - "Missable_Victory_Road_2F_Item_1": 0x518a7, - "Missable_Victory_Road_2F_Item_2": 0x518ae, - "Missable_Victory_Road_2F_Item_3": 0x518b5, - "Missable_Victory_Road_2F_Item_4": 0x518bc, - "Warps_MtMoonB1F": 0x5198d, - "Starter2_L": 0x51beb, - "Starter3_L": 0x51bf3, - "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_0_ITEM": 0x51ca4, - "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_1_ITEM": 0x51cb2, - "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_2_ITEM": 0x51cc0, - "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_3_ITEM": 0x51cce, - "Gift_Lapras": 0x51cef, - "Event_SKC7F": 0x51d7a, - "Warps_SilphCo7F": 0x51e49, - "Missable_Silph_Co_7F_Item_1": 0x51ea5, - "Missable_Silph_Co_7F_Item_2": 0x51eac, - "Trainersanity_EVENT_BEAT_MANSION_2_TRAINER_0_ITEM": 0x51fd2, - "Warps_PokemonMansion2F": 0x52045, - "Missable_Pokemon_Mansion_2F_Item": 0x52063, - "Trainersanity_EVENT_BEAT_MANSION_3_TRAINER_0_ITEM": 0x52213, - "Trainersanity_EVENT_BEAT_MANSION_3_TRAINER_1_ITEM": 0x52221, - "Warps_PokemonMansion3F": 0x5225e, - "Missable_Pokemon_Mansion_3F_Item_1": 0x52280, - "Missable_Pokemon_Mansion_3F_Item_2": 0x52287, - "Trainersanity_EVENT_BEAT_MANSION_4_TRAINER_0_ITEM": 0x523c9, - "Trainersanity_EVENT_BEAT_MANSION_4_TRAINER_1_ITEM": 0x523d7, - "Warps_PokemonMansionB1F": 0x52414, - "Missable_Pokemon_Mansion_B1F_Item_1": 0x5242e, - "Missable_Pokemon_Mansion_B1F_Item_2": 0x52435, - "Missable_Pokemon_Mansion_B1F_Item_3": 0x5243c, - "Missable_Pokemon_Mansion_B1F_Item_4": 0x52443, - "Missable_Pokemon_Mansion_B1F_Item_5": 0x52450, - "Option_Safari_Zone_Battle_Type": 0x52565, - "Prize_Mon_A2": 0x527ef, - "Prize_Mon_B2": 0x527f0, - "Prize_Mon_C2": 0x527f1, - "Prize_Mon_D2": 0x527fa, - "Prize_Mon_E2": 0x527fb, - "Prize_Mon_F2": 0x527fc, - "Prize_Item_A": 0x52805, - "Prize_Item_B": 0x52806, - "Prize_Item_C": 0x52807, - "Prize_Mon_A": 0x5293c, - "Prize_Mon_B": 0x5293e, - "Prize_Mon_C": 0x52940, - "Prize_Mon_D": 0x52942, - "Prize_Mon_E": 0x52944, - "Prize_Mon_F": 0x52946, - "Start_Inventory": 0x52a7b, - "Map_Fly_Location": 0x52c75, - "Reset_A": 0x52d21, - "Reset_B": 0x52d4d, - "Reset_C": 0x52d79, - "Reset_D": 0x52da5, - "Reset_E": 0x52dd1, - "Reset_F": 0x52dfd, - "Reset_G": 0x52e29, - "Reset_H": 0x52e55, - "Reset_I": 0x52e81, - "Reset_J": 0x52ead, - "Reset_K": 0x52ed9, - "Reset_L": 0x52f05, - "Reset_M": 0x52f31, - "Reset_N": 0x52f5d, - "Reset_O": 0x52f89, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_0_ITEM": 0x50d8b, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_1_ITEM": 0x50d99, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_2_ITEM": 0x50da7, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_3_ITEM": 0x50db5, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_4_ITEM": 0x50dc3, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_5_ITEM": 0x50dd1, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_6_ITEM": 0x50ddf, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_7_ITEM": 0x50ded, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_8_ITEM": 0x50dfb, + "Trainersanity_EVENT_BEAT_ROUTE_20_TRAINER_9_ITEM": 0x50e09, + "Starter2_B": 0x51026, + "Starter3_B": 0x51028, + "Starter1_B": 0x5102a, + "Starter2_A": 0x51145, + "Starter3_A": 0x51147, + "Starter1_A": 0x51149, + "Option_Route23_Badges": 0x51296, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_0_ITEM": 0x513ac, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_1_ITEM": 0x513ba, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_2_ITEM": 0x513c8, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_3_ITEM": 0x513d6, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_4_ITEM": 0x513e4, + "Trainersanity_EVENT_BEAT_ROUTE_24_TRAINER_5_ITEM": 0x513f2, + "Event_Nugget_Bridge": 0x51409, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_0_ITEM": 0x51591, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_1_ITEM": 0x5159f, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_2_ITEM": 0x515ad, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_3_ITEM": 0x515bb, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_4_ITEM": 0x515c9, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_5_ITEM": 0x515d7, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_6_ITEM": 0x515e5, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_7_ITEM": 0x515f3, + "Trainersanity_EVENT_BEAT_ROUTE_25_TRAINER_8_ITEM": 0x51601, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_0_ITEM": 0x5179a, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_1_ITEM": 0x517a8, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_2_ITEM": 0x517b6, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_3_ITEM": 0x517c4, + "Trainersanity_EVENT_BEAT_VICTORY_ROAD_2_TRAINER_4_ITEM": 0x517d2, + "Trainersanity_EVENT_BEAT_MOLTRES_ITEM": 0x517e0, + "Warps_VictoryRoad2F": 0x5187d, + "Static_Encounter_Moltres": 0x518c7, + "Missable_Victory_Road_2F_Item_1": 0x518cf, + "Missable_Victory_Road_2F_Item_2": 0x518d6, + "Missable_Victory_Road_2F_Item_3": 0x518dd, + "Missable_Victory_Road_2F_Item_4": 0x518e4, + "Warps_MtMoonB1F": 0x519b5, + "Starter2_L": 0x51c13, + "Starter3_L": 0x51c1b, + "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_0_ITEM": 0x51ccc, + "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_1_ITEM": 0x51cda, + "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_2_ITEM": 0x51ce8, + "Trainersanity_EVENT_BEAT_SILPH_CO_7F_TRAINER_3_ITEM": 0x51cf6, + "Gift_Lapras": 0x51d17, + "Event_SKC7F": 0x51da2, + "Warps_SilphCo7F": 0x51e71, + "Missable_Silph_Co_7F_Item_1": 0x51ecd, + "Missable_Silph_Co_7F_Item_2": 0x51ed4, + "Trainersanity_EVENT_BEAT_MANSION_2_TRAINER_0_ITEM": 0x51ffa, + "Warps_PokemonMansion2F": 0x5206d, + "Missable_Pokemon_Mansion_2F_Item": 0x5208b, + "Trainersanity_EVENT_BEAT_MANSION_3_TRAINER_0_ITEM": 0x5223b, + "Trainersanity_EVENT_BEAT_MANSION_3_TRAINER_1_ITEM": 0x52249, + "Warps_PokemonMansion3F": 0x52286, + "Missable_Pokemon_Mansion_3F_Item_1": 0x522a8, + "Missable_Pokemon_Mansion_3F_Item_2": 0x522af, + "Trainersanity_EVENT_BEAT_MANSION_4_TRAINER_0_ITEM": 0x523f1, + "Trainersanity_EVENT_BEAT_MANSION_4_TRAINER_1_ITEM": 0x523ff, + "Warps_PokemonMansionB1F": 0x5243c, + "Missable_Pokemon_Mansion_B1F_Item_1": 0x52456, + "Missable_Pokemon_Mansion_B1F_Item_2": 0x5245d, + "Missable_Pokemon_Mansion_B1F_Item_3": 0x52464, + "Missable_Pokemon_Mansion_B1F_Item_4": 0x5246b, + "Missable_Pokemon_Mansion_B1F_Item_5": 0x52478, + "Option_Safari_Zone_Battle_Type": 0x5258d, + "Prize_Mon_A2": 0x52817, + "Prize_Mon_B2": 0x52818, + "Prize_Mon_C2": 0x52819, + "Prize_Mon_D2": 0x52822, + "Prize_Mon_E2": 0x52823, + "Prize_Mon_F2": 0x52824, + "Prize_Item_A": 0x5282d, + "Prize_Item_B": 0x5282e, + "Prize_Item_C": 0x5282f, + "Prize_Mon_A": 0x52964, + "Prize_Mon_B": 0x52966, + "Prize_Mon_C": 0x52968, + "Prize_Mon_D": 0x5296a, + "Prize_Mon_E": 0x5296c, + "Prize_Mon_F": 0x5296e, + "Start_Inventory": 0x52aa3, + "Map_Fly_Location": 0x52c9d, + "Reset_A": 0x52d49, + "Reset_B": 0x52d75, + "Reset_C": 0x52da1, + "Reset_D": 0x52dcd, + "Reset_E": 0x52df9, + "Reset_F": 0x52e25, + "Reset_G": 0x52e51, + "Reset_H": 0x52e7d, + "Reset_I": 0x52ea9, + "Reset_J": 0x52ed5, + "Reset_K": 0x52f01, + "Reset_L": 0x52f2d, + "Reset_M": 0x52f59, + "Reset_N": 0x52f85, + "Reset_O": 0x52fb1, "Warps_Route2": 0x54026, "Missable_Route_2_Item_1": 0x5404a, "Missable_Route_2_Item_2": 0x54051, @@ -1539,16 +1539,18 @@ rom_addresses = { "Event_SKC11F": 0x623bd, "Warps_SilphCo11F": 0x62446, "Ghost_Battle4": 0x708e1, - "Trade_Terry": 0x71b77, - "Trade_Marcel": 0x71b85, - "Trade_Sailor": 0x71ba1, - "Trade_Dux": 0x71baf, - "Trade_Marc": 0x71bbd, - "Trade_Lola": 0x71bcb, - "Trade_Doris": 0x71bd9, - "Trade_Crinkles": 0x71be7, - "Trade_Spot": 0x71bf5, - "Mon_Palettes": 0x725d3, + "Town_Map_Order": 0x70f0f, + "Town_Map_Coords": 0x71381, + "Trade_Terry": 0x71b7a, + "Trade_Marcel": 0x71b88, + "Trade_Sailor": 0x71ba4, + "Trade_Dux": 0x71bb2, + "Trade_Marc": 0x71bc0, + "Trade_Lola": 0x71bce, + "Trade_Doris": 0x71bdc, + "Trade_Crinkles": 0x71bea, + "Trade_Spot": 0x71bf8, + "Mon_Palettes": 0x725d6, "Badge_Viridian_Gym": 0x749d9, "Event_Viridian_Gym": 0x749ed, "Trainersanity_EVENT_BEAT_VIRIDIAN_GYM_TRAINER_0_ITEM": 0x74a48, From db30a0116e66abdef37d0c83fadba8a194c4f526 Mon Sep 17 00:00:00 2001 From: PoryGone <98504756+PoryGone@users.noreply.github.com> Date: Tue, 5 Mar 2024 18:55:56 -0500 Subject: [PATCH 080/166] Celeste 64: Implement New Game (#2798) Co-authored-by: chandler05 <66492208+chandler05@users.noreply.github.com> Co-authored-by: Silvris <58583688+Silvris@users.noreply.github.com> Co-authored-by: Zach Parks --- README.md | 1 + docs/CODEOWNERS | 3 + worlds/celeste64/Items.py | 58 ++++++++++ worlds/celeste64/Locations.py | 142 +++++++++++++++++++++++++ worlds/celeste64/Names/ItemName.py | 11 ++ worlds/celeste64/Names/LocationName.py | 31 ++++++ worlds/celeste64/Names/__init__.py | 0 worlds/celeste64/Options.py | 25 +++++ worlds/celeste64/Regions.py | 11 ++ worlds/celeste64/Rules.py | 104 ++++++++++++++++++ worlds/celeste64/__init__.py | 92 ++++++++++++++++ worlds/celeste64/docs/en_Celeste 64.md | 24 +++++ worlds/celeste64/docs/guide_en.md | 32 ++++++ 13 files changed, 534 insertions(+) create mode 100644 worlds/celeste64/Items.py create mode 100644 worlds/celeste64/Locations.py create mode 100644 worlds/celeste64/Names/ItemName.py create mode 100644 worlds/celeste64/Names/LocationName.py create mode 100644 worlds/celeste64/Names/__init__.py create mode 100644 worlds/celeste64/Options.py create mode 100644 worlds/celeste64/Regions.py create mode 100644 worlds/celeste64/Rules.py create mode 100644 worlds/celeste64/__init__.py create mode 100644 worlds/celeste64/docs/en_Celeste 64.md create mode 100644 worlds/celeste64/docs/guide_en.md diff --git a/README.md b/README.md index 4a3c53548c..3c3c41475b 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Currently, the following games are supported: * Final Fantasy Mystic Quest * TUNIC * Kirby's Dream Land 3 +* Celeste 64 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 6ec3802ede..d6730b7308 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -28,6 +28,9 @@ # Bumper Stickers /worlds/bumpstik/ @FelicitusNeko +# Celeste 64 +/worlds/celeste64/ @PoryGone + # ChecksFinder /worlds/checksfinder/ @jonloveslegos diff --git a/worlds/celeste64/Items.py b/worlds/celeste64/Items.py new file mode 100644 index 0000000000..94db0e8ef4 --- /dev/null +++ b/worlds/celeste64/Items.py @@ -0,0 +1,58 @@ +from typing import Dict, NamedTuple, Optional + +from BaseClasses import Item, ItemClassification +from .Names import ItemName + + +celeste_64_base_id: int = 0xCA0000 + + +class Celeste64Item(Item): + game = "Celeste 64" + + +class Celeste64ItemData(NamedTuple): + code: Optional[int] = None + type: ItemClassification = ItemClassification.filler + + +item_data_table: Dict[str, Celeste64ItemData] = { + ItemName.strawberry: Celeste64ItemData( + code = celeste_64_base_id + 0, + type=ItemClassification.progression_skip_balancing, + ), + ItemName.dash_refill: Celeste64ItemData( + code = celeste_64_base_id + 1, + type=ItemClassification.progression, + ), + ItemName.double_dash_refill: Celeste64ItemData( + code = celeste_64_base_id + 2, + type=ItemClassification.progression, + ), + ItemName.feather: Celeste64ItemData( + code = celeste_64_base_id + 3, + type=ItemClassification.progression, + ), + ItemName.coin: Celeste64ItemData( + code = celeste_64_base_id + 4, + type=ItemClassification.progression, + ), + ItemName.cassette: Celeste64ItemData( + code = celeste_64_base_id + 5, + type=ItemClassification.progression, + ), + ItemName.traffic_block: Celeste64ItemData( + code = celeste_64_base_id + 6, + type=ItemClassification.progression, + ), + ItemName.spring: Celeste64ItemData( + code = celeste_64_base_id + 7, + type=ItemClassification.progression, + ), + ItemName.breakables: Celeste64ItemData( + code = celeste_64_base_id + 8, + type=ItemClassification.progression, + ) +} + +item_table = {name: data.code for name, data in item_data_table.items() if data.code is not None} diff --git a/worlds/celeste64/Locations.py b/worlds/celeste64/Locations.py new file mode 100644 index 0000000000..92ca425f83 --- /dev/null +++ b/worlds/celeste64/Locations.py @@ -0,0 +1,142 @@ +from typing import Dict, NamedTuple, Optional + +from BaseClasses import Location +from .Names import LocationName + + +celeste_64_base_id: int = 0xCA0000 + + +class Celeste64Location(Location): + game = "Celeste 64" + + +class Celeste64LocationData(NamedTuple): + region: str + address: Optional[int] = None + + +location_data_table: Dict[str, Celeste64LocationData] = { + LocationName.strawberry_1 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 0, + ), + LocationName.strawberry_2 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 1, + ), + LocationName.strawberry_3 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 2, + ), + LocationName.strawberry_4 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 3, + ), + LocationName.strawberry_5 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 4, + ), + LocationName.strawberry_6 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 5, + ), + LocationName.strawberry_7 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 6, + ), + LocationName.strawberry_8 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 7, + ), + LocationName.strawberry_9 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 8, + ), + LocationName.strawberry_10 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 9, + ), + LocationName.strawberry_11 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 10, + ), + LocationName.strawberry_12 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 11, + ), + LocationName.strawberry_13 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 12, + ), + LocationName.strawberry_14 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 13, + ), + LocationName.strawberry_15 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 14, + ), + LocationName.strawberry_16 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 15, + ), + LocationName.strawberry_17 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 16, + ), + LocationName.strawberry_18 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 17, + ), + LocationName.strawberry_19 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 18, + ), + LocationName.strawberry_20 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 19, + ), + LocationName.strawberry_21 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 20, + ), + LocationName.strawberry_22 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 21, + ), + LocationName.strawberry_23 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 22, + ), + LocationName.strawberry_24 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 23, + ), + LocationName.strawberry_25 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 24, + ), + LocationName.strawberry_26 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 25, + ), + LocationName.strawberry_27 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 26, + ), + LocationName.strawberry_28 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 27, + ), + LocationName.strawberry_29 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 28, + ), + LocationName.strawberry_30 : Celeste64LocationData( + region = "Forsaken City", + address = celeste_64_base_id + 29, + ) +} + +location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None} diff --git a/worlds/celeste64/Names/ItemName.py b/worlds/celeste64/Names/ItemName.py new file mode 100644 index 0000000000..5e4daf8e4f --- /dev/null +++ b/worlds/celeste64/Names/ItemName.py @@ -0,0 +1,11 @@ +strawberry = "Strawberry" + +dash_refill = "Dash Refills" +double_dash_refill = "Double Dash Refills" +feather = "Feathers" +coin = "Coins" +cassette = "Cassettes" + +traffic_block = "Traffic Blocks" +spring = "Springs" +breakables = "Breakable Blocks" diff --git a/worlds/celeste64/Names/LocationName.py b/worlds/celeste64/Names/LocationName.py new file mode 100644 index 0000000000..a9902f70f7 --- /dev/null +++ b/worlds/celeste64/Names/LocationName.py @@ -0,0 +1,31 @@ +# Strawberry Locations +strawberry_1 = "First Strawberry" +strawberry_2 = "Floating Blocks Strawberry" +strawberry_3 = "South-East Tower Top Strawberry" +strawberry_4 = "Theo Strawberry" +strawberry_5 = "Fall Through Spike Floor Strawberry" +strawberry_6 = "Troll Strawberry" +strawberry_7 = "Falling Blocks Strawberry" +strawberry_8 = "Traffic Block Strawberry" +strawberry_9 = "South-West Dash Refills Strawberry" +strawberry_10 = "South-East Tower Side Strawberry" +strawberry_11 = "Girders Strawberry" +strawberry_12 = "North-East Tower Bottom Strawberry" +strawberry_13 = "Breakable Blocks Strawberry" +strawberry_14 = "Feather Maze Strawberry" +strawberry_15 = "Feather Chain Strawberry" +strawberry_16 = "Feather Hidden Strawberry" +strawberry_17 = "Double Dash Puzzle Strawberry" +strawberry_18 = "Double Dash Spike Climb Strawberry" +strawberry_19 = "Double Dash Spring Strawberry" +strawberry_20 = "North-East Tower Breakable Bottom Strawberry" +strawberry_21 = "Theo Tower Lower Cassette Strawberry" +strawberry_22 = "Theo Tower Upper Cassette Strawberry" +strawberry_23 = "South End of Bridge Cassette Strawberry" +strawberry_24 = "You Are Ready Cassette Strawberry" +strawberry_25 = "Cassette Hidden in the House Strawberry" +strawberry_26 = "North End of Bridge Cassette Strawberry" +strawberry_27 = "Distant Feather Cassette Strawberry" +strawberry_28 = "Feather Arches Cassette Strawberry" +strawberry_29 = "North-East Tower Cassette Strawberry" +strawberry_30 = "Badeline Cassette Strawberry" diff --git a/worlds/celeste64/Names/__init__.py b/worlds/celeste64/Names/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/worlds/celeste64/Options.py b/worlds/celeste64/Options.py new file mode 100644 index 0000000000..f94fbb0293 --- /dev/null +++ b/worlds/celeste64/Options.py @@ -0,0 +1,25 @@ +from dataclasses import dataclass + +from Options import Range, DeathLink, PerGameCommonOptions + + +class StrawberriesRequired(Range): + """How many Strawberries you must receive to finish""" + display_name = "Strawberries Required" + range_start = 0 + range_end = 20 + default = 15 + +class DeathLinkAmnesty(Range): + """How many deaths it takes to send a DeathLink""" + display_name = "Death Link Amnesty" + range_start = 1 + range_end = 30 + default = 10 + + +@dataclass +class Celeste64Options(PerGameCommonOptions): + death_link: DeathLink + death_link_amnesty: DeathLinkAmnesty + strawberries_required: StrawberriesRequired diff --git a/worlds/celeste64/Regions.py b/worlds/celeste64/Regions.py new file mode 100644 index 0000000000..6f01c873a4 --- /dev/null +++ b/worlds/celeste64/Regions.py @@ -0,0 +1,11 @@ +from typing import Dict, List, NamedTuple + + +class Celeste64RegionData(NamedTuple): + connecting_regions: List[str] = [] + + +region_data_table: Dict[str, Celeste64RegionData] = { + "Menu": Celeste64RegionData(["Forsaken City"]), + "Forsaken City": Celeste64RegionData(), +} diff --git a/worlds/celeste64/Rules.py b/worlds/celeste64/Rules.py new file mode 100644 index 0000000000..3baa231892 --- /dev/null +++ b/worlds/celeste64/Rules.py @@ -0,0 +1,104 @@ +from worlds.generic.Rules import set_rule + +from . import Celeste64World +from .Names import ItemName, LocationName + + +def set_rules(world: Celeste64World): + set_rule(world.multiworld.get_location(LocationName.strawberry_4, world.player), + lambda state: state.has_all({ItemName.traffic_block, + ItemName.breakables}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_5, world.player), + lambda state: state.has(ItemName.breakables, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_6, world.player), + lambda state: state.has(ItemName.dash_refill, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_8, world.player), + lambda state: state.has(ItemName.traffic_block, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_9, world.player), + lambda state: state.has(ItemName.dash_refill, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_11, world.player), + lambda state: state.has(ItemName.dash_refill, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_12, world.player), + lambda state: state.has_all({ItemName.dash_refill, + ItemName.double_dash_refill}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_13, world.player), + lambda state: state.has_all({ItemName.dash_refill, + ItemName.breakables}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_14, world.player), + lambda state: state.has_all({ItemName.dash_refill, + ItemName.feather}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_15, world.player), + lambda state: state.has_all({ItemName.dash_refill, + ItemName.feather}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_16, world.player), + lambda state: state.has_all({ItemName.dash_refill, + ItemName.feather}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_17, world.player), + lambda state: state.has_all({ItemName.dash_refill, + ItemName.double_dash_refill, + ItemName.feather, + ItemName.traffic_block}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_18, world.player), + lambda state: state.has(ItemName.double_dash_refill, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_19, world.player), + lambda state: state.has_all({ItemName.double_dash_refill, + ItemName.spring}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_20, world.player), + lambda state: state.has_all({ItemName.dash_refill, + ItemName.feather, + ItemName.breakables}, world.player)) + + set_rule(world.multiworld.get_location(LocationName.strawberry_21, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.traffic_block, + ItemName.breakables}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_22, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.dash_refill, + ItemName.breakables}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_23, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.dash_refill, + ItemName.coin}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_24, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.traffic_block, + ItemName.dash_refill}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_25, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.dash_refill, + ItemName.double_dash_refill}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_26, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.dash_refill}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_27, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.feather, + ItemName.coin, + ItemName.dash_refill}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_28, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.feather, + ItemName.coin, + ItemName.dash_refill}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_29, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.feather, + ItemName.coin, + ItemName.dash_refill}, world.player)) + set_rule(world.multiworld.get_location(LocationName.strawberry_30, world.player), + lambda state: state.has_all({ItemName.cassette, + ItemName.feather, + ItemName.traffic_block, + ItemName.spring, + ItemName.breakables, + ItemName.dash_refill, + ItemName.double_dash_refill}, world.player)) + + # Completion condition. + world.multiworld.completion_condition[world.player] = lambda state: (state.has(ItemName.strawberry,world.player,world.options.strawberries_required.value) and + state.has_all({ItemName.feather, + ItemName.traffic_block, + ItemName.breakables, + ItemName.dash_refill, + ItemName.double_dash_refill}, world.player)) diff --git a/worlds/celeste64/__init__.py b/worlds/celeste64/__init__.py new file mode 100644 index 0000000000..0d3b5d0158 --- /dev/null +++ b/worlds/celeste64/__init__.py @@ -0,0 +1,92 @@ +from typing import List + +from BaseClasses import ItemClassification, Region, Tutorial +from worlds.AutoWorld import WebWorld, World +from .Items import Celeste64Item, item_data_table, item_table +from .Locations import Celeste64Location, location_data_table, location_table +from .Names import ItemName +from .Options import Celeste64Options + + +class Celeste64WebWorld(WebWorld): + theme = "ice" + + setup_en = Tutorial( + tutorial_name="Start Guide", + description="A guide to playing Celeste 64 in Archipelago.", + language="English", + file_name="guide_en.md", + link="guide/en", + authors=["PoryGone"] + ) + + tutorials = [setup_en] + + +class Celeste64World(World): + """Relive the magic of Celeste Mountain alongside Madeline in this small, heartfelt 3D platformer. + Created in a week(ish) by the Celeste team to celebrate the game’s sixth anniversary 🍓✨""" + + game = "Celeste 64" + web = Celeste64WebWorld() + options_dataclass = Celeste64Options + options: Celeste64Options + location_name_to_id = location_table + item_name_to_id = item_table + + + def create_item(self, name: str) -> Celeste64Item: + # Only make required amount of strawberries be Progression + if getattr(self, "options", None) and name == ItemName.strawberry: + classification: ItemClassification = ItemClassification.filler + self.prog_strawberries = getattr(self, "prog_strawberries", 0) + if self.prog_strawberries < self.options.strawberries_required.value: + classification = ItemClassification.progression_skip_balancing + self.prog_strawberries += 1 + + return Celeste64Item(name, classification, item_data_table[name].code, self.player) + else: + return Celeste64Item(name, item_data_table[name].type, item_data_table[name].code, self.player) + + def create_items(self) -> None: + item_pool: List[Celeste64Item] = [] + + item_pool += [self.create_item(name) for name in item_data_table.keys()] + + item_pool += [self.create_item(ItemName.strawberry) for _ in range(21)] + + self.multiworld.itempool += item_pool + + + def create_regions(self) -> None: + from .Regions import region_data_table + # Create regions. + for region_name in region_data_table.keys(): + region = Region(region_name, self.player, self.multiworld) + self.multiworld.regions.append(region) + + # Create locations. + for region_name, region_data in region_data_table.items(): + region = self.multiworld.get_region(region_name, self.player) + region.add_locations({ + location_name: location_data.address for location_name, location_data in location_data_table.items() + if location_data.region == region_name + }, Celeste64Location) + region.add_exits(region_data_table[region_name].connecting_regions) + + + def get_filler_item_name(self) -> str: + return ItemName.strawberry + + + def set_rules(self) -> None: + from .Rules import set_rules + set_rules(self) + + + def fill_slot_data(self): + return { + "death_link": self.options.death_link.value, + "death_link_amnesty": self.options.death_link_amnesty.value, + "strawberries_required": self.options.strawberries_required.value + } diff --git a/worlds/celeste64/docs/en_Celeste 64.md b/worlds/celeste64/docs/en_Celeste 64.md new file mode 100644 index 0000000000..efc42bfe56 --- /dev/null +++ b/worlds/celeste64/docs/en_Celeste 64.md @@ -0,0 +1,24 @@ +# Celeste 64 + +## What is this game? + +Relive the magic of Celeste Mountain alongside Madeline in this small, heartfelt 3D platformer. +Created in a week(ish) by the Celeste team to celebrate the game's sixth anniversary. + +Ported to Archipelago in a week(ish) by PoryGone, this World provides the following as unlockable items: +- Strawberries +- Dash Refills +- Double Dash Refills +- Feathers +- Coins +- Cassettes +- Traffic Blocks +- Springs +- Breakable Blocks + +The goal is to collect a certain number of Strawberries, then visit Badeline on her floating island. + +## 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. diff --git a/worlds/celeste64/docs/guide_en.md b/worlds/celeste64/docs/guide_en.md new file mode 100644 index 0000000000..116a3b13e9 --- /dev/null +++ b/worlds/celeste64/docs/guide_en.md @@ -0,0 +1,32 @@ +# Celeste 64 Setup Guide + +## Required Software +- Archipelago Build of Celeste 64 from: [Celeste 64 Archipelago Releases Page](https://github.com/PoryGoneDev/Celeste64/releases/) + +## Installation Procedures (Windows) + +1. Download the above release and extract it. + +## Joining a MultiWorld Game + +1. Before launching the game, edit the `AP.json` file in the root of the Celeste 64 install. + +2. For the `Url` field, enter the address of the server, such as `archipelago.gg:38281`. Your server host should be able to tell you this. + +3. For the `SlotName` field, enter your "name" field from the yaml or website config. + +4. For the `Password` field, enter the server password if one exists; otherwise leave this field blank. + +5. Save the file, and run `Celeste64.exe`. If you can continue past the title screen, then you are successfully connected. + +An Example `AP.json` file: + +``` +{ + "Url": "archipelago:12345", + "SlotName": "Maddy", + "Password": "" +} +``` + + From e33ea0147bd19bcf4b03bb2cd25ff1d18aaa89f8 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Wed, 6 Mar 2024 10:43:34 -0500 Subject: [PATCH 081/166] LTTP: Missed per_slot_random change (#2907) --- worlds/alttp/Shops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/alttp/Shops.py b/worlds/alttp/Shops.py index 1d548d8fdb..dbe8cc1f9d 100644 --- a/worlds/alttp/Shops.py +++ b/worlds/alttp/Shops.py @@ -453,7 +453,7 @@ def get_price(multiworld, item, player: int, price_type=None): price = item["price"] if multiworld.randomize_shop_prices[player]: adjust = 2 if price < 100 else 5 - price = int((price / adjust) * (0.5 + multiworld.random.random() * 1.5)) * adjust + price = int((price / adjust) * (0.5 + multiworld.per_slot_randoms[player].random() * 1.5)) * adjust multiworld.per_slot_randoms[player].shuffle(price_types) for p_type in price_types: if any(x in item['item'] for x in price_blacklist[p_type]): From ac791f299975cdc0ef6ce34f03c215bc6d830992 Mon Sep 17 00:00:00 2001 From: BadMagic100 Date: Wed, 6 Mar 2024 23:24:08 -0800 Subject: [PATCH 082/166] CI: Avoid race condition in labeler workflow (#2910) --- .github/workflows/label-pull-requests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/label-pull-requests.yml b/.github/workflows/label-pull-requests.yml index 42881aa49d..e26f6f34a4 100644 --- a/.github/workflows/label-pull-requests.yml +++ b/.github/workflows/label-pull-requests.yml @@ -18,6 +18,7 @@ jobs: sync-labels: true peer_review: name: 'Apply peer review label' + needs: labeler if: >- (github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'ready_for_review') && !github.event.pull_request.draft @@ -30,6 +31,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} unblock_draft_prs: name: 'Remove waiting-on labels' + needs: labeler if: github.event.action == 'converted_to_draft' || github.event.action == 'closed' runs-on: ubuntu-latest steps: From c6b1039e0fc5a8b41c934ecb96534c9e937985eb Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Thu, 7 Mar 2024 01:40:09 -0600 Subject: [PATCH 083/166] Core: call from_any on the options class when creating item links options (#2783) --- worlds/AutoWorld.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index dd0f46f6a6..4179637199 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -422,9 +422,9 @@ class World(metaclass=AutoWorldRegister): An example case is ItemLinks creating these.""" # TODO remove loop when worlds use options dataclass for option_key, option in cls.options_dataclass.type_hints.items(): - getattr(multiworld, option_key)[new_player_id] = option(option.default) + getattr(multiworld, option_key)[new_player_id] = option.from_any(option.default) group = cls(multiworld, new_player_id) - group.options = cls.options_dataclass(**{option_key: option(option.default) + group.options = cls.options_dataclass(**{option_key: option.from_any(option.default) for option_key, option in cls.options_dataclass.type_hints.items()}) return group From 862d77820d0a2826a8a426869c3b7149173a336a Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Thu, 7 Mar 2024 02:48:55 -0500 Subject: [PATCH 084/166] Fill: Improve clarity of remaining_fill messages (#2894) * Update Fill.py Make the fill message more descriptive * Removing kwarg --- Fill.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Fill.py b/Fill.py index ae44710469..2d6257eae3 100644 --- a/Fill.py +++ b/Fill.py @@ -208,7 +208,8 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati def remaining_fill(multiworld: MultiWorld, locations: typing.List[Location], - itempool: typing.List[Item]) -> None: + itempool: typing.List[Item], + name: str = "Remaining") -> None: unplaced_items: typing.List[Item] = [] placements: typing.List[Location] = [] swapped_items: typing.Counter[typing.Tuple[int, str]] = Counter() @@ -265,10 +266,10 @@ def remaining_fill(multiworld: MultiWorld, placements.append(spot_to_fill) placed += 1 if not placed % 1000: - _log_fill_progress("Remaining", placed, total) + _log_fill_progress(name, placed, total) if total > 1000: - _log_fill_progress("Remaining", placed, total) + _log_fill_progress(name, placed, total) if unplaced_items and locations: # There are leftover unplaceable items and locations that won't accept them @@ -466,7 +467,7 @@ def distribute_items_restrictive(multiworld: MultiWorld) -> None: inaccessible_location_rules(multiworld, multiworld.state, defaultlocations) - remaining_fill(multiworld, excludedlocations, filleritempool) + remaining_fill(multiworld, excludedlocations, filleritempool, "Remaining Excluded") if excludedlocations: raise FillError( f"Not enough filler items for excluded locations. There are {len(excludedlocations)} more locations than items") From b4bb88fcf7986859bb9efb2d6a159fcebcbab105 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Thu, 7 Mar 2024 03:18:22 -0600 Subject: [PATCH 085/166] SNIClient: dynamically generate patch file identifier (#2870) Co-authored-by: beauxq --- worlds/AutoSNIClient.py | 45 +++++++++++++++++++++++++++++++++++- worlds/LauncherComponents.py | 4 ---- worlds/alttp/Client.py | 1 + worlds/dkc3/Client.py | 1 + worlds/lufia2ac/Client.py | 1 + worlds/sm/Client.py | 1 + worlds/smw/Client.py | 1 + worlds/smz3/Client.py | 1 + 8 files changed, 50 insertions(+), 5 deletions(-) diff --git a/worlds/AutoSNIClient.py b/worlds/AutoSNIClient.py index a30dbbb46d..2b984d9c88 100644 --- a/worlds/AutoSNIClient.py +++ b/worlds/AutoSNIClient.py @@ -1,11 +1,35 @@ from __future__ import annotations import abc -from typing import TYPE_CHECKING, ClassVar, Dict, Tuple, Any, Optional +from typing import TYPE_CHECKING, ClassVar, Dict, Iterable, Tuple, Any, Optional, Union + +from typing_extensions import TypeGuard + +from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components if TYPE_CHECKING: from SNIClient import SNIContext +component = Component('SNI Client', 'SNIClient', component_type=Type.CLIENT, file_identifier=SuffixIdentifier(".apsoe")) +components.append(component) + + +def valid_patch_suffix(obj: object) -> TypeGuard[Union[str, Iterable[str]]]: + """ make sure this is a valid value for the class variable `patch_suffix` """ + + def valid_individual(one: object) -> TypeGuard[str]: + """ check an individual suffix """ + # TODO: decide: len(one) > 3 and one.startswith(".ap") ? + # or keep it more general? + return isinstance(one, str) and len(one) > 1 and one.startswith(".") + + if isinstance(obj, str): + return valid_individual(obj) + if not isinstance(obj, Iterable): + return False + obj_it: Iterable[object] = obj + return all(valid_individual(each) for each in obj_it) + class AutoSNIClientRegister(abc.ABCMeta): game_handlers: ClassVar[Dict[str, SNIClient]] = {} @@ -15,6 +39,22 @@ class AutoSNIClientRegister(abc.ABCMeta): new_class = super().__new__(cls, name, bases, dct) if "game" in dct: AutoSNIClientRegister.game_handlers[dct["game"]] = new_class() + + if "patch_suffix" in dct: + patch_suffix = dct["patch_suffix"] + assert valid_patch_suffix(patch_suffix), f"class {name} defining invalid {patch_suffix=}" + + existing_identifier = component.file_identifier + assert isinstance(existing_identifier, SuffixIdentifier), f"{existing_identifier=}" + new_suffixes = [*existing_identifier.suffixes] + + if isinstance(patch_suffix, str): + new_suffixes.append(patch_suffix) + else: + new_suffixes.extend(patch_suffix) + + component.file_identifier = SuffixIdentifier(*new_suffixes) + return new_class @staticmethod @@ -27,6 +67,9 @@ class AutoSNIClientRegister(abc.ABCMeta): class SNIClient(abc.ABC, metaclass=AutoSNIClientRegister): + patch_suffix: ClassVar[Union[str, Iterable[str]]] = () + """The file extension(s) this client is meant to open and patch (e.g. ".aplttp")""" + @abc.abstractmethod async def validate_rom(self, ctx: SNIContext) -> bool: """ TODO: interface documentation here """ diff --git a/worlds/LauncherComponents.py b/worlds/LauncherComponents.py index 7814aac5ae..41c0bb8329 100644 --- a/worlds/LauncherComponents.py +++ b/worlds/LauncherComponents.py @@ -85,10 +85,6 @@ components: List[Component] = [ file_identifier=SuffixIdentifier('.archipelago', '.zip')), Component('Generate', 'Generate', cli=True), Component('Text Client', 'CommonClient', 'ArchipelagoTextClient', func=launch_textclient), - # SNI - Component('SNI Client', 'SNIClient', - file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3', '.apdkc3', - '.apsmw', '.apl2ac', '.apkdl3')), Component('Links Awakening DX Client', 'LinksAwakeningClient', file_identifier=SuffixIdentifier('.apladx')), Component('LttP Adjuster', 'LttPAdjuster'), diff --git a/worlds/alttp/Client.py b/worlds/alttp/Client.py index edc68473b9..5b27f559ef 100644 --- a/worlds/alttp/Client.py +++ b/worlds/alttp/Client.py @@ -471,6 +471,7 @@ async def track_locations(ctx, roomid, roomdata) -> bool: class ALTTPSNIClient(SNIClient): game = "A Link to the Past" + patch_suffix = [".aplttp", ".apz3"] async def deathlink_kill_player(self, ctx): from SNIClient import DeathState, snes_read, snes_buffered_write, snes_flush_writes diff --git a/worlds/dkc3/Client.py b/worlds/dkc3/Client.py index 77ed51fecb..efa199e1d0 100644 --- a/worlds/dkc3/Client.py +++ b/worlds/dkc3/Client.py @@ -24,6 +24,7 @@ DEATH_LINK_ACTIVE_ADDR = DKC3_ROMNAME_START + 0x15 # DKC3_TODO: Find a perma class DKC3SNIClient(SNIClient): game = "Donkey Kong Country 3" + patch_suffix = ".apdkc3" async def deathlink_kill_player(self, ctx): pass diff --git a/worlds/lufia2ac/Client.py b/worlds/lufia2ac/Client.py index ac0de19bfd..9025a1137b 100644 --- a/worlds/lufia2ac/Client.py +++ b/worlds/lufia2ac/Client.py @@ -30,6 +30,7 @@ L2AC_RX_ADDR: int = SRAM_START + 0x2800 class L2ACSNIClient(SNIClient): game: str = "Lufia II Ancient Cave" + patch_suffix = ".apl2ac" async def validate_rom(self, ctx: SNIContext) -> bool: from SNIClient import snes_read diff --git a/worlds/sm/Client.py b/worlds/sm/Client.py index 756fd4bf36..ed3f2d5b3d 100644 --- a/worlds/sm/Client.py +++ b/worlds/sm/Client.py @@ -37,6 +37,7 @@ SM_REMOTE_ITEM_FLAG_ADDR = ROM_START + 0x277F06 # 1 byte class SMSNIClient(SNIClient): game = "Super Metroid" + patch_suffix = [".apsm", ".apm3"] async def deathlink_kill_player(self, ctx): from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read diff --git a/worlds/smw/Client.py b/worlds/smw/Client.py index 92aeac4d4a..50899abe37 100644 --- a/worlds/smw/Client.py +++ b/worlds/smw/Client.py @@ -57,6 +57,7 @@ SMW_UNCOLLECTABLE_LEVELS = [0x25, 0x07, 0x0B, 0x40, 0x0E, 0x1F, 0x20, 0x1B, 0x1A class SMWSNIClient(SNIClient): game = "Super Mario World" + patch_suffix = ".apsmw" async def deathlink_kill_player(self, ctx): from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read diff --git a/worlds/smz3/Client.py b/worlds/smz3/Client.py index b07aa850c3..0a248aa5d3 100644 --- a/worlds/smz3/Client.py +++ b/worlds/smz3/Client.py @@ -32,6 +32,7 @@ SMZ3_RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte class SMZ3SNIClient(SNIClient): game = "SMZ3" + patch_suffix = ".apsmz3" async def validate_rom(self, ctx): from SNIClient import snes_buffered_write, snes_flush_writes, snes_read From 3e0ff3f72dc9e3a6bc9817d64def2ada7a00eee8 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Fri, 8 Mar 2024 00:46:14 -0600 Subject: [PATCH 086/166] KDL3: Post-PR adjustments (#2917) * logic adjustments, add patch_suffix * forgot the other two consumables --- worlds/kdl3/Client.py | 4 +++- worlds/kdl3/Rules.py | 42 +++++++++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/worlds/kdl3/Client.py b/worlds/kdl3/Client.py index 3fef042900..c10dd6cebb 100644 --- a/worlds/kdl3/Client.py +++ b/worlds/kdl3/Client.py @@ -97,6 +97,7 @@ def cmd_gift(self: "SNIClientCommandProcessor"): class KDL3SNIClient(SNIClient): game = "Kirby's Dream Land 3" + patch_suffix = ".apkdl3" levels = None consumables = None stars = None @@ -406,7 +407,8 @@ class KDL3SNIClient(SNIClient): ctx.locations_checked.add(new_check_id) location = ctx.location_names[new_check_id] snes_logger.info( - f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') + f'New Check: {location} ({len(ctx.locations_checked)}/' + f'{len(ctx.missing_locations) + len(ctx.checked_locations)})') await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}]) except Exception as ex: # we crashed, so print log and clean up diff --git a/worlds/kdl3/Rules.py b/worlds/kdl3/Rules.py index 81ad8f1f1f..91abc21d06 100644 --- a/worlds/kdl3/Rules.py +++ b/worlds/kdl3/Rules.py @@ -206,13 +206,19 @@ def set_rules(world: "KDL3World") -> None: lambda state: can_reach_needle(state, world.player)) set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u2, world.player), lambda state: can_reach_ice(state, world.player) and - (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) + or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) + or can_reach_nago(state, world.player))) set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u3, world.player), lambda state: can_reach_ice(state, world.player) and - (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) + or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) + or can_reach_nago(state, world.player))) set_rule(world.multiworld.get_location(LocationName.sand_canyon_5_u4, world.player), lambda state: can_reach_ice(state, world.player) and - (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) + or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) + or can_reach_nago(state, world.player))) set_rule(world.multiworld.get_location(LocationName.cloudy_park_6_u1, world.player), lambda state: can_reach_cutter(state, world.player)) @@ -242,7 +248,9 @@ def set_rules(world: "KDL3World") -> None: for i in range(12, 18): set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player), lambda state: can_reach_ice(state, world.player) and - (can_reach_rick(state, world.player) or can_reach_coo(state, world.player))) + (can_reach_rick(state, world.player) or can_reach_coo(state, world.player) + or can_reach_chuchu(state, world.player) or can_reach_pitch(state, world.player) + or can_reach_nago(state, world.player))) for i in range(21, 23): set_rule(world.multiworld.get_location(f"Sand Canyon 5 - Star {i}", world.player), lambda state: can_reach_chuchu(state, world.player)) @@ -267,32 +275,32 @@ def set_rules(world: "KDL3World") -> None: # Kirby cannot eat enemies fully submerged in water. Vast majority of cases, the enemy can be brought to the surface # and eaten by inhaling while falling on top of them set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_2_E3, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_3_E6, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) # Ripple Field 4 E5, E7, and E8 are doable, but too strict to leave in logic set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E5, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E7, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_4_E8, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E1, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E2, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E3, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Ripple_Field_5_E4, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E7, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E8, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E9, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) set_rule(world.multiworld.get_location(EnemyAbilities.Sand_Canyon_4_E10, world.player), - lambda state: can_reach_kine(state, world.player)) + lambda state: can_reach_kine(state, world.player) or can_reach_chuchu(state, world.player)) for boss_flag, purification, i in zip(["Level 1 Boss - Purified", "Level 2 Boss - Purified", "Level 3 Boss - Purified", "Level 4 Boss - Purified", From 3e3b4c6732be219f756f7db22e8973540a1ae57a Mon Sep 17 00:00:00 2001 From: Seafo <92278897+Seatori@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:15:36 -0500 Subject: [PATCH 087/166] Minecraft: Add Pickaxes to the documentation (#2688) --- worlds/minecraft/docs/en_Minecraft.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/worlds/minecraft/docs/en_Minecraft.md b/worlds/minecraft/docs/en_Minecraft.md index 1ef347983b..bfe10d8cc5 100644 --- a/worlds/minecraft/docs/en_Minecraft.md +++ b/worlds/minecraft/docs/en_Minecraft.md @@ -64,12 +64,15 @@ sequence either by skipping it or watching hit play out. * Diamond Axe * Progessive Tools * Tier I + * Stone Pickaxe * Stone Shovel * Stone Hoe * Tier II + * Iron Pickaxe * Iron Shovel * Iron Hoe * Tier III + * Diamond Pickaxe * Diamond Shovel * Diamond Hoe * Netherite Ingot From 3c4ebb21140e5fd8ec6df63c1033915211ae8b01 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Sat, 9 Mar 2024 00:03:02 +0100 Subject: [PATCH 088/166] The Witness: Fix... I don't know how to explain this one (#2920) ``` for hint in generated_hints: hint = generated_hints.pop(0) ``` lmao --- worlds/witness/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index bd877a16ef..635a56796b 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -306,7 +306,6 @@ class WitnessWorld(World): duplicates = min(3, len(audio_logs) // hint_amount) for hint in generated_hints: - hint = generated_hints.pop(0) compact_hint_data = make_compact_hint_data(hint, self.player) for _ in range(0, duplicates): From be802b47231ededcb3d9b289d49c482820191ead Mon Sep 17 00:00:00 2001 From: Remy Jette Date: Sat, 9 Mar 2024 23:12:55 -0800 Subject: [PATCH 089/166] Core: Remove extra " character in /forbid_release help message (#2923) Was pointed out in the Discord: https://discord.com/channels/731205301247803413/731205301818359821/1215882443261870190 --- MultiServer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MultiServer.py b/MultiServer.py index 62dab3298e..c2d8e4ad58 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -1964,7 +1964,7 @@ class ServerCommandProcessor(CommonCommandProcessor): @mark_raw def _cmd_forbid_release(self, player_name: str) -> bool: - """"Disallow the specified player from using the !release command.""" + """Disallow the specified player from using the !release command.""" player = self.resolve_player(player_name) if player: team, slot, name = player From 4ce58c02403f044ba18bbf4640c6abe856d5714a Mon Sep 17 00:00:00 2001 From: axe-y <58866768+axe-y@users.noreply.github.com> Date: Sun, 10 Mar 2024 03:13:52 -0400 Subject: [PATCH 090/166] DLC Quest: AP World Status fix (#2908) --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 3f9a7f0ba6..68cab1d5e2 100644 --- a/setup.py +++ b/setup.py @@ -68,7 +68,6 @@ non_apworlds: set = { "Archipelago", "ChecksFinder", "Clique", - "DLCQuest", "Final Fantasy", "Lufia II Ancient Cave", "Meritous", From 939a5ec959108c35a49b1e12a56ae51f45ebb4fb Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Sun, 10 Mar 2024 01:18:25 -0600 Subject: [PATCH 091/166] LTTP: remove multiworld = None (#2290) --- test/bases.py | 8 +- test/general/test_items.py | 2 +- worlds/AutoWorld.py | 1 + worlds/alttp/Dungeons.py | 86 +++++++++---------- worlds/alttp/ItemPool.py | 46 +++++----- worlds/alttp/Items.py | 8 +- worlds/alttp/Rom.py | 10 +-- worlds/alttp/Rules.py | 4 +- worlds/alttp/test/__init__.py | 1 + worlds/alttp/test/dungeons/TestDungeon.py | 12 +-- worlds/alttp/test/inverted/TestInverted.py | 10 +-- .../TestInvertedMinor.py | 10 +-- .../test/inverted_owg/TestInvertedOWG.py | 12 +-- worlds/alttp/test/minor_glitches/TestMinor.py | 14 +-- worlds/alttp/test/options/TestOpenPyramid.py | 6 +- worlds/alttp/test/owg/TestVanillaOWG.py | 8 +- worlds/alttp/test/vanilla/TestVanilla.py | 6 +- 17 files changed, 122 insertions(+), 122 deletions(-) diff --git a/test/bases.py b/test/bases.py index 2d4111d193..07a3e60086 100644 --- a/test/bases.py +++ b/test/bases.py @@ -10,7 +10,7 @@ from worlds import AutoWorld from worlds.AutoWorld import World, call_all from BaseClasses import Location, MultiWorld, CollectionState, ItemClassification, Item -from worlds.alttp.Items import ItemFactory +from worlds.alttp.Items import item_factory class TestBase(unittest.TestCase): @@ -91,15 +91,15 @@ class TestBase(unittest.TestCase): items = self.multiworld.itempool[:] items = [item for item in items if item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)] - items.extend(ItemFactory(item_pool[0], 1)) + items.extend(item_factory(item_pool[0], self.multiworld.worlds[1])) else: - items = ItemFactory(item_pool[0], 1) + items = item_factory(item_pool[0], self.multiworld.worlds[1]) return self.get_state(items) def _get_items_partial(self, item_pool, missing_item): new_items = item_pool[0].copy() new_items.remove(missing_item) - items = ItemFactory(new_items, 1) + items = item_factory(new_items, self.multiworld.worlds[1]) return self.get_state(items) diff --git a/test/general/test_items.py b/test/general/test_items.py index 1612937225..82b6030379 100644 --- a/test/general/test_items.py +++ b/test/general/test_items.py @@ -8,7 +8,7 @@ class TestBase(unittest.TestCase): def test_create_item(self): """Test that a world can successfully create all items in its datapackage""" for game_name, world_type in AutoWorldRegister.world_types.items(): - proxy_world = world_type(None, 0) # this is identical to MultiServer.py creating worlds + proxy_world = setup_solo_multiworld(world_type, ()).worlds[1] for item_name in world_type.item_name_to_id: with self.subTest("Create Item", item_name=item_name, game_name=game_name): item = proxy_world.create_item(item_name) diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index 4179637199..93e4a5c385 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -296,6 +296,7 @@ class World(metaclass=AutoWorldRegister): """path it was loaded from""" def __init__(self, multiworld: "MultiWorld", player: int): + assert multiworld is not None self.multiworld = multiworld self.player = player diff --git a/worlds/alttp/Dungeons.py b/worlds/alttp/Dungeons.py index c886fce920..f0b8c2d971 100644 --- a/worlds/alttp/Dungeons.py +++ b/worlds/alttp/Dungeons.py @@ -7,7 +7,7 @@ from BaseClasses import CollectionState, Region, MultiWorld from Fill import fill_restrictive from .Bosses import BossFactory, Boss -from .Items import ItemFactory +from .Items import item_factory from .Regions import lookup_boss_drops, key_drop_data from .Options import small_key_shuffle @@ -81,90 +81,90 @@ def create_dungeons(world: "ALTTPWorld"): return dungeon ES = make_dungeon('Hyrule Castle', None, ['Hyrule Castle', 'Sewers', 'Sewer Drop', 'Sewers (Dark)', 'Sanctuary'], - ItemFactory('Big Key (Hyrule Castle)', player), - ItemFactory(['Small Key (Hyrule Castle)'] * 4, player), - [ItemFactory('Map (Hyrule Castle)', player)]) + item_factory('Big Key (Hyrule Castle)', world), + item_factory(['Small Key (Hyrule Castle)'] * 4, world), + [item_factory('Map (Hyrule Castle)', world)]) EP = make_dungeon('Eastern Palace', 'Armos Knights', ['Eastern Palace'], - ItemFactory('Big Key (Eastern Palace)', player), - ItemFactory(['Small Key (Eastern Palace)'] * 2, player), - ItemFactory(['Map (Eastern Palace)', 'Compass (Eastern Palace)'], player)) + item_factory('Big Key (Eastern Palace)', world), + item_factory(['Small Key (Eastern Palace)'] * 2, world), + item_factory(['Map (Eastern Palace)', 'Compass (Eastern Palace)'], world)) DP = make_dungeon('Desert Palace', 'Lanmolas', ['Desert Palace North', 'Desert Palace Main (Inner)', 'Desert Palace Main (Outer)', - 'Desert Palace East'], ItemFactory('Big Key (Desert Palace)', player), - ItemFactory(['Small Key (Desert Palace)'] * 4, player), - ItemFactory(['Map (Desert Palace)', 'Compass (Desert Palace)'], player)) + 'Desert Palace East'], item_factory('Big Key (Desert Palace)', world), + item_factory(['Small Key (Desert Palace)'] * 4, world), + item_factory(['Map (Desert Palace)', 'Compass (Desert Palace)'], world)) ToH = make_dungeon('Tower of Hera', 'Moldorm', ['Tower of Hera (Bottom)', 'Tower of Hera (Basement)', 'Tower of Hera (Top)'], - ItemFactory('Big Key (Tower of Hera)', player), - [ItemFactory('Small Key (Tower of Hera)', player)], - ItemFactory(['Map (Tower of Hera)', 'Compass (Tower of Hera)'], player)) + item_factory('Big Key (Tower of Hera)', world), + [item_factory('Small Key (Tower of Hera)', world)], + item_factory(['Map (Tower of Hera)', 'Compass (Tower of Hera)'], world)) PoD = make_dungeon('Palace of Darkness', 'Helmasaur King', ['Palace of Darkness (Entrance)', 'Palace of Darkness (Center)', 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness (Bonk Section)', 'Palace of Darkness (North)', 'Palace of Darkness (Maze)', 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness (Final Section)'], - ItemFactory('Big Key (Palace of Darkness)', player), - ItemFactory(['Small Key (Palace of Darkness)'] * 6, player), - ItemFactory(['Map (Palace of Darkness)', 'Compass (Palace of Darkness)'], player)) + item_factory('Big Key (Palace of Darkness)', world), + item_factory(['Small Key (Palace of Darkness)'] * 6, world), + item_factory(['Map (Palace of Darkness)', 'Compass (Palace of Darkness)'], world)) TT = make_dungeon('Thieves Town', 'Blind', ['Thieves Town (Entrance)', 'Thieves Town (Deep)', 'Blind Fight'], - ItemFactory('Big Key (Thieves Town)', player), - ItemFactory(['Small Key (Thieves Town)'] * 3, player), - ItemFactory(['Map (Thieves Town)', 'Compass (Thieves Town)'], player)) + item_factory('Big Key (Thieves Town)', world), + item_factory(['Small Key (Thieves Town)'] * 3, world), + item_factory(['Map (Thieves Town)', 'Compass (Thieves Town)'], world)) SW = make_dungeon('Skull Woods', 'Mothula', ['Skull Woods Final Section (Entrance)', 'Skull Woods First Section', 'Skull Woods Second Section', 'Skull Woods Second Section (Drop)', 'Skull Woods Final Section (Mothula)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)'], - ItemFactory('Big Key (Skull Woods)', player), - ItemFactory(['Small Key (Skull Woods)'] * 5, player), - ItemFactory(['Map (Skull Woods)', 'Compass (Skull Woods)'], player)) + item_factory('Big Key (Skull Woods)', world), + item_factory(['Small Key (Skull Woods)'] * 5, world), + item_factory(['Map (Skull Woods)', 'Compass (Skull Woods)'], world)) SP = make_dungeon('Swamp Palace', 'Arrghus', ['Swamp Palace (Entrance)', 'Swamp Palace (First Room)', 'Swamp Palace (Starting Area)', 'Swamp Palace (West)', 'Swamp Palace (Center)', 'Swamp Palace (North)'], - ItemFactory('Big Key (Swamp Palace)', player), - ItemFactory(['Small Key (Swamp Palace)'] * 6, player), - ItemFactory(['Map (Swamp Palace)', 'Compass (Swamp Palace)'], player)) + item_factory('Big Key (Swamp Palace)', world), + item_factory(['Small Key (Swamp Palace)'] * 6, world), + item_factory(['Map (Swamp Palace)', 'Compass (Swamp Palace)'], world)) IP = make_dungeon('Ice Palace', 'Kholdstare', ['Ice Palace (Entrance)', 'Ice Palace (Second Section)', 'Ice Palace (Main)', 'Ice Palace (East)', - 'Ice Palace (East Top)', 'Ice Palace (Kholdstare)'], ItemFactory('Big Key (Ice Palace)', player), - ItemFactory(['Small Key (Ice Palace)'] * 6, player), - ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player)) + 'Ice Palace (East Top)', 'Ice Palace (Kholdstare)'], item_factory('Big Key (Ice Palace)', world), + item_factory(['Small Key (Ice Palace)'] * 6, world), + item_factory(['Map (Ice Palace)', 'Compass (Ice Palace)'], world)) MM = make_dungeon('Misery Mire', 'Vitreous', ['Misery Mire (Entrance)', 'Misery Mire (Main)', 'Misery Mire (West)', 'Misery Mire (Final Area)', - 'Misery Mire (Vitreous)'], ItemFactory('Big Key (Misery Mire)', player), - ItemFactory(['Small Key (Misery Mire)'] * 6, player), - ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player)) + 'Misery Mire (Vitreous)'], item_factory('Big Key (Misery Mire)', world), + item_factory(['Small Key (Misery Mire)'] * 6, world), + item_factory(['Map (Misery Mire)', 'Compass (Misery Mire)'], world)) TR = make_dungeon('Turtle Rock', 'Trinexx', ['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Pokey Room)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)', 'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'], - ItemFactory('Big Key (Turtle Rock)', player), - ItemFactory(['Small Key (Turtle Rock)'] * 6, player), - ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player)) + item_factory('Big Key (Turtle Rock)', world), + item_factory(['Small Key (Turtle Rock)'] * 6, world), + item_factory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], world)) if multiworld.mode[player] != 'inverted': AT = make_dungeon('Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None, - ItemFactory(['Small Key (Agahnims Tower)'] * 4, player), []) + item_factory(['Small Key (Agahnims Tower)'] * 4, world), []) GT = make_dungeon('Ganons Tower', 'Agahnim2', ['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], - ItemFactory('Big Key (Ganons Tower)', player), - ItemFactory(['Small Key (Ganons Tower)'] * 8, player), - ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player)) + item_factory('Big Key (Ganons Tower)', world), + item_factory(['Small Key (Ganons Tower)'] * 8, world), + item_factory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], world)) else: AT = make_dungeon('Inverted Agahnims Tower', 'Agahnim', ['Inverted Agahnims Tower', 'Agahnim 1'], None, - ItemFactory(['Small Key (Agahnims Tower)'] * 4, player), []) + item_factory(['Small Key (Agahnims Tower)'] * 4, world), []) GT = make_dungeon('Inverted Ganons Tower', 'Agahnim2', ['Inverted Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', - 'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)', player), - ItemFactory(['Small Key (Ganons Tower)'] * 8, player), - ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player)) + 'Agahnim 2'], item_factory('Big Key (Ganons Tower)', world), + item_factory(['Small Key (Ganons Tower)'] * 8, world), + item_factory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], world)) GT.bosses['bottom'] = BossFactory('Armos Knights', player) GT.bosses['middle'] = BossFactory('Lanmolas', player) @@ -259,7 +259,7 @@ def fill_dungeons_restrictive(multiworld: MultiWorld): if not key_drop_shuffle and player not in multiworld.groups: for key_loc in key_drop_data: key_data = key_drop_data[key_loc] - all_state_base.remove(ItemFactory(key_data[3], player)) + all_state_base.remove(item_factory(key_data[3], multiworld.worlds[player])) loc = multiworld.get_location(key_loc, player) if loc in all_state_base.events: diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index bb5bbaa61a..7a82d4c6be 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -9,8 +9,8 @@ from .Shops import TakeAny, total_shop_slots, set_up_shops, shop_table_by_locati from .Bosses import place_bosses from .Dungeons import get_dungeon_item_pool_player from .EntranceShuffle import connect_entrance -from .Items import ItemFactory, GetBeemizerItem, trap_replaceable, item_name_groups -from .Options import small_key_shuffle, compass_shuffle, big_key_shuffle, map_shuffle, TriforcePiecesMode +from .Items import item_factory, GetBeemizerItem, trap_replaceable, item_name_groups +from .Options import small_key_shuffle, compass_shuffle, big_key_shuffle, map_shuffle, TriforcePiecesMode, LTTPBosses from .StateHelpers import has_triforce_pieces, has_melee_weapon from .Regions import key_drop_data @@ -240,9 +240,9 @@ def generate_itempool(world): if multiworld.timer[player] in ['ohko', 'timed_ohko']: multiworld.can_take_damage[player] = False if multiworld.goal[player] in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']: - multiworld.push_item(multiworld.get_location('Ganon', player), ItemFactory('Nothing', player), False) + multiworld.push_item(multiworld.get_location('Ganon', player), item_factory('Nothing', world), False) else: - multiworld.push_item(multiworld.get_location('Ganon', player), ItemFactory('Triforce', player), False) + multiworld.push_item(multiworld.get_location('Ganon', player), item_factory('Triforce', world), False) if multiworld.goal[player] in ['triforce_hunt', 'local_triforce_hunt']: region = multiworld.get_region('Light World', player) @@ -252,7 +252,7 @@ def generate_itempool(world): region.locations.append(loc) - multiworld.push_item(loc, ItemFactory('Triforce', player), False) + multiworld.push_item(loc, item_factory('Triforce', world), False) loc.event = True loc.locked = True @@ -271,7 +271,7 @@ def generate_itempool(world): ] for location_name, event_name in event_pairs: location = multiworld.get_location(location_name, player) - event = ItemFactory(event_name, player) + event = item_factory(event_name, world) multiworld.push_item(location, event, False) location.event = location.locked = True @@ -287,7 +287,7 @@ def generate_itempool(world): treasure_hunt_icon, additional_triforce_pieces = get_pool_core(multiworld, player) for item in precollected_items: - multiworld.push_precollected(ItemFactory(item, player)) + multiworld.push_precollected(item_factory(item, world)) if multiworld.mode[player] == 'standard' and not has_melee_weapon(multiworld.state, player): if "Link's Uncle" not in placed_items: @@ -326,9 +326,9 @@ def generate_itempool(world): multiworld.escape_assist[player].append('bombs') for (location, item) in placed_items.items(): - multiworld.get_location(location, player).place_locked_item(ItemFactory(item, player)) + multiworld.get_location(location, player).place_locked_item(item_factory(item, world)) - items = ItemFactory(pool, player) + items = item_factory(pool, world) # convert one Progressive Bow into Progressive Bow (Alt), in ID only, for ganon silvers hint text if multiworld.worlds[player].has_progressive_bows: for item in items: @@ -349,7 +349,7 @@ def generate_itempool(world): for key_loc in key_drop_data: key_data = key_drop_data[key_loc] - drop_item = ItemFactory(key_data[3], player) + drop_item = item_factory(key_data[3], world) if not multiworld.key_drop_shuffle[player]: if drop_item in dungeon_items: dungeon_items.remove(drop_item) @@ -370,7 +370,7 @@ def generate_itempool(world): loc.address = None elif "Small" in key_data[3] and multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: # key drop shuffle and universal keys are on. Add universal keys in place of key drop keys. - multiworld.itempool.append(ItemFactory(GetBeemizerItem(multiworld, player, 'Small Key (Universal)'), player)) + multiworld.itempool.append(item_factory(GetBeemizerItem(multiworld, player, 'Small Key (Universal)'), world)) dungeon_item_replacements = sum(difficulties[multiworld.difficulty[player]].extras, []) * 2 multiworld.random.shuffle(dungeon_item_replacements) @@ -382,7 +382,7 @@ def generate_itempool(world): or (multiworld.map_shuffle[player] == map_shuffle.option_start_with and item.type == 'Map')): dungeon_items.pop(x) multiworld.push_precollected(item) - multiworld.itempool.append(ItemFactory(dungeon_item_replacements.pop(), player)) + multiworld.itempool.append(item_factory(dungeon_item_replacements.pop(), world)) multiworld.itempool.extend([item for item in dungeon_items]) set_up_shops(multiworld, player) @@ -394,7 +394,7 @@ def generate_itempool(world): location.shop_slot is not None] for location in shop_locations: if location.shop.inventory[location.shop_slot]["item"] == "Single Arrow": - location.place_locked_item(ItemFactory("Single Arrow", player)) + location.place_locked_item(item_factory("Single Arrow", world)) else: shop_items += 1 else: @@ -406,9 +406,9 @@ def generate_itempool(world): multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal) * 0.5 for _ in range(shop_items): if multiworld.random.random() < chance_100: - items.append(ItemFactory(GetBeemizerItem(multiworld, player, "Rupees (100)"), player)) + items.append(item_factory(GetBeemizerItem(multiworld, player, "Rupees (100)"), world)) else: - items.append(ItemFactory(GetBeemizerItem(multiworld, player, "Rupees (50)"), player)) + items.append(item_factory(GetBeemizerItem(multiworld, player, "Rupees (50)"), world)) multiworld.random.shuffle(items) pool_count = len(items) @@ -431,7 +431,7 @@ def generate_itempool(world): new_items += ["Arrow Upgrade (+5)"] * 6 new_items.append("Arrow Upgrade (+5)" if progressive else "Arrow Upgrade (+10)") - items += [ItemFactory(item, player) for item in new_items] + items += [item_factory(item, world) for item in new_items] removed_filler = [] multiworld.random.shuffle(items) # Decide what gets tossed randomly. @@ -444,22 +444,22 @@ def generate_itempool(world): else: # no more junk to remove, condense progressive items def condense_items(items, small_item, big_item, rem, add): - small_item = ItemFactory(small_item, player) + small_item = item_factory(small_item, world) # while (len(items) >= pool_count + rem - 1 # minus 1 to account for the replacement item # and items.count(small_item) >= rem): if items.count(small_item) >= rem: for _ in range(rem): items.remove(small_item) - removed_filler.append(ItemFactory(small_item.name, player)) - items += [ItemFactory(big_item, player) for _ in range(add)] + removed_filler.append(item_factory(small_item.name, world)) + items += [item_factory(big_item, world) for _ in range(add)] return True return False def cut_item(items, item_to_cut, minimum_items): - item_to_cut = ItemFactory(item_to_cut, player) + item_to_cut = item_factory(item_to_cut, world) if items.count(item_to_cut) > minimum_items: items.remove(item_to_cut) - removed_filler.append(ItemFactory(item_to_cut.name, player)) + removed_filler.append(item_factory(item_to_cut.name, world)) return True return False @@ -551,7 +551,7 @@ def set_up_take_anys(world, player): if swords: sword = world.random.choice(swords) world.itempool.remove(sword) - world.itempool.append(ItemFactory('Rupees (20)', player)) + world.itempool.append(item_factory('Rupees (20)', world)) old_man_take_any.shop.add_inventory(0, sword.name, 0, 0) loc_name = "Old Man Sword Cave" location = ALttPLocation(player, loc_name, shop_table_by_location[loc_name], parent=old_man_take_any) @@ -577,7 +577,7 @@ def set_up_take_anys(world, player): location = ALttPLocation(player, take_any.name, shop_table_by_location[take_any.name], parent=take_any) location.shop_slot = 1 take_any.locations.append(location) - location.place_locked_item(ItemFactory("Boss Heart Container", player)) + location.place_locked_item(item_factory("Boss Heart Container", world)) def get_pool_core(world, player: int): diff --git a/worlds/alttp/Items.py b/worlds/alttp/Items.py index 8e513552ad..cb44f35d58 100644 --- a/worlds/alttp/Items.py +++ b/worlds/alttp/Items.py @@ -1,6 +1,7 @@ import typing from BaseClasses import ItemClassification as IC +from worlds.AutoWorld import World def GetBeemizerItem(world, player: int, item): @@ -17,13 +18,10 @@ def GetBeemizerItem(world, player: int, item): if not world.beemizer_trap_chance[player] or world.random.random() > (world.beemizer_trap_chance[player] / 100): return "Bee" if isinstance(item, str) else world.create_item("Bee", player) else: - return "Bee Trap" if isinstance(item, str) else world.create_item("Bee Trap", player) + return "Bee Trap" if isinstance(item, str) else world.create_item("Bee Trap", player) -# should be replaced with direct world.create_item(item) call in the future -def ItemFactory(items: typing.Union[str, typing.Iterable[str]], player: int): - from worlds.alttp import ALTTPWorld - world = ALTTPWorld(None, player) +def item_factory(items: typing.Union[str, typing.Iterable[str]], world: World): ret = [] singleton = False if isinstance(items, str): diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index ff4947bb01..6ef1f0db19 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -34,7 +34,7 @@ from .Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmith DeathMountain_texts, \ LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, \ SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names -from .Items import ItemFactory, item_table, item_name_groups, progression_items +from .Items import item_table, item_name_groups, progression_items from .EntranceShuffle import door_addresses from .Options import small_key_shuffle @@ -996,7 +996,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_byte(0x18003A, 0x01 if world.dark_world_light_cone else 0x00) GREEN_TWENTY_RUPEES = 0x47 - GREEN_CLOCK = ItemFactory('Green Clock', player).code + GREEN_CLOCK = item_table["Green Clock"].item_code rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on @@ -1777,13 +1777,13 @@ def write_custom_shops(rom, world, player): if item['player'] and world.game[item['player']] != "A Link to the Past": # item not native to ALTTP item_code = get_nonnative_item_sprite(world.worlds[item['player']].item_name_to_id[item['item']]) else: - item_code = ItemFactory(item['item'], player).code + item_code = item_table[item["item"]].item_code if item['item'] == 'Single Arrow' and item['player'] == 0 and world.retro_bow[player]: rom.write_byte(0x186500 + shop.sram_offset + slot, arrow_mask) item_data = [shop_id, item_code] + price_data + \ - [item['max'], ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] + \ - replacement_price_data + [0 if item['player'] == player else min(ROM_PLAYER_LIMIT, item['player'])] + [item["max"], item_table[item["replacement"]].item_code if item["replacement"] else 0xFF] + \ + replacement_price_data + [0 if item["player"] == player else min(ROM_PLAYER_LIMIT, item["player"])] items_data.extend(item_data) rom.write_bytes(0x184800, shop_data) diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index b86a793fb9..c3156116e4 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -8,7 +8,7 @@ from worlds.generic.Rules import (add_item_rule, add_rule, forbid_item, from . import OverworldGlitchRules from .Bosses import GanonDefeatRule -from .Items import ItemFactory, item_name_groups, item_table, progression_items +from .Items import item_factory, item_name_groups, item_table, progression_items from .Options import small_key_shuffle from .OverworldGlitchRules import no_logic_rules, overworld_glitches_rules from .Regions import LTTPRegionType, location_table @@ -1181,7 +1181,7 @@ def set_trock_key_rules(world, player): forbid_item(world.get_location(location, player), 'Big Key (Turtle Rock)', player) else: # A key is required in the Big Key Chest to prevent a possible softlock. Place an extra key to ensure 100% locations still works - item = ItemFactory('Small Key (Turtle Rock)', player) + item = item_factory('Small Key (Turtle Rock)', world.worlds[player]) location = world.get_location('Turtle Rock - Big Key Chest', player) location.place_locked_item(item) location.event = True diff --git a/worlds/alttp/test/__init__.py b/worlds/alttp/test/__init__.py index 5baaa7e88e..49033a6ce3 100644 --- a/worlds/alttp/test/__init__.py +++ b/worlds/alttp/test/__init__.py @@ -14,3 +14,4 @@ class LTTPTestBase(unittest.TestCase): for name, option in AutoWorldRegister.world_types["A Link to the Past"].options_dataclass.type_hints.items(): setattr(args, name, {1: option.from_any(getattr(option, "default"))}) self.multiworld.set_options(args) + self.world = self.multiworld.worlds[1] diff --git a/worlds/alttp/test/dungeons/TestDungeon.py b/worlds/alttp/test/dungeons/TestDungeon.py index 1f8288ace0..128f8b41b7 100644 --- a/worlds/alttp/test/dungeons/TestDungeon.py +++ b/worlds/alttp/test/dungeons/TestDungeon.py @@ -2,7 +2,7 @@ from BaseClasses import CollectionState, ItemClassification from worlds.alttp.Dungeons import get_dungeon_item_pool from worlds.alttp.EntranceShuffle import mandatory_connections, connect_simple from worlds.alttp.ItemPool import difficulties -from worlds.alttp.Items import ItemFactory +from worlds.alttp.Items import item_factory from worlds.alttp.Regions import create_regions from worlds.alttp.Shops import create_shops from worlds.alttp.test import LTTPTestBase @@ -24,10 +24,10 @@ class TestDungeon(LTTPTestBase): connect_simple(self.multiworld, 'Big Bomb Shop', 'Big Bomb Shop', 1) self.multiworld.get_region('Menu', 1).exits = [] self.multiworld.swamp_patch_required[1] = True - self.multiworld.worlds[1].set_rules() - self.multiworld.worlds[1].create_items() + self.world.set_rules() + self.world.create_items() self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld)) - self.multiworld.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1)) + self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world)) def run_tests(self, access_pool): for exit in self.remove_exits: @@ -40,9 +40,9 @@ class TestDungeon(LTTPTestBase): if all_except and len(all_except) > 0: items = self.multiworld.itempool[:] items = [item for item in items if item.name not in all_except and not ("Bottle" in item.name and "AnyBottle" in all_except)] - items.extend(ItemFactory(item_pool[0], 1)) + items.extend(item_factory(item_pool[0], self.world)) else: - items = ItemFactory(items, 1) + items = item_factory(items, self.world) state = CollectionState(self.multiworld) state.reachable_regions[1].add(self.multiworld.get_region('Menu', 1)) for region_name in self.starting_regions: diff --git a/worlds/alttp/test/inverted/TestInverted.py b/worlds/alttp/test/inverted/TestInverted.py index f2c585e465..069639e81b 100644 --- a/worlds/alttp/test/inverted/TestInverted.py +++ b/worlds/alttp/test/inverted/TestInverted.py @@ -2,7 +2,7 @@ from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool from worlds.alttp.EntranceShuffle import link_inverted_entrances from worlds.alttp.InvertedRegions import create_inverted_regions from worlds.alttp.ItemPool import difficulties -from worlds.alttp.Items import ItemFactory +from worlds.alttp.Items import item_factory from worlds.alttp.Regions import mark_light_world_regions from worlds.alttp.Shops import create_shops from test.TestBase import TestBase @@ -18,14 +18,14 @@ class TestInverted(TestBase, LTTPTestBase): self.multiworld.bombless_start[1].value = True self.multiworld.shuffle_capacity_upgrades[1].value = True create_inverted_regions(self.multiworld, 1) - self.multiworld.worlds[1].create_dungeons() + self.world.create_dungeons() create_shops(self.multiworld, 1) link_inverted_entrances(self.multiworld, 1) - self.multiworld.worlds[1].create_items() + self.world.create_items() self.multiworld.required_medallions[1] = ['Ether', 'Quake'] self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld)) - self.multiworld.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1)) + self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world)) self.multiworld.get_location('Agahnim 1', 1).item = None self.multiworld.get_location('Agahnim 2', 1).item = None mark_light_world_regions(self.multiworld, 1) - self.multiworld.worlds[1].set_rules() + self.world.set_rules() diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py index 0219332e07..21dbae6933 100644 --- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py +++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py @@ -2,7 +2,7 @@ from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool from worlds.alttp.EntranceShuffle import link_inverted_entrances from worlds.alttp.InvertedRegions import create_inverted_regions from worlds.alttp.ItemPool import difficulties -from worlds.alttp.Items import ItemFactory +from worlds.alttp.Items import item_factory from worlds.alttp.Regions import mark_light_world_regions from worlds.alttp.Shops import create_shops from test.TestBase import TestBase @@ -19,14 +19,14 @@ class TestInvertedMinor(TestBase, LTTPTestBase): self.multiworld.shuffle_capacity_upgrades[1].value = True self.multiworld.difficulty_requirements[1] = difficulties['normal'] create_inverted_regions(self.multiworld, 1) - self.multiworld.worlds[1].create_dungeons() + self.world.create_dungeons() create_shops(self.multiworld, 1) link_inverted_entrances(self.multiworld, 1) - self.multiworld.worlds[1].create_items() + self.world.create_items() self.multiworld.required_medallions[1] = ['Ether', 'Quake'] self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld)) - self.multiworld.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1)) + self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world)) self.multiworld.get_location('Agahnim 1', 1).item = None self.multiworld.get_location('Agahnim 2', 1).item = None mark_light_world_regions(self.multiworld, 1) - self.multiworld.worlds[1].set_rules() + self.world.set_rules() diff --git a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py index 849f06098a..138324a9ff 100644 --- a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py +++ b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py @@ -2,7 +2,7 @@ from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool from worlds.alttp.EntranceShuffle import link_inverted_entrances from worlds.alttp.InvertedRegions import create_inverted_regions from worlds.alttp.ItemPool import difficulties -from worlds.alttp.Items import ItemFactory +from worlds.alttp.Items import item_factory from worlds.alttp.Regions import mark_light_world_regions from worlds.alttp.Shops import create_shops from test.TestBase import TestBase @@ -19,16 +19,16 @@ class TestInvertedOWG(TestBase, LTTPTestBase): self.multiworld.shuffle_capacity_upgrades[1].value = True self.multiworld.difficulty_requirements[1] = difficulties['normal'] create_inverted_regions(self.multiworld, 1) - self.multiworld.worlds[1].create_dungeons() + self.world.create_dungeons() create_shops(self.multiworld, 1) link_inverted_entrances(self.multiworld, 1) - self.multiworld.worlds[1].create_items() + self.world.create_items() self.multiworld.required_medallions[1] = ['Ether', 'Quake'] self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld)) - self.multiworld.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1)) + self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world)) self.multiworld.get_location('Agahnim 1', 1).item = None self.multiworld.get_location('Agahnim 2', 1).item = None self.multiworld.precollected_items[1].clear() - self.multiworld.itempool.append(ItemFactory('Pegasus Boots', 1)) + self.multiworld.itempool.append(item_factory('Pegasus Boots', self.world)) mark_light_world_regions(self.multiworld, 1) - self.multiworld.worlds[1].set_rules() + self.world.set_rules() diff --git a/worlds/alttp/test/minor_glitches/TestMinor.py b/worlds/alttp/test/minor_glitches/TestMinor.py index c7de74d3a6..547509d58c 100644 --- a/worlds/alttp/test/minor_glitches/TestMinor.py +++ b/worlds/alttp/test/minor_glitches/TestMinor.py @@ -1,7 +1,7 @@ from worlds.alttp.Dungeons import get_dungeon_item_pool from worlds.alttp.InvertedRegions import mark_dark_world_regions from worlds.alttp.ItemPool import difficulties -from worlds.alttp.Items import ItemFactory +from worlds.alttp.Items import item_factory from test.TestBase import TestBase from worlds.alttp.test import LTTPTestBase @@ -14,15 +14,15 @@ class TestMinor(TestBase, LTTPTestBase): self.multiworld.bombless_start[1].value = True self.multiworld.shuffle_capacity_upgrades[1].value = True self.multiworld.difficulty_requirements[1] = difficulties['normal'] - self.multiworld.worlds[1].er_seed = 0 - self.multiworld.worlds[1].create_regions() - self.multiworld.worlds[1].create_items() + self.world.er_seed = 0 + self.world.create_regions() + self.world.create_items() self.multiworld.required_medallions[1] = ['Ether', 'Quake'] self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld)) - self.multiworld.itempool.extend(ItemFactory( + self.multiworld.itempool.extend(item_factory( ['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', - 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1)) + 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world)) self.multiworld.get_location('Agahnim 1', 1).item = None self.multiworld.get_location('Agahnim 2', 1).item = None mark_dark_world_regions(self.multiworld, 1) - self.multiworld.worlds[1].set_rules() + self.world.set_rules() diff --git a/worlds/alttp/test/options/TestOpenPyramid.py b/worlds/alttp/test/options/TestOpenPyramid.py index 895ecb95a9..c7912c43d7 100644 --- a/worlds/alttp/test/options/TestOpenPyramid.py +++ b/worlds/alttp/test/options/TestOpenPyramid.py @@ -1,5 +1,5 @@ -from test.TestBase import WorldTestBase -from ...Items import ItemFactory +from test.bases import WorldTestBase +from ...Items import item_factory class PyramidTestBase(WorldTestBase): @@ -32,6 +32,6 @@ class GoalPyramidTest(PyramidTestBase): self.assertFalse(self.can_reach_entrance("Pyramid Hole")) self.collect_by_name(["Hammer", "Progressive Glove", "Moon Pearl"]) self.assertFalse(self.can_reach_entrance("Pyramid Hole")) - self.multiworld.state.collect(ItemFactory("Beat Agahnim 2", 1)) + self.collect(item_factory("Beat Agahnim 2", self.multiworld.worlds[1])) self.assertTrue(self.can_reach_entrance("Pyramid Hole")) diff --git a/worlds/alttp/test/owg/TestVanillaOWG.py b/worlds/alttp/test/owg/TestVanillaOWG.py index 1f8f2707ed..aafc04a77e 100644 --- a/worlds/alttp/test/owg/TestVanillaOWG.py +++ b/worlds/alttp/test/owg/TestVanillaOWG.py @@ -1,7 +1,7 @@ from worlds.alttp.Dungeons import get_dungeon_item_pool from worlds.alttp.InvertedRegions import mark_dark_world_regions from worlds.alttp.ItemPool import difficulties -from worlds.alttp.Items import ItemFactory +from worlds.alttp.Items import item_factory from test.TestBase import TestBase from worlds.alttp.test import LTTPTestBase @@ -19,10 +19,10 @@ class TestVanillaOWG(TestBase, LTTPTestBase): self.multiworld.worlds[1].create_items() self.multiworld.required_medallions[1] = ['Ether', 'Quake'] self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld)) - self.multiworld.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1)) + self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world)) self.multiworld.get_location('Agahnim 1', 1).item = None self.multiworld.get_location('Agahnim 2', 1).item = None self.multiworld.precollected_items[1].clear() - self.multiworld.itempool.append(ItemFactory('Pegasus Boots', 1)) + self.multiworld.itempool.append(item_factory('Pegasus Boots', self.world)) mark_dark_world_regions(self.multiworld, 1) - self.multiworld.worlds[1].set_rules() \ No newline at end of file + self.world.set_rules() diff --git a/worlds/alttp/test/vanilla/TestVanilla.py b/worlds/alttp/test/vanilla/TestVanilla.py index 3f4fbad8c2..e79b4f2ea3 100644 --- a/worlds/alttp/test/vanilla/TestVanilla.py +++ b/worlds/alttp/test/vanilla/TestVanilla.py @@ -1,7 +1,7 @@ from worlds.alttp.Dungeons import get_dungeon_item_pool from worlds.alttp.InvertedRegions import mark_dark_world_regions from worlds.alttp.ItemPool import difficulties -from worlds.alttp.Items import ItemFactory +from worlds.alttp.Items import item_factory from test.TestBase import TestBase from worlds.alttp.test import LTTPTestBase @@ -18,8 +18,8 @@ class TestVanilla(TestBase, LTTPTestBase): self.multiworld.worlds[1].create_items() self.multiworld.required_medallions[1] = ['Ether', 'Quake'] self.multiworld.itempool.extend(get_dungeon_item_pool(self.multiworld)) - self.multiworld.itempool.extend(ItemFactory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], 1)) + self.multiworld.itempool.extend(item_factory(['Green Pendant', 'Red Pendant', 'Blue Pendant', 'Beat Agahnim 1', 'Beat Agahnim 2', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'], self.world)) self.multiworld.get_location('Agahnim 1', 1).item = None self.multiworld.get_location('Agahnim 2', 1).item = None mark_dark_world_regions(self.multiworld, 1) - self.multiworld.worlds[1].set_rules() \ No newline at end of file + self.world.set_rules() From 8c11c385f36716edbc15e0e43f667f22b1754a32 Mon Sep 17 00:00:00 2001 From: Zach Parks Date: Sun, 10 Mar 2024 01:56:57 -0600 Subject: [PATCH 092/166] ALTTP: Fix NotImplemented error when using non-`none` values for `timer`. (#2924) --- worlds/alttp/ItemPool.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index 7a82d4c6be..0e799a61e6 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -234,8 +234,8 @@ def generate_itempool(world): raise NotImplementedError(f"Goal {multiworld.goal[player]} for player {player}") if multiworld.mode[player] not in ('open', 'standard', 'inverted'): raise NotImplementedError(f"Mode {multiworld.mode[player]} for player {player}") - if multiworld.timer[player] not in {False, 'display', 'timed', 'timed_ohko', 'ohko', 'timed_countdown'}: - raise NotImplementedError(f"Timer {multiworld.mode[player]} for player {player}") + if multiworld.timer[player] not in (False, 'display', 'timed', 'timed_ohko', 'ohko', 'timed_countdown'): + raise NotImplementedError(f"Timer {multiworld.timer[player]} for player {player}") if multiworld.timer[player] in ['ohko', 'timed_ohko']: multiworld.can_take_damage[player] = False From a4f89396d9a8eafe412bb5a6da7ba8506231e878 Mon Sep 17 00:00:00 2001 From: "Robyn (Reckoner)" Date: Sun, 10 Mar 2024 12:50:25 +0000 Subject: [PATCH 093/166] Docs: Fix typos in Minecraft info page (#2686) Fixed typos in game page --- worlds/minecraft/docs/en_Minecraft.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worlds/minecraft/docs/en_Minecraft.md b/worlds/minecraft/docs/en_Minecraft.md index bfe10d8cc5..c700f59a51 100644 --- a/worlds/minecraft/docs/en_Minecraft.md +++ b/worlds/minecraft/docs/en_Minecraft.md @@ -11,9 +11,9 @@ Some recipes are locked from being able to be crafted and shuffled into the item structures appear in each dimension. Crafting recipes are re-learned when they are received from other players as item checks, and occasionally when completing your own achievements. See below for which recipes are shuffled. -## What is considered a location check in minecraft? +## What is considered a location check in Minecraft? -Location checks in are completed when the player completes various Minecraft achievements. Opening the advancements menu +Location checks are completed when the player completes various Minecraft achievements. Opening the advancements menu in-game by pressing "L" will display outstanding achievements. ## When the player receives an item, what happens? @@ -24,7 +24,7 @@ inventory directly. ## What is the victory condition? Victory is achieved when the player kills the Ender Dragon, enters the portal in The End, and completes the credits -sequence either by skipping it or watching hit play out. +sequence either by skipping it or watching it play out. ## Which recipes are locked? From 5a4d88d554b25b446169d6cf33d1bd7974fe73bd Mon Sep 17 00:00:00 2001 From: qwint Date: Sun, 10 Mar 2024 08:46:44 -0500 Subject: [PATCH 094/166] Clients: add /item_groups and /location_groups (#2822) --- CommonClient.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CommonClient.py b/CommonClient.py index c75ca3fd80..3665b9f177 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -115,6 +115,15 @@ class ClientCommandProcessor(CommandProcessor): for item_name in AutoWorldRegister.world_types[self.ctx.game].item_name_to_id: self.output(item_name) + def _cmd_item_groups(self): + """List all item group names for the currently running game.""" + if not self.ctx.game: + self.output("No game set, cannot determine existing item groups.") + return False + self.output(f"Item Group Names for {self.ctx.game}") + for group_name in AutoWorldRegister.world_types[self.ctx.game].item_name_groups: + self.output(group_name) + def _cmd_locations(self): """List all location names for the currently running game.""" if not self.ctx.game: @@ -124,6 +133,15 @@ class ClientCommandProcessor(CommandProcessor): for location_name in AutoWorldRegister.world_types[self.ctx.game].location_name_to_id: self.output(location_name) + def _cmd_location_groups(self): + """List all location group names for the currently running game.""" + if not self.ctx.game: + self.output("No game set, cannot determine existing location groups.") + return False + self.output(f"Location Group Names for {self.ctx.game}") + for group_name in AutoWorldRegister.world_types[self.ctx.game].location_name_groups: + self.output(group_name) + def _cmd_ready(self): """Send ready status to server.""" self.ctx.ready = not self.ctx.ready From 37add8ee594582f5a0f06103d152ed368d41f8a1 Mon Sep 17 00:00:00 2001 From: panicbit Date: Sun, 10 Mar 2024 14:48:00 +0100 Subject: [PATCH 095/166] LADX: shuffle instruments (#2804) * ladx: shuffle instruments * set correct default for shuffled instruments --- worlds/ladx/Options.py | 17 +++++++++++++++++ worlds/ladx/__init__.py | 13 +++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/worlds/ladx/Options.py b/worlds/ladx/Options.py index 117242208b..ec45706407 100644 --- a/worlds/ladx/Options.py +++ b/worlds/ladx/Options.py @@ -179,6 +179,22 @@ class ShuffleStoneBeaks(DungeonItemShuffle): display_name = "Shuffle Stone Beaks" ladxr_item = "STONE_BEAK" +class ShuffleInstruments(DungeonItemShuffle): + """ + Shuffle Instruments + [Original Dungeon] The item will be within its original dungeon + [Own Dungeons] The item will be within a dungeon in your world + [Own World] The item will be somewhere in your world + [Any World] The item could be anywhere + [Different World] The item will be somewhere in another world + [Vanilla] The item will be in its vanilla location in your world + """ + display_name = "Shuffle Instruments" + ladxr_item = "INSTRUMENT" + default = 100 + option_vanilla = 100 + alias_false = 100 + class Goal(Choice, LADXROption): """ The Goal of the game @@ -465,6 +481,7 @@ links_awakening_options: typing.Dict[str, typing.Type[Option]] = { 'shuffle_compasses': ShuffleCompasses, 'shuffle_stone_beaks': ShuffleStoneBeaks, 'music': Music, + 'shuffle_instruments': ShuffleInstruments, 'music_change_condition': MusicChangeCondition, 'nag_messages': NagMessages, 'ap_title_screen': APTitleScreen, diff --git a/worlds/ladx/__init__.py b/worlds/ladx/__init__.py index 6742dffd30..9de3462ad0 100644 --- a/worlds/ladx/__init__.py +++ b/worlds/ladx/__init__.py @@ -23,7 +23,7 @@ from .LADXR.settings import Settings as LADXRSettings from .LADXR.worldSetup import WorldSetup as LADXRWorldSetup from .Locations import (LinksAwakeningLocation, LinksAwakeningRegion, create_regions_from_ladxr, get_locations_to_id) -from .Options import DungeonItemShuffle, links_awakening_options +from .Options import DungeonItemShuffle, links_awakening_options, ShuffleInstruments from .Rom import LADXDeltaPatch DEVELOPER_MODE = False @@ -184,7 +184,7 @@ class LinksAwakeningWorld(World): self.pre_fill_items = [] # For any and different world, set item rule instead - for option in ["maps", "compasses", "small_keys", "nightmare_keys", "stone_beaks"]: + for option in ["maps", "compasses", "small_keys", "nightmare_keys", "stone_beaks", "instruments"]: option = "shuffle_" + option option = self.player_options[option] @@ -224,7 +224,10 @@ class LinksAwakeningWorld(World): continue if isinstance(item.item_data, DungeonItemData): - if item.item_data.dungeon_item_type == DungeonItemType.INSTRUMENT: + item_type = item.item_data.ladxr_id[:-1] + shuffle_type = dungeon_item_types[item_type] + + if item.item_data.dungeon_item_type == DungeonItemType.INSTRUMENT and shuffle_type == ShuffleInstruments.option_vanilla: # Find instrument, lock # TODO: we should be able to pinpoint the region we want, save a lookup table please found = False @@ -240,10 +243,8 @@ class LinksAwakeningWorld(World): found = True break if found: - break + break else: - item_type = item.item_data.ladxr_id[:-1] - shuffle_type = dungeon_item_types[item_type] if shuffle_type == DungeonItemShuffle.option_original_dungeon: self.prefill_original_dungeon[item.item_data.dungeon_index - 1].append(item) self.pre_fill_items.append(item) From 3602ed45a48232ea2d0ba6738636b56aaa10a8c4 Mon Sep 17 00:00:00 2001 From: Kappatechy Date: Sun, 10 Mar 2024 08:36:42 -0600 Subject: [PATCH 096/166] Bumper Stickers: logic fixes for "off-by-one" errors (#2855) * Corrected logic error. Per discussion here: https://discord.com/channels/731205301247803413/1148330200891932742/1192138309120577646 At the moment, the logic expects Treasure Bumper 2 to require 1 bumper, Treasure Bumper 3 to require 2, etc., and for Treasure Bumper 1 to be in Sphere 1. This is incorrect, each Bumper check should require 1 Bumper item of it's type. This corrects that. I've verified I was able to generate with it by editing my apworld locally, but I'm also not a programmer and don't know anything about tests. However, I'd think this is a simple change. * Correct logic in Bumper Sticker unit tests Off By One errors were rampant in the Bumper Stickers unit test logic. This should correct those errors. * Correct use of "range" function The function setting the access rules for Treasure and Booster Bumpers was stopping one short of being applied to all the related locations. This has been corrected. * Restoring and clarifying designer's original level access intent The original creator of the AP version of Bumper Stickers intentionally set the Treasure Bumper requirements to logically reach each level 1 higher than the actual game requires, and logic tests were built based on this. This design decision has now been restored. * Revert "Restoring and clarifying designer's original level access intent" This reverts commit 5186c5fcc3229a60569cdb96d774d4ccac721a06. * Correct test logic for level 5 While 33 Treasure Bumpers are generated, only 32 are needed to reach level 5. This push corrects the unit test for the level 5 checks. * Rename generically-named variables Change variables from generic names (x, y, n) to more meaningful names, for ease of readability. --------- Co-authored-by: The T --- worlds/bumpstik/__init__.py | 12 ++++----- worlds/bumpstik/test/TestLogic.py | 42 ++++++++++++++++--------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/worlds/bumpstik/__init__.py b/worlds/bumpstik/__init__.py index d93b25cda5..9fc9fc214e 100644 --- a/worlds/bumpstik/__init__.py +++ b/worlds/bumpstik/__init__.py @@ -116,12 +116,12 @@ class BumpStikWorld(World): self.multiworld.itempool += item_pool def set_rules(self): - for x in range(1, 32): - self.multiworld.get_location(f"Treasure Bumper {x + 1}", self.player).access_rule = \ - lambda state, x = x: state.has("Treasure Bumper", self.player, x) - for x in range(1, 5): - self.multiworld.get_location(f"Bonus Booster {x + 1}", self.player).access_rule = \ - lambda state, x = x: state.has("Booster Bumper", self.player, x) + for treasure_count in range(1, 33): + self.multiworld.get_location(f"Treasure Bumper {treasure_count}", self.player).access_rule = \ + lambda state, treasure_held = treasure_count: state.has("Treasure Bumper", self.player, treasure_held) + for booster_count in range(1, 6): + self.multiworld.get_location(f"Bonus Booster {booster_count}", self.player).access_rule = \ + lambda state, booster_held = booster_count: state.has("Booster Bumper", self.player, booster_held) self.multiworld.get_location("Level 5 - Cleared all Hazards", self.player).access_rule = \ lambda state: state.has("Hazard Bumper", self.player, 25) diff --git a/worlds/bumpstik/test/TestLogic.py b/worlds/bumpstik/test/TestLogic.py index e374b7b1e9..a252f1e584 100644 --- a/worlds/bumpstik/test/TestLogic.py +++ b/worlds/bumpstik/test/TestLogic.py @@ -3,36 +3,38 @@ from . import BumpStikTestBase class TestRuleLogic(BumpStikTestBase): def testLogic(self): - for x in range(1, 33): - if x == 32: + for treasure_bumpers_held in range(1, 33): + if treasure_bumpers_held == 32: self.assertFalse(self.can_reach_location("Level 5 - Cleared all Hazards")) self.collect(self.get_item_by_name("Treasure Bumper")) - if x % 8 == 0: - bb_count = round(x / 8) + if treasure_bumpers_held % 8 == 0: + bb_count = round(treasure_bumpers_held / 8) if bb_count < 4: - self.assertFalse(self.can_reach_location(f"Treasure Bumper {x + 1}")) + self.assertFalse(self.can_reach_location(f"Treasure Bumper {treasure_bumpers_held + 1}")) + # Can't reach Treasure Bumper 9 check until level 2 is unlocked, etc. + # But we don't have enough Treasure Bumpers to reach this check anyway?? elif bb_count == 4: bb_count += 1 + # Level 4 has two new Bonus Booster checks; need to check both - for y in range(self.count("Booster Bumper"), bb_count): - self.assertTrue(self.can_reach_location(f"Bonus Booster {y + 1}"), - f"BB {y + 1} check not reachable with {self.count('Booster Bumper')} BBs") - if y < 4: - self.assertFalse(self.can_reach_location(f"Bonus Booster {y + 2}"), - f"BB {y + 2} check reachable with {self.count('Treasure Bumper')} TBs") - self.collect(self.get_item_by_name("Booster Bumper")) + for booster_bumpers_held in range(self.count("Booster Bumper"), bb_count + 1): + if booster_bumpers_held > 0: + self.assertTrue(self.can_reach_location(f"Bonus Booster {booster_bumpers_held}"), + f"Bonus Booster {booster_bumpers_held} check not reachable with {self.count('Booster Bumper')} Booster Bumpers") + if booster_bumpers_held < 5: + self.assertFalse(self.can_reach_location(f"Bonus Booster {booster_bumpers_held + 1}"), + f"Bonus Booster {booster_bumpers_held + 1} check reachable with {self.count('Treasure Bumper')} Treasure Bumpers and {self.count('Booster Bumper')} Booster Bumpers") + if booster_bumpers_held < bb_count: + self.collect(self.get_item_by_name("Booster Bumper")) - if x < 31: - self.assertFalse(self.can_reach_location(f"Treasure Bumper {x + 2}")) - elif x == 31: - self.assertFalse(self.can_reach_location("Level 5 - 50,000+ Total Points")) + self.assertTrue(self.can_reach_location(f"Treasure Bumper {treasure_bumpers_held}"), + f"Treasure Bumper {treasure_bumpers_held} check not reachable with {self.count('Treasure Bumper')} Treasure Bumpers") - if x < 32: - self.assertTrue(self.can_reach_location(f"Treasure Bumper {x + 1}"), - f"TB {x + 1} check not reachable with {self.count('Treasure Bumper')} TBs") - elif x == 32: + if treasure_bumpers_held < 32: + self.assertFalse(self.can_reach_location(f"Treasure Bumper {treasure_bumpers_held + 1}")) + elif treasure_bumpers_held == 32: self.assertTrue(self.can_reach_location("Level 5 - 50,000+ Total Points")) self.assertFalse(self.can_reach_location("Level 5 - Cleared all Hazards")) self.collect(self.get_items_by_name("Hazard Bumper")) From b8c24def8d96d0dc61daba06074d393cb2511c21 Mon Sep 17 00:00:00 2001 From: Seldom <38388947+Seldom-SE@users.noreply.github.com> Date: Sun, 10 Mar 2024 08:03:44 -0700 Subject: [PATCH 097/166] Terraria: Logic fix: Witch Doctor sells Bewitching Table (#2880) --- worlds/terraria/Rules.dsv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/terraria/Rules.dsv b/worlds/terraria/Rules.dsv index 43a21b4957..38ca4e575f 100644 --- a/worlds/terraria/Rules.dsv +++ b/worlds/terraria/Rules.dsv @@ -207,7 +207,7 @@ Clothier; Npc; Dungeon; ; Skeletron; Dungeon Heist; Achievement; Dungeon; Bone; ; Dungeon | (@calamity & #Skeletron); -Bewitching Table; Minions(1); Dungeon; +Bewitching Table; Minions(1); Dungeon | (Witch Doctor & Wizard); Mechanic; ; Dungeon; Wire; ; Mechanic; Decryption Computer; Calamity; Mysterious Circuitry & Dubious Plating & Wire; From 2e1a5b0e3b0acd0559fc9e2246ad9803d5086da2 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Sun, 10 Mar 2024 12:47:45 -0500 Subject: [PATCH 098/166] Core: create the per world random object in the world constructor (#2083) * Core: create the per world random object in the world constructor * remove the check that multiworld exists * add a deprecation warning to per_slot_randoms * move random import and fix conflicts * assert worlds don't exist before setting the multiworld seed * fix the dlcq and sdv tests * actually use the seed --- BaseClasses.py | 9 ++++----- test/general/__init__.py | 9 ++++++--- test/general/test_fill.py | 3 +-- worlds/AutoWorld.py | 3 +++ worlds/dlcquest/test/__init__.py | 3 +-- worlds/stardew_valley/test/__init__.py | 3 +-- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 2be9a9820d..446eea5b48 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -85,7 +85,7 @@ class MultiWorld(): game: Dict[int, str] random: random.Random - per_slot_randoms: Dict[int, random.Random] + per_slot_randoms: Utils.DeprecateDict[int, random.Random] """Deprecated. Please use `self.random` instead.""" class AttributeProxy(): @@ -217,7 +217,8 @@ class MultiWorld(): set_player_attr('game', "A Link to the Past") set_player_attr('completion_condition', lambda state: True) self.worlds = {} - self.per_slot_randoms = {} + self.per_slot_randoms = Utils.DeprecateDict("Using per_slot_randoms is now deprecated. Please use the " + "world's random object instead (usually self.random)") self.plando_options = PlandoOptions.none def get_all_ids(self) -> Tuple[int, ...]: @@ -251,14 +252,13 @@ class MultiWorld(): return {group_id for group_id, group in self.groups.items() if player in group["players"]} def set_seed(self, seed: Optional[int] = None, secure: bool = False, name: Optional[str] = None): + assert not self.worlds, "seed needs to be initialized before Worlds" self.seed = get_seed(seed) if secure: self.secure() else: self.random.seed(self.seed) self.seed_name = name if name else str(self.seed) - self.per_slot_randoms = {player: random.Random(self.random.getrandbits(64)) for player in - range(1, self.players + 1)} def set_options(self, args: Namespace) -> None: # TODO - remove this section once all worlds use options dataclasses @@ -275,7 +275,6 @@ class MultiWorld(): for player in self.player_ids: world_type = AutoWorld.AutoWorldRegister.world_types[self.game[player]] self.worlds[player] = world_type(self, player) - self.worlds[player].random = self.per_slot_randoms[player] options_dataclass: typing.Type[Options.PerGameCommonOptions] = world_type.options_dataclass self.worlds[player].options = options_dataclass(**{option_key: getattr(args, option_key)[player] for option_key in options_dataclass.type_hints}) diff --git a/test/general/__init__.py b/test/general/__init__.py index 5e0f22f4ec..2819628dd0 100644 --- a/test/general/__init__.py +++ b/test/general/__init__.py @@ -1,5 +1,5 @@ from argparse import Namespace -from typing import Type, Tuple +from typing import Optional, Tuple, Type from BaseClasses import MultiWorld, CollectionState from worlds.AutoWorld import call_all, World @@ -7,18 +7,21 @@ from worlds.AutoWorld import call_all, World gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill") -def setup_solo_multiworld(world_type: Type[World], steps: Tuple[str, ...] = gen_steps) -> MultiWorld: +def setup_solo_multiworld( + world_type: Type[World], steps: Tuple[str, ...] = gen_steps, seed: Optional[int] = None +) -> MultiWorld: """ Creates a multiworld with a single player of `world_type`, sets default options, and calls provided gen steps. :param world_type: Type of the world to generate a multiworld for :param steps: The gen steps that should be called on the generated multiworld before returning. Default calls steps through pre_fill + :param seed: The seed to be used when creating this multiworld """ multiworld = MultiWorld(1) multiworld.game[1] = world_type.game multiworld.player_name = {1: "Tester"} - multiworld.set_seed() + multiworld.set_seed(seed) multiworld.state = CollectionState(multiworld) args = Namespace() for name, option in world_type.options_dataclass.type_hints.items(): diff --git a/test/general/test_fill.py b/test/general/test_fill.py index 489417771d..70e9e822bf 100644 --- a/test/general/test_fill.py +++ b/test/general/test_fill.py @@ -13,6 +13,7 @@ from worlds.generic.Rules import CollectionRule, add_item_rule, locality_rules, def generate_multiworld(players: int = 1) -> MultiWorld: multiworld = MultiWorld(players) + multiworld.set_seed(0) multiworld.player_name = {} multiworld.state = CollectionState(multiworld) for i in range(players): @@ -32,8 +33,6 @@ def generate_multiworld(players: int = 1) -> MultiWorld: world.options = world.options_dataclass(**{option_key: getattr(multiworld, option_key)[player_id] for option_key in world.options_dataclass.type_hints}) - multiworld.set_seed(0) - return multiworld diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index 93e4a5c385..4d9b31d17d 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -3,6 +3,7 @@ from __future__ import annotations import hashlib import logging import pathlib +import random import re import sys import time @@ -299,6 +300,8 @@ class World(metaclass=AutoWorldRegister): assert multiworld is not None self.multiworld = multiworld self.player = player + self.random = random.Random(multiworld.random.getrandbits(64)) + multiworld.per_slot_randoms[player] = self.random def __getattr__(self, item: str) -> Any: if item == "settings": diff --git a/worlds/dlcquest/test/__init__.py b/worlds/dlcquest/test/__init__.py index e998bd8a5e..8a39b43a2c 100644 --- a/worlds/dlcquest/test/__init__.py +++ b/worlds/dlcquest/test/__init__.py @@ -37,8 +37,7 @@ def setup_dlc_quest_solo_multiworld(test_options=None, seed=None, _cache: Dict[F if frozen_options in _cache: return _cache[frozen_options] - multiworld = setup_base_solo_multiworld(DLCqworld, ()) - multiworld.set_seed(seed) + multiworld = setup_base_solo_multiworld(DLCqworld, (), seed=seed) # print(f"Seed: {multiworld.seed}") # Uncomment to print the seed for every test args = Namespace() for name, option in DLCqworld.options_dataclass.type_hints.items(): diff --git a/worlds/stardew_valley/test/__init__.py b/worlds/stardew_valley/test/__init__.py index ba037f7a65..948fb83b0b 100644 --- a/worlds/stardew_valley/test/__init__.py +++ b/worlds/stardew_valley/test/__init__.py @@ -124,8 +124,7 @@ def setup_solo_multiworld(test_options=None, seed=None, if frozen_options in _cache: return _cache[frozen_options] - multiworld = setup_base_solo_multiworld(StardewValleyWorld, ()) - multiworld.set_seed(seed) + multiworld = setup_base_solo_multiworld(StardewValleyWorld, (), seed=seed) # print(f"Seed: {multiworld.seed}") # Uncomment to print the seed for every test args = Namespace() for name, option in StardewValleyWorld.options_dataclass.type_hints.items(): From c7e735da15278e75beaa411a65844aae200f3e2e Mon Sep 17 00:00:00 2001 From: axe-y <58866768+axe-y@users.noreply.github.com> Date: Sun, 10 Mar 2024 17:12:55 -0400 Subject: [PATCH 099/166] DLCQuest: progression coin bundle update (#2785) --- worlds/dlcquest/Items.py | 96 +++++--- worlds/dlcquest/Locations.py | 11 + worlds/dlcquest/Options.py | 16 ++ worlds/dlcquest/Regions.py | 13 + worlds/dlcquest/Rules.py | 302 +++++++++++++++--------- worlds/dlcquest/__init__.py | 10 +- worlds/dlcquest/data/items.csv | 16 +- worlds/dlcquest/test/TestItemShuffle.py | 22 +- 8 files changed, 330 insertions(+), 156 deletions(-) diff --git a/worlds/dlcquest/Items.py b/worlds/dlcquest/Items.py index e7008f7b12..65b36fe617 100644 --- a/worlds/dlcquest/Items.py +++ b/worlds/dlcquest/Items.py @@ -25,6 +25,10 @@ class Group(enum.Enum): Item = enum.auto() Coin = enum.auto() Trap = enum.auto() + Twice = enum.auto() + Piece = enum.auto() + Deprecated = enum.auto() + @dataclass(frozen=True) @@ -85,49 +89,75 @@ initialize_item_table() initialize_groups() -def create_trap_items(world, World_Options: Options.DLCQuestOptions, trap_needed: int, random: Random) -> List[Item]: +def create_trap_items(world, world_options: Options.DLCQuestOptions, trap_needed: int, random: Random) -> List[Item]: traps = [] for i in range(trap_needed): trap = random.choice(items_by_group[Group.Trap]) - traps.append(world.create_item(trap)) + traps.append(world.create_item(trap, ItemClassification.trap)) return traps -def create_items(world, World_Options: Options.DLCQuestOptions, locations_count: int, random: Random): +def create_items(world, world_options: Options.DLCQuestOptions, locations_count: int, random: Random): created_items = [] - if World_Options.campaign == Options.Campaign.option_basic or World_Options.campaign == Options.Campaign.option_both: - for item in items_by_group[Group.DLCQuest]: - if item.has_any_group(Group.DLC): - created_items.append(world.create_item(item)) - if item.has_any_group(Group.Item) and World_Options.item_shuffle == Options.ItemShuffle.option_shuffled: - created_items.append(world.create_item(item)) - if World_Options.coinsanity == Options.CoinSanity.option_coin: - coin_bundle_needed = math.floor(825 / World_Options.coinbundlequantity) - for item in items_by_group[Group.DLCQuest]: - if item.has_any_group(Group.Coin): - for i in range(coin_bundle_needed): - created_items.append(world.create_item(item)) - if 825 % World_Options.coinbundlequantity != 0: - created_items.append(world.create_item(item)) + if world_options.campaign == Options.Campaign.option_basic or world_options.campaign == Options.Campaign.option_both: + create_items_basic(world_options, created_items, world) - if (World_Options.campaign == Options.Campaign.option_live_freemium_or_die or - World_Options.campaign == Options.Campaign.option_both): - for item in items_by_group[Group.Freemium]: - if item.has_any_group(Group.DLC): - created_items.append(world.create_item(item)) - if item.has_any_group(Group.Item) and World_Options.item_shuffle == Options.ItemShuffle.option_shuffled: - created_items.append(world.create_item(item)) - if World_Options.coinsanity == Options.CoinSanity.option_coin: - coin_bundle_needed = math.floor(889 / World_Options.coinbundlequantity) - for item in items_by_group[Group.Freemium]: - if item.has_any_group(Group.Coin): - for i in range(coin_bundle_needed): - created_items.append(world.create_item(item)) - if 889 % World_Options.coinbundlequantity != 0: - created_items.append(world.create_item(item)) + if (world_options.campaign == Options.Campaign.option_live_freemium_or_die or + world_options.campaign == Options.Campaign.option_both): + create_items_lfod(world_options, created_items, world) - trap_items = create_trap_items(world, World_Options, locations_count - len(created_items), random) + trap_items = create_trap_items(world, world_options, locations_count - len(created_items), random) created_items += trap_items return created_items + + +def create_items_lfod(world_options, created_items, world): + for item in items_by_group[Group.Freemium]: + if item.has_any_group(Group.DLC): + created_items.append(world.create_item(item)) + if item.has_any_group(Group.Item) and world_options.item_shuffle == Options.ItemShuffle.option_shuffled: + created_items.append(world.create_item(item)) + if item.has_any_group(Group.Twice): + created_items.append(world.create_item(item)) + if world_options.coinsanity == Options.CoinSanity.option_coin: + if world_options.coinbundlequantity == -1: + create_coin_piece(created_items, world, 889, 200, Group.Freemium) + return + create_coin(world_options, created_items, world, 889, 200, Group.Freemium) + + +def create_items_basic(world_options, created_items, world): + for item in items_by_group[Group.DLCQuest]: + if item.has_any_group(Group.DLC): + created_items.append(world.create_item(item)) + if item.has_any_group(Group.Item) and world_options.item_shuffle == Options.ItemShuffle.option_shuffled: + created_items.append(world.create_item(item)) + if item.has_any_group(Group.Twice): + created_items.append(world.create_item(item)) + if world_options.coinsanity == Options.CoinSanity.option_coin: + if world_options.coinbundlequantity == -1: + create_coin_piece(created_items, world, 825, 250, Group.DLCQuest) + return + create_coin(world_options, created_items, world, 825, 250, Group.DLCQuest) + + +def create_coin(world_options, created_items, world, total_coins, required_coins, group): + coin_bundle_required = math.ceil(required_coins / world_options.coinbundlequantity) + coin_bundle_useful = math.ceil((total_coins - coin_bundle_required * world_options.coinbundlequantity) / world_options.coinbundlequantity) + for item in items_by_group[group]: + if item.has_any_group(Group.Coin): + for i in range(coin_bundle_required): + created_items.append(world.create_item(item)) + for i in range(coin_bundle_useful): + created_items.append(world.create_item(item, ItemClassification.useful)) + + +def create_coin_piece(created_items, world, total_coins, required_coins, group): + for item in items_by_group[group]: + if item.has_any_group(Group.Piece): + for i in range(required_coins*10): + created_items.append(world.create_item(item)) + for i in range((total_coins - required_coins) * 10): + created_items.append(world.create_item(item, ItemClassification.useful)) diff --git a/worlds/dlcquest/Locations.py b/worlds/dlcquest/Locations.py index a9fdd00a20..dfc5248529 100644 --- a/worlds/dlcquest/Locations.py +++ b/worlds/dlcquest/Locations.py @@ -76,3 +76,14 @@ for i in range(1, 826): for i in range(1, 890): item_coin_freemium = f"Live Freemium or Die: {i} Coin" location_table[item_coin_freemium] = offset + 825 + 58 + i + + +offset_special = 3829200000 + +for i in range(1, 8251): + item_coin_piece = f"DLC Quest: {i} Coin Piece" + location_table[item_coin_piece] = offset_special + i + +for i in range(1, 8891): + item_coin_piece_freemium = f"Live Freemium or Die: {i} Coin Piece" + location_table[item_coin_piece_freemium] = offset_special + 8250 + i \ No newline at end of file diff --git a/worlds/dlcquest/Options.py b/worlds/dlcquest/Options.py index 769acbec15..067e349b94 100644 --- a/worlds/dlcquest/Options.py +++ b/worlds/dlcquest/Options.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +import datetime from Options import Choice, DeathLink, NamedRange, PerGameCommonOptions @@ -48,6 +49,20 @@ class CoinSanityRange(NamedRange): "normal": 20, "high": 50, } + if datetime.datetime.today().month == 4: + if datetime.datetime.today().day == 1: + special_range_names["surprise"] = -1 + else: + special_range_names["coin piece"] = -1 + + +class PermanentCoins(Choice): + """If purchasing a pack decreases your current coins amounts.""" + internal_name = "permanent_coins" + display_name = "Permanent Coins" + option_false = 0 + option_true = 1 + default = 0 class EndingChoice(Choice): @@ -83,6 +98,7 @@ class DLCQuestOptions(PerGameCommonOptions): double_jump_glitch: DoubleJumpGlitch coinsanity: CoinSanity coinbundlequantity: CoinSanityRange + permanent_coins: PermanentCoins time_is_money: TimeIsMoney ending_choice: EndingChoice campaign: Campaign diff --git a/worlds/dlcquest/Regions.py b/worlds/dlcquest/Regions.py index 6dad9fc10c..5b256afd45 100644 --- a/worlds/dlcquest/Regions.py +++ b/worlds/dlcquest/Regions.py @@ -182,9 +182,22 @@ def create_coinsanity_locations_lfod(has_coinsanity: bool, coin_bundle_size: int def create_coinsanity_locations(has_coinsanity: bool, coin_bundle_size: int, player: int, region: Region, last_coin_number: int, campaign_prefix: str): if not has_coinsanity: return + if coin_bundle_size == -1: + create_coinsanity_piece_locations(player, region, last_coin_number, campaign_prefix) + return + coin_bundle_needed = math.ceil(last_coin_number / coin_bundle_size) for i in range(1, coin_bundle_needed + 1): number_coins = min(last_coin_number, coin_bundle_size * i) item_coin = f"{campaign_prefix}: {number_coins} Coin" region.locations += [DLCQuestLocation(player, item_coin, location_table[item_coin], region)] + + +def create_coinsanity_piece_locations(player: int, region: Region, total_coin: int, campaign_prefix:str): + + pieces_needed = total_coin * 10 + for i in range(1, pieces_needed + 1): + number_piece = i + item_piece = f"{campaign_prefix}: {number_piece} Coin Piece" + region.locations += [DLCQuestLocation(player, item_piece, location_table[item_piece], region)] diff --git a/worlds/dlcquest/Rules.py b/worlds/dlcquest/Rules.py index 5792d9c3ab..3461d0633e 100644 --- a/worlds/dlcquest/Rules.py +++ b/worlds/dlcquest/Rules.py @@ -1,5 +1,4 @@ import math -import re from BaseClasses import ItemClassification from worlds.generic.Rules import add_rule, item_name_in_locations, set_rule @@ -19,23 +18,23 @@ def has_enough_coin_freemium(player: int, coin: int): return lambda state: state.prog_items[player][" coins freemium"] >= coin -def set_rules(world, player, World_Options: Options.DLCQuestOptions): - set_basic_rules(World_Options, player, world) - set_lfod_rules(World_Options, player, world) - set_completion_condition(World_Options, player, world) +def set_rules(world, player, world_options: Options.DLCQuestOptions): + set_basic_rules(world_options, player, world) + set_lfod_rules(world_options, player, world) + set_completion_condition(world_options, player, world) -def set_basic_rules(World_Options, player, world): - if World_Options.campaign == Options.Campaign.option_live_freemium_or_die: +def set_basic_rules(world_options, player, world): + if world_options.campaign == Options.Campaign.option_live_freemium_or_die: return set_basic_entrance_rules(player, world) - set_basic_self_obtained_items_rules(World_Options, player, world) - set_basic_shuffled_items_rules(World_Options, player, world) - set_double_jump_glitchless_rules(World_Options, player, world) - set_easy_double_jump_glitch_rules(World_Options, player, world) - self_basic_coinsanity_funded_purchase_rules(World_Options, player, world) - set_basic_self_funded_purchase_rules(World_Options, player, world) - self_basic_win_condition(World_Options, player, world) + set_basic_self_obtained_items_rules(world_options, player, world) + set_basic_shuffled_items_rules(world_options, player, world) + set_double_jump_glitchless_rules(world_options, player, world) + set_easy_double_jump_glitch_rules(world_options, player, world) + self_basic_coinsanity_funded_purchase_rules(world_options, player, world) + set_basic_self_funded_purchase_rules(world_options, player, world) + self_basic_win_condition(world_options, player, world) def set_basic_entrance_rules(player, world): @@ -49,13 +48,13 @@ def set_basic_entrance_rules(player, world): lambda state: state.has("Double Jump Pack", player)) -def set_basic_self_obtained_items_rules(World_Options, player, world): - if World_Options.item_shuffle != Options.ItemShuffle.option_disabled: +def set_basic_self_obtained_items_rules(world_options, player, world): + if world_options.item_shuffle != Options.ItemShuffle.option_disabled: return set_rule(world.get_entrance("Behind Ogre", player), lambda state: state.has("Gun Pack", player)) - if World_Options.time_is_money == Options.TimeIsMoney.option_required: + if world_options.time_is_money == Options.TimeIsMoney.option_required: set_rule(world.get_entrance("Tree", player), lambda state: state.has("Time is Money Pack", player)) set_rule(world.get_entrance("Cave Tree", player), @@ -70,35 +69,35 @@ def set_basic_self_obtained_items_rules(World_Options, player, world): lambda state: state.has("Time is Money Pack", player)) -def set_basic_shuffled_items_rules(World_Options, player, world): - if World_Options.item_shuffle != Options.ItemShuffle.option_shuffled: +def set_basic_shuffled_items_rules(world_options, player, world): + if world_options.item_shuffle != Options.ItemShuffle.option_shuffled: return set_rule(world.get_entrance("Behind Ogre", player), - lambda state: state.has("Gun", player)) + lambda state: state.has("DLC Quest: Progressive Weapon", player, 2)) set_rule(world.get_entrance("Tree", player), - lambda state: state.has("Sword", player) or state.has("Gun", player)) + lambda state: state.has("DLC Quest: Progressive Weapon", player)) set_rule(world.get_entrance("Cave Tree", player), - lambda state: state.has("Sword", player) or state.has("Gun", player)) + lambda state: state.has("DLC Quest: Progressive Weapon", player)) set_rule(world.get_entrance("True Double Jump", player), lambda state: state.has("Double Jump Pack", player)) set_rule(world.get_location("Shepherd Sheep", player), - lambda state: state.has("Sword", player) or state.has("Gun", player)) + lambda state: state.has("DLC Quest: Progressive Weapon", player)) set_rule(world.get_location("North West Ceiling Sheep", player), - lambda state: state.has("Sword", player) or state.has("Gun", player)) + lambda state: state.has("DLC Quest: Progressive Weapon", player)) set_rule(world.get_location("North West Alcove Sheep", player), - lambda state: state.has("Sword", player) or state.has("Gun", player)) + lambda state: state.has("DLC Quest: Progressive Weapon", player)) set_rule(world.get_location("West Cave Sheep", player), - lambda state: state.has("Sword", player) or state.has("Gun", player)) + lambda state: state.has("DLC Quest: Progressive Weapon", player)) set_rule(world.get_location("Gun", player), lambda state: state.has("Gun Pack", player)) - if World_Options.time_is_money == Options.TimeIsMoney.option_required: + if world_options.time_is_money == Options.TimeIsMoney.option_required: set_rule(world.get_location("Sword", player), lambda state: state.has("Time is Money Pack", player)) -def set_double_jump_glitchless_rules(World_Options, player, world): - if World_Options.double_jump_glitch != Options.DoubleJumpGlitch.option_none: +def set_double_jump_glitchless_rules(world_options, player, world): + if world_options.double_jump_glitch != Options.DoubleJumpGlitch.option_none: return set_rule(world.get_entrance("Cloud Double Jump", player), lambda state: state.has("Double Jump Pack", player)) @@ -106,8 +105,8 @@ def set_double_jump_glitchless_rules(World_Options, player, world): lambda state: state.has("Double Jump Pack", player)) -def set_easy_double_jump_glitch_rules(World_Options, player, world): - if World_Options.double_jump_glitch == Options.DoubleJumpGlitch.option_all: +def set_easy_double_jump_glitch_rules(world_options, player, world): + if world_options.double_jump_glitch == Options.DoubleJumpGlitch.option_all: return set_rule(world.get_entrance("Behind Tree Double Jump", player), lambda state: state.has("Double Jump Pack", player)) @@ -115,71 +114,74 @@ def set_easy_double_jump_glitch_rules(World_Options, player, world): lambda state: state.has("Double Jump Pack", player)) -def self_basic_coinsanity_funded_purchase_rules(World_Options, player, world): - if World_Options.coinsanity != Options.CoinSanity.option_coin: +def self_basic_coinsanity_funded_purchase_rules(world_options, player, world): + if world_options.coinsanity != Options.CoinSanity.option_coin: return - number_of_bundle = math.floor(825 / World_Options.coinbundlequantity) + if world_options.coinbundlequantity == -1: + self_basic_coinsanity_piece_rules(player, world) + return + number_of_bundle = math.floor(825 / world_options.coinbundlequantity) for i in range(number_of_bundle): - item_coin = f"DLC Quest: {World_Options.coinbundlequantity * (i + 1)} Coin" + item_coin = f"DLC Quest: {world_options.coinbundlequantity * (i + 1)} Coin" set_rule(world.get_location(item_coin, player), - has_enough_coin(player, World_Options.coinbundlequantity * (i + 1))) - if 825 % World_Options.coinbundlequantity != 0: + has_enough_coin(player, world_options.coinbundlequantity * (i + 1))) + if 825 % world_options.coinbundlequantity != 0: set_rule(world.get_location("DLC Quest: 825 Coin", player), has_enough_coin(player, 825)) set_rule(world.get_location("Movement Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(4 / World_Options.coinbundlequantity))) + math.ceil(4 / world_options.coinbundlequantity))) set_rule(world.get_location("Animation Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(5 / World_Options.coinbundlequantity))) + math.ceil(5 / world_options.coinbundlequantity))) set_rule(world.get_location("Audio Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(5 / World_Options.coinbundlequantity))) + math.ceil(5 / world_options.coinbundlequantity))) set_rule(world.get_location("Pause Menu Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(5 / World_Options.coinbundlequantity))) + math.ceil(5 / world_options.coinbundlequantity))) set_rule(world.get_location("Time is Money Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(20 / World_Options.coinbundlequantity))) + math.ceil(20 / world_options.coinbundlequantity))) set_rule(world.get_location("Double Jump Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(100 / World_Options.coinbundlequantity))) + math.ceil(100 / world_options.coinbundlequantity))) set_rule(world.get_location("Pet Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(5 / World_Options.coinbundlequantity))) + math.ceil(5 / world_options.coinbundlequantity))) set_rule(world.get_location("Sexy Outfits Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(5 / World_Options.coinbundlequantity))) + math.ceil(5 / world_options.coinbundlequantity))) set_rule(world.get_location("Top Hat Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(5 / World_Options.coinbundlequantity))) + math.ceil(5 / world_options.coinbundlequantity))) set_rule(world.get_location("Map Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(140 / World_Options.coinbundlequantity))) + math.ceil(140 / world_options.coinbundlequantity))) set_rule(world.get_location("Gun Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(75 / World_Options.coinbundlequantity))) + math.ceil(75 / world_options.coinbundlequantity))) set_rule(world.get_location("The Zombie Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(5 / World_Options.coinbundlequantity))) + math.ceil(5 / world_options.coinbundlequantity))) set_rule(world.get_location("Night Map Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(75 / World_Options.coinbundlequantity))) + math.ceil(75 / world_options.coinbundlequantity))) set_rule(world.get_location("Psychological Warfare Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(50 / World_Options.coinbundlequantity))) + math.ceil(50 / world_options.coinbundlequantity))) set_rule(world.get_location("Armor for your Horse Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(250 / World_Options.coinbundlequantity))) + math.ceil(250 / world_options.coinbundlequantity))) set_rule(world.get_location("Finish the Fight Pack", player), lambda state: state.has("DLC Quest: Coin Bundle", player, - math.ceil(5 / World_Options.coinbundlequantity))) + math.ceil(5 / world_options.coinbundlequantity))) -def set_basic_self_funded_purchase_rules(World_Options, player, world): - if World_Options.coinsanity != Options.CoinSanity.option_none: +def set_basic_self_funded_purchase_rules(world_options, player, world): + if world_options.coinsanity != Options.CoinSanity.option_none: return set_rule(world.get_location("Movement Pack", player), has_enough_coin(player, 4)) @@ -215,25 +217,25 @@ def set_basic_self_funded_purchase_rules(World_Options, player, world): has_enough_coin(player, 5)) -def self_basic_win_condition(World_Options, player, world): - if World_Options.ending_choice == Options.EndingChoice.option_any: +def self_basic_win_condition(world_options, player, world): + if world_options.ending_choice == Options.EndingChoice.option_any: set_rule(world.get_location("Winning Basic", player), lambda state: state.has("Finish the Fight Pack", player)) - if World_Options.ending_choice == Options.EndingChoice.option_true: + if world_options.ending_choice == Options.EndingChoice.option_true: set_rule(world.get_location("Winning Basic", player), lambda state: state.has("Armor for your Horse Pack", player) and state.has("Finish the Fight Pack", player)) -def set_lfod_rules(World_Options, player, world): - if World_Options.campaign == Options.Campaign.option_basic: +def set_lfod_rules(world_options, player, world): + if world_options.campaign == Options.Campaign.option_basic: return set_lfod_entrance_rules(player, world) set_boss_door_requirements_rules(player, world) - set_lfod_self_obtained_items_rules(World_Options, player, world) - set_lfod_shuffled_items_rules(World_Options, player, world) - self_lfod_coinsanity_funded_purchase_rules(World_Options, player, world) - set_lfod_self_funded_purchase_rules(World_Options, has_enough_coin_freemium, player, world) + set_lfod_self_obtained_items_rules(world_options, player, world) + set_lfod_shuffled_items_rules(world_options, player, world) + self_lfod_coinsanity_funded_purchase_rules(world_options, player, world) + set_lfod_self_funded_purchase_rules(world_options, has_enough_coin_freemium, player, world) def set_lfod_entrance_rules(player, world): @@ -251,8 +253,6 @@ def set_lfod_entrance_rules(player, world): lambda state: state.has("Death of Comedy Pack", player)) set_rule(world.get_location("Story is Important", player), lambda state: state.has("DLC NPC Pack", player)) - set_rule(world.get_entrance("Pickaxe Hard Cave", player), - lambda state: state.has("Pickaxe", player)) def set_boss_door_requirements_rules(player, world): @@ -280,8 +280,8 @@ def set_boss_door_requirements_rules(player, world): set_rule(world.get_entrance("Boss Door", player), has_3_swords) -def set_lfod_self_obtained_items_rules(World_Options, player, world): - if World_Options.item_shuffle != Options.ItemShuffle.option_disabled: +def set_lfod_self_obtained_items_rules(world_options, player, world): + if world_options.item_shuffle != Options.ItemShuffle.option_disabled: return set_rule(world.get_entrance("Vines", player), lambda state: state.has("Incredibly Important Pack", player)) @@ -292,13 +292,15 @@ def set_lfod_self_obtained_items_rules(World_Options, player, world): state.has("Name Change Pack", player)) -def set_lfod_shuffled_items_rules(World_Options, player, world): - if World_Options.item_shuffle != Options.ItemShuffle.option_shuffled: +def set_lfod_shuffled_items_rules(world_options, player, world): + if world_options.item_shuffle != Options.ItemShuffle.option_shuffled: return set_rule(world.get_entrance("Vines", player), - lambda state: state.has("Wooden Sword", player) or state.has("Pickaxe", player)) + lambda state: state.has("Live Freemium or Die: Progressive Weapon", player)) set_rule(world.get_entrance("Behind Rocks", player), - lambda state: state.has("Pickaxe", player)) + lambda state: state.has("Live Freemium or Die: Progressive Weapon", player, 2)) + set_rule(world.get_entrance("Pickaxe Hard Cave", player), + lambda state: state.has("Live Freemium or Die: Progressive Weapon", player, 2)) set_rule(world.get_location("Wooden Sword", player), lambda state: state.has("Incredibly Important Pack", player)) @@ -311,83 +313,84 @@ def set_lfod_shuffled_items_rules(World_Options, player, world): lambda state: state.can_reach("Cut Content", 'region', player)) -def self_lfod_coinsanity_funded_purchase_rules(World_Options, player, world): - if World_Options.coinsanity != Options.CoinSanity.option_coin: +def self_lfod_coinsanity_funded_purchase_rules(world_options, player, world): + if world_options.coinsanity != Options.CoinSanity.option_coin: return - number_of_bundle = math.floor(889 / World_Options.coinbundlequantity) + if world_options.coinbundlequantity == -1: + self_lfod_coinsanity_piece_rules(player, world) + return + number_of_bundle = math.floor(889 / world_options.coinbundlequantity) for i in range(number_of_bundle): - item_coin_freemium = "Live Freemium or Die: number Coin" - item_coin_loc_freemium = re.sub("number", str(World_Options.coinbundlequantity * (i + 1)), - item_coin_freemium) - set_rule(world.get_location(item_coin_loc_freemium, player), - has_enough_coin_freemium(player, World_Options.coinbundlequantity * (i + 1))) - if 889 % World_Options.coinbundlequantity != 0: + item_coin_freemium = f"Live Freemium or Die: {world_options.coinbundlequantity * (i + 1)} Coin" + set_rule(world.get_location(item_coin_freemium, player), + has_enough_coin_freemium(player, world_options.coinbundlequantity * (i + 1))) + if 889 % world_options.coinbundlequantity != 0: set_rule(world.get_location("Live Freemium or Die: 889 Coin", player), has_enough_coin_freemium(player, 889)) add_rule(world.get_entrance("Boss Door", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(889 / World_Options.coinbundlequantity))) + math.ceil(200 / world_options.coinbundlequantity))) set_rule(world.get_location("Particles Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(5 / World_Options.coinbundlequantity))) + math.ceil(5 / world_options.coinbundlequantity))) set_rule(world.get_location("Day One Patch Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(5 / World_Options.coinbundlequantity))) + math.ceil(5 / world_options.coinbundlequantity))) set_rule(world.get_location("Checkpoint Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(5 / World_Options.coinbundlequantity))) + math.ceil(5 / world_options.coinbundlequantity))) set_rule(world.get_location("Incredibly Important Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(15 / World_Options.coinbundlequantity))) + math.ceil(15 / world_options.coinbundlequantity))) set_rule(world.get_location("Wall Jump Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(35 / World_Options.coinbundlequantity))) + math.ceil(35 / world_options.coinbundlequantity))) set_rule(world.get_location("Health Bar Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(5 / World_Options.coinbundlequantity))) + math.ceil(5 / world_options.coinbundlequantity))) set_rule(world.get_location("Parallax Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(5 / World_Options.coinbundlequantity))) + math.ceil(5 / world_options.coinbundlequantity))) set_rule(world.get_location("Harmless Plants Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(130 / World_Options.coinbundlequantity))) + math.ceil(130 / world_options.coinbundlequantity))) set_rule(world.get_location("Death of Comedy Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(15 / World_Options.coinbundlequantity))) + math.ceil(15 / world_options.coinbundlequantity))) set_rule(world.get_location("Canadian Dialog Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(10 / World_Options.coinbundlequantity))) + math.ceil(10 / world_options.coinbundlequantity))) set_rule(world.get_location("DLC NPC Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(15 / World_Options.coinbundlequantity))) + math.ceil(15 / world_options.coinbundlequantity))) set_rule(world.get_location("Cut Content Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(40 / World_Options.coinbundlequantity))) + math.ceil(40 / world_options.coinbundlequantity))) set_rule(world.get_location("Name Change Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(150 / World_Options.coinbundlequantity))) + math.ceil(150 / world_options.coinbundlequantity))) set_rule(world.get_location("Season Pass", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(199 / World_Options.coinbundlequantity))) + math.ceil(199 / world_options.coinbundlequantity))) set_rule(world.get_location("High Definition Next Gen Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(20 / World_Options.coinbundlequantity))) + math.ceil(20 / world_options.coinbundlequantity))) set_rule(world.get_location("Increased HP Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(10 / World_Options.coinbundlequantity))) + math.ceil(10 / world_options.coinbundlequantity))) set_rule(world.get_location("Remove Ads Pack", player), lambda state: state.has("Live Freemium or Die: Coin Bundle", player, - math.ceil(25 / World_Options.coinbundlequantity))) + math.ceil(25 / world_options.coinbundlequantity))) -def set_lfod_self_funded_purchase_rules(World_Options, has_enough_coin_freemium, player, world): - if World_Options.coinsanity != Options.CoinSanity.option_none: +def set_lfod_self_funded_purchase_rules(world_options, has_enough_coin_freemium, player, world): + if world_options.coinsanity != Options.CoinSanity.option_none: return add_rule(world.get_entrance("Boss Door", player), - has_enough_coin_freemium(player, 889)) + has_enough_coin_freemium(player, 200)) set_rule(world.get_location("Particles Pack", player), has_enough_coin_freemium(player, 5)) @@ -425,11 +428,98 @@ def set_lfod_self_funded_purchase_rules(World_Options, has_enough_coin_freemium, has_enough_coin_freemium(player, 25)) -def set_completion_condition(World_Options, player, world): - if World_Options.campaign == Options.Campaign.option_basic: +def set_completion_condition(world_options, player, world): + if world_options.campaign == Options.Campaign.option_basic: world.completion_condition[player] = lambda state: state.has("Victory Basic", player) - if World_Options.campaign == Options.Campaign.option_live_freemium_or_die: + if world_options.campaign == Options.Campaign.option_live_freemium_or_die: world.completion_condition[player] = lambda state: state.has("Victory Freemium", player) - if World_Options.campaign == Options.Campaign.option_both: + if world_options.campaign == Options.Campaign.option_both: world.completion_condition[player] = lambda state: state.has("Victory Basic", player) and state.has( "Victory Freemium", player) + + +def self_basic_coinsanity_piece_rules(player, world): + for i in range(1,8251): + + item_coin = f"DLC Quest: {i} Coin Piece" + set_rule(world.get_location(item_coin, player), + has_enough_coin(player, math.ceil(i / 10))) + + set_rule(world.get_location("Movement Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 40)) + set_rule(world.get_location("Animation Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 50)) + set_rule(world.get_location("Audio Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 50)) + set_rule(world.get_location("Pause Menu Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 50)) + set_rule(world.get_location("Time is Money Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 200)) + set_rule(world.get_location("Double Jump Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 100)) + set_rule(world.get_location("Pet Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 50)) + set_rule(world.get_location("Sexy Outfits Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 50)) + set_rule(world.get_location("Top Hat Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 50)) + set_rule(world.get_location("Map Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 1400)) + set_rule(world.get_location("Gun Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 750)) + set_rule(world.get_location("The Zombie Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 50)) + set_rule(world.get_location("Night Map Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 750)) + set_rule(world.get_location("Psychological Warfare Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 500)) + set_rule(world.get_location("Armor for your Horse Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 2500)) + set_rule(world.get_location("Finish the Fight Pack", player), + lambda state: state.has("DLC Quest: Coin Piece", player, 50)) + + +def self_lfod_coinsanity_piece_rules(player, world): + for i in range(1, 8891): + + item_coin_freemium = f"Live Freemium or Die: {i} Coin Piece" + set_rule(world.get_location(item_coin_freemium, player), + has_enough_coin_freemium(player, math.ceil(i / 10))) + + add_rule(world.get_entrance("Boss Door", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 2000)) + + set_rule(world.get_location("Particles Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50)) + set_rule(world.get_location("Day One Patch Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50)) + set_rule(world.get_location("Checkpoint Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50)) + set_rule(world.get_location("Incredibly Important Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 150)) + set_rule(world.get_location("Wall Jump Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 350)) + set_rule(world.get_location("Health Bar Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50)) + set_rule(world.get_location("Parallax Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50)) + set_rule(world.get_location("Harmless Plants Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 1300)) + set_rule(world.get_location("Death of Comedy Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 150)) + set_rule(world.get_location("Canadian Dialog Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 100)) + set_rule(world.get_location("DLC NPC Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 150)) + set_rule(world.get_location("Cut Content Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 400)) + set_rule(world.get_location("Name Change Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 1500)) + set_rule(world.get_location("Season Pass", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 199)) + set_rule(world.get_location("High Definition Next Gen Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 20)) + set_rule(world.get_location("Increased HP Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 100)) + set_rule(world.get_location("Remove Ads Pack", player), + lambda state: state.has("Live Freemium or Die: Coin Piece", player, 250)) diff --git a/worlds/dlcquest/__init__.py b/worlds/dlcquest/__init__.py index db55b1903b..2200729a32 100644 --- a/worlds/dlcquest/__init__.py +++ b/worlds/dlcquest/__init__.py @@ -1,6 +1,6 @@ from typing import Union -from BaseClasses import Tutorial, CollectionState +from BaseClasses import Tutorial, CollectionState, ItemClassification from worlds.AutoWorld import WebWorld, World from . import Options from .Items import DLCQuestItem, ItemData, create_items, item_table, items_by_group, Group @@ -82,11 +82,13 @@ class DLCqworld(World): if self.options.coinsanity == Options.CoinSanity.option_coin and self.options.coinbundlequantity >= 5: self.multiworld.push_precollected(self.create_item("Movement Pack")) - def create_item(self, item: Union[str, ItemData]) -> DLCQuestItem: + def create_item(self, item: Union[str, ItemData], classification: ItemClassification = None) -> DLCQuestItem: if isinstance(item, str): item = item_table[item] + if classification is None: + classification = item.classification - return DLCQuestItem(item.name, item.classification, item.code, self.player) + return DLCQuestItem(item.name, classification, item.code, self.player) def get_filler_item_name(self) -> str: trap = self.multiworld.random.choice(items_by_group[Group.Trap]) @@ -94,7 +96,7 @@ class DLCqworld(World): def fill_slot_data(self): options_dict = self.options.as_dict( - "death_link", "ending_choice", "campaign", "coinsanity", "item_shuffle" + "death_link", "ending_choice", "campaign", "coinsanity", "item_shuffle", "permanent_coins" ) options_dict.update({ "coinbundlerange": self.options.coinbundlequantity.value, diff --git a/worlds/dlcquest/data/items.csv b/worlds/dlcquest/data/items.csv index cc5ac0bbe4..82150254b3 100644 --- a/worlds/dlcquest/data/items.csv +++ b/worlds/dlcquest/data/items.csv @@ -27,8 +27,8 @@ id,name,classification,groups 25,Canadian Dialog Pack,filler,"DLC,Freemium" 26,DLC NPC Pack,progression,"DLC,Freemium" 27,Cut Content Pack,progression,"DLC,Freemium" -28,Name Change Pack,progression,"DLC,Freemium" -29,Pickaxe,progression,"Item,Freemium" +28,Name Change Pack,progression,"DLC,Freemium,Trap" +29,Pickaxe,progression,"Deprecated" 30,Season Pass,progression,"DLC,Freemium" 31,High Definition Next Gen Pack,filler,"DLC,Freemium" 32,Increased HP Pack,useful,"DLC,Freemium" @@ -36,13 +36,17 @@ id,name,classification,groups 34,Big Sword Pack,progression,"DLC,Freemium" 35,Really Big Sword Pack,progression,"DLC,Freemium" 36,Unfathomable Sword Pack,progression,"DLC,Freemium" -37,Gun,progression,"Item,DLCQuest" -38,Sword,progression,"Item,DLCQuest" -39,Wooden Sword,progression,"Item,Freemium" +37,Gun,progression,"Deprecated" +38,Sword,progression,"Deprecated" +39,Wooden Sword,progression,"Deprecated" 40,Box of Various Supplies,progression,"Item,Freemium" 41,Humble Indie Bindle,progression,"Item,Freemium" 42,DLC Quest: Coin Bundle,progression,"Coin,DLCQuest" 43,Live Freemium or Die: Coin Bundle,progression,"Coin,Freemium" 44,Zombie Sheep,trap,Trap 45,Temporary Spike,trap,Trap -46,Loading Screen,trap,Trap \ No newline at end of file +46,Loading Screen,trap,Trap +48,DLC Quest: Progressive Weapon,progression,"Item,Twice,DLCQuest" +49,Live Freemium or Die: Progressive Weapon,progression,"Item,Twice,Freemium" +50,DLC Quest: Coin Piece,progression,"Piece,DLCQuest" +51,Live Freemium or Die: Coin Piece,progression,"Piece,Freemium" \ No newline at end of file diff --git a/worlds/dlcquest/test/TestItemShuffle.py b/worlds/dlcquest/test/TestItemShuffle.py index bfe999246a..7a9e5d95ba 100644 --- a/worlds/dlcquest/test/TestItemShuffle.py +++ b/worlds/dlcquest/test/TestItemShuffle.py @@ -7,7 +7,10 @@ wooden_sword = "Wooden Sword" pickaxe = "Pickaxe" humble_bindle = "Humble Indie Bindle" box_supplies = "Box of Various Supplies" -items = [sword, gun, wooden_sword, pickaxe, humble_bindle, box_supplies] +locations = [sword, gun, wooden_sword, pickaxe, humble_bindle, box_supplies] +prog_weapon_basic = "DLC Quest: Progressive Weapon" +prog_weapon_lfod = "Live Freemium or Die: Progressive Weapon" +items = [prog_weapon_basic, prog_weapon_lfod, humble_bindle, box_supplies] important_pack = "Incredibly Important Pack" @@ -22,9 +25,14 @@ class TestItemShuffle(DLCQuestTestBase): with self.subTest(f"{item}"): self.assertIn(item, item_names) + def test_progressive_weapon_in_pool(self): + item_names = [item.name for item in self.multiworld.get_items()] + self.assertEqual(item_names.count(prog_weapon_basic), 2) + self.assertEqual(item_names.count(prog_weapon_lfod), 2) + def test_item_locations_in_pool(self): location_names = {location.name for location in self.multiworld.get_locations()} - for item_location in items: + for item_location in locations: with self.subTest(f"{item_location}"): self.assertIn(item_location, location_names) @@ -42,7 +50,7 @@ class TestItemShuffle(DLCQuestTestBase): movement_pack = self.multiworld.create_item("Movement Pack", self.player) self.collect(movement_pack) self.assertFalse(self.can_reach_location(gun)) - sword_item = self.multiworld.create_item(sword, self.player) + sword_item = self.multiworld.create_item(prog_weapon_basic, self.player) self.collect(sword_item) self.assertFalse(self.can_reach_location(gun)) gun_pack = self.multiworld.create_item("Gun Pack", self.player) @@ -57,7 +65,7 @@ class TestItemShuffle(DLCQuestTestBase): def test_bindle_location_has_correct_rules(self): self.assertFalse(self.can_reach_location(humble_bindle)) - wooden_sword_item = self.multiworld.create_item(wooden_sword, self.player) + wooden_sword_item = self.multiworld.create_item(prog_weapon_lfod, self.player) self.collect(wooden_sword_item) self.assertFalse(self.can_reach_location(humble_bindle)) plants_pack = self.multiworld.create_item("Harmless Plants Pack", self.player) @@ -78,7 +86,7 @@ class TestItemShuffle(DLCQuestTestBase): def test_box_supplies_location_has_correct_rules(self): self.assertFalse(self.can_reach_location(box_supplies)) - wooden_sword_item = self.multiworld.create_item(wooden_sword, self.player) + wooden_sword_item = self.multiworld.create_item(prog_weapon_lfod, self.player) self.collect(wooden_sword_item) self.assertFalse(self.can_reach_location(box_supplies)) plants_pack = self.multiworld.create_item("Harmless Plants Pack", self.player) @@ -96,7 +104,7 @@ class TestItemShuffle(DLCQuestTestBase): def test_pickaxe_location_has_correct_rules(self): self.assertFalse(self.can_reach_location(pickaxe)) - wooden_sword_item = self.multiworld.create_item(wooden_sword, self.player) + wooden_sword_item = self.multiworld.create_item(prog_weapon_lfod, self.player) self.collect(wooden_sword_item) self.assertFalse(self.can_reach_location(pickaxe)) plants_pack = self.multiworld.create_item("Harmless Plants Pack", self.player) @@ -125,6 +133,6 @@ class TestNoItemShuffle(DLCQuestTestBase): def test_item_locations_not_in_pool(self): location_names = {location.name for location in self.multiworld.get_locations()} - for item_location in items: + for item_location in locations: with self.subTest(f"{item_location}"): self.assertNotIn(item_location, location_names) \ No newline at end of file From 6badc752375630bdd1054fec6d3d89a5f90ba3ae Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Mon, 11 Mar 2024 00:16:48 -0600 Subject: [PATCH 100/166] BizHawkClient: Fix error logging in python 3.8 (#2930) --- worlds/_bizhawk/context.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/worlds/_bizhawk/context.py b/worlds/_bizhawk/context.py index 4ee6e24f59..85e2c99097 100644 --- a/worlds/_bizhawk/context.py +++ b/worlds/_bizhawk/context.py @@ -7,7 +7,6 @@ checking or launching the client, otherwise it will probably cause circular impo import asyncio import enum import subprocess -import traceback from typing import Any, Dict, Optional from CommonClient import CommonContext, ClientCommandProcessor, get_base_parser, server_loop, logger, gui_enabled @@ -260,7 +259,7 @@ def launch() -> None: try: await watcher_task except Exception as e: - logger.error("".join(traceback.format_exception(e))) + logger.exception(e) await ctx.exit_event.wait() await ctx.shutdown() From fed3d04c8daba1f1de53adc25989493943f701ea Mon Sep 17 00:00:00 2001 From: wildham <64616385+wildham0@users.noreply.github.com> Date: Mon, 11 Mar 2024 04:51:51 -0400 Subject: [PATCH 101/166] FF1: Fix resending items on disconnect/connect (#2817) --- data/lua/connector_ff1.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/lua/connector_ff1.lua b/data/lua/connector_ff1.lua index 455b046961..afae5d3c81 100644 --- a/data/lua/connector_ff1.lua +++ b/data/lua/connector_ff1.lua @@ -322,7 +322,7 @@ function processBlock(block) end end end - if #itemsBlock ~= itemIndex then + if #itemsBlock > itemIndex then wU8(ITEM_INDEX, #itemsBlock) end From 9c920fbc53b15f7759a04021391f69d8d5082be2 Mon Sep 17 00:00:00 2001 From: chandler05 <66492208+chandler05@users.noreply.github.com> Date: Mon, 11 Mar 2024 03:55:22 -0500 Subject: [PATCH 102/166] MultiServer: Improve command response to help troubleshooting (#2833) * Update MultiServer.py * Improve logging again * Add to other log as well * Update MultiServer.py --- MultiServer.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/MultiServer.py b/MultiServer.py index c2d8e4ad58..c374d6d704 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -1052,17 +1052,19 @@ def get_intended_text(input_text: str, possible_answers) -> typing.Tuple[str, bo if picks[0][1] == 100: return picks[0][0], True, "Perfect Match" elif picks[0][1] < 75: - return picks[0][0], False, f"Didn't find something that closely matches, " \ - f"did you mean {picks[0][0]}? ({picks[0][1]}% sure)" + return picks[0][0], False, f"Didn't find something that closely matches '{input_text}', " \ + f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)" elif dif > 5: return picks[0][0], True, "Close Match" else: - return picks[0][0], False, f"Too many close matches, did you mean {picks[0][0]}? ({picks[0][1]}% sure)" + return picks[0][0], False, f"Too many close matches for '{input_text}', " \ + f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)" else: if picks[0][1] > 90: return picks[0][0], True, "Only Option Match" else: - return picks[0][0], False, f"Did you mean {picks[0][0]}? ({picks[0][1]}% sure)" + return picks[0][0], False, f"Didn't find something that closely matches '{input_text}', " \ + f"did you mean '{picks[0][0]}'? ({picks[0][1]}% sure)" class CommandMeta(type): From 5fecb7f0433c306b8ddb4b368eb14fdbd0454e3b Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Mon, 11 Mar 2024 04:00:28 -0500 Subject: [PATCH 103/166] LTTP: fix some hashed string comparisons (#2927) --- worlds/alttp/ItemPool.py | 2 +- worlds/alttp/Options.py | 2 +- worlds/alttp/Rules.py | 2 +- worlds/alttp/__init__.py | 21 ++++++++++--------- .../TestInvertedMinor.py | 5 +++-- .../test/inverted_owg/TestInvertedOWG.py | 5 +++-- worlds/alttp/test/minor_glitches/TestMinor.py | 3 ++- worlds/alttp/test/owg/TestVanillaOWG.py | 3 ++- worlds/alttp/test/vanilla/TestVanilla.py | 3 ++- 9 files changed, 26 insertions(+), 20 deletions(-) diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index 0e799a61e6..3929342aa5 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -605,7 +605,7 @@ def get_pool_core(world, player: int): placed_items[loc] = item # provide boots to major glitch dependent seeds - if logic in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.glitch_boots[player]: + if logic.current_key in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.glitch_boots[player]: precollected_items.append('Pegasus Boots') pool.remove('Pegasus Boots') pool.append('Rupees (20)') diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py index afd5295545..2b23dc341c 100644 --- a/worlds/alttp/Options.py +++ b/worlds/alttp/Options.py @@ -156,7 +156,7 @@ class OpenPyramid(Choice): return world.goal[player].current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} elif self.value == self.option_auto: return world.goal[player].current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} \ - and (world.entrance_shuffle[player] in {'vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'} or not + and (world.entrance_shuffle[player].current_key in {'vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'} or not world.shuffle_ganon) elif self.value == self.option_open: return True diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index c3156116e4..320f9fe6fd 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -89,7 +89,7 @@ def set_rules(world): if world.mode[player] != 'inverted': set_big_bomb_rules(world, player) - if world.glitches_required[player] in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.entrance_shuffle[player] not in {'insanity', 'insanity_legacy', 'madness'}: + if world.glitches_required[player].current_key in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.entrance_shuffle[player].current_key not in {'insanity', 'insanity_legacy', 'madness'}: path_to_courtyard = mirrorless_path_to_castle_courtyard(world, player) add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.multiworld.get_entrance('Dark Death Mountain Offset Mirror', player).can_reach(state) and all(rule(state) for rule in path_to_courtyard), 'or') else: diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index a7ade61c9e..63c53007d8 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -642,17 +642,18 @@ class ALTTPWorld(World): return ALttPItem(name, self.player, **item_init_table[name]) @classmethod - def stage_fill_hook(cls, world, progitempool, usefulitempool, filleritempool, fill_locations): + def stage_fill_hook(cls, multiworld, progitempool, usefulitempool, filleritempool, fill_locations): trash_counts = {} - for player in world.get_game_players("A Link to the Past"): - if not world.ganonstower_vanilla[player] or \ - world.glitches_required[player] in {'overworld_glitches', 'hybrid_major_glitches', "no_logic"}: + for player in multiworld.get_game_players("A Link to the Past"): + world = multiworld.worlds[player] + if not multiworld.ganonstower_vanilla[player] or \ + world.options.glitches_required.current_key in {'overworld_glitches', 'hybrid_major_glitches', "no_logic"}: pass - elif 'triforce_hunt' in world.goal[player].current_key and ('local' in world.goal[player].current_key or world.players == 1): - trash_counts[player] = world.random.randint(world.crystals_needed_for_gt[player] * 2, - world.crystals_needed_for_gt[player] * 4) + elif 'triforce_hunt' in world.options.goal.current_key and ('local' in world.options.goal.current_key or world.players == 1): + trash_counts[player] = multiworld.random.randint(world.options.crystals_needed_for_gt * 2, + world.options.crystals_needed_for_gt * 4) else: - trash_counts[player] = world.random.randint(0, world.crystals_needed_for_gt[player] * 2) + trash_counts[player] = multiworld.random.randint(0, world.options.crystals_needed_for_gt * 2) if trash_counts: locations_mapping = {player: [] for player in trash_counts} @@ -662,14 +663,14 @@ class ALTTPWorld(World): for player, trash_count in trash_counts.items(): gtower_locations = locations_mapping[player] - world.random.shuffle(gtower_locations) + multiworld.random.shuffle(gtower_locations) while gtower_locations and filleritempool and trash_count > 0: spot_to_fill = gtower_locations.pop() for index, item in enumerate(filleritempool): if spot_to_fill.item_rule(item): filleritempool.pop(index) # remove from outer fill - world.push_item(spot_to_fill, item, False) + multiworld.push_item(spot_to_fill, item, False) fill_locations.remove(spot_to_fill) # very slow, unfortunately trash_count -= 1 break diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py index 21dbae6933..912cca4390 100644 --- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py +++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py @@ -1,8 +1,9 @@ -from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool +from worlds.alttp.Dungeons import get_dungeon_item_pool from worlds.alttp.EntranceShuffle import link_inverted_entrances from worlds.alttp.InvertedRegions import create_inverted_regions from worlds.alttp.ItemPool import difficulties from worlds.alttp.Items import item_factory +from worlds.alttp.Options import GlitchesRequired from worlds.alttp.Regions import mark_light_world_regions from worlds.alttp.Shops import create_shops from test.TestBase import TestBase @@ -14,7 +15,7 @@ class TestInvertedMinor(TestBase, LTTPTestBase): def setUp(self): self.world_setup() self.multiworld.mode[1].value = 2 - self.multiworld.glitches_required[1] = "minor_glitches" + self.multiworld.glitches_required[1] = GlitchesRequired.from_any("minor_glitches") self.multiworld.bombless_start[1].value = True self.multiworld.shuffle_capacity_upgrades[1].value = True self.multiworld.difficulty_requirements[1] = difficulties['normal'] diff --git a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py index 138324a9ff..fc38437e3e 100644 --- a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py +++ b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py @@ -1,8 +1,9 @@ -from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool +from worlds.alttp.Dungeons import get_dungeon_item_pool from worlds.alttp.EntranceShuffle import link_inverted_entrances from worlds.alttp.InvertedRegions import create_inverted_regions from worlds.alttp.ItemPool import difficulties from worlds.alttp.Items import item_factory +from worlds.alttp.Options import GlitchesRequired from worlds.alttp.Regions import mark_light_world_regions from worlds.alttp.Shops import create_shops from test.TestBase import TestBase @@ -13,7 +14,7 @@ from worlds.alttp.test import LTTPTestBase class TestInvertedOWG(TestBase, LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.glitches_required[1] = "overworld_glitches" + self.multiworld.glitches_required[1] = GlitchesRequired.from_any("overworld_glitches") self.multiworld.mode[1].value = 2 self.multiworld.bombless_start[1].value = True self.multiworld.shuffle_capacity_upgrades[1].value = True diff --git a/worlds/alttp/test/minor_glitches/TestMinor.py b/worlds/alttp/test/minor_glitches/TestMinor.py index 547509d58c..a7b529382e 100644 --- a/worlds/alttp/test/minor_glitches/TestMinor.py +++ b/worlds/alttp/test/minor_glitches/TestMinor.py @@ -3,6 +3,7 @@ from worlds.alttp.InvertedRegions import mark_dark_world_regions from worlds.alttp.ItemPool import difficulties from worlds.alttp.Items import item_factory from test.TestBase import TestBase +from worlds.alttp.Options import GlitchesRequired from worlds.alttp.test import LTTPTestBase @@ -10,7 +11,7 @@ from worlds.alttp.test import LTTPTestBase class TestMinor(TestBase, LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.glitches_required[1] = "minor_glitches" + self.multiworld.glitches_required[1] = GlitchesRequired.from_any("minor_glitches") self.multiworld.bombless_start[1].value = True self.multiworld.shuffle_capacity_upgrades[1].value = True self.multiworld.difficulty_requirements[1] = difficulties['normal'] diff --git a/worlds/alttp/test/owg/TestVanillaOWG.py b/worlds/alttp/test/owg/TestVanillaOWG.py index aafc04a77e..3506154587 100644 --- a/worlds/alttp/test/owg/TestVanillaOWG.py +++ b/worlds/alttp/test/owg/TestVanillaOWG.py @@ -3,6 +3,7 @@ from worlds.alttp.InvertedRegions import mark_dark_world_regions from worlds.alttp.ItemPool import difficulties from worlds.alttp.Items import item_factory from test.TestBase import TestBase +from worlds.alttp.Options import GlitchesRequired from worlds.alttp.test import LTTPTestBase @@ -11,7 +12,7 @@ class TestVanillaOWG(TestBase, LTTPTestBase): def setUp(self): self.world_setup() self.multiworld.difficulty_requirements[1] = difficulties['normal'] - self.multiworld.glitches_required[1] = "overworld_glitches" + self.multiworld.glitches_required[1] = GlitchesRequired.from_any("overworld_glitches") self.multiworld.bombless_start[1].value = True self.multiworld.shuffle_capacity_upgrades[1].value = True self.multiworld.worlds[1].er_seed = 0 diff --git a/worlds/alttp/test/vanilla/TestVanilla.py b/worlds/alttp/test/vanilla/TestVanilla.py index e79b4f2ea3..5865ddf987 100644 --- a/worlds/alttp/test/vanilla/TestVanilla.py +++ b/worlds/alttp/test/vanilla/TestVanilla.py @@ -3,13 +3,14 @@ from worlds.alttp.InvertedRegions import mark_dark_world_regions from worlds.alttp.ItemPool import difficulties from worlds.alttp.Items import item_factory from test.TestBase import TestBase +from worlds.alttp.Options import GlitchesRequired from worlds.alttp.test import LTTPTestBase class TestVanilla(TestBase, LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.glitches_required[1] = "no_glitches" + self.multiworld.glitches_required[1] = GlitchesRequired.from_any("no_glitches") self.multiworld.difficulty_requirements[1] = difficulties['normal'] self.multiworld.bombless_start[1].value = True self.multiworld.shuffle_capacity_upgrades[1].value = True From 078d7930734066f1766d2f35b5f595b626666c31 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Mon, 11 Mar 2024 17:22:30 -0500 Subject: [PATCH 104/166] Tests: add test for 2-player-multiworlds (#2386) * Tests: add test for all games multiworld and test for two player multiworld per game * make assertSteps behave like call_all * review improvements * fix stage calling and loc copying in accessibility * add docstrings * lttp is on the options api now * skip the all games multiworld for now. likely needs to be modified to test specific worlds * move skip to the class --- test/general/__init__.py | 35 ++++++++++--- test/multiworld/__init__.py | 0 test/multiworld/test_multiworlds.py | 77 +++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 test/multiworld/__init__.py create mode 100644 test/multiworld/test_multiworlds.py diff --git a/test/general/__init__.py b/test/general/__init__.py index 2819628dd0..fe890e0b34 100644 --- a/test/general/__init__.py +++ b/test/general/__init__.py @@ -1,8 +1,8 @@ from argparse import Namespace -from typing import Optional, Tuple, Type +from typing import List, Optional, Tuple, Type, Union -from BaseClasses import MultiWorld, CollectionState -from worlds.AutoWorld import call_all, World +from BaseClasses import CollectionState, MultiWorld +from worlds.AutoWorld import World, call_all gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill") @@ -18,14 +18,33 @@ def setup_solo_multiworld( steps through pre_fill :param seed: The seed to be used when creating this multiworld """ - multiworld = MultiWorld(1) - multiworld.game[1] = world_type.game - multiworld.player_name = {1: "Tester"} + return setup_multiworld(world_type, steps, seed) + + +def setup_multiworld(worlds: Union[List[Type[World]], Type[World]], steps: Tuple[str, ...] = gen_steps, + seed: Optional[int] = None) -> MultiWorld: + """ + Creates a multiworld with a player for each provided world type, allowing duplicates, setting default options, and + calling the provided gen steps. + + :param worlds: type/s of worlds to generate a multiworld for + :param steps: gen steps that should be called before returning. Default calls through pre_fill + :param seed: The seed to be used when creating this multiworld + """ + if not isinstance(worlds, list): + worlds = [worlds] + players = len(worlds) + multiworld = MultiWorld(players) + multiworld.game = {player: world_type.game for player, world_type in enumerate(worlds, 1)} + multiworld.player_name = {player: f"Tester{player}" for player in multiworld.player_ids} multiworld.set_seed(seed) multiworld.state = CollectionState(multiworld) args = Namespace() - for name, option in world_type.options_dataclass.type_hints.items(): - setattr(args, name, {1: option.from_any(option.default)}) + for player, world_type in enumerate(worlds, 1): + for key, option in world_type.options_dataclass.type_hints.items(): + updated_options = getattr(args, key, {}) + updated_options[player] = option.from_any(option.default) + setattr(args, key, updated_options) multiworld.set_options(args) for step in steps: call_all(multiworld, step) diff --git a/test/multiworld/__init__.py b/test/multiworld/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/multiworld/test_multiworlds.py b/test/multiworld/test_multiworlds.py new file mode 100644 index 0000000000..677f0de829 --- /dev/null +++ b/test/multiworld/test_multiworlds.py @@ -0,0 +1,77 @@ +import unittest +from typing import List, Tuple +from unittest import TestCase + +from BaseClasses import CollectionState, Location, MultiWorld +from Fill import distribute_items_restrictive +from Options import Accessibility +from worlds.AutoWorld import AutoWorldRegister, call_all, call_single +from ..general import gen_steps, setup_multiworld + + +class MultiworldTestBase(TestCase): + multiworld: MultiWorld + + # similar to the implementation in WorldTestBase.test_fill + # but for multiple players and doesn't allow minimal accessibility + def fulfills_accessibility(self) -> bool: + """ + Checks that the multiworld satisfies locations accessibility requirements, failing if all locations are cleared + but not beatable, or some locations are unreachable. + """ + locations = [loc for loc in self.multiworld.get_locations()] + state = CollectionState(self.multiworld) + while locations: + sphere: List[Location] = [] + for n in range(len(locations) - 1, -1, -1): + if locations[n].can_reach(state): + sphere.append(locations.pop(n)) + self.assertTrue(sphere, f"Unreachable locations: {locations}") + if not sphere: + return False + for location in sphere: + if location.item: + state.collect(location.item, True, location) + return self.multiworld.has_beaten_game(state, 1) + + def assertSteps(self, steps: Tuple[str, ...]) -> None: + """Calls each step individually, continuing if a step for a specific world step fails.""" + world_types = {world.__class__ for world in self.multiworld.worlds.values()} + for step in steps: + for player, world in self.multiworld.worlds.items(): + with self.subTest(game=world.game, step=step): + call_single(self.multiworld, step, player) + for world_type in sorted(world_types, key=lambda world: world.__name__): + with self.subTest(game=world_type.game, step=f"stage_{step}"): + stage_callable = getattr(world_type, f"stage_{step}", None) + if stage_callable: + stage_callable(self.multiworld) + + +@unittest.skip("too slow for main") +class TestAllGamesMultiworld(MultiworldTestBase): + def test_fills(self) -> None: + """Tests that a multiworld with one of every registered game world can generate.""" + all_worlds = list(AutoWorldRegister.world_types.values()) + self.multiworld = setup_multiworld(all_worlds, ()) + for world in self.multiworld.worlds.values(): + world.options.accessibility.value = Accessibility.option_locations + self.assertSteps(gen_steps) + with self.subTest("filling multiworld", seed=self.multiworld.seed): + distribute_items_restrictive(self.multiworld) + call_all(self.multiworld, "post_fill") + self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game") + + +class TestTwoPlayerMulti(MultiworldTestBase): + def test_two_player_single_game_fills(self) -> None: + """Tests that a multiworld of two players for each registered game world can generate.""" + for world in AutoWorldRegister.world_types.values(): + self.multiworld = setup_multiworld([world, world], ()) + for world in self.multiworld.worlds.values(): + world.options.accessibility.value = Accessibility.option_locations + self.assertSteps(gen_steps) + with self.subTest("filling multiworld", seed=self.multiworld.seed): + distribute_items_restrictive(self.multiworld) + call_all(self.multiworld, "post_fill") + self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game") From d20d09e68282418cba441ab0217f6a3b30d622f3 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Mon, 11 Mar 2024 17:23:41 -0500 Subject: [PATCH 105/166] The Messenger: content update (#2823) * map option objects to a `World.options` dict * convert RoR2 to options dict system for testing * add temp behavior for lttp with notes * copy/paste bad * convert `set_default_common_options` to a namespace property * reorganize test call order * have fill_restrictive use the new options system * update world api * update soe tests * fix world api * core: auto initialize a dataclass on the World class with the option results * core: auto initialize a dataclass on the World class with the option results: small tying improvement * add `as_dict` method to the options dataclass * fix namespace issues with tests * have current option updates use `.value` instead of changing the option * update ror2 to use the new options system again * revert the junk pool dict since it's cased differently * fix begin_with_loop typo * write new and old options to spoiler * change factorio option behavior back * fix comparisons * move common and per_game_common options to new system * core: automatically create missing options_dataclass from legacy option_definitions * remove spoiler special casing and add back the Factorio option changing but in new system * give ArchipIDLE the default options_dataclass so its options get generated and spoilered properly * reimplement `inspect.get_annotations` * move option info generation for webhost to new system * need to include Common and PerGame common since __annotations__ doesn't include super * use get_type_hints for the options dictionary * typing.get_type_hints returns the bases too. * forgot to sweep through generate * sweep through all the tests * swap to a metaclass property * move remaining usages from get_type_hints to metaclass property * move remaining usages from __annotations__ to metaclass property * move remaining usages from legacy dictionaries to metaclass property * remove legacy dictionaries * cache the metaclass property * clarify inheritance in world api * move the messenger to new options system * add an assert for my dumb * update the doc * rename o to options * missed a spot * update new messenger options * comment spacing Co-authored-by: Doug Hoskisson * fix tests * fix missing import * make the documentation definition more accurate * use options system for loc creation * type cast MessengerWorld * fix typo and use quotes for cast * LTTP: set random seed in tests * ArchipIdle: remove change here as it's default on AutoWorld * Stardew: Need to set state because `set_default_common_options` used to * The Messenger: update shop rando and helpers to new system; optimize imports * Add a kwarg to `as_dict` to do the casing for you * RoR2: use new kwarg for less code * RoR2: revert some accidental reverts * The Messenger: remove an unnecessary variable * remove TypeVar that isn't used * CommonOptions not abstract * Docs: fix mistake in options api.md Co-authored-by: Doug Hoskisson * create options for item link worlds * revert accidental doc removals * Item Links: set default options on group * Messenger: Limited Movement option first draft * The Messenger: add automated setup through the launcher * drop tomllib * don't uselessly import launcher * The Messenger: fix missing goal requirement for power seal hunt * make hard mode goal harder * make fire seal a bit more lenient * have limited movement force minimal accessibility * add an early meditation option * clean up precollected notes tests a bit * add linux support * add steam deck support * await monokickstart * minor styling cleanup * more minor styling cleanup * Initial implementation of Generic ER * Move ERType to Entrance.Type, fix typing imports * updates based on testing (read: flailing) * Updates from feedback * Various bug fixes in ERCollectionState * Use deque instead of queue.Queue * Allow partial entrances in collection state earlier, doc improvements * Prevent early loops in region graph, improve reusability of ER stage code * Typos, grammar, PEP8, and style "fixes" * use RuntimeError instead of bare Exceptions * return tuples from connect since it's slightly faster for our purposes * move the shuffle to the beginning of find_pairing * do er_state placements within pairing lookups to remove code duplication * requested adjustments * Add some temporary performance logging * Use CollectionState to track available exits and placed regions * remove seal shuffle option * some cleanup stuff * portal rando progress * pre-emptive region creation * seals need to be in the datapackage * put mega shards in old order * fix typos and make it actually work * fix more missed connections and add portal events * fix all the portal rando code * finish initial logic implementation * remove/comment out debug stuff * does not actually support plando yet * typos and fix a crash when 3 available portals was selected * finish initial logic for all connections and remove/rename as necessary * fix typos and add some more leniency * move item classification determination to its own method rather than split between two spots * super complicated solution for handling installing the alpha builds * fix logic bugs and add a test * implement logic to shuffle the cutscene portals even though it's probably not possible * just use the one list * fix some issues with the mod checking/downloading * Core: have webhost slot name links go through the launcher so that components can use them * add uri support to the launcher component function * generate output file under specific conditions * cleanup connections.py * set topology_present to true when portals are shuffled * add requirement for ghost pit loc since it's pretty hard without movement * bring hard logic back * misc cleanup * fix asset grabbing of latest version * implement ER * just use the entrances for the spoiler instead of manipulating the cache * remove test defaults * remove excessive comprehension * cleanup and cater data for the client * add elemental skylands to the shuffle pools * initial attempts at hint text * use network items for offline seeds * change around the offline seed data again * move er after portal shuffle and ensure a minimal sphere 1 * Add a method to automatically disconnect entrances in a coupled-compliant way Update docs and cleanup todos * Make find_placeable_exits deterministic by sorting blocked_connections set * add more ER transitions * fix spoiler output of portal warps * add path to hint_data * rename entrance to tot to be a bit clearer * cleanup imports and update description for hard logic * cleanup for PR to main * missed a spot * cleanup monokickstart * add location_name_groups * update docs for new setup * client can reconnect on its own now, no need for a button. * fix mod download link grabbing the wrong assets * cleanup mod pulling a bit and display version it's trying to update to * plando support * comment out broken steam deck support * supports plando * satisfy flake for currently unused file * fix the items accessibility test * review comments * add searing crags portal to starting portals when disabled like option says * address sliver comments * rip out currently unused transition shuffle * add aerobatics warrior requirement to fire seal --------- Co-authored-by: el-u <109771707+el-u@users.noreply.github.com> Co-authored-by: Doug Hoskisson Co-authored-by: Doug Hoskisson Co-authored-by: Sean Dempsey Co-authored-by: qwint --- worlds/messenger/__init__.py | 272 ++++++-- worlds/messenger/client_setup.py | 164 +++++ worlds/messenger/connections.py | 725 ++++++++++++++++++++++ worlds/messenger/constants.py | 48 ++ worlds/messenger/docs/en_The Messenger.md | 4 +- worlds/messenger/docs/setup_en.md | 35 +- worlds/messenger/options.py | 75 ++- worlds/messenger/portals.py | 290 +++++++++ worlds/messenger/regions.py | 525 +++++++++++++--- worlds/messenger/rules.py | 547 +++++++++++----- worlds/messenger/subclasses.py | 76 +-- worlds/messenger/test/__init__.py | 2 +- worlds/messenger/test/test_access.py | 64 +- worlds/messenger/test/test_logic.py | 14 +- worlds/messenger/test/test_notes.py | 42 +- worlds/messenger/test/test_options.py | 35 ++ worlds/messenger/test/test_portals.py | 33 + worlds/messenger/test/test_shop.py | 19 - worlds/messenger/test/test_shop_chest.py | 17 +- 19 files changed, 2570 insertions(+), 417 deletions(-) create mode 100644 worlds/messenger/client_setup.py create mode 100644 worlds/messenger/connections.py create mode 100644 worlds/messenger/portals.py create mode 100644 worlds/messenger/test/test_options.py create mode 100644 worlds/messenger/test/test_portals.py diff --git a/worlds/messenger/__init__.py b/worlds/messenger/__init__.py index f4a28729f1..c40ca02f42 100644 --- a/worlds/messenger/__init__.py +++ b/worlds/messenger/__init__.py @@ -1,14 +1,33 @@ import logging -from typing import Any, Dict, List, Optional +from typing import Any, ClassVar, Dict, List, Optional, TextIO -from BaseClasses import CollectionState, Item, ItemClassification, Tutorial +from BaseClasses import CollectionState, Entrance, Item, ItemClassification, MultiWorld, Tutorial +from Options import Accessibility +from Utils import output_path +from settings import FilePath, Group from worlds.AutoWorld import WebWorld, World -from .constants import ALL_ITEMS, ALWAYS_LOCATIONS, BOSS_LOCATIONS, FILLER, NOTES, PHOBEKINS -from .options import Goal, Logic, MessengerOptions, NotesNeeded, PowerSeals -from .regions import MEGA_SHARDS, REGIONS, REGION_CONNECTIONS, SEALS +from worlds.LauncherComponents import Component, Type, components +from .client_setup import launch_game +from .connections import CONNECTIONS, RANDOMIZED_CONNECTIONS, TRANSITIONS +from .constants import ALL_ITEMS, ALWAYS_LOCATIONS, BOSS_LOCATIONS, FILLER, NOTES, PHOBEKINS, PROG_ITEMS, USEFUL_ITEMS +from .options import AvailablePortals, Goal, Logic, MessengerOptions, NotesNeeded, ShuffleTransitions +from .portals import PORTALS, add_closed_portal_reqs, disconnect_portals, shuffle_portals, validate_portals +from .regions import LEVELS, MEGA_SHARDS, LOCATIONS, REGION_CONNECTIONS from .rules import MessengerHardRules, MessengerOOBRules, MessengerRules -from .shop import FIGURINES, SHOP_ITEMS, shuffle_shop_prices -from .subclasses import MessengerItem, MessengerRegion +from .shop import FIGURINES, PROG_SHOP_ITEMS, SHOP_ITEMS, USEFUL_SHOP_ITEMS, shuffle_shop_prices +from .subclasses import MessengerEntrance, MessengerItem, MessengerRegion, MessengerShopLocation + +components.append( + Component("The Messenger", component_type=Type.CLIENT, func=launch_game)#, game_name="The Messenger", supports_uri=True) +) + + +class MessengerSettings(Group): + class GamePath(FilePath): + description = "The Messenger game executable" + is_exe = True + + game_path: GamePath = GamePath("TheMessenger.exe") class MessengerWeb(WebWorld): @@ -35,17 +54,10 @@ class MessengerWorld(World): adventure full of thrills, surprises, and humor. """ game = "The Messenger" - - item_name_groups = { - "Notes": set(NOTES), - "Keys": set(NOTES), - "Crest": {"Sun Crest", "Moon Crest"}, - "Phobe": set(PHOBEKINS), - "Phobekin": set(PHOBEKINS), - } - options_dataclass = MessengerOptions options: MessengerOptions + settings_key = "messenger_settings" + settings: ClassVar[MessengerSettings] base_offset = 0xADD_000 item_name_to_id = {item: item_id @@ -54,58 +66,144 @@ class MessengerWorld(World): for location_id, location in enumerate([ *ALWAYS_LOCATIONS, - *[seal for seals in SEALS.values() for seal in seals], *[shard for shards in MEGA_SHARDS.values() for shard in shards], *BOSS_LOCATIONS, *[f"The Shop - {shop_loc}" for shop_loc in SHOP_ITEMS], *FIGURINES, "Money Wrench", ], base_offset)} + item_name_groups = { + "Notes": set(NOTES), + "Keys": set(NOTES), + "Crest": {"Sun Crest", "Moon Crest"}, + "Phobe": set(PHOBEKINS), + "Phobekin": set(PHOBEKINS), + } + location_name_groups = { + "Notes": { + "Autumn Hills - Key of Hope", + "Searing Crags - Key of Strength", + "Underworld - Key of Chaos", + "Sunken Shrine - Key of Love", + "Elemental Skylands - Key of Symbiosis", + "Corrupted Future - Key of Courage", + }, + "Keys": { + "Autumn Hills - Key of Hope", + "Searing Crags - Key of Strength", + "Underworld - Key of Chaos", + "Sunken Shrine - Key of Love", + "Elemental Skylands - Key of Symbiosis", + "Corrupted Future - Key of Courage", + }, + "Phobe": { + "Catacombs - Necro", + "Bamboo Creek - Claustro", + "Searing Crags - Pyro", + "Cloud Ruins - Acro", + }, + "Phobekin": { + "Catacombs - Necro", + "Bamboo Creek - Claustro", + "Searing Crags - Pyro", + "Cloud Ruins - Acro", + }, + } - required_client_version = (0, 4, 2) + required_client_version = (0, 4, 3) web = MessengerWeb() total_seals: int = 0 required_seals: int = 0 + created_seals: int = 0 total_shards: int = 0 shop_prices: Dict[str, int] figurine_prices: Dict[str, int] _filler_items: List[str] + starting_portals: List[str] + plando_portals: List[str] + spoiler_portal_mapping: Dict[str, str] + portal_mapping: List[int] + transitions: List[Entrance] + reachable_locs: int = 0 def generate_early(self) -> None: if self.options.goal == Goal.option_power_seal_hunt: - self.options.shuffle_seals.value = PowerSeals.option_true self.total_seals = self.options.total_seals.value + if self.options.limited_movement: + self.options.accessibility.value = Accessibility.option_minimal + if self.options.logic_level < Logic.option_hard: + self.options.logic_level.value = Logic.option_hard + + if self.options.early_meditation: + self.multiworld.early_items[self.player]["Meditation"] = 1 + self.shop_prices, self.figurine_prices = shuffle_shop_prices(self) + starting_portals = ["Autumn Hills", "Howling Grotto", "Glacial Peak", "Riviere Turquoise", "Sunken Shrine", "Searing Crags"] + self.starting_portals = [f"{portal} Portal" + for portal in starting_portals[:3] + + self.random.sample(starting_portals[3:], k=self.options.available_portals - 3)] + # super complicated method for adding searing crags to starting portals if it wasn't chosen + # need to add a check for transition shuffle when that gets added back in + if not self.options.shuffle_portals and "Searing Crags Portal" not in self.starting_portals: + self.starting_portals.append("Searing Crags Portal") + if len(self.starting_portals) > 4: + portals_to_strip = [portal for portal in ["Riviere Turquoise Portal", "Sunken Shrine Portal"] + if portal in self.starting_portals] + self.starting_portals.remove(self.random.choice(portals_to_strip)) + + self.plando_portals = [] + self.portal_mapping = [] + self.spoiler_portal_mapping = {} + self.transitions = [] + def create_regions(self) -> None: # MessengerRegion adds itself to the multiworld - for region in [MessengerRegion(reg_name, self) for reg_name in REGIONS]: - if region.name in REGION_CONNECTIONS: - region.add_exits(REGION_CONNECTIONS[region.name]) + # create simple regions + simple_regions = [MessengerRegion(level, self) for level in LEVELS] + # create complex regions that have sub-regions + complex_regions = [MessengerRegion(f"{parent} - {reg_name}", self, parent) + for parent, sub_region in CONNECTIONS.items() + for reg_name in sub_region] + + for region in complex_regions: + region_name = region.name.replace(f"{region.parent} - ", "") + connection_data = CONNECTIONS[region.parent][region_name] + for exit_region in connection_data: + region.connect(self.multiworld.get_region(exit_region, self.player)) + + # all regions need to be created before i can do these connections so we create and connect the complex first + for region in [level for level in simple_regions if level.name in REGION_CONNECTIONS]: + region.add_exits(REGION_CONNECTIONS[region.name]) def create_items(self) -> None: # create items that are always in the item pool + main_movement_items = ["Rope Dart", "Wingsuit"] itempool: List[MessengerItem] = [ self.create_item(item) for item in self.item_name_to_id - if item not in - { - "Power Seal", *NOTES, *FIGURINES, + if "Time Shard" not in item and item not in { + "Power Seal", *NOTES, *FIGURINES, *main_movement_items, *{collected_item.name for collected_item in self.multiworld.precollected_items[self.player]}, - } and "Time Shard" not in item + } ] + if self.options.limited_movement: + itempool.append(self.create_item(self.random.choice(main_movement_items))) + else: + itempool += [self.create_item(move_item) for move_item in main_movement_items] + if self.options.goal == Goal.option_open_music_box: # make a list of all notes except those in the player's defined starting inventory, and adjust the # amount we need to put in the itempool and precollect based on that notes = [note for note in NOTES if note not in self.multiworld.precollected_items[self.player]] self.random.shuffle(notes) precollected_notes_amount = NotesNeeded.range_end - \ - self.options.notes_needed - \ - (len(NOTES) - len(notes)) + self.options.notes_needed - \ + (len(NOTES) - len(notes)) if precollected_notes_amount: for note in notes[:precollected_notes_amount]: self.multiworld.push_precollected(self.create_item(note)) @@ -116,26 +214,27 @@ class MessengerWorld(World): total_seals = min(len(self.multiworld.get_unfilled_locations(self.player)) - len(itempool), self.options.total_seals.value) if total_seals < self.total_seals: - logging.warning(f"Not enough locations for total seals setting " - f"({self.options.total_seals}). Adjusting to {total_seals}") + logging.warning( + f"Not enough locations for total seals setting " + f"({self.options.total_seals}). Adjusting to {total_seals}" + ) self.total_seals = total_seals self.required_seals = int(self.options.percent_seals_required.value / 100 * self.total_seals) seals = [self.create_item("Power Seal") for _ in range(self.total_seals)] - for i in range(self.required_seals): - seals[i].classification = ItemClassification.progression_skip_balancing itempool += seals + self.multiworld.itempool += itempool remaining_fill = len(self.multiworld.get_unfilled_locations(self.player)) - len(itempool) if remaining_fill < 10: self._filler_items = self.random.choices( - list(FILLER)[2:], - weights=list(FILLER.values())[2:], - k=remaining_fill + list(FILLER)[2:], + weights=list(FILLER.values())[2:], + k=remaining_fill ) - itempool += [self.create_filler() for _ in range(remaining_fill)] + filler = [self.create_filler() for _ in range(remaining_fill)] - self.multiworld.itempool += itempool + self.multiworld.itempool += filler def set_rules(self) -> None: logic = self.options.logic_level @@ -144,16 +243,59 @@ class MessengerWorld(World): elif logic == Logic.option_hard: MessengerHardRules(self).set_messenger_rules() else: - MessengerOOBRules(self).set_messenger_rules() + raise ValueError(f"Somehow you have a logic option that's currently invalid." + f" {logic} for {self.multiworld.get_player_name(self.player)}") + # MessengerOOBRules(self).set_messenger_rules() + + add_closed_portal_reqs(self) + # i need portal shuffle to happen after rules exist so i can validate it + attempts = 5 + if self.options.shuffle_portals: + self.portal_mapping = [] + self.spoiler_portal_mapping = {} + for _ in range(attempts): + disconnect_portals(self) + shuffle_portals(self) + if validate_portals(self): + break + # failsafe mostly for invalid plandoed portals with no transition shuffle + else: + raise RuntimeError("Unable to generate valid portal output.") + + def write_spoiler_header(self, spoiler_handle: TextIO) -> None: + if self.options.available_portals < 6: + spoiler_handle.write(f"\nStarting Portals:\n\n") + for portal in self.starting_portals: + spoiler_handle.write(f"{portal}\n") + + spoiler = self.multiworld.spoiler + + if self.options.shuffle_portals: + # sort the portals as they appear left to right in-game + portal_info = sorted( + self.spoiler_portal_mapping.items(), + key=lambda portal: + ["Autumn Hills", "Riviere Turquoise", + "Howling Grotto", "Sunken Shrine", + "Searing Crags", "Glacial Peak"].index(portal[0])) + for portal, output in portal_info: + spoiler.set_entrance(f"{portal} Portal", output, "I can write anything I want here lmao", self.player) def fill_slot_data(self) -> Dict[str, Any]: - return { + slot_data = { "shop": {SHOP_ITEMS[item].internal_name: price for item, price in self.shop_prices.items()}, "figures": {FIGURINES[item].internal_name: price for item, price in self.figurine_prices.items()}, "max_price": self.total_shards, "required_seals": self.required_seals, + "starting_portals": self.starting_portals, + "portal_exits": self.portal_mapping, + "transitions": [[TRANSITIONS.index("Corrupted Future") if transition.name == "Artificer's Portal" + else TRANSITIONS.index(RANDOMIZED_CONNECTIONS[transition.parent_region.name]), + TRANSITIONS.index(transition.connected_region.name)] + for transition in self.transitions], **self.options.as_dict("music_box", "death_link", "logic_level"), } + return slot_data def get_filler_item_name(self) -> str: if not getattr(self, "_filler_items", None): @@ -166,15 +308,35 @@ class MessengerWorld(World): def create_item(self, name: str) -> MessengerItem: item_id: Optional[int] = self.item_name_to_id.get(name, None) - override_prog = getattr(self, "multiworld") is not None and \ - name in {"Windmill Shuriken"} and \ - self.options.logic_level > Logic.option_normal - count = 0 + return MessengerItem( + name, + ItemClassification.progression if item_id is None else self.get_item_classification(name), + item_id, + self.player + ) + + def get_item_classification(self, name: str) -> ItemClassification: if "Time Shard " in name: count = int(name.strip("Time Shard ()")) count = count if count >= 100 else 0 self.total_shards += count - return MessengerItem(name, self.player, item_id, override_prog, count) + return ItemClassification.progression_skip_balancing if count else ItemClassification.filler + + if name == "Windmill Shuriken" and getattr(self, "multiworld", None) is not None: + return ItemClassification.progression if self.options.logic_level else ItemClassification.filler + + if name == "Power Seal": + self.created_seals += 1 + return ItemClassification.progression_skip_balancing \ + if self.required_seals >= self.created_seals else ItemClassification.filler + + if name in {*NOTES, *PROG_ITEMS, *PHOBEKINS, *PROG_SHOP_ITEMS}: + return ItemClassification.progression + + if name in {*USEFUL_ITEMS, *USEFUL_SHOP_ITEMS}: + return ItemClassification.useful + + return ItemClassification.filler def collect(self, state: "CollectionState", item: "Item") -> bool: change = super().collect(state, item) @@ -187,3 +349,25 @@ class MessengerWorld(World): if change and "Time Shard" in item.name: state.prog_items[self.player]["Shards"] -= int(item.name.strip("Time Shard ()")) return change + + @classmethod + def stage_generate_output(cls, multiworld: MultiWorld, output_directory: str) -> None: + # using stage_generate_output because it doesn't increase the logged player count for players without output + # only generate output if there's a single player + if multiworld.players > 1: + return + # the messenger client calls into AP with specific args, so check the out path matches what the client sends + out_path = output_path(multiworld.get_out_file_name_base(1) + ".aptm") + if "The Messenger\\Archipelago\\output" not in out_path: + return + import orjson + data = { + "name": multiworld.get_player_name(1), + "slot_data": multiworld.worlds[1].fill_slot_data(), + "loc_data": {loc.address: {loc.item.name: [loc.item.code, loc.item.flags]} + for loc in multiworld.get_filled_locations() if loc.address}, + } + + output = orjson.dumps(data, option=orjson.OPT_NON_STR_KEYS) + with open(out_path, "wb") as f: + f.write(output) diff --git a/worlds/messenger/client_setup.py b/worlds/messenger/client_setup.py new file mode 100644 index 0000000000..9fd08e52d8 --- /dev/null +++ b/worlds/messenger/client_setup.py @@ -0,0 +1,164 @@ +import io +import logging +import os.path +import subprocess +import urllib.request +from shutil import which +from tkinter.messagebox import askyesnocancel +from typing import Any, Optional +from zipfile import ZipFile +from Utils import open_file + +import requests + +from Utils import is_windows, messagebox, tuplize_version + + +MOD_URL = "https://api.github.com/repos/alwaysintreble/TheMessengerRandomizerModAP/releases/latest" + + +def launch_game(url: Optional[str] = None) -> None: + """Check the game installation, then launch it""" + def courier_installed() -> bool: + """Check if Courier is installed""" + return os.path.exists(os.path.join(game_folder, "TheMessenger_Data", "Managed", "Assembly-CSharp.Courier.mm.dll")) + + def mod_installed() -> bool: + """Check if the mod is installed""" + return os.path.exists(os.path.join(game_folder, "Mods", "TheMessengerRandomizerAP", "courier.toml")) + + def request_data(request_url: str) -> Any: + """Fetches json response from given url""" + logging.info(f"requesting {request_url}") + response = requests.get(request_url) + if response.status_code == 200: # success + try: + data = response.json() + except requests.exceptions.JSONDecodeError: + raise RuntimeError(f"Unable to fetch data. (status code {response.status_code})") + else: + raise RuntimeError(f"Unable to fetch data. (status code {response.status_code})") + return data + + def install_courier() -> None: + """Installs latest version of Courier""" + # can't use latest since courier uses pre-release tags + courier_url = "https://api.github.com/repos/Brokemia/Courier/releases" + latest_download = request_data(courier_url)[0]["assets"][-1]["browser_download_url"] + + with urllib.request.urlopen(latest_download) as download: + with ZipFile(io.BytesIO(download.read()), "r") as zf: + for member in zf.infolist(): + zf.extract(member, path=game_folder) + + os.chdir(game_folder) + # linux and mac handling + if not is_windows: + mono_exe = which("mono") + if not mono_exe: + # steam deck support but doesn't currently work + messagebox("Failure", "Failed to install Courier", True) + raise RuntimeError("Failed to install Courier") + # # download and use mono kickstart + # # this allows steam deck support + # mono_kick_url = "https://github.com/flibitijibibo/MonoKickstart/archive/refs/heads/master.zip" + # target = os.path.join(folder, "monoKickstart") + # os.makedirs(target, exist_ok=True) + # with urllib.request.urlopen(mono_kick_url) as download: + # with ZipFile(io.BytesIO(download.read()), "r") as zf: + # for member in zf.infolist(): + # zf.extract(member, path=target) + # installer = subprocess.Popen([os.path.join(target, "precompiled"), + # os.path.join(folder, "MiniInstaller.exe")], shell=False) + # os.remove(target) + else: + installer = subprocess.Popen([mono_exe, os.path.join(game_folder, "MiniInstaller.exe")], shell=False) + else: + installer = subprocess.Popen(os.path.join(game_folder, "MiniInstaller.exe"), shell=False) + + failure = installer.wait() + if failure: + messagebox("Failure", "Failed to install Courier", True) + os.chdir(working_directory) + raise RuntimeError("Failed to install Courier") + os.chdir(working_directory) + + if courier_installed(): + messagebox("Success!", "Courier successfully installed!") + return + messagebox("Failure", "Failed to install Courier", True) + raise RuntimeError("Failed to install Courier") + + def install_mod() -> None: + """Installs latest version of the mod""" + assets = request_data(MOD_URL)["assets"] + if len(assets) == 1: + release_url = assets[0]["browser_download_url"] + else: + for asset in assets: + if "TheMessengerRandomizerAP" in asset["name"]: + release_url = asset["browser_download_url"] + break + else: + messagebox("Failure", "Failed to find latest mod download", True) + raise RuntimeError("Failed to install Mod") + + mod_folder = os.path.join(game_folder, "Mods") + os.makedirs(mod_folder, exist_ok=True) + with urllib.request.urlopen(release_url) as download: + with ZipFile(io.BytesIO(download.read()), "r") as zf: + for member in zf.infolist(): + zf.extract(member, path=mod_folder) + + messagebox("Success!", "Latest mod successfully installed!") + + def available_mod_update(latest_version: str) -> bool: + """Check if there's an available update""" + latest_version = latest_version.lstrip("v") + toml_path = os.path.join(game_folder, "Mods", "TheMessengerRandomizerAP", "courier.toml") + with open(toml_path, "r") as f: + installed_version = f.read().splitlines()[1].strip("version = \"") + + logging.info(f"Installed version: {installed_version}. Latest version: {latest_version}") + # one of the alpha builds + return "alpha" in latest_version or tuplize_version(latest_version) > tuplize_version(installed_version) + + from . import MessengerWorld + game_folder = os.path.dirname(MessengerWorld.settings.game_path) + working_directory = os.getcwd() + if not courier_installed(): + should_install = askyesnocancel("Install Courier", + "No Courier installation detected. Would you like to install now?") + if not should_install: + return + logging.info("Installing Courier") + install_courier() + if not mod_installed(): + should_install = askyesnocancel("Install Mod", + "No randomizer mod detected. Would you like to install now?") + if not should_install: + return + logging.info("Installing Mod") + install_mod() + else: + latest = request_data(MOD_URL)["tag_name"] + if available_mod_update(latest): + should_update = askyesnocancel("Update Mod", + f"New mod version detected. Would you like to update to {latest} now?") + if should_update: + logging.info("Updating mod") + install_mod() + elif should_update is None: + return + if not is_windows: + if url: + open_file(f"steam://rungameid/764790//{url}/") + else: + open_file("steam://rungameid/764790") + else: + os.chdir(game_folder) + if url: + subprocess.Popen([MessengerWorld.settings.game_path, str(url)]) + else: + subprocess.Popen(MessengerWorld.settings.game_path) + os.chdir(working_directory) diff --git a/worlds/messenger/connections.py b/worlds/messenger/connections.py new file mode 100644 index 0000000000..5e1871e287 --- /dev/null +++ b/worlds/messenger/connections.py @@ -0,0 +1,725 @@ +from typing import Dict, List + +CONNECTIONS: Dict[str, Dict[str, List[str]]] = { + "Ninja Village": { + "Right": [ + "Autumn Hills - Left", + "Ninja Village - Nest", + ], + "Nest": [ + "Ninja Village - Right", + ], + }, + "Autumn Hills": { + "Left": [ + "Ninja Village - Right", + "Autumn Hills - Climbing Claws Shop", + ], + "Right": [ + "Forlorn Temple - Left", + "Autumn Hills - Leaf Golem Shop", + ], + "Bottom": [ + "Catacombs - Bottom Left", + "Autumn Hills - Double Swing Checkpoint", + ], + "Portal": [ + "Tower HQ", + "Autumn Hills - Dimension Climb Shop", + ], + "Climbing Claws Shop": [ + "Autumn Hills - Left", + "Autumn Hills - Hope Path Shop", + "Autumn Hills - Lakeside Checkpoint", + "Autumn Hills - Key of Hope Checkpoint", + ], + "Hope Path Shop": [ + "Autumn Hills - Climbing Claws Shop", + "Autumn Hills - Hope Latch Checkpoint", + "Autumn Hills - Lakeside Checkpoint", + ], + "Dimension Climb Shop": [ + "Autumn Hills - Lakeside Checkpoint", + "Autumn Hills - Portal", + "Autumn Hills - Double Swing Checkpoint", + ], + "Leaf Golem Shop": [ + "Autumn Hills - Spike Ball Swing Checkpoint", + "Autumn Hills - Right", + ], + "Hope Latch Checkpoint": [ + "Autumn Hills - Hope Path Shop", + "Autumn Hills - Key of Hope Checkpoint", + ], + "Key of Hope Checkpoint": [ + "Autumn Hills - Hope Latch Checkpoint", + "Autumn Hills - Lakeside Checkpoint", + ], + "Lakeside Checkpoint": [ + "Autumn Hills - Climbing Claws Shop", + "Autumn Hills - Dimension Climb Shop", + ], + "Double Swing Checkpoint": [ + "Autumn Hills - Dimension Climb Shop", + "Autumn Hills - Spike Ball Swing Checkpoint", + "Autumn Hills - Bottom", + ], + "Spike Ball Swing Checkpoint": [ + "Autumn Hills - Double Swing Checkpoint", + "Autumn Hills - Leaf Golem Shop", + ], + }, + "Forlorn Temple": { + "Left": [ + "Autumn Hills - Right", + "Forlorn Temple - Outside Shop", + ], + "Right": [ + "Bamboo Creek - Top Left", + "Forlorn Temple - Demon King Shop", + ], + "Bottom": [ + "Catacombs - Top Left", + "Forlorn Temple - Outside Shop", + ], + "Outside Shop": [ + "Forlorn Temple - Left", + "Forlorn Temple - Bottom", + "Forlorn Temple - Entrance Shop", + ], + "Entrance Shop": [ + "Forlorn Temple - Outside Shop", + "Forlorn Temple - Sunny Day Checkpoint", + ], + "Climb Shop": [ + "Forlorn Temple - Rocket Maze Checkpoint", + "Forlorn Temple - Rocket Sunset Shop", + ], + "Rocket Sunset Shop": [ + "Forlorn Temple - Climb Shop", + "Forlorn Temple - Descent Shop", + ], + "Descent Shop": [ + "Forlorn Temple - Rocket Sunset Shop", + "Forlorn Temple - Saw Gauntlet Shop", + ], + "Saw Gauntlet Shop": [ + "Forlorn Temple - Demon King Shop", + ], + "Demon King Shop": [ + "Forlorn Temple - Saw Gauntlet Shop", + "Forlorn Temple - Right", + ], + "Sunny Day Checkpoint": [ + "Forlorn Temple - Rocket Maze Checkpoint", + ], + "Rocket Maze Checkpoint": [ + "Forlorn Temple - Sunny Day Checkpoint", + "Forlorn Temple - Climb Shop", + ], + }, + "Catacombs": { + "Top Left": [ + "Forlorn Temple - Bottom", + "Catacombs - Triple Spike Crushers Shop", + ], + "Bottom Left": [ + "Autumn Hills - Bottom", + "Catacombs - Triple Spike Crushers Shop", + "Catacombs - Death Trap Checkpoint", + ], + "Bottom": [ + "Dark Cave - Right", + "Catacombs - Dirty Pond Checkpoint", + ], + "Right": [ + "Bamboo Creek - Bottom Left", + "Catacombs - Ruxxtin Shop", + ], + "Triple Spike Crushers Shop": [ + "Catacombs - Bottom Left", + "Catacombs - Death Trap Checkpoint", + ], + "Ruxxtin Shop": [ + "Catacombs - Right", + "Catacombs - Dirty Pond Checkpoint", + ], + "Death Trap Checkpoint": [ + "Catacombs - Triple Spike Crushers Shop", + "Catacombs - Bottom Left", + "Catacombs - Dirty Pond Checkpoint", + ], + "Crusher Gauntlet Checkpoint": [ + "Catacombs - Dirty Pond Checkpoint", + ], + "Dirty Pond Checkpoint": [ + "Catacombs - Bottom", + "Catacombs - Death Trap Checkpoint", + "Catacombs - Crusher Gauntlet Checkpoint", + "Catacombs - Ruxxtin Shop", + ], + }, + "Bamboo Creek": { + "Bottom Left": [ + "Catacombs - Right", + "Bamboo Creek - Spike Crushers Shop", + ], + "Top Left": [ + "Bamboo Creek - Abandoned Shop", + "Forlorn Temple - Right", + ], + "Right": [ + "Howling Grotto - Left", + "Bamboo Creek - Time Loop Shop", + ], + "Spike Crushers Shop": [ + "Bamboo Creek - Bottom Left", + "Bamboo Creek - Abandoned Shop", + ], + "Abandoned Shop": [ + "Bamboo Creek - Spike Crushers Shop", + "Bamboo Creek - Spike Doors Checkpoint", + ], + "Time Loop Shop": [ + "Bamboo Creek - Right", + "Bamboo Creek - Spike Doors Checkpoint", + ], + "Spike Ball Pits Checkpoint": [ + "Bamboo Creek - Spike Doors Checkpoint", + ], + "Spike Doors Checkpoint": [ + "Bamboo Creek - Abandoned Shop", + "Bamboo Creek - Spike Ball Pits Checkpoint", + "Bamboo Creek - Time Loop Shop", + ], + }, + "Howling Grotto": { + "Left": [ + "Bamboo Creek - Right", + "Howling Grotto - Wingsuit Shop", + ], + "Top": [ + "Howling Grotto - Crushing Pits Shop", + "Quillshroom Marsh - Bottom Left", + ], + "Right": [ + "Howling Grotto - Emerald Golem Shop", + "Quillshroom Marsh - Top Left", + ], + "Bottom": [ + "Howling Grotto - Lost Woods Checkpoint", + "Sunken Shrine - Left", + ], + "Portal": [ + "Howling Grotto - Crushing Pits Shop", + "Tower HQ", + ], + "Wingsuit Shop": [ + "Howling Grotto - Left", + "Howling Grotto - Lost Woods Checkpoint", + ], + "Crushing Pits Shop": [ + "Howling Grotto - Lost Woods Checkpoint", + "Howling Grotto - Portal", + "Howling Grotto - Breezy Crushers Checkpoint", + "Howling Grotto - Top", + ], + "Emerald Golem Shop": [ + "Howling Grotto - Breezy Crushers Checkpoint", + "Howling Grotto - Right", + ], + "Lost Woods Checkpoint": [ + "Howling Grotto - Wingsuit Shop", + "Howling Grotto - Crushing Pits Shop", + "Howling Grotto - Bottom", + ], + "Breezy Crushers Checkpoint": [ + "Howling Grotto - Crushing Pits Shop", + "Howling Grotto - Emerald Golem Shop", + ], + }, + "Quillshroom Marsh": { + "Top Left": [ + "Howling Grotto - Right", + "Quillshroom Marsh - Seashell Checkpoint", + "Quillshroom Marsh - Spikey Window Shop", + ], + "Bottom Left": [ + "Howling Grotto - Top", + "Quillshroom Marsh - Sand Trap Shop", + "Quillshroom Marsh - Bottom Right", + ], + "Top Right": [ + "Quillshroom Marsh - Queen of Quills Shop", + "Searing Crags - Left", + ], + "Bottom Right": [ + "Quillshroom Marsh - Bottom Left", + "Quillshroom Marsh - Sand Trap Shop", + "Searing Crags - Bottom", + ], + "Spikey Window Shop": [ + "Quillshroom Marsh - Top Left", + "Quillshroom Marsh - Seashell Checkpoint", + "Quillshroom Marsh - Quicksand Checkpoint", + ], + "Sand Trap Shop": [ + "Quillshroom Marsh - Quicksand Checkpoint", + "Quillshroom Marsh - Bottom Left", + "Quillshroom Marsh - Bottom Right", + "Quillshroom Marsh - Spike Wave Checkpoint", + ], + "Queen of Quills Shop": [ + "Quillshroom Marsh - Spike Wave Checkpoint", + "Quillshroom Marsh - Top Right", + ], + "Seashell Checkpoint": [ + "Quillshroom Marsh - Top Left", + "Quillshroom Marsh - Spikey Window Shop", + ], + "Quicksand Checkpoint": [ + "Quillshroom Marsh - Spikey Window Shop", + "Quillshroom Marsh - Sand Trap Shop", + ], + "Spike Wave Checkpoint": [ + "Quillshroom Marsh - Sand Trap Shop", + "Quillshroom Marsh - Queen of Quills Shop", + ], + }, + "Searing Crags": { + "Left": [ + "Quillshroom Marsh - Top Right", + "Searing Crags - Rope Dart Shop", + ], + "Top": [ + "Searing Crags - Colossuses Shop", + "Glacial Peak - Bottom", + ], + "Bottom": [ + "Searing Crags - Portal", + "Quillshroom Marsh - Bottom Right", + ], + "Right": [ + "Searing Crags - Portal", + "Underworld - Left", + ], + "Portal": [ + "Searing Crags - Bottom", + "Searing Crags - Right", + "Searing Crags - Before Final Climb Shop", + "Searing Crags - Colossuses Shop", + "Tower HQ", + ], + "Rope Dart Shop": [ + "Searing Crags - Left", + "Searing Crags - Triple Ball Spinner Checkpoint", + ], + "Falling Rocks Shop": [ + "Searing Crags - Triple Ball Spinner Checkpoint", + "Searing Crags - Searing Mega Shard Shop", + ], + "Searing Mega Shard Shop": [ + "Searing Crags - Falling Rocks Shop", + "Searing Crags - Before Final Climb Shop", + "Searing Crags - Key of Strength Shop", + ], + "Before Final Climb Shop": [ + "Searing Crags - Raining Rocks Checkpoint", + "Searing Crags - Portal", + "Searing Crags - Colossuses Shop", + ], + "Colossuses Shop": [ + "Searing Crags - Before Final Climb Shop", + "Searing Crags - Key of Strength Shop", + "Searing Crags - Portal", + "Searing Crags - Top", + ], + "Key of Strength Shop": [ + "Searing Crags - Searing Mega Shard Shop", + ], + "Triple Ball Spinner Checkpoint": [ + "Searing Crags - Rope Dart Shop", + "Searing Crags - Falling Rocks Shop", + ], + "Raining Rocks Checkpoint": [ + "Searing Crags - Searing Mega Shard Shop", + "Searing Crags - Before Final Climb Shop", + ], + }, + "Glacial Peak": { + "Bottom": [ + "Searing Crags - Top", + "Glacial Peak - Ice Climbers' Shop", + ], + "Left": [ + "Elemental Skylands - Air Shmup", + "Glacial Peak - Projectile Spike Pit Checkpoint", + "Glacial Peak - Glacial Mega Shard Shop", + ], + "Top": [ + "Glacial Peak - Tower Entrance Shop", + "Cloud Ruins - Left", + ], + "Portal": [ + "Glacial Peak - Tower Entrance Shop", + "Tower HQ", + ], + "Ice Climbers' Shop": [ + "Glacial Peak - Bottom", + "Glacial Peak - Projectile Spike Pit Checkpoint", + ], + "Glacial Mega Shard Shop": [ + "Glacial Peak - Left", + "Glacial Peak - Air Swag Checkpoint", + ], + "Tower Entrance Shop": [ + "Glacial Peak - Top", + "Glacial Peak - Free Climbing Checkpoint", + "Glacial Peak - Portal", + ], + "Projectile Spike Pit Checkpoint": [ + "Glacial Peak - Ice Climbers' Shop", + "Glacial Peak - Left", + ], + "Air Swag Checkpoint": [ + "Glacial Peak - Glacial Mega Shard Shop", + "Glacial Peak - Free Climbing Checkpoint", + ], + "Free Climbing Checkpoint": [ + "Glacial Peak - Air Swag Checkpoint", + "Glacial Peak - Tower Entrance Shop", + ], + }, + "Tower of Time": { + "Left": [ + "Tower of Time - Final Chance Shop", + ], + "Final Chance Shop": [ + "Tower of Time - First Checkpoint", + ], + "Arcane Golem Shop": [ + "Tower of Time - Sixth Checkpoint", + ], + "First Checkpoint": [ + "Tower of Time - Second Checkpoint", + ], + "Second Checkpoint": [ + "Tower of Time - Third Checkpoint", + ], + "Third Checkpoint": [ + "Tower of Time - Fourth Checkpoint", + ], + "Fourth Checkpoint": [ + "Tower of Time - Fifth Checkpoint", + ], + "Fifth Checkpoint": [ + "Tower of Time - Sixth Checkpoint", + ], + "Sixth Checkpoint": [ + "Tower of Time - Arcane Golem Shop", + ], + }, + "Cloud Ruins": { + "Left": [ + "Glacial Peak - Top", + "Cloud Ruins - Cloud Entrance Shop", + ], + "Cloud Entrance Shop": [ + "Cloud Ruins - Left", + "Cloud Ruins - Spike Float Checkpoint", + ], + "Pillar Glide Shop": [ + "Cloud Ruins - Spike Float Checkpoint", + "Cloud Ruins - Ghost Pit Checkpoint", + "Cloud Ruins - Crushers' Descent Shop", + ], + "Crushers' Descent Shop": [ + "Cloud Ruins - Pillar Glide Shop", + "Cloud Ruins - Toothbrush Alley Checkpoint", + ], + "Seeing Spikes Shop": [ + "Cloud Ruins - Toothbrush Alley Checkpoint", + "Cloud Ruins - Sliding Spikes Shop", + ], + "Sliding Spikes Shop": [ + "Cloud Ruins - Seeing Spikes Shop", + "Cloud Ruins - Saw Pit Checkpoint", + ], + "Final Flight Shop": [ + "Cloud Ruins - Saw Pit Checkpoint", + "Cloud Ruins - Manfred's Shop", + ], + "Manfred's Shop": [ + "Cloud Ruins - Final Flight Shop", + ], + "Spike Float Checkpoint": [ + "Cloud Ruins - Cloud Entrance Shop", + "Cloud Ruins - Pillar Glide Shop", + ], + "Ghost Pit Checkpoint": [ + "Cloud Ruins - Pillar Glide Shop", + ], + "Toothbrush Alley Checkpoint": [ + "Cloud Ruins - Crushers' Descent Shop", + "Cloud Ruins - Seeing Spikes Shop", + ], + "Saw Pit Checkpoint": [ + "Cloud Ruins - Sliding Spikes Shop", + "Cloud Ruins - Final Flight Shop", + ], + }, + "Underworld": { + "Left": [ + "Underworld - Left Shop", + "Searing Crags - Right", + ], + "Left Shop": [ + "Underworld - Left", + "Underworld - Hot Dip Checkpoint", + ], + "Fireball Wave Shop": [ + "Underworld - Hot Dip Checkpoint", + "Underworld - Long Climb Shop", + ], + "Long Climb Shop": [ + "Underworld - Fireball Wave Shop", + "Underworld - Hot Tub Checkpoint", + ], + "Barm'athaziel Shop": [ + "Underworld - Hot Tub Checkpoint", + ], + "Key of Chaos Shop": [ + ], + "Hot Dip Checkpoint": [ + "Underworld - Left Shop", + "Underworld - Fireball Wave Shop", + "Underworld - Lava Run Checkpoint", + ], + "Hot Tub Checkpoint": [ + "Underworld - Long Climb Shop", + "Underworld - Barm'athaziel Shop", + ], + "Lava Run Checkpoint": [ + "Underworld - Hot Dip Checkpoint", + "Underworld - Key of Chaos Shop", + ], + }, + "Dark Cave": { + "Right": [ + "Catacombs - Bottom", + "Dark Cave - Left", + ], + "Left": [ + "Riviere Turquoise - Right", + ], + }, + "Riviere Turquoise": { + "Right": [ + "Riviere Turquoise - Portal", + ], + "Portal": [ + "Riviere Turquoise - Waterfall Shop", + "Tower HQ", + ], + "Waterfall Shop": [ + "Riviere Turquoise - Portal", + "Riviere Turquoise - Flower Flight Checkpoint", + ], + "Launch of Faith Shop": [ + "Riviere Turquoise - Flower Flight Checkpoint", + "Riviere Turquoise - Log Flume Shop", + ], + "Log Flume Shop": [ + "Riviere Turquoise - Log Climb Shop", + ], + "Log Climb Shop": [ + "Riviere Turquoise - Restock Shop", + ], + "Restock Shop": [ + "Riviere Turquoise - Butterfly Matriarch Shop", + ], + "Butterfly Matriarch Shop": [ + ], + "Flower Flight Checkpoint": [ + "Riviere Turquoise - Waterfall Shop", + "Riviere Turquoise - Launch of Faith Shop", + ], + }, + "Elemental Skylands": { + "Air Shmup": [ + "Elemental Skylands - Air Intro Shop", + ], + "Air Intro Shop": [ + "Elemental Skylands - Air Seal Checkpoint", + "Elemental Skylands - Air Generator Shop", + ], + "Air Seal Checkpoint": [ + "Elemental Skylands - Air Intro Shop", + "Elemental Skylands - Air Generator Shop", + ], + "Air Generator Shop": [ + "Elemental Skylands - Earth Shmup", + ], + "Earth Shmup": [ + "Elemental Skylands - Earth Intro Shop", + ], + "Earth Intro Shop": [ + "Elemental Skylands - Earth Generator Shop", + ], + "Earth Generator Shop": [ + "Elemental Skylands - Fire Shmup", + ], + "Fire Shmup": [ + "Elemental Skylands - Fire Intro Shop", + ], + "Fire Intro Shop": [ + "Elemental Skylands - Fire Generator Shop", + ], + "Fire Generator Shop": [ + "Elemental Skylands - Water Shmup", + ], + "Water Shmup": [ + "Elemental Skylands - Water Intro Shop", + ], + "Water Intro Shop": [ + "Elemental Skylands - Water Generator Shop", + ], + "Water Generator Shop": [ + "Elemental Skylands - Right", + ], + "Right": [ + "Glacial Peak - Left", + ], + }, + "Sunken Shrine": { + "Left": [ + "Howling Grotto - Bottom", + "Sunken Shrine - Portal", + ], + "Portal": [ + "Sunken Shrine - Left", + "Sunken Shrine - Above Portal Shop", + "Sunken Shrine - Sun Path Shop", + "Sunken Shrine - Moon Path Shop", + "Tower HQ", + ], + "Above Portal Shop": [ + "Sunken Shrine - Portal", + "Sunken Shrine - Lifeguard Shop", + ], + "Lifeguard Shop": [ + "Sunken Shrine - Above Portal Shop", + "Sunken Shrine - Lightfoot Tabi Checkpoint", + ], + "Sun Path Shop": [ + "Sunken Shrine - Portal", + "Sunken Shrine - Tabi Gauntlet Shop", + ], + "Tabi Gauntlet Shop": [ + "Sunken Shrine - Sun Path Shop", + "Sunken Shrine - Sun Crest Checkpoint", + ], + "Moon Path Shop": [ + "Sunken Shrine - Portal", + "Sunken Shrine - Waterfall Paradise Checkpoint", + ], + "Lightfoot Tabi Checkpoint": [ + "Sunken Shrine - Portal", + ], + "Sun Crest Checkpoint": [ + "Sunken Shrine - Tabi Gauntlet Shop", + "Sunken Shrine - Portal", + ], + "Waterfall Paradise Checkpoint": [ + "Sunken Shrine - Moon Path Shop", + "Sunken Shrine - Moon Crest Checkpoint", + ], + "Moon Crest Checkpoint": [ + "Sunken Shrine - Waterfall Paradise Checkpoint", + "Sunken Shrine - Portal", + ], + }, +} + +RANDOMIZED_CONNECTIONS: Dict[str, str] = { + "Ninja Village - Right": "Autumn Hills - Left", + "Autumn Hills - Left": "Ninja Village - Right", + "Autumn Hills - Right": "Forlorn Temple - Left", + "Autumn Hills - Bottom": "Catacombs - Bottom Left", + "Forlorn Temple - Left": "Autumn Hills - Right", + "Forlorn Temple - Right": "Bamboo Creek - Top Left", + "Forlorn Temple - Bottom": "Catacombs - Top Left", + "Catacombs - Top Left": "Forlorn Temple - Bottom", + "Catacombs - Bottom Left": "Autumn Hills - Bottom", + "Catacombs - Bottom": "Dark Cave - Right", + "Catacombs - Right": "Bamboo Creek - Bottom Left", + "Bamboo Creek - Bottom Left": "Catacombs - Right", + "Bamboo Creek - Right": "Howling Grotto - Left", + "Bamboo Creek - Top Left": "Forlorn Temple - Right", + "Howling Grotto - Left": "Bamboo Creek - Right", + "Howling Grotto - Top": "Quillshroom Marsh - Bottom Left", + "Howling Grotto - Right": "Quillshroom Marsh - Top Left", + "Howling Grotto - Bottom": "Sunken Shrine - Left", + "Quillshroom Marsh - Top Left": "Howling Grotto - Right", + "Quillshroom Marsh - Bottom Left": "Howling Grotto - Top", + "Quillshroom Marsh - Top Right": "Searing Crags - Left", + "Quillshroom Marsh - Bottom Right": "Searing Crags - Bottom", + "Searing Crags - Left": "Quillshroom Marsh - Top Right", + "Searing Crags - Top": "Glacial Peak - Bottom", + "Searing Crags - Bottom": "Quillshroom Marsh - Bottom Right", + "Searing Crags - Right": "Underworld - Left", + "Glacial Peak - Bottom": "Searing Crags - Top", + "Glacial Peak - Top": "Cloud Ruins - Left", + "Glacial Peak - Left": "Elemental Skylands - Air Shmup", + "Cloud Ruins - Left": "Glacial Peak - Top", + "Elemental Skylands - Right": "Glacial Peak - Left", + "Tower HQ": "Tower of Time - Left", + "Artificer": "Corrupted Future", + "Underworld - Left": "Searing Crags - Right", + "Dark Cave - Right": "Catacombs - Bottom", + "Dark Cave - Left": "Riviere Turquoise - Right", + "Sunken Shrine - Left": "Howling Grotto - Bottom", +} + +TRANSITIONS: List[str] = [ + "Ninja Village - Right", + "Autumn Hills - Left", + "Autumn Hills - Right", + "Autumn Hills - Bottom", + "Forlorn Temple - Left", + "Forlorn Temple - Bottom", + "Forlorn Temple - Right", + "Catacombs - Top Left", + "Catacombs - Right", + "Catacombs - Bottom", + "Catacombs - Bottom Left", + "Dark Cave - Right", + "Dark Cave - Left", + "Riviere Turquoise - Right", + "Howling Grotto - Left", + "Howling Grotto - Right", + "Howling Grotto - Top", + "Howling Grotto - Bottom", + "Sunken Shrine - Left", + "Bamboo Creek - Top Left", + "Bamboo Creek - Bottom Left", + "Bamboo Creek - Right", + "Quillshroom Marsh - Top Left", + "Quillshroom Marsh - Bottom Left", + "Quillshroom Marsh - Top Right", + "Quillshroom Marsh - Bottom Right", + "Searing Crags - Left", + "Searing Crags - Bottom", + "Searing Crags - Right", + "Searing Crags - Top", + "Glacial Peak - Bottom", + "Glacial Peak - Top", + "Glacial Peak - Left", + "Elemental Skylands - Air Shmup", + "Elemental Skylands - Right", + "Tower HQ", + "Tower of Time - Left", + "Corrupted Future", + "Cloud Ruins - Left", + "Underworld - Left", +] diff --git a/worlds/messenger/constants.py b/worlds/messenger/constants.py index f05d276cea..0c4d6a944c 100644 --- a/worlds/messenger/constants.py +++ b/worlds/messenger/constants.py @@ -24,6 +24,8 @@ PROG_ITEMS = [ # "Astral Seed", # "Astral Tea Leaves", "Money Wrench", + "Candle", + "Seashell", ] PHOBEKINS = [ @@ -103,6 +105,52 @@ ALWAYS_LOCATIONS = [ "Searing Crags - Pyro", "Bamboo Creek - Claustro", "Cloud Ruins - Acro", + # seals + "Ninja Village Seal - Tree House", + "Autumn Hills Seal - Trip Saws", + "Autumn Hills Seal - Double Swing Saws", + "Autumn Hills Seal - Spike Ball Swing", + "Autumn Hills Seal - Spike Ball Darts", + "Catacombs Seal - Triple Spike Crushers", + "Catacombs Seal - Crusher Gauntlet", + "Catacombs Seal - Dirty Pond", + "Bamboo Creek Seal - Spike Crushers and Doors", + "Bamboo Creek Seal - Spike Ball Pits", + "Bamboo Creek Seal - Spike Crushers and Doors v2", + "Howling Grotto Seal - Windy Saws and Balls", + "Howling Grotto Seal - Crushing Pits", + "Howling Grotto Seal - Breezy Crushers", + "Quillshroom Marsh Seal - Spikey Window", + "Quillshroom Marsh Seal - Sand Trap", + "Quillshroom Marsh Seal - Do the Spike Wave", + "Searing Crags Seal - Triple Ball Spinner", + "Searing Crags Seal - Raining Rocks", + "Searing Crags Seal - Rhythm Rocks", + "Glacial Peak Seal - Ice Climbers", + "Glacial Peak Seal - Projectile Spike Pit", + "Glacial Peak Seal - Glacial Air Swag", + "Tower of Time Seal - Time Waster", + "Tower of Time Seal - Lantern Climb", + "Tower of Time Seal - Arcane Orbs", + "Cloud Ruins Seal - Ghost Pit", + "Cloud Ruins Seal - Toothbrush Alley", + "Cloud Ruins Seal - Saw Pit", + "Cloud Ruins Seal - Money Farm Room", + "Underworld Seal - Sharp and Windy Climb", + "Underworld Seal - Spike Wall", + "Underworld Seal - Fireball Wave", + "Underworld Seal - Rising Fanta", + "Forlorn Temple Seal - Rocket Maze", + "Forlorn Temple Seal - Rocket Sunset", + "Sunken Shrine Seal - Ultra Lifeguard", + "Sunken Shrine Seal - Waterfall Paradise", + "Sunken Shrine Seal - Tabi Gauntlet", + "Riviere Turquoise Seal - Bounces and Balls", + "Riviere Turquoise Seal - Launch of Faith", + "Riviere Turquoise Seal - Flower Power", + "Elemental Skylands Seal - Air", + "Elemental Skylands Seal - Water", + "Elemental Skylands Seal - Fire", ] BOSS_LOCATIONS = [ diff --git a/worlds/messenger/docs/en_The Messenger.md b/worlds/messenger/docs/en_The Messenger.md index 374753b487..f071ba1c14 100644 --- a/worlds/messenger/docs/en_The Messenger.md +++ b/worlds/messenger/docs/en_The Messenger.md @@ -69,8 +69,8 @@ for it. The groups you can use for The Messenger are: * Sometimes upon teleporting back to HQ, Ninja will run left and enter a different portal than the one entered by the player. This may also cause a softlock. * Text entry menus don't accept controller input -* Opening the shop chest in power seal hunt mode from the tower of time HQ will softlock the game. -* If you are unable to reset file slots, load into a save slot, let the game save, and close it. +* In power seal hunt mode, the chest must be opened by entering the shop from a level. Teleporting to HQ and opening the + chest will not work. ## What do I do if I have a problem? diff --git a/worlds/messenger/docs/setup_en.md b/worlds/messenger/docs/setup_en.md index 9617baf3e0..d986b70f9c 100644 --- a/worlds/messenger/docs/setup_en.md +++ b/worlds/messenger/docs/setup_en.md @@ -9,10 +9,20 @@ ## Installation -1. Read the [Game Info Page](/games/The%20Messenger/info/en) for how the game works, caveats and known issues -2. Download and install Courier Mod Loader using the instructions on the release page +Read changes to the base game on the [Game Info Page](/games/The%20Messenger/info/en) + +### Automated Installation + +1. Download and install the latest [Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases/latest) +2. Launch the Archipelago Launcher (ArchipelagoLauncher.exe) +3. Click on "The Messenger" +4. Follow the prompts + +### Manual Installation + +1. Download and install Courier Mod Loader using the instructions on the release page * [Latest release is currently 0.7.1](https://github.com/Brokemia/Courier/releases) -3. Download and install the randomizer mod +2. Download and install the randomizer mod 1. Download the latest TheMessengerRandomizerAP.zip from [The Messenger Randomizer Mod AP releases page](https://github.com/alwaysintreble/TheMessengerRandomizerModAP/releases) 2. Extract the zip file to `TheMessenger/Mods/` of your game's install location @@ -32,19 +42,17 @@ ## Joining a MultiWorld Game 1. Launch the game -2. Navigate to `Options > Third Party Mod Options` -3. Select `Reset Randomizer File Slots` - * This will set up all of your save slots with new randomizer save files. You can have up to 3 randomizer files at a - time, but must do this step again to start new runs afterward. -4. Enter connection info using the relevant option buttons +2. Navigate to `Options > Archipelago Options` +3. Enter connection info using the relevant option buttons * **The game is limited to alphanumerical characters, `.`, and `-`.** * This defaults to `archipelago.gg` and does not need to be manually changed if connecting to a game hosted on the website. * If using a name that cannot be entered in the in game menus, there is a config file (APConfig.toml) in the game directory. When using this, all connection information must be entered in the file. -5. Select the `Connect to Archipelago` button -6. Navigate to save file selection -7. Select a new valid randomizer save +4. Select the `Connect to Archipelago` button +5. Navigate to save file selection +6. Start a new game + * If you're already connected, deleting a save will not disconnect you and is completely safe. ## Continuing a MultiWorld Game @@ -52,6 +60,5 @@ At any point while playing, it is completely safe to quit. Returning to the titl disconnect you from the server. To reconnect to an in progress MultiWorld, simply load the correct save file for that MultiWorld. -If the reconnection fails, the message on screen will state you are disconnected. If this happens, you can return to the -main menu and connect to the server as in [Joining a Multiworld Game](#joining-a-multiworld-game), then load the correct -save file. +If the reconnection fails, the message on screen will state you are disconnected. If this happens, the game will attempt +to reconnect in the background. An option will also be added to the in game menu to change the port, if necessary. diff --git a/worlds/messenger/options.py b/worlds/messenger/options.py index 6984e21547..c56ee70043 100644 --- a/worlds/messenger/options.py +++ b/worlds/messenger/options.py @@ -17,29 +17,78 @@ class Logic(Choice): """ The level of logic to use when determining what locations in your world are accessible. - Normal: can require damage boosts, but otherwise approachable for someone who has beaten the game. - Hard: has leashing, normal clips, time warps and turtle boosting in logic. - OoB: places everything with the minimum amount of rules possible. Expect to do OoB. Not guaranteed completable. + Normal: Can require damage boosts, but otherwise approachable for someone who has beaten the game. + Hard: Expects more knowledge and tighter execution. Has leashing, normal clips and much tighter d-boosting in logic. """ display_name = "Logic Level" option_normal = 0 option_hard = 1 - option_oob = 2 + alias_oob = 1 alias_challenging = 1 -class PowerSeals(DefaultOnToggle): - """Whether power seal locations should be randomized.""" - display_name = "Shuffle Seals" - - class MegaShards(Toggle): """Whether mega shards should be item locations.""" display_name = "Shuffle Mega Time Shards" +class LimitedMovement(Toggle): + """ + Removes either rope dart or wingsuit from the itempool. Forces logic to at least hard and accessibility to minimal. + """ + display_name = "Limited Movement" + + +class EarlyMed(Toggle): + """Guarantees meditation will be found early""" + display_name = "Early Meditation" + + +class AvailablePortals(Range): + """Number of portals that are available from the start. Autumn Hills, Howling Grotto, and Glacial Peak are always available. If portal outputs are not randomized, Searing Crags will also be available.""" + display_name = "Available Starting Portals" + range_start = 3 + range_end = 6 + default = 6 + + +class ShufflePortals(Choice): + """ + Whether the portals lead to random places. + Entering a portal from its vanilla area will always lead to HQ, and will unlock it if relevant. + Supports plando. + + None: Portals will take you where they're supposed to. + Shops: Portals can lead to any area except Music Box and Elemental Skylands, with each portal output guaranteed to not overlap with another portal's. Will only put you at a portal or a shop. + Checkpoints: Like Shops except checkpoints without shops are also valid drop points. + Anywhere: Like Checkpoints except it's possible for multiple portals to output to the same map. + """ + display_name = "Shuffle Portal Outputs" + option_none = 0 + alias_off = 0 + option_shops = 1 + option_checkpoints = 2 + option_anywhere = 3 + + +class ShuffleTransitions(Choice): + """ + Whether the transitions between the levels should be randomized. + Supports plando. + + None: Level transitions lead where they should. + Coupled: Returning through a transition will take you from whence you came. + Decoupled: Any level transition can take you to any other level transition. + """ + display_name = "Shuffle Level Transitions" + option_none = 0 + alias_off = 0 + option_coupled = 1 + option_decoupled = 2 + + class Goal(Choice): - """Requirement to finish the game. Power Seal Hunt will force power seal locations to be shuffled.""" + """Requirement to finish the game.""" display_name = "Goal" option_open_music_box = 0 option_power_seal_hunt = 1 @@ -137,8 +186,12 @@ class MessengerOptions(DeathLinkMixin, PerGameCommonOptions): accessibility: MessengerAccessibility start_inventory: StartInventoryPool logic_level: Logic - shuffle_seals: PowerSeals shuffle_shards: MegaShards + limited_movement: LimitedMovement + early_meditation: EarlyMed + available_portals: AvailablePortals + shuffle_portals: ShufflePortals + # shuffle_transitions: ShuffleTransitions goal: Goal music_box: MusicBox notes_needed: NotesNeeded diff --git a/worlds/messenger/portals.py b/worlds/messenger/portals.py new file mode 100644 index 0000000000..64438b0184 --- /dev/null +++ b/worlds/messenger/portals.py @@ -0,0 +1,290 @@ +from typing import List, TYPE_CHECKING + +from BaseClasses import CollectionState, PlandoOptions +from .options import ShufflePortals +from ..generic import PlandoConnection + +if TYPE_CHECKING: + from . import MessengerWorld + + +PORTALS = [ + "Autumn Hills", + "Riviere Turquoise", + "Howling Grotto", + "Sunken Shrine", + "Searing Crags", + "Glacial Peak", +] + + +REGION_ORDER = [ + "Autumn Hills", + "Forlorn Temple", + "Catacombs", + "Bamboo Creek", + "Howling Grotto", + "Quillshroom Marsh", + "Searing Crags", + "Glacial Peak", + "Tower of Time", + "Cloud Ruins", + "Underworld", + "Riviere Turquoise", + "Elemental Skylands", + "Sunken Shrine", +] + + +SHOP_POINTS = { + "Autumn Hills": [ + "Climbing Claws", + "Hope Path", + "Dimension Climb", + "Leaf Golem", + ], + "Forlorn Temple": [ + "Outside", + "Entrance", + "Climb", + "Rocket Sunset", + "Descent", + "Saw Gauntlet", + "Demon King", + ], + "Catacombs": [ + "Triple Spike Crushers", + "Ruxxtin", + ], + "Bamboo Creek": [ + "Spike Crushers", + "Abandoned", + "Time Loop", + ], + "Howling Grotto": [ + "Wingsuit", + "Crushing Pits", + "Emerald Golem", + ], + "Quillshroom Marsh": [ + "Spikey Window", + "Sand Trap", + "Queen of Quills", + ], + "Searing Crags": [ + "Rope Dart", + "Falling Rocks", + "Searing Mega Shard", + "Before Final Climb", + "Colossuses", + "Key of Strength", + ], + "Glacial Peak": [ + "Ice Climbers'", + "Glacial Mega Shard", + "Tower Entrance", + ], + "Tower of Time": [ + "Final Chance", + "Arcane Golem", + ], + "Cloud Ruins": [ + "Cloud Entrance", + "Pillar Glide", + "Crushers' Descent", + "Seeing Spikes", + "Final Flight", + "Manfred's", + ], + "Underworld": [ + "Left", + "Fireball Wave", + "Long Climb", + # "Barm'athaziel", # not currently valid + "Key of Chaos", + ], + "Riviere Turquoise": [ + "Waterfall", + "Launch of Faith", + "Log Flume", + "Log Climb", + "Restock", + "Butterfly Matriarch", + ], + "Elemental Skylands": [ + "Air Intro", + "Air Generator", + "Earth Intro", + "Earth Generator", + "Fire Intro", + "Fire Generator", + "Water Intro", + "Water Generator", + ], + "Sunken Shrine": [ + "Above Portal", + "Lifeguard", + "Sun Path", + "Tabi Gauntlet", + "Moon Path", + ] +} + + +CHECKPOINTS = { + "Autumn Hills": [ + "Hope Latch", + "Key of Hope", + "Lakeside", + "Double Swing", + "Spike Ball Swing", + ], + "Forlorn Temple": [ + "Sunny Day", + "Rocket Maze", + ], + "Catacombs": [ + "Death Trap", + "Crusher Gauntlet", + "Dirty Pond", + ], + "Bamboo Creek": [ + "Spike Ball Pits", + "Spike Doors", + ], + "Howling Grotto": [ + "Lost Woods", + "Breezy Crushers", + ], + "Quillshroom Marsh": [ + "Seashell", + "Quicksand", + "Spike Wave", + ], + "Searing Crags": [ + "Triple Ball Spinner", + "Raining Rocks", + ], + "Glacial Peak": [ + "Projectile Spike Pit", + "Air Swag", + "Free Climbing", + ], + "Tower of Time": [ + "First", + "Second", + "Third", + "Fourth", + "Fifth", + "Sixth", + ], + "Cloud Ruins": [ + "Spike Float", + "Ghost Pit", + "Toothbrush Alley", + "Saw Pit", + ], + "Underworld": [ + "Hot Dip", + "Hot Tub", + "Lava Run", + ], + "Riviere Turquoise": [ + "Flower Flight", + ], + "Elemental Skylands": [ + "Air Seal", + ], + "Sunken Shrine": [ + "Lightfoot Tabi", + "Sun Crest", + "Waterfall Paradise", + "Moon Crest", + ] +} + + +def shuffle_portals(world: "MessengerWorld") -> None: + def create_mapping(in_portal: str, warp: str) -> None: + nonlocal available_portals + parent = out_to_parent[warp] + exit_string = f"{parent.strip(' ')} - " + + if "Portal" in warp: + exit_string += "Portal" + world.portal_mapping.append(int(f"{REGION_ORDER.index(parent)}00")) + elif warp_point in SHOP_POINTS[parent]: + exit_string += f"{warp_point} Shop" + world.portal_mapping.append(int(f"{REGION_ORDER.index(parent)}1{SHOP_POINTS[parent].index(warp_point)}")) + else: + exit_string += f"{warp_point} Checkpoint" + world.portal_mapping.append(int(f"{REGION_ORDER.index(parent)}2{CHECKPOINTS[parent].index(warp_point)}")) + + world.spoiler_portal_mapping[in_portal] = exit_string + connect_portal(world, in_portal, exit_string) + + available_portals.remove(warp) + if shuffle_type < ShufflePortals.option_anywhere: + available_portals = [port for port in available_portals if port not in shop_points[parent]] + + def handle_planned_portals(plando_connections: List[PlandoConnection]) -> None: + for connection in plando_connections: + if connection.entrance not in PORTALS: + continue + # let it crash here if input is invalid + create_mapping(connection.entrance, connection.exit) + world.plando_portals.append(connection.entrance) + + shuffle_type = world.options.shuffle_portals + shop_points = SHOP_POINTS.copy() + for portal in PORTALS: + shop_points[portal].append(f"{portal} Portal") + if shuffle_type > ShufflePortals.option_shops: + shop_points.update(CHECKPOINTS) + out_to_parent = {checkpoint: parent for parent, checkpoints in shop_points.items() for checkpoint in checkpoints} + available_portals = [val for zone in shop_points.values() for val in zone] + + plando = world.multiworld.plando_connections[world.player] + if plando and world.multiworld.plando_options & PlandoOptions.connections: + handle_planned_portals(plando) + world.multiworld.plando_connections[world.player] = [connection for connection in plando + if connection.entrance not in PORTALS] + for portal in PORTALS: + warp_point = world.random.choice(available_portals) + create_mapping(portal, warp_point) + + +def connect_portal(world: "MessengerWorld", portal: str, out_region: str) -> None: + entrance = world.multiworld.get_entrance(f"ToTHQ {portal} Portal", world.player) + entrance.connect(world.multiworld.get_region(out_region, world.player)) + + +def disconnect_portals(world: "MessengerWorld") -> None: + for portal in [port for port in PORTALS if port not in world.plando_portals]: + entrance = world.multiworld.get_entrance(f"ToTHQ {portal} Portal", world.player) + entrance.connected_region.entrances.remove(entrance) + entrance.connected_region = None + if portal in world.spoiler_portal_mapping: + del world.spoiler_portal_mapping[portal] + if len(world.portal_mapping) > len(world.spoiler_portal_mapping): + world.portal_mapping = world.portal_mapping[:len(world.spoiler_portal_mapping)] + + +def validate_portals(world: "MessengerWorld") -> bool: + # if world.options.shuffle_transitions: + # return True + new_state = CollectionState(world.multiworld) + new_state.update_reachable_regions(world.player) + reachable_locs = 0 + for loc in world.multiworld.get_locations(world.player): + reachable_locs += loc.can_reach(new_state) + if reachable_locs > 5: + return True + return False + + +def add_closed_portal_reqs(world: "MessengerWorld") -> None: + closed_portals = [entrance for entrance in PORTALS if f"{entrance} Portal" not in world.starting_portals] + for portal in closed_portals: + tower_exit = world.multiworld.get_entrance(f"ToTHQ {portal} Portal", world.player) + tower_exit.access_rule = lambda state: state.has(portal, world.player) diff --git a/worlds/messenger/regions.py b/worlds/messenger/regions.py index 43de4dd1f6..153f8510f1 100644 --- a/worlds/messenger/regions.py +++ b/worlds/messenger/regions.py @@ -1,103 +1,446 @@ -from typing import Dict, List, Set +from typing import Dict, List -REGIONS: Dict[str, List[str]] = { - "Menu": [], - "Tower HQ": [], - "The Shop": [], - "The Craftsman's Corner": [], - "Tower of Time": [], - "Ninja Village": ["Ninja Village - Candle", "Ninja Village - Astral Seed"], - "Autumn Hills": ["Autumn Hills - Climbing Claws", "Autumn Hills - Key of Hope", "Autumn Hills - Leaf Golem"], - "Forlorn Temple": ["Forlorn Temple - Demon King"], - "Catacombs": ["Catacombs - Necro", "Catacombs - Ruxxtin's Amulet", "Catacombs - Ruxxtin"], - "Bamboo Creek": ["Bamboo Creek - Claustro"], - "Howling Grotto": ["Howling Grotto - Wingsuit", "Howling Grotto - Emerald Golem"], - "Quillshroom Marsh": ["Quillshroom Marsh - Seashell", "Quillshroom Marsh - Queen of Quills"], - "Searing Crags": ["Searing Crags - Rope Dart"], - "Searing Crags Upper": ["Searing Crags - Power Thistle", "Searing Crags - Key of Strength", - "Searing Crags - Astral Tea Leaves"], - "Glacial Peak": [], - "Cloud Ruins": [], - "Cloud Ruins Right": ["Cloud Ruins - Acro"], - "Underworld": ["Searing Crags - Pyro", "Underworld - Key of Chaos"], - "Dark Cave": [], - "Riviere Turquoise Entrance": [], - "Riviere Turquoise": ["Riviere Turquoise - Butterfly Matriarch"], - "Sunken Shrine": ["Sunken Shrine - Lightfoot Tabi", "Sunken Shrine - Sun Crest", "Sunken Shrine - Moon Crest", - "Sunken Shrine - Key of Love"], - "Elemental Skylands": ["Elemental Skylands - Key of Symbiosis"], + +LOCATIONS: Dict[str, List[str]] = { + "Ninja Village - Nest": [ + "Ninja Village - Candle", + "Ninja Village - Astral Seed", + "Ninja Village Seal - Tree House", + ], + "Autumn Hills - Climbing Claws Shop": [ + "Autumn Hills - Climbing Claws", + "Autumn Hills Seal - Trip Saws", + ], + "Autumn Hills - Key of Hope Checkpoint": [ + "Autumn Hills - Key of Hope", + ], + "Autumn Hills - Double Swing Checkpoint": [ + "Autumn Hills Seal - Double Swing Saws", + ], + "Autumn Hills - Spike Ball Swing Checkpoint": [ + "Autumn Hills Seal - Spike Ball Swing", + "Autumn Hills Seal - Spike Ball Darts", + ], + "Autumn Hills - Leaf Golem Shop": [ + "Autumn Hills - Leaf Golem", + ], + "Forlorn Temple - Rocket Maze Checkpoint": [ + "Forlorn Temple Seal - Rocket Maze", + ], + "Forlorn Temple - Rocket Sunset Shop": [ + "Forlorn Temple Seal - Rocket Sunset", + ], + "Forlorn Temple - Demon King Shop": [ + "Forlorn Temple - Demon King", + ], + "Catacombs - Top Left": [ + "Catacombs - Necro", + ], + "Catacombs - Triple Spike Crushers Shop": [ + "Catacombs Seal - Triple Spike Crushers", + ], + "Catacombs - Dirty Pond Checkpoint": [ + "Catacombs Seal - Crusher Gauntlet", + "Catacombs Seal - Dirty Pond", + ], + "Catacombs - Ruxxtin Shop": [ + "Catacombs - Ruxxtin's Amulet", + "Catacombs - Ruxxtin", + ], + "Bamboo Creek - Spike Crushers Shop": [ + "Bamboo Creek Seal - Spike Crushers and Doors", + ], + "Bamboo Creek - Spike Ball Pits Checkpoint": [ + "Bamboo Creek Seal - Spike Ball Pits", + ], + "Bamboo Creek - Time Loop Shop": [ + "Bamboo Creek Seal - Spike Crushers and Doors v2", + "Bamboo Creek - Claustro", + ], + "Howling Grotto - Wingsuit Shop": [ + "Howling Grotto - Wingsuit", + "Howling Grotto Seal - Windy Saws and Balls", + ], + "Howling Grotto - Crushing Pits Shop": [ + "Howling Grotto Seal - Crushing Pits", + ], + "Howling Grotto - Breezy Crushers Checkpoint": [ + "Howling Grotto Seal - Breezy Crushers", + ], + "Howling Grotto - Emerald Golem Shop": [ + "Howling Grotto - Emerald Golem", + ], + "Quillshroom Marsh - Seashell Checkpoint": [ + "Quillshroom Marsh - Seashell", + ], + "Quillshroom Marsh - Spikey Window Shop": [ + "Quillshroom Marsh Seal - Spikey Window", + ], + "Quillshroom Marsh - Sand Trap Shop": [ + "Quillshroom Marsh Seal - Sand Trap", + ], + "Quillshroom Marsh - Spike Wave Checkpoint": [ + "Quillshroom Marsh Seal - Do the Spike Wave", + ], + "Quillshroom Marsh - Queen of Quills Shop": [ + "Quillshroom Marsh - Queen of Quills", + ], + "Searing Crags - Rope Dart Shop": [ + "Searing Crags - Rope Dart", + ], + "Searing Crags - Triple Ball Spinner Checkpoint": [ + "Searing Crags Seal - Triple Ball Spinner", + ], + "Searing Crags - Raining Rocks Checkpoint": [ + "Searing Crags Seal - Raining Rocks", + ], + "Searing Crags - Colossuses Shop": [ + "Searing Crags Seal - Rhythm Rocks", + "Searing Crags - Power Thistle", + "Searing Crags - Astral Tea Leaves", + ], + "Searing Crags - Key of Strength Shop": [ + "Searing Crags - Key of Strength", + ], + "Searing Crags - Portal": [ + "Searing Crags - Pyro", + ], + "Glacial Peak - Ice Climbers' Shop": [ + "Glacial Peak Seal - Ice Climbers", + ], + "Glacial Peak - Projectile Spike Pit Checkpoint": [ + "Glacial Peak Seal - Projectile Spike Pit", + ], + "Glacial Peak - Air Swag Checkpoint": [ + "Glacial Peak Seal - Glacial Air Swag", + ], + "Tower of Time - First Checkpoint": [ + "Tower of Time Seal - Time Waster", + ], + "Tower of Time - Fourth Checkpoint": [ + "Tower of Time Seal - Lantern Climb", + ], + "Tower of Time - Fifth Checkpoint": [ + "Tower of Time Seal - Arcane Orbs", + ], + "Cloud Ruins - Ghost Pit Checkpoint": [ + "Cloud Ruins Seal - Ghost Pit", + ], + "Cloud Ruins - Toothbrush Alley Checkpoint": [ + "Cloud Ruins Seal - Toothbrush Alley", + ], + "Cloud Ruins - Saw Pit Checkpoint": [ + "Cloud Ruins Seal - Saw Pit", + ], + "Cloud Ruins - Final Flight Shop": [ + "Cloud Ruins - Acro", + ], + "Cloud Ruins - Manfred's Shop": [ + "Cloud Ruins Seal - Money Farm Room", + ], + "Underworld - Left Shop": [ + "Underworld Seal - Sharp and Windy Climb", + ], + "Underworld - Fireball Wave Shop": [ + "Underworld Seal - Spike Wall", + "Underworld Seal - Fireball Wave", + ], + "Underworld - Hot Tub Checkpoint": [ + "Underworld Seal - Rising Fanta", + ], + "Underworld - Key of Chaos Shop": [ + "Underworld - Key of Chaos", + ], + "Riviere Turquoise - Waterfall Shop": [ + "Riviere Turquoise Seal - Bounces and Balls", + ], + "Riviere Turquoise - Launch of Faith Shop": [ + "Riviere Turquoise Seal - Launch of Faith", + ], + "Riviere Turquoise - Restock Shop": [ + "Riviere Turquoise Seal - Flower Power", + ], + "Riviere Turquoise - Butterfly Matriarch Shop": [ + "Riviere Turquoise - Butterfly Matriarch", + ], + "Sunken Shrine - Lifeguard Shop": [ + "Sunken Shrine Seal - Ultra Lifeguard", + ], + "Sunken Shrine - Lightfoot Tabi Checkpoint": [ + "Sunken Shrine - Lightfoot Tabi", + ], + "Sunken Shrine - Portal": [ + "Sunken Shrine - Key of Love", + ], + "Sunken Shrine - Tabi Gauntlet Shop": [ + "Sunken Shrine Seal - Tabi Gauntlet", + ], + "Sunken Shrine - Sun Crest Checkpoint": [ + "Sunken Shrine - Sun Crest", + ], + "Sunken Shrine - Waterfall Paradise Checkpoint": [ + "Sunken Shrine Seal - Waterfall Paradise", + ], + "Sunken Shrine - Moon Crest Checkpoint": [ + "Sunken Shrine - Moon Crest", + ], + "Elemental Skylands - Air Seal Checkpoint": [ + "Elemental Skylands Seal - Air", + ], + "Elemental Skylands - Water Intro Shop": [ + "Elemental Skylands Seal - Water", + ], + "Elemental Skylands - Fire Intro Shop": [ + "Elemental Skylands Seal - Fire", + ], + "Elemental Skylands - Right": [ + "Elemental Skylands - Key of Symbiosis", + ], "Corrupted Future": ["Corrupted Future - Key of Courage"], "Music Box": ["Rescue Phantom"], } -SEALS: Dict[str, List[str]] = { - "Ninja Village": ["Ninja Village Seal - Tree House"], - "Autumn Hills": ["Autumn Hills Seal - Trip Saws", "Autumn Hills Seal - Double Swing Saws", - "Autumn Hills Seal - Spike Ball Swing", "Autumn Hills Seal - Spike Ball Darts"], - "Catacombs": ["Catacombs Seal - Triple Spike Crushers", "Catacombs Seal - Crusher Gauntlet", - "Catacombs Seal - Dirty Pond"], - "Bamboo Creek": ["Bamboo Creek Seal - Spike Crushers and Doors", "Bamboo Creek Seal - Spike Ball Pits", - "Bamboo Creek Seal - Spike Crushers and Doors v2"], - "Howling Grotto": ["Howling Grotto Seal - Windy Saws and Balls", "Howling Grotto Seal - Crushing Pits", - "Howling Grotto Seal - Breezy Crushers"], - "Quillshroom Marsh": ["Quillshroom Marsh Seal - Spikey Window", "Quillshroom Marsh Seal - Sand Trap", - "Quillshroom Marsh Seal - Do the Spike Wave"], - "Searing Crags": ["Searing Crags Seal - Triple Ball Spinner"], - "Searing Crags Upper": ["Searing Crags Seal - Raining Rocks", "Searing Crags Seal - Rhythm Rocks"], - "Glacial Peak": ["Glacial Peak Seal - Ice Climbers", "Glacial Peak Seal - Projectile Spike Pit", - "Glacial Peak Seal - Glacial Air Swag"], - "Tower of Time": ["Tower of Time Seal - Time Waster", "Tower of Time Seal - Lantern Climb", - "Tower of Time Seal - Arcane Orbs"], - "Cloud Ruins Right": ["Cloud Ruins Seal - Ghost Pit", "Cloud Ruins Seal - Toothbrush Alley", - "Cloud Ruins Seal - Saw Pit", "Cloud Ruins Seal - Money Farm Room"], - "Underworld": ["Underworld Seal - Sharp and Windy Climb", "Underworld Seal - Spike Wall", - "Underworld Seal - Fireball Wave", "Underworld Seal - Rising Fanta"], - "Forlorn Temple": ["Forlorn Temple Seal - Rocket Maze", "Forlorn Temple Seal - Rocket Sunset"], - "Sunken Shrine": ["Sunken Shrine Seal - Ultra Lifeguard", "Sunken Shrine Seal - Waterfall Paradise", - "Sunken Shrine Seal - Tabi Gauntlet"], - "Riviere Turquoise Entrance": ["Riviere Turquoise Seal - Bounces and Balls"], - "Riviere Turquoise": ["Riviere Turquoise Seal - Launch of Faith", "Riviere Turquoise Seal - Flower Power"], - "Elemental Skylands": ["Elemental Skylands Seal - Air", "Elemental Skylands Seal - Water", - "Elemental Skylands Seal - Fire"] + +SUB_REGIONS: Dict[str, List[str]] = { + "Ninja Village": [ + "Right", + ], + "Autumn Hills": [ + "Left", + "Right", + "Bottom", + "Portal", + "Climbing Claws Shop", + "Hope Path Shop", + "Dimension Climb Shop", + "Leaf Golem Shop", + "Hope Path Checkpoint", + "Key of Hope Checkpoint", + "Lakeside Checkpoint", + "Double Swing Checkpoint", + "Spike Ball Swing Checkpoint", + ], + "Forlorn Temple": [ + "Left", + "Right", + "Bottom", + "Outside Shop", + "Entrance Shop", + "Climb Shop", + "Rocket Sunset Shop", + "Descent Shop", + "Final Fall Shop", + "Demon King Shop", + "Sunny Day Checkpoint", + "Rocket Maze Checkpoint", + ], + "Catacombs": [ + "Top Left", + "Bottom Left", + "Bottom", + "Right", + "Triple Spike Crushers Shop", + "Ruxxtin Shop", + "Death Trap Checkpoint", + "Crusher Gauntlet Checkpoint", + "Dirty Pond Checkpoint", + ], + "Bamboo Creek": [ + "Bottom Left", + "Top Left", + "Right", + "Spike Crushers Shop", + "Abandoned Shop", + "Time Loop Shop", + "Spike Ball Pits Checkpoint", + "Spike Doors Checkpoint", + ], + "Howling Grotto": [ + "Left", + "Top", + "Right", + "Bottom", + "Portal", + "Wingsuit Shop", + "Crushing Pits Shop", + "Emerald Golem Shop", + "Lost Woods Checkpoint", + "Breezy Crushers Checkpoint", + ], + "Quillshroom Marsh": [ + "Top Left", + "Bottom Left", + "Top Right", + "Bottom Right", + "Spikey Window Shop", + "Sand Trap Shop", + "Queen of Quills Shop", + "Seashell Checkpoint", + "Quicksand Checkpoint", + "Spike Wave Checkpoint", + ], + "Searing Crags": [ + "Left", + "Top", + "Bottom", + "Right", + "Portal", + "Rope Dart Shop", + "Falling Rocks Shop", + "Searing Mega Shard Shop", + "Before Final Climb Shop", + "Colossuses Shop", + "Key of Strength Shop", + "Triple Ball Spinner Checkpoint", + "Raining Rocks Checkpoint", + ], + "Glacial Peak": [ + "Bottom", + "Top", + "Portal", + "Ice Climbers' Shop", + "Glacial Mega Shard Shop", + "Tower Entrance Shop", + "Projectile Spike Pit Checkpoint", + "Air Swag Checkpoint", + "Free Climbing Checkpoint", + ], + "Tower of Time": [ + "Left", + "Entrance Shop", + "Arcane Golem Shop", + "First Checkpoint", + "Second Checkpoint", + "Third Checkpoint", + "Fourth Checkpoint", + "Fifth Checkpoint", + "Sixth Checkpoint", + ], + "Cloud Ruins": [ + "Left", + "Entrance Shop", + "Pillar Glide Shop", + "Crushers' Descent Shop", + "Seeing Spikes Shop", + "Sliding Spikes Shop", + "Final Flight Shop", + "Manfred's Shop", + "Spike Float Checkpoint", + "Ghost Pit Checkpoint", + "Toothbrush Alley Checkpoint", + "Saw Pit Checkpoint", + ], + "Underworld": [ + "Left", + "Entrance Shop", + "Fireball Wave Shop", + "Long Climb Shop", + "Barm'athaziel Shop", + "Key of Chaos Shop", + "Hot Dip Checkpoint", + "Hot Tub Checkpoint", + "Lava Run Checkpoint", + ], + "Riviere Turquoise": [ + "Right", + "Portal", + "Waterfall Shop", + "Launch of Faith Shop", + "Log Flume Shop", + "Log Climb Shop", + "Restock Shop", + "Butterfly Matriarch Shop", + "Flower Flight Checkpoint", + ], + "Elemental Skylands": [ + "Air Shmup", + "Air Intro Shop", + "Air Seal Checkpoint", + "Air Generator Shop", + "Earth Shmup", + "Earth Intro Shop", + "Earth Generator Shop", + "Fire Shmup", + "Fire Intro Shop", + "Fire Generator Shop", + "Water Shmup", + "Water Intro Shop", + "Water Generator Shop", + "Right", + ], + "Sunken Shrine": [ + "Left", + "Portal", + "Entrance Shop", + "Lifeguard Shop", + "Sun Path Shop", + "Tabi Gauntlet Shop", + "Moon Path Shop", + "Ninja Tabi Checkpoint", + "Sun Crest Checkpoint", + "Waterfall Paradise Checkpoint", + "Moon Crest Checkpoint", + ], } + +# order is slightly funky here for back compat MEGA_SHARDS: Dict[str, List[str]] = { - "Autumn Hills": ["Autumn Hills Mega Shard", "Hidden Entrance Mega Shard"], - "Catacombs": ["Catacombs Mega Shard"], - "Bamboo Creek": ["Above Entrance Mega Shard", "Abandoned Mega Shard", "Time Loop Mega Shard"], - "Howling Grotto": ["Bottom Left Mega Shard", "Near Portal Mega Shard", "Pie in the Sky Mega Shard"], - "Quillshroom Marsh": ["Quillshroom Marsh Mega Shard"], - "Searing Crags Upper": ["Searing Crags Mega Shard"], - "Glacial Peak": ["Glacial Peak Mega Shard"], - "Cloud Ruins": ["Cloud Entrance Mega Shard", "Time Warp Mega Shard"], - "Cloud Ruins Right": ["Money Farm Room Mega Shard 1", "Money Farm Room Mega Shard 2"], - "Underworld": ["Under Entrance Mega Shard", "Hot Tub Mega Shard", "Projectile Pit Mega Shard"], - "Forlorn Temple": ["Sunny Day Mega Shard", "Down Under Mega Shard"], - "Sunken Shrine": ["Mega Shard of the Moon", "Beginner's Mega Shard", "Mega Shard of the Stars", "Mega Shard of the Sun"], - "Riviere Turquoise Entrance": ["Waterfall Mega Shard"], - "Riviere Turquoise": ["Quick Restock Mega Shard 1", "Quick Restock Mega Shard 2"], - "Elemental Skylands": ["Earth Mega Shard", "Water Mega Shard"], + "Autumn Hills - Lakeside Checkpoint": ["Autumn Hills Mega Shard"], + "Forlorn Temple - Outside Shop": ["Hidden Entrance Mega Shard"], + "Catacombs - Top Left": ["Catacombs Mega Shard"], + "Bamboo Creek - Spike Crushers Shop": ["Above Entrance Mega Shard"], + "Bamboo Creek - Abandoned Shop": ["Abandoned Mega Shard"], + "Bamboo Creek - Time Loop Shop": ["Time Loop Mega Shard"], + "Howling Grotto - Lost Woods Checkpoint": ["Bottom Left Mega Shard"], + "Howling Grotto - Breezy Crushers Checkpoint": ["Near Portal Mega Shard", "Pie in the Sky Mega Shard"], + "Quillshroom Marsh - Spikey Window Shop": ["Quillshroom Marsh Mega Shard"], + "Searing Crags - Searing Mega Shard Shop": ["Searing Crags Mega Shard"], + "Glacial Peak - Glacial Mega Shard Shop": ["Glacial Peak Mega Shard"], + "Cloud Ruins - Cloud Entrance Shop": ["Cloud Entrance Mega Shard", "Time Warp Mega Shard"], + "Cloud Ruins - Manfred's Shop": ["Money Farm Room Mega Shard 1", "Money Farm Room Mega Shard 2"], + "Underworld - Left Shop": ["Under Entrance Mega Shard"], + "Underworld - Hot Tub Checkpoint": ["Hot Tub Mega Shard", "Projectile Pit Mega Shard"], + "Forlorn Temple - Sunny Day Checkpoint": ["Sunny Day Mega Shard"], + "Forlorn Temple - Demon King Shop": ["Down Under Mega Shard"], + "Sunken Shrine - Waterfall Paradise Checkpoint": ["Mega Shard of the Moon"], + "Sunken Shrine - Portal": ["Beginner's Mega Shard"], + "Sunken Shrine - Above Portal Shop": ["Mega Shard of the Stars"], + "Sunken Shrine - Sun Crest Checkpoint": ["Mega Shard of the Sun"], + "Riviere Turquoise - Waterfall Shop": ["Waterfall Mega Shard"], + "Riviere Turquoise - Restock Shop": ["Quick Restock Mega Shard 1", "Quick Restock Mega Shard 2"], + "Elemental Skylands - Earth Intro Shop": ["Earth Mega Shard"], + "Elemental Skylands - Water Generator Shop": ["Water Mega Shard"], } -REGION_CONNECTIONS: Dict[str, Set[str]] = { - "Menu": {"Tower HQ"}, - "Tower HQ": {"Autumn Hills", "Howling Grotto", "Searing Crags", "Glacial Peak", "Tower of Time", - "Riviere Turquoise Entrance", "Sunken Shrine", "Corrupted Future", "The Shop", - "The Craftsman's Corner", "Music Box"}, - "Autumn Hills": {"Ninja Village", "Forlorn Temple", "Catacombs"}, - "Forlorn Temple": {"Catacombs", "Bamboo Creek"}, - "Catacombs": {"Autumn Hills", "Bamboo Creek", "Dark Cave"}, - "Bamboo Creek": {"Catacombs", "Howling Grotto"}, - "Howling Grotto": {"Bamboo Creek", "Quillshroom Marsh", "Sunken Shrine"}, - "Quillshroom Marsh": {"Howling Grotto", "Searing Crags"}, - "Searing Crags": {"Searing Crags Upper", "Quillshroom Marsh", "Underworld"}, - "Searing Crags Upper": {"Searing Crags", "Glacial Peak"}, - "Glacial Peak": {"Searing Crags Upper", "Tower HQ", "Cloud Ruins", "Elemental Skylands"}, - "Cloud Ruins": {"Cloud Ruins Right"}, - "Cloud Ruins Right": {"Underworld"}, - "Dark Cave": {"Catacombs", "Riviere Turquoise Entrance"}, - "Riviere Turquoise Entrance": {"Riviere Turquoise"}, - "Sunken Shrine": {"Howling Grotto"}, +REGION_CONNECTIONS: Dict[str, Dict[str, str]] = { + "Menu": {"Tower HQ": "Start Game"}, + "Tower HQ": { + "Autumn Hills - Portal": "ToTHQ Autumn Hills Portal", + "Howling Grotto - Portal": "ToTHQ Howling Grotto Portal", + "Searing Crags - Portal": "ToTHQ Searing Crags Portal", + "Glacial Peak - Portal": "ToTHQ Glacial Peak Portal", + "Tower of Time - Left": "Artificer's Challenge", + "Riviere Turquoise - Portal": "ToTHQ Riviere Turquoise Portal", + "Sunken Shrine - Portal": "ToTHQ Sunken Shrine Portal", + "Corrupted Future": "Artificer's Portal", + "The Shop": "Home", + "Music Box": "Shrink Down", + }, + "The Shop": { + "The Craftsman's Corner": "Money Sink", + }, } -"""Vanilla layout mapping with all Tower HQ portals open. from -> to""" +"""Vanilla layout mapping with all Tower HQ portals open. format is source[exit_region][entrance_name]""" + + +# regions that don't have sub-regions +LEVELS: List[str] = [ + "Menu", + "Tower HQ", + "The Shop", + "The Craftsman's Corner", + "Corrupted Future", + "Music Box", +] diff --git a/worlds/messenger/rules.py b/worlds/messenger/rules.py index b13a453f7f..50e1fa113d 100644 --- a/worlds/messenger/rules.py +++ b/worlds/messenger/rules.py @@ -1,7 +1,7 @@ from typing import Dict, TYPE_CHECKING from BaseClasses import CollectionState -from worlds.generic.Rules import add_rule, allow_self_locking_items, CollectionRule +from worlds.generic.Rules import CollectionRule, add_rule, allow_self_locking_items from .constants import NOTES, PHOBEKINS from .options import MessengerAccessibility @@ -12,6 +12,7 @@ if TYPE_CHECKING: class MessengerRules: player: int world: "MessengerWorld" + connection_rules: Dict[str, CollectionRule] region_rules: Dict[str, CollectionRule] location_rules: Dict[str, CollectionRule] maximum_price: int @@ -27,83 +28,286 @@ class MessengerRules: self.maximum_price = min(maximum_price, world.total_shards) self.required_seals = max(1, world.required_seals) - self.region_rules = { - "Ninja Village": self.has_wingsuit, - "Autumn Hills": self.has_wingsuit, - "Catacombs": self.has_wingsuit, - "Bamboo Creek": self.has_wingsuit, - "Searing Crags Upper": self.has_vertical, - "Cloud Ruins": lambda state: self.has_vertical(state) and state.has("Ruxxtin's Amulet", self.player), - "Cloud Ruins Right": lambda state: self.has_wingsuit(state) and - (self.has_dart(state) or self.can_dboost(state)), - "Underworld": self.has_tabi, - "Riviere Turquoise": lambda state: self.has_dart(state) or - (self.has_wingsuit(state) and self.can_destroy_projectiles(state)), - "Forlorn Temple": lambda state: state.has_all({"Wingsuit", *PHOBEKINS}, self.player) and self.can_dboost(state), - "Glacial Peak": self.has_vertical, - "Elemental Skylands": lambda state: state.has("Magic Firefly", self.player) and self.has_wingsuit(state), - "Music Box": lambda state: (state.has_all(NOTES, self.player) - or self.has_enough_seals(state)) and self.has_dart(state), - "The Craftsman's Corner": lambda state: state.has("Money Wrench", self.player) and self.can_shop(state), + # dict of connection names and requirements to traverse the exit + self.connection_rules = { + # from ToTHQ + "Artificer's Portal": + lambda state: state.has_all({"Demon King Crown", "Magic Firefly"}, self.player), + "Shrink Down": + lambda state: state.has_all(NOTES, self.player) or self.has_enough_seals(state), + # the shop + "Money Sink": + lambda state: state.has("Money Wrench", self.player) and self.can_shop(state), + # Autumn Hills + "Autumn Hills - Portal -> Autumn Hills - Dimension Climb Shop": + lambda state: self.has_wingsuit(state) and self.has_dart(state), + "Autumn Hills - Dimension Climb Shop -> Autumn Hills - Portal": + self.has_vertical, + "Autumn Hills - Climbing Claws Shop -> Autumn Hills - Hope Path Shop": + self.has_dart, + "Autumn Hills - Climbing Claws Shop -> Autumn Hills - Key of Hope Checkpoint": + self.false, # hard logic only + "Autumn Hills - Hope Path Shop -> Autumn Hills - Hope Latch Checkpoint": + self.has_dart, + "Autumn Hills - Hope Path Shop -> Autumn Hills - Climbing Claws Shop": + lambda state: self.has_dart(state) and self.can_dboost(state), + "Autumn Hills - Hope Path Shop -> Autumn Hills - Lakeside Checkpoint": + lambda state: self.has_dart(state) and self.can_dboost(state), + "Autumn Hills - Hope Latch Checkpoint -> Autumn Hills - Hope Path Shop": + self.can_dboost, + "Autumn Hills - Hope Latch Checkpoint -> Autumn Hills - Key of Hope Checkpoint": + lambda state: self.has_dart(state) and self.has_wingsuit(state), + # Forlorn Temple + "Forlorn Temple - Outside Shop -> Forlorn Temple - Entrance Shop": + lambda state: state.has_all(PHOBEKINS, self.player), + "Forlorn Temple - Entrance Shop -> Forlorn Temple - Outside Shop": + lambda state: state.has_all(PHOBEKINS, self.player), + "Forlorn Temple - Entrance Shop -> Forlorn Temple - Sunny Day Checkpoint": + lambda state: self.has_vertical(state) and self.can_dboost(state), + "Forlorn Temple - Sunny Day Checkpoint -> Forlorn Temple - Rocket Maze Checkpoint": + self.has_vertical, + "Forlorn Temple - Rocket Sunset Shop -> Forlorn Temple - Descent Shop": + lambda state: self.has_dart(state) and (self.can_dboost(state) or self.has_wingsuit(state)), + "Forlorn Temple - Saw Gauntlet Shop -> Forlorn Temple - Demon King Shop": + self.has_vertical, + "Forlorn Temple - Demon King Shop -> Forlorn Temple - Saw Gauntlet Shop": + self.has_vertical, + # Howling Grotto + "Howling Grotto - Portal -> Howling Grotto - Crushing Pits Shop": + self.has_wingsuit, + "Howling Grotto - Wingsuit Shop -> Howling Grotto - Left": + self.has_wingsuit, + "Howling Grotto - Wingsuit Shop -> Howling Grotto - Lost Woods Checkpoint": + self.has_wingsuit, + "Howling Grotto - Lost Woods Checkpoint -> Howling Grotto - Bottom": + lambda state: state.has("Seashell", self.player), + "Howling Grotto - Crushing Pits Shop -> Howling Grotto - Portal": + lambda state: self.has_wingsuit(state) or self.can_dboost(state), + "Howling Grotto - Breezy Crushers Checkpoint -> Howling Grotto - Emerald Golem Shop": + self.has_wingsuit, + "Howling Grotto - Breezy Crushers Checkpoint -> Howling Grotto - Crushing Pits Shop": + lambda state: (self.has_wingsuit(state) or self.can_dboost( + state + ) or self.can_destroy_projectiles(state)) + and state.multiworld.get_region( + "Howling Grotto - Emerald Golem Shop", self.player + ).can_reach(state), + "Howling Grotto - Emerald Golem Shop -> Howling Grotto - Right": + self.has_wingsuit, + # Searing Crags + "Searing Crags - Rope Dart Shop -> Searing Crags - Triple Ball Spinner Checkpoint": + self.has_vertical, + "Searing Crags - Portal -> Searing Crags - Right": + self.has_tabi, + "Searing Crags - Portal -> Searing Crags - Before Final Climb Shop": + self.has_wingsuit, + "Searing Crags - Portal -> Searing Crags - Colossuses Shop": + self.has_wingsuit, + "Searing Crags - Bottom -> Searing Crags - Portal": + self.has_wingsuit, + "Searing Crags - Right -> Searing Crags - Portal": + lambda state: self.has_tabi(state) and self.has_wingsuit(state), + "Searing Crags - Colossuses Shop -> Searing Crags - Key of Strength Shop": + lambda state: state.has("Power Thistle", self.player) + and (self.has_dart(state) + or (self.has_wingsuit(state) + and self.can_destroy_projectiles(state))), + "Searing Crags - Falling Rocks Shop -> Searing Crags - Searing Mega Shard Shop": + self.has_dart, + "Searing Crags - Searing Mega Shard Shop -> Searing Crags - Before Final Climb Shop": + lambda state: self.has_dart(state) or self.can_destroy_projectiles(state), + "Searing Crags - Searing Mega Shard Shop -> Searing Crags - Falling Rocks Shop": + self.has_dart, + "Searing Crags - Searing Mega Shard Shop -> Searing Crags - Key of Strength Shop": + self.false, + "Searing Crags - Before Final Climb Shop -> Searing Crags - Colossuses Shop": + self.has_dart, + # Glacial Peak + "Glacial Peak - Portal -> Glacial Peak - Tower Entrance Shop": + self.has_vertical, + "Glacial Peak - Left -> Elemental Skylands - Air Shmup": + lambda state: state.has("Magic Firefly", self.player) + and state.multiworld.get_location("Quillshroom Marsh - Queen of Quills", self.player) + .can_reach(state), + "Glacial Peak - Tower Entrance Shop -> Glacial Peak - Top": + lambda state: state.has("Ruxxtin's Amulet", self.player), + "Glacial Peak - Projectile Spike Pit Checkpoint -> Glacial Peak - Left": + lambda state: self.has_dart(state) or (self.can_dboost(state) and self.has_wingsuit(state)), + # Tower of Time + "Tower of Time - Left -> Tower of Time - Final Chance Shop": + self.has_dart, + "Tower of Time - Second Checkpoint -> Tower of Time - Third Checkpoint": + lambda state: self.has_wingsuit(state) and (self.has_dart(state) or self.can_dboost(state)), + "Tower of Time - Third Checkpoint -> Tower of Time - Fourth Checkpoint": + lambda state: self.has_wingsuit(state) or self.can_dboost(state), + "Tower of Time - Fourth Checkpoint -> Tower of Time - Fifth Checkpoint": + lambda state: self.has_wingsuit(state) and self.has_dart(state), + "Tower of Time - Fifth Checkpoint -> Tower of Time - Sixth Checkpoint": + self.has_wingsuit, + # Cloud Ruins + "Cloud Ruins - Cloud Entrance Shop -> Cloud Ruins - Spike Float Checkpoint": + self.has_wingsuit, + "Cloud Ruins - Spike Float Checkpoint -> Cloud Ruins - Cloud Entrance Shop": + lambda state: self.has_vertical(state) or self.can_dboost(state), + "Cloud Ruins - Spike Float Checkpoint -> Cloud Ruins - Pillar Glide Shop": + lambda state: self.has_vertical(state) or self.can_dboost(state), + "Cloud Ruins - Pillar Glide Shop -> Cloud Ruins - Spike Float Checkpoint": + lambda state: self.has_vertical(state) and self.can_double_dboost(state), + "Cloud Ruins - Pillar Glide Shop -> Cloud Ruins - Ghost Pit Checkpoint": + lambda state: self.has_dart(state) and self.has_wingsuit(state), + "Cloud Ruins - Pillar Glide Shop -> Cloud Ruins - Crushers' Descent Shop": + lambda state: self.has_wingsuit(state) and (self.has_dart(state) or self.can_dboost(state)), + "Cloud Ruins - Toothbrush Alley Checkpoint -> Cloud Ruins - Seeing Spikes Shop": + self.has_vertical, + "Cloud Ruins - Seeing Spikes Shop -> Cloud Ruins - Sliding Spikes Shop": + self.has_wingsuit, + "Cloud Ruins - Sliding Spikes Shop -> Cloud Ruins - Seeing Spikes Shop": + self.has_wingsuit, + "Cloud Ruins - Sliding Spikes Shop -> Cloud Ruins - Saw Pit Checkpoint": + self.has_vertical, + "Cloud Ruins - Final Flight Shop -> Cloud Ruins - Manfred's Shop": + lambda state: self.has_wingsuit(state) and self.has_dart(state), + "Cloud Ruins - Manfred's Shop -> Cloud Ruins - Final Flight Shop": + lambda state: self.has_wingsuit(state) and self.can_dboost(state), + # Underworld + "Underworld - Left -> Underworld - Left Shop": + self.has_tabi, + "Underworld - Left Shop -> Underworld - Left": + self.has_tabi, + "Underworld - Hot Dip Checkpoint -> Underworld - Lava Run Checkpoint": + self.has_tabi, + "Underworld - Fireball Wave Shop -> Underworld - Long Climb Shop": + lambda state: self.can_destroy_projectiles(state) or self.has_tabi(state) or self.has_vertical(state), + "Underworld - Long Climb Shop -> Underworld - Hot Tub Checkpoint": + lambda state: self.has_tabi(state) + and (self.can_destroy_projectiles(state) + or self.has_wingsuit(state)) + or (self.has_wingsuit(state) + and (self.has_dart(state) + or self.can_dboost(state) + or self.can_destroy_projectiles(state))), + "Underworld - Hot Tub Checkpoint -> Underworld - Long Climb Shop": + lambda state: self.has_tabi(state) + or self.can_destroy_projectiles(state) + or (self.has_dart(state) and self.has_wingsuit(state)), + # Dark Cave + "Dark Cave - Right -> Dark Cave - Left": + lambda state: state.has("Candle", self.player) and self.has_dart(state), + # Riviere Turquoise + "Riviere Turquoise - Waterfall Shop -> Riviere Turquoise - Flower Flight Checkpoint": + lambda state: self.has_dart(state) or ( + self.has_wingsuit(state) and self.can_destroy_projectiles(state)), + "Riviere Turquoise - Launch of Faith Shop -> Riviere Turquoise - Flower Flight Checkpoint": + lambda state: self.has_dart(state) and self.can_dboost(state), + "Riviere Turquoise - Flower Flight Checkpoint -> Riviere Turquoise - Waterfall Shop": + lambda state: False, + # Elemental Skylands + "Elemental Skylands - Air Intro Shop -> Elemental Skylands - Air Seal Checkpoint": + self.has_wingsuit, + "Elemental Skylands - Air Intro Shop -> Elemental Skylands - Air Generator Shop": + self.has_wingsuit, + # Sunken Shrine + "Sunken Shrine - Portal -> Sunken Shrine - Sun Path Shop": + self.has_tabi, + "Sunken Shrine - Portal -> Sunken Shrine - Moon Path Shop": + self.has_tabi, + "Sunken Shrine - Moon Path Shop -> Sunken Shrine - Waterfall Paradise Checkpoint": + self.has_tabi, + "Sunken Shrine - Waterfall Paradise Checkpoint -> Sunken Shrine - Moon Path Shop": + self.has_tabi, + "Sunken Shrine - Tabi Gauntlet Shop -> Sunken Shrine - Sun Path Shop": + lambda state: self.can_dboost(state) or self.has_dart(state), } self.location_rules = { # ninja village - "Ninja Village Seal - Tree House": self.has_dart, + "Ninja Village Seal - Tree House": + self.has_dart, + "Ninja Village - Candle": + lambda state: state.multiworld.get_location("Searing Crags - Astral Tea Leaves", self.player).can_reach( + state), # autumn hills - "Autumn Hills - Key of Hope": self.has_dart, - "Autumn Hills Seal - Spike Ball Darts": self.is_aerobatic, + "Autumn Hills Seal - Spike Ball Darts": + self.is_aerobatic, + "Autumn Hills Seal - Trip Saws": + self.has_wingsuit, + # forlorn temple + "Forlorn Temple Seal - Rocket Maze": + self.has_vertical, # bamboo creek - "Bamboo Creek - Claustro": lambda state: self.has_dart(state) or self.can_dboost(state), + "Bamboo Creek - Claustro": + lambda state: self.has_wingsuit(state) and (self.has_dart(state) or self.can_dboost(state)), + "Above Entrance Mega Shard": + lambda state: self.has_dart(state) or self.can_dboost(state), + "Bamboo Creek Seal - Spike Ball Pits": + self.has_wingsuit, # howling grotto - "Howling Grotto Seal - Windy Saws and Balls": self.has_wingsuit, - "Howling Grotto Seal - Crushing Pits": lambda state: self.has_wingsuit(state) and self.has_dart(state), - "Howling Grotto - Emerald Golem": self.has_wingsuit, + "Howling Grotto Seal - Windy Saws and Balls": + self.has_wingsuit, + "Howling Grotto Seal - Crushing Pits": + lambda state: self.has_wingsuit(state) and self.has_dart(state), + "Howling Grotto - Emerald Golem": + self.has_wingsuit, # searing crags - "Searing Crags Seal - Triple Ball Spinner": self.has_vertical, "Searing Crags - Astral Tea Leaves": - lambda state: state.can_reach("Ninja Village - Astral Seed", "Location", self.player), - "Searing Crags - Key of Strength": lambda state: state.has("Power Thistle", self.player) - and (self.has_dart(state) - or (self.has_wingsuit(state) - and self.can_destroy_projectiles(state))), + lambda state: state.multiworld.get_location("Ninja Village - Astral Seed", self.player).can_reach(state), + "Searing Crags Seal - Triple Ball Spinner": + self.can_dboost, + "Searing Crags - Pyro": + self.has_tabi, # glacial peak - "Glacial Peak Seal - Ice Climbers": self.has_dart, - "Glacial Peak Seal - Projectile Spike Pit": self.can_destroy_projectiles, - # cloud ruins - "Cloud Ruins Seal - Ghost Pit": self.has_dart, + "Glacial Peak Seal - Ice Climbers": + self.has_dart, + "Glacial Peak Seal - Projectile Spike Pit": + self.can_destroy_projectiles, # tower of time - "Tower of Time Seal - Time Waster": self.has_dart, - "Tower of Time Seal - Lantern Climb": lambda state: self.has_wingsuit(state) and self.has_dart(state), - "Tower of Time Seal - Arcane Orbs": lambda state: self.has_wingsuit(state) and self.has_dart(state), + "Tower of Time Seal - Time Waster": + self.has_dart, + # cloud ruins + "Time Warp Mega Shard": + lambda state: self.has_vertical(state) or self.can_dboost(state), + "Cloud Ruins Seal - Ghost Pit": + self.has_vertical, + "Cloud Ruins Seal - Toothbrush Alley": + self.has_dart, + "Cloud Ruins Seal - Saw Pit": + self.has_vertical, # underworld - "Underworld Seal - Sharp and Windy Climb": self.has_wingsuit, - "Underworld Seal - Fireball Wave": self.is_aerobatic, - "Underworld Seal - Rising Fanta": self.has_dart, + "Underworld Seal - Sharp and Windy Climb": + self.has_wingsuit, + "Underworld Seal - Fireball Wave": + self.is_aerobatic, + "Underworld Seal - Rising Fanta": + self.has_dart, + "Hot Tub Mega Shard": + lambda state: self.has_tabi(state) or self.has_dart(state), # sunken shrine - "Sunken Shrine - Sun Crest": self.has_tabi, - "Sunken Shrine - Moon Crest": self.has_tabi, - "Sunken Shrine - Key of Love": lambda state: state.has_all({"Sun Crest", "Moon Crest"}, self.player), - "Sunken Shrine Seal - Waterfall Paradise": self.has_tabi, - "Sunken Shrine Seal - Tabi Gauntlet": self.has_tabi, - "Mega Shard of the Moon": self.has_tabi, - "Mega Shard of the Sun": self.has_tabi, + "Sunken Shrine - Key of Love": + lambda state: state.has_all({"Sun Crest", "Moon Crest"}, self.player), + "Sunken Shrine Seal - Waterfall Paradise": + self.has_tabi, + "Sunken Shrine Seal - Tabi Gauntlet": + self.has_tabi, + "Mega Shard of the Sun": + self.has_tabi, # riviere turquoise - "Riviere Turquoise Seal - Bounces and Balls": self.can_dboost, - "Riviere Turquoise Seal - Launch of Faith": lambda state: self.can_dboost(state) or self.has_dart(state), + "Riviere Turquoise Seal - Bounces and Balls": + self.can_dboost, + "Riviere Turquoise Seal - Launch of Faith": + lambda state: self.has_vertical(state), # elemental skylands - "Elemental Skylands - Key of Symbiosis": self.has_dart, - "Elemental Skylands Seal - Air": self.has_wingsuit, - "Elemental Skylands Seal - Water": lambda state: self.has_dart(state) and - state.has("Currents Master", self.player), - "Elemental Skylands Seal - Fire": lambda state: self.has_dart(state) and self.can_destroy_projectiles(state), - "Earth Mega Shard": self.has_dart, - "Water Mega Shard": self.has_dart, - # corrupted future - "Corrupted Future - Key of Courage": lambda state: state.has_all({"Demon King Crown", "Magic Firefly"}, - self.player), - # tower hq - "Money Wrench": self.can_shop, + "Elemental Skylands - Key of Symbiosis": + self.has_dart, + "Elemental Skylands Seal - Air": + self.has_wingsuit, + "Elemental Skylands Seal - Water": + lambda state: self.has_dart(state) and state.has("Currents Master", self.player), + "Elemental Skylands Seal - Fire": + lambda state: self.has_dart(state) and self.can_destroy_projectiles(state) and self.is_aerobatic(state), + "Earth Mega Shard": + self.has_dart, + "Water Mega Shard": + self.has_dart, } def has_wingsuit(self, state: CollectionState) -> bool: @@ -128,6 +332,9 @@ class MessengerRules: return state.has_any({"Path of Resilience", "Meditation"}, self.player) and \ state.has("Second Wind", self.player) + def can_double_dboost(self, state: CollectionState) -> bool: + return state.has_all({"Path of Resilience", "Meditation", "Second Wind"}, self.player) + def is_aerobatic(self, state: CollectionState) -> bool: return self.has_wingsuit(state) and state.has("Aerobatics Warrior", self.player) @@ -135,87 +342,147 @@ class MessengerRules: """I know this is stupid, but it's easier to read in the dicts.""" return True + def false(self, state: CollectionState) -> bool: + """It's a bit easier to just always create the connections that are only possible in hard or higher logic.""" + return False + def can_shop(self, state: CollectionState) -> bool: return state.has("Shards", self.player, self.maximum_price) def set_messenger_rules(self) -> None: multiworld = self.world.multiworld - for region in multiworld.get_regions(self.player): - if region.name in self.region_rules: - for entrance in region.entrances: - entrance.access_rule = self.region_rules[region.name] - for loc in region.locations: - if loc.name in self.location_rules: - loc.access_rule = self.location_rules[loc.name] + for entrance_name, rule in self.connection_rules.items(): + entrance = multiworld.get_entrance(entrance_name, self.player) + entrance.access_rule = rule + for loc in multiworld.get_locations(self.player): + if loc.name in self.location_rules: + loc.access_rule = self.location_rules[loc.name] - multiworld.completion_condition[self.player] = lambda state: state.has("Rescue Phantom", self.player) - if multiworld.accessibility[self.player]: # not locations accessibility + if self.world.options.music_box and not self.world.options.limited_movement: + add_rule(multiworld.get_entrance("Shrink Down", self.player), self.has_dart) + multiworld.completion_condition[self.player] = lambda state: state.has("Do the Thing!", self.player) + if self.world.options.accessibility: # not locations accessibility set_self_locking_items(self.world, self.player) class MessengerHardRules(MessengerRules): - extra_rules: Dict[str, CollectionRule] - def __init__(self, world: "MessengerWorld") -> None: super().__init__(world) - self.region_rules.update({ - "Ninja Village": self.has_vertical, - "Autumn Hills": self.has_vertical, - "Catacombs": self.has_vertical, - "Bamboo Creek": self.has_vertical, - "Riviere Turquoise": self.true, - "Forlorn Temple": lambda state: self.has_vertical(state) and state.has_all(PHOBEKINS, self.player), - "Searing Crags Upper": lambda state: self.can_destroy_projectiles(state) or self.has_windmill(state) - or self.has_vertical(state), - "Glacial Peak": lambda state: self.can_destroy_projectiles(state) or self.has_windmill(state) - or self.has_vertical(state), - "Elemental Skylands": lambda state: state.has("Magic Firefly", self.player) or - self.has_windmill(state) or - self.has_dart(state), - }) + self.connection_rules.update( + { + # Autumn Hills + "Autumn Hills - Portal -> Autumn Hills - Dimension Climb Shop": + self.has_dart, + "Autumn Hills - Climbing Claws Shop -> Autumn Hills - Key of Hope Checkpoint": + self.true, # super easy normal clip - also possible with moderately difficult cloud stepping + # Howling Grotto + "Howling Grotto - Portal -> Howling Grotto - Crushing Pits Shop": + self.true, + "Howling Grotto - Lost Woods Checkpoint -> Howling Grotto - Bottom": + self.true, # just memorize the pattern :) + "Howling Grotto - Crushing Pits Shop -> Howling Grotto - Portal": + self.true, + "Howling Grotto - Breezy Crushers Checkpoint -> Howling Grotto - Emerald Golem Shop": + lambda state: self.has_wingsuit(state) or # there's a very easy normal clip here but it's 16-bit only + "Howling Grotto - Breezy Crushers Checkpoint" in self.world.spoiler_portal_mapping.values(), + # Searing Crags + "Searing Crags - Rope Dart Shop -> Searing Crags - Triple Ball Spinner Checkpoint": + lambda state: self.has_vertical(state) or self.can_destroy_projectiles(state), + # it's doable without anything but one jump is pretty hard and time warping is no longer reliable + "Searing Crags - Falling Rocks Shop -> Searing Crags - Searing Mega Shard Shop": + lambda state: self.has_vertical(state) or self.can_destroy_projectiles(state), + "Searing Crags - Searing Mega Shard Shop -> Searing Crags - Falling Rocks Shop": + lambda state: self.has_dart(state) or + (self.can_destroy_projectiles(state) and + (self.has_wingsuit(state) or self.can_dboost(state))), + "Searing Crags - Searing Mega Shard Shop -> Searing Crags - Key of Strength Shop": + lambda state: self.can_leash(state) or self.has_windmill(state), + "Searing Crags - Before Final Climb Shop -> Searing Crags - Colossuses Shop": + self.true, + # Glacial Peak + "Glacial Peak - Left -> Elemental Skylands - Air Shmup": + lambda state: self.has_windmill(state) or + (state.has("Magic Firefly", self.player) and + state.multiworld.get_location( + "Quillshroom Marsh - Queen of Quills", self.player).can_reach(state)) or + (self.has_dart(state) and self.can_dboost(state)), + "Glacial Peak - Projectile Spike Pit Checkpoint -> Glacial Peak - Left": + lambda state: self.has_vertical(state) or self.has_windmill(state), + # Cloud Ruins + "Cloud Ruins - Sliding Spikes Shop -> Cloud Ruins - Saw Pit Checkpoint": + self.true, + # Elemental Skylands + "Elemental Skylands - Air Intro Shop -> Elemental Skylands - Air Generator Shop": + self.true, + # Riviere Turquoise + "Riviere Turquoise - Waterfall Shop -> Riviere Turquoise - Flower Flight Checkpoint": + self.true, + "Riviere Turquoise - Launch of Faith Shop -> Riviere Turquoise - Flower Flight Checkpoint": + self.can_dboost, + "Riviere Turquoise - Flower Flight Checkpoint -> Riviere Turquoise - Waterfall Shop": + self.can_double_dboost, + } + ) - self.location_rules.update({ - "Howling Grotto Seal - Windy Saws and Balls": self.true, - "Searing Crags Seal - Triple Ball Spinner": self.true, - "Searing Crags Seal - Raining Rocks": lambda state: self.has_vertical(state) or self.can_destroy_projectiles(state), - "Searing Crags Seal - Rhythm Rocks": lambda state: self.has_vertical(state) or self.can_destroy_projectiles(state), - "Searing Crags - Power Thistle": lambda state: self.has_vertical(state) or self.can_destroy_projectiles(state), - "Glacial Peak Seal - Ice Climbers": lambda state: self.has_vertical(state) or self.can_dboost(state), - "Glacial Peak Seal - Projectile Spike Pit": self.true, - "Glacial Peak Seal - Glacial Air Swag": lambda state: self.has_windmill(state) or self.has_vertical(state), - "Glacial Peak Mega Shard": lambda state: self.has_windmill(state) or self.has_vertical(state), - "Cloud Ruins Seal - Ghost Pit": self.true, - "Bamboo Creek - Claustro": self.has_wingsuit, - "Tower of Time Seal - Lantern Climb": self.has_wingsuit, - "Elemental Skylands Seal - Water": lambda state: self.has_dart(state) or self.can_dboost(state) - or self.has_windmill(state), - "Elemental Skylands Seal - Fire": lambda state: (self.has_dart(state) or self.can_dboost(state) - or self.has_windmill(state)) and - self.can_destroy_projectiles(state), - "Earth Mega Shard": lambda state: self.has_dart(state) or self.can_dboost(state) or self.has_windmill(state), - "Water Mega Shard": lambda state: self.has_dart(state) or self.can_dboost(state) or self.has_windmill(state), - }) - - self.extra_rules = { - "Searing Crags - Key of Strength": lambda state: self.has_dart(state) or self.has_windmill(state), - "Elemental Skylands - Key of Symbiosis": lambda state: self.has_windmill(state) or self.can_dboost(state), - "Autumn Hills Seal - Spike Ball Darts": lambda state: self.has_dart(state) or self.has_windmill(state), - "Underworld Seal - Fireball Wave": self.has_windmill, - } + self.location_rules.update( + { + "Autumn Hills Seal - Spike Ball Darts": + lambda state: self.has_vertical(state) and self.has_windmill(state) or self.is_aerobatic(state), + "Bamboo Creek - Claustro": + self.has_wingsuit, + "Bamboo Creek Seal - Spike Ball Pits": + self.true, + "Howling Grotto Seal - Windy Saws and Balls": + self.true, + "Searing Crags Seal - Triple Ball Spinner": + self.true, + "Glacial Peak Seal - Ice Climbers": + lambda state: self.has_vertical(state) or self.can_dboost(state), + "Glacial Peak Seal - Projectile Spike Pit": + lambda state: self.can_dboost(state) or self.can_destroy_projectiles(state), + "Glacial Peak Seal - Glacial Air Swag": + lambda state: self.has_windmill(state) or self.has_vertical(state), + "Glacial Peak Mega Shard": + lambda state: self.has_windmill(state) or self.has_vertical(state), + "Cloud Ruins Seal - Ghost Pit": + self.true, + "Cloud Ruins Seal - Toothbrush Alley": + self.true, + "Cloud Ruins Seal - Saw Pit": + self.true, + "Underworld Seal - Fireball Wave": + lambda state: self.is_aerobatic(state) or self.has_windmill(state), + "Riviere Turquoise Seal - Bounces and Balls": + self.true, + "Riviere Turquoise Seal - Launch of Faith": + lambda state: self.can_dboost(state) or self.has_vertical(state), + "Elemental Skylands - Key of Symbiosis": + lambda state: self.has_dart(state) or self.can_dboost(state) or self.has_windmill(state), + "Elemental Skylands Seal - Water": + lambda state: self.has_dart(state) or self.can_dboost(state) or self.has_windmill(state), + "Elemental Skylands Seal - Fire": + lambda state: (self.has_dart(state) or self.can_dboost(state) or self.has_windmill(state)) + and self.can_destroy_projectiles(state), + "Earth Mega Shard": + lambda state: self.has_dart(state) or self.can_dboost(state) or self.has_windmill(state), + "Water Mega Shard": + lambda state: self.has_dart(state) or self.can_dboost(state) or self.has_windmill(state), + } + ) def has_windmill(self, state: CollectionState) -> bool: return state.has("Windmill Shuriken", self.player) - def set_messenger_rules(self) -> None: - super().set_messenger_rules() - for loc, rule in self.extra_rules.items(): - if not self.world.options.shuffle_seals and "Seal" in loc: - continue - if not self.world.options.shuffle_shards and "Shard" in loc: - continue - add_rule(self.world.multiworld.get_location(loc, self.player), rule, "or") + def can_dboost(self, state: CollectionState) -> bool: + return state.has("Second Wind", self.player) # who really needs meditation + + def can_destroy_projectiles(self, state: CollectionState) -> bool: + return super().can_destroy_projectiles(state) or self.has_windmill(state) + + def can_leash(self, state: CollectionState) -> bool: + return self.has_dart(state) and self.can_dboost(state) class MessengerOOBRules(MessengerRules): @@ -226,7 +493,9 @@ class MessengerOOBRules(MessengerRules): self.required_seals = max(1, world.required_seals) self.region_rules = { "Elemental Skylands": - lambda state: state.has_any({"Windmill Shuriken", "Wingsuit", "Rope Dart", "Magic Firefly"}, self.player), + lambda state: state.has_any( + {"Windmill Shuriken", "Wingsuit", "Rope Dart", "Magic Firefly"}, self.player + ), "Music Box": lambda state: state.has_all(set(NOTES), self.player) or self.has_enough_seals(state), } @@ -240,8 +509,10 @@ class MessengerOOBRules(MessengerRules): lambda state: state.has_all({"Demon King Crown", "Magic Firefly"}, self.player), "Autumn Hills Seal - Spike Ball Darts": self.has_dart, "Ninja Village Seal - Tree House": self.has_dart, - "Underworld Seal - Fireball Wave": lambda state: state.has_any({"Wingsuit", "Windmill Shuriken"}, - self.player), + "Underworld Seal - Fireball Wave": lambda state: state.has_any( + {"Wingsuit", "Windmill Shuriken"}, + self.player + ), "Tower of Time Seal - Time Waster": self.has_dart, } @@ -251,18 +522,8 @@ class MessengerOOBRules(MessengerRules): def set_self_locking_items(world: "MessengerWorld", player: int) -> None: - multiworld = world.multiworld - - # do the ones for seal shuffle on and off first - allow_self_locking_items(multiworld.get_location("Searing Crags - Key of Strength", player), "Power Thistle") - allow_self_locking_items(multiworld.get_location("Sunken Shrine - Key of Love", player), "Sun Crest", "Moon Crest") - allow_self_locking_items(multiworld.get_location("Corrupted Future - Key of Courage", player), "Demon King Crown") - - # add these locations when seals are shuffled - if world.options.shuffle_seals: - allow_self_locking_items(multiworld.get_location("Elemental Skylands Seal - Water", player), "Currents Master") - # add these locations when seals and shards aren't shuffled - elif not world.options.shuffle_shards: - for entrance in multiworld.get_region("Cloud Ruins", player).entrances: - entrance.access_rule = lambda state: state.has("Wingsuit", player) or state.has("Rope Dart", player) - allow_self_locking_items(multiworld.get_region("Forlorn Temple", player), *PHOBEKINS) + # locations where these placements are always valid + allow_self_locking_items(world.get_location("Searing Crags - Key of Strength"), "Power Thistle") + allow_self_locking_items(world.get_location("Sunken Shrine - Key of Love"), "Sun Crest", "Moon Crest") + allow_self_locking_items(world.get_location("Corrupted Future - Key of Courage"), "Demon King Crown") + allow_self_locking_items(world.get_location("Elemental Skylands Seal - Water"), "Currents Master") diff --git a/worlds/messenger/subclasses.py b/worlds/messenger/subclasses.py index b6a0b80b21..b60aeb179f 100644 --- a/worlds/messenger/subclasses.py +++ b/worlds/messenger/subclasses.py @@ -1,36 +1,48 @@ from functools import cached_property -from typing import Optional, TYPE_CHECKING, cast +from typing import Optional, TYPE_CHECKING -from BaseClasses import CollectionState, Item, ItemClassification, Location, Region -from .constants import NOTES, PHOBEKINS, PROG_ITEMS, USEFUL_ITEMS -from .regions import MEGA_SHARDS, REGIONS, SEALS -from .shop import FIGURINES, PROG_SHOP_ITEMS, SHOP_ITEMS, USEFUL_SHOP_ITEMS +from BaseClasses import CollectionState, Entrance, Item, ItemClassification, Location, Region +from .regions import LOCATIONS, MEGA_SHARDS +from .shop import FIGURINES, SHOP_ITEMS if TYPE_CHECKING: from . import MessengerWorld +class MessengerEntrance(Entrance): + world: Optional["MessengerWorld"] = None + + class MessengerRegion(Region): - - def __init__(self, name: str, world: "MessengerWorld") -> None: + parent: str + entrance_type = MessengerEntrance + + def __init__(self, name: str, world: "MessengerWorld", parent: Optional[str] = None) -> None: super().__init__(name, world.player, world.multiworld) - locations = [loc for loc in REGIONS[self.name]] - if self.name == "The Shop": + self.parent = parent + locations = [] + if name in LOCATIONS: + locations = [loc for loc in LOCATIONS[name]] + # portal event locations since portals can be opened from their exit regions + if name.endswith("Portal"): + locations.append(name.replace(" -", "")) + + if name == "The Shop": shop_locations = {f"The Shop - {shop_loc}": world.location_name_to_id[f"The Shop - {shop_loc}"] for shop_loc in SHOP_ITEMS} self.add_locations(shop_locations, MessengerShopLocation) - elif self.name == "The Craftsman's Corner": + elif name == "The Craftsman's Corner": self.add_locations({figurine: world.location_name_to_id[figurine] for figurine in FIGURINES}, MessengerLocation) - elif self.name == "Tower HQ": + elif name == "Tower HQ": locations.append("Money Wrench") - if world.options.shuffle_seals and self.name in SEALS: - locations += [seal_loc for seal_loc in SEALS[self.name]] - if world.options.shuffle_shards and self.name in MEGA_SHARDS: - locations += [shard for shard in MEGA_SHARDS[self.name]] + + if world.options.shuffle_shards and name in MEGA_SHARDS: + locations += MEGA_SHARDS[name] loc_dict = {loc: world.location_name_to_id.get(loc, None) for loc in locations} self.add_locations(loc_dict, MessengerLocation) - world.multiworld.regions.append(self) + + self.multiworld.regions.append(self) class MessengerLocation(Location): @@ -39,46 +51,36 @@ class MessengerLocation(Location): def __init__(self, player: int, name: str, loc_id: Optional[int], parent: MessengerRegion) -> None: super().__init__(player, name, loc_id, parent) if loc_id is None: - self.place_locked_item(MessengerItem(name, parent.player, None)) + if name == "Rescue Phantom": + name = "Do the Thing!" + self.place_locked_item(MessengerItem(name, ItemClassification.progression, None, parent.player)) class MessengerShopLocation(MessengerLocation): @cached_property def cost(self) -> int: name = self.name.replace("The Shop - ", "") # TODO use `remove_prefix` when 3.8 finally gets dropped - world = cast("MessengerWorld", self.parent_region.multiworld.worlds[self.player]) + world = self.parent_region.multiworld.worlds[self.player] shop_data = SHOP_ITEMS[name] if shop_data.prerequisite: prereq_cost = 0 if isinstance(shop_data.prerequisite, set): for prereq in shop_data.prerequisite: - prereq_cost +=\ - cast(MessengerShopLocation, - world.multiworld.get_location(prereq, self.player)).cost + loc = world.multiworld.get_location(prereq, self.player) + assert isinstance(loc, MessengerShopLocation) + prereq_cost += loc.cost else: - prereq_cost +=\ - cast(MessengerShopLocation, - world.multiworld.get_location(shop_data.prerequisite, self.player)).cost + loc = world.multiworld.get_location(shop_data.prerequisite, self.player) + assert isinstance(loc, MessengerShopLocation) + prereq_cost += loc.cost return world.shop_prices[name] + prereq_cost return world.shop_prices[name] def access_rule(self, state: CollectionState) -> bool: - world = cast("MessengerWorld", state.multiworld.worlds[self.player]) + world = state.multiworld.worlds[self.player] can_afford = state.has("Shards", self.player, min(self.cost, world.total_shards)) return can_afford class MessengerItem(Item): game = "The Messenger" - - def __init__(self, name: str, player: int, item_id: Optional[int] = None, override_progression: bool = False, - count: int = 0) -> None: - if count: - item_class = ItemClassification.progression_skip_balancing - elif item_id is None or override_progression or name in {*NOTES, *PROG_ITEMS, *PHOBEKINS, *PROG_SHOP_ITEMS}: - item_class = ItemClassification.progression - elif name in {*USEFUL_ITEMS, *USEFUL_SHOP_ITEMS}: - item_class = ItemClassification.useful - else: - item_class = ItemClassification.filler - super().__init__(name, item_class, item_id, player) diff --git a/worlds/messenger/test/__init__.py b/worlds/messenger/test/__init__.py index f3fcd4ae2d..83bb248d64 100644 --- a/worlds/messenger/test/__init__.py +++ b/worlds/messenger/test/__init__.py @@ -1,4 +1,4 @@ -from test.TestBase import WorldTestBase +from test.bases import WorldTestBase from .. import MessengerWorld diff --git a/worlds/messenger/test/test_access.py b/worlds/messenger/test/test_access.py index 7a77a9b066..016f3b57cd 100644 --- a/worlds/messenger/test/test_access.py +++ b/worlds/messenger/test/test_access.py @@ -22,11 +22,27 @@ class AccessTest(MessengerTestBase): def test_dart(self) -> None: """locations that hard require the Rope Dart""" locations = [ - "Ninja Village Seal - Tree House", "Autumn Hills - Key of Hope", "Howling Grotto Seal - Crushing Pits", - "Glacial Peak Seal - Ice Climbers", "Tower of Time Seal - Time Waster", "Tower of Time Seal - Lantern Climb", - "Tower of Time Seal - Arcane Orbs", "Cloud Ruins Seal - Ghost Pit", "Underworld Seal - Rising Fanta", - "Elemental Skylands - Key of Symbiosis", "Elemental Skylands Seal - Water", - "Elemental Skylands Seal - Fire", "Earth Mega Shard", "Water Mega Shard", "Rescue Phantom", + "Ninja Village Seal - Tree House", + "Autumn Hills - Key of Hope", + "Forlorn Temple - Demon King", + "Down Under Mega Shard", + "Howling Grotto Seal - Crushing Pits", + "Glacial Peak Seal - Ice Climbers", + "Tower of Time Seal - Time Waster", + "Tower of Time Seal - Lantern Climb", + "Tower of Time Seal - Arcane Orbs", + "Cloud Ruins Seal - Ghost Pit", + "Cloud Ruins Seal - Money Farm Room", + "Cloud Ruins Seal - Toothbrush Alley", + "Money Farm Room Mega Shard 1", + "Money Farm Room Mega Shard 2", + "Underworld Seal - Rising Fanta", + "Elemental Skylands - Key of Symbiosis", + "Elemental Skylands Seal - Water", + "Elemental Skylands Seal - Fire", + "Earth Mega Shard", + "Water Mega Shard", + "Rescue Phantom", ] items = [["Rope Dart"]] self.assertAccessDependency(locations, items) @@ -136,11 +152,37 @@ class AccessTest(MessengerTestBase): items = [["Demon King Crown"]] self.assertAccessDependency(locations, items) + def test_dboost(self) -> None: + """ + short for damage boosting, d-boosting is a technique in video games where the player intentionally or + unintentionally takes damage and uses the several following frames of invincibility to defeat or get past an + enemy or obstacle, most commonly used in platformers such as the Super Mario games + """ + locations = [ + "Riviere Turquoise Seal - Bounces and Balls", "Searing Crags Seal - Triple Ball Spinner", + "Forlorn Temple - Demon King", "Forlorn Temple Seal - Rocket Maze", "Forlorn Temple Seal - Rocket Sunset", + "Sunny Day Mega Shard", "Down Under Mega Shard", + ] + items = [["Path of Resilience", "Meditation", "Second Wind"]] + self.assertAccessDependency(locations, items) + + def test_currents(self) -> None: + """there's one of these but oh man look at it go""" + self.assertAccessDependency(["Elemental Skylands Seal - Water"], [["Currents Master"]]) + + def test_strike(self) -> None: + """strike is pretty cool but it doesn't block much""" + locations = [ + "Glacial Peak Seal - Projectile Spike Pit", "Elemental Skylands Seal - Fire", + ] + items = [["Strike of the Ninja"]] + self.assertAccessDependency(locations, items) + def test_goal(self) -> None: """Test some different states to verify goal requires the correct items""" - self.collect_all_but([*NOTES, "Rescue Phantom"]) + self.collect_all_but([*NOTES, "Do the Thing!"]) self.assertEqual(self.can_reach_location("Rescue Phantom"), False) - self.collect_all_but(["Key of Love", "Rescue Phantom"]) + self.collect_all_but(["Key of Love", "Do the Thing!"]) self.assertBeatable(False) self.collect_by_name(["Key of Love"]) self.assertEqual(self.can_reach_location("Rescue Phantom"), True) @@ -159,14 +201,12 @@ class ItemsAccessTest(MessengerTestBase): "Searing Crags - Key of Strength": ["Power Thistle"], "Sunken Shrine - Key of Love": ["Sun Crest", "Moon Crest"], "Corrupted Future - Key of Courage": ["Demon King Crown"], - "Cloud Ruins - Acro": ["Ruxxtin's Amulet"], - "Forlorn Temple - Demon King": PHOBEKINS } - self.multiworld.state = self.multiworld.get_all_state(True) - self.remove_by_name(location_lock_pairs.values()) + self.collect_all_but([item for items in location_lock_pairs.values() for item in items]) for loc in location_lock_pairs: for item_name in location_lock_pairs[loc]: item = self.get_item_by_name(item_name) with self.subTest("Fulfills Accessibility", location=loc, item=item_name): - self.assertTrue(self.multiworld.get_location(loc, self.player).can_fill(self.multiworld.state, item, True)) + self.assertTrue(self.multiworld.get_location(loc, self.player).can_fill(self.multiworld.state, item, + True)) diff --git a/worlds/messenger/test/test_logic.py b/worlds/messenger/test/test_logic.py index 15df89b920..c13bd5c5a0 100644 --- a/worlds/messenger/test/test_logic.py +++ b/worlds/messenger/test/test_logic.py @@ -41,7 +41,7 @@ class HardLogicTest(MessengerTestBase): # cloud ruins "Cloud Ruins - Acro", "Cloud Ruins Seal - Ghost Pit", "Cloud Ruins Seal - Toothbrush Alley", "Cloud Ruins Seal - Saw Pit", "Cloud Ruins Seal - Money Farm Room", - "Cloud Entrance Mega Shard", "Time Warp Mega Shard", "Money Farm Room Mega Shard 1", "Money Farm Room Mega Shard 2", + "Money Farm Room Mega Shard 1", "Money Farm Room Mega Shard 2", # underworld "Underworld Seal - Rising Fanta", "Underworld Seal - Sharp and Windy Climb", # elemental skylands @@ -80,18 +80,6 @@ class HardLogicTest(MessengerTestBase): self.collect(item) self.assertTrue(self.can_reach_location(special_loc)) - def test_glacial(self) -> None: - """Test Glacial Peak locations.""" - self.assertAccessDependency(["Glacial Peak Seal - Ice Climbers"], - [["Second Wind", "Meditation"], ["Rope Dart"], ["Wingsuit"]], - True) - self.assertAccessDependency(["Glacial Peak Seal - Projectile Spike Pit"], - [["Strike of the Ninja"], ["Windmill Shuriken"], ["Rope Dart"], ["Wingsuit"]], - True) - self.assertAccessDependency(["Glacial Peak Seal - Glacial Air Swag", "Glacial Peak Mega Shard"], - [["Windmill Shuriken"], ["Wingsuit"], ["Rope Dart"]], - True) - class NoLogicTest(MessengerTestBase): options = { diff --git a/worlds/messenger/test/test_notes.py b/worlds/messenger/test/test_notes.py index 46cec5f3c8..fdb1cef1df 100644 --- a/worlds/messenger/test/test_notes.py +++ b/worlds/messenger/test/test_notes.py @@ -2,29 +2,19 @@ from . import MessengerTestBase from ..constants import NOTES -class TwoNoteGoalTest(MessengerTestBase): - options = { - "notes_needed": 2, - } +class PrecollectedNotesTestBase(MessengerTestBase): + starting_notes: int = 0 + + @property + def run_default_tests(self) -> bool: + return False def test_precollected_notes(self) -> None: - self.assertEqual(self.multiworld.state.count_group("Notes", self.player), 4) - - -class FourNoteGoalTest(MessengerTestBase): - options = { - "notes_needed": 4, - } - - def test_precollected_notes(self) -> None: - self.assertEqual(self.multiworld.state.count_group("Notes", self.player), 2) - - -class DefaultGoalTest(MessengerTestBase): - def test_precollected_notes(self) -> None: - self.assertEqual(self.multiworld.state.count_group("Notes", self.player), 0) + self.assertEqual(self.multiworld.state.count_group("Notes", self.player), self.starting_notes) def test_goal(self) -> None: + if self.__class__ is not PrecollectedNotesTestBase: + return self.assertBeatable(False) self.collect_by_name(NOTES) rope_dart = self.get_item_by_name("Rope Dart") @@ -33,3 +23,17 @@ class DefaultGoalTest(MessengerTestBase): self.remove(rope_dart) self.collect_by_name("Wingsuit") self.assertBeatable(True) + + +class TwoNoteGoalTest(PrecollectedNotesTestBase): + options = { + "notes_needed": 2, + } + starting_notes = 4 + + +class FourNoteGoalTest(PrecollectedNotesTestBase): + options = { + "notes_needed": 4, + } + starting_notes = 2 diff --git a/worlds/messenger/test/test_options.py b/worlds/messenger/test/test_options.py new file mode 100644 index 0000000000..ea84af8038 --- /dev/null +++ b/worlds/messenger/test/test_options.py @@ -0,0 +1,35 @@ +from BaseClasses import CollectionState +from Fill import distribute_items_restrictive +from . import MessengerTestBase +from .. import MessengerWorld +from ..options import Logic + + +class LimitedMovementTest(MessengerTestBase): + options = { + "limited_movement": "true", + "shuffle_shards": "true", + } + + @property + def run_default_tests(self) -> bool: + # This test base fails reachability tests. Not sure if the core tests should change to support that + return False + + def test_options(self) -> None: + """Tests that options were correctly changed.""" + assert isinstance(self.multiworld.worlds[self.player], MessengerWorld) + self.assertEqual(Logic.option_hard, self.world.options.logic_level) + + +class EarlyMeditationTest(MessengerTestBase): + options = { + "early_meditation": "true", + } + + def test_option(self) -> None: + """Checks that Meditation gets placed early""" + distribute_items_restrictive(self.multiworld) + sphere1 = self.multiworld.get_reachable_locations(CollectionState(self.multiworld)) + items = [loc.item.name for loc in sphere1] + self.assertIn("Meditation", items) diff --git a/worlds/messenger/test/test_portals.py b/worlds/messenger/test/test_portals.py new file mode 100644 index 0000000000..6ebb183813 --- /dev/null +++ b/worlds/messenger/test/test_portals.py @@ -0,0 +1,33 @@ +from BaseClasses import CollectionState +from . import MessengerTestBase +from ..portals import PORTALS + + +class PortalTestBase(MessengerTestBase): + def test_portal_reqs(self) -> None: + """tests the paths to open a portal if only that portal is closed with vanilla connections.""" + # portal and requirements to reach it if it's the only closed portal + portal_requirements = { + "Autumn Hills Portal": [["Wingsuit"]], # grotto -> bamboo -> catacombs -> hills + "Riviere Turquoise Portal": [["Candle", "Wingsuit", "Rope Dart"]], # hills -> catacombs -> dark cave -> riviere + "Howling Grotto Portal": [["Wingsuit"], ["Meditation", "Second Wind"]], # crags -> quillshroom -> grotto + "Sunken Shrine Portal": [["Seashell"]], # crags -> quillshroom -> grotto -> shrine + "Searing Crags Portal": [["Wingsuit"], ["Rope Dart"]], # grotto -> quillshroom -> crags there's two separate paths + "Glacial Peak Portal": [["Wingsuit", "Second Wind", "Meditation"], ["Rope Dart"]], # grotto -> quillshroom -> crags -> peak or crags -> peak + } + + for portal in PORTALS: + name = f"{portal} Portal" + entrance_name = f"ToTHQ {name}" + with self.subTest(portal=name, entrance_name=entrance_name): + entrance = self.multiworld.get_entrance(entrance_name, self.player) + # this emulates the portal being initially closed + entrance.access_rule = lambda state: state.has(name, self.player) + for grouping in portal_requirements[name]: + test_state = CollectionState(self.multiworld) + self.assertFalse(entrance.can_reach(test_state), "reachable with nothing") + items = self.get_items_by_name(grouping) + for item in items: + test_state.collect(item) + self.assertTrue(entrance.can_reach(test_state), grouping) + entrance.access_rule = lambda state: True diff --git a/worlds/messenger/test/test_shop.py b/worlds/messenger/test/test_shop.py index ee7e82d6cd..971ff1763b 100644 --- a/worlds/messenger/test/test_shop.py +++ b/worlds/messenger/test/test_shop.py @@ -24,25 +24,6 @@ class ShopCostTest(MessengerTestBase): self.assertTrue(loc in SHOP_ITEMS) self.assertEqual(len(prices), len(SHOP_ITEMS)) - def test_dboost(self) -> None: - locations = [ - "Riviere Turquoise Seal - Bounces and Balls", - "Forlorn Temple - Demon King", "Forlorn Temple Seal - Rocket Maze", "Forlorn Temple Seal - Rocket Sunset", - "Sunny Day Mega Shard", "Down Under Mega Shard", - ] - items = [["Path of Resilience", "Meditation", "Second Wind"]] - self.assertAccessDependency(locations, items) - - def test_currents(self) -> None: - self.assertAccessDependency(["Elemental Skylands Seal - Water"], [["Currents Master"]]) - - def test_strike(self) -> None: - locations = [ - "Glacial Peak Seal - Projectile Spike Pit", "Elemental Skylands Seal - Fire", - ] - items = [["Strike of the Ninja"]] - self.assertAccessDependency(locations, items) - class ShopCostMinTest(ShopCostTest): options = { diff --git a/worlds/messenger/test/test_shop_chest.py b/worlds/messenger/test/test_shop_chest.py index f2030c63de..2ac3069726 100644 --- a/worlds/messenger/test/test_shop_chest.py +++ b/worlds/messenger/test/test_shop_chest.py @@ -4,19 +4,14 @@ from . import MessengerTestBase class AllSealsRequired(MessengerTestBase): options = { - "shuffle_seals": "false", "goal": "power_seal_hunt", } - def test_seals_shuffled(self) -> None: - """Shuffle seals should be forced on when shop chest is the goal so test it.""" - self.assertTrue(self.multiworld.shuffle_seals[self.player]) - def test_chest_access(self) -> None: """Defaults to a total of 45 power seals in the pool and required.""" with self.subTest("Access Dependency"): self.assertEqual(len([seal for seal in self.multiworld.itempool if seal.name == "Power Seal"]), - self.multiworld.total_seals[self.player]) + self.world.options.total_seals) locations = ["Rescue Phantom"] items = [["Power Seal"]] self.assertAccessDependency(locations, items) @@ -24,7 +19,7 @@ class AllSealsRequired(MessengerTestBase): self.assertEqual(self.can_reach_location("Rescue Phantom"), False) self.assertBeatable(False) - self.collect_all_but(["Power Seal", "Rescue Phantom"]) + self.collect_all_but(["Power Seal", "Do the Thing!"]) self.assertEqual(self.can_reach_location("Rescue Phantom"), False) self.assertBeatable(False) self.collect_by_name("Power Seal") @@ -40,7 +35,7 @@ class HalfSealsRequired(MessengerTestBase): def test_seals_amount(self) -> None: """Should have 45 power seals in the item pool and half that required""" - self.assertEqual(self.multiworld.total_seals[self.player], 45) + self.assertEqual(self.world.options.total_seals, 45) self.assertEqual(self.world.total_seals, 45) self.assertEqual(self.world.required_seals, 22) total_seals = [seal for seal in self.multiworld.itempool if seal.name == "Power Seal"] @@ -59,7 +54,7 @@ class ThirtyThirtySeals(MessengerTestBase): def test_seals_amount(self) -> None: """Should have 30 power seals in the pool and 33 percent of that required.""" - self.assertEqual(self.multiworld.total_seals[self.player], 30) + self.assertEqual(self.world.options.total_seals, 30) self.assertEqual(self.world.total_seals, 30) self.assertEqual(self.world.required_seals, 10) total_seals = [seal for seal in self.multiworld.itempool if seal.name == "Power Seal"] @@ -77,7 +72,7 @@ class MaxSealsNoShards(MessengerTestBase): def test_seals_amount(self) -> None: """Should set total seals to 70 since shards aren't shuffled.""" - self.assertEqual(self.multiworld.total_seals[self.player], 85) + self.assertEqual(self.world.options.total_seals, 85) self.assertEqual(self.world.total_seals, 70) @@ -90,7 +85,7 @@ class MaxSealsWithShards(MessengerTestBase): def test_seals_amount(self) -> None: """Should have 85 seals in the pool with all required and be a valid seed.""" - self.assertEqual(self.multiworld.total_seals[self.player], 85) + self.assertEqual(self.world.options.total_seals, 85) self.assertEqual(self.world.total_seals, 85) self.assertEqual(self.world.required_seals, 85) total_seals = [seal for seal in self.multiworld.itempool if seal.name == "Power Seal"] From d3019421de80b4aa44739fc836bd75b02a250067 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Mon, 11 Mar 2024 17:26:21 -0500 Subject: [PATCH 106/166] KDL3: fix invalid inno_setup components and deathlink messages (#2922) * remove component checking * fix missing deathlink messages * move reads under deathlink check --- inno_setup.iss | 8 ++++---- worlds/kdl3/Client.py | 7 +++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/inno_setup.iss b/inno_setup.iss index c1b634292f..7d089def95 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -131,10 +131,10 @@ Root: HKCR; Subkey: "{#MyAppName}l2acpatch"; ValueData: "Arc Root: HKCR; Subkey: "{#MyAppName}l2acpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}l2acpatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; -Root: HKCR; Subkey: ".apkdl3"; ValueData: "{#MyAppName}kdl3patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni -Root: HKCR; Subkey: "{#MyAppName}kdl3patch"; ValueData: "Archipelago Kirby's Dream Land 3 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni -Root: HKCR; Subkey: "{#MyAppName}kdl3patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni -Root: HKCR; Subkey: "{#MyAppName}kdl3patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: ".apkdl3"; ValueData: "{#MyAppName}kdl3patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}kdl3patch"; ValueData: "Archipelago Kirby's Dream Land 3 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}kdl3patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}kdl3patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Root: HKCR; Subkey: ".apmc"; ValueData: "{#MyAppName}mcdata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}mcdata"; ValueData: "Archipelago Minecraft Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; diff --git a/worlds/kdl3/Client.py b/worlds/kdl3/Client.py index c10dd6cebb..a1e68f8b67 100644 --- a/worlds/kdl3/Client.py +++ b/worlds/kdl3/Client.py @@ -309,10 +309,13 @@ class KDL3SNIClient(SNIClient): if current_bgm[0] in (0x00, 0x21, 0x22, 0x23, 0x25, 0x2A, 0x2B): return # null, title screen, opening, save select, true and false endings game_state = await snes_read(ctx, KDL3_GAME_STATE, 1) - current_hp = await snes_read(ctx, KDL3_KIRBY_HP, 1) if "DeathLink" in ctx.tags and game_state[0] == 0x00 and ctx.last_death_link + 1 < time.time(): + current_hp = await snes_read(ctx, KDL3_KIRBY_HP, 1) + current_world = struct.unpack("H", await snes_read(ctx, KDL3_CURRENT_WORLD, 2))[0] + current_level = struct.unpack("H", await snes_read(ctx, KDL3_CURRENT_LEVEL, 2))[0] currently_dead = current_hp[0] == 0x00 - await ctx.handle_deathlink_state(currently_dead) + message = deathlink_messages[self.levels[current_world][current_level - 1]] + await ctx.handle_deathlink_state(currently_dead, f"{ctx.player_names[ctx.slot]}{message}") recv_count = await snes_read(ctx, KDL3_RECV_COUNT, 2) recv_amount = unpack("H", recv_count)[0] From a3125cb06e4bc142161e245a32fb0f34562fab8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Bolduc?= <16137441+Jouramie@users.noreply.github.com> Date: Mon, 11 Mar 2024 19:30:14 -0400 Subject: [PATCH 107/166] Core: Fix OptionList and OptionSet to allow Iterable of Iterable (#2911) * fix, maybe * typegard for iterable of any * wow I'm so tired I just changed the method name without changing what it actually does... * also exclude bytes in is_iterable_but_str * apply pr comments * Update Utils.py Co-authored-by: Doug Hoskisson * Revert "also exclude bytes in is_iterable_but_str" This reverts commit cf087d2ee20727dbbe561c8c0f90aa85ef0a5d4b. --------- Co-authored-by: Doug Hoskisson --- Options.py | 8 ++++---- Utils.py | 11 ++++------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Options.py b/Options.py index ff8ad11c5a..f19c042f33 100644 --- a/Options.py +++ b/Options.py @@ -12,7 +12,7 @@ from dataclasses import dataclass from schema import And, Optional, Or, Schema -from Utils import get_fuzzy_results, is_iterable_of_str +from Utils import get_fuzzy_results, is_iterable_except_str if typing.TYPE_CHECKING: from BaseClasses import PlandoOptions @@ -847,7 +847,7 @@ class OptionList(Option[typing.List[typing.Any]], VerifyKeys): default: typing.Union[typing.List[typing.Any], typing.Tuple[typing.Any, ...]] = () supports_weighting = False - def __init__(self, value: typing.Iterable[str]): + def __init__(self, value: typing.Iterable[typing.Any]): self.value = list(deepcopy(value)) super(OptionList, self).__init__() @@ -857,7 +857,7 @@ class OptionList(Option[typing.List[typing.Any]], VerifyKeys): @classmethod def from_any(cls, data: typing.Any): - if is_iterable_of_str(data): + if is_iterable_except_str(data): cls.verify_keys(data) return cls(data) return cls.from_text(str(data)) @@ -883,7 +883,7 @@ class OptionSet(Option[typing.Set[str]], VerifyKeys): @classmethod def from_any(cls, data: typing.Any): - if is_iterable_of_str(data): + if is_iterable_except_str(data): cls.verify_keys(data) return cls(data) return cls.from_text(str(data)) diff --git a/Utils.py b/Utils.py index cea6405a38..3c63b42ccb 100644 --- a/Utils.py +++ b/Utils.py @@ -713,7 +713,7 @@ def messagebox(title: str, text: str, error: bool = False) -> None: import ctypes style = 0x10 if error else 0x0 return ctypes.windll.user32.MessageBoxW(0, text, title, style) - + # fall back to tk try: import tkinter @@ -969,11 +969,8 @@ class RepeatableChain: return sum(len(iterable) for iterable in self.iterable) -def is_iterable_of_str(obj: object) -> TypeGuard[typing.Iterable[str]]: - """ but not a `str` (because technically, `str` is `Iterable[str]`) """ +def is_iterable_except_str(obj: object) -> TypeGuard[typing.Iterable[typing.Any]]: + """ `str` is `Iterable`, but that's not what we want """ if isinstance(obj, str): return False - if not isinstance(obj, typing.Iterable): - return False - obj_it: typing.Iterable[object] = obj - return all(isinstance(v, str) for v in obj_it) + return isinstance(obj, typing.Iterable) From 3d56f3c096e6bb54e8532eaa2690e14340be5290 Mon Sep 17 00:00:00 2001 From: CubeSoldier <119427944+CubeSoldier@users.noreply.github.com> Date: Tue, 12 Mar 2024 00:42:56 +0100 Subject: [PATCH 108/166] Docs: Added snes9x-nwa as recommended emulator to the setup guides for SNES games (#1778) * Added snes9x-nwa as recommended emulator to the setup guides * Removed snes9x-nwa from the setup guides of DKC3 and SMW * Update worlds/alttp/docs/multiworld_en.md Co-authored-by: Aaron Wagener * Removed duplicate text Minor grammar and spelling fixes * Unified required software for SM, SMZ3 and SoE with ALTTP * Added instructions for usage of BSNES-Plus for ALTTP, SM and SMZ3 --------- Co-authored-by: Aaron Wagener --- worlds/alttp/docs/multiworld_en.md | 19 +++++++++++++---- worlds/sm/docs/multiworld_en.md | 33 +++++++++++++++++++++--------- worlds/smz3/docs/multiworld_en.md | 32 ++++++++++++++++++++--------- worlds/soe/docs/multiworld_en.md | 27 +++++++++++++++--------- 4 files changed, 77 insertions(+), 34 deletions(-) diff --git a/worlds/alttp/docs/multiworld_en.md b/worlds/alttp/docs/multiworld_en.md index 7521def36e..5d7fc43e31 100644 --- a/worlds/alttp/docs/multiworld_en.md +++ b/worlds/alttp/docs/multiworld_en.md @@ -5,11 +5,12 @@ - [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). - [SNI](https://github.com/alttpo/sni/releases). This is automatically included with your Archipelago installation above. - SNI is not compatible with (Q)Usb2Snes. -- Hardware or software capable of loading and playing SNES ROM files +- Hardware or software capable of loading and playing SNES ROM files, including: - An emulator capable of connecting to SNI - ([snes9x rr](https://github.com/gocha/snes9x-rr/releases), - [BizHawk](https://tasvideos.org/BizHawk), or - [RetroArch](https://retroarch.com?page=platforms) 1.10.1 or newer). Or, + ([snes9x-nwa](https://github.com/Skarsnik/snes9x-emunwa/releases), [snes9x-rr](https://github.com/gocha/snes9x-rr/releases), + [BSNES-plus](https://github.com/black-sliver/bsnes-plus), + [BizHawk](http://tasvideos.org/BizHawk.html), or + [RetroArch](https://retroarch.com?page=platforms) 1.10.1 or newer) - An SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), or other compatible hardware. **note: modded SNES minis are currently not supported by SNI. Some users have claimed success with QUsb2Snes for this system, but it is not supported.** @@ -47,6 +48,11 @@ client, and will also create your ROM in the same place as your patch file. When the client launched automatically, SNI should have also automatically launched in the background. If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. +#### snes9x-nwa + +1. Click on the Network Menu and check **Enable Emu Network Control** +2. Load your ROM file if it hasn't already been loaded. + ##### snes9x-rr 1. Load your ROM file if it hasn't already been loaded. @@ -58,6 +64,11 @@ first time launching, you may be prompted to allow it to communicate through the 6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install. +#### BSNES-Plus + +1. Load your ROM file if it hasn't already been loaded. +2. The emulator should automatically connect while SNI is running. + ##### BizHawk 1. Ensure you have the BSNES core loaded. This is done with the main menubar, under: diff --git a/worlds/sm/docs/multiworld_en.md b/worlds/sm/docs/multiworld_en.md index 0e82be7695..abd9f42f88 100644 --- a/worlds/sm/docs/multiworld_en.md +++ b/worlds/sm/docs/multiworld_en.md @@ -2,16 +2,18 @@ ## Required Software -- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). - - -- Hardware or software capable of loading and playing SNES ROM files - - An emulator capable of connecting to SNI such as: - - snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), - - BizHawk from: [TASVideos](https://tasvideos.org/BizHawk) - - RetroArch 1.10.1 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Or, - - An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other - compatible hardware +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). +- [SNI](https://github.com/alttpo/sni/releases). This is automatically included with your Archipelago installation above. +- SNI is not compatible with (Q)Usb2Snes. +- Hardware or software capable of loading and playing SNES ROM files, including: + - An emulator capable of connecting to SNI + ([snes9x-nwa](https://github.com/Skarsnik/snes9x-emunwa/releases), [snes9x-rr](https://github.com/gocha/snes9x-rr/releases), + [BSNES-plus](https://github.com/black-sliver/bsnes-plus), + [BizHawk](http://tasvideos.org/BizHawk.html), or + [RetroArch](https://retroarch.com?page=platforms) 1.10.1 or newer) + - An SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), or other compatible hardware. **note: + modded SNES minis are currently not supported by SNI. Some users have claimed success with QUsb2Snes for this system, + but it is not supported.** - Your legally obtained Super Metroid ROM file, probably named `Super Metroid (Japan, USA).sfc` ## Installation Procedures @@ -81,6 +83,11 @@ client, and will also create your ROM in the same place as your patch file. When the client launched automatically, SNI should have also automatically launched in the background. If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. +#### snes9x-nwa + +1. Click on the Network Menu and check **Enable Emu Network Control** +2. Load your ROM file if it hasn't already been loaded. + ##### snes9x-rr 1. Load your ROM file if it hasn't already been loaded. @@ -92,6 +99,12 @@ first time launching, you may be prompted to allow it to communicate through the 6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install. +#### BSNES-Plus + +1. Load your ROM file if it hasn't already been loaded. +2. The emulator should automatically connect while SNI is running. + + ##### BizHawk 1. Ensure you have the BSNES core loaded. This is done with the main menubar, under: diff --git a/worlds/smz3/docs/multiworld_en.md b/worlds/smz3/docs/multiworld_en.md index fadd55028f..5e226798a3 100644 --- a/worlds/smz3/docs/multiworld_en.md +++ b/worlds/smz3/docs/multiworld_en.md @@ -2,16 +2,18 @@ ## Required Software -- One of the client programs: - - [SNIClient](https://github.com/ArchipelagoMW/Archipelago/releases), included with the main - Archipelago install. -- Hardware or software capable of loading and playing SNES ROM files - - An emulator capable of connecting to SNI such as: - - snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), - - BizHawk from: [TASVideos](https://tasvideos.org/BizHawk), or - - RetroArch 1.10.3 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Or, - - An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other - compatible hardware +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). +- [SNI](https://github.com/alttpo/sni/releases). This is automatically included with your Archipelago installation above. +- SNI is not compatible with (Q)Usb2Snes. +- Hardware or software capable of loading and playing SNES ROM files, including: + - An emulator capable of connecting to SNI + ([snes9x-nwa](https://github.com/Skarsnik/snes9x-emunwa/releases), [snes9x-rr](https://github.com/gocha/snes9x-rr/releases), + [BSNES-plus](https://github.com/black-sliver/bsnes-plus), + [BizHawk](http://tasvideos.org/BizHawk.html), or + [RetroArch](https://retroarch.com?page=platforms) 1.10.1 or newer) + - An SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), or other compatible hardware. **note: + modded SNES minis are currently not supported by SNI. Some users have claimed success with QUsb2Snes for this system, + but it is not supported.** - Your legally obtained Super Metroid ROM file, probably named `Super Metroid (Japan, USA).sfc` and Your Japanese Zelda3 v1.0 ROM file, probably named `Zelda no Densetsu - Kamigami no Triforce (Japan).sfc` @@ -78,6 +80,11 @@ client, and will also create your ROM in the same place as your patch file. When the client launched automatically, SNI should have also automatically launched in the background. If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. +#### snes9x-nwa + +1. Click on the Network Menu and check **Enable Emu Network Control** +2. Load your ROM file if it hasn't already been loaded. + ##### snes9x-rr 1. Load your ROM file if it hasn't already been loaded. @@ -89,6 +96,11 @@ first time launching, you may be prompted to allow it to communicate through the 6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install. +#### BSNES-Plus + +1. Load your ROM file if it hasn't already been loaded. +2. The emulator should automatically connect while SNI is running. + ##### BizHawk 1. Ensure you have the BSNES core loaded. This is done with the main menubar, under: diff --git a/worlds/soe/docs/multiworld_en.md b/worlds/soe/docs/multiworld_en.md index 58b9aabf6a..89b1ff9fd9 100644 --- a/worlds/soe/docs/multiworld_en.md +++ b/worlds/soe/docs/multiworld_en.md @@ -2,16 +2,18 @@ ## Required Software -- SNI from: [SNI Releases Page](https://github.com/alttpo/sni/releases) - - v0.0.59 or newer (included in Archipelago 0.2.1 setup) -- Hardware or software capable of loading and playing SNES ROM files - - An emulator capable of connecting to SNI with ROM access. Any one of the following will work: - - snes9x-rr from: [snes9x-rr Releases Page](https://github.com/gocha/snes9x-rr/releases) - - BizHawk from: [TASVideos](https://tasvideos.org/BizHawk) - - bsnes-plus-nwa from: [bsnes-plus GitHub](https://github.com/black-sliver/bsnes-plus) - - RetroArch from: [RetroArch Website](https://retroarch.com?page=platforms). Or, - - Or SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other - compatible hardware. +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). +- [SNI](https://github.com/alttpo/sni/releases). This is automatically included with your Archipelago installation above. +- SNI is not compatible with (Q)Usb2Snes. +- Hardware or software capable of loading and playing SNES ROM files, including: + - An emulator capable of connecting to SNI + ([snes9x-nwa](https://github.com/Skarsnik/snes9x-emunwa/releases), [snes9x-rr](https://github.com/gocha/snes9x-rr/releases), + [BSNES-plus](https://github.com/black-sliver/bsnes-plus), + [BizHawk](http://tasvideos.org/BizHawk.html), or + [RetroArch](https://retroarch.com?page=platforms) 1.10.1 or newer) + - An SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), or other compatible hardware. **note: + modded SNES minis are currently not supported by SNI. Some users have claimed success with QUsb2Snes for this system, + but it is not supported.** - Your legally obtained Secret of Evermore US ROM file, probably named `Secret of Evermore (USA).sfc` ## Create a Config (.yaml) File @@ -63,6 +65,11 @@ page: [Evermizer apbpatch Page](https://evermizer.com/apbpatch) Start SNI either from the Archipelago install folder or the stand-alone version. If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. +#### snes9x-nwa + +1. Click on the Network Menu and check **Enable Emu Network Control** +2. Load your ROM file if it hasn't already been loaded. + ##### snes9x-rr 1. Load your ROM file if it hasn't already been loaded. From a7384b4b63baf82ed1ded7b89b39f244eb7b21f1 Mon Sep 17 00:00:00 2001 From: JaredWeakStrike <96694163+JaredWeakStrike@users.noreply.github.com> Date: Mon, 11 Mar 2024 19:52:16 -0400 Subject: [PATCH 109/166] KH2: Update all instances of multiworld.option_name to option.option_name (#2634) * update the multiworld to options * Update worlds/kh2/Rules.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> * does this work * namine sketches * wrong branch :) --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/kh2/OpenKH.py | 51 +++++++++++++++++++++--------------------- worlds/kh2/Regions.py | 10 ++++----- worlds/kh2/Rules.py | 41 ++++++++++++++++----------------- worlds/kh2/__init__.py | 26 ++++++++++----------- 4 files changed, 65 insertions(+), 63 deletions(-) diff --git a/worlds/kh2/OpenKH.py b/worlds/kh2/OpenKH.py index 6b0418c997..c30aeec67f 100644 --- a/worlds/kh2/OpenKH.py +++ b/worlds/kh2/OpenKH.py @@ -54,29 +54,30 @@ def patch_kh2(self, output_directory): formName = None levelsetting = list() - if self.multiworld.Keyblade_Minimum[self.player].value > self.multiworld.Keyblade_Maximum[self.player].value: + if self.options.Keyblade_Minimum.value > self.options.Keyblade_Maximum.value: logging.info( f"{self.multiworld.get_file_safe_player_name(self.player)} has Keyblade Minimum greater than Keyblade Maximum") - keyblademin = self.multiworld.Keyblade_Maximum[self.player].value - keyblademax = self.multiworld.Keyblade_Minimum[self.player].value + keyblademin = self.options.Keyblade_Maximum.value + keyblademax = self.options.Keyblade_Minimum.value else: - keyblademin = self.multiworld.Keyblade_Minimum[self.player].value - keyblademax = self.multiworld.Keyblade_Maximum[self.player].value + keyblademin = self.options.Keyblade_Minimum.value + keyblademax = self.options.Keyblade_Maximum.value - if self.multiworld.LevelDepth[self.player] == "level_50": + if self.options.LevelDepth == "level_50": levelsetting.extend(exclusion_table["Level50"]) - elif self.multiworld.LevelDepth[self.player] == "level_99": + elif self.options.LevelDepth == "level_99": levelsetting.extend(exclusion_table["Level99"]) - elif self.multiworld.LevelDepth[self.player] != "level_1": + elif self.options.LevelDepth != "level_1": levelsetting.extend(exclusion_table["Level50Sanity"]) - if self.multiworld.LevelDepth[self.player] == "level_99_sanity": + if self.options.LevelDepth == "level_99_sanity": levelsetting.extend(exclusion_table["Level99Sanity"]) mod_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.get_file_safe_player_name(self.player)}" all_valid_locations = {location for location, data in all_locations.items()} + for location in self.multiworld.get_filled_locations(self.player): if location.name in all_valid_locations: data = all_locations[location.name] @@ -142,11 +143,11 @@ def patch_kh2(self, output_directory): if data.locid == 2: formDict = {1: "Valor", 2: "Wisdom", 3: "Limit", 4: "Master", 5: "Final"} formDictExp = { - 1: self.multiworld.Valor_Form_EXP[self.player].value, - 2: self.multiworld.Wisdom_Form_EXP[self.player].value, - 3: self.multiworld.Limit_Form_EXP[self.player].value, - 4: self.multiworld.Master_Form_EXP[self.player].value, - 5: self.multiworld.Final_Form_EXP[self.player].value + 1: self.options.Valor_Form_EXP.value, + 2: self.options.Wisdom_Form_EXP.value, + 3: self.options.Limit_Form_EXP.value, + 4: self.options.Master_Form_EXP.value, + 5: self.options.Final_Form_EXP.value } formexp = formDictExp[data.charName] formName = formDict[data.charName] @@ -172,7 +173,7 @@ def patch_kh2(self, output_directory): for x in range(1, 7): self.formattedFmlv["Summon"].append({ "Ability": 123, - "Experience": int(formExp[0][x] / self.multiworld.Summon_EXP[self.player].value), + "Experience": int(formExp[0][x] / self.options.Summon_EXP.value), "FormId": 0, "FormLevel": x, "GrowthAbilityLevel": 0, @@ -192,7 +193,7 @@ def patch_kh2(self, output_directory): increaseStat(self.random.randint(0, 3)) itemcode = 0 self.formattedLvup["Sora"][self.i] = { - "Exp": int(soraExp[self.i] / self.multiworld.Sora_Level_EXP[self.player].value), + "Exp": int(soraExp[self.i] / self.options.Sora_Level_EXP.value), "Strength": self.strength, "Magic": self.magic, "Defense": self.defense, @@ -224,7 +225,7 @@ def patch_kh2(self, output_directory): "Unknown": 0 }) self.formattedLvup["Sora"][1] = { - "Exp": int(soraExp[1] / self.multiworld.Sora_Level_EXP[self.player].value), + "Exp": int(soraExp[1] / self.options.Sora_Level_EXP.value), "Strength": 2, "Magic": 6, "Defense": 2, @@ -379,35 +380,35 @@ def patch_kh2(self, output_directory): } lucky_emblem_text = { 0: "Your Goal is not Lucky Emblem. It is Hitlist or Three Proofs.", - 1: f"Lucky Emblem Required: {self.multiworld.LuckyEmblemsRequired[self.player]} out of {self.multiworld.LuckyEmblemsAmount[self.player]}", + 1: f"Lucky Emblem Required: {self.options.LuckyEmblemsRequired} out of {self.options.LuckyEmblemsAmount}", 2: "Your Goal is not Lucky Emblem. It is Hitlist or Three Proofs.", - 3: f"Lucky Emblem Required: {self.multiworld.LuckyEmblemsRequired[self.player]} out of {self.multiworld.LuckyEmblemsAmount[self.player]}" + 3: f"Lucky Emblem Required: {self.options.LuckyEmblemsRequired} out of {self.options.LuckyEmblemsAmount}" } hitlist_text = { 0: "Your Goal is not Hitlist. It is Lucky Emblem or Three Proofs", 1: "Your Goal is not Hitlist. It is Lucky Emblem or Three Proofs", - 2: f"Bounties Required: {self.multiworld.BountyRequired[self.player]} out of {self.multiworld.BountyAmount[self.player]}", - 3: f"Bounties Required: {self.multiworld.BountyRequired[self.player]} out of {self.multiworld.BountyAmount[self.player]}", + 2: f"Bounties Required: {self.options.BountyRequired} out of {self.options.BountyAmount}", + 3: f"Bounties Required: {self.options.BountyRequired} out of {self.options.BountyAmount}", } self.pooh_text = [ { 'id': 18326, - 'en': f"Your goal is {goal_to_text[self.multiworld.Goal[self.player].value]}" + 'en': f"Your goal is {goal_to_text[self.options.Goal.value]}" }, { 'id': 18327, - 'en': lucky_emblem_text[self.multiworld.Goal[self.player].value] + 'en': lucky_emblem_text[self.options.Goal.value] }, { 'id': 18328, - 'en': hitlist_text[self.multiworld.Goal[self.player].value] + 'en': hitlist_text[self.options.Goal.value] } ] self.level_depth_text = [ { 'id': 0x3BF1, - 'en': f"Your Level Depth is {self.multiworld.LevelDepth[self.player].current_option_name}" + 'en': f"Your Level Depth is {self.options.LevelDepth.current_option_name}" } ] mod_dir = os.path.join(output_directory, mod_name + "_" + Utils.__version__) diff --git a/worlds/kh2/Regions.py b/worlds/kh2/Regions.py index 235500ec89..7fc2ad8a87 100644 --- a/worlds/kh2/Regions.py +++ b/worlds/kh2/Regions.py @@ -935,7 +935,7 @@ def create_regions(self): for level_region_name in level_region_list: KH2REGIONS[level_region_name] = [] - if multiworld.LevelDepth[player] == "level_50": + if self.options.LevelDepth == "level_50": KH2REGIONS[RegionName.LevelsVS1] = [LocationName.Lvl2, LocationName.Lvl4, LocationName.Lvl7, LocationName.Lvl9, LocationName.Lvl10] KH2REGIONS[RegionName.LevelsVS3] = [LocationName.Lvl12, LocationName.Lvl14, LocationName.Lvl15, @@ -949,7 +949,7 @@ def create_regions(self): KH2REGIONS[RegionName.LevelsVS15] = [LocationName.Lvl50] # level 99 - elif multiworld.LevelDepth[player] == "level_99": + elif self.options.LevelDepth == "level_99": KH2REGIONS[RegionName.LevelsVS1] = [LocationName.Lvl7, LocationName.Lvl9] KH2REGIONS[RegionName.LevelsVS3] = [LocationName.Lvl12, LocationName.Lvl15, LocationName.Lvl17, LocationName.Lvl20] @@ -965,7 +965,7 @@ def create_regions(self): KH2REGIONS[RegionName.LevelsVS26] = [LocationName.Lvl99] # level sanity # has to be [] instead of {} for in - elif multiworld.LevelDepth[player] in ["level_50_sanity", "level_99_sanity"]: + elif self.options.LevelDepth in ["level_50_sanity", "level_99_sanity"]: KH2REGIONS[RegionName.LevelsVS1] = [LocationName.Lvl2, LocationName.Lvl3, LocationName.Lvl4, LocationName.Lvl5, LocationName.Lvl6, LocationName.Lvl7, LocationName.Lvl8, LocationName.Lvl9, LocationName.Lvl10] @@ -986,7 +986,7 @@ def create_regions(self): LocationName.Lvl46, LocationName.Lvl47, LocationName.Lvl48, LocationName.Lvl49, LocationName.Lvl50] # level 99 sanity - if multiworld.LevelDepth[player] == "level_99_sanity": + if self.options.LevelDepth == "level_99_sanity": KH2REGIONS[RegionName.LevelsVS15] = [LocationName.Lvl51, LocationName.Lvl52, LocationName.Lvl53, LocationName.Lvl54, LocationName.Lvl55, LocationName.Lvl56, LocationName.Lvl57, @@ -1012,7 +1012,7 @@ def create_regions(self): LocationName.Lvl95, LocationName.Lvl96, LocationName.Lvl97, LocationName.Lvl98, LocationName.Lvl99] KH2REGIONS[RegionName.Summon] = [] - if multiworld.SummonLevelLocationToggle[player]: + if self.options.SummonLevelLocationToggle: KH2REGIONS[RegionName.Summon] = [LocationName.Summonlvl2, LocationName.Summonlvl3, LocationName.Summonlvl4, diff --git a/worlds/kh2/Rules.py b/worlds/kh2/Rules.py index 1124f8109c..4370ad36b5 100644 --- a/worlds/kh2/Rules.py +++ b/worlds/kh2/Rules.py @@ -157,7 +157,7 @@ class KH2Rules: def form_list_unlock(self, state: CollectionState, parent_form_list, level_required, fight_logic=False) -> bool: form_access = {parent_form_list} - if self.multiworld.AutoFormLogic[self.player] and state.has(ItemName.SecondChance, self.player) and not fight_logic: + if self.world.options.AutoFormLogic and state.has(ItemName.SecondChance, self.player) and not fight_logic: if parent_form_list == ItemName.MasterForm: if state.has(ItemName.DriveConverter, self.player): form_access.add(auto_form_dict[parent_form_list]) @@ -170,8 +170,8 @@ class KH2Rules: forms_available = 0 form_list = [ItemName.ValorForm, ItemName.WisdomForm, ItemName.LimitForm, ItemName.MasterForm, ItemName.FinalForm] - if self.world.multiworld.FinalFormLogic[self.player] != "no_light_and_darkness": - if self.world.multiworld.FinalFormLogic[self.player] == "light_and_darkness": + if self.world.options.FinalFormLogic != "no_light_and_darkness": + if self.world.options.FinalFormLogic == "light_and_darkness": if state.has(ItemName.LightDarkness, self.player) and state.has_any(set(form_list), self.player): forms_available += 1 form_list.remove(ItemName.FinalForm) @@ -273,34 +273,35 @@ class KH2WorldRules(KH2Rules): def set_kh2_goal(self): final_xemnas_location = self.multiworld.get_location(LocationName.FinalXemnasEventLocation, self.player) - if self.multiworld.Goal[self.player] == "three_proofs": + if self.world.options.Goal == "three_proofs": final_xemnas_location.access_rule = lambda state: self.kh2_has_all(three_proofs, state) - if self.multiworld.FinalXemnas[self.player]: + if self.world.options.FinalXemnas: self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Victory, self.player, 1) else: self.multiworld.completion_condition[self.player] = lambda state: self.kh2_has_all(three_proofs, state) # lucky emblem hunt - elif self.multiworld.Goal[self.player] == "lucky_emblem_hunt": - final_xemnas_location.access_rule = lambda state: state.has(ItemName.LuckyEmblem, self.player, self.multiworld.LuckyEmblemsRequired[self.player].value) - if self.multiworld.FinalXemnas[self.player]: + elif self.world.options.Goal == "lucky_emblem_hunt": + final_xemnas_location.access_rule = lambda state: state.has(ItemName.LuckyEmblem, self.player, self.world.options.LuckyEmblemsRequired.value) + if self.world.options.FinalXemnas: self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Victory, self.player, 1) else: - self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.LuckyEmblem, self.player, self.multiworld.LuckyEmblemsRequired[self.player].value) + self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.LuckyEmblem, self.player, self.world.options.LuckyEmblemsRequired.value) # hitlist if == 2 - elif self.multiworld.Goal[self.player] == "hitlist": - final_xemnas_location.access_rule = lambda state: state.has(ItemName.Bounty, self.player, self.multiworld.BountyRequired[self.player].value) - if self.multiworld.FinalXemnas[self.player]: + elif self.world.options.Goal == "hitlist": + final_xemnas_location.access_rule = lambda state: state.has(ItemName.Bounty, self.player, self.world.options.BountyRequired.value) + if self.world.options.FinalXemnas: self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Victory, self.player, 1) else: - self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Bounty, self.player, self.multiworld.BountyRequired[self.player].value) + self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Bounty, self.player, self.world.options.BountyRequired.value) else: - final_xemnas_location.access_rule = lambda state: state.has(ItemName.Bounty, self.player, self.multiworld.BountyRequired[self.player].value) and \ - state.has(ItemName.LuckyEmblem, self.player, self.multiworld.LuckyEmblemsRequired[self.player].value) - if self.multiworld.FinalXemnas[self.player]: + + final_xemnas_location.access_rule = lambda state: state.has(ItemName.Bounty, self.player, self.world.options.BountyRequired.value) and \ + state.has(ItemName.LuckyEmblem, self.player, self.world.options.LuckyEmblemsRequired.value) + if self.world.options.FinalXemnas: self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Victory, self.player, 1) else: - self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Bounty, self.player, self.multiworld.BountyRequired[self.player].value) and \ - state.has(ItemName.LuckyEmblem, self.player, self.multiworld.LuckyEmblemsRequired[self.player].value) + self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Bounty, self.player, self.world.options.BountyRequired.value) and \ + state.has(ItemName.LuckyEmblem, self.player, self.world.options.LuckyEmblemsRequired.value) class KH2FormRules(KH2Rules): @@ -409,7 +410,7 @@ class KH2FightRules(KH2Rules): # if skip rules are of return false def __init__(self, world: KH2World) -> None: super().__init__(world) - self.fight_logic = self.multiworld.FightLogic[self.player].current_key + self.fight_logic = world.options.FightLogic.current_key self.fight_region_rules = { RegionName.ShanYu: lambda state: self.get_shan_yu_rules(state), @@ -935,7 +936,7 @@ class KH2FightRules(KH2Rules): def get_cor_skip_first_rules(self, state: CollectionState) -> bool: # if option is not allow skips return false else run rules - if not self.multiworld.CorSkipToggle[self.player]: + if not self.world.options.CorSkipToggle: return False # easy: aerial dodge 3,master form,fire # normal: aerial dodge 2, master form,fire diff --git a/worlds/kh2/__init__.py b/worlds/kh2/__init__.py index d02614d380..4125bcb24c 100644 --- a/worlds/kh2/__init__.py +++ b/worlds/kh2/__init__.py @@ -240,7 +240,7 @@ class KH2World(World): self.hitlist_verify() - prio_hitlist = [location for location in self.multiworld.priority_locations[self.player].value if + prio_hitlist = [location for location in self.options.priority_locations.value if location in self.random_super_boss_list] for bounty in range(self.options.BountyAmount.value): if prio_hitlist: @@ -261,11 +261,11 @@ class KH2World(World): if self.options.WeaponSlotStartHint: for location in all_weapon_slot: - self.multiworld.start_location_hints[self.player].value.add(location) + self.options.start_location_hints.value.add(location) if self.options.FillerItemsLocal: for item in filler_items: - self.multiworld.local_items[self.player].value.add(item) + self.options.local_items.value.add(item) # By imitating remote this doesn't have to be plandoded filler anymore # for location in {LocationName.JunkMedal, LocationName.JunkMedal}: # self.plando_locations[location] = random_stt_item @@ -325,7 +325,7 @@ class KH2World(World): self.item_quantity_dict[random_ability] -= 1 self.total_locations -= 1 self.slot_data_donald_weapon = [item_name.name for item_name in self.donald_weapon_abilities] - if not self.multiworld.DonaldGoofyStatsanity[self.player]: + if not self.options.DonaldGoofyStatsanity: # pre plando donald get bonuses self.donald_get_bonus_abilities += [self.create_item(random_prog_ability)] self.total_locations -= 1 @@ -385,7 +385,7 @@ class KH2World(World): location.place_locked_item(random_ability) self.goofy_weapon_abilities.remove(random_ability) - if not self.multiworld.DonaldGoofyStatsanity[self.player]: + if not self.options.DonaldGoofyStatsanity: # plando goofy get bonuses goofy_get_bonus_location_pool = [self.multiworld.get_location(location, self.player) for location in Goofy_Checks.keys() if Goofy_Checks[location].yml != "Keyblade"] @@ -406,7 +406,7 @@ class KH2World(World): location.place_locked_item(random_ability) self.donald_weapon_abilities.remove(random_ability) - if not self.multiworld.DonaldGoofyStatsanity[self.player]: + if not self.options.DonaldGoofyStatsanity: # plando goofy get bonuses donald_get_bonus_location_pool = [self.multiworld.get_location(location, self.player) for location in Donald_Checks.keys() if Donald_Checks[location].yml != "Keyblade"] @@ -428,7 +428,7 @@ class KH2World(World): """ Making sure the player doesn't put too many abilities in their starting inventory. """ - for item, value in self.multiworld.start_inventory[self.player].value.items(): + for item, value in self.options.start_inventory.value.items(): if item in ActionAbility_Table \ or item in SupportAbility_Table or exclusion_item_table["StatUps"] \ or item in DonaldAbility_Table or item in GoofyAbility_Table: @@ -461,7 +461,7 @@ class KH2World(World): """ Making sure hitlist have amount>=required. """ - for location in self.multiworld.exclude_locations[self.player].value: + for location in self.options.exclude_locations.value: if location in self.random_super_boss_list: self.random_super_boss_list.remove(location) @@ -491,7 +491,7 @@ class KH2World(World): self.options.BountyAmount.value = temp if self.options.BountyStartingHintToggle: - self.multiworld.start_hints[self.player].value.add(ItemName.Bounty) + self.options.start_hints.value.add(ItemName.Bounty) if ItemName.ProofofNonexistence in self.item_quantity_dict: del self.item_quantity_dict[ItemName.ProofofNonexistence] @@ -503,19 +503,19 @@ class KH2World(World): # Option to turn off all superbosses. Can do this individually but its like 20+ checks if not self.options.SuperBosses: for superboss in exclusion_table["SuperBosses"]: - self.multiworld.exclude_locations[self.player].value.add(superboss) + self.options.exclude_locations.value.add(superboss) # Option to turn off Olympus Colosseum Cups. if self.options.Cups == "no_cups": for cup in exclusion_table["Cups"]: - self.multiworld.exclude_locations[self.player].value.add(cup) + self.options.exclude_locations.value.add(cup) # exclude only hades paradox. If cups and hades paradox then nothing is excluded elif self.options.Cups == "cups": - self.multiworld.exclude_locations[self.player].value.add(LocationName.HadesCupTrophyParadoxCups) + self.options.exclude_locations.value.add(LocationName.HadesCupTrophyParadoxCups) if not self.options.AtlanticaToggle: for loc in exclusion_table["Atlantica"]: - self.multiworld.exclude_locations[self.player].value.add(loc) + self.options.exclude_locations.value.add(loc) def level_subtraction(self): """ From 6d3f7865ff238306df8cde0cf446507db5376614 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Mon, 11 Mar 2024 18:55:28 -0500 Subject: [PATCH 110/166] The Messenger: fix items accessibility reachability bug due to new rules (#2937) --- worlds/messenger/rules.py | 4 ++-- worlds/messenger/test/test_access.py | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/worlds/messenger/rules.py b/worlds/messenger/rules.py index 50e1fa113d..ff1b75d70f 100644 --- a/worlds/messenger/rules.py +++ b/worlds/messenger/rules.py @@ -523,7 +523,7 @@ class MessengerOOBRules(MessengerRules): def set_self_locking_items(world: "MessengerWorld", player: int) -> None: # locations where these placements are always valid - allow_self_locking_items(world.get_location("Searing Crags - Key of Strength"), "Power Thistle") + allow_self_locking_items(world.get_location("Searing Crags - Key of Strength").parent_region, "Power Thistle") allow_self_locking_items(world.get_location("Sunken Shrine - Key of Love"), "Sun Crest", "Moon Crest") - allow_self_locking_items(world.get_location("Corrupted Future - Key of Courage"), "Demon King Crown") + allow_self_locking_items(world.get_location("Corrupted Future - Key of Courage").parent_region, "Demon King Crown") allow_self_locking_items(world.get_location("Elemental Skylands Seal - Water"), "Currents Master") diff --git a/worlds/messenger/test/test_access.py b/worlds/messenger/test/test_access.py index 016f3b57cd..ad2265ffa0 100644 --- a/worlds/messenger/test/test_access.py +++ b/worlds/messenger/test/test_access.py @@ -1,3 +1,5 @@ +import typing + from . import MessengerTestBase from ..constants import NOTES, PHOBEKINS @@ -208,5 +210,8 @@ class ItemsAccessTest(MessengerTestBase): for item_name in location_lock_pairs[loc]: item = self.get_item_by_name(item_name) with self.subTest("Fulfills Accessibility", location=loc, item=item_name): - self.assertTrue(self.multiworld.get_location(loc, self.player).can_fill(self.multiworld.state, item, - True)) + location = self.multiworld.get_location(loc, self.player) + self.assertTrue(location.can_fill(self.multiworld.state, item, True)) + location.item = item + self.multiworld.state.update_reachable_regions(self.player) + self.assertTrue(self.can_reach_location(loc)) From cb2c00f6441793efc93209a6a3ab8d7710562c37 Mon Sep 17 00:00:00 2001 From: BadMagic100 Date: Tue, 12 Mar 2024 01:11:13 -0700 Subject: [PATCH 111/166] CI: Don't auto-remove content based labels (#2941) --- .github/workflows/label-pull-requests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label-pull-requests.yml b/.github/workflows/label-pull-requests.yml index e26f6f34a4..bc0f6999b6 100644 --- a/.github/workflows/label-pull-requests.yml +++ b/.github/workflows/label-pull-requests.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/labeler@v5 with: - sync-labels: true + sync-labels: false peer_review: name: 'Apply peer review label' needs: labeler From 51243abea1b0ad0aefe076ebdad5484175353297 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Tue, 12 Mar 2024 03:27:41 -0500 Subject: [PATCH 112/166] Docs: improve AutoWorld method docstrings (#2509) * clarify some autoworld docstrings * revert accidental change --- worlds/AutoWorld.py | 73 +++++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index 4d9b31d17d..faf14bed18 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -310,13 +310,15 @@ class World(metaclass=AutoWorldRegister): # overridable methods that get called by Main.py, sorted by execution order # can also be implemented as a classmethod and called "stage_", - # in that case the MultiWorld object is passed as an argument and it gets called once for the entire multiworld. + # in that case the MultiWorld object is passed as an argument, and it gets called once for the entire multiworld. # An example of this can be found in alttp as stage_pre_fill @classmethod def stage_assert_generate(cls, multiworld: "MultiWorld") -> None: - """Checks that a game is capable of generating, usually checks for some base file like a ROM. - This gets called once per present world type. Not run for unittests since they don't produce output""" + """ + Checks that a game is capable of generating, such as checking for some base file like a ROM. + This gets called once per present world type. Not run for unittests since they don't produce output. + """ pass def generate_early(self) -> None: @@ -361,16 +363,21 @@ class World(metaclass=AutoWorldRegister): pass def post_fill(self) -> None: - """Optional Method that is called after regular fill. Can be used to do adjustments before output generation. - This happens before progression balancing, so the items may not be in their final locations yet.""" + """ + Optional Method that is called after regular fill. Can be used to do adjustments before output generation. + This happens before progression balancing, so the items may not be in their final locations yet. + """ def generate_output(self, output_directory: str) -> None: - """This method gets called from a threadpool, do not use multiworld.random here. - If you need any last-second randomization, use self.random instead.""" + """ + This method gets called from a threadpool, do not use multiworld.random here. + If you need any last-second randomization, use self.random instead. + """ pass def fill_slot_data(self) -> Mapping[str, Any]: # json of WebHostLib.models.Slot - """What is returned from this function will be in the `slot_data` field + """ + What is returned from this function will be in the `slot_data` field in the `Connected` network package. It should be a `dict` with `str` keys, and should be serializable with json. @@ -378,15 +385,18 @@ class World(metaclass=AutoWorldRegister): The client will receive this as JSON in the `Connected` response. The generation does not wait for `generate_output` to complete before calling this. - `threading.Event` can be used if you need to wait for something from `generate_output`.""" + `threading.Event` can be used if you need to wait for something from `generate_output`. + """ # The reason for the `Mapping` type annotation, rather than `dict` # is so that type checkers won't worry about the mutability of `dict`, # so you can have more specific typing in your world implementation. return {} def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): - """Fill in additional entrance information text into locations, which is displayed when hinted. - structure is {player_id: {location_id: text}} You will need to insert your own player_id.""" + """ + Fill in additional entrance information text into locations, which is displayed when hinted. + structure is {player_id: {location_id: text}} You will need to insert your own player_id. + """ pass def modify_multidata(self, multidata: Dict[str, Any]) -> None: # TODO: TypedDict for multidata? @@ -395,13 +405,17 @@ class World(metaclass=AutoWorldRegister): # Spoiler writing is optional, these may not get called. def write_spoiler_header(self, spoiler_handle: TextIO) -> None: - """Write to the spoiler header. If individual it's right at the end of that player's options, - if as stage it's right under the common header before per-player options.""" + """ + Write to the spoiler header. If individual it's right at the end of that player's options, + if as stage it's right under the common header before per-player options. + """ pass def write_spoiler(self, spoiler_handle: TextIO) -> None: - """Write to the spoiler "middle", this is after the per-player options and before locations, - meant for useful or interesting info.""" + """ + Write to the spoiler "middle", this is after the per-player options and before locations, + meant for useful or interesting info. + """ pass def write_spoiler_end(self, spoiler_handle: TextIO) -> None: @@ -411,8 +425,10 @@ class World(metaclass=AutoWorldRegister): # end of ordered Main.py calls def create_item(self, name: str) -> "Item": - """Create an item for this world type and player. - Warning: this may be called with self.world = None, for example by MultiServer""" + """ + Create an item for this world type and player. + Warning: this may be called with self.world = None, for example by MultiServer + """ raise NotImplementedError def get_filler_item_name(self) -> str: @@ -422,8 +438,10 @@ class World(metaclass=AutoWorldRegister): @classmethod def create_group(cls, multiworld: "MultiWorld", new_player_id: int, players: Set[int]) -> World: - """Creates a group, which is an instance of World that is responsible for multiple others. - An example case is ItemLinks creating these.""" + """ + Creates a group, which is an instance of World that is responsible for multiple others. + An example case is ItemLinks creating these. + """ # TODO remove loop when worlds use options dataclass for option_key, option in cls.options_dataclass.type_hints.items(): getattr(multiworld, option_key)[new_player_id] = option.from_any(option.default) @@ -435,21 +453,27 @@ class World(metaclass=AutoWorldRegister): # decent place to implement progressive items, in most cases can stay as-is def collect_item(self, state: "CollectionState", item: "Item", remove: bool = False) -> Optional[str]: - """Collect an item name into state. For speed reasons items that aren't logically useful get skipped. + """ + Collect an item name into state. For speed reasons items that aren't logically useful get skipped. Collect None to skip item. :param state: CollectionState to collect into :param item: Item to decide on if it should be collected into state - :param remove: indicate if this is meant to remove from state instead of adding.""" + :param remove: indicate if this is meant to remove from state instead of adding. + """ if item.advancement: return item.name return None - # called to create all_state, return Items that are created during pre_fill def get_pre_fill_items(self) -> List["Item"]: + """ + Used to return items that need to be collected when creating a fresh all_state, but don't exist in the + multiworld itempool. + """ return [] # these two methods can be extended for pseudo-items on state def collect(self, state: "CollectionState", item: "Item") -> bool: + """Called when an item is collected in to state. Useful for things such as progressive items or currency.""" name = self.collect_item(state, item) if name: state.prog_items[self.player][name] += 1 @@ -457,6 +481,7 @@ class World(metaclass=AutoWorldRegister): return False def remove(self, state: "CollectionState", item: "Item") -> bool: + """Called when an item is removed from to state. Useful for things such as progressive items or currency.""" name = self.collect_item(state, item, True) if name: state.prog_items[self.player][name] -= 1 @@ -465,6 +490,7 @@ class World(metaclass=AutoWorldRegister): return True return False + # following methods should not need to be overridden. def create_filler(self) -> "Item": return self.create_item(self.get_filler_item_name()) @@ -513,7 +539,8 @@ def data_package_checksum(data: "GamesPackage") -> str: def _normalize_description(description): - """Normalizes a description in item_descriptions or location_descriptions. + """ + Normalizes a description in item_descriptions or location_descriptions. This allows authors to write descritions with nice indentation and line lengths in their world definitions without having it affect the rendered format. From 30ad2aa4a8e506728e55dbc47c8e986937e1c987 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:51:10 +0100 Subject: [PATCH 113/166] The Witness: Don't unnecessarily break people's 0.4.4 yamls (#2940) --- worlds/witness/options.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/worlds/witness/options.py b/worlds/witness/options.py index a24896e1d0..f47068d594 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -23,8 +23,11 @@ class EarlyCaves(Choice): If you choose "add_to_pool" and you are already playing a remote Door Shuffle mode, this setting will do nothing.""" display_name = "Early Caves" option_off = 0 + alias_false = 0 option_add_to_pool = 1 option_starting_inventory = 2 + alias_true = 2 + alias_on = 2 class ShuffleSymbols(DefaultOnToggle): @@ -39,8 +42,11 @@ class ShuffleLasers(Choice): be redirected as normal, for both applications of redirection.""" display_name = "Shuffle Lasers" option_off = 0 + alias_false = 0 option_local = 1 option_anywhere = 2 + alias_true = 2 + alias_on = 2 class ShuffleDoors(Choice): From c795c72471b9fe36df362c9ccce712adc0feddde Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Tue, 12 Mar 2024 14:52:57 +0100 Subject: [PATCH 114/166] kvui: allow sorting hints in the hint tab (#2684) --- data/client.kv | 6 ++++++ kvui.py | 57 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/data/client.kv b/data/client.kv index 3b48d216dd..bf98fa1517 100644 --- a/data/client.kv +++ b/data/client.kv @@ -61,36 +61,42 @@ found_text: "Found?" TooltipLabel: id: receiving + sort_key: 'receiving' text: root.receiving_text halign: 'center' valign: 'center' pos_hint: {"center_y": 0.5} TooltipLabel: id: item + sort_key: 'item' text: root.item_text halign: 'center' valign: 'center' pos_hint: {"center_y": 0.5} TooltipLabel: id: finding + sort_key: 'finding' text: root.finding_text halign: 'center' valign: 'center' pos_hint: {"center_y": 0.5} TooltipLabel: id: location + sort_key: 'location' text: root.location_text halign: 'center' valign: 'center' pos_hint: {"center_y": 0.5} TooltipLabel: id: entrance + sort_key: 'entrance' text: root.entrance_text halign: 'center' valign: 'center' pos_hint: {"center_y": 0.5} TooltipLabel: id: found + sort_key: 'found' text: root.found_text halign: 'center' valign: 'center' diff --git a/kvui.py b/kvui.py index 5e1b0fc030..1cd1c99834 100644 --- a/kvui.py +++ b/kvui.py @@ -2,6 +2,7 @@ import os import logging import sys import typing +import re if sys.platform == "win32": import ctypes @@ -72,6 +73,8 @@ if typing.TYPE_CHECKING: else: context_type = object +remove_between_brackets = re.compile(r"\[.*?]") + # I was surprised to find this didn't already exist in kivy :( class HoverBehavior(object): @@ -303,7 +306,6 @@ class HintLabel(RecycleDataViewBehavior, BoxLayout): selected = BooleanProperty(False) striped = BooleanProperty(False) index = None - no_select = [] def __init__(self): super(HintLabel, self).__init__() @@ -321,9 +323,7 @@ class HintLabel(RecycleDataViewBehavior, BoxLayout): def refresh_view_attrs(self, rv, index, data): self.index = index - if "select" in data and not data["select"] and index not in self.no_select: - self.no_select.append(index) - self.striped = data["striped"] + self.striped = data.get("striped", False) self.receiving_text = data["receiving"]["text"] self.item_text = data["item"]["text"] self.finding_text = data["finding"]["text"] @@ -337,24 +337,44 @@ class HintLabel(RecycleDataViewBehavior, BoxLayout): """ Add selection on touch down """ if super(HintLabel, self).on_touch_down(touch): return True - if self.index not in self.no_select: + if self.index: # skip header if self.collide_point(*touch.pos): if self.selected: self.parent.clear_selection() else: - text = "".join([self.receiving_text, "\'s ", self.item_text, " is at ", self.location_text, " in ", + text = "".join((self.receiving_text, "\'s ", self.item_text, " is at ", self.location_text, " in ", self.finding_text, "\'s World", (" at " + self.entrance_text) if self.entrance_text != "Vanilla" - else "", ". (", self.found_text.lower(), ")"]) + else "", ". (", self.found_text.lower(), ")")) temp = MarkupLabel(text).markup text = "".join( part for part in temp if not part.startswith(("[color", "[/color]", "[ref=", "[/ref]"))) Clipboard.copy(escape_markup(text).replace("&", "&").replace("&bl;", "[").replace("&br;", "]")) return self.parent.select_with_touch(self.index, touch) + else: + parent = self.parent + parent.clear_selection() + parent: HintLog = parent.parent + # find correct column + for child in self.children: + if child.collide_point(*touch.pos): + key = child.sort_key + parent.hint_sorter = lambda element: remove_between_brackets.sub("", element[key]["text"]).lower() + if key == parent.sort_key: + # second click reverses order + parent.reversed = not parent.reversed + else: + parent.sort_key = key + parent.reversed = False + break + else: + logging.warning("Did not find clicked header for sorting.") + + App.get_running_app().update_hints() def apply_selection(self, rv, index, is_selected): """ Respond to the selection of items in the view. """ - if self.index not in self.no_select: + if self.index: self.selected = is_selected @@ -646,20 +666,20 @@ class HintLog(RecycleView): "entrance": {"text": "[u]Entrance[/u]"}, "found": {"text": "[u]Status[/u]"}, "striped": True, - "select": False, } + sort_key: str = "" + reversed: bool = False + def __init__(self, parser): super(HintLog, self).__init__() self.data = [self.header] self.parser = parser def refresh_hints(self, hints): - self.data = [self.header] - striped = False + data = [] for hint in hints: - self.data.append({ - "striped": striped, + data.append({ "receiving": {"text": self.parser.handle_node({"type": "player_id", "text": hint["receiving_player"]})}, "item": {"text": self.parser.handle_node( {"type": "item_id", "text": hint["item"], "flags": hint["item_flags"]})}, @@ -672,7 +692,16 @@ class HintLog(RecycleView): "text": self.parser.handle_node({"type": "color", "color": "green" if hint["found"] else "red", "text": "Found" if hint["found"] else "Not Found"})}, }) - striped = not striped + + data.sort(key=self.hint_sorter, reverse=self.reversed) + for i in range(0, len(data), 2): + data[i]["striped"] = True + data.insert(0, self.header) + self.data = data + + @staticmethod + def hint_sorter(element: dict) -> str: + return "" class E(ExceptionHandler): From ae6c16bde19e0f299c60ecd8332435bee5e193e9 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Tue, 12 Mar 2024 19:58:02 +0100 Subject: [PATCH 115/166] MultiServer: send new read_hints datastore values on change (#2558) --- MultiServer.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/MultiServer.py b/MultiServer.py index c374d6d704..395577b663 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -707,15 +707,18 @@ class Context: self.save() # save goal completion flag def on_new_hint(self, team: int, slot: int): - key: str = f"_read_hints_{team}_{slot}" - targets: typing.Set[Client] = set(self.stored_data_notification_clients[key]) - if targets: - self.broadcast(targets, [{"cmd": "SetReply", "key": key, "value": self.hints[team, slot]}]) + self.on_changed_hints(team, slot) self.broadcast(self.clients[team][slot], [{ "cmd": "RoomUpdate", "hint_points": get_slot_points(self, team, slot) }]) + def on_changed_hints(self, team: int, slot: int): + key: str = f"_read_hints_{team}_{slot}" + targets: typing.Set[Client] = set(self.stored_data_notification_clients[key]) + if targets: + self.broadcast(targets, [{"cmd": "SetReply", "key": key, "value": self.hints[team, slot]}]) + def on_client_status_change(self, team: int, slot: int): key: str = f"_read_client_status_{team}_{slot}" targets: typing.Set[Client] = set(self.stored_data_notification_clients[key]) @@ -975,7 +978,10 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi "hint_points": get_slot_points(ctx, team, slot), "checked_locations": new_locations, # send back new checks only }]) - + old_hints = ctx.hints[team, slot].copy() + ctx.recheck_hints(team, slot) + if old_hints != ctx.hints[team, slot]: + ctx.on_changed_hints(team, slot) ctx.save() From 4bf676e588d3bc814baf345072524174e6b28749 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Tue, 12 Mar 2024 20:04:13 +0100 Subject: [PATCH 116/166] The Witness: Obelisk Keys (#2805) --- worlds/witness/WitnessItems.txt | 7 +++ worlds/witness/__init__.py | 61 +++++++++++++------ worlds/witness/options.py | 9 +++ worlds/witness/player_logic.py | 31 ++++++---- worlds/witness/presets.py | 6 ++ .../settings/Door_Shuffle/Obelisk_Keys.txt | 7 +++ worlds/witness/utils.py | 4 ++ 7 files changed, 94 insertions(+), 31 deletions(-) create mode 100644 worlds/witness/settings/Door_Shuffle/Obelisk_Keys.txt diff --git a/worlds/witness/WitnessItems.txt b/worlds/witness/WitnessItems.txt index 6f63eccc95..28dc4a4d97 100644 --- a/worlds/witness/WitnessItems.txt +++ b/worlds/witness/WitnessItems.txt @@ -265,6 +265,13 @@ Doors: 2165 - Caves Panels - 0x3369D,0x00FF8,0x0A16E,0x335AB,0x335AC 2170 - Tunnels Panels - 0x09E85,0x039B4 +2200 - Desert Obelisk Key - 0x0332B,0x03367,0x28B8A,0x037B6,0x037B2,0x000F7,0x3351D,0x0053C,0x00771,0x335C8,0x335C9,0x337F8,0x037BB,0x220E4,0x220E5,0x334B9,0x334BC,0x22106,0x0A14C,0x0A14D,0x00359 +2201 - Monastery Obelisk Key - 0x03ABC,0x03ABE,0x03AC0,0x03AC4,0x03AC5,0x03BE2,0x03BE3,0x0A409,0x006E5,0x006E6,0x006E7,0x034A7,0x034AD,0x034AF,0x03DAB,0x03DAC,0x03DAD,0x03E01,0x289F4,0x289F5,0x00263 +2202 - Treehouse Obelisk Key - 0x0053D,0x0053E,0x00769,0x33721,0x220A7,0x220BD,0x03B22,0x03B23,0x03B24,0x03B25,0x03A79,0x28ABD,0x28ABE,0x3388F,0x28B29,0x28B2A,0x018B6,0x033BE,0x033BF,0x033DD,0x033E5,0x28AE9,0x3348F,0x00097 +2203 - Mountainside Obelisk Key - 0x001A3,0x335AE,0x000D3,0x035F5,0x09D5D,0x09D5E,0x09D63,0x3370E,0x035DE,0x03601,0x03603,0x03D0D,0x3369A,0x336C8,0x33505,0x03A9E,0x016B2,0x3365F,0x03731,0x036CE,0x03C07,0x03A93,0x03AA6,0x3397C,0x0105D,0x0A304,0x035CB,0x035CF,0x00367 +2204 - Quarry Obelisk Key - 0x28A7B,0x005F6,0x00859,0x17CB9,0x28A4A,0x334B6,0x00614,0x0069D,0x28A4C,0x289CF,0x289D1,0x33692,0x03E77,0x03E7C,0x22073 +2205 - Town Obelisk Key - 0x035C7,0x01848,0x03D06,0x33530,0x33600,0x28A2F,0x28A37,0x334A3,0x3352F,0x33857,0x33879,0x03C19,0x28B30,0x035C9,0x03335,0x03412,0x038A6,0x038AA,0x03E3F,0x03E40,0x28B8E,0x28B91,0x03BCE,0x03BCF,0x03BD1,0x339B6,0x33A20,0x33A29,0x33A2A,0x33B06,0x0A16C + Lasers: 1500 - Symmetry Laser - 0x00509 1501 - Desert Laser - 0x012FB diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index 635a56796b..88de0f3134 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -89,6 +89,46 @@ class WitnessWorld(World): 'entity_to_name': StaticWitnessLogic.ENTITY_ID_TO_NAME, } + def determine_sufficient_progression(self): + """ + Determine whether there are enough progression items in this world to consider it "interactive". + In the case of singleplayer, this just outputs a warning. + In the case of multiplayer, the requirements are a bit stricter and an Exception is raised. + """ + + # A note on Obelisk Keys: + # Obelisk Keys are never relevant in singleplayer, because the locations they lock are irrelevant to in-game + # progress and irrelevant to all victory conditions. Thus, I consider them "fake progression" for singleplayer. + # However, those locations could obviously contain big items needed for other players, so I consider + # "Obelisk Keys only" valid for multiworld. + + # A note on Laser Shuffle: + # In singleplayer, I don't mind "Ice Rod Hunt" type gameplay, so "laser shuffle only" is valid. + # However, I do not want to allow "Ice Rod Hunt" style gameplay in multiworld, so "laser shuffle only" is + # not considered interactive enough for multiworld. + + interacts_sufficiently_with_multiworld = ( + self.options.shuffle_symbols + or self.options.shuffle_doors + or self.options.obelisk_keys and self.options.shuffle_EPs + ) + + has_locally_relevant_progression = ( + self.options.shuffle_symbols + or self.options.shuffle_doors + or self.options.shuffle_lasers + or self.options.shuffle_boat + or self.options.early_caves == "add_to_pool" and self.options.victory_condition == "challenge" + ) + + if not has_locally_relevant_progression and self.multiworld.players == 1: + warning(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have any progression" + f" items. Please turn on Symbol Shuffle, Door Shuffle or Laser Shuffle if that doesn't seem right.") + elif not interacts_sufficiently_with_multiworld and self.multiworld.players > 1: + raise Exception(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have enough" + f" progression items that can be placed in other players' worlds. Please turn on Symbol" + f" Shuffle, Door Shuffle or Obelisk Keys.") + def generate_early(self): disabled_locations = self.options.exclude_locations.value @@ -102,26 +142,9 @@ class WitnessWorld(World): ) self.regio: WitnessRegions = WitnessRegions(self.locat, self) - interacts_with_multiworld = ( - self.options.shuffle_symbols or - self.options.shuffle_doors or - self.options.shuffle_lasers == "anywhere" - ) + self.log_ids_to_hints = dict() - has_progression = ( - interacts_with_multiworld - or self.options.shuffle_lasers == "local" - or self.options.shuffle_boat - or self.options.early_caves == "add_to_pool" - ) - - if not has_progression and self.multiworld.players == 1: - warning(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have any progression" - f" items. Please turn on Symbol Shuffle, Door Shuffle or Laser Shuffle if that doesn't seem right.") - elif not interacts_with_multiworld and self.multiworld.players > 1: - raise Exception(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have enough" - f" progression items that can be placed in other players' worlds. Please turn on Symbol" - f" Shuffle, Door Shuffle or non-local Laser Shuffle.") + self.determine_sufficient_progression() if self.options.shuffle_lasers == "local": self.options.local_items.value |= self.item_name_groups["Lasers"] diff --git a/worlds/witness/options.py b/worlds/witness/options.py index f47068d594..5bce3e3a22 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -120,6 +120,14 @@ class EnvironmentalPuzzlesDifficulty(Choice): option_eclipse = 2 +class ObeliskKeys(DefaultOnToggle): + """ + Add one Obelisk Key item per Obelisk, locking you out of solving any of the associated Environmental Puzzles. + Does nothing if "Shuffle Environmental Puzzles" is set to "off". + """ + display_name = "Obelisk Keys" + + class ShufflePostgame(Toggle): """Adds locations into the pool that are guaranteed to become accessible after or at the same time as your goal. Use this if you don't play with release on victory. IMPORTANT NOTE: The possibility of your second @@ -263,6 +271,7 @@ class TheWitnessOptions(PerGameCommonOptions): disable_non_randomized_puzzles: DisableNonRandomizedPuzzles shuffle_discarded_panels: ShuffleDiscardedPanels shuffle_vault_boxes: ShuffleVaultBoxes + obelisk_keys: ObeliskKeys shuffle_EPs: ShuffleEnvironmentalPuzzles EP_difficulty: EnvironmentalPuzzlesDifficulty shuffle_postgame: ShufflePostgame diff --git a/worlds/witness/player_logic.py b/worlds/witness/player_logic.py index 229da0a287..099a3a64e6 100644 --- a/worlds/witness/player_logic.py +++ b/worlds/witness/player_logic.py @@ -63,26 +63,30 @@ class WitnessPlayerLogic: if panel_hex in self.DOOR_ITEMS_BY_ID: door_items = frozenset({frozenset([item]) for item in self.DOOR_ITEMS_BY_ID[panel_hex]}) - all_options = set() + all_options: Set[FrozenSet[str]] = set() for dependentItem in door_items: self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI.update(dependentItem) for items_option in these_items: all_options.add(items_option.union(dependentItem)) - # 0x28A0D depends on another entity for *non-power* reasons -> This dependency needs to be preserved, - # except in Expert, where that dependency doesn't exist, but now there *is* a power dependency. - # In the future, it would be wise to make a distinction between "power dependencies" and other dependencies. - if panel_hex == "0x28A0D" and not any("0x28998" in option for option in these_panels): - these_items = all_options + # If this entity is not an EP, and it has an associated door item, ignore the original power dependencies + if StaticWitnessLogic.ENTITIES_BY_HEX[panel_hex]["entityType"] != "EP": + # 0x28A0D depends on another entity for *non-power* reasons -> This dependency needs to be preserved, + # except in Expert, where that dependency doesn't exist, but now there *is* a power dependency. + # In the future, it'd be wise to make a distinction between "power dependencies" and other dependencies. + if panel_hex == "0x28A0D" and not any("0x28998" in option for option in these_panels): + these_items = all_options - # Another dependency that is not power-based: The Symmetry Island Upper Panel latches - elif panel_hex == "0x1C349": - these_items = all_options + # Another dependency that is not power-based: The Symmetry Island Upper Panel latches + elif panel_hex == "0x1C349": + these_items = all_options + + else: + return frozenset(all_options) - # For any other door entity, we just return a set with the item that opens it & disregard power dependencies else: - return frozenset(all_options) + these_items = all_options disabled_eps = {eHex for eHex in self.COMPLETELY_DISABLED_ENTITIES if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[eHex]["entityType"] == "EP"} @@ -429,6 +433,9 @@ class WitnessPlayerLogic: if lasers: adjustment_linesets_in_order.append(get_laser_shuffle()) + if world.options.shuffle_EPs and world.options.obelisk_keys: + adjustment_linesets_in_order.append(get_obelisk_keys()) + if world.options.shuffle_EPs == "obelisk_sides": ep_gen = ((ep_hex, ep_obj) for (ep_hex, ep_obj) in self.REFERENCE_LOGIC.ENTITIES_BY_HEX.items() if ep_obj["entityType"] == "EP") @@ -442,7 +449,7 @@ class WitnessPlayerLogic: adjustment_linesets_in_order.append(["Disabled Locations:"] + get_ep_obelisks()[1:]) if not world.options.shuffle_EPs: - adjustment_linesets_in_order.append(["Irrelevant Locations:"] + get_ep_all_individual()[1:]) + adjustment_linesets_in_order.append(["Disabled Locations:"] + get_ep_all_individual()[1:]) for yaml_disabled_location in self.YAML_DISABLED_LOCATIONS: if yaml_disabled_location not in self.REFERENCE_LOGIC.ENTITIES_BY_NAME: diff --git a/worlds/witness/presets.py b/worlds/witness/presets.py index 0f37fd50a3..2a53484a4c 100644 --- a/worlds/witness/presets.py +++ b/worlds/witness/presets.py @@ -14,6 +14,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "door_groupings": DoorGroupings.option_off, "shuffle_boat": True, "shuffle_lasers": ShuffleLasers.option_local, + "obelisk_keys": ObeliskKeys.option_false, "disable_non_randomized_puzzles": True, "shuffle_discarded_panels": False, @@ -35,6 +36,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "area_hint_percentage": AreaHintPercentage.default, "laser_hints": LaserHints.default, "death_link": DeathLink.default, + "death_link_amnesty": DeathLinkAmnesty.default, }, # For relative beginners who want to move to the next step. @@ -48,6 +50,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "door_groupings": DoorGroupings.option_regional, "shuffle_boat": True, "shuffle_lasers": ShuffleLasers.option_off, + "obelisk_keys": ObeliskKeys.option_false, "disable_non_randomized_puzzles": False, "shuffle_discarded_panels": True, @@ -69,6 +72,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "area_hint_percentage": AreaHintPercentage.default, "laser_hints": LaserHints.default, "death_link": DeathLink.default, + "death_link_amnesty": DeathLinkAmnesty.default, }, # Allsanity but without the BS (no expert, no tedious EPs). @@ -82,6 +86,7 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "door_groupings": DoorGroupings.option_off, "shuffle_boat": True, "shuffle_lasers": ShuffleLasers.option_anywhere, + "obelisk_keys": ObeliskKeys.option_true, "disable_non_randomized_puzzles": False, "shuffle_discarded_panels": True, @@ -103,5 +108,6 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "area_hint_percentage": AreaHintPercentage.default, "laser_hints": LaserHints.default, "death_link": DeathLink.default, + "death_link_amnesty": DeathLinkAmnesty.default, }, } diff --git a/worlds/witness/settings/Door_Shuffle/Obelisk_Keys.txt b/worlds/witness/settings/Door_Shuffle/Obelisk_Keys.txt new file mode 100644 index 0000000000..9ebcc9fa2f --- /dev/null +++ b/worlds/witness/settings/Door_Shuffle/Obelisk_Keys.txt @@ -0,0 +1,7 @@ +Items: +Desert Obelisk Key +Monastery Obelisk Key +Treehouse Obelisk Key +Mountainside Obelisk Key +Quarry Obelisk Key +Town Obelisk Key \ No newline at end of file diff --git a/worlds/witness/utils.py b/worlds/witness/utils.py index b1f1b6d831..43e039475d 100644 --- a/worlds/witness/utils.py +++ b/worlds/witness/utils.py @@ -177,6 +177,10 @@ def get_ep_obelisks() -> List[str]: return get_adjustment_file("settings/EP_Shuffle/EP_Sides.txt") +def get_obelisk_keys() -> List[str]: + return get_adjustment_file("settings/Door_Shuffle/Obelisk_Keys.txt") + + def get_ep_easy() -> List[str]: return get_adjustment_file("settings/EP_Shuffle/EP_Easy.txt") From 2692604c091a3e0d87e2535474d1f180c86da3d6 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:40:16 -0500 Subject: [PATCH 117/166] Core: String comparison with FreeText class (#2942) --- Options.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Options.py b/Options.py index f19c042f33..144220c8ee 100644 --- a/Options.py +++ b/Options.py @@ -180,6 +180,14 @@ class FreeText(Option[str]): def get_option_name(cls, value: str) -> str: return value + def __eq__(self, other): + if isinstance(other, self.__class__): + return other.value == self.value + elif isinstance(other, str): + return other == self.value + else: + raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}") + class NumericOption(Option[int], numbers.Integral, abc.ABC): default = 0 From a6e1ea8c48f7512c33951571dcc57546020f8188 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Tue, 12 Mar 2024 20:40:58 +0100 Subject: [PATCH 118/166] CommonClient: use rich text for /received (#2715) --- CommonClient.py | 15 +++++++++++---- NetUtils.py | 4 ++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CommonClient.py b/CommonClient.py index 3665b9f177..a7b7828a1d 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -20,8 +20,8 @@ if __name__ == "__main__": Utils.init_logging("TextClient", exception_logger="Client") from MultiServer import CommandProcessor -from NetUtils import Endpoint, decode, NetworkItem, encode, JSONtoTextParser, \ - ClientStatus, Permission, NetworkSlot, RawJSONtoTextParser +from NetUtils import (Endpoint, decode, NetworkItem, encode, JSONtoTextParser, ClientStatus, Permission, NetworkSlot, + RawJSONtoTextParser, add_json_text, add_json_location, add_json_item, JSONTypes) from Utils import Version, stream_input, async_start from worlds import network_data_package, AutoWorldRegister import os @@ -72,9 +72,16 @@ class ClientCommandProcessor(CommandProcessor): def _cmd_received(self) -> bool: """List all received items""" - self.output(f'{len(self.ctx.items_received)} received items:') + item: NetworkItem + self.output(f'{len(self.ctx.items_received)} received items, sorted by time:') for index, item in enumerate(self.ctx.items_received, 1): - self.output(f"{self.ctx.item_names[item.item]} from {self.ctx.player_names[item.player]}") + parts = [] + add_json_item(parts, item.item, self.ctx.slot, item.flags) + add_json_text(parts, " from ") + add_json_location(parts, item.location, item.player) + add_json_text(parts, " by ") + add_json_text(parts, item.player, type=JSONTypes.player_id) + self.ctx.on_print_json({"data": parts, "cmd": "PrintJSON"}) return True def _cmd_missing(self, filter_text = "") -> bool: diff --git a/NetUtils.py b/NetUtils.py index a2db6a2ac5..8fc3929e60 100644 --- a/NetUtils.py +++ b/NetUtils.py @@ -290,8 +290,8 @@ def add_json_item(parts: list, item_id: int, player: int = 0, item_flags: int = parts.append({"text": str(item_id), "player": player, "flags": item_flags, "type": JSONTypes.item_id, **kwargs}) -def add_json_location(parts: list, item_id: int, player: int = 0, **kwargs) -> None: - parts.append({"text": str(item_id), "player": player, "type": JSONTypes.location_id, **kwargs}) +def add_json_location(parts: list, location_id: int, player: int = 0, **kwargs) -> None: + parts.append({"text": str(location_id), "player": player, "type": JSONTypes.location_id, **kwargs}) class Hint(typing.NamedTuple): From b6b88070be542b6458220f89d7c65f1bec95fab3 Mon Sep 17 00:00:00 2001 From: Remy Jette Date: Tue, 12 Mar 2024 13:13:52 -0700 Subject: [PATCH 119/166] CommonClient: Fix item link group name when member slot name contains brackets (#2794) --- kvui.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kvui.py b/kvui.py index 1cd1c99834..dc8f4f4162 100644 --- a/kvui.py +++ b/kvui.py @@ -750,8 +750,10 @@ class KivyJSONtoTextParser(JSONtoTextParser): text = f"Game: {slot_info.game}
" \ f"Type: {SlotType(slot_info.type).name}" if slot_info.group_members: - text += f"
Members:
" + \ - "
".join(self.ctx.player_names[player] for player in slot_info.group_members) + text += f"
Members:
" + "
".join( + escape_markup(self.ctx.player_names[player]) + for player in slot_info.group_members + ) node.setdefault("refs", []).append(text) return super(KivyJSONtoTextParser, self)._handle_player_id(node) From f8d5fe0e1e5b88b6cc7fb7272a0fd679819f69b4 Mon Sep 17 00:00:00 2001 From: PoryGone <98504756+PoryGone@users.noreply.github.com> Date: Tue, 12 Mar 2024 17:00:13 -0400 Subject: [PATCH 120/166] SMW: v2.0 Content Update (#2762) Changelog: Features: - New optional Location Checks - 3-Up Moons - Hidden 1-Ups - Bonus Blocks - Blocksanity - All blocks that contain coins or items are included, with the exception of: - Blocks in Top Secret Area & Front Door/Bowser Castle - Blocks that are unreachable without glitches/unreasonable movement - New Items - Special Zone Clear - New Filler Items - 1 Coin - 5 Coins - 10 Coins - 50 Coins - New Trap Items - Reverse Trap - Thwimp Trap - SFX Shuffle - Palette Shuffle Overhaul - New Curated Palette can now be used for the Overworld and Level Palette Shuffle options - Foreground and Background Shuffle options have been merged into a single setting - Max possible Yoshi Egg value is 255 - UI in-game is updated to handle 3-digits - New `Display Received Item Popups` option: `progression_minus_yoshi_eggs` Quality of Life: - In-Game Indicators are now displayed on the map screen for location checks and received items - In-level sprites are displayed upon receiving certain items - The Camera Scroll unlocking is now only enabled on levels where it needs to be - SMW can now handle receiving more than 255 items - Significant World Code cleanup - New Options API - Removal of `world: MultiWorld` across the world - The PopTracker pack now has tabs for every level/sublevel, and can automatically swap tabs while playing if connected to the server Bug Fixes: - Several logic tweaks/fixes "Major credit to @TheLX5 for being the driving force for almost all of this update. We've been collaborating on design and polish of the features for the last few months, but all of the heavy lifting was all @TheLX5." --- worlds/smw/Aesthetics.py | 907 ++++++- worlds/smw/Client.py | 264 +- worlds/smw/Items.py | 15 +- worlds/smw/Levels.py | 722 ++++- worlds/smw/Locations.py | 767 +++++- worlds/smw/Names/ItemName.py | 17 +- worlds/smw/Names/LocationName.py | 608 +++++ worlds/smw/Names/TextBox.py | 18 +- worlds/smw/Options.py | 194 +- worlds/smw/Regions.py | 1941 +++++++++---- worlds/smw/Rom.py | 2401 ++++++++++++++++- worlds/smw/Rules.py | 17 +- worlds/smw/__init__.py | 140 +- worlds/smw/data/blocksanity.json | 747 +++++ worlds/smw/data/graphics/indicators.bin | Bin 0 -> 384 bytes .../level/castle_pillars/agnus_castle.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_pillars/cheese.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/chocolate_blue.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/dark_aqua_marine.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/dollhouse.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/gold_caslte.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/keves_castle.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/original_gray.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/original_green.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/original_mustard.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/original_white.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/pink_purple.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/purple_pink.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/sand_gray.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/sand_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_pillars/shenhe.mw3 | Bin 0 -> 514 bytes .../level/castle_pillars/whatsapp.mw3 | Bin 0 -> 514 bytes .../level/castle_small_windows/dark_lava.mw3 | Bin 0 -> 514 bytes .../castle_small_windows/dark_purple.mw3 | Bin 0 -> 514 bytes .../level/castle_small_windows/dollhouse.mw3 | Bin 0 -> 514 bytes .../castle_small_windows/forgotten_temple.mw3 | Bin 0 -> 514 bytes .../castle_small_windows/original_gray.mw3 | Bin 0 -> 514 bytes .../original_volcanic.mw3 | Bin 0 -> 514 bytes .../castle_small_windows/original_water.mw3 | Bin 0 -> 514 bytes .../level/castle_small_windows/sand_gray.mw3 | Bin 0 -> 514 bytes .../level/castle_small_windows/sand_green.mw3 | Bin 0 -> 514 bytes .../level/castle_small_windows/shenhe.mw3 | Bin 0 -> 514 bytes .../level/castle_small_windows/water.mw3 | Bin 0 -> 514 bytes .../level/castle_small_windows/whatsapp.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_wall/cheese.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_wall/dollhouse.mw3 | Bin 0 -> 514 bytes .../level/castle_wall/grand_marshall.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_wall/hot_wall.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_wall/original.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_wall/sand_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_wall/shenhe.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/castle_wall/water.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/brawler_pink.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_windows/cheese.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/dark_aqua_marine.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/dollhouse.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/original_brown.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/original_gray.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/original_water.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/red_castle.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_windows/shenhe.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/underwater.mw3 | Bin 0 -> 514 bytes .../palettes/level/castle_windows/water.mw3 | Bin 0 -> 514 bytes .../level/castle_windows/whatsapp.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/brawler_dark.mw3 | Bin 0 -> 514 bytes .../palettes/level/cave/brawler_purple.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/brawler_red.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/brawler_teal.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/bright_magma.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/cave/dark_red.mw3 | Bin 0 -> 514 bytes .../palettes/level/cave/glowing_mushroom.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/green_depths.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/level/cave/ice.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/magma_cave.mw3 | Bin 0 -> 514 bytes .../level/cave/original_chocolate.mw3 | Bin 0 -> 514 bytes .../palettes/level/cave/original_gray.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/original_ice.mw3 | Bin 0 -> 514 bytes .../palettes/level/cave/original_mustard.mw3 | Bin 0 -> 514 bytes .../palettes/level/cave/original_volcanic.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/level/cave/snow.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/level/cave/toxic.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave/toxic_moss.mw3 | Bin 0 -> 514 bytes .../bocchi_rock_hair_cube_things.mw3 | Bin 0 -> 514 bytes .../level/cave_rocks/brawler_volcanic.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave_rocks/ice.mw3 | Bin 0 -> 514 bytes .../palettes/level/cave_rocks/layer_2.mw3 | Bin 0 -> 514 bytes .../level/cave_rocks/original_gray.mw3 | Bin 0 -> 514 bytes .../level/cave_rocks/original_mustard.mw3 | Bin 0 -> 514 bytes .../cave_rocks/pyra_mythra_ft_pneuma.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave_rocks/snow.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/cave_rocks/toxic.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/clouds/atardecer.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/clouds/charcoal.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/clouds/cloudy.mw3 | Bin 0 -> 514 bytes .../palettes/level/clouds/cotton_candy.mw3 | Bin 0 -> 514 bytes .../palettes/level/clouds/original_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/clouds/original_orange.mw3 | Bin 0 -> 514 bytes .../palettes/level/forest/agnian_queen.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/forest/atardecer.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/forest/frozen.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/forest/halloween.mw3 | Bin 0 -> 514 bytes .../palettes/level/forest/kevesi_queen.mw3 | Bin 0 -> 514 bytes .../palettes/level/forest/original_dark.mw3 | Bin 0 -> 514 bytes .../palettes/level/forest/original_fall.mw3 | Bin 0 -> 514 bytes .../palettes/level/forest/original_green.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/forest/sakura.mw3 | Bin 0 -> 514 bytes .../level/forest/snow_dark_leaves.mw3 | Bin 0 -> 514 bytes .../level/forest/snow_green_leaves.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/brawler_cyan.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/brawler_orange.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/brawler_purple.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/creepypasta.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/crimson_house.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/golden_house.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/halloween_pallet.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/orange_lights.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/original_aqua.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/original_blue.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/original_dark.mw3 | Bin 0 -> 514 bytes .../level/ghost_house/original_white.mw3 | Bin 0 -> 514 bytes .../level/ghost_house_exit/evening_exit.mw3 | Bin 0 -> 514 bytes .../level/ghost_house_exit/golden_house.mw3 | Bin 0 -> 514 bytes .../level/ghost_house_exit/original.mw3 | Bin 0 -> 514 bytes .../ghost_house_exit/original_blue_door.mw3 | Bin 0 -> 514 bytes .../level/ghost_house_exit/underwater.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_clouds/atardecer.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_clouds/crimson.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_clouds/electro.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_clouds/geo.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_clouds/miku.mw3 | Bin 0 -> 514 bytes .../level/grass_clouds/original_blue.mw3 | Bin 0 -> 514 bytes .../level/grass_clouds/original_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_clouds/pizza.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_clouds/sakura.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_clouds/shukfr.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_clouds/snow_day.mw3 | Bin 0 -> 514 bytes .../level/grass_clouds/volcanic_rock.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/atardecer.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/autumn.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/brawler.mw3 | Bin 0 -> 514 bytes .../level/grass_forest/brawler_atardecer.mw3 | Bin 0 -> 514 bytes .../level/grass_forest/brawler_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/crimson.mw3 | Bin 0 -> 514 bytes .../level/grass_forest/deep_forest.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/electro.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_forest/geo.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_forest/miku.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_forest/myon.mw3 | Bin 0 -> 514 bytes .../level/grass_forest/original_aqua.mw3 | Bin 0 -> 514 bytes .../level/grass_forest/original_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/pizza.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/sakura.mw3 | Bin 0 -> 514 bytes .../level/grass_forest/snow_dark_leaves.mw3 | Bin 0 -> 514 bytes .../level/grass_forest/snow_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_forest/winter.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/atardecer.mw3 | Bin 0 -> 514 bytes .../level/grass_hills/brawler_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/crimson.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/electro.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_hills/geo.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_hills/miku.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/mogumogu.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/nocturno.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/original.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/sakura.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_hills/snow.mw3 | Bin 0 -> 514 bytes .../grass_hills/sunsetish_grass_hills.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_hills/toothpaste.mw3 | Bin 0 -> 514 bytes .../grass_mountains/brawler_lifeless.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/classic_sm.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/crimson.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/dry_hills.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/electro.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_mountains/geo.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/late_sandish.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_mountains/miku.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/original_aqua.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/original_blue.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/original_green.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/original_white.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/recksfr.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/sakura_hills.mw3 | Bin 0 -> 514 bytes .../level/grass_mountains/snow_day.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_rocks/atardecer.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_rocks/crimson.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_rocks/dark.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_rocks/electro.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_rocks/geo.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_rocks/ice.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_rocks/miku.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_rocks/napolitano.mw3 | Bin 0 -> 514 bytes .../level/grass_rocks/original_aqua.mw3 | Bin 0 -> 514 bytes .../grass_rocks/original_choco_volcanic.mw3 | Bin 0 -> 514 bytes .../level/grass_rocks/original_ice.mw3 | Bin 0 -> 514 bytes .../level/grass_rocks/original_volcanic.mw3 | Bin 0 -> 514 bytes .../grass_rocks/original_volcanic_green.mw3 | Bin 0 -> 514 bytes .../level/grass_rocks/original_white.mw3 | Bin 0 -> 514 bytes .../level/grass_rocks/original_white_2.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/grass_rocks/recks.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_rocks/sakura.mw3 | Bin 0 -> 514 bytes .../palettes/level/grass_rocks/thanks_doc.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/logs/brawler.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/logs/evening.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/logs/mahogany.mw3 | Bin 0 -> 514 bytes .../level/logs/not_quite_dawnbreak.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/logs/original.mw3 | Bin 0 -> 514 bytes .../level/logs/riesgo_de_chubascos.mw3 | Bin 0 -> 514 bytes .../level/mushroom_cave/argent_cave.mw3 | Bin 0 -> 514 bytes .../level/mushroom_cave/glowing_mushroom.mw3 | Bin 0 -> 514 bytes .../level/mushroom_cave/green_aqua.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/mushroom_cave/ice.mw3 | Bin 0 -> 514 bytes .../palettes/level/mushroom_cave/original.mw3 | Bin 0 -> 514 bytes .../level/mushroom_cave/really_dark.mw3 | Bin 0 -> 514 bytes .../palettes/level/mushroom_cave/toxic.mw3 | Bin 0 -> 514 bytes .../level/mushroom_clouds/atardecer.mw3 | Bin 0 -> 514 bytes .../level/mushroom_clouds/greenshroom.mw3 | Bin 0 -> 514 bytes .../level/mushroom_clouds/oilshroom.mw3 | Bin 0 -> 514 bytes .../level/mushroom_clouds/original_aqua.mw3 | Bin 0 -> 514 bytes .../level/mushroom_clouds/original_blue.mw3 | Bin 0 -> 514 bytes .../level/mushroom_clouds/original_yellow.mw3 | Bin 0 -> 514 bytes .../mushroom_clouds/riesgo_de_chubascos.mw3 | Bin 0 -> 514 bytes .../level/mushroom_forest/atardecer.mw3 | Bin 0 -> 514 bytes .../palettes/level/mushroom_forest/autumn.mw3 | Bin 0 -> 514 bytes .../mushroom_forest/count_shroomcula.mw3 | Bin 0 -> 514 bytes .../level/mushroom_forest/cursed_gold.mw3 | Bin 0 -> 514 bytes .../level/mushroom_forest/dark_green.mw3 | Bin 0 -> 514 bytes .../level/mushroom_forest/lifeless_gray.mw3 | Bin 0 -> 514 bytes .../level/mushroom_forest/original.mw3 | Bin 0 -> 514 bytes .../level/mushroom_forest/snow_dark.mw3 | Bin 0 -> 514 bytes .../level/mushroom_forest/snow_green.mw3 | Bin 0 -> 514 bytes .../level/mushroom_hills/atardecer.mw3 | Bin 0 -> 514 bytes .../mushroom_hills/atardecer_naranjo.mw3 | Bin 0 -> 514 bytes .../level/mushroom_hills/atardecer_verde.mw3 | Bin 0 -> 514 bytes .../palettes/level/mushroom_hills/future.mw3 | Bin 0 -> 514 bytes .../level/mushroom_hills/original.mw3 | Bin 0 -> 514 bytes .../riesgo_de_chubascos_azul.mw3 | Bin 0 -> 514 bytes .../riesgo_de_chubascos_cafe.mw3 | Bin 0 -> 514 bytes .../riesgo_de_chubascos_negro.mw3 | Bin 0 -> 514 bytes .../level/mushroom_hills/watermelon_skies.mw3 | Bin 0 -> 514 bytes .../level/mushroom_rocks/atardecer.mw3 | Bin 0 -> 514 bytes .../level/mushroom_rocks/brightshroom.mw3 | Bin 0 -> 514 bytes .../level/mushroom_rocks/original_green.mw3 | Bin 0 -> 514 bytes .../level/mushroom_rocks/original_ice.mw3 | Bin 0 -> 514 bytes .../mushroom_rocks/original_volcanic.mw3 | Bin 0 -> 514 bytes .../level/mushroom_rocks/original_white.mw3 | Bin 0 -> 514 bytes .../riesgo_de_chubascos_cafe.mw3 | Bin 0 -> 514 bytes .../riesgo_de_chubascos_negro.mw3 | Bin 0 -> 514 bytes .../level/mushroom_rocks/shuk_ft_reyn.mw3 | Bin 0 -> 514 bytes .../level/mushroom_stars/atardecer.mw3 | Bin 0 -> 514 bytes .../palettes/level/mushroom_stars/cool.mw3 | Bin 0 -> 514 bytes .../level/mushroom_stars/dark_night.mw3 | Bin 0 -> 514 bytes .../level/mushroom_stars/halloween.mw3 | Bin 0 -> 514 bytes .../level/mushroom_stars/light_pollution.mw3 | Bin 0 -> 514 bytes .../palettes/level/mushroom_stars/midas.mw3 | Bin 0 -> 514 bytes .../level/mushroom_stars/original_green.mw3 | Bin 0 -> 514 bytes .../level/mushroom_stars/original_night.mw3 | Bin 0 -> 514 bytes .../level/mushroom_stars/purpleish_night.mw3 | Bin 0 -> 514 bytes .../mushroom_stars/riesgo_de_chubascos.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/level/palettes.json | 358 +++ .../level/ship_exterior/blue_purple.mw3 | Bin 0 -> 514 bytes .../palettes/level/ship_exterior/doc_ship.mw3 | Bin 0 -> 514 bytes .../level/ship_exterior/grey_ship.mw3 | Bin 0 -> 514 bytes .../palettes/level/ship_exterior/original.mw3 | Bin 0 -> 514 bytes .../palettes/level/ship_exterior/reddish.mw3 | Bin 0 -> 514 bytes .../level/ship_interior/blue_purple.mw3 | Bin 0 -> 514 bytes .../level/ship_interior/bocchi_hitori.mw3 | Bin 0 -> 514 bytes .../level/ship_interior/bocchi_rock.mw3 | Bin 0 -> 514 bytes .../palettes/level/ship_interior/brawler.mw3 | Bin 0 -> 514 bytes .../level/ship_interior/grey_ship.mw3 | Bin 0 -> 514 bytes .../palettes/level/ship_interior/original.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/blue_grid.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/brawler_brown.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/cafe_claro.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/color_de_gato.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/color_del_gato_2.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/green_grid.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/gris.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/mario_pants.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/monado.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/morado.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/negro.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/onigiria.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/original.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/original_bonus.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/pink.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/red_grid.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/verde.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/verde_agua.mw3 | Bin 0 -> 514 bytes .../level/switch_palace/yellow_grid.mw3 | Bin 0 -> 514 bytes .../palettes/level/switch_palace/youbonus.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/water/dark_water.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/water/deep_aqua.mw3 | Bin 0 -> 514 bytes .../palettes/level/water/deep_chocolate.mw3 | Bin 0 -> 514 bytes .../palettes/level/water/harmless_magma.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/level/water/murky.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/water/oil_spill.mw3 | Bin 0 -> 514 bytes .../palettes/level/water/original_brown.mw3 | Bin 0 -> 514 bytes .../palettes/level/water/original_gray.mw3 | Bin 0 -> 514 bytes .../palettes/level/water/original_green.mw3 | Bin 0 -> 514 bytes .../palettes/level/water/original_mustard.mw3 | Bin 0 -> 514 bytes .../level/water/original_volcanic.mw3 | Bin 0 -> 514 bytes .../palettes/level/water/pickle_juice.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/atardecer.mw3 | Bin 0 -> 514 bytes .../level/yoshi_house/brawler_green.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/yoshi_house/choco.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/crimson.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/yoshi_house/miku.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/mogumogu.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/monocromo.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/yoshi_house/neon.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/yoshi_house/nieve.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/yoshi_house/night.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/nocturno.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/original.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/sakura.mw3 | Bin 0 -> 514 bytes .../data/palettes/level/yoshi_house/snow.mw3 | Bin 0 -> 514 bytes .../palettes/level/yoshi_house/strong_sun.mw3 | Bin 0 -> 514 bytes .../yoshi_house/sunsetish_grass_hills.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/forest/atardecer.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/forest/burnt_forest.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/forest/dark_forest.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/forest/halloween.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/forest/ice_forest.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/forest/lost_woods.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/forest/mono.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/forest/original.mw3 | Bin 0 -> 514 bytes .../palettes/map/forest/original_special.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/forest/sepia.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/forest/snow_day.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/main/atardecer.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/main/brawler.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/main/cake_frosting.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/main/invertido.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/main/mono.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/main/morning.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/main/night.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/main/night_time.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/main/original.mw3 | Bin 0 -> 514 bytes .../palettes/map/main/original_special.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/main/sepia.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/main/snow_day.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/palettes.json | 92 + .../data/palettes/map/special/black_out.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/special/blood_star.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/special/brawler.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/special/green.mw3 | Bin 0 -> 514 bytes .../map/special/light_pollution_map.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/special/original.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/special/purple.mw3 | Bin 0 -> 514 bytes .../palettes/map/special/white_special.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/star/blood_moon.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/star/mono.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/star/mountain_top.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/star/original.mw3 | Bin 0 -> 514 bytes .../palettes/map/star/original_special.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/star/pink_star.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/star/sepia.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/star/yellow_star.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/valley/Tamaulipas.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/valley/bowser.mw3 | Bin 0 -> 514 bytes .../palettes/map/valley/castle_colors.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/valley/dark cave.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/valley/dream_world.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/valley/fire cave.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/valley/invertido.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/valley/mono.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/valley/orange.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/valley/original.mw3 | Bin 0 -> 514 bytes .../palettes/map/valley/original_special.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/valley/purple_blue.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/valley/sepia.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/valley/snow.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/vanilla/DOMO.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/vanilla/aqua_marine.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/vanilla/dark cave.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/vanilla/fire cave.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/vanilla/gold_mine.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/vanilla/invertido.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/vanilla/mono.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/vanilla/original.mw3 | Bin 0 -> 514 bytes .../palettes/map/vanilla/original_special.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/vanilla/purple.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/vanilla/sepia.mw3 | Bin 0 -> 514 bytes .../palettes/map/vanilla/witches_cauldron.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/yoshi/atardecer.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/yoshi/gum.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/yoshi/lava_island.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/yoshi/mono.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/yoshi/original.mw3 | Bin 0 -> 514 bytes .../palettes/map/yoshi/original_special.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/yoshi/sepia.mw3 | Bin 0 -> 514 bytes .../smw/data/palettes/map/yoshi/snow_day.mw3 | Bin 0 -> 514 bytes worlds/smw/data/palettes/map/yoshi/sunset.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/yoshi/tritanopia.mw3 | Bin 0 -> 514 bytes .../data/palettes/map/yoshi/yochis_ailand.mw3 | Bin 0 -> 514 bytes 395 files changed, 8433 insertions(+), 775 deletions(-) create mode 100644 worlds/smw/data/blocksanity.json create mode 100644 worlds/smw/data/graphics/indicators.bin create mode 100644 worlds/smw/data/palettes/level/castle_pillars/agnus_castle.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/cheese.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/chocolate_blue.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/dark_aqua_marine.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/dollhouse.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/gold_caslte.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/keves_castle.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/original_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/original_mustard.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/original_white.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/pink_purple.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/purple_pink.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/sand_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/sand_green.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/shenhe.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_pillars/whatsapp.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/dark_lava.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/dark_purple.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/dollhouse.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/forgotten_temple.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/original_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/original_volcanic.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/original_water.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/sand_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/sand_green.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/shenhe.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/water.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_small_windows/whatsapp.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/cheese.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/dollhouse.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/grand_marshall.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/hot_wall.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/original.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/sand_green.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/shenhe.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_wall/water.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/brawler_pink.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/cheese.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/dark_aqua_marine.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/dollhouse.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/original_brown.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/original_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/original_water.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/red_castle.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/shenhe.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/underwater.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/water.mw3 create mode 100644 worlds/smw/data/palettes/level/castle_windows/whatsapp.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/brawler_dark.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/brawler_purple.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/brawler_red.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/brawler_teal.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/bright_magma.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/dark_red.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/glowing_mushroom.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/green_depths.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/ice.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/magma_cave.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/original_chocolate.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/original_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/original_ice.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/original_mustard.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/original_volcanic.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/snow.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/toxic.mw3 create mode 100644 worlds/smw/data/palettes/level/cave/toxic_moss.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/bocchi_rock_hair_cube_things.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/brawler_volcanic.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/ice.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/layer_2.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/original_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/original_mustard.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/pyra_mythra_ft_pneuma.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/snow.mw3 create mode 100644 worlds/smw/data/palettes/level/cave_rocks/toxic.mw3 create mode 100644 worlds/smw/data/palettes/level/clouds/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/clouds/charcoal.mw3 create mode 100644 worlds/smw/data/palettes/level/clouds/cloudy.mw3 create mode 100644 worlds/smw/data/palettes/level/clouds/cotton_candy.mw3 create mode 100644 worlds/smw/data/palettes/level/clouds/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/clouds/original_orange.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/agnian_queen.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/frozen.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/halloween.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/kevesi_queen.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/original_dark.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/original_fall.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/sakura.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/snow_dark_leaves.mw3 create mode 100644 worlds/smw/data/palettes/level/forest/snow_green_leaves.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/brawler_cyan.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/brawler_orange.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/brawler_purple.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/creepypasta.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/crimson_house.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/golden_house.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/halloween_pallet.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/orange_lights.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/original_aqua.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/original_blue.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/original_dark.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house/original_white.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house_exit/evening_exit.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house_exit/golden_house.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house_exit/original.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house_exit/original_blue_door.mw3 create mode 100644 worlds/smw/data/palettes/level/ghost_house_exit/underwater.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/crimson.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/electro.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/geo.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/miku.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/original_blue.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/pizza.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/sakura.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/shukfr.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/snow_day.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_clouds/volcanic_rock.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/autumn.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/brawler.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/brawler_atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/brawler_green.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/crimson.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/deep_forest.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/electro.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/geo.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/miku.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/myon.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/original_aqua.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/pizza.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/sakura.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/snow_dark_leaves.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/snow_green.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_forest/winter.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/brawler_green.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/crimson.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/electro.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/geo.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/miku.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/mogumogu.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/nocturno.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/original.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/sakura.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/snow.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/sunsetish_grass_hills.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_hills/toothpaste.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/brawler_lifeless.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/classic_sm.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/crimson.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/dry_hills.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/electro.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/geo.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/late_sandish.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/miku.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/original_aqua.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/original_blue.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/original_white.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/recksfr.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/sakura_hills.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_mountains/snow_day.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/crimson.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/dark.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/electro.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/geo.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/ice.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/miku.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/napolitano.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/original_aqua.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/original_choco_volcanic.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/original_ice.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/original_volcanic.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/original_volcanic_green.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/original_white.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/original_white_2.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/recks.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/sakura.mw3 create mode 100644 worlds/smw/data/palettes/level/grass_rocks/thanks_doc.mw3 create mode 100644 worlds/smw/data/palettes/level/logs/brawler.mw3 create mode 100644 worlds/smw/data/palettes/level/logs/evening.mw3 create mode 100644 worlds/smw/data/palettes/level/logs/mahogany.mw3 create mode 100644 worlds/smw/data/palettes/level/logs/not_quite_dawnbreak.mw3 create mode 100644 worlds/smw/data/palettes/level/logs/original.mw3 create mode 100644 worlds/smw/data/palettes/level/logs/riesgo_de_chubascos.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_cave/argent_cave.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_cave/glowing_mushroom.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_cave/green_aqua.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_cave/ice.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_cave/original.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_cave/really_dark.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_cave/toxic.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_clouds/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_clouds/greenshroom.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_clouds/oilshroom.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_clouds/original_aqua.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_clouds/original_blue.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_clouds/original_yellow.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_clouds/riesgo_de_chubascos.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/autumn.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/count_shroomcula.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/cursed_gold.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/dark_green.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/lifeless_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/original.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/snow_dark.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_forest/snow_green.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/atardecer_naranjo.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/atardecer_verde.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/future.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/original.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/riesgo_de_chubascos_azul.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/riesgo_de_chubascos_cafe.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/riesgo_de_chubascos_negro.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_hills/watermelon_skies.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/brightshroom.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/original_ice.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/original_volcanic.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/original_white.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/riesgo_de_chubascos_cafe.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/riesgo_de_chubascos_negro.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_rocks/shuk_ft_reyn.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/cool.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/dark_night.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/halloween.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/light_pollution.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/midas.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/original_night.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/purpleish_night.mw3 create mode 100644 worlds/smw/data/palettes/level/mushroom_stars/riesgo_de_chubascos.mw3 create mode 100644 worlds/smw/data/palettes/level/palettes.json create mode 100644 worlds/smw/data/palettes/level/ship_exterior/blue_purple.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_exterior/doc_ship.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_exterior/grey_ship.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_exterior/original.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_exterior/reddish.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_interior/blue_purple.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_interior/bocchi_hitori.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_interior/bocchi_rock.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_interior/brawler.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_interior/grey_ship.mw3 create mode 100644 worlds/smw/data/palettes/level/ship_interior/original.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/blue_grid.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/brawler_brown.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/cafe_claro.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/color_de_gato.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/color_del_gato_2.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/green_grid.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/gris.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/mario_pants.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/monado.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/morado.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/negro.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/onigiria.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/original.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/original_bonus.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/pink.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/red_grid.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/verde.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/verde_agua.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/yellow_grid.mw3 create mode 100644 worlds/smw/data/palettes/level/switch_palace/youbonus.mw3 create mode 100644 worlds/smw/data/palettes/level/water/dark_water.mw3 create mode 100644 worlds/smw/data/palettes/level/water/deep_aqua.mw3 create mode 100644 worlds/smw/data/palettes/level/water/deep_chocolate.mw3 create mode 100644 worlds/smw/data/palettes/level/water/harmless_magma.mw3 create mode 100644 worlds/smw/data/palettes/level/water/murky.mw3 create mode 100644 worlds/smw/data/palettes/level/water/oil_spill.mw3 create mode 100644 worlds/smw/data/palettes/level/water/original_brown.mw3 create mode 100644 worlds/smw/data/palettes/level/water/original_gray.mw3 create mode 100644 worlds/smw/data/palettes/level/water/original_green.mw3 create mode 100644 worlds/smw/data/palettes/level/water/original_mustard.mw3 create mode 100644 worlds/smw/data/palettes/level/water/original_volcanic.mw3 create mode 100644 worlds/smw/data/palettes/level/water/pickle_juice.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/brawler_green.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/choco.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/crimson.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/miku.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/mogumogu.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/monocromo.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/neon.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/nieve.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/night.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/nocturno.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/original.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/sakura.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/snow.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/strong_sun.mw3 create mode 100644 worlds/smw/data/palettes/level/yoshi_house/sunsetish_grass_hills.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/burnt_forest.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/dark_forest.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/halloween.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/ice_forest.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/lost_woods.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/mono.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/original.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/original_special.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/sepia.mw3 create mode 100644 worlds/smw/data/palettes/map/forest/snow_day.mw3 create mode 100644 worlds/smw/data/palettes/map/main/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/map/main/brawler.mw3 create mode 100644 worlds/smw/data/palettes/map/main/cake_frosting.mw3 create mode 100644 worlds/smw/data/palettes/map/main/invertido.mw3 create mode 100644 worlds/smw/data/palettes/map/main/mono.mw3 create mode 100644 worlds/smw/data/palettes/map/main/morning.mw3 create mode 100644 worlds/smw/data/palettes/map/main/night.mw3 create mode 100644 worlds/smw/data/palettes/map/main/night_time.mw3 create mode 100644 worlds/smw/data/palettes/map/main/original.mw3 create mode 100644 worlds/smw/data/palettes/map/main/original_special.mw3 create mode 100644 worlds/smw/data/palettes/map/main/sepia.mw3 create mode 100644 worlds/smw/data/palettes/map/main/snow_day.mw3 create mode 100644 worlds/smw/data/palettes/map/palettes.json create mode 100644 worlds/smw/data/palettes/map/special/black_out.mw3 create mode 100644 worlds/smw/data/palettes/map/special/blood_star.mw3 create mode 100644 worlds/smw/data/palettes/map/special/brawler.mw3 create mode 100644 worlds/smw/data/palettes/map/special/green.mw3 create mode 100644 worlds/smw/data/palettes/map/special/light_pollution_map.mw3 create mode 100644 worlds/smw/data/palettes/map/special/original.mw3 create mode 100644 worlds/smw/data/palettes/map/special/purple.mw3 create mode 100644 worlds/smw/data/palettes/map/special/white_special.mw3 create mode 100644 worlds/smw/data/palettes/map/star/blood_moon.mw3 create mode 100644 worlds/smw/data/palettes/map/star/mono.mw3 create mode 100644 worlds/smw/data/palettes/map/star/mountain_top.mw3 create mode 100644 worlds/smw/data/palettes/map/star/original.mw3 create mode 100644 worlds/smw/data/palettes/map/star/original_special.mw3 create mode 100644 worlds/smw/data/palettes/map/star/pink_star.mw3 create mode 100644 worlds/smw/data/palettes/map/star/sepia.mw3 create mode 100644 worlds/smw/data/palettes/map/star/yellow_star.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/Tamaulipas.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/bowser.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/castle_colors.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/dark cave.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/dream_world.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/fire cave.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/invertido.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/mono.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/orange.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/original.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/original_special.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/purple_blue.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/sepia.mw3 create mode 100644 worlds/smw/data/palettes/map/valley/snow.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/DOMO.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/aqua_marine.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/dark cave.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/fire cave.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/gold_mine.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/invertido.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/mono.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/original.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/original_special.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/purple.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/sepia.mw3 create mode 100644 worlds/smw/data/palettes/map/vanilla/witches_cauldron.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/atardecer.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/gum.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/lava_island.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/mono.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/original.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/original_special.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/sepia.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/snow_day.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/sunset.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/tritanopia.mw3 create mode 100644 worlds/smw/data/palettes/map/yoshi/yochis_ailand.mw3 diff --git a/worlds/smw/Aesthetics.py b/worlds/smw/Aesthetics.py index 73ca616508..16b2b138f3 100644 --- a/worlds/smw/Aesthetics.py +++ b/worlds/smw/Aesthetics.py @@ -1,3 +1,82 @@ +import json +import pkgutil + +from worlds.AutoWorld import World + +tileset_names = [ + "grass_hills", + "grass_forest", + "grass_rocks", + "grass_clouds", + "grass_mountains", + "cave", + "cave_rocks", + "water", + "mushroom_rocks", + "mushroom_clouds", + "mushroom_forest", + "mushroom_hills", + "mushroom_stars", + "mushroom_cave", + "forest", + "logs", + "clouds", + "castle_pillars", + "castle_windows", + "castle_wall", + "castle_small_windows", + "ghost_house", + "ghost_house_exit", + "ship_exterior", + "ship_interior", + "switch_palace", + "yoshi_house" +] + +map_names = [ + "main", + "yoshi", + "vanilla", + "forest", + "valley", + "special", + "star" +] + +level_palette_index = [ + 0xFF,0x03,0x09,0x01,0x15,0x0A,0x04,0x12,0x19,0x06,0x07,0x12,0x09,0x0F,0x13,0x09, # Levels 000-00F + 0x03,0x07,0xFF,0x15,0x19,0x04,0x04,0xFF,0x17,0xFF,0x14,0x12,0x02,0x05,0xFF,0x11, # Levels 010-01F + 0x12,0x15,0x04,0x02,0x02,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 020-02F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 030-03F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 040-04F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 050-05F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 060-06F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 070-07F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 080-08F + 0xFF,0xFF,0xFF,0x12,0x12,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 090-09F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 0A0-0AF + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x19,0x08,0x09, # Levels 0B0-0BF + 0x02,0x08,0x05,0x04,0x16,0x1A,0x04,0x02,0x0C,0x19,0x19,0x09,0xFF,0x02,0x02,0x02, # Levels 0C0-0CF + 0x04,0x04,0x05,0x12,0x14,0xFF,0x12,0x10,0x05,0xFF,0x19,0x12,0x14,0x0F,0x15,0xFF, # Levels 0D0-0DF + 0x12,0x12,0xFF,0x04,0x15,0xFF,0x19,0x14,0x12,0x05,0x05,0x16,0x15,0x15,0x15,0x12, # Levels 0E0-0EF + 0x16,0x15,0x15,0x09,0x19,0x04,0x04,0x13,0x18,0x15,0x15,0x16,0x15,0x19,0x15,0x04, # Levels 0F0-0FF + 0xFF,0x11,0x08,0x02,0x1A,0x00,0x01,0x15,0xFF,0x05,0x05,0x05,0xFF,0x11,0x12,0x05, # Levels 100-10F + 0x12,0x14,0xFF,0x0D,0x15,0x06,0x05,0x05,0x05,0x0C,0x05,0x19,0x12,0x15,0x0E,0x01, # Levels 110-11F + 0x07,0x19,0x0E,0x0E,0xFF,0x04,0x0E,0x02,0x02,0xFF,0x09,0x04,0x0B,0x02,0xFF,0xFF, # Levels 120-12F + 0x07,0xFF,0x0C,0xFF,0x05,0x0C,0x0C,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 130-13F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 140-14F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 150-15F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 160-16F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 170-17F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 180-18F + 0xFF,0xFF,0xFF,0x12,0x12,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 190-19F + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, # Levels 1A0-1AF + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x19,0x19,0x12,0x02,0x05, # Levels 1B0-1BF + 0x02,0x07,0x05,0x05,0x03,0x03,0x00,0xFF,0x0F,0x10,0x05,0x05,0x12,0x11,0x14,0x14, # Levels 1C0-1CF + 0x11,0x12,0x12,0x12,0x11,0x03,0x03,0x19,0x19,0x15,0x16,0x15,0x15,0x15,0xFF,0x05, # Levels 1D0-1DF + 0x10,0x02,0x06,0x06,0x19,0x05,0x16,0x16,0x15,0x15,0x15,0xFF,0x06,0x05,0x05,0x06, # Levels 1E0-1EF + 0x05,0x05,0x12,0x14,0x12,0x05,0xFF,0x19,0x05,0x16,0x15,0x15,0x11,0x05,0x12,0x09 # Levels 1F0-1FF +] mario_palettes = [ [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0x1F, 0x39, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0xB6, 0x30, 0xDF, 0x35, 0xFF, 0x03], # Mario @@ -145,36 +224,413 @@ valid_ow_palettes = { 0x2D24: [0x00, 0x02, 0x03], # Star Road } -def generate_shuffled_level_music(world, player): +valid_sfxs = [ + [0x01, 1], # Jump + [0x01, 0], # Hit head + [0x02, 0], # Contact/Spinjump on an enemy + [0x03, 0], # Kick item + [0x04, 0], # Go in pipe, get hurt + [0x05, 0], # Midway point + [0x06, 0], # Yoshi gulp + [0x07, 0], # Dry bones collapse + [0x08, 0], # Kill enemy with a spin jump + [0x09, 0], # Fly with cape + [0x0A, 0], # Get powerup + [0x0B, 0], # ON/OFF switch + [0x0C, 0], # Carry item past the goal + [0x0D, 0], # Get cape + [0x0E, 0], # Swim + [0x0F, 0], # Hurt while flying + [0x10, 0], # Magikoopa shoot magic + [0x13, 0], # Enemy stomp #1 + [0x14, 0], # Enemy stomp #2 + [0x15, 0], # Enemy stomp #3 + [0x16, 0], # Enemy stomp #4 + [0x17, 0], # Enemy stomp #5 + [0x18, 0], # Enemy stomp #6 + [0x19, 0], # Enemy stomp #7 + [0x1C, 0], # Yoshi Coin + [0x1E, 0], # P-Balloon + [0x1F, 0], # Koopaling defeated + [0x20, 0], # Yoshi spit + [0x23, 0], # Lemmy/Wendy falling + [0x25, 0], # Blargg roar + [0x26, 0], # Firework whistle + [0x27, 0], # Firework bang + [0x2A, 0], # Peach pops up from the Clown Car + [0x04, 1], # Grinder + [0x01, 3], # Coin + [0x02, 3], # Hit a ? block + [0x03, 3], # Hit a block with a vine inside + [0x04, 3], # Spin jump + [0x05, 3], # 1up + [0x06, 3], # Shatter block + [0x07, 3], # Shoot fireball + [0x08, 3], # Springboard + [0x09, 3], # Bullet bill + [0x0A, 3], # Egg hatch + [0x0B, 3], # Item going into item box + [0x0C, 3], # Item falls from item box + [0x0E, 3], # L/R scroll + [0x0F, 3], # Door + [0x13, 3], # Lose Yoshi + [0x14, 3], # SMW2: New level available + [0x15, 3], # OW tile reveal + [0x16, 3], # OW castle collapse + [0x17, 3], # Fire spit + [0x18, 3], # Thunder + [0x19, 3], # Clap + [0x1A, 3], # Castle bomb + [0x1C, 3], # OW switch palace block ejection + [0x1E, 3], # Whistle + [0x1F, 3], # Yoshi mount + [0x20, 3], # Lemmy/Wendy going in lava + [0x21, 3], # Yoshi's tongue + [0x22, 3], # Message box hit + [0x23, 3], # Landing in a level tile + [0x24, 3], # P-Switch running out + [0x25, 3], # Yoshi defeats an enemy + [0x26, 3], # Swooper + [0x27, 3], # Podoboo + [0x28, 3], # Enemy hurt + [0x29, 3], # Correct + [0x2A, 3], # Wrong + [0x2B, 3], # Firework whistle + [0x2C, 3] # Firework bang +] + +game_sfx_calls = [ + 0x0565E, # Jump + 0x1BABD, # Spin jump + 0x06D41, # Hit head on ceiling + 0x0B4F2, # Hit head on sprite + 0x07EB5, # Shooting a fireball + 0x0507B, # Cape spin + 0x058A8, # Cape smash + 0x075F3, # Taking damage + 0x075E2, # Taking damage while flying + 0x07919, # Something during a boss fight + 0x05AA9, # Swim + 0x1BC04, # Spin jump off water + 0x05BA5, # Jump off a net + 0x05BB2, # Punching a net + 0x06C10, # Entering a door + 0x05254, # Entering a pipe #1 + 0x07439, # Entering a pipe #2 + 0x052A5, # Shot from a diagonal pipe + 0x072E8, # Hit a midway point + 0x07236, # Hit a wrong block + 0x07B7D, # Spawn a powerup during the goal tape + 0x1C342, # Invisible mushroom spawn + 0x04E3F, # Scrolling the screen with L/R + 0x0AAFD, # Pressing a P-Switch + 0x04557, # P-Switch running out + 0x0BAD7, # Climbing door turning + 0x0C109, # Break goal tape + 0x0C548, # Putting item in item box + 0x10012, # Trigger item box + 0x2B34D, # Collecting a coin + 0x07358, # Collecting a Yoshi Coin + 0x0C57A, # Collecting a powerup (generic) + 0x0C59C, # Collecting a feather + 0x0C309, # Collecting a P-Balloon + 0x0E6A9, # Bouncing off a springboard + 0x1117D, # Bouncing off a note block + 0x14DEC, # Bouncing off a wall springboard + 0x1067F, # Block shattering + 0x1081E, # Activate ON/OFF switch #1 + 0x1118C, # Activate ON/OFF switch #2 + 0x12045, # Fireballs hitting a block/sprite + 0x12124, # Fireballs converting an enemy into a coin + 0x12106, # Fireballs defeating a Chuck + 0x18D7D, # Activating a message box + 0x1C209, # Activating a red question block + 0x0A290, # Baby Yoshi swallowing an item #1 + 0x1C037, # Baby Yoshi swallowing an item #2 + 0x0F756, # Yoshi egg hatching + 0x0A2C5, # Yoshi growing #1 + 0x1C06C, # Yoshi growing #2 + 0x0ED5F, # Mounting Yoshi + 0x0F71D, # Yoshi hurt + 0x12481, # Yoshi hurt (projectiles) + 0x0EF0E, # Yoshi flying + 0x06F90, # Yoshi stomping an enemy + 0x06FB6, # Yoshi ground pound (yellow shell) + 0x07024, # Yoshi bounces off a triangle + 0x11BE9, # Yoshi stomping the ground + 0x0F0D3, # Yoshi swallowing a sprite + 0x0F0FD, # Yoshi eating a green berry + 0x1BA7D, # Yoshi sticking out tongue + 0x0F5A1, # Yoshi unable to eat + 0x0F2DF, # Yoshi spitting out an item + 0x0F28F, # Yoshi spitting out flames + 0x0F3EC, # Collecting Yoshi's wings (eaten) + 0x0F6C8, # Collecting Yoshi's wings (touched) + 0x7FE04, # Defeated sprite combo #1 (using Y index) + 0x7FE0E, # Defeated sprite combo #2 (using Y index) + 0x7FE18, # Defeated sprite combo #3 (using Y index) + 0x7FE22, # Defeated sprite combo #4 (using Y index) + 0x7FE2C, # Defeated sprite combo #5 (using Y index) + 0x7FE36, # Defeated sprite combo #6 (using Y index) + 0x7FE40, # Defeated sprite combo #7 (using Y index) + 0x7FE4B, # Defeated sprite combo #1 (using X index) + 0x7FE55, # Defeated sprite combo #2 (using X index) + 0x7FE5F, # Defeated sprite combo #3 (using X index) + 0x7FE69, # Defeated sprite combo #4 (using X index) + 0x7FE73, # Defeated sprite combo #5 (using X index) + 0x7FE7D, # Defeated sprite combo #6 (using X index) + 0x7FE87, # Defeated sprite combo #7 (using X index) + 0x0A728, # Kicking a carryable item + 0x0B12F, # Kicking a stunned and vulnerable enemy + 0x0A8D8, # Performing a spinjump on a immune enemy + 0x0A93F, # Defeating an enemy via spinjump + 0x0999E, # Thrown sprite hitting the ground from the side + 0x192B8, # Creating/Eating block moving + 0x195EC, # Rex stomped + 0x134A7, # Bullet bill blaster shooting + 0x13088, # Bullet bill generator #1 + 0x130DF, # Bullet bill generator #2 + 0x09631, # Bob-omb explosion + 0x15918, # Popping a bubble + 0x15D64, # Sumo bro stomping the ground + 0x15ECC, # Sumo bro lightning spawning flames + 0x1726B, # Bouncing off wiggler + 0x08390, # Banzai bill spawn + 0x0AF17, # Thwomp hitting the ground + 0x0AFFC, # Thwimp hitting the ground + 0x14707, # Chuck running + 0x14381, # Chuck whistling + 0x144F8, # Chuck clapping + 0x14536, # Chuck jumping + 0x145AE, # Chuck splitting + 0x147D2, # Chuck bounce + 0x147F6, # Chuck hurt + 0x147B8, # Chuck defeated + 0x19D55, # Dino torch shooting fire + 0x19FFA, # Blargg attacking + 0x188FF, # Swooper bat swooping + 0x08584, # Bowser statue flame spawn + 0x18ADA, # Bowser statue shooting a flame + 0x13043, # Bowser statue flame from generator + 0x0BF28, # Magikoopa shooting a magic spell + 0x0BC5F, # Magikoopa's magic spell hitting the ground + 0x0D745, # Line guided sprites' motor + 0x0DB70, # Grinder sound + 0x0E0A1, # Podoboo jumping + 0x0E5F2, # Dry bones/Bony beetle collapsing + 0x15474, # Giant wooden pillar hitting the ground + 0x2C9C1, # Spiked columns hitting the ground + 0x19B03, # Reznor shooting a fireball + 0x19A66, # Reznor: Hitting a platform + 0x1D752, # Reznor: Bridge collapsing + 0x19ABB, # Reznor: Defeated + 0x180E9, # Big Boo: Reappearing + 0x18233, # Big Boo: Hurt + 0x181DE, # Big Boo: Defeated + 0x1CEC1, # Wendy/Lemmy: Hitting a dummy + 0x1CECB, # Wendy/Lemmy: Hurt + 0x1CE33, # Wendy/Lemmy: Hurt (correct) + 0x1CE46, # Wendy/Lemmy: Hurt (incorrect) + 0x1CE24, # Wendy/Lemmy: Defeated + 0x1CE7E, # Wendy/Lemmy: Falling into lava + 0x0CF0A, # Ludwig: Jumping + 0x0D059, # Ludwig: Shooting a fireball + 0x10414, # Morton/Roy: Pillar drop + 0x0D299, # Morton/Roy: Ground smash + 0x0D3AB, # Morton/Roy/Ludwig: Hit by a fireball + 0x0D2FD, # Morton/Roy/Ludwig: Bouncing off + 0x0D31E, # Morton/Roy/Ludwig: Bouncing off (immune) + 0x0D334, # Morton/Roy/Ludwig: Bouncing off (immune, going up a wall) + 0x0CFD0, # Morton/Roy/Ludwig: Defeated + 0x0FCCE, # Iggy/Larry: Being hit + 0x0FD40, # Iggy/Larry: Being hit by a fireball + 0x0FB60, # Iggy/Larry: Falling in lava + 0x1A8B2, # Peach emerging from Clown Car + 0x1A8E3, # Peach throwing an item + 0x1B0B8, # Bumping into Clown Car + 0x1B129, # Bowser: Hurt + 0x1AB8C, # Bowser: Slamming the ground (third phase) + 0x1A5D0, # Bowser: Throwing a Mechakoopa + 0x1A603, # Bowser: Dropping a ball + 0x1A7F6, # Bowser: Spawning a flame + 0x1B1A3, # Bowser's ball slam #1 + 0x1B1B1, # Bowser's ball slam #2 + 0x1E016, # Bowser's arena lightning effect + 0x26CAA, # Map: Level tile reveal + 0x26763, # Map: Terrain reveal + 0x21170, # Map: Using a star + 0x2666F, # Map: Castle destruction + 0x272A4, # Map: Switch palace blocks spawning + 0x203CC, # Map: Earthquake + 0x27A78, # Map: Fish jumping + 0x27736, # Map: Valley of bowser thunder + 0x013C0, # Menu: Nintendo presents + 0x01AE3, # Menu: File menu option select + 0x01AF9, # Menu: File menu option change + 0x01BBB, # Menu: Saving game + 0x273FF, # Menu: Map misc menu appearing + 0x27567, # Menu: Something during the map + 0x1767A, # Cutscene: Castle door opening + 0x17683, # Cutscene: Castle door closing + 0x17765, # Cutscene: Ghost house door opening + 0x1776E, # Cutscene: Ghost house door closing + 0x04720, # Cutscene: Detonator fuse + 0x04732, # Cutscene: Bouncing off something + 0x0475F, # Cutscene: Tossing the castle + 0x04798, # Cutscene: Picking up the castle + 0x047AC, # Cutscene: Huff + 0x047D1, # Cutscene: Hitting a castle + 0x1C830, # Cutscene: Shooting a firework + 0x625AF, # Cutscene: Egg shattering + 0x64F2C, # Cutscene: Hitting a hill + 0x6512A, # Cutscene: Castle crashing + 0x65295, # Cutscene: Explosion + 0x652B2, # Cutscene: Castle sinking + 0x652BD, # Cutscene: Castle flying + 0x652D8, # Cutscene: Fake explosion + 0x653E7, # Cutscene: Castle being hit by a hammer + 0x657D8 # Cutscene: Castle being mopped away +] + +def generate_shuffled_sfx(rom, world: World): + # Adjust "hitting sprites in succession" codes + rom.write_bytes(0x0A60B, bytearray([0x22, 0x00, 0xFE, 0x0F, 0xEA, 0xEA])) # jsl $0FFE00 : nop #2 # Thrown sprites combo #1 + rom.write_bytes(0x0A659, bytearray([0x22, 0x47, 0xFE, 0x0F, 0xEA, 0xEA])) # jsl $0FFE47 : nop #2 # Thrown sprites combo #2 + rom.write_bytes(0x0A865, bytearray([0x22, 0x47, 0xFE, 0x0F, 0xEA, 0xEA])) # jsl $0FFE47 : nop #2 # Star combo + rom.write_bytes(0x0AB57, bytearray([0x22, 0x00, 0xFE, 0x0F, 0xEA, 0xEA])) # jsl $0FFE00 : nop #2 # Bouncing off enemies + rom.write_bytes(0x172C0, bytearray([0x22, 0x00, 0xFE, 0x0F, 0xEA, 0xEA])) # jsl $0FFE00 : nop #2 # Star combo (wigglers) + rom.write_bytes(0x1961D, bytearray([0x22, 0x00, 0xFE, 0x0F, 0xEA, 0xEA])) # jsl $0FFE00 : nop #2 # Star combo (rexes) + rom.write_bytes(0x19639, bytearray([0x22, 0x00, 0xFE, 0x0F, 0xEA, 0xEA])) # jsl $0FFE00 : nop #2 # Bouncing off rexes + + COMBO_SFX_ADDR = 0x7FE00 + rom.write_bytes(COMBO_SFX_ADDR + 0x0000, bytearray([0xC0, 0x01])) # COMBO_Y: CPY #$01 + rom.write_bytes(COMBO_SFX_ADDR + 0x0002, bytearray([0xD0, 0x06])) # BNE label_0FFE0A + rom.write_bytes(COMBO_SFX_ADDR + 0x0004, bytearray([0xA9, 0x13])) # LDA #$13 + rom.write_bytes(COMBO_SFX_ADDR + 0x0006, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0009, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x000A, bytearray([0xC0, 0x02])) # label_0FFE0A: CPY #$02 + rom.write_bytes(COMBO_SFX_ADDR + 0x000C, bytearray([0xD0, 0x06])) # BNE label_0FFE14 + rom.write_bytes(COMBO_SFX_ADDR + 0x000E, bytearray([0xA9, 0x14])) # LDA #$14 + rom.write_bytes(COMBO_SFX_ADDR + 0x0010, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0013, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0014, bytearray([0xC0, 0x03])) # label_0FFE14: CPY #$03 + rom.write_bytes(COMBO_SFX_ADDR + 0x0016, bytearray([0xD0, 0x06])) # BNE label_0FFE1E + rom.write_bytes(COMBO_SFX_ADDR + 0x0018, bytearray([0xA9, 0x15])) # LDA #$15 + rom.write_bytes(COMBO_SFX_ADDR + 0x001A, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x001D, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x001E, bytearray([0xC0, 0x04])) # label_0FFE1E: CPY #$04 + rom.write_bytes(COMBO_SFX_ADDR + 0x0020, bytearray([0xD0, 0x06])) # BNE label_0FFE28 + rom.write_bytes(COMBO_SFX_ADDR + 0x0022, bytearray([0xA9, 0x16])) # LDA #$16 + rom.write_bytes(COMBO_SFX_ADDR + 0x0024, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0027, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0028, bytearray([0xC0, 0x05])) # label_0FFE28: CPY #$05 + rom.write_bytes(COMBO_SFX_ADDR + 0x002A, bytearray([0xD0, 0x06])) # BNE label_0FFE32 + rom.write_bytes(COMBO_SFX_ADDR + 0x002C, bytearray([0xA9, 0x17])) # LDA #$17 + rom.write_bytes(COMBO_SFX_ADDR + 0x002E, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0031, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0032, bytearray([0xC0, 0x06])) # label_0FFE32: CPY #$06 + rom.write_bytes(COMBO_SFX_ADDR + 0x0034, bytearray([0xD0, 0x06])) # BNE label_0FFE3C + rom.write_bytes(COMBO_SFX_ADDR + 0x0036, bytearray([0xA9, 0x18])) # LDA #$18 + rom.write_bytes(COMBO_SFX_ADDR + 0x0038, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x003B, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x003C, bytearray([0xC0, 0x07])) # label_0FFE3C: CPY #$07 + rom.write_bytes(COMBO_SFX_ADDR + 0x003E, bytearray([0xD0, 0x06])) # BNE label_0FFE46 + rom.write_bytes(COMBO_SFX_ADDR + 0x0040, bytearray([0xA9, 0x19])) # LDA #$19 + rom.write_bytes(COMBO_SFX_ADDR + 0x0042, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0045, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0046, bytearray([0x6B])) # label_0FFE46: RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0047, bytearray([0xE0, 0x01])) # COMBO_X: CPX #$01 + rom.write_bytes(COMBO_SFX_ADDR + 0x0049, bytearray([0xD0, 0x06])) # BNE label_0FFE51 + rom.write_bytes(COMBO_SFX_ADDR + 0x004B, bytearray([0xA9, 0x13])) # LDA #$13 + rom.write_bytes(COMBO_SFX_ADDR + 0x004D, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0050, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0051, bytearray([0xE0, 0x02])) # label_0FFE51: CPX #$02 + rom.write_bytes(COMBO_SFX_ADDR + 0x0053, bytearray([0xD0, 0x06])) # BNE label_0FFE5B + rom.write_bytes(COMBO_SFX_ADDR + 0x0055, bytearray([0xA9, 0x14])) # LDA #$14 + rom.write_bytes(COMBO_SFX_ADDR + 0x0057, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x005A, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x005B, bytearray([0xE0, 0x03])) # label_0FFE5B: CPX #$03 + rom.write_bytes(COMBO_SFX_ADDR + 0x005D, bytearray([0xD0, 0x06])) # BNE label_0FFE65 + rom.write_bytes(COMBO_SFX_ADDR + 0x005F, bytearray([0xA9, 0x15])) # LDA #$15 + rom.write_bytes(COMBO_SFX_ADDR + 0x0061, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0064, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0065, bytearray([0xE0, 0x04])) # label_0FFE65: CPX #$04 + rom.write_bytes(COMBO_SFX_ADDR + 0x0067, bytearray([0xD0, 0x06])) # BNE label_0FFE6F + rom.write_bytes(COMBO_SFX_ADDR + 0x0069, bytearray([0xA9, 0x16])) # LDA #$16 + rom.write_bytes(COMBO_SFX_ADDR + 0x006B, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x006E, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x006F, bytearray([0xE0, 0x05])) # label_0FFE6F: CPX #$05 + rom.write_bytes(COMBO_SFX_ADDR + 0x0071, bytearray([0xD0, 0x06])) # BNE label_0FFE79 + rom.write_bytes(COMBO_SFX_ADDR + 0x0073, bytearray([0xA9, 0x17])) # LDA #$17 + rom.write_bytes(COMBO_SFX_ADDR + 0x0075, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0078, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0079, bytearray([0xE0, 0x06])) # label_0FFE79: CPX #$06 + rom.write_bytes(COMBO_SFX_ADDR + 0x007B, bytearray([0xD0, 0x06])) # BNE label_0FFE83 + rom.write_bytes(COMBO_SFX_ADDR + 0x007D, bytearray([0xA9, 0x18])) # LDA #$18 + rom.write_bytes(COMBO_SFX_ADDR + 0x007F, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x0082, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x0083, bytearray([0xE0, 0x07])) # label_0FFE83: CPX #$07 + rom.write_bytes(COMBO_SFX_ADDR + 0x0085, bytearray([0xD0, 0x06])) # BNE label_0FFE8D + rom.write_bytes(COMBO_SFX_ADDR + 0x0087, bytearray([0xA9, 0x19])) # LDA #$19 + rom.write_bytes(COMBO_SFX_ADDR + 0x0089, bytearray([0x8D, 0xF9, 0x1D])) # STA $1DF9 + rom.write_bytes(COMBO_SFX_ADDR + 0x008C, bytearray([0x6B])) # RTL + rom.write_bytes(COMBO_SFX_ADDR + 0x008D, bytearray([0x6B])) # label_0FFE8D: RTL + + # Adjust "Hit head on ceiling" code + rom.write_bytes(0x06D41 + 0x00, bytearray([0xA9, 0x01])) # lda #$01 + rom.write_bytes(0x06D41 + 0x02, bytearray([0x8D, 0xF9, 0x1D])) # sta $1DF9 + rom.write_bytes(0x06D41 + 0x05, bytearray([0xEA, 0xEA, 0xEA, 0xEA])) # nop #4 + + # Manually add "Map: Stepping onto a level tile" random SFX + selected_sfx = world.random.choice(valid_sfxs) + rom.write_byte(0x2169F + 0x01, selected_sfx[0]) + rom.write_byte(0x2169F + 0x04, selected_sfx[1] + 0xF9) + + # Disable panning on Bowser's flames + rom.write_bytes(0x1A83D, bytearray([0xEA, 0xEA, 0xEA])) # nop #3 + + # Randomize SFX calls + for address in game_sfx_calls: + # Get random SFX + if world.options.sfx_shuffle != "singularity": + selected_sfx = world.random.choice(valid_sfxs) + # Write randomized SFX num + rom.write_byte(address + 0x01, selected_sfx[0]) + # Write randomized SFX port + rom.write_byte(address + 0x03, selected_sfx[1] + 0xF9) + +def generate_shuffled_level_music(world: World): shuffled_level_music = level_music_value_data.copy() - if world.music_shuffle[player] == "consistent": - world.per_slot_randoms[player].shuffle(shuffled_level_music) - elif world.music_shuffle[player] == "singularity": - single_song = world.per_slot_randoms[player].choice(shuffled_level_music) + if world.options.music_shuffle == "consistent": + world.random.shuffle(shuffled_level_music) + elif world.options.music_shuffle == "singularity": + single_song = world.random.choice(shuffled_level_music) shuffled_level_music = [single_song for i in range(len(shuffled_level_music))] return shuffled_level_music -def generate_shuffled_ow_music(world, player): +def generate_shuffled_ow_music(world: World): shuffled_ow_music = ow_music_value_data.copy() - if world.music_shuffle[player] == "consistent" or world.music_shuffle[player] == "full": - world.per_slot_randoms[player].shuffle(shuffled_ow_music) - elif world.music_shuffle[player] == "singularity": - single_song = world.per_slot_randoms[player].choice(shuffled_ow_music) + if world.options.music_shuffle == "consistent" or world.options.music_shuffle == "full": + world.random.shuffle(shuffled_ow_music) + elif world.options.music_shuffle == "singularity": + single_song = world.random.choice(shuffled_ow_music) shuffled_ow_music = [single_song for i in range(len(shuffled_ow_music))] return shuffled_ow_music -def generate_shuffled_ow_palettes(rom, world, player): - if world.overworld_palette_shuffle[player]: - for address, valid_palettes in valid_ow_palettes.items(): - chosen_palette = world.per_slot_randoms[player].choice(valid_palettes) - rom.write_byte(address, chosen_palette) +def generate_shuffled_ow_palettes(rom, world: World): + if world.options.overworld_palette_shuffle != "on_legacy": + return -def generate_shuffled_header_data(rom, world, player): - if world.music_shuffle[player] != "full" and not world.foreground_palette_shuffle[player] and not world.background_palette_shuffle[player]: + for address, valid_palettes in valid_ow_palettes.items(): + chosen_palette = world.random.choice(valid_palettes) + rom.write_byte(address, chosen_palette) + +def generate_shuffled_header_data(rom, world: World): + if world.options.music_shuffle != "full" and world.options.level_palette_shuffle != "on_legacy": return for level_id in range(0, 0x200): @@ -194,24 +650,425 @@ def generate_shuffled_header_data(rom, world, player): tileset = level_header[4] & 0x0F - if world.music_shuffle[player] == "full": + if world.options.music_shuffle == "full": level_header[2] &= 0x8F - level_header[2] |= (world.per_slot_randoms[player].randint(0, 7) << 5) + level_header[2] |= (world.random.randint(0, 7) << 5) - if (world.foreground_palette_shuffle[player] and tileset in valid_foreground_palettes): - level_header[3] &= 0xF8 - level_header[3] |= world.per_slot_randoms[player].choice(valid_foreground_palettes[tileset]) + if world.options.level_palette_shuffle == "on_legacy": + if tileset in valid_foreground_palettes: + level_header[3] &= 0xF8 + level_header[3] |= world.random.choice(valid_foreground_palettes[tileset]) - if world.background_palette_shuffle[player]: layer2_ptr_list = list(rom.read_bytes(0x2E600 + level_id * 3, 3)) layer2_ptr = (layer2_ptr_list[2] << 16 | layer2_ptr_list[1] << 8 | layer2_ptr_list[0]) if layer2_ptr in valid_background_palettes: level_header[0] &= 0x1F - level_header[0] |= (world.per_slot_randoms[player].choice(valid_background_palettes[layer2_ptr]) << 5) + level_header[0] |= (world.random.choice(valid_background_palettes[layer2_ptr]) << 5) if layer2_ptr in valid_background_colors: level_header[1] &= 0x1F - level_header[1] |= (world.per_slot_randoms[player].choice(valid_background_colors[layer2_ptr]) << 5) + level_header[1] |= (world.random.choice(valid_background_colors[layer2_ptr]) << 5) rom.write_bytes(layer1_ptr, bytes(level_header)) + +def generate_curated_level_palette_data(rom, world: World): + PALETTE_LEVEL_CODE_ADDR = 0x88000 + PALETTE_INDEX_ADDR = 0x8F000 + PALETTE_LEVEL_TILESET_ADDR = 0x8F200 + PALETTE_LEVEL_PTR_ADDR = 0x92000 + PALETTE_LEVEL_DATA_ADDR = 0xA8000 + + addr = pc_to_snes(PALETTE_LEVEL_PTR_ADDR) + snes_level_palette_pointers_1 = bytearray([0xBF, (addr)&0xFF, (addr>>8)&0xFF, (addr>>16)&0xFF]) + snes_level_palette_pointers_2 = bytearray([0xBF, (addr+2)&0xFF, (addr>>8)&0xFF, (addr>>16)&0xFF]) + + # Enable curated palette loader + rom.write_bytes(0x02BED, bytearray([0x5C, 0x00, 0x80, 0x11])) # org $00ABED : jml custom_palettes + rom.write_bytes(0x02330, bytearray([0x5C, 0x02, 0x80, 0x11])) # org $00A318 : jml custom_palettes_original + rom.write_bytes(0x013D7, bytearray([0x20, 0x30, 0xA3])) # org $0093D7 : jmp $A330 + rom.write_bytes(0x014DA, bytearray([0x20, 0x30, 0xA3])) # org $0094DA : jmp $A330 + rom.write_bytes(0x015EC, bytearray([0x20, 0x30, 0xA3])) # org $0095EC : jmp $A330 + rom.write_bytes(0x0165B, bytearray([0x20, 0x30, 0xA3])) # org $00965B : jmp $A330 + rom.write_bytes(0x02DD9, bytearray([0x20, 0x30, 0xA3])) # org $00ADD9 : jmp $A330 + rom.write_bytes(0x02E1F, bytearray([0x20, 0x30, 0xA3])) # org $00AE1F : jmp $A330 + + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0000, bytearray([0x80, 0x09])) # bra custom_palettes + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0002, bytearray([0xC2, 0x30])) # .original rep #$30 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0004, bytearray([0xA9, 0xDD, 0x7F])) # lda #$7FDD + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0007, bytearray([0x5C, 0xF2, 0xAB, 0x00])) # jml $00ABF2 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x000B, bytearray([0xC2, 0x30])) # custom_palettes: rep #$30 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x000D, bytearray([0xA9, 0x70, 0xB1])) # lda #$B170 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0010, bytearray([0x85, 0x0A])) # sta !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0012, bytearray([0x64, 0x0C])) # stz !_ptr+$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0014, bytearray([0xA9, 0x10, 0x00])) # lda.w #$0010 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0017, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0019, bytearray([0xA9, 0x07, 0x00])) # lda #$0007 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x001C, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x001E, bytearray([0xA9, 0x01, 0x00])) # lda #$0001 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0021, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0023, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0026, bytearray([0xAE, 0x0B, 0x01])) # .get_index ldx $010B + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0029, bytearray([0xBF, 0x00, 0xF2, 0x11])) # lda.l level_tilesets,x + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x002D, bytearray([0x29, 0xFF, 0x00])) # and #$00FF + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0030, bytearray([0xEB])) # xba + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0031, bytearray([0x85, 0x00])) # sta !_tileset + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0033, bytearray([0xBF, 0x00, 0xF0, 0x11])) # lda.l level_index,x + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0037, bytearray([0x29, 0xFF, 0x00])) # and #$00FF + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x003A, bytearray([0x05, 0x00])) # ora !_tileset + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x003C, bytearray([0x85, 0x0A])) # sta !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x003E, bytearray([0x0A])) # asl + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x003F, bytearray([0x18])) # clc + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0040, bytearray([0x65, 0x0A])) # adc !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0042, bytearray([0x85, 0x0E])) # sta !_num + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0044, bytearray([0xAA])) # tax + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0045, snes_level_palette_pointers_1) # .back_color lda.l palette_pointers,x + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0049, bytearray([0x85, 0x0A])) # sta !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x004B, snes_level_palette_pointers_2) # lda.l palette_pointers+$02,x + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x004F, bytearray([0x85, 0x0C])) # sta !_ptr+$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0051, bytearray([0xA7, 0x0A])) # lda [!_ptr] + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0053, bytearray([0x8D, 0x01, 0x07])) # sta $0701 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0056, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0058, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x005A, bytearray([0xA9, 0x02, 0x00])) # .background lda.w #$0001*$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x005D, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x005F, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0062, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0064, bytearray([0xA9, 0x01, 0x00])) # lda #$0001 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0067, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0069, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x006C, bytearray([0xA9, 0x42, 0x00])) # .foreground lda.w #$0021*$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x006F, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0071, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0074, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0076, bytearray([0xA9, 0x01, 0x00])) # lda #$0001 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0079, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x007B, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x007E, bytearray([0xA9, 0x52, 0x00])) # .berries lda.w #$0029*$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0081, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0083, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0086, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0088, bytearray([0xA9, 0x02, 0x00])) # lda #$0002 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x008B, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x008D, bytearray([0xA5, 0x0A])) # lda !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x008F, bytearray([0x48])) # pha + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0090, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0093, bytearray([0x68])) # pla + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0094, bytearray([0x85, 0x0A])) # sta !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0096, bytearray([0xA9, 0x32, 0x01])) # lda.w #$0099*$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0099, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x009B, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x009E, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00A0, bytearray([0xA9, 0x02, 0x00])) # lda #$0002 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00A3, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00A5, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00A8, bytearray([0xA9, 0x82, 0x00])) # .global lda.w #$0041*$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00AB, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00AD, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00B0, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00B2, bytearray([0xA9, 0x0B, 0x00])) # lda #$000B + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00B5, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00B7, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00BA, bytearray([0xA5, 0x00])) # .sprite_specific lda !_tileset + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00BC, bytearray([0xC9, 0x00, 0x05])) # cmp #$0500 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00BF, bytearray([0xD0, 0x1D])) # bne .end + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00C1, bytearray([0xAD, 0x2E, 0x19])) # lda $192E + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00C4, bytearray([0x29, 0x0F, 0x00])) # and #$000F + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00C7, bytearray([0xC9, 0x02, 0x00])) # cmp #$0002 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00CA, bytearray([0xD0, 0x12])) # bne .end + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00CC, bytearray([0xA9, 0xC2, 0x01])) # lda.w #$00E1*$02 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00CF, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00D1, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00D4, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00D6, bytearray([0xA9, 0x01, 0x00])) # lda #$0001 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00D9, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00DB, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00DE, bytearray([0xE2, 0x30])) # .end sep #$30 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00E0, bytearray([0x5C, 0xEC, 0xAC, 0x00])) # jml $00ACEC + + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00E4, bytearray([0xA6, 0x04])) # load_colors: ldx !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00E6, bytearray([0xA4, 0x06])) # ldy !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00E8, bytearray([0xA7, 0x0A])) # .x_loop lda [!_ptr] + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00EA, bytearray([0x9D, 0x03, 0x07])) # sta $0703,x + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00ED, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00EF, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F1, bytearray([0xE8])) # inx + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F2, bytearray([0xE8])) # inx + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F3, bytearray([0x88])) # dey + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F4, bytearray([0x10, 0xF2])) # bpl .x_loop + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F6, bytearray([0xA5, 0x04])) # lda !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F8, bytearray([0x18])) # clc + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F9, bytearray([0x69, 0x20, 0x00])) # adc #$0020 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00FC, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00FE, bytearray([0xC6, 0x08])) # dec !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0100, bytearray([0x10, 0xE2])) # bpl load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0102, bytearray([0x60])) # rts + + # Load palette paths + data = pkgutil.get_data(__name__, f"data/palettes/level/palettes.json").decode("utf-8") + tilesets = json.loads(data) + + # Writes the level tileset index to ROM + rom.write_bytes(PALETTE_LEVEL_TILESET_ADDR, bytearray(level_palette_index)) + + # Builds the table in ROM that holds the palette index for each level, including sublevels + for level_id in range(0x200): + tileset_num = level_palette_index[level_id] + if tileset_num != 0xFF: + tileset = tileset_names[tileset_num] + else: + tileset = tileset_names[0x19] + palette = world.random.randint(0, len(tilesets[tileset])-1) + rom.write_bytes(PALETTE_INDEX_ADDR + level_id, bytearray([palette])) + + # Writes the actual level palette data and pointer to said data to the ROM + pal_offset = 0x0000 + tileset_num = 0 + bank_palette_count = 0 + for tileset in tilesets.keys(): + for palette in range(len(tilesets[tileset])): + # Handle bank crossing + if bank_palette_count == 110: + pal_offset = (pal_offset & 0xF8000) + 0x8000 + bank_palette_count = 0 + # Write pointer + data_ptr = pc_to_snes(PALETTE_LEVEL_DATA_ADDR + pal_offset) + rom.write_bytes(PALETTE_LEVEL_PTR_ADDR + ((tileset_num*3)<<8) + (palette*3), bytearray([data_ptr & 0xFF, (data_ptr>>8)&0xFF, (data_ptr>>16)&0xFF])) + # Write data + rom.write_bytes(PALETTE_LEVEL_DATA_ADDR + pal_offset, read_palette_file(tileset, tilesets[tileset][palette], "level")) + pal_offset += 0x128 + bank_palette_count += 1 + tileset_num += 1 + + # Fix eaten berry tiles + EATEN_BERRY_ADDR = 0x68248 + rom.write_byte(EATEN_BERRY_ADDR + 0x01, 0x04) + rom.write_byte(EATEN_BERRY_ADDR + 0x03, 0x04) + rom.write_byte(EATEN_BERRY_ADDR + 0x05, 0x04) + rom.write_byte(EATEN_BERRY_ADDR + 0x07, 0x04) + + # Fix title screen changing background colors + rom.write_bytes(0x1D30, bytearray([0xEA, 0xEA, 0xEA])) + + # Skips level intros automatically + rom.write_byte(0x4896, 0x80) + +def generate_curated_map_palette_data(rom, world: World): + PALETTE_MAP_CODE_ADDR = 0x88200 + PALETTE_UPLOADER_EDIT = 0x88400 + PALETTE_MAP_INDEX_ADDR = 0x8F400 + PALETTE_MAP_PTR_ADDR = 0x90000 + PALETTE_MAP_DATA_ADDR = 0x98000 + + addr = pc_to_snes(PALETTE_MAP_PTR_ADDR) + snes_map_palette_pointers_1 = bytearray([0xBF, (addr)&0xFF, (addr>>8)&0xFF, (addr>>16)&0xFF]) + snes_map_palette_pointers_2 = bytearray([0xBF, (addr+2)&0xFF, (addr>>8)&0xFF, (addr>>16)&0xFF]) + + rom.write_bytes(0x02D25, bytearray([0x5C, 0x09, 0x82, 0x11])) # org $00AD25 : jml map_palettes + + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0000, bytearray([0xC2, 0x30])) # map_og_palettes: rep #$30 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0002, bytearray([0xA0, 0xD8, 0xB3])) # ldy #$B3D8 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0005, bytearray([0x5C, 0x2A, 0xAD, 0x00])) # jml $00AD2A + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0009, bytearray([0xC2, 0x30])) # map_palettes: rep #$30 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x000B, bytearray([0xAD, 0x31, 0x19])) # .prepare_index lda $1931 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x000E, bytearray([0x29, 0x0F, 0x00])) # and #$000F + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0011, bytearray([0x3A])) # dec + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0012, bytearray([0xAA])) # tax + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0013, bytearray([0xEB])) # xba + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0014, bytearray([0x85, 0x0E])) # sta !_num + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0016, bytearray([0xBF, 0x00, 0xF4, 0x11])) # lda.l map_index,x + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x001A, bytearray([0x29, 0xFF, 0x00])) # and #$00FF + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x001D, bytearray([0x05, 0x0E])) # ora !_num + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x001F, bytearray([0x85, 0x0A])) # sta !_ptr + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0021, bytearray([0x0A])) # asl + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0022, bytearray([0x18])) # clc + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0023, bytearray([0x65, 0x0A])) # adc !_ptr + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0025, bytearray([0xAA])) # tax + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0026, snes_map_palette_pointers_1) # lda.l map_palette_pointers,x + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x002A, bytearray([0x85, 0x0A])) # sta !_ptr + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x002C, snes_map_palette_pointers_2) # lda.l map_palette_pointers+$02,x + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0030, bytearray([0x85, 0x0C])) # sta !_ptr+$02 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0032, bytearray([0xA7, 0x0A])) # .load_back_color lda [!_ptr] + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0034, bytearray([0x8D, 0x01, 0x07])) # sta $0701 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0037, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0039, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x003B, bytearray([0xA9, 0x82, 0x00])) # .load_layer_2 lda.w #$0041*$02 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x003E, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0040, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0043, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0045, bytearray([0xA9, 0x03, 0x00])) # lda #$0003 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0048, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x004A, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x004D, bytearray([0xA9, 0x52, 0x00])) # .load_layer_1 lda.w #$0029*$02 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0050, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0052, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0055, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0057, bytearray([0xA9, 0x05, 0x00])) # lda #$0005 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x005A, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x005C, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x005F, bytearray([0xA9, 0x10, 0x00])) # .load_layer_3 lda.w #$0008*$02 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0062, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0064, bytearray([0xA9, 0x07, 0x00])) # lda #$0007 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0067, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0069, bytearray([0xA9, 0x01, 0x00])) # lda #$0001 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x006C, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x006E, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0071, bytearray([0xA9, 0x02, 0x01])) # .load_sprites lda.w #$0081*$02 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0074, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0076, bytearray([0xA9, 0x06, 0x00])) # lda #$0006 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0079, bytearray([0x85, 0x06])) # sta !_x_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x007B, bytearray([0xA9, 0x07, 0x00])) # lda #$0007 + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x007E, bytearray([0x85, 0x08])) # sta !_y_span + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0080, bytearray([0x20, 0xE4, 0x80])) # jsr load_colors + rom.write_bytes(PALETTE_MAP_CODE_ADDR + 0x0083, bytearray([0x5C, 0xA3, 0xAD, 0x00])) # .return jml $00ADA3 + + rom.write_bytes(0x2488, bytearray([0x5C, 0x00, 0x84, 0x11])) # org $00A488 : jml palette_upload + + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0000, bytearray([0xAD, 0x00, 0x01])) # palette_upload: lda $0100 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0003, bytearray([0xC9, 0x0E])) # cmp #$0E + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0005, bytearray([0xF0, 0x0A])) # beq .map + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0007, bytearray([0xAC, 0x80, 0x06])) # .regular ldy $0680 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x000A, bytearray([0xBE, 0x81, 0xA4])) # ldx.w $A47F+2,y + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x000D, bytearray([0x5C, 0x8E, 0xA4, 0x00])) # jml $00A48E + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0011, bytearray([0xAD, 0xD9, 0x13])) # .map lda $13D9 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0014, bytearray([0xC9, 0x0A])) # cmp #$0A + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0016, bytearray([0xD0, 0xEF])) # bne .regular + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0018, bytearray([0xAD, 0xE8, 0x1D])) # lda $1DE8 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x001B, bytearray([0xC9, 0x06])) # cmp #$06 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x001D, bytearray([0xD0, 0xE8])) # bne .regular + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x001F, bytearray([0x9C, 0x03, 0x07])) # stz $0703 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0022, bytearray([0x9C, 0x04, 0x07])) # stz $0704 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0025, bytearray([0x9C, 0x21, 0x21])) # stz $2121 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0028, bytearray([0xA2, 0x06])) # ldx #$06 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x002A, bytearray([0xBD, 0x49, 0x92])) # .loop lda.w $9249,x + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x002D, bytearray([0x9D, 0x20, 0x43])) # sta $4320,x + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0030, bytearray([0xCA])) # dex + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0031, bytearray([0x10, 0xF7])) # bpl .loop + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0033, bytearray([0xA9, 0x04])) # lda #$04 + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0035, bytearray([0x8D, 0x0B, 0x42])) # sta $420B + rom.write_bytes(PALETTE_UPLOADER_EDIT + 0x0038, bytearray([0x5C, 0xCF, 0xA4, 0x00])) # jml $00A4CF + + # Insert this piece of ASM again in case levels are disabled + PALETTE_LEVEL_CODE_ADDR = 0x88000 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00E4, bytearray([0xA6, 0x04])) # load_colors: ldx !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00E6, bytearray([0xA4, 0x06])) # ldy !_x_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00E8, bytearray([0xA7, 0x0A])) # .x_loop lda [!_ptr] + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00EA, bytearray([0x9D, 0x03, 0x07])) # sta $0703,x + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00ED, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00EF, bytearray([0xE6, 0x0A])) # inc !_ptr + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F1, bytearray([0xE8])) # inx + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F2, bytearray([0xE8])) # inx + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F3, bytearray([0x88])) # dey + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F4, bytearray([0x10, 0xF2])) # bpl .x_loop + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F6, bytearray([0xA5, 0x04])) # lda !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F8, bytearray([0x18])) # clc + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00F9, bytearray([0x69, 0x20, 0x00])) # adc #$0020 + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00FC, bytearray([0x85, 0x04])) # sta !_index + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x00FE, bytearray([0xC6, 0x08])) # dec !_y_span + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0100, bytearray([0x10, 0xE2])) # bpl load_colors + rom.write_bytes(PALETTE_LEVEL_CODE_ADDR + 0x0102, bytearray([0x60])) # rts + + # Load palette paths + data = pkgutil.get_data(__name__, f"data/palettes/map/palettes.json").decode("utf-8") + maps = json.loads(data) + + for map_id in range(0x07): + current_map_name = map_names[map_id] + palette = world.random.randint(0, len(maps[current_map_name])-1) + rom.write_bytes(PALETTE_MAP_INDEX_ADDR + map_id, bytearray([palette])) + + # Writes the actual map palette data and pointer to said data to the ROM + pal_offset = 0x0000 + map_num = 0 + bank_palette_count = 0 + for current_map in maps.keys(): + for palette in range(len(maps[current_map])): + # Handle bank crossing + if bank_palette_count == 113: + pal_offset = (pal_offset & 0xF8000) + 0x8000 + bank_palette_count = 0 + # Write pointer + data_ptr = pc_to_snes(PALETTE_MAP_DATA_ADDR + pal_offset) + rom.write_bytes(PALETTE_MAP_PTR_ADDR + ((map_num*3)<<8) + (palette*3), bytearray([data_ptr & 0xFF, (data_ptr>>8)&0xFF, (data_ptr>>16)&0xFF])) + # Write data + rom.write_bytes(PALETTE_MAP_DATA_ADDR + pal_offset, read_palette_file(current_map, maps[current_map][palette], "map")) + # Update map mario palette + chosen_palette = world.options.mario_palette.value + rom.write_bytes(PALETTE_MAP_DATA_ADDR + pal_offset + 206, bytes(ow_mario_palettes[chosen_palette])) + pal_offset += 0x11C + bank_palette_count += 1 + map_num += 1 + + +def pc_to_snes(address): + return ((address << 1) & 0x7F0000) | (address & 0x7FFF) | 0x8000 + +def read_palette_file(tileset, filename, type_): + palette_file = pkgutil.get_data(__name__, f"data/palettes/{type_}/{tileset}/{filename}") + colors = bytearray([]) + + # Copy back colors + colors += bytearray([palette_file[0x200], palette_file[0x201]]) + + if type_ == "level": + # Copy background colors + colors += bytearray([palette_file[(0x01*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x11*2)+(i)] for i in range(14)]) + + # Copy foreground colors + colors += bytearray([palette_file[(0x21*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x31*2)+(i)] for i in range(14)]) + + # Copy berry colors + colors += bytearray([palette_file[(0x29*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x39*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x49*2)+(i)] for i in range(14)]) + + # Copy global colors + colors += bytearray([palette_file[(0x41*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x51*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x61*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x71*2)+(i)] for i in range(14)]) + + # Copy sprite colors + colors += bytearray([palette_file[(0x81*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x91*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xA1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xB1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xC1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xD1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xE1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xF1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xE9*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xF9*2)+(i)] for i in range(14)]) + + elif type_ == "map": + # Copy layer 2 colors + colors += bytearray([palette_file[(0x41*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x51*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x61*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x71*2)+(i)] for i in range(14)]) + + # Copy layer 1 colors + colors += bytearray([palette_file[(0x29*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x39*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x49*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x59*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x69*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x79*2)+(i)] for i in range(14)]) + + # Copy layer 3 colors + colors += bytearray([palette_file[(0x08*2)+(i)] for i in range(16)]) + colors += bytearray([palette_file[(0x18*2)+(i)] for i in range(16)]) + + # Copy sprite colors + colors += bytearray([palette_file[(0x81*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0x91*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xA1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xB1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xC1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xD1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xE1*2)+(i)] for i in range(14)]) + colors += bytearray([palette_file[(0xF1*2)+(i)] for i in range(14)]) + + return colors diff --git a/worlds/smw/Client.py b/worlds/smw/Client.py index 50899abe37..eb9b4ec3d3 100644 --- a/worlds/smw/Client.py +++ b/worlds/smw/Client.py @@ -1,5 +1,4 @@ import logging -import asyncio import time from NetUtils import ClientStatus, color @@ -17,11 +16,19 @@ SRAM_START = 0xE00000 SMW_ROMHASH_START = 0x7FC0 ROMHASH_SIZE = 0x15 -SMW_PROGRESS_DATA = WRAM_START + 0x1F02 -SMW_DRAGON_COINS_DATA = WRAM_START + 0x1F2F -SMW_PATH_DATA = WRAM_START + 0x1EA2 -SMW_EVENT_ROM_DATA = ROM_START + 0x2D608 -SMW_ACTIVE_LEVEL_DATA = ROM_START + 0x37F70 +SMW_PROGRESS_DATA = WRAM_START + 0x1F02 +SMW_DRAGON_COINS_DATA = WRAM_START + 0x1F2F +SMW_PATH_DATA = WRAM_START + 0x1EA2 +SMW_EVENT_ROM_DATA = ROM_START + 0x2D608 +SMW_ACTIVE_LEVEL_DATA = ROM_START + 0x37F70 +SMW_MOON_DATA = WRAM_START + 0x1FEE +SMW_HIDDEN_1UP_DATA = WRAM_START + 0x1F3C +SMW_BONUS_BLOCK_DATA = WRAM_START + 0x1A000 +SMW_BLOCKSANITY_DATA = WRAM_START + 0x1A400 +SMW_BLOCKSANITY_FLAGS = WRAM_START + 0x1A010 +SMW_LEVEL_CLEAR_FLAGS = WRAM_START + 0x1A200 +SMW_SPECIAL_WORLD_CLEAR = WRAM_START + 0x1F1E + SMW_GOAL_DATA = ROM_START + 0x01BFA0 SMW_REQUIRED_BOSSES_DATA = ROM_START + 0x01BFA1 @@ -31,22 +38,32 @@ SMW_RECEIVE_MSG_DATA = ROM_START + 0x01BFA4 SMW_DEATH_LINK_ACTIVE_ADDR = ROM_START + 0x01BFA5 SMW_DRAGON_COINS_ACTIVE_ADDR = ROM_START + 0x01BFA6 SMW_SWAMP_DONUT_GH_ADDR = ROM_START + 0x01BFA7 +SMW_MOON_ACTIVE_ADDR = ROM_START + 0x01BFA8 +SMW_HIDDEN_1UP_ACTIVE_ADDR = ROM_START + 0x01BFA9 +SMW_BONUS_BLOCK_ACTIVE_ADDR = ROM_START + 0x01BFAA +SMW_BLOCKSANITY_ACTIVE_ADDR = ROM_START + 0x01BFAB -SMW_GAME_STATE_ADDR = WRAM_START + 0x100 -SMW_MARIO_STATE_ADDR = WRAM_START + 0x71 -SMW_BOSS_STATE_ADDR = WRAM_START + 0xD9B -SMW_ACTIVE_BOSS_ADDR = WRAM_START + 0x13FC -SMW_CURRENT_LEVEL_ADDR = WRAM_START + 0x13BF -SMW_MESSAGE_BOX_ADDR = WRAM_START + 0x1426 -SMW_BONUS_STAR_ADDR = WRAM_START + 0xF48 -SMW_EGG_COUNT_ADDR = WRAM_START + 0x1F24 -SMW_BOSS_COUNT_ADDR = WRAM_START + 0x1F26 -SMW_NUM_EVENTS_ADDR = WRAM_START + 0x1F2E -SMW_SFX_ADDR = WRAM_START + 0x1DFC -SMW_PAUSE_ADDR = WRAM_START + 0x13D4 -SMW_MESSAGE_QUEUE_ADDR = WRAM_START + 0xC391 -SMW_RECV_PROGRESS_ADDR = WRAM_START + 0x1F2B +SMW_GAME_STATE_ADDR = WRAM_START + 0x100 +SMW_MARIO_STATE_ADDR = WRAM_START + 0x71 +SMW_BOSS_STATE_ADDR = WRAM_START + 0xD9B +SMW_ACTIVE_BOSS_ADDR = WRAM_START + 0x13FC +SMW_CURRENT_LEVEL_ADDR = WRAM_START + 0x13BF +SMW_CURRENT_SUBLEVEL_ADDR = WRAM_START + 0x10B +SMW_MESSAGE_BOX_ADDR = WRAM_START + 0x1426 +SMW_BONUS_STAR_ADDR = WRAM_START + 0xF48 +SMW_EGG_COUNT_ADDR = WRAM_START + 0x1F24 +SMW_BOSS_COUNT_ADDR = WRAM_START + 0x1F26 +SMW_NUM_EVENTS_ADDR = WRAM_START + 0x1F2E +SMW_SFX_ADDR = WRAM_START + 0x1DFC +SMW_PAUSE_ADDR = WRAM_START + 0x13D4 +SMW_MESSAGE_QUEUE_ADDR = WRAM_START + 0xC391 +SMW_ACTIVE_THWIMP_ADDR = WRAM_START + 0x0F3C +SMW_GOAL_ITEM_COUNT = WRAM_START + 0x1A01E + +SMW_RECV_PROGRESS_ADDR = WRAM_START + 0x01F2B + +SMW_BLOCKSANITY_BLOCK_COUNT = 582 SMW_GOAL_LEVELS = [0x28, 0x31, 0x32] SMW_INVALID_MARIO_STATES = [0x05, 0x06, 0x0A, 0x0C, 0x0D] @@ -115,6 +132,9 @@ class SMWSNIClient(SNIClient): if death_link: await ctx.update_death_link(bool(death_link[0] & 0b1)) + if ctx.rom != rom_name: + ctx.current_sublevel_value = 0 + ctx.rom = rom_name return True @@ -176,6 +196,11 @@ class SMWSNIClient(SNIClient): self.trap_queue.append((trap_item, trap_msg)) + def should_show_message(self, ctx, next_item): + return ctx.receive_option == 1 or \ + (ctx.receive_option == 2 and ((next_item.flags & 1) != 0)) or \ + (ctx.receive_option == 3 and ((next_item.flags & 1) != 0 and next_item.item != 0xBC0002)) + async def handle_trap_queue(self, ctx): from SNIClient import snes_buffered_write, snes_flush_writes, snes_read @@ -217,6 +242,13 @@ class SMWSNIClient(SNIClient): self.add_trap_to_queue(next_trap, message) return else: + if next_trap.item == 0xBC001D: + # Special case thwimp trap + # Do not fire if the previous thwimp hasn't reached the player's Y pos + active_thwimp = await snes_read(ctx, SMW_ACTIVE_THWIMP_ADDR, 0x1) + if active_thwimp[0] != 0xFF: + self.add_trap_to_queue(next_trap, message) + return verify_game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) if verify_game_state[0] == 0x14 and len(trap_rom_data[next_trap.item]) > 2: snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([trap_rom_data[next_trap.item][2]])) @@ -236,13 +268,14 @@ class SMWSNIClient(SNIClient): if active_boss[0] != 0x00: return - if ctx.receive_option == 1 or (ctx.receive_option == 2 and ((next_trap.flags & 1) != 0)): + if self.should_show_message(ctx, next_trap): self.add_message_to_queue(message) async def game_watcher(self, ctx): from SNIClient import snes_buffered_write, snes_flush_writes, snes_read - + + boss_state = await snes_read(ctx, SMW_BOSS_STATE_ADDR, 0x1) game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) mario_state = await snes_read(ctx, SMW_MARIO_STATE_ADDR, 0x1) if game_state is None: @@ -259,6 +292,7 @@ class SMWSNIClient(SNIClient): elif game_state[0] < 0x0B: # We haven't loaded a save file ctx.message_queue = [] + ctx.current_sublevel_value = 0 return elif mario_state[0] in SMW_INVALID_MARIO_STATES: # Mario can't come to the phone right now @@ -304,8 +338,18 @@ class SMWSNIClient(SNIClient): progress_data = bytearray(await snes_read(ctx, SMW_PROGRESS_DATA, 0x0F)) dragon_coins_data = bytearray(await snes_read(ctx, SMW_DRAGON_COINS_DATA, 0x0C)) dragon_coins_active = await snes_read(ctx, SMW_DRAGON_COINS_ACTIVE_ADDR, 0x1) - from worlds.smw.Rom import item_rom_data, ability_rom_data, trap_rom_data - from worlds.smw.Levels import location_id_to_level_id, level_info_dict + moon_data = bytearray(await snes_read(ctx, SMW_MOON_DATA, 0x0C)) + moon_active = await snes_read(ctx, SMW_MOON_ACTIVE_ADDR, 0x1) + hidden_1up_data = bytearray(await snes_read(ctx, SMW_HIDDEN_1UP_DATA, 0x0C)) + hidden_1up_active = await snes_read(ctx, SMW_HIDDEN_1UP_ACTIVE_ADDR, 0x1) + bonus_block_data = bytearray(await snes_read(ctx, SMW_BONUS_BLOCK_DATA, 0x0C)) + bonus_block_active = await snes_read(ctx, SMW_BONUS_BLOCK_ACTIVE_ADDR, 0x1) + blocksanity_data = bytearray(await snes_read(ctx, SMW_BLOCKSANITY_DATA, SMW_BLOCKSANITY_BLOCK_COUNT)) + blocksanity_flags = bytearray(await snes_read(ctx, SMW_BLOCKSANITY_FLAGS, 0xC)) + blocksanity_active = await snes_read(ctx, SMW_BLOCKSANITY_ACTIVE_ADDR, 0x1) + level_clear_flags = bytearray(await snes_read(ctx, SMW_LEVEL_CLEAR_FLAGS, 0x60)) + from worlds.smw.Rom import item_rom_data, ability_rom_data, trap_rom_data, icon_rom_data + from worlds.smw.Levels import location_id_to_level_id, level_info_dict, level_blocks_data from worlds import AutoWorldRegister for loc_name, level_data in location_id_to_level_id.items(): loc_id = AutoWorldRegister.world_types[ctx.game].location_name_to_id[loc_name] @@ -327,6 +371,54 @@ class SMWSNIClient(SNIClient): if bit_set: new_checks.append(loc_id) + elif level_data[1] == 3: + # Moon Check + if not moon_active or moon_active[0] == 0: + continue + + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = moon_data[progress_byte] + masked_data = data & (1 << progress_bit) + bit_set = (masked_data != 0) + + if bit_set: + new_checks.append(loc_id) + elif level_data[1] == 4: + # Hidden 1-Up Check + if not hidden_1up_active or hidden_1up_active[0] == 0: + continue + + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = hidden_1up_data[progress_byte] + masked_data = data & (1 << progress_bit) + bit_set = (masked_data != 0) + + if bit_set: + new_checks.append(loc_id) + elif level_data[1] == 5: + # Bonus Block Check + if not bonus_block_active or bonus_block_active[0] == 0: + continue + + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = bonus_block_data[progress_byte] + masked_data = data & (1 << progress_bit) + bit_set = (masked_data != 0) + + if bit_set: + new_checks.append(loc_id) + elif level_data[1] >= 100: + if not blocksanity_active or blocksanity_active[0] == 0: + continue + block_index = level_data[1] - 100 + if blocksanity_data[block_index] != 0: + new_checks.append(loc_id) else: event_id_value = event_id + level_data[1] @@ -360,12 +452,48 @@ class SMWSNIClient(SNIClient): f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}]) + # Send Current Room for Tracker + current_sublevel_data = await snes_read(ctx, SMW_CURRENT_SUBLEVEL_ADDR, 2) + current_sublevel_value = current_sublevel_data[0] + (current_sublevel_data[1] << 8) + + if game_state[0] != 0x14: + current_sublevel_value = 0 + + if ctx.current_sublevel_value != current_sublevel_value: + ctx.current_sublevel_value = current_sublevel_value + + # Send level id data to tracker + await ctx.send_msgs( + [ + { + "cmd": "Set", + "key": f"smw_curlevelid_{ctx.team}_{ctx.slot}", + "default": 0, + "want_reply": False, + "operations": [ + { + "operation": "replace", + "value": ctx.current_sublevel_value, + } + ], + } + ] + ) + if game_state[0] != 0x14: # Don't receive items or collect locations outside of in-level mode + ctx.current_sublevel_value = 0 + return + + if boss_state[0] in SMW_BOSS_STATES: + # Don't receive items or collect locations inside boss battles return - recv_count = await snes_read(ctx, SMW_RECV_PROGRESS_ADDR, 1) - recv_index = recv_count[0] + recv_count = await snes_read(ctx, SMW_RECV_PROGRESS_ADDR, 2) + if recv_count is None: + # Add a small failsafe in case we get a None. Other SNI games do this... + return + recv_index = recv_count[0] | (recv_count[1] << 8) if recv_index < len(ctx.items_received): item = ctx.items_received[recv_index] @@ -375,7 +503,7 @@ class SMWSNIClient(SNIClient): color(ctx.player_names[item.player], 'yellow'), ctx.location_names[item.location], recv_index, len(ctx.items_received))) - if ctx.receive_option == 1 or (ctx.receive_option == 2 and ((item.flags & 1) != 0)): + if self.should_show_message(ctx, item): if item.item != 0xBC0012 and item.item not in trap_rom_data: # Don't send messages for Boss Tokens item_name = ctx.item_names[item.item] @@ -384,7 +512,7 @@ class SMWSNIClient(SNIClient): receive_message = generate_received_text(item_name, player_name) self.add_message_to_queue(receive_message) - snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index])) + snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index&0xFF, (recv_index>>8)&0xFF])) if item.item in trap_rom_data: item_name = ctx.item_names[item.item] player_name = ctx.player_names[item.player] @@ -405,6 +533,15 @@ class SMWSNIClient(SNIClient): snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([item_rom_data[item.item][2]])) snes_buffered_write(ctx, WRAM_START + item_rom_data[item.item][0], bytes([new_item_count])) + elif item.item in icon_rom_data: + queue_addr = await snes_read(ctx, WRAM_START + icon_rom_data[item.item][0], 2) + queue_addr = queue_addr[0] + (queue_addr[1] << 8) + queue_addr += 1 + snes_buffered_write(ctx, WRAM_START + icon_rom_data[item.item][0], bytes([queue_addr&0xFF, (queue_addr>>8)&0xFF])) + if (goal[0] == 0 and item.item == 0xBC0012) or (goal[0] == 1 and item.item == 0xBC0002): + goal_item_count = await snes_read(ctx, SMW_GOAL_ITEM_COUNT, 1) + snes_buffered_write(ctx, SMW_GOAL_ITEM_COUNT, bytes([goal_item_count[0] + 1])) + elif item.item in ability_rom_data: # Handle Upgrades for rom_data in ability_rom_data[item.item]: @@ -449,6 +586,12 @@ class SMWSNIClient(SNIClient): path_data = bytearray(await snes_read(ctx, SMW_PATH_DATA, 0x60)) donut_gh_swapped = await snes_read(ctx, SMW_SWAMP_DONUT_GH_ADDR, 0x1) new_dragon_coin = False + new_moon = False + new_hidden_1up = False + new_bonus_block = False + new_blocksanity = False + new_blocksanity_flags = False + for loc_id in ctx.checked_locations: if loc_id not in ctx.locations_checked: ctx.locations_checked.add(loc_id) @@ -470,10 +613,64 @@ class SMWSNIClient(SNIClient): dragon_coins_data[progress_byte] = new_data new_dragon_coin = True + elif level_data[1] == 3: + # Moon Check + + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = moon_data[progress_byte] + new_data = data | (1 << progress_bit) + moon_data[progress_byte] = new_data + + new_moon = True + elif level_data[1] == 4: + # Hidden 1-Up Check + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = hidden_1up_data[progress_byte] + new_data = data | (1 << progress_bit) + hidden_1up_data[progress_byte] = new_data + + new_hidden_1up = True + elif level_data[1] == 5: + # Bonus block prize Check + + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = bonus_block_data[progress_byte] + new_data = data | (1 << progress_bit) + bonus_block_data[progress_byte] = new_data + + new_bonus_block = True + elif level_data[1] >= 100: + # Blocksanity flag Check + block_index = level_data[1] - 100 + blocksanity_data[block_index] = 1 + new_blocksanity = True + + # All blocksanity blocks flag + new_blocksanity_flags = True + for block_id in level_blocks_data[level_data[0]]: + if blocksanity_data[block_id] != 1: + new_blocksanity_flags = False + continue + if new_blocksanity_flags is True: + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + data = blocksanity_flags[progress_byte] + new_data = data | (1 << progress_bit) + blocksanity_flags[progress_byte] = new_data else: if level_data[0] in SMW_UNCOLLECTABLE_LEVELS: continue + # Handle map indicators + flag = 1 if level_data[1] == 0 else 2 + level_clear_flags[level_data[0]] |= flag + event_id = event_data[level_data[0]] event_id_value = event_id + level_data[1] @@ -514,7 +711,18 @@ class SMWSNIClient(SNIClient): if new_dragon_coin: snes_buffered_write(ctx, SMW_DRAGON_COINS_DATA, bytes(dragon_coins_data)) + if new_moon: + snes_buffered_write(ctx, SMW_MOON_DATA, bytes(moon_data)) + if new_hidden_1up: + snes_buffered_write(ctx, SMW_HIDDEN_1UP_DATA, bytes(hidden_1up_data)) + if new_bonus_block: + snes_buffered_write(ctx, SMW_BONUS_BLOCK_DATA, bytes(bonus_block_data)) + if new_blocksanity: + snes_buffered_write(ctx, SMW_BLOCKSANITY_DATA, bytes(blocksanity_data)) + if new_blocksanity_flags: + snes_buffered_write(ctx, SMW_BLOCKSANITY_FLAGS, bytes(blocksanity_flags)) if new_events > 0: + snes_buffered_write(ctx, SMW_LEVEL_CLEAR_FLAGS, bytes(level_clear_flags)) snes_buffered_write(ctx, SMW_PROGRESS_DATA, bytes(progress_data)) snes_buffered_write(ctx, SMW_PATH_DATA, bytes(path_data)) old_events = await snes_read(ctx, SMW_NUM_EVENTS_ADDR, 0x1) diff --git a/worlds/smw/Items.py b/worlds/smw/Items.py index 5b6cce5a7f..eaf58b9b8e 100644 --- a/worlds/smw/Items.py +++ b/worlds/smw/Items.py @@ -18,6 +18,10 @@ class SMWItem(Item): # Separate tables for each type of item. junk_table = { + ItemName.one_coin: ItemData(0xBC0017, False), + ItemName.five_coins: ItemData(0xBC0018, False), + ItemName.ten_coins: ItemData(0xBC0019, False), + ItemName.fifty_coins: ItemData(0xBC001A, False), ItemName.one_up_mushroom: ItemData(0xBC0001, False), } @@ -36,6 +40,7 @@ upgrade_table = { ItemName.progressive_powerup: ItemData(0xBC000A, True), ItemName.p_balloon: ItemData(0xBC000B, True), ItemName.super_star_active: ItemData(0xBC000D, True), + ItemName.special_world_clear: ItemData(0xBC001B, True), } switch_palace_table = { @@ -46,10 +51,12 @@ switch_palace_table = { } trap_table = { - ItemName.ice_trap: ItemData(0xBC0013, False, True), - ItemName.stun_trap: ItemData(0xBC0014, False, True), - ItemName.literature_trap: ItemData(0xBC0015, False, True), - ItemName.timer_trap: ItemData(0xBC0016, False, True), + ItemName.ice_trap: ItemData(0xBC0013, False, True), + ItemName.stun_trap: ItemData(0xBC0014, False, True), + ItemName.literature_trap: ItemData(0xBC0015, False, True), + ItemName.timer_trap: ItemData(0xBC0016, False, True), + ItemName.reverse_controls_trap: ItemData(0xBC001C, False, True), + ItemName.thwimp_trap: ItemData(0xBC001D, False, True), } event_table = { diff --git a/worlds/smw/Levels.py b/worlds/smw/Levels.py index 3940a08c7c..7aa9428b91 100644 --- a/worlds/smw/Levels.py +++ b/worlds/smw/Levels.py @@ -1,4 +1,5 @@ +from worlds.AutoWorld import World from .Names import LocationName @@ -75,6 +76,103 @@ ow_boss_rooms = [ ] +level_blocks_data = { + 0x01: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + 0x02: [12, 13], + 0x04: [14, 15, 16, 17, 18, 19], + 0x05: [20, 21, 22, 23, 24, 25], + 0x06: [26, 27, 28, 29], + 0x07: [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], + 0x09: [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], + 0x0A: [50, 51, 52, 53, 54, 55, 56, 57, 58, 59], + 0x0B: [60, 61, 62], + 0x0C: [63, 64, 65, 66, 67, 68], + 0x0D: [69, 70, 71], + 0x0E: [72], + 0x0F: [73, 74, 75, 76], + 0x10: [77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, + 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, + 109, 110, 111 + ], + 0x11: [112], + 0x13: [113, 114, 115, 116, 117], + 0x15: [118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 135, 136, 137, 138, 139, 140 + ], + 0x18: [141, 142], + 0x1A: [143, 144, 145], + 0x1B: [146, 147, 148, 149, 150], + 0x1C: [151, 152, 153, 154], + 0x1D: [155, 156, 157], + 0x1F: [158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168], + 0x20: [169], + 0x21: [170, 171, 172], + 0x22: [173, 174, 175, 176, 177], + 0x23: [178, 179, 180, 181, 182, 183, 184, 185, 186], + 0x24: [187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, + 201, 202 + ], + 0x25: [203, 204, 205, 206, 207, 208], + 0x26: [209, 210, 211, 212], + 0x27: [213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, + 227, 228, 229 + ], + 0x29: [230, 231, 232, 233], + 0x2A: [234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, + 248, 249 + ], + 0x2B: [250, 251, 252, 253, 254], + 0x2D: [255, 256, 257, 258, 259, 260, 261, 262], + 0x2E: [263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, + 277, 278, 279 + ], + 0x2F: [280, 281, 282, 283, 284], + 0x33: [285, 286, 287, 288, 289, 290], + 0x34: [291, 292, 293], + 0x35: [294, 295], + 0x37: [296, 297], + 0x38: [298, 299, 300, 301], + 0x39: [302, 303, 304, 305], + 0x3A: [306, 307, 308, 309, 310, 311, 312, 313, 314], + 0x3B: [315, 316], + 0x3C: [317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330], + 0x3D: [331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341], + 0x3E: [342, 343, 344, 345, 346, 347, 348, 349, 350, 351], + 0x40: [352, 353, 354, 355, 356], + 0x41: [357, 358, 359, 360, 361], + 0x42: [362, 363, 364, 365, 366], + 0x43: [367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379], + 0x44: [380, 381, 382, 383, 384, 385, 386], + 0x46: [387, 388, 389], + 0x47: [390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, + 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416 + ], + 0x49: [417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, + 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, + 444, 445, 446 + ], + 0x4A: [447, 448, 449, 450, 451], + 0x4B: [452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, + 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, + 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489 + ], + 0x4C: [490], + 0x4E: [491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, + 505, 506, 507, 508, 509, 510, 511, 512 + ], + 0x4F: [513, 514, 515, 516, 517, 518, 519, 520, 521, 522], + 0x50: [523, 524, 525], + 0x51: [526, 527], + 0x54: [528], + 0x56: [529], + 0x59: [530, 531, 532, 533, 534, 535, 536, 537, 538], + 0x5A: [539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, + 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, + 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, + 579, 580, 581 + ] +} + class SMWPath(): thisEndDirection: int otherLevelID: int @@ -330,12 +428,15 @@ switch_palace_levels = [ location_id_to_level_id = { LocationName.yoshis_island_1_exit_1: [0x29, 0], LocationName.yoshis_island_1_dragon: [0x29, 2], + LocationName.yoshis_island_1_moon: [0x29, 3], LocationName.yoshis_island_2_exit_1: [0x2A, 0], LocationName.yoshis_island_2_dragon: [0x2A, 2], LocationName.yoshis_island_3_exit_1: [0x27, 0], LocationName.yoshis_island_3_dragon: [0x27, 2], + LocationName.yoshis_island_3_bonus_block: [0x27, 5], LocationName.yoshis_island_4_exit_1: [0x26, 0], LocationName.yoshis_island_4_dragon: [0x26, 2], + LocationName.yoshis_island_4_hidden_1up: [0x26, 4], LocationName.yoshis_island_castle: [0x25, 0], LocationName.yoshis_island_koopaling: [0x25, 0], LocationName.yellow_switch_palace: [0x14, 0], @@ -343,13 +444,17 @@ location_id_to_level_id = { LocationName.donut_plains_1_exit_1: [0x15, 0], LocationName.donut_plains_1_exit_2: [0x15, 1], LocationName.donut_plains_1_dragon: [0x15, 2], + LocationName.donut_plains_1_hidden_1up: [0x15, 4], LocationName.donut_plains_2_exit_1: [0x09, 0], LocationName.donut_plains_2_exit_2: [0x09, 1], LocationName.donut_plains_2_dragon: [0x09, 2], LocationName.donut_plains_3_exit_1: [0x05, 0], LocationName.donut_plains_3_dragon: [0x05, 2], + LocationName.donut_plains_3_bonus_block: [0x05, 5], LocationName.donut_plains_4_exit_1: [0x06, 0], LocationName.donut_plains_4_dragon: [0x06, 2], + LocationName.donut_plains_4_moon: [0x06, 3], + LocationName.donut_plains_4_hidden_1up: [0x06, 4], LocationName.donut_secret_1_exit_1: [0x0A, 0], LocationName.donut_secret_1_exit_2: [0x0A, 1], LocationName.donut_secret_1_dragon: [0x0A, 2], @@ -360,6 +465,7 @@ location_id_to_level_id = { LocationName.donut_secret_house_exit_1: [0x13, 0], LocationName.donut_secret_house_exit_2: [0x13, 1], LocationName.donut_plains_castle: [0x07, 0], + LocationName.donut_plains_castle_hidden_1up: [0x07, 4], LocationName.donut_plains_koopaling: [0x07, 0], LocationName.green_switch_palace: [0x08, 0], @@ -371,8 +477,10 @@ location_id_to_level_id = { LocationName.vanilla_dome_2_dragon: [0x3C, 2], LocationName.vanilla_dome_3_exit_1: [0x2E, 0], LocationName.vanilla_dome_3_dragon: [0x2E, 2], + LocationName.vanilla_dome_3_moon: [0x2E, 3], LocationName.vanilla_dome_4_exit_1: [0x3D, 0], LocationName.vanilla_dome_4_dragon: [0x3D, 2], + LocationName.vanilla_dome_4_hidden_1up: [0x3D, 4], LocationName.vanilla_secret_1_exit_1: [0x2D, 0], LocationName.vanilla_secret_1_exit_2: [0x2D, 1], LocationName.vanilla_secret_1_dragon: [0x2D, 2], @@ -382,7 +490,9 @@ location_id_to_level_id = { LocationName.vanilla_secret_3_dragon: [0x02, 2], LocationName.vanilla_ghost_house_exit_1: [0x2B, 0], LocationName.vanilla_ghost_house_dragon: [0x2B, 2], + LocationName.vanilla_ghost_house_hidden_1up: [0x2B, 4], LocationName.vanilla_fortress: [0x0B, 0], + LocationName.vanilla_fortress_hidden_1up: [0x0B, 4], LocationName.vanilla_reznor: [0x0B, 0], LocationName.vanilla_dome_castle: [0x40, 0], LocationName.vanilla_dome_koopaling: [0x40, 0], @@ -390,13 +500,16 @@ location_id_to_level_id = { LocationName.butter_bridge_1_exit_1: [0x0C, 0], LocationName.butter_bridge_1_dragon: [0x0C, 2], + LocationName.butter_bridge_1_bonus_block: [0x0C, 5], LocationName.butter_bridge_2_exit_1: [0x0D, 0], LocationName.butter_bridge_2_dragon: [0x0D, 2], LocationName.cheese_bridge_exit_1: [0x0F, 0], LocationName.cheese_bridge_exit_2: [0x0F, 1], LocationName.cheese_bridge_dragon: [0x0F, 2], + LocationName.cheese_bridge_moon: [0x0F, 3], LocationName.cookie_mountain_exit_1: [0x10, 0], LocationName.cookie_mountain_dragon: [0x10, 2], + LocationName.cookie_mountain_hidden_1up: [0x10, 4], LocationName.soda_lake_exit_1: [0x11, 0], LocationName.soda_lake_dragon: [0x11, 2], LocationName.twin_bridges_castle: [0x0E, 0], @@ -410,12 +523,14 @@ location_id_to_level_id = { LocationName.forest_of_illusion_3_exit_1: [0x47, 0], LocationName.forest_of_illusion_3_exit_2: [0x47, 1], LocationName.forest_of_illusion_3_dragon: [0x47, 2], + LocationName.forest_of_illusion_3_hidden_1up: [0x47, 4], LocationName.forest_of_illusion_4_exit_1: [0x43, 0], LocationName.forest_of_illusion_4_exit_2: [0x43, 1], LocationName.forest_of_illusion_4_dragon: [0x43, 2], LocationName.forest_ghost_house_exit_1: [0x41, 0], LocationName.forest_ghost_house_exit_2: [0x41, 1], LocationName.forest_ghost_house_dragon: [0x41, 2], + LocationName.forest_ghost_house_moon: [0x41, 3], LocationName.forest_secret_exit_1: [0x46, 0], LocationName.forest_secret_dragon: [0x46, 2], LocationName.forest_fortress: [0x1F, 0], @@ -427,12 +542,15 @@ location_id_to_level_id = { LocationName.chocolate_island_1_exit_1: [0x22, 0], LocationName.chocolate_island_1_dragon: [0x22, 2], + LocationName.chocolate_island_1_moon: [0x22, 3], LocationName.chocolate_island_2_exit_1: [0x24, 0], LocationName.chocolate_island_2_exit_2: [0x24, 1], LocationName.chocolate_island_2_dragon: [0x24, 2], + LocationName.chocolate_island_2_hidden_1up: [0x24, 4], LocationName.chocolate_island_3_exit_1: [0x23, 0], LocationName.chocolate_island_3_exit_2: [0x23, 1], LocationName.chocolate_island_3_dragon: [0x23, 2], + LocationName.chocolate_island_3_bonus_block: [0x23, 5], LocationName.chocolate_island_4_exit_1: [0x1D, 0], LocationName.chocolate_island_4_dragon: [0x1D, 2], LocationName.chocolate_island_5_exit_1: [0x1C, 0], @@ -442,6 +560,7 @@ location_id_to_level_id = { LocationName.chocolate_fortress: [0x1B, 0], LocationName.chocolate_reznor: [0x1B, 0], LocationName.chocolate_castle: [0x1A, 0], + LocationName.chocolate_castle_hidden_1up: [0x1A, 4], LocationName.chocolate_koopaling: [0x1A, 0], LocationName.sunken_ghost_ship: [0x18, 0], @@ -449,9 +568,11 @@ location_id_to_level_id = { LocationName.valley_of_bowser_1_exit_1: [0x3A, 0], LocationName.valley_of_bowser_1_dragon: [0x3A, 2], + LocationName.valley_of_bowser_1_moon: [0x3A, 3], LocationName.valley_of_bowser_2_exit_1: [0x39, 0], LocationName.valley_of_bowser_2_exit_2: [0x39, 1], LocationName.valley_of_bowser_2_dragon: [0x39, 2], + LocationName.valley_of_bowser_2_hidden_1up: [0x39, 4], LocationName.valley_of_bowser_3_exit_1: [0x37, 0], LocationName.valley_of_bowser_3_dragon: [0x37, 2], LocationName.valley_of_bowser_4_exit_1: [0x33, 0], @@ -464,6 +585,7 @@ location_id_to_level_id = { LocationName.valley_castle: [0x34, 0], LocationName.valley_koopaling: [0x34, 0], LocationName.valley_castle_dragon: [0x34, 2], + LocationName.valley_castle_hidden_1up: [0x34, 4], LocationName.star_road_1_exit_1: [0x58, 0], LocationName.star_road_1_exit_2: [0x58, 1], @@ -479,6 +601,7 @@ location_id_to_level_id = { LocationName.special_zone_1_exit_1: [0x4E, 0], LocationName.special_zone_1_dragon: [0x4E, 2], + LocationName.special_zone_1_hidden_1up: [0x4E, 4], LocationName.special_zone_2_exit_1: [0x4F, 0], LocationName.special_zone_2_dragon: [0x4F, 2], LocationName.special_zone_3_exit_1: [0x50, 0], @@ -493,19 +616,602 @@ location_id_to_level_id = { LocationName.special_zone_7_dragon: [0x4A, 2], LocationName.special_zone_8_exit_1: [0x49, 0], LocationName.special_zone_8_dragon: [0x49, 2], + + LocationName.vanilla_secret_2_yoshi_block_1: [0x01, 100], + LocationName.vanilla_secret_2_green_block_1: [0x01, 101], + LocationName.vanilla_secret_2_powerup_block_1: [0x01, 102], + LocationName.vanilla_secret_2_powerup_block_2: [0x01, 103], + LocationName.vanilla_secret_2_multi_coin_block_1: [0x01, 104], + LocationName.vanilla_secret_2_gray_pow_block_1: [0x01, 105], + LocationName.vanilla_secret_2_coin_block_1: [0x01, 106], + LocationName.vanilla_secret_2_coin_block_2: [0x01, 107], + LocationName.vanilla_secret_2_coin_block_3: [0x01, 108], + LocationName.vanilla_secret_2_coin_block_4: [0x01, 109], + LocationName.vanilla_secret_2_coin_block_5: [0x01, 110], + LocationName.vanilla_secret_2_coin_block_6: [0x01, 111], + LocationName.vanilla_secret_3_powerup_block_1: [0x02, 112], + LocationName.vanilla_secret_3_powerup_block_2: [0x02, 113], + LocationName.donut_ghost_house_vine_block_1: [0x04, 114], + LocationName.donut_ghost_house_directional_coin_block_1: [0x04, 115], + LocationName.donut_ghost_house_life_block_1: [0x04, 116], + LocationName.donut_ghost_house_life_block_2: [0x04, 117], + LocationName.donut_ghost_house_life_block_3: [0x04, 118], + LocationName.donut_ghost_house_life_block_4: [0x04, 119], + LocationName.donut_plains_3_green_block_1: [0x05, 120], + LocationName.donut_plains_3_coin_block_1: [0x05, 121], + LocationName.donut_plains_3_coin_block_2: [0x05, 122], + LocationName.donut_plains_3_vine_block_1: [0x05, 123], + LocationName.donut_plains_3_powerup_block_1: [0x05, 124], + LocationName.donut_plains_3_bonus_block_1: [0x05, 125], + LocationName.donut_plains_4_coin_block_1: [0x06, 126], + LocationName.donut_plains_4_powerup_block_1: [0x06, 127], + LocationName.donut_plains_4_coin_block_2: [0x06, 128], + LocationName.donut_plains_4_yoshi_block_1: [0x06, 129], + LocationName.donut_plains_castle_yellow_block_1: [0x07, 130], + LocationName.donut_plains_castle_coin_block_1: [0x07, 131], + LocationName.donut_plains_castle_powerup_block_1: [0x07, 132], + LocationName.donut_plains_castle_coin_block_2: [0x07, 133], + LocationName.donut_plains_castle_vine_block_1: [0x07, 134], + LocationName.donut_plains_castle_invis_life_block_1: [0x07, 135], + LocationName.donut_plains_castle_coin_block_3: [0x07, 136], + LocationName.donut_plains_castle_coin_block_4: [0x07, 137], + LocationName.donut_plains_castle_coin_block_5: [0x07, 138], + LocationName.donut_plains_castle_green_block_1: [0x07, 139], + LocationName.donut_plains_2_coin_block_1: [0x09, 140], + LocationName.donut_plains_2_coin_block_2: [0x09, 141], + LocationName.donut_plains_2_coin_block_3: [0x09, 142], + LocationName.donut_plains_2_yellow_block_1: [0x09, 143], + LocationName.donut_plains_2_powerup_block_1: [0x09, 144], + LocationName.donut_plains_2_multi_coin_block_1: [0x09, 145], + LocationName.donut_plains_2_flying_block_1: [0x09, 146], + LocationName.donut_plains_2_green_block_1: [0x09, 147], + LocationName.donut_plains_2_yellow_block_2: [0x09, 148], + LocationName.donut_plains_2_vine_block_1: [0x09, 149], + LocationName.donut_secret_1_coin_block_1: [0x0A, 150], + LocationName.donut_secret_1_coin_block_2: [0x0A, 151], + LocationName.donut_secret_1_powerup_block_1: [0x0A, 152], + LocationName.donut_secret_1_coin_block_3: [0x0A, 153], + LocationName.donut_secret_1_powerup_block_2: [0x0A, 154], + LocationName.donut_secret_1_powerup_block_3: [0x0A, 155], + LocationName.donut_secret_1_life_block_1: [0x0A, 156], + LocationName.donut_secret_1_powerup_block_4: [0x0A, 157], + LocationName.donut_secret_1_powerup_block_5: [0x0A, 158], + LocationName.donut_secret_1_key_block_1: [0x0A, 159], + LocationName.vanilla_fortress_powerup_block_1: [0x0B, 160], + LocationName.vanilla_fortress_powerup_block_2: [0x0B, 161], + LocationName.vanilla_fortress_yellow_block_1: [0x0B, 162], + LocationName.butter_bridge_1_powerup_block_1: [0x0C, 163], + LocationName.butter_bridge_1_multi_coin_block_1: [0x0C, 164], + LocationName.butter_bridge_1_multi_coin_block_2: [0x0C, 165], + LocationName.butter_bridge_1_multi_coin_block_3: [0x0C, 166], + LocationName.butter_bridge_1_life_block_1: [0x0C, 167], + LocationName.butter_bridge_1_bonus_block_1: [0x0C, 168], + LocationName.butter_bridge_2_powerup_block_1: [0x0D, 169], + LocationName.butter_bridge_2_green_block_1: [0x0D, 170], + LocationName.butter_bridge_2_yoshi_block_1: [0x0D, 171], + LocationName.twin_bridges_castle_powerup_block_1: [0x0E, 172], + LocationName.cheese_bridge_powerup_block_1: [0x0F, 173], + LocationName.cheese_bridge_powerup_block_2: [0x0F, 174], + LocationName.cheese_bridge_wings_block_1: [0x0F, 175], + LocationName.cheese_bridge_powerup_block_3: [0x0F, 176], + LocationName.cookie_mountain_coin_block_1: [0x10, 177], + LocationName.cookie_mountain_coin_block_2: [0x10, 178], + LocationName.cookie_mountain_coin_block_3: [0x10, 179], + LocationName.cookie_mountain_coin_block_4: [0x10, 180], + LocationName.cookie_mountain_coin_block_5: [0x10, 181], + LocationName.cookie_mountain_coin_block_6: [0x10, 182], + LocationName.cookie_mountain_coin_block_7: [0x10, 183], + LocationName.cookie_mountain_coin_block_8: [0x10, 184], + LocationName.cookie_mountain_coin_block_9: [0x10, 185], + LocationName.cookie_mountain_powerup_block_1: [0x10, 186], + LocationName.cookie_mountain_life_block_1: [0x10, 187], + LocationName.cookie_mountain_vine_block_1: [0x10, 188], + LocationName.cookie_mountain_yoshi_block_1: [0x10, 189], + LocationName.cookie_mountain_coin_block_10: [0x10, 190], + LocationName.cookie_mountain_coin_block_11: [0x10, 191], + LocationName.cookie_mountain_powerup_block_2: [0x10, 192], + LocationName.cookie_mountain_coin_block_12: [0x10, 193], + LocationName.cookie_mountain_coin_block_13: [0x10, 194], + LocationName.cookie_mountain_coin_block_14: [0x10, 195], + LocationName.cookie_mountain_coin_block_15: [0x10, 196], + LocationName.cookie_mountain_coin_block_16: [0x10, 197], + LocationName.cookie_mountain_coin_block_17: [0x10, 198], + LocationName.cookie_mountain_coin_block_18: [0x10, 199], + LocationName.cookie_mountain_coin_block_19: [0x10, 200], + LocationName.cookie_mountain_coin_block_20: [0x10, 201], + LocationName.cookie_mountain_coin_block_21: [0x10, 202], + LocationName.cookie_mountain_coin_block_22: [0x10, 203], + LocationName.cookie_mountain_coin_block_23: [0x10, 204], + LocationName.cookie_mountain_coin_block_24: [0x10, 205], + LocationName.cookie_mountain_coin_block_25: [0x10, 206], + LocationName.cookie_mountain_coin_block_26: [0x10, 207], + LocationName.cookie_mountain_coin_block_27: [0x10, 208], + LocationName.cookie_mountain_coin_block_28: [0x10, 209], + LocationName.cookie_mountain_coin_block_29: [0x10, 210], + LocationName.cookie_mountain_coin_block_30: [0x10, 211], + LocationName.soda_lake_powerup_block_1: [0x11, 212], + LocationName.donut_secret_house_powerup_block_1: [0x13, 213], + LocationName.donut_secret_house_multi_coin_block_1: [0x13, 214], + LocationName.donut_secret_house_life_block_1: [0x13, 215], + LocationName.donut_secret_house_vine_block_1: [0x13, 216], + LocationName.donut_secret_house_directional_coin_block_1: [0x13, 217], + LocationName.donut_plains_1_coin_block_1: [0x15, 218], + LocationName.donut_plains_1_coin_block_2: [0x15, 219], + LocationName.donut_plains_1_yoshi_block_1: [0x15, 220], + LocationName.donut_plains_1_vine_block_1: [0x15, 221], + LocationName.donut_plains_1_green_block_1: [0x15, 222], + LocationName.donut_plains_1_green_block_2: [0x15, 223], + LocationName.donut_plains_1_green_block_3: [0x15, 224], + LocationName.donut_plains_1_green_block_4: [0x15, 225], + LocationName.donut_plains_1_green_block_5: [0x15, 226], + LocationName.donut_plains_1_green_block_6: [0x15, 227], + LocationName.donut_plains_1_green_block_7: [0x15, 228], + LocationName.donut_plains_1_green_block_8: [0x15, 229], + LocationName.donut_plains_1_green_block_9: [0x15, 230], + LocationName.donut_plains_1_green_block_10: [0x15, 231], + LocationName.donut_plains_1_green_block_11: [0x15, 232], + LocationName.donut_plains_1_green_block_12: [0x15, 233], + LocationName.donut_plains_1_green_block_13: [0x15, 234], + LocationName.donut_plains_1_green_block_14: [0x15, 235], + LocationName.donut_plains_1_green_block_15: [0x15, 236], + LocationName.donut_plains_1_green_block_16: [0x15, 237], + LocationName.donut_plains_1_yellow_block_1: [0x15, 238], + LocationName.donut_plains_1_yellow_block_2: [0x15, 239], + LocationName.donut_plains_1_yellow_block_3: [0x15, 240], + LocationName.sunken_ghost_ship_powerup_block_1: [0x18, 241], + LocationName.sunken_ghost_ship_star_block_1: [0x18, 242], + LocationName.chocolate_castle_yellow_block_1: [0x1A, 243], + LocationName.chocolate_castle_yellow_block_2: [0x1A, 244], + LocationName.chocolate_castle_green_block_1: [0x1A, 245], + LocationName.chocolate_fortress_powerup_block_1: [0x1B, 246], + LocationName.chocolate_fortress_powerup_block_2: [0x1B, 247], + LocationName.chocolate_fortress_coin_block_1: [0x1B, 248], + LocationName.chocolate_fortress_coin_block_2: [0x1B, 249], + LocationName.chocolate_fortress_green_block_1: [0x1B, 250], + LocationName.chocolate_island_5_yoshi_block_1: [0x1C, 251], + LocationName.chocolate_island_5_powerup_block_1: [0x1C, 252], + LocationName.chocolate_island_5_life_block_1: [0x1C, 253], + LocationName.chocolate_island_5_yellow_block_1: [0x1C, 254], + LocationName.chocolate_island_4_yellow_block_1: [0x1D, 255], + LocationName.chocolate_island_4_blue_pow_block_1: [0x1D, 256], + LocationName.chocolate_island_4_powerup_block_1: [0x1D, 257], + LocationName.forest_fortress_yellow_block_1: [0x1F, 258], + LocationName.forest_fortress_powerup_block_1: [0x1F, 259], + LocationName.forest_fortress_life_block_1: [0x1F, 260], + LocationName.forest_fortress_life_block_2: [0x1F, 261], + LocationName.forest_fortress_life_block_3: [0x1F, 262], + LocationName.forest_fortress_life_block_4: [0x1F, 263], + LocationName.forest_fortress_life_block_5: [0x1F, 264], + LocationName.forest_fortress_life_block_6: [0x1F, 265], + LocationName.forest_fortress_life_block_7: [0x1F, 266], + LocationName.forest_fortress_life_block_8: [0x1F, 267], + LocationName.forest_fortress_life_block_9: [0x1F, 268], + LocationName.forest_castle_green_block_1: [0x20, 269], + LocationName.chocolate_ghost_house_powerup_block_1: [0x21, 270], + LocationName.chocolate_ghost_house_powerup_block_2: [0x21, 271], + LocationName.chocolate_ghost_house_life_block_1: [0x21, 272], + LocationName.chocolate_island_1_flying_block_1: [0x22, 273], + LocationName.chocolate_island_1_flying_block_2: [0x22, 274], + LocationName.chocolate_island_1_yoshi_block_1: [0x22, 275], + LocationName.chocolate_island_1_green_block_1: [0x22, 276], + LocationName.chocolate_island_1_life_block_1: [0x22, 277], + LocationName.chocolate_island_3_powerup_block_1: [0x23, 278], + LocationName.chocolate_island_3_powerup_block_2: [0x23, 279], + LocationName.chocolate_island_3_powerup_block_3: [0x23, 280], + LocationName.chocolate_island_3_green_block_1: [0x23, 281], + LocationName.chocolate_island_3_bonus_block_1: [0x23, 282], + LocationName.chocolate_island_3_vine_block_1: [0x23, 283], + LocationName.chocolate_island_3_life_block_1: [0x23, 284], + LocationName.chocolate_island_3_life_block_2: [0x23, 285], + LocationName.chocolate_island_3_life_block_3: [0x23, 286], + LocationName.chocolate_island_2_multi_coin_block_1: [0x24, 287], + LocationName.chocolate_island_2_invis_coin_block_1: [0x24, 288], + LocationName.chocolate_island_2_yoshi_block_1: [0x24, 289], + LocationName.chocolate_island_2_coin_block_1: [0x24, 290], + LocationName.chocolate_island_2_coin_block_2: [0x24, 291], + LocationName.chocolate_island_2_multi_coin_block_2: [0x24, 292], + LocationName.chocolate_island_2_powerup_block_1: [0x24, 293], + LocationName.chocolate_island_2_blue_pow_block_1: [0x24, 294], + LocationName.chocolate_island_2_yellow_block_1: [0x24, 295], + LocationName.chocolate_island_2_yellow_block_2: [0x24, 296], + LocationName.chocolate_island_2_green_block_1: [0x24, 297], + LocationName.chocolate_island_2_green_block_2: [0x24, 298], + LocationName.chocolate_island_2_green_block_3: [0x24, 299], + LocationName.chocolate_island_2_green_block_4: [0x24, 300], + LocationName.chocolate_island_2_green_block_5: [0x24, 301], + LocationName.chocolate_island_2_green_block_6: [0x24, 302], + LocationName.yoshis_island_castle_coin_block_1: [0x25, 303], + LocationName.yoshis_island_castle_coin_block_2: [0x25, 304], + LocationName.yoshis_island_castle_powerup_block_1: [0x25, 305], + LocationName.yoshis_island_castle_coin_block_3: [0x25, 306], + LocationName.yoshis_island_castle_coin_block_4: [0x25, 307], + LocationName.yoshis_island_castle_flying_block_1: [0x25, 308], + LocationName.yoshis_island_4_yellow_block_1: [0x26, 309], + LocationName.yoshis_island_4_powerup_block_1: [0x26, 310], + LocationName.yoshis_island_4_multi_coin_block_1: [0x26, 311], + LocationName.yoshis_island_4_star_block_1: [0x26, 312], + LocationName.yoshis_island_3_yellow_block_1: [0x27, 313], + LocationName.yoshis_island_3_yellow_block_2: [0x27, 314], + LocationName.yoshis_island_3_yellow_block_3: [0x27, 315], + LocationName.yoshis_island_3_yellow_block_4: [0x27, 316], + LocationName.yoshis_island_3_yellow_block_5: [0x27, 317], + LocationName.yoshis_island_3_yellow_block_6: [0x27, 318], + LocationName.yoshis_island_3_yellow_block_7: [0x27, 319], + LocationName.yoshis_island_3_yellow_block_8: [0x27, 320], + LocationName.yoshis_island_3_yellow_block_9: [0x27, 321], + LocationName.yoshis_island_3_coin_block_1: [0x27, 322], + LocationName.yoshis_island_3_yoshi_block_1: [0x27, 323], + LocationName.yoshis_island_3_coin_block_2: [0x27, 324], + LocationName.yoshis_island_3_powerup_block_1: [0x27, 325], + LocationName.yoshis_island_3_yellow_block_10: [0x27, 326], + LocationName.yoshis_island_3_yellow_block_11: [0x27, 327], + LocationName.yoshis_island_3_yellow_block_12: [0x27, 328], + LocationName.yoshis_island_3_bonus_block_1: [0x27, 329], + LocationName.yoshis_island_1_flying_block_1: [0x29, 330], + LocationName.yoshis_island_1_yellow_block_1: [0x29, 331], + LocationName.yoshis_island_1_life_block_1: [0x29, 332], + LocationName.yoshis_island_1_powerup_block_1: [0x29, 333], + LocationName.yoshis_island_2_flying_block_1: [0x2A, 334], + LocationName.yoshis_island_2_flying_block_2: [0x2A, 335], + LocationName.yoshis_island_2_flying_block_3: [0x2A, 336], + LocationName.yoshis_island_2_flying_block_4: [0x2A, 337], + LocationName.yoshis_island_2_flying_block_5: [0x2A, 338], + LocationName.yoshis_island_2_flying_block_6: [0x2A, 339], + LocationName.yoshis_island_2_coin_block_1: [0x2A, 340], + LocationName.yoshis_island_2_yellow_block_1: [0x2A, 341], + LocationName.yoshis_island_2_coin_block_2: [0x2A, 342], + LocationName.yoshis_island_2_coin_block_3: [0x2A, 343], + LocationName.yoshis_island_2_yoshi_block_1: [0x2A, 344], + LocationName.yoshis_island_2_coin_block_4: [0x2A, 345], + LocationName.yoshis_island_2_yoshi_block_2: [0x2A, 346], + LocationName.yoshis_island_2_coin_block_5: [0x2A, 347], + LocationName.yoshis_island_2_vine_block_1: [0x2A, 348], + LocationName.yoshis_island_2_yellow_block_2: [0x2A, 349], + LocationName.vanilla_ghost_house_powerup_block_1: [0x2B, 350], + LocationName.vanilla_ghost_house_vine_block_1: [0x2B, 351], + LocationName.vanilla_ghost_house_powerup_block_2: [0x2B, 352], + LocationName.vanilla_ghost_house_multi_coin_block_1: [0x2B, 353], + LocationName.vanilla_ghost_house_blue_pow_block_1: [0x2B, 354], + LocationName.vanilla_secret_1_coin_block_1: [0x2D, 355], + LocationName.vanilla_secret_1_powerup_block_1: [0x2D, 356], + LocationName.vanilla_secret_1_multi_coin_block_1: [0x2D, 357], + LocationName.vanilla_secret_1_vine_block_1: [0x2D, 358], + LocationName.vanilla_secret_1_vine_block_2: [0x2D, 359], + LocationName.vanilla_secret_1_coin_block_2: [0x2D, 360], + LocationName.vanilla_secret_1_coin_block_3: [0x2D, 361], + LocationName.vanilla_secret_1_powerup_block_2: [0x2D, 362], + LocationName.vanilla_dome_3_coin_block_1: [0x2E, 363], + LocationName.vanilla_dome_3_flying_block_1: [0x2E, 364], + LocationName.vanilla_dome_3_flying_block_2: [0x2E, 365], + LocationName.vanilla_dome_3_powerup_block_1: [0x2E, 366], + LocationName.vanilla_dome_3_flying_block_3: [0x2E, 367], + LocationName.vanilla_dome_3_invis_coin_block_1: [0x2E, 368], + LocationName.vanilla_dome_3_powerup_block_2: [0x2E, 369], + LocationName.vanilla_dome_3_multi_coin_block_1: [0x2E, 370], + LocationName.vanilla_dome_3_powerup_block_3: [0x2E, 371], + LocationName.vanilla_dome_3_yoshi_block_1: [0x2E, 372], + LocationName.vanilla_dome_3_powerup_block_4: [0x2E, 373], + LocationName.vanilla_dome_3_pswitch_coin_block_1: [0x2E, 374], + LocationName.vanilla_dome_3_pswitch_coin_block_2: [0x2E, 375], + LocationName.vanilla_dome_3_pswitch_coin_block_3: [0x2E, 376], + LocationName.vanilla_dome_3_pswitch_coin_block_4: [0x2E, 377], + LocationName.vanilla_dome_3_pswitch_coin_block_5: [0x2E, 378], + LocationName.vanilla_dome_3_pswitch_coin_block_6: [0x2E, 379], + LocationName.donut_secret_2_directional_coin_block_1: [0x2F, 380], + LocationName.donut_secret_2_vine_block_1: [0x2F, 381], + LocationName.donut_secret_2_star_block_1: [0x2F, 382], + LocationName.donut_secret_2_powerup_block_1: [0x2F, 383], + LocationName.donut_secret_2_star_block_2: [0x2F, 384], + LocationName.valley_of_bowser_4_yellow_block_1: [0x33, 385], + LocationName.valley_of_bowser_4_powerup_block_1: [0x33, 386], + LocationName.valley_of_bowser_4_vine_block_1: [0x33, 387], + LocationName.valley_of_bowser_4_yoshi_block_1: [0x33, 388], + LocationName.valley_of_bowser_4_life_block_1: [0x33, 389], + LocationName.valley_of_bowser_4_powerup_block_2: [0x33, 390], + LocationName.valley_castle_yellow_block_1: [0x34, 391], + LocationName.valley_castle_yellow_block_2: [0x34, 392], + LocationName.valley_castle_green_block_1: [0x34, 393], + LocationName.valley_fortress_green_block_1: [0x35, 394], + LocationName.valley_fortress_yellow_block_1: [0x35, 395], + LocationName.valley_of_bowser_3_powerup_block_1: [0x37, 396], + LocationName.valley_of_bowser_3_powerup_block_2: [0x37, 397], + LocationName.valley_ghost_house_pswitch_coin_block_1: [0x38, 398], + LocationName.valley_ghost_house_multi_coin_block_1: [0x38, 399], + LocationName.valley_ghost_house_powerup_block_1: [0x38, 400], + LocationName.valley_ghost_house_directional_coin_block_1: [0x38, 401], + LocationName.valley_of_bowser_2_powerup_block_1: [0x39, 402], + LocationName.valley_of_bowser_2_yellow_block_1: [0x39, 403], + LocationName.valley_of_bowser_2_powerup_block_2: [0x39, 404], + LocationName.valley_of_bowser_2_wings_block_1: [0x39, 405], + LocationName.valley_of_bowser_1_green_block_1: [0x3A, 406], + LocationName.valley_of_bowser_1_invis_coin_block_1: [0x3A, 407], + LocationName.valley_of_bowser_1_invis_coin_block_2: [0x3A, 408], + LocationName.valley_of_bowser_1_invis_coin_block_3: [0x3A, 409], + LocationName.valley_of_bowser_1_yellow_block_1: [0x3A, 410], + LocationName.valley_of_bowser_1_yellow_block_2: [0x3A, 411], + LocationName.valley_of_bowser_1_yellow_block_3: [0x3A, 412], + LocationName.valley_of_bowser_1_yellow_block_4: [0x3A, 413], + LocationName.valley_of_bowser_1_vine_block_1: [0x3A, 414], + LocationName.chocolate_secret_powerup_block_1: [0x3B, 415], + LocationName.chocolate_secret_powerup_block_2: [0x3B, 416], + LocationName.vanilla_dome_2_coin_block_1: [0x3C, 417], + LocationName.vanilla_dome_2_powerup_block_1: [0x3C, 418], + LocationName.vanilla_dome_2_coin_block_2: [0x3C, 419], + LocationName.vanilla_dome_2_coin_block_3: [0x3C, 420], + LocationName.vanilla_dome_2_vine_block_1: [0x3C, 421], + LocationName.vanilla_dome_2_invis_life_block_1: [0x3C, 422], + LocationName.vanilla_dome_2_coin_block_4: [0x3C, 423], + LocationName.vanilla_dome_2_coin_block_5: [0x3C, 424], + LocationName.vanilla_dome_2_powerup_block_2: [0x3C, 425], + LocationName.vanilla_dome_2_powerup_block_3: [0x3C, 426], + LocationName.vanilla_dome_2_powerup_block_4: [0x3C, 427], + LocationName.vanilla_dome_2_powerup_block_5: [0x3C, 428], + LocationName.vanilla_dome_2_multi_coin_block_1: [0x3C, 429], + LocationName.vanilla_dome_2_multi_coin_block_2: [0x3C, 430], + LocationName.vanilla_dome_4_powerup_block_1: [0x3D, 431], + LocationName.vanilla_dome_4_powerup_block_2: [0x3D, 432], + LocationName.vanilla_dome_4_coin_block_1: [0x3D, 433], + LocationName.vanilla_dome_4_coin_block_2: [0x3D, 434], + LocationName.vanilla_dome_4_coin_block_3: [0x3D, 435], + LocationName.vanilla_dome_4_life_block_1: [0x3D, 436], + LocationName.vanilla_dome_4_coin_block_4: [0x3D, 437], + LocationName.vanilla_dome_4_coin_block_5: [0x3D, 438], + LocationName.vanilla_dome_4_coin_block_6: [0x3D, 439], + LocationName.vanilla_dome_4_coin_block_7: [0x3D, 440], + LocationName.vanilla_dome_4_coin_block_8: [0x3D, 441], + LocationName.vanilla_dome_1_flying_block_1: [0x3E, 442], + LocationName.vanilla_dome_1_powerup_block_1: [0x3E, 443], + LocationName.vanilla_dome_1_powerup_block_2: [0x3E, 444], + LocationName.vanilla_dome_1_coin_block_1: [0x3E, 445], + LocationName.vanilla_dome_1_life_block_1: [0x3E, 446], + LocationName.vanilla_dome_1_powerup_block_3: [0x3E, 447], + LocationName.vanilla_dome_1_vine_block_1: [0x3E, 448], + LocationName.vanilla_dome_1_star_block_1: [0x3E, 449], + LocationName.vanilla_dome_1_powerup_block_4: [0x3E, 450], + LocationName.vanilla_dome_1_coin_block_2: [0x3E, 451], + LocationName.vanilla_dome_castle_life_block_1: [0x40, 452], + LocationName.vanilla_dome_castle_life_block_2: [0x40, 453], + LocationName.vanilla_dome_castle_powerup_block_1: [0x40, 454], + LocationName.vanilla_dome_castle_life_block_3: [0x40, 455], + LocationName.vanilla_dome_castle_green_block_1: [0x40, 456], + LocationName.forest_ghost_house_coin_block_1: [0x41, 457], + LocationName.forest_ghost_house_powerup_block_1: [0x41, 458], + LocationName.forest_ghost_house_flying_block_1: [0x41, 459], + LocationName.forest_ghost_house_powerup_block_2: [0x41, 460], + LocationName.forest_ghost_house_life_block_1: [0x41, 461], + LocationName.forest_of_illusion_1_powerup_block_1: [0x42, 462], + LocationName.forest_of_illusion_1_yoshi_block_1: [0x42, 463], + LocationName.forest_of_illusion_1_powerup_block_2: [0x42, 464], + LocationName.forest_of_illusion_1_key_block_1: [0x42, 465], + LocationName.forest_of_illusion_1_life_block_1: [0x42, 466], + LocationName.forest_of_illusion_4_multi_coin_block_1: [0x43, 467], + LocationName.forest_of_illusion_4_coin_block_1: [0x43, 468], + LocationName.forest_of_illusion_4_coin_block_2: [0x43, 469], + LocationName.forest_of_illusion_4_coin_block_3: [0x43, 470], + LocationName.forest_of_illusion_4_coin_block_4: [0x43, 471], + LocationName.forest_of_illusion_4_powerup_block_1: [0x43, 472], + LocationName.forest_of_illusion_4_coin_block_5: [0x43, 473], + LocationName.forest_of_illusion_4_coin_block_6: [0x43, 474], + LocationName.forest_of_illusion_4_coin_block_7: [0x43, 475], + LocationName.forest_of_illusion_4_powerup_block_2: [0x43, 476], + LocationName.forest_of_illusion_4_coin_block_8: [0x43, 477], + LocationName.forest_of_illusion_4_coin_block_9: [0x43, 478], + LocationName.forest_of_illusion_4_coin_block_10: [0x43, 479], + LocationName.forest_of_illusion_2_green_block_1: [0x44, 480], + LocationName.forest_of_illusion_2_powerup_block_1: [0x44, 481], + LocationName.forest_of_illusion_2_invis_coin_block_1: [0x44, 482], + LocationName.forest_of_illusion_2_invis_coin_block_2: [0x44, 483], + LocationName.forest_of_illusion_2_invis_life_block_1: [0x44, 484], + LocationName.forest_of_illusion_2_invis_coin_block_3: [0x44, 485], + LocationName.forest_of_illusion_2_yellow_block_1: [0x44, 486], + LocationName.forest_secret_powerup_block_1: [0x46, 487], + LocationName.forest_secret_powerup_block_2: [0x46, 488], + LocationName.forest_secret_life_block_1: [0x46, 489], + LocationName.forest_of_illusion_3_yoshi_block_1: [0x47, 490], + LocationName.forest_of_illusion_3_coin_block_1: [0x47, 491], + LocationName.forest_of_illusion_3_multi_coin_block_1: [0x47, 492], + LocationName.forest_of_illusion_3_coin_block_2: [0x47, 493], + LocationName.forest_of_illusion_3_multi_coin_block_2: [0x47, 494], + LocationName.forest_of_illusion_3_coin_block_3: [0x47, 495], + LocationName.forest_of_illusion_3_coin_block_4: [0x47, 496], + LocationName.forest_of_illusion_3_coin_block_5: [0x47, 497], + LocationName.forest_of_illusion_3_coin_block_6: [0x47, 498], + LocationName.forest_of_illusion_3_coin_block_7: [0x47, 499], + LocationName.forest_of_illusion_3_coin_block_8: [0x47, 500], + LocationName.forest_of_illusion_3_coin_block_9: [0x47, 501], + LocationName.forest_of_illusion_3_coin_block_10: [0x47, 502], + LocationName.forest_of_illusion_3_coin_block_11: [0x47, 503], + LocationName.forest_of_illusion_3_coin_block_12: [0x47, 504], + LocationName.forest_of_illusion_3_coin_block_13: [0x47, 505], + LocationName.forest_of_illusion_3_coin_block_14: [0x47, 506], + LocationName.forest_of_illusion_3_coin_block_15: [0x47, 507], + LocationName.forest_of_illusion_3_coin_block_16: [0x47, 508], + LocationName.forest_of_illusion_3_coin_block_17: [0x47, 509], + LocationName.forest_of_illusion_3_coin_block_18: [0x47, 510], + LocationName.forest_of_illusion_3_coin_block_19: [0x47, 511], + LocationName.forest_of_illusion_3_coin_block_20: [0x47, 512], + LocationName.forest_of_illusion_3_coin_block_21: [0x47, 513], + LocationName.forest_of_illusion_3_coin_block_22: [0x47, 514], + LocationName.forest_of_illusion_3_coin_block_23: [0x47, 515], + LocationName.forest_of_illusion_3_coin_block_24: [0x47, 516], + LocationName.special_zone_8_yoshi_block_1: [0x49, 517], + LocationName.special_zone_8_coin_block_1: [0x49, 518], + LocationName.special_zone_8_coin_block_2: [0x49, 519], + LocationName.special_zone_8_coin_block_3: [0x49, 520], + LocationName.special_zone_8_coin_block_4: [0x49, 521], + LocationName.special_zone_8_coin_block_5: [0x49, 522], + LocationName.special_zone_8_blue_pow_block_1: [0x49, 523], + LocationName.special_zone_8_powerup_block_1: [0x49, 524], + LocationName.special_zone_8_star_block_1: [0x49, 525], + LocationName.special_zone_8_coin_block_6: [0x49, 526], + LocationName.special_zone_8_coin_block_7: [0x49, 527], + LocationName.special_zone_8_coin_block_8: [0x49, 528], + LocationName.special_zone_8_coin_block_9: [0x49, 529], + LocationName.special_zone_8_coin_block_10: [0x49, 530], + LocationName.special_zone_8_coin_block_11: [0x49, 531], + LocationName.special_zone_8_coin_block_12: [0x49, 532], + LocationName.special_zone_8_coin_block_13: [0x49, 533], + LocationName.special_zone_8_coin_block_14: [0x49, 534], + LocationName.special_zone_8_coin_block_15: [0x49, 535], + LocationName.special_zone_8_coin_block_16: [0x49, 536], + LocationName.special_zone_8_coin_block_17: [0x49, 537], + LocationName.special_zone_8_coin_block_18: [0x49, 538], + LocationName.special_zone_8_multi_coin_block_1: [0x49, 539], + LocationName.special_zone_8_coin_block_19: [0x49, 540], + LocationName.special_zone_8_coin_block_20: [0x49, 541], + LocationName.special_zone_8_coin_block_21: [0x49, 542], + LocationName.special_zone_8_coin_block_22: [0x49, 543], + LocationName.special_zone_8_coin_block_23: [0x49, 544], + LocationName.special_zone_8_powerup_block_2: [0x49, 545], + LocationName.special_zone_8_flying_block_1: [0x49, 546], + LocationName.special_zone_7_powerup_block_1: [0x4A, 547], + LocationName.special_zone_7_yoshi_block_1: [0x4A, 548], + LocationName.special_zone_7_coin_block_1: [0x4A, 549], + LocationName.special_zone_7_powerup_block_2: [0x4A, 550], + LocationName.special_zone_7_coin_block_2: [0x4A, 551], + LocationName.special_zone_6_powerup_block_1: [0x4B, 552], + LocationName.special_zone_6_coin_block_1: [0x4B, 553], + LocationName.special_zone_6_coin_block_2: [0x4B, 554], + LocationName.special_zone_6_yoshi_block_1: [0x4B, 555], + LocationName.special_zone_6_life_block_1: [0x4B, 556], + LocationName.special_zone_6_multi_coin_block_1: [0x4B, 557], + LocationName.special_zone_6_coin_block_3: [0x4B, 558], + LocationName.special_zone_6_coin_block_4: [0x4B, 559], + LocationName.special_zone_6_coin_block_5: [0x4B, 560], + LocationName.special_zone_6_coin_block_6: [0x4B, 561], + LocationName.special_zone_6_coin_block_7: [0x4B, 562], + LocationName.special_zone_6_coin_block_8: [0x4B, 563], + LocationName.special_zone_6_coin_block_9: [0x4B, 564], + LocationName.special_zone_6_coin_block_10: [0x4B, 565], + LocationName.special_zone_6_coin_block_11: [0x4B, 566], + LocationName.special_zone_6_coin_block_12: [0x4B, 567], + LocationName.special_zone_6_coin_block_13: [0x4B, 568], + LocationName.special_zone_6_coin_block_14: [0x4B, 569], + LocationName.special_zone_6_coin_block_15: [0x4B, 570], + LocationName.special_zone_6_coin_block_16: [0x4B, 571], + LocationName.special_zone_6_coin_block_17: [0x4B, 572], + LocationName.special_zone_6_coin_block_18: [0x4B, 573], + LocationName.special_zone_6_coin_block_19: [0x4B, 574], + LocationName.special_zone_6_coin_block_20: [0x4B, 575], + LocationName.special_zone_6_coin_block_21: [0x4B, 576], + LocationName.special_zone_6_coin_block_22: [0x4B, 577], + LocationName.special_zone_6_coin_block_23: [0x4B, 578], + LocationName.special_zone_6_coin_block_24: [0x4B, 579], + LocationName.special_zone_6_coin_block_25: [0x4B, 580], + LocationName.special_zone_6_coin_block_26: [0x4B, 581], + LocationName.special_zone_6_coin_block_27: [0x4B, 582], + LocationName.special_zone_6_coin_block_28: [0x4B, 583], + LocationName.special_zone_6_powerup_block_2: [0x4B, 584], + LocationName.special_zone_6_coin_block_29: [0x4B, 585], + LocationName.special_zone_6_coin_block_30: [0x4B, 586], + LocationName.special_zone_6_coin_block_31: [0x4B, 587], + LocationName.special_zone_6_coin_block_32: [0x4B, 588], + LocationName.special_zone_6_coin_block_33: [0x4B, 589], + LocationName.special_zone_5_yoshi_block_1: [0x4C, 590], + LocationName.special_zone_1_vine_block_1: [0x4E, 591], + LocationName.special_zone_1_vine_block_2: [0x4E, 592], + LocationName.special_zone_1_vine_block_3: [0x4E, 593], + LocationName.special_zone_1_vine_block_4: [0x4E, 594], + LocationName.special_zone_1_life_block_1: [0x4E, 595], + LocationName.special_zone_1_vine_block_5: [0x4E, 596], + LocationName.special_zone_1_blue_pow_block_1: [0x4E, 597], + LocationName.special_zone_1_vine_block_6: [0x4E, 598], + LocationName.special_zone_1_powerup_block_1: [0x4E, 599], + LocationName.special_zone_1_pswitch_coin_block_1: [0x4E, 600], + LocationName.special_zone_1_pswitch_coin_block_2: [0x4E, 601], + LocationName.special_zone_1_pswitch_coin_block_3: [0x4E, 602], + LocationName.special_zone_1_pswitch_coin_block_4: [0x4E, 603], + LocationName.special_zone_1_pswitch_coin_block_5: [0x4E, 604], + LocationName.special_zone_1_pswitch_coin_block_6: [0x4E, 605], + LocationName.special_zone_1_pswitch_coin_block_7: [0x4E, 606], + LocationName.special_zone_1_pswitch_coin_block_8: [0x4E, 607], + LocationName.special_zone_1_pswitch_coin_block_9: [0x4E, 608], + LocationName.special_zone_1_pswitch_coin_block_10: [0x4E, 609], + LocationName.special_zone_1_pswitch_coin_block_11: [0x4E, 610], + LocationName.special_zone_1_pswitch_coin_block_12: [0x4E, 611], + LocationName.special_zone_1_pswitch_coin_block_13: [0x4E, 612], + LocationName.special_zone_2_powerup_block_1: [0x4F, 613], + LocationName.special_zone_2_coin_block_1: [0x4F, 614], + LocationName.special_zone_2_coin_block_2: [0x4F, 615], + LocationName.special_zone_2_powerup_block_2: [0x4F, 616], + LocationName.special_zone_2_coin_block_3: [0x4F, 617], + LocationName.special_zone_2_coin_block_4: [0x4F, 618], + LocationName.special_zone_2_powerup_block_3: [0x4F, 619], + LocationName.special_zone_2_multi_coin_block_1: [0x4F, 620], + LocationName.special_zone_2_coin_block_5: [0x4F, 621], + LocationName.special_zone_2_coin_block_6: [0x4F, 622], + LocationName.special_zone_3_powerup_block_1: [0x50, 623], + LocationName.special_zone_3_yoshi_block_1: [0x50, 624], + LocationName.special_zone_3_wings_block_1: [0x50, 625], + LocationName.special_zone_4_powerup_block_1: [0x51, 626], + LocationName.special_zone_4_star_block_1: [0x51, 627], + LocationName.star_road_2_star_block_1: [0x54, 628], + LocationName.star_road_3_key_block_1: [0x56, 629], + LocationName.star_road_4_powerup_block_1: [0x59, 630], + LocationName.star_road_4_green_block_1: [0x59, 631], + LocationName.star_road_4_green_block_2: [0x59, 632], + LocationName.star_road_4_green_block_3: [0x59, 633], + LocationName.star_road_4_green_block_4: [0x59, 634], + LocationName.star_road_4_green_block_5: [0x59, 635], + LocationName.star_road_4_green_block_6: [0x59, 636], + LocationName.star_road_4_green_block_7: [0x59, 637], + LocationName.star_road_4_key_block_1: [0x59, 638], + LocationName.star_road_5_directional_coin_block_1: [0x5A, 639], + LocationName.star_road_5_life_block_1: [0x5A, 640], + LocationName.star_road_5_vine_block_1: [0x5A, 641], + LocationName.star_road_5_yellow_block_1: [0x5A, 642], + LocationName.star_road_5_yellow_block_2: [0x5A, 643], + LocationName.star_road_5_yellow_block_3: [0x5A, 644], + LocationName.star_road_5_yellow_block_4: [0x5A, 645], + LocationName.star_road_5_yellow_block_5: [0x5A, 646], + LocationName.star_road_5_yellow_block_6: [0x5A, 647], + LocationName.star_road_5_yellow_block_7: [0x5A, 648], + LocationName.star_road_5_yellow_block_8: [0x5A, 649], + LocationName.star_road_5_yellow_block_9: [0x5A, 650], + LocationName.star_road_5_yellow_block_10: [0x5A, 651], + LocationName.star_road_5_yellow_block_11: [0x5A, 652], + LocationName.star_road_5_yellow_block_12: [0x5A, 653], + LocationName.star_road_5_yellow_block_13: [0x5A, 654], + LocationName.star_road_5_yellow_block_14: [0x5A, 655], + LocationName.star_road_5_yellow_block_15: [0x5A, 656], + LocationName.star_road_5_yellow_block_16: [0x5A, 657], + LocationName.star_road_5_yellow_block_17: [0x5A, 658], + LocationName.star_road_5_yellow_block_18: [0x5A, 659], + LocationName.star_road_5_yellow_block_19: [0x5A, 660], + LocationName.star_road_5_yellow_block_20: [0x5A, 661], + LocationName.star_road_5_green_block_1: [0x5A, 662], + LocationName.star_road_5_green_block_2: [0x5A, 663], + LocationName.star_road_5_green_block_3: [0x5A, 664], + LocationName.star_road_5_green_block_4: [0x5A, 665], + LocationName.star_road_5_green_block_5: [0x5A, 666], + LocationName.star_road_5_green_block_6: [0x5A, 667], + LocationName.star_road_5_green_block_7: [0x5A, 668], + LocationName.star_road_5_green_block_8: [0x5A, 669], + LocationName.star_road_5_green_block_9: [0x5A, 670], + LocationName.star_road_5_green_block_10: [0x5A, 671], + LocationName.star_road_5_green_block_11: [0x5A, 672], + LocationName.star_road_5_green_block_12: [0x5A, 673], + LocationName.star_road_5_green_block_13: [0x5A, 674], + LocationName.star_road_5_green_block_14: [0x5A, 675], + LocationName.star_road_5_green_block_15: [0x5A, 676], + LocationName.star_road_5_green_block_16: [0x5A, 677], + LocationName.star_road_5_green_block_17: [0x5A, 678], + LocationName.star_road_5_green_block_18: [0x5A, 679], + LocationName.star_road_5_green_block_19: [0x5A, 680], + LocationName.star_road_5_green_block_20: [0x5A, 681] } -def generate_level_list(world, player): +def generate_level_list(world: World): - if not world.level_shuffle[player]: + if not world.options.level_shuffle: out_level_list = full_level_list.copy() out_level_list[0x00] = 0x03 out_level_list[0x11] = 0x28 - if world.bowser_castle_doors[player] == "fast": + if world.options.bowser_castle_doors == "fast": out_level_list[0x41] = 0x82 out_level_list[0x42] = 0x32 - elif world.bowser_castle_doors[player] == "slow": + elif world.options.bowser_castle_doors == "slow": out_level_list[0x41] = 0x31 out_level_list[0x42] = 0x81 @@ -552,7 +1258,7 @@ def generate_level_list(world, player): shuffled_level_list.append(0x16) single_levels_copy = (easy_single_levels_copy.copy() + hard_single_levels_copy.copy()) - if not world.exclude_special_zone[player]: + if not world.options.exclude_special_zone: single_levels_copy.extend(special_zone_levels_copy) world.random.shuffle(single_levels_copy) @@ -619,10 +1325,10 @@ def generate_level_list(world, player): shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) # Front/Back Door - if world.bowser_castle_doors[player] == "fast": + if world.options.bowser_castle_doors == "fast": shuffled_level_list.append(0x82) shuffled_level_list.append(0x32) - elif world.bowser_castle_doors[player] == "slow": + elif world.options.bowser_castle_doors == "slow": shuffled_level_list.append(0x31) shuffled_level_list.append(0x81) else: @@ -646,7 +1352,7 @@ def generate_level_list(world, player): # Special Zone shuffled_level_list.append(0x4D) - if not world.exclude_special_zone[player]: + if not world.options.exclude_special_zone: shuffled_level_list.append(single_levels_copy.pop(0)) shuffled_level_list.append(single_levels_copy.pop(0)) shuffled_level_list.append(single_levels_copy.pop(0)) diff --git a/worlds/smw/Locations.py b/worlds/smw/Locations.py index a8b7f7a4ec..47e821fc61 100644 --- a/worlds/smw/Locations.py +++ b/worlds/smw/Locations.py @@ -1,9 +1,9 @@ import typing from BaseClasses import Location +from worlds.AutoWorld import World from .Names import LocationName - class SMWLocation(Location): game: str = "Super Mario World" @@ -197,6 +197,624 @@ dragon_coin_location_table = { LocationName.special_zone_8_dragon: 0xBC0162, } +moon_location_table = { + LocationName.yoshis_island_1_moon: 0xBC0300, + LocationName.donut_plains_4_moon: 0xBC030B, + LocationName.vanilla_dome_3_moon: 0xBC0318, + LocationName.cheese_bridge_moon: 0xBC0325, + LocationName.forest_ghost_house_moon: 0xBC0332, + LocationName.chocolate_island_1_moon: 0xBC0338, + LocationName.valley_of_bowser_1_moon: 0xBC0345 +} + +hidden_1ups_location_table = { + LocationName.yoshis_island_4_hidden_1up: 0xBC0403, + LocationName.donut_plains_1_hidden_1up: 0xBC0406, + LocationName.donut_plains_4_hidden_1up: 0xBC040B, + LocationName.donut_plains_castle_hidden_1up: 0xBC0412, + LocationName.vanilla_dome_4_hidden_1up: 0xBC0419, + LocationName.vanilla_ghost_house_hidden_1up: 0xBC041E, + LocationName.vanilla_fortress_hidden_1up: 0xBC0420, + LocationName.cookie_mountain_hidden_1up: 0xBC0427, + LocationName.forest_of_illusion_3_hidden_1up: 0xBC042E, + LocationName.chocolate_island_2_hidden_1up: 0xBC0439, + LocationName.chocolate_castle_hidden_1up: 0xBC0443, + LocationName.valley_of_bowser_2_hidden_1up: 0xBC0446, + LocationName.valley_castle_hidden_1up: 0xBC044F, + LocationName.special_zone_1_hidden_1up: 0xBC045B +} +bonus_block_location_table = { + LocationName.yoshis_island_3_bonus_block: 0xBC0502, + LocationName.donut_plains_3_bonus_block: 0xBC050A, + LocationName.butter_bridge_1_bonus_block: 0xBC0523, + LocationName.chocolate_island_3_bonus_block: 0xBC053B +} + +blocksanity_location_table = { + LocationName.vanilla_secret_2_yoshi_block_1: 0xBC0600, + LocationName.vanilla_secret_2_green_block_1: 0xBC0601, + LocationName.vanilla_secret_2_powerup_block_1: 0xBC0602, + LocationName.vanilla_secret_2_powerup_block_2: 0xBC0603, + LocationName.vanilla_secret_2_multi_coin_block_1: 0xBC0604, + LocationName.vanilla_secret_2_gray_pow_block_1: 0xBC0605, + LocationName.vanilla_secret_2_coin_block_1: 0xBC0606, + LocationName.vanilla_secret_2_coin_block_2: 0xBC0607, + LocationName.vanilla_secret_2_coin_block_3: 0xBC0608, + LocationName.vanilla_secret_2_coin_block_4: 0xBC0609, + LocationName.vanilla_secret_2_coin_block_5: 0xBC060A, + LocationName.vanilla_secret_2_coin_block_6: 0xBC060B, + LocationName.vanilla_secret_3_powerup_block_1: 0xBC060C, + LocationName.vanilla_secret_3_powerup_block_2: 0xBC060D, + LocationName.donut_ghost_house_vine_block_1: 0xBC060E, + LocationName.donut_ghost_house_directional_coin_block_1: 0xBC060F, + LocationName.donut_ghost_house_life_block_1: 0xBC0610, + LocationName.donut_ghost_house_life_block_2: 0xBC0611, + LocationName.donut_ghost_house_life_block_3: 0xBC0612, + LocationName.donut_ghost_house_life_block_4: 0xBC0613, + LocationName.donut_plains_3_green_block_1: 0xBC0614, + LocationName.donut_plains_3_coin_block_1: 0xBC0615, + LocationName.donut_plains_3_coin_block_2: 0xBC0616, + LocationName.donut_plains_3_vine_block_1: 0xBC0617, + LocationName.donut_plains_3_powerup_block_1: 0xBC0618, + LocationName.donut_plains_3_bonus_block_1: 0xBC0619, + LocationName.donut_plains_4_coin_block_1: 0xBC061A, + LocationName.donut_plains_4_powerup_block_1: 0xBC061B, + LocationName.donut_plains_4_coin_block_2: 0xBC061C, + LocationName.donut_plains_4_yoshi_block_1: 0xBC061D, + LocationName.donut_plains_castle_yellow_block_1: 0xBC061E, + LocationName.donut_plains_castle_coin_block_1: 0xBC061F, + LocationName.donut_plains_castle_powerup_block_1: 0xBC0620, + LocationName.donut_plains_castle_coin_block_2: 0xBC0621, + LocationName.donut_plains_castle_vine_block_1: 0xBC0622, + LocationName.donut_plains_castle_invis_life_block_1: 0xBC0623, + LocationName.donut_plains_castle_coin_block_3: 0xBC0624, + LocationName.donut_plains_castle_coin_block_4: 0xBC0625, + LocationName.donut_plains_castle_coin_block_5: 0xBC0626, + LocationName.donut_plains_castle_green_block_1: 0xBC0627, + LocationName.donut_plains_2_coin_block_1: 0xBC0628, + LocationName.donut_plains_2_coin_block_2: 0xBC0629, + LocationName.donut_plains_2_coin_block_3: 0xBC062A, + LocationName.donut_plains_2_yellow_block_1: 0xBC062B, + LocationName.donut_plains_2_powerup_block_1: 0xBC062C, + LocationName.donut_plains_2_multi_coin_block_1: 0xBC062D, + LocationName.donut_plains_2_flying_block_1: 0xBC062E, + LocationName.donut_plains_2_green_block_1: 0xBC062F, + LocationName.donut_plains_2_yellow_block_2: 0xBC0630, + LocationName.donut_plains_2_vine_block_1: 0xBC0631, + LocationName.donut_secret_1_coin_block_1: 0xBC0632, + LocationName.donut_secret_1_coin_block_2: 0xBC0633, + LocationName.donut_secret_1_powerup_block_1: 0xBC0634, + LocationName.donut_secret_1_coin_block_3: 0xBC0635, + LocationName.donut_secret_1_powerup_block_2: 0xBC0636, + LocationName.donut_secret_1_powerup_block_3: 0xBC0637, + LocationName.donut_secret_1_life_block_1: 0xBC0638, + LocationName.donut_secret_1_powerup_block_4: 0xBC0639, + LocationName.donut_secret_1_powerup_block_5: 0xBC063A, + LocationName.donut_secret_1_key_block_1: 0xBC063B, + LocationName.vanilla_fortress_powerup_block_1: 0xBC063C, + LocationName.vanilla_fortress_powerup_block_2: 0xBC063D, + LocationName.vanilla_fortress_yellow_block_1: 0xBC063E, + LocationName.butter_bridge_1_powerup_block_1: 0xBC063F, + LocationName.butter_bridge_1_multi_coin_block_1: 0xBC0640, + LocationName.butter_bridge_1_multi_coin_block_2: 0xBC0641, + LocationName.butter_bridge_1_multi_coin_block_3: 0xBC0642, + LocationName.butter_bridge_1_life_block_1: 0xBC0643, + LocationName.butter_bridge_1_bonus_block_1: 0xBC0644, + LocationName.butter_bridge_2_powerup_block_1: 0xBC0645, + LocationName.butter_bridge_2_green_block_1: 0xBC0646, + LocationName.butter_bridge_2_yoshi_block_1: 0xBC0647, + LocationName.twin_bridges_castle_powerup_block_1: 0xBC0648, + LocationName.cheese_bridge_powerup_block_1: 0xBC0649, + LocationName.cheese_bridge_powerup_block_2: 0xBC064A, + LocationName.cheese_bridge_wings_block_1: 0xBC064B, + LocationName.cheese_bridge_powerup_block_3: 0xBC064C, + LocationName.cookie_mountain_coin_block_1: 0xBC064D, + LocationName.cookie_mountain_coin_block_2: 0xBC064E, + LocationName.cookie_mountain_coin_block_3: 0xBC064F, + LocationName.cookie_mountain_coin_block_4: 0xBC0650, + LocationName.cookie_mountain_coin_block_5: 0xBC0651, + LocationName.cookie_mountain_coin_block_6: 0xBC0652, + LocationName.cookie_mountain_coin_block_7: 0xBC0653, + LocationName.cookie_mountain_coin_block_8: 0xBC0654, + LocationName.cookie_mountain_coin_block_9: 0xBC0655, + LocationName.cookie_mountain_powerup_block_1: 0xBC0656, + LocationName.cookie_mountain_life_block_1: 0xBC0657, + LocationName.cookie_mountain_vine_block_1: 0xBC0658, + LocationName.cookie_mountain_yoshi_block_1: 0xBC0659, + LocationName.cookie_mountain_coin_block_10: 0xBC065A, + LocationName.cookie_mountain_coin_block_11: 0xBC065B, + LocationName.cookie_mountain_powerup_block_2: 0xBC065C, + LocationName.cookie_mountain_coin_block_12: 0xBC065D, + LocationName.cookie_mountain_coin_block_13: 0xBC065E, + LocationName.cookie_mountain_coin_block_14: 0xBC065F, + LocationName.cookie_mountain_coin_block_15: 0xBC0660, + LocationName.cookie_mountain_coin_block_16: 0xBC0661, + LocationName.cookie_mountain_coin_block_17: 0xBC0662, + LocationName.cookie_mountain_coin_block_18: 0xBC0663, + LocationName.cookie_mountain_coin_block_19: 0xBC0664, + LocationName.cookie_mountain_coin_block_20: 0xBC0665, + LocationName.cookie_mountain_coin_block_21: 0xBC0666, + LocationName.cookie_mountain_coin_block_22: 0xBC0667, + LocationName.cookie_mountain_coin_block_23: 0xBC0668, + LocationName.cookie_mountain_coin_block_24: 0xBC0669, + LocationName.cookie_mountain_coin_block_25: 0xBC066A, + LocationName.cookie_mountain_coin_block_26: 0xBC066B, + LocationName.cookie_mountain_coin_block_27: 0xBC066C, + LocationName.cookie_mountain_coin_block_28: 0xBC066D, + LocationName.cookie_mountain_coin_block_29: 0xBC066E, + LocationName.cookie_mountain_coin_block_30: 0xBC066F, + LocationName.soda_lake_powerup_block_1: 0xBC0670, + LocationName.donut_secret_house_powerup_block_1: 0xBC0671, + LocationName.donut_secret_house_multi_coin_block_1: 0xBC0672, + LocationName.donut_secret_house_life_block_1: 0xBC0673, + LocationName.donut_secret_house_vine_block_1: 0xBC0674, + LocationName.donut_secret_house_directional_coin_block_1: 0xBC0675, + LocationName.donut_plains_1_coin_block_1: 0xBC0676, + LocationName.donut_plains_1_coin_block_2: 0xBC0677, + LocationName.donut_plains_1_yoshi_block_1: 0xBC0678, + LocationName.donut_plains_1_vine_block_1: 0xBC0679, + LocationName.donut_plains_1_green_block_1: 0xBC067A, + LocationName.donut_plains_1_green_block_2: 0xBC067B, + LocationName.donut_plains_1_green_block_3: 0xBC067C, + LocationName.donut_plains_1_green_block_4: 0xBC067D, + LocationName.donut_plains_1_green_block_5: 0xBC067E, + LocationName.donut_plains_1_green_block_6: 0xBC067F, + LocationName.donut_plains_1_green_block_7: 0xBC0680, + LocationName.donut_plains_1_green_block_8: 0xBC0681, + LocationName.donut_plains_1_green_block_9: 0xBC0682, + LocationName.donut_plains_1_green_block_10: 0xBC0683, + LocationName.donut_plains_1_green_block_11: 0xBC0684, + LocationName.donut_plains_1_green_block_12: 0xBC0685, + LocationName.donut_plains_1_green_block_13: 0xBC0686, + LocationName.donut_plains_1_green_block_14: 0xBC0687, + LocationName.donut_plains_1_green_block_15: 0xBC0688, + LocationName.donut_plains_1_green_block_16: 0xBC0689, + LocationName.donut_plains_1_yellow_block_1: 0xBC068A, + LocationName.donut_plains_1_yellow_block_2: 0xBC068B, + LocationName.donut_plains_1_yellow_block_3: 0xBC068C, + LocationName.sunken_ghost_ship_powerup_block_1: 0xBC068D, + LocationName.sunken_ghost_ship_star_block_1: 0xBC068E, + LocationName.chocolate_castle_yellow_block_1: 0xBC068F, + LocationName.chocolate_castle_yellow_block_2: 0xBC0690, + LocationName.chocolate_castle_green_block_1: 0xBC0691, + LocationName.chocolate_fortress_powerup_block_1: 0xBC0692, + LocationName.chocolate_fortress_powerup_block_2: 0xBC0693, + LocationName.chocolate_fortress_coin_block_1: 0xBC0694, + LocationName.chocolate_fortress_coin_block_2: 0xBC0695, + LocationName.chocolate_fortress_green_block_1: 0xBC0696, + LocationName.chocolate_island_5_yoshi_block_1: 0xBC0697, + LocationName.chocolate_island_5_powerup_block_1: 0xBC0698, + LocationName.chocolate_island_5_life_block_1: 0xBC0699, + LocationName.chocolate_island_5_yellow_block_1: 0xBC069A, + LocationName.chocolate_island_4_yellow_block_1: 0xBC069B, + LocationName.chocolate_island_4_blue_pow_block_1: 0xBC069C, + LocationName.chocolate_island_4_powerup_block_1: 0xBC069D, + LocationName.forest_fortress_yellow_block_1: 0xBC069E, + LocationName.forest_fortress_powerup_block_1: 0xBC069F, + LocationName.forest_fortress_life_block_1: 0xBC06A0, + LocationName.forest_fortress_life_block_2: 0xBC06A1, + LocationName.forest_fortress_life_block_3: 0xBC06A2, + LocationName.forest_fortress_life_block_4: 0xBC06A3, + LocationName.forest_fortress_life_block_5: 0xBC06A4, + LocationName.forest_fortress_life_block_6: 0xBC06A5, + LocationName.forest_fortress_life_block_7: 0xBC06A6, + LocationName.forest_fortress_life_block_8: 0xBC06A7, + LocationName.forest_fortress_life_block_9: 0xBC06A8, + LocationName.forest_castle_green_block_1: 0xBC06A9, + LocationName.chocolate_ghost_house_powerup_block_1: 0xBC06AA, + LocationName.chocolate_ghost_house_powerup_block_2: 0xBC06AB, + LocationName.chocolate_ghost_house_life_block_1: 0xBC06AC, + LocationName.chocolate_island_1_flying_block_1: 0xBC06AD, + LocationName.chocolate_island_1_flying_block_2: 0xBC06AE, + LocationName.chocolate_island_1_yoshi_block_1: 0xBC06AF, + LocationName.chocolate_island_1_green_block_1: 0xBC06B0, + LocationName.chocolate_island_1_life_block_1: 0xBC06B1, + LocationName.chocolate_island_3_powerup_block_1: 0xBC06B2, + LocationName.chocolate_island_3_powerup_block_2: 0xBC06B3, + LocationName.chocolate_island_3_powerup_block_3: 0xBC06B4, + LocationName.chocolate_island_3_green_block_1: 0xBC06B5, + LocationName.chocolate_island_3_bonus_block_1: 0xBC06B6, + LocationName.chocolate_island_3_vine_block_1: 0xBC06B7, + LocationName.chocolate_island_3_life_block_1: 0xBC06B8, + LocationName.chocolate_island_3_life_block_2: 0xBC06B9, + LocationName.chocolate_island_3_life_block_3: 0xBC06BA, + LocationName.chocolate_island_2_multi_coin_block_1: 0xBC06BB, + LocationName.chocolate_island_2_invis_coin_block_1: 0xBC06BC, + LocationName.chocolate_island_2_yoshi_block_1: 0xBC06BD, + LocationName.chocolate_island_2_coin_block_1: 0xBC06BE, + LocationName.chocolate_island_2_coin_block_2: 0xBC06BF, + LocationName.chocolate_island_2_multi_coin_block_2: 0xBC06C0, + LocationName.chocolate_island_2_powerup_block_1: 0xBC06C1, + LocationName.chocolate_island_2_blue_pow_block_1: 0xBC06C2, + LocationName.chocolate_island_2_yellow_block_1: 0xBC06C3, + LocationName.chocolate_island_2_yellow_block_2: 0xBC06C4, + LocationName.chocolate_island_2_green_block_1: 0xBC06C5, + LocationName.chocolate_island_2_green_block_2: 0xBC06C6, + LocationName.chocolate_island_2_green_block_3: 0xBC06C7, + LocationName.chocolate_island_2_green_block_4: 0xBC06C8, + LocationName.chocolate_island_2_green_block_5: 0xBC06C9, + LocationName.chocolate_island_2_green_block_6: 0xBC06CA, + LocationName.yoshis_island_castle_coin_block_1: 0xBC06CB, + LocationName.yoshis_island_castle_coin_block_2: 0xBC06CC, + LocationName.yoshis_island_castle_powerup_block_1: 0xBC06CD, + LocationName.yoshis_island_castle_coin_block_3: 0xBC06CE, + LocationName.yoshis_island_castle_coin_block_4: 0xBC06CF, + LocationName.yoshis_island_castle_flying_block_1: 0xBC06D0, + LocationName.yoshis_island_4_yellow_block_1: 0xBC06D1, + LocationName.yoshis_island_4_powerup_block_1: 0xBC06D2, + LocationName.yoshis_island_4_multi_coin_block_1: 0xBC06D3, + LocationName.yoshis_island_4_star_block_1: 0xBC06D4, + LocationName.yoshis_island_3_yellow_block_1: 0xBC06D5, + LocationName.yoshis_island_3_yellow_block_2: 0xBC06D6, + LocationName.yoshis_island_3_yellow_block_3: 0xBC06D7, + LocationName.yoshis_island_3_yellow_block_4: 0xBC06D8, + LocationName.yoshis_island_3_yellow_block_5: 0xBC06D9, + LocationName.yoshis_island_3_yellow_block_6: 0xBC06DA, + LocationName.yoshis_island_3_yellow_block_7: 0xBC06DB, + LocationName.yoshis_island_3_yellow_block_8: 0xBC06DC, + LocationName.yoshis_island_3_yellow_block_9: 0xBC06DD, + LocationName.yoshis_island_3_coin_block_1: 0xBC06DE, + LocationName.yoshis_island_3_yoshi_block_1: 0xBC06DF, + LocationName.yoshis_island_3_coin_block_2: 0xBC06E0, + LocationName.yoshis_island_3_powerup_block_1: 0xBC06E1, + LocationName.yoshis_island_3_yellow_block_10: 0xBC06E2, + LocationName.yoshis_island_3_yellow_block_11: 0xBC06E3, + LocationName.yoshis_island_3_yellow_block_12: 0xBC06E4, + LocationName.yoshis_island_3_bonus_block_1: 0xBC06E5, + LocationName.yoshis_island_1_flying_block_1: 0xBC06E6, + LocationName.yoshis_island_1_yellow_block_1: 0xBC06E7, + LocationName.yoshis_island_1_life_block_1: 0xBC06E8, + LocationName.yoshis_island_1_powerup_block_1: 0xBC06E9, + LocationName.yoshis_island_2_flying_block_1: 0xBC06EA, + LocationName.yoshis_island_2_flying_block_2: 0xBC06EB, + LocationName.yoshis_island_2_flying_block_3: 0xBC06EC, + LocationName.yoshis_island_2_flying_block_4: 0xBC06ED, + LocationName.yoshis_island_2_flying_block_5: 0xBC06EE, + LocationName.yoshis_island_2_flying_block_6: 0xBC06EF, + LocationName.yoshis_island_2_coin_block_1: 0xBC06F0, + LocationName.yoshis_island_2_yellow_block_1: 0xBC06F1, + LocationName.yoshis_island_2_coin_block_2: 0xBC06F2, + LocationName.yoshis_island_2_coin_block_3: 0xBC06F3, + LocationName.yoshis_island_2_yoshi_block_1: 0xBC06F4, + LocationName.yoshis_island_2_coin_block_4: 0xBC06F5, + LocationName.yoshis_island_2_yoshi_block_2: 0xBC06F6, + LocationName.yoshis_island_2_coin_block_5: 0xBC06F7, + LocationName.yoshis_island_2_vine_block_1: 0xBC06F8, + LocationName.yoshis_island_2_yellow_block_2: 0xBC06F9, + LocationName.vanilla_ghost_house_powerup_block_1: 0xBC06FA, + LocationName.vanilla_ghost_house_vine_block_1: 0xBC06FB, + LocationName.vanilla_ghost_house_powerup_block_2: 0xBC06FC, + LocationName.vanilla_ghost_house_multi_coin_block_1: 0xBC06FD, + LocationName.vanilla_ghost_house_blue_pow_block_1: 0xBC06FE, + LocationName.vanilla_secret_1_coin_block_1: 0xBC06FF, + LocationName.vanilla_secret_1_powerup_block_1: 0xBC0700, + LocationName.vanilla_secret_1_multi_coin_block_1: 0xBC0701, + LocationName.vanilla_secret_1_vine_block_1: 0xBC0702, + LocationName.vanilla_secret_1_vine_block_2: 0xBC0703, + LocationName.vanilla_secret_1_coin_block_2: 0xBC0704, + LocationName.vanilla_secret_1_coin_block_3: 0xBC0705, + LocationName.vanilla_secret_1_powerup_block_2: 0xBC0706, + LocationName.vanilla_dome_3_coin_block_1: 0xBC0707, + LocationName.vanilla_dome_3_flying_block_1: 0xBC0708, + LocationName.vanilla_dome_3_flying_block_2: 0xBC0709, + LocationName.vanilla_dome_3_powerup_block_1: 0xBC070A, + LocationName.vanilla_dome_3_flying_block_3: 0xBC070B, + LocationName.vanilla_dome_3_invis_coin_block_1: 0xBC070C, + LocationName.vanilla_dome_3_powerup_block_2: 0xBC070D, + LocationName.vanilla_dome_3_multi_coin_block_1: 0xBC070E, + LocationName.vanilla_dome_3_powerup_block_3: 0xBC070F, + LocationName.vanilla_dome_3_yoshi_block_1: 0xBC0710, + LocationName.vanilla_dome_3_powerup_block_4: 0xBC0711, + LocationName.vanilla_dome_3_pswitch_coin_block_1: 0xBC0712, + LocationName.vanilla_dome_3_pswitch_coin_block_2: 0xBC0713, + LocationName.vanilla_dome_3_pswitch_coin_block_3: 0xBC0714, + LocationName.vanilla_dome_3_pswitch_coin_block_4: 0xBC0715, + LocationName.vanilla_dome_3_pswitch_coin_block_5: 0xBC0716, + LocationName.vanilla_dome_3_pswitch_coin_block_6: 0xBC0717, + LocationName.donut_secret_2_directional_coin_block_1: 0xBC0718, + LocationName.donut_secret_2_vine_block_1: 0xBC0719, + LocationName.donut_secret_2_star_block_1: 0xBC071A, + LocationName.donut_secret_2_powerup_block_1: 0xBC071B, + LocationName.donut_secret_2_star_block_2: 0xBC071C, + LocationName.valley_of_bowser_4_yellow_block_1: 0xBC071D, + LocationName.valley_of_bowser_4_powerup_block_1: 0xBC071E, + LocationName.valley_of_bowser_4_vine_block_1: 0xBC071F, + LocationName.valley_of_bowser_4_yoshi_block_1: 0xBC0720, + LocationName.valley_of_bowser_4_life_block_1: 0xBC0721, + LocationName.valley_of_bowser_4_powerup_block_2: 0xBC0722, + LocationName.valley_castle_yellow_block_1: 0xBC0723, + LocationName.valley_castle_yellow_block_2: 0xBC0724, + LocationName.valley_castle_green_block_1: 0xBC0725, + LocationName.valley_fortress_green_block_1: 0xBC0726, + LocationName.valley_fortress_yellow_block_1: 0xBC0727, + LocationName.valley_of_bowser_3_powerup_block_1: 0xBC0728, + LocationName.valley_of_bowser_3_powerup_block_2: 0xBC0729, + LocationName.valley_ghost_house_pswitch_coin_block_1: 0xBC072A, + LocationName.valley_ghost_house_multi_coin_block_1: 0xBC072B, + LocationName.valley_ghost_house_powerup_block_1: 0xBC072C, + LocationName.valley_ghost_house_directional_coin_block_1: 0xBC072D, + LocationName.valley_of_bowser_2_powerup_block_1: 0xBC072E, + LocationName.valley_of_bowser_2_yellow_block_1: 0xBC072F, + LocationName.valley_of_bowser_2_powerup_block_2: 0xBC0730, + LocationName.valley_of_bowser_2_wings_block_1: 0xBC0731, + LocationName.valley_of_bowser_1_green_block_1: 0xBC0732, + LocationName.valley_of_bowser_1_invis_coin_block_1: 0xBC0733, + LocationName.valley_of_bowser_1_invis_coin_block_2: 0xBC0734, + LocationName.valley_of_bowser_1_invis_coin_block_3: 0xBC0735, + LocationName.valley_of_bowser_1_yellow_block_1: 0xBC0736, + LocationName.valley_of_bowser_1_yellow_block_2: 0xBC0737, + LocationName.valley_of_bowser_1_yellow_block_3: 0xBC0738, + LocationName.valley_of_bowser_1_yellow_block_4: 0xBC0739, + LocationName.valley_of_bowser_1_vine_block_1: 0xBC073A, + LocationName.chocolate_secret_powerup_block_1: 0xBC073B, + LocationName.chocolate_secret_powerup_block_2: 0xBC073C, + LocationName.vanilla_dome_2_coin_block_1: 0xBC073D, + LocationName.vanilla_dome_2_powerup_block_1: 0xBC073E, + LocationName.vanilla_dome_2_coin_block_2: 0xBC073F, + LocationName.vanilla_dome_2_coin_block_3: 0xBC0740, + LocationName.vanilla_dome_2_vine_block_1: 0xBC0741, + LocationName.vanilla_dome_2_invis_life_block_1: 0xBC0742, + LocationName.vanilla_dome_2_coin_block_4: 0xBC0743, + LocationName.vanilla_dome_2_coin_block_5: 0xBC0744, + LocationName.vanilla_dome_2_powerup_block_2: 0xBC0745, + LocationName.vanilla_dome_2_powerup_block_3: 0xBC0746, + LocationName.vanilla_dome_2_powerup_block_4: 0xBC0747, + LocationName.vanilla_dome_2_powerup_block_5: 0xBC0748, + LocationName.vanilla_dome_2_multi_coin_block_1: 0xBC0749, + LocationName.vanilla_dome_2_multi_coin_block_2: 0xBC074A, + LocationName.vanilla_dome_4_powerup_block_1: 0xBC074B, + LocationName.vanilla_dome_4_powerup_block_2: 0xBC074C, + LocationName.vanilla_dome_4_coin_block_1: 0xBC074D, + LocationName.vanilla_dome_4_coin_block_2: 0xBC074E, + LocationName.vanilla_dome_4_coin_block_3: 0xBC074F, + LocationName.vanilla_dome_4_life_block_1: 0xBC0750, + LocationName.vanilla_dome_4_coin_block_4: 0xBC0751, + LocationName.vanilla_dome_4_coin_block_5: 0xBC0752, + LocationName.vanilla_dome_4_coin_block_6: 0xBC0753, + LocationName.vanilla_dome_4_coin_block_7: 0xBC0754, + LocationName.vanilla_dome_4_coin_block_8: 0xBC0755, + LocationName.vanilla_dome_1_flying_block_1: 0xBC0756, + LocationName.vanilla_dome_1_powerup_block_1: 0xBC0757, + LocationName.vanilla_dome_1_powerup_block_2: 0xBC0758, + LocationName.vanilla_dome_1_coin_block_1: 0xBC0759, + LocationName.vanilla_dome_1_life_block_1: 0xBC075A, + LocationName.vanilla_dome_1_powerup_block_3: 0xBC075B, + LocationName.vanilla_dome_1_vine_block_1: 0xBC075C, + LocationName.vanilla_dome_1_star_block_1: 0xBC075D, + LocationName.vanilla_dome_1_powerup_block_4: 0xBC075E, + LocationName.vanilla_dome_1_coin_block_2: 0xBC075F, + LocationName.vanilla_dome_castle_life_block_1: 0xBC0760, + LocationName.vanilla_dome_castle_life_block_2: 0xBC0761, + LocationName.vanilla_dome_castle_powerup_block_1: 0xBC0762, + LocationName.vanilla_dome_castle_life_block_3: 0xBC0763, + LocationName.vanilla_dome_castle_green_block_1: 0xBC0764, + LocationName.forest_ghost_house_coin_block_1: 0xBC0765, + LocationName.forest_ghost_house_powerup_block_1: 0xBC0766, + LocationName.forest_ghost_house_flying_block_1: 0xBC0767, + LocationName.forest_ghost_house_powerup_block_2: 0xBC0768, + LocationName.forest_ghost_house_life_block_1: 0xBC0769, + LocationName.forest_of_illusion_1_powerup_block_1: 0xBC076A, + LocationName.forest_of_illusion_1_yoshi_block_1: 0xBC076B, + LocationName.forest_of_illusion_1_powerup_block_2: 0xBC076C, + LocationName.forest_of_illusion_1_key_block_1: 0xBC076D, + LocationName.forest_of_illusion_1_life_block_1: 0xBC076E, + LocationName.forest_of_illusion_4_multi_coin_block_1: 0xBC076F, + LocationName.forest_of_illusion_4_coin_block_1: 0xBC0770, + LocationName.forest_of_illusion_4_coin_block_2: 0xBC0771, + LocationName.forest_of_illusion_4_coin_block_3: 0xBC0772, + LocationName.forest_of_illusion_4_coin_block_4: 0xBC0773, + LocationName.forest_of_illusion_4_powerup_block_1: 0xBC0774, + LocationName.forest_of_illusion_4_coin_block_5: 0xBC0775, + LocationName.forest_of_illusion_4_coin_block_6: 0xBC0776, + LocationName.forest_of_illusion_4_coin_block_7: 0xBC0777, + LocationName.forest_of_illusion_4_powerup_block_2: 0xBC0778, + LocationName.forest_of_illusion_4_coin_block_8: 0xBC0779, + LocationName.forest_of_illusion_4_coin_block_9: 0xBC077A, + LocationName.forest_of_illusion_4_coin_block_10: 0xBC077B, + LocationName.forest_of_illusion_2_green_block_1: 0xBC077C, + LocationName.forest_of_illusion_2_powerup_block_1: 0xBC077D, + LocationName.forest_of_illusion_2_invis_coin_block_1: 0xBC077E, + LocationName.forest_of_illusion_2_invis_coin_block_2: 0xBC077F, + LocationName.forest_of_illusion_2_invis_life_block_1: 0xBC0780, + LocationName.forest_of_illusion_2_invis_coin_block_3: 0xBC0781, + LocationName.forest_of_illusion_2_yellow_block_1: 0xBC0782, + LocationName.forest_secret_powerup_block_1: 0xBC0783, + LocationName.forest_secret_powerup_block_2: 0xBC0784, + LocationName.forest_secret_life_block_1: 0xBC0785, + LocationName.forest_of_illusion_3_yoshi_block_1: 0xBC0786, + LocationName.forest_of_illusion_3_coin_block_1: 0xBC0787, + LocationName.forest_of_illusion_3_multi_coin_block_1: 0xBC0788, + LocationName.forest_of_illusion_3_coin_block_2: 0xBC0789, + LocationName.forest_of_illusion_3_multi_coin_block_2: 0xBC078A, + LocationName.forest_of_illusion_3_coin_block_3: 0xBC078B, + LocationName.forest_of_illusion_3_coin_block_4: 0xBC078C, + LocationName.forest_of_illusion_3_coin_block_5: 0xBC078D, + LocationName.forest_of_illusion_3_coin_block_6: 0xBC078E, + LocationName.forest_of_illusion_3_coin_block_7: 0xBC078F, + LocationName.forest_of_illusion_3_coin_block_8: 0xBC0790, + LocationName.forest_of_illusion_3_coin_block_9: 0xBC0791, + LocationName.forest_of_illusion_3_coin_block_10: 0xBC0792, + LocationName.forest_of_illusion_3_coin_block_11: 0xBC0793, + LocationName.forest_of_illusion_3_coin_block_12: 0xBC0794, + LocationName.forest_of_illusion_3_coin_block_13: 0xBC0795, + LocationName.forest_of_illusion_3_coin_block_14: 0xBC0796, + LocationName.forest_of_illusion_3_coin_block_15: 0xBC0797, + LocationName.forest_of_illusion_3_coin_block_16: 0xBC0798, + LocationName.forest_of_illusion_3_coin_block_17: 0xBC0799, + LocationName.forest_of_illusion_3_coin_block_18: 0xBC079A, + LocationName.forest_of_illusion_3_coin_block_19: 0xBC079B, + LocationName.forest_of_illusion_3_coin_block_20: 0xBC079C, + LocationName.forest_of_illusion_3_coin_block_21: 0xBC079D, + LocationName.forest_of_illusion_3_coin_block_22: 0xBC079E, + LocationName.forest_of_illusion_3_coin_block_23: 0xBC079F, + LocationName.forest_of_illusion_3_coin_block_24: 0xBC07A0, + LocationName.special_zone_8_yoshi_block_1: 0xBC07A1, + LocationName.special_zone_8_coin_block_1: 0xBC07A2, + LocationName.special_zone_8_coin_block_2: 0xBC07A3, + LocationName.special_zone_8_coin_block_3: 0xBC07A4, + LocationName.special_zone_8_coin_block_4: 0xBC07A5, + LocationName.special_zone_8_coin_block_5: 0xBC07A6, + LocationName.special_zone_8_blue_pow_block_1: 0xBC07A7, + LocationName.special_zone_8_powerup_block_1: 0xBC07A8, + LocationName.special_zone_8_star_block_1: 0xBC07A9, + LocationName.special_zone_8_coin_block_6: 0xBC07AA, + LocationName.special_zone_8_coin_block_7: 0xBC07AB, + LocationName.special_zone_8_coin_block_8: 0xBC07AC, + LocationName.special_zone_8_coin_block_9: 0xBC07AD, + LocationName.special_zone_8_coin_block_10: 0xBC07AE, + LocationName.special_zone_8_coin_block_11: 0xBC07AF, + LocationName.special_zone_8_coin_block_12: 0xBC07B0, + LocationName.special_zone_8_coin_block_13: 0xBC07B1, + LocationName.special_zone_8_coin_block_14: 0xBC07B2, + LocationName.special_zone_8_coin_block_15: 0xBC07B3, + LocationName.special_zone_8_coin_block_16: 0xBC07B4, + LocationName.special_zone_8_coin_block_17: 0xBC07B5, + LocationName.special_zone_8_coin_block_18: 0xBC07B6, + LocationName.special_zone_8_multi_coin_block_1: 0xBC07B7, + LocationName.special_zone_8_coin_block_19: 0xBC07B8, + LocationName.special_zone_8_coin_block_20: 0xBC07B9, + LocationName.special_zone_8_coin_block_21: 0xBC07BA, + LocationName.special_zone_8_coin_block_22: 0xBC07BB, + LocationName.special_zone_8_coin_block_23: 0xBC07BC, + LocationName.special_zone_8_powerup_block_2: 0xBC07BD, + LocationName.special_zone_8_flying_block_1: 0xBC07BE, + LocationName.special_zone_7_powerup_block_1: 0xBC07BF, + LocationName.special_zone_7_yoshi_block_1: 0xBC07C0, + LocationName.special_zone_7_coin_block_1: 0xBC07C1, + LocationName.special_zone_7_powerup_block_2: 0xBC07C2, + LocationName.special_zone_7_coin_block_2: 0xBC07C3, + LocationName.special_zone_6_powerup_block_1: 0xBC07C4, + LocationName.special_zone_6_coin_block_1: 0xBC07C5, + LocationName.special_zone_6_coin_block_2: 0xBC07C6, + LocationName.special_zone_6_yoshi_block_1: 0xBC07C7, + LocationName.special_zone_6_life_block_1: 0xBC07C8, + LocationName.special_zone_6_multi_coin_block_1: 0xBC07C9, + LocationName.special_zone_6_coin_block_3: 0xBC07CA, + LocationName.special_zone_6_coin_block_4: 0xBC07CB, + LocationName.special_zone_6_coin_block_5: 0xBC07CC, + LocationName.special_zone_6_coin_block_6: 0xBC07CD, + LocationName.special_zone_6_coin_block_7: 0xBC07CE, + LocationName.special_zone_6_coin_block_8: 0xBC07CF, + LocationName.special_zone_6_coin_block_9: 0xBC07D0, + LocationName.special_zone_6_coin_block_10: 0xBC07D1, + LocationName.special_zone_6_coin_block_11: 0xBC07D2, + LocationName.special_zone_6_coin_block_12: 0xBC07D3, + LocationName.special_zone_6_coin_block_13: 0xBC07D4, + LocationName.special_zone_6_coin_block_14: 0xBC07D5, + LocationName.special_zone_6_coin_block_15: 0xBC07D6, + LocationName.special_zone_6_coin_block_16: 0xBC07D7, + LocationName.special_zone_6_coin_block_17: 0xBC07D8, + LocationName.special_zone_6_coin_block_18: 0xBC07D9, + LocationName.special_zone_6_coin_block_19: 0xBC07DA, + LocationName.special_zone_6_coin_block_20: 0xBC07DB, + LocationName.special_zone_6_coin_block_21: 0xBC07DC, + LocationName.special_zone_6_coin_block_22: 0xBC07DD, + LocationName.special_zone_6_coin_block_23: 0xBC07DE, + LocationName.special_zone_6_coin_block_24: 0xBC07DF, + LocationName.special_zone_6_coin_block_25: 0xBC07E0, + LocationName.special_zone_6_coin_block_26: 0xBC07E1, + LocationName.special_zone_6_coin_block_27: 0xBC07E2, + LocationName.special_zone_6_coin_block_28: 0xBC07E3, + LocationName.special_zone_6_powerup_block_2: 0xBC07E4, + LocationName.special_zone_6_coin_block_29: 0xBC07E5, + LocationName.special_zone_6_coin_block_30: 0xBC07E6, + LocationName.special_zone_6_coin_block_31: 0xBC07E7, + LocationName.special_zone_6_coin_block_32: 0xBC07E8, + LocationName.special_zone_6_coin_block_33: 0xBC07E9, + LocationName.special_zone_5_yoshi_block_1: 0xBC07EA, + LocationName.special_zone_1_vine_block_1: 0xBC07EB, + LocationName.special_zone_1_vine_block_2: 0xBC07EC, + LocationName.special_zone_1_vine_block_3: 0xBC07ED, + LocationName.special_zone_1_vine_block_4: 0xBC07EE, + LocationName.special_zone_1_life_block_1: 0xBC07EF, + LocationName.special_zone_1_vine_block_5: 0xBC07F0, + LocationName.special_zone_1_blue_pow_block_1: 0xBC07F1, + LocationName.special_zone_1_vine_block_6: 0xBC07F2, + LocationName.special_zone_1_powerup_block_1: 0xBC07F3, + LocationName.special_zone_1_pswitch_coin_block_1: 0xBC07F4, + LocationName.special_zone_1_pswitch_coin_block_2: 0xBC07F5, + LocationName.special_zone_1_pswitch_coin_block_3: 0xBC07F6, + LocationName.special_zone_1_pswitch_coin_block_4: 0xBC07F7, + LocationName.special_zone_1_pswitch_coin_block_5: 0xBC07F8, + LocationName.special_zone_1_pswitch_coin_block_6: 0xBC07F9, + LocationName.special_zone_1_pswitch_coin_block_7: 0xBC07FA, + LocationName.special_zone_1_pswitch_coin_block_8: 0xBC07FB, + LocationName.special_zone_1_pswitch_coin_block_9: 0xBC07FC, + LocationName.special_zone_1_pswitch_coin_block_10: 0xBC07FD, + LocationName.special_zone_1_pswitch_coin_block_11: 0xBC07FE, + LocationName.special_zone_1_pswitch_coin_block_12: 0xBC07FF, + LocationName.special_zone_1_pswitch_coin_block_13: 0xBC0800, + LocationName.special_zone_2_powerup_block_1: 0xBC0801, + LocationName.special_zone_2_coin_block_1: 0xBC0802, + LocationName.special_zone_2_coin_block_2: 0xBC0803, + LocationName.special_zone_2_powerup_block_2: 0xBC0804, + LocationName.special_zone_2_coin_block_3: 0xBC0805, + LocationName.special_zone_2_coin_block_4: 0xBC0806, + LocationName.special_zone_2_powerup_block_3: 0xBC0807, + LocationName.special_zone_2_multi_coin_block_1: 0xBC0808, + LocationName.special_zone_2_coin_block_5: 0xBC0809, + LocationName.special_zone_2_coin_block_6: 0xBC080A, + LocationName.special_zone_3_powerup_block_1: 0xBC080B, + LocationName.special_zone_3_yoshi_block_1: 0xBC080C, + LocationName.special_zone_3_wings_block_1: 0xBC080D, + LocationName.special_zone_4_powerup_block_1: 0xBC080E, + LocationName.special_zone_4_star_block_1: 0xBC080F, + LocationName.star_road_2_star_block_1: 0xBC0810, + LocationName.star_road_3_key_block_1: 0xBC0811, + LocationName.star_road_4_powerup_block_1: 0xBC0812, + LocationName.star_road_4_green_block_1: 0xBC0813, + LocationName.star_road_4_green_block_2: 0xBC0814, + LocationName.star_road_4_green_block_3: 0xBC0815, + LocationName.star_road_4_green_block_4: 0xBC0816, + LocationName.star_road_4_green_block_5: 0xBC0817, + LocationName.star_road_4_green_block_6: 0xBC0818, + LocationName.star_road_4_green_block_7: 0xBC0819, + LocationName.star_road_4_key_block_1: 0xBC081A, + LocationName.star_road_5_directional_coin_block_1: 0xBC081B, + LocationName.star_road_5_life_block_1: 0xBC081C, + LocationName.star_road_5_vine_block_1: 0xBC081D, + LocationName.star_road_5_yellow_block_1: 0xBC081E, + LocationName.star_road_5_yellow_block_2: 0xBC081F, + LocationName.star_road_5_yellow_block_3: 0xBC0820, + LocationName.star_road_5_yellow_block_4: 0xBC0821, + LocationName.star_road_5_yellow_block_5: 0xBC0822, + LocationName.star_road_5_yellow_block_6: 0xBC0823, + LocationName.star_road_5_yellow_block_7: 0xBC0824, + LocationName.star_road_5_yellow_block_8: 0xBC0825, + LocationName.star_road_5_yellow_block_9: 0xBC0826, + LocationName.star_road_5_yellow_block_10: 0xBC0827, + LocationName.star_road_5_yellow_block_11: 0xBC0828, + LocationName.star_road_5_yellow_block_12: 0xBC0829, + LocationName.star_road_5_yellow_block_13: 0xBC082A, + LocationName.star_road_5_yellow_block_14: 0xBC082B, + LocationName.star_road_5_yellow_block_15: 0xBC082C, + LocationName.star_road_5_yellow_block_16: 0xBC082D, + LocationName.star_road_5_yellow_block_17: 0xBC082E, + LocationName.star_road_5_yellow_block_18: 0xBC082F, + LocationName.star_road_5_yellow_block_19: 0xBC0830, + LocationName.star_road_5_yellow_block_20: 0xBC0831, + LocationName.star_road_5_green_block_1: 0xBC0832, + LocationName.star_road_5_green_block_2: 0xBC0833, + LocationName.star_road_5_green_block_3: 0xBC0834, + LocationName.star_road_5_green_block_4: 0xBC0835, + LocationName.star_road_5_green_block_5: 0xBC0836, + LocationName.star_road_5_green_block_6: 0xBC0837, + LocationName.star_road_5_green_block_7: 0xBC0838, + LocationName.star_road_5_green_block_8: 0xBC0839, + LocationName.star_road_5_green_block_9: 0xBC083A, + LocationName.star_road_5_green_block_10: 0xBC083B, + LocationName.star_road_5_green_block_11: 0xBC083C, + LocationName.star_road_5_green_block_12: 0xBC083D, + LocationName.star_road_5_green_block_13: 0xBC083E, + LocationName.star_road_5_green_block_14: 0xBC083F, + LocationName.star_road_5_green_block_15: 0xBC0840, + LocationName.star_road_5_green_block_16: 0xBC0841, + LocationName.star_road_5_green_block_17: 0xBC0842, + LocationName.star_road_5_green_block_18: 0xBC0843, + LocationName.star_road_5_green_block_19: 0xBC0844, + LocationName.star_road_5_green_block_20: 0xBC0845 +} + bowser_location_table = { LocationName.bowser: 0xBC0200, } @@ -208,6 +826,10 @@ yoshi_house_location_table = { all_locations = { **level_location_table, **dragon_coin_location_table, + **moon_location_table, + **hidden_1ups_location_table, + **bonus_block_location_table, + **blocksanity_location_table, **bowser_location_table, **yoshi_house_location_table, } @@ -234,20 +856,149 @@ special_zone_dragon_coin_names = [ LocationName.special_zone_8_dragon, ] +special_zone_hidden_1up_names = [ + LocationName.special_zone_1_hidden_1up +] + +special_zone_blocksanity_names = [ + LocationName.special_zone_8_yoshi_block_1, + LocationName.special_zone_8_coin_block_1, + LocationName.special_zone_8_coin_block_2, + LocationName.special_zone_8_coin_block_3, + LocationName.special_zone_8_coin_block_4, + LocationName.special_zone_8_coin_block_5, + LocationName.special_zone_8_blue_pow_block_1, + LocationName.special_zone_8_powerup_block_1, + LocationName.special_zone_8_star_block_1, + LocationName.special_zone_8_coin_block_6, + LocationName.special_zone_8_coin_block_7, + LocationName.special_zone_8_coin_block_8, + LocationName.special_zone_8_coin_block_9, + LocationName.special_zone_8_coin_block_10, + LocationName.special_zone_8_coin_block_11, + LocationName.special_zone_8_coin_block_12, + LocationName.special_zone_8_coin_block_13, + LocationName.special_zone_8_coin_block_14, + LocationName.special_zone_8_coin_block_15, + LocationName.special_zone_8_coin_block_16, + LocationName.special_zone_8_coin_block_17, + LocationName.special_zone_8_coin_block_18, + LocationName.special_zone_8_multi_coin_block_1, + LocationName.special_zone_8_coin_block_19, + LocationName.special_zone_8_coin_block_20, + LocationName.special_zone_8_coin_block_21, + LocationName.special_zone_8_coin_block_22, + LocationName.special_zone_8_coin_block_23, + LocationName.special_zone_8_powerup_block_2, + LocationName.special_zone_8_flying_block_1, + LocationName.special_zone_7_powerup_block_1, + LocationName.special_zone_7_yoshi_block_1, + LocationName.special_zone_7_coin_block_1, + LocationName.special_zone_7_powerup_block_2, + LocationName.special_zone_7_coin_block_2, + LocationName.special_zone_6_powerup_block_1, + LocationName.special_zone_6_coin_block_1, + LocationName.special_zone_6_coin_block_2, + LocationName.special_zone_6_yoshi_block_1, + LocationName.special_zone_6_life_block_1, + LocationName.special_zone_6_multi_coin_block_1, + LocationName.special_zone_6_coin_block_3, + LocationName.special_zone_6_coin_block_4, + LocationName.special_zone_6_coin_block_5, + LocationName.special_zone_6_coin_block_6, + LocationName.special_zone_6_coin_block_7, + LocationName.special_zone_6_coin_block_8, + LocationName.special_zone_6_coin_block_9, + LocationName.special_zone_6_coin_block_10, + LocationName.special_zone_6_coin_block_11, + LocationName.special_zone_6_coin_block_12, + LocationName.special_zone_6_coin_block_13, + LocationName.special_zone_6_coin_block_14, + LocationName.special_zone_6_coin_block_15, + LocationName.special_zone_6_coin_block_16, + LocationName.special_zone_6_coin_block_17, + LocationName.special_zone_6_coin_block_18, + LocationName.special_zone_6_coin_block_19, + LocationName.special_zone_6_coin_block_20, + LocationName.special_zone_6_coin_block_21, + LocationName.special_zone_6_coin_block_22, + LocationName.special_zone_6_coin_block_23, + LocationName.special_zone_6_coin_block_24, + LocationName.special_zone_6_coin_block_25, + LocationName.special_zone_6_coin_block_26, + LocationName.special_zone_6_coin_block_27, + LocationName.special_zone_6_coin_block_28, + LocationName.special_zone_6_powerup_block_2, + LocationName.special_zone_6_coin_block_29, + LocationName.special_zone_6_coin_block_30, + LocationName.special_zone_6_coin_block_31, + LocationName.special_zone_6_coin_block_32, + LocationName.special_zone_6_coin_block_33, + LocationName.special_zone_5_yoshi_block_1, + LocationName.special_zone_1_vine_block_1, + LocationName.special_zone_1_vine_block_2, + LocationName.special_zone_1_vine_block_3, + LocationName.special_zone_1_vine_block_4, + LocationName.special_zone_1_life_block_1, + LocationName.special_zone_1_vine_block_5, + LocationName.special_zone_1_blue_pow_block_1, + LocationName.special_zone_1_vine_block_6, + LocationName.special_zone_1_powerup_block_1, + LocationName.special_zone_1_pswitch_coin_block_1, + LocationName.special_zone_1_pswitch_coin_block_2, + LocationName.special_zone_1_pswitch_coin_block_3, + LocationName.special_zone_1_pswitch_coin_block_4, + LocationName.special_zone_1_pswitch_coin_block_5, + LocationName.special_zone_1_pswitch_coin_block_6, + LocationName.special_zone_1_pswitch_coin_block_7, + LocationName.special_zone_1_pswitch_coin_block_8, + LocationName.special_zone_1_pswitch_coin_block_9, + LocationName.special_zone_1_pswitch_coin_block_10, + LocationName.special_zone_1_pswitch_coin_block_11, + LocationName.special_zone_1_pswitch_coin_block_12, + LocationName.special_zone_1_pswitch_coin_block_13, + LocationName.special_zone_2_powerup_block_1, + LocationName.special_zone_2_coin_block_1, + LocationName.special_zone_2_coin_block_2, + LocationName.special_zone_2_powerup_block_2, + LocationName.special_zone_2_coin_block_3, + LocationName.special_zone_2_coin_block_4, + LocationName.special_zone_2_powerup_block_3, + LocationName.special_zone_2_multi_coin_block_1, + LocationName.special_zone_2_coin_block_5, + LocationName.special_zone_2_coin_block_6, + LocationName.special_zone_3_powerup_block_1, + LocationName.special_zone_3_yoshi_block_1, + LocationName.special_zone_3_wings_block_1, + LocationName.special_zone_4_powerup_block_1, + LocationName.special_zone_4_star_block_1 +] + location_table = {} -def setup_locations(world, player: int): +def setup_locations(world: World): location_table = {**level_location_table} - # Dragon Coins here - if world.dragon_coin_checks[player].value: - location_table.update({**dragon_coin_location_table}) + if world.options.dragon_coin_checks: + location_table.update(dragon_coin_location_table) - if world.goal[player] == "yoshi_egg_hunt": - location_table.update({**yoshi_house_location_table}) + if world.options.moon_checks: + location_table.update(moon_location_table) + + if world.options.hidden_1up_checks: + location_table.update(hidden_1ups_location_table) + + if world.options.bonus_block_checks: + location_table.update(bonus_block_location_table) + + if world.options.blocksanity: + location_table.update(blocksanity_location_table) + + if world.options.goal == "yoshi_egg_hunt": + location_table.update(yoshi_house_location_table) else: - location_table.update({**bowser_location_table}) + location_table.update(bowser_location_table) return location_table diff --git a/worlds/smw/Names/ItemName.py b/worlds/smw/Names/ItemName.py index fecb18685e..e6eced4100 100644 --- a/worlds/smw/Names/ItemName.py +++ b/worlds/smw/Names/ItemName.py @@ -1,5 +1,9 @@ # Junk Definitions one_up_mushroom = "1-Up Mushroom" +one_coin = "1 coin" +five_coins = "5 coins" +ten_coins = "10 coins" +fifty_coins = "50 coins" # Collectable Definitions yoshi_egg = "Yoshi Egg" @@ -22,11 +26,16 @@ green_switch_palace = "Green Switch Palace" red_switch_palace = "Red Switch Palace" blue_switch_palace = "Blue Switch Palace" +# Special Zone clear flag definition +special_world_clear = "Special Zone Clear" + # Trap Definitions -ice_trap = "Ice Trap" -stun_trap = "Stun Trap" -literature_trap = "Literature Trap" -timer_trap = "Timer Trap" +ice_trap = "Ice Trap" +stun_trap = "Stun Trap" +literature_trap = "Literature Trap" +timer_trap = "Timer Trap" +reverse_controls_trap = "Reverse Trap" +thwimp_trap = "Thwimp Trap" # Other Definitions victory = "The Princess" diff --git a/worlds/smw/Names/LocationName.py b/worlds/smw/Names/LocationName.py index cc01b05ece..847b724f78 100644 --- a/worlds/smw/Names/LocationName.py +++ b/worlds/smw/Names/LocationName.py @@ -4,12 +4,15 @@ yoshis_house_tile = "Yoshi's House - Tile" yoshis_island_1_exit_1 = "Yoshi's Island 1 - Normal Exit" yoshis_island_1_dragon = "Yoshi's Island 1 - Dragon Coins" +yoshis_island_1_moon = "Yoshi's Island 1 - 3-Up Moon" yoshis_island_2_exit_1 = "Yoshi's Island 2 - Normal Exit" yoshis_island_2_dragon = "Yoshi's Island 2 - Dragon Coins" yoshis_island_3_exit_1 = "Yoshi's Island 3 - Normal Exit" yoshis_island_3_dragon = "Yoshi's Island 3 - Dragon Coins" +yoshis_island_3_bonus_block = "Yoshi's Island 3 - 1-Up from Bonus Block" yoshis_island_4_exit_1 = "Yoshi's Island 4 - Normal Exit" yoshis_island_4_dragon = "Yoshi's Island 4 - Dragon Coins" +yoshis_island_4_hidden_1up = "Yoshi's Island 4 - Hidden 1-Up" yoshis_island_castle = "#1 Iggy's Castle - Normal Exit" yoshis_island_koopaling = "#1 Iggy's Castle - Boss" @@ -18,13 +21,17 @@ yellow_switch_palace = "Yellow Switch Palace" donut_plains_1_exit_1 = "Donut Plains 1 - Normal Exit" donut_plains_1_exit_2 = "Donut Plains 1 - Secret Exit" donut_plains_1_dragon = "Donut Plains 1 - Dragon Coins" +donut_plains_1_hidden_1up = "Donut Plains 1 - Hidden 1-Up" donut_plains_2_exit_1 = "Donut Plains 2 - Normal Exit" donut_plains_2_exit_2 = "Donut Plains 2 - Secret Exit" donut_plains_2_dragon = "Donut Plains 2 - Dragon Coins" donut_plains_3_exit_1 = "Donut Plains 3 - Normal Exit" donut_plains_3_dragon = "Donut Plains 3 - Dragon Coins" +donut_plains_3_bonus_block = "Donut Plains 3 - 1-Up from Bonus Block" donut_plains_4_exit_1 = "Donut Plains 4 - Normal Exit" donut_plains_4_dragon = "Donut Plains 4 - Dragon Coins" +donut_plains_4_moon = "Donut Plains 4 - 3-Up Moon" +donut_plains_4_hidden_1up = "Donut Plains 4 - Hidden 1-Up" donut_secret_1_exit_1 = "Donut Secret 1 - Normal Exit" donut_secret_1_exit_2 = "Donut Secret 1 - Secret Exit" donut_secret_1_dragon = "Donut Secret 1 - Dragon Coins" @@ -35,6 +42,7 @@ donut_ghost_house_exit_2 = "Donut Ghost House - Secret Exit" donut_secret_house_exit_1 = "Donut Secret House - Normal Exit" donut_secret_house_exit_2 = "Donut Secret House - Secret Exit" donut_plains_castle = "#2 Morton's Castle - Normal Exit" +donut_plains_castle_hidden_1up = "#2 Morton's Castle - Hidden 1-Up" donut_plains_koopaling = "#2 Morton's Castle - Boss" green_switch_palace = "Green Switch Palace" @@ -47,8 +55,10 @@ vanilla_dome_2_exit_2 = "Vanilla Dome 2 - Secret Exit" vanilla_dome_2_dragon = "Vanilla Dome 2 - Dragon Coins" vanilla_dome_3_exit_1 = "Vanilla Dome 3 - Normal Exit" vanilla_dome_3_dragon = "Vanilla Dome 3 - Dragon Coins" +vanilla_dome_3_moon = "Vanilla Dome 3 - 3-Up Moon" vanilla_dome_4_exit_1 = "Vanilla Dome 4 - Normal Exit" vanilla_dome_4_dragon = "Vanilla Dome 4 - Dragon Coins" +vanilla_dome_4_hidden_1up = "Vanilla Dome 4 - Hidden 1-Up" vanilla_secret_1_exit_1 = "Vanilla Secret 1 - Normal Exit" vanilla_secret_1_exit_2 = "Vanilla Secret 1 - Secret Exit" vanilla_secret_1_dragon = "Vanilla Secret 1 - Dragon Coins" @@ -58,7 +68,9 @@ vanilla_secret_3_exit_1 = "Vanilla Secret 3 - Normal Exit" vanilla_secret_3_dragon = "Vanilla Secret 3 - Dragon Coins" vanilla_ghost_house_exit_1 = "Vanilla Ghost House - Normal Exit" vanilla_ghost_house_dragon = "Vanilla Ghost House - Dragon Coins" +vanilla_ghost_house_hidden_1up = "Vanilla Ghost House - Hidden 1-Up" vanilla_fortress = "Vanilla Fortress - Normal Exit" +vanilla_fortress_hidden_1up = "Vanilla Fortress - Hidden 1-Up" vanilla_reznor = "Vanilla Fortress - Boss" vanilla_dome_castle = "#3 Lemmy's Castle - Normal Exit" vanilla_dome_koopaling = "#3 Lemmy's Castle - Boss" @@ -67,13 +79,16 @@ red_switch_palace = "Red Switch Palace" butter_bridge_1_exit_1 = "Butter Bridge 1 - Normal Exit" butter_bridge_1_dragon = "Butter Bridge 1 - Dragon Coins" +butter_bridge_1_bonus_block = "Butter Bridge 1 - 1-Up from Bonus Block" butter_bridge_2_exit_1 = "Butter Bridge 2 - Normal Exit" butter_bridge_2_dragon = "Butter Bridge 2 - Dragon Coins" cheese_bridge_exit_1 = "Cheese Bridge - Normal Exit" cheese_bridge_exit_2 = "Cheese Bridge - Secret Exit" cheese_bridge_dragon = "Cheese Bridge - Dragon Coins" +cheese_bridge_moon = "Cheese Bridge - 3-Up Moon" cookie_mountain_exit_1 = "Cookie Mountain - Normal Exit" cookie_mountain_dragon = "Cookie Mountain - Dragon Coins" +cookie_mountain_hidden_1up = "Cookie Mountain - Hidden 1-Up" soda_lake_exit_1 = "Soda Lake - Normal Exit" soda_lake_dragon = "Soda Lake - Dragon Coins" twin_bridges_castle = "#4 Ludwig's Castle - Normal Exit" @@ -87,12 +102,14 @@ forest_of_illusion_2_dragon = "Forest of Illusion 2 - Dragon Coins" forest_of_illusion_3_exit_1 = "Forest of Illusion 3 - Normal Exit" forest_of_illusion_3_exit_2 = "Forest of Illusion 3 - Secret Exit" forest_of_illusion_3_dragon = "Forest of Illusion 3 - Dragon Coins" +forest_of_illusion_3_hidden_1up = "Forest of Illusion 3 - Hidden 1-Up" forest_of_illusion_4_exit_1 = "Forest of Illusion 4 - Normal Exit" forest_of_illusion_4_exit_2 = "Forest of Illusion 4 - Secret Exit" forest_of_illusion_4_dragon = "Forest of Illusion 4 - Dragon Coins" forest_ghost_house_exit_1 = "Forest Ghost House - Normal Exit" forest_ghost_house_exit_2 = "Forest Ghost House - Secret Exit" forest_ghost_house_dragon = "Forest Ghost House - Dragon Coins" +forest_ghost_house_moon = "Forest Ghost House - 3-Up Moon" forest_secret_exit_1 = "Forest Secret - Normal Exit" forest_secret_dragon = "Forest Secret - Dragon Coins" forest_fortress = "Forest Fortress - Normal Exit" @@ -105,12 +122,15 @@ blue_switch_palace = "Blue Switch Palace" chocolate_island_1_exit_1 = "Chocolate Island 1 - Normal Exit" chocolate_island_1_dragon = "Chocolate Island 1 - Dragon Coins" +chocolate_island_1_moon = "Chocolate Island 1 - 3-Up Moon" chocolate_island_2_exit_1 = "Chocolate Island 2 - Normal Exit" chocolate_island_2_exit_2 = "Chocolate Island 2 - Secret Exit" chocolate_island_2_dragon = "Chocolate Island 2 - Dragon Coins" +chocolate_island_2_hidden_1up = "Chocolate Island 2 - Hidden 1-Up" chocolate_island_3_exit_1 = "Chocolate Island 3 - Normal Exit" chocolate_island_3_exit_2 = "Chocolate Island 3 - Secret Exit" chocolate_island_3_dragon = "Chocolate Island 3 - Dragon Coins" +chocolate_island_3_bonus_block = "Chocolate Island 3 - 1-Up from Bonus Block" chocolate_island_4_exit_1 = "Chocolate Island 4 - Normal Exit" chocolate_island_4_dragon = "Chocolate Island 4 - Dragon Coins" chocolate_island_5_exit_1 = "Chocolate Island 5 - Normal Exit" @@ -120,6 +140,7 @@ chocolate_secret_exit_1 = "Chocolate Secret - Normal Exit" chocolate_fortress = "Chocolate Fortress - Normal Exit" chocolate_reznor = "Chocolate Fortress - Boss" chocolate_castle = "#6 Wendy's Castle - Normal Exit" +chocolate_castle_hidden_1up = "#6 Wendy's Castle - Hidden 1-Up" chocolate_koopaling = "#6 Wendy's Castle - Boss" sunken_ghost_ship = "Sunken Ghost Ship - Normal Exit" @@ -127,9 +148,11 @@ sunken_ghost_ship_dragon = "Sunken Ghost Ship - Dragon Coins" valley_of_bowser_1_exit_1 = "Valley of Bowser 1 - Normal Exit" valley_of_bowser_1_dragon = "Valley of Bowser 1 - Dragon Coins" +valley_of_bowser_1_moon = "Valley of Bowser 1 - 3-Up Moon" valley_of_bowser_2_exit_1 = "Valley of Bowser 2 - Normal Exit" valley_of_bowser_2_exit_2 = "Valley of Bowser 2 - Secret Exit" valley_of_bowser_2_dragon = "Valley of Bowser 2 - Dragon Coins" +valley_of_bowser_2_hidden_1up = "Valley of Bowser 2 - Hidden 1-Up" valley_of_bowser_3_exit_1 = "Valley of Bowser 3 - Normal Exit" valley_of_bowser_3_dragon = "Valley of Bowser 3 - Dragon Coins" valley_of_bowser_4_exit_1 = "Valley of Bowser 4 - Normal Exit" @@ -141,6 +164,7 @@ valley_fortress = "Valley Fortress - Normal Exit" valley_reznor = "Valley Fortress - Boss" valley_castle = "#7 Larry's Castle - Normal Exit" valley_castle_dragon = "#7 Larry's Castle - Dragon Coins" +valley_castle_hidden_1up = "#7 Larry's Castle - Hidden 1-Up" valley_koopaling = "#7 Larry's Castle - Boss" front_door = "Front Door" @@ -161,6 +185,7 @@ star_road_5_exit_2 = "Star Road 5 - Secret Exit" special_zone_1_exit_1 = "Gnarly - Normal Exit" special_zone_1_dragon = "Gnarly - Dragon Coins" +special_zone_1_hidden_1up = "Gnarly - Hidden 1-Up" special_zone_2_exit_1 = "Tubular - Normal Exit" special_zone_2_dragon = "Tubular - Dragon Coins" special_zone_3_exit_1 = "Way Cool - Normal Exit" @@ -362,3 +387,586 @@ special_zone_7_region = "Outrageous" special_zone_8_tile = "Funky - Tile" special_zone_8_region = "Funky" special_complete = "Special Zone - Star Road - Complete" + +vanilla_secret_2_yoshi_block_1 = "Vanilla Secret 2 - Yoshi Block #1" +vanilla_secret_2_green_block_1 = "Vanilla Secret 2 - Green Switch Palace Block #1" +vanilla_secret_2_powerup_block_1 = "Vanilla Secret 2 - Powerup Block #1" +vanilla_secret_2_powerup_block_2 = "Vanilla Secret 2 - Powerup Block #2" +vanilla_secret_2_multi_coin_block_1 = "Vanilla Secret 2 - Multi Coin Block #1" +vanilla_secret_2_gray_pow_block_1 = "Vanilla Secret 2 - Gray P-Switch Block #1" +vanilla_secret_2_coin_block_1 = "Vanilla Secret 2 - Coin Block #1" +vanilla_secret_2_coin_block_2 = "Vanilla Secret 2 - Coin Block #2" +vanilla_secret_2_coin_block_3 = "Vanilla Secret 2 - Coin Block #3" +vanilla_secret_2_coin_block_4 = "Vanilla Secret 2 - Coin Block #4" +vanilla_secret_2_coin_block_5 = "Vanilla Secret 2 - Coin Block #5" +vanilla_secret_2_coin_block_6 = "Vanilla Secret 2 - Coin Block #6" +vanilla_secret_3_powerup_block_1 = "Vanilla Secret 3 - Powerup Block #1" +vanilla_secret_3_powerup_block_2 = "Vanilla Secret 3 - Powerup Block #2" +donut_ghost_house_vine_block_1 = "Donut Ghost House - Vine|P-Switch Block #1" +donut_ghost_house_directional_coin_block_1 = "Donut Ghost House - Directional Coin Block #1" +donut_ghost_house_life_block_1 = "Donut Ghost House - 1-Up Mushroom Block #1" +donut_ghost_house_life_block_2 = "Donut Ghost House - 1-Up Mushroom Block #2" +donut_ghost_house_life_block_3 = "Donut Ghost House - 1-Up Mushroom Block #3" +donut_ghost_house_life_block_4 = "Donut Ghost House - 1-Up Mushroom Block #4" +donut_plains_3_green_block_1 = "Donut Plains 3 - Green Switch Palace Block #1" +donut_plains_3_coin_block_1 = "Donut Plains 3 - Coin Block #1" +donut_plains_3_coin_block_2 = "Donut Plains 3 - Coin Block #2" +donut_plains_3_vine_block_1 = "Donut Plains 3 - Vine Block #1" +donut_plains_3_powerup_block_1 = "Donut Plains 3 - Powerup Block #1" +donut_plains_3_bonus_block_1 = "Donut Plains 3 - Bonus Block #1" +donut_plains_4_coin_block_1 = "Donut Plains 4 - Coin Block #1" +donut_plains_4_powerup_block_1 = "Donut Plains 4 - Powerup Block #1" +donut_plains_4_coin_block_2 = "Donut Plains 4 - Coin Block #2" +donut_plains_4_yoshi_block_1 = "Donut Plains 4 - Yoshi Block #1" +donut_plains_castle_yellow_block_1 = "#2 Morton's Castle - Yellow Switch Palace Block #1" +donut_plains_castle_coin_block_1 = "#2 Morton's Castle - Coin Block #1" +donut_plains_castle_powerup_block_1 = "#2 Morton's Castle - Powerup Block #1" +donut_plains_castle_coin_block_2 = "#2 Morton's Castle - Coin Block #2" +donut_plains_castle_vine_block_1 = "#2 Morton's Castle - Vine Block #1" +donut_plains_castle_invis_life_block_1 = "#2 Morton's Castle - Invisible 1-Up Mushroom Block #1" +donut_plains_castle_coin_block_3 = "#2 Morton's Castle - Coin Block #3" +donut_plains_castle_coin_block_4 = "#2 Morton's Castle - Coin Block #4" +donut_plains_castle_coin_block_5 = "#2 Morton's Castle - Coin Block #5" +donut_plains_castle_green_block_1 = "#2 Morton's Castle - Green Switch Palace Block #1" +donut_plains_2_coin_block_1 = "Donut Plains 2 - Coin Block #1" +donut_plains_2_coin_block_2 = "Donut Plains 2 - Coin Block #2" +donut_plains_2_coin_block_3 = "Donut Plains 2 - Coin Block #3" +donut_plains_2_yellow_block_1 = "Donut Plains 2 - Yellow Switch Palace Block #1" +donut_plains_2_powerup_block_1 = "Donut Plains 2 - Powerup Block #1" +donut_plains_2_multi_coin_block_1 = "Donut Plains 2 - Multi Coin Block #1" +donut_plains_2_flying_block_1 = "Donut Plains 2 - Flying Question Block #1" +donut_plains_2_green_block_1 = "Donut Plains 2 - Green Switch Palace Block #1" +donut_plains_2_yellow_block_2 = "Donut Plains 2 - Yellow Switch Palace Block #2" +donut_plains_2_vine_block_1 = "Donut Plains 2 - Vine Block #1" +donut_secret_1_coin_block_1 = "Donut Secret 1 - Coin Block #1" +donut_secret_1_coin_block_2 = "Donut Secret 1 - Coin Block #2" +donut_secret_1_powerup_block_1 = "Donut Secret 1 - Powerup Block #1" +donut_secret_1_coin_block_3 = "Donut Secret 1 - Coin Block #3" +donut_secret_1_powerup_block_2 = "Donut Secret 1 - Powerup Block #2" +donut_secret_1_powerup_block_3 = "Donut Secret 1 - Powerup Block #3" +donut_secret_1_life_block_1 = "Donut Secret 1 - 1-Up Mushroom Block #1" +donut_secret_1_powerup_block_4 = "Donut Secret 1 - Powerup Block #4" +donut_secret_1_powerup_block_5 = "Donut Secret 1 - Powerup Block #5" +donut_secret_1_key_block_1 = "Donut Secret 1 - Key Block #1" +vanilla_fortress_powerup_block_1 = "Vanilla Fortress - Powerup Block #1" +vanilla_fortress_powerup_block_2 = "Vanilla Fortress - Powerup Block #2" +vanilla_fortress_yellow_block_1 = "Vanilla Fortress - Yellow Switch Palace Block #1" +butter_bridge_1_powerup_block_1 = "Butter Bridge 1 - Powerup Block #1" +butter_bridge_1_multi_coin_block_1 = "Butter Bridge 1 - Multi Coin Block #1" +butter_bridge_1_multi_coin_block_2 = "Butter Bridge 1 - Multi Coin Block #2" +butter_bridge_1_multi_coin_block_3 = "Butter Bridge 1 - Multi Coin Block #3" +butter_bridge_1_life_block_1 = "Butter Bridge 1 - 1-Up Mushroom Block #1" +butter_bridge_1_bonus_block_1 = "Butter Bridge 1 - Bonus Block #1" +butter_bridge_2_powerup_block_1 = "Butter Bridge 2 - Powerup Block #1" +butter_bridge_2_green_block_1 = "Butter Bridge 2 - Green Switch Palace Block #1" +butter_bridge_2_yoshi_block_1 = "Butter Bridge 2 - Yoshi Block #1" +twin_bridges_castle_powerup_block_1 = "#4 Ludwig Castle - Powerup Block #1" +cheese_bridge_powerup_block_1 = "Cheese Bridge Area - Powerup Block #1" +cheese_bridge_powerup_block_2 = "Cheese Bridge Area - Powerup Block #2" +cheese_bridge_wings_block_1 = "Cheese Bridge Area - Wings Block #1" +cheese_bridge_powerup_block_3 = "Cheese Bridge Area - Powerup Block #3" +cookie_mountain_coin_block_1 = "Cookie Mountain - Coin Block #1" +cookie_mountain_coin_block_2 = "Cookie Mountain - Coin Block #2" +cookie_mountain_coin_block_3 = "Cookie Mountain - Coin Block #3" +cookie_mountain_coin_block_4 = "Cookie Mountain - Coin Block #4" +cookie_mountain_coin_block_5 = "Cookie Mountain - Coin Block #5" +cookie_mountain_coin_block_6 = "Cookie Mountain - Coin Block #6" +cookie_mountain_coin_block_7 = "Cookie Mountain - Coin Block #7" +cookie_mountain_coin_block_8 = "Cookie Mountain - Coin Block #8" +cookie_mountain_coin_block_9 = "Cookie Mountain - Coin Block #9" +cookie_mountain_powerup_block_1 = "Cookie Mountain - Powerup Block #1" +cookie_mountain_life_block_1 = "Cookie Mountain - 1-Up Mushroom Block #1" +cookie_mountain_vine_block_1 = "Cookie Mountain - Vine Block #1" +cookie_mountain_yoshi_block_1 = "Cookie Mountain - Yoshi Block #1" +cookie_mountain_coin_block_10 = "Cookie Mountain - Coin Block #10" +cookie_mountain_coin_block_11 = "Cookie Mountain - Coin Block #11" +cookie_mountain_powerup_block_2 = "Cookie Mountain - Powerup Block #2" +cookie_mountain_coin_block_12 = "Cookie Mountain - Coin Block #12" +cookie_mountain_coin_block_13 = "Cookie Mountain - Coin Block #13" +cookie_mountain_coin_block_14 = "Cookie Mountain - Coin Block #14" +cookie_mountain_coin_block_15 = "Cookie Mountain - Coin Block #15" +cookie_mountain_coin_block_16 = "Cookie Mountain - Coin Block #16" +cookie_mountain_coin_block_17 = "Cookie Mountain - Coin Block #17" +cookie_mountain_coin_block_18 = "Cookie Mountain - Coin Block #18" +cookie_mountain_coin_block_19 = "Cookie Mountain - Coin Block #19" +cookie_mountain_coin_block_20 = "Cookie Mountain - Coin Block #20" +cookie_mountain_coin_block_21 = "Cookie Mountain - Coin Block #21" +cookie_mountain_coin_block_22 = "Cookie Mountain - Coin Block #22" +cookie_mountain_coin_block_23 = "Cookie Mountain - Coin Block #23" +cookie_mountain_coin_block_24 = "Cookie Mountain - Coin Block #24" +cookie_mountain_coin_block_25 = "Cookie Mountain - Coin Block #25" +cookie_mountain_coin_block_26 = "Cookie Mountain - Coin Block #26" +cookie_mountain_coin_block_27 = "Cookie Mountain - Coin Block #27" +cookie_mountain_coin_block_28 = "Cookie Mountain - Coin Block #28" +cookie_mountain_coin_block_29 = "Cookie Mountain - Coin Block #29" +cookie_mountain_coin_block_30 = "Cookie Mountain - Coin Block #30" +soda_lake_powerup_block_1 = "Soda Lake - Powerup Block #1" +donut_secret_house_powerup_block_1 = "Donut Secret House - Powerup Block #1" +donut_secret_house_multi_coin_block_1 = "Donut Secret House - Multi Coin Block #1" +donut_secret_house_life_block_1 = "Donut Secret House - 1-Up Mushroom Block #1" +donut_secret_house_vine_block_1 = "Donut Secret House - Vine Block #1" +donut_secret_house_directional_coin_block_1 = "Donut Secret House - Directional Coin Block #1" +donut_plains_1_coin_block_1 = "Donut Plains 1 - Coin Block #1" +donut_plains_1_coin_block_2 = "Donut Plains 1 - Coin Block #2" +donut_plains_1_yoshi_block_1 = "Donut Plains 1 - Yoshi Block #1" +donut_plains_1_vine_block_1 = "Donut Plains 1 - Vine Block #1" +donut_plains_1_green_block_1 = "Donut Plains 1 - Green Switch Palace Block #1" +donut_plains_1_green_block_2 = "Donut Plains 1 - Green Switch Palace Block #2" +donut_plains_1_green_block_3 = "Donut Plains 1 - Green Switch Palace Block #3" +donut_plains_1_green_block_4 = "Donut Plains 1 - Green Switch Palace Block #4" +donut_plains_1_green_block_5 = "Donut Plains 1 - Green Switch Palace Block #5" +donut_plains_1_green_block_6 = "Donut Plains 1 - Green Switch Palace Block #6" +donut_plains_1_green_block_7 = "Donut Plains 1 - Green Switch Palace Block #7" +donut_plains_1_green_block_8 = "Donut Plains 1 - Green Switch Palace Block #8" +donut_plains_1_green_block_9 = "Donut Plains 1 - Green Switch Palace Block #9" +donut_plains_1_green_block_10 = "Donut Plains 1 - Green Switch Palace Block #10" +donut_plains_1_green_block_11 = "Donut Plains 1 - Green Switch Palace Block #11" +donut_plains_1_green_block_12 = "Donut Plains 1 - Green Switch Palace Block #12" +donut_plains_1_green_block_13 = "Donut Plains 1 - Green Switch Palace Block #13" +donut_plains_1_green_block_14 = "Donut Plains 1 - Green Switch Palace Block #14" +donut_plains_1_green_block_15 = "Donut Plains 1 - Green Switch Palace Block #15" +donut_plains_1_green_block_16 = "Donut Plains 1 - Green Switch Palace Block #16" +donut_plains_1_yellow_block_1 = "Donut Plains 1 - Yellow Switch Palace Block #1" +donut_plains_1_yellow_block_2 = "Donut Plains 1 - Yellow Switch Palace Block #2" +donut_plains_1_yellow_block_3 = "Donut Plains 1 - Yellow Switch Palace Block #3" +sunken_ghost_ship_powerup_block_1 = "Sunken Ghost Ship - Powerup Block #1" +sunken_ghost_ship_star_block_1 = "Sunken Ghost Ship - Star Block #1" +chocolate_castle_yellow_block_1 = "#6 Wendy's Castle - Yellow Switch Palace Block #1" +chocolate_castle_yellow_block_2 = "#6 Wendy's Castle - Yellow Switch Palace Block #2" +chocolate_castle_green_block_1 = "#6 Wendy's Castle - Green Switch Palace Block #1" +chocolate_fortress_powerup_block_1 = "Chocolate Fortress - Powerup Block #1" +chocolate_fortress_powerup_block_2 = "Chocolate Fortress - Powerup Block #2" +chocolate_fortress_coin_block_1 = "Chocolate Fortress - Coin Block #1" +chocolate_fortress_coin_block_2 = "Chocolate Fortress - Coin Block #2" +chocolate_fortress_green_block_1 = "Chocolate Fortress - Green Switch Palace Block #1" +chocolate_island_5_yoshi_block_1 = "Chocolate Island 5 - Yoshi Block #1" +chocolate_island_5_powerup_block_1 = "Chocolate Island 5 - Powerup Block #1" +chocolate_island_5_life_block_1 = "Chocolate Island 5 - 1-Up Mushroom Block #1" +chocolate_island_5_yellow_block_1 = "Chocolate Island 5 - Yellow Switch Palace Block #1" +chocolate_island_4_yellow_block_1 = "Chocolate Island 4 - Yellow Switch Palace Block #1" +chocolate_island_4_blue_pow_block_1 = "Chocolate Island 4 - Blue P-Switch Block #1" +chocolate_island_4_powerup_block_1 = "Chocolate Island 4 - Powerup Block #1" +forest_fortress_yellow_block_1 = "Forest Fortress - Yellow Switch Palace Block #1" +forest_fortress_powerup_block_1 = "Forest Fortress - Powerup Block #1" +forest_fortress_life_block_1 = "Forest Fortress - 1-Up Mushroom Block #1" +forest_fortress_life_block_2 = "Forest Fortress - 1-Up Mushroom Block #2" +forest_fortress_life_block_3 = "Forest Fortress - 1-Up Mushroom Block #3" +forest_fortress_life_block_4 = "Forest Fortress - 1-Up Mushroom Block #4" +forest_fortress_life_block_5 = "Forest Fortress - 1-Up Mushroom Block #5" +forest_fortress_life_block_6 = "Forest Fortress - 1-Up Mushroom Block #6" +forest_fortress_life_block_7 = "Forest Fortress - 1-Up Mushroom Block #7" +forest_fortress_life_block_8 = "Forest Fortress - 1-Up Mushroom Block #8" +forest_fortress_life_block_9 = "Forest Fortress - 1-Up Mushroom Block #9" +forest_castle_green_block_1 = "#5 Roy's Castle - Green Switch Palace Block #1" +chocolate_ghost_house_powerup_block_1 = "Choco Ghost House - Powerup Block #1" +chocolate_ghost_house_powerup_block_2 = "Choco Ghost House - Powerup Block #2" +chocolate_ghost_house_life_block_1 = "Choco Ghost House - 1-Up Mushroom Block #1" +chocolate_island_1_flying_block_1 = "Chocolate Island 1 - Flying Question Block #1" +chocolate_island_1_flying_block_2 = "Chocolate Island 1 - Flying Question Block #2" +chocolate_island_1_yoshi_block_1 = "Chocolate Island 1 - Yoshi Block #1" +chocolate_island_1_green_block_1 = "Chocolate Island 1 - Green|Yellow Switch Palace Block #1" +chocolate_island_1_life_block_1 = "Chocolate Island 1 - 1-Up Mushroom Block #1" +chocolate_island_3_powerup_block_1 = "Chocolate Island 3 - Powerup Block #1" +chocolate_island_3_powerup_block_2 = "Chocolate Island 3 - Powerup Block #2" +chocolate_island_3_powerup_block_3 = "Chocolate Island 3 - Powerup Block #3" +chocolate_island_3_green_block_1 = "Chocolate Island 3 - Green Switch Palace Block #1" +chocolate_island_3_bonus_block_1 = "Chocolate Island 3 - Bonus Block #1" +chocolate_island_3_vine_block_1 = "Chocolate Island 3 - Vine Block #1" +chocolate_island_3_life_block_1 = "Chocolate Island 3 - 1-Up Mushroom Block #1" +chocolate_island_3_life_block_2 = "Chocolate Island 3 - 1-Up Mushroom Block #2" +chocolate_island_3_life_block_3 = "Chocolate Island 3 - 1-Up Mushroom Block #3" +chocolate_island_2_multi_coin_block_1 = "Chocolate Island 2 - Multi Coin Block #1" +chocolate_island_2_invis_coin_block_1 = "Chocolate Island 2 - Invisible Coin Block #1" +chocolate_island_2_yoshi_block_1 = "Chocolate Island 2 - Yoshi Block #1" +chocolate_island_2_coin_block_1 = "Chocolate Island 2 - Coin Block #1" +chocolate_island_2_coin_block_2 = "Chocolate Island 2 - Coin Block #2" +chocolate_island_2_multi_coin_block_2 = "Chocolate Island 2 - Multi Coin Block #2" +chocolate_island_2_powerup_block_1 = "Chocolate Island 2 - Powerup Block #1" +chocolate_island_2_blue_pow_block_1 = "Chocolate Island 2 - Blue P-Switch Block #1" +chocolate_island_2_yellow_block_1 = "Chocolate Island 2 - Yellow Switch Palace Block #1" +chocolate_island_2_yellow_block_2 = "Chocolate Island 2 - Yellow Switch Palace Block #2" +chocolate_island_2_green_block_1 = "Chocolate Island 2 - Green Switch Palace Block #1" +chocolate_island_2_green_block_2 = "Chocolate Island 2 - Green Switch Palace Block #2" +chocolate_island_2_green_block_3 = "Chocolate Island 2 - Green Switch Palace Block #3" +chocolate_island_2_green_block_4 = "Chocolate Island 2 - Green Switch Palace Block #4" +chocolate_island_2_green_block_5 = "Chocolate Island 2 - Green Switch Palace Block #5" +chocolate_island_2_green_block_6 = "Chocolate Island 2 - Green Switch Palace Block #6" +yoshis_island_castle_coin_block_1 = "#1 Iggy's Castle - Coin Block #1" +yoshis_island_castle_coin_block_2 = "#1 Iggy's Castle - Coin Block #2" +yoshis_island_castle_powerup_block_1 = "#1 Iggy's Castle - Powerup Block #1" +yoshis_island_castle_coin_block_3 = "#1 Iggy's Castle - Coin Block #3" +yoshis_island_castle_coin_block_4 = "#1 Iggy's Castle - Coin Block #4" +yoshis_island_castle_flying_block_1 = "#1 Iggy's Castle - Flying Question Block #1" +yoshis_island_4_yellow_block_1 = "Yoshi's Island 4 - Yellow Switch Palace Block #1" +yoshis_island_4_powerup_block_1 = "Yoshi's Island 4 - Powerup Block #1" +yoshis_island_4_multi_coin_block_1 = "Yoshi's Island 4 - Multi Coin Block #1" +yoshis_island_4_star_block_1 = "Yoshi's Island 4 - Star Block #1" +yoshis_island_3_yellow_block_1 = "Yoshi's Island 3 - Yellow Switch Palace Block #1" +yoshis_island_3_yellow_block_2 = "Yoshi's Island 3 - Yellow Switch Palace Block #2" +yoshis_island_3_yellow_block_3 = "Yoshi's Island 3 - Yellow Switch Palace Block #3" +yoshis_island_3_yellow_block_4 = "Yoshi's Island 3 - Yellow Switch Palace Block #4" +yoshis_island_3_yellow_block_5 = "Yoshi's Island 3 - Yellow Switch Palace Block #5" +yoshis_island_3_yellow_block_6 = "Yoshi's Island 3 - Yellow Switch Palace Block #6" +yoshis_island_3_yellow_block_7 = "Yoshi's Island 3 - Yellow Switch Palace Block #7" +yoshis_island_3_yellow_block_8 = "Yoshi's Island 3 - Yellow Switch Palace Block #8" +yoshis_island_3_yellow_block_9 = "Yoshi's Island 3 - Yellow Switch Palace Block #9" +yoshis_island_3_coin_block_1 = "Yoshi's Island 3 - Coin Block #1" +yoshis_island_3_yoshi_block_1 = "Yoshi's Island 3 - Yoshi Block #1" +yoshis_island_3_coin_block_2 = "Yoshi's Island 3 - Coin Block #2" +yoshis_island_3_powerup_block_1 = "Yoshi's Island 3 - Powerup Block #1" +yoshis_island_3_yellow_block_10 = "Yoshi's Island 3 - Yellow Switch Palace Block #10" +yoshis_island_3_yellow_block_11 = "Yoshi's Island 3 - Yellow Switch Palace Block #11" +yoshis_island_3_yellow_block_12 = "Yoshi's Island 3 - Yellow Switch Palace Block #12" +yoshis_island_3_bonus_block_1 = "Yoshi's Island 3 - Bonus Block #1" +yoshis_island_1_flying_block_1 = "Yoshi's Island 1 - Flying Question Block #1" +yoshis_island_1_yellow_block_1 = "Yoshi's Island 1 - Yellow Switch Palace Block #1" +yoshis_island_1_life_block_1 = "Yoshi's Island 1 - 1-Up Mushroom Block #1" +yoshis_island_1_powerup_block_1 = "Yoshi's Island 1 - Powerup Block #1" +yoshis_island_2_flying_block_1 = "Yoshi's Island 2 - Flying Question Block #1" +yoshis_island_2_flying_block_2 = "Yoshi's Island 2 - Flying Question Block #2" +yoshis_island_2_flying_block_3 = "Yoshi's Island 2 - Flying Question Block #3" +yoshis_island_2_flying_block_4 = "Yoshi's Island 2 - Flying Question Block #4" +yoshis_island_2_flying_block_5 = "Yoshi's Island 2 - Flying Question Block #5" +yoshis_island_2_flying_block_6 = "Yoshi's Island 2 - Flying Question Block #6" +yoshis_island_2_coin_block_1 = "Yoshi's Island 2 - Coin Block #1" +yoshis_island_2_yellow_block_1 = "Yoshi's Island 2 - Yellow Switch Palace Block #1" +yoshis_island_2_coin_block_2 = "Yoshi's Island 2 - Coin Block #2" +yoshis_island_2_coin_block_3 = "Yoshi's Island 2 - Coin Block #3" +yoshis_island_2_yoshi_block_1 = "Yoshi's Island 2 - Yoshi Block #1" +yoshis_island_2_coin_block_4 = "Yoshi's Island 2 - Coin Block #4" +yoshis_island_2_yoshi_block_2 = "Yoshi's Island 2 - Yoshi Block #2" +yoshis_island_2_coin_block_5 = "Yoshi's Island 2 - Coin Block #5" +yoshis_island_2_vine_block_1 = "Yoshi's Island 2 - Vine Block #1" +yoshis_island_2_yellow_block_2 = "Yoshi's Island 2 - Yellow Switch Palace Block #2" +vanilla_ghost_house_powerup_block_1 = "Vanilla Ghost House - Powerup Block #1" +vanilla_ghost_house_vine_block_1 = "Vanilla Ghost House - Vine Block #1" +vanilla_ghost_house_powerup_block_2 = "Vanilla Ghost House - Powerup Block #2" +vanilla_ghost_house_multi_coin_block_1 = "Vanilla Ghost House - Multi Coin Block #1" +vanilla_ghost_house_blue_pow_block_1 = "Vanilla Ghost House - Blue P-Switch Block #1" +vanilla_secret_1_coin_block_1 = "Vanilla Secret 1 - Coin Block #1" +vanilla_secret_1_powerup_block_1 = "Vanilla Secret 1 - Powerup Block #1" +vanilla_secret_1_multi_coin_block_1 = "Vanilla Secret 1 - Multi Coin Block #1" +vanilla_secret_1_vine_block_1 = "Vanilla Secret 1 - Vine Block #1" +vanilla_secret_1_vine_block_2 = "Vanilla Secret 1 - Vine Block #2" +vanilla_secret_1_coin_block_2 = "Vanilla Secret 1 - Coin Block #2" +vanilla_secret_1_coin_block_3 = "Vanilla Secret 1 - Coin Block #3" +vanilla_secret_1_powerup_block_2 = "Vanilla Secret 1 - Powerup Block #2" +vanilla_dome_3_coin_block_1 = "Vanilla Dome 3 - Coin Block #1" +vanilla_dome_3_flying_block_1 = "Vanilla Dome 3 - Flying Question Block #1" +vanilla_dome_3_flying_block_2 = "Vanilla Dome 3 - Flying Question Block #2" +vanilla_dome_3_powerup_block_1 = "Vanilla Dome 3 - Powerup Block #1" +vanilla_dome_3_flying_block_3 = "Vanilla Dome 3 - Flying Question Block #3" +vanilla_dome_3_invis_coin_block_1 = "Vanilla Dome 3 - Invisible Coin Block #1" +vanilla_dome_3_powerup_block_2 = "Vanilla Dome 3 - Powerup Block #2" +vanilla_dome_3_multi_coin_block_1 = "Vanilla Dome 3 - Multi Coin Block #1" +vanilla_dome_3_powerup_block_3 = "Vanilla Dome 3 - Powerup Block #3" +vanilla_dome_3_yoshi_block_1 = "Vanilla Dome 3 - Yoshi Block #1" +vanilla_dome_3_powerup_block_4 = "Vanilla Dome 3 - Powerup Block #4" +vanilla_dome_3_pswitch_coin_block_1 = "Vanilla Dome 3 - P-Switch Coin Block #1" +vanilla_dome_3_pswitch_coin_block_2 = "Vanilla Dome 3 - P-Switch Coin Block #2" +vanilla_dome_3_pswitch_coin_block_3 = "Vanilla Dome 3 - P-Switch Coin Block #3" +vanilla_dome_3_pswitch_coin_block_4 = "Vanilla Dome 3 - P-Switch Coin Block #4" +vanilla_dome_3_pswitch_coin_block_5 = "Vanilla Dome 3 - P-Switch Coin Block #5" +vanilla_dome_3_pswitch_coin_block_6 = "Vanilla Dome 3 - P-Switch Coin Block #6" +donut_secret_2_directional_coin_block_1 = "Donut Secret 2 - Directional Coin Block #1" +donut_secret_2_vine_block_1 = "Donut Secret 2 - Vine Block #1" +donut_secret_2_star_block_1 = "Donut Secret 2 - Star Block #1" +donut_secret_2_powerup_block_1 = "Donut Secret 2 - Powerup Block #1" +donut_secret_2_star_block_2 = "Donut Secret 2 - Star Block #2" +valley_of_bowser_4_yellow_block_1 = "Valley of Bowser 4 - Yellow Switch Palace Block #1" +valley_of_bowser_4_powerup_block_1 = "Valley of Bowser 4 - Powerup Block #1" +valley_of_bowser_4_vine_block_1 = "Valley of Bowser 4 - Vine Block #1" +valley_of_bowser_4_yoshi_block_1 = "Valley of Bowser 4 - Yoshi Block #1" +valley_of_bowser_4_life_block_1 = "Valley of Bowser 4 - 1-Up Mushroom Block #1" +valley_of_bowser_4_powerup_block_2 = "Valley of Bowser 4 - Powerup Block #2" +valley_castle_yellow_block_1 = "#7 Larry's Castle - Yellow Switch Palace Block #1" +valley_castle_yellow_block_2 = "#7 Larry's Castle - Yellow Switch Palace Block #2" +valley_castle_green_block_1 = "#7 Larry's Castle - Green Switch Palace Block #1" +valley_fortress_green_block_1 = "Valley Fortress - Green Switch Palace Block #1" +valley_fortress_yellow_block_1 = "Valley Fortress - Yellow Switch Palace Block #1" +valley_of_bowser_3_powerup_block_1 = "Valley of Bowser 3 - Powerup Block #1" +valley_of_bowser_3_powerup_block_2 = "Valley of Bowser 3 - Powerup Block #2" +valley_ghost_house_pswitch_coin_block_1 = "Valley Ghost House - P-Switch Coin Block #1" +valley_ghost_house_multi_coin_block_1 = "Valley Ghost House - Multi Coin Block #1" +valley_ghost_house_powerup_block_1 = "Valley Ghost House - Powerup Block #1" +valley_ghost_house_directional_coin_block_1 = "Valley Ghost House - Directional Coin Block #1" +valley_of_bowser_2_powerup_block_1 = "Valley of Bowser 2 - Powerup Block #1" +valley_of_bowser_2_yellow_block_1 = "Valley of Bowser 2 - Yellow Switch Palace Block #1" +valley_of_bowser_2_powerup_block_2 = "Valley of Bowser 2 - Powerup Block #2" +valley_of_bowser_2_wings_block_1 = "Valley of Bowser 2 - Wings Block #1" +valley_of_bowser_1_green_block_1 = "Valley of Bowser 1 - Green Switch Palace Block #1" +valley_of_bowser_1_invis_coin_block_1 = "Valley of Bowser 1 - Invisible Coin Block #1" +valley_of_bowser_1_invis_coin_block_2 = "Valley of Bowser 1 - Invisible Coin Block #2" +valley_of_bowser_1_invis_coin_block_3 = "Valley of Bowser 1 - Invisible Coin Block #3" +valley_of_bowser_1_yellow_block_1 = "Valley of Bowser 1 - Yellow Switch Palace Block #1" +valley_of_bowser_1_yellow_block_2 = "Valley of Bowser 1 - Yellow Switch Palace Block #2" +valley_of_bowser_1_yellow_block_3 = "Valley of Bowser 1 - Yellow Switch Palace Block #3" +valley_of_bowser_1_yellow_block_4 = "Valley of Bowser 1 - Yellow Switch Palace Block #4" +valley_of_bowser_1_vine_block_1 = "Valley of Bowser 1 - Vine Block #1" +chocolate_secret_powerup_block_1 = "Chocolate Secret - Powerup Block #1" +chocolate_secret_powerup_block_2 = "Chocolate Secret - Powerup Block #2" +vanilla_dome_2_coin_block_1 = "Vanilla Dome 2 - Coin Block #1" +vanilla_dome_2_powerup_block_1 = "Vanilla Dome 2 - Powerup Block #1" +vanilla_dome_2_coin_block_2 = "Vanilla Dome 2 - Coin Block #2" +vanilla_dome_2_coin_block_3 = "Vanilla Dome 2 - Coin Block #3" +vanilla_dome_2_vine_block_1 = "Vanilla Dome 2 - Vine Block #1" +vanilla_dome_2_invis_life_block_1 = "Vanilla Dome 2 - Invisible 1-Up Mushroom Block #1" +vanilla_dome_2_coin_block_4 = "Vanilla Dome 2 - Coin Block #4" +vanilla_dome_2_coin_block_5 = "Vanilla Dome 2 - Coin Block #5" +vanilla_dome_2_powerup_block_2 = "Vanilla Dome 2 - Powerup Block #2" +vanilla_dome_2_powerup_block_3 = "Vanilla Dome 2 - Powerup Block #3" +vanilla_dome_2_powerup_block_4 = "Vanilla Dome 2 - Powerup Block #4" +vanilla_dome_2_powerup_block_5 = "Vanilla Dome 2 - Powerup Block #5" +vanilla_dome_2_multi_coin_block_1 = "Vanilla Dome 2 - Multi Coin Block #1" +vanilla_dome_2_multi_coin_block_2 = "Vanilla Dome 2 - Multi Coin Block #2" +vanilla_dome_4_powerup_block_1 = "Vanilla Dome 4 - Powerup Block #1" +vanilla_dome_4_powerup_block_2 = "Vanilla Dome 4 - Powerup Block #2" +vanilla_dome_4_coin_block_1 = "Vanilla Dome 4 - Coin Block #1" +vanilla_dome_4_coin_block_2 = "Vanilla Dome 4 - Coin Block #2" +vanilla_dome_4_coin_block_3 = "Vanilla Dome 4 - Coin Block #3" +vanilla_dome_4_life_block_1 = "Vanilla Dome 4 - 1-Up Mushroom Block #1" +vanilla_dome_4_coin_block_4 = "Vanilla Dome 4 - Coin Block #4" +vanilla_dome_4_coin_block_5 = "Vanilla Dome 4 - Coin Block #5" +vanilla_dome_4_coin_block_6 = "Vanilla Dome 4 - Coin Block #6" +vanilla_dome_4_coin_block_7 = "Vanilla Dome 4 - Coin Block #7" +vanilla_dome_4_coin_block_8 = "Vanilla Dome 4 - Coin Block #8" +vanilla_dome_1_flying_block_1 = "Vanilla Dome 1 - Flying Question Block #1" +vanilla_dome_1_powerup_block_1 = "Vanilla Dome 1 - Powerup Block #1" +vanilla_dome_1_powerup_block_2 = "Vanilla Dome 1 - Powerup Block #2" +vanilla_dome_1_coin_block_1 = "Vanilla Dome 1 - Coin Block #1" +vanilla_dome_1_life_block_1 = "Vanilla Dome 1 - 1-Up Mushroom Block #1" +vanilla_dome_1_powerup_block_3 = "Vanilla Dome 1 - Powerup Block #3" +vanilla_dome_1_vine_block_1 = "Vanilla Dome 1 - Vine Block #1" +vanilla_dome_1_star_block_1 = "Vanilla Dome 1 - Star Block #1" +vanilla_dome_1_powerup_block_4 = "Vanilla Dome 1 - Powerup Block #4" +vanilla_dome_1_coin_block_2 = "Vanilla Dome 1 - Coin Block #2" +vanilla_dome_castle_life_block_1 = "#3 Lemmy's Castle - 1-Up Mushroom Block #1" +vanilla_dome_castle_life_block_2 = "#3 Lemmy's Castle - 1-Up Mushroom Block #2" +vanilla_dome_castle_powerup_block_1 = "#3 Lemmy's Castle - Powerup Block #1" +vanilla_dome_castle_life_block_3 = "#3 Lemmy's Castle - 1-Up Mushroom Block #3" +vanilla_dome_castle_green_block_1 = "#3 Lemmy's Castle - Green Switch Palace Block #1" +forest_ghost_house_coin_block_1 = "Forest Ghost House - Coin Block #1" +forest_ghost_house_powerup_block_1 = "Forest Ghost House - Powerup Block #1" +forest_ghost_house_flying_block_1 = "Forest Ghost House - Flying Question Block #1" +forest_ghost_house_powerup_block_2 = "Forest Ghost House - Powerup Block #2" +forest_ghost_house_life_block_1 = "Forest Ghost House - 1-Up Mushroom Block #1" +forest_of_illusion_1_powerup_block_1 = "Forest of Illusion 1 - Powerup Block #1" +forest_of_illusion_1_yoshi_block_1 = "Forest of Illusion 1 - Yoshi Block #1" +forest_of_illusion_1_powerup_block_2 = "Forest of Illusion 1 - Powerup Block #2" +forest_of_illusion_1_key_block_1 = "Forest of Illusion 1 - Key Block #1" +forest_of_illusion_1_life_block_1 = "Forest of Illusion 1 - 1-Up Mushroom Block #1" +forest_of_illusion_4_multi_coin_block_1 = "Forest of Illusion 4 - Multi Coin Block #1" +forest_of_illusion_4_coin_block_1 = "Forest of Illusion 4 - Coin Block #1" +forest_of_illusion_4_coin_block_2 = "Forest of Illusion 4 - Coin Block #2" +forest_of_illusion_4_coin_block_3 = "Forest of Illusion 4 - Coin Block #3" +forest_of_illusion_4_coin_block_4 = "Forest of Illusion 4 - Coin Block #4" +forest_of_illusion_4_powerup_block_1 = "Forest of Illusion 4 - Powerup Block #1" +forest_of_illusion_4_coin_block_5 = "Forest of Illusion 4 - Coin Block #5" +forest_of_illusion_4_coin_block_6 = "Forest of Illusion 4 - Coin Block #6" +forest_of_illusion_4_coin_block_7 = "Forest of Illusion 4 - Coin Block #7" +forest_of_illusion_4_powerup_block_2 = "Forest of Illusion 4 - Powerup Block #2" +forest_of_illusion_4_coin_block_8 = "Forest of Illusion 4 - Coin Block #8" +forest_of_illusion_4_coin_block_9 = "Forest of Illusion 4 - Coin Block #9" +forest_of_illusion_4_coin_block_10 = "Forest of Illusion 4 - Coin Block #10" +forest_of_illusion_2_green_block_1 = "Forest of Illusion 2 - Green Switch Palace Block #1" +forest_of_illusion_2_powerup_block_1 = "Forest of Illusion 2 - Powerup Block #1" +forest_of_illusion_2_invis_coin_block_1 = "Forest of Illusion 2 - Invisible Coin Block #1" +forest_of_illusion_2_invis_coin_block_2 = "Forest of Illusion 2 - Invisible Coin Block #2" +forest_of_illusion_2_invis_life_block_1 = "Forest of Illusion 2 - Invisible 1-Up Mushroom Block #1" +forest_of_illusion_2_invis_coin_block_3 = "Forest of Illusion 2 - Invisible Coin Block #3" +forest_of_illusion_2_yellow_block_1 = "Forest of Illusion 2 - Yellow Switch Palace Block #1" +forest_secret_powerup_block_1 = "Forest Secret Area - Powerup Block #1" +forest_secret_powerup_block_2 = "Forest Secret Area - Powerup Block #2" +forest_secret_life_block_1 = "Forest Secret Area - 1-Up Mushroom Block #1" +forest_of_illusion_3_yoshi_block_1 = "Forest of Illusion 3 - Yoshi Block #1" +forest_of_illusion_3_coin_block_1 = "Forest of Illusion 3 - Coin Block #1" +forest_of_illusion_3_multi_coin_block_1 = "Forest of Illusion 3 - Multi Coin Block #1" +forest_of_illusion_3_coin_block_2 = "Forest of Illusion 3 - Coin Block #2" +forest_of_illusion_3_multi_coin_block_2 = "Forest of Illusion 3 - Multi Coin Block #2" +forest_of_illusion_3_coin_block_3 = "Forest of Illusion 3 - Coin Block #3" +forest_of_illusion_3_coin_block_4 = "Forest of Illusion 3 - Coin Block #4" +forest_of_illusion_3_coin_block_5 = "Forest of Illusion 3 - Coin Block #5" +forest_of_illusion_3_coin_block_6 = "Forest of Illusion 3 - Coin Block #6" +forest_of_illusion_3_coin_block_7 = "Forest of Illusion 3 - Coin Block #7" +forest_of_illusion_3_coin_block_8 = "Forest of Illusion 3 - Coin Block #8" +forest_of_illusion_3_coin_block_9 = "Forest of Illusion 3 - Coin Block #9" +forest_of_illusion_3_coin_block_10 = "Forest of Illusion 3 - Coin Block #10" +forest_of_illusion_3_coin_block_11 = "Forest of Illusion 3 - Coin Block #11" +forest_of_illusion_3_coin_block_12 = "Forest of Illusion 3 - Coin Block #12" +forest_of_illusion_3_coin_block_13 = "Forest of Illusion 3 - Coin Block #13" +forest_of_illusion_3_coin_block_14 = "Forest of Illusion 3 - Coin Block #14" +forest_of_illusion_3_coin_block_15 = "Forest of Illusion 3 - Coin Block #15" +forest_of_illusion_3_coin_block_16 = "Forest of Illusion 3 - Coin Block #16" +forest_of_illusion_3_coin_block_17 = "Forest of Illusion 3 - Coin Block #17" +forest_of_illusion_3_coin_block_18 = "Forest of Illusion 3 - Coin Block #18" +forest_of_illusion_3_coin_block_19 = "Forest of Illusion 3 - Coin Block #19" +forest_of_illusion_3_coin_block_20 = "Forest of Illusion 3 - Coin Block #20" +forest_of_illusion_3_coin_block_21 = "Forest of Illusion 3 - Coin Block #21" +forest_of_illusion_3_coin_block_22 = "Forest of Illusion 3 - Coin Block #22" +forest_of_illusion_3_coin_block_23 = "Forest of Illusion 3 - Coin Block #23" +forest_of_illusion_3_coin_block_24 = "Forest of Illusion 3 - Coin Block #24" +special_zone_8_yoshi_block_1 = "Funky - Yoshi Block #1" +special_zone_8_coin_block_1 = "Funky - Coin Block #1" +special_zone_8_coin_block_2 = "Funky - Coin Block #2" +special_zone_8_coin_block_3 = "Funky - Coin Block #3" +special_zone_8_coin_block_4 = "Funky - Coin Block #4" +special_zone_8_coin_block_5 = "Funky - Coin Block #5" +special_zone_8_blue_pow_block_1 = "Funky - Blue P-Switch Block #1" +special_zone_8_powerup_block_1 = "Funky - Powerup Block #1" +special_zone_8_star_block_1 = "Funky - Star Block #1" +special_zone_8_coin_block_6 = "Funky - Coin Block #6" +special_zone_8_coin_block_7 = "Funky - Coin Block #7" +special_zone_8_coin_block_8 = "Funky - Coin Block #8" +special_zone_8_coin_block_9 = "Funky - Coin Block #9" +special_zone_8_coin_block_10 = "Funky - Coin Block #10" +special_zone_8_coin_block_11 = "Funky - Coin Block #11" +special_zone_8_coin_block_12 = "Funky - Coin Block #12" +special_zone_8_coin_block_13 = "Funky - Coin Block #13" +special_zone_8_coin_block_14 = "Funky - Coin Block #14" +special_zone_8_coin_block_15 = "Funky - Coin Block #15" +special_zone_8_coin_block_16 = "Funky - Coin Block #16" +special_zone_8_coin_block_17 = "Funky - Coin Block #17" +special_zone_8_coin_block_18 = "Funky - Coin Block #18" +special_zone_8_multi_coin_block_1 = "Funky - Multi Coin Block #1" +special_zone_8_coin_block_19 = "Funky - Coin Block #19" +special_zone_8_coin_block_20 = "Funky - Coin Block #20" +special_zone_8_coin_block_21 = "Funky - Coin Block #21" +special_zone_8_coin_block_22 = "Funky - Coin Block #22" +special_zone_8_coin_block_23 = "Funky - Coin Block #23" +special_zone_8_powerup_block_2 = "Funky - Powerup Block #2" +special_zone_8_flying_block_1 = "Funky - Flying Question Block #1" +special_zone_7_powerup_block_1 = "Outrageous - Powerup Block #1" +special_zone_7_yoshi_block_1 = "Outrageous - Yoshi Block #1" +special_zone_7_coin_block_1 = "Outrageous - Coin Block #1" +special_zone_7_powerup_block_2 = "Outrageous - Powerup Block #2" +special_zone_7_coin_block_2 = "Outrageous - Coin Block #2" +special_zone_6_powerup_block_1 = "Mondo - Powerup Block #1" +special_zone_6_coin_block_1 = "Mondo - Coin Block #1" +special_zone_6_coin_block_2 = "Mondo - Coin Block #2" +special_zone_6_yoshi_block_1 = "Mondo - Yoshi Block #1" +special_zone_6_life_block_1 = "Mondo - 1-Up Mushroom Block #1" +special_zone_6_multi_coin_block_1 = "Mondo - Multi Coin Block #1" +special_zone_6_coin_block_3 = "Mondo - Coin Block #3" +special_zone_6_coin_block_4 = "Mondo - Coin Block #4" +special_zone_6_coin_block_5 = "Mondo - Coin Block #5" +special_zone_6_coin_block_6 = "Mondo - Coin Block #6" +special_zone_6_coin_block_7 = "Mondo - Coin Block #7" +special_zone_6_coin_block_8 = "Mondo - Coin Block #8" +special_zone_6_coin_block_9 = "Mondo - Coin Block #9" +special_zone_6_coin_block_10 = "Mondo - Coin Block #10" +special_zone_6_coin_block_11 = "Mondo - Coin Block #11" +special_zone_6_coin_block_12 = "Mondo - Coin Block #12" +special_zone_6_coin_block_13 = "Mondo - Coin Block #13" +special_zone_6_coin_block_14 = "Mondo - Coin Block #14" +special_zone_6_coin_block_15 = "Mondo - Coin Block #15" +special_zone_6_coin_block_16 = "Mondo - Coin Block #16" +special_zone_6_coin_block_17 = "Mondo - Coin Block #17" +special_zone_6_coin_block_18 = "Mondo - Coin Block #18" +special_zone_6_coin_block_19 = "Mondo - Coin Block #19" +special_zone_6_coin_block_20 = "Mondo - Coin Block #20" +special_zone_6_coin_block_21 = "Mondo - Coin Block #21" +special_zone_6_coin_block_22 = "Mondo - Coin Block #22" +special_zone_6_coin_block_23 = "Mondo - Coin Block #23" +special_zone_6_coin_block_24 = "Mondo - Coin Block #24" +special_zone_6_coin_block_25 = "Mondo - Coin Block #25" +special_zone_6_coin_block_26 = "Mondo - Coin Block #26" +special_zone_6_coin_block_27 = "Mondo - Coin Block #27" +special_zone_6_coin_block_28 = "Mondo - Coin Block #28" +special_zone_6_powerup_block_2 = "Mondo - Powerup Block #2" +special_zone_6_coin_block_29 = "Mondo - Coin Block #29" +special_zone_6_coin_block_30 = "Mondo - Coin Block #30" +special_zone_6_coin_block_31 = "Mondo - Coin Block #31" +special_zone_6_coin_block_32 = "Mondo - Coin Block #32" +special_zone_6_coin_block_33 = "Mondo - Coin Block #33" +special_zone_5_yoshi_block_1 = "Groovy - Yoshi Block #1" +special_zone_1_vine_block_1 = "Gnarly - Vine Block #1" +special_zone_1_vine_block_2 = "Gnarly - Vine Block #2" +special_zone_1_vine_block_3 = "Gnarly - Vine Block #3" +special_zone_1_vine_block_4 = "Gnarly - Vine Block #4" +special_zone_1_life_block_1 = "Gnarly - 1-Up Mushroom Block #1" +special_zone_1_vine_block_5 = "Gnarly - Vine Block #5" +special_zone_1_blue_pow_block_1 = "Gnarly - Blue P-Switch Block #1" +special_zone_1_vine_block_6 = "Gnarly - Vine Block #6" +special_zone_1_powerup_block_1 = "Gnarly - Powerup Block #1" +special_zone_1_pswitch_coin_block_1 = "Gnarly - P-Switch Coin Block #1" +special_zone_1_pswitch_coin_block_2 = "Gnarly - P-Switch Coin Block #2" +special_zone_1_pswitch_coin_block_3 = "Gnarly - P-Switch Coin Block #3" +special_zone_1_pswitch_coin_block_4 = "Gnarly - P-Switch Coin Block #4" +special_zone_1_pswitch_coin_block_5 = "Gnarly - P-Switch Coin Block #5" +special_zone_1_pswitch_coin_block_6 = "Gnarly - P-Switch Coin Block #6" +special_zone_1_pswitch_coin_block_7 = "Gnarly - P-Switch Coin Block #7" +special_zone_1_pswitch_coin_block_8 = "Gnarly - P-Switch Coin Block #8" +special_zone_1_pswitch_coin_block_9 = "Gnarly - P-Switch Coin Block #9" +special_zone_1_pswitch_coin_block_10 = "Gnarly - P-Switch Coin Block #10" +special_zone_1_pswitch_coin_block_11 = "Gnarly - P-Switch Coin Block #11" +special_zone_1_pswitch_coin_block_12 = "Gnarly - P-Switch Coin Block #12" +special_zone_1_pswitch_coin_block_13 = "Gnarly - P-Switch Coin Block #13" +special_zone_2_powerup_block_1 = "Tubular - Powerup Block #1" +special_zone_2_coin_block_1 = "Tubular - Coin Block #1" +special_zone_2_coin_block_2 = "Tubular - Coin Block #2" +special_zone_2_powerup_block_2 = "Tubular - Powerup Block #2" +special_zone_2_coin_block_3 = "Tubular - Coin Block #3" +special_zone_2_coin_block_4 = "Tubular - Coin Block #4" +special_zone_2_powerup_block_3 = "Tubular - Powerup Block #3" +special_zone_2_multi_coin_block_1 = "Tubular - Multi Coin Block #1" +special_zone_2_coin_block_5 = "Tubular - Coin Block #5" +special_zone_2_coin_block_6 = "Tubular - Coin Block #6" +special_zone_3_powerup_block_1 = "Way Cool - Powerup Block #1" +special_zone_3_yoshi_block_1 = "Way Cool - Yoshi Block #1" +special_zone_3_wings_block_1 = "Way Cool - Wings Block #1" +special_zone_4_powerup_block_1 = "Awesome - Powerup Block #1" +special_zone_4_star_block_1 = "Awesome - Star Block #1" +star_road_2_star_block_1 = "Star Road 2 - Star Block #1" +star_road_3_key_block_1 = "Star Road 3 - Key Block #1" +star_road_4_powerup_block_1 = "Star Road 4 - Powerup Block #1" +star_road_4_green_block_1 = "Star Road 4 - Green Switch Palace Block #1" +star_road_4_green_block_2 = "Star Road 4 - Green Switch Palace Block #2" +star_road_4_green_block_3 = "Star Road 4 - Green Switch Palace Block #3" +star_road_4_green_block_4 = "Star Road 4 - Green Switch Palace Block #4" +star_road_4_green_block_5 = "Star Road 4 - Green Switch Palace Block #5" +star_road_4_green_block_6 = "Star Road 4 - Green Switch Palace Block #6" +star_road_4_green_block_7 = "Star Road 4 - Green Switch Palace Block #7" +star_road_4_key_block_1 = "Star Road 4 - Key Block #1" +star_road_5_directional_coin_block_1 = "Star Road 5 - Directional Coin Block #1" +star_road_5_life_block_1 = "Star Road 5 - 1-Up Mushroom Block #1" +star_road_5_vine_block_1 = "Star Road 5 - Vine Block #1" +star_road_5_yellow_block_1 = "Star Road 5 - Yellow Switch Palace Block #1" +star_road_5_yellow_block_2 = "Star Road 5 - Yellow Switch Palace Block #2" +star_road_5_yellow_block_3 = "Star Road 5 - Yellow Switch Palace Block #3" +star_road_5_yellow_block_4 = "Star Road 5 - Yellow Switch Palace Block #4" +star_road_5_yellow_block_5 = "Star Road 5 - Yellow Switch Palace Block #5" +star_road_5_yellow_block_6 = "Star Road 5 - Yellow Switch Palace Block #6" +star_road_5_yellow_block_7 = "Star Road 5 - Yellow Switch Palace Block #7" +star_road_5_yellow_block_8 = "Star Road 5 - Yellow Switch Palace Block #8" +star_road_5_yellow_block_9 = "Star Road 5 - Yellow Switch Palace Block #9" +star_road_5_yellow_block_10 = "Star Road 5 - Yellow Switch Palace Block #10" +star_road_5_yellow_block_11 = "Star Road 5 - Yellow Switch Palace Block #11" +star_road_5_yellow_block_12 = "Star Road 5 - Yellow Switch Palace Block #12" +star_road_5_yellow_block_13 = "Star Road 5 - Yellow Switch Palace Block #13" +star_road_5_yellow_block_14 = "Star Road 5 - Yellow Switch Palace Block #14" +star_road_5_yellow_block_15 = "Star Road 5 - Yellow Switch Palace Block #15" +star_road_5_yellow_block_16 = "Star Road 5 - Yellow Switch Palace Block #16" +star_road_5_yellow_block_17 = "Star Road 5 - Yellow Switch Palace Block #17" +star_road_5_yellow_block_18 = "Star Road 5 - Yellow Switch Palace Block #18" +star_road_5_yellow_block_19 = "Star Road 5 - Yellow Switch Palace Block #19" +star_road_5_yellow_block_20 = "Star Road 5 - Yellow Switch Palace Block #20" +star_road_5_green_block_1 = "Star Road 5 - Green Switch Palace Block #1" +star_road_5_green_block_2 = "Star Road 5 - Green Switch Palace Block #2" +star_road_5_green_block_3 = "Star Road 5 - Green Switch Palace Block #3" +star_road_5_green_block_4 = "Star Road 5 - Green Switch Palace Block #4" +star_road_5_green_block_5 = "Star Road 5 - Green Switch Palace Block #5" +star_road_5_green_block_6 = "Star Road 5 - Green Switch Palace Block #6" +star_road_5_green_block_7 = "Star Road 5 - Green Switch Palace Block #7" +star_road_5_green_block_8 = "Star Road 5 - Green Switch Palace Block #8" +star_road_5_green_block_9 = "Star Road 5 - Green Switch Palace Block #9" +star_road_5_green_block_10 = "Star Road 5 - Green Switch Palace Block #10" +star_road_5_green_block_11 = "Star Road 5 - Green Switch Palace Block #11" +star_road_5_green_block_12 = "Star Road 5 - Green Switch Palace Block #12" +star_road_5_green_block_13 = "Star Road 5 - Green Switch Palace Block #13" +star_road_5_green_block_14 = "Star Road 5 - Green Switch Palace Block #14" +star_road_5_green_block_15 = "Star Road 5 - Green Switch Palace Block #15" +star_road_5_green_block_16 = "Star Road 5 - Green Switch Palace Block #16" +star_road_5_green_block_17 = "Star Road 5 - Green Switch Palace Block #17" +star_road_5_green_block_18 = "Star Road 5 - Green Switch Palace Block #18" +star_road_5_green_block_19 = "Star Road 5 - Green Switch Palace Block #19" +star_road_5_green_block_20 = "Star Road 5 - Green Switch Palace Block #20" diff --git a/worlds/smw/Names/TextBox.py b/worlds/smw/Names/TextBox.py index cecf088661..2302a5f85f 100644 --- a/worlds/smw/Names/TextBox.py +++ b/worlds/smw/Names/TextBox.py @@ -1,5 +1,5 @@ -from BaseClasses import MultiWorld +from worlds.AutoWorld import World import math @@ -63,21 +63,23 @@ def generate_text_box(input_string): return out_bytes -def generate_goal_text(world: MultiWorld, player: int): +def generate_goal_text(world: World): out_array = bytearray() - if world.goal[player] == "yoshi_egg_hunt": - required_yoshi_eggs = max(math.floor( - world.number_of_yoshi_eggs[player].value * (world.percentage_of_yoshi_eggs[player].value / 100.0)), 1) + if world.options.goal == "yoshi_egg_hunt": + required_yoshi_eggs = world.required_egg_count + actual_yoshi_eggs = world.actual_egg_count out_array += bytearray([0x9F, 0x9F]) out_array += string_to_bytes(" You must acquire") out_array[-1] += 0x80 - out_array += string_to_bytes(f' {required_yoshi_eggs:02} Yoshi Eggs,') + out_array += string_to_bytes(f' {required_yoshi_eggs:03} of {actual_yoshi_eggs:03}') + out_array[-1] += 0x80 + out_array += string_to_bytes(f' Yoshi Eggs,') out_array[-1] += 0x80 out_array += string_to_bytes("then return here.") out_array[-1] += 0x80 - out_array += bytearray([0x9F, 0x9F, 0x9F]) + out_array += bytearray([0x9F, 0x9F]) else: - bosses_required = world.bosses_required[player].value + bosses_required = world.options.bosses_required.value out_array += bytearray([0x9F, 0x9F]) out_array += string_to_bytes(" You must defeat") out_array[-1] += 0x80 diff --git a/worlds/smw/Options.py b/worlds/smw/Options.py index 60135896c8..ab7fcccdba 100644 --- a/worlds/smw/Options.py +++ b/worlds/smw/Options.py @@ -1,6 +1,6 @@ -import typing +from dataclasses import dataclass -from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList +from Options import Choice, Range, Toggle, DeathLink, DefaultOnToggle, PerGameCommonOptions class Goal(Choice): @@ -27,11 +27,13 @@ class BossesRequired(Range): class NumberOfYoshiEggs(Range): """ - How many Yoshi Eggs are in the pool for Yoshi Egg Hunt + Maximum possible number of Yoshi Eggs that will be in the item pool + If fewer available locations exist in the pool than this number, the number of available locations will be used instead. + Required Percentage of Yoshi Eggs will be calculated based off of that number. """ - display_name = "Total Number of Yoshi Eggs" + display_name = "Max Number of Yoshi Eggs" range_start = 1 - range_end = 80 + range_end = 255 default = 50 @@ -52,6 +54,40 @@ class DragonCoinChecks(Toggle): display_name = "Dragon Coin Checks" +class MoonChecks(Toggle): + """ + Whether collecting a 3-Up Moon in a level will grant a check + """ + display_name = "3up Moon Checks" + + +class Hidden1UpChecks(Toggle): + """ + Whether collecting a hidden 1-Up mushroom in a level will grant a check + These checks are considered cryptic as there's no actual indicator that they're in their respective places + Enable this option at your own risk + """ + display_name = "Hidden 1-Up Checks" + + +class BonusBlockChecks(Toggle): + """ + Whether collecting a 1-Up mushroom from a Bonus Block in a level will grant a check + """ + display_name = "Bonus Block Checks" + + +class Blocksanity(Toggle): + """ + Whether hitting a block with an item or coin inside will grant a check + Note that some blocks are excluded due to how the option and the game works! + Exclusion list: + * Blocks in Top Secret Area & Front Door/Bowser Castle + * Blocks that are unreachable unless you glitch your way in + """ + display_name = "Blocksanity" + + class BowserCastleDoors(Choice): """ How the doors of Bowser's Castle behave @@ -127,16 +163,6 @@ class SwapDonutGhostHouseExits(Toggle): display_name = "Swap Donut GH Exits" -class DisplaySentItemPopups(Choice): - """ - What messages to display in-game for items sent - """ - display_name = "Display Sent Item Popups" - option_none = 0 - option_all = 1 - default = 1 - - class DisplayReceivedItemPopups(Choice): """ What messages to display in-game for items received @@ -145,7 +171,18 @@ class DisplayReceivedItemPopups(Choice): option_none = 0 option_all = 1 option_progression = 2 - default = 2 + option_progression_minus_yoshi_eggs = 3 + default = 3 + + +class JunkFillPercentage(Range): + """ + Replace a percentage of non-required Yoshi Eggs in the item pool with random junk items (only applicable on Yoshi Egg Hunt goal) + """ + display_name = "Junk Fill Percentage" + range_start = 0 + range_end = 100 + default = 0 class TrapFillPercentage(Range): @@ -197,6 +234,20 @@ class TimerTrapWeight(BaseTrapWeight): display_name = "Timer Trap Weight" +class ReverseTrapWeight(BaseTrapWeight): + """ + Likelihood of a receiving a trap which causes the controls to be reversed in the current level + """ + display_name = "Reverse Trap Weight" + + +class ThwimpTrapWeight(BaseTrapWeight): + """ + Likelihood of a receiving a trap which causes a Thwimp to spawn above the player + """ + display_name = "Thwimp Trap Weight" + + class Autosave(DefaultOnToggle): """ Whether a save prompt will appear after every level @@ -239,6 +290,21 @@ class MusicShuffle(Choice): default = 0 +class SFXShuffle(Choice): + """ + Shuffles almost every instance of sound effect playback + Archipelago elements that play sound effects aren't randomized + None: No SFX are shuffled + Full: Each individual SFX call has a random SFX + Singularity: The entire game uses one SFX for every SFX call + """ + display_name = "Sound Effect Shuffle" + option_none = 0 + option_full = 1 + option_singularity = 2 + default = 0 + + class MarioPalette(Choice): """ Mario palette color @@ -255,25 +321,32 @@ class MarioPalette(Choice): default = 0 -class ForegroundPaletteShuffle(Toggle): +class LevelPaletteShuffle(Choice): """ - Whether to shuffle level foreground palettes + Whether to shuffle level palettes + Off: Do not shuffle palettes + On Legacy: Uses only the palette sets from the original game + On Curated: Uses custom, hand-crafted palette sets """ - display_name = "Foreground Palette Shuffle" + display_name = "Level Palette Shuffle" + option_off = 0 + option_on_legacy = 1 + option_on_curated = 2 + default = 0 -class BackgroundPaletteShuffle(Toggle): - """ - Whether to shuffle level background palettes - """ - display_name = "Background Palette Shuffle" - - -class OverworldPaletteShuffle(Toggle): +class OverworldPaletteShuffle(Choice): """ Whether to shuffle overworld palettes + Off: Do not shuffle palettes + On Legacy: Uses only the palette sets from the original game + On Curated: Uses custom, hand-crafted palette sets """ display_name = "Overworld Palette Shuffle" + option_off = 0 + option_on_legacy = 1 + option_on_curated = 2 + default = 0 class StartingLifeCount(Range): @@ -286,34 +359,39 @@ class StartingLifeCount(Range): default = 5 - -smw_options: typing.Dict[str, type(Option)] = { - "death_link": DeathLink, - "goal": Goal, - "bosses_required": BossesRequired, - "number_of_yoshi_eggs": NumberOfYoshiEggs, - "percentage_of_yoshi_eggs": PercentageOfYoshiEggs, - "dragon_coin_checks": DragonCoinChecks, - "bowser_castle_doors": BowserCastleDoors, - "bowser_castle_rooms": BowserCastleRooms, - "level_shuffle": LevelShuffle, - "exclude_special_zone": ExcludeSpecialZone, - "boss_shuffle": BossShuffle, - "swap_donut_gh_exits": SwapDonutGhostHouseExits, - #"display_sent_item_popups": DisplaySentItemPopups, - "display_received_item_popups": DisplayReceivedItemPopups, - "trap_fill_percentage": TrapFillPercentage, - "ice_trap_weight": IceTrapWeight, - "stun_trap_weight": StunTrapWeight, - "literature_trap_weight": LiteratureTrapWeight, - "timer_trap_weight": TimerTrapWeight, - "autosave": Autosave, - "early_climb": EarlyClimb, - "overworld_speed": OverworldSpeed, - "music_shuffle": MusicShuffle, - "mario_palette": MarioPalette, - "foreground_palette_shuffle": ForegroundPaletteShuffle, - "background_palette_shuffle": BackgroundPaletteShuffle, - "overworld_palette_shuffle": OverworldPaletteShuffle, - "starting_life_count": StartingLifeCount, -} +@dataclass +class SMWOptions(PerGameCommonOptions): + death_link: DeathLink + goal: Goal + bosses_required: BossesRequired + max_yoshi_egg_cap: NumberOfYoshiEggs + percentage_of_yoshi_eggs: PercentageOfYoshiEggs + dragon_coin_checks: DragonCoinChecks + moon_checks: MoonChecks + hidden_1up_checks: Hidden1UpChecks + bonus_block_checks: BonusBlockChecks + blocksanity: Blocksanity + bowser_castle_doors: BowserCastleDoors + bowser_castle_rooms: BowserCastleRooms + level_shuffle: LevelShuffle + exclude_special_zone: ExcludeSpecialZone + boss_shuffle: BossShuffle + swap_donut_gh_exits: SwapDonutGhostHouseExits + display_received_item_popups: DisplayReceivedItemPopups + junk_fill_percentage: JunkFillPercentage + trap_fill_percentage: TrapFillPercentage + ice_trap_weight: IceTrapWeight + stun_trap_weight: StunTrapWeight + literature_trap_weight: LiteratureTrapWeight + timer_trap_weight: TimerTrapWeight + reverse_trap_weight: ReverseTrapWeight + thwimp_trap_weight: ThwimpTrapWeight + autosave: Autosave + early_climb: EarlyClimb + overworld_speed: OverworldSpeed + music_shuffle: MusicShuffle + sfx_shuffle: SFXShuffle + mario_palette: MarioPalette + level_palette_shuffle: LevelPaletteShuffle + overworld_palette_shuffle: OverworldPaletteShuffle + starting_life_count: StartingLifeCount diff --git a/worlds/smw/Regions.py b/worlds/smw/Regions.py index 885f209aa7..2f8a128a56 100644 --- a/worlds/smw/Regions.py +++ b/worlds/smw/Regions.py @@ -1,467 +1,470 @@ import typing -from BaseClasses import MultiWorld, Region, Entrance +from BaseClasses import CollectionState, MultiWorld, Region, Entrance from .Locations import SMWLocation from .Levels import level_info_dict from .Names import LocationName, ItemName from worlds.generic.Rules import add_rule, set_rule +from worlds.AutoWorld import World -def create_regions(world, player: int, active_locations): - menu_region = create_region(world, player, active_locations, 'Menu', None) +def create_regions(world: World, active_locations): + multiworld: MultiWorld = world.multiworld + player: int = world.player - yoshis_island_region = create_region(world, player, active_locations, LocationName.yoshis_island_region, None) + menu_region = create_region(multiworld, player, active_locations, 'Menu', None) + yoshis_island_region = create_region(multiworld, player, active_locations, LocationName.yoshis_island_region, None) - yoshis_house_tile = create_region(world, player, active_locations, LocationName.yoshis_house_tile, None) + yoshis_house_tile = create_region(multiworld, player, active_locations, LocationName.yoshis_house_tile, None) yoshis_house_region_locations = [] - if world.goal[player] == "yoshi_egg_hunt": + if world.options.goal == "yoshi_egg_hunt": yoshis_house_region_locations.append(LocationName.yoshis_house) - yoshis_house_region = create_region(world, player, active_locations, LocationName.yoshis_house, + yoshis_house_region = create_region(multiworld, player, active_locations, LocationName.yoshis_house, yoshis_house_region_locations) - yoshis_island_1_tile = create_region(world, player, active_locations, LocationName.yoshis_island_1_tile, None) - yoshis_island_1_region = create_region(world, player, active_locations, LocationName.yoshis_island_1_region, None) - yoshis_island_1_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_1_exit_1, + yoshis_island_1_tile = create_region(multiworld, player, active_locations, LocationName.yoshis_island_1_tile, None) + yoshis_island_1_region = create_region(multiworld, player, active_locations, LocationName.yoshis_island_1_region, None) + yoshis_island_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.yoshis_island_1_exit_1, [LocationName.yoshis_island_1_exit_1]) - yoshis_island_2_tile = create_region(world, player, active_locations, LocationName.yoshis_island_2_tile, None) - yoshis_island_2_region = create_region(world, player, active_locations, LocationName.yoshis_island_2_region, None) - yoshis_island_2_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_2_exit_1, + yoshis_island_2_tile = create_region(multiworld, player, active_locations, LocationName.yoshis_island_2_tile, None) + yoshis_island_2_region = create_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, None) + yoshis_island_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.yoshis_island_2_exit_1, [LocationName.yoshis_island_2_exit_1]) - yoshis_island_3_tile = create_region(world, player, active_locations, LocationName.yoshis_island_3_tile, None) - yoshis_island_3_region = create_region(world, player, active_locations, LocationName.yoshis_island_3_region, None) - yoshis_island_3_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_3_exit_1, + yoshis_island_3_tile = create_region(multiworld, player, active_locations, LocationName.yoshis_island_3_tile, None) + yoshis_island_3_region = create_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, None) + yoshis_island_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.yoshis_island_3_exit_1, [LocationName.yoshis_island_3_exit_1]) - yoshis_island_4_tile = create_region(world, player, active_locations, LocationName.yoshis_island_4_tile, None) - yoshis_island_4_region = create_region(world, player, active_locations, LocationName.yoshis_island_4_region, None) - yoshis_island_4_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_4_exit_1, + yoshis_island_4_tile = create_region(multiworld, player, active_locations, LocationName.yoshis_island_4_tile, None) + yoshis_island_4_region = create_region(multiworld, player, active_locations, LocationName.yoshis_island_4_region, None) + yoshis_island_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.yoshis_island_4_exit_1, [LocationName.yoshis_island_4_exit_1]) - yoshis_island_castle_tile = create_region(world, player, active_locations, LocationName.yoshis_island_castle_tile, None) - yoshis_island_castle_region = create_region(world, player, active_locations, LocationName.yoshis_island_castle_region, None) - yoshis_island_castle = create_region(world, player, active_locations, LocationName.yoshis_island_castle, + yoshis_island_castle_tile = create_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_tile, None) + yoshis_island_castle_region = create_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_region, None) + yoshis_island_castle = create_region(multiworld, player, active_locations, LocationName.yoshis_island_castle, [LocationName.yoshis_island_castle, LocationName.yoshis_island_koopaling]) - yellow_switch_palace_tile = create_region(world, player, active_locations, LocationName.yellow_switch_palace_tile, None) - yellow_switch_palace = create_region(world, player, active_locations, LocationName.yellow_switch_palace, + yellow_switch_palace_tile = create_region(multiworld, player, active_locations, LocationName.yellow_switch_palace_tile, None) + yellow_switch_palace = create_region(multiworld, player, active_locations, LocationName.yellow_switch_palace, [LocationName.yellow_switch_palace]) - donut_plains_1_tile = create_region(world, player, active_locations, LocationName.donut_plains_1_tile, None) - donut_plains_1_region = create_region(world, player, active_locations, LocationName.donut_plains_1_region, None) - donut_plains_1_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_1_exit_1, + donut_plains_1_tile = create_region(multiworld, player, active_locations, LocationName.donut_plains_1_tile, None) + donut_plains_1_region = create_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, None) + donut_plains_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_plains_1_exit_1, [LocationName.donut_plains_1_exit_1]) - donut_plains_1_exit_2 = create_region(world, player, active_locations, LocationName.donut_plains_1_exit_2, + donut_plains_1_exit_2 = create_region(multiworld, player, active_locations, LocationName.donut_plains_1_exit_2, [LocationName.donut_plains_1_exit_2]) - donut_plains_2_tile = create_region(world, player, active_locations, LocationName.donut_plains_2_tile, None) - donut_plains_2_region = create_region(world, player, active_locations, LocationName.donut_plains_2_region, None) - donut_plains_2_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_2_exit_1, + donut_plains_2_tile = create_region(multiworld, player, active_locations, LocationName.donut_plains_2_tile, None) + donut_plains_2_region = create_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, None) + donut_plains_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_plains_2_exit_1, [LocationName.donut_plains_2_exit_1]) - donut_plains_2_exit_2 = create_region(world, player, active_locations, LocationName.donut_plains_2_exit_2, + donut_plains_2_exit_2 = create_region(multiworld, player, active_locations, LocationName.donut_plains_2_exit_2, [LocationName.donut_plains_2_exit_2]) - donut_plains_3_tile = create_region(world, player, active_locations, LocationName.donut_plains_3_tile, None) - donut_plains_3_region = create_region(world, player, active_locations, LocationName.donut_plains_3_region, None) - donut_plains_3_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_3_exit_1, + donut_plains_3_tile = create_region(multiworld, player, active_locations, LocationName.donut_plains_3_tile, None) + donut_plains_3_region = create_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, None) + donut_plains_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_plains_3_exit_1, [LocationName.donut_plains_3_exit_1]) - donut_plains_4_tile = create_region(world, player, active_locations, LocationName.donut_plains_4_tile, None) - donut_plains_4_region = create_region(world, player, active_locations, LocationName.donut_plains_4_region, None) - donut_plains_4_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_4_exit_1, + donut_plains_4_tile = create_region(multiworld, player, active_locations, LocationName.donut_plains_4_tile, None) + donut_plains_4_region = create_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, None) + donut_plains_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_plains_4_exit_1, [LocationName.donut_plains_4_exit_1]) - donut_secret_1_tile = create_region(world, player, active_locations, LocationName.donut_secret_1_tile, None) - donut_secret_1_region = create_region(world, player, active_locations, LocationName.donut_secret_1_region, None) - donut_secret_1_exit_1 = create_region(world, player, active_locations, LocationName.donut_secret_1_exit_1, + donut_secret_1_tile = create_region(multiworld, player, active_locations, LocationName.donut_secret_1_tile, None) + donut_secret_1_region = create_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, None) + donut_secret_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_secret_1_exit_1, [LocationName.donut_secret_1_exit_1]) - donut_secret_1_exit_2 = create_region(world, player, active_locations, LocationName.donut_secret_1_exit_2, + donut_secret_1_exit_2 = create_region(multiworld, player, active_locations, LocationName.donut_secret_1_exit_2, [LocationName.donut_secret_1_exit_2]) - donut_secret_2_tile = create_region(world, player, active_locations, LocationName.donut_secret_2_tile, None) - donut_secret_2_region = create_region(world, player, active_locations, LocationName.donut_secret_2_region, None) - donut_secret_2_exit_1 = create_region(world, player, active_locations, LocationName.donut_secret_2_exit_1, + donut_secret_2_tile = create_region(multiworld, player, active_locations, LocationName.donut_secret_2_tile, None) + donut_secret_2_region = create_region(multiworld, player, active_locations, LocationName.donut_secret_2_region, None) + donut_secret_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_secret_2_exit_1, [LocationName.donut_secret_2_exit_1]) - donut_ghost_house_tile = create_region(world, player, active_locations, LocationName.donut_ghost_house_tile, None) - donut_ghost_house_region = create_region(world, player, active_locations, LocationName.donut_ghost_house_region, None) - donut_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.donut_ghost_house_exit_1, + donut_ghost_house_tile = create_region(multiworld, player, active_locations, LocationName.donut_ghost_house_tile, None) + donut_ghost_house_region = create_region(multiworld, player, active_locations, LocationName.donut_ghost_house_region, None) + donut_ghost_house_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_ghost_house_exit_1, [LocationName.donut_ghost_house_exit_1]) - donut_ghost_house_exit_2 = create_region(world, player, active_locations, LocationName.donut_ghost_house_exit_2, + donut_ghost_house_exit_2 = create_region(multiworld, player, active_locations, LocationName.donut_ghost_house_exit_2, [LocationName.donut_ghost_house_exit_2]) - donut_secret_house_tile = create_region(world, player, active_locations, LocationName.donut_secret_house_tile, None) - donut_secret_house_region = create_region(world, player, active_locations, LocationName.donut_secret_house_region, None) - donut_secret_house_exit_1 = create_region(world, player, active_locations, LocationName.donut_secret_house_exit_1, + donut_secret_house_tile = create_region(multiworld, player, active_locations, LocationName.donut_secret_house_tile, None) + donut_secret_house_region = create_region(multiworld, player, active_locations, LocationName.donut_secret_house_region, None) + donut_secret_house_exit_1 = create_region(multiworld, player, active_locations, LocationName.donut_secret_house_exit_1, [LocationName.donut_secret_house_exit_1]) - donut_secret_house_exit_2 = create_region(world, player, active_locations, LocationName.donut_secret_house_exit_2, + donut_secret_house_exit_2 = create_region(multiworld, player, active_locations, LocationName.donut_secret_house_exit_2, [LocationName.donut_secret_house_exit_2]) - donut_plains_castle_tile = create_region(world, player, active_locations, LocationName.donut_plains_castle_tile, None) - donut_plains_castle_region = create_region(world, player, active_locations, LocationName.donut_plains_castle_region, None) - donut_plains_castle = create_region(world, player, active_locations, LocationName.donut_plains_castle, + donut_plains_castle_tile = create_region(multiworld, player, active_locations, LocationName.donut_plains_castle_tile, None) + donut_plains_castle_region = create_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, None) + donut_plains_castle = create_region(multiworld, player, active_locations, LocationName.donut_plains_castle, [LocationName.donut_plains_castle, LocationName.donut_plains_koopaling]) - green_switch_palace_tile = create_region(world, player, active_locations, LocationName.green_switch_palace_tile, None) - green_switch_palace = create_region(world, player, active_locations, LocationName.green_switch_palace, + green_switch_palace_tile = create_region(multiworld, player, active_locations, LocationName.green_switch_palace_tile, None) + green_switch_palace = create_region(multiworld, player, active_locations, LocationName.green_switch_palace, [LocationName.green_switch_palace]) - donut_plains_top_secret_tile = create_region(world, player, active_locations, LocationName.donut_plains_top_secret_tile, None) - donut_plains_top_secret = create_region(world, player, active_locations, LocationName.donut_plains_top_secret, None) + donut_plains_top_secret_tile = create_region(multiworld, player, active_locations, LocationName.donut_plains_top_secret_tile, None) + donut_plains_top_secret = create_region(multiworld, player, active_locations, LocationName.donut_plains_top_secret, None) - vanilla_dome_1_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_1_tile, None) - vanilla_dome_1_region = create_region(world, player, active_locations, LocationName.vanilla_dome_1_region, None) - vanilla_dome_1_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_1_exit_1, + vanilla_dome_1_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_tile, None) + vanilla_dome_1_region = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, None) + vanilla_dome_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_exit_1, [LocationName.vanilla_dome_1_exit_1]) - vanilla_dome_1_exit_2 = create_region(world, player, active_locations, LocationName.vanilla_dome_1_exit_2, + vanilla_dome_1_exit_2 = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_exit_2, [LocationName.vanilla_dome_1_exit_2]) - vanilla_dome_2_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_2_tile, None) - vanilla_dome_2_region = create_region(world, player, active_locations, LocationName.vanilla_dome_2_region, None) - vanilla_dome_2_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_2_exit_1, + vanilla_dome_2_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_tile, None) + vanilla_dome_2_region = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, None) + vanilla_dome_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_exit_1, [LocationName.vanilla_dome_2_exit_1]) - vanilla_dome_2_exit_2 = create_region(world, player, active_locations, LocationName.vanilla_dome_2_exit_2, + vanilla_dome_2_exit_2 = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_exit_2, [LocationName.vanilla_dome_2_exit_2]) - vanilla_dome_3_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_3_tile, None) - vanilla_dome_3_region = create_region(world, player, active_locations, LocationName.vanilla_dome_3_region, None) - vanilla_dome_3_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_3_exit_1, + vanilla_dome_3_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_tile, None) + vanilla_dome_3_region = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, None) + vanilla_dome_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_exit_1, [LocationName.vanilla_dome_3_exit_1]) - vanilla_dome_4_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_4_tile, None) - vanilla_dome_4_region = create_region(world, player, active_locations, LocationName.vanilla_dome_4_region, None) - vanilla_dome_4_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_4_exit_1, + vanilla_dome_4_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_tile, None) + vanilla_dome_4_region = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, None) + vanilla_dome_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_exit_1, [LocationName.vanilla_dome_4_exit_1]) - vanilla_secret_1_tile = create_region(world, player, active_locations, LocationName.vanilla_secret_1_tile, None) - vanilla_secret_1_region = create_region(world, player, active_locations, LocationName.vanilla_secret_1_region, None) - vanilla_secret_1_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_secret_1_exit_1, + vanilla_secret_1_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_tile, None) + vanilla_secret_1_region = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, None) + vanilla_secret_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_exit_1, [LocationName.vanilla_secret_1_exit_1]) - vanilla_secret_1_exit_2 = create_region(world, player, active_locations, LocationName.vanilla_secret_1_exit_2, + vanilla_secret_1_exit_2 = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_exit_2, [LocationName.vanilla_secret_1_exit_2]) - vanilla_secret_2_tile = create_region(world, player, active_locations, LocationName.vanilla_secret_2_tile, None) - vanilla_secret_2_region = create_region(world, player, active_locations, LocationName.vanilla_secret_2_region, None) - vanilla_secret_2_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_secret_2_exit_1, + vanilla_secret_2_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_tile, None) + vanilla_secret_2_region = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, None) + vanilla_secret_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_exit_1, [LocationName.vanilla_secret_2_exit_1]) - vanilla_secret_3_tile = create_region(world, player, active_locations, LocationName.vanilla_secret_3_tile, None) - vanilla_secret_3_region = create_region(world, player, active_locations, LocationName.vanilla_secret_3_region, None) - vanilla_secret_3_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_secret_3_exit_1, + vanilla_secret_3_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_3_tile, None) + vanilla_secret_3_region = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_3_region, None) + vanilla_secret_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_secret_3_exit_1, [LocationName.vanilla_secret_3_exit_1]) - vanilla_ghost_house_tile = create_region(world, player, active_locations, LocationName.vanilla_ghost_house_tile, None) - vanilla_ghost_house_region = create_region(world, player, active_locations, LocationName.vanilla_ghost_house_region, None) - vanilla_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_ghost_house_exit_1, + vanilla_ghost_house_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_tile, None) + vanilla_ghost_house_region = create_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, None) + vanilla_ghost_house_exit_1 = create_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_exit_1, [LocationName.vanilla_ghost_house_exit_1]) - vanilla_fortress_tile = create_region(world, player, active_locations, LocationName.vanilla_fortress_tile, None) - vanilla_fortress_region = create_region(world, player, active_locations, LocationName.vanilla_fortress_region, None) - vanilla_fortress = create_region(world, player, active_locations, LocationName.vanilla_fortress, + vanilla_fortress_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_fortress_tile, None) + vanilla_fortress_region = create_region(multiworld, player, active_locations, LocationName.vanilla_fortress_region, None) + vanilla_fortress = create_region(multiworld, player, active_locations, LocationName.vanilla_fortress, [LocationName.vanilla_fortress, LocationName.vanilla_reznor]) - vanilla_dome_castle_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_castle_tile, None) - vanilla_dome_castle_region = create_region(world, player, active_locations, LocationName.vanilla_dome_castle_region, None) - vanilla_dome_castle = create_region(world, player, active_locations, LocationName.vanilla_dome_castle, + vanilla_dome_castle_tile = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle_tile, None) + vanilla_dome_castle_region = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle_region, None) + vanilla_dome_castle = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle, [LocationName.vanilla_dome_castle, LocationName.vanilla_dome_koopaling]) - red_switch_palace_tile = create_region(world, player, active_locations, LocationName.red_switch_palace_tile, None) - red_switch_palace = create_region(world, player, active_locations, LocationName.red_switch_palace, + red_switch_palace_tile = create_region(multiworld, player, active_locations, LocationName.red_switch_palace_tile, None) + red_switch_palace = create_region(multiworld, player, active_locations, LocationName.red_switch_palace, [LocationName.red_switch_palace]) - butter_bridge_1_tile = create_region(world, player, active_locations, LocationName.butter_bridge_1_tile, None) - butter_bridge_1_region = create_region(world, player, active_locations, LocationName.butter_bridge_1_region, None) - butter_bridge_1_exit_1 = create_region(world, player, active_locations, LocationName.butter_bridge_1_exit_1, + butter_bridge_1_tile = create_region(multiworld, player, active_locations, LocationName.butter_bridge_1_tile, None) + butter_bridge_1_region = create_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, None) + butter_bridge_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.butter_bridge_1_exit_1, [LocationName.butter_bridge_1_exit_1]) - butter_bridge_2_tile = create_region(world, player, active_locations, LocationName.butter_bridge_2_tile, None) - butter_bridge_2_region = create_region(world, player, active_locations, LocationName.butter_bridge_2_region, None) - butter_bridge_2_exit_1 = create_region(world, player, active_locations, LocationName.butter_bridge_2_exit_1, + butter_bridge_2_tile = create_region(multiworld, player, active_locations, LocationName.butter_bridge_2_tile, None) + butter_bridge_2_region = create_region(multiworld, player, active_locations, LocationName.butter_bridge_2_region, None) + butter_bridge_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.butter_bridge_2_exit_1, [LocationName.butter_bridge_2_exit_1]) - cheese_bridge_tile = create_region(world, player, active_locations, LocationName.cheese_bridge_tile, None) - cheese_bridge_region = create_region(world, player, active_locations, LocationName.cheese_bridge_region, None) - cheese_bridge_exit_1 = create_region(world, player, active_locations, LocationName.cheese_bridge_exit_1, + cheese_bridge_tile = create_region(multiworld, player, active_locations, LocationName.cheese_bridge_tile, None) + cheese_bridge_region = create_region(multiworld, player, active_locations, LocationName.cheese_bridge_region, None) + cheese_bridge_exit_1 = create_region(multiworld, player, active_locations, LocationName.cheese_bridge_exit_1, [LocationName.cheese_bridge_exit_1]) - cheese_bridge_exit_2 = create_region(world, player, active_locations, LocationName.cheese_bridge_exit_2, + cheese_bridge_exit_2 = create_region(multiworld, player, active_locations, LocationName.cheese_bridge_exit_2, [LocationName.cheese_bridge_exit_2]) - cookie_mountain_tile = create_region(world, player, active_locations, LocationName.cookie_mountain_tile, None) - cookie_mountain_region = create_region(world, player, active_locations, LocationName.cookie_mountain_region, None) - cookie_mountain_exit_1 = create_region(world, player, active_locations, LocationName.cookie_mountain_exit_1, + cookie_mountain_tile = create_region(multiworld, player, active_locations, LocationName.cookie_mountain_tile, None) + cookie_mountain_region = create_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, None) + cookie_mountain_exit_1 = create_region(multiworld, player, active_locations, LocationName.cookie_mountain_exit_1, [LocationName.cookie_mountain_exit_1]) - soda_lake_tile = create_region(world, player, active_locations, LocationName.soda_lake_tile, None) - soda_lake_region = create_region(world, player, active_locations, LocationName.soda_lake_region, None) - soda_lake_exit_1 = create_region(world, player, active_locations, LocationName.soda_lake_exit_1, + soda_lake_tile = create_region(multiworld, player, active_locations, LocationName.soda_lake_tile, None) + soda_lake_region = create_region(multiworld, player, active_locations, LocationName.soda_lake_region, None) + soda_lake_exit_1 = create_region(multiworld, player, active_locations, LocationName.soda_lake_exit_1, [LocationName.soda_lake_exit_1]) - twin_bridges_castle_tile = create_region(world, player, active_locations, LocationName.twin_bridges_castle_tile, None) - twin_bridges_castle_region = create_region(world, player, active_locations, LocationName.twin_bridges_castle_region, None) - twin_bridges_castle = create_region(world, player, active_locations, LocationName.twin_bridges_castle, + twin_bridges_castle_tile = create_region(multiworld, player, active_locations, LocationName.twin_bridges_castle_tile, None) + twin_bridges_castle_region = create_region(multiworld, player, active_locations, LocationName.twin_bridges_castle_region, None) + twin_bridges_castle = create_region(multiworld, player, active_locations, LocationName.twin_bridges_castle, [LocationName.twin_bridges_castle, LocationName.twin_bridges_koopaling]) - forest_of_illusion_1_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_tile, None) - forest_of_illusion_1_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_region, None) - forest_of_illusion_1_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_exit_1, + forest_of_illusion_1_tile = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_tile, None) + forest_of_illusion_1_region = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_region, None) + forest_of_illusion_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_exit_1, [LocationName.forest_of_illusion_1_exit_1]) - forest_of_illusion_1_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_exit_2, + forest_of_illusion_1_exit_2 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_exit_2, [LocationName.forest_of_illusion_1_exit_2]) - forest_of_illusion_2_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_tile, None) - forest_of_illusion_2_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_region, None) - forest_of_illusion_2_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_exit_1, + forest_of_illusion_2_tile = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_tile, None) + forest_of_illusion_2_region = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, None) + forest_of_illusion_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_exit_1, [LocationName.forest_of_illusion_2_exit_1]) - forest_of_illusion_2_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_exit_2, + forest_of_illusion_2_exit_2 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_exit_2, [LocationName.forest_of_illusion_2_exit_2]) - forest_of_illusion_3_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_tile, None) - forest_of_illusion_3_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_region, None) - forest_of_illusion_3_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_exit_1, + forest_of_illusion_3_tile = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_tile, None) + forest_of_illusion_3_region = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, None) + forest_of_illusion_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_exit_1, [LocationName.forest_of_illusion_3_exit_1]) - forest_of_illusion_3_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_exit_2, + forest_of_illusion_3_exit_2 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_exit_2, [LocationName.forest_of_illusion_3_exit_2]) - forest_of_illusion_4_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_tile, None) - forest_of_illusion_4_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_region, None) - forest_of_illusion_4_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_exit_1, + forest_of_illusion_4_tile = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_tile, None) + forest_of_illusion_4_region = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, None) + forest_of_illusion_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_exit_1, [LocationName.forest_of_illusion_4_exit_1]) - forest_of_illusion_4_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_exit_2, + forest_of_illusion_4_exit_2 = create_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_exit_2, [LocationName.forest_of_illusion_4_exit_2]) - forest_ghost_house_tile = create_region(world, player, active_locations, LocationName.forest_ghost_house_tile, None) - forest_ghost_house_region = create_region(world, player, active_locations, LocationName.forest_ghost_house_region, None) - forest_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.forest_ghost_house_exit_1, + forest_ghost_house_tile = create_region(multiworld, player, active_locations, LocationName.forest_ghost_house_tile, None) + forest_ghost_house_region = create_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, None) + forest_ghost_house_exit_1 = create_region(multiworld, player, active_locations, LocationName.forest_ghost_house_exit_1, [LocationName.forest_ghost_house_exit_1]) - forest_ghost_house_exit_2 = create_region(world, player, active_locations, LocationName.forest_ghost_house_exit_2, + forest_ghost_house_exit_2 = create_region(multiworld, player, active_locations, LocationName.forest_ghost_house_exit_2, [LocationName.forest_ghost_house_exit_2]) - forest_secret_tile = create_region(world, player, active_locations, LocationName.forest_secret_tile, None) - forest_secret_region = create_region(world, player, active_locations, LocationName.forest_secret_region, None) - forest_secret_exit_1 = create_region(world, player, active_locations, LocationName.forest_secret_exit_1, + forest_secret_tile = create_region(multiworld, player, active_locations, LocationName.forest_secret_tile, None) + forest_secret_region = create_region(multiworld, player, active_locations, LocationName.forest_secret_region, None) + forest_secret_exit_1 = create_region(multiworld, player, active_locations, LocationName.forest_secret_exit_1, [LocationName.forest_secret_exit_1]) - forest_fortress_tile = create_region(world, player, active_locations, LocationName.forest_fortress_tile, None) - forest_fortress_region = create_region(world, player, active_locations, LocationName.forest_fortress_region, None) - forest_fortress = create_region(world, player, active_locations, LocationName.forest_fortress, + forest_fortress_tile = create_region(multiworld, player, active_locations, LocationName.forest_fortress_tile, None) + forest_fortress_region = create_region(multiworld, player, active_locations, LocationName.forest_fortress_region, None) + forest_fortress = create_region(multiworld, player, active_locations, LocationName.forest_fortress, [LocationName.forest_fortress, LocationName.forest_reznor]) - forest_castle_tile = create_region(world, player, active_locations, LocationName.forest_castle_tile, None) - forest_castle_region = create_region(world, player, active_locations, LocationName.forest_castle_region, None) - forest_castle = create_region(world, player, active_locations, LocationName.forest_castle, + forest_castle_tile = create_region(multiworld, player, active_locations, LocationName.forest_castle_tile, None) + forest_castle_region = create_region(multiworld, player, active_locations, LocationName.forest_castle_region, None) + forest_castle = create_region(multiworld, player, active_locations, LocationName.forest_castle, [LocationName.forest_castle, LocationName.forest_koopaling]) - blue_switch_palace_tile = create_region(world, player, active_locations, LocationName.blue_switch_palace_tile, None) - blue_switch_palace = create_region(world, player, active_locations, LocationName.blue_switch_palace, + blue_switch_palace_tile = create_region(multiworld, player, active_locations, LocationName.blue_switch_palace_tile, None) + blue_switch_palace = create_region(multiworld, player, active_locations, LocationName.blue_switch_palace, [LocationName.blue_switch_palace]) - chocolate_island_1_tile = create_region(world, player, active_locations, LocationName.chocolate_island_1_tile, None) - chocolate_island_1_region = create_region(world, player, active_locations, LocationName.chocolate_island_1_region, None) - chocolate_island_1_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_1_exit_1, + chocolate_island_1_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_island_1_tile, None) + chocolate_island_1_region = create_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, None) + chocolate_island_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.chocolate_island_1_exit_1, [LocationName.chocolate_island_1_exit_1]) - chocolate_island_2_tile = create_region(world, player, active_locations, LocationName.chocolate_island_2_tile, None) - chocolate_island_2_region = create_region(world, player, active_locations, LocationName.chocolate_island_2_region, None) - chocolate_island_2_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_2_exit_1, + chocolate_island_2_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_island_2_tile, None) + chocolate_island_2_region = create_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, None) + chocolate_island_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.chocolate_island_2_exit_1, [LocationName.chocolate_island_2_exit_1]) - chocolate_island_2_exit_2 = create_region(world, player, active_locations, LocationName.chocolate_island_2_exit_2, + chocolate_island_2_exit_2 = create_region(multiworld, player, active_locations, LocationName.chocolate_island_2_exit_2, [LocationName.chocolate_island_2_exit_2]) - chocolate_island_3_tile = create_region(world, player, active_locations, LocationName.chocolate_island_3_tile, None) - chocolate_island_3_region = create_region(world, player, active_locations, LocationName.chocolate_island_3_region, None) - chocolate_island_3_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_3_exit_1, + chocolate_island_3_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_island_3_tile, None) + chocolate_island_3_region = create_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, None) + chocolate_island_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.chocolate_island_3_exit_1, [LocationName.chocolate_island_3_exit_1]) - chocolate_island_3_exit_2 = create_region(world, player, active_locations, LocationName.chocolate_island_3_exit_2, + chocolate_island_3_exit_2 = create_region(multiworld, player, active_locations, LocationName.chocolate_island_3_exit_2, [LocationName.chocolate_island_3_exit_2]) - chocolate_island_4_tile = create_region(world, player, active_locations, LocationName.chocolate_island_4_tile, None) - chocolate_island_4_region = create_region(world, player, active_locations, LocationName.chocolate_island_4_region, None) - chocolate_island_4_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_4_exit_1, + chocolate_island_4_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_island_4_tile, None) + chocolate_island_4_region = create_region(multiworld, player, active_locations, LocationName.chocolate_island_4_region, None) + chocolate_island_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.chocolate_island_4_exit_1, [LocationName.chocolate_island_4_exit_1]) - chocolate_island_5_tile = create_region(world, player, active_locations, LocationName.chocolate_island_5_tile, None) - chocolate_island_5_region = create_region(world, player, active_locations, LocationName.chocolate_island_5_region, None) - chocolate_island_5_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_5_exit_1, + chocolate_island_5_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_island_5_tile, None) + chocolate_island_5_region = create_region(multiworld, player, active_locations, LocationName.chocolate_island_5_region, None) + chocolate_island_5_exit_1 = create_region(multiworld, player, active_locations, LocationName.chocolate_island_5_exit_1, [LocationName.chocolate_island_5_exit_1]) - chocolate_ghost_house_tile = create_region(world, player, active_locations, LocationName.chocolate_ghost_house_tile, None) - chocolate_ghost_house_region = create_region(world, player, active_locations, LocationName.chocolate_ghost_house_region, None) - chocolate_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_ghost_house_exit_1, + chocolate_ghost_house_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_ghost_house_tile, None) + chocolate_ghost_house_region = create_region(multiworld, player, active_locations, LocationName.chocolate_ghost_house_region, None) + chocolate_ghost_house_exit_1 = create_region(multiworld, player, active_locations, LocationName.chocolate_ghost_house_exit_1, [LocationName.chocolate_ghost_house_exit_1]) - chocolate_secret_tile = create_region(world, player, active_locations, LocationName.chocolate_secret_tile, None) - chocolate_secret_region = create_region(world, player, active_locations, LocationName.chocolate_secret_region, None) - chocolate_secret_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_secret_exit_1, + chocolate_secret_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_secret_tile, None) + chocolate_secret_region = create_region(multiworld, player, active_locations, LocationName.chocolate_secret_region, None) + chocolate_secret_exit_1 = create_region(multiworld, player, active_locations, LocationName.chocolate_secret_exit_1, [LocationName.chocolate_secret_exit_1]) - chocolate_fortress_tile = create_region(world, player, active_locations, LocationName.chocolate_fortress_tile, None) - chocolate_fortress_region = create_region(world, player, active_locations, LocationName.chocolate_fortress_region, None) - chocolate_fortress = create_region(world, player, active_locations, LocationName.chocolate_fortress, + chocolate_fortress_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_fortress_tile, None) + chocolate_fortress_region = create_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, None) + chocolate_fortress = create_region(multiworld, player, active_locations, LocationName.chocolate_fortress, [LocationName.chocolate_fortress, LocationName.chocolate_reznor]) - chocolate_castle_tile = create_region(world, player, active_locations, LocationName.chocolate_castle_tile, None) - chocolate_castle_region = create_region(world, player, active_locations, LocationName.chocolate_castle_region, None) - chocolate_castle = create_region(world, player, active_locations, LocationName.chocolate_castle, + chocolate_castle_tile = create_region(multiworld, player, active_locations, LocationName.chocolate_castle_tile, None) + chocolate_castle_region = create_region(multiworld, player, active_locations, LocationName.chocolate_castle_region, None) + chocolate_castle = create_region(multiworld, player, active_locations, LocationName.chocolate_castle, [LocationName.chocolate_castle, LocationName.chocolate_koopaling]) - sunken_ghost_ship_tile = create_region(world, player, active_locations, LocationName.sunken_ghost_ship_tile, None) - sunken_ghost_ship_region = create_region(world, player, active_locations, LocationName.sunken_ghost_ship_region, None) - sunken_ghost_ship = create_region(world, player, active_locations, LocationName.sunken_ghost_ship, + sunken_ghost_ship_tile = create_region(multiworld, player, active_locations, LocationName.sunken_ghost_ship_tile, None) + sunken_ghost_ship_region = create_region(multiworld, player, active_locations, LocationName.sunken_ghost_ship_region, None) + sunken_ghost_ship = create_region(multiworld, player, active_locations, LocationName.sunken_ghost_ship, [LocationName.sunken_ghost_ship]) - valley_of_bowser_1_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_1_tile, None) - valley_of_bowser_1_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_1_region, None) - valley_of_bowser_1_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_1_exit_1, + valley_of_bowser_1_tile = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_tile, None) + valley_of_bowser_1_region = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, None) + valley_of_bowser_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_exit_1, [LocationName.valley_of_bowser_1_exit_1]) - valley_of_bowser_2_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_tile, None) - valley_of_bowser_2_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_region, None) - valley_of_bowser_2_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_exit_1, + valley_of_bowser_2_tile = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_tile, None) + valley_of_bowser_2_region = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_region, None) + valley_of_bowser_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_exit_1, [LocationName.valley_of_bowser_2_exit_1]) - valley_of_bowser_2_exit_2 = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_exit_2, + valley_of_bowser_2_exit_2 = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_exit_2, [LocationName.valley_of_bowser_2_exit_2]) - valley_of_bowser_3_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_3_tile, None) - valley_of_bowser_3_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_3_region, None) - valley_of_bowser_3_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_3_exit_1, + valley_of_bowser_3_tile = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_3_tile, None) + valley_of_bowser_3_region = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_3_region, None) + valley_of_bowser_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_3_exit_1, [LocationName.valley_of_bowser_3_exit_1]) - valley_of_bowser_4_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_tile, None) - valley_of_bowser_4_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_region, None) - valley_of_bowser_4_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_exit_1, + valley_of_bowser_4_tile = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_tile, None) + valley_of_bowser_4_region = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_region, None) + valley_of_bowser_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_exit_1, [LocationName.valley_of_bowser_4_exit_1]) - valley_of_bowser_4_exit_2 = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_exit_2, + valley_of_bowser_4_exit_2 = create_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_exit_2, [LocationName.valley_of_bowser_4_exit_2]) - valley_ghost_house_tile = create_region(world, player, active_locations, LocationName.valley_ghost_house_tile, None) - valley_ghost_house_region = create_region(world, player, active_locations, LocationName.valley_ghost_house_region, None) - valley_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.valley_ghost_house_exit_1, + valley_ghost_house_tile = create_region(multiworld, player, active_locations, LocationName.valley_ghost_house_tile, None) + valley_ghost_house_region = create_region(multiworld, player, active_locations, LocationName.valley_ghost_house_region, None) + valley_ghost_house_exit_1 = create_region(multiworld, player, active_locations, LocationName.valley_ghost_house_exit_1, [LocationName.valley_ghost_house_exit_1]) - valley_ghost_house_exit_2 = create_region(world, player, active_locations, LocationName.valley_ghost_house_exit_2, + valley_ghost_house_exit_2 = create_region(multiworld, player, active_locations, LocationName.valley_ghost_house_exit_2, [LocationName.valley_ghost_house_exit_2]) - valley_fortress_tile = create_region(world, player, active_locations, LocationName.valley_fortress_tile, None) - valley_fortress_region = create_region(world, player, active_locations, LocationName.valley_fortress_region, None) - valley_fortress = create_region(world, player, active_locations, LocationName.valley_fortress, + valley_fortress_tile = create_region(multiworld, player, active_locations, LocationName.valley_fortress_tile, None) + valley_fortress_region = create_region(multiworld, player, active_locations, LocationName.valley_fortress_region, None) + valley_fortress = create_region(multiworld, player, active_locations, LocationName.valley_fortress, [LocationName.valley_fortress, LocationName.valley_reznor]) - valley_castle_tile = create_region(world, player, active_locations, LocationName.valley_castle_tile, None) - valley_castle_region = create_region(world, player, active_locations, LocationName.valley_castle_region, None) - valley_castle = create_region(world, player, active_locations, LocationName.valley_castle, + valley_castle_tile = create_region(multiworld, player, active_locations, LocationName.valley_castle_tile, None) + valley_castle_region = create_region(multiworld, player, active_locations, LocationName.valley_castle_region, None) + valley_castle = create_region(multiworld, player, active_locations, LocationName.valley_castle, [LocationName.valley_castle, LocationName.valley_koopaling]) - front_door_tile = create_region(world, player, active_locations, LocationName.front_door_tile, None) - front_door_region = create_region(world, player, active_locations, LocationName.front_door, None) - back_door_tile = create_region(world, player, active_locations, LocationName.back_door_tile, None) - back_door_region = create_region(world, player, active_locations, LocationName.back_door, None) + front_door_tile = create_region(multiworld, player, active_locations, LocationName.front_door_tile, None) + front_door_region = create_region(multiworld, player, active_locations, LocationName.front_door, None) + back_door_tile = create_region(multiworld, player, active_locations, LocationName.back_door_tile, None) + back_door_region = create_region(multiworld, player, active_locations, LocationName.back_door, None) bowser_region_locations = [] - if world.goal[player] == "bowser": + if world.options.goal == "bowser": bowser_region_locations += [LocationName.bowser] - bowser_region = create_region(world, player, active_locations, LocationName.bowser_region, bowser_region_locations) + bowser_region = create_region(multiworld, player, active_locations, LocationName.bowser_region, bowser_region_locations) - donut_plains_star_road = create_region(world, player, active_locations, LocationName.donut_plains_star_road, None) - vanilla_dome_star_road = create_region(world, player, active_locations, LocationName.vanilla_dome_star_road, None) - twin_bridges_star_road = create_region(world, player, active_locations, LocationName.twin_bridges_star_road, None) - forest_star_road = create_region(world, player, active_locations, LocationName.forest_star_road, None) - valley_star_road = create_region(world, player, active_locations, LocationName.valley_star_road, None) - star_road_donut = create_region(world, player, active_locations, LocationName.star_road_donut, None) - star_road_vanilla = create_region(world, player, active_locations, LocationName.star_road_vanilla, None) - star_road_twin_bridges = create_region(world, player, active_locations, LocationName.star_road_twin_bridges, None) - star_road_forest = create_region(world, player, active_locations, LocationName.star_road_forest, None) - star_road_valley = create_region(world, player, active_locations, LocationName.star_road_valley, None) - star_road_special = create_region(world, player, active_locations, LocationName.star_road_special, None) - special_star_road = create_region(world, player, active_locations, LocationName.special_star_road, None) + donut_plains_star_road = create_region(multiworld, player, active_locations, LocationName.donut_plains_star_road, None) + vanilla_dome_star_road = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_star_road, None) + twin_bridges_star_road = create_region(multiworld, player, active_locations, LocationName.twin_bridges_star_road, None) + forest_star_road = create_region(multiworld, player, active_locations, LocationName.forest_star_road, None) + valley_star_road = create_region(multiworld, player, active_locations, LocationName.valley_star_road, None) + star_road_donut = create_region(multiworld, player, active_locations, LocationName.star_road_donut, None) + star_road_vanilla = create_region(multiworld, player, active_locations, LocationName.star_road_vanilla, None) + star_road_twin_bridges = create_region(multiworld, player, active_locations, LocationName.star_road_twin_bridges, None) + star_road_forest = create_region(multiworld, player, active_locations, LocationName.star_road_forest, None) + star_road_valley = create_region(multiworld, player, active_locations, LocationName.star_road_valley, None) + star_road_special = create_region(multiworld, player, active_locations, LocationName.star_road_special, None) + special_star_road = create_region(multiworld, player, active_locations, LocationName.special_star_road, None) - star_road_1_tile = create_region(world, player, active_locations, LocationName.star_road_1_tile, None) - star_road_1_region = create_region(world, player, active_locations, LocationName.star_road_1_region, None) - star_road_1_exit_1 = create_region(world, player, active_locations, LocationName.star_road_1_exit_1, + star_road_1_tile = create_region(multiworld, player, active_locations, LocationName.star_road_1_tile, None) + star_road_1_region = create_region(multiworld, player, active_locations, LocationName.star_road_1_region, None) + star_road_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.star_road_1_exit_1, [LocationName.star_road_1_exit_1]) - star_road_1_exit_2 = create_region(world, player, active_locations, LocationName.star_road_1_exit_2, + star_road_1_exit_2 = create_region(multiworld, player, active_locations, LocationName.star_road_1_exit_2, [LocationName.star_road_1_exit_2]) - star_road_2_tile = create_region(world, player, active_locations, LocationName.star_road_2_tile, None) - star_road_2_region = create_region(world, player, active_locations, LocationName.star_road_2_region, None) - star_road_2_exit_1 = create_region(world, player, active_locations, LocationName.star_road_2_exit_1, + star_road_2_tile = create_region(multiworld, player, active_locations, LocationName.star_road_2_tile, None) + star_road_2_region = create_region(multiworld, player, active_locations, LocationName.star_road_2_region, None) + star_road_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.star_road_2_exit_1, [LocationName.star_road_2_exit_1]) - star_road_2_exit_2 = create_region(world, player, active_locations, LocationName.star_road_2_exit_2, + star_road_2_exit_2 = create_region(multiworld, player, active_locations, LocationName.star_road_2_exit_2, [LocationName.star_road_2_exit_2]) - star_road_3_tile = create_region(world, player, active_locations, LocationName.star_road_3_tile, None) - star_road_3_region = create_region(world, player, active_locations, LocationName.star_road_3_region, None) - star_road_3_exit_1 = create_region(world, player, active_locations, LocationName.star_road_3_exit_1, + star_road_3_tile = create_region(multiworld, player, active_locations, LocationName.star_road_3_tile, None) + star_road_3_region = create_region(multiworld, player, active_locations, LocationName.star_road_3_region, None) + star_road_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.star_road_3_exit_1, [LocationName.star_road_3_exit_1]) - star_road_3_exit_2 = create_region(world, player, active_locations, LocationName.star_road_3_exit_2, + star_road_3_exit_2 = create_region(multiworld, player, active_locations, LocationName.star_road_3_exit_2, [LocationName.star_road_3_exit_2]) - star_road_4_tile = create_region(world, player, active_locations, LocationName.star_road_4_tile, None) - star_road_4_region = create_region(world, player, active_locations, LocationName.star_road_4_region, None) - star_road_4_exit_1 = create_region(world, player, active_locations, LocationName.star_road_4_exit_1, + star_road_4_tile = create_region(multiworld, player, active_locations, LocationName.star_road_4_tile, None) + star_road_4_region = create_region(multiworld, player, active_locations, LocationName.star_road_4_region, None) + star_road_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.star_road_4_exit_1, [LocationName.star_road_4_exit_1]) - star_road_4_exit_2 = create_region(world, player, active_locations, LocationName.star_road_4_exit_2, + star_road_4_exit_2 = create_region(multiworld, player, active_locations, LocationName.star_road_4_exit_2, [LocationName.star_road_4_exit_2]) - star_road_5_tile = create_region(world, player, active_locations, LocationName.star_road_5_tile, None) - star_road_5_region = create_region(world, player, active_locations, LocationName.star_road_5_region, None) - star_road_5_exit_1 = create_region(world, player, active_locations, LocationName.star_road_5_exit_1, + star_road_5_tile = create_region(multiworld, player, active_locations, LocationName.star_road_5_tile, None) + star_road_5_region = create_region(multiworld, player, active_locations, LocationName.star_road_5_region, None) + star_road_5_exit_1 = create_region(multiworld, player, active_locations, LocationName.star_road_5_exit_1, [LocationName.star_road_5_exit_1]) - star_road_5_exit_2 = create_region(world, player, active_locations, LocationName.star_road_5_exit_2, + star_road_5_exit_2 = create_region(multiworld, player, active_locations, LocationName.star_road_5_exit_2, [LocationName.star_road_5_exit_2]) - special_zone_1_tile = create_region(world, player, active_locations, LocationName.special_zone_1_tile, None) - special_zone_1_region = create_region(world, player, active_locations, LocationName.special_zone_1_region, None) - special_zone_1_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_1_exit_1, + special_zone_1_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_1_tile, None) + special_zone_1_region = create_region(multiworld, player, active_locations, LocationName.special_zone_1_region, None) + special_zone_1_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_1_exit_1, [LocationName.special_zone_1_exit_1]) - special_zone_2_tile = create_region(world, player, active_locations, LocationName.special_zone_2_tile, None) - special_zone_2_region = create_region(world, player, active_locations, LocationName.special_zone_2_region, None) - special_zone_2_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_2_exit_1, + special_zone_2_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_2_tile, None) + special_zone_2_region = create_region(multiworld, player, active_locations, LocationName.special_zone_2_region, None) + special_zone_2_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_2_exit_1, [LocationName.special_zone_2_exit_1]) - special_zone_3_tile = create_region(world, player, active_locations, LocationName.special_zone_3_tile, None) - special_zone_3_region = create_region(world, player, active_locations, LocationName.special_zone_3_region, None) - special_zone_3_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_3_exit_1, + special_zone_3_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_3_tile, None) + special_zone_3_region = create_region(multiworld, player, active_locations, LocationName.special_zone_3_region, None) + special_zone_3_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_3_exit_1, [LocationName.special_zone_3_exit_1]) - special_zone_4_tile = create_region(world, player, active_locations, LocationName.special_zone_4_tile, None) - special_zone_4_region = create_region(world, player, active_locations, LocationName.special_zone_4_region, None) - special_zone_4_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_4_exit_1, + special_zone_4_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_4_tile, None) + special_zone_4_region = create_region(multiworld, player, active_locations, LocationName.special_zone_4_region, None) + special_zone_4_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_4_exit_1, [LocationName.special_zone_4_exit_1]) - special_zone_5_tile = create_region(world, player, active_locations, LocationName.special_zone_5_tile, None) - special_zone_5_region = create_region(world, player, active_locations, LocationName.special_zone_5_region, None) - special_zone_5_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_5_exit_1, + special_zone_5_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_5_tile, None) + special_zone_5_region = create_region(multiworld, player, active_locations, LocationName.special_zone_5_region, None) + special_zone_5_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_5_exit_1, [LocationName.special_zone_5_exit_1]) - special_zone_6_tile = create_region(world, player, active_locations, LocationName.special_zone_6_tile, None) - special_zone_6_region = create_region(world, player, active_locations, LocationName.special_zone_6_region, None) - special_zone_6_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_6_exit_1, + special_zone_6_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_6_tile, None) + special_zone_6_region = create_region(multiworld, player, active_locations, LocationName.special_zone_6_region, None) + special_zone_6_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_6_exit_1, [LocationName.special_zone_6_exit_1]) - special_zone_7_tile = create_region(world, player, active_locations, LocationName.special_zone_7_tile, None) - special_zone_7_region = create_region(world, player, active_locations, LocationName.special_zone_7_region, None) - special_zone_7_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_7_exit_1, + special_zone_7_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_7_tile, None) + special_zone_7_region = create_region(multiworld, player, active_locations, LocationName.special_zone_7_region, None) + special_zone_7_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_7_exit_1, [LocationName.special_zone_7_exit_1]) - special_zone_8_tile = create_region(world, player, active_locations, LocationName.special_zone_8_tile, None) - special_zone_8_region = create_region(world, player, active_locations, LocationName.special_zone_8_region, None) - special_zone_8_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_8_exit_1, + special_zone_8_tile = create_region(multiworld, player, active_locations, LocationName.special_zone_8_tile, None) + special_zone_8_region = create_region(multiworld, player, active_locations, LocationName.special_zone_8_region, None) + special_zone_8_exit_1 = create_region(multiworld, player, active_locations, LocationName.special_zone_8_exit_1, [LocationName.special_zone_8_exit_1]) - special_complete = create_region(world, player, active_locations, LocationName.special_complete, None) + special_complete = create_region(multiworld, player, active_locations, LocationName.special_complete, None) # Set up the regions correctly. - world.regions += [ + multiworld.regions += [ menu_region, yoshis_island_region, yoshis_house_tile, @@ -725,323 +728,1327 @@ def create_regions(world, player: int, active_locations): ] - if world.dragon_coin_checks[player]: - add_location_to_region(world, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_dragon, + if world.options.dragon_coin_checks: + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_dragon, lambda state: (state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.progressive_powerup, player, 1))) - add_location_to_region(world, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_dragon, lambda state: (state.has(ItemName.yoshi_activate, player) or state.has(ItemName.mario_climb, player))) - add_location_to_region(world, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_dragon, lambda state: state.has(ItemName.p_switch, player)) - add_location_to_region(world, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_dragon, lambda state: (state.has(ItemName.yoshi_activate, player) or state.has(ItemName.mario_swim, player) or (state.has(ItemName.mario_carry, player) and state.has(ItemName.p_switch, player)))) - add_location_to_region(world, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_dragon, lambda state: (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player) or (state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.mario_run, player)))) - add_location_to_region(world, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_dragon) - add_location_to_region(world, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_dragon, lambda state: ((state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player) or (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) - add_location_to_region(world, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_dragon) - add_location_to_region(world, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_dragon, lambda state: state.has(ItemName.mario_swim, player)) - add_location_to_region(world, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_dragon, lambda state: (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player))) - add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_dragon, lambda state: (state.has(ItemName.mario_carry, player) and state.has(ItemName.mario_run, player) and (state.has(ItemName.super_star_active, player) or state.has(ItemName.progressive_powerup, player, 1)))) - add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_dragon, lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.p_switch, player) and (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player)))) - add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_dragon) - add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_dragon) - add_location_to_region(world, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_dragon, lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.mario_carry, player))) - add_location_to_region(world, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_dragon, lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) - add_location_to_region(world, player, active_locations, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_dragon, lambda state: state.has(ItemName.mario_swim, player)) - add_location_to_region(world, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_dragon, lambda state: state.has(ItemName.mario_climb, player)) - add_location_to_region(world, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_dragon) - add_location_to_region(world, player, active_locations, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_dragon, lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) - add_location_to_region(world, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_dragon, lambda state: (state.has(ItemName.yoshi_activate, player) or state.has(ItemName.mario_climb, player))) - add_location_to_region(world, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_dragon, lambda state: (state.has(ItemName.yoshi_activate, player) or state.has(ItemName.mario_climb, player))) - add_location_to_region(world, player, active_locations, LocationName.soda_lake_region, LocationName.soda_lake_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.soda_lake_region, LocationName.soda_lake_dragon, lambda state: state.has(ItemName.mario_swim, player)) - add_location_to_region(world, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_dragon, lambda state: state.has(ItemName.mario_swim, player)) - add_location_to_region(world, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_dragon, lambda state: (state.has(ItemName.yoshi_activate, player) or state.has(ItemName.mario_carry, player))) - add_location_to_region(world, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_dragon, lambda state: (state.has(ItemName.yoshi_activate, player) or state.has(ItemName.mario_carry, player) or state.has(ItemName.p_switch, player) or state.has(ItemName.progressive_powerup, player, 2))) - add_location_to_region(world, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_dragon, lambda state: state.has(ItemName.p_switch, player)) - add_location_to_region(world, player, active_locations, LocationName.forest_secret_region, LocationName.forest_secret_dragon) - add_location_to_region(world, player, active_locations, LocationName.forest_castle_region, LocationName.forest_castle_dragon) - add_location_to_region(world, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_dragon, - lambda state: state.has(ItemName.mario_swim, player)) - add_location_to_region(world, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.forest_secret_region, LocationName.forest_secret_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_castle_region, LocationName.forest_castle_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_dragon, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_dragon, lambda state: (state.has(ItemName.blue_switch_palace, player) and (state.has(ItemName.p_switch, player) or state.has(ItemName.green_switch_palace, player) or (state.has(ItemName.yellow_switch_palace, player) or state.has(ItemName.red_switch_palace, player))))) - add_location_to_region(world, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_dragon) - add_location_to_region(world, player, active_locations, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_dragon, lambda state: (state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) - add_location_to_region(world, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_dragon, - lambda state: (state.has(ItemName.mario_swim, player) or - (state.has(ItemName.mario_carry, player) and state.has(ItemName.p_switch, player)))) - add_location_to_region(world, player, active_locations, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_dragon, + lambda state: (state.has(ItemName.mario_carry, player) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship_dragon, lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.super_star_active, player) and state.has(ItemName.progressive_powerup, player, 3))) - add_location_to_region(world, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_dragon) - add_location_to_region(world, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_dragon, lambda state: state.has(ItemName.yoshi_activate, player)) - add_location_to_region(world, player, active_locations, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_dragon) - add_location_to_region(world, player, active_locations, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_dragon, lambda state: state.has(ItemName.p_switch, player)) - add_location_to_region(world, player, active_locations, LocationName.valley_castle_region, LocationName.valley_castle_dragon) - add_location_to_region(world, player, active_locations, LocationName.star_road_1_region, LocationName.star_road_1_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.valley_castle_region, LocationName.valley_castle_dragon) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_1_region, LocationName.star_road_1_dragon, lambda state: (state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.progressive_powerup, player, 1))) - add_location_to_region(world, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_dragon, lambda state: state.has(ItemName.mario_climb, player)) - add_location_to_region(world, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_dragon, lambda state: state.has(ItemName.p_balloon, player)) - add_location_to_region(world, player, active_locations, LocationName.special_zone_3_region, LocationName.special_zone_3_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_3_region, LocationName.special_zone_3_dragon, lambda state: state.has(ItemName.yoshi_activate, player)) - add_location_to_region(world, player, active_locations, LocationName.special_zone_4_region, LocationName.special_zone_4_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_4_region, LocationName.special_zone_4_dragon, lambda state: state.has(ItemName.progressive_powerup, player, 1)) - add_location_to_region(world, player, active_locations, LocationName.special_zone_5_region, LocationName.special_zone_5_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_5_region, LocationName.special_zone_5_dragon, lambda state: state.has(ItemName.progressive_powerup, player, 1)) - add_location_to_region(world, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_dragon, lambda state: state.has(ItemName.mario_swim, player)) - add_location_to_region(world, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_dragon, - lambda state: state.has(ItemName.progressive_powerup, player, 1)) - add_location_to_region(world, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_dragon, + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_dragon, lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_dragon, + lambda state: ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player)) or + state.has(ItemName.progressive_powerup, player, 3) or + state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_carry, player))) + if world.options.moon_checks: + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_moon, + lambda state: ((state.has(ItemName.mario_run, player) and + state.has(ItemName.progressive_powerup, player, 3)) or + state.has(ItemName.yoshi_activate, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_moon, + lambda state: (state.has(ItemName.mario_run, player) and + state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_moon, + lambda state: (state.has(ItemName.mario_run, player) and + state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_moon, + lambda state: (state.has(ItemName.mario_run, player) and + (state.has(ItemName.progressive_powerup, player, 3) or + state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_moon, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_moon, + lambda state: ((state.has(ItemName.mario_run, player) and + state.has(ItemName.progressive_powerup, player, 3)) or + state.has(ItemName.yoshi_activate, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_moon) + if world.options.hidden_1up_checks: + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_hidden_1up, + lambda state: (state.has(ItemName.yoshi_activate, player) or + (state.has(ItemName.mario_run, player, player) and + state.has(ItemName.progressive_powerup, player, 3)))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_hidden_1up) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_hidden_1up, + lambda state: (state.has(ItemName.mario_run, player) and + state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_hidden_1up) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_hidden_1up) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_hidden_1up) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_fortress_region, LocationName.vanilla_fortress_hidden_1up, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_hidden_1up, + lambda state: (state.has(ItemName.mario_swim, player) or + state.has(ItemName.yoshi_activate, player) or + (state.has(ItemName.mario_run, player, player) and + state.has(ItemName.progressive_powerup, player, 3)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_hidden_1up, + lambda state: (state.has(ItemName.mario_carry, player) or + state.has(ItemName.yoshi_activate, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_hidden_1up) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_castle_region, LocationName.chocolate_castle_hidden_1up, + lambda state: (state.has(ItemName.progressive_powerup, player, 1))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_hidden_1up) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_castle_region, LocationName.valley_castle_hidden_1up) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_hidden_1up, + lambda state: state.has(ItemName.mario_climb, player)) + + if world.options.bonus_block_checks: + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_bonus_block) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_bonus_block) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_bonus_block) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_bonus_block) + + if world.options.blocksanity: + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_green_block_1, + lambda state:( ((state.has(ItemName.green_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_gray_pow_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_coin_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_coin_block_5) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_coin_block_6) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_powerup_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_directional_coin_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_life_block_1, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_life_block_2, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_life_block_3, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_life_block_4, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_vine_block_1, + lambda state: (state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_bonus_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_invis_life_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_coin_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_coin_block_5) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_flying_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_yellow_block_2, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_vine_block_1, + lambda state:( ((state.has(ItemName.mario_climb, player) and state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_coin_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_coin_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_coin_block_3, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_powerup_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_powerup_block_3, + lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.p_balloon, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_life_block_1, + lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.p_balloon, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_powerup_block_4, + lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.p_balloon, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_powerup_block_5, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_key_block_1, + lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_fortress_region, LocationName.vanilla_fortress_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_fortress_region, LocationName.vanilla_fortress_powerup_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_fortress_region, LocationName.vanilla_fortress_yellow_block_1, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_swim, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_multi_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_multi_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_life_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_bonus_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_powerup_block_1, + lambda state: state.has(ItemName.mario_carry, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_yoshi_block_1, + lambda state: state.has(ItemName.mario_carry, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.twin_bridges_castle_region, LocationName.twin_bridges_castle_powerup_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_wings_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_powerup_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_5) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_6) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_7) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_8) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_9) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_life_block_1, + lambda state:( (state.has(ItemName.mario_climb, player)) or (state.has(ItemName.mario_swim, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_yoshi_block_1, + lambda state: state.has(ItemName.red_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_10) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_11) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_12) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_13) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_14) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_15) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_16) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_17) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_18) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_19) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_20) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_21) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_22) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_23) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_24) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_25) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_26) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_27) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_28) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_29) + add_location_to_region(multiworld, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_coin_block_30) + add_location_to_region(multiworld, player, active_locations, LocationName.soda_lake_region, LocationName.soda_lake_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_house_region, LocationName.donut_secret_house_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_house_region, LocationName.donut_secret_house_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_house_region, LocationName.donut_secret_house_life_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_house_region, LocationName.donut_secret_house_vine_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_house_region, LocationName.donut_secret_house_directional_coin_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_1, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_2, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_3, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_4, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_5, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_6, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_7, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_8, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_9, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_10, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_11, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_12, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_13, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_14, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_15, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_green_block_16, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_yellow_block_2, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_yellow_block_3, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship_star_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_castle_region, LocationName.chocolate_castle_yellow_block_1, + lambda state: (state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.yellow_switch_palace, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_castle_region, LocationName.chocolate_castle_yellow_block_2, + lambda state: (state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.yellow_switch_palace, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_castle_region, LocationName.chocolate_castle_green_block_1, + lambda state: (state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.yellow_switch_palace, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_life_block_1, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.progressive_powerup, player, 3)))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_yellow_block_1, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_yellow_block_1, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.blue_switch_palace, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_blue_pow_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_1, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_2, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_3, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_4, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_5, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_6, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_7, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_8, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_fortress_region, LocationName.forest_fortress_life_block_9, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_castle_region, LocationName.forest_castle_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_ghost_house_region, LocationName.chocolate_ghost_house_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_ghost_house_region, LocationName.chocolate_ghost_house_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_ghost_house_region, LocationName.chocolate_ghost_house_life_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_flying_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_flying_block_2, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_yoshi_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_green_block_1, + lambda state:( ((state.has(ItemName.green_switch_palace, player) and state.has(ItemName.blue_switch_palace, player) and state.has(ItemName.p_switch, player))) or ((state.has(ItemName.green_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.blue_switch_palace, player) and state.has(ItemName.p_switch, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_life_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_powerup_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_bonus_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_life_block_1, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_life_block_2, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_life_block_3, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_invis_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_multi_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_blue_pow_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_yellow_block_2, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_green_block_2, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_green_block_3, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_green_block_4, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_green_block_5, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_green_block_6, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle_coin_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle_coin_block_2, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle_powerup_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle_coin_block_3, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle_coin_block_4, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle_flying_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_star_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_2, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_3, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_4, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_5, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_6, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_7, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_8, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_9, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_10, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_11, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_yellow_block_12, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_bonus_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_flying_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_life_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_flying_block_1, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_flying_block_2, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_flying_block_3, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_flying_block_4, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_flying_block_5, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_flying_block_6, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_coin_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_yoshi_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_coin_block_5) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_yellow_block_2, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_blue_pow_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_vine_block_2, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_coin_block_2, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_coin_block_3, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_powerup_block_2, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_flying_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_flying_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_flying_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_invis_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_powerup_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_yoshi_block_1, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_powerup_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_pswitch_coin_block_1, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_pswitch_coin_block_2, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_pswitch_coin_block_3, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_pswitch_coin_block_4, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_pswitch_coin_block_5, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_pswitch_coin_block_6, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.p_switch, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_directional_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_star_block_1, + lambda state:( (state.has(ItemName.mario_climb, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_star_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_yoshi_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_life_block_1, + lambda state: (state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.mario_climb, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_powerup_block_2, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_climb, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_castle_region, LocationName.valley_castle_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_castle_region, LocationName.valley_castle_yellow_block_2, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_castle_region, LocationName.valley_castle_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_fortress_region, LocationName.valley_fortress_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_fortress_region, LocationName.valley_fortress_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_pswitch_coin_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_multi_coin_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_directional_coin_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_yellow_block_1, + lambda state: state.has(ItemName.yellow_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_wings_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_invis_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_invis_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_invis_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_yellow_block_1, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_yellow_block_2, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_yellow_block_3, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_yellow_block_4, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_secret_region, LocationName.chocolate_secret_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_secret_region, LocationName.chocolate_secret_powerup_block_2, + lambda state: state.has(ItemName.mario_run, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_coin_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_coin_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_coin_block_3, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_vine_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_invis_life_block_1, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_coin_block_4, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_coin_block_5, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_powerup_block_2, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_powerup_block_3, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_powerup_block_4, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_powerup_block_5, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_multi_coin_block_1, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_multi_coin_block_2, + lambda state:( ((state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_climb, player))) or ((state.has(ItemName.mario_swim, player) and state.has(ItemName.yoshi_activate, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_life_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_5) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_6) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_7) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_coin_block_8, + lambda state: state.has(ItemName.mario_carry, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_flying_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_life_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_powerup_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_vine_block_1, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.red_switch_palace, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_star_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_powerup_block_4, + lambda state:( ((state.has(ItemName.mario_run, player) and state.has(ItemName.super_star_active, player))) or ((state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 1))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_coin_block_2, + lambda state:( ((state.has(ItemName.mario_run, player) and state.has(ItemName.super_star_active, player))) or ((state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 1))))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle_life_block_1, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.progressive_powerup, player, 1)))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle_life_block_2, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.progressive_powerup, player, 1)))) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle_life_block_3, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle_green_block_1, + lambda state: state.has(ItemName.green_switch_palace, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_flying_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_life_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_key_block_1, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_life_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_5) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_6) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_7) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_8) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_9) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_10) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_green_block_1, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_invis_coin_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_invis_coin_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_invis_life_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_invis_coin_block_3, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_yellow_block_1, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.mario_swim, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_secret_region, LocationName.forest_secret_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_secret_region, LocationName.forest_secret_powerup_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_secret_region, LocationName.forest_secret_life_block_1, + lambda state:( (state.has(ItemName.blue_switch_palace, player)) or (state.has(ItemName.mario_carry, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_multi_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_2, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_multi_coin_block_2, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_3, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_4, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_5, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_6, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_7, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_8, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_9, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_10, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_11, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_12, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_13, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_14, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_15, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_16, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_17, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_18, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_19, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_20, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_21, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_22, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_23, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_coin_block_24, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_yoshi_block_1, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_5) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_blue_pow_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_star_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_6, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_7, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_8, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_9, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_10, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_11, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_12, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_13, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_14, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_15, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_16, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_17, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_18, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_multi_coin_block_1, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_19, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_20, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_21, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_22, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_coin_block_23, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_powerup_block_2, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_flying_block_1, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.progressive_powerup, player, 3)) or (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_powerup_block_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_yoshi_block_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_coin_block_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_powerup_block_2, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_coin_block_2, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_powerup_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_yoshi_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_life_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_multi_coin_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_3, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_4, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_5, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_6, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_7, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_8, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_9, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_10, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_11, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_12, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_13, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_14, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_15, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_16, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_17, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_18, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_19, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_20, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_21, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_22, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_23, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_24, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_25, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_26, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_27, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_28, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_powerup_block_2, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_29, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_30, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_31, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_32, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_coin_block_33, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_5_region, LocationName.special_zone_5_yoshi_block_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_vine_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_vine_block_2) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_vine_block_3) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_vine_block_4) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_life_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_vine_block_5, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_blue_pow_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_vine_block_6, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_powerup_block_1, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_1, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_2, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_3, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_4, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_5, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_6, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_7, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_8, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_9, + lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_10, + lambda state:( ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) or ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_11, + lambda state:( ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) or ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_12, + lambda state:( ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) or ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_pswitch_coin_block_13, + lambda state:( ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) or ((state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_coin_block_1, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_coin_block_2, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_powerup_block_2, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_coin_block_3, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_coin_block_4, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_powerup_block_3, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_multi_coin_block_1, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_coin_block_5, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_coin_block_6, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_3_region, LocationName.special_zone_3_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_3_region, LocationName.special_zone_3_yoshi_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_3_region, LocationName.special_zone_3_wings_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_4_region, LocationName.special_zone_4_powerup_block_1, + lambda state: state.has(ItemName.progressive_powerup, player, 2)) + add_location_to_region(multiworld, player, active_locations, LocationName.special_zone_4_region, LocationName.special_zone_4_star_block_1, + lambda state:( ((state.has(ItemName.progressive_powerup, player, 2) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.progressive_powerup, player, 2) and state.has(ItemName.p_switch, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_2_region, LocationName.star_road_2_star_block_1, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_3_region, LocationName.star_road_3_key_block_1, + lambda state:( (state.has(ItemName.mario_carry, player)) or (state.has(ItemName.progressive_powerup, player, 2)))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_powerup_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_1, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_2, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_3, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_4, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_5, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_6, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_7, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_key_block_1, + lambda state:( ((state.has(ItemName.mario_climb, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.green_switch_palace, player) and state.has(ItemName.red_switch_palace, player) and state.has(ItemName.mario_climb, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_directional_coin_block_1) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_life_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_vine_block_1, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_1, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_climb, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_2, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_3, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_4, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_5, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_6, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_7, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_8, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_9, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_10, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_11, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_12, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_13, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_14, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_15, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_16, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_17, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_18, + lambda state: (state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_19, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.green_switch_palace, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_climb, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_yellow_block_20, + lambda state:( ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.yellow_switch_palace, player) and state.has(ItemName.green_switch_palace, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_climb, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_1, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_2, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_3, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_4, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_5, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_6, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_7, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_8, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_9, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_10, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_11, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_12, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_13, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_14, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_15, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_16, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_17, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_18, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_19, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_20, + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) + +def connect_regions(world: World, level_to_tile_dict): + multiworld: MultiWorld = world.multiworld + player: int = world.player -def connect_regions(world, player, level_to_tile_dict): names: typing.Dict[str, int] = {} - connect(world, player, names, "Menu", LocationName.yoshis_island_region) - connect(world, player, names, LocationName.yoshis_island_region, LocationName.yoshis_house_tile) - connect(world, player, names, LocationName.yoshis_house_tile, LocationName.donut_plains_top_secret) - connect(world, player, names, LocationName.yoshis_island_region, LocationName.yoshis_island_1_tile) - connect(world, player, names, LocationName.yoshis_island_region, LocationName.yoshis_island_2_tile) + connect(world, "Menu", LocationName.yoshis_island_region) + connect(world, LocationName.yoshis_island_region, LocationName.yoshis_house_tile) + connect(world, LocationName.yoshis_island_region, LocationName.yoshis_island_1_tile) + connect(world, LocationName.yoshis_island_region, LocationName.yoshis_island_2_tile) # Connect regions within levels using rules - connect(world, player, names, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_exit_1) - connect(world, player, names, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_exit_1) - connect(world, player, names, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_exit_1) - connect(world, player, names, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_exit_1) - connect(world, player, names, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle, + connect(world, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_exit_1) + connect(world, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_exit_1) + connect(world, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_exit_1) + connect(world, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_exit_1) + connect(world, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle, lambda state: (state.has(ItemName.mario_climb, player))) - connect(world, player, names, LocationName.donut_plains_1_region, LocationName.donut_plains_1_exit_1) - connect(world, player, names, LocationName.donut_plains_1_region, LocationName.donut_plains_1_exit_2, + connect(world, LocationName.donut_plains_1_region, LocationName.donut_plains_1_exit_1) + connect(world, LocationName.donut_plains_1_region, LocationName.donut_plains_1_exit_2, lambda state: (state.has(ItemName.mario_carry, player) and (state.has(ItemName.yoshi_activate, player) or state.has(ItemName.green_switch_palace, player) or (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) - connect(world, player, names, LocationName.donut_plains_2_region, LocationName.donut_plains_2_exit_1) - connect(world, player, names, LocationName.donut_plains_2_region, LocationName.donut_plains_2_exit_2, + connect(world, LocationName.donut_plains_2_region, LocationName.donut_plains_2_exit_1) + connect(world, LocationName.donut_plains_2_region, LocationName.donut_plains_2_exit_2, lambda state: (state.has(ItemName.mario_carry, player) and (state.has(ItemName.yoshi_activate, player) or (state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.mario_climb, player) and state.has(ItemName.progressive_powerup, player, 1))))) - connect(world, player, names, LocationName.donut_secret_1_region, LocationName.donut_secret_1_exit_1, + connect(world, LocationName.donut_secret_1_region, LocationName.donut_secret_1_exit_1, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.donut_secret_1_region, LocationName.donut_secret_1_exit_2, + connect(world, LocationName.donut_secret_1_region, LocationName.donut_secret_1_exit_2, lambda state: (state.has(ItemName.mario_carry, player) and state.has(ItemName.mario_swim, player) and state.has(ItemName.p_switch, player))) - connect(world, player, names, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_exit_1, + connect(world, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_exit_1, lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) - connect(world, player, names, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_exit_2, + connect(world, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_exit_2, lambda state: (state.has(ItemName.mario_climb, player) or (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3)))) - connect(world, player, names, LocationName.donut_secret_house_region, LocationName.donut_secret_house_exit_1, + connect(world, LocationName.donut_secret_house_region, LocationName.donut_secret_house_exit_1, lambda state: state.has(ItemName.p_switch, player)) - connect(world, player, names, LocationName.donut_secret_house_region, LocationName.donut_secret_house_exit_2, + connect(world, LocationName.donut_secret_house_region, LocationName.donut_secret_house_exit_2, lambda state: (state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player) and (state.has(ItemName.mario_climb, player) or (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) - connect(world, player, names, LocationName.donut_plains_3_region, LocationName.donut_plains_3_exit_1) - connect(world, player, names, LocationName.donut_plains_4_region, LocationName.donut_plains_4_exit_1) - connect(world, player, names, LocationName.donut_secret_2_region, LocationName.donut_secret_2_exit_1) - connect(world, player, names, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle) + connect(world, LocationName.donut_plains_3_region, LocationName.donut_plains_3_exit_1) + connect(world, LocationName.donut_plains_4_region, LocationName.donut_plains_4_exit_1) + connect(world, LocationName.donut_secret_2_region, LocationName.donut_secret_2_exit_1) + connect(world, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle) - connect(world, player, names, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_exit_1, + connect(world, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_exit_1, lambda state: (state.has(ItemName.mario_run, player) and (state.has(ItemName.super_star_active, player) or state.has(ItemName.progressive_powerup, player, 1)))) - connect(world, player, names, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_exit_2, + connect(world, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_exit_2, lambda state: (state.has(ItemName.mario_carry, player) and ((state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_climb, player)) or (state.has(ItemName.yoshi_activate, player) and state.has(ItemName.red_switch_palace, player)) or (state.has(ItemName.red_switch_palace, player) and state.has(ItemName.mario_climb, player))))) - connect(world, player, names, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_exit_1, + connect(world, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_exit_1, lambda state: (state.has(ItemName.mario_swim, player) and (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player)))) - connect(world, player, names, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_exit_2, + connect(world, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_exit_2, lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player) and (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player)))) - connect(world, player, names, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_exit_1, + connect(world, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_exit_1, lambda state: state.has(ItemName.mario_climb, player)) - connect(world, player, names, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_exit_2, + connect(world, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_exit_2, lambda state: (state.has(ItemName.mario_climb, player) and (state.has(ItemName.mario_carry, player) and state.has(ItemName.blue_switch_palace, player)))) - connect(world, player, names, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_exit_1, + connect(world, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_exit_1, lambda state: state.has(ItemName.p_switch, player)) - connect(world, player, names, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_exit_1) - connect(world, player, names, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_exit_1) - connect(world, player, names, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_exit_1) - connect(world, player, names, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_exit_1, + connect(world, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_exit_1) + connect(world, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_exit_1) + connect(world, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_exit_1) + connect(world, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_exit_1, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.vanilla_fortress_region, LocationName.vanilla_fortress, + connect(world, LocationName.vanilla_fortress_region, LocationName.vanilla_fortress, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle) + connect(world, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle) - connect(world, player, names, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_exit_1) - connect(world, player, names, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_exit_1) - connect(world, player, names, LocationName.cheese_bridge_region, LocationName.cheese_bridge_exit_1, + connect(world, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_exit_1) + connect(world, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_exit_1) + connect(world, LocationName.cheese_bridge_region, LocationName.cheese_bridge_exit_1, lambda state: state.has(ItemName.mario_climb, player)) - connect(world, player, names, LocationName.cheese_bridge_region, LocationName.cheese_bridge_exit_2, - lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) - connect(world, player, names, LocationName.soda_lake_region, LocationName.soda_lake_exit_1, + connect(world, LocationName.cheese_bridge_region, LocationName.cheese_bridge_exit_2, + lambda state: (state.has(ItemName.mario_run, player) and + (state.has(ItemName.progressive_powerup, player, 3) or + state.has(ItemName.yoshi_activate, player)))) + connect(world, LocationName.soda_lake_region, LocationName.soda_lake_exit_1, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.cookie_mountain_region, LocationName.cookie_mountain_exit_1) - connect(world, player, names, LocationName.twin_bridges_castle_region, LocationName.twin_bridges_castle, + connect(world, LocationName.cookie_mountain_region, LocationName.cookie_mountain_exit_1) + connect(world, LocationName.twin_bridges_castle_region, LocationName.twin_bridges_castle, lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.mario_climb, player))) - connect(world, player, names, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_exit_1) - connect(world, player, names, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_exit_2, + connect(world, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_exit_1) + connect(world, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_exit_2, lambda state: (state.has(ItemName.mario_carry, player) and state.has(ItemName.p_balloon, player))) - connect(world, player, names, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_exit_1, + connect(world, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_exit_1, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_exit_2, + connect(world, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_exit_2, lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_carry, player))) - connect(world, player, names, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_exit_1, + connect(world, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_exit_1, lambda state: (state.has(ItemName.mario_carry, player) or state.has(ItemName.yoshi_activate, player))) - connect(world, player, names, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_exit_2, + connect(world, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_exit_2, lambda state: (state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.progressive_powerup, player, 1))) - connect(world, player, names, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_exit_1) - connect(world, player, names, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_exit_2, + connect(world, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_exit_1) + connect(world, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_exit_2, lambda state: state.has(ItemName.mario_carry, player)) - connect(world, player, names, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_exit_1, + connect(world, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_exit_1, lambda state: state.has(ItemName.p_switch, player)) - connect(world, player, names, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_exit_2, + connect(world, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_exit_2, lambda state: state.has(ItemName.p_switch, player)) - connect(world, player, names, LocationName.forest_secret_region, LocationName.forest_secret_exit_1) - connect(world, player, names, LocationName.forest_fortress_region, LocationName.forest_fortress) - connect(world, player, names, LocationName.forest_castle_region, LocationName.forest_castle) + connect(world, LocationName.forest_secret_region, LocationName.forest_secret_exit_1) + connect(world, LocationName.forest_fortress_region, LocationName.forest_fortress) + connect(world, LocationName.forest_castle_region, LocationName.forest_castle) - connect(world, player, names, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_exit_1, + connect(world, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_exit_1, lambda state: state.has(ItemName.p_switch, player)) - connect(world, player, names, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_exit_1) - connect(world, player, names, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_exit_2, + connect(world, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_exit_1) + connect(world, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_exit_2, lambda state: state.has(ItemName.mario_carry, player)) - connect(world, player, names, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_exit_1, + connect(world, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_exit_1, lambda state: (state.has(ItemName.mario_climb, player) or (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3)))) - connect(world, player, names, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_exit_2, + connect(world, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_exit_2, lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) - connect(world, player, names, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_exit_1) - connect(world, player, names, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_exit_1) - connect(world, player, names, LocationName.chocolate_ghost_house_region, LocationName.chocolate_ghost_house_exit_1) - connect(world, player, names, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress) - connect(world, player, names, LocationName.chocolate_secret_region, LocationName.chocolate_secret_exit_1, + connect(world, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_exit_1) + connect(world, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_exit_1) + connect(world, LocationName.chocolate_ghost_house_region, LocationName.chocolate_ghost_house_exit_1) + connect(world, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress) + connect(world, LocationName.chocolate_secret_region, LocationName.chocolate_secret_exit_1, lambda state: state.has(ItemName.mario_run, player)) - connect(world, player, names, LocationName.chocolate_castle_region, LocationName.chocolate_castle, + connect(world, LocationName.chocolate_castle_region, LocationName.chocolate_castle, lambda state: (state.has(ItemName.progressive_powerup, player, 1))) - connect(world, player, names, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship, + connect(world, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_exit_1) - connect(world, player, names, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_exit_1) - connect(world, player, names, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_exit_2, + connect(world, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_exit_1) + connect(world, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_exit_1) + connect(world, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_exit_2, lambda state: state.has(ItemName.mario_carry, player)) - connect(world, player, names, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_exit_1) - connect(world, player, names, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_exit_1, + connect(world, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_exit_1) + connect(world, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_exit_1, lambda state: state.has(ItemName.mario_climb, player)) - connect(world, player, names, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_exit_2, + connect(world, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_exit_2, lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.yoshi_activate, player))) - connect(world, player, names, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_exit_1, + connect(world, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_exit_1, lambda state: state.has(ItemName.p_switch, player)) - connect(world, player, names, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_exit_2, + connect(world, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_exit_2, lambda state: (state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.mario_run, player))) - connect(world, player, names, LocationName.valley_fortress_region, LocationName.valley_fortress, + connect(world, LocationName.valley_fortress_region, LocationName.valley_fortress, lambda state: state.has(ItemName.progressive_powerup, player, 1)) - connect(world, player, names, LocationName.valley_castle_region, LocationName.valley_castle) - connect(world, player, names, LocationName.front_door, LocationName.bowser_region, + connect(world, LocationName.valley_castle_region, LocationName.valley_castle) + connect(world, LocationName.front_door, LocationName.bowser_region, lambda state: (state.has(ItemName.mario_climb, player) and state.has(ItemName.mario_run, player) and state.has(ItemName.mario_swim, player) and state.has(ItemName.progressive_powerup, player, 1) and - state.has(ItemName.koopaling, player, world.bosses_required[player].value))) - connect(world, player, names, LocationName.back_door, LocationName.bowser_region, - lambda state: state.has(ItemName.koopaling, player, world.bosses_required[player].value)) + state.has(ItemName.koopaling, player, world.options.bosses_required.value))) + connect(world, LocationName.back_door, LocationName.bowser_region, + lambda state: state.has(ItemName.koopaling, player, world.options.bosses_required.value)) - connect(world, player, names, LocationName.star_road_1_region, LocationName.star_road_1_exit_1, + connect(world, LocationName.star_road_1_region, LocationName.star_road_1_exit_1, lambda state: (state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.progressive_powerup, player, 1))) - connect(world, player, names, LocationName.star_road_1_region, LocationName.star_road_1_exit_2, + connect(world, LocationName.star_road_1_region, LocationName.star_road_1_exit_2, lambda state: (state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.progressive_powerup, player, 1))) - connect(world, player, names, LocationName.star_road_2_region, LocationName.star_road_2_exit_1, + connect(world, LocationName.star_road_2_region, LocationName.star_road_2_exit_1, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.star_road_2_region, LocationName.star_road_2_exit_2, + connect(world, LocationName.star_road_2_region, LocationName.star_road_2_exit_2, lambda state: (state.has(ItemName.mario_swim, player) and state.has(ItemName.mario_carry, player))) - connect(world, player, names, LocationName.star_road_3_region, LocationName.star_road_3_exit_1) - connect(world, player, names, LocationName.star_road_3_region, LocationName.star_road_3_exit_2, + connect(world, LocationName.star_road_3_region, LocationName.star_road_3_exit_1) + connect(world, LocationName.star_road_3_region, LocationName.star_road_3_exit_2, lambda state: state.has(ItemName.mario_carry, player)) - connect(world, player, names, LocationName.star_road_4_region, LocationName.star_road_4_exit_1) - connect(world, player, names, LocationName.star_road_4_region, LocationName.star_road_4_exit_2, + connect(world, LocationName.star_road_4_region, LocationName.star_road_4_exit_1) + connect(world, LocationName.star_road_4_region, LocationName.star_road_4_exit_2, lambda state: (state.has(ItemName.mario_carry, player) and (state.has(ItemName.yoshi_activate, player) or (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.red_switch_palace, player))))) - connect(world, player, names, LocationName.star_road_5_region, LocationName.star_road_5_exit_1, + connect(world, LocationName.star_road_5_region, LocationName.star_road_5_exit_1, lambda state: state.has(ItemName.p_switch, player)) - connect(world, player, names, LocationName.star_road_5_region, LocationName.star_road_5_exit_2, + connect(world, LocationName.star_road_5_region, LocationName.star_road_5_exit_2, lambda state: (state.has(ItemName.mario_carry, player) and state.has(ItemName.mario_climb, player) and state.has(ItemName.p_switch, player) and @@ -1050,26 +2057,29 @@ def connect_regions(world, player, level_to_tile_dict): state.has(ItemName.red_switch_palace, player) and state.has(ItemName.blue_switch_palace, player))) - connect(world, player, names, LocationName.special_zone_1_region, LocationName.special_zone_1_exit_1, + connect(world, LocationName.special_zone_1_region, LocationName.special_zone_1_exit_1, lambda state: (state.has(ItemName.mario_climb, player) and (state.has(ItemName.p_switch, player) or (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) - connect(world, player, names, LocationName.special_zone_2_region, LocationName.special_zone_2_exit_1, + connect(world, LocationName.special_zone_2_region, LocationName.special_zone_2_exit_1, lambda state: state.has(ItemName.p_balloon, player)) - connect(world, player, names, LocationName.special_zone_3_region, LocationName.special_zone_3_exit_1, + connect(world, LocationName.special_zone_3_region, LocationName.special_zone_3_exit_1, lambda state: (state.has(ItemName.mario_climb, player) or - state.has(ItemName.p_switch, player) or - (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3)))) - connect(world, player, names, LocationName.special_zone_4_region, LocationName.special_zone_4_exit_1, + state.has(ItemName.yoshi_activate, player))) + connect(world, LocationName.special_zone_4_region, LocationName.special_zone_4_exit_1, + lambda state: (state.has(ItemName.progressive_powerup, player, 2) or + state.has(ItemName.super_star_active, player))) + connect(world, LocationName.special_zone_5_region, LocationName.special_zone_5_exit_1, lambda state: state.has(ItemName.progressive_powerup, player, 1)) - connect(world, player, names, LocationName.special_zone_5_region, LocationName.special_zone_5_exit_1, - lambda state: state.has(ItemName.progressive_powerup, player, 1)) - connect(world, player, names, LocationName.special_zone_6_region, LocationName.special_zone_6_exit_1, + connect(world, LocationName.special_zone_6_region, LocationName.special_zone_6_exit_1, lambda state: state.has(ItemName.mario_swim, player)) - connect(world, player, names, LocationName.special_zone_7_region, LocationName.special_zone_7_exit_1, - lambda state: state.has(ItemName.progressive_powerup, player, 1)) - connect(world, player, names, LocationName.special_zone_8_region, LocationName.special_zone_8_exit_1, + connect(world, LocationName.special_zone_7_region, LocationName.special_zone_7_exit_1, lambda state: state.has(ItemName.progressive_powerup, player, 1)) + connect(world, LocationName.special_zone_8_region, LocationName.special_zone_8_exit_1, + lambda state: ((state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player)) or + state.has(ItemName.progressive_powerup, player, 3) or + state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_carry, player))) @@ -1085,52 +2095,52 @@ def connect_regions(world, player, level_to_tile_dict): current_tile_name = current_tile_data.levelName if ("Star Road - " not in current_tile_name) and (" - Star Road" not in current_tile_name): current_tile_name += " - Tile" - connect(world, player, names, current_tile_name, current_level_data.levelName) + connect(world, current_tile_name, current_level_data.levelName) # Connect Exit regions to next tile regions if current_tile_data.exit1Path: next_tile_id = current_tile_data.exit1Path.otherLevelID - if world.swap_donut_gh_exits[player] and current_tile_id == 0x04: + if world.options.swap_donut_gh_exits and current_tile_id == 0x04: next_tile_id = current_tile_data.exit2Path.otherLevelID next_tile_name = level_info_dict[next_tile_id].levelName if ("Star Road - " not in next_tile_name) and (" - Star Road" not in next_tile_name): next_tile_name += " - Tile" current_exit_name = (current_level_data.levelName + " - Normal Exit") - connect(world, player, names, current_exit_name, next_tile_name) + connect(world, current_exit_name, next_tile_name) if current_tile_data.exit2Path: next_tile_id = current_tile_data.exit2Path.otherLevelID - if world.swap_donut_gh_exits[player] and current_tile_id == 0x04: + if world.options.swap_donut_gh_exits and current_tile_id == 0x04: next_tile_id = current_tile_data.exit1Path.otherLevelID next_tile_name = level_info_dict[next_tile_id].levelName if ("Star Road - " not in next_tile_name) and (" - Star Road" not in next_tile_name): next_tile_name += " - Tile" current_exit_name = (current_level_data.levelName + " - Secret Exit") - connect(world, player, names, current_exit_name, next_tile_name) + connect(world, current_exit_name, next_tile_name) - connect(world, player, names, LocationName.donut_plains_star_road, LocationName.star_road_donut) - connect(world, player, names, LocationName.star_road_donut, LocationName.donut_plains_star_road) - connect(world, player, names, LocationName.star_road_donut, LocationName.star_road_1_tile) - connect(world, player, names, LocationName.vanilla_dome_star_road, LocationName.star_road_vanilla) - connect(world, player, names, LocationName.star_road_vanilla, LocationName.vanilla_dome_star_road) - connect(world, player, names, LocationName.star_road_vanilla, LocationName.star_road_2_tile) - connect(world, player, names, LocationName.twin_bridges_star_road, LocationName.star_road_twin_bridges) - connect(world, player, names, LocationName.star_road_twin_bridges, LocationName.twin_bridges_star_road) - connect(world, player, names, LocationName.star_road_twin_bridges, LocationName.star_road_3_tile) - connect(world, player, names, LocationName.forest_star_road, LocationName.star_road_forest) - connect(world, player, names, LocationName.star_road_forest, LocationName.forest_star_road) - connect(world, player, names, LocationName.star_road_forest, LocationName.star_road_4_tile) - connect(world, player, names, LocationName.valley_star_road, LocationName.star_road_valley) - connect(world, player, names, LocationName.star_road_valley, LocationName.valley_star_road) - connect(world, player, names, LocationName.star_road_valley, LocationName.star_road_5_tile) - connect(world, player, names, LocationName.star_road_special, LocationName.special_star_road) - connect(world, player, names, LocationName.special_star_road, LocationName.star_road_special) - connect(world, player, names, LocationName.special_star_road, LocationName.special_zone_1_tile) + connect(world, LocationName.donut_plains_star_road, LocationName.star_road_donut) + connect(world, LocationName.star_road_donut, LocationName.donut_plains_star_road) + connect(world, LocationName.star_road_donut, LocationName.star_road_1_tile) + connect(world, LocationName.vanilla_dome_star_road, LocationName.star_road_vanilla) + connect(world, LocationName.star_road_vanilla, LocationName.vanilla_dome_star_road) + connect(world, LocationName.star_road_vanilla, LocationName.star_road_2_tile) + connect(world, LocationName.twin_bridges_star_road, LocationName.star_road_twin_bridges) + connect(world, LocationName.star_road_twin_bridges, LocationName.twin_bridges_star_road) + connect(world, LocationName.star_road_twin_bridges, LocationName.star_road_3_tile) + connect(world, LocationName.forest_star_road, LocationName.star_road_forest) + connect(world, LocationName.star_road_forest, LocationName.forest_star_road) + connect(world, LocationName.star_road_forest, LocationName.star_road_4_tile) + connect(world, LocationName.valley_star_road, LocationName.star_road_valley) + connect(world, LocationName.star_road_valley, LocationName.valley_star_road) + connect(world, LocationName.star_road_valley, LocationName.star_road_5_tile) + connect(world, LocationName.star_road_special, LocationName.special_star_road) + connect(world, LocationName.special_star_road, LocationName.star_road_special) + connect(world, LocationName.special_star_road, LocationName.special_zone_1_tile) - connect(world, player, names, LocationName.star_road_valley, LocationName.front_door_tile) + connect(world, LocationName.star_road_valley, LocationName.front_door_tile) -def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None): - ret = Region(name, player, world) +def create_region(multiworld: MultiWorld, player: int, active_locations, name: str, locations=None): + ret = Region(name, player, multiworld) if locations: for locationName in locations: loc_id = active_locations.get(locationName, 0) @@ -1140,9 +2150,9 @@ def create_region(world: MultiWorld, player: int, active_locations, name: str, l return ret -def add_location_to_region(world: MultiWorld, player: int, active_locations, region_name: str, location_name: str, +def add_location_to_region(multiworld: MultiWorld, player: int, active_locations, region_name: str, location_name: str, rule: typing.Optional[typing.Callable] = None): - region = world.get_region(region_name, player) + region = multiworld.get_region(region_name, player) loc_id = active_locations.get(location_name, 0) if loc_id: location = SMWLocation(player, location_name, loc_id, region) @@ -1151,23 +2161,8 @@ def add_location_to_region(world: MultiWorld, player: int, active_locations, reg add_rule(location, rule) - -def connect(world: MultiWorld, player: int, used_names: typing.Dict[str, int], source: str, target: str, +def connect(world: World, source: str, target: str, rule: typing.Optional[typing.Callable] = None): - source_region = world.get_region(source, player) - target_region = world.get_region(target, 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(player, name, source_region) - - if rule: - connection.access_rule = rule - - source_region.exits.append(connection) - connection.connect(target_region) + source_region: Region = world.get_region(source) + target_region: Region = world.get_region(target) + source_region.connect(target_region, rule=rule) diff --git a/worlds/smw/Rom.py b/worlds/smw/Rom.py index 0f5ec7e4f0..66226d5036 100644 --- a/worlds/smw/Rom.py +++ b/worlds/smw/Rom.py @@ -1,6 +1,7 @@ import Utils +from worlds.AutoWorld import World from worlds.Files import APDeltaPatch -from .Aesthetics import generate_shuffled_header_data, generate_shuffled_ow_palettes +from .Aesthetics import generate_shuffled_header_data, generate_shuffled_ow_palettes, generate_curated_level_palette_data, generate_curated_map_palette_data, generate_shuffled_sfx from .Levels import level_info_dict, full_bowser_rooms, standard_bowser_rooms, submap_boss_rooms, ow_boss_rooms from .Names.TextBox import generate_goal_text, title_text_mapping, generate_text_box @@ -10,38 +11,48 @@ ROM_PLAYER_LIMIT = 65535 import hashlib import os import math +import pkgutil ability_rom_data = { - 0xBC0003: [[0x1F2C, 0x7]], # Run 0x80 - 0xBC0004: [[0x1F2C, 0x6]], # Carry 0x40 - 0xBC0005: [[0x1F2C, 0x2]], # Swim 0x04 - 0xBC0006: [[0x1F2C, 0x3]], # Spin Jump 0x08 - 0xBC0007: [[0x1F2C, 0x5]], # Climb 0x20 - 0xBC0008: [[0x1F2C, 0x1]], # Yoshi 0x02 - 0xBC0009: [[0x1F2C, 0x4]], # P-Switch 0x10 + 0xBC0003: [[0x1F1C, 0x7]], # Run 0x80 + 0xBC0004: [[0x1F1C, 0x6]], # Carry 0x40 + 0xBC0005: [[0x1F1C, 0x2]], # Swim 0x04 + 0xBC0006: [[0x1F1C, 0x3]], # Spin Jump 0x08 + 0xBC0007: [[0x1F1C, 0x5]], # Climb 0x20 + 0xBC0008: [[0x1F1C, 0x1]], # Yoshi 0x02 + 0xBC0009: [[0x1F1C, 0x4]], # P-Switch 0x10 #0xBC000A: [[]] 0xBC000B: [[0x1F2D, 0x3]], # P-Balloon 0x08 - 0xBC000D: [[0x1F2D, 0x4]], # Super Star 0x10 + 0xBC000D: [[0x1F2D, 0x4]] # Super Star 0x10 } +icon_rom_data = { + 0xBC0002: [0x1B00C], # Yoshi Egg + 0xBC0012: [0x1B00E], # Boss Token + 0xBC0017: [0x1B004], # 1 coin + 0xBC0018: [0x1B006], # 5 coins + 0xBC0019: [0x1B008], # 10 coins + 0xBC001A: [0x1B00A], # 50 coins + + 0xBC0001: [0x1B010] # 1-Up Mushroom +} + item_rom_data = { - 0xBC0001: [0x18E4, 0x1], # 1-Up Mushroom - - 0xBC0002: [0x1F24, 0x1, 0x1F], # Yoshi Egg - 0xBC0012: [0x1F26, 0x1, 0x09], # Boss Token - - 0xBC000E: [0x1F28, 0x1, 0x1C], # Yellow Switch Palace - 0xBC000F: [0x1F27, 0x1, 0x1C], # Green Switch Palace - 0xBC0010: [0x1F2A, 0x1, 0x1C], # Red Switch Palace - 0xBC0011: [0x1F29, 0x1, 0x1C], # Blue Switch Palace + 0xBC000E: [0x1F28, 0x1, 0x1C], # Yellow Switch Palace + 0xBC000F: [0x1F27, 0x1, 0x1C], # Green Switch Palace + 0xBC0010: [0x1F2A, 0x1, 0x1C], # Red Switch Palace + 0xBC0011: [0x1F29, 0x1, 0x1C], # Blue Switch Palace + 0xBC001B: [0x1F1E, 0x80, 0x39] # Special Zone Clear } trap_rom_data = { - 0xBC0013: [0x0086, 0x1, 0x0E], # Ice Trap + 0xBC0013: [0x0086, 0x1, 0x0E], # Ice Trap 0xBC0014: [0x18BD, 0x7F, 0x18], # Stun Trap - 0xBC0016: [0x0F31, 0x1], # Timer Trap + 0xBC0016: [0x0F31, 0x1], # Timer Trap + 0xBC001C: [0x18B4, 0x1, 0x44], # Reverse controls trap + 0xBC001D: [0x18B7, 0x1], # Thwimp Trap } @@ -109,7 +120,7 @@ def handle_ability_code(rom): rom.write_bytes(RUN_SUB_ADDR + 0x04, bytearray([0xC8])) # INY rom.write_bytes(RUN_SUB_ADDR + 0x05, bytearray([0xA9, 0x70])) # LDA #70 rom.write_bytes(RUN_SUB_ADDR + 0x07, bytearray([0xAA])) # TAX - rom.write_bytes(RUN_SUB_ADDR + 0x08, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(RUN_SUB_ADDR + 0x08, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(RUN_SUB_ADDR + 0x0B, bytearray([0x89, 0x80])) # BIT #80 rom.write_bytes(RUN_SUB_ADDR + 0x0D, bytearray([0xF0, 0x04])) # BEQ +0x04 rom.write_bytes(RUN_SUB_ADDR + 0x0F, bytearray([0x8A])) # TXA @@ -126,7 +137,7 @@ def handle_ability_code(rom): PURPLE_BLOCK_CARRY_SUB_ADDR = 0x01BA28 rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x04, bytearray([0x89, 0x40])) # BIT #40 rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x06, bytearray([0xF0, 0x09])) # BEQ +0x09 rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x08, bytearray([0x28])) # PLP @@ -145,7 +156,7 @@ def handle_ability_code(rom): SPRINGBOARD_CARRY_SUB_ADDR = 0x01BA40 rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x00, bytearray([0x48])) # PHA rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x01, bytearray([0x08])) # PHP - rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x02, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x02, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x05, bytearray([0x89, 0x40])) # BIT #40 rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x07, bytearray([0xF0, 0x08])) # BEQ +0x08 rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x09, bytearray([0xA9, 0x0B])) # LDA #0B @@ -157,7 +168,7 @@ def handle_ability_code(rom): # End Springboard Carry # Shell Carry - rom.write_bytes(0xAA66, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(0xAA66, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(0xAA69, bytearray([0x89, 0x40])) # BIT #40 rom.write_bytes(0xAA6B, bytearray([0xF0, 0x07])) # BEQ +0x07 rom.write_bytes(0xAA6D, bytearray([0x22, 0x60, 0xBA, 0x03])) # JSL $03BA60 @@ -180,7 +191,7 @@ def handle_ability_code(rom): YOSHI_CARRY_SUB_ADDR = 0x01BA70 rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x04, bytearray([0x89, 0x40])) # BIT #40 rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x06, bytearray([0xF0, 0x0A])) # BEQ +0x0A rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x08, bytearray([0xA9, 0x12])) # LDA #12 @@ -197,7 +208,7 @@ def handle_ability_code(rom): CLIMB_SUB_ADDR = 0x01BA88 rom.write_bytes(CLIMB_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(CLIMB_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(CLIMB_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(CLIMB_SUB_ADDR + 0x04, bytearray([0x89, 0x20])) # BIT #20 rom.write_bytes(CLIMB_SUB_ADDR + 0x06, bytearray([0xF0, 0x09])) # BEQ +0x09 rom.write_bytes(CLIMB_SUB_ADDR + 0x08, bytearray([0xA5, 0x8B])) # LDA $8B @@ -213,7 +224,7 @@ def handle_ability_code(rom): CLIMB_ROPE_SUB_ADDR = 0x01BC70 rom.write_bytes(CLIMB_ROPE_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(CLIMB_ROPE_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(CLIMB_ROPE_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(CLIMB_ROPE_SUB_ADDR + 0x04, bytearray([0x89, 0x20])) # BIT #20 rom.write_bytes(CLIMB_ROPE_SUB_ADDR + 0x06, bytearray([0xF0, 0x07])) # BEQ +0x07 rom.write_bytes(CLIMB_ROPE_SUB_ADDR + 0x08, bytearray([0x28])) # PLP @@ -230,7 +241,7 @@ def handle_ability_code(rom): P_SWITCH_SUB_ADDR = 0x01BAA0 rom.write_bytes(P_SWITCH_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(P_SWITCH_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(P_SWITCH_SUB_ADDR + 0x04, bytearray([0x89, 0x10])) # BIT #10 rom.write_bytes(P_SWITCH_SUB_ADDR + 0x06, bytearray([0xF0, 0x04])) # BEQ +0x04 rom.write_bytes(P_SWITCH_SUB_ADDR + 0x08, bytearray([0xA9, 0xB0])) # LDA #B0 @@ -242,7 +253,7 @@ def handle_ability_code(rom): # End P-Switch # Spin Jump - rom.write_bytes(0x5645, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(0x5645, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(0x5648, bytearray([0x89, 0x08])) # BIT #08 rom.write_bytes(0x564A, bytearray([0xF0, 0x12])) # BEQ +0x12 rom.write_bytes(0x564C, bytearray([0x22, 0xB8, 0xBA, 0x03])) # JSL $03BAB8 @@ -264,7 +275,7 @@ def handle_ability_code(rom): SPIN_JUMP_WATER_SUB_ADDR = 0x01BBF8 rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x04, bytearray([0x89, 0x08])) # BIT #08 rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x06, bytearray([0xF0, 0x09])) # BEQ +0x09 rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x08, bytearray([0x1A])) # INC @@ -281,7 +292,7 @@ def handle_ability_code(rom): SPIN_JUMP_SPRING_SUB_ADDR = 0x01BC0C rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x04, bytearray([0x89, 0x08])) # BIT #08 rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x06, bytearray([0xF0, 0x05])) # BEQ +0x05 rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x08, bytearray([0xA9, 0x01])) # LDA #01 @@ -297,7 +308,7 @@ def handle_ability_code(rom): SWIM_SUB_ADDR = 0x01BAC8 rom.write_bytes(SWIM_SUB_ADDR + 0x00, bytearray([0x48])) # PHA rom.write_bytes(SWIM_SUB_ADDR + 0x01, bytearray([0x08])) # PHP - rom.write_bytes(SWIM_SUB_ADDR + 0x02, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SWIM_SUB_ADDR + 0x02, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(SWIM_SUB_ADDR + 0x05, bytearray([0x89, 0x04])) # BIT #04 rom.write_bytes(SWIM_SUB_ADDR + 0x07, bytearray([0xF0, 0x0C])) # BEQ +0x0C rom.write_bytes(SWIM_SUB_ADDR + 0x09, bytearray([0x28])) # PLP @@ -321,7 +332,7 @@ def handle_ability_code(rom): SWIM_SUB_ADDR = 0x01BAE8 rom.write_bytes(SWIM_SUB_ADDR + 0x00, bytearray([0x48])) # PHA rom.write_bytes(SWIM_SUB_ADDR + 0x01, bytearray([0x08])) # PHP - rom.write_bytes(SWIM_SUB_ADDR + 0x02, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SWIM_SUB_ADDR + 0x02, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(SWIM_SUB_ADDR + 0x05, bytearray([0x89, 0x04])) # BIT #04 rom.write_bytes(SWIM_SUB_ADDR + 0x07, bytearray([0xF0, 0x0A])) # BEQ +0x0A rom.write_bytes(SWIM_SUB_ADDR + 0x09, bytearray([0x28])) # PLP @@ -344,7 +355,7 @@ def handle_ability_code(rom): YOSHI_SUB_ADDR = 0x01BB08 rom.write_bytes(YOSHI_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(YOSHI_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(YOSHI_SUB_ADDR + 0x01, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(YOSHI_SUB_ADDR + 0x04, bytearray([0x89, 0x02])) # BIT #02 rom.write_bytes(YOSHI_SUB_ADDR + 0x06, bytearray([0xF0, 0x06])) # BEQ +0x06 rom.write_bytes(YOSHI_SUB_ADDR + 0x08, bytearray([0x28])) # PLP @@ -366,7 +377,7 @@ def handle_ability_code(rom): YOSHI_SUB_ADDR = 0x01BB20 rom.write_bytes(YOSHI_SUB_ADDR + 0x00, bytearray([0x08])) # PHP rom.write_bytes(YOSHI_SUB_ADDR + 0x01, bytearray([0x9C, 0x1E, 0x14])) # STZ $141E - rom.write_bytes(YOSHI_SUB_ADDR + 0x04, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(YOSHI_SUB_ADDR + 0x04, bytearray([0xAD, 0x1C, 0x1F])) # LDA $1F1C rom.write_bytes(YOSHI_SUB_ADDR + 0x07, bytearray([0x89, 0x02])) # BIT #02 rom.write_bytes(YOSHI_SUB_ADDR + 0x09, bytearray([0xF0, 0x05])) # BEQ +0x05 rom.write_bytes(YOSHI_SUB_ADDR + 0x0B, bytearray([0x28])) # PLP @@ -654,6 +665,7 @@ def handle_level_shuffle(rom, active_level_dict): for level_id, tile_id in active_level_dict.items(): rom.write_byte(0x37F70 + level_id, tile_id) + rom.write_byte(0x37F00 + tile_id, level_id) def handle_collected_paths(rom): @@ -673,38 +685,2139 @@ def handle_collected_paths(rom): def handle_vertical_scroll(rom): - rom.write_bytes(0x285BA, bytearray([0x22, 0x90, 0xBC, 0x03])) # JSL $03BC90 + rom.write_bytes(0x285BA, bytearray([0x22, 0x80, 0xF4, 0x0F])) # JSL $0FF480 - VERTICAL_SCROLL_SUB_ADDR = 0x01BC90 - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x00, bytearray([0x4A])) # LSR - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x01, bytearray([0x4A])) # LSR - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x02, bytearray([0x4A])) # LSR - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x03, bytearray([0x4A])) # LSR - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x04, bytearray([0x08])) # PHP - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x05, bytearray([0xC9, 0x02])) # CMP #02 - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x07, bytearray([0xD0, 0x02])) # BNE +0x02 - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x09, bytearray([0xA9, 0x01])) # LDA #01 - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0B, bytearray([0x28])) # PLP - rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0C, bytearray([0x6B])) # RTL + VERTICAL_SCROLL_SUB_ADDR = 0x7F480 + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0000, bytearray([0x4A])) # vertical_scroll: lsr + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0001, bytearray([0x4A])) # lsr + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0002, bytearray([0x4A])) # lsr + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0003, bytearray([0x4A])) # lsr + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0004, bytearray([0x08])) # php + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0005, bytearray([0xC9, 0x02])) # cmp #$02 + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0007, bytearray([0xD0, 0x0B])) # bne + + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0009, bytearray([0xC2, 0x10])) # rep #$10 + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x000B, bytearray([0xDA])) # phx + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x000C, bytearray([0xAE, 0x0B, 0x01])) # ldx $010B + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x000F, bytearray([0xBF, 0x00, 0xF5, 0x0F])) # lda.l vertical_scroll_levels,x + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0013, bytearray([0xFA])) # plx + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0014, bytearray([0x28])) # + plp + rom.write_bytes(VERTICAL_SCROLL_SUB_ADDR + 0x0015, bytearray([0x6B])) # rtl + + vertical_scroll_table = [ + 0x02, 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, # Levels 000-00F + 0x01, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, # Levels 010-01F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 020-02F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 030-03F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 040-04F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 050-05F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 060-06F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 070-07F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 080-08F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 090-09F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 0A0-0AF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 0B0-0BF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 0C0-0CF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, # Levels 0D0-0DF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, # Levels 0E0-0EF + 0x02, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, # Levels 0F0-0FF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x01, # Levels 100-10F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 110-11F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 120-12F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 130-13F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 140-14F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 150-15F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 160-16F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 170-17F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 180-18F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 190-19F + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 1A0-1AF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 1B0-1BF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 1C0-1CF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 1D0-1DF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # Levels 1E0-1EF + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02] # Levels 1F0-1FF + + rom.write_bytes(0x7F500, bytes(vertical_scroll_table)) -def handle_music_shuffle(rom, world, player): +def handle_bonus_block(rom): + rom.write_bytes(0x71A5, bytearray([0x5C, 0x19, 0x8E, 0x05])) # JML $058E19 + + BONUS_BLOCK_ADDR = 0x28E19 + rom.write_bytes(BONUS_BLOCK_ADDR + 0x00, bytearray([0xA9, 0x06])) # LDA #$06 + rom.write_bytes(BONUS_BLOCK_ADDR + 0x02, bytearray([0xAC, 0xC0, 0x0D])) # LDY $0DC0 + rom.write_bytes(BONUS_BLOCK_ADDR + 0x05, bytearray([0xD0, 0x1E])) # BNE IGNORE + rom.write_bytes(BONUS_BLOCK_ADDR + 0x07, bytearray([0xDA])) # PHX + rom.write_bytes(BONUS_BLOCK_ADDR + 0x08, bytearray([0xAD, 0xBF, 0x13])) # LDA $13BF + rom.write_bytes(BONUS_BLOCK_ADDR + 0x0B, bytearray([0x4A])) # LSR + rom.write_bytes(BONUS_BLOCK_ADDR + 0x0C, bytearray([0x4A])) # LSR + rom.write_bytes(BONUS_BLOCK_ADDR + 0x0D, bytearray([0x4A])) # LSR + rom.write_bytes(BONUS_BLOCK_ADDR + 0x0E, bytearray([0x48])) # PHA + rom.write_bytes(BONUS_BLOCK_ADDR + 0x0F, bytearray([0xAD, 0xBF, 0x13])) # LDA $13BF + rom.write_bytes(BONUS_BLOCK_ADDR + 0x12, bytearray([0x29, 0x07])) # AND #$07 + rom.write_bytes(BONUS_BLOCK_ADDR + 0x14, bytearray([0xAA])) # TAX + rom.write_bytes(BONUS_BLOCK_ADDR + 0x15, bytearray([0xBF, 0x5B, 0xB3, 0x05])) # LDA $05B35B,x + rom.write_bytes(BONUS_BLOCK_ADDR + 0x19, bytearray([0xFA])) # PLX + rom.write_bytes(BONUS_BLOCK_ADDR + 0x1A, bytearray([0x1F, 0x00, 0xA0, 0x7F])) # ORA $7FA000,x + rom.write_bytes(BONUS_BLOCK_ADDR + 0x1E, bytearray([0x9F, 0x00, 0xA0, 0x7F])) # STA $7FA000,x + rom.write_bytes(BONUS_BLOCK_ADDR + 0x22, bytearray([0xFA])) # PLX + rom.write_bytes(BONUS_BLOCK_ADDR + 0x23, bytearray([0xA9, 0x05])) # LDA #$05 + rom.write_bytes(BONUS_BLOCK_ADDR + 0x25, bytearray([0x5C, 0xD0, 0xF1, 0x00])) # IGNORE: JML $00F1D0 + + +def handle_blocksanity(rom): + import json + blocksanity_data = pkgutil.get_data(__name__, f"data/blocksanity.json").decode("utf-8") + blocksanity_data = json.loads(blocksanity_data) + blocksanity_coords = bytearray([]) + blocksanity_bytes = bytearray([]) + + block_count = 0 + entries = 0 + for level_name, level_data in blocksanity_data.items(): + # Calculate blocksanity pointer + if level_data == []: + # Skip if the level doesn't have any data + blocksanity_bytes += bytearray([0xFF, 0xFF]) + continue + level_ptr = 0x80C0 + entries + blocksanity_bytes += bytearray([level_ptr & 0xFF, (level_ptr >> 8) & 0xFF]) + + # Get block data + block_coords = bytearray([]) + for x in range(len(level_data)): + block_coords += bytearray([ + int(level_data[x][1], 16) & 0xFF, (int(level_data[x][1], 16) >> 8) & 0xFF, + int(level_data[x][2], 16) & 0xFF, (int(level_data[x][2], 16) >> 8) & 0xFF, + block_count & 0xFF, (block_count >> 8) & 0xFF]) + entries += 6 + block_count += 1 + block_coords += bytearray([0xFF, 0xFF]) + entries += 2 + + blocksanity_coords += block_coords + + blocksanity_bytes += blocksanity_coords + + rom.write_bytes(0x80000, blocksanity_bytes) + rom.write_bytes(0x071D0, bytearray([0x5C, 0x00, 0xF7, 0x0F])) # org $00F1D0 : jml blocksanity_main + rom.write_bytes(0x0AD59, bytearray([0x5C, 0x15, 0xF7, 0x0F])) # org $01AD5C : jml blocksanity_flying_init + rom.write_bytes(0x0AE16, bytearray([0x22, 0x39, 0xF7, 0x0F])) # org $01AE16 : jsl blocksanity_flying_main + + BLOCKSANITY_ADDR = 0x7F700 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0000, bytearray([0x85, 0x05])) # blocksanity_main: sta $05 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0002, bytearray([0x8B])) # phb + rom.write_bytes(BLOCKSANITY_ADDR + 0x0003, bytearray([0xA9, 0x10])) # lda.b #blocksanity_pointers>>16 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0005, bytearray([0x48])) # pha + rom.write_bytes(BLOCKSANITY_ADDR + 0x0006, bytearray([0xAB])) # plb + rom.write_bytes(BLOCKSANITY_ADDR + 0x0007, bytearray([0x5A])) # phy + rom.write_bytes(BLOCKSANITY_ADDR + 0x0008, bytearray([0x20, 0x63, 0xF7])) # jsr process_block + rom.write_bytes(BLOCKSANITY_ADDR + 0x000B, bytearray([0x7A])) # ply + rom.write_bytes(BLOCKSANITY_ADDR + 0x000C, bytearray([0xAB])) # plb + rom.write_bytes(BLOCKSANITY_ADDR + 0x000D, bytearray([0xA5, 0x05])) # lda $05 + rom.write_bytes(BLOCKSANITY_ADDR + 0x000F, bytearray([0xC9, 0x05])) # cmp #$05 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0011, bytearray([0x5C, 0xD4, 0xF1, 0x00])) # jml $00F1D4 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0015, bytearray([0xB5, 0xD8])) # blocksanity_flying_init: lda $D8,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0017, bytearray([0x29, 0xF0])) # and #$F0 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0019, bytearray([0x9F, 0x20, 0xB8, 0x7F])) # sta !sprite_blocksanity_y_lo,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x001D, bytearray([0xBD, 0xD4, 0x14])) # lda $14D4,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0020, bytearray([0x9F, 0x30, 0xB8, 0x7F])) # sta !sprite_blocksanity_y_hi,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0024, bytearray([0xBD, 0xE0, 0x14])) # lda $14E0,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0027, bytearray([0x9F, 0x10, 0xB8, 0x7F])) # sta !sprite_blocksanity_x_hi,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x002B, bytearray([0xB5, 0xE4])) # lda $E4,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x002D, bytearray([0x29, 0xF0])) # and #$F0 + rom.write_bytes(BLOCKSANITY_ADDR + 0x002F, bytearray([0x9F, 0x00, 0xB8, 0x7F])) # sta !sprite_blocksanity_x_lo,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0033, bytearray([0x4A])) # lsr + rom.write_bytes(BLOCKSANITY_ADDR + 0x0034, bytearray([0x4A])) # lsr + rom.write_bytes(BLOCKSANITY_ADDR + 0x0035, bytearray([0x5C, 0x5D, 0xAD, 0x01])) # jml $01AD5D + rom.write_bytes(BLOCKSANITY_ADDR + 0x0039, bytearray([0xBF, 0x20, 0xB8, 0x7F])) # blocksanity_flying_main: lda !sprite_blocksanity_y_lo,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x003D, bytearray([0x85, 0x98])) # sta $98 + rom.write_bytes(BLOCKSANITY_ADDR + 0x003F, bytearray([0xBF, 0x30, 0xB8, 0x7F])) # lda !sprite_blocksanity_y_hi,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0043, bytearray([0x85, 0x99])) # sta $99 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0045, bytearray([0xBF, 0x00, 0xB8, 0x7F])) # lda !sprite_blocksanity_x_lo,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0049, bytearray([0x85, 0x9A])) # sta $9A + rom.write_bytes(BLOCKSANITY_ADDR + 0x004B, bytearray([0xBF, 0x10, 0xB8, 0x7F])) # lda !sprite_blocksanity_x_hi,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x004F, bytearray([0x85, 0x9B])) # sta $9B + rom.write_bytes(BLOCKSANITY_ADDR + 0x0051, bytearray([0x8B])) # phb + rom.write_bytes(BLOCKSANITY_ADDR + 0x0052, bytearray([0xA9, 0x10])) # lda.b #blocksanity_pointers>>16 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0054, bytearray([0x48])) # pha + rom.write_bytes(BLOCKSANITY_ADDR + 0x0055, bytearray([0xAB])) # plb + rom.write_bytes(BLOCKSANITY_ADDR + 0x0056, bytearray([0x5A])) # phy + rom.write_bytes(BLOCKSANITY_ADDR + 0x0057, bytearray([0xDA])) # phx + rom.write_bytes(BLOCKSANITY_ADDR + 0x0058, bytearray([0x20, 0x63, 0xF7])) # jsr process_block + rom.write_bytes(BLOCKSANITY_ADDR + 0x005B, bytearray([0xFA])) # plx + rom.write_bytes(BLOCKSANITY_ADDR + 0x005C, bytearray([0x7A])) # ply + rom.write_bytes(BLOCKSANITY_ADDR + 0x005D, bytearray([0xAB])) # plb + rom.write_bytes(BLOCKSANITY_ADDR + 0x005E, bytearray([0xB5, 0xE4])) # lda $E4,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0060, bytearray([0x85, 0x9A])) # sta $9A + rom.write_bytes(BLOCKSANITY_ADDR + 0x0062, bytearray([0x6B])) # rtl + rom.write_bytes(BLOCKSANITY_ADDR + 0x0063, bytearray([0xA9, 0x0F])) # process_block: lda #$0F + rom.write_bytes(BLOCKSANITY_ADDR + 0x0065, bytearray([0x14, 0x98])) # trb $98 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0067, bytearray([0x14, 0x9A])) # trb $9A + rom.write_bytes(BLOCKSANITY_ADDR + 0x0069, bytearray([0xC2, 0x30])) # rep #$30 + rom.write_bytes(BLOCKSANITY_ADDR + 0x006B, bytearray([0xA5, 0x60])) # lda $60 + rom.write_bytes(BLOCKSANITY_ADDR + 0x006D, bytearray([0x29, 0xFF, 0x00])) # and #$00FF + rom.write_bytes(BLOCKSANITY_ADDR + 0x0070, bytearray([0x0A])) # asl + rom.write_bytes(BLOCKSANITY_ADDR + 0x0071, bytearray([0x18])) # clc + rom.write_bytes(BLOCKSANITY_ADDR + 0x0072, bytearray([0x69, 0x00, 0x80])) # adc.w #blocksanity_pointers + rom.write_bytes(BLOCKSANITY_ADDR + 0x0075, bytearray([0x48])) # pha + rom.write_bytes(BLOCKSANITY_ADDR + 0x0076, bytearray([0xA0, 0x00, 0x00])) # ldy #$0000 + rom.write_bytes(BLOCKSANITY_ADDR + 0x0079, bytearray([0xB3, 0x01])) # lda ($01,s),y + rom.write_bytes(BLOCKSANITY_ADDR + 0x007B, bytearray([0x48])) # pha + rom.write_bytes(BLOCKSANITY_ADDR + 0x007C, bytearray([0xB3, 0x01])) # .loop lda ($01,s),y + rom.write_bytes(BLOCKSANITY_ADDR + 0x007E, bytearray([0xC9, 0xFF, 0xFF])) # cmp #$FFFF + rom.write_bytes(BLOCKSANITY_ADDR + 0x0081, bytearray([0xF0, 0x16])) # beq .return + rom.write_bytes(BLOCKSANITY_ADDR + 0x0083, bytearray([0xC5, 0x9A])) # cmp $9A + rom.write_bytes(BLOCKSANITY_ADDR + 0x0085, bytearray([0xD0, 0x0A])) # bne .next_block_x + rom.write_bytes(BLOCKSANITY_ADDR + 0x0087, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0088, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0089, bytearray([0xB3, 0x01])) # lda ($01,s),y + rom.write_bytes(BLOCKSANITY_ADDR + 0x008B, bytearray([0xC5, 0x98])) # cmp $98 + rom.write_bytes(BLOCKSANITY_ADDR + 0x008D, bytearray([0xF0, 0x0F])) # beq .valid_block + rom.write_bytes(BLOCKSANITY_ADDR + 0x008F, bytearray([0x80, 0x02])) # bra .next_block_y + rom.write_bytes(BLOCKSANITY_ADDR + 0x0091, bytearray([0xC8])) # .next_block_x iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0092, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0093, bytearray([0xC8])) # .next_block_y iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0094, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0095, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0096, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x0097, bytearray([0x80, 0xE3])) # bra .loop + rom.write_bytes(BLOCKSANITY_ADDR + 0x0099, bytearray([0x68])) # .return pla + rom.write_bytes(BLOCKSANITY_ADDR + 0x009A, bytearray([0x68])) # pla + rom.write_bytes(BLOCKSANITY_ADDR + 0x009B, bytearray([0xE2, 0x30])) # sep #$30 + rom.write_bytes(BLOCKSANITY_ADDR + 0x009D, bytearray([0x60])) # rts + rom.write_bytes(BLOCKSANITY_ADDR + 0x009E, bytearray([0xC8])) # .valid_block iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x009F, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x00A0, bytearray([0xB3, 0x01])) # lda ($01,s),y + rom.write_bytes(BLOCKSANITY_ADDR + 0x00A2, bytearray([0xAA])) # tax + rom.write_bytes(BLOCKSANITY_ADDR + 0x00A3, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00A5, bytearray([0xDA])) # phx + rom.write_bytes(BLOCKSANITY_ADDR + 0x00A6, bytearray([0xBF, 0x00, 0xA4, 0x7F])) # lda !blocksanity_data_flags,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x00AA, bytearray([0xD0, 0x08])) # bne .processed + rom.write_bytes(BLOCKSANITY_ADDR + 0x00AC, bytearray([0x1A])) # inc + rom.write_bytes(BLOCKSANITY_ADDR + 0x00AD, bytearray([0x9F, 0x00, 0xA4, 0x7F])) # sta !blocksanity_data_flags,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x00B1, bytearray([0x20, 0xBA, 0xF7])) # jsr blocksanity_check_flags + rom.write_bytes(BLOCKSANITY_ADDR + 0x00B4, bytearray([0xFA])) # .processed plx + rom.write_bytes(BLOCKSANITY_ADDR + 0x00B5, bytearray([0xFA])) # plx + rom.write_bytes(BLOCKSANITY_ADDR + 0x00B6, bytearray([0xFA])) # plx + rom.write_bytes(BLOCKSANITY_ADDR + 0x00B7, bytearray([0xE2, 0x10])) # sep #$10 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00B9, bytearray([0x60])) # rts + rom.write_bytes(BLOCKSANITY_ADDR + 0x00BA, bytearray([0xC2, 0x20])) # blocksanity_check_flags: rep #$20 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00BC, bytearray([0xA0, 0x00, 0x00])) # ldy #$0000 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00BF, bytearray([0xB3, 0x05])) # .loop lda ($05,s),y + rom.write_bytes(BLOCKSANITY_ADDR + 0x00C1, bytearray([0xC9, 0xFF, 0xFF])) # cmp #$FFFF + rom.write_bytes(BLOCKSANITY_ADDR + 0x00C4, bytearray([0xF0, 0x14])) # beq .check + rom.write_bytes(BLOCKSANITY_ADDR + 0x00C6, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x00C7, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x00C8, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x00C9, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x00CA, bytearray([0xB3, 0x05])) # lda ($05,s),y + rom.write_bytes(BLOCKSANITY_ADDR + 0x00CC, bytearray([0xAA])) # tax + rom.write_bytes(BLOCKSANITY_ADDR + 0x00CD, bytearray([0xBF, 0x00, 0xA4, 0x7F])) # lda !blocksanity_data_flags,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x00D1, bytearray([0x29, 0xFF, 0x00])) # and #$00FF + rom.write_bytes(BLOCKSANITY_ADDR + 0x00D4, bytearray([0xF0, 0x22])) # beq .invalid + rom.write_bytes(BLOCKSANITY_ADDR + 0x00D6, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x00D7, bytearray([0xC8])) # iny + rom.write_bytes(BLOCKSANITY_ADDR + 0x00D8, bytearray([0x80, 0xE5])) # bra .loop + rom.write_bytes(BLOCKSANITY_ADDR + 0x00DA, bytearray([0xE2, 0x20])) # .check sep #$20 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00DC, bytearray([0xA9, 0x00])) # lda #$00 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00DE, bytearray([0xEB])) # xba + rom.write_bytes(BLOCKSANITY_ADDR + 0x00DF, bytearray([0xA5, 0x60])) # lda $60 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00E1, bytearray([0x4A])) # lsr + rom.write_bytes(BLOCKSANITY_ADDR + 0x00E2, bytearray([0x4A])) # lsr + rom.write_bytes(BLOCKSANITY_ADDR + 0x00E3, bytearray([0x4A])) # lsr + rom.write_bytes(BLOCKSANITY_ADDR + 0x00E4, bytearray([0xA8])) # tay + rom.write_bytes(BLOCKSANITY_ADDR + 0x00E5, bytearray([0xA5, 0x60])) # lda $60 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00E7, bytearray([0x29, 0x07])) # and #$07 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00E9, bytearray([0xAA])) # tax + rom.write_bytes(BLOCKSANITY_ADDR + 0x00EA, bytearray([0xBF, 0x5B, 0xB3, 0x05])) # lda.l $05B35B,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x00EE, bytearray([0xBB])) # tyx + rom.write_bytes(BLOCKSANITY_ADDR + 0x00EF, bytearray([0x1F, 0x10, 0xA0, 0x7F])) # ora !blocksanity_flags,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x00F3, bytearray([0x9F, 0x10, 0xA0, 0x7F])) # sta !blocksanity_flags,x + rom.write_bytes(BLOCKSANITY_ADDR + 0x00F7, bytearray([0x60])) # rts + rom.write_bytes(BLOCKSANITY_ADDR + 0x00F8, bytearray([0xE2, 0x20])) # .invalid sep #$20 + rom.write_bytes(BLOCKSANITY_ADDR + 0x00FA, bytearray([0x60])) # rts + +def handle_ram(rom): + rom.write_byte(0x07FD8, 0x02) # Expand SRAM + rom.write_bytes(0x01CF5, bytearray([0x5C, 0x00, 0xF2, 0x0F])) # org $009CF5 : jml init_sram + rom.write_bytes(0x01C0F, bytearray([0x5C, 0x00, 0xF3, 0x0F])) # org $009C0F : jml save_sram + rom.write_bytes(0x013BB, bytearray([0x5C, 0xA0, 0xF0, 0x0F])) # org $0093BB : jml init_ram + + INIT_SRAM_ADDR = 0x7F200 + rom.write_bytes(INIT_SRAM_ADDR + 0x0000, bytearray([0xD0, 0x74])) # init_sram: bne .clear + rom.write_bytes(INIT_SRAM_ADDR + 0x0002, bytearray([0x9C, 0x09, 0x01])) # stz $0109 + rom.write_bytes(INIT_SRAM_ADDR + 0x0005, bytearray([0xDA])) # phx + rom.write_bytes(INIT_SRAM_ADDR + 0x0006, bytearray([0x08])) # php + rom.write_bytes(INIT_SRAM_ADDR + 0x0007, bytearray([0xE2, 0x10])) # sep #$10 + rom.write_bytes(INIT_SRAM_ADDR + 0x0009, bytearray([0xA2, 0x5F])) # ldx.b #$5F + rom.write_bytes(INIT_SRAM_ADDR + 0x000B, bytearray([0xBF, 0x00, 0x08, 0x70])) # - lda !level_clears_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x000F, bytearray([0x9F, 0x00, 0xA2, 0x7F])) # sta !level_clears,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0013, bytearray([0xCA])) # dex + rom.write_bytes(INIT_SRAM_ADDR + 0x0014, bytearray([0x10, 0xF5])) # bpl - + rom.write_bytes(INIT_SRAM_ADDR + 0x0016, bytearray([0xA2, 0x0B])) # ldx #$0B + rom.write_bytes(INIT_SRAM_ADDR + 0x0018, bytearray([0xBF, 0x40, 0x09, 0x70])) # - lda !blocksanity_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x001C, bytearray([0x9F, 0x10, 0xA0, 0x7F])) # sta !blocksanity_flags,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0020, bytearray([0xBF, 0x10, 0x09, 0x70])) # lda !moons_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0024, bytearray([0x9D, 0xEE, 0x1F])) # sta !moons_flags,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0027, bytearray([0xBF, 0x00, 0x09, 0x70])) # lda !yoshi_coins_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x002B, bytearray([0x9D, 0x2F, 0x1F])) # sta !yoshi_coins_flags,x + rom.write_bytes(INIT_SRAM_ADDR + 0x002E, bytearray([0xBF, 0x30, 0x09, 0x70])) # lda !bonus_block_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0032, bytearray([0x9F, 0x00, 0xA0, 0x7F])) # sta !bonus_block_flags,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0036, bytearray([0xBF, 0x20, 0x09, 0x70])) # lda !checkpoints_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x003A, bytearray([0x9D, 0x3C, 0x1F])) # sta !checkpoints_flags,x + rom.write_bytes(INIT_SRAM_ADDR + 0x003D, bytearray([0xCA])) # dex + rom.write_bytes(INIT_SRAM_ADDR + 0x003E, bytearray([0x10, 0xD8])) # bpl - + rom.write_bytes(INIT_SRAM_ADDR + 0x0040, bytearray([0xC2, 0x10])) # rep #$10 + rom.write_bytes(INIT_SRAM_ADDR + 0x0042, bytearray([0xA2, 0x45, 0x02])) # ldx.w #!blocksanity_locs-1 + rom.write_bytes(INIT_SRAM_ADDR + 0x0045, bytearray([0xBF, 0x00, 0x0A, 0x70])) # - lda !blocksanity_data_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0049, bytearray([0x9F, 0x00, 0xA4, 0x7F])) # sta !blocksanity_data_flags,x + rom.write_bytes(INIT_SRAM_ADDR + 0x004D, bytearray([0xCA])) # dex + rom.write_bytes(INIT_SRAM_ADDR + 0x004E, bytearray([0x10, 0xF5])) # bpl - + rom.write_bytes(INIT_SRAM_ADDR + 0x0050, bytearray([0xE2, 0x10])) # sep #$10 + #rom.write_bytes(INIT_SRAM_ADDR + 0x0052, bytearray([0xAF, 0x50, 0x09, 0x70])) # lda !received_items_count_sram+$00 + #rom.write_bytes(INIT_SRAM_ADDR + 0x0056, bytearray([0x8F, 0x0E, 0xA0, 0x7F])) # sta !received_items_count+$00 + #rom.write_bytes(INIT_SRAM_ADDR + 0x005A, bytearray([0xAF, 0x51, 0x09, 0x70])) # lda !received_items_count_sram+$01 + #rom.write_bytes(INIT_SRAM_ADDR + 0x005E, bytearray([0x8F, 0x0F, 0xA0, 0x7F])) # sta !received_items_count+$01 + rom.write_bytes(INIT_SRAM_ADDR + 0x0052, bytearray([0xEA] * 0x17)) # Ugly, will apply be better when we port everything to a Base Patch + #rom.write_bytes(INIT_SRAM_ADDR + 0x0062, bytearray([0xAF, 0x52, 0x09, 0x70])) # lda !special_world_clear_sram + #rom.write_bytes(INIT_SRAM_ADDR + 0x0066, bytearray([0x8D, 0xFF, 0x1F])) # sta !special_world_clear_flag + rom.write_bytes(INIT_SRAM_ADDR + 0x0069, bytearray([0xAF, 0x54, 0x09, 0x70])) # lda !goal_item_count_sram + rom.write_bytes(INIT_SRAM_ADDR + 0x006D, bytearray([0x8F, 0x1E, 0xA0, 0x7F])) # sta !goal_item_count + rom.write_bytes(INIT_SRAM_ADDR + 0x0071, bytearray([0x28])) # plp + rom.write_bytes(INIT_SRAM_ADDR + 0x0072, bytearray([0x5C, 0xFB, 0x9C, 0x00])) # jml $009CFB + rom.write_bytes(INIT_SRAM_ADDR + 0x0076, bytearray([0xDA])) # .clear phx + rom.write_bytes(INIT_SRAM_ADDR + 0x0077, bytearray([0xA2, 0x5F, 0x00])) # ldx.w #$005F + rom.write_bytes(INIT_SRAM_ADDR + 0x007A, bytearray([0xA9, 0x00])) # lda #$00 + rom.write_bytes(INIT_SRAM_ADDR + 0x007C, bytearray([0x9F, 0x00, 0x08, 0x70])) # - sta !level_clears_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0080, bytearray([0xCA])) # dex + rom.write_bytes(INIT_SRAM_ADDR + 0x0081, bytearray([0x10, 0xF9])) # bpl - + rom.write_bytes(INIT_SRAM_ADDR + 0x0083, bytearray([0xA2, 0x0B, 0x00])) # ldx.w #$000B + rom.write_bytes(INIT_SRAM_ADDR + 0x0086, bytearray([0x9F, 0x40, 0x09, 0x70])) # - sta !blocksanity_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x008A, bytearray([0x9F, 0x00, 0x09, 0x70])) # sta !yoshi_coins_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x008E, bytearray([0x9F, 0x30, 0x09, 0x70])) # sta !bonus_block_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0092, bytearray([0x9F, 0x10, 0x09, 0x70])) # sta !moons_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x0096, bytearray([0x9F, 0x20, 0x09, 0x70])) # sta !checkpoints_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x009A, bytearray([0xCA])) # dex + rom.write_bytes(INIT_SRAM_ADDR + 0x009B, bytearray([0x10, 0xE9])) # bpl - + rom.write_bytes(INIT_SRAM_ADDR + 0x009D, bytearray([0xA2, 0x45, 0x02])) # ldx.w #!blocksanity_locs-1 + rom.write_bytes(INIT_SRAM_ADDR + 0x00A0, bytearray([0x9F, 0x00, 0x0A, 0x70])) # - sta !blocksanity_data_sram,x + rom.write_bytes(INIT_SRAM_ADDR + 0x00A4, bytearray([0xCA])) # dex + rom.write_bytes(INIT_SRAM_ADDR + 0x00A5, bytearray([0x10, 0xF9])) # bpl - + rom.write_bytes(INIT_SRAM_ADDR + 0x00A7, bytearray([0x8F, 0x52, 0x09, 0x70])) # sta !special_world_clear_sram + rom.write_bytes(INIT_SRAM_ADDR + 0x00AB, bytearray([0x8F, 0x50, 0x09, 0x70])) # sta !received_items_count_sram+$00 + rom.write_bytes(INIT_SRAM_ADDR + 0x00AF, bytearray([0x8F, 0x51, 0x09, 0x70])) # sta !received_items_count_sram+$01 + rom.write_bytes(INIT_SRAM_ADDR + 0x00B3, bytearray([0x8F, 0x54, 0x09, 0x70])) # sta !goal_item_count_sram + rom.write_bytes(INIT_SRAM_ADDR + 0x00B7, bytearray([0xFA])) # plx + rom.write_bytes(INIT_SRAM_ADDR + 0x00B8, bytearray([0x5C, 0x22, 0x9D, 0x00])) # jml $009D22 + + SAVE_SRAM_ADDR = 0x7F300 + rom.write_bytes(SAVE_SRAM_ADDR + 0x0000, bytearray([0xE2, 0x30])) # save_sram: sep #$30 + rom.write_bytes(SAVE_SRAM_ADDR + 0x0002, bytearray([0xAB])) # plb + rom.write_bytes(SAVE_SRAM_ADDR + 0x0003, bytearray([0xA2, 0x5F])) # ldx.b #$5F + rom.write_bytes(SAVE_SRAM_ADDR + 0x0005, bytearray([0xBF, 0x00, 0xA2, 0x7F])) # - lda !level_clears,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0009, bytearray([0x9F, 0x00, 0x08, 0x70])) # sta !level_clears_sram,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x000D, bytearray([0xCA])) # dex + rom.write_bytes(SAVE_SRAM_ADDR + 0x000E, bytearray([0x10, 0xF5])) # bpl - + rom.write_bytes(SAVE_SRAM_ADDR + 0x0010, bytearray([0xA2, 0x0B])) # ldx #$0B + rom.write_bytes(SAVE_SRAM_ADDR + 0x0012, bytearray([0xBF, 0x10, 0xA0, 0x7F])) # - lda !blocksanity_flags,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0016, bytearray([0x9F, 0x40, 0x09, 0x70])) # sta !blocksanity_sram,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x001A, bytearray([0xBD, 0x2F, 0x1F])) # lda !yoshi_coins_flags,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x001D, bytearray([0x9F, 0x00, 0x09, 0x70])) # sta !yoshi_coins_sram,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0021, bytearray([0xBD, 0xEE, 0x1F])) # lda !moons_flags,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0024, bytearray([0x9F, 0x10, 0x09, 0x70])) # sta !moons_sram,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0028, bytearray([0xBF, 0x00, 0xA0, 0x7F])) # lda !bonus_block_flags,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x002C, bytearray([0x9F, 0x30, 0x09, 0x70])) # sta !bonus_block_sram,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0030, bytearray([0xBD, 0x3C, 0x1F])) # lda !checkpoints_flags,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0033, bytearray([0x9F, 0x20, 0x09, 0x70])) # sta !checkpoints_sram,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0037, bytearray([0xCA])) # dex + rom.write_bytes(SAVE_SRAM_ADDR + 0x0038, bytearray([0x10, 0xD8])) # bpl - + rom.write_bytes(SAVE_SRAM_ADDR + 0x003A, bytearray([0xC2, 0x10])) # rep #$10 + rom.write_bytes(SAVE_SRAM_ADDR + 0x003C, bytearray([0xA2, 0x45, 0x02])) # ldx.w #!blocksanity_locs-1 + rom.write_bytes(SAVE_SRAM_ADDR + 0x003F, bytearray([0xBF, 0x00, 0xA4, 0x7F])) # - lda !blocksanity_data_flags,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0043, bytearray([0x9F, 0x00, 0x0A, 0x70])) # sta !blocksanity_data_sram,x + rom.write_bytes(SAVE_SRAM_ADDR + 0x0047, bytearray([0xCA])) # dex + rom.write_bytes(SAVE_SRAM_ADDR + 0x0048, bytearray([0x10, 0xF5])) # bpl - + rom.write_bytes(SAVE_SRAM_ADDR + 0x004A, bytearray([0xE2, 0x10])) # sep #$10 + #rom.write_bytes(SAVE_SRAM_ADDR + 0x004C, bytearray([0xAD, 0xFF, 0x1F])) # lda !special_world_clear_flag + #rom.write_bytes(SAVE_SRAM_ADDR + 0x004F, bytearray([0x8F, 0x52, 0x09, 0x70])) # sta !special_world_clear_sram + #rom.write_bytes(SAVE_SRAM_ADDR + 0x0053, bytearray([0xAF, 0x0E, 0xA0, 0x7F])) # lda !received_items_count+$00 + #rom.write_bytes(SAVE_SRAM_ADDR + 0x0057, bytearray([0x8F, 0x50, 0x09, 0x70])) # sta !received_items_count_sram+$00 + #rom.write_bytes(SAVE_SRAM_ADDR + 0x005B, bytearray([0xAF, 0x0F, 0xA0, 0x7F])) # lda !received_items_count+$01 + #rom.write_bytes(SAVE_SRAM_ADDR + 0x005F, bytearray([0x8F, 0x51, 0x09, 0x70])) # sta !received_items_count_sram+$01 + rom.write_bytes(SAVE_SRAM_ADDR + 0x004C, bytearray([0xEA] * 0x17)) # Ugly, will apply be better when we port everything to a Base Patch + rom.write_bytes(SAVE_SRAM_ADDR + 0x0063, bytearray([0xAF, 0x0F, 0xA0, 0x7F])) # lda !goal_item_count + rom.write_bytes(SAVE_SRAM_ADDR + 0x0067, bytearray([0x8F, 0x51, 0x09, 0x70])) # sta !goal_item_count_sram + rom.write_bytes(SAVE_SRAM_ADDR + 0x006B, bytearray([0x6B])) # rtl + + INIT_RAM_ADDR = 0x7F0A0 + rom.write_bytes(INIT_RAM_ADDR + 0x0000, bytearray([0xA9, 0xAA])) # init_ram: lda #$AA + rom.write_bytes(INIT_RAM_ADDR + 0x0002, bytearray([0x8D, 0x00, 0x04])) # sta $0400 + rom.write_bytes(INIT_RAM_ADDR + 0x0005, bytearray([0xA9, 0x00])) # clear_level_data: lda #$00 + rom.write_bytes(INIT_RAM_ADDR + 0x0007, bytearray([0xA2, 0x5F])) # ldx #$5F + rom.write_bytes(INIT_RAM_ADDR + 0x0009, bytearray([0x9F, 0x00, 0xA2, 0x7F])) # .loop sta !level_clears,x + rom.write_bytes(INIT_RAM_ADDR + 0x000D, bytearray([0xCA])) # dex + rom.write_bytes(INIT_RAM_ADDR + 0x000E, bytearray([0x10, 0xF9])) # bpl .loop + rom.write_bytes(INIT_RAM_ADDR + 0x0010, bytearray([0xC2, 0x10])) # rep #$10 + rom.write_bytes(INIT_RAM_ADDR + 0x0012, bytearray([0xA2, 0x0B, 0x00])) # ldx.w #$000B + rom.write_bytes(INIT_RAM_ADDR + 0x0015, bytearray([0x9F, 0x10, 0xA0, 0x7F])) # - sta !blocksanity_flags,x + rom.write_bytes(INIT_RAM_ADDR + 0x0019, bytearray([0x9D, 0x2F, 0x1F])) # sta !yoshi_coins_flags,x + rom.write_bytes(INIT_RAM_ADDR + 0x001C, bytearray([0x9D, 0xEE, 0x1F])) # sta !moons_flags,x + rom.write_bytes(INIT_RAM_ADDR + 0x001F, bytearray([0x9F, 0x00, 0xA0, 0x7F])) # sta !bonus_block_flags,x + rom.write_bytes(INIT_RAM_ADDR + 0x0023, bytearray([0x9D, 0x3C, 0x1F])) # sta !checkpoints_flags,x + rom.write_bytes(INIT_RAM_ADDR + 0x0026, bytearray([0xCA])) # dex + rom.write_bytes(INIT_RAM_ADDR + 0x0027, bytearray([0x10, 0xEC])) # bpl - + rom.write_bytes(INIT_RAM_ADDR + 0x0029, bytearray([0xA2, 0x45, 0x02])) # ldx.w #!blocksanity_locs-1 + rom.write_bytes(INIT_RAM_ADDR + 0x002C, bytearray([0x9F, 0x00, 0xA4, 0x7F])) # - sta !blocksanity_data_flags,x + rom.write_bytes(INIT_RAM_ADDR + 0x0030, bytearray([0xCA])) # dex + rom.write_bytes(INIT_RAM_ADDR + 0x0031, bytearray([0x10, 0xF9])) # bpl - + rom.write_bytes(INIT_RAM_ADDR + 0x0033, bytearray([0xA2, 0x22, 0x04])) # ldx #$0422 + rom.write_bytes(INIT_RAM_ADDR + 0x0036, bytearray([0x9F, 0x00, 0xB0, 0x7F])) # - sta !score_sprite_count,x + rom.write_bytes(INIT_RAM_ADDR + 0x003A, bytearray([0xCA])) # dex + rom.write_bytes(INIT_RAM_ADDR + 0x003B, bytearray([0x10, 0xF9])) # bpl - + #rom.write_bytes(INIT_RAM_ADDR + 0x003D, bytearray([0x8D, 0xFF, 0x1F])) # sta !special_world_clear_flag + rom.write_bytes(INIT_RAM_ADDR + 0x003D, bytearray([0xEA, 0xEA, 0xEA])) # sta !special_world_clear_flag + rom.write_bytes(INIT_RAM_ADDR + 0x0040, bytearray([0x8F, 0x0E, 0xA0, 0x7F])) # sta !received_items_count+$00 + rom.write_bytes(INIT_RAM_ADDR + 0x0044, bytearray([0x8F, 0x0F, 0xA0, 0x7F])) # sta !received_items_count+$01 + rom.write_bytes(INIT_RAM_ADDR + 0x0048, bytearray([0x8F, 0x1E, 0xA0, 0x7F])) # sta !goal_item_count + rom.write_bytes(INIT_RAM_ADDR + 0x004C, bytearray([0xA9, 0xFF])) # lda #$FF + rom.write_bytes(INIT_RAM_ADDR + 0x004E, bytearray([0x8D, 0x3C, 0x0F])) # sta !thwimp_index + rom.write_bytes(INIT_RAM_ADDR + 0x0051, bytearray([0xE2, 0x10])) # sep #$10 + rom.write_bytes(INIT_RAM_ADDR + 0x0053, bytearray([0x22, 0x20, 0xF1, 0x0F])) # jsl clear_tilemap + rom.write_bytes(INIT_RAM_ADDR + 0x0057, bytearray([0x5C, 0xC0, 0x93, 0x00])) # jml $0093C0 + +def handle_map_indicators(rom): + rom.write_bytes(0x265EE, bytearray([0x4C, 0x00, 0xA3])) # org $04E5EE : jmp check_events + + GET_MAP_LEVEL_NUM_ADDR = 0x22340 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0000, bytearray([0xC2, 0x30])) # get_translevel_num: rep #$30 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0002, bytearray([0xAE, 0xD6, 0x0D])) # ldx $0DD6 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0005, bytearray([0xBD, 0x1F, 0x1F])) # lda $1F1F,x + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0008, bytearray([0x85, 0x00])) # sta $00 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x000A, bytearray([0xBD, 0x21, 0x1F])) # lda $1F21,x + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x000D, bytearray([0x85, 0x02])) # sta $02 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x000F, bytearray([0x8A])) # txa + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0010, bytearray([0x4A])) # lsr + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0011, bytearray([0x4A])) # lsr + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0012, bytearray([0xAA])) # tax + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0013, bytearray([0x20, 0x85, 0x98])) # jsr $9885 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0016, bytearray([0xA6, 0x04])) # ldx $04 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0018, bytearray([0xBF, 0x00, 0xD0, 0x7E])) # lda $7ED000,x + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x001C, bytearray([0xE2, 0x30])) # sep #$30 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x001E, bytearray([0x85, 0x60])) # sta $60 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0020, bytearray([0xAA])) # tax + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0021, bytearray([0xBF, 0x00, 0xFF, 0x06])) # lda $06FF00,x + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0025, bytearray([0xC9, 0xFF])) # cmp #$FF + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0027, bytearray([0xF0, 0x02])) # beq + + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x0029, bytearray([0x85, 0x60])) # sta $60 + rom.write_bytes(GET_MAP_LEVEL_NUM_ADDR + 0x002B, bytearray([0x60])) # + rts + + GET_MAP_LEVEL_BIT_ADDR = 0x22380 + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x0000, bytearray([0xA5, 0x60])) # get_translevel_bit: lda $60 + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x0002, bytearray([0x4A])) # lsr + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x0003, bytearray([0x4A])) # lsr + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x0004, bytearray([0x4A])) # lsr + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x0005, bytearray([0xA8])) # tay + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x0006, bytearray([0xA5, 0x60])) # lda $60 + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x0008, bytearray([0x29, 0x07])) # and #$07 + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x000A, bytearray([0xAA])) # tax + rom.write_bytes(GET_MAP_LEVEL_BIT_ADDR + 0x000B, bytearray([0x60])) # rts + + UPDATE_MAP_PTRS_ADDR = 0x223C0 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x0000, bytearray([0xE6, 0x00])) # update_flag_pointers: inc $00 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x0002, bytearray([0xE6, 0x00])) # inc $00 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x0004, bytearray([0xE6, 0x03])) # inc $03 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x0006, bytearray([0xE6, 0x03])) # inc $03 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x0008, bytearray([0xE6, 0x06])) # inc $06 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x000A, bytearray([0xE6, 0x06])) # inc $06 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x000C, bytearray([0xE6, 0x62])) # inc $62 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x000E, bytearray([0xE6, 0x62])) # inc $62 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x0010, bytearray([0xE6, 0x63])) # inc $63 + rom.write_bytes(UPDATE_MAP_PTRS_ADDR + 0x0012, bytearray([0x60])) # rts + + CLEAR_TILEMAP_ADDR = 0x7F120 + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0000, bytearray([0xC2, 0x20])) # clear_tilemap: rep #$20 + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0002, bytearray([0xA9, 0x1F, 0x39])) # lda.w #$3900+!icon_disabled + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0005, bytearray([0xA2, 0x1E])) # ldx #$1E + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0007, bytearray([0x9F, 0x20, 0xA1, 0x7F])) # .loop sta !ow_tilemap_switches,x + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x000B, bytearray([0x9F, 0x00, 0xA1, 0x7F])) # sta !ow_tilemap_abilities,x + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x000F, bytearray([0x9F, 0x40, 0xA1, 0x7F])) # sta !ow_tilemap_flags_top,x + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0013, bytearray([0x9F, 0x60, 0xA1, 0x7F])) # sta !ow_tilemap_flags_mid,x + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0017, bytearray([0x9F, 0x80, 0xA1, 0x7F])) # sta !ow_tilemap_flags_bot,x + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x001B, bytearray([0xCA])) # dex + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x001C, bytearray([0xCA])) # dex + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x001D, bytearray([0x10, 0xE8])) # bpl .loop + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x001F, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0021, bytearray([0xA9, 0x07])) # lda #$07 + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0023, bytearray([0x85, 0x63])) # sta $63 + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0025, bytearray([0x0A])) # asl + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0026, bytearray([0x85, 0x62])) # sta $62 + rom.write_bytes(CLEAR_TILEMAP_ADDR + 0x0028, bytearray([0x6B])) # rtl + + CLEAR_TILEMAP_FLAGS_ADDR = 0x7F180 + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0000, bytearray([0xC2, 0x20])) # clear_tilemap_flags: rep #$20 + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0002, bytearray([0xA9, 0x1F, 0x39])) # lda.w #$3900+!icon_disabled + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0005, bytearray([0xA2, 0x0C])) # ldx.b #($07*2)-2 + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0007, bytearray([0x9F, 0x40, 0xA1, 0x7F])) # .loop sta !ow_tilemap_flags_top,x + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x000B, bytearray([0x9F, 0x60, 0xA1, 0x7F])) # sta !ow_tilemap_flags_mid,x + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x000F, bytearray([0x9F, 0x80, 0xA1, 0x7F])) # sta !ow_tilemap_flags_bot,x + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0013, bytearray([0xCA])) # dex + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0014, bytearray([0xCA])) # dex + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0015, bytearray([0x10, 0xF0])) # bpl .loop + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0017, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0019, bytearray([0xA9, 0x06])) # lda #$06 + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x001B, bytearray([0x85, 0x63])) # sta $63 + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x001D, bytearray([0x0A])) # asl + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x001E, bytearray([0x85, 0x62])) # sta $62 + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0020, bytearray([0xA9, 0xFF])) # lda #$FF + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0022, bytearray([0x8D, 0x3C, 0x0F])) # sta !thwimp_index + rom.write_bytes(CLEAR_TILEMAP_FLAGS_ADDR + 0x0025, bytearray([0x6B])) # rtl + + CHECK_EVENTS_ADDR = 0x22300 + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0000, bytearray([0xDA])) # check_events: phx + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0001, bytearray([0x20, 0x40, 0xA3])) # jsr get_translevel_num + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0004, bytearray([0xAD, 0xD5, 0x0D])) # lda $0DD5 + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0007, bytearray([0xF0, 0x17])) # beq .dont_sync + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0009, bytearray([0x30, 0x15])) # bmi .dont_sync + rom.write_bytes(CHECK_EVENTS_ADDR + 0x000B, bytearray([0xC9, 0x05])) # cmp #$05 + rom.write_bytes(CHECK_EVENTS_ADDR + 0x000D, bytearray([0xB0, 0x11])) # bcs .dont_sync + rom.write_bytes(CHECK_EVENTS_ADDR + 0x000F, bytearray([0x29, 0x07])) # and #$07 + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0011, bytearray([0xAA])) # tax + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0012, bytearray([0xBF, 0x7D, 0x9E, 0x00])) # lda.l $009E7D,x + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0016, bytearray([0xA6, 0x60])) # ldx $60 + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0018, bytearray([0x1F, 0x00, 0xA2, 0x7F])) # ora !level_clears,x + rom.write_bytes(CHECK_EVENTS_ADDR + 0x001C, bytearray([0x9F, 0x00, 0xA2, 0x7F])) # sta !level_clears,x + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0020, bytearray([0xFA])) # .dont_sync plx + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0021, bytearray([0xAD, 0xD5, 0x0D])) # lda $0DD5 + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0024, bytearray([0xC9, 0x02])) # cmp #$02 + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0026, bytearray([0xD0, 0x03])) # bne .no_secret + rom.write_bytes(CHECK_EVENTS_ADDR + 0x0028, bytearray([0xEE, 0xEA, 0x1D])) # inc $1DEA + rom.write_bytes(CHECK_EVENTS_ADDR + 0x002B, bytearray([0x4C, 0xF8, 0xE5])) # .no_secret jmp $E5F8 + + DRAW_MAP_TILEMAP_ADDR = 0x221B6 + rom.write_bytes(0x00222, bytearray([0x5C, 0xB6, 0xA1, 0x04])) # org $008222 : jml draw_ow_tilemap + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0000, bytearray([0xAD, 0xD9, 0x13])) # draw_ow_tilemap: lda $13D9 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0003, bytearray([0xC9, 0x0A])) # cmp #$0A + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0005, bytearray([0xD0, 0x04])) # bne write_tilemap + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0007, bytearray([0x5C, 0x29, 0x82, 0x00])) # jml $008229 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x000B, bytearray([0xC2, 0x20])) # write_tilemap: rep #$20 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x000D, bytearray([0xA0, 0x80])) # ldy #$80 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x000F, bytearray([0x8C, 0x15, 0x21])) # sty $2115 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0012, bytearray([0xA9, 0x27, 0x50])) # write_abilities: lda #!vram_abilities_top + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0015, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0018, bytearray([0xA2, 0x00])) # ldx.b #$00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x001A, bytearray([0xBF, 0xA2, 0xA2, 0x04])) # ..loop lda.l abilities_top,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x001E, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0021, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0022, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0023, bytearray([0xE0, 0x14])) # cpx.b #$0A*2 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0025, bytearray([0x90, 0xF3])) # bcc ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0027, bytearray([0xA9, 0x47, 0x50])) # .mid lda #!vram_abilities_mid + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x002A, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x002D, bytearray([0xA2, 0x00])) # ldx.b #$00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x002F, bytearray([0xBF, 0xB6, 0xA2, 0x04])) # ..loop lda.l abilities_bottom,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0033, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0036, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0037, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0038, bytearray([0xE0, 0x14])) # cpx.b #$0A*2 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x003A, bytearray([0x90, 0xF3])) # bcc ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x003C, bytearray([0xA9, 0x67, 0x50])) # .bot lda #!vram_abilities_bot + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x003F, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0042, bytearray([0xA2, 0x00])) # ldx.b #$00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0044, bytearray([0xBF, 0x00, 0xA1, 0x7F])) # ..loop lda !ow_tilemap_abilities,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0048, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x004B, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x004C, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x004D, bytearray([0xE0, 0x14])) # cpx.b #$0A*2 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x004F, bytearray([0x90, 0xF3])) # bcc ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0051, bytearray([0xA9, 0x32, 0x50])) # write_switches: lda #!vram_switches_top + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0054, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0057, bytearray([0xA2, 0x00])) # ldx.b #$00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0059, bytearray([0xBF, 0xCA, 0xA2, 0x04])) # ..loop lda.l switches_top,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x005D, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0060, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0061, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0062, bytearray([0xE0, 0x0A])) # cpx.b #$05*2 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0064, bytearray([0x90, 0xF3])) # bcc ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0066, bytearray([0xA9, 0x52, 0x50])) # .mid lda #!vram_switches_mid + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0069, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x006C, bytearray([0xA2, 0x00])) # ldx.b #$00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x006E, bytearray([0xBF, 0xD4, 0xA2, 0x04])) # ..loop lda.l switches_bottom,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0072, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0075, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0076, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0077, bytearray([0xE0, 0x0A])) # cpx.b #$05*2 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0079, bytearray([0x90, 0xF3])) # bcc ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x007B, bytearray([0xA9, 0x72, 0x50])) # .bot lda #!vram_switches_bot + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x007E, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0081, bytearray([0xA2, 0x00])) # ldx.b #$00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0083, bytearray([0xBF, 0x20, 0xA1, 0x7F])) # ..loop lda !ow_tilemap_switches,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0087, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x008A, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x008B, bytearray([0xE8])) # inx + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x008C, bytearray([0xE0, 0x0A])) # cpx.b #$05*2 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x008E, bytearray([0x90, 0xF3])) # bcc ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0090, bytearray([0xD4, 0x00])) # write_level_data: pei ($00) + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0092, bytearray([0xA5, 0x63])) # lda $63 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0094, bytearray([0x29, 0xFF, 0x00])) # and #$00FF + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0097, bytearray([0x85, 0x00])) # sta $00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0099, bytearray([0xF0, 0x48])) # beq .skip_flags + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x009B, bytearray([0xA9, 0x3E, 0x50])) # .top lda.w #!vram_level_data_top+$01 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x009E, bytearray([0x38])) # sec + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x009F, bytearray([0xE5, 0x00])) # sbc $00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00A1, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00A4, bytearray([0xA6, 0x62])) # ldx.b $62 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00A6, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00A7, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00A8, bytearray([0xBF, 0x40, 0xA1, 0x7F])) # ..loop lda.l !ow_tilemap_flags_top,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00AC, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00AF, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00B0, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00B1, bytearray([0x10, 0xF5])) # bpl ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00B3, bytearray([0xA9, 0x5E, 0x50])) # .mid lda.w #!vram_level_data_mid+$01 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00B6, bytearray([0x38])) # sec + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00B7, bytearray([0xE5, 0x00])) # sbc $00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00B9, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00BC, bytearray([0xA6, 0x62])) # ldx.b $62 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00BE, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00BF, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00C0, bytearray([0xBF, 0x60, 0xA1, 0x7F])) # ..loop lda.l !ow_tilemap_flags_mid,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00C4, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00C7, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00C8, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00C9, bytearray([0x10, 0xF5])) # bpl ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00CB, bytearray([0xA9, 0x7E, 0x50])) # .bot lda.w #!vram_level_data_bot+$01 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00CE, bytearray([0x38])) # sec + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00CF, bytearray([0xE5, 0x00])) # sbc $00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00D1, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00D4, bytearray([0xA6, 0x62])) # ldx.b $62 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00D6, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00D7, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00D8, bytearray([0xBF, 0x80, 0xA1, 0x7F])) # ..loop lda.l !ow_tilemap_flags_bot,x + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00DC, bytearray([0x8D, 0x18, 0x21])) # sta $2118 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00DF, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00E0, bytearray([0xCA])) # dex + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00E1, bytearray([0x10, 0xF5])) # bpl ..loop + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00E3, bytearray([0x68])) # .skip_flags pla + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00E4, bytearray([0x85, 0x00])) # sta $00 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00E6, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00E8, bytearray([0x5C, 0x37, 0x82, 0x00])) # jml $008237 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00EC, bytearray([0x0F, 0x39, 0x12, 0x39])) # abilities_top: dw $390F,$3912 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00F0, bytearray([0x11, 0x39, 0x02, 0x39])) # dw $3911,$3902 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00F4, bytearray([0x12, 0x39, 0x02, 0x39])) # dw $3912,$3902 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00F8, bytearray([0x18, 0x39, 0x0F, 0x39])) # dw $3918,$390F + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x00FC, bytearray([0x0F, 0x39, 0x12, 0x39])) # dw $390F,$3912 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0100, bytearray([0x4E, 0x39, 0x4F, 0x39])) # abilities_bottom: dw $394E,$394F + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0104, bytearray([0x54, 0x39, 0x40, 0x39])) # dw $3954,$3940 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0108, bytearray([0x56, 0x39, 0x4B, 0x39])) # dw $3956,$394B + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x010C, bytearray([0x4E, 0x39, 0x52, 0x39])) # dw $394E,$3952 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0110, bytearray([0x41, 0x39, 0x53, 0x39])) # dw $3941,$3953 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0114, bytearray([0x18, 0x39, 0x06, 0x39])) # switches_top: dw $3918,$3906 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0118, bytearray([0x11, 0x39, 0x01, 0x39])) # dw $3911,$3901 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x011C, bytearray([0x12, 0x39])) # dw $3912 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x011E, bytearray([0x12, 0x39, 0x12, 0x39])) # switches_bottom: dw $3912,$3912 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0122, bytearray([0x12, 0x39, 0x12, 0x39])) # dw $3912,$3912 + rom.write_bytes(DRAW_MAP_TILEMAP_ADDR + 0x0126, bytearray([0x4F, 0x39])) # dw $394F + + BUILD_TILEMAP_ADDR = 0x26F3E + rom.write_bytes(0x021C7, bytearray([0x22, 0x3E, 0xEF, 0x04])) # org $00A1C7 : jsl prepare_dynamic_tilemap + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0000, bytearray([0x22, 0x41, 0x82, 0x04])) # prepare_dynamic_tilemap: jsl $048241 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0004, bytearray([0xA0, 0x22])) # .handle_powerup: ldy #$22 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0006, bytearray([0xAD, 0x2D, 0x1F])) # lda $1F2D + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0009, bytearray([0x4A])) # lsr + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x000A, bytearray([0x90, 0x01])) # bcc $01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x000C, bytearray([0xC8])) # iny + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x000D, bytearray([0x4A])) # lsr + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x000E, bytearray([0x90, 0x01])) # bcc $01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0010, bytearray([0xC8])) # iny + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0011, bytearray([0x4A])) # lsr + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0012, bytearray([0x90, 0x01])) # bcc $01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0014, bytearray([0xC8])) # iny + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0015, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0016, bytearray([0x8F, 0x00, 0xA1, 0x7F])) # sta !ow_tilemap_abilities ; Progressive powerup + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x001A, bytearray([0xA0, 0x5E])) # .handle_spinjump: ldy #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x001C, bytearray([0xAD, 0x1C, 0x1F])) # lda $1F1C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x001F, bytearray([0x29, 0x08])) # and #$08 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0021, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0023, bytearray([0xA0, 0x3F])) # ldy #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0025, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0026, bytearray([0x8F, 0x02, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$02 ; Spin jump + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x002A, bytearray([0xA0, 0x5E])) # .handle_run: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x002C, bytearray([0xAD, 0x1C, 0x1F])) # lda $1F1C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x002F, bytearray([0x29, 0x80])) # and #$80 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0031, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0033, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0035, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0036, bytearray([0x8F, 0x04, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$04 ; Run + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x003A, bytearray([0xA0, 0x5E])) # .handle_carry: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x003C, bytearray([0xAD, 0x1C, 0x1F])) # lda $1F1C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x003F, bytearray([0x29, 0x40])) # and #$40 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0041, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0043, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0045, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0046, bytearray([0x8F, 0x06, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$06 ; Carry + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x004A, bytearray([0xA0, 0x5E])) # .handle_swim: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x004C, bytearray([0xAD, 0x1C, 0x1F])) # lda $1F1C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x004F, bytearray([0x29, 0x04])) # and #$04 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0051, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0053, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0055, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0056, bytearray([0x8F, 0x08, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$08 ; Swim + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x005A, bytearray([0xA0, 0x5E])) # .handle_climb: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x005C, bytearray([0xAD, 0x1C, 0x1F])) # lda $1F1C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x005F, bytearray([0x29, 0x20])) # and #$20 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0061, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0063, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0065, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0066, bytearray([0x8F, 0x0A, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$0A ; Climb + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x006A, bytearray([0xA0, 0x5E])) # .handle_yoshi: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x006C, bytearray([0xAD, 0x1C, 0x1F])) # lda $1F1C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x006F, bytearray([0x29, 0x02])) # and #$02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0071, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0073, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0075, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0076, bytearray([0x8F, 0x0C, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$0C ; Yoshi + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x007A, bytearray([0xA0, 0x5E])) # .handle_pswitch: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x007C, bytearray([0xAD, 0x1C, 0x1F])) # lda $1F1C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x007F, bytearray([0x29, 0x10])) # and #$10 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0081, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0083, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0085, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0086, bytearray([0x8F, 0x0E, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$0E ; P-Switch + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x008A, bytearray([0xA0, 0x5E])) # .handle_pballoon: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x008C, bytearray([0xAD, 0x2D, 0x1F])) # lda $1F2D + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x008F, bytearray([0x29, 0x08])) # and #$08 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0091, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0093, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0095, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0096, bytearray([0x8F, 0x10, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$10 ; P-Balloon + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x009A, bytearray([0xA0, 0x5E])) # .handle_star: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x009C, bytearray([0xAD, 0x2D, 0x1F])) # lda $1F2D + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x009F, bytearray([0x29, 0x10])) # and #$10 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00A1, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00A3, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00A5, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00A6, bytearray([0x8F, 0x12, 0xA1, 0x7F])) # sta !ow_tilemap_abilities+$12 ; Star + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00AA, bytearray([0xA0, 0x5E])) # .handle_yellow_switch: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00AC, bytearray([0xAD, 0x28, 0x1F])) # lda $1F28 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00AF, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00B1, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00B3, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00B4, bytearray([0x8F, 0x20, 0xA1, 0x7F])) # sta !ow_tilemap_switches+$00 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00B8, bytearray([0xA0, 0x5E])) # .handle_green_switch: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00BA, bytearray([0xAD, 0x27, 0x1F])) # lda $1F27 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00BD, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00BF, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00C1, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00C2, bytearray([0x8F, 0x22, 0xA1, 0x7F])) # sta !ow_tilemap_switches+$02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00C6, bytearray([0xA0, 0x5E])) # .handle_red_switch: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00C8, bytearray([0xAD, 0x2A, 0x1F])) # lda $1F2A + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00CB, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00CD, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00CF, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00D0, bytearray([0x8F, 0x24, 0xA1, 0x7F])) # sta !ow_tilemap_switches+$04 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00D4, bytearray([0xA0, 0x5E])) # .handle_blue_switch: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00D6, bytearray([0xAD, 0x29, 0x1F])) # lda $1F29 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00D9, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00DB, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00DD, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00DE, bytearray([0x8F, 0x26, 0xA1, 0x7F])) # sta !ow_tilemap_switches+$06 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00E2, bytearray([0xA0, 0x5E])) # .handle_special_world_clear: ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00E4, bytearray([0xAD, 0x1E, 0x1F])) # lda !special_world_clear_flag + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00E7, bytearray([0xF0, 0x02])) # beq $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00E9, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00EB, bytearray([0x98])) # tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00EC, bytearray([0x8F, 0x28, 0xA1, 0x7F])) # sta !ow_tilemap_switches+$08 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00F0, bytearray([0x22, 0x80, 0xF1, 0x0F])) # jsl clear_tilemap_flags + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00F4, bytearray([0xAD, 0xD9, 0x13])) # lda $13D9 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00F7, bytearray([0xC9, 0x01])) # cmp #$01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00F9, bytearray([0xF0, 0x05])) # beq process_level + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00FB, bytearray([0xC9, 0x03])) # cmp #$03 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00FD, bytearray([0xF0, 0x01])) # beq process_level + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x00FF, bytearray([0x6B])) # rtl + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0100, bytearray([0x20, 0x40, 0xA3])) # process_level: jsr get_translevel_num + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0103, bytearray([0xA6, 0x60])) # ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0105, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0109, bytearray([0x10, 0x01])) # bpl .handle_data + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x010B, bytearray([0x6B])) # rtl + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x010C, bytearray([0x64, 0x62])) # .handle_data stz $62 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x010E, bytearray([0x64, 0x63])) # stz $63 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0110, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0112, bytearray([0xA9, 0x40, 0xA1])) # lda.w #!ow_tilemap_flags_top + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0115, bytearray([0x85, 0x00])) # sta $00 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0117, bytearray([0xA9, 0x60, 0xA1])) # lda.w #!ow_tilemap_flags_mid + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x011A, bytearray([0x85, 0x03])) # sta $03 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x011C, bytearray([0xA9, 0x80, 0xA1])) # lda.w #!ow_tilemap_flags_bot + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x011F, bytearray([0x85, 0x06])) # sta $06 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0121, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0123, bytearray([0xA9, 0x7F])) # lda.b #!ow_tilemap_flags_top>>16 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0125, bytearray([0x85, 0x02])) # sta $02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0127, bytearray([0x85, 0x05])) # sta $05 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0129, bytearray([0x85, 0x08])) # sta $08 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x012B, bytearray([0xAF, 0xAB, 0xBF, 0x03])) # handle_blocksanity: lda.l blocksanity_enabled_flag + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x012F, bytearray([0xF0, 0x30])) # beq handle_bonus_blocks + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0131, bytearray([0xA6, 0x60])) # ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0133, bytearray([0xA0, 0x1F])) # ldy.b #!icon_disabled + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0135, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0139, bytearray([0x29, 0x40])) # and #$40 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x013B, bytearray([0xF0, 0x24])) # beq handle_bonus_blocks + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x013D, bytearray([0xA0, 0x5E])) # ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x013F, bytearray([0x5A])) # phy + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0140, bytearray([0x20, 0x80, 0xA3])) # jsr get_translevel_bit + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0143, bytearray([0xDA])) # phx + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0144, bytearray([0xBB])) # tyx + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0145, bytearray([0xBF, 0x10, 0xA0, 0x7F])) # lda.l !blocksanity_flags,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0149, bytearray([0xFA])) # plx + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x014A, bytearray([0x7A])) # ply + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x014B, bytearray([0x3F, 0xA6, 0xA8, 0x0D])) # and.l $0DA8A6,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x014F, bytearray([0xF0, 0x02])) # beq .write + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0151, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0153, bytearray([0x98])) # .write tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0154, bytearray([0x87, 0x06])) # sta [$06] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0156, bytearray([0xA9, 0x01])) # lda #$01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0158, bytearray([0x87, 0x00])) # sta [$00] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x015A, bytearray([0xA9, 0x12])) # lda #$12 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x015C, bytearray([0x87, 0x03])) # sta [$03] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x015E, bytearray([0x20, 0xC0, 0xA3])) # jsr update_flag_pointers + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0161, bytearray([0xAF, 0xAA, 0xBF, 0x03])) # handle_bonus_blocks: lda.l bonus_block_enabled_flag + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0165, bytearray([0xF0, 0x30])) # beq handle_checkpoints + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0167, bytearray([0xA6, 0x60])) # ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0169, bytearray([0xA0, 0x1F])) # ldy.b #!icon_disabled + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x016B, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x016F, bytearray([0x29, 0x20])) # and #$20 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0171, bytearray([0xF0, 0x24])) # beq handle_checkpoints + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0173, bytearray([0xA0, 0x5E])) # ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0175, bytearray([0x5A])) # phy + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0176, bytearray([0x20, 0x80, 0xA3])) # jsr get_translevel_bit + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0179, bytearray([0xDA])) # phx + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x017A, bytearray([0xBB])) # tyx + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x017B, bytearray([0xBF, 0x00, 0xA0, 0x7F])) # lda !bonus_block_flags,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x017F, bytearray([0xFA])) # plx + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0180, bytearray([0x7A])) # ply + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0181, bytearray([0x3F, 0xA6, 0xA8, 0x0D])) # and.l $0DA8A6,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0185, bytearray([0xF0, 0x02])) # beq .write + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0187, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0189, bytearray([0x98])) # .write tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x018A, bytearray([0x87, 0x06])) # sta [$06] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x018C, bytearray([0xA9, 0x01])) # lda #$01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x018E, bytearray([0x87, 0x00])) # sta [$00] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0190, bytearray([0xA9, 0x4E])) # lda #$4E + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0192, bytearray([0x87, 0x03])) # sta [$03] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0194, bytearray([0x20, 0xC0, 0xA3])) # jsr update_flag_pointers + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0197, bytearray([0xAF, 0xA9, 0xBF, 0x03])) # handle_checkpoints: lda.l checkpoints_enabled_flag + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x019B, bytearray([0xF0, 0x2A])) # beq handle_moons + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x019D, bytearray([0xA6, 0x60])) # ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x019F, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01A3, bytearray([0x29, 0x10])) # and #$10 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01A5, bytearray([0xF0, 0x20])) # beq handle_moons + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01A7, bytearray([0xA0, 0x5E])) # ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01A9, bytearray([0x5A])) # phy + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01AA, bytearray([0x20, 0x80, 0xA3])) # jsr get_translevel_bit + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01AD, bytearray([0xB9, 0x3C, 0x1F])) # lda !checkpoints_flags,y + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01B0, bytearray([0x7A])) # ply + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01B1, bytearray([0x3F, 0xA6, 0xA8, 0x0D])) # and.l $0DA8A6,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01B5, bytearray([0xF0, 0x02])) # beq .write + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01B7, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01B9, bytearray([0x98])) # .write tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01BA, bytearray([0x87, 0x06])) # sta [$06] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01BC, bytearray([0xA9, 0x07])) # lda #$07 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01BE, bytearray([0x87, 0x00])) # sta [$00] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01C0, bytearray([0xA9, 0x48])) # lda #$48 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01C2, bytearray([0x87, 0x03])) # sta [$03] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01C4, bytearray([0x20, 0xC0, 0xA3])) # jsr update_flag_pointers + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01C7, bytearray([0xAF, 0xA8, 0xBF, 0x03])) # handle_moons: lda.l moon_enabled_flag + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01CB, bytearray([0xF0, 0x2A])) # beq handle_dragon_coins + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01CD, bytearray([0xA6, 0x60])) # ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01CF, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01D3, bytearray([0x29, 0x08])) # and #$08 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01D5, bytearray([0xF0, 0x20])) # beq handle_dragon_coins + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01D7, bytearray([0xA0, 0x5E])) # ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01D9, bytearray([0x5A])) # phy + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01DA, bytearray([0x20, 0x80, 0xA3])) # jsr get_translevel_bit + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01DD, bytearray([0xB9, 0xEE, 0x1F])) # lda !moons_flags,y + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01E0, bytearray([0x7A])) # ply + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01E1, bytearray([0x3F, 0xA6, 0xA8, 0x0D])) # and.l $0DA8A6,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01E5, bytearray([0xF0, 0x02])) # beq .write + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01E7, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01E9, bytearray([0x98])) # .write tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01EA, bytearray([0x87, 0x06])) # sta [$06] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01EC, bytearray([0xA9, 0x0C])) # lda #$0C + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01EE, bytearray([0x87, 0x00])) # sta [$00] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01F0, bytearray([0xA9, 0x4E])) # lda #$4E + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01F2, bytearray([0x87, 0x03])) # sta [$03] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01F4, bytearray([0x20, 0xC0, 0xA3])) # jsr update_flag_pointers + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01F7, bytearray([0xAF, 0xA6, 0xBF, 0x03])) # handle_dragon_coins: lda.l dragon_coin_enabled_flag + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01FB, bytearray([0xF0, 0x2A])) # beq handle_exit_2 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01FD, bytearray([0xA6, 0x60])) # ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x01FF, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0203, bytearray([0x29, 0x04])) # and #$04 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0205, bytearray([0xF0, 0x20])) # beq handle_exit_2 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0207, bytearray([0xA0, 0x5E])) # ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0209, bytearray([0x5A])) # phy + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x020A, bytearray([0x20, 0x80, 0xA3])) # jsr get_translevel_bit + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x020D, bytearray([0xB9, 0x2F, 0x1F])) # lda !yoshi_coins_flags,y + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0210, bytearray([0x7A])) # ply + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0211, bytearray([0x3F, 0xA6, 0xA8, 0x0D])) # and.l $0DA8A6,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0215, bytearray([0xF0, 0x02])) # beq .write + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0217, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0219, bytearray([0x98])) # .write tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x021A, bytearray([0x87, 0x06])) # sta [$06] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x021C, bytearray([0xA9, 0x03])) # lda #$03 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x021E, bytearray([0x87, 0x00])) # sta [$00] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0220, bytearray([0xA9, 0x02])) # lda #$02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0222, bytearray([0x87, 0x03])) # sta [$03] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0224, bytearray([0x20, 0xC0, 0xA3])) # jsr update_flag_pointers + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0227, bytearray([0xA6, 0x60])) # handle_exit_2: ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0229, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x022D, bytearray([0x29, 0x02])) # and #$02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x022F, bytearray([0xF0, 0x1A])) # beq handle_exit_1 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0231, bytearray([0xA0, 0x5E])) # ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0233, bytearray([0xBF, 0x00, 0xA2, 0x7F])) # lda !level_clears,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0237, bytearray([0x29, 0x02])) # and #$02 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0239, bytearray([0xF0, 0x02])) # beq .write + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x023B, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x023D, bytearray([0x98])) # .write tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x023E, bytearray([0x87, 0x06])) # sta [$06] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0240, bytearray([0xA9, 0x04])) # lda #$04 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0242, bytearray([0x87, 0x00])) # sta [$00] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0244, bytearray([0xA9, 0x24])) # lda #$24 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0246, bytearray([0x87, 0x03])) # sta [$03] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0248, bytearray([0x20, 0xC0, 0xA3])) # jsr update_flag_pointers + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x024B, bytearray([0xA6, 0x60])) # handle_exit_1: ldx $60 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x024D, bytearray([0xBF, 0x00, 0xF4, 0x0F])) # lda.l level_data,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0251, bytearray([0x29, 0x01])) # and #$01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0253, bytearray([0xF0, 0x1A])) # beq .dont_draw + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0255, bytearray([0xA0, 0x5E])) # ldy.b #!icon_not_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0257, bytearray([0xBF, 0x00, 0xA2, 0x7F])) # lda !level_clears,x + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x025B, bytearray([0x29, 0x01])) # and #$01 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x025D, bytearray([0xF0, 0x02])) # beq .write + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x025F, bytearray([0xA0, 0x3F])) # ldy.b #!icon_obtained + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0261, bytearray([0x98])) # .write tya + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0262, bytearray([0x87, 0x06])) # sta [$06] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0264, bytearray([0xA9, 0x04])) # lda #$04 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0266, bytearray([0x87, 0x00])) # sta [$00] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x0268, bytearray([0xA9, 0x23])) # lda #$23 + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x026A, bytearray([0x87, 0x03])) # sta [$03] + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x026C, bytearray([0x20, 0xC0, 0xA3])) # jsr update_flag_pointers + rom.write_bytes(BUILD_TILEMAP_ADDR + 0x026F, bytearray([0x6B])) # .dont_draw rtl + + LEVEL_INDICATOR_DATA_ADDR = 0x7F400 + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0000, bytearray([0x80,0x45,0x45,0x80,0x43,0x65,0x5D,0x51])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0008, bytearray([0x01,0x47,0x47,0x51,0x65,0x45,0x41,0x4F])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0010, bytearray([0x55,0x45,0x80,0x43,0x01,0x57,0x80,0x80])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0018, bytearray([0x45,0x80,0x51,0x41,0x45,0x45,0x80,0x41])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0020, bytearray([0x45,0x41,0x4D,0x67,0x57,0x41,0x55,0x65])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0028, bytearray([0x80,0x4D,0x45,0x55,0x80,0x47,0x4D,0x45])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0030, bytearray([0x80,0x80,0x80,0x43,0x55,0x41,0x80,0x45])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0038, bytearray([0x47,0x57,0x4D,0x41,0x47,0x55,0x47,0x01])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0040, bytearray([0x41,0x4F,0x43,0x47,0x47,0x01,0x45,0x57])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0048, bytearray([0x80,0x45,0x45,0x45,0x45,0x80,0x55,0x45])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0050, bytearray([0x45,0x45,0x80,0x80,0x43,0x80,0x43,0x80])) + rom.write_bytes(LEVEL_INDICATOR_DATA_ADDR + 0x0058, bytearray([0x07,0x43,0x43,0x80,0x80,0x80,0x80,0x80])) + + +def handle_indicators(rom): + INDICATOR_QUEUE_CODE = 0x86000 + rom.write_bytes(0x022E6, bytearray([0x22, 0x00, 0xE0, 0x10])) # org $00A2E6 : jsl gm14_hijack + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0000, bytearray([0xAD, 0x00, 0x01])) # gm14_hijack: lda $0100 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0003, bytearray([0xC9, 0x14])) # cmp #$14 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0005, bytearray([0xD0, 0x04])) # bne .invalid + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0007, bytearray([0xA5, 0x71])) # lda $71 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0009, bytearray([0xF0, 0x04])) # beq .valid + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x000B, bytearray([0x5C, 0xB1, 0x8A, 0x02])) # .invalid jml $028AB1 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x000F, bytearray([0xC2, 0x30])) # .valid rep #$30 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0011, bytearray([0xAF, 0x04, 0xB0, 0x7F])) # lda !score_sprite_add_1_coin + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0015, bytearray([0xF0, 0x03])) # beq .no_1_coin + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0017, bytearray([0x20, 0xC1, 0xE0])) # jsr add_1_coin + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x001A, bytearray([0xAF, 0x06, 0xB0, 0x7F])) # .no_1_coin lda !score_sprite_add_5_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x001E, bytearray([0xF0, 0x03])) # beq .no_5_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0020, bytearray([0x20, 0xDF, 0xE0])) # jsr add_5_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0023, bytearray([0xAF, 0x08, 0xB0, 0x7F])) # .no_5_coins lda !score_sprite_add_10_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0027, bytearray([0xF0, 0x03])) # beq .no_10_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0029, bytearray([0x20, 0xFD, 0xE0])) # jsr add_10_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x002C, bytearray([0xAF, 0x0A, 0xB0, 0x7F])) # .no_10_coins lda !score_sprite_add_15_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0030, bytearray([0xF0, 0x03])) # beq .no_15_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0032, bytearray([0x20, 0x1B, 0xE1])) # jsr add_15_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0035, bytearray([0xAF, 0x10, 0xB0, 0x7F])) # .no_15_coins lda !score_sprite_add_1up + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0039, bytearray([0xF0, 0x03])) # beq .no_1up + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x003B, bytearray([0x20, 0x39, 0xE1])) # jsr add_1up + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x003E, bytearray([0xAF, 0x0C, 0xB0, 0x7F])) # .no_1up lda !score_sprite_add_yoshi_egg + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0042, bytearray([0xF0, 0x03])) # beq .no_yoshi_egg + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0044, bytearray([0x20, 0x57, 0xE1])) # jsr add_yoshi_egg + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0047, bytearray([0xAF, 0x0E, 0xB0, 0x7F])) # .no_yoshi_egg lda !score_sprite_add_boss_token + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x004B, bytearray([0xF0, 0x03])) # beq .no_boss_token + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x004D, bytearray([0x20, 0xCF, 0xE1])) # jsr add_boss_token + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0050, bytearray([0xE2, 0x30])) # .no_boss_token sep #$30 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0052, bytearray([0x20, 0xED, 0xE1])) # jsr goal_sanity_check + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0055, bytearray([0x20, 0x5C, 0xE0])) # jsr score_sprite_queue + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0058, bytearray([0x5C, 0xB1, 0x8A, 0x02])) # jml $028AB1 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x005C, bytearray([0xAF, 0x20, 0xB0, 0x7F])) # score_sprite_queue: lda !score_sprite_queue_delay + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0060, bytearray([0xF0, 0x06])) # beq .spawn + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0062, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0063, bytearray([0x8F, 0x20, 0xB0, 0x7F])) # sta !score_sprite_queue_delay + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0067, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0068, bytearray([0xA9, 0x08])) # .spawn lda #$08 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x006A, bytearray([0x8F, 0x20, 0xB0, 0x7F])) # sta !score_sprite_queue_delay + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x006E, bytearray([0xC2, 0x30])) # rep #$30 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0070, bytearray([0xAF, 0x02, 0xB0, 0x7F])) # lda !score_sprite_index + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0074, bytearray([0xCF, 0x00, 0xB0, 0x7F])) # cmp !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0078, bytearray([0xD0, 0x03])) # bne .check_slots + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x007A, bytearray([0xE2, 0x30])) # sep #$30 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x007C, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x007D, bytearray([0xA0, 0x05, 0x00])) # .check_slots ldy #$0005 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0080, bytearray([0xB9, 0xE1, 0x16])) # ..loop lda !score_sprite_num,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0083, bytearray([0x29, 0xFF, 0x00])) # and #$00FF + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0086, bytearray([0xF0, 0x06])) # beq .found_free + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0088, bytearray([0x88])) # dey + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0089, bytearray([0x10, 0xF5])) # bpl ..loop + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x008B, bytearray([0xE2, 0x30])) # sep #$30 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x008D, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x008E, bytearray([0xAF, 0x02, 0xB0, 0x7F])) # .found_free lda !score_sprite_index + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0092, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0093, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0094, bytearray([0x8F, 0x02, 0xB0, 0x7F])) # sta !score_sprite_index + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0098, bytearray([0xBF, 0x22, 0xB0, 0x7F])) # lda !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x009C, bytearray([0xE2, 0x30])) # sep #$30 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x009E, bytearray([0x99, 0xE1, 0x16])) # sta !score_sprite_num,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00A1, bytearray([0xA5, 0x94])) # lda $94 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00A3, bytearray([0x99, 0xED, 0x16])) # sta !score_sprite_x_lo,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00A6, bytearray([0xA5, 0x95])) # lda $95 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00A8, bytearray([0x99, 0xF3, 0x16])) # sta !score_sprite_x_hi,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00AB, bytearray([0xA5, 0x96])) # lda $96 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00AD, bytearray([0x99, 0xE7, 0x16])) # sta !score_sprite_y_lo,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00B0, bytearray([0xA5, 0x97])) # lda $97 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00B2, bytearray([0x99, 0xF9, 0x16])) # sta !score_sprite_y_hi,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00B5, bytearray([0xA9, 0x30])) # lda #$30 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00B7, bytearray([0x99, 0xFF, 0x16])) # sta !score_sprite_timer,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00BA, bytearray([0xAD, 0xF9, 0x13])) # lda $13F9 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00BD, bytearray([0x99, 0x05, 0x17])) # sta !score_sprite_layer,y + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00C0, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00C1, bytearray([0xAF, 0x04, 0xB0, 0x7F])) # add_1_coin: lda !score_sprite_add_1_coin + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00C5, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00C6, bytearray([0x8F, 0x04, 0xB0, 0x7F])) # sta !score_sprite_add_1_coin + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00CA, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00CE, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00CF, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00D3, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00D4, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00D6, bytearray([0xA9, 0x11])) # lda #$11 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00D8, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00DC, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00DE, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00DF, bytearray([0xAF, 0x06, 0xB0, 0x7F])) # add_5_coins: lda !score_sprite_add_5_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00E3, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00E4, bytearray([0x8F, 0x06, 0xB0, 0x7F])) # sta !score_sprite_add_5_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00E8, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00EC, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00ED, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00F1, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00F2, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00F4, bytearray([0xA9, 0x12])) # lda #$12 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00F6, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00FA, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00FC, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x00FD, bytearray([0xAF, 0x08, 0xB0, 0x7F])) # add_10_coins: lda !score_sprite_add_10_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0101, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0102, bytearray([0x8F, 0x08, 0xB0, 0x7F])) # sta !score_sprite_add_10_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0106, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x010A, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x010B, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x010F, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0110, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0112, bytearray([0xA9, 0x13])) # lda #$13 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0114, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0118, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x011A, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x011B, bytearray([0xAF, 0x0A, 0xB0, 0x7F])) # add_15_coins: lda !score_sprite_add_15_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x011F, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0120, bytearray([0x8F, 0x0A, 0xB0, 0x7F])) # sta !score_sprite_add_15_coins + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0124, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0128, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0129, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x012D, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x012E, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0130, bytearray([0xA9, 0x14])) # lda #$14 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0132, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0136, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0138, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0139, bytearray([0xAF, 0x10, 0xB0, 0x7F])) # add_1up: lda !score_sprite_add_1up + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x013D, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x013E, bytearray([0x8F, 0x10, 0xB0, 0x7F])) # sta !score_sprite_add_1up + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0142, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0146, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0147, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x014B, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x014C, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x014E, bytearray([0xA9, 0x16])) # lda #$16 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0150, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0154, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0156, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0157, bytearray([0xAF, 0x0C, 0xB0, 0x7F])) # add_yoshi_egg: lda !score_sprite_add_yoshi_egg + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x015B, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x015C, bytearray([0x8F, 0x0C, 0xB0, 0x7F])) # sta !score_sprite_add_yoshi_egg + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0160, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0164, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0165, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0169, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x016A, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x016C, bytearray([0xA9, 0x15])) # lda #$15 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x016E, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0172, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0174, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0175, bytearray([0xAF, 0x12, 0xB0, 0x7F])) # add_mushroom: lda !score_sprite_add_mushroom + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0179, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x017A, bytearray([0x8F, 0x12, 0xB0, 0x7F])) # sta !score_sprite_add_mushroom + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x017E, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0182, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0183, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0187, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0188, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x018A, bytearray([0xA9, 0x17])) # lda #$17 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x018C, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0190, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0192, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0193, bytearray([0xAF, 0x14, 0xB0, 0x7F])) # add_flower: lda !score_sprite_add_flower + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0197, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0198, bytearray([0x8F, 0x14, 0xB0, 0x7F])) # sta !score_sprite_add_flower + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x019C, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01A0, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01A1, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01A5, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01A6, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01A8, bytearray([0xA9, 0x18])) # lda #$18 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01AA, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01AE, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01B0, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01B1, bytearray([0xAF, 0x16, 0xB0, 0x7F])) # add_feather: lda !score_sprite_add_feather + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01B5, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01B6, bytearray([0x8F, 0x16, 0xB0, 0x7F])) # sta !score_sprite_add_feather + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01BA, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01BE, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01BF, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01C3, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01C4, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01C6, bytearray([0xA9, 0x19])) # lda #$19 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01C8, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01CC, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01CE, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01CF, bytearray([0xAF, 0x0E, 0xB0, 0x7F])) # add_boss_token: lda !score_sprite_add_boss_token + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01D3, bytearray([0x3A])) # dec + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01D4, bytearray([0x8F, 0x0E, 0xB0, 0x7F])) # sta !score_sprite_add_boss_token + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01D8, bytearray([0xAF, 0x00, 0xB0, 0x7F])) # lda !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01DC, bytearray([0x1A])) # inc + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01DD, bytearray([0x8F, 0x00, 0xB0, 0x7F])) # sta !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01E1, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01E2, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01E4, bytearray([0xA9, 0x1A])) # lda #$1A + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01E6, bytearray([0x9F, 0x22, 0xB0, 0x7F])) # sta !score_sprite_queue,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01EA, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01EC, bytearray([0x60])) # rts + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01ED, bytearray([0xAF, 0xA0, 0xBF, 0x03])) # goal_sanity_check: lda $03BFA0 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01F1, bytearray([0x29, 0x01])) # and #$01 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01F3, bytearray([0x49, 0x01])) # eor #$01 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01F5, bytearray([0x0A])) # asl + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01F6, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01F8, bytearray([0xBF, 0x0C, 0xB0, 0x7F])) # lda !score_sprite_add_yoshi_egg,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01FC, bytearray([0xD0, 0x18])) # bne .return + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x01FE, bytearray([0xAF, 0x02, 0xB0, 0x7F])) # .check_queue lda !score_sprite_index + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0202, bytearray([0xCF, 0x00, 0xB0, 0x7F])) # cmp !score_sprite_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0206, bytearray([0xD0, 0x0E])) # bne .return + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0208, bytearray([0xE2, 0x20])) # .check_count sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x020A, bytearray([0xAF, 0x1E, 0xA0, 0x7F])) # lda !goal_item_count + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x020E, bytearray([0xDD, 0x24, 0x1F])) # cmp $1F24,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0211, bytearray([0xF0, 0x03])) # beq .return + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0213, bytearray([0x9D, 0x24, 0x1F])) # sta $1F24,x + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0216, bytearray([0xE2, 0x20])) # .return sep #$20 + rom.write_bytes(INDICATOR_QUEUE_CODE + 0x0218, bytearray([0x60])) # rts + + # Add code for indicators when receiving items during levels + INDICATOR_CODE = 0x84000 + rom.write_bytes(0x12DBA, bytearray([0x5C, 0x00, 0xC0, 0x10])) # org $02ADBA : jsl score_sprites + rom.write_bytes(INDICATOR_CODE + 0x0000, bytearray([0xBD, 0xE1, 0x16])) # score_sprites: lda !score_sprite_num,x + rom.write_bytes(INDICATOR_CODE + 0x0003, bytearray([0xF0, 0x2D])) # beq .return + rom.write_bytes(INDICATOR_CODE + 0x0005, bytearray([0x8E, 0xE9, 0x15])) # stx $15E9 + rom.write_bytes(INDICATOR_CODE + 0x0008, bytearray([0xC2, 0x30])) # rep #$30 + rom.write_bytes(INDICATOR_CODE + 0x000A, bytearray([0x29, 0x1F, 0x00])) # and #$001F + rom.write_bytes(INDICATOR_CODE + 0x000D, bytearray([0x85, 0x00])) # sta $00 + rom.write_bytes(INDICATOR_CODE + 0x000F, bytearray([0x0A])) # asl + rom.write_bytes(INDICATOR_CODE + 0x0010, bytearray([0x18])) # clc + rom.write_bytes(INDICATOR_CODE + 0x0011, bytearray([0x65, 0x00])) # adc $00 + rom.write_bytes(INDICATOR_CODE + 0x0013, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_CODE + 0x0014, bytearray([0xBF, 0x37, 0xC0, 0x10])) # lda.l .pointers-3,x + rom.write_bytes(INDICATOR_CODE + 0x0018, bytearray([0x85, 0x00])) # sta $00 + rom.write_bytes(INDICATOR_CODE + 0x001A, bytearray([0xE2, 0x30])) # sep #$30 + rom.write_bytes(INDICATOR_CODE + 0x001C, bytearray([0xBF, 0x39, 0xC0, 0x10])) # lda.l .pointers-3+2,x + rom.write_bytes(INDICATOR_CODE + 0x0020, bytearray([0x85, 0x02])) # sta $02 + rom.write_bytes(INDICATOR_CODE + 0x0022, bytearray([0xE2, 0x10])) # sep #$10 + rom.write_bytes(INDICATOR_CODE + 0x0024, bytearray([0xAE, 0xE9, 0x15])) # ldx $15E9 + rom.write_bytes(INDICATOR_CODE + 0x0027, bytearray([0x8B])) # phb + rom.write_bytes(INDICATOR_CODE + 0x0028, bytearray([0x48])) # pha + rom.write_bytes(INDICATOR_CODE + 0x0029, bytearray([0xAB])) # plb + rom.write_bytes(INDICATOR_CODE + 0x002A, bytearray([0x4B])) # phk + rom.write_bytes(INDICATOR_CODE + 0x002B, bytearray([0xF4, 0x30, 0xC0])) # pea.w .return_code-1 + rom.write_bytes(INDICATOR_CODE + 0x002E, bytearray([0xDC, 0x00, 0x00])) # jml [$0000] + rom.write_bytes(INDICATOR_CODE + 0x0031, bytearray([0xAB])) # .return_code plb + rom.write_bytes(INDICATOR_CODE + 0x0032, bytearray([0x5C, 0xC5, 0xAD, 0x02])) # .return jml $02ADC5 + rom.write_bytes(INDICATOR_CODE + 0x0036, bytearray([0x9E, 0xE1, 0x16])) # .kill stz !score_sprite_num,x + rom.write_bytes(INDICATOR_CODE + 0x0039, bytearray([0x6B])) # rtl + rom.write_bytes(INDICATOR_CODE + 0x003A, bytearray([0x97, 0xC0, 0x10])) # .pointers dl original_score_sprites ; 01 - 10 points + rom.write_bytes(INDICATOR_CODE + 0x003D, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 02 - 20 points + rom.write_bytes(INDICATOR_CODE + 0x0040, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 03 - 40 points + rom.write_bytes(INDICATOR_CODE + 0x0043, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 04 - 80 points + rom.write_bytes(INDICATOR_CODE + 0x0046, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 05 - 100 points + rom.write_bytes(INDICATOR_CODE + 0x0049, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 06 - 200 points + rom.write_bytes(INDICATOR_CODE + 0x004C, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 07 - 400 points + rom.write_bytes(INDICATOR_CODE + 0x004F, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 08 - 800 points + rom.write_bytes(INDICATOR_CODE + 0x0052, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 09 - 1000 points + rom.write_bytes(INDICATOR_CODE + 0x0055, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 0A - 2000 points + rom.write_bytes(INDICATOR_CODE + 0x0058, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 0B - 4000 points + rom.write_bytes(INDICATOR_CODE + 0x005B, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 0C - 8000 points + rom.write_bytes(INDICATOR_CODE + 0x005E, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 0D - 1-up + rom.write_bytes(INDICATOR_CODE + 0x0061, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 0E - 2-up + rom.write_bytes(INDICATOR_CODE + 0x0064, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 0F - 3-up + rom.write_bytes(INDICATOR_CODE + 0x0067, bytearray([0x97, 0xC0, 0x10])) # dl original_score_sprites ; 10 - 5-up + rom.write_bytes(INDICATOR_CODE + 0x006A, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 11 - 1 coin + rom.write_bytes(INDICATOR_CODE + 0x006D, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 12 - 5 coins + rom.write_bytes(INDICATOR_CODE + 0x0070, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 13 - 10 coins + rom.write_bytes(INDICATOR_CODE + 0x0073, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 14 - 15 coins + rom.write_bytes(INDICATOR_CODE + 0x0076, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 15 - Yoshi Egg + rom.write_bytes(INDICATOR_CODE + 0x0079, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 16 - 1up Mushroom + rom.write_bytes(INDICATOR_CODE + 0x007C, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 17 - Mushroom + rom.write_bytes(INDICATOR_CODE + 0x007F, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 18 - Flower + rom.write_bytes(INDICATOR_CODE + 0x0082, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 19 - Feather + rom.write_bytes(INDICATOR_CODE + 0x0085, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 1A - Boss token + rom.write_bytes(INDICATOR_CODE + 0x0088, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 1B - + rom.write_bytes(INDICATOR_CODE + 0x008B, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 1C - + rom.write_bytes(INDICATOR_CODE + 0x008E, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 1D - + rom.write_bytes(INDICATOR_CODE + 0x0091, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 1E - + rom.write_bytes(INDICATOR_CODE + 0x0094, bytearray([0xA7, 0xC0, 0x10])) # dl icon_score ; 1F - + rom.write_bytes(INDICATOR_CODE + 0x0097, bytearray([0xA9, 0x02])) # original_score_sprites: lda #$02 + rom.write_bytes(INDICATOR_CODE + 0x0099, bytearray([0x48])) # pha + rom.write_bytes(INDICATOR_CODE + 0x009A, bytearray([0xAB])) # plb + rom.write_bytes(INDICATOR_CODE + 0x009B, bytearray([0x4B])) # phk + rom.write_bytes(INDICATOR_CODE + 0x009C, bytearray([0xF4, 0xA5, 0xC0])) # pea.w .jslrtsreturn-1 + rom.write_bytes(INDICATOR_CODE + 0x009F, bytearray([0xF4, 0x88, 0xB8])) # pea.w $B889-1 + rom.write_bytes(INDICATOR_CODE + 0x00A2, bytearray([0x5C, 0xC9, 0xAD, 0x02])) # jml $02ADC9 + rom.write_bytes(INDICATOR_CODE + 0x00A6, bytearray([0x6B])) # .jslrtsreturn rtl + rom.write_bytes(INDICATOR_CODE + 0x00A7, bytearray([0xBD, 0xFF, 0x16])) # icon_score: lda !score_sprite_timer,x + rom.write_bytes(INDICATOR_CODE + 0x00AA, bytearray([0xD0, 0x04])) # bne .active + rom.write_bytes(INDICATOR_CODE + 0x00AC, bytearray([0x9E, 0xE1, 0x16])) # stz !score_sprite_num,x + rom.write_bytes(INDICATOR_CODE + 0x00AF, bytearray([0x6B])) # rtl + rom.write_bytes(INDICATOR_CODE + 0x00B0, bytearray([0xDE, 0xFF, 0x16])) # .active dec !score_sprite_timer,x + rom.write_bytes(INDICATOR_CODE + 0x00B3, bytearray([0xC9, 0x30])) # cmp #$30 + rom.write_bytes(INDICATOR_CODE + 0x00B5, bytearray([0xD0, 0x14])) # bne .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x00B7, bytearray([0xBD, 0xE1, 0x16])) # lda !score_sprite_num,x + rom.write_bytes(INDICATOR_CODE + 0x00BA, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x00BB, bytearray([0xE9, 0x11])) # sbc #$11 + rom.write_bytes(INDICATOR_CODE + 0x00BD, bytearray([0x0A])) # asl + rom.write_bytes(INDICATOR_CODE + 0x00BE, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x00BF, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_CODE + 0x00C1, bytearray([0xB9, 0x4B, 0xC2])) # lda .reward_ptrs,y + rom.write_bytes(INDICATOR_CODE + 0x00C4, bytearray([0x85, 0x00])) # sta $00 + rom.write_bytes(INDICATOR_CODE + 0x00C6, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_CODE + 0x00C8, bytearray([0x6C, 0x00, 0x00])) # jmp ($0000) + rom.write_bytes(INDICATOR_CODE + 0x00CB, bytearray([0xBD, 0xFF, 0x16])) # .handle_movement lda !score_sprite_timer,x + rom.write_bytes(INDICATOR_CODE + 0x00CE, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x00CF, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x00D0, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x00D1, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x00D2, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x00D3, bytearray([0xA5, 0x13])) # lda $13 + rom.write_bytes(INDICATOR_CODE + 0x00D5, bytearray([0x39, 0xF0, 0xC0])) # and .speed,y + rom.write_bytes(INDICATOR_CODE + 0x00D8, bytearray([0xD0, 0x14])) # bne ..skip_update + rom.write_bytes(INDICATOR_CODE + 0x00DA, bytearray([0xBD, 0xE7, 0x16])) # lda !score_sprite_y_lo,x + rom.write_bytes(INDICATOR_CODE + 0x00DD, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x00DE, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x00DF, bytearray([0xE5, 0x1C])) # sbc $1C + rom.write_bytes(INDICATOR_CODE + 0x00E1, bytearray([0xC9, 0x04])) # cmp #$04 + rom.write_bytes(INDICATOR_CODE + 0x00E3, bytearray([0x90, 0x09])) # bcc ..skip_update + rom.write_bytes(INDICATOR_CODE + 0x00E5, bytearray([0xDE, 0xE7, 0x16])) # dec !score_sprite_y_lo,x + rom.write_bytes(INDICATOR_CODE + 0x00E8, bytearray([0x98])) # tya + rom.write_bytes(INDICATOR_CODE + 0x00E9, bytearray([0xD0, 0x03])) # bne ..skip_update + rom.write_bytes(INDICATOR_CODE + 0x00EB, bytearray([0xDE, 0xF9, 0x16])) # dec !score_sprite_y_hi,x + rom.write_bytes(INDICATOR_CODE + 0x00EE, bytearray([0x80, 0x05])) # ..skip_update bra .gfx + rom.write_bytes(INDICATOR_CODE + 0x00F0, bytearray([0x03, 0x01, 0x00, 0x00])) # .speed db $03,$01,$00,$00 + rom.write_bytes(INDICATOR_CODE + 0x00F4, bytearray([0x6B])) # .return rtl + rom.write_bytes(INDICATOR_CODE + 0x00F5, bytearray([0xBD, 0x05, 0x17])) # .gfx lda !score_sprite_layer,x + rom.write_bytes(INDICATOR_CODE + 0x00F8, bytearray([0x0A])) # asl + rom.write_bytes(INDICATOR_CODE + 0x00F9, bytearray([0x0A])) # asl + rom.write_bytes(INDICATOR_CODE + 0x00FA, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x00FB, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_CODE + 0x00FD, bytearray([0xB9, 0x1C, 0x00])) # lda $001C,y + rom.write_bytes(INDICATOR_CODE + 0x0100, bytearray([0x85, 0x02])) # sta $02 + rom.write_bytes(INDICATOR_CODE + 0x0102, bytearray([0xB9, 0x1A, 0x00])) # lda $001A,y + rom.write_bytes(INDICATOR_CODE + 0x0105, bytearray([0x85, 0x04])) # sta $04 + rom.write_bytes(INDICATOR_CODE + 0x0107, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_CODE + 0x0109, bytearray([0xBD, 0xF3, 0x16])) # lda !score_sprite_x_hi,x + rom.write_bytes(INDICATOR_CODE + 0x010C, bytearray([0xEB])) # xba + rom.write_bytes(INDICATOR_CODE + 0x010D, bytearray([0xBD, 0xED, 0x16])) # lda !score_sprite_x_lo,x + rom.write_bytes(INDICATOR_CODE + 0x0110, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(INDICATOR_CODE + 0x0112, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x0113, bytearray([0xE5, 0x04])) # sbc $04 + rom.write_bytes(INDICATOR_CODE + 0x0115, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x0116, bytearray([0xE9, 0x06, 0x00])) # sbc #$0006 + rom.write_bytes(INDICATOR_CODE + 0x0119, bytearray([0xC9, 0xEA, 0x00])) # cmp #$00EA + rom.write_bytes(INDICATOR_CODE + 0x011C, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(INDICATOR_CODE + 0x011E, bytearray([0xB0, 0xD4])) # bcs .return + rom.write_bytes(INDICATOR_CODE + 0x0120, bytearray([0xBD, 0xE7, 0x16])) # lda !score_sprite_y_lo,x + rom.write_bytes(INDICATOR_CODE + 0x0123, bytearray([0xC5, 0x02])) # cmp $02 + rom.write_bytes(INDICATOR_CODE + 0x0125, bytearray([0xBD, 0xF9, 0x16])) # lda !score_sprite_y_hi,x + rom.write_bytes(INDICATOR_CODE + 0x0128, bytearray([0xE5, 0x03])) # sbc $03 + rom.write_bytes(INDICATOR_CODE + 0x012A, bytearray([0xD0, 0xC8])) # bne .return + rom.write_bytes(INDICATOR_CODE + 0x012C, bytearray([0xBF, 0x9E, 0xAD, 0x02])) # lda $02AD9E,x + rom.write_bytes(INDICATOR_CODE + 0x0130, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x0131, bytearray([0xBD, 0xE7, 0x16])) # lda !score_sprite_y_lo,x + rom.write_bytes(INDICATOR_CODE + 0x0134, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x0135, bytearray([0xE5, 0x02])) # sbc $02 + rom.write_bytes(INDICATOR_CODE + 0x0137, bytearray([0x99, 0x01, 0x02])) # sta $0201,y + rom.write_bytes(INDICATOR_CODE + 0x013A, bytearray([0x99, 0x05, 0x02])) # sta $0205,y + rom.write_bytes(INDICATOR_CODE + 0x013D, bytearray([0xBD, 0xED, 0x16])) # lda !score_sprite_x_lo,x + rom.write_bytes(INDICATOR_CODE + 0x0140, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x0141, bytearray([0xE5, 0x04])) # sbc $04 + rom.write_bytes(INDICATOR_CODE + 0x0143, bytearray([0x18])) # clc + rom.write_bytes(INDICATOR_CODE + 0x0144, bytearray([0x69, 0x09])) # adc #$09 + rom.write_bytes(INDICATOR_CODE + 0x0146, bytearray([0x99, 0x00, 0x02])) # sta $0200,y + rom.write_bytes(INDICATOR_CODE + 0x0149, bytearray([0x18])) # clc + rom.write_bytes(INDICATOR_CODE + 0x014A, bytearray([0x69, 0x05])) # adc #$05 + rom.write_bytes(INDICATOR_CODE + 0x014C, bytearray([0x99, 0x04, 0x02])) # sta $0204,y + rom.write_bytes(INDICATOR_CODE + 0x014F, bytearray([0xDA])) # phx + rom.write_bytes(INDICATOR_CODE + 0x0150, bytearray([0xBD, 0xE1, 0x16])) # lda !score_sprite_num,x + rom.write_bytes(INDICATOR_CODE + 0x0153, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x0154, bytearray([0xE9, 0x11])) # sbc #$11 + rom.write_bytes(INDICATOR_CODE + 0x0156, bytearray([0x0A])) # asl + rom.write_bytes(INDICATOR_CODE + 0x0157, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_CODE + 0x0158, bytearray([0xBD, 0x09, 0xC2])) # lda ..num_tile+$00,x + rom.write_bytes(INDICATOR_CODE + 0x015B, bytearray([0x99, 0x02, 0x02])) # sta $0202,y + rom.write_bytes(INDICATOR_CODE + 0x015E, bytearray([0xBD, 0x0A, 0xC2])) # lda ..num_tile+$01,x + rom.write_bytes(INDICATOR_CODE + 0x0161, bytearray([0x99, 0x06, 0x02])) # sta $0206,y + rom.write_bytes(INDICATOR_CODE + 0x0164, bytearray([0xBD, 0x27, 0xC2])) # lda ..num_props+$00,x + rom.write_bytes(INDICATOR_CODE + 0x0167, bytearray([0x99, 0x03, 0x02])) # sta $0203,y + rom.write_bytes(INDICATOR_CODE + 0x016A, bytearray([0xBD, 0x28, 0xC2])) # lda ..num_props+$01,x + rom.write_bytes(INDICATOR_CODE + 0x016D, bytearray([0x99, 0x07, 0x02])) # sta $0207,y + rom.write_bytes(INDICATOR_CODE + 0x0170, bytearray([0xFA])) # plx + rom.write_bytes(INDICATOR_CODE + 0x0171, bytearray([0x98])) # tya + rom.write_bytes(INDICATOR_CODE + 0x0172, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x0173, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x0174, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x0175, bytearray([0xA9, 0x00])) # lda #$00 + rom.write_bytes(INDICATOR_CODE + 0x0177, bytearray([0x99, 0x20, 0x04])) # sta $0420,y + rom.write_bytes(INDICATOR_CODE + 0x017A, bytearray([0x99, 0x21, 0x04])) # sta $0421,y + rom.write_bytes(INDICATOR_CODE + 0x017D, bytearray([0xBF, 0x45, 0xC2, 0x10])) # lda.l ..oam_2,x + rom.write_bytes(INDICATOR_CODE + 0x0181, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x0182, bytearray([0xBD, 0xE7, 0x16])) # lda !score_sprite_y_lo,x + rom.write_bytes(INDICATOR_CODE + 0x0185, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x0186, bytearray([0xE5, 0x02])) # sbc $02 + rom.write_bytes(INDICATOR_CODE + 0x0188, bytearray([0x99, 0x01, 0x02])) # sta $0201,y + rom.write_bytes(INDICATOR_CODE + 0x018B, bytearray([0x99, 0x05, 0x02])) # sta $0205,y + rom.write_bytes(INDICATOR_CODE + 0x018E, bytearray([0xBD, 0xED, 0x16])) # lda !score_sprite_x_lo,x + rom.write_bytes(INDICATOR_CODE + 0x0191, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x0192, bytearray([0xE5, 0x04])) # sbc $04 + rom.write_bytes(INDICATOR_CODE + 0x0194, bytearray([0xE9, 0x07])) # sbc #$07 + rom.write_bytes(INDICATOR_CODE + 0x0196, bytearray([0x99, 0x00, 0x02])) # sta $0200,y + rom.write_bytes(INDICATOR_CODE + 0x0199, bytearray([0x18])) # clc + rom.write_bytes(INDICATOR_CODE + 0x019A, bytearray([0x69, 0x08])) # adc #$08 + rom.write_bytes(INDICATOR_CODE + 0x019C, bytearray([0x99, 0x04, 0x02])) # sta $0204,y + rom.write_bytes(INDICATOR_CODE + 0x019F, bytearray([0xDA])) # phx + rom.write_bytes(INDICATOR_CODE + 0x01A0, bytearray([0xBD, 0xE1, 0x16])) # lda !score_sprite_num,x + rom.write_bytes(INDICATOR_CODE + 0x01A3, bytearray([0x38])) # sec + rom.write_bytes(INDICATOR_CODE + 0x01A4, bytearray([0xE9, 0x11])) # sbc #$11 + rom.write_bytes(INDICATOR_CODE + 0x01A6, bytearray([0xAA])) # tax + rom.write_bytes(INDICATOR_CODE + 0x01A7, bytearray([0xBD, 0xCD, 0xC1])) # lda ..icon_tile,x + rom.write_bytes(INDICATOR_CODE + 0x01AA, bytearray([0x99, 0x02, 0x02])) # sta $0202,y + rom.write_bytes(INDICATOR_CODE + 0x01AD, bytearray([0xBD, 0xDC, 0xC1])) # lda ..icon_props,x + rom.write_bytes(INDICATOR_CODE + 0x01B0, bytearray([0x99, 0x03, 0x02])) # sta $0203,y + rom.write_bytes(INDICATOR_CODE + 0x01B3, bytearray([0xBD, 0xFA, 0xC1])) # lda ..plus_props,x + rom.write_bytes(INDICATOR_CODE + 0x01B6, bytearray([0x99, 0x07, 0x02])) # sta $0207,y + rom.write_bytes(INDICATOR_CODE + 0x01B9, bytearray([0xBD, 0xEB, 0xC1])) # lda ..plus_tile,x + rom.write_bytes(INDICATOR_CODE + 0x01BC, bytearray([0x99, 0x06, 0x02])) # sta $0206,y + rom.write_bytes(INDICATOR_CODE + 0x01BF, bytearray([0xFA])) # plx + rom.write_bytes(INDICATOR_CODE + 0x01C0, bytearray([0x98])) # tya + rom.write_bytes(INDICATOR_CODE + 0x01C1, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x01C2, bytearray([0x4A])) # lsr + rom.write_bytes(INDICATOR_CODE + 0x01C3, bytearray([0xA8])) # tay + rom.write_bytes(INDICATOR_CODE + 0x01C4, bytearray([0xA9, 0x00])) # lda #$00 + rom.write_bytes(INDICATOR_CODE + 0x01C6, bytearray([0x99, 0x20, 0x04])) # sta $0420,y + rom.write_bytes(INDICATOR_CODE + 0x01C9, bytearray([0x99, 0x21, 0x04])) # sta $0421,y + rom.write_bytes(INDICATOR_CODE + 0x01CC, bytearray([0x6B])) # rtl + rom.write_bytes(INDICATOR_CODE + 0x01CD, bytearray([0x1B])) # ..icon_tile db $1B ; 1 coin + rom.write_bytes(INDICATOR_CODE + 0x01CE, bytearray([0x1B])) # db $1B ; 5 coins + rom.write_bytes(INDICATOR_CODE + 0x01CF, bytearray([0x1B])) # db $1B ; 10 coins + rom.write_bytes(INDICATOR_CODE + 0x01D0, bytearray([0x1B])) # db $1B ; 15 coins + rom.write_bytes(INDICATOR_CODE + 0x01D1, bytearray([0x0A])) # db $0A ; yoshi egg + rom.write_bytes(INDICATOR_CODE + 0x01D2, bytearray([0x0B])) # db $0B ; 1up mushroom + rom.write_bytes(INDICATOR_CODE + 0x01D3, bytearray([0x0B])) # db $0B ; mushroom + rom.write_bytes(INDICATOR_CODE + 0x01D4, bytearray([0x7E])) # db $7E ; flower + rom.write_bytes(INDICATOR_CODE + 0x01D5, bytearray([0x7F])) # db $7F ; feather + rom.write_bytes(INDICATOR_CODE + 0x01D6, bytearray([0x38])) # db $38 ; boss token + rom.write_bytes(INDICATOR_CODE + 0x01D7, bytearray([0x5A])) # db $5A ; + rom.write_bytes(INDICATOR_CODE + 0x01D8, bytearray([0x5A])) # db $5A ; + rom.write_bytes(INDICATOR_CODE + 0x01D9, bytearray([0x5A])) # db $5A ; + rom.write_bytes(INDICATOR_CODE + 0x01DA, bytearray([0x5A])) # db $5A ; + rom.write_bytes(INDICATOR_CODE + 0x01DB, bytearray([0x0B])) # db $0B ; + rom.write_bytes(INDICATOR_CODE + 0x01DC, bytearray([0x34])) # ..icon_props db $34 ; coin + rom.write_bytes(INDICATOR_CODE + 0x01DD, bytearray([0x34])) # db $34 ; coin + rom.write_bytes(INDICATOR_CODE + 0x01DE, bytearray([0x34])) # db $34 ; coin + rom.write_bytes(INDICATOR_CODE + 0x01DF, bytearray([0x34])) # db $34 ; coin + rom.write_bytes(INDICATOR_CODE + 0x01E0, bytearray([0x3A])) # db $3A ; yoshi egg + rom.write_bytes(INDICATOR_CODE + 0x01E1, bytearray([0x3A])) # db $3A ; 1up mushroom + rom.write_bytes(INDICATOR_CODE + 0x01E2, bytearray([0x38])) # db $38 ; mushroom + rom.write_bytes(INDICATOR_CODE + 0x01E3, bytearray([0x3A])) # db $3A ; flower + rom.write_bytes(INDICATOR_CODE + 0x01E4, bytearray([0x34])) # db $34 ; feather + rom.write_bytes(INDICATOR_CODE + 0x01E5, bytearray([0x34])) # db $34 ; boss token + rom.write_bytes(INDICATOR_CODE + 0x01E6, bytearray([0x34])) # db $34 ; + rom.write_bytes(INDICATOR_CODE + 0x01E7, bytearray([0x3A])) # db $3A ; + rom.write_bytes(INDICATOR_CODE + 0x01E8, bytearray([0x38])) # db $38 ; + rom.write_bytes(INDICATOR_CODE + 0x01E9, bytearray([0x36])) # db $36 ; + rom.write_bytes(INDICATOR_CODE + 0x01EA, bytearray([0x36])) # db $36 ; + rom.write_bytes(INDICATOR_CODE + 0x01EB, bytearray([0x1A])) # ..plus_tile db $1A ; 1 coin + rom.write_bytes(INDICATOR_CODE + 0x01EC, bytearray([0x1A])) # db $1A ; 3 coins + rom.write_bytes(INDICATOR_CODE + 0x01ED, bytearray([0x1A])) # db $1A ; 5 coins + rom.write_bytes(INDICATOR_CODE + 0x01EE, bytearray([0x1A])) # db $1A ; 10 coins + rom.write_bytes(INDICATOR_CODE + 0x01EF, bytearray([0x1A])) # db $1A ; yoshi egg + rom.write_bytes(INDICATOR_CODE + 0x01F0, bytearray([0x1A])) # db $1A ; 1up mushroom + rom.write_bytes(INDICATOR_CODE + 0x01F1, bytearray([0x1A])) # db $1A ; mushroom + rom.write_bytes(INDICATOR_CODE + 0x01F2, bytearray([0x1A])) # db $1A ; flower + rom.write_bytes(INDICATOR_CODE + 0x01F3, bytearray([0x1A])) # db $1A ; feather + rom.write_bytes(INDICATOR_CODE + 0x01F4, bytearray([0x1A])) # db $1A ; boss token + rom.write_bytes(INDICATOR_CODE + 0x01F5, bytearray([0x1A])) # db $1A ; + rom.write_bytes(INDICATOR_CODE + 0x01F6, bytearray([0x1A])) # db $1A ; + rom.write_bytes(INDICATOR_CODE + 0x01F7, bytearray([0x1A])) # db $1A ; + rom.write_bytes(INDICATOR_CODE + 0x01F8, bytearray([0x1A])) # db $1A ; + rom.write_bytes(INDICATOR_CODE + 0x01F9, bytearray([0x1A])) # db $1A ; + rom.write_bytes(INDICATOR_CODE + 0x01FA, bytearray([0x32])) # ..plus_props db $32 ; 1 coin + rom.write_bytes(INDICATOR_CODE + 0x01FB, bytearray([0x32])) # db $32 ; 5 coins + rom.write_bytes(INDICATOR_CODE + 0x01FC, bytearray([0x32])) # db $32 ; 10 coins + rom.write_bytes(INDICATOR_CODE + 0x01FD, bytearray([0x32])) # db $32 ; 50 coins + rom.write_bytes(INDICATOR_CODE + 0x01FE, bytearray([0x32])) # db $32 ; yoshi egg + rom.write_bytes(INDICATOR_CODE + 0x01FF, bytearray([0x32])) # db $32 ; 1up mushroom + rom.write_bytes(INDICATOR_CODE + 0x0200, bytearray([0x32])) # db $32 ; mushroom + rom.write_bytes(INDICATOR_CODE + 0x0201, bytearray([0x32])) # db $32 ; flower + rom.write_bytes(INDICATOR_CODE + 0x0202, bytearray([0x32])) # db $32 ; feather + rom.write_bytes(INDICATOR_CODE + 0x0203, bytearray([0x32])) # db $32 ; boss token + rom.write_bytes(INDICATOR_CODE + 0x0204, bytearray([0x32])) # db $32 ; + rom.write_bytes(INDICATOR_CODE + 0x0205, bytearray([0x32])) # db $32 ; + rom.write_bytes(INDICATOR_CODE + 0x0206, bytearray([0x32])) # db $32 ; + rom.write_bytes(INDICATOR_CODE + 0x0207, bytearray([0x32])) # db $32 ; + rom.write_bytes(INDICATOR_CODE + 0x0208, bytearray([0x32])) # db $32 ; + rom.write_bytes(INDICATOR_CODE + 0x0209, bytearray([0x4B, 0x69])) # ..num_tile db $4B,$69 ; 1 coin + rom.write_bytes(INDICATOR_CODE + 0x020B, bytearray([0x5B, 0x69])) # db $5B,$69 ; 5 coins + rom.write_bytes(INDICATOR_CODE + 0x020D, bytearray([0x4B, 0x4A])) # db $4B,$4A ; 10 coins + rom.write_bytes(INDICATOR_CODE + 0x020F, bytearray([0x5B, 0x4A])) # db $4B,$5B ; 50 coins + rom.write_bytes(INDICATOR_CODE + 0x0211, bytearray([0x4B, 0x69])) # db $4B,$69 ; yoshi egg + rom.write_bytes(INDICATOR_CODE + 0x0213, bytearray([0x4B, 0x69])) # db $4B,$69 ; 1up mushroom + rom.write_bytes(INDICATOR_CODE + 0x0215, bytearray([0x4B, 0x69])) # db $4B,$69 ; mushroom + rom.write_bytes(INDICATOR_CODE + 0x0217, bytearray([0x4B, 0x69])) # db $4B,$69 ; flower + rom.write_bytes(INDICATOR_CODE + 0x0219, bytearray([0x4B, 0x69])) # db $4B,$69 ; feather + rom.write_bytes(INDICATOR_CODE + 0x021B, bytearray([0x4B, 0x69])) # db $4B,$69 ; boss token + rom.write_bytes(INDICATOR_CODE + 0x021D, bytearray([0x69, 0x69])) # db $69,$69 ; + rom.write_bytes(INDICATOR_CODE + 0x021F, bytearray([0x69, 0x69])) # db $69,$69 ; + rom.write_bytes(INDICATOR_CODE + 0x0221, bytearray([0x69, 0x69])) # db $69,$69 ; + rom.write_bytes(INDICATOR_CODE + 0x0223, bytearray([0x69, 0x69])) # db $69,$69 ; + rom.write_bytes(INDICATOR_CODE + 0x0225, bytearray([0x69, 0x69])) # db $69,$69 ; + rom.write_bytes(INDICATOR_CODE + 0x0227, bytearray([0x34, 0x34])) # ..num_props db $34,$34 ; 1 coin + rom.write_bytes(INDICATOR_CODE + 0x0229, bytearray([0x34, 0x34])) # db $34,$34 ; 5 coins + rom.write_bytes(INDICATOR_CODE + 0x022B, bytearray([0x34, 0x34])) # db $34,$34 ; 10 coins + rom.write_bytes(INDICATOR_CODE + 0x022D, bytearray([0x34, 0x34])) # db $34,$34 ; 50 coins + rom.write_bytes(INDICATOR_CODE + 0x022F, bytearray([0x34, 0x34])) # db $34,$34 ; yoshi egg + rom.write_bytes(INDICATOR_CODE + 0x0231, bytearray([0x34, 0x34])) # db $34,$34 ; 1up mushroom + rom.write_bytes(INDICATOR_CODE + 0x0233, bytearray([0x34, 0x34])) # db $34,$34 ; mushroom + rom.write_bytes(INDICATOR_CODE + 0x0235, bytearray([0x34, 0x34])) # db $34,$34 ; flower + rom.write_bytes(INDICATOR_CODE + 0x0237, bytearray([0x34, 0x34])) # db $34,$34 ; feather + rom.write_bytes(INDICATOR_CODE + 0x0239, bytearray([0x34, 0x34])) # db $34,$34 ; boss token + rom.write_bytes(INDICATOR_CODE + 0x023B, bytearray([0x34, 0x34])) # db $34,$34 ; + rom.write_bytes(INDICATOR_CODE + 0x023D, bytearray([0x34, 0x34])) # db $34,$34 ; + rom.write_bytes(INDICATOR_CODE + 0x023F, bytearray([0x34, 0x34])) # db $34,$34 ; + rom.write_bytes(INDICATOR_CODE + 0x0241, bytearray([0x34, 0x34])) # db $34,$34 ; + rom.write_bytes(INDICATOR_CODE + 0x0243, bytearray([0x34, 0x34])) # db $34,$34 ; + rom.write_bytes(INDICATOR_CODE + 0x0245, bytearray([0x50, 0x58, 0x60, 0x68, 0x70, 0x78]))# ..oam_2 db $50,$58,$60,$68,$70,$78 + rom.write_bytes(INDICATOR_CODE + 0x024B, bytearray([0x69, 0xC2])) # .reward_ptrs dw .one_coin + rom.write_bytes(INDICATOR_CODE + 0x024D, bytearray([0x6D, 0xC2])) # dw .five_coins + rom.write_bytes(INDICATOR_CODE + 0x024F, bytearray([0x71, 0xC2])) # dw .ten_coins + rom.write_bytes(INDICATOR_CODE + 0x0251, bytearray([0x75, 0xC2])) # dw .fifty_coins + rom.write_bytes(INDICATOR_CODE + 0x0253, bytearray([0x8A, 0xC2])) # dw .yoshi_egg + rom.write_bytes(INDICATOR_CODE + 0x0255, bytearray([0xA7, 0xC2])) # dw .green_mushroom + rom.write_bytes(INDICATOR_CODE + 0x0257, bytearray([0xAD, 0xC2])) # dw .mushroom + rom.write_bytes(INDICATOR_CODE + 0x0259, bytearray([0xAF, 0xC2])) # dw .flower + rom.write_bytes(INDICATOR_CODE + 0x025B, bytearray([0xB1, 0xC2])) # dw .shared_item + rom.write_bytes(INDICATOR_CODE + 0x025D, bytearray([0x9C, 0xC2])) # dw .boss_token + rom.write_bytes(INDICATOR_CODE + 0x025F, bytearray([0xCB, 0xC0])) # dw .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x0261, bytearray([0xCB, 0xC0])) # dw .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x0263, bytearray([0xCB, 0xC0])) # dw .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x0265, bytearray([0xCB, 0xC0])) # dw .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x0267, bytearray([0xCB, 0xC0])) # dw .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x0269, bytearray([0xA9, 0x01])) # .one_coin lda #$01 + rom.write_bytes(INDICATOR_CODE + 0x026B, bytearray([0x80, 0x0A])) # bra .shared_coins + rom.write_bytes(INDICATOR_CODE + 0x026D, bytearray([0xA9, 0x05])) # .five_coins lda #$05 + rom.write_bytes(INDICATOR_CODE + 0x026F, bytearray([0x80, 0x06])) # bra .shared_coins + rom.write_bytes(INDICATOR_CODE + 0x0271, bytearray([0xA9, 0x0A])) # .ten_coins lda #$0A + rom.write_bytes(INDICATOR_CODE + 0x0273, bytearray([0x80, 0x02])) # bra .shared_coins + rom.write_bytes(INDICATOR_CODE + 0x0275, bytearray([0xA9, 0x32])) # .fifty_coins lda #$32 + rom.write_bytes(INDICATOR_CODE + 0x0277, bytearray([0x18])) # .shared_coins clc + rom.write_bytes(INDICATOR_CODE + 0x0278, bytearray([0x6D, 0xCC, 0x13])) # adc $13CC + rom.write_bytes(INDICATOR_CODE + 0x027B, bytearray([0x90, 0x02])) # bcc + + rom.write_bytes(INDICATOR_CODE + 0x027D, bytearray([0xA9, 0xFF])) # lda #$FF + rom.write_bytes(INDICATOR_CODE + 0x027F, bytearray([0x8D, 0xCC, 0x13])) # + sta $13CC + rom.write_bytes(INDICATOR_CODE + 0x0282, bytearray([0xA9, 0x01])) # lda #$01 + rom.write_bytes(INDICATOR_CODE + 0x0284, bytearray([0x8D, 0xFC, 0x1D])) # sta $1DFC + rom.write_bytes(INDICATOR_CODE + 0x0287, bytearray([0x4C, 0xCB, 0xC0])) # jmp .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x028A, bytearray([0xAD, 0x24, 0x1F])) # .yoshi_egg lda $1F24 + rom.write_bytes(INDICATOR_CODE + 0x028D, bytearray([0xC9, 0xFF])) # cmp #$FF + rom.write_bytes(INDICATOR_CODE + 0x028F, bytearray([0xF0, 0x03])) # beq ..nope + rom.write_bytes(INDICATOR_CODE + 0x0291, bytearray([0xEE, 0x24, 0x1F])) # inc $1F24 + rom.write_bytes(INDICATOR_CODE + 0x0294, bytearray([0xA9, 0x1F])) # ..nope lda #$1F + rom.write_bytes(INDICATOR_CODE + 0x0296, bytearray([0x8D, 0xFC, 0x1D])) # sta $1DFC + rom.write_bytes(INDICATOR_CODE + 0x0299, bytearray([0x4C, 0xCB, 0xC0])) # jmp .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x029C, bytearray([0xEE, 0x26, 0x1F])) # .boss_token inc $1F26 + rom.write_bytes(INDICATOR_CODE + 0x029F, bytearray([0xA9, 0x09])) # lda #$09 + rom.write_bytes(INDICATOR_CODE + 0x02A1, bytearray([0x8D, 0xFC, 0x1D])) # sta $1DFC + rom.write_bytes(INDICATOR_CODE + 0x02A4, bytearray([0x4C, 0xCB, 0xC0])) # jmp .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x02A7, bytearray([0xEE, 0xE4, 0x18])) # .green_mushroom inc $18E4 + rom.write_bytes(INDICATOR_CODE + 0x02AA, bytearray([0x4C, 0xCB, 0xC0])) # jmp .handle_movement + rom.write_bytes(INDICATOR_CODE + 0x02AD, bytearray([0x80, 0x02])) # .mushroom bra .shared_item + rom.write_bytes(INDICATOR_CODE + 0x02AF, bytearray([0x80, 0x00])) # .flower bra .shared_item + rom.write_bytes(INDICATOR_CODE + 0x02B1, bytearray([0xA9, 0x0B])) # .shared_item lda #$0B + rom.write_bytes(INDICATOR_CODE + 0x02B3, bytearray([0x8D, 0xFC, 0x1D])) # sta $1DFC + rom.write_bytes(INDICATOR_CODE + 0x02B6, bytearray([0x4C, 0xCB, 0xC0])) # jmp .handle_movement + +def handle_traps(rom): + TRAPS_CODE = 0x86C00 + rom.write_bytes(0x022D8, bytearray([0x22, 0x00, 0xEC, 0x10])) # org $00A2D8 : jsl score_sprites + rom.write_bytes(TRAPS_CODE + 0x0000, bytearray([0xAD, 0x00, 0x01])) # handle_traps: lda $0100 + rom.write_bytes(TRAPS_CODE + 0x0003, bytearray([0xC9, 0x14])) # cmp #$14 + rom.write_bytes(TRAPS_CODE + 0x0005, bytearray([0xD0, 0x04])) # bne .invalid + rom.write_bytes(TRAPS_CODE + 0x0007, bytearray([0xA5, 0x71])) # lda $71 + rom.write_bytes(TRAPS_CODE + 0x0009, bytearray([0xF0, 0x09])) # beq .valid + rom.write_bytes(TRAPS_CODE + 0x000B, bytearray([0xA9, 0xFF])) # .invalid lda #$FF + rom.write_bytes(TRAPS_CODE + 0x000D, bytearray([0x8D, 0x3C, 0x0F])) # sta !thwimp_index + rom.write_bytes(TRAPS_CODE + 0x0010, bytearray([0x5C, 0xBD, 0xE2, 0x00])) # jml $00E2BD + rom.write_bytes(TRAPS_CODE + 0x0014, bytearray([0xAD, 0xB4, 0x18])) # .valid lda !reverse_controls_trap + rom.write_bytes(TRAPS_CODE + 0x0017, bytearray([0xF0, 0x03])) # beq .no_reverse_controls + rom.write_bytes(TRAPS_CODE + 0x0019, bytearray([0x20, 0x2B, 0xEC])) # jsr reverse_controls_trap + rom.write_bytes(TRAPS_CODE + 0x001C, bytearray([0xAD, 0xB7, 0x18])) # .no_reverse_controls lda !thwimp_trap + rom.write_bytes(TRAPS_CODE + 0x001F, bytearray([0xF0, 0x03])) # beq .no_thwimp + rom.write_bytes(TRAPS_CODE + 0x0021, bytearray([0x20, 0x86, 0xEC])) # jsr spawn_thwimp + rom.write_bytes(TRAPS_CODE + 0x0024, bytearray([0x20, 0xCB, 0xEC])) # .no_thwimp jsr handle_thwimp + rom.write_bytes(TRAPS_CODE + 0x0027, bytearray([0x5C, 0xBD, 0xE2, 0x00])) # jml $00E2BD + rom.write_bytes(TRAPS_CODE + 0x002B, bytearray([0xA5, 0x15])) # reverse_controls_trap: lda $15 + rom.write_bytes(TRAPS_CODE + 0x002D, bytearray([0x89, 0x03])) # bit #$03 + rom.write_bytes(TRAPS_CODE + 0x002F, bytearray([0xF0, 0x04])) # beq ..no_swap_hold + rom.write_bytes(TRAPS_CODE + 0x0031, bytearray([0x49, 0x03])) # eor #$03 + rom.write_bytes(TRAPS_CODE + 0x0033, bytearray([0x85, 0x15])) # sta $15 + rom.write_bytes(TRAPS_CODE + 0x0035, bytearray([0xA5, 0x16])) # ..no_swap_hold lda $16 + rom.write_bytes(TRAPS_CODE + 0x0037, bytearray([0x89, 0x03])) # bit #$03 + rom.write_bytes(TRAPS_CODE + 0x0039, bytearray([0xF0, 0x04])) # beq ..no_swap_press + rom.write_bytes(TRAPS_CODE + 0x003B, bytearray([0x49, 0x03])) # eor #$03 + rom.write_bytes(TRAPS_CODE + 0x003D, bytearray([0x85, 0x16])) # sta $16 + rom.write_bytes(TRAPS_CODE + 0x003F, bytearray([0xA5, 0x15])) # .swap_up_and_down lda $15 + rom.write_bytes(TRAPS_CODE + 0x0041, bytearray([0x89, 0x0C])) # bit #$0C + rom.write_bytes(TRAPS_CODE + 0x0043, bytearray([0xF0, 0x04])) # beq .no_swap_hold + rom.write_bytes(TRAPS_CODE + 0x0045, bytearray([0x49, 0x0C])) # eor #$0C + rom.write_bytes(TRAPS_CODE + 0x0047, bytearray([0x85, 0x15])) # sta $15 + rom.write_bytes(TRAPS_CODE + 0x0049, bytearray([0xA5, 0x16])) # .no_swap_hold lda $16 + rom.write_bytes(TRAPS_CODE + 0x004B, bytearray([0x89, 0x0C])) # bit #$0C + rom.write_bytes(TRAPS_CODE + 0x004D, bytearray([0xF0, 0x04])) # beq ..no_swap_press + rom.write_bytes(TRAPS_CODE + 0x004F, bytearray([0x49, 0x0C])) # eor #$0C + rom.write_bytes(TRAPS_CODE + 0x0051, bytearray([0x85, 0x16])) # sta $16 + rom.write_bytes(TRAPS_CODE + 0x0053, bytearray([0xA5, 0x16])) # .swap_a_and_b lda $16 + rom.write_bytes(TRAPS_CODE + 0x0055, bytearray([0x10, 0x0C])) # bpl ..no_swap_b + rom.write_bytes(TRAPS_CODE + 0x0057, bytearray([0x49, 0x80])) # eor #$80 + rom.write_bytes(TRAPS_CODE + 0x0059, bytearray([0x85, 0x16])) # sta $16 + rom.write_bytes(TRAPS_CODE + 0x005B, bytearray([0xA5, 0x18])) # lda $18 + rom.write_bytes(TRAPS_CODE + 0x005D, bytearray([0x49, 0x80])) # eor #$80 + rom.write_bytes(TRAPS_CODE + 0x005F, bytearray([0x85, 0x18])) # sta $18 + rom.write_bytes(TRAPS_CODE + 0x0061, bytearray([0x80, 0x0E])) # bra .swap_l_and_r + rom.write_bytes(TRAPS_CODE + 0x0063, bytearray([0xA5, 0x18])) # ..no_swap_b lda $18 + rom.write_bytes(TRAPS_CODE + 0x0065, bytearray([0x10, 0x0A])) # bpl .swap_l_and_r + rom.write_bytes(TRAPS_CODE + 0x0067, bytearray([0x49, 0x80])) # eor #$80 + rom.write_bytes(TRAPS_CODE + 0x0069, bytearray([0x85, 0x18])) # sta $18 + rom.write_bytes(TRAPS_CODE + 0x006B, bytearray([0xA5, 0x16])) # lda $16 + rom.write_bytes(TRAPS_CODE + 0x006D, bytearray([0x49, 0x80])) # eor #$80 + rom.write_bytes(TRAPS_CODE + 0x006F, bytearray([0x85, 0x16])) # sta $16 + rom.write_bytes(TRAPS_CODE + 0x0071, bytearray([0xA5, 0x17])) # .swap_l_and_r lda $17 + rom.write_bytes(TRAPS_CODE + 0x0073, bytearray([0x89, 0x30])) # bit #$30 + rom.write_bytes(TRAPS_CODE + 0x0075, bytearray([0xF0, 0x04])) # beq ..no_swap_hold + rom.write_bytes(TRAPS_CODE + 0x0077, bytearray([0x49, 0x30])) # eor #$30 + rom.write_bytes(TRAPS_CODE + 0x0079, bytearray([0x85, 0x17])) # sta $17 + rom.write_bytes(TRAPS_CODE + 0x007B, bytearray([0xA5, 0x18])) # ..no_swap_hold lda $18 + rom.write_bytes(TRAPS_CODE + 0x007D, bytearray([0x89, 0x30])) # bit #$30 + rom.write_bytes(TRAPS_CODE + 0x007F, bytearray([0xF0, 0x04])) # beq ..no_swap_press + rom.write_bytes(TRAPS_CODE + 0x0081, bytearray([0x49, 0x30])) # eor #$30 + rom.write_bytes(TRAPS_CODE + 0x0083, bytearray([0x85, 0x18])) # sta $18 + rom.write_bytes(TRAPS_CODE + 0x0085, bytearray([0x60])) # ..no_swap_press rts + rom.write_bytes(TRAPS_CODE + 0x0086, bytearray([0xAE, 0x3C, 0x0F])) # spawn_thwimp: ldx !thwimp_index + rom.write_bytes(TRAPS_CODE + 0x0089, bytearray([0x10, 0x06])) # bpl .return + rom.write_bytes(TRAPS_CODE + 0x008B, bytearray([0x22, 0xE4, 0xA9, 0x02])) # jsl $02A9E4 + rom.write_bytes(TRAPS_CODE + 0x008F, bytearray([0x10, 0x01])) # bpl .found + rom.write_bytes(TRAPS_CODE + 0x0091, bytearray([0x60])) # .return rts + rom.write_bytes(TRAPS_CODE + 0x0092, bytearray([0xBB])) # .found tyx + rom.write_bytes(TRAPS_CODE + 0x0093, bytearray([0x9C, 0xB7, 0x18])) # stz !thwimp_trap + rom.write_bytes(TRAPS_CODE + 0x0096, bytearray([0xA9, 0x10])) # lda #$10 + rom.write_bytes(TRAPS_CODE + 0x0098, bytearray([0x8D, 0xF9, 0x1D])) # sta $1DF9 + rom.write_bytes(TRAPS_CODE + 0x009B, bytearray([0xA9, 0x27])) # lda #$27 + rom.write_bytes(TRAPS_CODE + 0x009D, bytearray([0x95, 0x9E])) # sta $9E,x + rom.write_bytes(TRAPS_CODE + 0x009F, bytearray([0xA9, 0x08])) # lda #$08 + rom.write_bytes(TRAPS_CODE + 0x00A1, bytearray([0x9D, 0xC8, 0x14])) # sta $14C8,x + rom.write_bytes(TRAPS_CODE + 0x00A4, bytearray([0x22, 0xD2, 0xF7, 0x07])) # jsl $07F7D2 + rom.write_bytes(TRAPS_CODE + 0x00A8, bytearray([0xA5, 0x94])) # lda $94 + rom.write_bytes(TRAPS_CODE + 0x00AA, bytearray([0x95, 0xE4])) # sta $E4,x + rom.write_bytes(TRAPS_CODE + 0x00AC, bytearray([0xA5, 0x95])) # lda $95 + rom.write_bytes(TRAPS_CODE + 0x00AE, bytearray([0x9D, 0xE0, 0x14])) # sta $14E0,x + rom.write_bytes(TRAPS_CODE + 0x00B1, bytearray([0xA5, 0x1C])) # lda $1C + rom.write_bytes(TRAPS_CODE + 0x00B3, bytearray([0x38])) # sec + rom.write_bytes(TRAPS_CODE + 0x00B4, bytearray([0xE9, 0x0F])) # sbc #$0F + rom.write_bytes(TRAPS_CODE + 0x00B6, bytearray([0x95, 0xD8])) # sta $D8,x + rom.write_bytes(TRAPS_CODE + 0x00B8, bytearray([0xA5, 0x1D])) # lda $1D + rom.write_bytes(TRAPS_CODE + 0x00BA, bytearray([0xE9, 0x00])) # sbc #$00 + rom.write_bytes(TRAPS_CODE + 0x00BC, bytearray([0x9D, 0xD4, 0x14])) # sta $14D4,x + rom.write_bytes(TRAPS_CODE + 0x00BF, bytearray([0xBD, 0x86, 0x16])) # lda $1686,x + rom.write_bytes(TRAPS_CODE + 0x00C2, bytearray([0x09, 0x80])) # ora #$80 + rom.write_bytes(TRAPS_CODE + 0x00C4, bytearray([0x9D, 0x86, 0x16])) # sta $1686,x + rom.write_bytes(TRAPS_CODE + 0x00C7, bytearray([0x8E, 0x3C, 0x0F])) # stx !thwimp_index + rom.write_bytes(TRAPS_CODE + 0x00CA, bytearray([0x60])) # rts + rom.write_bytes(TRAPS_CODE + 0x00CB, bytearray([0xAE, 0x3C, 0x0F])) # handle_thwimp: ldx !thwimp_index + rom.write_bytes(TRAPS_CODE + 0x00CE, bytearray([0x30, 0x1C])) # bmi .return + rom.write_bytes(TRAPS_CODE + 0x00D0, bytearray([0xBD, 0xD4, 0x14])) # lda $14D4,x + rom.write_bytes(TRAPS_CODE + 0x00D3, bytearray([0xEB])) # xba + rom.write_bytes(TRAPS_CODE + 0x00D4, bytearray([0xB5, 0xD8])) # lda $D8,x + rom.write_bytes(TRAPS_CODE + 0x00D6, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(TRAPS_CODE + 0x00D8, bytearray([0x38])) # sec + rom.write_bytes(TRAPS_CODE + 0x00D9, bytearray([0xE5, 0x96])) # sbc $96 + rom.write_bytes(TRAPS_CODE + 0x00DB, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(TRAPS_CODE + 0x00DD, bytearray([0x30, 0x0D])) # bmi .return + rom.write_bytes(TRAPS_CODE + 0x00DF, bytearray([0xA9, 0xFF])) # lda #$FF + rom.write_bytes(TRAPS_CODE + 0x00E1, bytearray([0x8D, 0x3C, 0x0F])) # sta !thwimp_index + rom.write_bytes(TRAPS_CODE + 0x00E4, bytearray([0xBD, 0x86, 0x16])) # lda $1686,x + rom.write_bytes(TRAPS_CODE + 0x00E7, bytearray([0x29, 0x7F])) # and #$7F + rom.write_bytes(TRAPS_CODE + 0x00E9, bytearray([0x9D, 0x86, 0x16])) # sta $1686,x + rom.write_bytes(TRAPS_CODE + 0x00EC, bytearray([0x60])) # .return rts + + + +def read_graphics_file(filename): + return pkgutil.get_data(__name__, f"data/graphics/{filename}") + +def handle_uncompressed_player_gfx(rom): + # Decompresses and moves into a expanded region the player, yoshi and animated graphics + # This should make swapping the graphics a lot easier. + # Maybe I should look into making a 32x32 version at some point... + # It also moves some 8x8 tiles in GFX00, thus making some free space for indicators and other stuff + # in VRAM during gameplay, will come super handy later. + # + # FOR FUTURE REFERENCE + # Player graphics are now located at 0xE0000 + # Player auxiliary tiles are now located at 0xE6000 + # Yoshi graphics are now located at 0xE8800 + SMW_COMPRESSED_PLAYER_GFX = 0x40000 + SMW_COMPRESSED_ANIMATED_GFX = 0x43FC0 + SMW_COMPRESSED_GFX_00 = 0x459F9 + SMW_COMPRESSED_GFX_10 = 0x4EF1E + SMW_COMPRESSED_GFX_28 = 0x5C06C + compressed_player_gfx = rom.read_bytes(SMW_COMPRESSED_PLAYER_GFX, 0x3FC0) + compressed_animated_gfx = rom.read_bytes(SMW_COMPRESSED_ANIMATED_GFX, 0x1A39) + compressed_gfx_00 = rom.read_bytes(SMW_COMPRESSED_GFX_00, 0x0838) + compressed_gfx_10 = rom.read_bytes(SMW_COMPRESSED_GFX_10, 0x0891) + compressed_gfx_28 = rom.read_bytes(SMW_COMPRESSED_GFX_28, 0x0637) + decompressed_player_gfx = decompress_gfx(compressed_player_gfx) + decompressed_animated_gfx = convert_3bpp(decompress_gfx(compressed_animated_gfx)) + decompressed_gfx_00 = convert_3bpp(decompress_gfx(compressed_gfx_00)) + decompressed_gfx_10 = convert_3bpp(decompress_gfx(compressed_gfx_10)) + decompressed_gfx_28 = decompress_gfx(compressed_gfx_28) + + # Copy berry tiles + order = [0x26C, 0x26D, 0x26E, 0x26F, + 0x27C, 0x27D, 0x27E, 0x27F, + 0x2E0, 0x2E1, 0x2E2, 0x2E3, + 0x2E4, 0x2E5, 0x2E6, 0x2E7] + decompressed_animated_gfx += copy_gfx_tiles(decompressed_player_gfx, order, [5, 32]) + + # Copy Mario's auxiliary tiles + order = [0x80, 0x91, 0x81, 0x90, 0x82, 0x83] + decompressed_gfx_00 += copy_gfx_tiles(decompressed_player_gfx, order, [5, 32]) + order = [0x69, 0x69, 0x0C, 0x69, 0x1A, 0x1B, 0x0D, 0x69, 0x22, 0x23, 0x32, 0x33, 0x0A, 0x0B, 0x20, 0x21, + 0x30, 0x31, 0x7E, 0x69, 0x80, 0x4A, 0x81, 0x5B, 0x82, 0x4B, 0x83, 0x5A, 0x84, 0x69, 0x85, 0x85] + player_small_tiles = copy_gfx_tiles(decompressed_gfx_00, order, [5, 32]) + + # Copy OW mario tiles + order = [0x06, 0x07, 0x16, 0x17, + 0x08, 0x09, 0x18, 0x19, + 0x0A, 0x0B, 0x1A, 0x1B, + 0x0C, 0x0D, 0x1C, 0x1D, + 0x0E, 0x0F, 0x1E, 0x1F, + 0x20, 0x21, 0x30, 0x31, + 0x24, 0x25, 0x34, 0x35, + 0x46, 0x47, 0x56, 0x57, + 0x64, 0x65, 0x74, 0x75, + 0x66, 0x67, 0x76, 0x77, + 0x2E, 0x2F, 0x3E, 0x3F, + 0x40, 0x41, 0x50, 0x51, + 0x42, 0x43, 0x52, 0x53] + player_map_tiles = copy_gfx_tiles(decompressed_gfx_10, order, [5, 32]) + + # Copy HUD mario tiles + order = [0x30, 0x31, 0x32, 0x33, 0x34] + player_name_tiles = copy_gfx_tiles(decompressed_gfx_28, order, [4, 16]) + + rom.write_bytes(0xE0000, decompressed_player_gfx) + rom.write_bytes(0xE8000, decompressed_animated_gfx) + rom.write_bytes(0xE6000, player_small_tiles) + rom.write_bytes(0xE6400, player_map_tiles) + rom.write_bytes(0xE6C00, player_name_tiles) + + # Skip Player & Animated tile decompression + rom.write_bytes(0x03888, bytearray([0x60])) # RTS + + # Edit Mario DMA routine + MARIO_GFX_DMA_ADDR = 0x02300 + rom.write_bytes(MARIO_GFX_DMA_ADDR + 0x0000, bytearray([0xA2, 0x04])) # LDX #$04 + rom.write_bytes(MARIO_GFX_DMA_ADDR + 0x0002, bytearray([0x22, 0x00, 0xF0, 0x10])) # JSL $10F000 ; upload_score_sprite_gfx + rom.write_bytes(MARIO_GFX_DMA_ADDR + 0x0006, bytearray([0x22, 0x00, 0xF8, 0x0F])) # JSL $0FF800 ; player_code + rom.write_bytes(MARIO_GFX_DMA_ADDR + 0x000A, bytearray([0x60])) # RTS + + PLAYER_UPLOAD_ADDR = 0x7F800 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0000, bytearray([0xC2, 0x20])) # player_code: rep #$20 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0002, bytearray([0xAC, 0x84, 0x0D])) # ldy $0D84 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0005, bytearray([0xD0, 0x03])) # bne .upload_player_palette + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0007, bytearray([0x4C, 0xD2, 0xF8])) # jmp .skip_everything + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x000A, bytearray([0xA0, 0x86])) # .upload_player_palette ldy #$86 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x000C, bytearray([0x8C, 0x21, 0x21])) # sty $2121 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x000F, bytearray([0xA9, 0x00, 0x22])) # lda #$2200 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0012, bytearray([0x8D, 0x20, 0x43])) # sta $4320 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0015, bytearray([0xA8])) # tay + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0016, bytearray([0xAD, 0x82, 0x0D])) # lda $0D82 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0019, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x001C, bytearray([0x8C, 0x24, 0x43])) # sty $4324 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x001F, bytearray([0xA9, 0x14, 0x00])) # lda #$0014 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0022, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0025, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0028, bytearray([0xA0, 0x80])) # ldy #$80 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x002A, bytearray([0x8C, 0x15, 0x21])) # sty $2115 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x002D, bytearray([0xA9, 0x01, 0x18])) # lda #$1801 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0030, bytearray([0x8D, 0x20, 0x43])) # sta $4320 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0033, bytearray([0xA0, 0x1C])) # ldy.b #player_gfx>>16 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0035, bytearray([0x8C, 0x24, 0x43])) # sty $4324 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0038, bytearray([0xA9, 0x00, 0x60])) # .upload_player_top lda #$6000 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x003B, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x003E, bytearray([0xA8])) # tay + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x003F, bytearray([0xB9, 0x85, 0x0D])) # - lda $0D85,y + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0042, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0045, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0048, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x004B, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x004E, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x004F, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0050, bytearray([0xC0, 0x06])) # cpy #$06 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0052, bytearray([0xD0, 0xEB])) # bne - + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0054, bytearray([0xA9, 0x00, 0x61])) # .upload_player_bottom lda #$6100 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0057, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x005A, bytearray([0xA8])) # tay + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x005B, bytearray([0xB9, 0x8F, 0x0D])) # - lda $0D8F,y + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x005E, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0061, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0064, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0067, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x006A, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x006B, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x006C, bytearray([0xC0, 0x06])) # cpy #$06 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x006E, bytearray([0xD0, 0xEB])) # bne - + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0070, bytearray([0xAC, 0x9B, 0x0D])) # .upload_player_extended ldy $0D9B + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0073, bytearray([0xC0, 0x02])) # cpy #$02 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0075, bytearray([0xF0, 0x5B])) # beq .skip_everything + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0077, bytearray([0xA9, 0xC0, 0x60])) # lda #$60C0 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x007A, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x007D, bytearray([0xAD, 0x99, 0x0D])) # lda $0D99 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0080, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0083, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0086, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0089, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x008C, bytearray([0xA0, 0x1D])) # .upload_misc_tiles ldy.b #animated_tiles>>16 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x008E, bytearray([0x8C, 0x24, 0x43])) # sty $4324 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0091, bytearray([0xA9, 0x60, 0x60])) # lda #$6060 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0094, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0097, bytearray([0xA0, 0x06])) # ldy #$06 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x0099, bytearray([0xCC, 0x84, 0x0D])) # cpy $0D84 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x009C, bytearray([0xB0, 0x34])) # bcs .skip_everything + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x009E, bytearray([0xB9, 0x85, 0x0D])) # - lda $0D85,y + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00A1, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00A4, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00A7, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00AA, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00AD, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00AE, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00AF, bytearray([0xCC, 0x84, 0x0D])) # cpy $0D84 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00B2, bytearray([0x90, 0xEA])) # bcc - + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00B4, bytearray([0xA9, 0x60, 0x61])) # lda #$6160 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00B7, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00BA, bytearray([0xA0, 0x06])) # ldy #$06 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00BC, bytearray([0xB9, 0x8F, 0x0D])) # - lda $0D8F,y + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00BF, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00C2, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00C5, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00C8, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00CB, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00CC, bytearray([0xC8])) # iny + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00CD, bytearray([0xCC, 0x84, 0x0D])) # cpy $0D84 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00D0, bytearray([0x90, 0xEA])) # bcc - + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00D2, bytearray([0xE2, 0x20])) # .skip_everything sep #$20 + rom.write_bytes(PLAYER_UPLOAD_ADDR + 0x00D4, bytearray([0x6B])) # rtl + + # Obtain data for new 8x8 tile + CHAR_TILE_CODE_ADDR = 0x05FE2 + rom.write_bytes(0x063B1, bytearray([0x20, 0xE2, 0xDF])) # jsr $DFE2 + rom.write_bytes(CHAR_TILE_CODE_ADDR + 0x0000, bytearray([0xB9, 0x1A, 0xDF])) # lda $DF1A,y + rom.write_bytes(CHAR_TILE_CODE_ADDR + 0x0003, bytearray([0x10, 0x06])) # bpl $06 + rom.write_bytes(CHAR_TILE_CODE_ADDR + 0x0005, bytearray([0x29, 0x7F])) # and #$7F + rom.write_bytes(CHAR_TILE_CODE_ADDR + 0x0007, bytearray([0x85, 0x0D])) # sta $0D + rom.write_bytes(CHAR_TILE_CODE_ADDR + 0x0009, bytearray([0xA9, 0x04])) # lda #$04 + rom.write_bytes(CHAR_TILE_CODE_ADDR + 0x000B, bytearray([0x60])) # rts + + rom.write_bytes(0x0640D, bytearray([0x20, 0xEE, 0xDF])) # jsr $DFEE + CAPE_TILE_CODE_ADDR = 0x05FEE + rom.write_bytes(CAPE_TILE_CODE_ADDR + 0x0000, bytearray([0xA5, 0x0D])) # lda $0D + rom.write_bytes(CAPE_TILE_CODE_ADDR + 0x0002, bytearray([0xE0, 0x2B])) # cpx #$2B + rom.write_bytes(CAPE_TILE_CODE_ADDR + 0x0004, bytearray([0x90, 0x07])) # bcc $07 + rom.write_bytes(CAPE_TILE_CODE_ADDR + 0x0006, bytearray([0xE0, 0x40])) # cpx #$40 + rom.write_bytes(CAPE_TILE_CODE_ADDR + 0x0008, bytearray([0xB0, 0x03])) # bcs $03 + rom.write_bytes(CAPE_TILE_CODE_ADDR + 0x000A, bytearray([0xBD, 0xD7, 0xE1])) # lda $E1D7,x + rom.write_bytes(CAPE_TILE_CODE_ADDR + 0x000D, bytearray([0x60])) # rts + + # Edit Mario's 8x8 tile data + MARIO_AUX_TILE_DATA_ADDR = 0x05F1A + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0000, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0008, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0010, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0018, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0020, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0028, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0030, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0038, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0040, bytearray([0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0048, bytearray([0x00,0x00,0x82,0x82,0x82,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0050, bytearray([0x00,0x00,0x84,0x00,0x00,0x00,0x00,0x86])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0058, bytearray([0x86,0x86,0x00,0x00,0x88,0x88,0x8A,0x8A])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0060, bytearray([0x8C,0x8C,0x00,0x00,0x90,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0068, bytearray([0x00,0x8E,0x00,0x00,0x00,0x00,0x92,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0070, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0078, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0080, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x82])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0088, bytearray([0x82,0x82,0x00,0x00,0x00,0x00,0x00,0x84])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0090, bytearray([0x00,0x00,0x00,0x00,0x86,0x86,0x86,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x0098, bytearray([0x00,0x88,0x88,0x8A,0x8A,0x8C,0x8C,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x00A0, bytearray([0x00,0x90,0x00,0x00,0x00,0x00,0x8E,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x00A8, bytearray([0x00,0x00,0x00,0x92,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x00B0, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + rom.write_bytes(MARIO_AUX_TILE_DATA_ADDR + 0x00B8, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00])) + + MARIO_AUX_TILE_OFFSETS_ADDR = 0x05FDA # ends at $00E00C + rom.write_bytes(MARIO_AUX_TILE_OFFSETS_ADDR + 0x0000, bytearray([0x00,0x02,0x80,0x80,0x00,0x02,0x0C,0x0D])) + rom.write_bytes(MARIO_AUX_TILE_OFFSETS_ADDR + 0x0022, bytearray([0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x02])) + rom.write_bytes(MARIO_AUX_TILE_OFFSETS_ADDR + 0x002A, bytearray([0x02,0x80,0x04,0x0C,0x0D,0xFF,0xFF,0xFF])) + + MARIO_AUX_CAPE_TILE_DATA_ADDR = 0x061FF + rom.write_bytes(MARIO_AUX_CAPE_TILE_DATA_ADDR + 0x0000, bytearray([0x00,0x8C,0x14,0x14,0x2E])) + rom.write_bytes(MARIO_AUX_CAPE_TILE_DATA_ADDR + 0x0005, bytearray([0x00,0xCA,0x16,0x16,0x2E])) + rom.write_bytes(MARIO_AUX_CAPE_TILE_DATA_ADDR + 0x000A, bytearray([0x00,0x8E,0x18,0x18,0x2E])) + rom.write_bytes(MARIO_AUX_CAPE_TILE_DATA_ADDR + 0x000F, bytearray([0x00,0xEB,0x1A,0x1A,0x2E])) + rom.write_bytes(MARIO_AUX_CAPE_TILE_DATA_ADDR + 0x0014, bytearray([0x04,0xED,0x1C,0x1C])) + + # Edit player data offsets + rom.write_bytes(0x07649, bytearray([0x69, 0x00, 0x80])) # adc #$8000 + rom.write_bytes(0x07667, bytearray([0x69, 0x00, 0x80])) # adc #$8000 + rom.write_bytes(0x0767C, bytearray([0x69, 0x00, 0x80])) # adc #$8000 + rom.write_bytes(0x07691, bytearray([0x69, 0x00, 0xE0])) # adc #$E000 + + # Fix berries + FIX_BERRIES_ADDR = 0x7FFE0 + rom.write_bytes(FIX_BERRIES_ADDR + 0x0000, bytearray([0xA0, 0x1D])) # fix_berries: ldy.b #animated_tiles>>16 + rom.write_bytes(FIX_BERRIES_ADDR + 0x0002, bytearray([0x8C, 0x24, 0x43])) # sty $4324 + rom.write_bytes(FIX_BERRIES_ADDR + 0x0005, bytearray([0xAD, 0x76, 0x0D])) # lda $0D76 + rom.write_bytes(FIX_BERRIES_ADDR + 0x0008, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(FIX_BERRIES_ADDR + 0x000B, bytearray([0x6B])) # rtl + + # Fix animated graphics + rom.write_bytes(0x018D1, bytearray([0x1D])) # db $1D + rom.write_bytes(0x0239E, bytearray([0x1D])) # db $1D + + rom.write_bytes(0x023F0, bytearray([0x22, 0xE0, 0xFF, 0x0F])) # jsl $0FFFE0 + rom.write_bytes(0x023F4, bytearray([0xEA])) # nop + rom.write_bytes(0x023F5, bytearray([0xEA])) # nop + + rom.write_bytes(0x0E1A8, bytearray([0x69, 0x00, 0x88])) # adc #$8800 + rom.write_bytes(0x0EEB4, bytearray([0x69, 0x00, 0x88])) # adc #$8800 + rom.write_bytes(0x0EEC9, bytearray([0x69, 0x00, 0x88])) # adc #$8800 + rom.write_bytes(0x16A3E, bytearray([0x69, 0x00, 0x88])) # adc #$8800 + + ANIMATED_TILE_DATA_ADDR = 0x2B999 + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0000, bytearray([0x00,0x98,0x00,0x9A,0x00,0x9C,0x00,0x9E])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0008, bytearray([0x80,0x98,0x80,0x9A,0x80,0x9C,0x80,0x9E])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0010, bytearray([0x00,0x99,0x00,0x99,0x00,0x99,0x00,0x99])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0018, bytearray([0x80,0xA0,0x80,0xA2,0x80,0xA4,0x80,0xA6])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0020, bytearray([0x00,0x99,0x00,0x9B,0x00,0x9D,0x00,0x9F])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0028, bytearray([0x00,0xB0,0x80,0xB0,0x00,0xB1,0x80,0xB1])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0030, bytearray([0x20,0xAF,0x20,0xAF,0x20,0xAF,0x20,0xAF])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0038, bytearray([0x20,0xAF,0x20,0xAF,0x20,0xAF,0x20,0xAF])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0040, bytearray([0x80,0x96,0x80,0x96,0x80,0x96,0x80,0x96])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0048, bytearray([0x00,0xA7,0x80,0xA7,0x00,0xA7,0x80,0xA7])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0050, bytearray([0x20,0xAF,0x20,0xAF,0x20,0xAF,0x20,0xAF])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0058, bytearray([0x00,0xAF,0x00,0xAF,0x00,0xAF,0x00,0xAF])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0060, bytearray([0x00,0x94,0x00,0x94,0x00,0x94,0x00,0x94])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0068, bytearray([0x80,0x99,0x80,0x9B,0x80,0x9D,0x80,0x9F])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0070, bytearray([0x00,0xA0,0x00,0xA2,0x00,0xA4,0x00,0xA6])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0078, bytearray([0x80,0x91,0x80,0x93,0x80,0x95,0x80,0x97])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0080, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0088, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0090, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0098, bytearray([0x00,0xA0,0x00,0xA2,0x00,0xA4,0x00,0xA6])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00A0, bytearray([0x80,0x91,0x80,0x93,0x80,0x95,0x80,0x97])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00A8, bytearray([0x00,0x80,0x00,0x82,0x00,0x84,0x00,0x86])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00B0, bytearray([0x00,0x86,0x00,0x84,0x00,0x82,0x00,0x80])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00B8, bytearray([0x00,0xA1,0x00,0xA3,0x00,0xA5,0x00,0xA3])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00C0, bytearray([0x00,0xA0,0x00,0xA2,0x00,0xA4,0x00,0xA6])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00C8, bytearray([0x00,0xA8,0x00,0xAA,0x00,0xAC,0x00,0xAE])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00D0, bytearray([0x80,0xA8,0x80,0xAA,0x80,0xAC,0x80,0xAE])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00D8, bytearray([0x80,0xAE,0x80,0xAC,0x80,0xAA,0x80,0xA8])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00E0, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00E8, bytearray([0x80,0xA1,0x80,0xA3,0x80,0xA5,0x80,0xA3])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00F0, bytearray([0x80,0x80,0x80,0x82,0x80,0x84,0x80,0x86])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x00F8, bytearray([0x00,0x81,0x00,0x83,0x00,0x85,0x00,0x87])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0100, bytearray([0x80,0x81,0x80,0x83,0x80,0x85,0x80,0x87])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0108, bytearray([0x80,0x86,0x80,0x84,0x80,0x82,0x80,0x80])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0110, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0118, bytearray([0x80,0xA9,0x80,0xAB,0x80,0xAD,0x80,0xAB])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0120, bytearray([0x00,0x91,0x00,0x93,0x00,0x95,0x00,0x97])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0128, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0130, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0138, bytearray([0x80,0xA1,0x80,0xA3,0x80,0xA5,0x80,0xA3])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0140, bytearray([0x00,0xA9,0x00,0xAB,0x00,0xAD,0x00,0xAB])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0148, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0150, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0158, bytearray([0x00,0x98,0x00,0x98,0x00,0x98,0x00,0x98])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0160, bytearray([0x80,0x94,0x80,0x94,0x80,0x94,0x80,0x94])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0168, bytearray([0x80,0x99,0x80,0x9B,0x80,0x9D,0x80,0x9F])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0170, bytearray([0x80,0x99,0x80,0x9B,0x80,0x9D,0x80,0x9F])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0178, bytearray([0x80,0x99,0x80,0x9B,0x80,0x9D,0x80,0x9F])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0180, bytearray([0x00,0x98,0x00,0x9A,0x00,0x9C,0x00,0x9E])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0188, bytearray([0x80,0xAF,0x80,0xAF,0x80,0xAF,0x80,0xAF])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0190, bytearray([0x00,0x96,0x00,0x96,0x00,0x96,0x00,0x96])) + rom.write_bytes(ANIMATED_TILE_DATA_ADDR + 0x0198, bytearray([0x80,0x96,0x80,0x96,0x80,0x96,0x80,0x96])) + + # Insert hand drawn graphics for in level indicators + rom.write_bytes(0xE7000, read_graphics_file("indicators.bin")) + # Upload indicator GFX + UPLOAD_INDICATOR_GFX = 0x87000 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0000, bytearray([0xAD, 0x00, 0x01])) # upload_score_sprite_gfx: lda $0100 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0003, bytearray([0xC9, 0x13])) # cmp #$13 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0005, bytearray([0xF0, 0x03])) # beq .check_level + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0007, bytearray([0x4C, 0x9D, 0xF0])) # jmp .check_map + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x000A, bytearray([0xA5, 0x7C])) # .check_level lda $7C + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x000C, bytearray([0xF0, 0x03])) # beq ..perform + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x000E, bytearray([0x4C, 0x9C, 0xF0])) # jmp .skip + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0011, bytearray([0xE6, 0x7C])) # ..perform inc $7C + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0013, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0015, bytearray([0xA0, 0x80])) # ldy #$80 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0017, bytearray([0x8C, 0x15, 0x21])) # sty $2115 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x001A, bytearray([0xA9, 0x01, 0x18])) # lda #$1801 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x001D, bytearray([0x8D, 0x20, 0x43])) # sta $4320 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0020, bytearray([0xA0, 0x1C])) # ldy.b #$1C + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0022, bytearray([0x8C, 0x24, 0x43])) # sty $4324 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0025, bytearray([0xA9, 0x00, 0xF0])) # lda.w #$F000 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0028, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x002B, bytearray([0xA9, 0xA0, 0x64])) # .nums_01 lda #$64A0 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x002E, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0031, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0034, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0037, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x003A, bytearray([0xA9, 0xA0, 0x65])) # .nums_35 lda #$65A0 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x003D, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0040, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0043, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0046, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0049, bytearray([0xA9, 0xA0, 0x61])) # .plus_coin lda #$61A0 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x004C, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x004F, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0052, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0055, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0058, bytearray([0xA9, 0xA0, 0x60])) # .egg_mushroom lda #$60A0 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x005B, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x005E, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0061, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0064, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0067, bytearray([0xA9, 0xE0, 0x67])) # .thwimp lda #$67E0 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x006A, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x006D, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0070, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0073, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0076, bytearray([0xA9, 0x80, 0x63])) # .token lda #$6380 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0079, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x007C, bytearray([0xA9, 0x20, 0x00])) # lda #$0020 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x007F, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0082, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0085, bytearray([0xA9, 0x00, 0xEC])) # .layer_3 lda #$EC00 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0088, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x008B, bytearray([0xA9, 0x80, 0x41])) # lda #$4180 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x008E, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0091, bytearray([0xA9, 0x50, 0x00])) # lda #$0050 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0094, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0097, bytearray([0x8E, 0x0B, 0x42])) # stx $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x009A, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x009C, bytearray([0x6B])) # .skip rtl + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x009D, bytearray([0xC9, 0x0E])) # .check_map cmp #$0E + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x009F, bytearray([0xF0, 0x51])) # beq .map_pal + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00A1, bytearray([0xC9, 0x0D])) # cmp #$0D + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00A3, bytearray([0xD0, 0xF7])) # bne .skip + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00A5, bytearray([0xA5, 0x7C])) # lda $7C + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00A7, bytearray([0xD0, 0xF3])) # bne .skip + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00A9, bytearray([0xE6, 0x7C])) # inc $7C + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00AB, bytearray([0xC2, 0x20])) # rep #$20 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00AD, bytearray([0xA0, 0x80])) # ldy #$80 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00AF, bytearray([0x8C, 0x15, 0x21])) # sty $2115 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00B2, bytearray([0xA9, 0x01, 0x18])) # lda #$1801 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00B5, bytearray([0x8D, 0x20, 0x43])) # sta $4320 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00B8, bytearray([0xA0, 0x1C])) # ldy.b #$1C + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00BA, bytearray([0x8C, 0x24, 0x43])) # sty $4324 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00BD, bytearray([0xA9, 0x00, 0xE4])) # lda.w #$E400 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00C0, bytearray([0x8D, 0x22, 0x43])) # sta $4322 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00C3, bytearray([0xDA])) # phx + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00C4, bytearray([0x9B])) # txy + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00C5, bytearray([0xA2, 0x18])) # ldx.b #(.map_targets_end-.map_targets-1)*2 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00C7, bytearray([0xA9, 0x40, 0x00])) # ..loop lda #$0040 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00CA, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00CD, bytearray([0xBF, 0x80, 0xFF, 0x10])) # lda.l .map_targets,x + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00D1, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00D4, bytearray([0x8C, 0x0B, 0x42])) # sty $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00D7, bytearray([0xBF, 0x80, 0xFF, 0x10])) # lda.l .map_targets,x + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00DB, bytearray([0x18])) # clc + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00DC, bytearray([0x69, 0x00, 0x01])) # adc #$0100 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00DF, bytearray([0x8D, 0x16, 0x21])) # sta $2116 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00E2, bytearray([0xA9, 0x40, 0x00])) # lda #$0040 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00E5, bytearray([0x8D, 0x25, 0x43])) # sta $4325 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00E8, bytearray([0x8C, 0x0B, 0x42])) # sty $420B + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00EB, bytearray([0xCA])) # dex + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00EC, bytearray([0xCA])) # dex + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00ED, bytearray([0x10, 0xD8])) # bpl .loop + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00EF, bytearray([0xFA])) # plx + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00F0, bytearray([0xE2, 0x20])) # sep #$20 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00F2, bytearray([0xA9, 0xA3])) # .map_pal lda #$A3 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00F4, bytearray([0x8D, 0x21, 0x21])) # sta $2121 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00F7, bytearray([0xAF, 0x9C, 0xB5, 0x00])) # lda $00B59C + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00FB, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x00FE, bytearray([0xAF, 0x9D, 0xB5, 0x00])) # lda $00B59D + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0102, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0105, bytearray([0xAF, 0x9E, 0xB5, 0x00])) # lda $00B59E + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0109, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x010C, bytearray([0xAF, 0x9F, 0xB5, 0x00])) # lda $00B59F + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0110, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0113, bytearray([0xAF, 0xA0, 0xB5, 0x00])) # lda $00B5A0 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0117, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x011A, bytearray([0xAF, 0xA1, 0xB5, 0x00])) # lda $00B5A1 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x011E, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0121, bytearray([0xAF, 0xA2, 0xB5, 0x00])) # lda $00B5A2 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0125, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0128, bytearray([0xAF, 0xA3, 0xB5, 0x00])) # lda $00B5A3 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x012C, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x012F, bytearray([0xAF, 0xA4, 0xB5, 0x00])) # lda $00B5A4 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0133, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x0136, bytearray([0xAF, 0xA5, 0xB5, 0x00])) # lda $00B5A5 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x013A, bytearray([0x8D, 0x22, 0x21])) # sta $2122 + rom.write_bytes(UPLOAD_INDICATOR_GFX + 0x013D, bytearray([0x6B])) # rtl + + vram_targets = bytearray([ + 0x20,0x64, 0x00,0x64, 0xE0,0x62, + 0x60,0x66, 0x40,0x66, + 0x60,0x64, + 0x40,0x62, 0x00,0x62, + 0xE0,0x60, 0xC0,0x60, 0xA0,0x60, 0x80,0x60, 0x60,0x60 + ]) + rom.write_bytes(0x87F80, vram_targets) + +def decompress_gfx(compressed_graphics): + # This code decompresses graphics in LC_LZ2 format in order to be able to swap player and yoshi's graphics with ease. + decompressed_gfx = bytearray([]) + i = 0 + while True: + cmd = compressed_graphics[i] + i += 1 + if cmd == 0xFF: + break + else: + if (cmd >> 5) == 0x07: + size = ((cmd & 0x03) << 8) + compressed_graphics[i] + 1 + cmd = (cmd & 0x1C) >> 2 + i += 1 + else: + size = (cmd & 0x1F) + 1 + cmd = cmd >> 5 + if cmd == 0x00: + decompressed_gfx += bytearray([compressed_graphics[i+j] for j in range(size)]) + i += size + elif cmd == 0x01: + byte_fill = compressed_graphics[i] + i += 1 + decompressed_gfx += bytearray([byte_fill for j in range(size)]) + elif cmd == 0x02: + byte_fill_1 = compressed_graphics[i] + i += 1 + byte_fill_2 = compressed_graphics[i] + i += 1 + for j in range(size): + if (j & 0x1) == 0x00: + decompressed_gfx += bytearray([byte_fill_1]) + else: + decompressed_gfx += bytearray([byte_fill_2]) + elif cmd == 0x03: + byte_read = compressed_graphics[i] + i += 1 + decompressed_gfx += bytearray([(byte_read + j) for j in range(size)]) + elif cmd == 0x04: + position = (compressed_graphics[i] << 8) + compressed_graphics[i+1] + i += 2 + for j in range(size): + copy_byte = decompressed_gfx[position+j] + decompressed_gfx += bytearray([copy_byte]) + return decompressed_gfx + + +def convert_3bpp(decompressed_gfx): + i = 0 + converted_gfx = bytearray([]) + while i < len(decompressed_gfx): + converted_gfx += bytearray([decompressed_gfx[i+j] for j in range(16)]) + i += 16 + for j in range(8): + converted_gfx += bytearray([decompressed_gfx[i]]) + converted_gfx += bytearray([0x00]) + i += 1 + return converted_gfx + + +def copy_gfx_tiles(original, order, size): + result = bytearray([]) + for x in range(len(order)): + z = order[x] << size[0] + result += bytearray([original[z+y] for y in range(size[1])]) + return result + + +def file_to_bytes(filename): + return open(os.path.dirname(__file__)+filename, "rb").read() + + +def handle_music_shuffle(rom, world: World): from .Aesthetics import generate_shuffled_level_music, generate_shuffled_ow_music, level_music_address_data, ow_music_address_data - shuffled_level_music = generate_shuffled_level_music(world, player) + shuffled_level_music = generate_shuffled_level_music(world) for i in range(len(shuffled_level_music)): rom.write_byte(level_music_address_data[i], shuffled_level_music[i]) - shuffled_ow_music = generate_shuffled_ow_music(world, player) + shuffled_ow_music = generate_shuffled_ow_music(world) for i in range(len(shuffled_ow_music)): for addr in ow_music_address_data[i]: rom.write_byte(addr, shuffled_ow_music[i]) -def handle_mario_palette(rom, world, player): +def handle_mario_palette(rom, world: World): from .Aesthetics import mario_palettes, fire_mario_palettes, ow_mario_palettes - chosen_palette = world.mario_palette[player].value + chosen_palette = world.options.mario_palette.value rom.write_bytes(0x32C8, bytes(mario_palettes[chosen_palette])) rom.write_bytes(0x32F0, bytes(fire_mario_palettes[chosen_palette])) @@ -723,9 +2836,9 @@ def handle_swap_donut_gh_exits(rom): rom.write_bytes(0x26371, bytes([0x32])) -def handle_bowser_rooms(rom, world, player: int): - if world.bowser_castle_rooms[player] == "random_two_room": - chosen_rooms = world.per_slot_randoms[player].sample(standard_bowser_rooms, 2) +def handle_bowser_rooms(rom, world: World): + if world.options.bowser_castle_rooms == "random_two_room": + chosen_rooms = world.random.sample(standard_bowser_rooms, 2) rom.write_byte(0x3A680, chosen_rooms[0].roomID) rom.write_byte(0x3A684, chosen_rooms[0].roomID) @@ -737,8 +2850,8 @@ def handle_bowser_rooms(rom, world, player: int): rom.write_byte(chosen_rooms[len(chosen_rooms)-1].exitAddress, 0xBD) - elif world.bowser_castle_rooms[player] == "random_five_room": - chosen_rooms = world.per_slot_randoms[player].sample(standard_bowser_rooms, 5) + elif world.options.bowser_castle_rooms == "random_five_room": + chosen_rooms = world.random.sample(standard_bowser_rooms, 5) rom.write_byte(0x3A680, chosen_rooms[0].roomID) rom.write_byte(0x3A684, chosen_rooms[0].roomID) @@ -750,9 +2863,9 @@ def handle_bowser_rooms(rom, world, player: int): rom.write_byte(chosen_rooms[len(chosen_rooms)-1].exitAddress, 0xBD) - elif world.bowser_castle_rooms[player] == "gauntlet": + elif world.options.bowser_castle_rooms == "gauntlet": chosen_rooms = standard_bowser_rooms.copy() - world.per_slot_randoms[player].shuffle(chosen_rooms) + world.random.shuffle(chosen_rooms) rom.write_byte(0x3A680, chosen_rooms[0].roomID) rom.write_byte(0x3A684, chosen_rooms[0].roomID) @@ -763,12 +2876,12 @@ def handle_bowser_rooms(rom, world, player: int): rom.write_byte(chosen_rooms[i-1].exitAddress, chosen_rooms[i].roomID) rom.write_byte(chosen_rooms[len(chosen_rooms)-1].exitAddress, 0xBD) - elif world.bowser_castle_rooms[player] == "labyrinth": + elif world.options.bowser_castle_rooms == "labyrinth": bowser_rooms_copy = full_bowser_rooms.copy() entrance_point = bowser_rooms_copy.pop(0) - world.per_slot_randoms[player].shuffle(bowser_rooms_copy) + world.random.shuffle(bowser_rooms_copy) rom.write_byte(entrance_point.exitAddress, bowser_rooms_copy[0].roomID) for i in range(0, len(bowser_rooms_copy) - 1): @@ -777,13 +2890,13 @@ def handle_bowser_rooms(rom, world, player: int): rom.write_byte(bowser_rooms_copy[len(bowser_rooms_copy)-1].exitAddress, 0xBD) -def handle_boss_shuffle(rom, world, player): - if world.boss_shuffle[player] == "simple": +def handle_boss_shuffle(rom, world: World): + if world.options.boss_shuffle == "simple": submap_boss_rooms_copy = submap_boss_rooms.copy() ow_boss_rooms_copy = ow_boss_rooms.copy() - world.per_slot_randoms[player].shuffle(submap_boss_rooms_copy) - world.per_slot_randoms[player].shuffle(ow_boss_rooms_copy) + world.random.shuffle(submap_boss_rooms_copy) + world.random.shuffle(ow_boss_rooms_copy) for i in range(len(submap_boss_rooms_copy)): rom.write_byte(submap_boss_rooms[i].exitAddress, submap_boss_rooms_copy[i].roomID) @@ -794,21 +2907,21 @@ def handle_boss_shuffle(rom, world, player): if ow_boss_rooms[i].exitAddressAlt is not None: rom.write_byte(ow_boss_rooms[i].exitAddressAlt, ow_boss_rooms_copy[i].roomID) - elif world.boss_shuffle[player] == "full": + elif world.options.boss_shuffle == "full": for i in range(len(submap_boss_rooms)): - chosen_boss = world.per_slot_randoms[player].choice(submap_boss_rooms) + chosen_boss = world.random.choice(submap_boss_rooms) rom.write_byte(submap_boss_rooms[i].exitAddress, chosen_boss.roomID) for i in range(len(ow_boss_rooms)): - chosen_boss = world.per_slot_randoms[player].choice(ow_boss_rooms) + chosen_boss = world.random.choice(ow_boss_rooms) rom.write_byte(ow_boss_rooms[i].exitAddress, chosen_boss.roomID) if ow_boss_rooms[i].exitAddressAlt is not None: rom.write_byte(ow_boss_rooms[i].exitAddressAlt, chosen_boss.roomID) - elif world.boss_shuffle[player] == "singularity": - chosen_submap_boss = world.per_slot_randoms[player].choice(submap_boss_rooms) - chosen_ow_boss = world.per_slot_randoms[player].choice(ow_boss_rooms) + elif world.options.boss_shuffle == "singularity": + chosen_submap_boss = world.random.choice(submap_boss_rooms) + chosen_ow_boss = world.random.choice(ow_boss_rooms) for i in range(len(submap_boss_rooms)): rom.write_byte(submap_boss_rooms[i].exitAddress, chosen_submap_boss.roomID) @@ -820,8 +2933,8 @@ def handle_boss_shuffle(rom, world, player): rom.write_byte(ow_boss_rooms[i].exitAddressAlt, chosen_ow_boss.roomID) -def patch_rom(world, rom, player, active_level_dict): - goal_text = generate_goal_text(world, player) +def patch_rom(world: World, rom, player, active_level_dict): + goal_text = generate_goal_text(world) rom.write_bytes(0x2A6E2, goal_text) rom.write_byte(0x2B1D8, 0x80) @@ -829,19 +2942,23 @@ def patch_rom(world, rom, player, active_level_dict): intro_text = generate_text_box("Bowser has stolen all of Mario's abilities. Can you help Mario travel across Dinosaur land to get them back and save the Princess from him?") rom.write_bytes(0x2A5D9, intro_text) - handle_bowser_rooms(rom, world, player) - handle_boss_shuffle(rom, world, player) + handle_bowser_rooms(rom, world) + handle_boss_shuffle(rom, world) + + # Handle ROM expansion + rom.write_bytes(0x07FD7, bytearray([0x0A])) + rom.write_bytes(0x80000, bytearray([0x00 for _ in range(0x80000)])) # Prevent Title Screen Deaths rom.write_byte(0x1C6A, 0x80) # Title Screen Text player_name_bytes = bytearray() - player_name = world.get_player_name(player) + player_name = world.multiworld.get_player_name(player) for i in range(16): char = " " if i < len(player_name): - char = world.get_player_name(player)[i] + char = player_name[i] upper_char = char.upper() if upper_char not in title_text_mapping: for byte in title_text_mapping["."]: @@ -869,33 +2986,58 @@ def patch_rom(world, rom, player, active_level_dict): rom.write_bytes(0x2B88E, bytearray([0x2C, 0x31, 0x73, 0x31, 0x75, 0x31, 0x82, 0x30, 0x30, 0x31, 0xFC, 0x38, 0x31, 0x31, 0x73, 0x31, 0x73, 0x31, 0x7C, 0x30, 0xFC, 0x38, 0xFC, 0x38, 0xFC, 0x38])) # 1 Player Game - rom.write_bytes(0x2B6D7, bytearray([0xFC, 0x38, 0xFC, 0x38, 0x16, 0x38, 0x18, 0x38, 0x0D, 0x38, 0xFC, 0x38, 0x0B, 0x38, 0x22, 0x38, + rom.write_bytes(0x2B6D7, bytearray([0x16, 0x38, 0x18, 0x38, 0x0D, 0x38, 0xFC, 0x38, 0x0B, 0x38, 0x22, 0x38, 0xFC, 0x38, 0x19, 0x38, 0x18, 0x38, 0x1B, 0x38, 0x22, 0x38, 0x10, 0x38, 0x18, 0x38, 0x17, 0x38, - 0x0E, 0x38, 0xFC, 0x38, 0xFC, 0x38])) # Mod by PoryGone + 0x0E, 0x38, 0xFC, 0x38, 0x15, 0x38, 0x21, 0x38, 0x05, 0x38])) # Mod by PoryGone + lx5 # Title Options rom.write_bytes(0x1E6A, bytearray([0x01])) rom.write_bytes(0x1E6C, bytearray([0x01])) rom.write_bytes(0x1E6E, bytearray([0x01])) + # Save current level number to RAM (not translevel) + rom.write_bytes(0x2D8B9, bytearray([0x20, 0x46, 0xDC])) # org $05D8B9 : jsr level_num + rom.write_bytes(0x2DC46 + 0x0000, bytearray([0xA5, 0x0E])) # level_num: lda $0E + rom.write_bytes(0x2DC46 + 0x0002, bytearray([0x8D, 0x0B, 0x01])) # sta $010B + rom.write_bytes(0x2DC46 + 0x0005, bytearray([0x0A])) # asl + rom.write_bytes(0x2DC46 + 0x0006, bytearray([0x60])) # rts + # Always allow Start+Select rom.write_bytes(0x2267, bytearray([0xEA, 0xEA])) # Always bring up save prompt on beating a level - if world.autosave[player]: + if world.options.autosave: rom.write_bytes(0x20F93, bytearray([0x00])) - if world.overworld_speed[player] == "fast": + if world.options.overworld_speed == "fast": rom.write_bytes(0x21414, bytearray([0x20, 0x10])) - elif world.overworld_speed[player] == "slow": + elif world.options.overworld_speed == "slow": rom.write_bytes(0x21414, bytearray([0x05, 0x05])) # Starting Life Count - rom.write_bytes(0x1E25, bytearray([world.starting_life_count[player].value - 1])) + rom.write_bytes(0x1E25, bytearray([world.options.starting_life_count.value - 1])) # Repurpose Bonus Stars counter for Boss Token or Yoshi Eggs rom.write_bytes(0x3F1AA, bytearray([0x00] * 0x20)) + # Make bonus star counter go up to 255 (999 in theory, but can't load a 16-bit addr there) + rom.write_bytes(0x00F5B, bytearray([0x4C, 0x73, 0x8F])) + rom.write_byte(0x00F95, 0x08) + rom.write_byte(0x00F97, 0x0C) + rom.write_byte(0x00FAC, 0x02) + rom.write_byte(0x00F9E, 0x1D) + rom.write_byte(0x00FA5, 0x1D) + rom.write_byte(0x00FA8, 0x02) + rom.write_byte(0x00FB0, 0x1D) + rom.write_byte(0x00FB8, 0x02) + rom.write_byte(0x00FBE, 0x1D) + rom.write_byte(0x00FC2, 0x03) + + # Move Dragon coins one spot to the left & fix tilemap + rom.write_byte(0x00FF0, 0xFE) + rom.write_byte(0x00C94, 0x3C) + rom.write_byte(0x00C9C, 0x38) + # Delete Routine that would copy Mario position data over repurposed Luigi save data rom.write_bytes(0x20F9F, bytearray([0xEA] * 0x3D)) @@ -904,6 +3046,10 @@ def patch_rom(world, rom, player, active_level_dict): rom.write_bytes(0x6EB1, bytearray([0xEA, 0xEA])) rom.write_bytes(0x6EB4, bytearray([0xEA, 0xEA, 0xEA])) + # Move Thwimps tilemap to another spot in VRAM in order to make them global + rom.write_bytes(0x09C13, bytearray([0x7E, 0x7E, 0x7F, 0x7F])) + rom.write_byte(0x3F425, 0x32) + handle_ability_code(rom) handle_yoshi_box(rom) @@ -913,44 +3059,97 @@ def patch_rom(world, rom, player, active_level_dict): handle_vertical_scroll(rom) + handle_ram(rom) + handle_bonus_block(rom) + handle_blocksanity(rom) + + handle_uncompressed_player_gfx(rom) + + # Handle Special Zone Clear flag + rom.write_bytes(0x02A74, bytearray([0x1E, 0x1F])) + rom.write_bytes(0x09826, bytearray([0x1E, 0x1F])) + rom.write_bytes(0x0B9CD, bytearray([0x1E, 0x1F])) + rom.write_bytes(0x12986, bytearray([0x1E, 0x1F])) + rom.write_bytes(0x62E0F, bytearray([0x1E, 0x1F])) + + handle_indicators(rom) + handle_map_indicators(rom) + + # Handle extra traps + handle_traps(rom) + + # Mario Start! -> Player Start! + text_data_top_tiles = bytearray([ + 0x00,0xFF,0x4D,0x4C,0x03,0x4D,0x5D,0xFF,0x4C,0x4B, + 0x4A,0x03,0x4E,0x01,0x00,0x02,0x00,0x4a,0x4E,0xFF + ]) + text_data_top_props = bytearray([ + 0x34,0x30,0x34,0x34,0x34,0x34,0x34,0x30,0x34,0x34, + 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x30 + ]) + text_data_bottom_tiles = bytearray([ + 0x10,0xFF,0x00,0x5C,0x13,0x00,0x5D,0xFF,0x5C,0x5B, + 0x00,0x13,0x5E,0x11,0x00,0x12,0x00,0x03,0x5E,0xFF + ]) + text_data_bottom_props = bytearray([ + 0x34,0x30,0xb4,0x34,0x34,0xb4,0xf4,0x30,0x34,0x34, + 0xB4,0x34,0x34,0x34,0xb4,0x34,0xb4,0xb4,0x34,0x30 + ]) + + rom.write_bytes(0x010D1, text_data_top_tiles) + rom.write_bytes(0x01139, text_data_top_props) + rom.write_bytes(0x01105, text_data_bottom_tiles) + rom.write_bytes(0x0116A, text_data_bottom_props) + # Handle Level Shuffle handle_level_shuffle(rom, active_level_dict) # Handle Music Shuffle - if world.music_shuffle[player] != "none": - handle_music_shuffle(rom, world, player) + if world.options.music_shuffle != "none": + handle_music_shuffle(rom, world) - generate_shuffled_ow_palettes(rom, world, player) + generate_shuffled_ow_palettes(rom, world) - generate_shuffled_header_data(rom, world, player) + generate_shuffled_header_data(rom, world) - if world.swap_donut_gh_exits[player]: + if world.options.level_palette_shuffle == "on_curated": + generate_curated_level_palette_data(rom, world) + + if world.options.overworld_palette_shuffle == "on_curated": + generate_curated_map_palette_data(rom, world) + + if world.options.sfx_shuffle != "none": + generate_shuffled_sfx(rom, world) + + if world.options.swap_donut_gh_exits: handle_swap_donut_gh_exits(rom) - handle_mario_palette(rom, world, player) + handle_mario_palette(rom, world) # Store all relevant option results in ROM - rom.write_byte(0x01BFA0, world.goal[player].value) - if world.goal[player].value == 0: - rom.write_byte(0x01BFA1, world.bosses_required[player].value) + rom.write_byte(0x01BFA0, world.options.goal.value) + if world.options.goal.value == 0: + rom.write_byte(0x01BFA1, world.options.bosses_required.value) else: rom.write_byte(0x01BFA1, 0x7F) - required_yoshi_eggs = max(math.floor( - world.number_of_yoshi_eggs[player].value * (world.percentage_of_yoshi_eggs[player].value / 100.0)), 1) + required_yoshi_eggs = world.required_egg_count rom.write_byte(0x01BFA2, required_yoshi_eggs) - #rom.write_byte(0x01BFA3, world.display_sent_item_popups[player].value) - rom.write_byte(0x01BFA4, world.display_received_item_popups[player].value) - rom.write_byte(0x01BFA5, world.death_link[player].value) - rom.write_byte(0x01BFA6, world.dragon_coin_checks[player].value) - rom.write_byte(0x01BFA7, world.swap_donut_gh_exits[player].value) + #rom.write_byte(0x01BFA3, world.options.display_sent_item_popups.value) + rom.write_byte(0x01BFA4, world.options.display_received_item_popups.value) + rom.write_byte(0x01BFA5, world.options.death_link.value) + rom.write_byte(0x01BFA6, world.options.dragon_coin_checks.value) + rom.write_byte(0x01BFA7, world.options.swap_donut_gh_exits.value) + rom.write_byte(0x01BFA8, world.options.moon_checks.value) + rom.write_byte(0x01BFA9, world.options.hidden_1up_checks.value) + rom.write_byte(0x01BFAA, world.options.bonus_block_checks.value) + rom.write_byte(0x01BFAB, world.options.blocksanity.value) from Utils import __version__ - rom.name = bytearray(f'SMW{__version__.replace(".", "")[0:3]}_{player}_{world.seed:11}\0', 'utf8')[:21] + rom.name = bytearray(f'SMW{__version__.replace(".", "")[0:3]}_{player}_{world.multiworld.seed:11}\0', 'utf8')[:21] rom.name.extend([0] * (21 - len(rom.name))) rom.write_bytes(0x7FC0, rom.name) - def get_base_rom_bytes(file_name: str = "") -> bytes: base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) if not base_rom_bytes: diff --git a/worlds/smw/Rules.py b/worlds/smw/Rules.py index 82f22c3a34..a900b4fd20 100644 --- a/worlds/smw/Rules.py +++ b/worlds/smw/Rules.py @@ -2,19 +2,18 @@ import math from BaseClasses import MultiWorld from .Names import LocationName, ItemName -from worlds.AutoWorld import LogicMixin +from worlds.AutoWorld import World from worlds.generic.Rules import add_rule, set_rule -def set_rules(world: MultiWorld, player: int): +def set_rules(world: World): - if world.goal[player] == "yoshi_egg_hunt": - required_yoshi_eggs = max(math.floor( - world.number_of_yoshi_eggs[player].value * (world.percentage_of_yoshi_eggs[player].value / 100.0)), 1) + if world.options.goal == "yoshi_egg_hunt": + required_yoshi_eggs = world.required_egg_count - add_rule(world.get_location(LocationName.yoshis_house, player), - lambda state: state.has(ItemName.yoshi_egg, player, required_yoshi_eggs)) + add_rule(world.multiworld.get_location(LocationName.yoshis_house, world.player), + lambda state: state.has(ItemName.yoshi_egg, world.player, required_yoshi_eggs)) else: - add_rule(world.get_location(LocationName.bowser, player), lambda state: state.has(ItemName.mario_carry, player)) + add_rule(world.multiworld.get_location(LocationName.bowser, world.player), lambda state: state.has(ItemName.mario_carry, world.player)) - world.completion_condition[player] = lambda state: state.has(ItemName.victory, player) + world.multiworld.completion_condition[world.player] = lambda state: state.has(ItemName.victory, world.player) diff --git a/worlds/smw/__init__.py b/worlds/smw/__init__.py index 431287c32b..1916108102 100644 --- a/worlds/smw/__init__.py +++ b/worlds/smw/__init__.py @@ -1,3 +1,4 @@ +import dataclasses import os import typing import math @@ -5,9 +6,9 @@ import settings import threading from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification -from .Items import SMWItem, ItemData, item_table -from .Locations import SMWLocation, all_locations, setup_locations, special_zone_level_names, special_zone_dragon_coin_names -from .Options import smw_options +from .Items import SMWItem, ItemData, item_table, junk_table +from .Locations import SMWLocation, all_locations, setup_locations, special_zone_level_names, special_zone_dragon_coin_names, special_zone_hidden_1up_names, special_zone_blocksanity_names +from .Options import SMWOptions from .Regions import create_regions, connect_regions from .Levels import full_level_list, generate_level_list, location_id_to_level_id from .Rules import set_rules @@ -50,11 +51,14 @@ class SMWWorld(World): lost all of his abilities. Can he get them back in time to save the Princess? """ game: str = "Super Mario World" - option_definitions = smw_options + settings: typing.ClassVar[SMWSettings] + + options_dataclass = SMWOptions + options: SMWOptions + topology_present = False - data_version = 3 - required_client_version = (0, 3, 5) + required_client_version = (0, 4, 4) item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = all_locations @@ -62,9 +66,9 @@ class SMWWorld(World): active_level_dict: typing.Dict[int,int] web = SMWWeb() - def __init__(self, world: MultiWorld, player: int): + def __init__(self, multiworld: MultiWorld, player: int): self.rom_name_available_event = threading.Event() - super().__init__(world, player) + super().__init__(multiworld, player) @classmethod def stage_assert_generate(cls, multiworld: MultiWorld): @@ -72,37 +76,34 @@ class SMWWorld(World): if not os.path.exists(rom_file): raise FileNotFoundError(rom_file) - def _get_slot_data(self): - return { - #"death_link": self.multiworld.death_link[self.player].value, - "active_levels": self.active_level_dict, - } - def fill_slot_data(self) -> dict: - slot_data = self._get_slot_data() - for option_name in smw_options: - option = getattr(self.multiworld, option_name)[self.player] - slot_data[option_name] = option.value + slot_data = self.options.as_dict( + "dragon_coin_checks", + "moon_checks", + "hidden_1up_checks", + "bonus_block_checks", + "blocksanity", + ) + slot_data["active_levels"] = self.active_level_dict return slot_data def generate_early(self): - if self.multiworld.early_climb[self.player]: + if self.options.early_climb: self.multiworld.local_early_items[self.player][ItemName.mario_climb] = 1 - def create_regions(self): - location_table = setup_locations(self.multiworld, self.player) - create_regions(self.multiworld, self.player, location_table) + location_table = setup_locations(self) + create_regions(self, location_table) # Not generate basic itempool: typing.List[SMWItem] = [] - self.active_level_dict = dict(zip(generate_level_list(self.multiworld, self.player), full_level_list)) - self.topology_present = self.multiworld.level_shuffle[self.player] + self.active_level_dict = dict(zip(generate_level_list(self), full_level_list)) + self.topology_present = self.options.level_shuffle + + connect_regions(self, self.active_level_dict) - connect_regions(self.multiworld, self.player, self.active_level_dict) - # Add Boss Token amount requirements for Worlds add_rule(self.multiworld.get_region(LocationName.donut_plains_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 1)) add_rule(self.multiworld.get_region(LocationName.vanilla_dome_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 2)) @@ -110,18 +111,29 @@ class SMWWorld(World): add_rule(self.multiworld.get_region(LocationName.chocolate_island_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 5)) add_rule(self.multiworld.get_region(LocationName.valley_of_bowser_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 6)) - if self.multiworld.exclude_special_zone[self.player]: - exclusion_pool = set() - if self.multiworld.dragon_coin_checks[self.player]: - exclusion_pool.update(special_zone_level_names) + exclusion_pool = set() + if self.options.exclude_special_zone: + exclusion_pool.update(special_zone_level_names) + if self.options.dragon_coin_checks: exclusion_pool.update(special_zone_dragon_coin_names) - elif self.multiworld.number_of_yoshi_eggs[self.player].value <= 72: - exclusion_pool.update(special_zone_level_names) + if self.options.hidden_1up_checks: + exclusion_pool.update(special_zone_hidden_1up_names) + if self.options.blocksanity: + exclusion_pool.update(special_zone_blocksanity_names) + exclusion_rules(self.multiworld, self.player, exclusion_pool) total_required_locations = 96 - if self.multiworld.dragon_coin_checks[self.player]: + if self.options.dragon_coin_checks: total_required_locations += 49 + if self.options.moon_checks: + total_required_locations += 7 + if self.options.hidden_1up_checks: + total_required_locations += 14 + if self.options.bonus_block_checks: + total_required_locations += 4 + if self.options.blocksanity: + total_required_locations += 582 itempool += [self.create_item(ItemName.mario_run)] itempool += [self.create_item(ItemName.mario_carry)] @@ -137,31 +149,53 @@ class SMWWorld(World): itempool += [self.create_item(ItemName.green_switch_palace)] itempool += [self.create_item(ItemName.red_switch_palace)] itempool += [self.create_item(ItemName.blue_switch_palace)] + itempool += [self.create_item(ItemName.special_world_clear)] - if self.multiworld.goal[self.player] == "yoshi_egg_hunt": - itempool += [self.create_item(ItemName.yoshi_egg) - for _ in range(self.multiworld.number_of_yoshi_eggs[self.player])] + if self.options.goal == "yoshi_egg_hunt": + raw_egg_count = total_required_locations - len(itempool) - len(exclusion_pool) + total_egg_count = min(raw_egg_count, self.options.max_yoshi_egg_cap.value) + self.required_egg_count = max(math.floor(total_egg_count * (self.options.percentage_of_yoshi_eggs.value / 100.0)), 1) + extra_egg_count = total_egg_count - self.required_egg_count + removed_egg_count = math.floor(extra_egg_count * (self.options.junk_fill_percentage.value / 100.0)) + self.actual_egg_count = total_egg_count - removed_egg_count + + itempool += [self.create_item(ItemName.yoshi_egg) for _ in range(self.actual_egg_count)] + self.multiworld.get_location(LocationName.yoshis_house, self.player).place_locked_item(self.create_item(ItemName.victory)) else: + self.actual_egg_count = 0 + self.required_egg_count = 0 + self.multiworld.get_location(LocationName.bowser, self.player).place_locked_item(self.create_item(ItemName.victory)) junk_count = total_required_locations - len(itempool) trap_weights = [] - trap_weights += ([ItemName.ice_trap] * self.multiworld.ice_trap_weight[self.player].value) - trap_weights += ([ItemName.stun_trap] * self.multiworld.stun_trap_weight[self.player].value) - trap_weights += ([ItemName.literature_trap] * self.multiworld.literature_trap_weight[self.player].value) - trap_weights += ([ItemName.timer_trap] * self.multiworld.timer_trap_weight[self.player].value) - trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.multiworld.trap_fill_percentage[self.player].value / 100.0)) + trap_weights += ([ItemName.ice_trap] * self.options.ice_trap_weight.value) + trap_weights += ([ItemName.stun_trap] * self.options.stun_trap_weight.value) + trap_weights += ([ItemName.literature_trap] * self.options.literature_trap_weight.value) + trap_weights += ([ItemName.timer_trap] * self.options.timer_trap_weight.value) + trap_weights += ([ItemName.reverse_controls_trap] * self.options.reverse_trap_weight.value) + trap_weights += ([ItemName.thwimp_trap] * self.options.thwimp_trap_weight.value) + trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.options.trap_fill_percentage.value / 100.0)) junk_count -= trap_count trap_pool = [] for i in range(trap_count): - trap_item = self.multiworld.random.choice(trap_weights) + trap_item = self.random.choice(trap_weights) trap_pool.append(self.create_item(trap_item)) itempool += trap_pool - itempool += [self.create_item(ItemName.one_up_mushroom) for _ in range(junk_count)] + junk_weights = [] + junk_weights += ([ItemName.one_coin] * 15) + junk_weights += ([ItemName.five_coins] * 15) + junk_weights += ([ItemName.ten_coins] * 25) + junk_weights += ([ItemName.fifty_coins] * 25) + junk_weights += ([ItemName.one_up_mushroom] * 20) + + junk_pool = [self.create_item(self.random.choice(junk_weights)) for _ in range(junk_count)] + + itempool += junk_pool boss_location_names = [LocationName.yoshis_island_koopaling, LocationName.donut_plains_koopaling, LocationName.vanilla_dome_koopaling, LocationName.twin_bridges_koopaling, LocationName.forest_koopaling, LocationName.chocolate_koopaling, @@ -176,18 +210,18 @@ class SMWWorld(World): def generate_output(self, output_directory: str): rompath = "" # if variable is not declared finally clause may fail try: - world = self.multiworld + multiworld = self.multiworld player = self.player rom = LocalRom(get_base_rom_path()) - patch_rom(self.multiworld, rom, self.player, self.active_level_dict) + patch_rom(self, rom, self.player, self.active_level_dict) rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc") rom.write_to_file(rompath) self.rom_name = rom.name patch = SMWDeltaPatch(os.path.splitext(rompath)[0]+SMWDeltaPatch.patch_file_ending, player=player, - player_name=world.player_name[player], patched_path=rompath) + player_name=multiworld.player_name[player], patched_path=rompath) patch.write() except: raise @@ -243,7 +277,15 @@ class SMWWorld(World): if level_index >= world_cutoffs[i]: continue - if self.multiworld.dragon_coin_checks[self.player].value == 0 and "Dragon Coins" in loc_name: + if not self.options.dragon_coin_checks and "Dragon Coins" in loc_name: + continue + if not self.options.moon_checks and "3-Up Moon" in loc_name: + continue + if not self.options.hidden_1up_checks and "Hidden 1-Up" in loc_name: + continue + if not self.options.bonus_block_checks and "1-Up from Bonus Block" in loc_name: + continue + if not self.options.blocksanity and "Block #" in loc_name: continue location = self.multiworld.get_location(loc_name, self.player) @@ -271,7 +313,7 @@ class SMWWorld(World): return created_item def get_filler_item_name(self) -> str: - return ItemName.one_up_mushroom + return self.random.choice(list(junk_table.keys())) def set_rules(self): - set_rules(self.multiworld, self.player) + set_rules(self) diff --git a/worlds/smw/data/blocksanity.json b/worlds/smw/data/blocksanity.json new file mode 100644 index 0000000000..e3737d2597 --- /dev/null +++ b/worlds/smw/data/blocksanity.json @@ -0,0 +1,747 @@ +{ + "000_bonus": [], + "001_vanilla_secret_2": [ + ["yoshi", "0170", "0130", []], + ["green", "02F0", "0170", ["greenswitch carry", "greenswitch cape"]], + ["power", "0660", "0110", []], + ["power", "0B70", "0100", []], + ["multi", "0DC0", "0120", []], + ["gray", "0E70", "0120", []], + ["single", "1180", "0130", []], + ["single", "1190", "0130", []], + ["single", "11A0", "0130", []], + ["single", "11B0", "0130", []], + ["single", "11C0", "0130", []], + ["single", "11D0", "0130", []] + ], + "002_vanilla_secret_3": [ + ["power", "0270", "00D0", ["swim"]], + ["power", "06E0", "00E0", ["swim"]] + ], + "003_top_secret_area": [], + "004_donut_ghost_house": [ + ["vine", "0120", "0120", []], + ["dir", "0070", "0140", ["pswitch"]], + ["life", "0610", "0140", ["run cape"]], + ["life", "0640", "0140", ["run cape"]], + ["life", "0670", "0140", ["run cape"]], + ["life", "06A0", "0140", ["run cape"]] + ], + "005_donut_plains_3": [ + ["green", "01B0", "00E0", ["greenswitch"]], + ["single", "0450", "00F0", []], + ["single", "0480", "00F0", []], + ["vine", "04E0", "0130", ["mushroom spin"]], + ["power", "0BD0", "0140", []], + ["bonus", "1250", "00F0", []] + ], + "006_donut_plains_4": [ + ["single", "0660", "0130", []], + ["power", "0670", "0130", []], + ["single", "0680", "0130", []], + ["yoshi", "0AF0", "0150", []] + ], + "007_donut_plains_castle": [ + ["yellow", "01E0", "00C0", ["yellowswitch"]], + ["single", "00A0", "0680", []], + ["power", "00B0", "0680", []], + ["single", "00C0", "0680", []], + ["vine", "0050", "0450", []], + ["inlife", "0030", "0320", ["climb"]], + ["single", "0050", "0250", []], + ["single", "0080", "0250", []], + ["single", "00B0", "0250", []], + ["green", "0090", "0060", ["greenswitch"]] + ], + "008_green_switch_palace": [], + "009_donut_plains_2": [ + ["single", "00D0", "0120", []], + ["single", "00E0", "0120", []], + ["single", "00F0", "0120", []], + ["yellow", "0100", "0120", ["yellowswitch"]], + ["power", "0330", "00D0", []], + ["multi", "03C0", "00C0", []], + ["fly", "0820", "00E0", []], + ["green", "0560", "00E0", ["greenswitch"]], + ["yellow", "0050", "0140", ["yellowswitch"]], + ["vine", "02B0", "00E0", ["carry spin mushroom", "yoshi"]] + ], + "00A_donut_secret_1": [ + ["single", "02C0", "0130", ["swim"]], + ["single", "02D0", "0130", ["swim"]], + ["power", "02E0", "0130", ["swim"]], + ["single", "02F0", "0130", ["swim"]], + ["power", "00E0", "0480", ["swim"]], + ["power", "0060", "0250", ["swim balloon"]], + ["life", "0110", "0070", ["swim balloon"]], + ["power", "01A0", "0250", ["swim balloon"]], + ["power", "0570", "0150", ["swim"]], + ["key", "0940", "0150", ["swim carry pswitch"]] + ], + "00B_vanilla_fortress": [ + ["power", "04E0", "0130", ["swim"]], + ["power", "0220", "0130", ["swim"]], + ["yellow", "0780", "0110", ["yellowswitch swim"]] + ], + "00C_butter_bridge_1": [ + ["power", "08A0", "0110", []], + ["multi", "08B0", "00D0", []], + ["multi", "0860", "0090", []], + ["multi", "08E0", "0050", []], + ["life", "0840", "0050", []], + ["bonus", "0BD0", "0130", []] + ], + "00D_butter_bridge_2": [ + ["power", "0310", "0100", ["carry"]], + ["green", "0AC0", "0120", ["greenswitch"]], + ["yoshi", "0C70", "0110", ["carry"]] + ], + "00E_twin_bridges_castle": [ + ["power", "01B0", "0370", ["climb"]] + ], + "00F_cheese_bridge": [ + ["power", "00C0", "0140", []], + ["power", "0560", "00E0", []], + ["wings", "0A10", "0140", []], + ["power", "0B60", "0150", []] + ], + "010_cookie_mountain": [ + ["single", "01C0", "0130", []], + ["single", "01D0", "0130", []], + ["single", "01E0", "0130", []], + ["single", "01F0", "0130", []], + ["single", "0200", "0130", []], + ["single", "0210", "0130", []], + ["single", "0220", "0130", []], + ["single", "0230", "0130", []], + ["single", "0240", "0130", []], + ["power", "0200", "00F0", []], + ["life", "0A40", "0070", ["climb", "swim"]], + ["vine", "0B20", "0140", []], + ["yoshi", "0C40", "0140", ["redswitch"]], + ["single", "11C0", "0140", []], + ["single", "11D0", "0140", []], + ["power", "11E0", "0140", []], + ["single", "11F0", "0140", []], + ["single", "1200", "0140", []], + ["single", "1210", "0140", []], + ["single", "1220", "0140", []], + ["single", "1230", "0140", []], + ["single", "1240", "0140", []], + ["single", "1250", "0140", []], + ["single", "11B0", "0100", []], + ["single", "11C0", "0100", []], + ["single", "11D0", "0100", []], + ["single", "11E0", "0100", []], + ["single", "11F0", "0100", []], + ["single", "1200", "0100", []], + ["single", "1210", "0100", []], + ["single", "1220", "0100", []], + ["single", "1230", "0100", []], + ["single", "1240", "0100", []], + ["single", "1250", "0100", []], + ["single", "1360", "0140", []] + ], + "011_soda_lake": [ + ["power", "0200", "0110", ["swim"]] + ], + "012_test": [], + "013_donut_secret_house": [ + ["power", "0480", "0140", []], + ["multi", "0310", "0140", []], + ["life", "04A0", "0140", ["pswitch"]], + ["vine", "01E0", "0110", ["pswitch"]], + ["dir", "0180", "0130", ["pswitch"]] + ], + "014_yellow_switch_palace": [], + "015_donut_plains_1": [ + ["single", "0710", "0140", []], + ["single", "0720", "0140", []], + ["yoshi", "0D20", "00F0", []], + ["vine", "0DB0", "0130", []], + ["green", "11A0", "0070", ["greenswitch cape"]], + ["green", "11A0", "0080", ["greenswitch cape"]], + ["green", "11A0", "0090", ["greenswitch cape"]], + ["green", "11A0", "00A0", ["greenswitch cape"]], + ["green", "11A0", "00B0", ["greenswitch cape"]], + ["green", "11A0", "00C0", ["greenswitch cape"]], + ["green", "11A0", "00D0", ["greenswitch cape"]], + ["green", "11A0", "00E0", ["greenswitch cape"]], + ["green", "11A0", "00F0", ["greenswitch cape"]], + ["green", "11A0", "0100", ["greenswitch cape"]], + ["green", "11A0", "0110", ["greenswitch cape"]], + ["green", "11A0", "0120", ["greenswitch cape"]], + ["green", "11A0", "0130", ["greenswitch cape"]], + ["green", "11A0", "0140", ["greenswitch cape"]], + ["green", "11A0", "0150", ["greenswitch cape"]], + ["green", "11A0", "0160", ["greenswitch cape"]], + ["yellow", "1240", "0120", ["yellowswitch"]], + ["yellow", "1280", "0140", ["yellowswitch"]], + ["yellow", "1290", "0140", ["yellowswitch"]] + ], + "016_test": [], + "017_test": [], + "018_sunken_ghost_ship": [ + ["power", "0110", "0070", ["swim"]], + ["star", "0100", "0C80", ["swim"]] + ], + "019_test": [], + "01A_chocolate_castle": [ + ["yellow", "09C0", "0110", ["yellowswitch"]], + ["yellow", "0150", "0110", ["yellowswitch"]], + ["green", "0750", "0140", ["greenswitch"]] + ], + "01B_chocolate_fortress": [ + ["power", "0380", "0140", []], + ["power", "0360", "0150", []], + ["single", "0370", "0150", []], + ["single", "0380", "0150", []], + ["green", "0AC0", "0130", ["greenswitch"]] + ], + "01C_chocolate_island_5": [ + ["yoshi", "0170", "0130", []], + ["power", "0180", "0150", []], + ["life", "0340", "0170", ["carry", "cape"]], + ["yellow", "0560", "0140", ["yellowswitch pswitch"]] + ], + "01D_chocolate_island_4": [ + ["yellow", "0700", "0140", ["yellowswitch blueswitch"]], + ["pow", "0920", "00A0", []], + ["power", "0B50", "0040", []] + ], + "01E_test": [], + "01F_forest_fortress": [ + ["yellow", "02B0", "00E0", ["yellowswitch"]], + ["power", "0750", "00D0", []], + ["life", "0ED0", "0140", ["run cape"]], + ["life", "0F10", "0140", ["run cape"]], + ["life", "0F10", "0100", ["run cape"]], + ["life", "0F40", "0130", ["run cape"]], + ["life", "0F70", "0140", ["run cape"]], + ["life", "0F70", "00F0", ["run cape"]], + ["life", "0FA0", "0130", ["run cape"]], + ["life", "0FD0", "0140", ["run cape"]], + ["life", "0FD0", "0100", ["run cape"]] + ], + "020_forest_castle": [ + ["green", "0CC0", "0120", ["greenswitch"]] + ], + "021_chocolate_ghost_house": [ + ["power", "0910", "0140", []], + ["power", "0110", "0140", []], + ["life", "05D0", "0140", []] + ], + "022_chocolate_island_1": [ + ["fly", "0490", "0120", ["pswitch"]], + ["fly", "0CD0", "0100", ["pswitch"]], + ["yoshi", "0E10", "0110", ["pswitch"]], + ["green", "0F00", "0140", ["greenswitch blueswitch", "greenswitch cape", "yellowswitch blueswitch", "yellowswitch cape"]], + ["life", "0070", "0120", ["pswitch"]] + ], + "023_chocolate_island_3": [ + ["power", "0530", "0140", []], + ["power", "0A20", "0140", []], + ["power", "0F50", "00F0", []], + ["green", "1080", "00F0", ["greenswitch"]], + ["bonus", "11D0", "0140", []], + ["vine", "1220", "0140", []], + ["life", "1640", "0140", ["run cape"]], + ["life", "1670", "0140", ["run cape"]], + ["life", "16A0", "0140", ["run cape"]] + ], + "024_chocolate_island_2": [ + ["multi", "00E0", "00A0", []], + ["invis", "00F0", "00D0", []], + ["yoshi", "0280", "0040", []], + ["single", "0080", "0140", []], + ["single", "05C0", "0140", []], + ["multi" , "05F0", "0120", []], + ["power", "0620", "0100", []], + ["pow", "0040", "0140", []], + ["yellow", "0190", "0110", ["yellowswitch"]], + ["yellow", "01A0", "0110", ["yellowswitch"]], + ["green", "0240", "0110", ["greenswitch"]], + ["green", "0250", "0110", ["greenswitch"]], + ["green", "0260", "0110", ["greenswitch"]], + ["green", "0270", "0110", ["greenswitch"]], + ["green", "0280", "0110", ["greenswitch"]], + ["green", "0290", "0110", ["greenswitch"]] + ], + "101_yoshis_island_castle": [ + ["single", "0280", "00F0", ["climb"]], + ["single", "0290", "00F0", ["climb"]], + ["power", "02A0", "00F0", ["climb"]], + ["single", "02B0", "00F0", ["climb"]], + ["single", "02C0", "00F0", ["climb"]], + ["fly", "0250", "0150", ["climb"]] + ], + "102_yoshis_island_4": [ + ["yellow", "00D0", "00F0", ["yellowswitch"]], + ["power", "0160", "0140", []], + ["multi", "0290", "0140", []], + ["star", "04E0", "0120", []] + ], + "103_yoshis_island_3": [ + ["yellow", "0250", "00C0", ["yellowswitch"]], + ["yellow", "0290", "0140", ["yellowswitch"]], + ["yellow", "02F0", "00E0", ["yellowswitch carry", "yellowswitch yoshi", "yellowswitch run cape"]], + ["yellow", "0300", "00E0", ["yellowswitch carry", "yellowswitch yoshi", "yellowswitch run cape"]], + ["yellow", "0310", "00E0", ["yellowswitch carry", "yellowswitch yoshi", "yellowswitch run cape"]], + ["yellow", "0320", "00E0", ["yellowswitch carry", "yellowswitch yoshi", "yellowswitch run cape"]], + ["yellow", "0330", "00E0", ["yellowswitch carry", "yellowswitch yoshi", "yellowswitch run cape"]], + ["yellow", "0340", "00E0", ["yellowswitch carry", "yellowswitch yoshi", "yellowswitch run cape"]], + ["yellow", "0350", "00E0", ["yellowswitch carry", "yellowswitch yoshi", "yellowswitch run cape"]], + ["single", "04B0", "00A0", []], + ["yoshi", "04C0", "00A0", []], + ["single", "0AC0", "0140", []], + ["power", "0B00", "00C0", []], + ["yellow", "0CD0", "0120", ["yellowswitch"]], + ["yellow", "0CE0", "0120", ["yellowswitch"]], + ["yellow", "0DA0", "00F0", ["yellowswitch"]], + ["bonus", "10A0", "0080", []] + ], + "104_yoshis_house": [], + "105_yoshis_island_1": [ + ["fly", "0250", "0140", []], + ["yellow", "09C0", "0140", ["yellowswitch"]], + ["life", "0D10", "00F0", []], + ["power", "0F30", "0110", []] + ], + "106_yoshis_island_2": [ + ["fly", "0080", "00F0", ["carry", "yoshi"]], + ["fly", "00C0", "00E0", ["carry", "yoshi"]], + ["fly", "0130", "00F0", ["carry", "yoshi"]], + ["fly", "0140", "00D0", ["carry", "yoshi"]], + ["fly", "0180", "0100", ["carry", "yoshi"]], + ["fly", "01C0", "00E0", ["carry", "yoshi"]], + ["single", "0260", "0140", []], + ["yellow", "0270", "0140", ["yellowswitch"]], + ["single", "0280", "0140", []], + ["single", "0350", "0150", []], + ["yoshi", "0360", "0150", []], + ["single", "0B20", "0150", []], + ["yoshi", "0B30", "0150", []], + ["single", "0B40", "0150", []], + ["vine", "0C80", "0100", []], + ["yellow", "0DF0", "0120", ["yellowswitch"]] + ], + "107_vanilla_ghost_house": [ + ["power", "0200", "0100", []], + ["vine", "0750", "0150", []], + ["power", "0860", "0140", []], + ["multi", "0A00", "0140", []], + ["pow", "05E0", "0120", []] + ], + "108_test": [], + "109_vanilla_secret_1": [ + ["single", "0030", "0590", []], + ["power", "0040", "0590", []], + ["multi", "0110", "0490", []], + ["vine", "01B0", "0520", []], + ["vine", "0180", "0470", ["climb"]], + ["single", "0190", "0350", ["climb"]], + ["single", "01A0", "0350", ["climb"]], + ["power", "01B0", "0350", ["climb"]] + ], + "10A_vanilla_dome_3": [ + ["single", "01C0", "0140", []], + ["fly", "0200", "0160", []], + ["fly", "0230", "0120", []], + ["power", "02D0", "0110", []], + ["fly", "0480", "0150", []], + ["invis", "0800", "0130", []], + ["power", "0830", "0130", []], + ["multi", "0D90", "0120", []], + ["power", "03D0", "0130", []], + ["yoshi", "10A0", "0100", ["carry", "yoshi"]], + ["power", "1A60", "0140", []], + ["pswitch", "1E40", "0090", ["run cape pswitch"]], + ["pswitch", "1E50", "00A0", ["run cape pswitch"]], + ["pswitch", "1E60", "00B0", ["run cape pswitch"]], + ["pswitch", "1E70", "00C0", ["run cape pswitch"]], + ["pswitch", "1E80", "00D0", ["run cape pswitch"]], + ["pswitch", "1E90", "00E0", ["run cape pswitch"]] + ], + "10B_donut_secret_2": [ + ["dir", "00A0", "0170", []], + ["vine", "0220", "0100", []], + ["star", "0250", "0040", ["climb", "yoshi"]], + ["power", "0060", "0140", []], + ["star", "0B00", "0140", []] + ], + "10C_test": [], + "10D_front_door": [], + "10E_back_door": [], + "10F_valley_of_bowser_4": [ + ["yellow", "0370", "0130", ["yellowswitch"]], + ["power", "0210", "0130", []], + ["vine", "05E0", "0110", []], + ["yoshi", "0610", "0040", ["climb"]], + ["life", "07A0", "00D0", ["mushroom spin climb"]], + ["power", "0B60", "0110", ["yellowswitch climb"]] + ], + "110_valley_castle": [ + ["yellow", "0290", "0030", ["yellowswitch"]], + ["yellow", "0330", "0110", ["yellowswitch"]], + ["green", "0980", "0140", ["greenswitch"]] + ], + "111_valley_fortress": [ + ["green", "0220", "0140", ["greenswitch"]], + ["yellow", "0940", "0100", ["yellowswitch"]] + ], + "112_test": [], + "113_valley_of_bowser_3": [ + ["power", "0130", "0110", []], + ["power", "08A0", "00E0", []] + ], + "114_valley_ghost_house": [ + ["pswitch", "0160", "0100", ["pswitch"]], + ["multi", "0570", "0110", ["pswitch"]], + ["power", "00E0", "0100", []], + ["dir", "0270", "0140", ["pswitch"]] + ], + "115_valley_of_bowser_2": [ + ["power", "0330", "0130", []], + ["yellow", "0720", "0140", ["yellowswitch"]], + ["power", "0010", "00A0", []], + ["wings", "00D0", "0140", []] + ], + "116_valley_of_bowser_1": [ + ["green", "0810", "0140", ["greenswitch"]], + ["invis", "0D40", "0100", []], + ["invis", "0D50", "0100", []], + ["invis", "0D60", "0100", []], + ["yellow", "0D60", "0080", ["yellowswitch cape"]], + ["yellow", "0D60", "0090", ["yellowswitch cape"]], + ["yellow", "0D60", "00A0", ["yellowswitch cape"]], + ["yellow", "0D60", "00B0", ["yellowswitch cape"]], + ["vine", "0F20", "0120", []] + ], + "117_chocolate_secret": [ + ["power", "04A0", "0120", []], + ["power", "0960", "0140", []] + ], + "118_vanilla_dome_2": [ + ["single", "0240", "0100", ["swim"]], + ["power", "0250", "0100", ["swim"]], + ["single", "0260", "0100", ["swim"]], + ["single", "0270", "0100", ["swim"]], + ["vine", "03B0", "0100", ["swim"]], + ["inlife", "0720", "00F0", ["swim climb", "swim yoshi"]], + ["single", "0760", "00F0", ["swim climb", "swim yoshi"]], + ["single", "0770", "00F0", ["swim climb", "swim yoshi"]], + ["power", "0780", "00F0", ["swim climb", "swim yoshi"]], + ["power", "0880", "0100", ["swim climb", "swim yoshi"]], + ["power", "0730", "0040", ["swim climb", "swim yoshi"]], + ["power", "0D10", "0100", ["swim climb", "swim yoshi"]], + ["multi", "0290", "0130", ["swim climb", "swim yoshi"]], + ["multi", "1150", "0140", ["swim climb", "swim yoshi"]] + ], + "119_vanilla_dome_4": [ + ["power", "0690", "0100", []], + ["power", "0CB0", "0140", []], + ["single", "0E10", "0120", []], + ["single", "0E20", "0120", []], + ["single", "0E30", "0120", []], + ["life", "0E40", "0120", []], + ["single", "0E50", "0120", []], + ["single", "0E60", "0120", []], + ["single", "0E70", "0120", []], + ["single", "0E80", "0120", []], + ["single", "0E90", "0120", ["carry"]] + ], + "11A_vanilla_dome_1": [ + ["fly", "0250", "0110", []], + ["power", "0400", "0120", []], + ["power", "0450", "00E0", []], + ["single", "0460", "0120", []], + ["life", "04D0", "0120", []], + ["power", "0640", "0180", []], + ["vine", "0680", "00E0", ["carry", "redswitch"]], + ["star", "00F0", "00E0", []], + ["power", "13A0", "0140", ["run star", "run mushroom"]], + ["single", "17D0", "0150", ["run star", "run mushroom"]] + ], + "11B_red_switch_palace": [], + "11C_vanilla_dome_castle": [ + ["life", "0110", "0100", ["carry", "mushroom"]], + ["life", "0210", "0100", ["carry", "mushroom"]], + ["power", "03A0", "0130", []], + ["life", "0170", "0140", ["pswitch"]], + ["green", "0B90", "0140", ["greenswitch"]] + ], + "11D_forest_ghost_house": [ + ["single", "0950", "0110", []], + ["power", "0990", "0110", []], + ["fly", "0190", "0150", []], + ["power", "0370", "0140", []], + ["life", "0640", "0160", []] + ], + "11E_forest_of_illusion_1": [ + ["power", "01A0", "0110", []], + ["yoshi", "0360", "0130", []], + ["power", "0FA0", "0150", []], + ["key", "0E00", "0160", ["pballoon"]], + ["life", "0610", "0130", []] + ], + "11F_forest_of_illusion_4": [ + ["multi", "0540", "0140", []], + ["single", "05E0", "0140", []], + ["single", "05F0", "0140", []], + ["single", "0600", "0140", []], + ["single", "0620", "0140", []], + ["power", "0630", "0140", []], + ["single", "0640", "0140", []], + ["single", "0E30", "0140", []], + ["single", "0E40", "0140", []], + ["power", "0E40", "0100", []], + ["single", "0EF0", "0140", []], + ["single", "0F00", "0140", []], + ["single", "0EF0", "0100", []] + ], + "120_forest_of_illusion_2": [ + ["green", "0070", "0130", ["greenswitch swim"]], + ["power", "0480", "0040", ["swim"]], + ["invis", "0600", "0120", ["swim"]], + ["invis", "0610", "0120", ["swim"]], + ["inlife", "0620", "0120", ["swim"]], + ["invis", "0630", "0120", ["swim"]], + ["yellow", "0950", "0160", ["yellowswitch swim"]] + ], + "121_blue_switch_palace": [], + "122_forest_secret": [ + ["power", "0330", "00A0", []], + ["power", "0450", "0110", []], + ["life", "06A0", "00B0", ["blueswitch", "carry"]] + ], + "123_forest_of_illusion_3": [ + ["yoshi", "0350", "0150", []], + ["single", "04C0", "0150", []], + ["multi", "04D0", "0140", []], + ["single", "04F0", "0120", ["carry", "yoshi"]], + ["multi", "0D90", "0110", ["carry", "yoshi"]], + ["single", "0D80", "0150", ["carry", "yoshi"]], + ["single", "0D90", "0150", ["carry", "yoshi"]], + ["single", "0DA0", "0150", ["carry", "yoshi"]], + ["single", "0E40", "0140", ["carry", "yoshi"]], + ["single", "0E50", "0120", ["carry", "yoshi"]], + ["single", "0E70", "0150", ["carry", "yoshi"]], + ["single", "0E90", "0110", ["carry", "yoshi"]], + ["single", "0EB0", "0130", ["carry", "yoshi"]], + ["single", "0EE0", "0140", ["carry", "yoshi"]], + ["single", "0EF0", "0100", ["carry", "yoshi"]], + ["single", "0F10", "0120", ["carry", "yoshi"]], + ["single", "0F50", "0130", ["carry", "yoshi"]], + ["single", "0F70", "0150", ["carry", "yoshi"]], + ["single", "0F80", "0110", ["carry", "yoshi"]], + ["single", "0F90", "0130", ["carry", "yoshi"]], + ["single", "0FC0", "0150", ["carry", "yoshi"]], + ["single", "0FD0", "0120", ["carry", "yoshi"]], + ["single", "11A0", "0150", ["carry", "yoshi"]], + ["single", "11B0", "0120", ["carry", "yoshi"]], + ["single", "1230", "0150", ["carry", "yoshi"]], + ["single", "1240", "0140", ["carry", "yoshi"]], + ["single", "1250", "0130", ["carry", "yoshi"]] + ], + "124_test": [], + "125_special_zone_8": [ + ["yoshi", "0390", "0100", ["carry", "yoshi"]], + ["single", "04A0", "0130", []], + ["single", "04B0", "0130", []], + ["single", "04C0", "0130", []], + ["single", "04D0", "0130", []], + ["single", "04E0", "0130", []], + ["pow", "0560", "0140", []], + ["power", "05D0", "0140", []], + ["star", "0750", "00F0", []], + ["single", "0670", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0680", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0690", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "06A0", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "06B0", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "06C0", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "06F0", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0700", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0710", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0720", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0730", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0740", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "0750", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["multi", "0CA0", "0100", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "1100", "0120", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "1110", "0120", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "1120", "0120", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "1130", "0120", ["mushroom spin", "cape", "carry", "yoshi"]], + ["single", "1140", "0120", ["mushroom spin", "cape", "carry", "yoshi"]], + ["power", "13F0", "0140", ["mushroom spin", "cape", "carry", "yoshi"]], + ["fly", "1570", "00F0", ["mushroom spin", "cape", "carry", "yoshi"]] + ], + "126_special_zone_7": [ + ["power", "0350", "0150", ["mushroom"]], + ["yoshi", "0C80", "0140", ["mushroom"]], + ["single", "0F90", "0140", ["mushroom"]], + ["power", "0FA0", "0140", ["mushroom"]], + ["single", "0FB0", "0140", ["mushroom"]] + ], + "127_special_zone_6": [ + ["power", "0370", "00F0", ["swim"]], + ["single", "0610", "0140", ["swim"]], + ["single", "0630", "0120", ["swim"]], + ["yoshi", "0650", "0100", ["swim"]], + ["life", "07D0", "0140", ["swim"]], + ["multi", "0950", "0140", ["swim"]], + ["single", "0D80", "0140", ["swim"]], + ["single", "0D90", "0140", ["swim"]], + ["single", "0DA0", "0140", ["swim"]], + ["single", "0DB0", "0140", ["swim"]], + ["single", "0DC0", "0140", ["swim"]], + ["single", "0DD0", "0140", ["swim"]], + ["single", "0DE0", "0140", ["swim"]], + ["single", "0DF0", "0140", ["swim"]], + ["single", "0E00", "0140", ["swim"]], + ["single", "0E10", "0140", ["swim"]], + ["single", "0E20", "0140", ["swim"]], + ["single", "0E30", "0140", ["swim"]], + ["single", "0E40", "0140", ["swim"]], + ["single", "0E50", "0140", ["swim"]], + ["single", "0E60", "0140", ["swim"]], + ["single", "0E70", "0140", ["swim"]], + ["single", "0D80", "0100", ["swim"]], + ["single", "0D90", "0100", ["swim"]], + ["single", "0DA0", "0100", ["swim"]], + ["single", "0DB0", "0100", ["swim"]], + ["single", "0DC0", "0100", ["swim"]], + ["single", "0DD0", "0100", ["swim"]], + ["single", "0DE0", "0100", ["swim"]], + ["single", "0DF0", "0100", ["swim"]], + ["single", "0E00", "0100", ["swim"]], + ["single", "0E10", "0100", ["swim"]], + ["power", "0E20", "0100", ["swim"]], + ["single", "0E30", "0100", ["swim"]], + ["single", "0E40", "0100", ["swim"]], + ["single", "0E50", "0100", ["swim"]], + ["single", "0E60", "0100", ["swim"]], + ["single", "0E70", "0100", ["swim"]] + ], + "128_special_zone_5": [ + ["yoshi", "01D0", "0160", ["mushroom"]] + ], + "129_test": [], + "12A_special_zone_1": [ + ["vine", "0020", "03C0", []], + ["vine", "0050", "03C0", []], + ["vine", "0080", "03C0", []], + ["vine", "00B0", "03C0", []], + ["life", "0110", "0340", ["climb"]], + ["vine", "0120", "0280", ["climb"]], + ["pow", "0080", "01F0", ["climb"]], + ["vine", "00B0", "01F0", ["climb"]], + ["power", "00F0", "00D0", ["climb"]], + ["pswitch", "0190", "00C0", ["climb pswitch cape"]], + ["pswitch", "01C0", "0130", ["climb pswitch cape"]], + ["pswitch", "0180", "01A0", ["climb pswitch cape"]], + ["pswitch", "01D0", "01A0", ["climb pswitch cape"]], + ["pswitch", "01C0", "0270", ["climb pswitch cape"]], + ["pswitch", "01A0", "02C0", ["climb pswitch cape"]], + ["pswitch", "0190", "0310", ["climb pswitch cape"]], + ["pswitch", "01B0", "0370", ["climb pswitch cape"]], + ["pswitch", "0180", "03D0", ["climb pswitch cape"]], + ["pswitch", "0200", "0120", ["climb pswitch cape", "climb pswitch carry"]], + ["pswitch", "0210", "0130", ["climb pswitch cape", "climb pswitch carry"]], + ["pswitch", "0220", "0140", ["climb pswitch cape", "climb pswitch carry"]], + ["pswitch", "0230", "0150", ["climb pswitch cape", "climb pswitch carry"]] + ], + "12B_special_zone_2": [ + ["power", "02E0", "0120", []], + ["single", "0380", "0110", ["pballoon"]], + ["single", "0450", "0140", ["pballoon"]], + ["power", "04A0", "00F0", ["pballoon"]], + ["single", "05C0", "0150", ["pballoon"]], + ["single", "05C0", "00F0", ["pballoon"]], + ["power", "0760", "0140", ["pballoon"]], + ["multi", "07E0", "00E0", ["pballoon"]], + ["single", "0850", "0100", ["pballoon"]], + ["single", "0920", "0140", ["pballoon"]] + ], + "12C_special_zone_3": [ + ["power", "03F0", "0110", []], + ["yoshi", "0080", "0140", []], + ["wings", "0A50", "0140", []] + ], + "12D_special_zone_4": [ + ["power", "0490", "0140", ["flower"]], + ["star", "0AF0", "00F0", ["flower carry", "flower pswitch"]] + ], + "12E_test": [], + "12F_test": [], + "130_star_road_2": [ + ["star", "0460", "0130", ["swim"]] + ], + "131_test": [], + "132_star_road_3": [ + ["key", "0080", "0030", ["carry"]] + ], + "133_test": [], + "134_star_road_1": [], + "135_star_road_4": [ + ["power", "0540", "0090", []], + ["green", "0C00", "0140", ["greenswitch yoshi carry"]], + ["green", "0C10", "0140", ["greenswitch yoshi carry"]], + ["green", "0C20", "0140", ["greenswitch yoshi carry"]], + ["green", "0C30", "0140", ["greenswitch yoshi carry"]], + ["green", "0C40", "0140", ["greenswitch yoshi carry"]], + ["green", "0C50", "0140", ["greenswitch yoshi carry"]], + ["green", "0C60", "0140", ["greenswitch yoshi carry"]], + ["key", "0D40", "0160", ["carry yoshi", "greenswitch redswitch carry"]] + ], + "136_star_road_5": [ + ["dir", "0510", "0140", []], + ["life", "07D0", "0150", ["pswitch"]], + ["vine", "08E0", "0100", ["pswitch"]], + ["yellow", "08F0", "0050", ["yellowswitch pswitch climb carry", "yellowswitch specialworld yoshi carry"]], + ["yellow", "0900", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0910", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0920", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0930", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0940", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0950", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0960", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0970", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0980", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0990", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "09A0", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "09B0", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "09C0", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "09D0", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "09E0", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "09F0", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0A00", "0050", ["yellowswitch specialworld yoshi carry"]], + ["yellow", "0A10", "0050", ["yellowswitch greenswitch yoshi carry", "yellowswitch greenswitch pswitch climb carry cape"]], + ["yellow", "0A10", "0060", ["yellowswitch greenswitch yoshi carry", "yellowswitch greenswitch pswitch climb carry cape"]], + ["green", "0A20", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0A30", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0A40", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0A50", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0A60", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0A70", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0A80", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0A90", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0AA0", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0AB0", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0AC0", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0AD0", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0AE0", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0AF0", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0B00", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0B10", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0B20", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0B30", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0B40", "0080", ["greenswitch specialworld yoshi carry"]], + ["green", "0B50", "0080", ["greenswitch specialworld yoshi carry"]] + ], + "137_test": [], + "138_test": [], + "139_test": [], + "13A_test": [], + "13B_test": [] +} \ No newline at end of file diff --git a/worlds/smw/data/graphics/indicators.bin b/worlds/smw/data/graphics/indicators.bin new file mode 100644 index 0000000000000000000000000000000000000000..70e7036533c98150c4b24254ea55bb0470faacc7 GIT binary patch literal 384 zcmZRuF!1r%60sy>ipLg%GYl0B=)gcBAYp<+001SYgZ~ry?XZr8_ zzvVFbS`)d@|EcoTAo+8a3=9&f>6-yfoXyV37-1||jO2h0o@ z0Azk4uYC>BHNyXK$ct;pJ7~JDjTA`3&?`~AU_iy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVW#@zquT z>p}Evxq54mJOe|ulAZm2@B7jBvl$rv1NjQfVhr*O^^EhGAo5}i(hTv8@=Wr~F!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C4p{pbK>e<81Z z4bU~h|8dBRYsfojx~`2BNJG~Rv9FL9a9WY3=9RjW==PQq~qu2Gcf!I@)elH7~~o18Rs)W^GErg%>Vr%`nMbXPG?|J)_=gvfB``Ef915V zkyYNWejjK)IsnP<^_Um9CQ&bMLKV6!#J)makYD1HWh1y4C{B8we7N9TIhZ`ce<1mli8@)AtB~ap{sYNR;<+Uf ytqGAwcOgigh3^4Vr#Wu+HAw!P zB?E&*vPu2a`X4~O5d96zALV~C|M!RJ-){6foqS0 z4nXoC*J!$~jTA^jmxb5|@@c(we6nl=7lXXz5f=`>g7j?$_f7vp-Fwb*N{~eIoZ@&L znEMd^1G)DtGAkMJLe&yl+}*RT#*9^pTbydZ-((1j3rbQgl;A22>( odH^*aoBT1ykm6tUf9pAN(Pbg-F)^`nnV%}gSPyg`Driyx0DN|Hp8x;= literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/dollhouse.mw3 b/worlds/smw/data/palettes/level/castle_pillars/dollhouse.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..06849ceaf1d56e8efed4daa1df681ef3abcb1c59 GIT binary patch literal 514 zcmZQzxLe& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}p|2rA` z8kqg)3SjO#X^|iMwH93#VqYOI$S?89vJqSi@|H(jIQ$CIw;9|w{SUMc< kz>Hh|m}5xsulm3B9J%PaA?`6Tv2vN8D#ln3bRRkZ09~Db`2YX_ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/gold_caslte.mw3 b/worlds/smw/data/palettes/level/castle_pillars/gold_caslte.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f4c03d2cb17a6f69a5082b7016d2370311e332fd GIT binary patch literal 514 zcmZQzm{Z)xX~cGo$&z^v^MB^a#S9F;d0y*-=tADvatuIueHBv^YnT5K^@;XbAo0~z z|LZ~YY`J=CkURqeLO%n;|9S=n1!ge@d4_t%`AiUbkPQ&?Ve+*ma-siI<*PyR=PVf* zB$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3f88E`Wu)(%Kv2k?+?+x-RO5Z1Cs*t17-#c z05ZRj*S-em8sYyqg7j?$_f0|O zf!uS>a!Qay@|@y$9hmzN{sYPXXOw6D&%B+%9_U|)`w{*F@i}tW<{H)^%Om^;k{4tU z2Zk3!9^Hi?`3H;-m>xjQ$0mQwF{Joc{oi_yTy$B8drVBMT;`{WG1dd!hYC6b0FJqQ Al>h($ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/original_gray.mw3 b/worlds/smw/data/palettes/level/castle_pillars/original_gray.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..37143fb2b8892a981d671d3484adc747c5a55b6b GIT binary patch literal 514 zcmZQzxLdzi!NYKk?Kw*$yKP>Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVfR)m8uN zLGrWZ>a9WY3=D5P{ew0|iKgEMvi}3I0<#!{JVQO>d?tvz7=tuJJfl35JTpwb)nNdBB91A|1eN&VFNA8`E*%pc`{GXM97=-+PiJDq_^f%ySYKRN)}U&w1;19Xk> ze;o4S8uAXBu4^L&($IB7+*8O4@=JWOYy=mByyX!W4!?r*Z3g#ELFR${bIx)~kVNvF z;&>gH`w{*F$^U1RXa3K;oxvXHUx@z^{sZwja@Xb>)*;Iy{0EX3WDp027epT2g&_F{ oj1QO|K+VS{f6OtY_*ebkdX8LlS%`Z~Osrhyr;0Jw1Ko!X0BN&%n*aa+ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/original_green.mw3 b/worlds/smw/data/palettes/level/castle_pillars/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..4124e1900166d1399ba09d8b5064a1f79efbcc40 GIT binary patch literal 514 zcmZQzxLcpVxrFfrqYl$Vrmswsiy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe(j^$+Sn;;XCv zgX!6F_0}MH28N?5p1NywgOw!g85lsS6_~{sDDF2iBzduC(cB9|v3``2l4}kj70m%MBUi%uL zYlQ#fkQdjGchGcQ8!3>6t{Y-sAuq@;@yW6gTnzG-M_f4k3evY3+&2Z82XfCj%PBz; z$#aV1bzts8_zxuipHZIqKl645d!TByERXOXNM4XZ92i~@d2|D7f+HAw!P zB?E&*vPu2a`X4~O5d96zALV~C|M!RJ-){6foq*fP|1-)n|7YILU=Q>!#Qg~Wf%qJ`YjX|jkmV8n1IY_Ahy%k5B9HDuko*J22TTv3 l=3|pT<``1^tNw32M=rW7#62b^Rxa~X#Te^>?n4DivH+X+brk>r literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/original_white.mw3 b/worlds/smw/data/palettes/level/castle_pillars/original_white.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f235216f1da2e7d822ea64899b28253d7f7ab7da GIT binary patch literal 514 zcmZQzxLbeCPa-@jeop$m{Qu>Xiy0Vx^Sssv(Rb_XbwT9+dIkoC`iA-kKnN0FUG*PK z&z7sV2FWupyz%r8+7u<4ejCUJt7aBskY}i8oX-T27h{lSh-Z{%l4pj=*P6(M{!f*! z2FahZWMGg;HmRRl{{ybSf%&8SPv-yr5dGVYey1}qDKI|(>PH74`wMyPYk;m1{*Oam zTtnVL({*j6KpMJkh36e;jQyi}Y zb3ej=Ao>4{^34C4w=>uS{R{Cw!haw>NAB8O!#ZSng#SSDf(+uo@Pf#ryAUM*fbjv- n1E~4fvwiHViV{8TZJstYLNUnO9lpsWRv=-^*?}mA^IDbKg$1P{_hXbzuo9}Is=me^8=uMbO5rykk`Hj z=o;bwION4O8_2KP-t=7HRE&T>kS zMDm>CcpaGg5dH(n|7VnE{?ELf!5-*ei2D)#1MxX>*XA15A}FR tAo&N3511Z6&BrEx%rT_+SN-35j$CwEhU41pv|Hd71zK literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/purple_pink.mw3 b/worlds/smw/data/palettes/level/castle_pillars/purple_pink.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5bea72483006a71241be6d52843c36883e07f1b8 GIT binary patch literal 514 zcmZQzm{ZK5!|lSKAXKugrlNLoF$2SIp4a*yx{!Ca90O2ZU&YkK+U0*leWHC9NPKnG z|9TKTTdv+3B+tNrP{F|Pzn+0Xfmw_}o}r#`J`+S9Vgty0n0&2?Ty8QeDonFn&uIm;u4nX!7^4iw` zT_gM-hrGCkyo09e+DL&kblnj93VA_(iBFb|;9`)sJmSLPSCGEV;JzuyJdk_NSxyO( zNS;$1uLE-*!haz7|BUj?|CzTl*aQ6waX-R;AU;R#+FZjrWO;=DK=OhN;=u5N$fLUu rB>#Z%0n-Dh`Pk%-IffMfs{dQhk&7-1agT|KmCO88F~)kJ`_KUZh7Wj` literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/sand_green.mw3 b/worlds/smw/data/palettes/level/castle_pillars/sand_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5757d8fbfaa1584b28898d2532f44253d37fd77e GIT binary patch literal 514 zcmZQzxLdzmz(;YTZiv}7I}7j0#S9F;d0y*-=)3jxx*&3qf~szYX_x5}l^`#W`0A?v z^&on-T)j0&o`IoDR8MWA)n~U*Zw7|{K)wRA7=t`RJ>z^Ph`bnsG($Y2Jd->#Oup7c zF7$t@d^Je^oFxN;M6yZ!)cPMly%7Bk%pc`{GXM97=-+PiJDq_^f%ySYKRN)}U&w1; z19Xk>e;o4S8uAXBu4^L&($IB7>?`C2`6WJCHiC;m-tveGhhIVZHiP@7AoD=(IcGT~ zNFsSoal8)9eF*=7yYIU{sYMiGKd4i3nGv1 sLXi9e#s^Ffpyp$fKjs)x{Hy+NJx4COEW|w~CRQ%G0D^aT0RR91 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/shenhe.mw3 b/worlds/smw/data/palettes/level/castle_pillars/shenhe.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..93dc170a525e495da6d936d262e48c0a4db03d74 GIT binary patch literal 514 zcmZQzxLdzip^qm(RoL!hRC)H~Vg`oaJg@aZ^xgV;T@blQK~*=yw9E8~N{|;we0A0T zdJsKZuHG6X&%n^4tL3yIEHwKzko_Ns6_~{sBm4*AbL6hgHLOFHNB9pU-^X)B;HL;g p9^Hi?d5GU}%O7(LDgIUex1J*xT{px%CMH%c^Haqb>w)e=2LJ}Les}-? literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_pillars/whatsapp.mw3 b/worlds/smw/data/palettes/level/castle_pillars/whatsapp.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..198f46eca8a90c9fe2ebbad1e53e254daa9d12c0 GIT binary patch literal 514 zcmZQzxLd!7gPGSwaH;4sNp-o&#S9F;d0y*-=)3jxx*#%h<(PNSz@zquT z>p}Evxq54mJOe|E;8VfVdb!5e!WkI;1NjQfVhr*O^^EhGAo2>#4a^_qe=`5~hsoEP z$c6q-m9GZLpR;6OkVrPEpIZL|s28Fip&n%ZcB9|v3``2l511J+07$-&*S-em8sYyq zg7j?$_f0|Of!uS>a!Qay@|@y$ z9hmzN{sYPXXOw6D&%B+%9_U|)`w{*F@i}tW<{H)^%Om^;k{4tU2Zk3!9^Hi?`3H;- mm>xjQ$0mQwF{Joc{oi_yTy$B8drVBMT;`{WG1dd!hYkR=M0!a8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/dark_lava.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/dark_lava.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..477701e86a9bda2b95fd8b41c15acc4eedccd012 GIT binary patch literal 514 zcmZQzxLcnl(joFnCD-)4t6|vWVg`oaJg@aZ^xgV;T@aZnkSdU2x>}-5;GGOee0A0T zdJsKZuHG6X&%kg(hEr8bH{W!VB?H5MAYXx5j6t5Eo^d`CM4pRbBSQtFJd->#Oup7c zF7$t@d^Je^98i};vPu2a`X4~O5d96zALV~C|M!RJ-){6foq*fP|1-)n|7YILU=Q>!#Qg~Wf%qJ`YjX|jkmV8n1IY_Ahy%k5B9HDu tko*J22TTv3=3|pT<``1^tNw32M=rW7#62b^Rxa~X#Te^>?n4Es0syKqc#Z%7 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/dark_purple.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/dark_purple.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..29eff5aeffa30eeb48f5d158f3cdcba0f81e99d9 GIT binary patch literal 514 zcmZQzxLbczfnC$XFw1ha<6Ga!#S9F;d0y*-=)3jxx*#$|L{&G#ludPs#2H7B`0A?v z^&on-T)i~|!}cr&hNTk6HMmTD9M^|3F#NA)U{GKdV~}U4XPnQ(z+jQhz~Ijj!ciu8 zMX-zyCSPkJ7y3U{z8WO&rOCih7^WMyG3^V`EQtOF=8y6}ng9Dk^lvx%ovzILOtHg= z0Rw={*ORob`K~%k&Q>2o8f<@o$To>%3L2)}f!O7JL__6nYsDwaMsP96TOM)Y@GD5) zW^mv1KQqLB4#gfr0jDit+eKjRL-k*=sD#*Tq4gZs1Yz>X{$pU^$X%OjScfc+>OUWj z0FG@U5P5VLg8awAo508-hFgB6>E|T#?5g7P!0<;0ApIsLRxa~X#Te^>@qr4QH~`L| BZ(jfa literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/dollhouse.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/dollhouse.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..73f69240205bd32096a8b301ad63b7d04578b653 GIT binary patch literal 514 zcmZQzxLez^Ph`g1|ZVg$}d5-^mVe+*ma-siI<*PyR=PVf* zB$7?)r`G=f>V@cUVE!oolli|tME`c9-{~!~3d|3f8886I{&zC=H8A_p6~Nqg(jq_h zYc0Ag#J)makYD1HWh1y4RiKaf1c z?(GcrK>tGAkMJLe&yl+}*RT#*9^pTbyr~2zd?51ZE(FOxV0^&zfEl;^F~^YNU-f_M YIdai;L)>FxV&yVFRgAG7=st7+0M!V9?f?J) literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/forgotten_temple.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/forgotten_temple.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..35d97033f84735c0b0cf9a3d60736834483f3622 GIT binary patch literal 514 zcmZQzxLe;MaZDjgm(OgIQ<(SUVg`oaJg@aZ^xgV;T@blQK~*=y^r^}c2{m1i`0A?v z^&on-T)j0&o`KV@cUVE!oolli|tME`c9-{}lY3d|3H`q2T%{z6{+ z8lY>0|KpGs*N}J6bX^-MkcO@sVqYOI$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE} zK@!PxisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*{4vLn;$QWD>p61KWg+e{F|l%)pDM;!4|E?YNEQJAM7((c literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/original_gray.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/original_gray.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..37143fb2b8892a981d671d3484adc747c5a55b6b GIT binary patch literal 514 zcmZQzxLdzi!NYKk?Kw*$yKP>Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVfR)m8uN zLGrWZ>a9WY3=D5P{ew0|iKgEMvi}3I0<#!{JVQO>d?tvz7=tuJJfl35JTpwb)nNdBB91A|1eN&VFNA8`E*%pc`{GXM97=-+PiJDq_^f%ySYKRN)}U&w1;19Xk> ze;o4S8uAXBu4^L&($IB7+*8O4@=JWOYy=mByyX!W4!?r*Z3g#ELFR${bIx)~kVNvF z;&>gH`w{*F$^U1RXa3K;oxvXHUx@z^{sZwja@Xb>)*;Iy{0EX3WDp027epT2g&_F{ oj1QO|K+VS{f6OtY_*ebkdX8LlS%`Z~Osrhyr;0Jw1Ko!X0BN&%n*aa+ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/original_volcanic.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/original_volcanic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..21d82d7c84a14040cc3cd7c5df0feac6bf07fd93 GIT binary patch literal 514 zcmZQzxLdzi!NYKk?Kw*$yKP>Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVfR)m8uN zLGrWZ>a9WY3=BtAJayOV1}jO}Gcf!I@)elH7~~o18Rs)Wy8QeDonFn&uIm;Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVfR)m8uN zLGrWZ>a9WY3=AhC&lR1vG_c$3#lY|%$X8$%V~}U4XPnOjkr!i-W{78$XOd@z$=905 zh5k>KuLjAVvt(e9NH(dTTK@y67oxv``J?<#=KuZ>{o9Rxr!z1qFh2n5M+YGL3wiBp zfUXh#k3(KuL*7Btb#0_T8oF+XeTBRrzr-iYMsP96TOM)Y@GD5)W^msWWFE*p=PaiL zNhHrHj@N;?58*$M{C`Gy=Ksvw8SH`ng}5K#KM96#~ee7f7So3=g38ug}BGW#L8uUsu*KE(0%9t0F}Ubvj6}9 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/sand_gray.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/sand_gray.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5b11808ae6b95c2de07c589fc05b2662555af87b GIT binary patch literal 514 zcmZQzxLdzmz(;YTZiv}7I}7j0#S9F;d0y*-=)3jxx*&3qf~szY>3P)`B4u_U@zquT z>p}Evxq54mJW#|jAnZ$;X?9pP1H*qHUx8VSL7t(WaXu47UW`GSA)Zm5NuC)dUuz;4 z`ae~^8YF+tl7T@Y*`$7I{STmCi2erVkMcj6|NBGqZ#Vj#&cLL=`~avQ9f0gF zx<>dv4ta46c?V6`wUGj8=(-{H74m}o5}zy^!NnkNdBlaouONM!!F^MZc_8z^Ph`bnsG($Y2Jd->#Oup7c zF7$t@d^Je^oFxN;M6yZ!)cPMly%7Bk%pc`{GXM97=-+PiJDq_^f%ySYKRN)}U&w1; z19Xk>e;o4S8uAXBu4^L&($IB7>?`C2`6WJCHiC;m-tveGhhIVZHiP@7AoD=(IcGT~ zNFsSoal8)9eF*=7yYIU{sYMiGKd4i3nGv1 sLXi9e#s^Ffpyp$fKjs)x{Hy+NJx4COEW|w~CRQ%G01k0?!TBm4*AbL6hgHLOFHNB9pU-^X)B;HL;g p9^Hi?d5GU}%O7(LDgIUex1J*xT{px%CMH%c^Haqb>w)e=2LJ}Les}-? literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/water.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/water.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9822a3c2eaa268eda71e8bfbd8ae929ccf3836e0 GIT binary patch literal 514 zcmZQz*j0W+ro-@+t&Urs-DR)I#S9F;d0y*-=)3jxx(q=1MGC6A8K&u)iv?cWg2Y!> z{jUenv*qfoLGnpFZMx5Gy@S>#iDWY{{I6$VP+%5gkY}i8oX-T27vZ$v{mb}|t(F-k zUuz;4`ae~^8YKV5k%7T1ePa2R>K#BnM1KSGNBN)3|NSBQw;TOVf2e!F?twD{1_0Ur zLD0VDmRz*@ZG8-Bu=_T1SV?*qz7Eul!!DmEIYTejIX+o7f{Q`k@`wwEUqSjdgZrlc zf$jyF&*QW?%q)FIajh%NeW?Dk7OLmJrynAChYco=>_1@m1p4KDtV5PZ^9a+#kh##j%G4^)uE0{~N!gh2oR literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_small_windows/whatsapp.mw3 b/worlds/smw/data/palettes/level/castle_small_windows/whatsapp.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..198f46eca8a90c9fe2ebbad1e53e254daa9d12c0 GIT binary patch literal 514 zcmZQzxLd!7gPGSwaH;4sNp-o&#S9F;d0y*-=)3jxx*#%h<(PNSz@zquT z>p}Evxq54mJOe|E;8VfVdb!5e!WkI;1NjQfVhr*O^^EhGAo2>#4a^_qe=`5~hsoEP z$c6q-m9GZLpR;6OkVrPEpIZL|s28Fip&n%ZcB9|v3``2l511J+07$-&*S-em8sYyq zg7j?$_f0|Of!uS>a!Qay@|@y$ z9hmzN{sYPXXOw6D&%B+%9_U|)`w{*F@i}tW<{H)^%Om^;k{4tU2Zk3!9^Hi?`3H;- mm>xjQ$0mQwF{Joc{oi_yTy$B8drVBMT;`{WG1dd!hYkR=M0!a8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_wall/cheese.mw3 b/worlds/smw/data/palettes/level/castle_wall/cheese.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..913ad39778755fba300dd2b321b776473ff1f906 GIT binary patch literal 514 zcmZQzxLfZlc~RBW=%&>?`~AU_iy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVW#@zquT z>p}Evxq52`hW)_|4An|@_WQl>N8it8VEA9pz@We^#vsp7&p4k6q@RHSVFOIQ)nNdBB91A|1eN&VFNA3(Jb{SC|?<$p5&_lM};ZuC2yfk}b+0W$*z0GVINYhMF& zjqraQ^5Pou4w|lOBL&hhw1e#{`=%iCK<+tbIVDIU zc}{V>4$OTB|AFNHGs-jnXWq_W5A-j@{Rsbo_#C-wa}Dc|z^Ph`g1|ZVg$}d5-^mVe+*ma-siI<*PyR=PVf* zB$7?)r`G=f>V@cUVE!oolli|tME`c9-{~!~3d|3f8886I{&zC=H8A_p6~Nqg(jq_h zYc0Ag#J)makYD1HWh1y4RiKaf1c z?(GcrK>tGAkMJLe&yl+}*RT#*9^pTbyr~2zd?51ZE(FOxV0^&zfEl;^F~^YNU-f_M YIdai;L)>FxV&yVFRgAG7=st7+0M!V9?f?J) literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_wall/grand_marshall.mw3 b/worlds/smw/data/palettes/level/castle_wall/grand_marshall.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..574d557f1eadc3cf447eff1f3bce37bad9cd25f9 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM=jB5Q4;4SN#Xm zv*qfoLGla?OC+8taAKA^_iCET;rXB+n_1 z*MYeY;Xjc4e@1!c|IFJN?1BD;xF6v^5T7G=ZLVP*vOL0nAbCLsabS2sVg> ze;o4S8uAXBu4^L&(lE4x?JML3`6WJCHiC;m-tveGhhIVZHiP@7AoD=(IcGT~NFsSo zal8)9eF*=7yYIU{sYMiGKd4i3nGv1LXi9e o#s^Ffpyp$fKjs)x{Hy+NJx4COEW|w~CRQ%G0L;vM0|KpGs z*N}J6bX^-MkcO@s;+{fYkYD1HWh1y4h`dNd7;gJoA6%?F{xn|3dta@E?fJk-Ik6unt)s;XjbPAcHtCydd)EE(FOxV0^&z l0BSxq`D2bD#lPzR)^p^d%R<~^Vq)boKUIve9_T)F002widoTb1 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_wall/sand_green.mw3 b/worlds/smw/data/palettes/level/castle_wall/sand_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5757d8fbfaa1584b28898d2532f44253d37fd77e GIT binary patch literal 514 zcmZQzxLdzmz(;YTZiv}7I}7j0#S9F;d0y*-=)3jxx*&3qf~szYX_x5}l^`#W`0A?v z^&on-T)j0&o`IoDR8MWA)n~U*Zw7|{K)wRA7=t`RJ>z^Ph`bnsG($Y2Jd->#Oup7c zF7$t@d^Je^oFxN;M6yZ!)cPMly%7Bk%pc`{GXM97=-+PiJDq_^f%ySYKRN)}U&w1; z19Xk>e;o4S8uAXBu4^L&($IB7>?`C2`6WJCHiC;m-tveGhhIVZHiP@7AoD=(IcGT~ zNFsSoal8)9eF*=7yYIU{sYMiGKd4i3nGv1 sLXi9e#s^Ffpyp$fKjs)x{Hy+NJx4COEW|w~CRQ%G0D^aT0RR91 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_wall/shenhe.mw3 b/worlds/smw/data/palettes/level/castle_wall/shenhe.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..93dc170a525e495da6d936d262e48c0a4db03d74 GIT binary patch literal 514 zcmZQzxLdzip^qm(RoL!hRC)H~Vg`oaJg@aZ^xgV;T@blQK~*=yw9E8~N{|;we0A0T zdJsKZuHG6X&%n^4tL3yIEHwKzko_Ns6_~{sBm4*AbL6hgHLOFHNB9pU-^X)B;HL;g p9^Hi?d5GU}%O7(LDgIUex1J*xT{px%CMH%c^Haqb>w)e=2LJ}Les}-? literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_wall/water.mw3 b/worlds/smw/data/palettes/level/castle_wall/water.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a0955e820349da62f84748d19bd6cd4ad55b6f55 GIT binary patch literal 514 zcmZQz*j0W+ro-@+t&Urs-DR)I#S9F;d0y*-=)3jxx(p2A*^5P<$gnB8Yj&ve=z+vn zSN*RC(X-|1tr>v&lX%*6pWAu|txpokW?=YV&%mI-EXE+uP|rA@2_i4TX~FxK@gG|) zGfckLL@xAys(dv_{*5C8gIW5-@-5XnfP9Gl2Ii0QKbimgL-cPq`knqz_ki64X9f%a zvj2miea$VoX!YCr7}8+(ZRW6&^e}uKs2hh}K2LInUaWI`vTOtwgS_Pt7Y@II^lb+B zP5%Sk3o@U_X>*ua`i$aQSD5=y{bwyy&wo!pMDPw9Odi>P!0-w5%llY|ERX8HevWHA zGQ1FZbQgl`Kd5=otT`OF{7TD@N#)tQ%k{$0bwk{5Vq)boKUIve9vB~}Ac+S6-r|Dm literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/brawler_pink.mw3 b/worlds/smw/data/palettes/level/castle_windows/brawler_pink.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..69c496fc5d07949af94a1b203eb275589a595434 GIT binary patch literal 514 zcmZQzxLfaI5a>B2^hTsj+PXNJkw zn#hIzPnE9*$)B@iV30^Qsh?W^1E?0Fzk&Iq{7>fp{t*4!jee&yFexxU0P05vAoB}( z?Q4Lp5&n-uUR*=oLDO|@q(BRiKal)?MtSD{%-b34f&PWKAK^a`pCfl|u3;UrJi>ngPyZ1Tq(LyCXZ|E=f9MVE!R$Hc_SWqzs{V?EG)=l}o}uzREc literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/cheese.mw3 b/worlds/smw/data/palettes/level/castle_windows/cheese.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d91826e8647a061c9dcf6971e7b05f325e531e43 GIT binary patch literal 514 zcmZQzxLfZlc~RBW=%&>?`~AU_iy0Vx^Sssv(Rb_XbwT7Z9&O1S#S5y!T0fjX;;XCv z*MsQUa`o0Ac?O1RB|H25-uI*LXEQMT2l5q|#Teun>KW%VLFC04q#5EF<(cG}Ve+*m za-siI<*PyR=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3H`q2T%{z6{+ z8lY>0|KpGs*N}J6bX^-MkcO@sVqYOI$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE} zK@!PxisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*{4vLn;$QWD>p61KWg+e{F|l%)pDM;!4|E?o002{;d5-`9 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/dark_aqua_marine.mw3 b/worlds/smw/data/palettes/level/castle_windows/dark_aqua_marine.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..501f11d1a95448f15b53c23769d3654c04e315cc GIT binary patch literal 514 zcmZQzm{ZKaut4O11cPFN?g7imK;Cbj*ZLs3kaxBm15n<9qd@?s0wlh=>VG|mo-J2z z4U%VIfaq0lTM)*;@V}mcL4jF}L7t(WaXu47UW`GSA)Zm5NuC)dUuz;4`ae~^8YF+t zl7T@Y*`$7I{STmCi2erVkMcj6|NBGqZ#Vj#&cLL=`~avQ9oz-#DdhcM40Mg~e;o24 z*J!$~jTA^j*A1}`xjQ k$0mQwF{Joc{oi_yTy$B8drVBMT;`{WG1dd!hYFfh0I76y&;S4c literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/dollhouse.mw3 b/worlds/smw/data/palettes/level/castle_windows/dollhouse.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d3e5fe4b407f7e9f907cd0496673edce9147644b GIT binary patch literal 514 zcmZQzxLez^Ph`g1|ZVg$}d5-^mVe+*ma-siI<*PyR=PVf* zB$7?)r`G=f>V@cUVE!oolli|tME`c9-{~!~3d|3f8886I{&zC=H8A@b7#=V#m(8~{ z3xz0!x$mS!e(cv;4DDe13VA_(iBFb|;9`)sJmSLPSCGEV;J)d9pnV|soU@z~B#}I) zI9>#Z%0n-C! i-15g9LyCXZ|E=f9Mb`~+kBNzu%luR^#(JRp&;bA^{)5H< literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/original_brown.mw3 b/worlds/smw/data/palettes/level/castle_windows/original_brown.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..0bcc7305b7aa91c58bc8ebdca8f486cecebe6572 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kh!!%yFayJXAYXx5j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 vg&_F{j1QO|K+VS{f6OtY_*ebkdX8LlS%`Z~Osrhyr;0Jw1Ko!T4yynFNDg@( literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/original_gray.mw3 b/worlds/smw/data/palettes/level/castle_windows/original_gray.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..37143fb2b8892a981d671d3484adc747c5a55b6b GIT binary patch literal 514 zcmZQzxLdzi!NYKk?Kw*$yKP>Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVfR)m8uN zLGrWZ>a9WY3=D5P{ew0|iKgEMvi}3I0<#!{JVQO>d?tvz7=tuJJfl35JTpwb)nNdBB91A|1eN&VFNA8`E*%pc`{GXM97=-+PiJDq_^f%ySYKRN)}U&w1;19Xk> ze;o4S8uAXBu4^L&($IB7+*8O4@=JWOYy=mByyX!W4!?r*Z3g#ELFR${bIx)~kVNvF z;&>gH`w{*F$^U1RXa3K;oxvXHUx@z^{sZwja@Xb>)*;Iy{0EX3WDp027epT2g&_F{ oj1QO|K+VS{f6OtY_*ebkdX8LlS%`Z~Osrhyr;0Jw1Ko!X0BN&%n*aa+ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/original_water.mw3 b/worlds/smw/data/palettes/level/castle_windows/original_water.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d61f6dba36f6fca7c93c3764bd9a43c16022c64b GIT binary patch literal 514 zcmZQzxLfaJ$q{reODH)sdq(l(Vg`oaJg@aZ^xgV;T@VS8hmaug)m8uNLG*07dTWq8 z1H*~Pb48~u4eU01F);iG@)elH7~~o18Rs)Wy8QeDonFn&uIm;87j zo*5=zYa$o=KUKaOB!AA5fk7hKq<(7s51?9z{s!ic@;{mX`$P0^H~O8Bm4*AbL6hgHLOFHNB9pU-^X)B;HL;g p9^Hi?d5GU}%O7(LDgIUex1J*xT{px%CMH%c^Haqb>w)e=2LJ}Les}-? literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/underwater.mw3 b/worlds/smw/data/palettes/level/castle_windows/underwater.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..db5c1a996ccb3c7dd95ec0caee5079dbb9474eb4 GIT binary patch literal 514 zcmZQzxLe;Mb5w=R(A_e_ai!& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}pe<81Z z4bU~h|8dBRYsfojx~`2BNW;($wy%&E literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/water.mw3 b/worlds/smw/data/palettes/level/castle_windows/water.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a0955e820349da62f84748d19bd6cd4ad55b6f55 GIT binary patch literal 514 zcmZQz*j0W+ro-@+t&Urs-DR)I#S9F;d0y*-=)3jxx(p2A*^5P<$gnB8Yj&ve=z+vn zSN*RC(X-|1tr>v&lX%*6pWAu|txpokW?=YV&%mI-EXE+uP|rA@2_i4TX~FxK@gG|) zGfckLL@xAys(dv_{*5C8gIW5-@-5XnfP9Gl2Ii0QKbimgL-cPq`knqz_ki64X9f%a zvj2miea$VoX!YCr7}8+(ZRW6&^e}uKs2hh}K2LInUaWI`vTOtwgS_Pt7Y@II^lb+B zP5%Sk3o@U_X>*ua`i$aQSD5=y{bwyy&wo!pMDPw9Odi>P!0-w5%llY|ERX8HevWHA zGQ1FZbQgl`Kd5=otT`OF{7TD@N#)tQ%k{$0bwk{5Vq)boKUIve9vB~}Ac+S6-r|Dm literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/castle_windows/whatsapp.mw3 b/worlds/smw/data/palettes/level/castle_windows/whatsapp.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..198f46eca8a90c9fe2ebbad1e53e254daa9d12c0 GIT binary patch literal 514 zcmZQzxLd!7gPGSwaH;4sNp-o&#S9F;d0y*-=)3jxx*#%h<(PNSz@zquT z>p}Evxq54mJOe|E;8VfVdb!5e!WkI;1NjQfVhr*O^^EhGAo2>#4a^_qe=`5~hsoEP z$c6q-m9GZLpR;6OkVrPEpIZL|s28Fip&n%ZcB9|v3``2l511J+07$-&*S-em8sYyq zg7j?$_f0|Of!uS>a!Qay@|@y$ z9hmzN{sYPXXOw6D&%B+%9_U|)`w{*F@i}tW<{H)^%Om^;k{4tU2Zk3!9^Hi?`3H;- mm>xjQ$0mQwF{Joc{oi_yTy$B8drVBMT;`{WG1dd!hYkR=M0!a8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/brawler_dark.mw3 b/worlds/smw/data/palettes/level/cave/brawler_dark.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9197e99e15d5423640d948542383bfb75363a3f6 GIT binary patch literal 514 zcmZQzxLeP_;KVhqv@@r?3J^2{*#S`)d@ z|EcoTAo+8a3=9&f>6-yfoXyV37-1||jO2SEMk0Azn5uYC>B zHNyXK$ct;pJ7~JDjTA^j*A20+kQd~a_+;4#E(UqaBQ6|%1?k%i?wf+l1G(p%<&+?a zz^Pi2Ml=PT3sI^QJ~lF!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}pe<81Z z4bU~h|8dBRYsfojx~`2BNW;($wy%&Ek{G%$Kx@&cVl_czu-H(u8r)y-l&Fg2-XP|!ooydZ; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/brawler_red.mw3 b/worlds/smw/data/palettes/level/cave/brawler_red.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..e3f5cdfaaf02453750d8396e33a511a4682070f2 GIT binary patch literal 514 zcmZQzxLfaJ$q{reODH)sdq(l(Vg`oaJg@aZ^xgV;T@X1@;HHF);#^rdsW>T+`0A?v z^&on-T)j0&o`GRKZ>64*WVzx`Lk5QbK)wRA7=t`RJ>z^Pi2MYWDI7aEZgc$OfXUaI z$c6q-m9GZLpR;6OkVrPEpIZL|s28HYf%&8SPv-yr5dGVYey1}qDKI}^X21X-`wMyP zYk;m1{*OamTtnVL({*j6KpKX2uziKRAiu;X%SLc9$Xgz9;qWU+-)3;%6l5OAJ?AW^ z1W6>%DUR2Hxewt#koJstYLNUnO9lpsWRv=-^*?}mA^IDbKg$1P{_hXbzuo9}db%u^R*n$^1_0S#$ZKB% zbdB(TW(;Yt`@}Wm9W-6nMhc{1%7g4H`=%iCK=S7- zrvynP&nb@Afw>RiKal)?MtSD{%-b34f&PWKAK^a`pCfl|u3;UrJi>ngPyZ1N784{RHP5auJ|6WRR;`E|NRcH6vu27Lzl2LO!egcbk* literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/bright_magma.mw3 b/worlds/smw/data/palettes/level/cave/bright_magma.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..92e059d863c9d8cd0611069f71cf58ca5981004d GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@cB_;K9(tAjG(xv5E;KzPjpv zJ&2wyS8olHXJ9zV;V0Q9@& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}pe<81Z z4bU~h|8dBRYsfojx~`2BNW;($wy%&Ekd_;UAyB{IHPS?n8o7c~v&p`hG0DX~xDF6Tf literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/dark_red.mw3 b/worlds/smw/data/palettes/level/cave/dark_red.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b2cc6068059096dcbd367b40890b7fcb95dc6c47 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@dNalgg3K(;>j4sHX}NUtRUT z9z@TUtG5QpGcc%W_(`@1XsE7rWnlOZKuLjAVvt(e9NH(dTTK@y67oxv``J?<#=KuZ>{o9Rxr!z1qFh5{szyKio3wiBp zfUXh#k3(KuL*7Btb#0_T8isbTeTBRrzr-iYMsP96TOM)Y@GD5)W^msWWFE*p=PaiL zNhHrHj@N;?58*$M{C`Gy=Ksvw8SH`ng}5K#KM964w?^a8-ft#BjOX;{RsJWx<+=}ynY6K2Kom8j$nSo literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/glowing_mushroom.mw3 b/worlds/smw/data/palettes/level/cave/glowing_mushroom.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5b5531c7ef2d07ee1f87979af80c1999de696da1 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@dM};$_np&=SL5*j)+|UtRUT z9z@TUtG5QpGcXubU9EF5Xb;((!@%$#$X8$%V~}U4XPnOjk!R&x!JBO6;@%k!ldm
^GErg%>Vr%`nMbXPG?|JV1B^NfB``E7xLQI z09_;eABViShP;EO>)J?xGz{%v`wDqMeu+<(jo@OCw>;v);a8Bp&EUQ%$UKmH&RI?g zl1QFY9Ipd&AHshi`Tvaa%>S9UGuQ+D3voZfe;__b?%G_#I%Ii-|3LDB4C27>g2LsJ19Kn3e<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! p|A6rU(*vma*yJ5FAJ{eoAd&PAbAFc3q|GSrqwg*Z`U(0{0CwMW-$hNhI+>NOc42t5}~p?WUI~osKMlG zP2@uVr^;7@he3GF!v+-Z>dBiU!3wjaZ|y0N-%k3|1FhJk6jU~U4|^*!2D7E zC-Z-Qi242sQ95&NA@b-h1o^L7u|+G@7`J?@p{Cc`IEXw*{WAqG%^t%Kx?8;<<{S8l Wr$=QYI#{KT7A}GU;qHv>5^{% literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/magma_cave.mw3 b/worlds/smw/data/palettes/level/cave/magma_cave.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ca297deb25781716a786e000559beb2390427ff0 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@abhk;&oB;mZ-kVa5UyUtRUT z9z@TUtG5QpGcasZykphPaf#zQ3j@P{AYXx5j6t5Eo^d`CL_UXSlfZP&Jz{qHF!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}pe<81Z z4bU~h|8dBRYsfojx~`2BNW;($wy%&Ekd_;UAyB{IHPS?n8o7c~v&p`hG07lq>ng9R* literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/original_chocolate.mw3 b/worlds/smw/data/palettes/level/cave/original_chocolate.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..db2693d6be989e7dbabb14dd22f313cae46319dc GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kh!!%yFayJXAYXx5j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 rg&_F{j1QO|K+VS{@1Xg>wjl^%J|aGm-H(u8r)y-l&Fg2-XP|!o@5h1T literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/original_gray.mw3 b/worlds/smw/data/palettes/level/cave/original_gray.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2e01f09820c82ddd4c95fe1fd2e947082ee462a3 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kgZ@?6nrO9Q*jUJMNXfqVsKF$Q^tddB%o5P2~MX@+=4c_w*gn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 rg&_F{j1QO|K+VS{@1Xg>wjl^%J|aGm-H(u8r)y-l&Fg2-XP|!o<;Z~^ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/original_ice.mw3 b/worlds/smw/data/palettes/level/cave/original_ice.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..6d17d16efefb033235542a15d3997fd2f80ec50b GIT binary patch literal 514 zcmZQzxLfaJ$q{reODH)sdq(l(Vg`oaJg@aZ^xgV;T@VS8hmaug)m8uNLG*07dTWsU z`_Qd<-^(oPtLyjHGcf!IVg+U~26={h#`#Pjc?O1)I)09qLo4!R;qtX6a-siI<*PyR z=PVf*B$7?)r`G>~%QrB8l>f>6-yfoXyV37-1||jO2h0o@0Azk4uYC>BHNyXK$ct;p zJ7~JDjTA`3&<=KQAuq@;@yW6gTnzG-M_f4k3evY3+&2Z82lCH3%PBz;$#aV1buj%0 zlK;;r&-|ZxJA*yYzYzZ;{0HK5k{G%$Kx@&cVl_czu-H(u8r)y-l&Fg2-XP|!oT`7oS literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/original_mustard.mw3 b/worlds/smw/data/palettes/level/cave/original_mustard.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..001ed133195bd4ed71a53ceedcb86de5716982b8 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%p4Kfmf-I)mUn~5d*`2AYXx5j6t5Eo^d`CL|%+RnjxN1o=Kh=CSPkJ z7y3U{z8WNd&XR#aBH5&VYW)wOUWon%=8y6}ng9Dk^lvx%ozB3d!2AHHA02?~FXXka z0lG%`KMr|u4S5Gm*R_!XY3RBk_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~ zB#}I)I9>+_kxeb;$Av|AFKM8N`9%1(8R0 rAxQoK;{&D#Q1h|LJ7_+zZ3se`kBCoX_ao%j=^EK>^ZFU|8R#DXk$itI literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/original_volcanic.mw3 b/worlds/smw/data/palettes/level/cave/original_volcanic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..96befdfa3d55f38e0766ba31d0118b851dd59beb GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6XkF1}8;XjbCz%0ff&rr`ep9vx_#vsiQ&nV9%&kU2VHIWPbpDJGsl0Rq3 zz#x%qQa`o+2T(6We*^PJ`Jc@H{UQ3d8~sjaU{YXy0Mw5TK=v2%+SdSGBm5tSytszE zgQn}+NP#qT-4OcLsJ19Kn3 ze<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6!|A6rU(*vma h*yJ5FAJ{eoAd&PAbAD`zBsY!LRE9UTYd}-|LYkT6qv;rN$Q#dGA!nmM*BD(EqJO4|jN=MV?xemv?D87~Z%NH_j!%}2;9`)sJmSLPSCGEV z;J)d9W{CcGu3O?P^Q+76gu>j9?7yWFk$iE=`@~HJ=PAMDk^Q$+LOpgxtacf)d;{}G z`Jc@H{UPT2D@5tcwS~x|yAb5RX2lk*RAb!ot%jOjYvUmDAob4_yfk|ZKj?1tf|zgM XC!QXajga?NU8^f(7Haibhk*eA6NZpJ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/toxic.mw3 b/worlds/smw/data/palettes/level/cave/toxic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a78538ddba77375eaf781000fd04c3c71426fffa GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@dNa(#mmMfI~D}a*ZTNe0A0T zdJsKZuHG6X&%nUtv?eTDR$uRm8w10CAYXx5j6t5Eo^d`CL|%+RnjxN1o=Kh=CSPkJ z7y3U{z8WNd&XR#aBH5&VYW)wOUWon%=8y6}ng9Dk^lvx%ozB3d!2AHHA02?~FXXka z0lG%`KMr|u4S5Gm*R_!XY3RBk_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~ zB#}I)I9>+_kxeb;$Av|AFKM8N`9%1(8R0 rAxQoK;{&D#Q1h|LJ7_+zZ3se`kBCoX_ao%j=^EK>^ZFU|8R#DXq4|G8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave/toxic_moss.mw3 b/worlds/smw/data/palettes/level/cave/toxic_moss.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9afe6110309837b40676296ef6a9d03876b2d874 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@cA2kSMWO=BNsrW|}TYe0A0T zdJsKZuHG6f&v25%Pjab*yQ!xc1H*ryc?!&84Dt;1jPscw@~pDyiZ3)fl(@BF^0g*% zq5o6mt3mSTEEyOil1=KT*8c$Nh3Ic!{wV*G`M*Cz|8}F_=?qK?%nz6uFaXH?g}nAP zK-UQW$00ASA@88+x;9cE4MRKFzCvD*U*eNxBe)pkEswZx_!XpYGq`UGG7sdQbCy$r zB$DS8$Lqk{hwvXr{y(EU^MB^;4E8|(Lfnt=ABfM9yEfOb4p|=IKajj2gE%m}AoA!g r1j#>Oe8BVoYCbl32h9hz4M7O=5%G!aeuVrwT_d|~UO$6A1N{R4q3?i> literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave_rocks/bocchi_rock_hair_cube_things.mw3 b/worlds/smw/data/palettes/level/cave_rocks/bocchi_rock_hair_cube_things.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..0fb33b2d6a380aa40e8ca073ed8350eb9bd2ac53 GIT binary patch literal 514 zcmZQzxLfZn6QX17`Y&=`l6>~$Vg`oaJg@aZ^xgV;T@dLZF+n54w9E9P&EHUv`0A?v z^&on-T)j0&o`K;+K=ywiR$vxmkY}i8oX-T2hnT+~Xg*B7)n zNdBB91A|1eN&VFNA8`E*%pc`{GXM97=-+PiJDq_^f%yS50|o%uU&w1;19Xk>e;o4S z8uAXBu4^L&(lE4x-CM{D@=JWOYy=mByyX!W4!?r*Z3g#ELFR${bIx)~kVNvF;&>gH z`w{*F$^U1RXa3K;oxvXHUx@z^{sZwja@Xb>)*;Iy{0EX3WDp027epT2g&_F{j1QO| WK+VS{@1Xg>wjl_&eu(?A%L4!^5PVbs literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave_rocks/brawler_volcanic.mw3 b/worlds/smw/data/palettes/level/cave_rocks/brawler_volcanic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5a3cf230f04df8c4c561247c61b8355424aecf8f GIT binary patch literal 514 zcmZQzxLZF>=epuwArsXbI=6Hu7c(&Y=6S6TqVLw%>w-uD$J*-cseHa~JjxS6;;XCv z*MsQUa`o0Ac?O0PDn7a!bR(6d?HL&U1NjQfVhr*O^^EhGpzy8QeDonFn&uIm;d&PAbAD`9?4QG)9M-Zx9b@g{sXZBvlxRsLp|esCW!n+iBQ=cvejmP)L`

z*`Q1+)(x zfaD8#)v$+8h#4DyyoTsZs+(zhAh zH~r5H(f`hMOPpnXb@`o8nER3aw^SmMFHU)%xT)YgC73+2|CUOq$F7LgE<=`YVE!oo zlli|t#C(5+D4n^s5P5VLg8bL4*rJtcj9nfS-VT}%Y#V~MGuQ*e8y$e;&sk0hl1Rob F4*)fog=hc( literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave_rocks/layer_2.mw3 b/worlds/smw/data/palettes/level/cave_rocks/layer_2.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ff354e34fefadbbee66984960563071d2397b343 GIT binary patch literal 514 zcmZQzxLdzi!NYKk?Kw*$yKP>Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVfR)m8uN zLGrWZ>a9WY3=AhC&lLfA!vC2W82$tK3d~{*@(lHi^O+#>Vhqv@@r?3J@^JZD6S>g; zsq)nz`E!;G3=+vE^;7GA0QExjH!y#c|H=H{AEJM|(eHEyCI#jPK>g?dWPc&AeGSZh zZ1Un7@(!A=Ya<2H&}AX^74m}o5}zy^!NnkNdBlaouONM!!F^Mhf6iG>36e;jQyi}Y zb05NgAou=flxP0Wyq&=w=wFEY5&i@5Ida$L8rC7pBm4)F7i16zh8IL0-Gw0e2aFGx X9ze~w|Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=O8|DnVfR)m8uN zLGrWZ>a9WY3=AhC&lR1vG_c$3#lY|%$X8$%V~}U4XPnOjkr!i-W{78$XOd@z$=905 zh5k>KuLjAVvt(e9NH(dTTK@y67oxv``J?<#=KuZ>{o9Rxr!z1qFh2n5M+YGL3wiBp zfUXh#k3(KuL*7Btb#0_T8oF+XeTBRrzr-iYMsP96TOM)Y@GD5)W^msWWFE*p=PaiL zNhHrHj@N;?58*$M{C`Gy=Ksvw8SH`ng}5K#KM964w?^a8-j4_hqxcRJOD($c&PvY literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave_rocks/original_mustard.mw3 b/worlds/smw/data/palettes/level/cave_rocks/original_mustard.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8150d4687553f90c4e65aea1f8b15d570836f2f6 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%p4Kfmf-I)mUn~5d*`2AYXx5j6t5Eo^d`CL|%+RnjxN1o=Kh=CSPkJ z7y3U{z8WNd&XR#aBH5&VYW)wOUWon%=8y6}ng9Dk^lvx%ozB3d!2AHHA02?~FXXka z0lG%`KMr|u4S5Gm*R_!XY3RBk_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~ zB#}I)I9>+_kxeb;$Av|AFKM8N`9%1(8R0 dAxQoK;{&D#Q1h|LJ7_+zZ3x1xAL4%O@&K0!ba?;( literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave_rocks/pyra_mythra_ft_pneuma.mw3 b/worlds/smw/data/palettes/level/cave_rocks/pyra_mythra_ft_pneuma.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8f2b2817d8054bed1bdfd8d9073a69fb4710f227 GIT binary patch literal 514 zcmZQzxLdzi!NYKk?Kw*}`)yv6iy0Vx^Sssv(Rb_XbwMPfV54M)X_slUVyh8Ie0A0T zdJsKZuHG6X&%kgZ@?6n6k((0xOc@yd1NjQfVhr*O^^EhGAo2@&oF$hiy6Umn!{lpC z*fj9Jyd&PAbAD`9?4RxLRE9UTYd}-|LYkT6qv;rN$Q#dGA!nmM*BD(EqJO4|jN=MV?xemv?D87~Z%NH_j!%}2;9`)sJmSLPSCGEV z;J)d9W{CcGu3O?P^Q+76gu>j9?7yWFk$iE=`@~HJ=PAMDk^Q$+LOpgxtacf)d;{}G z`Jc@H{UPT2D@5tcwS~x|yAb5RX2lk*RAcP&pzwCkd|=xUw4K2o7~bdrB!A9wN{~b{ Gc6k7U?u2Rp literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/cave_rocks/toxic.mw3 b/worlds/smw/data/palettes/level/cave_rocks/toxic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..4f98b11bc175b53cbc37e5cb633966e404599c40 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@bmPp}Evxq54mJOcxl)0(hsS$(}LZVU|nfqVsKF$Q^tddB%o5P2~MX@+=4c_w*gn0&2? zT;v);a8Bp&EUT2f2e!U zSxyO(NS;$1uLE-*!hazB{~6_(|1)oAum}1V;(mnxKzxqewYi3M$npsPf#d}l#DU=j gkwNOd$DV5~no`4J%Be++>1b z^0g*%q5o6mtAYITbC$;>B$B0){wB@OhRHWDf0X~p{NEpSFXXka0lG%` zKMr|u4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK r;{&D#Q1h|LA64#(3v!RL>Jd{_yEeDye)10J^1{^s?wEXP literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/clouds/cloudy.mw3 b/worlds/smw/data/palettes/level/clouds/cloudy.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b7d07d348c420a90492bdd105f53ec279a624841 GIT binary patch literal 514 zcmZQzkjVD5>UKKo#UC6PJvn)DF$2SIp4a*y`fh!_E{ObJ&%mHi-%$Sm2tnejtNw%O z*>d&P3=Ge8isKB!-utfg%y!hYWMBZRW)@?RXQ*eK&jixXz;IH>&+&3-MV>5>4Uw-k zkqiBwDqqb2v|rPdfuT6cr2bmk_cWM%1M^4upUnULA^Nu){Z40KQeb|-%zyzv<`?qX z*8p83{2zzBxQ4ugrt8{B0igNl0OX!RUXWknlVu~g800OFxN!Itq;E60ZwfLGB!A9w zN{~eIoZ@&LnEMd^1IhnqlxP0Wyq&=w=wEdIf%qJ`YjX|jkmV8n1IY_Ahy%k5B9F~| t4;UXXJ%E~zP5!8gr|w$aU?mBAbXkyllvR(Is@k==J@=D$K$pLk1^_(oe6auk literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/clouds/cotton_candy.mw3 b/worlds/smw/data/palettes/level/clouds/cotton_candy.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f9ddeb89c87de921311bde0f2cb67ef47e9d5d3d GIT binary patch literal 514 zcmZQzxKr}Q@k1h*xkFx3!Q&qb*x95KH4(Rg6^#CM7eq;au literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/clouds/original_green.mw3 b/worlds/smw/data/palettes/level/clouds/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..79af508740ade55e9958691a077933c26c99be22 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM=jB5Q4;4SN#Xm zv*qfoLGtfIx8{8>v#hVK-wRX;R?RHNAkR?GIG+h5&%kg}$ItO{XhohZT)x&sF7$t@ zd^Je^oFxN;M6yZ!)cPNA`3B~X@;{mX`$P0^H~O8`=%iCK>j&rIVDIUc}{V> z4yOM=^8Xp-ng26yXRrtQ7vg_}|3G|>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK;{&D# nQ1h|LA64#(3v!RL>Jd{_yEeDye)10J^1t%|+hKuC literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/clouds/original_orange.mw3 b/worlds/smw/data/palettes/level/clouds/original_orange.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..453b717b9038d5e68d016baa245dbfd5cd5c9e22 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM=jB5Q4;4SN#Xm zv*qfoLGtfIx8{8>v#hVK-wRX;R?RHNAkR?GIG+h5&%kg}$ItO{XhohZT)x&sF7$t@ zd^Je^oFxN;M6yZ!)cPNA`3B~X@;{mX`$P0^H~O8`=%iCK>j&rIVDIUc}{V> z4yOM=^8Xp-ng26yXRrtQ7vg_}|3G|>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK;{&D# nQ1h|LA64#(3vtgnT_d|~UO$6A1Kp1b=0^hn<}87Z literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/forest/agnian_queen.mw3 b/worlds/smw/data/palettes/level/forest/agnian_queen.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f187d8a1abb47b6d168742b1bdfb21058bb0908f GIT binary patch literal 514 zcmZQzxLcpYW1xE3vfR%){a*FtVg`oaJg@aZ^xgV;T@d-do`K;(okD#$$Afy1`0A?v zV0yM(y){Ulf#F(Ib$$-dz3Old1_qGoa1JpBd4_t%`AiV`Y~A;^#eNdWbAY-b^0g*% zq5o6mt3mSTEEyOil1=KT*8c$Ng~&HBf0X~p{NEp<05u<*{81H8-L<;GN)q;s~#~`wQF;G?kDenE+1YG0Ihs}V*mgE literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/forest/atardecer.mw3 b/worlds/smw/data/palettes/level/forest/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..95e07701c24ecfc33f23fff8746f0ddcdbea60a8 GIT binary patch literal 514 zcmZQzxL>Xzct-G|Qi$|y^}E)Siy0Vx^Sssv(Rb_Xbs2#2|LYkT9@Ht+Pvm}34-#Kp z^&d>nmaDf0$tUv|ivChu$@c_fn0r++K{1GEVuFUWn1`=0duWC(w|(eHFkHedD&>roS&TuRp`LL*6GVOvYaH8urYO#MEtq_*iCpOaRQYO%d~2$H zo?$(*d;{}G`Jc@H{UP!OELA+Qnh<$(7lQnElJzXx6?WY6M^!v^*Xjl<`ls^A#S9F;d0y*-=)3jxx*+m@Jp;poI)(aKGs1r$K1c4_T*EqKd4&H!@`4QF!0>{| wqq`6!|A6rU(*vma*yN9@cd>|hrpRN1ew%AW1c@9uFM84KU zF7$t@d^Je^oFxN;M6yZ!)cPMlwGjCR=8y6}ng9Dk^lvx%ozB3d!2E!j0Rw={FXXka z0lG%`KUBWUV6DYPhc7PTz7YN58uAXBu4^L&(lE4x?JML3`6WJCHiC;m-tveGhhIVZ zHiP@7AoD=(IcGT~2;};B9SC268R0*W{C`Gy=Ksvwf&PZcBm4)F=g3`~YgmUYkMJKz zUXVc?7+w&0bQgl;A22>(dH^*aoBUA~Pu;b;!AcVL=&~U9D61YZRkdq#d+sOifG*#v F008ilqjx)G5@nhd!tWiLb8u z52k0!)mww)85ovIXlfpF=rLsvWnciQW)Br(kY}i8oX-T2&(?i!TkI#1JO`*7B42AF z7y3U{z8WN-Wy!#BEonyG?mB6pUWj}H^GErg%>Vr%`nMbXPG?|JV1B^NfB``E7xLQI z09_;eABViShP;EO>)J?xGz{%v`wDqMeu+<(jo@OCw>;v);a8Bp&EUQ%$UKmH&RI?g z0=Yh32f|liM)(gT|DREw`9Je^2791?L3{;fg#SQ%j@-4mhIPpD2>*fP1sTMF;RTUL wcOgjr0pkOv2T=2|$sbkm)Lp9^tR!KNE(>yxvg#32Rl7E~=YH}I=<>R{09|l>m;e9( literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/forest/original_dark.mw3 b/worlds/smw/data/palettes/level/forest/original_dark.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..7c28daa0d3a68859aa714f1336a6023ff43ff20c GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=822~v$rZpe0A0T zdJsKZuHG6X&%m&rw^Hwcs*JV*GXukaAYXx5j6t5Eo^d`CL_S;hy=}3dMDiSo`l7xoIda$L8rC7pBm4)F7i16zh8IL0-Gw0e l2aFGx9ze~Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=OBPMnPct)m8uN zLGrWZ>a9WY3=A(Bc$NBCjit65F);iG^0ym_F~~F2GtOs%$Uo415L7Q3Z)EQcldm^GErg%>Vr%`nMbXPG?|JV1B^NfB``E7xLQI z09_;eABViShP;EO>)J?xGz{%v`wDqMeu+<(jo@OCw>;v);a8Bp&EUQ%$UKmH&RI?g zl1QFY9Ipf6D=;Jc2a^BKD9`+#c{_tW(7zzQ0yDyYAU;R#+FZjrWO;=DK=OhN;=u5N y$fLUuB>#Z%0n-Dh`Pk%-s(9+I)eTmXut%2#xkp*`h^eYwo7;0gc?Wd)-}wMS#eK>E literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/forest/original_green.mw3 b/worlds/smw/data/palettes/level/forest/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9139338c3158de0604b83766bdd3977d0b83e024 GIT binary patch literal 514 zcmZQzxLcpVxrFfrqYl$Vrmswsiy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe(g%n#~8;;XCv zgX!6F_0}MH28Q*#m3j|UWwaHT85lsS6_~{sKuLjAVvt(e9NH(dTTK@y67b4%l{89cV^M8Mc{_RG;(;1k6_5;mF2O#?kdF^X} zt`Yu^Ltb1%-a*rKZKOaNx^9Slg}flY#3#!}a52bR9&zFDD@fmFaNiVU9>_iCET;s4 zTpzCkb05NgAo>4{^34C4w=>uS{fq8D5T7G=ZLVP*vOL0nAbCLsabS2sIN%G*rUsW+@q{|#8lO;&F#6LyaT%Y?|cBq=7sYRqNJ7#KjRWy{1EH|p-7JF1*P6(M z{!f*!2FahZWMGg;HmRRl{{yHOqQ8Opqx?_i|NapD+l_vwKVW>o^njTG1Ay!= zx<>dv4ta46c?V6`wUGj87}~-174m}o5}zy^!NnkNdBlaouONM!!F^MZc_8$gLp7}rXb_RQ(e?fc&W`zGhe2(0;xrTMf@(BNdjt?;S@npis$HAgb3b_pbou$w0B{_E4gdfE literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/forest/snow_dark_leaves.mw3 b/worlds/smw/data/palettes/level/forest/snow_dark_leaves.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..320256986039c9f78b33679b884188f295190230 GIT binary patch literal 514 zcmZQz*jxTgMZ?zHwLkKGY-Qf$Vg`oaJg@aZ^xgV;T?U|hrt6#7O+}fmbILarfy7r= z{jUenv*qfoLGnu_-b-xPEQ*VEn^O+q&nYjKiPDiVjh6!P85lfG&$pnd27WPc&A zeNBa&js9F?bXkc0nI%n0&2?T}Q@kFFbH UzJBP3+=)d-<<&sq@`<{l7znNiM zDpzD%4U%7LyHvs=`%_VA`A#r@sl=ReW&acA@5O(IL(JQ5^gI2z?n$#%K>N@E$o@iJ z`z^Ph&xzPWq@=*V% z$F7LgE<=`YVE!oolli|tM8Cg6luoTBL>}FRApbQhwrHgq#S9F;d0y*-=)3jxx*)QNV-e305miYRNfl9$`0A?v z^&on-T)j0&UQtw0>YKuLjAVvt(e9NH(dTTK@y67oxv``J?<#=Kua6c?O2DvtMn}W;( zx#yhalpu-ZImPihF!v$+2a^BKD9`+#c{_tW(7zD(Bm4*AbL6hgHLOFHNB9pUFUTMc n3@?a0x(h+_4;UXXJ%E~zO}?P+a-CWI%zAkTbXiRIqpJV_gm8JI literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/brawler_orange.mw3 b/worlds/smw/data/palettes/level/ghost_house/brawler_orange.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..750def41f99befdaf52b901569f3800f2ec30359 GIT binary patch literal 514 zcmZQzxLfbSvYJO>O$TQS4&S!$ipW+bYEaAN-YUBiyuQibi z{humd4U#`+$-p3yY*Ih9{s&MmM1KSGNBN)3|NSBQw;TOVXJArbe!$Ft0YL66 zx<>dv4ta46c?V6`wUGj87}~-174m}o5}zy^!NnkNdBlaouONM!!F^MZc_83(z-0OQ1XrT_o{ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/brawler_purple.mw3 b/worlds/smw/data/palettes/level/ghost_house/brawler_purple.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..60ac884bf7af4d8c9496772c41d0ca1076fc2161 GIT binary patch literal 514 zcmZQzxLe;YvqI&qiGbr5pQxAE&j0O&q+0J5);7vz`tWZ4KV26@XPE*yRZ>DvtMn}W;($)B^F z5+sp4r#M~*=01e~K=S_?<(dC8Z)dOv`WNDUg#SQ%j@-4mhIPpD2>*fP1sTMF;RTV$ h=Dr7v511Z6&BrERPa9WY3=GReUdw!txhgVCfPvvZkgvcj#vsp7&p4k6B7a=srHqcMpJoBjJcxX)iCpOa zRQYO<{5eYo28m>o`l3&od9vlE)MSG0^ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/crimson_house.mw3 b/worlds/smw/data/palettes/level/ghost_house/crimson_house.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..fc7369f7efb1dfae084baefc9480880b4d48d64e GIT binary patch literal 514 zcmZQzu-9W@;9%fmkY=!FsaKp_%)s!Q=e0hFzFS|f3nHB?IfBk*K}eAJ>Zd&PAbAFcVAk1uvhwx%_w9lFdIkmsW-$hNhI+>NOc41MJi3CH1tY}%ioxV- zP2@uVr^;7@Dnt$bLai z`m-uAa2rdSB%Ofrveg)~<4DOr$2igZR z|GnmQyJ+{>@ph^(_o4bPo++OBzxXZIdSRG6vi}$uR5PFFzO6%+NA=%EmLDu~tPpv0 j7lQ16&iIDuD>H6+WcOjyf6j7BkVNvF;&>f&SuRlkZV-R8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/halloween_pallet.mw3 b/worlds/smw/data/palettes/level/ghost_house/halloween_pallet.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f73c16e461017810a7ea3e7ead5d0b1767b856f7 GIT binary patch literal 514 zcmZQzxLf~#!GZAr;{vM$?}yQoiy0Vx^Sssv(Rb_XbwQ+)B}dS?EC>k_UtRUT9z@TU ztG5QpGcdeoFlG#6oXxnOk%8eqkgvcj#vsp7&p4k6BF}3g5+Ic!U-Q2PCSPkJ7y3U{ zz8WNd&XR#aBH5&VYW)wOUWon%=8y6}ng9Dk^lvx%ozB3d!2E!j0Rw>SFXXka0lG%` zKMr|u4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK f;{&D#Q1h|L7t~#@GpnCjFYkaZi|Kw;748ZE4*`2g literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/orange_lights.mw3 b/worlds/smw/data/palettes/level/ghost_house/orange_lights.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8eddf82fd34ef08dc9bb833c177a38de28a82c74 GIT binary patch literal 514 zcmZQzxLbcrk_UtRUT9z@TU ztG5QpFOzwru}L*T>9^H9e+GvC^$ZLO%wi1k4E2ojnLzSOB#tV)($F&vwk>ys$=905 zh5k>KuLjAVvt(e9NH(dTTK@y67oxv``J?<#=KnzR?gGWO8~sjaU{YXyz|4RFK=OsW z_BBA)2>-_+FRmf)py|3cQXmaOJJ`NLUXWknlVu~g800OFxN!Itq;E60ZwfLGtGAkMJLe&yl+}*RT#*9^pTbydZ-(FuWl0 l=q?1wKVW>o^Z;r;Hu-|O%XMb;GwbCY&}A{*kE-IB3IHUlf8YQB literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/original_aqua.mw3 b/worlds/smw/data/palettes/level/ghost_house/original_aqua.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..fec63947beaad9cc35209b449c6dee93b0001380 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM=jB5Q4;4SN#Xm zv*qfoLGla?Cpr8iFKbrV$p$kpfK@Y#F~~F2GtOs%$e$45l+DpRZ))TOldmSFXXka0lG%` zKMr|u4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK f;{&D#Q1h|L7t~#@GpnCjFYkaZi|Kw;760o2ea(J8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/original_blue.mw3 b/worlds/smw/data/palettes/level/ghost_house/original_blue.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b46979edd84e64bd8aca975625ffd7f32daf5f44 GIT binary patch literal 514 zcmZQzxLfaJ$q{reODH)sdq(l(Vg`oaJg@aZ^xgV;T@VS8hmaug)m8uNLG*07dTWq8 z1H(xUKgr9Q6?U@03=IE)dy8QeDonFn&uIm;-nC?ea@xLAb1-g7- literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/original_dark.mw3 b/worlds/smw/data/palettes/level/ghost_house/original_dark.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..eb6152098a04082fe556948c8bc4f3cd45f58114 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kh!!%yFayJXAYXx5j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 kg&_F{j1QO|K+VS{Ur=|s&a8fBy}SduET;QWRUB3U06}tj(EtDd literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house/original_white.mw3 b/worlds/smw/data/palettes/level/ghost_house/original_white.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..0d5c43f3b913762f13d854b75c804a34075a0c6f GIT binary patch literal 514 zcmZQzxLbeCPa-@jeop$m{Qu>Xiy0Vx^Sssv(Rb_XbwT9+dIkoC`iA-kKnN0FUG*PK z&z7sV2FWupoaFG6ysTMaCmYPb09MT`#vsp7&p4k6B7Z`JQ#MEQys42BOup7cF7$t@ zd^Je^oFxN;M6yZ!)cPMlvmp8#m_N$@Wd83D(ZAj3cRB-;0`miA1`GhQzmV6y2Iv~$ z|2X8uHRK&MUDrkmq+w_W+gHd7@=JWOYy=mByyX!W4!?r*Z3g#ELFR$nbIx)~kVNvF z;&>gH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2g&_F{ gj1QO|K+VS{Ur=|s&a8fBy}SduET;QWRs6390KAxge;o4S8uAXBu4^L&(lE4x?JML3`6WJCHiC;m-tveGhhIVZHiP@7AoD=(IcGT~ zNFsSoal8)9eF*=7yYIU{sYMiGKd4i3nGv1 oLXi9e#s^Ffpyp%BBgY@QEXX~|sz*#!?b_U)`^h_?%g34n0E-uTmjD0& literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house_exit/golden_house.mw3 b/worlds/smw/data/palettes/level/ghost_house_exit/golden_house.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..45f5c6701ab330a23be3fb11143af114833973ce GIT binary patch literal 514 zcmZQz_@5mhctx^8?vGl%`hEAw#S9F;d0y*-=)3jxx*#$^piyG6%o7<-O>b3@`0A?v z^&on-T)j0&o`K;cho9tS%?dl&Uz^Pi2Ml=PT3sI^QJ~lF!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}pe<81Z z4bU~h|8dBRYsfojx~`2BNW;($wy%&EV!Z literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house_exit/original.mw3 b/worlds/smw/data/palettes/level/ghost_house_exit/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..3856df591aadbe01ae684091675ada83490c3846 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kh!!%yFayJXAYXx5j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 og&_F{j1QO|K+VS{kBC2XS&(~_Rgaje+O@eo_mg)(mp`lm0QE6=Z2$lO literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house_exit/original_blue_door.mw3 b/worlds/smw/data/palettes/level/ghost_house_exit/original_blue_door.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b43818c578274c2bf468d43dfca69633519d56e7 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kh!!%yFayJXAYXx5j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 gg&_F{j1QO|K+VS{@1Xg>wjl_&eu(?A$sbk$0QUZP6aWAK literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ghost_house_exit/underwater.mw3 b/worlds/smw/data/palettes/level/ghost_house_exit/underwater.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a92bc7a1ba7400a9bb69e641009d1e3b1a58ed09 GIT binary patch literal 514 zcmZQzxLe;MaZDk@kjt$nOfP+MF$2SIp4a*y`fh!_E{IGJXp~qi^F)SI(_0lJzPjpv zJ&2wyS8olHPZnvF;WnJ;7#8&_iGksNJp+RRvlxRsLp|esCXhS>!wC^i*&NODrbbRM z`C1dX(Eq9O)gXCKO9qDTY58d(#XEs~i2erVkMcj6|NBGC-){6fUEMIvPt~0P1Axpg zx<>dvGln$S{`=%iC zK=S7-rvynP&nb@Afw>RiKal)?MtSD{%-b34f&PWKAK^a`pCfl|u3;UrJi>ngPyOnKz^Lze})M_KiVsj6L@+jBp82XuK=T>yexeJ20_ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/atardecer.mw3 b/worlds/smw/data/palettes/level/grass_clouds/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2b055f2fe658d20e7e05d399ad5d48e13e1e5bd1 GIT binary patch literal 514 zcmZQzn4j-y)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T?U~1|9S=nh5CkiBjyM7Ao0~z z|H1TZxq52`hW$X1BEf2-SB!u3irFE2Bj&3tajf!e|JWe>T-}ehrGAphbAf6>>W!G^ zD8$&x`Prv~%wKQH!0y8QeGh53~+Mxx?nCvT9NRs%c;PD?c2Y3;S`)d@|EcoT5I$dKf39gAvU~&cNBN)3|NSBI z7g)qN>sTT3=q?1=|C;d~(t(wFPrI@RE-WFYc GpA`T}A$TkR literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/crimson.mw3 b/worlds/smw/data/palettes/level/grass_clouds/crimson.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..aefeb5d356809ad9276aa7faf8a38877aeeee7d1 GIT binary patch literal 514 zcmZQzxLe;Xa7p5aOoghI)9#?j#S9F;d0y*-=)3jxx*!rF?_{Z|^g{+DzPjpvJ&2wy zS8olHXJGJ=3~}lfxDob4hJoQfkgusE#vsp7&p4k6BA>1M-nQ6JB6$u_7ev0+L@xAy zs(dv_{+uNPgG92BWQFSPAeeju^GErg%>Vr%`nMbXPIs5oRO-=VzyKio3wiBpJ_-I( zla$Ah2D?vOL*79{H_P#KD5gBfJ%zj=zr-iYMsP96TOM)Y@GD5)W^mv1KQqKV=PaiL zfm|Q21K}$$Bm4)_53>6|^L7S%pnpMp1!jc*KzxYZ$npsPf#k&)#DU=jkw#M* literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/electro.mw3 b/worlds/smw/data/palettes/level/grass_clouds/electro.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8a3971982011180cb6e3848aeab88bbe6f8d4eef GIT binary patch literal 514 zcmZQzxLdzW$J_S2XGqk~tlIL)#S9F;d0y*-=)3jxx*&3y&T)g|CNC`1^dQFYIzQ@f0>VnAE zn#hIzPnE9*$)B@iV30`Wvjn*SCf~sPQT`|Me}9Pn?MA=T7xSvit=3||03iDddF^XL z)vojXXU32QyH8w0-a*r~KNRFXbO3TsAuq@;@yW6gTnzG-M_f4k3evY3+&2Z82a-Q$ zIVDIUc}{V>4ur43jPM^w9%A=)2791?L3{;fg#SSNQyyz`4eOBQ5&i?oZ(yllk!OI& qV{;$G@3`fUs(9+I)eTmXut(Poa*wj=5mQyWHn-<~@($?oD`Wt47Jw=M literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/geo.mw3 b/worlds/smw/data/palettes/level/grass_clouds/geo.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f085c6f368ebc772b2b379c8624fc2242376fe16 GIT binary patch literal 514 zcmZQzxLe;Qa8l%xYLwY-t3Otgiy0Vx^Sssv(Rb_XbwMOV-pP_*>ys)-e0A0TdJsKZ zuHG6X&%gjP;g1yq!+#*3UrUTZo}r#`J`+SfTlc+fv7bcp9H1_Ue65LG=>JstYLNUn zO9lps(w7lPy= ne#b3;RK-(wt!}WAggv@$kb9I>kC>|3wYfd_lXpOuU!w>B%ZGhr literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/miku.mw3 b/worlds/smw/data/palettes/level/grass_clouds/miku.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..3b009fbc0f67ca53fbc5c3e22315015100936092 GIT binary patch literal 514 zcmZQzxLcpBdPLXNZbO(!@{Hoi#S9F;d0y*-=)3jxx*#%HHO6e0A0T zdJsKZuHG6X&%kg*ubCwJY63JZAQ}b_@!{i&7Kg$1P{_hXbzuo9}x{A^pug&oc7yx8{A+LQ+ zrJ9ZSe`XA6u=~U{8_2KP-t=7HqT zSxyN8xjtS8!dGBM_zxuipHZIqKl645d!T+_kxeb;$Av|AFKM8N`9% u1(8R0AxIwLcii$vRXlaq>IN%G*rV$Pxkp*`h^eYwo7;0gc?WcPO+x@;f>6-yfoXyV37-1}32WK=aW7$o@iJ`x>BY zg#Y7^7uS$?&~#lJDUgP)8)9D}FUT+P$+8h#4DyyoTsZs+(zhAhHwBpoa?d%-DM29D z$Lqk{hwvXr{y(EU^MB^;4E8|(qWcfT=g3`~YgmUYkMJKzUXVc?7+w&0bQgl;A22>( pdH^*aoBUA~Pu;b;!AcVL=&~U9D61YZRkdq#d+sOifG*z<3jmy9dvyQ+ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/original_green.mw3 b/worlds/smw/data/palettes/level/grass_clouds/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..e896e0dce38ba4cab348a36f6cddc6ffc5476668 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM)=$`9VEMe09}- zFg;tY-Wnv&!0?iB4U<0eOy+;V3=Ck^%wi1k4E2ojnIQ5EK+tbtGAkMJLe&yl+}*RT#*9^pTbydZ-(FuWl0=q?1w tKVW>o^Z;r;Hu<9}p1NywgOw!g(PcsIQC2--s%qEf_S{e20bPD_F#v7?dvyQ+ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/pizza.mw3 b/worlds/smw/data/palettes/level/grass_clouds/pizza.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2be2f2db72ee79f505eab6693a29eed838d54c15 GIT binary patch literal 514 zcmZQzxLf~C)LgDYYp2moD=Fv6#S9F;d0y*-=)3jxx*+m@Jp+S6eM7wh^MiVj`0A?v zV0yM(y){Ulfg##xzH_kXFUDvi1_rQdW-$hNhI+>NOc418x(|ZtMdOX^yZjKK0Gb8S-@yD){wMQ)e~A9=M!(Y;n1J>(GhhIa{e`^tH9*%0 z{|C$eVg$Kp9?&i+XNZ1r4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ik zka-~YoU@z~B#}I)I9>+_kxeb;$Av|AFKM z8N`9%1(8R0AxQoK;{&D#Q1h|LA64#(3vtgnT_d|~UO$6A1Kp1b*7^Ye Dku864 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/sakura.mw3 b/worlds/smw/data/palettes/level/grass_clouds/sakura.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9a0d8d868733599bfcd7f029a0b2d63d8cc8a4d3 GIT binary patch literal 514 zcmZQzxLa=+I5qM|tWDbdEcxQe#S9F;d0y*-=)3jxx*+m@Jp;poI)(ZNOb_Zo;;XCv zgX!6F_0}MH28NfSg4$C8rkl%_F))BtGl?8Sp<`>HIWPb zpDJGsl0Rq3z#x%qQa`o+2hc2t{s!ic@;{mX`$P0^H~O9afDvdvGXn+y*yYIU{sYMiGKd4i3nGv1 qLXbSf@3`fUs(9+I)eTmXut(Poa*wj=5mQyWHn-<~@($?oH>&}N;DQ|h literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/shukfr.mw3 b/worlds/smw/data/palettes/level/grass_clouds/shukfr.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..635d22c98abbfad7a277830f284640cf9738a4aa GIT binary patch literal 514 zcmZQz*i-Lm)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM)=$`9VEMe09}- zFg;tY-Wnv&!0=qb%hXWfoTsG(0|Qt!vlxRsLp|esCWt%(5csQ@E7&T)4$OTB|AFNHGs-jnXWq_W5A-j@{Rsbo_#C-wa}Dc|<&Ua(>aNudR+6ws*9~%yvg#32Rl7E~=YH}I=<>N)09|Kyod5s; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_clouds/snow_day.mw3 b/worlds/smw/data/palettes/level/grass_clouds/snow_day.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8a7c632110cfc650f68e642d6947cbd6ec99feb4 GIT binary patch literal 514 zcmZQz*jv6__l2ptU5DSRIKA}A#S9F;d0y*-=)3jxx(q=1OxHKDn~E}BPnvBi0*SA# z`d<&CXUo-FgXEV=yqDOnSrixTHm4lIKWSDh6Qv_#8ZQOnGcb6Xo^>q@`<{l7znNiM zDpzD%4U%7LyHvs=`%_VA`A#r@sl-V$W&acA@5O(IL(JQ5^gI2z?n$#%K>N@E$o@iJ z`dRJkW-ynEPYMyg^vTOtwgS_Pt7Y@II^lb+BP5(1P z^uKf65@(rTU4AE&f#H8W1A_vy7=t`RJ>z^Ph&xzPWq@=*V% z$F7LgE<=`YVE!oolli|tM8Cg6luoTBL>}FRApbQhwrHgqK zuVw(+f6kJDK_b~M?0%L#kPp${!2D7EC-Z-Qi2m(Hztb6*fc7(kOhN@9`wMyPYk;m1 z{*OamTtnVL({*j6KpLuE5D#QuAuq@;@yW6gTnzG-M_f4k3evY3+&2Z82a-Q$IVDIU zc}{V>4$OTB|AFNHGs-jnXWq_W5A-j@{Rsbo_#C-wa}Dc|Xzct-G|Qi$|y^}E)Siy0Vx^Sssv(Rb_Xbs2#2|LYkT9@Ht+`?5c%2Z^t) z`VXdO%hg+hQp@|D0tqk3{nM^l!y~fHpzo1-Wl=-;=(d4B>A#`kk)H=F5J8odE-Y)SC#} z*WA&nH~-I%Aq_T9TtnWW)0HQ#FAuxCA+L>|y>Wc9Yy=mByyX!W4!?r*Z3g#E|1(4E zKW8~5NCN2kcpU}?P-rMHi!sPE)HBX!g2>Ndjbr=I6vY{@1(UBekqiBwDqjteZ%x(D zGpt9JZ(#l?|C9N@KSbVurHUt36C#i9LXiJXvYutT!j4=1sEViVTHRnJ343(iAoG<~ TkC>|3wYfd_lXpOu|8E8WyYGc- literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/autumn.mw3 b/worlds/smw/data/palettes/level/grass_forest/autumn.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..89d75ed27fcf43223f5e976be54ab643331df81c GIT binary patch literal 514 zcmZQzxLbdkg`YK;bsFn0)_S4I#S9F;d0y*-=)3jxx*+m@Jp;poI)(b1j1THT;;XCv zgX!6F_0}MH28K?Cjk5fVl1%gY7#KjRZ!(H8$TQS4&S!$iKhS*;R4*EDWbX}=uQibi z{humd4U#`+$-p3y93NaCzCRfz-@yD){wMQ)e~AA1Li5uF7;iGx1I?Q4Lp z5&qANE(_5wt|9MmQMJtMrW2+-$i0QUAiu;X%SLc9$Xgz9;qWU+-)3;%6l5Mq{+#8M zAc^ET#ql~2z5+ACe<1n)jPlI?nYT081KkJWD=;Jc2jX+&uFW;9LzYMQ4&p$GLK@zquT z!SrmodTWq81H(xUKS_VB3cDHZ3=AOEx4|E>{)r-a(*?Ys}Yfa=r z|EJ1VgXGUyGB8Lao77LO{{hqs(ci%QQT`|Me}9Pn?MA=TSMll!Z4hI?03iDddF^X} zt`Yvvj3EtnpSXs+gQn}+NP#p=d60dDydb~CC(A}~G00mUapCYQNZ)2~-xOpXNdBDV zlpu-ZImPih5WWI4!haz7|BUj?|CzTl*aQ6w;wvyC{0HK5^ZFU|8R&jgFw-9ZG>v^% literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/brawler_atardecer.mw3 b/worlds/smw/data/palettes/level/grass_forest/brawler_atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..39b73646578db2923e2f86ec21e12edbd0b9fd02 GIT binary patch literal 514 zcmZQzxLcnlaZ18hQdn%e)Lya4#S9F;d0y*-=)3jxx**b-=Oj>my{wLqq!LJcb=CiR z5ItM2-Wnv&z>v?gS9U7fed%a+28RDYzK)O>gFHh$<9sHF{0R|G*&NODrbbRM`C1dX z(Eq9O)gbwEmJAFM$tLwv>wf_CLi9H`=%iCK=S7- zrvynP&nb@Af$$ZW5&i?o|7VnE{?ELf!5-*75MO~A;Xe?cBX@1CVI8tO!haxnK?ZSP zctPaRT?mqY!1#da0n~hK@<&xXb=T?!D@oX+%Yxjata`*$)vnF$xu3iPy8Qfb0O-GY A8vpuiy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe)5LJ#Ue;;XCv zgX!6F_0}MH28QD@x{5C(E4=!d7#KjRb%ewi7BefLLl?e0m%G9Ui%uL zYlQzZqsv0{i)+X`Xu7VA6iCCA2iaH13-U{RvTOtwgS_Pt7Y@II^lb+BO+n^?xjQ$0mPN#Zz~!Zm^PsJ-RH&y~?UbOjYgL+@AZ%JD|&#YXbmxS9`<& literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/crimson.mw3 b/worlds/smw/data/palettes/level/grass_forest/crimson.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..48465a548a19ed8921f65ccd00a1ad546ca80666 GIT binary patch literal 514 zcmZQzxLe;Xa7p5aOoi$UKk4|%#S9F;d0y*-=)3jxx*!rF?_{Z|^g{+DzPjpvJ&2wy zS8olHXJGJ=3~}lfxDob4hJoQfkgusE#vsp7&p4k6BA>1M-nQ6JB6$u_7ev0+L@xAy zs(dv_{+uNPgG91P{nYv&K)n$82Ii0QKbimgL-cPq`kn4Bsj1YX$AAGq_80Qn*L)KE zr6wtlAq{q)xQ4ughHjSQ=}=60kbQ-`Aiu;X%SLc9$Xgz9;qWU+-)3;%^glDiJ?AW^ z1c6*1uLI#LFeCg2(*K`Pp7}rXb_RQ(e?fc&W`zGhe2(0;xrTMf@(BNd}Q@k1h*xkFx3!Q&qb*x95KH4(RePBmm@$eYXGr literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/deep_forest.mw3 b/worlds/smw/data/palettes/level/grass_forest/deep_forest.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..91517aa15e2782b262e319d208a44a90add1daac GIT binary patch literal 514 zcmZQzxLbcjhE>&7vrY53CcDz)Vg`oaJg@aZ^xgV;T@d-do`K;(okG0=^MiVj`0A?v zV0yM(y){Ulfnlk{a}{?@4qXi`1_qF71!ge@d4_t%`AiV`Y~A;^#eNdWbAY-b^0g*% zq5o6mt3mSTEEyOil1=KT*8c$Ng~&HBf0X~p{NEpB zHNyXK$ct;pJ7~JDjTA`3&*IA`?nC$wB>$gLp7}rXb_RQ(f6@I1;&bG#%{8n;mPhyxBrnJy4h%1dJh}@(@(&mv qFg<{pk4^rlil^>c-C!jNdvsZldz4j=n5x>fxjpxjcR-idFaiMJx_T1; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/electro.mw3 b/worlds/smw/data/palettes/level/grass_forest/electro.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d462646a48d82dfff712776db834061c36bd00a9 GIT binary patch literal 514 zcmZQzxLdzW$J_S2XGqk~tlIL)#S9F;d0y*-=)3jxx*&3y&T)g|CNC`1^dQFYIzQ@f0>VnAE zn#hIzPnE9*$)B@iV30^Qsh?W^1E?1w-@yD){wMQ)e~A9=M!(Y+^Qz0O)?&Z_Ao~k> z?Q25SuJiq8#*hZPPh3OZLDRKA6y!d10J5);7vz`tWZ4KV26@XPE*yRZ>DvtMn}W;( z$)B^F5+sp4r#M~*!dGBM_zxuipHZIqKl645d!TyYIU{sYNx zV5wk{XMo6KbKe8T2TTv3=3|pTs^Y1;RySBl!X8}~rESI7VW D=5&Fs literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/geo.mw3 b/worlds/smw/data/palettes/level/grass_forest/geo.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f085c6f368ebc772b2b379c8624fc2242376fe16 GIT binary patch literal 514 zcmZQzxLe;Qa8l%xYLwY-t3Otgiy0Vx^Sssv(Rb_XbwMOV-pP_*>ys)-e0A0TdJsKZ zuHG6X&%gjP;g1yq!+#*3UrUTZo}r#`J`+SfTlc+fv7bcp9H1_Ue65LG=>JstYLNUn zO9lps(w7lPy= ne#b3;RK-(wt!}WAggv@$kb9I>kC>|3wYfd_lXpOuU!w>B%ZGhr literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/miku.mw3 b/worlds/smw/data/palettes/level/grass_forest/miku.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5eeaf6c571d8d219c44d366e30490526be5b9bb5 GIT binary patch literal 514 zcmZQzxLcpBdPLXNZbO(!@{Hoi#S9F;d0y*-=)3jxx*#%HHO6e0A0T zdJsKZuHG6X&%kg*ubCwJY63Hg@Q|o^K^+Mzum_N$@Wd83D(ZAj3ce;ww8?Vjr3>W}pe<81Z zO{JQR_$X|VgmHRK)KEP4DkCSl5h>?`C2`6WJCHiC;m-tveGhhIVZHiP@7AoD=- z=PaiLfm|Q21K}$$Bm4)F|IaAT{GWL{gFVo{Aie@K!haw>NAB8O!#ZSng#SSDf(+uo v@Pf#ryAUJ~@jGt$qbi=dYjuN_B<#_3gWRL6dc;)KuFdVapS%OQe04DZCOLoB literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/myon.mw3 b/worlds/smw/data/palettes/level/grass_forest/myon.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8420ad56ec5c393fc3bbc514acca2b2ff72024a9 GIT binary patch literal 514 zcmZQzxLe;UvrOlt&0C*O5h00_iy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe(g%n#~8;;XCv zgX!6F_0}MH1_m|FbfcvT`R1CI3=AOE3d~{*@(lHi^O+#>&vmkG*Sb!Ln*-Dhk*_t8 z3;mxeUk#GavSnb{npRY0Ts;G*7b4%l{89cV^M8Mc{_RG;(^uFkFrVXRzyKio-;3DS z{8as?_MaI;8tlHw7Pmb(bh88XvoPgB_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~a zbCy$rB$DS8$Lqk{hwvXre!3yZ?#Ti&d@y;0|3LB@u4{8A)*;Iy{0EZvR}p8BXN1V3 vyAUM5!uA2vIey&oM^!v^*Xjlg; zsq)nz`E!;G3=+vE^;7GA0L_BvZ(#l?|C9N@KScj_qu=QaOhEgY8886I{z6{+8lY>0 z|KpGs*N}J6bX^-MkcOchY+oTS$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE}K@!Px zisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*{81H8-L<;GN)q;s~#~`wQF;G?kDenF8@0p0E3x(@Bjb+ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/original_green.mw3 b/worlds/smw/data/palettes/level/grass_forest/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..607d80cc9b924227a3a2735a68740982286b2b89 GIT binary patch literal 514 zcmZQzxLcpVxrFfrqYl$Vrmswsiy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe(g%n#~8;;XCv zgX!6F_0}MH28Q*#m3j|UWwaHT85lsS6_~{sKuLjAVvt(e9NH(dTTK@y67b4%l{89cV^M8Mc{_RG;(;1k6_5;mF2O#?kdF^X} zt`Yu^Ltb1%-a*rKZKOaNx^9Slg}flY#3#!}a52bR9&zFDD@fmFaNiVU9>_iCET;s4 zTpzCkb05NgAo>4{^34C4w=>uS{fq8D5T7G=ZLVP*vOL0nAbCLsabS2sIN%G*rUsW+@q{|#8lO;&F#6LyaT%Y{Ad8%aeFlY literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/pizza.mw3 b/worlds/smw/data/palettes/level/grass_forest/pizza.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2be2f2db72ee79f505eab6693a29eed838d54c15 GIT binary patch literal 514 zcmZQzxLf~C)LgDYYp2moD=Fv6#S9F;d0y*-=)3jxx*+m@Jp+S6eM7wh^MiVj`0A?v zV0yM(y){Ulfg##xzH_kXFUDvi1_rQdW-$hNhI+>NOc418x(|ZtMdOX^yZjKK0Gb8S-@yD){wMQ)e~A9=M!(Y;n1J>(GhhIa{e`^tH9*%0 z{|C$eVg$Kp9?&i+XNZ1r4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ik zka-~YoU@z~B#}I)I9>+_kxeb;$Av|AFKM z8N`9%1(8R0AxQoK;{&D#Q1h|LA64#(3vtgnT_d|~UO$6A1Kp1b*7^Ye Dku864 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/sakura.mw3 b/worlds/smw/data/palettes/level/grass_forest/sakura.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5b7627fe74f241ea63801075421c77925867eaf9 GIT binary patch literal 514 zcmZQzxLa=+I5qM|tWDbdEcxQe#S9F;d0y*-=)3jxx*+m@Jp;poI)(ZNOb_Zo;;XCv zgX!6F_0}MH28NfSg4$C8rkl%_F))BtGl?8Sp<`>HIWPb zpDJGsl0Rq3z#x%qQa`o+2hc2t{s!ic@;{mX`$P0^H~O9afDvdvGXn+y*yYIU{sYMiGKd4i3nGv1 qLXbSf@3`fUs(9+I)eTmXut(Poa*wj=5mQyWHn-<~@($?o^P>TXOo77y literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_forest/snow_dark_leaves.mw3 b/worlds/smw/data/palettes/level/grass_forest/snow_dark_leaves.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..47bbd42b97a73fb83d8db5e553e0c5f3033cd178 GIT binary patch literal 514 zcmZQz*jxTgMZ?zHwLkKGY-Qf$Vg`oaJg@aZ^xgV;T?U|hrt6#7O+}fmC(Sk$fy7r= z{jUenv*qfoLGnu_-b-xPEQ*VEn^O+qpEN6$iPDiVjh6!P85lfG&$pnd27WPc&A zeNBa&js9F?bXkc0nI%n0&2?T}Q@kFFbH UzJBP3+=)d-<<&sq@`<{l7znNiM zDpzD%4U%7LyHvs=`%_VA`A#r@sl-V$W&acA@5O(IL(JQ5^gI2z?n$#%K>N@E$o@iJ z`z^Ph&xzPWq@=*V% z$F7LgE<=`YVE!oolli|tM8Cg6luoTBL>}FRApbQhwrHgqId~8@zquT z!SrmodTWq81B13>aMJqF;Od<~Hb`}FwHSjuLp|esCW!n4-3LMSqVY!d-Z1%E6S>g; zsq)nz`E!;G3=+ww%8>OpFn^T)$^73RqW@>n{B)Q+0~!F?U&w1;19Xk>e`YjEAQvPr zt|9MmQMJtMrW2+-$i0QUAiu;X%SLc9$Xgz9;qWU+-)3;%6l5Mq{+#8MAc^ET#ql~2 zz5+ACe<1n)jPlI?nYT081N{r)D=;Jc2jX+&uFW;9LzYMQ4Kvt1pwb3iaKYGRN5WW%fRhBqbdA5IS5Pq)iN83_A$>g~}wIKCI%ySfCY~}py z(?RC1H)UY>88_ESF1#MdhsX!9|KR?|zCRqo4^#V{&c$TJ{DqkT1Ax_Y+Sgbs*-Ovo z#*hY^ca6oCRoAsYQYa0(d?0VM(toS?WZ4KV26@XPE*yRZ>DvtMoBjvd2Qq)XDa7^m zsxbGV`cIDS9$UQd6%IQon0&2?TSolGj921Dx8LWnqL%eTA}NPnEz5SPf1kSQvR0&y8J#X0HbqwMF0Q* literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_hills/brawler_green.mw3 b/worlds/smw/data/palettes/level/grass_hills/brawler_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..612dcce98c7e3a8d3af8774ca257640fb30e7d09 GIT binary patch literal 514 zcmZQzxLcnf(5kUq_qA%jlYacVG|mo-J2z z4U%VI==U@ZyPg(Z{2R#r55yKW%Vf#h3xvUT6v`WhK}-$;hZ*P6(M{!f*! z2FahZWMGg;HmRRl{{ybSf%&8SPv-yr5dGVYey1@VcCuK~J7_&*ML zaSeF~P1m)N0%_YB?164V1|atq@`C&lpDY`}#UO8a#D&AJAbp#`eN&KmAo+8aQ-UOt z=M=~5K==yG2>*fP|1-)n|7V7n4dN>>Bm4*AbL6hgHLOFHNB9pUFUTMc3@=Q#f!qm_ vf57;F=>gPyWVgfQkE(d;uGI}zlCVdY1?g8-Jz}bA*XH)zPu>At{#rBuHX?(a literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_hills/crimson.mw3 b/worlds/smw/data/palettes/level/grass_hills/crimson.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9f56757809fe1820e702f06d645f8ce224f5e40e GIT binary patch literal 514 zcmZQzxLe;Xa7p5aOoi$UKk4|%#S9F;d0y*-=)3jxx*!rF?_~Ky=7$VOe0A0TdJsKZ zuHG6X&%od#8RFC}a3k!83o`lMQd6l%j{yUK>@VcCulXeS zOHEQ9LmKQpaSeF~4c#oq)1jF1Ao~h=L4Ju(mW|+IkheVI!r@ntzRlpi>3?R3d(K%- z2?Du3UI)TgU`F^4r2jvoJoA6%?F{xn|AP1m%n1L1_#C-wa}Dc|nnBd23UT5b8{Vg`oaJg@aZ^xgV;T@VSA2dM#xude!E529zw)mww) z85mya2!!SNRHR)=WMKFYpk9c41M^4upUnULA^Nu){Z3!Zt1h=%iva_G>@VcCuL)JV&i9`g zLmKQpaSeF~P1pWVko(X9$i6~ekYD1HWh1y4ehUx69nKaf1c?(GcrK>vdH3d{)qf%p)+k>wHo1Ice-sbG<3fXHKW-vdUF-*L+y jRq@nas~fB&VUMmGrESI7VWUfF&r literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_hills/geo.mw3 b/worlds/smw/data/palettes/level/grass_hills/geo.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ac56278a9fc417e061935f664ead05f4f6eef3f3 GIT binary patch literal 514 zcmZQzxLe;Qa8l%xYLwY-t3Otgiy0Vx^Sssv(Rb_XbwMOV-pR60_LC||e0A0TdJsKZ zuHG6X&%gjP;g1yq!+#*3UrUTZo}r#`J`+SfTlc+fv7bcp9H1_Ue65LG=>JstYLNUn zO9lps(w7lPy= ne#b3;RK-(wt!}WAggv@$kb9I>kC>|3wYfd_lXpOuU!w>BobP?s literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_hills/miku.mw3 b/worlds/smw/data/palettes/level/grass_hills/miku.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..49c78fadba25456b64c583e363d2952b1b134ede GIT binary patch literal 514 zcmZQzxLcpBn&#vYogXBUJfnDWF$2SIp4a*y`fh!_E{KH4J6U=}dqji8S6BV72hp?T z>a9WY3=Bs^jwz^E@dHg^VE7N@zwr`dkY}i8oX-T2&(?i!TkI#1JO`)?B42AF7y3U{ zz8WNd&XR#aBH5&VYW)wOUWj}H^GErg%>Vr%`nMbXPFGQSSFXXkasZ_HO z|Idse4R)WnhP;EDC6C|6BusgbeTBRrzr-iYMsP96TOM)Y@GD5)W^msWWFAQVoaK}t zkn7`hAbbU8g#SSD{~6_(|1)oAum}1V#8+TO_z%SA$X%OjScfc+@E=HCkU<<6UJ!Y7 r7lPy=e#b3;RK-(wt!}WAggv@$kb9I>kC>|3wYfd_lXpOuuPz1v|F?d% literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_hills/mogumogu.mw3 b/worlds/smw/data/palettes/level/grass_hills/mogumogu.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..88af74ceafcba91876e6e5fa21300926b8c484cb GIT binary patch literal 514 zcmZQzxLfb*xiQhCsIu%SoK;TZ7~o z7>>zk8gkqIEDFkGVE7N@D=>>O$TQS4&S!$iKUeX#?GG$XvI6Rc$k&?4h5k>KuLj9; znldnyqzR>6D%%0n3z2VN{wV*G`M*Cz|8}F_>CY`(!&1B%FaXH@_b&D|5rK2v;)F4z z!S1_g6J`5c$H(^y(0%9tWM3gK$S?89vJqSi@|H(jIQ$CIw;9|w{m%?>56Jx>*GoCV z+=uWVNdJ2mDW^E$i#pLpFnNUkK=RKGa$`T{BFiKE2a-RfBkX9U36aOK|oLtPn@SEqgK8U_sU$4u6MSgYF|9X)8Y`J=CkSYd-lM2O_$)*x+ z%~lKy|ABl3W-$hNhI+>NOc42O-S@V|eiF%ZfVv>^wI*_*|5N3wLGtG;85kszP3ouC z{{ZTR$Tu*5l>f>6-yfoXyV39T2aF2L511J+0LcDAUi%uLYlQ#fkQdjGchGcQ8!3>6 zp&e{rAuq@;@yW6gTnzG-M_f4k3evY3+&2Z82XfCj%PB!1*T?I?+=uWVNd7;gJoA6% z?F{xn|DyX3#OKIen`>ByERXOXNM4XZ92i~@d2|V?QRFn^T)$^73RqJO*5?{o$x1?C4p^U(pw{z6{+8lY>0|KpGs z*N}J6bX^-MkcO@sVqYOI$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE}K_J)1>%iQH z@E=J2KchVJf9CBB_CWul`wztD$X%OjScfc+@E=HCkU<<6UJ!Y77lPy;Fg{><05u<* k{81H8-L<;GN)q;s~#~`wQF;G?kDenF5eIf0B9I`(*OVf literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_hills/sakura.mw3 b/worlds/smw/data/palettes/level/grass_hills/sakura.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..6c65dc4ff010fd8050c2c46162422816227149e3 GIT binary patch literal 514 zcmZQzxLa=+I5qM|tWDbdEcxQe#S9F;d0y*-=)3jxx*!rF4LkgXr0E_0}MH z28NfSg4$C8rkl%_F);iG@*gmXF~~F2GtOs%$fq0d#coP9&AM3xldm^GErg%>Vr%`nMbXPJh7ofaw7<0|o%uU&w1;19Xk>e;o4S z8uAXBu4^L&(lE4x?JML3`6WJCHiC;m-tveGhhIVZHiP@7AoD=(IcGT~NFsSoal8(M zufUA(A4vW`qdfC}=IspjK>vdH3d{)qf%qJ`YjX|jkmV8n1IY_Ahy%k5B9HDukUYfi lxaE(kc*5b`%OY)j>eY^y=?Yi*ZG zSY&@HDlOj$<}Z~vX{PLd!u-AX?{J8D+l_vwKi55JwhCw;Isn;U$ZKCyA!nmM*BD(E zqJO4|jH9OO+DL&k?D87~Z%NH_j!%}2;9`)sJmSLPSCGEV;J)d9W{CcGu3O?P^Q+76 zgfcMvuV-LTU>0MLXQ*eK&jgW=AjnAa9RB1Vg`oaJg@aZ^xgV;T?V*3L={MUb=CiR5ItM2-Wnv& zz+l8Ui_uNcUg)JH1H=D%28NfCVhr*O^^EhGAo5NPewpkk_@S>U>)j z2w#C2;Xjc49mYFM)y&%%WPtt!@fDa6{sZxka={cOgiA s0b>JG0@QqL@<&xXb=T?!D@oX+%Yxjata`*$)vnF$xu3iPx_pHd0O>G!F8}}l literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_hills/toothpaste.mw3 b/worlds/smw/data/palettes/level/grass_hills/toothpaste.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..7c108ae348aef9e50761a7f0a7d74da10b299434 GIT binary patch literal 514 zcmZQzxLf}|Bq;T2Wd&P zAbAD`1NOb~f5&9$ZiPS>I&l40_-CUT+wQ{}56 z<})w^r54wO)kf99`=*fndx!B3Q#JE81{t7#L3{;fg#SSNV>#KG=W3DV5&i?o^D~GtNHaj>(On3VU%=SF plmIm!oBUA~Pu;b;!AcVL=&~U9D61YZRkdq#d+sOifG!^w2mod`f?5Co literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/brawler_lifeless.mw3 b/worlds/smw/data/palettes/level/grass_mountains/brawler_lifeless.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b9979692f09e9982bd6cf352a4902c41a3a9ec31 GIT binary patch literal 514 zcmZQzh$wz3!>#D6nx{EIca!PlVg`oaJg@aZ^xgV;T@d-do`FH3zM+1L)q{GF`0A?v zV0yM(y){Ulf#IU1u+urs?O{en3=AOETdc$wKuVw(+uN%j}Ffr{@l}U94P%lKjf%&8SPv-yr5dGVYey0~2ZL#|1#()7p_80Qn z*8p83{GS;^8tguC4S5Gm*R_!XX_)dL`wDqMeu+<(jo@OCw>;v);a8Bp&EUQ%$UKnz zIm;*fP|1-)n|7YILU=Q>!h_Aqm@E?fJk-Ik6unt)s;XjbPAcHtC zydd)EE(FOxV0^&z0BSxq`J*bHx@&cVl_c!ZWkK#yRy|^>YS-rW+)v&CT|Uzi0Bt&d AMF0Q* literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/classic_sm.mw3 b/worlds/smw/data/palettes/level/grass_mountains/classic_sm.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..0e77cc017034d032ad060dd9c0430a989b4397d8 GIT binary patch literal 514 zcmZQzxLcngx=NB)$zSb~o~ZrgVg`oaJg@aZ^xgV;T@d-do`FH3zM-C1=|Me6e09}- zFg;tY-Wnv&!0=kcNAi;1B~?*-1_qF7UL`RGd4_t%`AiV`Y~A;^#eNdWbAY-b^0g*% zq5o6ms~H$>)t|FuV30^Qsb>n)E{DlCFn^T)$^73RqJO*5?{tV88886I{z6{+8lY>0 z|1)DqgWV^tA@88+x;9cE4O1TEoi+ z$o26$5WWI4!haz7|BUj?|CzTl*aQ6w;wvyC{0HK51M-nQ6JB6$u_7ev0+L@xAy zs(dv_{+uNPgG91P{nYv&K)n$82Ii0QKbimgL-cPq`kn4Bsj1YX$AAGq_80Qn*L)KE zr6wtlAq{q)xQ4ughHjSQ=}=60kbQ-`Aiu;X%SLc9$Xgz9;qWU+-)3;%^glDiJ?AW^ z1c6*1uLI#LFeCg2(hsuxKl645d!TIN%G*rV$Pxkp*`h^eYwo7;0gc?Wd)7ZLz$XMKkN literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/dry_hills.mw3 b/worlds/smw/data/palettes/level/grass_mountains/dry_hills.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..61bdc7b82e90d8eb3d7633cc8ac403b190bd9462 GIT binary patch literal 514 zcmZQzxLYr*RIatt>X%zg_`LMV#S9F;d0y*-=)3jxx*+m@Jp+S6eM9{(w+Hng@zquT z!SrmodTWq81A{N;CEj|m3ZeT(3=AOEzud$aKuLjAVvt(e9NH(dTTK@y67b4%l{89cV^M8Mc{_RG;(;+TozyKio3wiBpfUXh# z&x|1rcAvP0yo09e+DL&kOnH!fg}flY#3#!}a52bR9&zFDD@fmFaNiVU9!UP2<&+?h z>*IAGdnnBd23UT5b8{Vg`oaJg@aZ^xgV;T@VSASC`{-1c|S%`d<&CXUo-F zgX9?)Ug-#g<@r>kT}fnM_z&c(%ZV|_Gt@KAXM)I|Q0O%^^7^^Y~c?V6`{!ozn&;iK4LSB$x;*(_~xESOukGOF76{K%7xNizF4*fj5WA7(5&i?oZ(yllk!OI&V{_jFMv&ie l%O6$o)Lp9^tR!KNt{dbYWz{35s&;K|&;8^b(B)Ui0033aek1?@ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/geo.mw3 b/worlds/smw/data/palettes/level/grass_mountains/geo.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f085c6f368ebc772b2b379c8624fc2242376fe16 GIT binary patch literal 514 zcmZQzxLe;Qa8l%xYLwY-t3Otgiy0Vx^Sssv(Rb_XbwMOV-pP_*>ys)-e0A0TdJsKZ zuHG6X&%gjP;g1yq!+#*3UrUTZo}r#`J`+SfTlc+fv7bcp9H1_Ue65LG=>JstYLNUn zO9lps(w7lPy= ne#b3;RK-(wt!}WAggv@$kb9I>kC>|3wYfd_lXpOuU!w>B%ZGhr literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/late_sandish.mw3 b/worlds/smw/data/palettes/level/grass_mountains/late_sandish.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d94156adda5ccdb3b4b02e310e0ed7b0848dc653 GIT binary patch literal 514 zcmZQzxLdzT^Q*48QI**pD>?hg#S9F;d0y*-=)3jxx*+m@Jp+S6eM7wg^MiVj`0A?v zV0yM(y){Ulfg#XxyXkk!NT(Qo1_qF717d4_t%`Ai`B7c#o0lN`S|?g*>NhsoEP z$c6q-m9GZLYZ@{z6vxdriu2x|43lqQ{wV*G`9IJ+kom&uztb6*447v#GhhIae1M>R zO_Wl-@PBg*X|R5A4S9#>787F2i!kLu?k(g6`6WJCHiC;m-tveGhhIVZHiP@7|AF>_ z6e0A0T zdJsKZuHG6X&%kg*ubCwJY63Hg@Q|o^K^+Mzum_N$@Wd83D(ZAj3ce;ww8?Vjr3>W}pe<81Z zO{JQR_$X|VgmHRK)KEP4DkCSl5h>?`C2`6WJCHiC;m-tveGhhIVZHiP@7AoD=- z=PaiLfm|Q21K}$$Bm4)F|IaAT{GWL{gFVo{Aie@K!haw>NAB8O!#ZSng#SSDf(+uo v@Pf#ryAUJ~@jGt$qbi=dYjuN_B<#_3gWRL6dc;)KuFdVapS%OQe04DZCOLoB literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/original_aqua.mw3 b/worlds/smw/data/palettes/level/grass_mountains/original_aqua.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..fda1d358f7f2b177b30d191173455e744cd288e8 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM)=$`9VEMe09}- zFg;tY-Wnv&z_6aTQtyGPjJ5(Z0|Qt!vlxRsLp|esCWw5t?t9x}KZ)cyK;01eS`)d@ z|EcoTAo+8a3=9&f>6-yfoXyV37-1}32WK=aW7$o@iJ`x>BY zg#Y7^7uS$?&~#lJDUgP)8)9D}FUT+P$+8h#4DyyoTsZs+(zhAhHwBpoa?d%-DM29D z$Lqk{hwvXr{y(EU^MB^;4E8|(qWcfT=g3`~YgmUYkMJKzUXVc?7+w&0bQgl;A22>( pdH^*aoBUA~Pu;b;!AcVL=&~U9D61YZRkdq#d+sOifG+<#9{`a&UtRUT9z@TU ztG5QpGcc^@t<-ye3nE`@A{Y8U zRlXV|f6kJDK_c0tero*>pk9c41M^4upUnULA^Nu){Z40KQeb`nG#?#+>@VcCuK~J7 z_&*MLaSeF~P1m)N0%_>FA@&vWg8UMnEE~baAa8lZg~P8PeVf63Q;>Ne_nfnw5(IL6 zybjEL2>*fP|1-)n|7YILU=Q>!y8l3Yj@-4mhIPpD2>*fP1sTMF;RTULcOgjr0pkOv o2T=2|$sbkm)Lp9^tR!KNE(>yxvg#32Rl7E~=YH}I=<<_`0RyRe#Q*>R literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/original_green.mw3 b/worlds/smw/data/palettes/level/grass_mountains/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..607d80cc9b924227a3a2735a68740982286b2b89 GIT binary patch literal 514 zcmZQzxLcpVxrFfrqYl$Vrmswsiy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe(g%n#~8;;XCv zgX!6F_0}MH28Q*#m3j|UWwaHT85lsS6_~{sKuLjAVvt(e9NH(dTTK@y67b4%l{89cV^M8Mc{_RG;(;1k6_5;mF2O#?kdF^X} zt`Yu^Ltb1%-a*rKZKOaNx^9Slg}flY#3#!}a52bR9&zFDD@fmFaNiVU9>_iCET;s4 zTpzCkb05NgAo>4{^34C4w=>uS{fq8D5T7G=ZLVP*vOL0nAbCLsabS2sIN%G*rUsW+@q{|#8lO;&F#6LyaT%Y{Ad8%aeFlY literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/original_white.mw3 b/worlds/smw/data/palettes/level/grass_mountains/original_white.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..7ecd4e3ccb0b0228f86f96f78655c486e0fa97ee GIT binary patch literal 514 zcmZQzxLbeCPa-@jeop$m{Qu>Xiy0Vx^Sssv(Rb_XbwT9+dIkoC`i6Q1<_Gm4@zquT z!SrmodTWq81H*dWO1%fFGTI8v3=Ck^%wi1k4E2ojnIQ7ny6?Q4Lp z5&n-uUR*=oLDO|@q(BS9UGuQ+Di|#)VpCfl|u3;UrJi>ngPyZ1P7{JayOV1}jO}qsxNaqpW(wRMoD{?YW=41G;=eECAaqeH;J) literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/recksfr.mw3 b/worlds/smw/data/palettes/level/grass_mountains/recksfr.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b5da161c2cb5e88c2ff4bc3e1f18f50f7615122a GIT binary patch literal 514 zcmZQz*i#=EH#zA_mT10taYgy$Vg`oaJg@aZ^xgV;T@d-do`FH3zM)=$`9VEMe09}- zFg;tY-Wnv&!0=qb%k-GZIZyXU1_rQdW-$hNhI+>NOb~enAUI~?9?6vjldm2Sq0_~+#r)s0m%MBUi%uL zYlQzZqsoDJAbD{Oc?V6`wUGj8nDQX|3VA_(iBFb|;9`)sJmSLPSCGEV;JzuyJdpf3 z%PBz;$#aV1bzts8_zxuipHZIqKl645d!TByERXOXNM4XZ92i~@ wd2|KuV!F~FF$9=z#x%qQg55|FApZ)!2D7EC-Z-Qh<>}`-|4qq_Jr1^F<=0Y{e`^t zH9*%0|7XUK2D?vOL*7Btb#0^o7X2Xi6!L=n5}zy^!NnkNdBlaouONM!!F^MZc_8_7 zmQ#X2u8-G&@D-R5{sYPXXOw6D&%B+%9_U{XUx69nKMp34^=< literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_mountains/snow_day.mw3 b/worlds/smw/data/palettes/level/grass_mountains/snow_day.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..3b182c3b7b217548e4d21cbb60891d514920f601 GIT binary patch literal 514 zcmZQz*jpazG~MrK++;&ByIWC{iy0Vx^Sssv(Rb_Xbs2#2PL>=&=dzqEPnw;}0*SA# z`d<&CXUo-FgXEV=yqDOnSrixTHm4lIKWSDh6Qv_#8ZQOnGcb6Xo^>q@`<{l7znNiM zDpzD%4U%7LyHvs=`%_VA`A#r@sl-V$W&acA@5O(IL(JQ5^gI2z?n$#%K>N@E$o@iJ z`z^Ph&xzPWq@=*V% z$F7LgE<=`YVE!oolli|tM8Cg6l+IjRh&;LrLH=u2Y|%)$%WU0`5^fq_8;8;*!PD+)NePs8_vZf%=}H50Rw={GvbVA zwpOy2p3jXT4YuzZi!H0JYk#Cr8g}_W-e{%&R`JQQ5nK%NmPcGT{0h>y8QeGh&kV7D zy(z@?_NoxR5t{$x*zU2#3t!=|lY+_Dn#hIzPnEBR@cA% H@3R5`5-D~h literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/crimson.mw3 b/worlds/smw/data/palettes/level/grass_rocks/crimson.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9527a0a24af2fb9f4c4bd1a016fcf9439a3070fd GIT binary patch literal 514 zcmZQzxLe;Xa7p5aOoghI)9#?j#S9F;d0y*-=)3jxx*!rF?_{Z|^g{+DzPjpvJ&2wy zS8olHXJGJ=3~}lfxDob4hJoQfkgusE#vsp7&p4k6BA>1M-nQ6JB6$u_7ev0+L@xAy zs(dv_{+uNPgG91P{nYv&K)n$82Ii0QKbimgL-cPq`kn4Bsj1YX$AAGq_80Qn*L)KE zr6wtlAq{q)xQ4ughHjSQ=}=60kbQ-`Aiu;X%SLc9$Xgz9;qWU+-)3;%^glDiJ?AW^ z1c6*1uLI#LFeCg2(hsuxKl645d!TIN%G*rV$Pxkp*`h^eYwo7;0gc?Wd)7ZLz$XMKkN literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/dark.mw3 b/worlds/smw/data/palettes/level/grass_rocks/dark.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..1f6aa06a19e97ac792f2b8d18b06d5ffacd3550b GIT binary patch literal 514 zcmZQzxLcnjvQ&gavByxrX-d%KVg`oaJg@aZ^xgV;T@d-do`FH3zM-B)?Lj?Ae09}- zFg;tY-Wnv&z`&;JVYtfnji-MQ0|Q7ki<%gNJVQO>d?twe1KkHf^`h}c_TDi0S`)d@ z|EcoTAo+8a3=9&f>6-yfoXyV39TWJwmar)CTo0Azn5uYC>B zHNyXyF{Hun6W5S;&~#lJDUgOK53;Y27vz`tWZ4KV26@XPE*yRZ>DvtMn}W;($)B^F z5+sp4r#M~*=01e~K=S_?<(dC8Z)dOv`WNDUg#SQ%j@-4mhIPpD2>*fP1sTMF;RTUL vcOgjr0pkOv2T=2|$sbkm)Lp9^tR!KNE(>wbI$a~XZC*cvJ_Fs43aaw~WKMi- literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/electro.mw3 b/worlds/smw/data/palettes/level/grass_rocks/electro.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..684b84a6af3fb391d7cb226b51fbb370cf3bdee3 GIT binary patch literal 514 zcmZQzxLdzW=a>nnBd23UT5b8{Vg`oaJg@aZ^xgV;T@VSASC`{-1c|S%`d<&CXUo-F zgX9?)Ug-#g<@r>kT}fnM_z&c(%ZV|_Gt@KAXM)I|Q0O%^^7^^Y~c?V6`{!ozn&;iK4LSB$x;*(_~xESOukGOF76{K%7xNizF4*fj5WA7(5&i?oZ(yllk!OI&V{_jFMv&ie l%O6$o)Lp9^tR!KNt{dbYWz{35s&;K|&;8^b(B)Ui0033aek1?@ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/geo.mw3 b/worlds/smw/data/palettes/level/grass_rocks/geo.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f085c6f368ebc772b2b379c8624fc2242376fe16 GIT binary patch literal 514 zcmZQzxLe;Qa8l%xYLwY-t3Otgiy0Vx^Sssv(Rb_XbwMOV-pP_*>ys)-e0A0TdJsKZ zuHG6X&%gjP;g1yq!+#*3UrUTZo}r#`J`+SfTlc+fv7bcp9H1_Ue65LG=>JstYLNUn zO9lps(w7lPy= ne#b3;RK-(wt!}WAggv@$kb9I>kC>|3wYfd_lXpOuU!w>B%ZGhr literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/ice.mw3 b/worlds/smw/data/palettes/level/grass_rocks/ice.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..349ce8e099d27437b0de972b33bb157d0f72b7fb GIT binary patch literal 514 zcmZQzxLeO;+3w06)E_639#K5Gn1SIp&ue`UeYd_|7eqQ)as-{ra0 z|1)DqgY6gBkay5@T^lKohA9tnZy_(pFY(E;5nK%NmPcGT{0h>y8QeDonFo?TXE`NE zB6&`6ybgq~z>M%8*w2je%>S9UGuQ+D3*sv6e0A0T zdJsKZuHG6X&%kg*ubCwJY63Hg@Q|o^K^+Mzum_N$@Wd83D(ZAj3ce;ww8?Vjr3>W}pe<81Z zO{JQR_$X|VgmHRK)KEP4DkCSl5h>?`C2`6WJCHiC;m-tveGhhIVZHiP@7AoD=- z=PaiLfm|Q21K}$$Bm4)F|IaAT{GWL{gFVo{Aie@K!haw>NAB8O!#ZSng#SSDf(+uo v@Pf#ryAUJ~@jGt$qbi=dYjuN_B<#_3gWRL6dc;)KuFdVapS%OQe04DZCOLoB literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/napolitano.mw3 b/worlds/smw/data/palettes/level/grass_rocks/napolitano.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..bc6ebc6621d9ff7114dc9d5482716febe0ac4c6d GIT binary patch literal 514 zcmZQzxLf}#);euY*4;eW^0?~B#S9F;d0y*-=)3jxx*+m@Jp+S6eM7wh^MiVj`0A?v zV0yM(y){Ulfx%o()|y-Je)<NOc418x(|ZtMdOX^yy8QeDonFsREIm;i literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/original_aqua.mw3 b/worlds/smw/data/palettes/level/grass_rocks/original_aqua.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..fda1d358f7f2b177b30d191173455e744cd288e8 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM)=$`9VEMe09}- zFg;tY-Wnv&z_6aTQtyGPjJ5(Z0|Qt!vlxRsLp|esCWw5t?t9x}KZ)cyK;01eS`)d@ z|EcoTAo+8a3=9&f>6-yfoXyV37-1}32WK=aW7$o@iJ`x>BY zg#Y7^7uS$?&~#lJDUgP)8)9D}FUT+P$+8h#4DyyoTsZs+(zhAhHwBpoa?d%-DM29D z$Lqk{hwvXr{y(EU^MB^;4E8|(qWcfT=g3`~YgmUYkMJKzUXVc?7+w&0bQgl;A22>( pdH^*aoBUA~Pu;b;!AcVL=&~U9D61YZRkdq#d+sOifG+<#9{`FayJXAYXx5j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 og&_F{j1QO|K+VS{kBC2XS&(~_Rgaje+O@eo_mg)(mv4v#0NH?eQ~&?~ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/original_ice.mw3 b/worlds/smw/data/palettes/level/grass_rocks/original_ice.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..041aa9edb1c368f843d5c8ba4954bff09d3e8520 GIT binary patch literal 514 zcmZQzxLfaJ$q{reODH)sdq(l(Vg`oaJg@aZ^xgV;T@VS8cd}GqK9>a&UtRUT9z@TU ztG5QpzYpD-_r1)rzPf&IJp;pkAXZ=&V~}U4XPnOjl4oEzspIE(IkX~A7A{|FA{Y8U zRlXV|f6kJDK_c0tero*>xO@ZiNBN)3|NSBQw;TOVXJArbe!$Ft0YK&#^4iw`T_gM- zhrGCkyo09e+DL&k4DDd|7V?7p5}zy^!NnkNdBlaouONM!!F^MZc_9Csvz!tnkvyk3 zUI){EAo>4{^34C4w=>uS{R{Cw!haw>NAB8O!#ZSng#SSDf(+uo@Pf#ryAUM*fbjv- o1E~4faNudR+6wsmj$^;S@npis$HAgb3b_pbou}F0RF6hOaK4? literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/original_volcanic.mw3 b/worlds/smw/data/palettes/level/grass_rocks/original_volcanic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d7a19dc3bcb262498519c6c0fbdac81d3179d996 GIT binary patch literal 514 zcmZQzxLdzi!NYKk?Kw*$yKP>Riy0Vx^Sssv(Rb_XbwT7J1y$V)(=Jm5<{+^A>Zwf_CLi9HDDF2iBzduC(cB9|v3`{`#f%?$_$o@iJ`x>BY zg#Y7^7uS$?&~#lJDUgP)8)9D}FUT+P$+8h#4DyyoTsZs+(zhAhHwBpoa?d%-DM1p+ zbBg12VD3Zs4(dH^*an>=#(W6CS59x+w5Yjb<J^wD0L2*? z66)>6LG*ta1!j;u1H*dWO1%fFGTIwf^vg6MBx{wV*G`M*Cz|8}F_=?qLj`+?@81CafNy!JJ- zc-C!jNdvsZldz4j=n5x>fxjpxjcR-h)Tnqpd40<5| literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/original_white_2.mw3 b/worlds/smw/data/palettes/level/grass_rocks/original_white_2.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..596e5b93b404d142b5333e64a9dd4ffc58fc6f69 GIT binary patch literal 514 zcmZQzxLbeCPa-@jeop$m{Qu>Xiy0Vx^Sssv(Rb_XbwT9+dIkoC`i6Q1<_Gm4@zquT z!SrmodTWq81H(%OUZp-(W2x;%3=Ck^%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@fp{t*4!jee&yFahmnX21X-`wMyPYk;m1 z{*OamTtnVL({*j6KpKX2uziKRAiu;X%SLc9$Xgz9;qWU+-)3;%6l5OAJ?AW^1W6>% zDUR2Hxewt#ko8_2KP<>1MLIJpR=42 zB#}I)I9>+_kxeb;$Av|AFKM8N`9%1(8R0 vAxQoK;{&D#Q1h|LA64#(3vtgnT_d|~UO$6A1Kp1bHq-$CHNAc1 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/sakura.mw3 b/worlds/smw/data/palettes/level/grass_rocks/sakura.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a339b4a62d15d843a22d44d485684349ad7335d3 GIT binary patch literal 514 zcmZQzxLfZbcv@6XYNMQq@^tmd#S9F;d0y*-=)3jxx*+m@Jp;poI)(ZNOb_Zo;;XCv zgX!6F_0}MH28NfSg4$C8rkl%_F))BtGl?8Sp<`>HIWPb zpDJGsl0Rq3z#x%qQa`o+2hc2t{s!ic@;{mX`$P0^H~O9afDvdvGXn+y*yYIU{sYMiGKd4i3nGv1 qLXbSf@3`fUs(9+I)eTmXut(Poa*wj=5mQyWHn-<~@($?oGwK0aD}X}) literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/grass_rocks/thanks_doc.mw3 b/worlds/smw/data/palettes/level/grass_rocks/thanks_doc.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2359c277f3764723464c94c2fa233fae98c13c80 GIT binary patch literal 514 zcmZQzxLeOF86Z1JQCzRmNhW%7F$2SIp4a*y`fh!_E{IGJXp~qi^F)S4&07^DzPjpv zJ&2wyS8olHXJGI*-R7Gewzq17Is?OhAfH7|j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zT8_2KP-t=7HqT zSxyO(NS;$1uLI#LFeCg2lK;;r&-|ZxJA*yYzaYK>Gs1r$K1c4_T*EqKd4&H!@`4QF z!0>{|qq`6!|A6rU(*vma*yN9@cP BdIkUh literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/logs/brawler.mw3 b/worlds/smw/data/palettes/level/logs/brawler.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ed25ef97949818757bcf172f6ae4d637a980bdc9 GIT binary patch literal 514 zcmZQzxLbc(>zxt5wXt_;_>B0;#S9F;d0y*-=)3jxx*+m@Jp+S6eM9{NAOwl8uKEwA zXUo-FgX9?)PICB3Ue>IzlMQBI0IOyeV~}U4XPnOjkv}2ADVw8t-qgqmCSPkJ7y3U{ zz8WNd&XR#aBH5&VYW)wOSrGjV%pc`{GXM97=-+PiJDq_^f%yS50|o%uU&w1;19Xk> ze;o4S8uAXBu4^L&(lE4x?JML3`6WJCHiC;m-tveGhhIVZHiP@7AoD=(IcGT~NFsSo zal8)9eF*=7yYIU{sYMiGKd4i3nGv1LXi9e r#s^Ffpyp$fKdR!XyH+_oEET;rXB+n_1 z*MYeY;Xjc4e@1!c|IFJN?1BD;_#fdv5T7G=ZLVP*vOL0nAbCLsabS2sIN%G*rUsW+@q{|#8lO;&F#6LyaT%Y{bT^TW_(fr literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/logs/mahogany.mw3 b/worlds/smw/data/palettes/level/logs/mahogany.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..80d5c2c1adfcd47b15b97c71aadf8b23c1441d51 GIT binary patch literal 514 zcmZQzxL@pP)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM=jB5Q4;4SN#Xm zv*qfoLGla?d=etM#v&4$Hf{_IVAafG4Dt;1jPscw@(*+$1l5bi8`*oq!(`0-of)JS6@ct7x<>dv z4ta46c?V6`wUGj8sCq#>kb4VxL4Ju(mW|+IkheVI!r@ntzRlpiDabsK{5i`hK@!Px zisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*{81H8-L<;GN)q;s~#~`wQF;G?kDenF8{wC03pbEu>b%7 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/logs/not_quite_dawnbreak.mw3 b/worlds/smw/data/palettes/level/logs/not_quite_dawnbreak.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ecce214b80fb83396478275dfdfd2b8d58b081ac GIT binary patch literal 514 zcmZQzm=Nb_)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T?U~1|9S=nh5Cm22S5lCUtRSd zOwX39w+6{GFf5hfRXV2PYx^aTfdQxCf=b|1fCs3oUiwl6o(d~^VEZy_(pFY(E;5nK%NmPcGT{0h>y8QeGh&kS+zIm;SFXXka0lG%` zKMr|u4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK r;{&D#Q1h|LA64#(3v!RL>Jd{_yEeDye)10J^8f1r$9sK1 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/logs/riesgo_de_chubascos.mw3 b/worlds/smw/data/palettes/level/logs/riesgo_de_chubascos.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..0a7e877996b9d17f580af8d38b7732521c6e1772 GIT binary patch literal 514 zcmZQzxRT{*)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T?U~1|9S=nh5Cm22S5lCUtRSd zOwX39w`O3dDsK^Z$-t|mqqxy9+=+n!teRPjL7t(WaXu47{(JstYLNMwsx1ORUMmA%NBSqhgH`%wM2U9nnEM*4z?l^RSQ;Xjc3x+3|~K2;&hqx#REV~2>eB19hDg&_TH sys2Vt^0?)Xs(9+I)eTmXut(R;zz|uita`*$)vnF$xu3iPx_pl%05~mvz^Pi2NHBVM9~ZTdKE(VDhyl za-siI<*PyR=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9isTHW}pe<81Z z4bU~h|CuqQ!R`~+kay5@T^lKohA9uSuaFnym-uAa2rdSB%Ofrveg)~<4DOqP%mc}v zvz!tnkvyk3UI*qrg#SSD{~6_(|1)oAum}1V;(mnxKzxqewYi3M$npsPf#d}l#DU=j gkwTvI?MXW#S9F;d0y*-=)3jxx**a`#mlBIpe2UAu)7o_zPjo^ zSlw*7dTR!teg+0jALqpW8llQ5l?)93>lqjnn8g_68R{A5GeP7Z=spONWvdsvuLqN_ zHIWPbpDJGslJBo!U@!-g$od^^Y~d56~~dX9y@nDQX^7V?7p5}zy^!NnkNdBlaouONM!!F|*J%n!#QzBYf%qJ`YjX|jkmV8n1IY_Ahy%k5B9HDuko*J2 q2TTv3=3|pTs^Y1;RySBl!X8}~;+}Q7Mt0l0eg=I8x*ruJhyVcm1%BuN literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_cave/green_aqua.mw3 b/worlds/smw/data/palettes/level/mushroom_cave/green_aqua.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..dcd6d0f0940e3ea2bf9060489acf768b5fbed102 GIT binary patch literal 514 zcmZQz_)(u`aID6w_I>TvI?MXW#S9F;d0y*-=)3jxx*+nPjHB)&+hoqCoNYoN@zquT z!Rltq)mt+F^)oOe3bYA9R53982l5q|#Teun>KW%VLF6CkJ_wR!s~5Yk2a~TgkqiBw zDqjtfcLdtfT;v);a8Bp&EUT2e`bh#&RI?gl1QFY z9Ipd&AHshi{r?%|ng26yXRrtQ7vg_}|3G|>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK q;{&D#Q1h|LA64#(3vtgnT_d|~UO$6A1Kp1b5<~#6+kVCX literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_cave/ice.mw3 b/worlds/smw/data/palettes/level/mushroom_cave/ice.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ec308ed93b4af4ef7d593fc85d0e2f05e386e6f8 GIT binary patch literal 514 zcmZQz*jvuPa8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T?U|hx9hu5|Flh6U-Kf0LE@{c z{?~)(*>d&PAbAD`9?4RxLRE9UTYd}-|LYkT6qv;rN$Q#dGA!nmM*BD(EqJO4|jN=MV?xemv?D87~Z%NH_j!%}2;9`)sJmSLPSCGEV z;J)d9W{CcGu3O?P^Q+76gu>j9?7yWFk$iE=`@~HJ=PAMDk^Q$+LOpgxtacf)d;{}G z`Jc@H{UPT2D@5tcwS~x|yAb5RX2lk*RAcP&pzwCkd|=xUw4K2o7~bdrB!A9wN{~b{ Gc6k5;euS$4 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_cave/original.mw3 b/worlds/smw/data/palettes/level/mushroom_cave/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..7d97ca364024b2a0a58712e3aed6d42483b51379 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kgZ@?6nrO9Q*jUJMNXfqVsKF$Q^tddB%o5P2~MX@+=4c_w*gn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 eg&_F{j1QO|K+VS{@1Xg>wjl_&eu(?A%L4%GRd#3q literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_cave/really_dark.mw3 b/worlds/smw/data/palettes/level/mushroom_cave/really_dark.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..696f6df3bb766de8bcf1c12f27eee72c2d32b459 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T?U3^i6j981c|S%`d<&CXUo-F zGcY6xv`Dl7)qrr400YB+pm_?+Vhr*O^^EhGAo3s?RQXyHxzPWq^3@=Dh#abX1M^4u zpUnULVfrCy8QeGh z&kXU;Im;u&_<-pF)O>964w?^a8-j4_hqxb`e3AeFln-v} literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_cave/toxic.mw3 b/worlds/smw/data/palettes/level/mushroom_cave/toxic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..75f87d3c389a58d8c717d3d6ad48e30f0b1105d7 GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@dNa(#mmMfI~D}a*ZTNe0A0T zdJsKZuHG6X&%ltWyUO&mrGedMF9wGHK)wRA7=t`RJ>z^Ph`bnsG($Y2Jd->#Oup7c zF7$t@d^Je^oFxN;M6ywOS^iF-sSy1Q%pc`{GXM97=-+PiJN=O2VZBAp3>W}pe<81Z z4bU~h|AFSC1CYG9hP;EO>)J?xG;~>reTBRrzr-iYMsP96TOM)Y@GD5)W^msWWFE*p z=PaiLNhHrHj@N;?58*$M{C`Gy=Ksvw8SH`ng}5K#KM964w?^a8-j4_hqxcRJOCJdc0d3C literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_clouds/atardecer.mw3 b/worlds/smw/data/palettes/level/mushroom_clouds/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8e3b464f8b538a27618dd8d89de920257a7c05f2 GIT binary patch literal 514 zcmZQz*k9~v)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T?U~1|9S=nh5Cm22S5lCUtRSd zOwX39w+6{GFuY_4kSb=Ckh^8ZzyMNh#C(+{j#ZxR9~(qISNEfBsh?!>T%cNzdL!mJ z3Nf~Fe)j1g{(4gehM#eBo#evn!F-T>5c?1Af9(6iA?m}_ey4LW88LrhX21Yo^EmBm ztd;Dg=W}C7gYCP8_2KP<>1MLHu zzupw$dV5uv`%(QT$99h`Uib=!ofJ&I)ngwL1RpKDr&EZ@NVQT`|Me}9Pl z1r{;RI#!50x(h+}zh->L^obd_ypQKgH$t9jlQU4EYx E0D~WSU;qFB literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_clouds/greenshroom.mw3 b/worlds/smw/data/palettes/level/mushroom_clouds/greenshroom.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..4ad23210af6238afb1f4a3287b7cf3e064fd2379 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM=jB5Q4;4SN#Xm zv*qfoLGla?2N)Tc5||X2A22g8fK@Y#F~~F2GtOs%$Uo415L7Q3Z)EQcldm#%n0y2CNBN)3|NWu*nSZAzRS z`=%iCK>j&rIVDIUc}{V> z4$OTB|AFNHGs-jnXWq_W5A-j@{|Ntq_#C-wa}Dc|9Q1sGdU;wLT7GscSsArtd1d)HB`yi-ZG~USG8zx_CA{Y8U zRlXV|&knT3Ff7~medH9NSrGjV%pc`{GXM97==ak69(P!x-LPGc0Rw>SFXXka0lG%` zKQo3j*nQ#}@(!A=Ya<2HFy%q^74m}o5}zy^!NnkNdBlaouONM!!F^MZc_8_7mQ#Wx zlIIl1>%iQH@E=J2KchVJf9CBB_CWta+>h`dh|iI`HrKEYSsvj(kh~y+I54~*^5`y{ s1q>sI-?7Vs^k)a=`fc{#Zy;-qt{dbYBRyWP4goL4T+Q>g=SFXXka0lG%` zKMr|u4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK r;{&D#Q1h|LA64#(3vtgnT_d|~UO$6A1Kp1bCKm$$(k^{@ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_clouds/original_blue.mw3 b/worlds/smw/data/palettes/level/mushroom_clouds/original_blue.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..268518d3a692d63ee8d59c5997feb5d8e31c4c25 GIT binary patch literal 514 zcmZQzxLfaO)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T@d-do`FH3zM=jB5Q4;4SN#Xm zv*qfoLGla?FBy21`dE#nwi_`pfK@Y#F~~F2GtOs%$Uo415L7Q3Z)EQcldmSFXXka0lG%` zKMr|u4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK r;{&D#Q1h|LX9wo`ZT8=9AZv~;3v!Q<9SFXXka0lG%` zKMr|u4S5Gm*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK r;{&D#Q1h|LA64#(3vtgnT_d|~UO$6A1Kp1b=0^hn(k^{% literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_clouds/riesgo_de_chubascos.mw3 b/worlds/smw/data/palettes/level/mushroom_clouds/riesgo_de_chubascos.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d515e876d315ffbf7ad43ee00777345b4cd0488d GIT binary patch literal 514 zcmZQzxRT{*)$MfFi$6FpdUEpQVg`oaJg@aZ^xgV;T?U~1|9S=nh5Cm22S5lCUtRSd zOwX39w+6|#2%HePqN}60(JDvtMoBn5p*uUNs z;(B{knEO%vC&zY=EnfHvhn*BmzScx8^na>+HH6QX*`I4#hb-T~{89cV^M8Mc`~?;< z&N^0zJh}@({(H^%j_DIKcKQ22`J*bHx@&cVl_c!Zbu%!44|E>{)r-a(*?Ys}Yfa=r z|EJ1VgXGUyGB8La#|PJk?@xxwH!y#c|H=H{AEJN0(EN0MCKKiv%nTR+WPc&AeGSkx z!vAr|i)+X`TvRPHyXl0X9qishUXWknlVu~g800OFxN!Itq;E60ZwfLGtGgkMJLe&yl+}*RT#*9^pTbydZ-(FuWl0=q?1w sKVW>o^Z;r;Hu<9}p1NywgOw!g(Pbg-S*L4cx6SKk&}X3gQNizg0JY3}s{jB1 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_forest/count_shroomcula.mw3 b/worlds/smw/data/palettes/level/mushroom_forest/count_shroomcula.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..74e336cf6f76a2511149f2f9101d85f2970def4f GIT binary patch literal 514 zcmZQzxLcph(;~7|;+V`c6?V^suztnviF9`*P6(M z{!f*!2FahZWMGg;HmRRl{{yHOqQ8Opqx?_i|NapD+l~HZr6{t9pJQgg03iDddF^X} zt`Yu^Ltb1%-a*rKZKOaNhIX)hg}flY#3#!}a52bR9&zFDD@fmFaNiVU9>_iCET;rX zB+n_1*MYeY;Xjc4e@1!c|IFJN?1BD;xF6v^5T7G=ZLVP*vOL0nAbCLsabS2sIN%G*rUrr+_O&C$Znh0&!Eph_oIR&0RUyLeP#dv literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_forest/cursed_gold.mw3 b/worlds/smw/data/palettes/level/mushroom_forest/cursed_gold.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..f0c7d6124fc6488512235418850c933adb91bae2 GIT binary patch literal 514 zcmZQzxLaS(T`aa%(M$5Vz*2$9#S9F;d0y*-XrPQPi2PsA!0@0>p}wL1K|M%(b=7|` zJzK8c8YIua;IBAC?waf$w)y-F3?S7C%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@^ZFU|8R&jg(82=%xU+p2 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_forest/dark_green.mw3 b/worlds/smw/data/palettes/level/mushroom_forest/dark_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2b1f15cbb1e0c83ef37f037f83936f374a5a7d8b GIT binary patch literal 514 zcmZQzxLd!N=ZS!-Xqx15*%ykFiy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe(j^$+Sn;;XCv zgX!6F_0}MH28Lq{3XB|#3m6rc7#KjR6_~{s%5Li9HBHNyXyF{Huvi)+X`TvRPHyXk}}53;Y27vz`tWZ4KV26@XPE*yRZ>DvtMn}W;( z$)B^F5+sp4r#M~*=01e~K=S_?<(dC8Z)dQF`yb&ykUU53+FZjrWO;=DK=OhN;=u5N x$fLUuB>#Z%0n-Dh`Pk%-s(9+I)eTmXut%4LxObhdk=-`0pFy92?nebloB+{eyat`0A?v zV0yM(y){Ulf#H~lmnMf|kKs8>1_qF71!ge@d4_t%`AiV`2f7b}>P6#??7d;~wI*_* z|5N3wLGmD763JWYEb6O)dLjB7m_N$@Wd85Zz)%^_z>py+>V8_$PivzQ0|o%uU&w1; z19Xk>e`XA6u=~U{8_2KP-t=7HqT zSxyO(NS;$1uLE-*!haz7|BUj?|CzTl*aQ6waX-R;AU;R#+FZjrWO;=DK=OhN;=u5N v$fLUuB>#Z%0n-Dh`Pk%-s(9+I)eTmXut%4LxM!WNk=-`0pFy92?negz(7Ak% literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_forest/original.mw3 b/worlds/smw/data/palettes/level/mushroom_forest/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..cab6dd134869338d9efedbd2ee146f0b759046ca GIT binary patch literal 514 zcmZQzxLcpVxrFfrqYl$Vrmswsiy0Vx^Sssv(Rb_XbwT9+dIp9Ebqe(j^$+Sn;;XCv zgX!6F_0}MH28Ndmyh?qn#!}mj7#KjR6_~{s^suztnviF9`*P6(M z{!f*!2FahZWMGg;HmRRl{{yHOqQ8Opqx?_i|NapD+l_vwGcYMIKVW9S03iDddF^X} zt`Yu^Ltb1%-a*rKZKOaNhIX)hg}flY#3#!}a52bR9&zFDD@fmFaNiVU9>_iCET;rX zB+n_1*MYeY;Xjc4e@1!c|IFJN?1BD;xF6v^5T7G=ZLVP*vOL0nAbCLsabS2sIN%G*rUrr+_O&C$Znh0&!Eph_oIT}`2b;#eINh; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_forest/snow_dark.mw3 b/worlds/smw/data/palettes/level/mushroom_forest/snow_dark.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..cf3390d5f186777ea4e124579d9c2da93929412c GIT binary patch literal 514 zcmZQz*jxTgMZ?zHwLkKGY-Qf$Vg`oaJg@aZ^xgV;T?U|hrt6#7O+^qAB)+=pe?5qv zEmv<1l3yzEUShjuiK4mHuOJBjq*<{{l#YyPycCGfz~E_m*0nI~d)gcz8>IfE+06{w zQn@1AY7l>|?NSMg>`z6dXUqKN5NwZ>^C>uJSCWXt%+Rd|5SOXf7D}F z#A=rz%QrB8l>f>6-yfpiUm;4T))FF*?n03NniX5LQjKxTA64%P8)CkG S=!e{iMMmY-K=-49s%ijA+mAm0 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_hills/atardecer.mw3 b/worlds/smw/data/palettes/level/mushroom_hills/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..19d9d32451714d3386e12dd46192f91856524237 GIT binary patch literal 514 zcmZQz*kAlSwmZo%{ZDvtMoBn5p*uUNs;(B{knEO%v zC&zY=EnfHvhn*BmzScx8^na>+HH6QX*`I4#hb-T~{89cV^M8Mc`~?;<&N^0zJh}@( w{(H^%j_DIKcKQ22`J*bHx@&cVl_c!Zbu%!4ZDvtMoBn5p*uUNs;(B{knEO%v zC&zY=EnfHvhn*BmzScx8^na>+HH6QX*`I4#hb-T~{89cV^M8Mc`~?;<&N^0zJh}@( w{(H^%j_DIKcKQ22`J*bHx@&cVl_c!Zbu%!4 s$MlIAyZn8i{81H8-L<;GN)q`Ih*dS(9PVg`oaJg@aZ^xgV;T@VS8hmaug)m8uNLG*07dTWq8 z1H%%L)u!xKuLj9$ zsxmMX1&Jm{WX}eg3en%d{89cV^M8Mc{sO-_#p8_2KP<>Geg|-)^>B4dHS5< zcpaGg5dH({hq!IMs)!y;9^pUi@(BN-%cHxH82O_rp1NywgOw!g(RD-IvrgB@ZkyN7 MpwB?}qk_CB0OTBb$N&HU literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_hills/original.mw3 b/worlds/smw/data/palettes/level/mushroom_hills/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..3e4854584838b4d79557679d1f869b60536b2d6f GIT binary patch literal 514 zcmZQzxLfaJ$q{reODH)sdq(l(Vg`oaJg@aZ^xgV;T@VS8hmaug)m8uNLG*07dTWq8 z1H(%OUZp-(W2x;%3=IE)dy8QeDonFn&uIm;MVg`oaJg@aZ^xgV;T?V*3L={MUb=CiR5ItM2-Wnv| zB5*?Fims00M#FF?28RFj3=9g)Vhr*O^^EhGK=KR>S(fiSi=)10%_(PK*bmfc#5_kK z##YYHJ{=^lsoEkC)jgwi{KIbBL`};?ZWn03i8^ zvhmCjYO?w@${5mM{Z}n!hd$HrbX*gPUA~YPgH`w;#EyHBxNPe%HJh?N>lzScx8^na>+HAH_`BwyO6Dr9+7|M_$5 w5Rq1d$fLUuWIn|2xaE(kc<99VnCB?O z*vk3Yr-S4*Ra*ptyjBLjj`UB0>2F~EDF2iBzdr**Re6iRcB86t4zZO=JlYHx03<(A zHl8^`O;*1~8ABSZ|Ek68&}SN+j%z}(%NO#3{1Trm8^OgOZ+XOp!>=HHo56k4|I85k z&sk0hl1QFY9Ipd&AHsiN_bFEE$w*%iu~LJ{*P6(M{!f*!hUo8#ZO4FBsH7!;Vr7~~o18Rs*B2II!InqwM8JvYh~c;NdF|5{s!ic@;{mX`!g_9mA43NH>xV<5L>Clqs@Q;K=Ko1 zy8QeGh&kV8u zoaK}tiR3xO@j5W~A^Zn+pJKJ1jPwN&D>ayWt%+Rd|5W*Ei2klfzO+wO$nvQE^XJ$h wBCQCKM|UB}e2Cw1%O6$o)Lp9^tR!KNt{Y6DG#!*kQd~a_+;4#E(UqaBQ6|%1?k%i?wf+l1IeGWoDw9FJf}Eb z2j)J6|3LEp8ReP(GjC_G2l^M{euV!(e2(0;xrTMf@(BNd}Q@k1h*w&pKTryKP=SgFXY@j|yZI0WX(%HUIzs literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_rocks/atardecer.mw3 b/worlds/smw/data/palettes/level/mushroom_rocks/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..4540f32bf586fae9a2e499cf2a0db820a10a5a67 GIT binary patch literal 514 zcmZQz*k8=dn$F727Ra`ht(tvuF$2SIp4a*y`fh!_E(1{he?0@kgF1!!hWZEfAo0~z z|H1TZxq54mJOjf^h5)HzRtdRVW)Qv+^Hr8OR(ZC6Y!H5~?nm2FKgr~|K(!$CM$B^* zVr=F7?9)N~^`;CAKjY>)$%WU0`5^fq_8;8;*!PD+)NePs8_vZf%=}H50Rw=|>O$TQS4&S!$iKhS*;R4*EDWbX}=uQibi z{humd4U#`+$-p3yY*Ih9{s&MmM1KSGNBN)3|NSBQ???Ym=hV|x{-)1>0YLT_^4iw` zT_gOT8ABTEK5-3s2TfPbtWcm@bO5rikQd~a_+;4#E(UqaBQ6|%1?k%i?wf+l1IeGW zoDw9FJf}Eb2j)J6|3LEp8ReP(GjC_G2l^M{euV!(e2(0;xrTMf@(BNd^suztnviF9`*P6(M z{!f*!2FahZWMGg;HmRRl{{yHOqQ8Opqx?_i|NapD+l_vwGcYMIKVW9S03iDddF^X} zt`Yu^Ltb1%-a*rKZKOaNhIX)hg}flY#3#!}a52bR9&zFDD@fmFaNiVU9>_iCET;rX zB+n_1*MYeY;Xjc4e@1!c|IFJN?1BD;xF6v^5T7G=ZLVP*vOL0nAbCLsabS2sIN%G*rUrr+_O&C$Znh0&!Eph_oITz#Qy8QeDonFn&uIm;gH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 vg&_F{j1QO|K+VS{pBYEXX}Zdc0m80$z%_n&)lNXiy0Vx^Sssv(Rb_XbwT9+dIkoC`iA-kKnN0FUG*PK z&z7sV2FWupyky{2>SHyQ+HS(Oup7cF7$t@ zd^Je^oFxN;M6yZ!)cPMlvmp8#m_N$@Wd83D(ZAj3cRB-;0`miA1`GhQzmV6y2Iv~$ z|2X8uHRK&MUDrkmq+w_W+gHd7@=JWOYy=mByyX!W4!?r*Z3g#ELFR$nbIx)~kVNvF z;&>gH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2g&_F{ rj1QO|K+VS{e^kX&cdc%)l7v0FEW|zQbdBt`dHoFf40Jy#_?-^`5!8Q# literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_rocks/riesgo_de_chubascos_cafe.mw3 b/worlds/smw/data/palettes/level/mushroom_rocks/riesgo_de_chubascos_cafe.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c8da0fa178818e329cfd38ba3fe45e3033c9cc65 GIT binary patch literal 514 zcmZQzFfZoT)iupGz33?HH7#m#F$2SIp4a*y`fh!_E(61rtpD{43<~uP^$&m$B)+=p zKbW2^S8vV0P*vU{a6;sYu8!hH!*C}C2C!;oF$Q^tddB%o5cvnX4}$7NW}p|3q2) zng}&n{TgKqX|VgQTFef8rs3(hCKS7TAuq@;@yW6gTnzG-M_f4k3evY3+&BHt46*;5 z<&+?arEvn>G&TYRqo literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_rocks/riesgo_de_chubascos_negro.mw3 b/worlds/smw/data/palettes/level/mushroom_rocks/riesgo_de_chubascos_negro.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..868945e9f427713dd200f8dfebd814fc4f7484fd GIT binary patch literal 514 zcmZQzxRTW(vQ*-^il%P1=~~Ch#S9F;d0y*-=)3jxx(q=1|Md(E3iS>34}cIPzPjo^ zn4T?HZw-=f5jY`oMOR00qhYuc0|Qt!vlxRsLp|esCW!n4-3LMSqVY!d-Z1%E6S>g; zsq)nzc}>+8fgrDyfv+R|lVI`<%pc`{GXM8yV5ll@5!h~2Rn8%{Qi(^K0Rw>KC(7E_ zM5xK?*C=C1gY{pvm>v2|!_#q1D0cZmUXWknlVu~g800OFxN!Itq;E60Z~C7ZV*feI zDM1p+bBg12VD3Zs59~h0YCRe03nEr(FnNUkK=NIYd}*JmkmXVR=g+Z2L|PFdkM2T{ qeu&?3%O6$o)Lp9^tR!KNuA6}&vRGO5h^eYwo7;0gc?Wd)=NbTG*MEZm literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_rocks/shuk_ft_reyn.mw3 b/worlds/smw/data/palettes/level/mushroom_rocks/shuk_ft_reyn.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..4fa3e7600a154feee12065d5cf90573876021953 GIT binary patch literal 514 zcmZQzxLdzeghSQKG)VHMY_;0tVg`oaJg@aZ^xgV;T@d-do`K;(okD#>{eyat`0A?v zV0yM(y){Ulf#IdWcUvFd4~gI77#KjR6_~{s^suztnviF9`*P6(M z{!f*!2FahZWMGg;{^K;ye}6hmzJd9p{7>fp{t*4&<9??zFexxUU}nGoAo~k>?Q4Lp z5&n-uUR*=oLDO|@q(B;mcCdR3c|m@OPnM0~Vvx5y;=LsJ19Kn3e<1n)jPlI?nYT081N{r}Kf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! s|A6rU(*vma*yN9@ct<8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_stars/atardecer.mw3 b/worlds/smw/data/palettes/level/mushroom_stars/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d64a4bb2298285ab8361056cd7ae64d93e611e7c GIT binary patch literal 514 zcmZQzkoTXTFYgb7lZzP`e)GK62hn%y>vb6z=I1jo$W_m;o>~8`{!cwfe09}-Fg;tY z-Wnv|BJh%dS4l^4qhYuc1H=D%1_lLYF$Q^tddB%o5dFEjA8kwhe#O;iGcfE2nqb5{ zMm&d)v_B){I2f#GM|Tqn8kdLSPnAH@EH`yc!Ma0ow4?RPpClM(Y5W(EuZR?ivF zY^`K3J)avx8f@M*7F$+b*ZxSMH0<($ywOVkt>TkqBe)pkEswZx_!XpYGq`X1A7~%Q z{Pm`#ev-*^i|tim?z_qo$12bEj}5|?W4p%|FMNf=P6{SpYa$o=KUKaO!spBE&o!+> zmTzGGDF2iBzduC&0*e@D9VIN%G*rV%)#K$^a RBfD*0KZ8E&pv%kq0|5WQcJlxL literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_stars/cool.mw3 b/worlds/smw/data/palettes/level/mushroom_stars/cool.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..326b693e141bde3e41e44aaa8bfaa10153da21a9 GIT binary patch literal 514 zcmZQzxLePrn_>ILw=n8zR%H3)Vg`oaJg@aZ^xgV;T@blcLaus#_00Nj^?&L?;;XCv zgX!6F_0|jwd&`$fyqDOnS)yoe^(zR%KWSDh6Qv_#8ZQOnGcb6Xo^>q@`<^xj$OfrD zX?8Qiwp6ajwi?7=Yr9m!BKuQOY57htA0)5rf5QB|`0sFt`t3%))1T{}G+PC<4;_Ha zE98x5u8_0QpKFXR3(-H*M8;9mb#0_T8g}^&g14mRImaikMcj6|NBGq`zu80)LKI1(On4gU$bJ1R;n>>`J*bHx@&cVl_c!Z XbwkY85B-okvB;>r8t8shkm(Bmpze(RERxTNO;cf%&8SPv-yr3=BWh85kZgJz##okie(_G!Gqs z>@VcCuK~J7_&?BmbP$!zz#y(6@6e*~+{7yoT^3?rAuq@;@yW6gTnzG-M_f4k3evY3 z+&2Z82lCH3%PBz;$#aV1bzts8_zxuipHZIqKl645d!YLu{zv!^#OKIen`>ByERXOX nNM4XZ92i~@d2|z^Pi2MWH2SN3s@kaLEF!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqW{0v@AOPtcRzPy1`GhQzmV6y z2Iv~$|I8TDVE2h@$UA7du8kB(!;}ZvSI7(UOMJ3y1Q&z68_2KP-t=7HqT zSxyO(NS;$1uLE-*!haz7|BUj?|CzTl*aQ6waX-R;AU;R#+FZjrWO;=DK=OhN;=u5N x$fLUuB>#Z%0n-Dh`Pk%-s(9+I)eTmXut%4LxM!WNk=-`0pFy92?nebnB>d&P zAbAFc7Lnt+$5gT{-v=@<{0H(Cn8g_68R{A5GeP7Z=spN4m9f=~vxCXkn#hIzPnEA` zVAxZC&XR#aB6&iZPnAg&?rz+u=Es-J}^53;Y27vz`tWZ4KV26@XPE*yRZ>DvtMoBn5pxaXYZ zlpu-ZImPihF!v$+2h#taQJ(of^L7S%pnoCmNB9rK=g3`~YgmUYkMJKzUXVc?7+w&0 wbQgl;A22>(dH^*aoBUA~Pu;b;!AcVL=&}&^tkX5J+vfE%=rhp$sNksz0AXHy#{d8T literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_stars/midas.mw3 b/worlds/smw/data/palettes/level/mushroom_stars/midas.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9adffb69bd5f2a5705eb84d423da9e6d5a32daef GIT binary patch literal 514 zcmZQzxR?H#=L+X+E;;%6=KsAX7c(&Y=6S6TqVLw%>oNf485rcM=U30H|5pE}9wffH z>OYvCEmv<1k>_CDEOe1`x>k%k1H=D%1_lLYF$Q^tddB%o5czeoLPn)dKf~3ohTl{89cV^M8MceoO5+{%=^nuvM}%U;vQ&@;U8m zq^16H#Yc6Wj=B)cTA@b-h s1o`hI>qWLQcHHtuRXlaq>IN%G*rV%)hQF?n-8QeEL7#!{M+K8P0m+4a^#A|> literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_stars/original_green.mw3 b/worlds/smw/data/palettes/level/mushroom_stars/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9abb79150620ce048da970ef845b24068098187f GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%p4Kfmf-I)mUn~5d*`2AYXx5j6t5Eo^d`CME-&9gP?lRcq4mnn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 ng&_F{j1QO|K+VS{kBC2XS%`bq=^EK>^ZFU|8R&jgpdfp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszEgQn}+NP#pA?O^)~c|m@OPnM0~Vvx5y;=LsJ19Kn3e<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! s|A6rU(*vma*yN9@cfp{t*2$>&)t3l=@U{sA9kXAo~k>?Q6uvs^#t& zVMv4BC$1sy@Y+PrvCtP&9^~FaUXWknlVu~g800OFxN!Itq;E60Z~C7Z;@)$XQ-UOt z=M=~5z}%1UA4vayMtSD{%-b34f&PW~AK^a`pCfl|u3;UrJi>ngPyZ1P7{JayOV1}jO}qsv0vvrgB@ZkyN7pwB?}qk;qx0FbA6`Tzg` literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/mushroom_stars/riesgo_de_chubascos.mw3 b/worlds/smw/data/palettes/level/mushroom_stars/riesgo_de_chubascos.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b17323af65559ee0197747e00867e0adebd6ba25 GIT binary patch literal 514 zcmZQz(2vv9)Q^L~$;Au|zj z3{~YV0xubOm2?z08iqSDF#HFqS6~)nkY}i8oX-T&|3LRa&~4QkJKJEGe65LG=>Jst zYLNbH-4=ltBd4%IO2_BDvZE}tt~W@P0bpDY`}#UO8a#D&AJAbp#`ebfKU5c|(rP6?7o zo>LsJ19Kn3e<1s}D^}~tNM8`KQiI7O`>#cyE0QnmQx&p2s{i~sc8EwTLgdk12r|Eo rH&x6{9=kj!ypO7Q>aNudR+6ws*9~#cI$a~XZC*cvJ_Fs43iRUuum^qR literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/palettes.json b/worlds/smw/data/palettes/level/palettes.json new file mode 100644 index 0000000000..9690c2069d --- /dev/null +++ b/worlds/smw/data/palettes/level/palettes.json @@ -0,0 +1,358 @@ +{ + "grass_hills": [ + "atardecer.mw3", + "brawler_green.mw3", + "crimson.mw3", + "electro.mw3", + "geo.mw3", + "miku.mw3", + "mogumogu.mw3", + "nocturno.mw3", + "original.mw3", + "sakura.mw3", + "snow.mw3", + "sunsetish_grass_hills.mw3", + "toothpaste.mw3" + ], + "grass_forest": [ + "atardecer.mw3", + "autumn.mw3", + "brawler.mw3", + "brawler_atardecer.mw3", + "brawler_green.mw3", + "crimson.mw3", + "deep_forest.mw3", + "electro.mw3", + "geo.mw3", + "miku.mw3", + "myon.mw3", + "original_aqua.mw3", + "original_green.mw3", + "pizza.mw3", + "sakura.mw3", + "snow_dark_leaves.mw3", + "snow_green.mw3", + "winter.mw3" + ], + "grass_rocks": [ + "atardecer.mw3", + "crimson.mw3", + "dark.mw3", + "electro.mw3", + "geo.mw3", + "ice.mw3", + "miku.mw3", + "napolitano.mw3", + "original_aqua.mw3", + "original_choco_volcanic.mw3", + "original_ice.mw3", + "original_volcanic.mw3", + "original_volcanic_green.mw3", + "original_white.mw3", + "original_white_2.mw3", + "recks.mw3", + "sakura.mw3", + "thanks_doc.mw3" + ], + "grass_clouds": [ + "atardecer.mw3", + "crimson.mw3", + "electro.mw3", + "geo.mw3", + "miku.mw3", + "original_blue.mw3", + "original_green.mw3", + "pizza.mw3", + "sakura.mw3", + "shukfr.mw3", + "snow_day.mw3", + "volcanic_rock.mw3" + ], + "grass_mountains": [ + "brawler_lifeless.mw3", + "classic_sm.mw3", + "crimson.mw3", + "dry_hills.mw3", + "electro.mw3", + "geo.mw3", + "late_sandish.mw3", + "miku.mw3", + "original_aqua.mw3", + "original_blue.mw3", + "original_green.mw3", + "original_white.mw3", + "recksfr.mw3", + "sakura_hills.mw3", + "snow_day.mw3" + ], + "cave": [ + "brawler_dark.mw3", + "brawler_purple.mw3", + "brawler_red.mw3", + "brawler_teal.mw3", + "bright_magma.mw3", + "dark_red.mw3", + "glowing_mushroom.mw3", + "green_depths.mw3", + "ice.mw3", + "magma_cave.mw3", + "original_chocolate.mw3", + "original_gray.mw3", + "original_ice.mw3", + "original_mustard.mw3", + "original_volcanic.mw3", + "snow.mw3", + "toxic.mw3", + "toxic_moss.mw3" + ], + "cave_rocks": [ + "bocchi_rock_hair_cube_things.mw3", + "brawler_volcanic.mw3", + "ice.mw3", + "layer_2.mw3", + "original_gray.mw3", + "original_mustard.mw3", + "pyra_mythra_ft_pneuma.mw3", + "snow.mw3", + "toxic.mw3" + ], + "water": [ + "dark_water.mw3", + "deep_aqua.mw3", + "deep_chocolate.mw3", + "harmless_magma.mw3", + "murky.mw3", + "oil_spill.mw3", + "original_brown.mw3", + "original_gray.mw3", + "original_green.mw3", + "original_mustard.mw3", + "original_volcanic.mw3", + "pickle_juice.mw3" + ], + "mushroom_rocks": [ + "atardecer.mw3", + "brightshroom.mw3", + "original_green.mw3", + "original_ice.mw3", + "original_volcanic.mw3", + "original_white.mw3", + "riesgo_de_chubascos_cafe.mw3", + "riesgo_de_chubascos_negro.mw3", + "shuk_ft_reyn.mw3" + ], + "mushroom_clouds": [ + "atardecer.mw3", + "greenshroom.mw3", + "oilshroom.mw3", + "original_aqua.mw3", + "original_blue.mw3", + "original_yellow.mw3", + "riesgo_de_chubascos.mw3" + ], + "mushroom_forest": [ + "atardecer.mw3", + "autumn.mw3", + "count_shroomcula.mw3", + "cursed_gold.mw3", + "dark_green.mw3", + "lifeless_gray.mw3", + "original.mw3", + "snow_dark.mw3", + "snow_green.mw3" + ], + "mushroom_hills": [ + "atardecer.mw3", + "atardecer_naranjo.mw3", + "atardecer_verde.mw3", + "future.mw3", + "original.mw3", + "riesgo_de_chubascos_azul.mw3", + "riesgo_de_chubascos_cafe.mw3", + "riesgo_de_chubascos_negro.mw3", + "watermelon_skies.mw3" + ], + "mushroom_stars": [ + "atardecer.mw3", + "cool.mw3", + "dark_night.mw3", + "halloween.mw3", + "light_pollution.mw3", + "midas.mw3", + "original_green.mw3", + "original_night.mw3", + "purpleish_night.mw3", + "riesgo_de_chubascos.mw3" + ], + "mushroom_cave": [ + "argent_cave.mw3", + "glowing_mushroom.mw3", + "green_aqua.mw3", + "ice.mw3", + "original.mw3", + "really_dark.mw3", + "toxic.mw3" + ], + "forest": [ + "agnian_queen.mw3", + "atardecer.mw3", + "frozen.mw3", + "halloween.mw3", + "kevesi_queen.mw3", + "original_dark.mw3", + "original_fall.mw3", + "original_green.mw3", + "sakura.mw3", + "snow_dark_leaves.mw3", + "snow_green_leaves.mw3" + ], + "logs": [ + "brawler.mw3", + "evening.mw3", + "mahogany.mw3", + "not_quite_dawnbreak.mw3", + "original.mw3", + "riesgo_de_chubascos.mw3" + ], + "clouds": [ + "atardecer.mw3", + "charcoal.mw3", + "cloudy.mw3", + "cotton_candy.mw3", + "original_green.mw3", + "original_orange.mw3" + ], + "castle_pillars": [ + "agnus_castle.mw3", + "cheese.mw3", + "chocolate_blue.mw3", + "dark_aqua_marine.mw3", + "dollhouse.mw3", + "gold_caslte.mw3", + "keves_castle.mw3", + "original_gray.mw3", + "original_green.mw3", + "original_mustard.mw3", + "original_white.mw3", + "pink_purple.mw3", + "purple_pink.mw3", + "sand_gray.mw3", + "sand_green.mw3", + "shenhe.mw3", + "whatsapp.mw3" + ], + "castle_windows": [ + "brawler_pink.mw3", + "cheese.mw3", + "dark_aqua_marine.mw3", + "dollhouse.mw3", + "original_brown.mw3", + "original_gray.mw3", + "original_water.mw3", + "red_castle.mw3", + "shenhe.mw3", + "underwater.mw3", + "water.mw3", + "whatsapp.mw3" + ], + "castle_wall": [ + "cheese.mw3", + "dollhouse.mw3", + "grand_marshall.mw3", + "hot_wall.mw3", + "original.mw3", + "sand_green.mw3", + "shenhe.mw3", + "water.mw3" + ], + "castle_small_windows": [ + "dark_lava.mw3", + "dark_purple.mw3", + "dollhouse.mw3", + "forgotten_temple.mw3", + "original_gray.mw3", + "original_volcanic.mw3", + "original_water.mw3", + "sand_gray.mw3", + "sand_green.mw3", + "shenhe.mw3", + "water.mw3", + "whatsapp.mw3" + ], + "ghost_house": [ + "brawler_cyan.mw3", + "brawler_orange.mw3", + "brawler_purple.mw3", + "creepypasta.mw3", + "crimson_house.mw3", + "golden_house.mw3", + "halloween_pallet.mw3", + "orange_lights.mw3", + "original_aqua.mw3", + "original_blue.mw3", + "original_dark.mw3", + "original_white.mw3" + ], + "ghost_house_exit": [ + "evening_exit.mw3", + "golden_house.mw3", + "original.mw3", + "original_blue_door.mw3", + "underwater.mw3" + ], + "ship_exterior": [ + "blue_purple.mw3", + "doc_ship.mw3", + "grey_ship.mw3", + "original.mw3", + "reddish.mw3" + ], + "ship_interior": [ + "blue_purple.mw3", + "bocchi_hitori.mw3", + "bocchi_rock.mw3", + "brawler.mw3", + "grey_ship.mw3", + "original.mw3" + ], + "switch_palace": [ + "blue_grid.mw3", + "brawler_brown.mw3", + "cafe_claro.mw3", + "color_del_gato_2.mw3", + "color_de_gato.mw3", + "green_grid.mw3", + "gris.mw3", + "mario_pants.mw3", + "monado.mw3", + "morado.mw3", + "negro.mw3", + "onigiria.mw3", + "original.mw3", + "original_bonus.mw3", + "pink.mw3", + "red_grid.mw3", + "verde.mw3", + "verde_agua.mw3", + "yellow_grid.mw3", + "youbonus.mw3" + ], + "yoshi_house": [ + "atardecer.mw3", + "brawler_green.mw3", + "choco.mw3", + "crimson.mw3", + "miku.mw3", + "mogumogu.mw3", + "monocromo.mw3", + "neon.mw3", + "nieve.mw3", + "night.mw3", + "nocturno.mw3", + "original.mw3", + "sakura.mw3", + "snow.mw3", + "strong_sun.mw3", + "sunsetish_grass_hills.mw3" + ] +} \ No newline at end of file diff --git a/worlds/smw/data/palettes/level/ship_exterior/blue_purple.mw3 b/worlds/smw/data/palettes/level/ship_exterior/blue_purple.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2e098de76404e6c399e3b5e06fbd264d83c28b36 GIT binary patch literal 514 zcmZQzxLdzU=d6X0FPmkS@7c)7#S9F;d0y*-=)3jxx**b#CsAaP3POH$)&F{s{A{^; zYmhtx14NcT=}H*`!+#)Ofmw_}o}r#`J`+S9VFFCP)nNdBB91A|1eN&VFN zA3(hj{SC|?<$p5&_lM};ZuC2yfk}b+0W$*z0NLLlU|++a_`p^n9YY%IK5-3s2Tj+t zkpgL$@*w*Pc|m@OPnM0~Vvx5y;=%iQH@E=J2 zKchVJf9CBB_CWta+>h`dh|iI`HrKEYSsvj(kh~y+I54~*^5`xE$v)Z^(Ve+*ma-siI<*PyR z=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3f8886IP8MGK8i)%pq`~eJ z*N}J6bX^-MkcKG_vagUA*fj9JyK@zquT z>p}Evxq54mJOhK5=2}xjw<$rE=?o12fqVsKF$Q^tddB%o5c#Dd&t)KLVDhyla-siI z<*PyR=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3f8886I{%pnn>PA}E z&8odIq`~eJ*N}J6bX^-MkcKG_vagUA*fj9JygnDPLa{Cvs) literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ship_exterior/original.mw3 b/worlds/smw/data/palettes/level/ship_exterior/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..20eec6ee80bedadf37912970c3927695055d3c49 GIT binary patch literal 514 zcmZQzxLbcB;7nw0(CY}F)C*CQiy0Vx^Sssv(Rb_XbwOl9tY!V=x)rG&VH)8e@zquT z>p}Evxq54mJOjf?4nN7uniY1k!3+%lfqVsKF$Q^tddB%o5cv}#oU%EZ=S_{AVDhyl za-siI<*PyR=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3f8886I{z6{+ z8lY>0|KpGs*N}J6bX^-MkcOchY+oTS$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE} zK@!PxisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*e0E^2-)8^)2D0YpvLN>u>G67X2zV*xYM!^nlm`H@hI|kJ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ship_exterior/reddish.mw3 b/worlds/smw/data/palettes/level/ship_exterior/reddish.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..674aafd0c0a6a2dda0b9a295989935373b814a51 GIT binary patch literal 514 zcmZQzxLfZka9TlFH$&u|hMDc;Vg`oaJg@aZ^xgV;T@cw2Ygun@XlT04^_MS5e0A0T zdJsKZuHG6X&%lr&@lK`K^rvH;9|OaGAYXx5j6t5Eo^d`CM1F+~x2m7+2Gh^BF!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}pXNH7* z4a5Z)(qQ+AYsfojx~`2BNW+u|*;mL5@=JWOYy=mByyX!W4!?r*Z3g#E{{!s<$)B^F z5+sp4r#M~*=01e~K=S_?<(dC8Z)dOv`WNDUg#SQ%j@-4mhIPpD2>*fP1sTMF;RTUL vcOgjr0pkOv2T=2|$!7=V`fc{#Zy;-qE(>yxkshyChk%!2uI71Q_@e^=o!@%I literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ship_interior/blue_purple.mw3 b/worlds/smw/data/palettes/level/ship_interior/blue_purple.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9ac0f022af3db2dcb4559286a7c215015b18d7a7 GIT binary patch literal 514 zcmZQzxLdzU=d6X0@Akx~ESciT#S9F;d0y*-=)3jxx**b#CsAaP3Y%q?@7YL@`0A?v z^&on-T)j0&o`C@(%b#?mjDg`lkgvcj#vsp7&p4k6B9AZuCSPkJ7y3U{z8WNd&XR#a zBH5&VYW)wOUWon%=8y6}ng9Dk^lvx%ozB3d!2E!j0Rw>SZxFDrVNiTvtB{T%4R)Wn zhP;EO>)J?xG)#GreTBRrzr-iYMsP96TOM)Y@GD5)W^mv1KhQpq{5i`hK@!PxisN-) z?nC$wB>$gLp7}rXb_RQ(e< a05u<*d_mpiIb3@`0A?v z^&on-T)j0&o`Io7=9qz|V|M7;R0f9sK)wRA7=t`RJ>z^Pi2O+bZc#ni0>$ZmF!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C6L3>W}pe<81Z z4bU~h|8dBRYsfojx~`2BNW;($wy%&EJstYLNUnO9lpsWRv=-^*?}mA^IDbKg$1P{_hXbzuo9}Is=me^8;oE3;?pfkk`Hj z=o;bwION4O8_2KP-t=7HRE&T>kS zMDm>CcpaGg5dH(n|7VnE{?ELf!5-*ei2D)#1MxX>*XA15A}FR hAo&N3511Z6&BrERPz^Ph&-FDhhmrJ8$&-Un0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 ig&_F{j1QO|K+VS{Ur=|s&a8fBy}SduET;R>RR93Jdw87y literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ship_interior/grey_ship.mw3 b/worlds/smw/data/palettes/level/ship_interior/grey_ship.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..786802304f335dde42c0a687bff76a19fc2d42ce GIT binary patch literal 514 zcmZQzxLe;U@KPpE_p@!8-_N+o#S9F;d0y*-=)3jxx*#%J^Sx!U*R?3i^f|>K@zquT z>p}Evxq54mJOhK5=2}xjw<$rE=?o12fqVsKF$Q^tddB%o5c#Dd&t)KLVDhyla-siI z<*PyR=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3f8886Ye%1f#=PZNV zzDHq5gWV^tA@88+x;9cE4O1RuUm-8ZFY(E;5nK%NmPcGT{0h>y8QeGh53~;?f6j7B zkVNvF;&>gH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 ig&_F{j1QO|K+VS{Ur=|s&a8fBy}SduET;R>RR92&9Dc(9 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/ship_interior/original.mw3 b/worlds/smw/data/palettes/level/ship_interior/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a208db211f51dd7967c5d1e2d5702488d0b9d7ed GIT binary patch literal 514 zcmZQzxLeP_a8$)pcdc%)l7#)_Vg`oaJg@aZ^xgV;T@aZd&?vE3=7|iarnf3ce0A0T zdJsKZuHG6X&%kh!!%yFayJXAYXx5j6t5Eo^d`CME-;br)-Yqc~c`Nn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 ig&_F{j1QO|K+VS{Ur=|s&a8fBy}SduET;R>RR91#$av}i literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/blue_grid.mw3 b/worlds/smw/data/palettes/level/switch_palace/blue_grid.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d613b8061deb3310e59c1ff98d2adeb268aa28e4 GIT binary patch literal 514 zcmZQzxLcnj!tKid1RImet0xyTF#P6utq-E_*4OKTNCpPE>iN|(>%Z0isRxO#uKEwA zXUo-FL*!pF@GAAO8cS_AVqo|WRIk7+#vsp7&p4k6BL6`5K~TMDypg>(Oup7cF7$t@ zd^Je^oFxN;M6yZ!)cPMly%7Bk%pc`{GXM97=-+PiJDq_^f%yS5$epMFBY zg#TlaXJFv=71xk=K-Godf$S^f1^FdDSvG=;LEiF+3x{7p`Zk06rXce`^5-n41W6>% zDUR2Hxewt#koc-C!jNdvsZldz4j=n5x>fxjpy842Aj804FSX7XSbN literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/brawler_brown.mw3 b/worlds/smw/data/palettes/level/switch_palace/brawler_brown.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a3073c31adca276933a73da4fe5187fd14cb73e7 GIT binary patch literal 514 zcmZQzxLcnj;%0i*uv>FuQhD{{Vg`oaJg@aZ^xgV;T@cB@AXh!VdS?B%`aks`@zquT z!SrmodTWUMO9o!0)0PHyo4ptq{sYx3FpDwBGt@KAXM)H-(0ve8FB)%T?+uf$HIWPb zpDJGsl0Rq3z#x%qQa`o+2T(6We*^PJ`Jc@H{UQ3d8~sjaU{YXyz|4RFK<+E#wXXrX zM)*Gtd2tPS2V=WXw`qPD+QIe}@`C&lpDY`}#UO8a#D&AJAbp#`eN&KmAorZJoDw9F zJf}Eb2j)J6|3LEp8ReP(GjC_G2l^M{euV!(e2(0;xrTMf@(BNd}Q@k1h*xkFx3!Q&qb*x95KH4(RgpqX8dweTD!4 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/cafe_claro.mw3 b/worlds/smw/data/palettes/level/switch_palace/cafe_claro.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..90ffc5cf73f8caf0c9e92d7ce04a6b688c9b556a GIT binary patch literal 514 zcmZQzxLcnj;%2HNyHR&zQhD{{Vg`oaJg@aZ^xgV;T@cB@AXh!VdS?B%`aks`@zquT z!SrmodTWUMO9o!0K2~F??M4g?|AFcin8g_68R{A5GeP7Z=spOl7mYWv_lC*Wn#hIz zPnE9*$)B@iV30^Qsh?W^1E?3Gzk&Iq{7>fp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszE1IUM=b{N{h_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK r;{&D#Q1h|LA64#(3v!RL>Jd{_yEeDye)10J^7Eqswefr( literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/color_de_gato.mw3 b/worlds/smw/data/palettes/level/switch_palace/color_de_gato.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b5ba743bfae9cf2bb8443b692f0b4f6ae023912d GIT binary patch literal 514 zcmZQzxLcnj;$|AlSj)6Asl0k}F$2SIp4a*y`fh!_E{J4ckgJ|wJ+uB>{hxY}`0A?v zV0yM(y){JsB?GTgAFHv{b|VIc|3LK$%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@DvtMn}W;($)B^F5+sp4 zr#M~*=01e~K=S_?<(dC8Z)dOvnh$Y5!haw>NAB8O!#ZSng#SSDf(+uo@Pf!=bKe8T r2TTv3=3|pTs^Y1;RySBl!X8}~rE=SKqoqbPe_ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/color_del_gato_2.mw3 b/worlds/smw/data/palettes/level/switch_palace/color_del_gato_2.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9c52a56a872a27b8dff95fb1439873ffd3a28293 GIT binary patch literal 514 zcmZQzxLcnj;%53?pjvWcQhD{{Vg`oaJg@aZ^xgV;T@cB@AXh!VdS?B%`aks`@zquT z!SrmodTWUMO9o!0K2~F??M4g?|AFcin8g_68R{A5GeP7Z=spOl7mYWv_lC*Wn#hIz zPnE9*$)B@iV30^Qsh?W^1E?3Gzk&Iq{7>fp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszE1IUN}fc`-TAo~h=L4Ju(mW|+IkheVI!r@ntzRlpiDabsK{5i`hK@!Px zisN-)?nC$wB>$gLp7}rXb_RQ(eIN%G*rUsW+@q{|#8lO;&F#6LyaT%Y{Ad8bl6{{5 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/green_grid.mw3 b/worlds/smw/data/palettes/level/switch_palace/green_grid.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..3a8ded956b435a03b03666b57f3ea4cd54bcc57b GIT binary patch literal 514 zcmZQzxLcnj!tKid1RImet0xyTF#P6utq-E_*4OKTNCpPE>iN|(>%Z0isRxO#uKEwA zXUo-FL*!pF@GAAO8cS_AVqo|WRIk7+#vsp7&p4k6BL6`5K~TMDypg>(Oup7cF7$t@ zd^Je^oFxN;M6yZ!)cPMly%7Bk%pc`{GXM97=-+PiJDq_^f%yS5$epMFBY zg#Tla2dZWi*N}HW)rH`J>?`C2`6WJCHiC;m-tveGhhIVZHiP@7AoD=-=PaiLNhHrH zj@N;?58*$M{C`Gy=Ksvw8SH`ng}5K#KM;5h569{#y5As literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/gris.mw3 b/worlds/smw/data/palettes/level/switch_palace/gris.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8e8df8008bb40d0a7d1988bb227c4fa1731010db GIT binary patch literal 514 zcmZQzxLcnj;$}J}NFsS-QhD{{Vg`oaJg@aZ^xgV;T@cB@AXh!VdS?B%`aks`@zquT z!SrmodTWUMO9o!0K2~F??M4g?|AFcin8g_68R{A5GeP7Z=spOl7mYWv_lC*Wn#hIz zPnE9*$)B@iV30^Qsh?W^1E?3Gzk&Iq{7>fp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszE!#PVsw<$pw+QIe}@`C&lpDY`}#UO8a#D&AJAbp#`eN&KmAorZJ1clF> z;&>gH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2g&_F{ sj1QO|K+VS{e^kX&cdc%)l7v0FEXX~|sz*#!?b_U)`^h_?%g>Jn0OBZp0{{R3 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/mario_pants.mw3 b/worlds/smw/data/palettes/level/switch_palace/mario_pants.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b18aee2e184171a3b62dc46b3b9f725efe844799 GIT binary patch literal 514 zcmZQzxLcnj!tKk&AaA)bsl0k}F$2SIp4a*y`fh!_E{J4ckgJ|wJ+uB>{hxY}`0A?v zV0yM(y){JsB?GTgAFHv{b|VIc|3LK$%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK;{&D# nQ1h|LA64#(3v!RL>Jd{_yEeDye)10J^7EqsawvPU literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/monado.mw3 b/worlds/smw/data/palettes/level/switch_palace/monado.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c37acf989847366753986709d39c92b78c2dee1b GIT binary patch literal 514 zcmZQzxLcnj;%1sFpkcZ(sl0k}F$2SIp4a*y`fh!_E{J4ckgJ|wJ+uB>{hxY}`0A?v zV0yM(y){JsB?GTgAFHv{b|VIc|3LK$%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@4{^34C4w=>uS{R?qF!haw>NAB8O!#ZSng#SSDf(+uo@Pf#r wyAUM*fbjv-1E~4faNudR+6wsmj$^;S@npis$HAgb3b_pbou$w0C>QBCIA2c literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/morado.mw3 b/worlds/smw/data/palettes/level/switch_palace/morado.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ab495c8b33b658f4539317aa5dc7da23923d8cf5 GIT binary patch literal 514 zcmZQzxLcnj;%2Jn8xp!Psl0k}F$2SIp4a*y`fh!_E{J4ckgJ|wJ+uB>{hxY}`0A?v zV0yM(y){JsB?GTgAFHv{b|VIc|3LK$%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@tGAkMJLe&yl+}*RT#*9^pTbydZ-(FuWl0=q?1w tKVW>o^Z;r;Hu<9}p1NywgOw!g(PcsIQC2--s%qEf_S{e20bPE6GyuHnd?)|_ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/negro.mw3 b/worlds/smw/data/palettes/level/switch_palace/negro.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..dd9db46ffdea238a8bc241c3948f2fe143f2385e GIT binary patch literal 514 zcmZQzxLcnj;%0hGB3pN3QhD{{Vg`oaJg@aZ^xgV;T@cB@AXh!VdS?B%`aks`@zquT z!SrmodTWUMO9o!0K2~F??M4g?|AFcin8g_68R{A5GeP7Z=spOl7mYWv_lC*Wn#hIz zPnE9*$)B@iV30^Qsh?W^1E?3Gzk&Iq{7>fp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszE0|UcS5lvML?O^)~c|m@OPnM0~Vvx5y;=LsJ19Kn3e<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! t|A6rU(*vma*yN9@cG*(EyN*d%FMt literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/onigiria.mw3 b/worlds/smw/data/palettes/level/switch_palace/onigiria.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..7632dc08b226eca8998e5e06798767184ed6748d GIT binary patch literal 514 zcmZQzxLcnj;%2I;tLwQjsl0k}F$2SIp4a*y`fh!_E{J4ckgJ|wJ+uB>{hxY}`0A?v zV0yM(y){JsB?GTgAFHv{b|VIc|3LK$%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@LsJ19Kn3e<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! t|A6rU(*vma*yN9@cfp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszEgQn}+NP#pA?O^)~c|m@OPnM0~Vvx5y;=LsJ19Kn3e<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! t|A6rU(*vma*yN9@cy8QeDonFn&uIm;~8`{!cwfe09}- zFg;tY-WnqRl7UyLkJVUeyAcD!f1r8=W-$hNhI+>NOc418x(|ZtMdOX^yZjKK0P2P4Z(#l?|C9N@KScj_qu=QaObW~om>Dnt$bE&p_BBA) z2>-_+FRmf)z;FA_=WYy!cCdYgydb~CC(A}~G00mUapCYQNZ)2~-xOpX$UWyQrvynP z&nb@Afw>RiKal)?MtSD{%-b34f&PWKAK^a`pCfl|u3;UrJi>ngPyZ1P7{JayOV1}jO}qsxNaqpW(wRMoD{?YW=41G@bDXaI5gdhP%K literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/red_grid.mw3 b/worlds/smw/data/palettes/level/switch_palace/red_grid.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..1336dc83468039d90876692cc112596cd79bd549 GIT binary patch literal 514 zcmZQzxLcnj!tKid1RImet0xyTF#P6utq-E_*4OKTNCpPE>iN|(>%Z0isRxO#uKEwA zXUo-FL*!pF@GAAO8cS_AVqo|WRIk7+#vsp7&p4k6BL6`5K~TMDypg>(Oup7cF7$t@ zd^Je^oFxN;M6yZ!)cPMly%7Bk%pc`{GXM97=-+PiJDq_^f%yS5$epMFBY zg#TlaX8^iHTtnUgRTqK>vagUA+_kxeb;$Av|AFKM8N`9EgvcXY24R8Z rA22>(dH^*aoBUA~Pu;b;!AcVL=&~U9D61YZRkdq#d+vuB3iG1@=e&2` literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/verde.mw3 b/worlds/smw/data/palettes/level/switch_palace/verde.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..92515d0c32f5169b0017cd26063b42750298e4be GIT binary patch literal 514 zcmZQzxLcnj;%3^;`;vEKQhD{{Vg`oaJg@aZ^xgV;T@cB@AXh!VdS?B%`aks`@zquT z!SrmodTWUMO9o!0K2~F??M4g?|AFcin8g_68R{A5GeP7Z=spOl7mYWv_lC*Wn#hIz zPnE9*$)B@iV30^Qsh?W^1E?3Gzk&Iq{7>fp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszEgB$M(UM@Zi?O^)~c|m@OPnM0~Vvx5y;=LsJ19Kn3e<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! t|A6rU(*vma*yN9@cfp{t*4!jee&yFexxUU}nGoAomsW+SdSG zBm5tSytszE1IUL7ei+)p_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~B#}I) zI9>+_kxeb;$Av|AFKM8N`9%1(8R0AxQoK r;{&D#Q1h|LA64#(3v!RL>Jd{_yEeDye)10J^7Eqs$>Mz4 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/yellow_grid.mw3 b/worlds/smw/data/palettes/level/switch_palace/yellow_grid.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c41276492eb0ccc73add33198c80b001d1874c60 GIT binary patch literal 514 zcmZQzxLcnj!tKid1RImet0xyTF#P6utq-E_*4OKTNCpPE>iN|(>%Z0isRxO#uKEwA zXUo-FL*!pF@GAAO8cS_AVqo|WRIk7+#vsp7&p4k6BL6`5K~TMDypg>(Oup7cF7$t@ zd^Je^oFxN;M6yZ!)cPMly%7Bk%pc`{GXM97=-+PiJDq_^f%yS5$epMFBY zg#TlaXJDuo7T1t>K-Godf$S^f1^FdDSvG=;LEiF+3x{7p`Zk06rXce`^5-n41W6>% zDUR2Hxewt#koc-C!jNdvsZldz4j=n5x>fxjpy842Aj80BACJQ2+n{ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/switch_palace/youbonus.mw3 b/worlds/smw/data/palettes/level/switch_palace/youbonus.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b213f566079581f1d272ed2ce0207c6749b38fab GIT binary patch literal 514 zcmZQzxLcnj;%2ID$q}|Osl0k}F$2SIp4a*y`fh!_E{J4ckgJ|wJ+uB>{hxY}`0A?v zV0yM(y){JsB?GTgAFHv{b|VIc|3LK$%wi1k4E2ojnIQ5HbRPuOi^dz-d&A^wP2@uV zr^;7@LsJ19Kn3e<1n)jPlI?nYT081N{qeKf-??K1c4_T*EqKd4&H!@`4QF!0>{|qq`6! t|A6rU(*vma*yN9@cp}Evxq54mJOjf~6;It`I?HVH^B5TZ1NjQfVhr*O^^EhGAo5}i(hTv8@=Wr~F!@>& zxzPWq^3@>ubCwJY63Hg@Q|o^K^+NPFFn^T)$^73RqJO*5?{o$x1?C4p{pbK>e<81Z z4bU~h|8dBRYsfojx~`2BNJG~Rv9FL9JuvF%l%5xn}&z{i9#S9F;d0y*-=)3jxx(q=1hFHsbzPLpaZl=q9LE@{c z{?~)(*>d&PAbAD`4!?qQR?TIOE0Y)){?{`wC@_mL$TQS4&S!$ib2IQV7zutAHIstL z*P6(M{!f*!2FYuxGB5;rt<4k2E(e+g(ci%QQT`|Me}9Pn51PNz(?vCv)@U(c0FeEK zy!JJhWp}CBm}5wT-RCKjZ*a_n!_zAkyZmd8O}rI+@yW6gTnzG-M_fSu*=BIx^glDi zeofULLjk8^zsn9V_o4c4n`niQ75`b0uaYo%WdAWRyl~+RU6+Y0kLo``2613`LFCb0 r2=bq+=n2IvE!^_ifw_L0{r4NlV)eg~9lVAo0~z z|LZ~YY`J=CkURqehhIT@8_z3=Tulas|Md(E3d~{*@(lHi^O+#>+zh-7MuJ~O&7@%R zwI*_*|5N3wLGqfa3=BbDYx4xM%YkM=^fxenl>f>6-yfp?gXZt_bWu&EHChZ90Azn5 zuYJvB*gH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 wg&_F{j1QO|K+VS{pBYEXX}Zdc0m80$z%_n&)lNb%7 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/murky.mw3 b/worlds/smw/data/palettes/level/water/murky.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..57ea1ce44f5e72698c17e239141e7b54c163b17a GIT binary patch literal 514 zcmZQz*j2t*f=y9PHBd&PAo;~2shY% zUuz;4`ae~^8YKV5aj}S5`o!`r)jNQEi2erVkMcj6|NBGqZ#Vj#{!sUT-2-O^3;?qK zgP?uQExBm*+xi&NVE1k2u#)sJd>yD8hh08Ta)w^4b9}OF1Q&z68_2KP<> zGehj>aoQYamOi7n))nSHRR38E)$`xe4-ve>29rnj-(r!rK)<|?b;$Cl{_E$s#v{WE wkwF7s2x80&%YfeM-h096fzpa1{> literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/oil_spill.mw3 b/worlds/smw/data/palettes/level/water/oil_spill.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ac1ffed27f62b1137ec363aebc34115fd4b19d87 GIT binary patch literal 514 zcmZQzxLcph1A#39EdrB^85n-^yw(TNckAnQL1aU$W&JUU7J;QAOGQB9tE>LkgXr0E z_0}MH28I)n=Zabcj!9^$GBErH@)elH7~~o18Rs)W(dH^*an|yX)uHR<={RXn;=&~U980qnPbqIJV=4zg|MVEKt002gCeFp#l literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/original_brown.mw3 b/worlds/smw/data/palettes/level/water/original_brown.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5f5366cebd110b6b7a55f1fbf5a4f1665ef1d4db GIT binary patch literal 514 zcmZQzxLbcB;7nw0(CY}F)C*CQiy0Vx^Sssv(Rb_XbwOl9tY!V=x)rG&VH)8e@zquT z>p}Evxq54mJOjf?4nN7uniY1k!3+%lfqVsKF$Q^tddB%o5cv}#oU%EZ=S_{AVDhyl za-siI<*PyR=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3f8886I{z6{+ z8lY>0|KpGs*N}J6bX^-MkcOchY+oTS$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE} zK@!PxisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*e0E^2-)8^)2D0YpvLN>u>G67X2zV*xYM!@6mv4v#0J7tJ?EnA( literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/original_gray.mw3 b/worlds/smw/data/palettes/level/water/original_gray.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b5087eccbed5ca09f590dc7d4c7288b950064b7b GIT binary patch literal 514 zcmZQzxLbcB;7nw0(CY}F)C*CQiy0Vx^Sssv(Rb_XbwOl9tY!V=x)rG&VH)8e@zquT z>p}Evxq54mJOjgt$a6)fEe-58doeKl2l5q|#Teun>KW%VLFC04q#5EF<(cG}Ve+*m za-siI<*PyR=PVf*B$7?)r`G=f>V@cUVE!oolli|tME`c9-{}lY3d|3H`q2T%{z6{+ z8lY>0|KpGs*N}J6bX^-MkcO@sVqYOI$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE} zK@!PxisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*e0E^2-)8^)2D0YpvLN>u>G67X2zV*xYM!@6mv4v#0I1}AC;$Ke literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/original_green.mw3 b/worlds/smw/data/palettes/level/water/original_green.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..6697f2839edc0baec036037589d7dc6746935572 GIT binary patch literal 514 zcmZQzxLbcB;7nw0(CY}F)C*CQiy0Vx^Sssv(Rb_XbwOl9tY!V=x)rG&VH)8e@zquT z>p}Evxq54mJOjf^#x+d(%rlw)1v4=G2l5q|#Teun>KW%VLF5^Lpx@?3VtpM+HAw!PB?E&*vPu2a`X4~O5d96zALV~C|M!RJ-){6foqtGAkMJLe&yl+}*RT#*9^pTbydZ-(FuWl0 x=q?1wKVW>o^Z;r;Hu>zpT))l!`we8x(PcsIG1BAp>Jac!%+)+^i!R>~3jj0+eLMgF literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/original_mustard.mw3 b/worlds/smw/data/palettes/level/water/original_mustard.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..bf14a8cb157e0ac7a87163cea6a81c71e0e9d307 GIT binary patch literal 514 zcmZQzxLbcB;7nw0(CY}F)C*CQiy0Vx^Sssv(Rb_XbwOl9tY!V=x)rG&VH)8e@zquT z>p}Evxq54mJOjf^241B;R%5B{Mhp!9fqVsKF$Q^tddB%o5cvnX4}$7NV@cUVE!oolli|tME`c9-{}lY3d|3f8886I{z6{+ z8lY>0|KpGs*N}J6bX^-MkcOchY+oTS$S?89vJqSi@|H(jIQ$CIw;9|w1(^qO&pFE} zK@!PxisN-)?nC$wB>$gLp7}rXb_RQ(e<05u<*e0E^2-)8^)2D0YpvLN>u>G67X2zV*xYM!@6mv4v#07%k(Qvd(} literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/original_volcanic.mw3 b/worlds/smw/data/palettes/level/water/original_volcanic.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ef22bf7bf6a0f10c77cd5cc8c7e52859bfb75d04 GIT binary patch literal 514 zcmZQzxLbcB;7nw0(CY}F)C*CQiy0Vx^Sssv(Rb_XbwOl9tY!V=x)rG&VH)8e@zquT z>p}Evxq54mJOjf~6;Iu@y1_~k_6!XFfqVsKF$Q^tddB%o5P2~MX@+=4c_w*gn0&2? zTgH`w;#E$^U1RXa3K;oxvXHUx@n={sZwja@Xb>)*;Iy{0EX3WDp027epT2 wg&_F{j1QO|K+VS{pBYEXX}Zdc0m80$z%_n&)lN0F>W)X8-^I literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/water/pickle_juice.mw3 b/worlds/smw/data/palettes/level/water/pickle_juice.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a27ebcfd89750ea2e47c9b4979b175c517aa673d GIT binary patch literal 514 zcmZQzxLePoR;kTzo+(u%7UDm-n1SIp&ue`UeYd_|7eqG1TGmI#rwFpky;K8ByERXOXNM4XZ z92i~@d2|Rd$>ZUW8WVR;fJaHPUm7OX8*#>fB``2jX3RVtd;Dg z=W}C7gU!3fV#})Q+8-&DhFw08H(KexReZ8+1Q&z68_2KP<>1MLHuzupw$ zdV5s}--sF2e{yX1*y4q+aM($~VG|mo-J2z z4U%VI==U@ZyPg(Z{2R#r55&Lg#Teun>KW%Vf#h3xvUT6v`WhK}-$;hZ*P6(M{!f*! z2FahZWMGg;HmRRl{{ybSf%&8SPv-yr5dGVYey3l|`d$BknE?ZU>@VcCuK~J7_&*ML zaSeF~P1m)N0%_YB?164V1|atq@`C&lpDY`}#UO8a#D&AJAbp#`eN&KmAo+8aQ-UOt z=M=~5K==yG2>*fP|1-)n|7V7n4dN>>Bm4*AbL6hgHLOFHNB9pUFUTMc3@=Q#f!qm_ vf57;F=>gPyWVgfQkE(d;uGI}zlCVdY1?g8-Jz}bA*XH)zPu>At{#rBulJG_-`$;6v0qTOt*P6(M{!f*!2I)U%$-p3y zY*Ih9{s&MkM81Lfqx?_i|Nap3w;TOVzpN=6{D7GO1Axpg3^VoAo+8aQ-VOQkJo{@58*$M{C`Gy=Ksvw8SI%L z@(Rod|AFK=a@Xb>)*;Iy{0EX3WDsYNXMo6KbKe8T2TTv3=3|pTs^Y1;RySBl!X8}~ WrE8)5+k)PX|) literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/crimson.mw3 b/worlds/smw/data/palettes/level/yoshi_house/crimson.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..e334904a372b04e6ddeb0cc1f681ee7283c5e8f1 GIT binary patch literal 514 zcmZQzxLe;Xa7p5aOoi$UKk4|%#S9F;d0y*-=)3jxx*!rF?_~Ky=7$VOe0A0TdJsKZ zuHG6X&%od#8RFC}a3k!83z^Phg; zsq)nz`E!;G3=+vE^;7GA0QExT8<;=J|78B}57EEf=y!Uzzz>-oJq8Q_vcHhmzUGtQ zFEvSd3~8|Y#5Lp{G<35ZPlsa4gX}Bh1^FdDSvG=;LEiF+3x{7p`Zk06rvI5C?m1^U zB?#pDcpV5|ff?aHkpBOS^34C4w=>uS{R`qNFeCg2;&bG#%{8n;mPhyxBrnJy4h%1d yJh}@(@(&mvFg<{pk4^rlil^>c-C!jNdvsZldz4j=n5x>fxjpxjcR-haAprnZw1M9M literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/miku.mw3 b/worlds/smw/data/palettes/level/yoshi_house/miku.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5d3b9e81144a649d6b51d4f089eb7eaa7bb8cd4d GIT binary patch literal 514 zcmZQzxLcpBn&#vYogXBUJfnDWF$2SIp4a*y`fh!_E{KH4J6U=}dqji8S6BV72hp?T z>a9WY3=Bs^jwz^E@dHg^VE7N@dqj&d$TQS4&S!$iXY0PVE%uX0o&(eck*_t83;mxe zUk#E!XUV`Ik!(^wwf+ZCFGRk9`J?<#=KuZ>{o9Rxr>j_bL~o8~zyKio3wiBpD%EVn z|1)DqgWV^tA@AU3$>X;%2~!?qUm-8ZFY(E;5nK%NmPcGT{0h>y8QeDonFo?TXE`MZ z)*;Iy{0EX3WDp027epT2 ug&=tqr8i!i<8jL$Rq@nas~fB&VUMmGrEtBV1!;Cz7q literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/mogumogu.mw3 b/worlds/smw/data/palettes/level/yoshi_house/mogumogu.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..cf76396c8fb0360e684214e03ce05fded5fa2e92 GIT binary patch literal 514 zcmZQzxLfb*xiQhCsIu%Vr%`nMbXPJeC@l$qkqfB``Ezjv{(i3ptQ z7AK4$4R+r}n<(4oIzGNvfbK&FAo~h=L4Ju(mW|+IkheVI!r@ntzRlpi>3?R3dqD07 zxn9ZIN%G*rV$Pxkp*`h^eYwo7;0gc?WcPi6Q{FsD)|( literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/monocromo.mw3 b/worlds/smw/data/palettes/level/yoshi_house/monocromo.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..8e1529f5149896fd2b6552cde7e7e59dba5fb36a GIT binary patch literal 514 zcmZQzxLdEOnyvfZw%AW1c~0@>0W$*z0NG#2YhMF#KZZ1h`_NT@>?`C2`6WJCHiC;m-tveGhhIVZ zHiP@7|AF>_*fPAv{nxLF5&f5&pw2kMJM5JT~_sn~zQYsEViV dTHRnJ343%|kb9I>kC>|3wYfd_lXt+B2LNGMh~NMK literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/neon.mw3 b/worlds/smw/data/palettes/level/yoshi_house/neon.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d3e183a770fd5c5a6e7721f0ef3212d8030f2ea9 GIT binary patch literal 514 zcmZQzxLeOq!vF$ca&j>P!*8C~`XKsleZ4M-gvx^zfy7r={jUenv*qfoL4pd*U@15u z4`)Fb=<;%*$O`V(pR;6OkVrPEpIZN;9wgI%V&2_)1?KHWztcf74^U)4A|UtuXD;Nm zhxh>^ghry|3wYfd_lXpOf!&m@77fJj8 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/nieve.mw3 b/worlds/smw/data/palettes/level/yoshi_house/nieve.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..07db0b1339ddcd516ef120e3783183d9ac0b80cd GIT binary patch literal 514 zcmZQzxLf}|bZg%CGRykv`n~m&iy0Vx^Sssv(Rb_XbwMOV{(b1)`tM~R@zquT!Rltq z)mtOvA^I5@{?{`w?5*d>%`QAwX;=r5&(?i!TkI#1JO`*7B42AF7y3U{z8WNd&XR#a zBH5&VYW)wOUWj}H^GErg%>VsC@(c{yjee(pFWX!HfSCaUfZSKeYhMGiA6)^={kY^o z_7(Di{1Trm8^OgOZ+XOp!>=HHo56k4|3Ldd^5-n41c6*1uLE-*!haz7|BUj?|CzTl z*fT-o6_^qJ1IcsbuFW;9LzYMQ4#(3v!RL>Jd{_yEeDye)10J@(r;7;SHAz literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/night.mw3 b/worlds/smw/data/palettes/level/yoshi_house/night.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..7cc0122339b0e41c8d372496b934ffcdf9743ac6 GIT binary patch literal 514 zcmZQzXpupNlZzP`e)GK62hn%y>vb8BKuLjAVvt(e9NH(dT zTK@y67b4%l{89cV^M8Mc{_RG;(^)m$-5xMAU;vQ)g}nAPTU;&ND*Z5|!R`~+kay5@ zT^lKohA9uSuaFnym-uAa2rdSB%Ofrveg)~<4DOr$2igacKW8~52;};B9SC268R0*W z{C`Gy=Ksvw8SH`n1@RS_5&i@5Ida$L8rC7pBm4)F7i16zh8IL0-Gw0e2aFGx9ze~< lCVy1LQ+KUyu#$v5x-7^&%Bn|9Rqfi`p8Lr=pvy0j0RS0@dUyZ; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/nocturno.mw3 b/worlds/smw/data/palettes/level/yoshi_house/nocturno.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..de764ef9e823f6e40030de28d50f132a76c5ef1e GIT binary patch literal 514 zcmZQzSf;Z~XQ>K|oLtPn@SEqgK8U_sU$4u6B;RbcOa~;sy6S&Dh@LH1Zw*q#z;IHb z*fQBv!mZhgf#E-p-)tquAkR?GIG+h3pRN1ew%AW1c@9t)M84KUF7$t@d^Je^oFxN; zM6yZ!)cPMly%6~Z=8y6}ng9Dk^lvx%ot|vkZ1sSd0Rw>SFXXka0lG%`KMr|u4S5Gm z*R_!XX&BnU_7(Di{1Trm8^OgOZ+XOp!>=HHo56ikka-~YoU@z~1af`64ur43jPM^w z{y(EU^MB^;4E8|(g7^x|2>*fj9JyG;SV_VjT^8gXWz{35s&;K|&;8^b(B+q^008FUeqaCq literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/original.mw3 b/worlds/smw/data/palettes/level/yoshi_house/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..051d266fbd86097ec67979f677f41cccc748400c GIT binary patch literal 514 zcmZQzxLfaJ$q{reODH)sdq(l(Vg`oaJg@aZ^xgV;T@VS8cd}GqK9>a&UtRUT9z@TU ztG5QpGcc^@t<-ye3nE`@A{Y8U zRlXV|f6kJDK_c0tero*>pk9c41M^4upUnULA^Nu){Z40KQeb`nG#?#+>@VcCuK~J7 z_&*MLaSeF~P1m)N0%_>FA@&vWg8UMnEE~baAa8lZg~P8PeVf63Q;>Ne_nfnw5(IL6 zybjEL2>*fP|1-)n|7YILU=Q>!y8l3Yj@-4mhIPpD2>*fP1sTMF;RTULcOgjr0pkOv o2T=2|$sbkm)Lp9^tR!KNE(>yxvg#32Rl7E~=YH}I=<@TU0RyRexc~qF literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/sakura.mw3 b/worlds/smw/data/palettes/level/yoshi_house/sakura.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d099ba93531275332326a6a5b04e9d7d76a46d19 GIT binary patch literal 514 zcmZQzxLa=+I5qM|tWDbdEcxQe#S9F;d0y*-=)3jxx*!rFZx|?B_9GS~zPjpvJ&2wy zS8olHXJB|KDyTgrV7j?%83V(AAYZmjj6t5Eo^d`CL_XbsFLqO^Y1Yjmn0&2?TW}pe<81Z4bU~h z|8dBRYsfojx~`2BNW;($wy%&EkC>|3wYfd_lXpOuFRupxL+*fT literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/snow.mw3 b/worlds/smw/data/palettes/level/yoshi_house/snow.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..4fea26a690b26e3b84314d2871e9fc2a1a769840 GIT binary patch literal 514 zcmZQz*jt|I`X+W$(fKsf;#t*`iy0Vx^Sssv(Rb_Xbs6CDnXYrnHx+@zS6BV72hp?T z>a9WYOC{b*Y}YJ`i*}n+4&l!!FP4eYkui;z0`VCbJWbEK7KVLKL&)FEuq~AqFPPwxG3G?^jzr!KsZ8!RzUKBT{d==0>bO5rykk`JZLe55i zt}(hSME^_^8AnamwUGj8*yT3}-jbT<9G@&3!NnkNdBlaouONM!!F|*J%n<$WT(`to z=2w^B31wjTU(djxz%0ff&rr`ep9vx#$rq=*Pux^+o)S#H)%P8)CkG=!e{i OMMmY-K=-49s%ijSagbO5 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/strong_sun.mw3 b/worlds/smw/data/palettes/level/yoshi_house/strong_sun.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..7c671aa368ed47023734baf45ab897f7b3cf5f5a GIT binary patch literal 514 zcmZQzxLeLF$2SIp4a*y`fh!_E{KH4w;Qf!*2@Noude!E529zw z)mww)85llu-Zopw6lc7inStRykiVW;j6t5Eo^d`CL_S;hy=}3dMDiSo`lBY zg#Y7^7uS$?&~#lJDUgP)8)9D}FUT+P$+8h#4DyyoTsZs+(zhAhHwBpoa?d%-DM29D z$Lm1&3P}C~$^U1RXa3K;oxvXHUl3n`8R0(=pCfl|u3;UrJi>ngPyZ1P7{JayOV1}jO}qsxNaqpW(wRMoD{?YW=41G@bGYyjEIe}(`6 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/level/yoshi_house/sunsetish_grass_hills.mw3 b/worlds/smw/data/palettes/level/yoshi_house/sunsetish_grass_hills.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c51bdfc6a7d3c0784d33fe3b73dca7557de88146 GIT binary patch literal 514 zcmZQzsLpm1d?~qB>AjnAa9RB1Vg`oaJg@aZ^xgV;T?V*3L={MUb=CiR5ItM2-Wnv& zz+l8Ui_uNcUg)JH1H=D%28NfCVhr*O^^EhGAo5NPewpkk_@S>U>)j z2w#C2;Xjc49mYFM)y&%%WPtt!@fDa6{sZxka={cOgiA s0b>JG0@QqL@<&xXb=T?!D@oX+%Yxjata`*$)vnF$xu3iPx_pHd0O>G!F8}}l literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/atardecer.mw3 b/worlds/smw/data/palettes/map/forest/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..5ce855399b9857bb393bf4cb8002786baa090204 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3OrBfJ#bm_%g&D->QtH*i)c>D(f3bWGNZwkhnBA7uh|`O4GaHBx zF~45dTmPdw1H*oxPYQYKng9F8C(A}~G00mUapCYQNZ)2~-}FDwe31M*rnR-7n6Gfy zNrCjE0+75IXB{g??p@vZWK=m24Kp1G03qq}3=Gl? Sx^AE2TE!s#!88GXd;kDyk!$P# literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/burnt_forest.mw3 b/worlds/smw/data/palettes/map/forest/burnt_forest.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..58b0648f18e36da4365a6d545a70ecf5a73d739a GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3K=gg*ZFCx z?h=uZi!tW}+h-W`Ep1M9tC+X`M|Y6=1TzJ1P1Ptrt6*gP|C#Sq%h!P9%fmEPx0v>u zg6skDA?DZXLiB^&P{>=){NF!5SvG=;LEiF+3x{7p`Zk06rvI6Np#hYCz__-yfq6TF zJrhVjDgeogF~~D;K=ggTb6T} zu?I@X#h7z~?K2GemNuulRm@xeqdQ1_f|-Ihhgp=LRWP#t|IGKQ>+4b>ov!Kp1G03qq}3=Gl? Qx^AE25aEGt2LAW}0O1;G(EtDd literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/halloween.mw3 b/worlds/smw/data/palettes/map/forest/halloween.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c3899751bf31b65cccd242e0402f0b9ea5c3c3be GIT binary patch literal 514 zcmZQzKn0VF85sUEU-p??+-1rP4qYC^f3A~l``))W z?t9++Pu#k}=Dx`X5s%x)&USBmnp3P#rd zpZQ+3d<{rG-u-66T*LcnAR9n@i23!p5d9$c6!O+H|M!nimW|+IkheVI!r@ntzRlpi zDabsK`~$|dwGGVMf$j$BM+G2xkZ(A0@9M@UqsoDJAo&KS2h1F~Qfildu**NFQ>bsK be*lD}%QG-YGw8Z~jzfe8x*4ePfi4RGbnI~U literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/ice_forest.mw3 b/worlds/smw/data/palettes/map/forest/ice_forest.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..32d62829bd5c4e0a1a2f6e8c2b46efc9275d8f9a GIT binary patch literal 514 zcmZQzKn0VF85sUE7spL5e(#%Y`&W3-_OWUx1ad|^PhT%ytd=|(BPy}sh#z~)gaaWk`;D#;RK=ggTb2hW z1y@VR#h7z~?K2GemNuulRm@xeqdQ1_f?06Vd!;Bpt6*gP|C#Sq%h!P9f#xR7H4Lr> z*#qK3%&*sl=m)u>khh-szkhtPYy=mByyX!W4!?r*Z3g#E|1$$a11SH1acyk_^L7S% zCXjwq0FoDDkZ0h?y{j9aj4B7>f#e&Q9x!v{N~smcVV8eUr%>Ne{{RR{muFy*X3%x} N9ES)GbTjbB2LNimaKZop literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/lost_woods.mw3 b/worlds/smw/data/palettes/map/forest/lost_woods.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ba9486b627ba4eeeb266c7448b718355e9a0e136 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3p>D%45QYyH3>X%a6ciW)7z7xE7#J8B94Igt2{14SIB2jJ2n@_C1okxo149b~ z3pbq%CYMY*$+CWZ|DArH0qW8bU?v@PBrnX;))aur_ZQ!M(7kikr|YPMneg1R^S$z* z?9zsWZqJ2}>gdR$)6q$Gr)I*CR;}z7ScgR3No92uhPAS<&zV*|A*TRXT4l*U?sfm3 z_gwNn?&O#D=r|So%ly-|^mB$$>A2@Sq^AE6=6PB5xlaxGnE;r}+tCT*Bzf+yi}~{+ pZ%(4iL(N!~`RJIJytRk9vgjtl57*L$T>sYvI9T99gg$qGBWaD}=Dbq~P$Ez934 zCDcmD#h7z~?K2GemNuulRm@xeqdQ1_f|-K%d!;Bpt6*gP|C#Sq%h!P9tBu|(Nh*G0 ze5Sj?48(_+U#|<%4{}2xZ$0yW|M+Ct2rdSB%Ofrveg)~<4DOr$X9k7_Q2qhq+S&%@ z?F{xzApNKSBrnDw&%lv;S2sQxRSv`h$u}@PVCKk`QnL=nF8`oTp}wL10T7Zd&%hwf QpzHQI4iO&cX5fzx0My@Y{r~^~ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/original_special.mw3 b/worlds/smw/data/palettes/map/forest/original_special.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..57fcf7dea59b1949ea5d66e58b10ea4d7744f92a GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3OD(0>K(H*2d!A!yXy;4<>RWP#t|IGKQ>+4b>ov!Kp1G03qq}3=Gl? Qx^AE25aEGt2LAW}057X;AOHXW literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/sepia.mw3 b/worlds/smw/data/palettes/map/forest/sepia.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b265f978060dd60e3aaea39f9a833111d1e074a7 GIT binary patch literal 514 zcmZQzKm|7O3=DI#ZQ^a>E1X1)Hmjn_GBAK-{?~(O5DvFPmk05A1t%(s8qIK$4hN}3 z2es*DW}8)GlEdxpmqX+yDt^%|w+pwkjt-CnsVn^eur%I$W#q3XY1e!qMZ*!?@*%I!9*el?r~bQMTF z#C$$ch<=b8CMnv)-!HGtE|kq+5jA=%a9LA2yim5>Zf-UM14v$9^oy>jS%74P6G%TQ z0Li}*(3cF547aNVsz(P9^TUBY2)Bc{7gYsF-pnlAZid@UKZt)xlV@Py6BRYO>4yjp JRQ-hF0{}_CVnYA` literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/forest/snow_day.mw3 b/worlds/smw/data/palettes/map/forest/snow_day.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c76540325baea28db53e1f9d91a6201488a4219d GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3%#7e1I1Ko=bKwOrBfJ#bm_%g&EAZ`WaM{jjsPc^ZsJ_8j$?GYU^;2{v#Hj!0I99 z*Xw%ge{^SH*bnqcA#XkNfB*Po*$6HMdCMa%9DW7q+YIiT{s*cD$-iS-TlkK7{ouACg6_`0H{H5f&c&j literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/brawler.mw3 b/worlds/smw/data/palettes/map/main/brawler.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..731696fcfc8204cffabd03f308427fc880a45460 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3o!k`;D#;RK=ggn-_dg%K9GvBM0uK~&5tM<(U=|5uOAq?U} z%&*sl=m)u>khh-szkhtPYy=mByyX!W4!?r*Z3g#E|1$$a11SH1acyk_^L7S%CXjwq z0FoDDkZ0h?y{j9aj4B7>f#e&Q9x!v{N~u|gW0!wWr%>Ne{{RR{muFy*X3%x}9ES)G JbTjbB2LL%pZ?pga literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/cake_frosting.mw3 b/worlds/smw/data/palettes/map/main/cake_frosting.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..aec0fd7e40c9db6eeb5d2ebf27bebc231a6535a5 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3Vfj+|CvFi83uhzn^WB?=B@wH9mJoK6jgi=B#)y1Kl8n6`5KTsO#cxJ4`C1=Vt&0Y zL_f$4g}n95|NY~WWh1y4Xn(W@*gSv93Ol=Sg}MiI55W5GRa=K^ z)B)wq|1*P3GYtBcHmAB(%v=AXJBY6#v_i;37+wE==6luhH6VGI{v#G1!XQ4x{CZu8 zevlgqdFz?~`^P8CMsP96TOM)Y@GD5)W^msWWFJWW0pr@*2IlPy_DmrCr~o7{#vsqY zk$YD+J{eUG!~@AUFg;-A$dyvF4#zJ4piZH_q5c67k}l7{AkCob_Bjp_9_VJ^j}HK& Crfz}& literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/mono.mw3 b/worlds/smw/data/palettes/map/main/mono.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d4d0f72d29550b6f60468a93058340f9db106760 GIT binary patch literal 514 zcmb7>ACtr|7{-GO3&BEVVWCh66ap)OLZI-%LSUs32owSbf&BuD#eRk2;^|?r7%eUy z?#(lSn^bP@oylaL`MrOhykRU4X&IZmw9@*HtGUT3V<7K4ZXD8k4t+R11Xm*!Ju>$Z z2GP!qain8Nd~RMB($DMX2Uo~;^+?7oQnagqj)62+&+FtT4=vj7+vFEH5?nCGmG7k8 z*T3&EB>yo;i~6r3`Az+|GS$PwLr5D#7+l>Sx!TG_7QL%|hk4-D`E}t+SH8o%JixcI pI_rvb7;*-c`jZAve)Ak~(UKl%HR-+otNGnWUjNsB`~ZFMY90Up literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/morning.mw3 b/worlds/smw/data/palettes/map/main/morning.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b2fe88e2cf89d9ad5e4dc2e3e714a09ce015444e GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3K=gg$Jg(z z3@8oP+;9G$86D|DTroZU{TGfLzf5fpX+4XzV|JT z`=0l|9#j5f{eDJ_wGSIbk;rOV_AOccz;K8K z#D|z)uj{S<(Vc<8GM#~;khh-szkhtPYy=mByyX!W4!?r*Z3g#ELH2>J;i5>K_0h>GBK=(hRz8 QpW|A^ApXHL0e^e|0GO{{UjP6A literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/night_time.mw3 b/worlds/smw/data/palettes/map/main/night_time.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..13a5af30c2e5e2144df1c975df6449f31d7bbdc7 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3FZHL`v$J=4gEkLUMB`fUg!WHTs)I9*}H_zve zdv3AWaliRSA&|Ub(6_WX)vaRQ`XAjvd~L%`uH`}K`u{WEtCp_;$;0#?)zCBp@ge5d z>q7K{+)&6{&-~v%K3O(`i$UJ`=SKPmvp zi!sPEaOB?AjZa3E1Mxue4NMQ1IdY}cti!R(Kd4ivZ>WC&grv(eFi11#x_ypAga^7A H_~Qcra8Pkk literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/original.mw3 b/worlds/smw/data/palettes/map/main/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..cb655d882e715528ba6092ec5ea9ca66a0d39290 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3ZBBKon795%cMxC5sMO9n99{o^=6luhH6ZzW)z;x4{YNZ3gh70W z`SrRG{UA3K^42r|_m5AOjo@OCw>;v);a8Bp&EUQ%$Ucz#1ID$r4b0ma?3qCNQ2|I^ zj6t4(BloUud@`yWhzF8yV0ysJkt?NU9gbc8L7hTwk0y@gXd9{r{QoRm<0aq7K{+)&6{&-~v%K3O(`i$UJ`=%iKK=Kb5*VZ;LZ)dP)0_jHuAbBwc zc?OQ$ySnkosB$14NWOvT0W(Lgl$v!ocKHW&3iS>34}g$#c?Je)23@z$aft9hHv@ls F007$Aa)1B; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/sepia.mw3 b/worlds/smw/data/palettes/map/main/sepia.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..3a6ece7743e941b78e5aa7daba2310af6678b62a GIT binary patch literal 514 zcmZQzKm|7O3=DI#ZQ^a>E1X1)Hmjn_GBAK-{?~(O5DvFPmk05A1t%(s8qIK$4hN}3 z2es*DW}8)GlEdxpmqX+?tA5c9x7+EquUOOwq`FXchFeVXWlb})n|>hv{qk@-QKOx1 zbF=4WgZO4vzk=rFU)G%A1abufgQ!sjkcF=Qe);|KO4ugxx$&0rBVdMj{QQ#!m*w%iV6A4pzb^oy>jS%74P6UaVP0Fr+rpf4F9 y8E#h#b|0qvB*kz$kUYe_nDS<3;dV3JZu&v|OPV|b1D~j<(M>-@c%bSh6dwSAuw_>O literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/main/snow_day.mw3 b/worlds/smw/data/palettes/map/main/snow_day.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..1ad307f078dc13fd67a195e16bc3b035c4f607d4 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3D^c_q_l0nDQUtVt?vE^2c>P zWP((gqycRP%lS)I*x7|E)IF$s09OlAQP2F}9Yh-jeM_5D-74m-{}IU7ar}_EDeY>y zJW%&>9gsXk|9|Ft)$%nUK15!}@v7CFXoP&dE<``b&O%#}bH$E9v4#We=H!wY5=E#*&!<2=Z iU#C#tQ2zi3F;#%=$0N_cAkCob_Bjp~9+)QJj}HK$18D^S literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/special/blood_star.mw3 b/worlds/smw/data/palettes/map/special/blood_star.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..bc778b202b99d347451c6490fd02d5bd54e698ff GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3K*{)U3mBz zBpKp0?Um}8|GR@sGYtBcHmAB(%v=8>kgwzTA#+pO)pU7=|MkapK=Kg%|C#Sq%hy26 z2g&O=UbUJNjgYU`h3E&_S;z}C+dn>8HiC;m-tveGhhIVZHiP@7|CxcI0hE8hxVE-| zc{_tW6G%TQ0LhCn$TM){-qnpyMwJ8cK=KVt512V}rPQp$vCBWGQ>bsKe*lD}%QG-Y RGw8Z~j)R2f#e&Q9x!v{N~u|gW0!wWr%>Ne{{RR{muFy*X3%x} N90v;zOcU_O2LP5hbAD^c_q_l0nDQU%)s!~}EAggC z{iz4ZAJ_Si2~uT})^86{?=M+lXBVze_n__pSiiE^6TKGiQ^xho|J^|<4THX=&8cn` z^Va_e+JfBkVCkUT{Hf98AD@--knL|(`7s@0rmgnYd&L_f&RLSCTR z{_)AO5nK%NmPcGT{0h>y8QeDog#<|c0pr@*2IlPy_DmrCr~o7{#vsqYk$YD+J{eUG x!~@AUFg;-A$dyvVl!cmKr%>Ne{{RRvRe*bm2ng6?kR2l|-OPf>OD(0>K5y;nZ{E)dR?P|I_!~go@Iv{z7{{PJPs^x1S=7Z#Q z9Islq7K{+)&61G}}KuSvG=;LEiF+3x{7p`Zk06rvI6Np#hYCz__-yfq6TF zJrhVjDgeogF~~D;eRLT9;MN{NEj9o?+0pv^mwSV&3{6fqWgu51E_NuBOW~{I5T*1Cod6|Id7{TD}IP zA0n^gc-3l7G(x^!7os0zXCW`pZ2$OV*$6HMdCMa%9DW7q+YIiT{$~b;22lP1d#! z-;kEz%D_<1{NEj9nqknlv^mwSV&3{6fqWgu51E_NuBOW~{I5T*1Cod6|Id7{TD}Hi zK1g22@v7CFXoP&dE<``b&O%x4WRr3#{R1E*U7mqK RnnBm?a~v!@FipT89{?(kc+UU; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/star/blood_moon.mw3 b/worlds/smw/data/palettes/map/star/blood_moon.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..42f155ef33e2d6114d8d27e32bbf88c02410d803 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3-GjOZV0m4Z zU=Ax5dlo4Md9Y!ILEqBmRJV$G>wk1-VDRIB=$B`Zbkk%(=>N}ruUftaVg6~It5$QO z5%TrA5d9!G6!O+H|M!nimW|+IkheVI!r@ntzRlpi>3?QmXaMCOFs`j_VBXGP&jfY= z3IUQA1G{R1E*U7mqKnnBm?a~v!@ JFipT89{_h-X&?Xq literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/star/mono.mw3 b/worlds/smw/data/palettes/map/star/mono.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..15d33bdf3a2378819ebd2e249a570bab1abede53 GIT binary patch literal 514 zcmaixudc%&7{!qjS0E>9CMpmJ!~|ld0)ePNT!EOWKp-j*6^Ok+Wo2Js^2)wKW#w$; zAkPbK9HVoN_wLXOS`B93ATn2M zg}NTCH~JRA*Fvbj-`IwF>QcPtgRy>=LM;}2cetOX?s1lG>X1#S$9W9191ObfJo#Dq zysQOy@=JG%=>MJHb5!|op^_gpdx7ZKdfxodYAt5DZ#)n67zhDp<^4n}%|Z?TR%r%S nYG!oG|DX55XmIqK3x1)D9?W7fZ?10~`=-@m-}}h@-~QtTOEYd; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/star/mountain_top.mw3 b/worlds/smw/data/palettes/map/star/mountain_top.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d2b96b0e3dd2731c9f1deaf3a467747982cc250a GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3yYJbTJJG*d&x(9U+!1C&@ zetCh_GwO@-XHLek|K7^E3=-9E>` L!UNL;{P6(*Ay{!c literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/star/original.mw3 b/worlds/smw/data/palettes/map/star/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2107a5555ebabc965facd1a3db5227009de6fa6d GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3$8|vR5dHs|?^Vm!Ak5csylOQk z8X;e=3(*gf#e&Q9x!v{N~u|gW0!wWr%>Ne{{RR{muFy*X3%x}90v;z JOcU_O2LQx1c!>Z2 literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/star/original_special.mw3 b/worlds/smw/data/palettes/map/star/original_special.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..d2bd439c97b52518cc44a75d80215d2e25a6b840 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3O!=v&&H>Q*st{f|ICOuszC|N7@LAbE)X|IGKQLek|K7^E3=-9E>`!UNL; H{P6(*hNO1x literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/star/pink_star.mw3 b/worlds/smw/data/palettes/map/star/pink_star.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..55f68ecf6ce2615a7bcd845c124aba707d75d654 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3E1X1)Hmjn_GBAK-{?~(O5DvFPmk05A1t%(s8qIK$4hN}3 z2es*DW}8)GlEdxpmxJWr3YeMgbgNA-x7$|?QC}!K!!0KHvZk5YO+S!4pXe9eefht` zY~uHogG@8C`V}-U|FY%`Cy*-&Wg+_YCI8pI6#&UY^jk;YFW&@oKg|3>*_~ds*$8bx5gV6t<`Cher4MP67&Q+^9(Fpl^U5I{=8wz>r zng9F8C(A}~G00mUapCYQNZ)2~-}FB-Ff@Sj4;a_hHZX5zuxA44M+G2xF$Q@Cj@-Mt z@yV!iARb7*f$0G=N3N8bbvSnU2XzYd4fPLzkaT$l25AOex6g5~@W3-GjOZVEy~U?n}rp zvDvtMoBju?2gyHRTwB|~yq&?G z38WttfaJv(XT7St3JG*d&x(9U+!20i1+qy+@ z0Oif=bwMf&gTAHBscseX*8k`Z;=4++D?L{N>5t+7@ge&EGvBM0uL1GzRa=K2Gm2%a zXRc>P$k*#a^n=_`$Xn0+-#S7c6Q+kbr0$ufc4+2{;z(Y z2`Fz~uM1KEQQs=&t^d&-#D}N{>A%l};Qv>@S1n%y;@_*b4nJlj!BEd!&y0|lV1Vcc zxuKA^p83Cje6nl=7lXXz5f=`>g7j?$_f7u;)q~_8Fs`j_VBXGP4>liU1_FTO#Teun zICAgm#wR1hVJwh*1JeU$j$A1<>u{JTf(Dykr%>Ne{{RRP;xHB=c?Je)23@z$aftAM K>4eev;{yQC=xv+; literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/dark cave.mw3 b/worlds/smw/data/palettes/map/valley/dark cave.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2b6c0f57cc614371d8f7671f472885839564eab9 GIT binary patch literal 514 zcmZQzKn0VF85sUEU$UHBtfR`Mcw7QimVse%@g>Xu^&pyo;gV%e9lAV-|6C{A_PuX$ z-1j_?N_6nCem^5a-G1f=%zx@3^2;v);a8Bp&EUT2f1rAh`~$|dwGGVM8SI%r`cVN$ zUW`GWfg|^>ZhSJT9Eb;!Z(w@B%#kal2J<1ZDzN!=3iS>34}cI^2EryJ&%hwfpzHQI M4iO&cX5fzx0DOOM5dZ)H literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/dream_world.mw3 b/worlds/smw/data/palettes/map/valley/dream_world.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..bcf8d9514213ab9280377dbbff6b92a1e9de38d9 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3S7c6Q+kbr0$ufc4+2Hc8U; z1j?J&>w;7m27ODLQ{5`&t^d&-#D8NTr(y!|Y;zRWRXTDc0UjyRbtF{h5W^}@! zp1GbGAz!Zx(GPM%A#XkNfB*Po*$6HMdCMa%9DW7q+YIiT{s*cD$vc%Id%7J(w`39y3%pAE=YS!V{W07BB`85pD)blpD3 MA;JUQ4E*r{0L7wk*Z=?k literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/fire cave.mw3 b/worlds/smw/data/palettes/map/valley/fire cave.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2980210dd233eaa0bfa804389754055f6a03dcb9 GIT binary patch literal 514 zcmZQzKn0VF85sUEODRn*mKD9lvxNgymVse%v6RyPdJxUPAf=R3hb|A|KiA2&eeYWw z_dO4!5*>W3-_OWUx1ad|^PhT%JeOpy;zdn87VB_`T7St3JG*d&x(9U+!218!2MbEF z=&|fKuh#{sFbw*ZHmAB(%v=AXJBYu5;TwY$BS^m_3y2TV|G)TNwR{bT&&?vp0@7d4 zT+fV<-_Hoq4{}2xZ$0yW|M+Ct2rdSB%Ofrveg)~<4DOr$2dW3jKVV#2+rYe?!JY}E z9~FS)#TeunICAgm#wVl7fp{SK2Brth9Jx|zQcBq6AJi$-H`G4>Lek|K7^E3=-9E=5 L!UNq5{P6(*j&WwS literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/invertido.mw3 b/worlds/smw/data/palettes/map/valley/invertido.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..37cf2e9f50d24ce7db77c7a9d4869aaad1eecbc5 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3w;7m27ODLQ{5`&t^d&-#DC+d7xpm;q+bK<9*F+`%=fD0Ye4e%s;$G18L1i8 zGuJbN_z?MeU5I{=8wz>rng9F8C(A}~G00mUapCYQNZ)2~-}FCFJxKlmp>o6^6h)t`s6eh*Q-MH4AXXq^5C}vBq5@G9fj~qcRv`KVk&*d@l`HobA|qp0 zMpm|Shw1FFrqegWFqiW#T!5%96^Q1ff(of)`f{o%5y5b}qfKnVgS{l4kW-*|G{u3qB`lpr!fxnA4&rhZIDDk8*F} zXBxWrZ|5zS{J(x=e{jKz9~nle!j|(8oBoyQ`*Bf+J~pYF6A`%Jd&{&Hse`{R@cU74 oE1bmG1voSoe2%YFaH@!m|Y{_!5Q|F8e}0n&(NlK=n! literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/orange.mw3 b/worlds/smw/data/palettes/map/valley/orange.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c9f6ac2ff2e90232ae61bc93765512936b7f356a GIT binary patch literal 514 zcmZQzKn0VF85sUE2gpt?)|2EGJuZMM%fK+XI6!u%9f)RN2$0RGLzf5fpX+4XzV|JT z`=0l|o`K;m5N}hwW7W-ZiQ_v9&~zYQfmw_}o}r#`J`+g&WWn!(d*s#%)H#EA3=9yJ z3Uv?a9st!cFx;!Q=M-RAE3n_ZUKhlFD-s}jSrnoR#D}O~D{$8g;#>&-Kl8n6`5KTs zOn-{3hcH5Yy{@S7c6Q+kbr0$ufc4+2o*J%E z2b4Fj*9ECC4EmNfr@B?lTmPdwh_7bYY5T?#q+bK<9*F+`%=fD0Ye4e%s;$G18L1i8 zGuJbN_z?MeU5I{=8wz>rng9F8C(A}~G00mUapCYQNZ)2~-}FCFJxKlm5v0G#0K|vr|Id7{TD}IvzgG>C$<}Q$ z@DN7G*Xu&`gWOQaThIL8KR#JDf{Q`k@`wwEUqSjdgZrlcnSr4Jlz+guwzh$JJA*wF zNIxn7$%`?_GjQbI)s0U^l>_lW@(oN6m^pH#)U3m?%Ri`7sBfr$0EDE=GcZUq=(>H5 MLxcyq8TjJ^0H*kEoB#j- literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/purple_blue.mw3 b/worlds/smw/data/palettes/map/valley/purple_blue.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2342f0acd205d0d9fa52a0f1a66488a4d74c4eab GIT binary patch literal 514 zcmZQzKn0VF85sUE_XSKYo@mjl(J6r{%fK+XxG!K!6^LeF=nKfHLzf5fpX+4XzV|JT z`=0l|o`K;m5VLZw;7vAjaqkQVnhxYEFxQ&Mh5k>KuLh}aiD577EN=Mj$m=JmQDzPD|@@7F{xo4$Za5ggi7=t_mNA6wS_+(T$5Dz5Z!1REbBUehTF95szgF1!! ehWZCUNV+@&gEWJ#+vm7eF^GRKO+bwgba?>wk8^(j literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/sepia.mw3 b/worlds/smw/data/palettes/map/valley/sepia.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..aa5aeb51d38b312a1873ec1334fd247f52534e2c GIT binary patch literal 514 zcmZQzKm|7O3=DI#ZQ^a>E1X1)Hmjn_GBAK-{?~(O5DvFPmk05A1t%(s8qIK$4hN}3 z2es*DW}8)GlEdxpmqX;g=$e`B^wJaER}4{GC_BR~Ci$|anb}Q0kjVXVn|M(pJ<++@ zW@aG1nbohLdHI($XE=de!N8y=I#Kb9E=a$q5r_}bzc2rO`6jUYVfsOCN670-LiB^& zFiFuS{(gCFcA;zri>T3CfypDVoIv_f0Z9IhfWBma zWVl@|P(3<;m>+Hjl84xjssbc$W)^NY!|kRY#J{A;GcfRpiW=SYLxcyaenRm90OQJG A%K!iX literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/valley/snow.mw3 b/worlds/smw/data/palettes/map/valley/snow.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..185d0d42c77a14450bf662802f4189397e97f64a GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@32sC!WN04)E% z{&u}VeYD$)MDHq)d|;S(_TFk9`+V<9;UNC{ywdtRK>7X224M9N{r}_d)yvm_zV)i$0y51a52bR9&zFDD@fmF zaNqPlGeo{cYi(_+v8muZC6Inp0MZ|&Q){^*_O5O`*aw*MEn2C@D`KV8tby)D2cR%` kP^VDeQ2zi3(Pe?LK|r2?L7G9=?Q>kK7{ouACg6_`0M-+IhyVZp literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/DOMO.mw3 b/worlds/smw/data/palettes/map/vanilla/DOMO.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..595638077050ab67064d7ce25977b4cd92cd9e71 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3-GjOZVEy%e|7Bzt z+64BSFO3DMFbw*ZHmAB(%v=AXJBZ)Pb3%k$7NlPW#ASf!|Id7{TD}IvpU+g!D8s-l zThCn2jF7L_h3E&lp^&$p`M-aBvTOtwgS_Pt7Y@II^lb+BP5%ScgXAADuB~lg-p*jp z1k#TRK=NV?@(digcXi{FQRP59kbDEv17?m~DK+bG?D7xl6zUu59{?fg@(c{p47zTg N;}GG2ZU+AN005Z&YH0uf literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/aqua_marine.mw3 b/worlds/smw/data/palettes/map/vanilla/aqua_marine.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..b382964de02c670e2c72b816639e5ed58d968d19 GIT binary patch literal 514 zcmZQzKn0VF85sUEzi^#gyxh=L^@$9sECa*j;uo&}>p?UF!wc7(I&^su|G7@K?R($i zxbJx&mFVDO{eDJOD(0>K(H+EhWNGGkA_3CxZVKW<^#5nRS1n%y;=}aUGuJaC z=HHo56k4|3LL1`3H<^Ya5uiGuShM^rHfh zycmN#14r&%-S}iwIS>yd-@x>MnIl(9?S(6L`3H3h^$qn8fRJ=~1_o&cUAND1i10u+ I1Alw~05OSj4gdfE literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/dark cave.mw3 b/worlds/smw/data/palettes/map/vanilla/dark cave.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2b6c0f57cc614371d8f7671f472885839564eab9 GIT binary patch literal 514 zcmZQzKn0VF85sUEU$UHBtfR`Mcw7QimVse%@g>Xu^&pyo;gV%e9lAV-|6C{A_PuX$ z-1j_?N_6nCem^5a-G1f=%zx@3^2;v);a8Bp&EUT2f1rAh`~$|dwGGVM8SI%r`cVN$ zUW`GWfg|^>ZhSJT9Eb;!Z(w@B%#kal2J<1ZDzN!=3iS>34}cI^2EryJ&%hwfpzHQI M4iO&cX5fzx0DOOM5dZ)H literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/fire cave.mw3 b/worlds/smw/data/palettes/map/vanilla/fire cave.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..2980210dd233eaa0bfa804389754055f6a03dcb9 GIT binary patch literal 514 zcmZQzKn0VF85sUEODRn*mKD9lvxNgymVse%v6RyPdJxUPAf=R3hb|A|KiA2&eeYWw z_dO4!5*>W3-_OWUx1ad|^PhT%JeOpy;zdn87VB_`T7St3JG*d&x(9U+!218!2MbEF z=&|fKuh#{sFbw*ZHmAB(%v=AXJBYu5;TwY$BS^m_3y2TV|G)TNwR{bT&&?vp0@7d4 zT+fV<-_Hoq4{}2xZ$0yW|M+Ct2rdSB%Ofrveg)~<4DOr$2dW3jKVV#2+rYe?!JY}E z9~FS)#TeunICAgm#wVl7fp{SK2Brth9Jx|zQcBq6AJi$-H`G4>Lek|K7^E3=-9E=5 L!UNq5{P6(*j&WwS literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/gold_mine.mw3 b/worlds/smw/data/palettes/map/vanilla/gold_mine.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ad5460a75e503f52ea46f721ad81310114e94831 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3BMkT7St3JG*d&x(9U+!20i1PYqx0 z29!6i*9ECC4EmNfr@B?lTmPdwh|eQAQTCf6NdIy-5FeudKl8n6`5F-aUbS`jF(XdV zdggj&gnYd&L_f$4g}n95|NY~WWh1y4#YMZEXYdb_RPU zkbYDEk{4r;XW+=as~ew;DhJ|$E!XJC+K&~^J9 MhX@aJGw{a;0KFq^6aWAK literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/invertido.mw3 b/worlds/smw/data/palettes/map/vanilla/invertido.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..37cf2e9f50d24ce7db77c7a9d4869aaad1eecbc5 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3w;7m27ODLQ{5`&t^d&-#DC+d7xpm;q+bK<9*F+`%=fD0Ye4e%s;$G18L1i8 zGuJbN_z?MeU5I{=8wz>rng9F8C(A}~G00mUapCYQNZ)2~-}FCFJxKlmp>o6^6h)t`s6eh*Q-MH4AXXq^5C}vBq5@G9fj~qcRv`KVk&*d@l`HobA|qp0 zMpm|Shw1FFrqegWFqiW#T!5%96^Q1ff(of)`f{o%5y5b}qfKnVgS{l4kW-*|G{u3qB`lpr!fxnA4&rhZIDDk8*F} zXBxWrZ|5zS{J(x=e{jKz9~nle!j|(8oBoyQ`*Bf+J~pYF6A`%Jd&{&Hse`{R@cU74 oE1bmG1voSoe2%YFaH@!m|Y{_!5Q|F8e}0n&(NlK=n! literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/original.mw3 b/worlds/smw/data/palettes/map/vanilla/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c165e81b818b54b39163774ae200a6a48ba7ac84 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3S7c6Q+kbr0$ufc4+2o*J%E z2b4Fj*9ECC4EmNfr@B?lTmPdwh_7bYY5T?#q+bK<9*F+`%=fD0Ye4e%s;$G18L1i8 zGuJbN_z?MeU5I{=8wz>rng9F8C(A}~G00mUapCYQNZ)2~-}FCFJxKlm5v0G#0K|vr|Id7{TD}IvzgG>C$<}Q$ z@DN7G*Xu&`gWOQaThIL8KR#JDf{Q`k@`wwEUqSjdgZrlcnSr4Jlz+guwzh$JJA*wF zNIxn7$%`?_GjQbI)s0U^l>_lW@(oN6m^pH#)U3m?%Ri`7sBfr$0EDE=GcZUq=(>H5 MLxcyq8TjJ^0H*kEoB#j- literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/purple.mw3 b/worlds/smw/data/palettes/map/vanilla/purple.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..db0008bca7cd3f70e23859b48b89c3b4b4578b05 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3@0M8eH0?l}YKU5Y$qGBWaD}=Dbq~Pu_o{E# z8`NtD?l+e)1*tF$`j$4Qx>d|u|D!udzjvfS+LSEqz{+X^uze8y|C#Sq%h!P9?^Roe zA2ZSptY@xg2I+^$*Xu&`gWOQaThIL8KR#JDf{Q`k@`wwEUqSjdgZrlcf$Bl>4;a_h zHZX5zuxA44M+G2xF$Q@Cj@-Mt@yV!iARb7*f$0G=N3N8bbvSnU2XzYd4fPLzkaT$l T25AOex6g5~@W3E1X1)Hmjn_GBAK-{?~(O5DvFPmk05A1t%(s8qIK$4hN}3 z2es*DW}8)GlEdxpmqX;g=$e`B^wJaER}4{GC_BR~Ci$|anb}Q0kjVXVn|M(pJ<++@ zW@aG1nbohLdHI($XE=de!N8y=I#Kb9E=a$q5r_}bzc2rO`6jUYVfsOCN670-LiB^& zFiFuS{(gCFcA;zri>T3CfypDVoIv_f0Z9IhfWBma zWVl@|P(3<;m>+Hjl84xjssbc$W)^NY!|kRY#J{A;GcfRpiW=SYLxcyaenRm90OQJG A%K!iX literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/vanilla/witches_cauldron.mw3 b/worlds/smw/data/palettes/map/vanilla/witches_cauldron.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..ef6a81e5d49de8b637df7da1cb7c326ffc509a0a GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3N&?W&-qGP}GXYW*cE?Cio7>K@cR0PDY39pJo5 zYpKkB^D0M>3d5jpX>+Pu#k}=Dx`X(~R6I;i+Jf}2(gN`z`u{WEtCp_;@$XezhaWRq zDpSu~&y0|-*M;Z@xuKA^p83Cje6nl=7lXXz5f=`>g7j?$_f7u;)q~_8Fs`j_VBXGP z&jiws3PAE=4Dt*dxp#HrlTqbBJdk_?(*tIXTq!l{aP0CA>J;i5>K_0h>GBK=(hRz8 OpW_hWfo=x=_y7Qrxo`#m literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/atardecer.mw3 b/worlds/smw/data/palettes/map/yoshi/atardecer.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..a75c898cee98948eef7a1a386a507995b6a208b5 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3OrBfJ#bm_%g&D->QtH*i)c>D(f3bWGNd8{6bvQ`>5sOb?^$_#x zb-ndJx-&5B2l}Ltx1RaGe|)lR1Q&z68_2KP<>1J#4%-!ZMN{lt8Q!%hlh z9x4FIi*eSma^&9CjZa3E1Mxue4NMQ1IdY}ctiv(oL1FNqPNBY`{s9n@F3-Rq&7kY{ PIj&U<;vY;C@W%%L&X;cj literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/gum.mw3 b/worlds/smw/data/palettes/map/yoshi/gum.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..cfde2f53bba4254934a8accd29a5a692f093e3bb GIT binary patch literal 514 zcmZQzKn0VF85sUE>jg|M*0#|!P?bTIWnh?GtQWAR4n#9B=mq2eS(xCtPPXlP-{QFM zdH?G%Wk1&MXJn|`&-{S-Pd!9l!LuP%rS5%5bnzS;ka~Z~3Ol=Sg}MiI55W4Pi!JKr z*t`$fZ(gqplArEj>p7`=e|!J{C?0N@ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/lava_island.mw3 b/worlds/smw/data/palettes/map/yoshi/lava_island.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..570bdee3aa9cab8df90100b5c9a771a876d82314 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3K=gg-w)m= zHdo+`#D4R7U68zC(6_WX)vaRQ`XAjv{G}q#WwLeu$JOT}>;KPuuUftaB!55HI($Lh zH^vo09>O3##Qb_)h<=b83VG|9|NF-$%SLc9$Xgz9;qWU+-)3;%^glB&G=TCC7}wS| zFmGqDX9DR*1t57b26+aK+`GE*$*6K59!S1{=>aoGu9TW}ICl95bqe(j^$&oMba@5_ SX$D=l&vA(GKsN(_d;kE?AaG6q literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/mono.mw3 b/worlds/smw/data/palettes/map/yoshi/mono.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..62c9761b4673734aa0e4edc80f8ccd0e93d2af97 GIT binary patch literal 514 zcmaixp>o6^6h)C0704B9DpnvO5D|zN1OgH9#0o@B1OgF(s6g}wRz~s*kt_EXA|qp0 zMpm|S2hp$$BdV<4aJ*s;hya`aCx7s>TRhXKmR zN~1J$>jK#=6+X9nNA`NYy;%WuXeM%DQPQjq{g%jDaXsfK4SH!lUUOX3L`cEd-bq7N z`5b@xmETgyza{0l-aDKEeVH#!@gu`ULY>CY49{HOkBd4Cp^1HtjDd^O-f?Y3?BTBq s{C*TCE6Ap?X6VX%+yEJW=g2?N>dJj<4g7y`yf@dY`+kqw|KERn0Uyk0>Hq)$ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/original.mw3 b/worlds/smw/data/palettes/map/yoshi/original.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..eb9451b1fe5c3a345c8fe04cefd208358367b772 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3j9wUd2>*|(&qvn(pZQ+3d<{tcUbS`jg1T>v zAX`9ui23!p5d9!G6!O+H|M!nimW|+IkheVI!r@ntzRlpi>3?QmXaMCOFs`j_VBXGP z&jiws3PAE=4Dt*dxp#HrlTqbBJdk_?(*tIXTq!l{aP0CA>J;i5>K_0h>GBK=(hRz8 OpW_hWfo=x=_y7QkXmBI| literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/original_special.mw3 b/worlds/smw/data/palettes/map/yoshi/original_special.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..269b45db617113eea999a51d7ae3edc8b8b95543 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3cLJ|9{Cf98AD@--lNp#E@>{uM$V!XQ4x z{CZu8evlgqdFz?~`^P8CMsP96TOM)Y@GD5)W^mv1KQk~ifbtI**VZ;LZ)dP)0_jHu zAbBwcc?OQ$ySnkosB$14NWOvT0W(Lgl$v!ocKHW&3iS>34}g$#c?Je)23@z$aft9h JHv@ls007S)aQpxO literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/sepia.mw3 b/worlds/smw/data/palettes/map/yoshi/sepia.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..3cbf6b0390bf10ab608d5765f20df390d3d9d083 GIT binary patch literal 514 zcmZQzKm|7O3=DI#ZQ^a>E1X1)Hmjn_GBAK-{?~(O5DvFPmk05A1t%(s8qIK$4hN}3 z2es*DW}8)GlEdxpmqX+?tA5c9x7+EquQ)&wq`FXchFeVXWlb})n|>hv{qk_T0Lh(h zbFvv9i^Za4iP{zX*-(vMG`fq_p{)aa%k LB0Ny_6N(Q2DI#SY literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/snow_day.mw3 b/worlds/smw/data/palettes/map/yoshi/snow_day.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..464b32bad17cfa0135d89d3cfaedc70423a89f08 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3;v);a8Bp&EUT2e`bh$i`LrORAW=Yc}nQM zfyhVc)LO2Hy{j9aj9tD(E7f>KtdyEH(7osY6b29K6zUu59{?e`EHE|*$TKiVGw8Z~ Pj%yWz_y^Mj{P6(*tigW< literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/sunset.mw3 b/worlds/smw/data/palettes/map/yoshi/sunset.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..9477a08cb8e69974c2be867e9e0d61af7474a796 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3v zD}+3RL41h$^}62rAKihz1=>-_ThIL8KR#JDf{Q`k@`wwEUqSjdgZrlcnIZBI7}wS| zFmGqDX9DR*1t9%m4Dt*dxp#HrlTqbBJdk_?(*tIXTq!l{aP0CA>J;i5>K_0h>GBK= U(hRz8pW|A^ApXHL0e^e|0OlcVq5uE@ literal 0 HcmV?d00001 diff --git a/worlds/smw/data/palettes/map/yoshi/tritanopia.mw3 b/worlds/smw/data/palettes/map/yoshi/tritanopia.mw3 new file mode 100644 index 0000000000000000000000000000000000000000..c90b7f9af0d2ad8b9261edccc53041384e0ac809 GIT binary patch literal 514 zcmZQzKn0VF85sUETZd0BF0~UfTCa*K%fK+X*gE`wJ&0ytuny0uLzf5fpX+4XzV|JT z`<@3=HHo56k4|IEP90LnjLTwB|~yq&?G z38WttfaJv(o!k`;D#;RK=gg->Y7y zrx8J;87bI^O^et^pb*q@S{zrEZAEN(%9IF2R%=fD0Ye4e%s;$Ep)O}-IA+( Date: Tue, 12 Mar 2024 14:03:57 -0700 Subject: [PATCH 121/166] Core: typing for `Option.default` and a few other ClassVars (#2899) * Core: typing for `Option.default` and a few other `Option` class variables This is a replacement for https://github.com/ArchipelagoMW/Archipelago/pull/2173 You can read discussion there for issues we found for why we can't have more specific typing on `default` instead of setting a default in `Option` (where we don't know the type), we check in the metaclass to make sure they have a default. * NumericOption doesn't need the type annotation that brings out the mypy bug * SoE default ClassVar --- Options.py | 20 ++++++++++++++------ worlds/soe/options.py | 6 +++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Options.py b/Options.py index 144220c8ee..e1ae339143 100644 --- a/Options.py +++ b/Options.py @@ -41,6 +41,11 @@ class AssembleOptions(abc.ABCMeta): aliases = {name[6:].lower(): option_id for name, option_id in attrs.items() if name.startswith("alias_")} + assert ( + name in {"Option", "VerifyKeys"} or # base abstract classes don't need default + "default" in attrs or + any(hasattr(base, "default") for base in bases) + ), f"Option class {name} needs default value" assert "random" not in aliases, "Choice option 'random' cannot be manually assigned." # auto-alias Off and On being parsed as True and False @@ -96,7 +101,7 @@ T = typing.TypeVar('T') class Option(typing.Generic[T], metaclass=AssembleOptions): value: T - default = 0 + default: typing.ClassVar[typing.Any] # something that __init__ will be able to convert to the correct type # convert option_name_long into Name Long as display_name, otherwise name_long is the result. # Handled in get_option_name() @@ -106,8 +111,9 @@ class Option(typing.Generic[T], metaclass=AssembleOptions): supports_weighting = True # filled by AssembleOptions: - name_lookup: typing.Dict[T, str] - options: typing.Dict[str, int] + name_lookup: typing.ClassVar[typing.Dict[T, str]] # type: ignore + # https://github.com/python/typing/discussions/1460 the reason for this type: ignore + options: typing.ClassVar[typing.Dict[str, int]] def __repr__(self) -> str: return f"{self.__class__.__name__}({self.current_option_name})" @@ -160,6 +166,8 @@ class FreeText(Option[str]): """Text option that allows users to enter strings. Needs to be validated by the world or option definition.""" + default = "" + def __init__(self, value: str): assert isinstance(value, str), "value of FreeText must be a string" self.value = value @@ -811,7 +819,7 @@ class VerifyKeys(metaclass=FreezeValidKeys): class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys, typing.Mapping[str, typing.Any]): - default: typing.Dict[str, typing.Any] = {} + default = {} supports_weighting = False def __init__(self, value: typing.Dict[str, typing.Any]): @@ -852,7 +860,7 @@ class OptionList(Option[typing.List[typing.Any]], VerifyKeys): # If only unique entries are needed and input order of elements does not matter, OptionSet should be used instead. # Not a docstring so it doesn't get grabbed by the options system. - default: typing.Union[typing.List[typing.Any], typing.Tuple[typing.Any, ...]] = () + default = () supports_weighting = False def __init__(self, value: typing.Iterable[typing.Any]): @@ -878,7 +886,7 @@ class OptionList(Option[typing.List[typing.Any]], VerifyKeys): class OptionSet(Option[typing.Set[str]], VerifyKeys): - default: typing.Union[typing.Set[str], typing.FrozenSet[str]] = frozenset() + default = frozenset() supports_weighting = False def __init__(self, value: typing.Iterable[str]): diff --git a/worlds/soe/options.py b/worlds/soe/options.py index cb9e9bb6de..c5ac02c22d 100644 --- a/worlds/soe/options.py +++ b/worlds/soe/options.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, fields -from typing import Any, cast, Dict, Iterator, List, Tuple, Protocol +from typing import Any, ClassVar, cast, Dict, Iterator, List, Tuple, Protocol from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Option, PerGameCommonOptions, \ ProgressionBalancing, Range, Toggle @@ -8,13 +8,13 @@ from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Option, # typing boilerplate class FlagsProtocol(Protocol): value: int - default: int + default: ClassVar[int] flags: List[str] class FlagProtocol(Protocol): value: int - default: int + default: ClassVar[int] flag: str From fb9ef19c1597c23c9022486d5554edc793bbd3fb Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:08:12 -0500 Subject: [PATCH 122/166] Core: add list/dict merging feature to triggers (#2793) * proof of concept * add dict support, block top/game level merge * prevent key error when option being merged is new * update triggers guide * Add documentation about add/remove/replace * move to trailing name instead of proper tag * update docs * confirm types * Update Utils.py * Update Generate.py * pep8 * move to + syntax * forgot to support sets * specify received type of type error * Update Generate.py Co-authored-by: Fabian Dill * Apply suggestion from review * add test for update weights * move test to new test case * Apply suggestions from code review Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --------- Co-authored-by: Fabian Dill Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --- Generate.py | 28 ++++++++++++++++++--- Utils.py | 3 +++ test/general/test_player_options.py | 39 +++++++++++++++++++++++++++++ worlds/generic/docs/triggers_en.md | 29 ++++++++++++++++++++- 4 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 test/general/test_player_options.py diff --git a/Generate.py b/Generate.py index ecdc81833a..56979334b5 100644 --- a/Generate.py +++ b/Generate.py @@ -323,13 +323,29 @@ def roll_percentage(percentage: Union[int, float]) -> bool: return random.random() < (float(percentage) / 100) -def update_weights(weights: dict, new_weights: dict, type: str, name: str) -> dict: +def update_weights(weights: dict, new_weights: dict, update_type: str, name: str) -> dict: logging.debug(f'Applying {new_weights}') - new_options = set(new_weights) - set(weights) - weights.update(new_weights) + cleaned_weights = {} + for option in new_weights: + option_name = option.lstrip("+") + if option.startswith("+") and option_name in weights: + cleaned_value = weights[option_name] + new_value = new_weights[option] + if isinstance(new_value, (set, dict)): + cleaned_value.update(new_value) + elif isinstance(new_value, list): + cleaned_value.extend(new_value) + else: + raise Exception(f"Cannot apply merge to non-dict, set, or list type {option_name}," + f" received {type(new_value).__name__}.") + cleaned_weights[option_name] = cleaned_value + else: + cleaned_weights[option_name] = new_weights[option] + new_options = set(cleaned_weights) - set(weights) + weights.update(cleaned_weights) if new_options: for new_option in new_options: - logging.warning(f'{type} Suboption "{new_option}" of "{name}" did not ' + logging.warning(f'{update_type} Suboption "{new_option}" of "{name}" did not ' f'overwrite a root option. ' f'This is probably in error.') return weights @@ -452,6 +468,10 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b world_type = AutoWorldRegister.world_types[ret.game] game_weights = weights[ret.game] + if any(weight.startswith("+") for weight in game_weights) or \ + any(weight.startswith("+") for weight in weights): + raise Exception(f"Merge tag cannot be used outside of trigger contexts.") + if "triggers" in game_weights: weights = roll_triggers(weights, game_weights["triggers"]) game_weights = weights[ret.game] diff --git a/Utils.py b/Utils.py index 3c63b42ccb..70decf45d8 100644 --- a/Utils.py +++ b/Utils.py @@ -225,6 +225,9 @@ class UniqueKeyLoader(SafeLoader): if key in mapping: logging.error(f"YAML duplicates sanity check failed{key_node.start_mark}") raise KeyError(f"Duplicate key {key} found in YAML. Already found keys: {mapping}.") + if (str(key).startswith("+") and (str(key)[1:] in mapping)) or (f"+{key}" in mapping): + logging.error(f"YAML merge duplicates sanity check failed{key_node.start_mark}") + raise KeyError(f"Equivalent key {key} found in YAML. Already found keys: {mapping}.") mapping.add(key) return super().construct_mapping(node, deep) diff --git a/test/general/test_player_options.py b/test/general/test_player_options.py new file mode 100644 index 0000000000..9650fbe97a --- /dev/null +++ b/test/general/test_player_options.py @@ -0,0 +1,39 @@ +import unittest +import Generate + + +class TestPlayerOptions(unittest.TestCase): + + def test_update_weights(self): + original_weights = { + "scalar_1": 50, + "scalar_2": 25, + "list_1": ["string"], + "dict_1": {"option_a": 50, "option_b": 50}, + "dict_2": {"option_f": 50}, + "set_1": {"option_c"} + } + + # test that we don't allow +merge syntax on scalar variables + with self.assertRaises(BaseException): + Generate.update_weights(original_weights, {"+scalar_1": 0}, "Tested", "") + + new_weights = Generate.update_weights(original_weights, {"scalar_2": 0, + "+list_1": ["string_2"], + "+dict_1": {"option_b": 0, "option_c": 50}, + "+set_1": {"option_c", "option_d"}, + "dict_2": {"option_g": 50}, + "+list_2": ["string_3"]}, + "Tested", "") + + self.assertEqual(new_weights["scalar_1"], 50) + self.assertEqual(new_weights["scalar_2"], 0) + self.assertEqual(new_weights["list_2"], ["string_3"]) + self.assertEqual(new_weights["list_1"], ["string", "string_2"]) + self.assertEqual(new_weights["dict_1"]["option_a"], 50) + self.assertEqual(new_weights["dict_1"]["option_b"], 0) + self.assertEqual(new_weights["dict_1"]["option_c"], 50) + self.assertNotIn("option_f", new_weights["dict_2"]) + self.assertEqual(new_weights["dict_2"]["option_g"], 50) + self.assertEqual(len(new_weights["set_1"]), 2) + self.assertIn("option_d", new_weights["set_1"]) diff --git a/worlds/generic/docs/triggers_en.md b/worlds/generic/docs/triggers_en.md index a9ffebb466..dc5cf5c51e 100644 --- a/worlds/generic/docs/triggers_en.md +++ b/worlds/generic/docs/triggers_en.md @@ -121,4 +121,31 @@ For example: In this example (thanks to @Black-Sliver), if the `pupdunk` option is rolled, then the difficulty values will be rolled again using the new options `normal`, `pupdunk_hard`, and `pupdunk_mystery`, and the exp modifier will be rerolled using new weights for 150 and 200. This allows for two more triggers that will only be used for the new `pupdunk_hard` -and `pupdunk_mystery` options so that they will only be triggered on "pupdunk AND hard/mystery". \ No newline at end of file +and `pupdunk_mystery` options so that they will only be triggered on "pupdunk AND hard/mystery". + +Options that define a list, set, or dict can additionally have the character `+` added to the start of their name, which applies the contents of +the activated trigger to the already present equivalents in the game options. + +For example: +```yaml +Super Metroid: + start_location: + landing_site: 50 + aqueduct: 50 + start_hints: + - Morph Ball +triggers: + - option_category: Super Metroid + option_name: start_location + option_result: aqueduct + options: + Super Metroid: + +start_hints: + - Gravity Suit +``` + +In this example, if the `start_location` option rolls `landing_site`, only a starting hint for Morph Ball will be created. +If `aqueduct` is rolled, a starting hint for Gravity Suit will also be created alongside the hint for Morph Ball. + +Note that for lists, items can only be added, not removed or replaced. For dicts, defining a value for a present key will +replace that value within the dict. \ No newline at end of file From ecd84fd1ca0f5f8bed75dba2b96f0c7cc51f992b Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Tue, 12 Mar 2024 22:27:17 +0100 Subject: [PATCH 123/166] CI: build: create setup (#2936) * CI: build: create setup also add /DNO_SIGNTOOL to inno_setup.iss * CI: build: trigger when changing setup-related files --- .github/workflows/build.yml | 25 ++++++++++++++++++++++--- inno_setup.iss | 5 ++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a40084b9ab..dc4e409783 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,11 +8,13 @@ on: - '.github/workflows/build.yml' - 'setup.py' - 'requirements.txt' + - '*.iss' pull_request: paths: - '.github/workflows/build.yml' - 'setup.py' - 'requirements.txt' + - '*.iss' workflow_dispatch: env: @@ -25,9 +27,9 @@ jobs: build-win-py38: # RCs will still be built and signed by hand runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.8' - name: Download run-time dependencies @@ -46,12 +48,29 @@ jobs: cd build Rename-Item "exe.$NAME" Archipelago 7z a -mx=9 -mhe=on -ms "../dist/$ZIP_NAME" Archipelago + Rename-Item Archipelago "exe.$NAME" # inno_setup.iss expects the original name - name: Store 7z - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.ZIP_NAME }} path: dist/${{ env.ZIP_NAME }} retention-days: 7 # keep for 7 days, should be enough + - name: Build Setup + run: | + & "${env:ProgramFiles(x86)}\Inno Setup 6\iscc.exe" inno_setup.iss /DNO_SIGNTOOL + if ( $? -eq $false ) { + Write-Error "Building setup failed!" + exit 1 + } + $contents = Get-ChildItem -Path setups/*.exe -Force -Recurse + $SETUP_NAME=$contents[0].Name + echo "SETUP_NAME=$SETUP_NAME" >> $Env:GITHUB_ENV + - name: Store Setup + uses: actions/upload-artifact@v4 + with: + name: ${{ env.SETUP_NAME }} + path: setups/${{ env.SETUP_NAME }} + retention-days: 7 # keep for 7 days, should be enough build-ubuntu2004: runs-on: ubuntu-20.04 diff --git a/inno_setup.iss b/inno_setup.iss index 7d089def95..5a6d608306 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -31,8 +31,11 @@ ArchitecturesAllowed=x64 arm64 AllowNoIcons=yes SetupIconFile={#MyAppIcon} UninstallDisplayIcon={app}\{#MyAppExeName} -; you will likely have to remove the following signtool line when testing/debugging locally. Don't include that change in PRs. +#ifndef NO_SIGNTOOL +; You will likely have to remove the SignTool= line when testing/debugging locally or run with iscc.exe /DNO_SIGNTOOL. +; Don't include that change in PRs. SignTool= signtool +#endif LicenseFile= LICENSE WizardStyle= modern SetupLogging=yes From d953927b3a1eb417975d6aa011107d813db87544 Mon Sep 17 00:00:00 2001 From: Kory Dondzila Date: Tue, 12 Mar 2024 17:17:18 -0500 Subject: [PATCH 124/166] Shivers: Renaming for clarity and consistency (#2869) * Moves plaque location to front for better tracker referencing. * Tiki should be Shaman. * Hanging should be Gallows. * Merrick spelling. * Clarity change. --- worlds/shivers/Items.py | 4 +- worlds/shivers/Options.py | 17 +- worlds/shivers/Rules.py | 8 +- worlds/shivers/data/excluded_locations.json | 80 ++++----- worlds/shivers/data/locations.json | 180 ++++++++++---------- worlds/shivers/data/regions.json | 12 +- 6 files changed, 155 insertions(+), 146 deletions(-) diff --git a/worlds/shivers/Items.py b/worlds/shivers/Items.py index caf24ded29..3b403be5cb 100644 --- a/worlds/shivers/Items.py +++ b/worlds/shivers/Items.py @@ -47,7 +47,7 @@ item_table = { "Key for Generator Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 29, "key"), "Key for Egypt Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 30, "key"), "Key for Library Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 31, "key"), - "Key for Tiki Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 32, "key"), + "Key for Shaman Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 32, "key"), "Key for UFO Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 33, "key"), "Key for Torture Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 34, "key"), "Key for Puzzle Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 35, "key"), @@ -90,7 +90,7 @@ item_table = { "Water Always Available in Lobby": ItemData(SHIVERS_ITEM_ID_OFFSET + 92, "filler2", ItemClassification.filler), "Wax Always Available in Library": ItemData(SHIVERS_ITEM_ID_OFFSET + 93, "filler2", ItemClassification.filler), "Wax Always Available in Anansi Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 94, "filler2", ItemClassification.filler), - "Wax Always Available in Tiki Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 95, "filler2", ItemClassification.filler), + "Wax Always Available in Shaman Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 95, "filler2", ItemClassification.filler), "Ash Always Available in Office": ItemData(SHIVERS_ITEM_ID_OFFSET + 96, "filler2", ItemClassification.filler), "Ash Always Available in Burial Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 97, "filler2", ItemClassification.filler), "Oil Always Available in Prehistoric Room": ItemData(SHIVERS_ITEM_ID_OFFSET + 98, "filler2", ItemClassification.filler), diff --git a/worlds/shivers/Options.py b/worlds/shivers/Options.py index 6d18804069..b70882f9a5 100644 --- a/worlds/shivers/Options.py +++ b/worlds/shivers/Options.py @@ -13,13 +13,16 @@ class LobbyAccess(Choice): option_local = 2 class PuzzleHintsRequired(DefaultOnToggle): - """If turned on puzzle hints will be available before the corresponding puzzle is required. For example: The Tiki + """If turned on puzzle hints will be available before the corresponding puzzle is required. For example: The Shaman Drums puzzle will be placed after access to the security cameras which give you the solution. Turning this off allows for greater randomization.""" display_name = "Puzzle Hints Required" class InformationPlaques(Toggle): - """Adds Information Plaques as checks.""" + """ + Adds Information Plaques as checks. + (40 Locations) + """ display_name = "Include Information Plaques" class FrontDoorUsable(Toggle): @@ -27,7 +30,10 @@ class FrontDoorUsable(Toggle): display_name = "Front Door Usable" class ElevatorsStaySolved(DefaultOnToggle): - """Adds elevators as checks and will remain open upon solving them.""" + """ + Adds elevators as checks and will remain open upon solving them. + (3 Locations) + """ display_name = "Elevators Stay Solved" class EarlyBeth(DefaultOnToggle): @@ -35,7 +41,10 @@ class EarlyBeth(DefaultOnToggle): display_name = "Early Beth" class EarlyLightning(Toggle): - """Allows lightning to be captured at any point in the game. You will still need to capture all ten Ixupi for victory.""" + """ + Allows lightning to be captured at any point in the game. You will still need to capture all ten Ixupi for victory. + (1 Location) + """ display_name = "Early Lightning" diff --git a/worlds/shivers/Rules.py b/worlds/shivers/Rules.py index 57488ff333..8aa8aa2c28 100644 --- a/worlds/shivers/Rules.py +++ b/worlds/shivers/Rules.py @@ -96,8 +96,8 @@ def get_rules_lookup(player: int): "To Lobby From Egypt": lambda state: state.has("Key for Egypt Room", player), "To Egypt From Lobby": lambda state: state.has("Key for Egypt Room", player), "To Janitor Closet": lambda state: state.has("Key for Janitor Closet", player), - "To Tiki From Burial": lambda state: state.has("Key for Tiki Room", player), - "To Burial From Tiki": lambda state: state.has("Key for Tiki Room", player), + "To Shaman From Burial": lambda state: state.has("Key for Shaman Room", player), + "To Burial From Shaman": lambda state: state.has("Key for Shaman Room", player), "To Inventions From UFO": lambda state: state.has("Key for UFO Room", player), "To UFO From Inventions": lambda state: state.has("Key for UFO Room", player), "To Torture From Inventions": lambda state: state.has("Key for Torture Room", player), @@ -145,7 +145,7 @@ def get_rules_lookup(player: int): "locations_puzzle_hints": { "Puzzle Solved Clock Tower Door": lambda state: state.can_reach("Three Floor Elevator", "Region", player), "Puzzle Solved Clock Chains": lambda state: state.can_reach("Bedroom", "Region", player), - "Puzzle Solved Tiki Drums": lambda state: state.can_reach("Clock Tower", "Region", player), + "Puzzle Solved Shaman Drums": lambda state: state.can_reach("Clock Tower", "Region", player), "Puzzle Solved Red Door": lambda state: state.can_reach("Maintenance Tunnels", "Region", player), "Puzzle Solved UFO Symbols": lambda state: state.can_reach("Library", "Region", player), "Puzzle Solved Maze Door": lambda state: state.can_reach("Projector Room", "Region", player), @@ -202,7 +202,7 @@ def set_rules(world: "ShiversWorld") -> None: forbid_item(multiworld.get_location("Ixupi Captured Water", player), "Water Always Available in Lobby", player) forbid_item(multiworld.get_location("Ixupi Captured Wax", player), "Wax Always Available in Library", player) forbid_item(multiworld.get_location("Ixupi Captured Wax", player), "Wax Always Available in Anansi Room", player) - forbid_item(multiworld.get_location("Ixupi Captured Wax", player), "Wax Always Available in Tiki Room", player) + forbid_item(multiworld.get_location("Ixupi Captured Wax", player), "Wax Always Available in Shaman Room", player) forbid_item(multiworld.get_location("Ixupi Captured Ash", player), "Ash Always Available in Office", player) forbid_item(multiworld.get_location("Ixupi Captured Ash", player), "Ash Always Available in Burial Room", player) forbid_item(multiworld.get_location("Ixupi Captured Oil", player), "Oil Always Available in Prehistoric Room", player) diff --git a/worlds/shivers/data/excluded_locations.json b/worlds/shivers/data/excluded_locations.json index a37285eb1d..29655d4a50 100644 --- a/worlds/shivers/data/excluded_locations.json +++ b/worlds/shivers/data/excluded_locations.json @@ -1,45 +1,45 @@ { "plaques": [ - "Information Plaque: Transforming Masks (Lobby)", - "Information Plaque: Jade Skull (Lobby)", - "Information Plaque: Bronze Unicorn (Prehistoric)", - "Information Plaque: Griffin (Prehistoric)", - "Information Plaque: Eagles Nest (Prehistoric)", - "Information Plaque: Large Spider (Prehistoric)", - "Information Plaque: Starfish (Prehistoric)", - "Information Plaque: Quartz Crystal (Ocean)", - "Information Plaque: Poseidon (Ocean)", - "Information Plaque: Colossus of Rhodes (Ocean)", - "Information Plaque: Poseidon's Temple (Ocean)", - "Information Plaque: Subterranean World (Underground Maze)", - "Information Plaque: Dero (Underground Maze)", - "Information Plaque: Tomb of the Ixupi (Egypt)", - "Information Plaque: The Sphinx (Egypt)", - "Information Plaque: Curse of Anubis (Egypt)", - "Information Plaque: Norse Burial Ship (Burial)", - "Information Plaque: Paracas Burial Bundles (Burial)", - "Information Plaque: Spectacular Coffins of Ghana (Burial)", - "Information Plaque: Cremation (Burial)", - "Information Plaque: Animal Crematorium (Burial)", - "Information Plaque: Witch Doctors of the Congo (Tiki)", - "Information Plaque: Sarombe doctor of Mozambique (Tiki)", - "Information Plaque: Fisherman's Canoe God (Gods)", - "Information Plaque: Mayan Gods (Gods)", - "Information Plaque: Thor (Gods)", - "Information Plaque: Celtic Janus Sculpture (Gods)", - "Information Plaque: Sumerian Bull God - An (Gods)", - "Information Plaque: Sumerian Lyre (Gods)", - "Information Plaque: Chuen (Gods)", - "Information Plaque: African Creation Myth (Anansi)", - "Information Plaque: Apophis the Serpent (Anansi)", - "Information Plaque: Death (Anansi)", - "Information Plaque: Cyclops (Pegasus)", - "Information Plaque: Lycanthropy (Werewolf)", - "Information Plaque: Coincidence or Extraterrestrial Visits? (UFO)", - "Information Plaque: Planets (UFO)", - "Information Plaque: Astronomical Construction (UFO)", - "Information Plaque: Guillotine (Torture)", - "Information Plaque: Aliens (UFO)" + "Information Plaque: (Lobby) Transforming Masks", + "Information Plaque: (Lobby) Jade Skull", + "Information Plaque: (Prehistoric) Bronze Unicorn", + "Information Plaque: (Prehistoric) Griffin", + "Information Plaque: (Prehistoric) Eagles Nest", + "Information Plaque: (Prehistoric) Large Spider", + "Information Plaque: (Prehistoric) Starfish", + "Information Plaque: (Ocean) Quartz Crystal", + "Information Plaque: (Ocean) Poseidon", + "Information Plaque: (Ocean) Colossus of Rhodes", + "Information Plaque: (Ocean) Poseidon's Temple", + "Information Plaque: (Underground Maze) Subterranean World", + "Information Plaque: (Underground Maze) Dero", + "Information Plaque: (Egypt) Tomb of the Ixupi", + "Information Plaque: (Egypt) The Sphinx", + "Information Plaque: (Egypt) Curse of Anubis", + "Information Plaque: (Burial) Norse Burial Ship", + "Information Plaque: (Burial) Paracas Burial Bundles", + "Information Plaque: (Burial) Spectacular Coffins of Ghana", + "Information Plaque: (Burial) Cremation", + "Information Plaque: (Burial) Animal Crematorium", + "Information Plaque: (Shaman) Witch Doctors of the Congo", + "Information Plaque: (Shaman) Sarombe doctor of Mozambique", + "Information Plaque: (Gods) Fisherman's Canoe God", + "Information Plaque: (Gods) Mayan Gods", + "Information Plaque: (Gods) Thor", + "Information Plaque: (Gods) Celtic Janus Sculpture", + "Information Plaque: (Gods) Sumerian Bull God - An", + "Information Plaque: (Gods) Sumerian Lyre", + "Information Plaque: (Gods) Chuen", + "Information Plaque: (Anansi) African Creation Myth", + "Information Plaque: (Anansi) Apophis the Serpent", + "Information Plaque: (Anansi) Death", + "Information Plaque: (Pegasus) Cyclops", + "Information Plaque: (Werewolf) Lycanthropy", + "Information Plaque: (UFO) Coincidence or Extraterrestrial Visits?", + "Information Plaque: (UFO) Planets", + "Information Plaque: (UFO) Astronomical Construction", + "Information Plaque: (Torture) Guillotine", + "Information Plaque: (UFO) Aliens" ], "elevators": [ "Puzzle Solved Office Elevator", diff --git a/worlds/shivers/data/locations.json b/worlds/shivers/data/locations.json index fdf8ed69d1..1d62f85d2d 100644 --- a/worlds/shivers/data/locations.json +++ b/worlds/shivers/data/locations.json @@ -13,7 +13,7 @@ "Puzzle Solved Columns of RA", "Puzzle Solved Burial Door", "Puzzle Solved Chinese Solitaire", - "Puzzle Solved Tiki Drums", + "Puzzle Solved Shaman Drums", "Puzzle Solved Lyre", "Puzzle Solved Red Door", "Puzzle Solved Fortune Teller Door", @@ -38,7 +38,7 @@ "Flashback Memory Obtained Theater Movie", "Flashback Memory Obtained Museum Blueprints", "Flashback Memory Obtained Beth's Address Book", - "Flashback Memory Obtained Merick's Notebook", + "Flashback Memory Obtained Merrick's Notebook", "Flashback Memory Obtained Professor Windlenot's Diary", "Ixupi Captured Water", "Ixupi Captured Wax", @@ -68,48 +68,48 @@ "Puzzle Hint Found: Gallows Information Plaque", "Puzzle Hint Found: Mastermind Information Plaque", "Puzzle Hint Found: Elevator Writing", - "Puzzle Hint Found: Tiki Security Camera", + "Puzzle Hint Found: Shaman Security Camera", "Puzzle Hint Found: Tape Recorder Heard", - "Information Plaque: Transforming Masks (Lobby)", - "Information Plaque: Jade Skull (Lobby)", - "Information Plaque: Bronze Unicorn (Prehistoric)", - "Information Plaque: Griffin (Prehistoric)", - "Information Plaque: Eagles Nest (Prehistoric)", - "Information Plaque: Large Spider (Prehistoric)", - "Information Plaque: Starfish (Prehistoric)", - "Information Plaque: Quartz Crystal (Ocean)", - "Information Plaque: Poseidon (Ocean)", - "Information Plaque: Colossus of Rhodes (Ocean)", - "Information Plaque: Poseidon's Temple (Ocean)", - "Information Plaque: Subterranean World (Underground Maze)", - "Information Plaque: Dero (Underground Maze)", - "Information Plaque: Tomb of the Ixupi (Egypt)", - "Information Plaque: The Sphinx (Egypt)", - "Information Plaque: Curse of Anubis (Egypt)", - "Information Plaque: Norse Burial Ship (Burial)", - "Information Plaque: Paracas Burial Bundles (Burial)", - "Information Plaque: Spectacular Coffins of Ghana (Burial)", - "Information Plaque: Cremation (Burial)", - "Information Plaque: Animal Crematorium (Burial)", - "Information Plaque: Witch Doctors of the Congo (Tiki)", - "Information Plaque: Sarombe doctor of Mozambique (Tiki)", - "Information Plaque: Fisherman's Canoe God (Gods)", - "Information Plaque: Mayan Gods (Gods)", - "Information Plaque: Thor (Gods)", - "Information Plaque: Celtic Janus Sculpture (Gods)", - "Information Plaque: Sumerian Bull God - An (Gods)", - "Information Plaque: Sumerian Lyre (Gods)", - "Information Plaque: Chuen (Gods)", - "Information Plaque: African Creation Myth (Anansi)", - "Information Plaque: Apophis the Serpent (Anansi)", - "Information Plaque: Death (Anansi)", - "Information Plaque: Cyclops (Pegasus)", - "Information Plaque: Lycanthropy (Werewolf)", - "Information Plaque: Coincidence or Extraterrestrial Visits? (UFO)", - "Information Plaque: Planets (UFO)", - "Information Plaque: Astronomical Construction (UFO)", - "Information Plaque: Guillotine (Torture)", - "Information Plaque: Aliens (UFO)", + "Information Plaque: (Lobby) Transforming Masks", + "Information Plaque: (Lobby) Jade Skull", + "Information Plaque: (Prehistoric) Bronze Unicorn", + "Information Plaque: (Prehistoric) Griffin", + "Information Plaque: (Prehistoric) Eagles Nest", + "Information Plaque: (Prehistoric) Large Spider", + "Information Plaque: (Prehistoric) Starfish", + "Information Plaque: (Ocean) Quartz Crystal", + "Information Plaque: (Ocean) Poseidon", + "Information Plaque: (Ocean) Colossus of Rhodes", + "Information Plaque: (Ocean) Poseidon's Temple", + "Information Plaque: (Underground Maze) Subterranean World", + "Information Plaque: (Underground Maze) Dero", + "Information Plaque: (Egypt) Tomb of the Ixupi", + "Information Plaque: (Egypt) The Sphinx", + "Information Plaque: (Egypt) Curse of Anubis", + "Information Plaque: (Burial) Norse Burial Ship", + "Information Plaque: (Burial) Paracas Burial Bundles", + "Information Plaque: (Burial) Spectacular Coffins of Ghana", + "Information Plaque: (Burial) Cremation", + "Information Plaque: (Burial) Animal Crematorium", + "Information Plaque: (Shaman) Witch Doctors of the Congo", + "Information Plaque: (Shaman) Sarombe doctor of Mozambique", + "Information Plaque: (Gods) Fisherman's Canoe God", + "Information Plaque: (Gods) Mayan Gods", + "Information Plaque: (Gods) Thor", + "Information Plaque: (Gods) Celtic Janus Sculpture", + "Information Plaque: (Gods) Sumerian Bull God - An", + "Information Plaque: (Gods) Sumerian Lyre", + "Information Plaque: (Gods) Chuen", + "Information Plaque: (Anansi) African Creation Myth", + "Information Plaque: (Anansi) Apophis the Serpent", + "Information Plaque: (Anansi) Death", + "Information Plaque: (Pegasus) Cyclops", + "Information Plaque: (Werewolf) Lycanthropy", + "Information Plaque: (UFO) Coincidence or Extraterrestrial Visits?", + "Information Plaque: (UFO) Planets", + "Information Plaque: (UFO) Astronomical Construction", + "Information Plaque: (Torture) Guillotine", + "Information Plaque: (UFO) Aliens", "Puzzle Solved Office Elevator", "Puzzle Solved Bedroom Elevator", "Puzzle Solved Three Floor Elevator", @@ -176,10 +176,10 @@ "Lobby": [ "Puzzle Solved Theater Door", "Flashback Memory Obtained Museum Brochure", - "Information Plaque: Jade Skull (Lobby)", - "Information Plaque: Transforming Masks (Lobby)", + "Information Plaque: (Lobby) Jade Skull", + "Information Plaque: (Lobby) Transforming Masks", "Accessible: Storage: Slide", - "Accessible: Storage: Eagles Head" + "Accessible: Storage: Transforming Mask" ], "Generator": [ "Final Riddle: Beth's Body Page 17" @@ -193,7 +193,7 @@ "Clock Tower": [ "Flashback Memory Obtained Beth's Ghost", "Accessible: Storage: Clock Tower", - "Puzzle Hint Found: Tiki Security Camera" + "Puzzle Hint Found: Shaman Security Camera" ], "Projector Room": [ "Flashback Memory Obtained Theater Movie" @@ -204,10 +204,10 @@ "Flashback Memory Obtained Museum Blueprints", "Accessible: Storage: Ocean", "Puzzle Hint Found: Sirens Song Heard", - "Information Plaque: Quartz Crystal (Ocean)", - "Information Plaque: Poseidon (Ocean)", - "Information Plaque: Colossus of Rhodes (Ocean)", - "Information Plaque: Poseidon's Temple (Ocean)" + "Information Plaque: (Ocean) Quartz Crystal", + "Information Plaque: (Ocean) Poseidon", + "Information Plaque: (Ocean) Colossus of Rhodes", + "Information Plaque: (Ocean) Poseidon's Temple" ], "Maze Staircase": [ "Puzzle Solved Maze Door" @@ -217,38 +217,38 @@ "Puzzle Solved Burial Door", "Accessible: Storage: Egypt", "Puzzle Hint Found: Egyptian Sphinx Heard", - "Information Plaque: Tomb of the Ixupi (Egypt)", - "Information Plaque: The Sphinx (Egypt)", - "Information Plaque: Curse of Anubis (Egypt)" + "Information Plaque: (Egypt) Tomb of the Ixupi", + "Information Plaque: (Egypt) The Sphinx", + "Information Plaque: (Egypt) Curse of Anubis" ], "Burial": [ "Puzzle Solved Chinese Solitaire", - "Flashback Memory Obtained Merick's Notebook", + "Flashback Memory Obtained Merrick's Notebook", "Accessible: Storage: Chinese Solitaire", - "Information Plaque: Norse Burial Ship (Burial)", - "Information Plaque: Paracas Burial Bundles (Burial)", - "Information Plaque: Spectacular Coffins of Ghana (Burial)", - "Information Plaque: Animal Crematorium (Burial)", - "Information Plaque: Cremation (Burial)" + "Information Plaque: (Burial) Norse Burial Ship", + "Information Plaque: (Burial) Paracas Burial Bundles", + "Information Plaque: (Burial) Spectacular Coffins of Ghana", + "Information Plaque: (Burial) Animal Crematorium", + "Information Plaque: (Burial) Cremation" ], - "Tiki": [ - "Puzzle Solved Tiki Drums", - "Accessible: Storage: Tiki Hut", - "Information Plaque: Witch Doctors of the Congo (Tiki)", - "Information Plaque: Sarombe doctor of Mozambique (Tiki)" + "Shaman": [ + "Puzzle Solved Shaman Drums", + "Accessible: Storage: Shaman Hut", + "Information Plaque: (Shaman) Witch Doctors of the Congo", + "Information Plaque: (Shaman) Sarombe doctor of Mozambique" ], "Gods Room": [ "Puzzle Solved Lyre", "Puzzle Solved Red Door", "Accessible: Storage: Lyre", "Final Riddle: Norse God Stone Message", - "Information Plaque: Fisherman's Canoe God (Gods)", - "Information Plaque: Mayan Gods (Gods)", - "Information Plaque: Thor (Gods)", - "Information Plaque: Celtic Janus Sculpture (Gods)", - "Information Plaque: Sumerian Bull God - An (Gods)", - "Information Plaque: Sumerian Lyre (Gods)", - "Information Plaque: Chuen (Gods)" + "Information Plaque: (Gods) Fisherman's Canoe God", + "Information Plaque: (Gods) Mayan Gods", + "Information Plaque: (Gods) Thor", + "Information Plaque: (Gods) Celtic Janus Sculpture", + "Information Plaque: (Gods) Sumerian Bull God - An", + "Information Plaque: (Gods) Sumerian Lyre", + "Information Plaque: (Gods) Chuen" ], "Blue Maze": [ "Puzzle Solved Fortune Teller Door" @@ -265,28 +265,28 @@ "Puzzle Solved UFO Symbols", "Accessible: Storage: UFO", "Final Riddle: Planets Aligned", - "Information Plaque: Coincidence or Extraterrestrial Visits? (UFO)", - "Information Plaque: Planets (UFO)", - "Information Plaque: Astronomical Construction (UFO)", - "Information Plaque: Aliens (UFO)" + "Information Plaque: (UFO) Coincidence or Extraterrestrial Visits?", + "Information Plaque: (UFO) Planets", + "Information Plaque: (UFO) Astronomical Construction", + "Information Plaque: (UFO) Aliens" ], "Anansi": [ "Puzzle Solved Anansi Musicbox", "Flashback Memory Obtained Ancient Astrology", "Accessible: Storage: Skeleton", "Accessible: Storage: Anansi", - "Information Plaque: African Creation Myth (Anansi)", - "Information Plaque: Apophis the Serpent (Anansi)", - "Information Plaque: Death (Anansi)", - "Information Plaque: Cyclops (Pegasus)", - "Information Plaque: Lycanthropy (Werewolf)" + "Information Plaque: (Anansi) African Creation Myth", + "Information Plaque: (Anansi) Apophis the Serpent", + "Information Plaque: (Anansi) Death", + "Information Plaque: (Pegasus) Cyclops", + "Information Plaque: (Werewolf) Lycanthropy" ], "Torture": [ "Puzzle Solved Gallows", - "Accessible: Storage: Hanging", + "Accessible: Storage: Gallows", "Final Riddle: Guillotine Dropped", "Puzzle Hint Found: Gallows Information Plaque", - "Information Plaque: Guillotine (Torture)" + "Information Plaque: (Torture) Guillotine" ], "Puzzle Room Mastermind": [ "Puzzle Solved Mastermind", @@ -296,17 +296,17 @@ "Puzzle Solved Marble Flipper" ], "Prehistoric": [ - "Information Plaque: Bronze Unicorn (Prehistoric)", - "Information Plaque: Griffin (Prehistoric)", - "Information Plaque: Eagles Nest (Prehistoric)", - "Information Plaque: Large Spider (Prehistoric)", - "Information Plaque: Starfish (Prehistoric)", + "Information Plaque: (Prehistoric) Bronze Unicorn", + "Information Plaque: (Prehistoric) Griffin", + "Information Plaque: (Prehistoric) Eagles Nest", + "Information Plaque: (Prehistoric) Large Spider", + "Information Plaque: (Prehistoric) Starfish", "Accessible: Storage: Eagles Nest" ], "Tar River": [ "Accessible: Storage: Tar River", - "Information Plaque: Subterranean World (Underground Maze)", - "Information Plaque: Dero (Underground Maze)" + "Information Plaque: (Underground Maze) Subterranean World", + "Information Plaque: (Underground Maze) Dero" ], "Theater": [ "Accessible: Storage: Theater" diff --git a/worlds/shivers/data/regions.json b/worlds/shivers/data/regions.json index 3e81136c45..963d100fad 100644 --- a/worlds/shivers/data/regions.json +++ b/worlds/shivers/data/regions.json @@ -27,9 +27,9 @@ ["Maze", ["To Maze Staircase From Maze", "To Tar River"]], ["Tar River", ["To Maze From Tar River", "To Lobby From Tar River"]], ["Egypt", ["To Lobby From Egypt", "To Burial From Egypt", "To Blue Maze From Egypt"]], - ["Burial", ["To Egypt From Burial", "To Tiki From Burial"]], - ["Tiki", ["To Burial From Tiki", "To Gods Room"]], - ["Gods Room", ["To Tiki From Gods Room", "To Anansi From Gods Room"]], + ["Burial", ["To Egypt From Burial", "To Shaman From Burial"]], + ["Shaman", ["To Burial From Shaman", "To Gods Room"]], + ["Gods Room", ["To Shaman From Gods Room", "To Anansi From Gods Room"]], ["Anansi", ["To Gods Room From Anansi", "To Werewolf From Anansi"]], ["Werewolf", ["To Anansi From Werewolf", "To Night Staircase From Werewolf"]], ["Night Staircase", ["To Werewolf From Night Staircase", "To Janitor Closet", "To UFO"]], @@ -109,13 +109,13 @@ ["To Tar River", "Tar River"], ["To Tar River From Lobby", "Tar River"], ["To Burial From Egypt", "Burial"], - ["To Burial From Tiki", "Burial"], + ["To Burial From Shaman", "Burial"], ["To Blue Maze From Three Floor Elevator", "Blue Maze"], ["To Blue Maze From Fortune Teller", "Blue Maze"], ["To Blue Maze From Inventions", "Blue Maze"], ["To Blue Maze From Egypt", "Blue Maze"], - ["To Tiki From Burial", "Tiki"], - ["To Tiki From Gods Room", "Tiki"], + ["To Shaman From Burial", "Shaman"], + ["To Shaman From Gods Room", "Shaman"], ["To Gods Room", "Gods Room" ], ["To Gods Room From Anansi", "Gods Room"], ["To Anansi From Gods Room", "Anansi"], From 1705213353b3d7fe1110d4d932c22ae09206d649 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Tue, 12 Mar 2024 17:21:58 -0500 Subject: [PATCH 125/166] FFMQ: Update Map Shuffle Seed description (#2658) * Update Map Shuffle Seed description * Update worlds/ffmq/Options.py Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/ffmq/Options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/ffmq/Options.py b/worlds/ffmq/Options.py index 4b9f4a4a88..af3625f28a 100644 --- a/worlds/ffmq/Options.py +++ b/worlds/ffmq/Options.py @@ -207,10 +207,10 @@ class CrestShuffle(Toggle): class MapShuffleSeed(FreeText): - """If this is a number, it will be used as a set seed number for Map, Crest, and Battlefield Reward shuffles. + """If this is a number, it will be used as a set seed number for Map, Crest, Battlefield Reward, and Companion shuffles. If this is "random" the seed will be chosen randomly. If it is any other text, it will be used as a seed group name. All players using the same seed group name will get the same shuffle results, as long as their Map Shuffle, - Crest Shuffle, and Shuffle Battlefield Rewards settings are the same.""" + Crest Shuffle, Shuffle Battlefield Rewards, Companion Shuffle, and Kaeli's Mom settings are the same.""" display_name = "Map Shuffle Seed" default = "random" From c4ec8682d501cafeb916f9a7fac549df6af4a0bf Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Tue, 12 Mar 2024 17:29:32 -0500 Subject: [PATCH 126/166] Core: fix incorrect ordering on the always_allow static method (#2938) --- BaseClasses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BaseClasses.py b/BaseClasses.py index 446eea5b48..24dc074b63 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1032,7 +1032,7 @@ class Location: locked: bool = False show_in_spoiler: bool = True progress_type: LocationProgressType = LocationProgressType.DEFAULT - always_allow = staticmethod(lambda item, state: False) + always_allow = staticmethod(lambda state, item: False) access_rule: Callable[[CollectionState], bool] = staticmethod(lambda state: True) item_rule = staticmethod(lambda item: True) item: Optional[Item] = None From 67ed0fdca56220d885c81160ab6864bd30b0c163 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Wed, 13 Mar 2024 08:25:51 +0100 Subject: [PATCH 127/166] CI: update actions (#2943) --- .github/workflows/analyze-modified-files.yml | 4 ++-- .github/workflows/build.yml | 8 ++++---- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/release.yml | 8 ++++---- .github/workflows/unittests.yml | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/analyze-modified-files.yml b/.github/workflows/analyze-modified-files.yml index d01365745c..c9995fa2d0 100644 --- a/.github/workflows/analyze-modified-files.yml +++ b/.github/workflows/analyze-modified-files.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: "Determine modified files (pull_request)" if: github.event_name == 'pull_request' @@ -50,7 +50,7 @@ jobs: run: | echo "diff=." >> $GITHUB_ENV - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: env.diff != '' with: python-version: 3.8 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc4e409783..80aaf70c21 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,14 +76,14 @@ jobs: runs-on: ubuntu-20.04 steps: # - copy code below to release.yml - - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install base dependencies run: | sudo apt update sudo apt -y install build-essential p7zip xz-utils wget libglib2.0-0 sudo apt -y install python3-gi libgirepository1.0-dev # should pull dependencies for gi installation below - name: Get a recent python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install build-time dependencies @@ -119,13 +119,13 @@ jobs: source venv/bin/activate python setup.py build_exe --yes - name: Store AppImage - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.APPIMAGE_NAME }} path: dist/${{ env.APPIMAGE_NAME }} retention-days: 7 - name: Store .tar.gz - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.TAR_NAME }} path: dist/${{ env.TAR_NAME }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 6aeb477a22..b0cfe35d2b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -43,7 +43,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cc68a88b76..2d7f1253b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: - name: Set env run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV # tag x.y.z will become "Archipelago x.y.z" - name: Create Release - uses: softprops/action-gh-release@b7e450da2a4b4cb4bfbae528f788167786cfcedf + uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a with: draft: true # don't publish right away, especially since windows build is added by hand prerelease: false @@ -35,14 +35,14 @@ jobs: - name: Set env run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV # - code below copied from build.yml - - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install base dependencies run: | sudo apt update sudo apt -y install build-essential p7zip xz-utils wget libglib2.0-0 sudo apt -y install python3-gi libgirepository1.0-dev # should pull dependencies for gi installation below - name: Get a recent python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install build-time dependencies @@ -74,7 +74,7 @@ jobs: echo "TAR_NAME=$TAR_NAME" >> $GITHUB_ENV # - code above copied from build.yml - - name: Add to Release - uses: softprops/action-gh-release@b7e450da2a4b4cb4bfbae528f788167786cfcedf + uses: softprops/action-gh-release@975c1b265e11dd76618af1c374e7981f9a6ff44a with: draft: true # see above prerelease: false diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 1a76a7f471..b2530bd06c 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -46,9 +46,9 @@ jobs: os: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python.version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python.version }} - name: Install dependencies From 72e6383cc799affce623290d3764fe7e2e2f6bb8 Mon Sep 17 00:00:00 2001 From: qwint Date: Wed, 13 Mar 2024 06:45:43 -0500 Subject: [PATCH 128/166] HK: Removes Vanilla Items from ItemPool and Uses Grimmchild1 when relevant (#2898) --- worlds/hk/Items.py | 1 + worlds/hk/Options.py | 11 ++++++++++- worlds/hk/__init__.py | 37 ++++++++++++++++++++++++++----------- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/worlds/hk/Items.py b/worlds/hk/Items.py index def5c32981..72878dfc71 100644 --- a/worlds/hk/Items.py +++ b/worlds/hk/Items.py @@ -35,6 +35,7 @@ item_name_groups = ({ "GeoChests": lookup_type_to_names["Geo"], "GeoRocks": lookup_type_to_names["Rock"], "GrimmkinFlames": lookup_type_to_names["Flame"], + "Grimmchild": {"Grimmchild1", "Grimmchild2"}, "Grubs": lookup_type_to_names["Grub"], "JournalEntries": lookup_type_to_names["Journal"], "JunkPitChests": lookup_type_to_names["JunkPitChest"], diff --git a/worlds/hk/Options.py b/worlds/hk/Options.py index ef7fbd0dfe..21e8c179e8 100644 --- a/worlds/hk/Options.py +++ b/worlds/hk/Options.py @@ -406,6 +406,15 @@ class ExtraPlatforms(DefaultOnToggle): """Places additional platforms to make traveling throughout Hallownest more convenient.""" +class AddUnshuffledLocations(Toggle): + """Adds non-randomized locations to the location pool, which allows syncing + of location state with co-op or automatic collection via collect. + + Note: This will increase the number of location checks required to purchase + hints to the total maximum. + """ + + class DeathLinkShade(Choice): """Sets whether to create a shade when you are killed by a DeathLink and how to handle your existing shade, if any. @@ -488,7 +497,7 @@ hollow_knight_options: typing.Dict[str, type(Option)] = { **{ option.__name__: option for option in ( - StartLocation, Goal, WhitePalace, ExtraPlatforms, StartingGeo, + StartLocation, Goal, WhitePalace, ExtraPlatforms, AddUnshuffledLocations, StartingGeo, DeathLink, DeathLinkShade, DeathLinkBreaksFragileCharms, MinimumGeoPrice, MaximumGeoPrice, MinimumGrubPrice, MaximumGrubPrice, diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py index 8b07b34eb0..25337598ec 100644 --- a/worlds/hk/__init__.py +++ b/worlds/hk/__init__.py @@ -234,7 +234,7 @@ class HKWorld(World): randomized_starting_items.update(items) # noinspection PyShadowingNames - def _add(item_name: str, location_name: str): + def _add(item_name: str, location_name: str, randomized: bool): """ Adds a pairing of an item and location, doing appropriate checks to see if it should be vanilla or not. """ @@ -252,7 +252,7 @@ class HKWorld(World): if item_name in junk_replace: item_name = self.get_filler_item_name() - item = self.create_item(item_name) + item = self.create_item(item_name) if not vanilla or location_name == "Start" or self.multiworld.AddUnshuffledLocations[self.player] else self.create_event(item_name) if location_name == "Start": if item_name in randomized_starting_items: @@ -277,30 +277,35 @@ class HKWorld(World): for option_key, option in hollow_knight_randomize_options.items(): randomized = getattr(self.multiworld, option_key)[self.player] + if all([not randomized, option_key in logicless_options, not self.multiworld.AddUnshuffledLocations[self.player]]): + continue for item_name, location_name in zip(option.items, option.locations): if item_name in junk_replace: item_name = self.get_filler_item_name() if (item_name == "Crystal_Heart" and self.multiworld.SplitCrystalHeart[self.player]) or \ (item_name == "Mothwing_Cloak" and self.multiworld.SplitMothwingCloak[self.player]): - _add("Left_" + item_name, location_name) - _add("Right_" + item_name, "Split_" + location_name) + _add("Left_" + item_name, location_name, randomized) + _add("Right_" + item_name, "Split_" + location_name, randomized) continue if item_name == "Mantis_Claw" and self.multiworld.SplitMantisClaw[self.player]: - _add("Left_" + item_name, "Left_" + location_name) - _add("Right_" + item_name, "Right_" + location_name) + _add("Left_" + item_name, "Left_" + location_name, randomized) + _add("Right_" + item_name, "Right_" + location_name, randomized) continue if item_name == "Shade_Cloak" and self.multiworld.SplitMothwingCloak[self.player]: if self.multiworld.random.randint(0, 1): item_name = "Left_Mothwing_Cloak" else: item_name = "Right_Mothwing_Cloak" + if item_name == "Grimmchild2" and self.multiworld.RandomizeGrimmkinFlames[self.player] and self.multiworld.RandomizeCharms[self.player]: + _add("Grimmchild1", location_name, randomized) + continue - _add(item_name, location_name) + _add(item_name, location_name, randomized) if self.multiworld.RandomizeElevatorPass[self.player]: randomized = True - _add("Elevator_Pass", "Elevator_Pass") + _add("Elevator_Pass", "Elevator_Pass", randomized) for shop, locations in self.created_multi_locations.items(): for _ in range(len(locations), getattr(self.multiworld, shop_to_option[shop])[self.player].value): @@ -475,6 +480,10 @@ class HKWorld(World): item_data = item_table[name] return HKItem(name, item_data.advancement, item_data.id, item_data.type, self.player) + def create_event(self, name: str) -> HKItem: + item_data = item_table[name] + return HKItem(name, item_data.advancement, None, item_data.type, self.player) + def create_location(self, name: str, vanilla=False) -> HKLocation: costs = None basename = name @@ -493,9 +502,15 @@ class HKWorld(World): name = f"{name}_{i}" region = self.multiworld.get_region("Menu", self.player) - loc = HKLocation(self.player, name, - self.location_name_to_id[name], region, costs=costs, vanilla=vanilla, - basename=basename) + + if vanilla and not self.multiworld.AddUnshuffledLocations[self.player]: + loc = HKLocation(self.player, name, + None, region, costs=costs, vanilla=vanilla, + basename=basename) + else: + loc = HKLocation(self.player, name, + self.location_name_to_id[name], region, costs=costs, vanilla=vanilla, + basename=basename) if multi is not None: multi.append(loc) From 3e3965272da10b793ed1d4c803e6b80ee59840e3 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:05:38 -0500 Subject: [PATCH 129/166] KDL3: Ensure all abilities accessible on non-minimal (#2929) --- worlds/kdl3/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/worlds/kdl3/__init__.py b/worlds/kdl3/__init__.py index 66c9b17b84..6d0c196ab1 100644 --- a/worlds/kdl3/__init__.py +++ b/worlds/kdl3/__init__.py @@ -129,6 +129,8 @@ class KDL3World(World): # randomize copy abilities valid_abilities = list(copy_ability_access_table.keys()) enemies_to_set = list(self.copy_abilities.keys()) + unplaced_abilities = set(key for key in copy_ability_access_table.keys() + if key not in ("No Ability", "Cutter Ability", "Burning Ability")) # now for the edge cases for abilities, enemies in enemy_restrictive: available_enemies = list() @@ -143,6 +145,7 @@ class KDL3World(World): chosen_ability = self.random.choice(abilities) self.copy_abilities[chosen_enemy] = chosen_ability enemies_to_set.remove(chosen_enemy) + unplaced_abilities.discard(chosen_ability) # two less restrictive ones, we need to ensure Cutter and Burning appear before their required stages sand_canyon_5 = self.get_region("Sand Canyon 5 - 9") # this is primarily for typing, but if this ever hits it's fine to crash @@ -160,6 +163,13 @@ class KDL3World(World): if burning_enemy: self.copy_abilities[burning_enemy] = "Burning Ability" enemies_to_set.remove(burning_enemy) + # ensure we place one of every ability + if unplaced_abilities and self.options.accessibility != self.options.accessibility.option_minimal: + # failsafe, on non-minimal we need to guarantee every copy ability exists + for ability in sorted(unplaced_abilities): + enemy = self.random.choice(enemies_to_set) + self.copy_abilities[enemy] = ability + enemies_to_set.remove(enemy) # place remaining for enemy in enemies_to_set: self.copy_abilities[enemy] = self.random.choice(valid_abilities) @@ -283,6 +293,8 @@ class KDL3World(World): self.boss_butch_bosses = [True for _ in range(6)] else: self.boss_butch_bosses = [self.random.choice([True, False]) for _ in range(6)] + else: + self.boss_butch_bosses = [False for _ in range(6)] def generate_output(self, output_directory: str): rom_path = "" From fa233b25831a4a0ea0d7ca3dae5381e164783c30 Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Thu, 14 Mar 2024 05:37:10 -0600 Subject: [PATCH 130/166] Pokemon Emerald: v2 Update (#2918) --- worlds/pokemon_emerald/CHANGELOG.md | 186 + worlds/pokemon_emerald/README.md | 57 +- worlds/pokemon_emerald/__init__.py | 821 ++-- worlds/pokemon_emerald/client.py | 536 ++- worlds/pokemon_emerald/data.py | 1463 ++++-- .../pokemon_emerald/data/base_patch.bsdiff4 | Bin 209743 -> 243175 bytes .../pokemon_emerald/data/extracted_data.json | 2 +- worlds/pokemon_emerald/data/items.json | 1016 +++-- worlds/pokemon_emerald/data/locations.json | 4057 ++++++++++++++++- .../data/regions/battle_frontier.json | 458 ++ .../pokemon_emerald/data/regions/cities.json | 1391 +++++- .../data/regions/dungeons.json | 1054 ++++- .../data/regions/{unused => }/islands.json | 120 +- .../pokemon_emerald/data/regions/routes.json | 1693 ++++++- .../data/regions/unused/battle_frontier.json | 368 +- .../data/regions/unused/dungeons.json | 50 +- .../data/regions/unused/routes.json | 82 - .../data/trade_pokemon_schema.json | 162 + .../docs/en_Pokemon Emerald.md | 26 +- .../{data/README.md => docs/region data.md} | 38 +- worlds/pokemon_emerald/docs/rom changes.md | 36 +- worlds/pokemon_emerald/docs/warps.md | 50 + worlds/pokemon_emerald/docs/wonder trades.md | 103 + worlds/pokemon_emerald/items.py | 6 +- worlds/pokemon_emerald/locations.py | 182 +- worlds/pokemon_emerald/opponents.py | 116 + worlds/pokemon_emerald/options.py | 415 +- worlds/pokemon_emerald/pokemon.py | 660 ++- worlds/pokemon_emerald/regions.py | 80 +- worlds/pokemon_emerald/rom.py | 725 ++- worlds/pokemon_emerald/rules.py | 1003 ++-- worlds/pokemon_emerald/sanity_check.py | 137 +- .../test/test_accessibility.py | 19 +- worlds/pokemon_emerald/util.py | 340 +- 34 files changed, 14212 insertions(+), 3240 deletions(-) create mode 100644 worlds/pokemon_emerald/CHANGELOG.md create mode 100644 worlds/pokemon_emerald/data/regions/battle_frontier.json rename worlds/pokemon_emerald/data/regions/{unused => }/islands.json (73%) delete mode 100644 worlds/pokemon_emerald/data/regions/unused/routes.json create mode 100644 worlds/pokemon_emerald/data/trade_pokemon_schema.json rename worlds/pokemon_emerald/{data/README.md => docs/region data.md} (74%) create mode 100644 worlds/pokemon_emerald/docs/warps.md create mode 100644 worlds/pokemon_emerald/docs/wonder trades.md create mode 100644 worlds/pokemon_emerald/opponents.py diff --git a/worlds/pokemon_emerald/CHANGELOG.md b/worlds/pokemon_emerald/CHANGELOG.md new file mode 100644 index 0000000000..dbc1123b77 --- /dev/null +++ b/worlds/pokemon_emerald/CHANGELOG.md @@ -0,0 +1,186 @@ +# 2.0.0 + +### Features +- Picking up items for other players will display the actual item name and receiving player in-game instead of +"ARCHIPELAGO ITEM". (This does have a limit, but you're unlikely to reach it in all but the largest multiworlds.) +- New goal `legendary_hunt`. Your goal is to catch/defeat some number of legendary encounters. That is, the static +encounters themselves, whatever species they may be. Legendary species found in the wild don't count. + - You can force the goal to require captures with `legendary_hunt_catch`. If you accidentally faint a legendary, you + can respawn it by beating the champion. + - The number of legendaries needed is controlled by the `legendary_hunt_count` option. + - The caves containing Kyogre and Groudon are fixed to one location per seed. You need to go to the weather + institute to trigger a permanent weather event at the corresponding locations. Only one weather event can be active + at a time. + - The move tutor for the move Sleep Talk has been changed to Dig and is unlimited use (for Sealed Chamber). + - Relicanth and Wailord are guaranteed to be reachable in the wild (for Sealed Chamber). Interacting with the Sealed + Chamber wall will give you dex info for Wailord and Relicanth. + - Event legendaries are included for this goal (see below for new ferry behavior and event tickets). + - The roamer is included in this count. It will _always_ be Latios no matter what your options are. Otherwise you + might not have any way of knowing which species is roaming to be able to track it. In legendary hunt, Latios will + never appear as a wild pokemon to make tracking it easier. The television broadcast that creates the roamer will + give you dex info for Latios. + - You can set which encounters are considered for this goal with the `allowed_legendary_hunt_encounters` option. +- New option `dexsanity`. Adds pokedex entries as locations. + - Added locations contribute either a Poke Ball, Great Ball, or Ultra Ball to the item pool, based on the evolution + stage. + - Logic uses only wild encounters for now. + - Defeating a gym leader awards "seen" info on 1/8th of the pokedex. +- New option `trainersanity`. Defeating a trainer awards a random item. + - Trainers no longer award money upon defeat. Instead they add a sellable item to the item pool. + - Missable trainers are prevented from disappearing when this is enabled. + - Gym trainers remain active after their leader is defeated. + - Does not include trainers in the Trick House. +- New option `berry_trees`. Adds berry trees as locations. + - All soil patches start with a fully grown berry tree that gives one item. + - There are 88 berry trees. + - Berries cannot be planted in soil with this option enabled. + - Soil that doesn't start with a tree on a fresh save contributes a Sitrus Berry to the item pool. +- New option `death_link`. Forgive me, Figment. +- Added Artisan Cave locations + - Requires Wailmer Pail and the ability to Surf to access. +- Added Trick House locations. The Trick Master is finally here! + - He will make new layouts only if you have the corresponding badge (or beat the game) and have completed the + previous layout (all vanilla behavior). + - If you neglect to pick up an item in a puzzle before completing it, the Trick Master will give the item to you + alongside the prize. + - Locations are enabled or disabled with their broader categories (npc gifts, overworld items, etc...) +- Added daily berry gift locations. There are a half dozen or so NPCs that give you one or two berries per day. + - All these locations are considered NPC gifts. + - The NPCs have been reworked to give this gift once permanently so they can be added as locations. +- New option `remote_items`. All randomized items are sent from the server instead of being patched into your game +(except for start inventory, which remains in the PC) + - As a side effect, when you pick up your own item, there will be a gap between the item disappearing from the + overworld and your game actually receiving it. It also causes gifts from NPCs which contain your own items to not + show up until after their text box closes. It can feel odd, but there should be no danger to it. + - If the seed is in race mode, this is forcibly enabled. + - Benefits include: + - Two players can play the same slot and both receive items that slot picks up for itself (as long as it was + randomized) + - You receive items you picked up for yourself if you lose progress on your save + - Competitive integrity; the patch file no longer has any knowledge of item placement +- New option `match_trainer_levels`. This is a sort of pseudo level cap for a randomizer context. + - When you start a trainer fight, all your pokemon have their levels temporarily set to the highest level in the + opponent's party. + - During the battle, all earned exp is set to 0 (EVs are still gained during battle as normal). When the outcome of + the battle is decided, your pokemon have their levels reset to what they were before the fight and exp is awarded as + it would have been without this option. Think of it as holding earned exp in reserve and awarding it at the end + instead, even giving it to fainted pokemon if they earned any before fainting. + - Exp gain is based on _your_ party's average level to moderate exp over the course of a seed. Wild battles are + entirely unchanged by this option. +- New option `match_trainer_levels_bonus`. A flat bonus to apply to your party's levels when using +`match_trainer_levels`. In case you want to give yourself a nerf or buff while still approximately matching your +opponent. +- New option `force_fully_evolved`. Define a level at which trainers will stop using pokemon that have further evolution +stages. +- New option `move_blacklist`. Define a list of moves that should not be given randomly to learnsets or TMs. Move names +are accurate to Gen 3 except for capitalization. +- New option `extra_bumpy_slope`. Adds a "bumpy slope" to Route 115 that lets you hop up the ledge with the Acro Bike. +- New option `modify_118`. Changes Route 118 so that it must be crossed with the Acro Bike, and cannot be crossed by +surfing. +- Changed `require_flash` option to a choice between none, only granite cave, only victory road, or both caves. +- Removed `static_encounters` option. +- New option `legendary_encounters`. Replaces `static_encounters`, but only concerns legendaries. +- New option `misc_pokemon`. Replaces `static_encounters`, but only concerns non-legendaries. +- Removed `fly_without_badge` option. (Don't worry) +- New option `hm_requirements`. Will eventually be able to give you more control over the badge requirements for all +HMs. For now, only includes the presets `vanilla` and `fly_without_badge`. +- Removed `allow_wild_legendaries`, `allow_starter_legendaries`, and `allow_trainer_legendaries` options. +- New options `wild_encounter_blacklist`, `starter_blacklist`, and `trainer_party_blacklist`. + - These take lists of species and prevent them from randomizing into the corresponding categories + - If adhering to your blacklist would make it impossible to choose a random species, your blacklist is ignored in + that case + - All three include a shorthand for excluding legendaries +- Removed `enable_ferry` option. + - The ferry is now always present. + - The S.S. Ticket item/location is now part of `key_items`. +- Added event tickets and islands. + - All event tickets are given to the player by Norman after defeating the Champion alongside the S.S. Ticket. + - As in vanilla, these tickets are only usable from Lilycove. Not Slateport or the Battle Frontier. +- New option `event_tickets`. Randomizes the above-mentioned tickets into the item pool. +- New option `enable_wonder_trading`. You can participate in Wonder Trading by interacting with the center receptionist +on the second floor of Pokemon Centers. + - Why is this an option instead of just being enabled? You might want to disable wonder trading in a meta yaml to + make sure certain rules can't be broken. Or you may want to turn it off for yourself to definitively prevent being + asked for help if you prefer to keep certain walls up between your game and others. Trades _do_ include items and + known moves, which means there is potential for an extra level of cooperation and even ways to go out of logic. But + that's not a boundary everyone wants broken down all the time. Please be respectful of someone's choice to not + participate if that's their preference. + - A lot of time was spent trying to make this all work without having to touch your client. Hopefully it goes + smoothly, but there's room for jank. Anything you decide to give to her you should consider gone forever, whether + because it was traded away or because something "went wrong in transit" and the pokemon's data got lost after being + removed from the server. + - Wonder Trading is _not_ resistant to save scumming in either direction. You _could_ abuse it to dupe pokemon, + because there's not realistically a way for me to prevent it, but I'd urge you to stick to the spirit of the design + unless everyone involved doesn't mind. + - The wonder trades you receive are stored in your save data even before you pick them up, so if you save after the + client tells you that you received a wonder trade, it's safe. You don't need to retrieve it from a poke center for + it to persist. However, if you reset your game to a point in time before your client popped the "Wonder trade + received" message, that pokemon is lost forever. +- New `easter_egg` passphrase system. + - All valid easter egg passphrases will be a phrase that it's possible to submit as a trendy phrase in Dewford Town. + Changing the trendy phrase does ***not*** trigger easter eggs. Only the phrase you put in your YAML can trigger an + easter egg. + - There may be other ways to learn more information. + - Phrases are case insensitive. Here are a couple examples of possible phrases: `"GET FOE"`, + `"HERE GOES GRANDMOTHER"`, `"late eh?"` (None of those do anything, but I'd love to hear what you think they would.) +- Added three new easter egg effects. +- Changed the original easter egg phrase to use the new system. +- Renamed `tm_moves` to `tm_tutor_moves`. Move tutors are also affected by this option (except the new Dig tutor). +- Renamed `tm_compatibility` to `tm_tutor_compatibility`. Move tutors are also affected by this option. +- Changed `tm_tutor_compatibility` to be a percent chance instead of a choice. Use `-1` for vanilla. +- Changed `hm_compatibility` to be a percent chance instead of a choice. Use `-1` for vanilla. +- New option `music`. Shuffles all looping music. Includes FRLG tracks and possibly some unused stuff. +- New option `fanfares`. Shuffles all fanfares. Includes FRLG tracks. When this is enabled, pressing B will interrupt +most fanfares. +- New option `purge_spinners`. Trainers that change which direction they face will do so predictably, and will no longer +turn to face you when you run. +- New option `normalize_encounter_rates`. Sets every encounter slot to (almost) equal probability. Does NOT make every +species equally likely to appear, but makes rare encounters less rare. +- Added `Trick House` location group. +- Removed `Postgame Locations` location group. + +### QoL + +- Can teach moves over HM moves. +- Fishing is much less random; pokemon will always bite if there's an encounter there. +- Mirage Island is now always present. +- Waking Rayquaza is no longer required. After releasing Kyogre, going to Sootopolis will immediately trigger the +Rayquaza cutscene. +- Renamed some locations to be more accurate. +- Most trainers will no longer ask to be registered in your Pokegear after battle. Also removed most step-based match +calls. +- Removed a ledge on Route 123. With careful routing, it's now possible to check every location without having to save +scum or go back around. +- Added "GO HOME" button on the start menu where "EXIT" used to be. Will teleport you to Littleroot. +- Some locations which are directly blocked by completing your goal are automatically excluded. + - For example, the S.S. Ticket and a Champion goal, or the Sludge Bomb TM and the Norman goal. + - Your particular options might still result in locations that can't be reached until after your goal. For example, + setting a Norman goal and setting your E4 requirement to 8 gyms means that post-Champion locations will not be + reachable before defeating Norman, but they are NOT excluded by this modification. That's one of the simpler + examples. It is extremely tedious to try to detect these sorts of situations, so I'll instead leave it to you to be + aware of your own options. +- Species in the pokedex are searchable by type even if you haven't caught that species yet + +### Fixes + +- Mt. Pyre summit state no longer changes when you finish the Sootopolis events, which would lock you out of one or two +locations. +- Whiting out under certain conditions no longer softlocks you by moving Mr. Briney to an inaccessible area. +- It's no longer possible to join a room using the wrong patch file, even if the slot names match. +- NPCs now stop moving while you're receiving an item. +- Creating a secret base no longer triggers sending the Secret Power TM location. +- Hopefully fix bug where receiving an item while walking over a trigger can skip that trigger (the Moving +Truck/Petalburg wrong warp) + +## Easter Eggs + +There are plenty among you who are capable of ~~cheating~~ finding information about the easter egg phrases by reading +source code, writing brute force scripts, and inspecting memory for clues and answers. By all means, go ahead, that can +be your version of this puzzle and I don't intend to stand in your way. **However**, I would ask that any information +you come up with by doing this, you keep entirely to yourself until the community as a whole has figured out what you +know. There was not previously a way to reasonably learn about or make guesses at the easter egg, but that has changed. +There are mechanisms by which solutions can be found or guessed over the course of multiple games by multiple people, +and I'd rather the fun not get spoiled immediately. + +Once a solution has been found I'd _still_ prefer discussion about hints and effects remain behind spoiler tags just in +case there are people who want to do the hunt on their own. Thank you all, and good luck. diff --git a/worlds/pokemon_emerald/README.md b/worlds/pokemon_emerald/README.md index 2c1e9e3560..8441afc56a 100644 --- a/worlds/pokemon_emerald/README.md +++ b/worlds/pokemon_emerald/README.md @@ -1,58 +1,3 @@ # Pokemon Emerald -Version 1.2.1 - -This README contains general info useful for understanding the world. Pretty much all the long lists of locations, -regions, and items are stored in `data/` and (mostly) loaded in by `data.py`. Access rules are in `rules.py`. Check -[data/README.md](data/README.md) for more detailed information on the JSON files holding most of the data. - -## Warps - -Quick note to start, you should not be defining or modifying encoded warps from this repository. They're encoded in the -source code repository for the mod, and then assigned to regions in `data/regions/`. All warps in the game already exist -within `extracted_data.json`, and all relevant warps are already placed in `data/regions/` (unless they were deleted -accidentally). - -Many warps are actually two or three events acting as one logical warp. Doorways, for example, are often 2 tiles wide -indoors but only 1 tile wide outdoors. Both indoor warps point to the outdoor warp, and the outdoor warp points to only -one of the indoor warps. We want to describe warps logically in a way that retains information about individual warp -events. That way a 2-tile-wide doorway doesnt look like a one-way warp next to an unrelated two-way warp, but if we want -to randomize the destinations of those warps, we can still get back each individual id of the multi-tile warp. - -This is how warps are encoded: - -`{source_map}:{source_warp_ids}/{dest_map}:{dest_warp_ids}[!]` - -- `source_map`: The map the warp events are located in -- `source_warp_ids`: The ids of all adjacent warp events in source_map which lead to the same destination (these must be -in ascending order) -- `dest_map`: The map of the warp event to which this one is connected -- `dest_warp_ids`: The ids of the warp events in dest_map -- `[!]`: If the warp expects to lead to a destination which doesnot lead back to it, add a ! to the end - -Example: `MAP_LAVARIDGE_TOWN_HOUSE:0,1/MAP_LAVARIDGE_TOWN:4` - -Example 2: `MAP_AQUA_HIDEOUT_B1F:14/MAP_AQUA_HIDEOUT_B1F:12!` - -Note: A warp must have its destination set to another warp event. However, that does not guarantee that the destination -warp event will warp back to the source. - -Note 2: Some warps _only_ act as destinations and cannot actually be interacted with by the player as sources. These are -usually places you fall from a hole above. At the time of writing, these are actually not accounted for, but there are -no instances where it changes logical access. - -Note 3: Some warp destinations go to the map `MAP_DYNAMIC` and have a special warp id. These edge cases are: - -- The Moving Truck -- Terra Cave -- Marine Cave -- The Department Store Elevator -- Secret Bases -- The Trade Center -- The Union Room -- The Record Corner -- 2P/4P Battle Colosseum - -Note 4: The trick house on Route 110 changes the warp destinations of its entrance and ending room as you progress -through the puzzles, but the source code only sets the trick house up for the first puzzle, and I assume the destination -gets overwritten at run time when certain flags are set. +Version 2.0.0 diff --git a/worlds/pokemon_emerald/__init__.py b/worlds/pokemon_emerald/__init__.py index 4d40dd1966..c17fd1bc19 100644 --- a/worlds/pokemon_emerald/__init__.py +++ b/worlds/pokemon_emerald/__init__.py @@ -5,7 +5,7 @@ from collections import Counter import copy import logging import os -from typing import Any, Set, List, Dict, Optional, Tuple, ClassVar +from typing import Any, Set, List, Dict, Optional, Tuple, ClassVar, TextIO, Union from BaseClasses import ItemClassification, MultiWorld, Tutorial, LocationProgressType from Fill import FillError, fill_restrictive @@ -14,21 +14,18 @@ import settings from worlds.AutoWorld import WebWorld, World from .client import PokemonEmeraldClient # Unused, but required to register with BizHawkClient -from .data import (SpeciesData, MapData, EncounterTableData, LearnsetMove, TrainerPokemonData, StaticEncounterData, - TrainerData, data as emerald_data) +from .data import LEGENDARY_POKEMON, MapData, SpeciesData, TrainerData, data as emerald_data from .items import (ITEM_GROUPS, PokemonEmeraldItem, create_item_label_to_code_map, get_item_classification, offset_item_value) from .locations import (LOCATION_GROUPS, PokemonEmeraldLocation, create_location_label_to_id_map, - create_locations_with_tags) -from .options import (Goal, ItemPoolType, RandomizeWildPokemon, RandomizeBadges, RandomizeTrainerParties, RandomizeHms, - RandomizeStarters, LevelUpMoves, RandomizeAbilities, RandomizeTypes, TmCompatibility, - HmCompatibility, RandomizeStaticEncounters, NormanRequirement, PokemonEmeraldOptions) -from .pokemon import get_random_species, get_random_move, get_random_damaging_move, get_random_type -from .regions import create_regions -from .rom import PokemonEmeraldDeltaPatch, generate_output, location_visited_event_to_id_map -from .rules import set_rules -from .sanity_check import validate_regions -from .util import int_to_bool_array, bool_array_to_int + create_locations_with_tags, set_free_fly, set_legendary_cave_entrances) +from .opponents import randomize_opponent_parties +from .options import (Goal, DarkCavesRequireFlash, HmRequirements, ItemPoolType, PokemonEmeraldOptions, + RandomizeWildPokemon, RandomizeBadges, RandomizeHms, NormanRequirement) +from .pokemon import (get_random_move, get_species_id_by_label, randomize_abilities, randomize_learnsets, + randomize_legendary_encounters, randomize_misc_pokemon, randomize_starters, + randomize_tm_hm_compatibility,randomize_types, randomize_wild_encounters) +from .rom import PokemonEmeraldDeltaPatch, create_patch class PokemonEmeraldWebWorld(WebWorld): @@ -89,22 +86,46 @@ class PokemonEmeraldWorld(World): item_name_groups = ITEM_GROUPS location_name_groups = LOCATION_GROUPS - data_version = 1 + data_version = 2 required_client_version = (0, 4, 3) - badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] = None - hm_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] = None - free_fly_location_id: int = 0 + badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] + hm_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] + free_fly_location_id: int + blacklisted_moves: Set[int] + blacklisted_wilds: Set[int] + blacklisted_starters: Set[int] + blacklisted_opponent_pokemon: Set[int] + hm_requirements: Dict[str, Union[int, List[str]]] + auth: bytes - modified_species: List[Optional[SpeciesData]] - modified_maps: List[MapData] + modified_species: Dict[int, SpeciesData] + modified_maps: Dict[str, MapData] modified_tmhm_moves: List[int] - modified_static_encounters: List[int] + modified_legendary_encounters: List[int] modified_starters: Tuple[int, int, int] modified_trainers: List[TrainerData] + def __init__(self, multiworld, player): + super(PokemonEmeraldWorld, self).__init__(multiworld, player) + self.badge_shuffle_info = None + self.hm_shuffle_info = None + self.free_fly_location_id = 0 + self.blacklisted_moves = set() + self.blacklisted_wilds = set() + self.blacklisted_starters = set() + self.blacklisted_opponent_pokemon = set() + self.modified_maps = copy.deepcopy(emerald_data.maps) + self.modified_species = copy.deepcopy(emerald_data.species) + self.modified_tmhm_moves = [] + self.modified_starters = emerald_data.starters + self.modified_trainers = [] + self.modified_legendary_encounters = [] + @classmethod def stage_assert_generate(cls, multiworld: MultiWorld) -> None: + from .sanity_check import validate_regions + if not os.path.exists(cls.settings.rom_file): raise FileNotFoundError(cls.settings.rom_file) @@ -114,15 +135,83 @@ class PokemonEmeraldWorld(World): return "Great Ball" def generate_early(self) -> None: - # If badges or HMs are vanilla, Norman locks you from using Surf, which means you're not guaranteed to be - # able to reach Fortree Gym, Mossdeep Gym, or Sootopolis Gym. So we can't require reaching those gyms to - # challenge Norman or it creates a circular dependency. - # This is never a problem for completely random badges/hms because the algo will not place Surf/Balance Badge - # on Norman on its own. It's never a problem for shuffled badges/hms because there is no scenario where Cut or - # the Stone Badge can be a lynchpin for access to any gyms, so they can always be put on Norman in a worst case - # scenario. - # This will also be a problem in warp rando if direct access to Norman's room requires Surf or if access - # any gym leader in general requires Surf. We will probably have to force this to 0 in that case. + self.hm_requirements = { + "HM01 Cut": ["Stone Badge"], + "HM02 Fly": ["Feather Badge"], + "HM03 Surf": ["Balance Badge"], + "HM04 Strength": ["Heat Badge"], + "HM05 Flash": ["Knuckle Badge"], + "HM06 Rock Smash": ["Dynamo Badge"], + "HM07 Waterfall": ["Rain Badge"], + "HM08 Dive": ["Mind Badge"], + } + if self.options.hm_requirements == HmRequirements.option_fly_without_badge: + self.hm_requirements["HM02 Fly"] = 0 + + self.blacklisted_moves = {emerald_data.move_labels[label] for label in self.options.move_blacklist.value} + + self.blacklisted_wilds = { + get_species_id_by_label(species_name) + for species_name in self.options.wild_encounter_blacklist.value + if species_name != "_Legendaries" + } + if "_Legendaries" in self.options.wild_encounter_blacklist.value: + self.blacklisted_wilds |= LEGENDARY_POKEMON + + self.blacklisted_starters = { + get_species_id_by_label(species_name) + for species_name in self.options.starter_blacklist.value + if species_name != "_Legendaries" + } + if "_Legendaries" in self.options.starter_blacklist.value: + self.blacklisted_starters |= LEGENDARY_POKEMON + + self.blacklisted_opponent_pokemon = { + get_species_id_by_label(species_name) + for species_name in self.options.trainer_party_blacklist.value + if species_name != "_Legendaries" + } + if "_Legendaries" in self.options.starter_blacklist.value: + self.blacklisted_opponent_pokemon |= LEGENDARY_POKEMON + + # In race mode we don't patch any item location information into the ROM + if self.multiworld.is_race and not self.options.remote_items: + logging.warning("Pokemon Emerald: Forcing Player %s (%s) to use remote items due to race mode.", + self.player, self.multiworld.player_name[self.player]) + self.options.remote_items.value = Toggle.option_true + + if self.options.goal == Goal.option_legendary_hunt: + # Prevent turning off all legendary encounters + if len(self.options.allowed_legendary_hunt_encounters.value) == 0: + raise ValueError(f"Pokemon Emerald: Player {self.player} ({self.multiworld.player_name[self.player]}) " + "needs to allow at least one legendary encounter when goal is legendary hunt.") + + # Prevent setting the number of required legendaries higher than the number of enabled legendaries + if self.options.legendary_hunt_count.value > len(self.options.allowed_legendary_hunt_encounters.value): + logging.warning("Pokemon Emerald: Legendary hunt count for Player %s (%s) higher than number of allowed " + "legendary encounters. Reducing to number of allowed encounters.", self.player, + self.multiworld.player_name[self.player]) + self.options.legendary_hunt_count.value = len(self.options.allowed_legendary_hunt_encounters.value) + + # Require random wild encounters if dexsanity is enabled + if self.options.dexsanity and self.options.wild_pokemon == RandomizeWildPokemon.option_vanilla: + raise ValueError(f"Pokemon Emerald: Player {self.player} ({self.multiworld.player_name[self.player]}) must " + "not leave wild encounters vanilla if enabling dexsanity.") + + # If badges or HMs are vanilla, Norman locks you from using Surf, + # which means you're not guaranteed to be able to reach Fortree Gym, + # Mossdeep Gym, or Sootopolis Gym. So we can't require reaching those + # gyms to challenge Norman or it creates a circular dependency. + # + # This is never a problem for completely random badges/hms because the + # algo will not place Surf/Balance Badge on Norman on its own. It's + # never a problem for shuffled badges/hms because there is no scenario + # where Cut or the Stone Badge can be a lynchpin for access to any gyms, + # so they can always be put on Norman in a worst case scenario. + # + # This will also be a problem in warp rando if direct access to Norman's + # room requires Surf or if access any gym leader in general requires + # Surf. We will probably have to force this to 0 in that case. max_norman_count = 7 if self.options.badges == RandomizeBadges.option_vanilla: @@ -141,17 +230,22 @@ class PokemonEmeraldWorld(World): self.options.norman_count.value = max_norman_count def create_regions(self) -> None: + from .regions import create_regions regions = create_regions(self) - tags = {"Badge", "HM", "KeyItem", "Rod", "Bike"} + tags = {"Badge", "HM", "KeyItem", "Rod", "Bike", "EventTicket"} # Tags with progression items always included if self.options.overworld_items: tags.add("OverworldItem") if self.options.hidden_items: tags.add("HiddenItem") if self.options.npc_gifts: tags.add("NpcGift") - if self.options.enable_ferry: - tags.add("Ferry") + if self.options.berry_trees: + tags.add("BerryTree") + if self.options.dexsanity: + tags.add("Pokedex") + if self.options.trainersanity: + tags.add("Trainer") create_locations_with_tags(self, regions, tags) self.multiworld.regions.extend(regions.values()) @@ -166,18 +260,18 @@ class PokemonEmeraldWorld(World): continue # Location not in multiworld if self.options.goal == Goal.option_champion: - # Always required to beat champion before receiving this + # Always required to beat champion before receiving these exclude_locations([ - "Littleroot Town - S.S. Ticket from Norman" + "Littleroot Town - S.S. Ticket from Norman", + "Littleroot Town - Aurora Ticket from Norman", + "Littleroot Town - Eon Ticket from Norman", + "Littleroot Town - Mystic Ticket from Norman", + "Littleroot Town - Old Sea Map from Norman", + "Ever Grande City - Champion Wallace", + "Meteor Falls 1F - Rival Steven", + "Trick House Puzzle 8 - Item", ]) - # S.S. Ticket requires beating champion, so ferry is not accessible until after goal - if not self.options.enable_ferry: - exclude_locations([ - "SS Tidal - Hidden Item in Lower Deck Trash Can", - "SS Tidal - TM49 from Thief" - ]) - # Construction workers don't move until champion is defeated if "Safari Zone Construction Workers" not in self.options.remove_roadblocks.value: exclude_locations([ @@ -186,8 +280,12 @@ class PokemonEmeraldWorld(World): "Safari Zone NE - Item on Ledge", "Safari Zone SE - Hidden Item in South Grass 1", "Safari Zone SE - Hidden Item in South Grass 2", - "Safari Zone SE - Item in Grass" + "Safari Zone SE - Item in Grass", ]) + elif self.options.goal == Goal.option_steven: + exclude_locations([ + "Meteor Falls 1F - Rival Steven", + ]) elif self.options.goal == Goal.option_norman: # If the player sets their options such that Surf or the Balance # Badge is vanilla, a very large number of locations become @@ -207,7 +305,7 @@ class PokemonEmeraldWorld(World): "Petalburg City - HM03 from Wally's Uncle", "Dewford Town - TM36 from Sludge Bomb Man", "Mauville City - Basement Key from Wattson", - "Mauville City - TM24 from Wattson" + "Mauville City - TM24 from Wattson", ]) def create_items(self) -> None: @@ -217,8 +315,9 @@ class PokemonEmeraldWorld(World): if location.address is not None ] - # Filter progression items which shouldn't be shuffled into the itempool. Their locations - # still exist, but event items will be placed and locked at their vanilla locations instead. + # Filter progression items which shouldn't be shuffled into the itempool. + # Their locations will still exist, but event items will be placed and + # locked at their vanilla locations instead. filter_tags = set() if not self.options.key_items: @@ -227,12 +326,17 @@ class PokemonEmeraldWorld(World): filter_tags.add("Rod") if not self.options.bikes: filter_tags.add("Bike") + if not self.options.event_tickets: + filter_tags.add("EventTicket") if self.options.badges in {RandomizeBadges.option_vanilla, RandomizeBadges.option_shuffle}: filter_tags.add("Badge") if self.options.hms in {RandomizeHms.option_vanilla, RandomizeHms.option_shuffle}: filter_tags.add("HM") + # If Badges and HMs are set to the `shuffle` option, don't add them to + # the normal item pool, but do create their items and save them and + # their locations for use in `pre_fill` later. if self.options.badges == RandomizeBadges.option_shuffle: self.badge_shuffle_info = [ (location, self.create_item_by_code(location.default_item_code)) @@ -244,14 +348,18 @@ class PokemonEmeraldWorld(World): for location in [l for l in item_locations if "HM" in l.tags] ] + # Filter down locations to actual items that will be filled and create + # the itempool. item_locations = [location for location in item_locations if len(filter_tags & location.tags) == 0] default_itempool = [self.create_item_by_code(location.default_item_code) for location in item_locations] + # Take the itempool as-is if self.options.item_pool_type == ItemPoolType.option_shuffled: self.multiworld.itempool += default_itempool - elif self.options.item_pool_type in {ItemPoolType.option_diverse, ItemPoolType.option_diverse_balanced}: - item_categories = ["Ball", "Heal", "Vitamin", "EvoStone", "Money", "TM", "Held", "Misc"] + # Recreate the itempool from random items + elif self.options.item_pool_type in (ItemPoolType.option_diverse, ItemPoolType.option_diverse_balanced): + item_categories = ["Ball", "Heal", "Candy", "Vitamin", "EvoStone", "Money", "TM", "Held", "Misc", "Berry"] # Count occurrences of types of vanilla items in pool item_category_counter = Counter() @@ -306,38 +414,23 @@ class PokemonEmeraldWorld(World): self.multiworld.itempool.append(item) def set_rules(self) -> None: + from .rules import set_rules set_rules(self) def generate_basic(self) -> None: - locations: List[PokemonEmeraldLocation] = self.multiworld.get_locations(self.player) + # Create auth + # self.auth = self.random.randbytes(16) # Requires >=3.9 + self.auth = self.random.getrandbits(16 * 8).to_bytes(16, "little") - # Set our free fly location - # If not enabled, set it to Littleroot Town by default - fly_location_name = "EVENT_VISITED_LITTLEROOT_TOWN" - if self.options.free_fly_location: - fly_location_name = self.random.choice([ - "EVENT_VISITED_SLATEPORT_CITY", - "EVENT_VISITED_MAUVILLE_CITY", - "EVENT_VISITED_VERDANTURF_TOWN", - "EVENT_VISITED_FALLARBOR_TOWN", - "EVENT_VISITED_LAVARIDGE_TOWN", - "EVENT_VISITED_FORTREE_CITY", - "EVENT_VISITED_LILYCOVE_CITY", - "EVENT_VISITED_MOSSDEEP_CITY", - "EVENT_VISITED_SOOTOPOLIS_CITY", - "EVENT_VISITED_EVER_GRANDE_CITY" - ]) - - self.free_fly_location_id = location_visited_event_to_id_map[fly_location_name] - - free_fly_location_location = self.multiworld.get_location("FREE_FLY_LOCATION", self.player) - free_fly_location_location.item = None - free_fly_location_location.place_locked_item(self.create_event(fly_location_name)) + randomize_types(self) + randomize_wild_encounters(self) + set_free_fly(self) + set_legendary_cave_entrances(self) # Key items which are considered in access rules but not randomized are converted to events and placed # in their vanilla locations so that the player can have them in their inventory for logic. def convert_unrandomized_items_to_events(tag: str) -> None: - for location in locations: + for location in self.multiworld.get_locations(self.player): if location.tags is not None and tag in location.tags: location.place_locked_item(self.create_event(self.item_id_to_name[location.default_item_code])) location.progress_type = LocationProgressType.DEFAULT @@ -351,30 +444,36 @@ class PokemonEmeraldWorld(World): convert_unrandomized_items_to_events("Rod") if not self.options.bikes: convert_unrandomized_items_to_events("Bike") + if not self.options.event_tickets: + convert_unrandomized_items_to_events("EventTicket") if not self.options.key_items: convert_unrandomized_items_to_events("KeyItem") def pre_fill(self) -> None: - # Items which are shuffled between their own locations + # Badges and HMs that are set to shuffle need to be placed at + # their own subset of locations if self.options.badges == RandomizeBadges.option_shuffle: badge_locations: List[PokemonEmeraldLocation] badge_items: List[PokemonEmeraldItem] # Sort order makes `fill_restrictive` try to place important badges later, which # makes it less likely to have to swap at all, and more likely for swaps to work. - # In the case of vanilla HMs, navigating Granite Cave is required to access more than 2 gyms, - # so Knuckle Badge deserves highest priority if Flash is logically required. badge_locations, badge_items = [list(l) for l in zip(*self.badge_shuffle_info)] badge_priority = { - "Knuckle Badge": 0 if (self.options.hms == RandomizeHms.option_vanilla and self.options.require_flash) else 3, + "Knuckle Badge": 3, "Balance Badge": 1, "Dynamo Badge": 1, "Mind Badge": 2, "Heat Badge": 2, "Rain Badge": 3, "Stone Badge": 4, - "Feather Badge": 5 + "Feather Badge": 5, } + # In the case of vanilla HMs, navigating Granite Cave is required to access more than 2 gyms, + # so Knuckle Badge deserves highest priority if Flash is logically required. + if self.options.hms == RandomizeHms.option_vanilla and \ + self.options.require_flash in (DarkCavesRequireFlash.option_both, DarkCavesRequireFlash.option_only_granite_cave): + badge_priority["Knuckle Badge"] = 0 badge_items.sort(key=lambda item: badge_priority.get(item.name, 0)) # Un-exclude badge locations, since we need to put progression items on them @@ -384,6 +483,11 @@ class PokemonEmeraldWorld(World): else location.progress_type collection_state = self.multiworld.get_all_state(False) + + # If HM shuffle is on, HMs are not placed and not in the pool, so + # `get_all_state` did not contain them. Collect them manually for + # this fill. We know that they will be included in all state after + # this stage. if self.hm_shuffle_info is not None: for _, item in self.hm_shuffle_info: collection_state.collect(item) @@ -406,25 +510,29 @@ class PokemonEmeraldWorld(World): logging.debug(f"Failed to shuffle badges for player {self.player}. Retrying.") continue + # Badges are guaranteed to be either placed or in the multiworld's itempool now if self.options.hms == RandomizeHms.option_shuffle: hm_locations: List[PokemonEmeraldLocation] hm_items: List[PokemonEmeraldItem] # Sort order makes `fill_restrictive` try to place important HMs later, which # makes it less likely to have to swap at all, and more likely for swaps to work. - # In the case of vanilla badges, navigating Granite Cave is required to access more than 2 gyms, - # so Flash deserves highest priority if it's logically required. hm_locations, hm_items = [list(l) for l in zip(*self.hm_shuffle_info)] hm_priority = { - "HM05 Flash": 0 if (self.options.badges == RandomizeBadges.option_vanilla and self.options.require_flash) else 3, + "HM05 Flash": 3, "HM03 Surf": 1, "HM06 Rock Smash": 1, "HM08 Dive": 2, "HM04 Strength": 2, "HM07 Waterfall": 3, "HM01 Cut": 4, - "HM02 Fly": 5 + "HM02 Fly": 5, } + # In the case of vanilla badges, navigating Granite Cave is required to access more than 2 gyms, + # so Flash deserves highest priority if it's logically required. + if self.options.badges == RandomizeBadges.option_vanilla and \ + self.options.require_flash in (DarkCavesRequireFlash.option_both, DarkCavesRequireFlash.option_only_granite_cave): + hm_priority["HM05 Flash"] = 0 hm_items.sort(key=lambda item: hm_priority.get(item.name, 0)) # Un-exclude HM locations, since we need to put progression items on them @@ -454,465 +562,110 @@ class PokemonEmeraldWorld(World): continue def generate_output(self, output_directory: str) -> None: - def randomize_abilities() -> None: - # Creating list of potential abilities - ability_label_to_value = {ability.label.lower(): ability.ability_id for ability in emerald_data.abilities} + self.modified_trainers = copy.deepcopy(emerald_data.trainers) + self.modified_tmhm_moves = copy.deepcopy(emerald_data.tmhm_moves) + self.modified_legendary_encounters = copy.deepcopy(emerald_data.legendary_encounters) + self.modified_misc_pokemon = copy.deepcopy(emerald_data.misc_pokemon) + self.modified_starters = copy.deepcopy(emerald_data.starters) - ability_blacklist_labels = {"cacophony"} - option_ability_blacklist = self.options.ability_blacklist.value - if option_ability_blacklist is not None: - ability_blacklist_labels |= {ability_label.lower() for ability_label in option_ability_blacklist} + randomize_abilities(self) + randomize_learnsets(self) + randomize_tm_hm_compatibility(self) + randomize_legendary_encounters(self) + randomize_misc_pokemon(self) + randomize_opponent_parties(self) + randomize_starters(self) - ability_blacklist = {ability_label_to_value[label] for label in ability_blacklist_labels} - ability_whitelist = [a.ability_id for a in emerald_data.abilities if a.ability_id not in ability_blacklist] + # Modify catch rate + min_catch_rate = min(self.options.min_catch_rate.value, 255) + for species in self.modified_species.values(): + species.catch_rate = max(species.catch_rate, min_catch_rate) - if self.options.abilities == RandomizeAbilities.option_follow_evolutions: - already_modified: Set[int] = set() - - # Loops through species and only tries to modify abilities if the pokemon has no pre-evolution - # or if the pre-evolution has already been modified. Then tries to modify all species that evolve - # from this one which have the same abilities. - # The outer while loop only runs three times for vanilla ordering: Once for a first pass, once for - # Hitmonlee/Hitmonchan, and once to verify that there's nothing left to do. - while True: - had_clean_pass = True - for species in self.modified_species: - if species is None: - continue - if species.species_id in already_modified: - continue - if species.pre_evolution is not None and species.pre_evolution not in already_modified: - continue - - had_clean_pass = False - - old_abilities = species.abilities - new_abilities = ( - 0 if old_abilities[0] == 0 else self.random.choice(ability_whitelist), - 0 if old_abilities[1] == 0 else self.random.choice(ability_whitelist) - ) - - evolutions = [species] - while len(evolutions) > 0: - evolution = evolutions.pop() - if evolution.abilities == old_abilities: - evolution.abilities = new_abilities - already_modified.add(evolution.species_id) - evolutions += [ - self.modified_species[evolution.species_id] - for evolution in evolution.evolutions - if evolution.species_id not in already_modified - ] - - if had_clean_pass: - break - else: # Not following evolutions - for species in self.modified_species: - if species is None: - continue - - old_abilities = species.abilities - new_abilities = ( - 0 if old_abilities[0] == 0 else self.random.choice(ability_whitelist), - 0 if old_abilities[1] == 0 else self.random.choice(ability_whitelist) - ) - - species.abilities = new_abilities - - def randomize_types() -> None: - if self.options.types == RandomizeTypes.option_shuffle: - type_map = list(range(18)) - self.random.shuffle(type_map) - - # We never want to map to the ??? type, so swap whatever index maps to ??? with ??? - # So ??? will always map to itself, and there are no pokemon which have the ??? type - mystery_type_index = type_map.index(9) - type_map[mystery_type_index], type_map[9] = type_map[9], type_map[mystery_type_index] - - for species in self.modified_species: - if species is not None: - species.types = (type_map[species.types[0]], type_map[species.types[1]]) - elif self.options.types == RandomizeTypes.option_completely_random: - for species in self.modified_species: - if species is not None: - new_type_1 = get_random_type(self.random) - new_type_2 = new_type_1 - if species.types[0] != species.types[1]: - while new_type_1 == new_type_2: - new_type_2 = get_random_type(self.random) - - species.types = (new_type_1, new_type_2) - elif self.options.types == RandomizeTypes.option_follow_evolutions: - already_modified: Set[int] = set() - - # Similar to follow evolutions for abilities, but only needs to loop through once. - # For every pokemon without a pre-evolution, generates a random mapping from old types to new types - # and then walks through the evolution tree applying that map. This means that evolutions that share - # types will have those types mapped to the same new types, and evolutions with new or diverging types - # will still have new or diverging types. - # Consider: - # - Charmeleon (Fire/Fire) -> Charizard (Fire/Flying) - # - Onyx (Rock/Ground) -> Steelix (Steel/Ground) - # - Nincada (Bug/Ground) -> Ninjask (Bug/Flying) && Shedinja (Bug/Ghost) - # - Azurill (Normal/Normal) -> Marill (Water/Water) - for species in self.modified_species: - if species is None: - continue - if species.species_id in already_modified: - continue - if species.pre_evolution is not None and species.pre_evolution not in already_modified: - continue - - type_map = list(range(18)) - self.random.shuffle(type_map) - - # We never want to map to the ??? type, so swap whatever index maps to ??? with ??? - # So ??? will always map to itself, and there are no pokemon which have the ??? type - mystery_type_index = type_map.index(9) - type_map[mystery_type_index], type_map[9] = type_map[9], type_map[mystery_type_index] - - evolutions = [species] - while len(evolutions) > 0: - evolution = evolutions.pop() - evolution.types = (type_map[evolution.types[0]], type_map[evolution.types[1]]) - already_modified.add(evolution.species_id) - evolutions += [self.modified_species[evo.species_id] for evo in evolution.evolutions] - - def randomize_learnsets() -> None: - type_bias = self.options.move_match_type_bias.value - normal_bias = self.options.move_normal_type_bias.value - - for species in self.modified_species: - if species is None: - continue - - old_learnset = species.learnset - new_learnset: List[LearnsetMove] = [] - - i = 0 - # Replace filler MOVE_NONEs at start of list - while old_learnset[i].move_id == 0: - if self.options.level_up_moves == LevelUpMoves.option_start_with_four_moves: - new_move = get_random_move(self.random, {move.move_id for move in new_learnset}, type_bias, - normal_bias, species.types) - else: - new_move = 0 - new_learnset.append(LearnsetMove(old_learnset[i].level, new_move)) - i += 1 - - while i < len(old_learnset): - # Guarantees the starter has a good damaging move - if i == 3: - new_move = get_random_damaging_move(self.random, {move.move_id for move in new_learnset}) - else: - new_move = get_random_move(self.random, {move.move_id for move in new_learnset}, type_bias, - normal_bias, species.types) - new_learnset.append(LearnsetMove(old_learnset[i].level, new_move)) - i += 1 - - species.learnset = new_learnset - - def randomize_tm_hm_compatibility() -> None: - for species in self.modified_species: - if species is None: - continue - - combatibility_array = int_to_bool_array(species.tm_hm_compatibility) - - # TMs - for i in range(0, 50): - if self.options.tm_compatibility == TmCompatibility.option_fully_compatible: - combatibility_array[i] = True - elif self.options.tm_compatibility == TmCompatibility.option_completely_random: - combatibility_array[i] = self.random.choice([True, False]) - - # HMs - for i in range(50, 58): - if self.options.hm_compatibility == HmCompatibility.option_fully_compatible: - combatibility_array[i] = True - elif self.options.hm_compatibility == HmCompatibility.option_completely_random: - combatibility_array[i] = self.random.choice([True, False]) - - species.tm_hm_compatibility = bool_array_to_int(combatibility_array) - - def randomize_tm_moves() -> None: + # Modify TM moves + if self.options.tm_tutor_moves: new_moves: Set[int] = set() for i in range(50): - new_move = get_random_move(self.random, new_moves) + new_move = get_random_move(self.random, new_moves | self.blacklisted_moves) new_moves.add(new_move) self.modified_tmhm_moves[i] = new_move - def randomize_wild_encounters() -> None: - should_match_bst = self.options.wild_pokemon in { - RandomizeWildPokemon.option_match_base_stats, - RandomizeWildPokemon.option_match_base_stats_and_type + create_patch(self, output_directory) + + del self.modified_trainers + del self.modified_tmhm_moves + del self.modified_legendary_encounters + del self.modified_misc_pokemon + del self.modified_starters + del self.modified_species + + def write_spoiler(self, spoiler_handle: TextIO): + if self.options.dexsanity: + from collections import defaultdict + + spoiler_handle.write(f"\n\nWild Pokemon ({self.multiworld.player_name[self.player]}):\n\n") + + species_maps = defaultdict(set) + for map in self.modified_maps.values(): + if map.land_encounters is not None: + for encounter in map.land_encounters.slots: + species_maps[encounter].add(map.name[4:]) + + if map.water_encounters is not None: + for encounter in map.water_encounters.slots: + species_maps[encounter].add(map.name[4:]) + + if map.fishing_encounters is not None: + for encounter in map.fishing_encounters.slots: + species_maps[encounter].add(map.name[4:]) + + lines = [f"{emerald_data.species[species].label}: {', '.join(maps)}\n" + for species, maps in species_maps.items()] + lines.sort() + for line in lines: + spoiler_handle.write(line) + + del self.modified_maps + + def extend_hint_information(self, hint_data): + if self.options.dexsanity: + from collections import defaultdict + + slot_to_rod = { + 0: "_OLD_ROD", + 1: "_OLD_ROD", + 2: "_GOOD_ROD", + 3: "_GOOD_ROD", + 4: "_GOOD_ROD", + 5: "_SUPER_ROD", + 6: "_SUPER_ROD", + 7: "_SUPER_ROD", + 8: "_SUPER_ROD", + 9: "_SUPER_ROD", } - should_match_type = self.options.wild_pokemon in { - RandomizeWildPokemon.option_match_type, - RandomizeWildPokemon.option_match_base_stats_and_type + + species_maps = defaultdict(set) + for map in self.modified_maps.values(): + if map.land_encounters is not None: + for encounter in map.land_encounters.slots: + species_maps[encounter].add(map.name[4:] + "_GRASS") + + if map.water_encounters is not None: + for encounter in map.water_encounters.slots: + species_maps[encounter].add(map.name[4:] + "_WATER") + + if map.fishing_encounters is not None: + for slot, encounter in enumerate(map.fishing_encounters.slots): + species_maps[encounter].add(map.name[4:] + slot_to_rod[slot]) + + hint_data[self.player] = { + self.location_name_to_id[f"Pokedex - {emerald_data.species[species].label}"]: ", ".join(maps) + for species, maps in species_maps.items() } - should_allow_legendaries = self.options.allow_wild_legendaries == Toggle.option_true - for map_data in self.modified_maps: - new_encounters: List[Optional[EncounterTableData]] = [None, None, None] - old_encounters = [map_data.land_encounters, map_data.water_encounters, map_data.fishing_encounters] - - for i, table in enumerate(old_encounters): - if table is not None: - species_old_to_new_map: Dict[int, int] = {} - for species_id in table.slots: - if species_id not in species_old_to_new_map: - original_species = emerald_data.species[species_id] - target_bst = sum(original_species.base_stats) if should_match_bst else None - target_type = self.random.choice(original_species.types) if should_match_type else None - - species_old_to_new_map[species_id] = get_random_species( - self.random, - self.modified_species, - target_bst, - target_type, - should_allow_legendaries - ).species_id - - new_slots: List[int] = [] - for species_id in table.slots: - new_slots.append(species_old_to_new_map[species_id]) - - new_encounters[i] = EncounterTableData(new_slots, table.rom_address) - - map_data.land_encounters = new_encounters[0] - map_data.water_encounters = new_encounters[1] - map_data.fishing_encounters = new_encounters[2] - - def randomize_static_encounters() -> None: - if self.options.static_encounters == RandomizeStaticEncounters.option_shuffle: - shuffled_species = [encounter.species_id for encounter in emerald_data.static_encounters] - self.random.shuffle(shuffled_species) - - self.modified_static_encounters = [] - for i, encounter in enumerate(emerald_data.static_encounters): - self.modified_static_encounters.append(StaticEncounterData( - shuffled_species[i], - encounter.rom_address - )) - else: - should_match_bst = self.options.static_encounters in { - RandomizeStaticEncounters.option_match_base_stats, - RandomizeStaticEncounters.option_match_base_stats_and_type - } - should_match_type = self.options.static_encounters in { - RandomizeStaticEncounters.option_match_type, - RandomizeStaticEncounters.option_match_base_stats_and_type - } - - for encounter in emerald_data.static_encounters: - original_species = self.modified_species[encounter.species_id] - target_bst = sum(original_species.base_stats) if should_match_bst else None - target_type = self.random.choice(original_species.types) if should_match_type else None - - self.modified_static_encounters.append(StaticEncounterData( - get_random_species(self.random, self.modified_species, target_bst, target_type).species_id, - encounter.rom_address - )) - - def randomize_opponent_parties() -> None: - should_match_bst = self.options.trainer_parties in { - RandomizeTrainerParties.option_match_base_stats, - RandomizeTrainerParties.option_match_base_stats_and_type - } - should_match_type = self.options.trainer_parties in { - RandomizeTrainerParties.option_match_type, - RandomizeTrainerParties.option_match_base_stats_and_type - } - allow_legendaries = self.options.allow_trainer_legendaries == Toggle.option_true - - per_species_tmhm_moves: Dict[int, List[int]] = {} - - for trainer in self.modified_trainers: - new_party = [] - for pokemon in trainer.party.pokemon: - original_species = emerald_data.species[pokemon.species_id] - target_bst = sum(original_species.base_stats) if should_match_bst else None - target_type = self.random.choice(original_species.types) if should_match_type else None - - new_species = get_random_species( - self.random, - self.modified_species, - target_bst, - target_type, - allow_legendaries - ) - - if new_species.species_id not in per_species_tmhm_moves: - per_species_tmhm_moves[new_species.species_id] = list({ - self.modified_tmhm_moves[i] - for i, is_compatible in enumerate(int_to_bool_array(new_species.tm_hm_compatibility)) - if is_compatible - }) - - tm_hm_movepool = per_species_tmhm_moves[new_species.species_id] - level_up_movepool = list({ - move.move_id - for move in new_species.learnset - if move.move_id != 0 and move.level <= pokemon.level - }) - - new_moves = ( - self.random.choice(tm_hm_movepool if self.random.random() < 0.25 and len(tm_hm_movepool) > 0 else level_up_movepool), - self.random.choice(tm_hm_movepool if self.random.random() < 0.25 and len(tm_hm_movepool) > 0 else level_up_movepool), - self.random.choice(tm_hm_movepool if self.random.random() < 0.25 and len(tm_hm_movepool) > 0 else level_up_movepool), - self.random.choice(tm_hm_movepool if self.random.random() < 0.25 and len(tm_hm_movepool) > 0 else level_up_movepool) - ) - - new_party.append(TrainerPokemonData(new_species.species_id, pokemon.level, new_moves)) - - trainer.party.pokemon = new_party - - def randomize_starters() -> None: - match_bst = self.options.starters in { - RandomizeStarters.option_match_base_stats, - RandomizeStarters.option_match_base_stats_and_type - } - match_type = self.options.starters in { - RandomizeStarters.option_match_type, - RandomizeStarters.option_match_base_stats_and_type - } - allow_legendaries = self.options.allow_starter_legendaries == Toggle.option_true - - starter_bsts = ( - sum(emerald_data.species[emerald_data.starters[0]].base_stats) if match_bst else None, - sum(emerald_data.species[emerald_data.starters[1]].base_stats) if match_bst else None, - sum(emerald_data.species[emerald_data.starters[2]].base_stats) if match_bst else None - ) - - starter_types = ( - self.random.choice(emerald_data.species[emerald_data.starters[0]].types) if match_type else None, - self.random.choice(emerald_data.species[emerald_data.starters[1]].types) if match_type else None, - self.random.choice(emerald_data.species[emerald_data.starters[2]].types) if match_type else None - ) - - new_starters = ( - get_random_species(self.random, self.modified_species, - starter_bsts[0], starter_types[0], allow_legendaries), - get_random_species(self.random, self.modified_species, - starter_bsts[1], starter_types[1], allow_legendaries), - get_random_species(self.random, self.modified_species, - starter_bsts[2], starter_types[2], allow_legendaries) - ) - - egg_code = self.options.easter_egg.value - egg_check_1 = 0 - egg_check_2 = 0 - - for i in egg_code: - egg_check_1 += ord(i) - egg_check_2 += egg_check_1 * egg_check_1 - - egg = 96 + egg_check_2 - (egg_check_1 * 0x077C) - if egg_check_2 == 0x14E03A and egg < 411 and egg > 0 and egg not in range(252, 277): - self.modified_starters = (egg, egg, egg) - else: - self.modified_starters = ( - new_starters[0].species_id, - new_starters[1].species_id, - new_starters[2].species_id - ) - - # Putting the unchosen starter onto the rival's team - rival_teams: List[List[Tuple[str, int, bool]]] = [ - [ - ("TRAINER_BRENDAN_ROUTE_103_TREECKO", 0, False), - ("TRAINER_BRENDAN_RUSTBORO_TREECKO", 1, False), - ("TRAINER_BRENDAN_ROUTE_110_TREECKO", 2, True ), - ("TRAINER_BRENDAN_ROUTE_119_TREECKO", 2, True ), - ("TRAINER_BRENDAN_LILYCOVE_TREECKO", 3, True ), - ("TRAINER_MAY_ROUTE_103_TREECKO", 0, False), - ("TRAINER_MAY_RUSTBORO_TREECKO", 1, False), - ("TRAINER_MAY_ROUTE_110_TREECKO", 2, True ), - ("TRAINER_MAY_ROUTE_119_TREECKO", 2, True ), - ("TRAINER_MAY_LILYCOVE_TREECKO", 3, True ) - ], - [ - ("TRAINER_BRENDAN_ROUTE_103_TORCHIC", 0, False), - ("TRAINER_BRENDAN_RUSTBORO_TORCHIC", 1, False), - ("TRAINER_BRENDAN_ROUTE_110_TORCHIC", 2, True ), - ("TRAINER_BRENDAN_ROUTE_119_TORCHIC", 2, True ), - ("TRAINER_BRENDAN_LILYCOVE_TORCHIC", 3, True ), - ("TRAINER_MAY_ROUTE_103_TORCHIC", 0, False), - ("TRAINER_MAY_RUSTBORO_TORCHIC", 1, False), - ("TRAINER_MAY_ROUTE_110_TORCHIC", 2, True ), - ("TRAINER_MAY_ROUTE_119_TORCHIC", 2, True ), - ("TRAINER_MAY_LILYCOVE_TORCHIC", 3, True ) - ], - [ - ("TRAINER_BRENDAN_ROUTE_103_MUDKIP", 0, False), - ("TRAINER_BRENDAN_RUSTBORO_MUDKIP", 1, False), - ("TRAINER_BRENDAN_ROUTE_110_MUDKIP", 2, True ), - ("TRAINER_BRENDAN_ROUTE_119_MUDKIP", 2, True ), - ("TRAINER_BRENDAN_LILYCOVE_MUDKIP", 3, True ), - ("TRAINER_MAY_ROUTE_103_MUDKIP", 0, False), - ("TRAINER_MAY_RUSTBORO_MUDKIP", 1, False), - ("TRAINER_MAY_ROUTE_110_MUDKIP", 2, True ), - ("TRAINER_MAY_ROUTE_119_MUDKIP", 2, True ), - ("TRAINER_MAY_LILYCOVE_MUDKIP", 3, True ) - ] - ] - - for i, starter in enumerate([new_starters[1], new_starters[2], new_starters[0]]): - potential_evolutions = [evolution.species_id for evolution in starter.evolutions] - picked_evolution = starter.species_id - if len(potential_evolutions) > 0: - picked_evolution = self.random.choice(potential_evolutions) - - for trainer_name, starter_position, is_evolved in rival_teams[i]: - trainer_data = self.modified_trainers[emerald_data.constants[trainer_name]] - trainer_data.party.pokemon[starter_position].species_id = picked_evolution if is_evolved else starter.species_id - - self.modified_species = copy.deepcopy(emerald_data.species) - self.modified_trainers = copy.deepcopy(emerald_data.trainers) - self.modified_maps = copy.deepcopy(emerald_data.maps) - self.modified_tmhm_moves = copy.deepcopy(emerald_data.tmhm_moves) - self.modified_static_encounters = copy.deepcopy(emerald_data.static_encounters) - self.modified_starters = copy.deepcopy(emerald_data.starters) - - # Randomize species data - if self.options.abilities != RandomizeAbilities.option_vanilla: - randomize_abilities() - - if self.options.types != RandomizeTypes.option_vanilla: - randomize_types() - - if self.options.level_up_moves != LevelUpMoves.option_vanilla: - randomize_learnsets() - - randomize_tm_hm_compatibility() # Options are checked within this function - - min_catch_rate = min(self.options.min_catch_rate.value, 255) - for species in self.modified_species: - if species is not None: - species.catch_rate = max(species.catch_rate, min_catch_rate) - - if self.options.tm_moves: - randomize_tm_moves() - - # Randomize wild encounters - if self.options.wild_pokemon != RandomizeWildPokemon.option_vanilla: - randomize_wild_encounters() - - # Randomize static encounters - if self.options.static_encounters != RandomizeStaticEncounters.option_vanilla: - randomize_static_encounters() - - # Randomize opponents - if self.options.trainer_parties != RandomizeTrainerParties.option_vanilla: - randomize_opponent_parties() - - # Randomize starters - if self.options.starters != RandomizeStarters.option_vanilla: - randomize_starters() - - generate_output(self, output_directory) + def modify_multidata(self, multidata: Dict[str, Any]): + import base64 + multidata["connect_names"][base64.b64encode(self.auth).decode("ascii")] = multidata["connect_names"][self.multiworld.player_name[self.player]] def fill_slot_data(self) -> Dict[str, Any]: slot_data = self.options.as_dict( @@ -921,23 +674,33 @@ class PokemonEmeraldWorld(World): "hms", "key_items", "bikes", + "event_tickets", "rods", "overworld_items", "hidden_items", "npc_gifts", + "berry_trees", "require_itemfinder", "require_flash", - "enable_ferry", "elite_four_requirement", "elite_four_count", "norman_requirement", "norman_count", + "legendary_hunt_catch", + "legendary_hunt_count", "extra_boulders", "remove_roadblocks", + "allowed_legendary_hunt_encounters", + "extra_bumpy_slope", "free_fly_location", - "fly_without_badge", + "remote_items", + "dexsanity", + "trainersanity", + "modify_118", + "death_link", ) slot_data["free_fly_location_id"] = self.free_fly_location_id + slot_data["hm_requirements"] = self.hm_requirements return slot_data def create_item(self, name: str) -> PokemonEmeraldItem: diff --git a/worlds/pokemon_emerald/client.py b/worlds/pokemon_emerald/client.py index d8b4b8d587..0dccc1fe17 100644 --- a/worlds/pokemon_emerald/client.py +++ b/worlds/pokemon_emerald/client.py @@ -1,19 +1,28 @@ -from typing import TYPE_CHECKING, Dict, Set +import asyncio +import copy +import orjson +import random +import time +from typing import TYPE_CHECKING, Optional, Dict, Set, Tuple +import uuid from NetUtils import ClientStatus +from Options import Toggle +import Utils import worlds._bizhawk as bizhawk from worlds._bizhawk.client import BizHawkClient -from .data import BASE_OFFSET, data -from .options import Goal +from .data import BASE_OFFSET, POKEDEX_OFFSET, data +from .options import Goal, RemoteItems +from .util import pokemon_data_to_json, json_to_pokemon_data if TYPE_CHECKING: from worlds._bizhawk.context import BizHawkClientContext -EXPECTED_ROM_NAME = "pokemon emerald version / AP 2" +EXPECTED_ROM_NAME = "pokemon emerald version / AP 5" -IS_CHAMPION_FLAG = data.constants["FLAG_IS_CHAMPION"] +DEFEATED_WALLACE_FLAG = data.constants["TRAINER_FLAGS_START"] + data.constants["TRAINER_WALLACE"] DEFEATED_STEVEN_FLAG = data.constants["TRAINER_FLAGS_START"] + data.constants["TRAINER_STEVEN"] DEFEATED_NORMAN_FLAG = data.constants["TRAINER_FLAGS_START"] + data.constants["TRAINER_NORMAN_1"] @@ -31,7 +40,7 @@ TRACKER_EVENT_FLAGS = [ "FLAG_RECEIVED_POKENAV", # Talk to Mr. Stone "FLAG_DELIVERED_STEVEN_LETTER", "FLAG_DELIVERED_DEVON_GOODS", - "FLAG_HIDE_ROUTE_119_TEAM_AQUA", # Clear Weather Institute + "FLAG_HIDE_ROUTE_119_TEAM_AQUA_SHELLY", # Clear Weather Institute "FLAG_MET_ARCHIE_METEOR_FALLS", # Magma steals meteorite "FLAG_GROUDON_AWAKENED_MAGMA_HIDEOUT", # Clear Magma Hideout "FLAG_MET_TEAM_AQUA_HARBOR", # Aqua steals submarine @@ -41,19 +50,19 @@ TRACKER_EVENT_FLAGS = [ "FLAG_HIDE_SKY_PILLAR_TOP_RAYQUAZA", # Rayquaza departs for Sootopolis "FLAG_OMIT_DIVE_FROM_STEVEN_LETTER", # Steven gives Dive HM (clears seafloor cavern grunt) "FLAG_IS_CHAMPION", - "FLAG_PURCHASED_HARBOR_MAIL" + "FLAG_PURCHASED_HARBOR_MAIL", ] EVENT_FLAG_MAP = {data.constants[flag_name]: flag_name for flag_name in TRACKER_EVENT_FLAGS} KEY_LOCATION_FLAGS = [ - "NPC_GIFT_RECEIVED_HM01", - "NPC_GIFT_RECEIVED_HM02", - "NPC_GIFT_RECEIVED_HM03", - "NPC_GIFT_RECEIVED_HM04", - "NPC_GIFT_RECEIVED_HM05", - "NPC_GIFT_RECEIVED_HM06", - "NPC_GIFT_RECEIVED_HM07", - "NPC_GIFT_RECEIVED_HM08", + "NPC_GIFT_RECEIVED_HM_CUT", + "NPC_GIFT_RECEIVED_HM_FLY", + "NPC_GIFT_RECEIVED_HM_SURF", + "NPC_GIFT_RECEIVED_HM_STRENGTH", + "NPC_GIFT_RECEIVED_HM_FLASH", + "NPC_GIFT_RECEIVED_HM_ROCK_SMASH", + "NPC_GIFT_RECEIVED_HM_WATERFALL", + "NPC_GIFT_RECEIVED_HM_DIVE", "NPC_GIFT_RECEIVED_ACRO_BIKE", "NPC_GIFT_RECEIVED_WAILMER_PAIL", "NPC_GIFT_RECEIVED_DEVON_GOODS_RUSTURF_TUNNEL", @@ -70,7 +79,7 @@ KEY_LOCATION_FLAGS = [ "HIDDEN_ITEM_ABANDONED_SHIP_RM_1_KEY", "HIDDEN_ITEM_ABANDONED_SHIP_RM_4_KEY", "HIDDEN_ITEM_ABANDONED_SHIP_RM_6_KEY", - "ITEM_ABANDONED_SHIP_HIDDEN_FLOOR_ROOM_4_SCANNER", + "ITEM_ABANDONED_SHIP_HIDDEN_FLOOR_ROOM_2_SCANNER", "ITEM_ABANDONED_SHIP_CAPTAINS_OFFICE_STORAGE_KEY", "NPC_GIFT_RECEIVED_OLD_ROD", "NPC_GIFT_RECEIVED_GOOD_ROD", @@ -78,6 +87,24 @@ KEY_LOCATION_FLAGS = [ ] KEY_LOCATION_FLAG_MAP = {data.locations[location_name].flag: location_name for location_name in KEY_LOCATION_FLAGS} +LEGENDARY_NAMES = { + "Groudon": "GROUDON", + "Kyogre": "KYOGRE", + "Rayquaza": "RAYQUAZA", + "Latias": "LATIAS", + "Latios": "LATIOS", + "Regirock": "REGIROCK", + "Regice": "REGICE", + "Registeel": "REGISTEEL", + "Mew": "MEW", + "Deoxys": "DEOXYS", + "Ho-oh": "HO_OH", + "Lugia": "LUGIA", +} + +DEFEATED_LEGENDARY_FLAG_MAP = {data.constants[f"FLAG_DEFEATED_{name}"]: name for name in LEGENDARY_NAMES.values()} +CAUGHT_LEGENDARY_FLAG_MAP = {data.constants[f"FLAG_CAUGHT_{name}"]: name for name in LEGENDARY_NAMES.values()} + class PokemonEmeraldClient(BizHawkClient): game = "Pokemon Emerald" @@ -86,14 +113,31 @@ class PokemonEmeraldClient(BizHawkClient): local_checked_locations: Set[int] local_set_events: Dict[str, bool] local_found_key_items: Dict[str, bool] - goal_flag: int + local_defeated_legendaries: Dict[str, bool] + goal_flag: Optional[int] + + wonder_trade_update_event: asyncio.Event + latest_wonder_trade_reply: dict + wonder_trade_cooldown: int + wonder_trade_cooldown_timer: int + + death_counter: Optional[int] + previous_death_link: float + ignore_next_death_link: bool def __init__(self) -> None: super().__init__() self.local_checked_locations = set() self.local_set_events = {} self.local_found_key_items = {} - self.goal_flag = IS_CHAMPION_FLAG + self.local_defeated_legendaries = {} + self.goal_flag = None + self.wonder_trade_update_event = asyncio.Event() + self.wonder_trade_cooldown = 5000 + self.wonder_trade_cooldown_timer = 0 + self.death_counter = None + self.previous_death_link = 0 + self.ignore_next_death_link = False async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: from CommonClient import logger @@ -123,88 +167,103 @@ class PokemonEmeraldClient(BizHawkClient): ctx.want_slot_data = True ctx.watcher_timeout = 0.125 + self.death_counter = None + self.previous_death_link = 0 + self.ignore_next_death_link = False + return True async def set_auth(self, ctx: "BizHawkClientContext") -> None: - slot_name_bytes = (await bizhawk.read(ctx.bizhawk_ctx, [(data.rom_addresses["gArchipelagoInfo"], 64, "ROM")]))[0] - ctx.auth = bytes([byte for byte in slot_name_bytes if byte != 0]).decode("utf-8") + import base64 + auth_raw = (await bizhawk.read(ctx.bizhawk_ctx, [(data.rom_addresses["gArchipelagoInfo"], 16, "ROM")]))[0] + ctx.auth = base64.b64encode(auth_raw).decode("utf-8") async def game_watcher(self, ctx: "BizHawkClientContext") -> None: - if ctx.slot_data is not None: - if ctx.slot_data["goal"] == Goal.option_champion: - self.goal_flag = IS_CHAMPION_FLAG - elif ctx.slot_data["goal"] == Goal.option_steven: - self.goal_flag = DEFEATED_STEVEN_FLAG - elif ctx.slot_data["goal"] == Goal.option_norman: - self.goal_flag = DEFEATED_NORMAN_FLAG + if ctx.server is None or ctx.server.socket.closed or ctx.slot_data is None: + return + + if ctx.slot_data["goal"] == Goal.option_champion: + self.goal_flag = DEFEATED_WALLACE_FLAG + elif ctx.slot_data["goal"] == Goal.option_steven: + self.goal_flag = DEFEATED_STEVEN_FLAG + elif ctx.slot_data["goal"] == Goal.option_norman: + self.goal_flag = DEFEATED_NORMAN_FLAG + elif ctx.slot_data["goal"] == Goal.option_legendary_hunt: + self.goal_flag = None + + if ctx.slot_data["remote_items"] == RemoteItems.option_true and not ctx.items_handling & 0b010: + ctx.items_handling = 0b011 + Utils.async_start(ctx.send_msgs([{ + "cmd": "ConnectUpdate", + "items_handling": ctx.items_handling + }])) try: + guards: Dict[str, Tuple[int, bytes, str]] = {} + # Checks that the player is in the overworld - overworld_guard = (data.ram_addresses["gMain"] + 4, (data.ram_addresses["CB2_Overworld"] + 1).to_bytes(4, "little"), "System Bus") - - # Read save block address - read_result = await bizhawk.guarded_read( - ctx.bizhawk_ctx, - [(data.ram_addresses["gSaveBlock1Ptr"], 4, "System Bus")], - [overworld_guard] + guards["IN OVERWORLD"] = ( + data.ram_addresses["gMain"] + 4, + (data.ram_addresses["CB2_Overworld"] + 1).to_bytes(4, "little"), + "System Bus" ) - if read_result is None: # Not in overworld - return - # Checks that the save block hasn't moved - save_block_address_guard = (data.ram_addresses["gSaveBlock1Ptr"], read_result[0], "System Bus") - - save_block_address = int.from_bytes(read_result[0], "little") - - # Handle giving the player items - read_result = await bizhawk.guarded_read( + # Read save block addresses + read_result = await bizhawk.read( ctx.bizhawk_ctx, [ - (save_block_address + 0x3778, 2, "System Bus"), # Number of received items - (data.ram_addresses["gArchipelagoReceivedItem"] + 4, 1, "System Bus") # Received item struct full? - ], - [overworld_guard, save_block_address_guard] + (data.ram_addresses["gSaveBlock1Ptr"], 4, "System Bus"), + (data.ram_addresses["gSaveBlock2Ptr"], 4, "System Bus"), + ] ) - if read_result is None: # Not in overworld, or save block moved - return - num_received_items = int.from_bytes(read_result[0], "little") - received_item_is_empty = read_result[1][0] == 0 + # Checks that the save data hasn't moved + guards["SAVE BLOCK 1"] = (data.ram_addresses["gSaveBlock1Ptr"], read_result[0], "System Bus") + guards["SAVE BLOCK 2"] = (data.ram_addresses["gSaveBlock2Ptr"], read_result[1], "System Bus") - # If the game hasn't received all items yet and the received item struct doesn't contain an item, then - # fill it with the next item - if num_received_items < len(ctx.items_received) and received_item_is_empty: - next_item = ctx.items_received[num_received_items] - await bizhawk.write(ctx.bizhawk_ctx, [ - (data.ram_addresses["gArchipelagoReceivedItem"] + 0, (next_item.item - BASE_OFFSET).to_bytes(2, "little"), "System Bus"), - (data.ram_addresses["gArchipelagoReceivedItem"] + 2, (num_received_items + 1).to_bytes(2, "little"), "System Bus"), - (data.ram_addresses["gArchipelagoReceivedItem"] + 4, [1], "System Bus"), # Mark struct full - (data.ram_addresses["gArchipelagoReceivedItem"] + 5, [next_item.flags & 1], "System Bus"), - ]) + sb1_address = int.from_bytes(guards["SAVE BLOCK 1"][1], "little") + sb2_address = int.from_bytes(guards["SAVE BLOCK 2"][1], "little") + + await self.handle_death_link(ctx, guards) + await self.handle_received_items(ctx, guards) + await self.handle_wonder_trade(ctx, guards) # Read flags in 2 chunks read_result = await bizhawk.guarded_read( ctx.bizhawk_ctx, - [(save_block_address + 0x1450, 0x96, "System Bus")], # Flags - [overworld_guard, save_block_address_guard] + [(sb1_address + 0x1450, 0x96, "System Bus")], # Flags + [guards["IN OVERWORLD"], guards["SAVE BLOCK 1"]] ) if read_result is None: # Not in overworld, or save block moved return - flag_bytes = read_result[0] read_result = await bizhawk.guarded_read( ctx.bizhawk_ctx, - [(save_block_address + 0x14E6, 0x96, "System Bus")], # Flags - [overworld_guard, save_block_address_guard] + [(sb1_address + 0x14E6, 0x96, "System Bus")], # Flags continued + [guards["IN OVERWORLD"], guards["SAVE BLOCK 1"]] ) if read_result is not None: flag_bytes += read_result[0] + # Read pokedex flags + pokedex_caught_bytes = bytes(0) + if ctx.slot_data["dexsanity"] == Toggle.option_true: + # Read pokedex flags + read_result = await bizhawk.guarded_read( + ctx.bizhawk_ctx, + [(sb2_address + 0x28, 0x34, "System Bus")], + [guards["IN OVERWORLD"], guards["SAVE BLOCK 2"]] + ) + if read_result is not None: + pokedex_caught_bytes = read_result[0] + game_clear = False local_checked_locations = set() local_set_events = {flag_name: False for flag_name in TRACKER_EVENT_FLAGS} local_found_key_items = {location_name: False for location_name in KEY_LOCATION_FLAGS} + defeated_legendaries = {legendary_name: False for legendary_name in LEGENDARY_NAMES.values()} + caught_legendaries = {legendary_name: False for legendary_name in LEGENDARY_NAMES.values()} # Check set flags for byte_i, byte in enumerate(flag_bytes): @@ -219,12 +278,45 @@ class PokemonEmeraldClient(BizHawkClient): if flag_id == self.goal_flag: game_clear = True + if flag_id in DEFEATED_LEGENDARY_FLAG_MAP: + defeated_legendaries[DEFEATED_LEGENDARY_FLAG_MAP[flag_id]] = True + + if flag_id in CAUGHT_LEGENDARY_FLAG_MAP: + caught_legendaries[CAUGHT_LEGENDARY_FLAG_MAP[flag_id]] = True + if flag_id in EVENT_FLAG_MAP: local_set_events[EVENT_FLAG_MAP[flag_id]] = True if flag_id in KEY_LOCATION_FLAG_MAP: local_found_key_items[KEY_LOCATION_FLAG_MAP[flag_id]] = True + # Check pokedex + if ctx.slot_data["dexsanity"] == Toggle.option_true: + for byte_i, byte in enumerate(pokedex_caught_bytes): + for i in range(8): + if byte & (1 << i) != 0: + dex_number = (byte_i * 8 + i) + 1 + + location_id = dex_number + BASE_OFFSET + POKEDEX_OFFSET + if location_id in ctx.server_locations: + local_checked_locations.add(location_id) + + # Count legendary hunt flags + if ctx.slot_data["goal"] == Goal.option_legendary_hunt: + # If legendary hunt doesn't require catching, add defeated legendaries to caught_legendaries + if ctx.slot_data["legendary_hunt_catch"] == Toggle.option_false: + for legendary, is_defeated in defeated_legendaries.items(): + if is_defeated: + caught_legendaries[legendary] = True + + num_caught = 0 + for legendary, is_caught in caught_legendaries.items(): + if is_caught and legendary in [LEGENDARY_NAMES[name] for name in ctx.slot_data["allowed_legendary_hunt_encounters"]]: + num_caught += 1 + + if num_caught >= ctx.slot_data["legendary_hunt_count"]: + game_clear = True + # Send locations if local_checked_locations != self.local_checked_locations: self.local_checked_locations = local_checked_locations @@ -232,14 +324,14 @@ class PokemonEmeraldClient(BizHawkClient): if local_checked_locations is not None: await ctx.send_msgs([{ "cmd": "LocationChecks", - "locations": list(local_checked_locations) + "locations": list(local_checked_locations), }]) # Send game clear if not ctx.finished_game and game_clear: await ctx.send_msgs([{ "cmd": "StatusUpdate", - "status": ClientStatus.CLIENT_GOAL + "status": ClientStatus.CLIENT_GOAL, }]) # Send tracker event flags @@ -254,7 +346,7 @@ class PokemonEmeraldClient(BizHawkClient): "key": f"pokemon_emerald_events_{ctx.team}_{ctx.slot}", "default": 0, "want_reply": False, - "operations": [{"operation": "or", "value": event_bitfield}] + "operations": [{"operation": "or", "value": event_bitfield}], }]) self.local_set_events = local_set_events @@ -269,9 +361,313 @@ class PokemonEmeraldClient(BizHawkClient): "key": f"pokemon_emerald_keys_{ctx.team}_{ctx.slot}", "default": 0, "want_reply": False, - "operations": [{"operation": "or", "value": key_bitfield}] + "operations": [{"operation": "or", "value": key_bitfield}], }]) self.local_found_key_items = local_found_key_items + + if ctx.slot_data["goal"] == Goal.option_legendary_hunt: + if caught_legendaries != self.local_defeated_legendaries and ctx.slot is not None: + legendary_bitfield = 0 + for i, legendary_name in enumerate(LEGENDARY_NAMES.values()): + if caught_legendaries[legendary_name]: + legendary_bitfield |= 1 << i + + await ctx.send_msgs([{ + "cmd": "Set", + "key": f"pokemon_emerald_legendaries_{ctx.team}_{ctx.slot}", + "default": 0, + "want_reply": False, + "operations": [{"operation": "or", "value": legendary_bitfield}], + }]) + self.local_defeated_legendaries = caught_legendaries except bizhawk.RequestFailedError: # Exit handler and return to main loop to reconnect pass + + async def handle_death_link(self, ctx: "BizHawkClientContext", guards: Dict[str, Tuple[int, bytes, str]]) -> None: + """ + Checks whether the player has died while connected and sends a death link if so. Queues a death link in the game + if a new one has been received. + """ + if ctx.slot_data.get("death_link", Toggle.option_false) == Toggle.option_true: + if "DeathLink" not in ctx.tags: + await ctx.update_death_link(True) + self.previous_death_link = ctx.last_death_link + + sb1_address = int.from_bytes(guards["SAVE BLOCK 1"][1], "little") + sb2_address = int.from_bytes(guards["SAVE BLOCK 2"][1], "little") + + read_result = await bizhawk.guarded_read( + ctx.bizhawk_ctx, [ + (sb1_address + 0x177C + (52 * 4), 4, "System Bus"), # White out stat + (sb1_address + 0x177C + (22 * 4), 4, "System Bus"), # Canary stat + (sb2_address + 0xAC, 4, "System Bus"), # Encryption key + ], + [guards["SAVE BLOCK 1"], guards["SAVE BLOCK 2"]] + ) + if read_result is None: # Save block moved + return + + encryption_key = int.from_bytes(read_result[2], "little") + times_whited_out = int.from_bytes(read_result[0], "little") ^ encryption_key + + # Canary is an unused stat that will always be 0. There is a low chance that we've done this read on + # a frame where the user has just entered a battle and the encryption key has been changed, but the data + # has not yet been encrypted with the new key. If `canary` is 0, `times_whited_out` is correct. + canary = int.from_bytes(read_result[1], "little") ^ encryption_key + + # Skip all deathlink code if save is not yet loaded (encryption key is zero) or white out stat not yet + # initialized (starts at 100 as a safety for subtracting values from an unsigned int). + if canary == 0 and encryption_key != 0 and times_whited_out >= 100: + if self.previous_death_link != ctx.last_death_link: + self.previous_death_link = ctx.last_death_link + if self.ignore_next_death_link: + self.ignore_next_death_link = False + else: + await bizhawk.write( + ctx.bizhawk_ctx, + [(data.ram_addresses["gArchipelagoDeathLinkQueued"], [1], "System Bus")] + ) + + if self.death_counter is None: + self.death_counter = times_whited_out + elif times_whited_out > self.death_counter: + await ctx.send_death(f"{ctx.player_names[ctx.slot]} is out of usable POKéMON! " + f"{ctx.player_names[ctx.slot]} whited out!") + self.ignore_next_death_link = True + self.death_counter = times_whited_out + + async def handle_received_items(self, ctx: "BizHawkClientContext", guards: Dict[str, Tuple[int, bytes, str]]) -> None: + """ + Checks the index of the most recently received item and whether the item queue is full. Writes the next item + into the game if necessary. + """ + received_item_address = data.ram_addresses["gArchipelagoReceivedItem"] + + sb1_address = int.from_bytes(guards["SAVE BLOCK 1"][1], "little") + + read_result = await bizhawk.guarded_read( + ctx.bizhawk_ctx, + [ + (sb1_address + 0x3778, 2, "System Bus"), # Number of received items + (received_item_address + 4, 1, "System Bus") # Received item struct full? + ], + [guards["IN OVERWORLD"], guards["SAVE BLOCK 1"]] + ) + if read_result is None: # Not in overworld, or save block moved + return + + num_received_items = int.from_bytes(read_result[0], "little") + received_item_is_empty = read_result[1][0] == 0 + + # If the game hasn't received all items yet and the received item struct doesn't contain an item, then + # fill it with the next item + if num_received_items < len(ctx.items_received) and received_item_is_empty: + next_item = ctx.items_received[num_received_items] + should_display = 1 if next_item.flags & 1 or next_item.player == ctx.slot else 0 + await bizhawk.write(ctx.bizhawk_ctx, [ + (received_item_address + 0, (next_item.item - BASE_OFFSET).to_bytes(2, "little"), "System Bus"), + (received_item_address + 2, (num_received_items + 1).to_bytes(2, "little"), "System Bus"), + (received_item_address + 4, [1], "System Bus"), + (received_item_address + 5, [should_display], "System Bus"), + ]) + + async def handle_wonder_trade(self, ctx: "BizHawkClientContext", guards: Dict[str, Tuple[int, bytes, str]]) -> None: + """ + Read wonder trade status from save data and either send a queued pokemon to data storage or attempt to retrieve + one from data storage and write it into the save. + """ + from CommonClient import logger + + sb1_address = int.from_bytes(guards["SAVE BLOCK 1"][1], "little") + + read_result = await bizhawk.guarded_read( + ctx.bizhawk_ctx, + [ + (sb1_address + 0x377C, 0x50, "System Bus"), # Wonder trade data + (sb1_address + 0x37CC, 1, "System Bus"), # Is wonder trade sent + ], + [guards["IN OVERWORLD"], guards["SAVE BLOCK 1"]] + ) + + if read_result is not None: + wonder_trade_pokemon_data = read_result[0] + trade_is_sent = read_result[1][0] + + if trade_is_sent == 0 and wonder_trade_pokemon_data[19] == 2: + # Game has wonder trade data to send. Send it to data storage, remove it from the game's memory, + # and mark that the game is waiting on receiving a trade + Utils.async_start(self.wonder_trade_send(ctx, pokemon_data_to_json(wonder_trade_pokemon_data))) + await bizhawk.write(ctx.bizhawk_ctx, [ + (sb1_address + 0x377C, bytes(0x50), "System Bus"), + (sb1_address + 0x37CC, [1], "System Bus"), + ]) + elif trade_is_sent != 0 and wonder_trade_pokemon_data[19] != 2: + # Game is waiting on receiving a trade. See if there are any available trades that were not + # sent by this player, and if so, try to receive one. + if self.wonder_trade_cooldown_timer <= 0 and f"pokemon_wonder_trades_{ctx.team}" in ctx.stored_data: + if any(item[0] != ctx.slot + for key, item in ctx.stored_data.get(f"pokemon_wonder_trades_{ctx.team}", {}).items() + if key != "_lock" and orjson.loads(item[1])["species"] <= 386): + received_trade = await self.wonder_trade_receive(ctx) + if received_trade is None: + self.wonder_trade_cooldown_timer = self.wonder_trade_cooldown + self.wonder_trade_cooldown *= 2 + self.wonder_trade_cooldown += random.randrange(0, 500) + else: + await bizhawk.write(ctx.bizhawk_ctx, [ + (sb1_address + 0x377C, json_to_pokemon_data(received_trade), "System Bus"), + ]) + logger.info("Wonder trade received!") + self.wonder_trade_cooldown = 5000 + + else: + # Very approximate "time since last loop", but extra delay is fine for this + self.wonder_trade_cooldown_timer -= int(ctx.watcher_timeout * 1000) + + async def wonder_trade_acquire(self, ctx: "BizHawkClientContext", keep_trying: bool = False) -> Optional[dict]: + """ + Acquires a lock on the `pokemon_wonder_trades_{ctx.team}` key in + datastorage. Locking the key means you have exclusive access + to modifying the value until you unlock it or the key expires (5 + seconds). + + If `keep_trying` is `True`, it will keep trying to acquire the lock + until successful. Otherwise it will return `None` if it fails to + acquire the lock. + """ + while not ctx.exit_event.is_set(): + lock = int(time.time_ns() / 1000000) + message_uuid = str(uuid.uuid4()) + await ctx.send_msgs([{ + "cmd": "Set", + "key": f"pokemon_wonder_trades_{ctx.team}", + "default": {"_lock": 0}, + "want_reply": True, + "operations": [{"operation": "update", "value": {"_lock": lock}}], + "uuid": message_uuid, + }]) + + self.wonder_trade_update_event.clear() + try: + await asyncio.wait_for(self.wonder_trade_update_event.wait(), 5) + except asyncio.TimeoutError: + if not keep_trying: + return None + continue + + reply = copy.deepcopy(self.latest_wonder_trade_reply) + + # Make sure the most recently received update was triggered by our lock attempt + if reply.get("uuid", None) != message_uuid: + if not keep_trying: + return None + await asyncio.sleep(self.wonder_trade_cooldown) + continue + + # Make sure the current value of the lock is what we set it to + # (I think this should theoretically never run) + if reply["value"]["_lock"] != lock: + if not keep_trying: + return None + await asyncio.sleep(self.wonder_trade_cooldown) + continue + + # Make sure that the lock value we replaced is at least 5 seconds old + # If it was unlocked before our change, its value was 0 and it will look decades old + if lock - reply["original_value"]["_lock"] < 5000: + # Multiple clients trying to lock the key may get stuck in a loop of checking the lock + # by trying to set it, which will extend its expiration. So if we see that the lock was + # too new when we replaced it, we should wait for increasingly longer periods so that + # eventually the lock will expire and a client will acquire it. + self.wonder_trade_cooldown *= 2 + self.wonder_trade_cooldown += random.randrange(0, 500) + + if not keep_trying: + self.wonder_trade_cooldown_timer = self.wonder_trade_cooldown + return None + await asyncio.sleep(self.wonder_trade_cooldown) + continue + + # We have the lock, reset the cooldown and return + self.wonder_trade_cooldown = 5000 + return reply + + async def wonder_trade_send(self, ctx: "BizHawkClientContext", data: str) -> None: + """ + Sends a wonder trade pokemon to data storage + """ + from CommonClient import logger + + reply = await self.wonder_trade_acquire(ctx, True) + + wonder_trade_slot = 0 + while str(wonder_trade_slot) in reply["value"]: + wonder_trade_slot += 1 + + await ctx.send_msgs([{ + "cmd": "Set", + "key": f"pokemon_wonder_trades_{ctx.team}", + "default": {"_lock": 0}, + "operations": [{"operation": "update", "value": { + "_lock": 0, + str(wonder_trade_slot): (ctx.slot, data), + }}], + }]) + + logger.info("Wonder trade sent! We'll notify you here when a trade has been found.") + + async def wonder_trade_receive(self, ctx: "BizHawkClientContext") -> Optional[str]: + """ + Tries to pop a pokemon out of the wonder trades. Returns `None` if + for some reason it can't immediately remove a compatible pokemon. + """ + reply = await self.wonder_trade_acquire(ctx) + + if reply is None: + return None + + candidate_slots = [ + int(slot) + for slot in reply["value"] + if slot != "_lock" \ + and reply["value"][slot][0] != ctx.slot \ + and orjson.loads(reply["value"][slot][1])["species"] <= 386 + ] + + if len(candidate_slots) == 0: + await ctx.send_msgs([{ + "cmd": "Set", + "key": f"pokemon_wonder_trades_{ctx.team}", + "default": {"_lock": 0}, + "operations": [{"operation": "update", "value": {"_lock": 0}}], + }]) + return None + + wonder_trade_slot = max(candidate_slots) + + await ctx.send_msgs([{ + "cmd": "Set", + "key": f"pokemon_wonder_trades_{ctx.team}", + "default": {"_lock": 0}, + "operations": [ + {"operation": "update", "value": {"_lock": 0}}, + {"operation": "pop", "value": str(wonder_trade_slot)}, + ] + }]) + + return reply["value"][str(wonder_trade_slot)][1] + + def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None: + if cmd == "Connected": + Utils.async_start(ctx.send_msgs([{ + "cmd": "SetNotify", + "keys": [f"pokemon_wonder_trades_{ctx.team}"], + }, { + "cmd": "Get", + "keys": [f"pokemon_wonder_trades_{ctx.team}"], + }])) + elif cmd == "SetReply": + if args.get("key", "") == f"pokemon_wonder_trades_{ctx.team}": + self.latest_wonder_trade_reply = args + self.wonder_trade_update_event.set() diff --git a/worlds/pokemon_emerald/data.py b/worlds/pokemon_emerald/data.py index bc51d84963..c4f7d7711c 100644 --- a/worlds/pokemon_emerald/data.py +++ b/worlds/pokemon_emerald/data.py @@ -5,7 +5,6 @@ defined data (like location labels or usable pokemon species), some cleanup and sorting, and Warp methods. """ from dataclasses import dataclass -import copy from enum import IntEnum import orjson from typing import Dict, List, NamedTuple, Optional, Set, FrozenSet, Tuple, Any, Union @@ -16,6 +15,25 @@ from BaseClasses import ItemClassification BASE_OFFSET = 3860000 +POKEDEX_OFFSET = 10000 + +IGNORABLE_MAPS = { + "MAP_ALTERING_CAVE", + "MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP1", + "MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP2", + "MAP_CAVE_OF_ORIGIN_UNUSED_RUBY_SAPPHIRE_MAP3", +} +"""These maps exist but don't show up in the rando or are unused, and so should be discarded""" + +POSTGAME_MAPS = { + "MAP_DESERT_UNDERPASS", + "MAP_SAFARI_ZONE_NORTHEAST", + "MAP_SAFARI_ZONE_SOUTHEAST", + "MAP_METEOR_FALLS_STEVENS_CAVE", +} +"""These maps have encounters and are locked behind beating the champion. Those encounter slots should be ignored for logical access to a species.""" + +NUM_REAL_SPECIES = 386 class Warp: @@ -55,14 +73,14 @@ class Warp: return f"{self.source_map}:{source_ids_string}/{self.dest_map}:{dest_ids_string}{'!' if self.is_one_way else ''}" - def connects_to(self, other: 'Warp') -> bool: + def connects_to(self, other: "Warp") -> bool: """ Returns true if this warp sends the player to `other` """ return self.dest_map == other.source_map and set(self.dest_ids) <= set(other.source_ids) @staticmethod - def decode(encoded_string: str) -> 'Warp': + def decode(encoded_string: str) -> "Warp": """ Create a Warp object from an encoded string """ @@ -87,6 +105,7 @@ class Warp: class ItemData(NamedTuple): label: str item_id: int + modern_id: Optional[int] classification: ItemClassification tags: FrozenSet[str] @@ -96,11 +115,25 @@ class LocationData(NamedTuple): label: str parent_region: str default_item: int - rom_address: int + address: Union[int, List[int]] flag: int tags: FrozenSet[str] +class EncounterTableData(NamedTuple): + slots: List[int] + address: int + + +@dataclass +class MapData: + name: str + header_address: int + land_encounters: Optional[EncounterTableData] + water_encounters: Optional[EncounterTableData] + fishing_encounters: Optional[EncounterTableData] + + class EventData(NamedTuple): name: str parent_region: str @@ -108,13 +141,21 @@ class EventData(NamedTuple): class RegionData: name: str + parent_map: MapData + has_grass: bool + has_water: bool + has_fishing: bool exits: List[str] warps: List[str] locations: List[str] events: List[EventData] - def __init__(self, name: str): + def __init__(self, name: str, parent_map: MapData, has_grass: bool, has_water: bool, has_fishing: bool): self.name = name + self.parent_map = parent_map + self.has_grass = has_grass + self.has_water = has_water + self.has_fishing = has_fishing self.exits = [] self.warps = [] self.locations = [] @@ -181,9 +222,9 @@ class EvolutionData(NamedTuple): species_id: int -class StaticEncounterData(NamedTuple): +class MiscPokemonData(NamedTuple): species_id: int - rom_address: int + address: int @dataclass @@ -191,16 +232,18 @@ class SpeciesData: name: str label: str species_id: int + national_dex_number: int base_stats: BaseStats types: Tuple[int, int] abilities: Tuple[int, int] evolutions: List[EvolutionData] pre_evolution: Optional[int] catch_rate: int + friendship: int learnset: List[LearnsetMove] tm_hm_compatibility: int - learnset_rom_address: int - rom_address: int + learnset_address: int + address: int class AbilityData(NamedTuple): @@ -208,19 +251,6 @@ class AbilityData(NamedTuple): label: str -class EncounterTableData(NamedTuple): - slots: List[int] - rom_address: int - - -@dataclass -class MapData: - name: str - land_encounters: Optional[EncounterTableData] - water_encounters: Optional[EncounterTableData] - fishing_encounters: Optional[EncounterTableData] - - class TrainerPokemonDataTypeEnum(IntEnum): NO_ITEM_DEFAULT_MOVES = 0 ITEM_DEFAULT_MOVES = 1 @@ -250,15 +280,15 @@ class TrainerPokemonData: class TrainerPartyData: pokemon: List[TrainerPokemonData] pokemon_data_type: TrainerPokemonDataTypeEnum - rom_address: int + address: int @dataclass class TrainerData: trainer_id: int party: TrainerPartyData - rom_address: int - battle_script_rom_address: int + address: int + script_address: int class PokemonEmeraldData: @@ -269,11 +299,13 @@ class PokemonEmeraldData: regions: Dict[str, RegionData] locations: Dict[str, LocationData] items: Dict[int, ItemData] - species: List[Optional[SpeciesData]] - static_encounters: List[StaticEncounterData] + species: Dict[int, SpeciesData] + legendary_encounters: List[MiscPokemonData] + misc_pokemon: List[MiscPokemonData] tmhm_moves: List[int] abilities: List[AbilityData] - maps: List[MapData] + move_labels: Dict[str, int] + maps: Dict[str, MapData] warps: Dict[str, Warp] warp_map: Dict[str, Optional[str]] trainers: List[TrainerData] @@ -286,29 +318,20 @@ class PokemonEmeraldData: self.regions = {} self.locations = {} self.items = {} - self.species = [] - self.static_encounters = [] + self.species = {} + self.legendary_encounters = [] + self.misc_pokemon = [] self.tmhm_moves = [] self.abilities = [] - self.maps = [] + self.move_labels = {} + self.maps = {} self.warps = {} self.warp_map = {} self.trainers = [] def load_json_data(data_name: str) -> Union[List[Any], Dict[str, Any]]: - return orjson.loads(pkgutil.get_data(__name__, "data/" + data_name).decode('utf-8-sig')) - - -data = PokemonEmeraldData() - -def create_data_copy() -> PokemonEmeraldData: - new_copy = PokemonEmeraldData() - new_copy.species = copy.deepcopy(data.species) - new_copy.tmhm_moves = copy.deepcopy(data.tmhm_moves) - new_copy.maps = copy.deepcopy(data.maps) - new_copy.static_encounters = copy.deepcopy(data.static_encounters) - new_copy.trainers = copy.deepcopy(data.trainers) + return orjson.loads(pkgutil.get_data(__name__, "data/" + data_name).decode("utf-8-sig")) def _init() -> None: @@ -319,6 +342,39 @@ def _init() -> None: location_attributes_json = load_json_data("locations.json") + # Create map data + for map_name, map_json in extracted_data["maps"].items(): + if map_name in IGNORABLE_MAPS: + continue + + land_encounters = None + water_encounters = None + fishing_encounters = None + + if "land_encounters" in map_json: + land_encounters = EncounterTableData( + map_json["land_encounters"]["slots"], + map_json["land_encounters"]["address"] + ) + if "water_encounters" in map_json: + water_encounters = EncounterTableData( + map_json["water_encounters"]["slots"], + map_json["water_encounters"]["address"] + ) + if "fishing_encounters" in map_json: + fishing_encounters = EncounterTableData( + map_json["fishing_encounters"]["slots"], + map_json["fishing_encounters"]["address"] + ) + + data.maps[map_name] = MapData( + map_name, + map_json["header_address"], + land_encounters, + water_encounters, + fishing_encounters + ) + # Load/merge region json files region_json_list = [] for file in pkg_resources.resource_listdir(__name__, "data/regions"): @@ -338,7 +394,13 @@ def _init() -> None: data.regions = {} for region_name, region_json in regions_json.items(): - new_region = RegionData(region_name) + new_region = RegionData( + region_name, + data.maps[region_json["parent_map"]], + region_json["has_grass"], + region_json["has_water"], + region_json["has_fishing"] + ) # Locations for location_name in region_json["locations"]: @@ -346,15 +408,35 @@ def _init() -> None: raise AssertionError(f"Location [{location_name}] was claimed by multiple regions") location_json = extracted_data["locations"][location_name] - new_location = LocationData( - location_name, - location_attributes_json[location_name]["label"], - region_name, - location_json["default_item"], - location_json["rom_address"], - location_json["flag"], - frozenset(location_attributes_json[location_name]["tags"]) - ) + if location_name.startswith("TRAINER_BRENDAN_") or location_name.startswith("TRAINER_MAY_"): + import re + locale = re.match("TRAINER_BRENDAN_([A-Z0-9_]+)_MUDKIP_REWARD", location_name).group(1) + alternate_rival_jsons = [extracted_data["locations"][alternate] for alternate in [ + f"TRAINER_BRENDAN_{locale}_TORCHIC_REWARD", + f"TRAINER_BRENDAN_{locale}_TREECKO_REWARD", + f"TRAINER_MAY_{locale}_MUDKIP_REWARD", + f"TRAINER_MAY_{locale}_TORCHIC_REWARD", + f"TRAINER_MAY_{locale}_TREECKO_REWARD", + ]] + new_location = LocationData( + location_name, + location_attributes_json[location_name]["label"], + region_name, + location_json["default_item"], + [location_json["address"]] + [j["address"] for j in alternate_rival_jsons], + location_json["flag"], + frozenset(location_attributes_json[location_name]["tags"]) + ) + else: + new_location = LocationData( + location_name, + location_attributes_json[location_name]["label"], + region_name, + location_json["default_item"], + location_json["address"], + location_json["flag"], + frozenset(location_attributes_json[location_name]["tags"]) + ) new_region.locations.append(location_name) data.locations[location_name] = new_location claimed_locations.add(location_name) @@ -401,6 +483,7 @@ def _init() -> None: data.items[data.constants[item_constant_name]] = ItemData( attributes["label"], data.constants[item_constant_name], + attributes["modern_id"], item_classification, frozenset(attributes["tags"]) ) @@ -408,408 +491,408 @@ def _init() -> None: # Create species data # Excludes extras like copies of Unown and special species values like SPECIES_EGG. - all_species: List[Tuple[str, str]] = [ - ("SPECIES_BULBASAUR", "Bulbasaur"), - ("SPECIES_IVYSAUR", "Ivysaur"), - ("SPECIES_VENUSAUR", "Venusaur"), - ("SPECIES_CHARMANDER", "Charmander"), - ("SPECIES_CHARMELEON", "Charmeleon"), - ("SPECIES_CHARIZARD", "Charizard"), - ("SPECIES_SQUIRTLE", "Squirtle"), - ("SPECIES_WARTORTLE", "Wartortle"), - ("SPECIES_BLASTOISE", "Blastoise"), - ("SPECIES_CATERPIE", "Caterpie"), - ("SPECIES_METAPOD", "Metapod"), - ("SPECIES_BUTTERFREE", "Butterfree"), - ("SPECIES_WEEDLE", "Weedle"), - ("SPECIES_KAKUNA", "Kakuna"), - ("SPECIES_BEEDRILL", "Beedrill"), - ("SPECIES_PIDGEY", "Pidgey"), - ("SPECIES_PIDGEOTTO", "Pidgeotto"), - ("SPECIES_PIDGEOT", "Pidgeot"), - ("SPECIES_RATTATA", "Rattata"), - ("SPECIES_RATICATE", "Raticate"), - ("SPECIES_SPEAROW", "Spearow"), - ("SPECIES_FEAROW", "Fearow"), - ("SPECIES_EKANS", "Ekans"), - ("SPECIES_ARBOK", "Arbok"), - ("SPECIES_PIKACHU", "Pikachu"), - ("SPECIES_RAICHU", "Raichu"), - ("SPECIES_SANDSHREW", "Sandshrew"), - ("SPECIES_SANDSLASH", "Sandslash"), - ("SPECIES_NIDORAN_F", "Nidoran Female"), - ("SPECIES_NIDORINA", "Nidorina"), - ("SPECIES_NIDOQUEEN", "Nidoqueen"), - ("SPECIES_NIDORAN_M", "Nidoran Male"), - ("SPECIES_NIDORINO", "Nidorino"), - ("SPECIES_NIDOKING", "Nidoking"), - ("SPECIES_CLEFAIRY", "Clefairy"), - ("SPECIES_CLEFABLE", "Clefable"), - ("SPECIES_VULPIX", "Vulpix"), - ("SPECIES_NINETALES", "Ninetales"), - ("SPECIES_JIGGLYPUFF", "Jigglypuff"), - ("SPECIES_WIGGLYTUFF", "Wigglytuff"), - ("SPECIES_ZUBAT", "Zubat"), - ("SPECIES_GOLBAT", "Golbat"), - ("SPECIES_ODDISH", "Oddish"), - ("SPECIES_GLOOM", "Gloom"), - ("SPECIES_VILEPLUME", "Vileplume"), - ("SPECIES_PARAS", "Paras"), - ("SPECIES_PARASECT", "Parasect"), - ("SPECIES_VENONAT", "Venonat"), - ("SPECIES_VENOMOTH", "Venomoth"), - ("SPECIES_DIGLETT", "Diglett"), - ("SPECIES_DUGTRIO", "Dugtrio"), - ("SPECIES_MEOWTH", "Meowth"), - ("SPECIES_PERSIAN", "Persian"), - ("SPECIES_PSYDUCK", "Psyduck"), - ("SPECIES_GOLDUCK", "Golduck"), - ("SPECIES_MANKEY", "Mankey"), - ("SPECIES_PRIMEAPE", "Primeape"), - ("SPECIES_GROWLITHE", "Growlithe"), - ("SPECIES_ARCANINE", "Arcanine"), - ("SPECIES_POLIWAG", "Poliwag"), - ("SPECIES_POLIWHIRL", "Poliwhirl"), - ("SPECIES_POLIWRATH", "Poliwrath"), - ("SPECIES_ABRA", "Abra"), - ("SPECIES_KADABRA", "Kadabra"), - ("SPECIES_ALAKAZAM", "Alakazam"), - ("SPECIES_MACHOP", "Machop"), - ("SPECIES_MACHOKE", "Machoke"), - ("SPECIES_MACHAMP", "Machamp"), - ("SPECIES_BELLSPROUT", "Bellsprout"), - ("SPECIES_WEEPINBELL", "Weepinbell"), - ("SPECIES_VICTREEBEL", "Victreebel"), - ("SPECIES_TENTACOOL", "Tentacool"), - ("SPECIES_TENTACRUEL", "Tentacruel"), - ("SPECIES_GEODUDE", "Geodude"), - ("SPECIES_GRAVELER", "Graveler"), - ("SPECIES_GOLEM", "Golem"), - ("SPECIES_PONYTA", "Ponyta"), - ("SPECIES_RAPIDASH", "Rapidash"), - ("SPECIES_SLOWPOKE", "Slowpoke"), - ("SPECIES_SLOWBRO", "Slowbro"), - ("SPECIES_MAGNEMITE", "Magnemite"), - ("SPECIES_MAGNETON", "Magneton"), - ("SPECIES_FARFETCHD", "Farfetch'd"), - ("SPECIES_DODUO", "Doduo"), - ("SPECIES_DODRIO", "Dodrio"), - ("SPECIES_SEEL", "Seel"), - ("SPECIES_DEWGONG", "Dewgong"), - ("SPECIES_GRIMER", "Grimer"), - ("SPECIES_MUK", "Muk"), - ("SPECIES_SHELLDER", "Shellder"), - ("SPECIES_CLOYSTER", "Cloyster"), - ("SPECIES_GASTLY", "Gastly"), - ("SPECIES_HAUNTER", "Haunter"), - ("SPECIES_GENGAR", "Gengar"), - ("SPECIES_ONIX", "Onix"), - ("SPECIES_DROWZEE", "Drowzee"), - ("SPECIES_HYPNO", "Hypno"), - ("SPECIES_KRABBY", "Krabby"), - ("SPECIES_KINGLER", "Kingler"), - ("SPECIES_VOLTORB", "Voltorb"), - ("SPECIES_ELECTRODE", "Electrode"), - ("SPECIES_EXEGGCUTE", "Exeggcute"), - ("SPECIES_EXEGGUTOR", "Exeggutor"), - ("SPECIES_CUBONE", "Cubone"), - ("SPECIES_MAROWAK", "Marowak"), - ("SPECIES_HITMONLEE", "Hitmonlee"), - ("SPECIES_HITMONCHAN", "Hitmonchan"), - ("SPECIES_LICKITUNG", "Lickitung"), - ("SPECIES_KOFFING", "Koffing"), - ("SPECIES_WEEZING", "Weezing"), - ("SPECIES_RHYHORN", "Rhyhorn"), - ("SPECIES_RHYDON", "Rhydon"), - ("SPECIES_CHANSEY", "Chansey"), - ("SPECIES_TANGELA", "Tangela"), - ("SPECIES_KANGASKHAN", "Kangaskhan"), - ("SPECIES_HORSEA", "Horsea"), - ("SPECIES_SEADRA", "Seadra"), - ("SPECIES_GOLDEEN", "Goldeen"), - ("SPECIES_SEAKING", "Seaking"), - ("SPECIES_STARYU", "Staryu"), - ("SPECIES_STARMIE", "Starmie"), - ("SPECIES_MR_MIME", "Mr. Mime"), - ("SPECIES_SCYTHER", "Scyther"), - ("SPECIES_JYNX", "Jynx"), - ("SPECIES_ELECTABUZZ", "Electabuzz"), - ("SPECIES_MAGMAR", "Magmar"), - ("SPECIES_PINSIR", "Pinsir"), - ("SPECIES_TAUROS", "Tauros"), - ("SPECIES_MAGIKARP", "Magikarp"), - ("SPECIES_GYARADOS", "Gyarados"), - ("SPECIES_LAPRAS", "Lapras"), - ("SPECIES_DITTO", "Ditto"), - ("SPECIES_EEVEE", "Eevee"), - ("SPECIES_VAPOREON", "Vaporeon"), - ("SPECIES_JOLTEON", "Jolteon"), - ("SPECIES_FLAREON", "Flareon"), - ("SPECIES_PORYGON", "Porygon"), - ("SPECIES_OMANYTE", "Omanyte"), - ("SPECIES_OMASTAR", "Omastar"), - ("SPECIES_KABUTO", "Kabuto"), - ("SPECIES_KABUTOPS", "Kabutops"), - ("SPECIES_AERODACTYL", "Aerodactyl"), - ("SPECIES_SNORLAX", "Snorlax"), - ("SPECIES_ARTICUNO", "Articuno"), - ("SPECIES_ZAPDOS", "Zapdos"), - ("SPECIES_MOLTRES", "Moltres"), - ("SPECIES_DRATINI", "Dratini"), - ("SPECIES_DRAGONAIR", "Dragonair"), - ("SPECIES_DRAGONITE", "Dragonite"), - ("SPECIES_MEWTWO", "Mewtwo"), - ("SPECIES_MEW", "Mew"), - ("SPECIES_CHIKORITA", "Chikorita"), - ("SPECIES_BAYLEEF", "Bayleaf"), - ("SPECIES_MEGANIUM", "Meganium"), - ("SPECIES_CYNDAQUIL", "Cindaquil"), - ("SPECIES_QUILAVA", "Quilava"), - ("SPECIES_TYPHLOSION", "Typhlosion"), - ("SPECIES_TOTODILE", "Totodile"), - ("SPECIES_CROCONAW", "Croconaw"), - ("SPECIES_FERALIGATR", "Feraligatr"), - ("SPECIES_SENTRET", "Sentret"), - ("SPECIES_FURRET", "Furret"), - ("SPECIES_HOOTHOOT", "Hoothoot"), - ("SPECIES_NOCTOWL", "Noctowl"), - ("SPECIES_LEDYBA", "Ledyba"), - ("SPECIES_LEDIAN", "Ledian"), - ("SPECIES_SPINARAK", "Spinarak"), - ("SPECIES_ARIADOS", "Ariados"), - ("SPECIES_CROBAT", "Crobat"), - ("SPECIES_CHINCHOU", "Chinchou"), - ("SPECIES_LANTURN", "Lanturn"), - ("SPECIES_PICHU", "Pichu"), - ("SPECIES_CLEFFA", "Cleffa"), - ("SPECIES_IGGLYBUFF", "Igglybuff"), - ("SPECIES_TOGEPI", "Togepi"), - ("SPECIES_TOGETIC", "Togetic"), - ("SPECIES_NATU", "Natu"), - ("SPECIES_XATU", "Xatu"), - ("SPECIES_MAREEP", "Mareep"), - ("SPECIES_FLAAFFY", "Flaafy"), - ("SPECIES_AMPHAROS", "Ampharos"), - ("SPECIES_BELLOSSOM", "Bellossom"), - ("SPECIES_MARILL", "Marill"), - ("SPECIES_AZUMARILL", "Azumarill"), - ("SPECIES_SUDOWOODO", "Sudowoodo"), - ("SPECIES_POLITOED", "Politoed"), - ("SPECIES_HOPPIP", "Hoppip"), - ("SPECIES_SKIPLOOM", "Skiploom"), - ("SPECIES_JUMPLUFF", "Jumpluff"), - ("SPECIES_AIPOM", "Aipom"), - ("SPECIES_SUNKERN", "Sunkern"), - ("SPECIES_SUNFLORA", "Sunflora"), - ("SPECIES_YANMA", "Yanma"), - ("SPECIES_WOOPER", "Wooper"), - ("SPECIES_QUAGSIRE", "Quagsire"), - ("SPECIES_ESPEON", "Espeon"), - ("SPECIES_UMBREON", "Umbreon"), - ("SPECIES_MURKROW", "Murkrow"), - ("SPECIES_SLOWKING", "Slowking"), - ("SPECIES_MISDREAVUS", "Misdreavus"), - ("SPECIES_UNOWN", "Unown"), - ("SPECIES_WOBBUFFET", "Wobbuffet"), - ("SPECIES_GIRAFARIG", "Girafarig"), - ("SPECIES_PINECO", "Pineco"), - ("SPECIES_FORRETRESS", "Forretress"), - ("SPECIES_DUNSPARCE", "Dunsparce"), - ("SPECIES_GLIGAR", "Gligar"), - ("SPECIES_STEELIX", "Steelix"), - ("SPECIES_SNUBBULL", "Snubbull"), - ("SPECIES_GRANBULL", "Granbull"), - ("SPECIES_QWILFISH", "Qwilfish"), - ("SPECIES_SCIZOR", "Scizor"), - ("SPECIES_SHUCKLE", "Shuckle"), - ("SPECIES_HERACROSS", "Heracross"), - ("SPECIES_SNEASEL", "Sneasel"), - ("SPECIES_TEDDIURSA", "Teddiursa"), - ("SPECIES_URSARING", "Ursaring"), - ("SPECIES_SLUGMA", "Slugma"), - ("SPECIES_MAGCARGO", "Magcargo"), - ("SPECIES_SWINUB", "Swinub"), - ("SPECIES_PILOSWINE", "Piloswine"), - ("SPECIES_CORSOLA", "Corsola"), - ("SPECIES_REMORAID", "Remoraid"), - ("SPECIES_OCTILLERY", "Octillery"), - ("SPECIES_DELIBIRD", "Delibird"), - ("SPECIES_MANTINE", "Mantine"), - ("SPECIES_SKARMORY", "Skarmory"), - ("SPECIES_HOUNDOUR", "Houndour"), - ("SPECIES_HOUNDOOM", "Houndoom"), - ("SPECIES_KINGDRA", "Kingdra"), - ("SPECIES_PHANPY", "Phanpy"), - ("SPECIES_DONPHAN", "Donphan"), - ("SPECIES_PORYGON2", "Porygon2"), - ("SPECIES_STANTLER", "Stantler"), - ("SPECIES_SMEARGLE", "Smeargle"), - ("SPECIES_TYROGUE", "Tyrogue"), - ("SPECIES_HITMONTOP", "Hitmontop"), - ("SPECIES_SMOOCHUM", "Smoochum"), - ("SPECIES_ELEKID", "Elekid"), - ("SPECIES_MAGBY", "Magby"), - ("SPECIES_MILTANK", "Miltank"), - ("SPECIES_BLISSEY", "Blissey"), - ("SPECIES_RAIKOU", "Raikou"), - ("SPECIES_ENTEI", "Entei"), - ("SPECIES_SUICUNE", "Suicune"), - ("SPECIES_LARVITAR", "Larvitar"), - ("SPECIES_PUPITAR", "Pupitar"), - ("SPECIES_TYRANITAR", "Tyranitar"), - ("SPECIES_LUGIA", "Lugia"), - ("SPECIES_HO_OH", "Ho-oh"), - ("SPECIES_CELEBI", "Celebi"), - ("SPECIES_TREECKO", "Treecko"), - ("SPECIES_GROVYLE", "Grovyle"), - ("SPECIES_SCEPTILE", "Sceptile"), - ("SPECIES_TORCHIC", "Torchic"), - ("SPECIES_COMBUSKEN", "Combusken"), - ("SPECIES_BLAZIKEN", "Blaziken"), - ("SPECIES_MUDKIP", "Mudkip"), - ("SPECIES_MARSHTOMP", "Marshtomp"), - ("SPECIES_SWAMPERT", "Swampert"), - ("SPECIES_POOCHYENA", "Poochyena"), - ("SPECIES_MIGHTYENA", "Mightyena"), - ("SPECIES_ZIGZAGOON", "Zigzagoon"), - ("SPECIES_LINOONE", "Linoon"), - ("SPECIES_WURMPLE", "Wurmple"), - ("SPECIES_SILCOON", "Silcoon"), - ("SPECIES_BEAUTIFLY", "Beautifly"), - ("SPECIES_CASCOON", "Cascoon"), - ("SPECIES_DUSTOX", "Dustox"), - ("SPECIES_LOTAD", "Lotad"), - ("SPECIES_LOMBRE", "Lombre"), - ("SPECIES_LUDICOLO", "Ludicolo"), - ("SPECIES_SEEDOT", "Seedot"), - ("SPECIES_NUZLEAF", "Nuzleaf"), - ("SPECIES_SHIFTRY", "Shiftry"), - ("SPECIES_NINCADA", "Nincada"), - ("SPECIES_NINJASK", "Ninjask"), - ("SPECIES_SHEDINJA", "Shedinja"), - ("SPECIES_TAILLOW", "Taillow"), - ("SPECIES_SWELLOW", "Swellow"), - ("SPECIES_SHROOMISH", "Shroomish"), - ("SPECIES_BRELOOM", "Breloom"), - ("SPECIES_SPINDA", "Spinda"), - ("SPECIES_WINGULL", "Wingull"), - ("SPECIES_PELIPPER", "Pelipper"), - ("SPECIES_SURSKIT", "Surskit"), - ("SPECIES_MASQUERAIN", "Masquerain"), - ("SPECIES_WAILMER", "Wailmer"), - ("SPECIES_WAILORD", "Wailord"), - ("SPECIES_SKITTY", "Skitty"), - ("SPECIES_DELCATTY", "Delcatty"), - ("SPECIES_KECLEON", "Kecleon"), - ("SPECIES_BALTOY", "Baltoy"), - ("SPECIES_CLAYDOL", "Claydol"), - ("SPECIES_NOSEPASS", "Nosepass"), - ("SPECIES_TORKOAL", "Torkoal"), - ("SPECIES_SABLEYE", "Sableye"), - ("SPECIES_BARBOACH", "Barboach"), - ("SPECIES_WHISCASH", "Whiscash"), - ("SPECIES_LUVDISC", "Luvdisc"), - ("SPECIES_CORPHISH", "Corphish"), - ("SPECIES_CRAWDAUNT", "Crawdaunt"), - ("SPECIES_FEEBAS", "Feebas"), - ("SPECIES_MILOTIC", "Milotic"), - ("SPECIES_CARVANHA", "Carvanha"), - ("SPECIES_SHARPEDO", "Sharpedo"), - ("SPECIES_TRAPINCH", "Trapinch"), - ("SPECIES_VIBRAVA", "Vibrava"), - ("SPECIES_FLYGON", "Flygon"), - ("SPECIES_MAKUHITA", "Makuhita"), - ("SPECIES_HARIYAMA", "Hariyama"), - ("SPECIES_ELECTRIKE", "Electrike"), - ("SPECIES_MANECTRIC", "Manectric"), - ("SPECIES_NUMEL", "Numel"), - ("SPECIES_CAMERUPT", "Camerupt"), - ("SPECIES_SPHEAL", "Spheal"), - ("SPECIES_SEALEO", "Sealeo"), - ("SPECIES_WALREIN", "Walrein"), - ("SPECIES_CACNEA", "Cacnea"), - ("SPECIES_CACTURNE", "Cacturne"), - ("SPECIES_SNORUNT", "Snorunt"), - ("SPECIES_GLALIE", "Glalie"), - ("SPECIES_LUNATONE", "Lunatone"), - ("SPECIES_SOLROCK", "Solrock"), - ("SPECIES_AZURILL", "Azurill"), - ("SPECIES_SPOINK", "Spoink"), - ("SPECIES_GRUMPIG", "Grumpig"), - ("SPECIES_PLUSLE", "Plusle"), - ("SPECIES_MINUN", "Minun"), - ("SPECIES_MAWILE", "Mawile"), - ("SPECIES_MEDITITE", "Meditite"), - ("SPECIES_MEDICHAM", "Medicham"), - ("SPECIES_SWABLU", "Swablu"), - ("SPECIES_ALTARIA", "Altaria"), - ("SPECIES_WYNAUT", "Wynaut"), - ("SPECIES_DUSKULL", "Duskull"), - ("SPECIES_DUSCLOPS", "Dusclops"), - ("SPECIES_ROSELIA", "Roselia"), - ("SPECIES_SLAKOTH", "Slakoth"), - ("SPECIES_VIGOROTH", "Vigoroth"), - ("SPECIES_SLAKING", "Slaking"), - ("SPECIES_GULPIN", "Gulpin"), - ("SPECIES_SWALOT", "Swalot"), - ("SPECIES_TROPIUS", "Tropius"), - ("SPECIES_WHISMUR", "Whismur"), - ("SPECIES_LOUDRED", "Loudred"), - ("SPECIES_EXPLOUD", "Exploud"), - ("SPECIES_CLAMPERL", "Clamperl"), - ("SPECIES_HUNTAIL", "Huntail"), - ("SPECIES_GOREBYSS", "Gorebyss"), - ("SPECIES_ABSOL", "Absol"), - ("SPECIES_SHUPPET", "Shuppet"), - ("SPECIES_BANETTE", "Banette"), - ("SPECIES_SEVIPER", "Seviper"), - ("SPECIES_ZANGOOSE", "Zangoose"), - ("SPECIES_RELICANTH", "Relicanth"), - ("SPECIES_ARON", "Aron"), - ("SPECIES_LAIRON", "Lairon"), - ("SPECIES_AGGRON", "Aggron"), - ("SPECIES_CASTFORM", "Castform"), - ("SPECIES_VOLBEAT", "Volbeat"), - ("SPECIES_ILLUMISE", "Illumise"), - ("SPECIES_LILEEP", "Lileep"), - ("SPECIES_CRADILY", "Cradily"), - ("SPECIES_ANORITH", "Anorith"), - ("SPECIES_ARMALDO", "Armaldo"), - ("SPECIES_RALTS", "Ralts"), - ("SPECIES_KIRLIA", "Kirlia"), - ("SPECIES_GARDEVOIR", "Gardevoir"), - ("SPECIES_BAGON", "Bagon"), - ("SPECIES_SHELGON", "Shelgon"), - ("SPECIES_SALAMENCE", "Salamence"), - ("SPECIES_BELDUM", "Beldum"), - ("SPECIES_METANG", "Metang"), - ("SPECIES_METAGROSS", "Metagross"), - ("SPECIES_REGIROCK", "Regirock"), - ("SPECIES_REGICE", "Regice"), - ("SPECIES_REGISTEEL", "Registeel"), - ("SPECIES_KYOGRE", "Kyogre"), - ("SPECIES_GROUDON", "Groudon"), - ("SPECIES_RAYQUAZA", "Rayquaza"), - ("SPECIES_LATIAS", "Latias"), - ("SPECIES_LATIOS", "Latios"), - ("SPECIES_JIRACHI", "Jirachi"), - ("SPECIES_DEOXYS", "Deoxys"), - ("SPECIES_CHIMECHO", "Chimecho") + all_species: List[Tuple[str, str, int]] = [ + ("SPECIES_BULBASAUR", "Bulbasaur", 1), + ("SPECIES_IVYSAUR", "Ivysaur", 2), + ("SPECIES_VENUSAUR", "Venusaur", 3), + ("SPECIES_CHARMANDER", "Charmander", 4), + ("SPECIES_CHARMELEON", "Charmeleon", 5), + ("SPECIES_CHARIZARD", "Charizard", 6), + ("SPECIES_SQUIRTLE", "Squirtle", 7), + ("SPECIES_WARTORTLE", "Wartortle", 8), + ("SPECIES_BLASTOISE", "Blastoise", 9), + ("SPECIES_CATERPIE", "Caterpie", 10), + ("SPECIES_METAPOD", "Metapod", 11), + ("SPECIES_BUTTERFREE", "Butterfree", 12), + ("SPECIES_WEEDLE", "Weedle", 13), + ("SPECIES_KAKUNA", "Kakuna", 14), + ("SPECIES_BEEDRILL", "Beedrill", 15), + ("SPECIES_PIDGEY", "Pidgey", 16), + ("SPECIES_PIDGEOTTO", "Pidgeotto", 17), + ("SPECIES_PIDGEOT", "Pidgeot", 18), + ("SPECIES_RATTATA", "Rattata", 19), + ("SPECIES_RATICATE", "Raticate", 20), + ("SPECIES_SPEAROW", "Spearow", 21), + ("SPECIES_FEAROW", "Fearow", 22), + ("SPECIES_EKANS", "Ekans", 23), + ("SPECIES_ARBOK", "Arbok", 24), + ("SPECIES_PIKACHU", "Pikachu", 25), + ("SPECIES_RAICHU", "Raichu", 26), + ("SPECIES_SANDSHREW", "Sandshrew", 27), + ("SPECIES_SANDSLASH", "Sandslash", 28), + ("SPECIES_NIDORAN_F", "Nidoran Female", 29), + ("SPECIES_NIDORINA", "Nidorina", 30), + ("SPECIES_NIDOQUEEN", "Nidoqueen", 31), + ("SPECIES_NIDORAN_M", "Nidoran Male", 32), + ("SPECIES_NIDORINO", "Nidorino", 33), + ("SPECIES_NIDOKING", "Nidoking", 34), + ("SPECIES_CLEFAIRY", "Clefairy", 35), + ("SPECIES_CLEFABLE", "Clefable", 36), + ("SPECIES_VULPIX", "Vulpix", 37), + ("SPECIES_NINETALES", "Ninetales", 38), + ("SPECIES_JIGGLYPUFF", "Jigglypuff", 39), + ("SPECIES_WIGGLYTUFF", "Wigglytuff", 40), + ("SPECIES_ZUBAT", "Zubat", 41), + ("SPECIES_GOLBAT", "Golbat", 42), + ("SPECIES_ODDISH", "Oddish", 43), + ("SPECIES_GLOOM", "Gloom", 44), + ("SPECIES_VILEPLUME", "Vileplume", 45), + ("SPECIES_PARAS", "Paras", 46), + ("SPECIES_PARASECT", "Parasect", 47), + ("SPECIES_VENONAT", "Venonat", 48), + ("SPECIES_VENOMOTH", "Venomoth", 49), + ("SPECIES_DIGLETT", "Diglett", 50), + ("SPECIES_DUGTRIO", "Dugtrio", 51), + ("SPECIES_MEOWTH", "Meowth", 52), + ("SPECIES_PERSIAN", "Persian", 53), + ("SPECIES_PSYDUCK", "Psyduck", 54), + ("SPECIES_GOLDUCK", "Golduck", 55), + ("SPECIES_MANKEY", "Mankey", 56), + ("SPECIES_PRIMEAPE", "Primeape", 57), + ("SPECIES_GROWLITHE", "Growlithe", 58), + ("SPECIES_ARCANINE", "Arcanine", 59), + ("SPECIES_POLIWAG", "Poliwag", 60), + ("SPECIES_POLIWHIRL", "Poliwhirl", 61), + ("SPECIES_POLIWRATH", "Poliwrath", 62), + ("SPECIES_ABRA", "Abra", 63), + ("SPECIES_KADABRA", "Kadabra", 64), + ("SPECIES_ALAKAZAM", "Alakazam", 65), + ("SPECIES_MACHOP", "Machop", 66), + ("SPECIES_MACHOKE", "Machoke", 67), + ("SPECIES_MACHAMP", "Machamp", 68), + ("SPECIES_BELLSPROUT", "Bellsprout", 69), + ("SPECIES_WEEPINBELL", "Weepinbell", 70), + ("SPECIES_VICTREEBEL", "Victreebel", 71), + ("SPECIES_TENTACOOL", "Tentacool", 72), + ("SPECIES_TENTACRUEL", "Tentacruel", 73), + ("SPECIES_GEODUDE", "Geodude", 74), + ("SPECIES_GRAVELER", "Graveler", 75), + ("SPECIES_GOLEM", "Golem", 76), + ("SPECIES_PONYTA", "Ponyta", 77), + ("SPECIES_RAPIDASH", "Rapidash", 78), + ("SPECIES_SLOWPOKE", "Slowpoke", 79), + ("SPECIES_SLOWBRO", "Slowbro", 80), + ("SPECIES_MAGNEMITE", "Magnemite", 81), + ("SPECIES_MAGNETON", "Magneton", 82), + ("SPECIES_FARFETCHD", "Farfetch'd", 83), + ("SPECIES_DODUO", "Doduo", 84), + ("SPECIES_DODRIO", "Dodrio", 85), + ("SPECIES_SEEL", "Seel", 86), + ("SPECIES_DEWGONG", "Dewgong", 87), + ("SPECIES_GRIMER", "Grimer", 88), + ("SPECIES_MUK", "Muk", 89), + ("SPECIES_SHELLDER", "Shellder", 90), + ("SPECIES_CLOYSTER", "Cloyster", 91), + ("SPECIES_GASTLY", "Gastly", 92), + ("SPECIES_HAUNTER", "Haunter", 93), + ("SPECIES_GENGAR", "Gengar", 94), + ("SPECIES_ONIX", "Onix", 95), + ("SPECIES_DROWZEE", "Drowzee", 96), + ("SPECIES_HYPNO", "Hypno", 97), + ("SPECIES_KRABBY", "Krabby", 98), + ("SPECIES_KINGLER", "Kingler", 99), + ("SPECIES_VOLTORB", "Voltorb", 100), + ("SPECIES_ELECTRODE", "Electrode", 101), + ("SPECIES_EXEGGCUTE", "Exeggcute", 102), + ("SPECIES_EXEGGUTOR", "Exeggutor", 103), + ("SPECIES_CUBONE", "Cubone", 104), + ("SPECIES_MAROWAK", "Marowak", 105), + ("SPECIES_HITMONLEE", "Hitmonlee", 106), + ("SPECIES_HITMONCHAN", "Hitmonchan", 107), + ("SPECIES_LICKITUNG", "Lickitung", 108), + ("SPECIES_KOFFING", "Koffing", 109), + ("SPECIES_WEEZING", "Weezing", 110), + ("SPECIES_RHYHORN", "Rhyhorn", 111), + ("SPECIES_RHYDON", "Rhydon", 112), + ("SPECIES_CHANSEY", "Chansey", 113), + ("SPECIES_TANGELA", "Tangela", 114), + ("SPECIES_KANGASKHAN", "Kangaskhan", 115), + ("SPECIES_HORSEA", "Horsea", 116), + ("SPECIES_SEADRA", "Seadra", 117), + ("SPECIES_GOLDEEN", "Goldeen", 118), + ("SPECIES_SEAKING", "Seaking", 119), + ("SPECIES_STARYU", "Staryu", 120), + ("SPECIES_STARMIE", "Starmie", 121), + ("SPECIES_MR_MIME", "Mr. Mime", 122), + ("SPECIES_SCYTHER", "Scyther", 123), + ("SPECIES_JYNX", "Jynx", 124), + ("SPECIES_ELECTABUZZ", "Electabuzz", 125), + ("SPECIES_MAGMAR", "Magmar", 126), + ("SPECIES_PINSIR", "Pinsir", 127), + ("SPECIES_TAUROS", "Tauros", 128), + ("SPECIES_MAGIKARP", "Magikarp", 129), + ("SPECIES_GYARADOS", "Gyarados", 130), + ("SPECIES_LAPRAS", "Lapras", 131), + ("SPECIES_DITTO", "Ditto", 132), + ("SPECIES_EEVEE", "Eevee", 133), + ("SPECIES_VAPOREON", "Vaporeon", 134), + ("SPECIES_JOLTEON", "Jolteon", 135), + ("SPECIES_FLAREON", "Flareon", 136), + ("SPECIES_PORYGON", "Porygon", 137), + ("SPECIES_OMANYTE", "Omanyte", 138), + ("SPECIES_OMASTAR", "Omastar", 139), + ("SPECIES_KABUTO", "Kabuto", 140), + ("SPECIES_KABUTOPS", "Kabutops", 141), + ("SPECIES_AERODACTYL", "Aerodactyl", 142), + ("SPECIES_SNORLAX", "Snorlax", 143), + ("SPECIES_ARTICUNO", "Articuno", 144), + ("SPECIES_ZAPDOS", "Zapdos", 145), + ("SPECIES_MOLTRES", "Moltres", 146), + ("SPECIES_DRATINI", "Dratini", 147), + ("SPECIES_DRAGONAIR", "Dragonair", 148), + ("SPECIES_DRAGONITE", "Dragonite", 149), + ("SPECIES_MEWTWO", "Mewtwo", 150), + ("SPECIES_MEW", "Mew", 151), + ("SPECIES_CHIKORITA", "Chikorita", 152), + ("SPECIES_BAYLEEF", "Bayleef", 153), + ("SPECIES_MEGANIUM", "Meganium", 154), + ("SPECIES_CYNDAQUIL", "Cindaquil", 155), + ("SPECIES_QUILAVA", "Quilava", 156), + ("SPECIES_TYPHLOSION", "Typhlosion", 157), + ("SPECIES_TOTODILE", "Totodile", 158), + ("SPECIES_CROCONAW", "Croconaw", 159), + ("SPECIES_FERALIGATR", "Feraligatr", 160), + ("SPECIES_SENTRET", "Sentret", 161), + ("SPECIES_FURRET", "Furret", 162), + ("SPECIES_HOOTHOOT", "Hoothoot", 163), + ("SPECIES_NOCTOWL", "Noctowl", 164), + ("SPECIES_LEDYBA", "Ledyba", 165), + ("SPECIES_LEDIAN", "Ledian", 166), + ("SPECIES_SPINARAK", "Spinarak", 167), + ("SPECIES_ARIADOS", "Ariados", 168), + ("SPECIES_CROBAT", "Crobat", 169), + ("SPECIES_CHINCHOU", "Chinchou", 170), + ("SPECIES_LANTURN", "Lanturn", 171), + ("SPECIES_PICHU", "Pichu", 172), + ("SPECIES_CLEFFA", "Cleffa", 173), + ("SPECIES_IGGLYBUFF", "Igglybuff", 174), + ("SPECIES_TOGEPI", "Togepi", 175), + ("SPECIES_TOGETIC", "Togetic", 176), + ("SPECIES_NATU", "Natu", 177), + ("SPECIES_XATU", "Xatu", 178), + ("SPECIES_MAREEP", "Mareep", 179), + ("SPECIES_FLAAFFY", "Flaaffy", 180), + ("SPECIES_AMPHAROS", "Ampharos", 181), + ("SPECIES_BELLOSSOM", "Bellossom", 182), + ("SPECIES_MARILL", "Marill", 183), + ("SPECIES_AZUMARILL", "Azumarill", 184), + ("SPECIES_SUDOWOODO", "Sudowoodo", 185), + ("SPECIES_POLITOED", "Politoed", 186), + ("SPECIES_HOPPIP", "Hoppip", 187), + ("SPECIES_SKIPLOOM", "Skiploom", 188), + ("SPECIES_JUMPLUFF", "Jumpluff", 189), + ("SPECIES_AIPOM", "Aipom", 190), + ("SPECIES_SUNKERN", "Sunkern", 191), + ("SPECIES_SUNFLORA", "Sunflora", 192), + ("SPECIES_YANMA", "Yanma", 193), + ("SPECIES_WOOPER", "Wooper", 194), + ("SPECIES_QUAGSIRE", "Quagsire", 195), + ("SPECIES_ESPEON", "Espeon", 196), + ("SPECIES_UMBREON", "Umbreon", 197), + ("SPECIES_MURKROW", "Murkrow", 198), + ("SPECIES_SLOWKING", "Slowking", 199), + ("SPECIES_MISDREAVUS", "Misdreavus", 200), + ("SPECIES_UNOWN", "Unown", 201), + ("SPECIES_WOBBUFFET", "Wobbuffet", 202), + ("SPECIES_GIRAFARIG", "Girafarig", 203), + ("SPECIES_PINECO", "Pineco", 204), + ("SPECIES_FORRETRESS", "Forretress", 205), + ("SPECIES_DUNSPARCE", "Dunsparce", 206), + ("SPECIES_GLIGAR", "Gligar", 207), + ("SPECIES_STEELIX", "Steelix", 208), + ("SPECIES_SNUBBULL", "Snubbull", 209), + ("SPECIES_GRANBULL", "Granbull", 210), + ("SPECIES_QWILFISH", "Qwilfish", 211), + ("SPECIES_SCIZOR", "Scizor", 212), + ("SPECIES_SHUCKLE", "Shuckle", 213), + ("SPECIES_HERACROSS", "Heracross", 214), + ("SPECIES_SNEASEL", "Sneasel", 215), + ("SPECIES_TEDDIURSA", "Teddiursa", 216), + ("SPECIES_URSARING", "Ursaring", 217), + ("SPECIES_SLUGMA", "Slugma", 218), + ("SPECIES_MAGCARGO", "Magcargo", 219), + ("SPECIES_SWINUB", "Swinub", 220), + ("SPECIES_PILOSWINE", "Piloswine", 221), + ("SPECIES_CORSOLA", "Corsola", 222), + ("SPECIES_REMORAID", "Remoraid", 223), + ("SPECIES_OCTILLERY", "Octillery", 224), + ("SPECIES_DELIBIRD", "Delibird", 225), + ("SPECIES_MANTINE", "Mantine", 226), + ("SPECIES_SKARMORY", "Skarmory", 227), + ("SPECIES_HOUNDOUR", "Houndour", 228), + ("SPECIES_HOUNDOOM", "Houndoom", 229), + ("SPECIES_KINGDRA", "Kingdra", 230), + ("SPECIES_PHANPY", "Phanpy", 231), + ("SPECIES_DONPHAN", "Donphan", 232), + ("SPECIES_PORYGON2", "Porygon2", 233), + ("SPECIES_STANTLER", "Stantler", 234), + ("SPECIES_SMEARGLE", "Smeargle", 235), + ("SPECIES_TYROGUE", "Tyrogue", 236), + ("SPECIES_HITMONTOP", "Hitmontop", 237), + ("SPECIES_SMOOCHUM", "Smoochum", 238), + ("SPECIES_ELEKID", "Elekid", 239), + ("SPECIES_MAGBY", "Magby", 240), + ("SPECIES_MILTANK", "Miltank", 241), + ("SPECIES_BLISSEY", "Blissey", 242), + ("SPECIES_RAIKOU", "Raikou", 243), + ("SPECIES_ENTEI", "Entei", 244), + ("SPECIES_SUICUNE", "Suicune", 245), + ("SPECIES_LARVITAR", "Larvitar", 246), + ("SPECIES_PUPITAR", "Pupitar", 247), + ("SPECIES_TYRANITAR", "Tyranitar", 248), + ("SPECIES_LUGIA", "Lugia", 249), + ("SPECIES_HO_OH", "Ho-oh", 250), + ("SPECIES_CELEBI", "Celebi", 251), + ("SPECIES_TREECKO", "Treecko", 252), + ("SPECIES_GROVYLE", "Grovyle", 253), + ("SPECIES_SCEPTILE", "Sceptile", 254), + ("SPECIES_TORCHIC", "Torchic", 255), + ("SPECIES_COMBUSKEN", "Combusken", 256), + ("SPECIES_BLAZIKEN", "Blaziken", 257), + ("SPECIES_MUDKIP", "Mudkip", 258), + ("SPECIES_MARSHTOMP", "Marshtomp", 259), + ("SPECIES_SWAMPERT", "Swampert", 260), + ("SPECIES_POOCHYENA", "Poochyena", 261), + ("SPECIES_MIGHTYENA", "Mightyena", 262), + ("SPECIES_ZIGZAGOON", "Zigzagoon", 263), + ("SPECIES_LINOONE", "Linoone", 264), + ("SPECIES_WURMPLE", "Wurmple", 265), + ("SPECIES_SILCOON", "Silcoon", 266), + ("SPECIES_BEAUTIFLY", "Beautifly", 267), + ("SPECIES_CASCOON", "Cascoon", 268), + ("SPECIES_DUSTOX", "Dustox", 269), + ("SPECIES_LOTAD", "Lotad", 270), + ("SPECIES_LOMBRE", "Lombre", 271), + ("SPECIES_LUDICOLO", "Ludicolo", 272), + ("SPECIES_SEEDOT", "Seedot", 273), + ("SPECIES_NUZLEAF", "Nuzleaf", 274), + ("SPECIES_SHIFTRY", "Shiftry", 275), + ("SPECIES_NINCADA", "Nincada", 290), + ("SPECIES_NINJASK", "Ninjask", 291), + ("SPECIES_SHEDINJA", "Shedinja", 292), + ("SPECIES_TAILLOW", "Taillow", 276), + ("SPECIES_SWELLOW", "Swellow", 277), + ("SPECIES_SHROOMISH", "Shroomish", 285), + ("SPECIES_BRELOOM", "Breloom", 286), + ("SPECIES_SPINDA", "Spinda", 327), + ("SPECIES_WINGULL", "Wingull", 278), + ("SPECIES_PELIPPER", "Pelipper", 279), + ("SPECIES_SURSKIT", "Surskit", 283), + ("SPECIES_MASQUERAIN", "Masquerain", 284), + ("SPECIES_WAILMER", "Wailmer", 320), + ("SPECIES_WAILORD", "Wailord", 321), + ("SPECIES_SKITTY", "Skitty", 300), + ("SPECIES_DELCATTY", "Delcatty", 301), + ("SPECIES_KECLEON", "Kecleon", 352), + ("SPECIES_BALTOY", "Baltoy", 343), + ("SPECIES_CLAYDOL", "Claydol", 344), + ("SPECIES_NOSEPASS", "Nosepass", 299), + ("SPECIES_TORKOAL", "Torkoal", 324), + ("SPECIES_SABLEYE", "Sableye", 302), + ("SPECIES_BARBOACH", "Barboach", 339), + ("SPECIES_WHISCASH", "Whiscash", 340), + ("SPECIES_LUVDISC", "Luvdisc", 370), + ("SPECIES_CORPHISH", "Corphish", 341), + ("SPECIES_CRAWDAUNT", "Crawdaunt", 342), + ("SPECIES_FEEBAS", "Feebas", 349), + ("SPECIES_MILOTIC", "Milotic", 350), + ("SPECIES_CARVANHA", "Carvanha", 318), + ("SPECIES_SHARPEDO", "Sharpedo", 319), + ("SPECIES_TRAPINCH", "Trapinch", 328), + ("SPECIES_VIBRAVA", "Vibrava", 329), + ("SPECIES_FLYGON", "Flygon", 330), + ("SPECIES_MAKUHITA", "Makuhita", 296), + ("SPECIES_HARIYAMA", "Hariyama", 297), + ("SPECIES_ELECTRIKE", "Electrike", 309), + ("SPECIES_MANECTRIC", "Manectric", 310), + ("SPECIES_NUMEL", "Numel", 322), + ("SPECIES_CAMERUPT", "Camerupt", 323), + ("SPECIES_SPHEAL", "Spheal", 363), + ("SPECIES_SEALEO", "Sealeo", 364), + ("SPECIES_WALREIN", "Walrein", 365), + ("SPECIES_CACNEA", "Cacnea", 331), + ("SPECIES_CACTURNE", "Cacturne", 332), + ("SPECIES_SNORUNT", "Snorunt", 361), + ("SPECIES_GLALIE", "Glalie", 362), + ("SPECIES_LUNATONE", "Lunatone", 337), + ("SPECIES_SOLROCK", "Solrock", 338), + ("SPECIES_AZURILL", "Azurill", 298), + ("SPECIES_SPOINK", "Spoink", 325), + ("SPECIES_GRUMPIG", "Grumpig", 326), + ("SPECIES_PLUSLE", "Plusle", 311), + ("SPECIES_MINUN", "Minun", 312), + ("SPECIES_MAWILE", "Mawile", 303), + ("SPECIES_MEDITITE", "Meditite", 307), + ("SPECIES_MEDICHAM", "Medicham", 308), + ("SPECIES_SWABLU", "Swablu", 333), + ("SPECIES_ALTARIA", "Altaria", 334), + ("SPECIES_WYNAUT", "Wynaut", 360), + ("SPECIES_DUSKULL", "Duskull", 355), + ("SPECIES_DUSCLOPS", "Dusclops", 356), + ("SPECIES_ROSELIA", "Roselia", 315), + ("SPECIES_SLAKOTH", "Slakoth", 287), + ("SPECIES_VIGOROTH", "Vigoroth", 288), + ("SPECIES_SLAKING", "Slaking", 289), + ("SPECIES_GULPIN", "Gulpin", 316), + ("SPECIES_SWALOT", "Swalot", 317), + ("SPECIES_TROPIUS", "Tropius", 357), + ("SPECIES_WHISMUR", "Whismur", 293), + ("SPECIES_LOUDRED", "Loudred", 294), + ("SPECIES_EXPLOUD", "Exploud", 295), + ("SPECIES_CLAMPERL", "Clamperl", 366), + ("SPECIES_HUNTAIL", "Huntail", 367), + ("SPECIES_GOREBYSS", "Gorebyss", 368), + ("SPECIES_ABSOL", "Absol", 359), + ("SPECIES_SHUPPET", "Shuppet", 353), + ("SPECIES_BANETTE", "Banette", 354), + ("SPECIES_SEVIPER", "Seviper", 336), + ("SPECIES_ZANGOOSE", "Zangoose", 335), + ("SPECIES_RELICANTH", "Relicanth", 369), + ("SPECIES_ARON", "Aron", 304), + ("SPECIES_LAIRON", "Lairon", 305), + ("SPECIES_AGGRON", "Aggron", 306), + ("SPECIES_CASTFORM", "Castform", 351), + ("SPECIES_VOLBEAT", "Volbeat", 313), + ("SPECIES_ILLUMISE", "Illumise", 314), + ("SPECIES_LILEEP", "Lileep", 345), + ("SPECIES_CRADILY", "Cradily", 346), + ("SPECIES_ANORITH", "Anorith", 347), + ("SPECIES_ARMALDO", "Armaldo", 348), + ("SPECIES_RALTS", "Ralts", 280), + ("SPECIES_KIRLIA", "Kirlia", 281), + ("SPECIES_GARDEVOIR", "Gardevoir", 282), + ("SPECIES_BAGON", "Bagon", 371), + ("SPECIES_SHELGON", "Shelgon", 372), + ("SPECIES_SALAMENCE", "Salamence", 373), + ("SPECIES_BELDUM", "Beldum", 374), + ("SPECIES_METANG", "Metang", 375), + ("SPECIES_METAGROSS", "Metagross", 376), + ("SPECIES_REGIROCK", "Regirock", 377), + ("SPECIES_REGICE", "Regice", 378), + ("SPECIES_REGISTEEL", "Registeel", 379), + ("SPECIES_KYOGRE", "Kyogre", 382), + ("SPECIES_GROUDON", "Groudon", 383), + ("SPECIES_RAYQUAZA", "Rayquaza", 384), + ("SPECIES_LATIAS", "Latias", 380), + ("SPECIES_LATIOS", "Latios", 381), + ("SPECIES_JIRACHI", "Jirachi", 385), + ("SPECIES_DEOXYS", "Deoxys", 386), + ("SPECIES_CHIMECHO", "Chimecho", 358), ] - species_list: List[SpeciesData] = [] max_species_id = 0 - for species_name, species_label in all_species: + for species_name, species_label, species_dex_number in all_species: species_id = data.constants[species_name] max_species_id = max(species_id, max_species_id) species_data = extracted_data["species"][species_id] learnset = [LearnsetMove(item["level"], item["move_id"]) for item in species_data["learnset"]["moves"]] - species_list.append(SpeciesData( + data.species[species_id] = SpeciesData( species_name, species_label, species_id, + species_dex_number, BaseStats( species_data["base_stats"][0], species_data["base_stats"][1], @@ -827,27 +910,52 @@ def _init() -> None: ) for evolution_json in species_data["evolutions"]], None, species_data["catch_rate"], + species_data["friendship"], learnset, int(species_data["tmhm_learnset"], 16), - species_data["learnset"]["rom_address"], - species_data["rom_address"] + species_data["learnset"]["address"], + species_data["address"] + ) + + for species in data.species.values(): + for evolution in species.evolutions: + data.species[evolution.species_id].pre_evolution = species.species_id + + # Replace default item for dex entry locations based on evo stage of species + evo_stage_to_ball_map = { + 0: data.constants["ITEM_POKE_BALL"], + 1: data.constants["ITEM_GREAT_BALL"], + 2: data.constants["ITEM_ULTRA_BALL"], + } + for species in data.species.values(): + evo_stage = 0 + pre_evolution = species.pre_evolution + while pre_evolution is not None: + evo_stage += 1 + pre_evolution = data.species[pre_evolution].pre_evolution + + dex_location_name = f"POKEDEX_REWARD_{str(species.national_dex_number).zfill(3)}" + data.locations[dex_location_name] = LocationData( + data.locations[dex_location_name].name, + data.locations[dex_location_name].label, + data.locations[dex_location_name].parent_region, + evo_stage_to_ball_map[evo_stage], + data.locations[dex_location_name].address, + data.locations[dex_location_name].flag, + data.locations[dex_location_name].tags + ) + + # Create legendary encounter data + for legendary_encounter_json in extracted_data["legendary_encounters"]: + data.legendary_encounters.append(MiscPokemonData( + legendary_encounter_json["species"], + legendary_encounter_json["address"] )) - data.species = [None for i in range(max_species_id + 1)] - - for species_data in species_list: - data.species[species_data.species_id] = species_data - - for species in data.species: - if species is not None: - for evolution in species.evolutions: - data.species[evolution.species_id].pre_evolution = species.species_id - - # Create static encounter data - for static_encounter_json in extracted_data["static_encounters"]: - data.static_encounters.append(StaticEncounterData( - static_encounter_json["species"], - static_encounter_json["rom_address"] + for misc_pokemon_json in extracted_data["misc_pokemon"]: + data.misc_pokemon.append(MiscPokemonData( + misc_pokemon_json["species"], + misc_pokemon_json["address"] )) # TM moves @@ -868,7 +976,7 @@ def _init() -> None: ("ABILITY_WATER_ABSORB", "Water Absorb"), ("ABILITY_OBLIVIOUS", "Oblivious"), ("ABILITY_CLOUD_NINE", "Cloud Nine"), - ("ABILITY_COMPOUND_EYES", "Compound Eyes"), + ("ABILITY_COMPOUND_EYES", "Compoundeyes"), ("ABILITY_INSOMNIA", "Insomnia"), ("ABILITY_COLOR_CHANGE", "Color Change"), ("ABILITY_IMMUNITY", "Immunity"), @@ -885,7 +993,7 @@ def _init() -> None: ("ABILITY_SYNCHRONIZE", "Synchronize"), ("ABILITY_CLEAR_BODY", "Clear Body"), ("ABILITY_NATURAL_CURE", "Natural Cure"), - ("ABILITY_LIGHTNING_ROD", "Lightning Rod"), + ("ABILITY_LIGHTNING_ROD", "Lightningrod"), ("ABILITY_SERENE_GRACE", "Serene Grace"), ("ABILITY_SWIFT_SWIM", "Swift Swim"), ("ABILITY_CHLOROPHYLL", "Chlorophyll"), @@ -934,36 +1042,362 @@ def _init() -> None: ("ABILITY_AIR_LOCK", "Air Lock") ]] - # Create map data - for map_name, map_json in extracted_data["maps"].items(): - land_encounters = None - water_encounters = None - fishing_encounters = None - - if map_json["land_encounters"] is not None: - land_encounters = EncounterTableData( - map_json["land_encounters"]["encounter_slots"], - map_json["land_encounters"]["rom_address"] - ) - if map_json["water_encounters"] is not None: - water_encounters = EncounterTableData( - map_json["water_encounters"]["encounter_slots"], - map_json["water_encounters"]["rom_address"] - ) - if map_json["fishing_encounters"] is not None: - fishing_encounters = EncounterTableData( - map_json["fishing_encounters"]["encounter_slots"], - map_json["fishing_encounters"]["rom_address"] - ) - - data.maps.append(MapData( - map_name, - land_encounters, - water_encounters, - fishing_encounters - )) - - data.maps.sort(key=lambda map: map.name) + # Move labels + data.move_labels = {r: data.constants[l] for l, r in [ + ("MOVE_POUND", "Pound"), + ("MOVE_KARATE_CHOP", "Karate Chop"), + ("MOVE_DOUBLE_SLAP", "Doubleslap"), + ("MOVE_COMET_PUNCH", "Comet Punch"), + ("MOVE_MEGA_PUNCH", "Mega Punch"), + ("MOVE_PAY_DAY", "Pay Day"), + ("MOVE_FIRE_PUNCH", "Fire Punch"), + ("MOVE_ICE_PUNCH", "Ice Punch"), + ("MOVE_THUNDER_PUNCH", "Thunderpunch"), + ("MOVE_SCRATCH", "Scratch"), + ("MOVE_VICE_GRIP", "Vicegrip"), + ("MOVE_GUILLOTINE", "Guillotine"), + ("MOVE_RAZOR_WIND", "Razor Wind"), + ("MOVE_SWORDS_DANCE", "Swords Dance"), + ("MOVE_CUT", "Cut"), + ("MOVE_GUST", "Gust"), + ("MOVE_WING_ATTACK", "Wing Attack"), + ("MOVE_WHIRLWIND", "Whirlwind"), + ("MOVE_FLY", "Fly"), + ("MOVE_BIND", "Bind"), + ("MOVE_SLAM", "Slam"), + ("MOVE_VINE_WHIP", "Vine Whip"), + ("MOVE_STOMP", "Stomp"), + ("MOVE_DOUBLE_KICK", "Double Kick"), + ("MOVE_MEGA_KICK", "Mega Kick"), + ("MOVE_JUMP_KICK", "Jump Kick"), + ("MOVE_ROLLING_KICK", "Rolling Kick"), + ("MOVE_SAND_ATTACK", "Sand-Attack"), + ("MOVE_HEADBUTT", "Headbutt"), + ("MOVE_HORN_ATTACK", "Horn Attack"), + ("MOVE_FURY_ATTACK", "Fury Attack"), + ("MOVE_HORN_DRILL", "Horn Drill"), + ("MOVE_TACKLE", "Tackle"), + ("MOVE_BODY_SLAM", "Body Slam"), + ("MOVE_WRAP", "Wrap"), + ("MOVE_TAKE_DOWN", "Take Down"), + ("MOVE_THRASH", "Thrash"), + ("MOVE_DOUBLE_EDGE", "Double-Edge"), + ("MOVE_TAIL_WHIP", "Tail Whip"), + ("MOVE_POISON_STING", "Poison Sting"), + ("MOVE_TWINEEDLE", "Twineedle"), + ("MOVE_PIN_MISSILE", "Pin Missile"), + ("MOVE_LEER", "Leer"), + ("MOVE_BITE", "Bite"), + ("MOVE_GROWL", "Growl"), + ("MOVE_ROAR", "Roar"), + ("MOVE_SING", "Sing"), + ("MOVE_SUPERSONIC", "Supersonic"), + ("MOVE_SONIC_BOOM", "Sonicboom"), + ("MOVE_DISABLE", "Disable"), + ("MOVE_ACID", "Acid"), + ("MOVE_EMBER", "Ember"), + ("MOVE_FLAMETHROWER", "Flamethrower"), + ("MOVE_MIST", "Mist"), + ("MOVE_WATER_GUN", "Water Gun"), + ("MOVE_HYDRO_PUMP", "Hydro Pump"), + ("MOVE_SURF", "Surf"), + ("MOVE_ICE_BEAM", "Ice Beam"), + ("MOVE_BLIZZARD", "Blizzard"), + ("MOVE_PSYBEAM", "Psybeam"), + ("MOVE_BUBBLE_BEAM", "Bubblebeam"), + ("MOVE_AURORA_BEAM", "Aurora Beam"), + ("MOVE_HYPER_BEAM", "Hyper Beam"), + ("MOVE_PECK", "Peck"), + ("MOVE_DRILL_PECK", "Drill Peck"), + ("MOVE_SUBMISSION", "Submission"), + ("MOVE_LOW_KICK", "Low Kick"), + ("MOVE_COUNTER", "Counter"), + ("MOVE_SEISMIC_TOSS", "Seismic Toss"), + ("MOVE_STRENGTH", "Strength"), + ("MOVE_ABSORB", "Absorb"), + ("MOVE_MEGA_DRAIN", "Mega Drain"), + ("MOVE_LEECH_SEED", "Leech Seed"), + ("MOVE_GROWTH", "Growth"), + ("MOVE_RAZOR_LEAF", "Razor Leaf"), + ("MOVE_SOLAR_BEAM", "Solarbeam"), + ("MOVE_POISON_POWDER", "Poisonpowder"), + ("MOVE_STUN_SPORE", "Stun Spore"), + ("MOVE_SLEEP_POWDER", "Sleep Powder"), + ("MOVE_PETAL_DANCE", "Petal Dance"), + ("MOVE_STRING_SHOT", "String Shot"), + ("MOVE_DRAGON_RAGE", "Dragon Rage"), + ("MOVE_FIRE_SPIN", "Fire Spin"), + ("MOVE_THUNDER_SHOCK", "Thundershock"), + ("MOVE_THUNDERBOLT", "Thunderbolt"), + ("MOVE_THUNDER_WAVE", "Thunder Wave"), + ("MOVE_THUNDER", "Thunder"), + ("MOVE_ROCK_THROW", "Rock Throw"), + ("MOVE_EARTHQUAKE", "Earthquake"), + ("MOVE_FISSURE", "Fissure"), + ("MOVE_DIG", "Dig"), + ("MOVE_TOXIC", "Toxic"), + ("MOVE_CONFUSION", "Confusion"), + ("MOVE_PSYCHIC", "Psychic"), + ("MOVE_HYPNOSIS", "Hypnosis"), + ("MOVE_MEDITATE", "Meditate"), + ("MOVE_AGILITY", "Agility"), + ("MOVE_QUICK_ATTACK", "Quick Attack"), + ("MOVE_RAGE", "Rage"), + ("MOVE_TELEPORT", "Teleport"), + ("MOVE_NIGHT_SHADE", "Night Shade"), + ("MOVE_MIMIC", "Mimic"), + ("MOVE_SCREECH", "Screech"), + ("MOVE_DOUBLE_TEAM", "Double Team"), + ("MOVE_RECOVER", "Recover"), + ("MOVE_HARDEN", "Harden"), + ("MOVE_MINIMIZE", "Minimize"), + ("MOVE_SMOKESCREEN", "Smokescreen"), + ("MOVE_CONFUSE_RAY", "Confuse Ray"), + ("MOVE_WITHDRAW", "Withdraw"), + ("MOVE_DEFENSE_CURL", "Defense Curl"), + ("MOVE_BARRIER", "Barrier"), + ("MOVE_LIGHT_SCREEN", "Light Screen"), + ("MOVE_HAZE", "Haze"), + ("MOVE_REFLECT", "Reflect"), + ("MOVE_FOCUS_ENERGY", "Focus Energy"), + ("MOVE_BIDE", "Bide"), + ("MOVE_METRONOME", "Metronome"), + ("MOVE_MIRROR_MOVE", "Mirror Move"), + ("MOVE_SELF_DESTRUCT", "Selfdestruct"), + ("MOVE_EGG_BOMB", "Egg Bomb"), + ("MOVE_LICK", "Lick"), + ("MOVE_SMOG", "Smog"), + ("MOVE_SLUDGE", "Sludge"), + ("MOVE_BONE_CLUB", "Bone Club"), + ("MOVE_FIRE_BLAST", "Fire Blast"), + ("MOVE_WATERFALL", "Waterfall"), + ("MOVE_CLAMP", "Clamp"), + ("MOVE_SWIFT", "Swift"), + ("MOVE_SKULL_BASH", "Skull Bash"), + ("MOVE_SPIKE_CANNON", "Spike Cannon"), + ("MOVE_CONSTRICT", "Constrict"), + ("MOVE_AMNESIA", "Amnesia"), + ("MOVE_KINESIS", "Kinesis"), + ("MOVE_SOFT_BOILED", "Softboiled"), + ("MOVE_HI_JUMP_KICK", "Hi Jump Kick"), + ("MOVE_GLARE", "Glare"), + ("MOVE_DREAM_EATER", "Dream Eater"), + ("MOVE_POISON_GAS", "Poison Gas"), + ("MOVE_BARRAGE", "Barrage"), + ("MOVE_LEECH_LIFE", "Leech Life"), + ("MOVE_LOVELY_KISS", "Lovely Kiss"), + ("MOVE_SKY_ATTACK", "Sky Attack"), + ("MOVE_TRANSFORM", "Transform"), + ("MOVE_BUBBLE", "Bubble"), + ("MOVE_DIZZY_PUNCH", "Dizzy Punch"), + ("MOVE_SPORE", "Spore"), + ("MOVE_FLASH", "Flash"), + ("MOVE_PSYWAVE", "Psywave"), + ("MOVE_SPLASH", "Splash"), + ("MOVE_ACID_ARMOR", "Acid Armor"), + ("MOVE_CRABHAMMER", "Crabhammer"), + ("MOVE_EXPLOSION", "Explosion"), + ("MOVE_FURY_SWIPES", "Fury Swipes"), + ("MOVE_BONEMERANG", "Bonemerang"), + ("MOVE_REST", "Rest"), + ("MOVE_ROCK_SLIDE", "Rock Slide"), + ("MOVE_HYPER_FANG", "Hyper Fang"), + ("MOVE_SHARPEN", "Sharpen"), + ("MOVE_CONVERSION", "Conversion"), + ("MOVE_TRI_ATTACK", "Tri Attack"), + ("MOVE_SUPER_FANG", "Super Fang"), + ("MOVE_SLASH", "Slash"), + ("MOVE_SUBSTITUTE", "Substitute"), + ("MOVE_SKETCH", "Sketch"), + ("MOVE_TRIPLE_KICK", "Triple Kick"), + ("MOVE_THIEF", "Thief"), + ("MOVE_SPIDER_WEB", "Spider Web"), + ("MOVE_MIND_READER", "Mind Reader"), + ("MOVE_NIGHTMARE", "Nightmare"), + ("MOVE_FLAME_WHEEL", "Flame Wheel"), + ("MOVE_SNORE", "Snore"), + ("MOVE_CURSE", "Curse"), + ("MOVE_FLAIL", "Flail"), + ("MOVE_CONVERSION_2", "Conversion 2"), + ("MOVE_AEROBLAST", "Aeroblast"), + ("MOVE_COTTON_SPORE", "Cotton Spore"), + ("MOVE_REVERSAL", "Reversal"), + ("MOVE_SPITE", "Spite"), + ("MOVE_POWDER_SNOW", "Powder Snow"), + ("MOVE_PROTECT", "Protect"), + ("MOVE_MACH_PUNCH", "Mach Punch"), + ("MOVE_SCARY_FACE", "Scary Face"), + ("MOVE_FAINT_ATTACK", "Faint Attack"), + ("MOVE_SWEET_KISS", "Sweet Kiss"), + ("MOVE_BELLY_DRUM", "Belly Drum"), + ("MOVE_SLUDGE_BOMB", "Sludge Bomb"), + ("MOVE_MUD_SLAP", "Mud-Slap"), + ("MOVE_OCTAZOOKA", "Octazooka"), + ("MOVE_SPIKES", "Spikes"), + ("MOVE_ZAP_CANNON", "Zap Cannon"), + ("MOVE_FORESIGHT", "Foresight"), + ("MOVE_DESTINY_BOND", "Destiny Bond"), + ("MOVE_PERISH_SONG", "Perish Song"), + ("MOVE_ICY_WIND", "Icy Wind"), + ("MOVE_DETECT", "Detect"), + ("MOVE_BONE_RUSH", "Bone Rush"), + ("MOVE_LOCK_ON", "Lock-On"), + ("MOVE_OUTRAGE", "Outrage"), + ("MOVE_SANDSTORM", "Sandstorm"), + ("MOVE_GIGA_DRAIN", "Giga Drain"), + ("MOVE_ENDURE", "Endure"), + ("MOVE_CHARM", "Charm"), + ("MOVE_ROLLOUT", "Rollout"), + ("MOVE_FALSE_SWIPE", "False Swipe"), + ("MOVE_SWAGGER", "Swagger"), + ("MOVE_MILK_DRINK", "Milk Drink"), + ("MOVE_SPARK", "Spark"), + ("MOVE_FURY_CUTTER", "Fury Cutter"), + ("MOVE_STEEL_WING", "Steel Wing"), + ("MOVE_MEAN_LOOK", "Mean Look"), + ("MOVE_ATTRACT", "Attract"), + ("MOVE_SLEEP_TALK", "Sleep Talk"), + ("MOVE_HEAL_BELL", "Heal Bell"), + ("MOVE_RETURN", "Return"), + ("MOVE_PRESENT", "Present"), + ("MOVE_FRUSTRATION", "Frustration"), + ("MOVE_SAFEGUARD", "Safeguard"), + ("MOVE_PAIN_SPLIT", "Pain Split"), + ("MOVE_SACRED_FIRE", "Sacred Fire"), + ("MOVE_MAGNITUDE", "Magnitude"), + ("MOVE_DYNAMIC_PUNCH", "Dynamicpunch"), + ("MOVE_MEGAHORN", "Megahorn"), + ("MOVE_DRAGON_BREATH", "Dragonbreath"), + ("MOVE_BATON_PASS", "Baton Pass"), + ("MOVE_ENCORE", "Encore"), + ("MOVE_PURSUIT", "Pursuit"), + ("MOVE_RAPID_SPIN", "Rapid Spin"), + ("MOVE_SWEET_SCENT", "Sweet Scent"), + ("MOVE_IRON_TAIL", "Iron Tail"), + ("MOVE_METAL_CLAW", "Metal Claw"), + ("MOVE_VITAL_THROW", "Vital Throw"), + ("MOVE_MORNING_SUN", "Morning Sun"), + ("MOVE_SYNTHESIS", "Synthesis"), + ("MOVE_MOONLIGHT", "Moonlight"), + ("MOVE_HIDDEN_POWER", "Hidden Power"), + ("MOVE_CROSS_CHOP", "Cross Chop"), + ("MOVE_TWISTER", "Twister"), + ("MOVE_RAIN_DANCE", "Rain Dance"), + ("MOVE_SUNNY_DAY", "Sunny Day"), + ("MOVE_CRUNCH", "Crunch"), + ("MOVE_MIRROR_COAT", "Mirror Coat"), + ("MOVE_PSYCH_UP", "Psych Up"), + ("MOVE_EXTREME_SPEED", "Extremespeed"), + ("MOVE_ANCIENT_POWER", "Ancientpower"), + ("MOVE_SHADOW_BALL", "Shadow Ball"), + ("MOVE_FUTURE_SIGHT", "Future Sight"), + ("MOVE_ROCK_SMASH", "Rock Smash"), + ("MOVE_WHIRLPOOL", "Whirlpool"), + ("MOVE_BEAT_UP", "Beat Up"), + ("MOVE_FAKE_OUT", "Fake Out"), + ("MOVE_UPROAR", "Uproar"), + ("MOVE_STOCKPILE", "Stockpile"), + ("MOVE_SPIT_UP", "Spit Up"), + ("MOVE_SWALLOW", "Swallow"), + ("MOVE_HEAT_WAVE", "Heat Wave"), + ("MOVE_HAIL", "Hail"), + ("MOVE_TORMENT", "Torment"), + ("MOVE_FLATTER", "Flatter"), + ("MOVE_WILL_O_WISP", "Will-O-Wisp"), + ("MOVE_MEMENTO", "Memento"), + ("MOVE_FACADE", "Facade"), + ("MOVE_FOCUS_PUNCH", "Focus Punch"), + ("MOVE_SMELLING_SALT", "Smellingsalt"), + ("MOVE_FOLLOW_ME", "Follow Me"), + ("MOVE_NATURE_POWER", "Nature Power"), + ("MOVE_CHARGE", "Charge"), + ("MOVE_TAUNT", "Taunt"), + ("MOVE_HELPING_HAND", "Helping Hand"), + ("MOVE_TRICK", "Trick"), + ("MOVE_ROLE_PLAY", "Role Play"), + ("MOVE_WISH", "Wish"), + ("MOVE_ASSIST", "Assist"), + ("MOVE_INGRAIN", "Ingrain"), + ("MOVE_SUPERPOWER", "Superpower"), + ("MOVE_MAGIC_COAT", "Magic Coat"), + ("MOVE_RECYCLE", "Recycle"), + ("MOVE_REVENGE", "Revenge"), + ("MOVE_BRICK_BREAK", "Brick Break"), + ("MOVE_YAWN", "Yawn"), + ("MOVE_KNOCK_OFF", "Knock Off"), + ("MOVE_ENDEAVOR", "Endeavor"), + ("MOVE_ERUPTION", "Eruption"), + ("MOVE_SKILL_SWAP", "Skill Swap"), + ("MOVE_IMPRISON", "Imprison"), + ("MOVE_REFRESH", "Refresh"), + ("MOVE_GRUDGE", "Grudge"), + ("MOVE_SNATCH", "Snatch"), + ("MOVE_SECRET_POWER", "Secret Power"), + ("MOVE_DIVE", "Dive"), + ("MOVE_ARM_THRUST", "Arm Thrust"), + ("MOVE_CAMOUFLAGE", "Camouflage"), + ("MOVE_TAIL_GLOW", "Tail Glow"), + ("MOVE_LUSTER_PURGE", "Luster Purge"), + ("MOVE_MIST_BALL", "Mist Ball"), + ("MOVE_FEATHER_DANCE", "Featherdance"), + ("MOVE_TEETER_DANCE", "Teeter Dance"), + ("MOVE_BLAZE_KICK", "Blaze Kick"), + ("MOVE_MUD_SPORT", "Mud Sport"), + ("MOVE_ICE_BALL", "Ice Ball"), + ("MOVE_NEEDLE_ARM", "Needle Arm"), + ("MOVE_SLACK_OFF", "Slack Off"), + ("MOVE_HYPER_VOICE", "Hyper Voice"), + ("MOVE_POISON_FANG", "Poison Fang"), + ("MOVE_CRUSH_CLAW", "Crush Claw"), + ("MOVE_BLAST_BURN", "Blast Burn"), + ("MOVE_HYDRO_CANNON", "Hydro Cannon"), + ("MOVE_METEOR_MASH", "Meteor Mash"), + ("MOVE_ASTONISH", "Astonish"), + ("MOVE_WEATHER_BALL", "Weather Ball"), + ("MOVE_AROMATHERAPY", "Aromatherapy"), + ("MOVE_FAKE_TEARS", "Fake Tears"), + ("MOVE_AIR_CUTTER", "Air Cutter"), + ("MOVE_OVERHEAT", "Overheat"), + ("MOVE_ODOR_SLEUTH", "Odor Sleuth"), + ("MOVE_ROCK_TOMB", "Rock Tomb"), + ("MOVE_SILVER_WIND", "Silver Wind"), + ("MOVE_METAL_SOUND", "Metal Sound"), + ("MOVE_GRASS_WHISTLE", "Grasswhistle"), + ("MOVE_TICKLE", "Tickle"), + ("MOVE_COSMIC_POWER", "Cosmic Power"), + ("MOVE_WATER_SPOUT", "Water Spout"), + ("MOVE_SIGNAL_BEAM", "Signal Beam"), + ("MOVE_SHADOW_PUNCH", "Shadow Punch"), + ("MOVE_EXTRASENSORY", "Extrasensory"), + ("MOVE_SKY_UPPERCUT", "Sky Uppercut"), + ("MOVE_SAND_TOMB", "Sand Tomb"), + ("MOVE_SHEER_COLD", "Sheer Cold"), + ("MOVE_MUDDY_WATER", "Muddy Water"), + ("MOVE_BULLET_SEED", "Bullet Seed"), + ("MOVE_AERIAL_ACE", "Aerial Ace"), + ("MOVE_ICICLE_SPEAR", "Icicle Spear"), + ("MOVE_IRON_DEFENSE", "Iron Defense"), + ("MOVE_BLOCK", "Block"), + ("MOVE_HOWL", "Howl"), + ("MOVE_DRAGON_CLAW", "Dragon Claw"), + ("MOVE_FRENZY_PLANT", "Frenzy Plant"), + ("MOVE_BULK_UP", "Bulk Up"), + ("MOVE_BOUNCE", "Bounce"), + ("MOVE_MUD_SHOT", "Mud Shot"), + ("MOVE_POISON_TAIL", "Poison Tail"), + ("MOVE_COVET", "Covet"), + ("MOVE_VOLT_TACKLE", "Volt Tackle"), + ("MOVE_MAGICAL_LEAF", "Magical Leaf"), + ("MOVE_WATER_SPORT", "Water Sport"), + ("MOVE_CALM_MIND", "Calm Mind"), + ("MOVE_LEAF_BLADE", "Leaf Blade"), + ("MOVE_DRAGON_DANCE", "Dragon Dance"), + ("MOVE_ROCK_BLAST", "Rock Blast"), + ("MOVE_SHOCK_WAVE", "Shock Wave"), + ("MOVE_WATER_PULSE", "Water Pulse"), + ("MOVE_DOOM_DESIRE", "Doom Desire"), + ("MOVE_PSYCHO_BOOST", "Psycho Boost"), + ]} # Create warp map for warp, destination in extracted_data["warps"].items(): @@ -975,21 +1409,56 @@ def _init() -> None: # Create trainer data for i, trainer_json in enumerate(extracted_data["trainers"]): party_json = trainer_json["party"] - pokemon_data_type = _str_to_pokemon_data_type(trainer_json["pokemon_data_type"]) + pokemon_data_type = _str_to_pokemon_data_type(trainer_json["data_type"]) data.trainers.append(TrainerData( i, TrainerPartyData( [TrainerPokemonData( p["species"], p["level"], - (p["moves"][0], p["moves"][1], p["moves"][2], p["moves"][3]) + (p["moves"][0], p["moves"][1], p["moves"][2], p["moves"][3]) if "moves" in p else None ) for p in party_json], pokemon_data_type, - trainer_json["party_rom_address"] + trainer_json["party_address"] ), - trainer_json["rom_address"], - trainer_json["battle_script_rom_address"] + trainer_json["address"], + trainer_json["script_address"] )) +data = PokemonEmeraldData() _init() + +LEGENDARY_POKEMON = frozenset([data.constants[species] for species in [ + "SPECIES_ARTICUNO", + "SPECIES_ZAPDOS", + "SPECIES_MOLTRES", + "SPECIES_MEWTWO", + "SPECIES_MEW", + "SPECIES_RAIKOU", + "SPECIES_ENTEI", + "SPECIES_SUICUNE", + "SPECIES_LUGIA", + "SPECIES_HO_OH", + "SPECIES_CELEBI", + "SPECIES_REGIROCK", + "SPECIES_REGICE", + "SPECIES_REGISTEEL", + "SPECIES_LATIAS", + "SPECIES_LATIOS", + "SPECIES_KYOGRE", + "SPECIES_GROUDON", + "SPECIES_RAYQUAZA", + "SPECIES_JIRACHI", + "SPECIES_DEOXYS", +]]) +"""Species IDs of legendary pokemon""" + +UNEVOLVED_POKEMON = frozenset({ + species.species_id + for species in data.species.values() + if len(species.evolutions) > 0 +}) +"""Species IDs of pokemon which have further evolution stages in the vanilla game""" + +NATIONAL_ID_TO_SPECIES_ID = {species.national_dex_number: i for i, species in data.species.items()} diff --git a/worlds/pokemon_emerald/data/base_patch.bsdiff4 b/worlds/pokemon_emerald/data/base_patch.bsdiff4 index c1843904a9caa81d52b766eb7056bf6e1bbc6546..0da226f617f6fd7d235c98c85b47767240514ccf 100644 GIT binary patch literal 243175 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%0DUCS6yUV%C+ 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

9ejY2o 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 Eg6#q-ApigX diff --git a/WebHostLib/static/static/icons/sc2/biomechanicaldrone.png b/WebHostLib/static/static/icons/sc2/biomechanicaldrone.png deleted file mode 100644 index e7ebf4031619f66461de686511e9212989c34ff6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6999 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 diff --git a/WebHostLib/static/static/icons/sc2/burstcapacitors.png b/WebHostLib/static/static/icons/sc2/burstcapacitors.png deleted file mode 100644 index 3af9b20a1698065fa07a7729b74cfed01b094a27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2579 zcmV+u3hecXP)EX>4Tx04R}tkv&MmKpe$iTct%Rg6&YmAwzYtAS&XhRVYG*P%E_RU~=gfG-*gu zTpR`0f`cE6RRF*B8G^>Onpui)9@T$_we!cF2S?B&;2?2m4e9tpFljzbi*RvAfDc| zbk6(4QC5}|;&b9LgDyz?$aUG}H_j!8{X8>jWHa-`QDULg#c~(3vY`@B5yur(qkMnP zWrgz=XSG^q?R)YUh6~!tGS_JiBZWmQL4*JqbyQG=g#@h{DJC+spY-q#Iew8`GPx>X zbWe2dEI& zG`&F}uE$B-+{bb3u|2bSm~YD~TYKzrZO`~}mbA6c*=L{2{?1x!e{1axx#W^dF1h5A zOD?(Ol1nbzMlzQ^ow>Asqn}&Y!I-_V84oS)V8EUO<};TjwsAbExXh)c@uaelYuNpA z5;y@Iid=gtb7^VUYgp#eBf!^zUmDO(0>20W1Ezt>uF$Z|rAITD?vGp=1imfJ@_;=C zybx&GLp$E-XD;1mPQM47i*$PkI3Q#$_+Fs!oUXUUgghV@yM9^fO+-pQ~S?>)ADbOCzVe$_O0mjM}X6c0bds&i76#uQD7X` z7;NzXU6zI|s+?I5o!IKLv)-_F*2dQQ*tLwIyjz%%t81 z27r%&Swlf;kx5`j0X8Z}KI&;eU==;)`>kVt5BOBjzosDjh``+5CYiZ(Aaki5$ivbe zUmw$;YdT*5&P!Vs1V%|3l>k>%_Ud`RmF7)sgobSeA9ey5i?lz}qW>M>T^&yXe*^vs z{H{e`Uq0GNK5Q&=>8}J{=9N$NylT(*D?n8s8~|>q#N;pPhXA`nJfXO?H7s-KGnq@j z64A0T;D>5^Hn?qGniMAeZnt>NO%*HWx2uLdExz@ZzF`>nzQ8D&z}e4o7i4}_k=O#w zjOi_DUQeI|PJI*kvPI3VcLmz)b~MS6;Cx7>*1{K6WP+A!gkh!akF`8L0=PrmLQ6D!u* z_~yQD0`|=opnX?>oekRm0gRjTHw9M3IOSH~H6Kacc)8HRm(EFp_nJ7gtFhz@#P=$A z(>p64X7low1lsStSsR~KFu?T$K>ahA8SP5Zq>9y`KGBnZ>->hyw%Sd=9sxeDXFUr1 zKxT48VD^idUo<|XsL9|%<8x-rwW2^uG+R#V_6@|X14Y9L6PNy`l-bri0=wuXN=FXdmz00!DI+Z5$0WxX6=(^m8CuNG&hr6k_KIp zS?$+#uRXVnhRrFWuruPnR1QkRj&>8UUYZM}CCx--WgwP}U%4))Uyk_AX$@pjob8&l z>asNSrYOU0nVHk`JeT@deJpRgX(oq$`Nw`eqaK7>9S3Z$KJ1G@g0Njbae?M?zfU>W zj31gX8d=qQTqLti@~ZTZ;bzApfUFfDx6b?hnBVF&4;zi}tSdD4XYG-h`ghF8XMLb} zaisw#9RjSQ8s^pdyqI_@)4Qzf!Jq==drB_eFsyxkg+V55fRwD`y5A$)=rmyW1cG0@ zs0P>GH|Mv5_xU8@k{Lv@2I3h`RxScgvdYh1>@;A15?BwI*sW@4$2}96C&BytCXv`m zw%v!?qggPC08)tbFPQ6%fY#W)U2*z1B5hMf!`jh~!YUM}9vN)x{z4mc$@cYhB$r%r p$t9Oua>*r^Tyn`Jms~m`{|C`oK4n}?j*$QW002ovPDHLkV1isK*}?z- diff --git a/WebHostLib/static/static/icons/sc2/crossspectrumdampeners.png b/WebHostLib/static/static/icons/sc2/crossspectrumdampeners.png deleted file mode 100644 index d1c0c6c9a01050df51d171481302cdffc38326dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5344 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 Eg7O=+9{>OV diff --git a/WebHostLib/static/static/icons/sc2/drillingclaws.png b/WebHostLib/static/static/icons/sc2/drillingclaws.png deleted file mode 100644 index 2b067a6e44d4f787505814b9bfdbc54e006a6d2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8451 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 G{C@z-2s91= diff --git a/WebHostLib/static/static/icons/sc2/hyperflightrotors.png b/WebHostLib/static/static/icons/sc2/hyperflightrotors.png deleted file mode 100644 index 3753258458769357ecd299f44b44dc9a3be45e8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14285 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 diff --git a/WebHostLib/static/static/icons/sc2/hyperfluxor.png b/WebHostLib/static/static/icons/sc2/hyperfluxor.png deleted file mode 100644 index cdd95bb515bef7d712352f33e40f92aca5fbb871..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9261 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 P00000NkvXXu0mjfj`g)7 diff --git a/WebHostLib/static/static/icons/sc2/impalerrounds.png b/WebHostLib/static/static/icons/sc2/impalerrounds.png deleted file mode 100644 index b00e0c475827388aa30c6470055aceecb04b8e27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4347 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

hpilznk6nM#o3T-srRT^HN9S9KCrk|Id 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 diff --git a/WebHostLib/static/static/icons/sc2/improvedsiegemode.png b/WebHostLib/static/static/icons/sc2/improvedsiegemode.png deleted file mode 100644 index f19dad952bb571357e72fe2bca831e127f3396db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14293 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 D09+_4 diff --git a/WebHostLib/static/static/icons/sc2/jotunboosters.png b/WebHostLib/static/static/icons/sc2/jotunboosters.png deleted file mode 100644 index 25720306e5c2d580647e3abdcfb2bf86af30d0fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5740 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 IM6N<$g5

j{pDw diff --git a/WebHostLib/static/static/icons/sc2/lasertargetingsystem.png b/WebHostLib/static/static/icons/sc2/lasertargetingsystem.png deleted file mode 100644 index c57899b270ff580c51980956089b24e629c18128..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14802 zcmWlgbu^uS9LKe*<}%&gHLkAdZWoi&&2%#xrn^noFidw$cg`-;-QE3re#g0-bN_Ij z@AuQM_veXFSCzv;CqajUgTqpgm(~RS>i_=%p#Z-Hen=p|!NG^vN=d0JNJ&wBa&@+{ zb+CkkV@e1}5S8zJPu6c@P%Ojpb)_9J~`#Cn+Bt`U= zr*g2r3`hc#CM>*9IY1R}_KeKr_U!zc<9A*Ag4?h}uF z8akOl~w(BpFm6(FFd;J{_ z*DowXLobLAcJmUvnOdubd@NqxJ|{u&Ezd)V2)G6L;qTJQzej-pxY|F^2NVGTBw3~8 zj+65fbuZ`F9NXJlM9x~*XivWKWxy;bW=e9>aIgRWO0LA+-;uCBUXW!+)Po7NP0fT`%YnWQ`um@5W7iY6in~7;;y|C^okI&aFakv- zszowB_NS$(>Q~W@6~7k3@&zwQtpE8&QdhYURN~#(BQb@5HAh&-ABtD?XSPxlfBd{N zLF{Qjs{9GwNc?q#&p3m&rHf{@trDR_X2X80e;U&AC9lsTv%4X-=?TWAoWI|y4wYev zECA($;&DTysS3D~K@wsn;1t&g*$$4XPh_+>7nXVLBY!%rcB%>LTP35?>Sqv6P*0G! zhy5<>GHYTRnOVguem>;1D#|PIi|X$Nv}}0wl`n+RgqZ0wxBB^7=2wP05k;1iqerTT zLv?8Ipndw$V95|uaDzVk!t@+F3cquLoUuq>+2-KiEJrJvD8|p#aDfHCb&W7%wxm1o z31M&iaqqa)4d3DSu~1iA^6T0^`C3V~d|5WiWIkLj+ICYY)v~A+Yzf%q@I3j)jDo@! zDe1>ps+MCF7^rH+lc85Y6^|PcPnDn{O%z6hDMd}Mse!jpm#=%W>_wY}g1W8Sb%ng` z{IW6mw{`(N4{@{m6fxImTb?MQy?yS>-=wa<3x4D$%I+@3`iED1Lix_vQ^&o-!j3}3 z$;*h(4jkVsKeVsMIBy)Edh<_;JwvP~zcZvLz73_;R4ibUL}+!2AKzvoh;n!LoS9oz zvDRgj#L+7WEg|)PG>AJx?Ybs~kj7lAuG5@Vbf@VueQ9tF(1K0031xb1dmf&7sv|q0L--hCT zE`4sse?qM9BFe%86TdXDMV;grKRlrBkrL(AwSB8Pak}WIX|0;IX;k<48gzB8cDB=u5nB$3$6DX?j+crI~y3 zv{D*Vni5f~>gzyE^c$14XUYe#L6hS;cF<`PtgRg(9!1e!Wvs!WK1n3#?U`^ldA07Z zV{&r9P}4B+_quA> zzy+FJ1IoTb>g!X+>c;QK)18d8*z>He^W23xFHT{v)9ujRTp1iN3eKX}k=fM@$mHqC zWY*@-xoDc&>uT3P*W=aeME|;}1>VL3JdYO7cfo+nw}Y`R_ZOgR{gj*&C%!jtX4>-O zB=g@hWYMG3Ui9-dIxPO~dYudE&8HM?_`5BZ_|y8WeMaou7oE3rsNRTYCRK7%p^XSx zO-_Z?LA1x&M)h9xNuy2q`zRWC>jHc^yI>hysMb47MCcq?GS7x2tR(*srM$eLjHGGF z6~l9Mou}*N)RwnAS;?}f;G8k&`~!veCb`JP^5FfS!Jq__hd*6;6{TsMLF<=PNRIuU zE7zUa=kxrXBZT6wu)Cb7lnIx+JG2{z)iLbcfZ(pz9>RMHW!w1(*NW z3k$X-N)L;$CE3f2EwPoSOXD=zXHD=Ml+e=i@b<3|u@Ojh19`H>T{mBz{nvUaH>D__iBJ3v7x(t}^FLH1 zDeTU3alP-*C~X&rtJ-JE{Nj|EUJ_B21spU*F=5<_dF!$sAAQSBuyz46)i$40D6%|J zwRp(V&4QcR^JuAmR4$H7SX3fk-QunXjQG`nXkK8bO4RRe2i7&wpMUu@8uWb1<(}Pg z|Dp3aM()R-a?skvnT#@P|H3Te{Xg1Oi`f#1!1FY*U}>?o>wupGj14`7-UDJteuj2i>l*1s?0Y`Y@tc8*yJFM9?F>O z{oRmb@M(#y@$lE{jsbHcAx`q8%!;h^batPv)5cJF0? zHq_kH?t7Ei71-Hl&5=wz@Vm0m_=>2XfcKe>(|p^v%EiXS(d`dxDFQ)(;k2CO>s!NG z(Q$Eg&CT)**#lh4Z^U1Z>?CsasrOQh2Q$AAR3JT1CFWIXM|!pI2DE?L^NH>BPM58d zs9P|*>mlxZ?O^;A;JudLH2VYEBEmfuyWBRs?KnTh7>GYaM6xJ7wmoruOJ#(vpP8oP!fh<1>A zU!blIVk6WpumRS{Z)+lidCb@Qux@Kc><=qIjCyYa1_lRPZeRE>!xph?^P9do@I@7r zjg3vX>`h%ht)Ysq9W7jHvseP94GPtUN@($-pztRtmU?ga#Z@xy?KFUVi}$@#@+iIc zOm}t(eD?3H0Z)I2`F3_BFYCk8doF&_uZIwy&5m2GYM707#ZurSIg=zc2$5}8QUmI~ zOqDb~Wz06Su%Mvk#YmogjCbL~HY%yrU^!Y+&JwAfo}MW=jX5wNE9h}#skx6?9E!~6 zL}DRjBEIhsRyK?_@m*v%CxD-uhgX6j>9+^@KVcypNz=Wh4l+PFC581ID`BWpunQDZi7|Xkv~2U-{J}*!*2ubN(CNfOgT(C z`;MEI2SV3PAyE3P@x*b9DG$WaNJ4QLQ0f#n^Y+C7P6#~vVu?<*%fpypqx0fK>bSua z79td?4vnd^R&_g@Xag058&6hDRRR;Qrv&&>Cs?+uRa-u6>UgD z9ZlEj1P+DAMP5V`__Qc-agQ(w3S=iN;OL}|U>*DYa z-3KB=50XL<56?%1r}>avz|`jvKoWy8r=P9gq1v(1-RS60+P{|S=zi<;(bE@wd5V~_ zW2`@)y7Ba%6y5L30ISjxu;gzK!yYCJ)4)_iF*UxlUQaR$B6dHOR8u<13Db0jgyay@ z&xiA4s%l(3qT?G+WHG+k+6?3Um%CNEL-q)-W>`#bKid_5*+m9yWq~;a4xv6K&&CrL zYWcJ-(#kO9jxlL}!5?(3`?SOE<+`cT#N}_BY;d!Rbbr^Dvx_5xn-A*Y>bVUdo9wM| zBpD|dw@OMie~WS+GUE}W%qLYhR}k-9SULZDuNX%g3Xk^9L_oS?jwWl|EFA^It?oy8 zCLAN~GS^1;(50C_Z6xxtjzejsUN;1bY|(13$450+X!z-f&5eiW&mnIJ*rN7BuqB>Mb8Hk1UObLG=xh8YE%zIKd;z8@el)UV^{4ElK_Nlla=$e$tq-igDeIan!zEHi2RR1YaDKx|;G? z9f|v*#-3gqs|XW5_%L+om$P`9oNPKtZc%l0R8$(r?DE3IUJsqt?5s47G@3N=Y6zV8 z8QPQHuUN-T0o5nq>B+O)1*NE0!`_2saU?;IUMkCC+dnL0lM{eFfgDebgn@C%#>NKt zfK0^#UDBxeo=}WzOxsCk-1KPL{Rm;#`%0bP5M|8SZU{RKa~bRqL_M^9`JGZotwZOz z#)aaTMNlZ;mSkU<;i2n~$99t&?wEz`SS=K`r~ zBj>Y7YtrBv8iOJT@ANc-w%;g3SniKaC~K9mafu0yHIfTwWM*2px<=|$L}b+d&|w80 zceB;ID(hok4cAvLd53bfwa0Tm1m)hmX`2ndz-Y#A)Tt>w9%MVayFiJS(7*>ypn(@E zX|TvGKWEnf>Ww$^W6aJrQdWtk4(Um)Tm<44$dKD^?jF}ubBjv(>e6!%{c)jjE5uR7 z8kRWk2D%Y^!rlfkwl7?LHRM2%8TWlFqk&ckX2z4urw$EPc14b)IVU3(KT|LeGEWO* zuEz%jq}}%;1DzYB>+>=fS^=0ogWQx3%^!R9^4t)0@)q>8C07Xrea8UvNceoxJPNarq&EVabirk|K8-TvNz>QwVaEk?%p&fdX(Nnyn|U_>im zYOa#1)L;K|UKp(w{Z90qYO&$Kt+|_L3QlFxMq4?o#$T?0tEf^BCOJQ_Uu0{zNbF7de2fiFtMn#sF_pa6_~mLVq3QZm?YH^Uw->5+M- zQTYLc%K(yVk`hyz2c+2L0z-Xi4g3cb6u^G7werQ9&9O}mu{~UT;-FBKuLkq2j;I=V z{Q}YT(n$U|7(3f@X&B!p8M{@BL3DTCi|y|}+1uzCqP_>j(4X_PjGP>OvEQL| z>GJ{t@(r3sozcasQ3XVHXG?I494=R97={9VVdpiYHm#~cDFcedKmx3}8M?mG%=>W= z*N-$?mG?0E`q(-7bcESK>YJXOL!G3GL;X&RK`vK#jCA#xTBzw{>uP_Ns02Ad3J<*p z>@VK!NoM$pzes!sAG=9|RKp%yU`hQrOdCnpT9xE%-&?QwUEqaVE+^=OS2t0y)V4wY zPkBvlbU|Li2Cdq4hm%^h_oq5c#pPGZu|>OgVIzb(!-hoKGr`Tzq=!9 zT6SSG{^D=?3%T+PSv2$w^%hGt@iC~$^MnF>=XmLgE21-JLWj_(1gA8;2+=zW%bYGGy;J!6)k%z$AUqrB3W zD?a=uiE*0kt8AsTmzQvrR#AO(_sw!!M#lI5r^DHLFoM1(#^m)`03or0GOG+<67BNB zXezm|jXmqf56n9|JH!-1gZpm>%lHqfTc+%am5GqtZ?9@yE(u!w_~XZ|Y#}87*CiS1 zw6L&L@uuWzyog_K<&lWPq$Q3fm56Z_fcfi3W@ObNl7ukE<>jdUOG57bhn4G`+d9Wx zCu8pyFyTaY49T@`y<#d1iKSy;B6*bljd)P_dRmLGT4ZPn_#z$hBlZa_%u<` z_Ugvd_2kM07&s#e!j%ZWfeQ}Qg!+>PkfQGIozPeoYe2DNpywiS$_(RS zFUlim!irlDxWUyM!$=@p%~5XEjC9?oKXA|ZreXTEUhTbGRr5&`;n|THaR|J z*FP`cV%T#jQWS0W9zFFPK23x=o;Fm52|d!R0A#sAM>oh%{TU}kp7A!*I82K*ZCWLW!ld&p zK8;#gSq}iAW;NRAIHsaTZ5gs`$1Th=-t#14WMovYuW}GWu+T`uf3!MTwcMr=^3lRO zJ!7Akfq?<2I1US~F)CT8w!B#m%QZ>)m%C_%;p_%pQ#uZX6>VAWBLxc%T>j@YT5mU# z6*2x)K=msbsz|ePfJ*4df4-DXRslkm`SBwxvfP%*PI5VkEh|P|mPtdJ+9a>3DK4}h zxrgdK&Zn*7G=S7KQ{Y7&&p_A>VJ&v^KN!y+5+&Mk?xfao8akUj7CqmrTr=!g?-B_G z5eznJc*NwaP|%25I61{OHuAYk4c>|X)$~X5Xv8S~Y$IejZ_W-E1wPwIbm=9G|I@CK zSKa9VsfKo_Vwq&!g2mm2&qR(;|8$yhQRV!^x4@m<`38Ib@`5!~3eURXr6Wk1tZXxc zTe3kjlJGB=GKia(q=BSrcY0-7f-gzsBH7MCtcWU^S_44`O@>M* z(L<$>U@K3Ip*DLhU|-1OofEY=U&EeOBUi}m!s0Q^^RLAo`~U1C{=E4*(e(t&9dLq3 z$U?HPEM>OtNWzX&(iI;+zrrs)C+#fZqa*wBU1?VHn&*BP=&XvSF`O*asF+!qQ{l)e z)M3_ga7Lw$*TAz@Gei}~RL2W-5c*oxZyZ{{#AN57mgZoG*MHYi5I!#g!iQ>2yVJ@Z zY4iSF>w5$x?ag?QIkd^(Dd16q{BwN^!z}faSuEXqwE?Dh#MM)?DL;K_ zzR2t*SOD}%2I)HJ;vgK!0$Y+Q(yxlAk7sfM7d@Al*@EM#j>75?e5gj+^BIvxbkgt4 zl0s6F%o0XoM_x3WRGOiAiEsrml#&dFl#x6;k~u|U7=}Wz*3n{XiovNrV)nA=)1ull z_qUvYu73-8JKJc_h=ut~tZ5z-3-^aDvvr2d903%X3@n6CWZ!h^f=dn^mLNk}CoORf zVrre0F;zLnJ66_$Pe(lS<0x__LXxioDiAxUL)$P5n}^Y@s)8bV*$H;ZeUHgA zeWgy6GK13Iqr*~r24&!@Fo?SOzVT}^0f7zQ!Cj7AgJuK|jV4FdSLvS7b$2%Ggg*8! zT$n$gS<)KvCP9=<%Ymf-)6}MFO4jTWLl(D<`F9leBWVHw8$^!B#}2C<-=d@d5_Vw` zJ&BaB_a?9kzQch07n5$m8b$N9j_2vJ5<|ABon7Q?shVk#RuT8Yd?+r>x+alJAF@pt z@#bVOr8#b-Buh#UM2jd))z6x7Um0c5v-#5rSrbKjc2n1Fz|%2sih^n22Bv1~f@5Pj zRI>#JwBmx|f`3aiI;;-=Jr-W5_aN=uo&E{?0Uc3RQ(13*lWJ?Kuj9}b`d*fL-0E$c zrIwAo`71kW^L;lvkVwgU6*V~0e9l{>$b4_)#>U4#+6W<%@}Jk_z5-ljdB8l*Kbv-v z(#*-qiWn8Ay5S9MORid1==WMG`_g49>U@VE=VIgO`{@m;tqFb=xAP5TjyG7-KVZXK zuJ4>_UG^;sEHkT;6s+YfYl~)yXEKzPhu&co$ZrVnaYz1APU0z5b32P&yzl-d-lO|+ za#`Qdgg7-fJN%n}ssHGTPR`gnxQ^5Ser7?i;%s4>w(sQjn zt-@Lsh%+l002BytnNPDXSqpW}lwS=RH+(22#V#@9L?2W*u(0w$01}IMr1WM*b!v$j zH)y1>9O~=S{yw3FWD4%9`e>GDtTyp!ym~oc^|}4IgL3Qaa`HS|jQs6vOP#4KJ9lcbjUj+%iSYKtL!FV{2{dt*Khl7cL~2~)gc zF&8ExH68&WLCgLQVlu(5&&j^^-oZ(Up?+RH*W|HRM?~j~%6DNm5~$X>$T!8ZzT3`g ztoNpaC(B1m4O!!hjWe^WYSdX$FeuPJFLdmpKxVi0lsM0IbD!C)RkNjK;YlAJyn(TC z?eACAg-$pA^78e0_U}4LOp)r~dM|gH1n*8lKjnAYLk;tL6&HA(M=QO9uM5>@Ub&A- zvE-r%nllc6_g4jmA~qR)HqHlue}VewEY=;n><8H7h2L1jJv@r=n|y}Qf^v?TA8IBJ zXKZ=-K*R`>BnW?0F+ugisN~;zU6D|`h8`(E{<_xxy2(kI@70T92;05~;Khr9Q$H7` zLtB4$$#{`haYO-l(j`4RYuj(tC)uh{BEK>__hEj}d-74ddu&O>P%Z`U%_p2lKAgjn-zKcQ@qJH z6iYUDU-uckUs|lauM(=KrWdd@6UVoQ@fQUHuRw})8M!&@Ef2FIw_`j@^{;T)9futj z7s#?<04kfNu&%+!lF>4M)%gL$A4>{N_P7}_LuSdxJ(SFRa~q+|s#ymkQ}r z+79X1xbn0P+YGu?2?OK*&IO!&`zpd5zy@BED11&7TWiN&u9E?`n|>UHv}t!IO^m9i^^FfD#94SM25;!xjY{>{;uT5- ze>wBNp2J>gb04-CZ6OC+u08}iJ9~Ra1wos#3&o6ULoFPdlpPY+04CWhGXq0LHIwshi=(F$` zg=~g|?kX}DQpWkA{&M-m^VYl&xXh&SornU^Z2c7G*Zv3>V5}bscW3x^=xx}})$_Y; zb?oWN^5yXKg`w-h`SrpV@(2e<*!%x30G8MCwQMYfpd?2cZB_(YoE3`@593`3ij({}#1)!&3KH`Pg@e?FQ5t=kP>0Tn6``cqujo)e=m=AG z?T}^NmfHvJ44@+ehv5L&*iu+odE#uX7n|Gn9=BALf-z}avqEQRrw;GmKcETSYo(CN zp$QXTj5-eC2H3`z)%)?uH31I(RsYXaXJ|eDz=#jFBEva8KLYt)_S~0UkfiI~bellUw@*Ng!EJ?5T z1BygDm^VvBahsd;;c-5o*Z2)(V1hCqsr4wS$4mxUimTv2d?*|-jx5?U<`6!*_Q?NQ z8|~I&D0=-$6bT~m1AOo)KvFVG-th^I4E0ktIxfocCFSc^ zatZTD*}9mri` zhKsSxZ%oI+&;!JW5$^ADMwgaE%+ok(fk4~BkQtEW)Z*aOtey*E3%sgOj@b3FaJz3# z7E;&+#7@Vi4f!Z~Zl92ned!#jB9^@{St8ka_HXQ2UYCH_zDY~@%!IuX#MzrPYw=S% zI#?P90mr^-*SzgbWvbMu)ASF1+eZE7oNsUtXz_x>)bi};O2DJ{{gBe=)uJ*VhuVz@ zcEo#m;@m7fMSO-IIfq=lg25&G02Cas{8X+8M!BmZl`277oNFYv+s55i%mB*k=MdU& zCVd{{?nY8jXb+cRMOH8G!k%t`x&~fk|Bb-4lwmw^7w+0J(hP5208qC+{f4lEjOnjTtoFmsUU9@5|<-ljI=-kurHu8&QrnG7J=_8=& z+Gbv1J8be(4s$ab(wtMr-%jyF z)5Mj<@hq^6-R(DyN{O82p^Bza9#w?sv6YsjI@Fglo%?lmUy4Y(N{PbqHL6XUj`Y@I zRlrDS?&S#+7V@P$MVhveV^>K}63`HBcESzKJyY-*SDexZ5~^I+>WS#x5`DF`78jpTj^F$_7c=!L^hyk&C- zf9!aBn3SD7zssN%ej@HOX}wRiWjKt8F<(ULjNAMNxF3)II$6@D`5!xIQSx49Sk)m~ zn}P%r07*aU6NH&E(2X4q?xoll~kz>v-CbS=SP9J};F^%^F zC<4rqb68L&RAQOW^3JxyQW$dJv6L!RvG6~5>RECyB>Go74Z)g?9$l7BMA6cABxciu z#Q5ibE-6Q=Vz1u8jXJb^>N+5_wk`qIKw`drjm9+Ut2czG3__H9xT5#X(2!L$+%3|V z0;s8c`>lL)zUGrE$HuwsEm(V>k)K+4TOiFs@)sm$&84y5Gi(zyI+`!C>$7L2E(1K@ zg074|a~La@&;qJ~BdHJ>LpLs)q%%8kYnWNN!;MgCN0MsEkp)RO69B*ayyZhtWv^8| zH-(jC*onpej$J9^sbc6bQ=+)R@p_BSpz(C=X~9jvQ@LfNsR1CwU#kWip$vpHg>cOH zU=}8vFGZJ8(eaw`s@8%&8VF!sZQ!kaPk0YQyneVuUzxvXpziW z=3nXC88>)`nWQ9jxfj|2fJ4Gl3zR}>>^#hNl0sX**A z`mUsGh-t{~W3#7nA|4GNG_$;bn51)e<_8Q^7%p2Gxhc>WP;u3atyXZYq>;M&2qDEH zxdtX@o@NhHVb7C-`ROCp_fV(x>5G5kH#8#(Yztne+7G|%U|0x%1s+2f>|9Nf1 zulfxQUPBr@;PK_%I|f-85YUQK_31xoX(O_eu{88#iB`K1#9z9%Ckv+LK+efVgul-V!=T*Q$jN_sg zm!RNq3A;hf3^4%RYiF3sV~@iy-5n&u5SF#i5F<{<3V$KRE5)NTvyRX2ZO5{@G>?GDbbk37J!fQ7o zAbpMBDQMHGP&fV2%9msoQl=E44BD6YrA@uI-eQc2j)OCjUXwg-b(XDj)Y`(qGdnw9 z$x!AlbaN>h*KdbB*a)ED?G;oCi`^X2Em!1&6mG(a?;A5yGKQ0Y_%UqZ7IBNi*%&VH zaQ!^rrK=sP%rHuN+8MWyVLLFup~ak{<6B)VoZ=y4e)Z>V+~rPFdq!}uF_8BIo)H0$ z$&(XnE#c-fmSpyvHg?#ZktWD5j(@>r!xsnk@0|KW-ULcQ z=)-LB^Vfvf2RQpxN{@=id{|^UbP88~Bqy=qf+H9^_tvu#q*9nM^dQti%N9|5lLpK6c zN@&{&5XO_l{S*C8+EBe*Q>j4A^-a?b4P4Rj3mDddygJ3zl~H;4c{V;YyvX^vAE^{I zUC9>3dgJRe*)eCm`$y#|6ld00FcFSIB8B;RWQyCv!3b^m=J*NGd#m*sO#n+@`n0J+ z6gIJR1(_6orlXaTLh4i#%F3Ubrchg;UuPKBjkjqHw(V!;?Tx0)EXcQG(M;TW<2X{x z-L!nqu<~+pd$I6(Ke)kaa@UrEWl^00b5Wso1xbtPg~sXaB)_L?eyoLFdRN-i8E6BTtHFlo@)q=d(w&!iB)xz0=KT_x9{Z|do3 zM{H})ms#)S={kw%y!m(W^kZ)r7Ua-lB3o>D*qpPj`PX}vY=)%qJ_YRpJp}F(jN0Th zW$T|sdaLhMe&NVyq)jVi{3I+f{YZpTb?SPsg$7Z{@8~c`#yEI*-m^dOG|*>rIp{<4 zJo*>9`*3F}-LU^z-}-5Rv(VFlDqxMefKgmc-9UPpUEMvoK8yw-#E3D{OqWgeNgAP2 zcD@qDvoPX#{>~}--}vN&ItzPV7D3z1Qg;BhNcV}TUqZqbuv^x7ox%z1n1)2tElB1} zoSay~OG_+?nwIrTi>-#GsB$ufLZL<|b*YI9uFO;j5TnwYZLd8?bsZ2jHih2spNMytsALh5XZO9)7ns27HF*_nJ$)QZM->n6;*yL+7gG6h z0s8Z88X8evyIqY6bt*1<@^_n$Cxqv_f;bsfvtv47HVOTV zFBty@gf;k&WYI{78lo(|Ys~1s1q5X4+Of&Wsh_Y!%x`iXI+muU)K%0FcBhW#Hxz-p zZSyte{FOS{05m(RtPY4sh?24?C*XHGwqZLu6meep063re?94RvIR+x+DRz%1- zgN_Bt{4(7Ak;PyNRxi^AI=x>4;%apoayaNE#I(MxRpxuGG<^?jgBADFF!m!VkoQZ5 zb<3fO^M;Do^6v~cDdq8$A)*h-{niOllelQ$R=VW`T6DyR2j8-}k!VAKxMc^8s^s+C zELuz{lqRQbqthVq+s^yStD76w{SD;K^Wv_}A*KIk8)iIocduJIHX3pGG&6V%8TzE* z1XjEui&~op%^ArmxMN^>AeB_jJ~PZT-9=b%(aOmw70}4%T#3ao&G?`UQZqSpRwFDd z`|g?e*F@F73#+wzYf0~D*E(Ts>MQj@;{4>zDXt9@S65`$VJdXUVNQvS?lUEkRcL>b zhcl8GGe{_s7jppw1eow0s*nroI4o$@kd`Z`@l$Jnq)lYRCl%!bckl*`Ug zlbkbBasZ>D+?I+~-}BPBlz~BvxtOi`_yi8mHPjZhsu@-!c5FJzufK`j&53)lS326pLky#e73DT6|*3e8Wb2dx2)C zJ+iVZc`8NIER#V803zA3hvx3=PT?D&qn|3Nm_4+#Lz2_W@G?tgZT@Ct5}5~DrOL8~ z8DsTQWslR9aizw!Xg}iW`tky48Ca7#RwP)<{3rh;P^e#d@sw|Ed%9Q9R*DUkzEgb* z!v-^@?~6DGt)lv@KCbq%H<9;UicI7Pj&F+JiAh;c;vi8xXQ8-cvR^nljxOXB7x=2o zF*Oc)3Oezn7;wzvh10#uvIw&HPRFH1kXbUgXI%Gt<4NSQjt9_yn>kGwE+cQkxY7Fi zT|dYYg#l$RMKQv+f7PtQO7xhy9zK*k-=P2;h)k4 z>49Z|dJp$JQid4WuL~K(1Oz*PRytZ%?#}R7PRdNLkSa?XDe1pjsNwZPd(@s+5zWMz zGy=Bm^V5$C*k2rZx9}4OG|!2&Y}$=IFi1q%(bu;mq851jj=Kz!;gBj@7*;}BG-Jy! zC>zlOb|+^|k%U1+Caqme6YPqkHwE0?-7o51w>Gyf1%YkG-%Dedsh0F;=6ZU17EYd# zq4zjDR;9Lo4w)h&ZHb9WS};4=QjGeH=r;XLD|Hn9eS{=TA7*Xox}Fx~hV1K+@OVZw z#yXv(B$~LBQOKTQG2bW9*o^Kl2~vdRC$Vn17&4yx3C&xuSrd8#>{BZXNre>E|B`Rf z9B?s&K!0<)8~dNwDn?4lrPT#n5iKP@Bo8O~k~2rh;Ht(SO&^y*m#L@poOYo+{rTS zCnvJun8%PrmgB(ZZ06aMl%&vLB3x;s!#tQ%l*T6d9AW68e3-K|R{;Q_%bxgj)8_(6 zro*t#D;Wy!Gt&VMlZ}Ye0AP*P?8SrO5?xqa0z1M?C#Iy|KI5?Qhfet)`#IKalThe;$1;51;n~ z6~^n-NmS}|=#MtAV%;j*?9S0hZC;a|6MoL6KRF9{bH}@Ox;d4wsGMyAknaq!ps+$l zlM*t;KmPEAhEb1SX+9XHTRVA%h2Gx_;Fla))Vd_4Sng;SOBQQ>_^Umtm~YEcVm329 z7n4vgty-*E{dtwOKwM=G^dzvTS(cjS=hr^54xhwY9$SskQ2}2E^DPQst%sp@Vm+aq zPaupIE^HN_iiWkFGMu#E>!3Kdgf1v%bdt9T2n+rHMoG#H4e<64S2P!HmmU;P1XidK zRN2@MK_ZojXbauGKz3j5b~E~OhX1ce6RWwL$~cZ%?WeEx=@x{?=3OtJlVN_Gw5{6;9wus5Tu()V2?fs(t(p|3Y*}rf!tq z?TbtNU>~~nMc0!+KhxeBNh~2`gJEjv&BRym5>_o}goo+{VQb4i>&S@x?1M2E+bFK% zG5KB?bp&12?$l3yi;6kvfu~wlQ!ZAMD0D{|*ZVKPYO}27NcUxmP8~IAOso7sL!muY zmtp103^E72I9w-cbS3vN3Txy^Kgo@do@s8{r#K@L5fRW={`Zb>EY#lLsD&OzoI_Z@ z-`HT8%IbOt_!R$zs4&1i`hq{Vqr(vp1Mef~D-y}v<*){$&8Zkqf06ZZ_#U zNf6POcp#*s>bLu{N~Y*GE?`vA?a^bwV6X=@cUNk4Do)+35Pi~5mvgm zREWFLt1ixU(!$gKmfG^=-$bu-;?3~C*7>4z8d`zG3R{;SokP{swNEF&FhHqJPpZIS z=!b?2n5CS-7LVO3V;FTQtoKFw4?lZWHwGLuIO(@sT>e8R0P)EX>4Tx04R}tkv&MmKpe$iTct%Rg6&YmAwzYtAS&XhRVYG*P%E_RU~=gfG-*gu zTpR`0f`cE6RRF*B8G^>Onpui)9@T$_we!cF2S?B&;2?2m4e9tpFljzbi*RvAfDc| zbk6(4QC5}|;&b9LgDyz?$aUG}H_j!8{X8>jWHa-`QDULg#c~(3vY`@B5yur(qkMnP zWrgz=XSG^q?R)YUh6~!tGS_JiBZWmQL4*JqbyQG=g#@h{DJC+spY-q#Iew8`GPx>X zX zu@D3Sf*?q6-xqNcDT=zdoxOe_YJ5OhxOwvBB%{+;Jf8YC^?|JVz-*V2m z@Rr`vTly`M?zd$kdcD?awOa1cqS0rv*`>EW&2P|_Ev*OK9*5f9)1kJuHvQpS9`+lg zHJUc&SNHfSKj^g zZ~fKv?(U9<-}10uq2jXQdrGQ3x6Ixg!lB7v%rSsQFy6g??>%+3l&zqYj7tj{uO87xuT7adY5xe(?O)SqLn0?3H7jJ$Z)pJze)c|H9Lw54`iF#PyM#7=X?|SeP?+r)elfO41>8`B$n#rs)^>)_Kv8l6RLuX5;#b$wI5Orc0 zZ912w&@$dF8(5wXaPTJwDX%JJ_m123c1KzHv~OX<^}*|p|K0!-1mQk=ZDX;=<78t; z3u%pkxycD$JMt=rFJ9r~&yEvRWUAIxQ(IRUF0dS! z{JmJib=)zTQoPU{^ zltnRFgirs?*PeXSKlEAwE2^$m$NKv4R#)J&8aaO95>-V77%Xa`s zb%mZn*Y>R&-ZgY%{256~ul^>(CX2#VOfDx26GJ2xzKv|Ez+`hVR?&nnG0J@3Pf3RQF&h;m ztsRTA3Y~C1XU*5JeiYMCTx6fkhPB9ztkn|_hQC}}Z0{cR1wYo)T5JE_lmGN!o>lnV zGvE9x+}<*Lp)l=614Z7d(iaZ>;K;r^-o4}385X0_$8il+9QN$<8m@Fo0 zD$Ce^-yZgCX?bYDH#zsh%P&q8IEz20)8}#d#nZI3RP)@6r@1sVh@6$Et!>!;-GBPR zGru+@O~y?&tJYmxUyW7JVze14E-L{^BpwWskd5eUWsqM>{g!={HEqJ`bW&I6As!Wx zC5c!hyc`IGmezU=GwZx1U)rn}NZG7d3i8oq($v;gb7SZ_W%;7sRqyC-M)w4{*XZxndrYxDh-W2U8w0K z<5#YdN{d)?%~&mF(rFR5yAV00a{BZrFCRR^+{`@1l~rfYpStwKS_4xJj)JW&i@Cxs z$t+m}y!Kpdd3M&VZ|6skKS8FikLbb*Lqo%;qMomQ|EGN9k3L9Qk(;l5{rl|RxrxS_ zDk>|yo%0JTrt$IVqyMjw#@_BnN}3wCblrL@8YRi=ubgIh;5tUDjjonbZomCD#AKSO z$ysDoCf{!2r$0E%=*SfLE*p0r*n_;fbgb{{*h^~-Oi|+TbH$r`Kf-E+s0tM|B_zUe z#FR)Zq4J#*XUW!;u$Ys>v77z$_Ox^4*eTxsp${`NGlkb7Qs}a9G`fx6J09Bd;O_FG zXP-TE=wBPzc;{_jn(n{q_Ajl_x@{X|!i-<)CzFV?|D6Zewc`#tJ3FbbtHxq7aqRGE z66rKscde(fxr*R|pQDFf`${s+fTR19y_>o#{%G5##yiZm!hD^8GrSs}e$$Q>*=#nL zm!Dse(^%KOI62K~EQ&rOp%rvsFp`xeGX62zT5BmRt){-Yk-9oBnZ!H`6MZZjb}?+f zpDPw857ind(EGZMpXwV323AM@k71Mbp4g(V`s(REE}p*3{sVjY?zg_dwr#z9`77Td z6b-Pxb3FyFBAOf8*tD^WWO@}<%`kP-$H^1dl8*9<(FZ>L)xUn*?d^Z@nGdb&xoh8T zJ@0wX2lwy2bH~G3HC5VoY2bx7?Jl`I=R4Bg+xy>U78i_-Zaa@Y@*Zkyn`mvUprNUq z!T2Wp6Qjfy`Y`A-7<2;X2Sf_G-w$nfGi9;RZc$Kg9Og&g{VW@A-FfS&6BobuFXPuV zHGCiyiMm#?CgH>eo|R;pXV!qqMX%VGsnKfBrcnInCtg9H)+7V*2Jh z4INFP_Ph44Kli;qtB;Hw!eBPw$e%&V7^p1YNNa0dXLVimEw3Cp^20X;n5wGs%EIDQ zOpll6=kNxDRW3nI5m1=Z>Cc+qSK&YwSUv zqv0<<|F_iFRgq20eExrY89~ruaTc;;_bvSFu|~^A2UdScFB4-6Jb!4GimEzV8*_*! zR0c*C$;M}@S~@z~Pn|sflQ$(>k%&gGE9vxY=3;N@(~m#N#Oxwt(JGddVuB$F!KA0S zqMiK4-3aCNEQA%t`leZa^$W=T|G?_<6cvpf)Hl_T5|fPFoPCF)<-tvxJ4%`w)@dA$ ze6)In*U$EmO~hGUmFS-xiQ}hf?(O8E_x=|w zl1?-B@^Rvo?d;gHj9URhX|Ws;EVt`8m??6NpP=%uTH@HWy`bMr5>ah|sw&lIZ(84B17>n%f8j(^za) zT3Q=9apDpKS4U`WDr0zbn6#XvzOIG#<_=n#YkA?^2p6tSAcup@&7bAgTlVqrLyz#( zPkzk7=MHiIeftQ9Vgv&LoMx55v<*Y&P6l521=j|rc;|ceapJYtIr)pT-20vn6SsPq zh#4_UNqX1Y_8dFb|Fc9Q>3>7hJ6f&Qx^dI`#}JGfWErR1fw!p_qs@bzF>LXlBTf8> zXy0Qj9(fp}G>WUNg5nA{4!aRgnVYtbMrNjF86KLz>vdtbTT$dJKmEz`Jo@mX+;`wX zBss(9KK(K5MlD4ag;qPf{OY&RYP4K9b%<~{M4r2p&wTc?n6!GvMty|GhlwptptsqX zotkIx>NFqu=m*f-Y<&LX`-zO4VKSlPh;b((CH&=|zvJi|?qOBUjURItIzHnpE7!)C zmoeJ%@syRIN@-GAEeS~_FIP*hRlwpXC7jf-eti`sUKbb7j&c3kIQ5NXT)lXmE0>4a zx~m6^NkC3#@y#u9`Qiw3b4xt`;t{s(=%Tl~fv-RQbN1iWLtBj#DVstN3>j4(8>Apm&j&S>Y8e}aJmn(DTkVd3QoRuo^;>>4I2({QMF^#d2vOK zTFf@LdvI{<&>IFU9*IUpIrFFrfYVXjJTW3ucpnv9V>& zpBm-0qn8K-!o*?;v<5TL)j0lzMG~yo*AQlYzl;iajpku?Qw(4u!=o`uhg(dW&#* zoE$lP5r;*kv}qedvY8fJ0d7ry_oe=Yr;!Vw9h zUW1~5kduSQQ$RQr=FI6U?A*PHckF+d{+VU|?n^&rX*rCy+(TPaC71edu)Mm$&Mm!6 zPmYlmQ!Fhk5=$n@q|?m#7V$5JC@%FNCKHTZd=*d0Ce*TO@&zM~%qXiXVdvQ7!ofFe z(N`69E*XvaRh4~JJv|Ix8DM_wCf5916eWvR(1Rw2NI1@o{+kpQ<B)umG1P>H*)8UNfg!Q}|~#U&KE92B@596fxBTX$?=%jRt;NZ9hM zBqAa?MkCv|b@Pj3=b4?p$vf{mz|`0{&;9H$$&>_W>1=Q2^tsEte)J-(o%JjSVkBpe z(b4-(`X;l8t0{zdzpa08<{#hCB1krqxoj|*w%Q8al|6U9jZ@FR#N5OL^E2~=!%+l5 zPh>TM#cZUstO%_f<>b+8WHvoZ!=0bu^ABmLE{sy&eHV{?>S4CKQ#9SOhgzg9rUtxB338&r0+wQ)N)kG3STIJw#uQGIf zoLqZ8on7nLwz-$xx8F)M7US5_v*g-LMB`dYi~&p<9kX%`#?YlxgM*__zu{FQNlYD! z%=@AAKg@~aui;;bQdd(>o+Fpj$1h;8=(%I(on$l_;!+e( zQ6YU7`-z5D&S%asd zfzI3RWL=el^wJ~Ty15FYeFL$CjF=o?Q(HL+IUh?wAwT@qliYpxetLV?^NlZm8&7!= zwj4c{++3dg{xdW+m$P^G+sHAh96NlR*N&ZIWo3nHmk0RFUwo8D9)2%IgNE`_4~Gw* zVE662X{c*Ot5xXQ&_!2IGslmgU~z5{)!IOHvCO$6FTOXM$w+TXT~blhOX0vOsnx}8 z&a!fXD`BR_CXqyuU3)h1UqAdl>Nal1Xo;|V;}nTZ0Rsb5G`H3gjO)qOClEAqeDCp7 z?Af!I>iPz>8UtX|KY5O-@)Dd5C*55=1eWG$S+^cRFt8k01WlF=J?+JpFJE0< z_6N_bWvf6WDM~*K_(R_;EGys9++JTfdAXmVzCk?o)hvyS&=yOgEiI<5zKnD9s5n5ToYBF*C`Y>1fuAvbOw5_W_RkC=?is;?gPHRUeFC99DUI2Gt0V|6qQosDdFPzvxE~7YO5Qtm~-(hPGPm=>l&M?cZb5k%IV3u z=hwWq!r`>tQ&v^lcILG7D8-&a#CVc;EJ8(f8B;S0 z%+5@+bLURN;UM19G7cYlm7o9O1-5V7L4mUfNs5zW$|a*p?P4tEnxFT-yr#f%tdPU%}Eq=2!bAmBafMkjIGc`fz3=N>L(yNh~;(RTNNm?shH%OTpt?bwIi31#1xh5 z8n9a}=upVZ&&6F-z{=7bW!`d{+gtHX&vWJCRqC3mkR%y7EwiC#124aF0!2;Ivtbif zYaU(Q>xnKe@tHq;jLwbgC~}u#G#Wrjv$#07At_49)llT@ngO%f^Q+41O85CDCQ&td z^hQ0p9206fjm2ys-)SWsi=bq(By-mjQ%!g*DRQOj%+D_{HW8%Gt);Qf%iM~H9E*|f zwBmHykUO-1z&lWax zxADo3{|Qe#`80RkwFjME&$Ev|L8;TZJCaI`Clcb|nsx68f*!q~C#!%YN+`07(O@JQ zPZEtLh$keZ_ySomhOB6bCSYnhKrj?XQL@;KTC7%qL?Vuyk&r}*<&_Y5c{cQdz|7PX zv-3B&W6w?mvxBpzFM)yItiOvD5@xc`LitgrZDPZXgomH>|*O}y(Cr_u$axLC}d>?qsd4#l0-};Q52EO z7Y11LEm2)ril8$frBiftv@kup%=t6t>FjDnN~MT|BNP-AqBE%|$c$bYB**JYeE00N zDp5?$t{Je5lwMAWDNk9ctLyr}FpuRqNvd!yutH@;DQ2sl$G^iA09^IX_v!NPU%qk&#(UMkBJ4CXp1;3p#SGW-Ph+TM|UT zd=?g$(P|W$Tbk+X8)9x|itddY@cWk-8XCekH_M;=$*1smoP6^e|70{+^Cp7+z_m4P zsGArX{diP@T9d#ot$>or;B+}K<(ddA_)#(<$&8WXqZZ_qv&emqvEZA5Yo5eNhchy2`o?_NSt zi61}lB!XVc@W>E8pO0ixq^W5g-~G)oH1# zDaT~ZVR?2QS(4Bx5^2XS;yJD4n-j$SW2B@sp^!**k(F|n9yP~?#+-*vNs&yZiNzCS zG8xh-iITEHY`HdGd*w9Kle3gnc`)P{P_kK80wJQYI7UH>&X7YYou#nQ#W=-$vn zeSJNOMj?~QvVGeo^To@TO~F9;=$ZioSe%|cUeMHjf5x6uP~a*+r#FxaCzzk};Vt)| zB<4vj&tRzBOI*?r7(9e1Wyp2pQ|HuTMCRm#j`gjjq!LMzVj4jZP}MAnL;|zb%#uIE z`O{ZenqR?evmwS)1XorG27|1whS6wM3S4$1N#-X{KSMAW=JS8~*BA^2BC#0Z*eW_f zi_L26I(g#a=hqY%pv+H9e&6mW=uIWkm8rN$Y-JvINdXp%1C244On3}UWElVWRW#W! zMc!g^jc|1`#$qT#c~Kq(6{Qpx+6b#2PXB^AyZhJjOPCFKx5kaR#Ov)f9nGe7dM<(zzHeqtN6PzAE$x29~LVQ)k zWHq8FV6s_hX{{m}7BL!ZIGk2Mi=u#5tHtdu0!882@ssEU9f^2?`wzT}j`c0vzIQv( zV37IwMSKgPD!bjbFC31(v}PXm&-F~BGvEHnw3N*;m_)4HME1SRs_!!Pf?}L*2RQ~U zMuWg|Ac82$Xti3F7M32G^GE*PVl%v5DB5Z&t!>3+k6^auQC;OkqbS@Mn!%ErL!LE< z+L|)-1_413&>HlpSrtjjuo?*?%V`$o{Hz97Fz5}K^UNd?5#o^~DKSfTcQg0he;0GJ ziw)D$bKhQ5U$C&| zp`)TGe@aypUqN|Iy)M?@VK3c)FWNvVYs9AY6Ik+b8sFjyxw&TM7XzpY zn2b4S^%_JmjhK|MS@Y2A^yJ#Cbar+iCKFigR*Zs`JMY@Xwp(wfv$NiF@Zig@{aX*R zZxAq{#_cqJXqVT)-yOJ}g-C+QWCFY0jvSAoW-^Fkns8Xepx2R%CzU`T(hj5noJF2S zn^nI{HPsMe9kSrYY;!Z%KStO$Ms-sKf)4ykA=GRZRTap{Y0#<2a+ZmkleDf|$8C4s zNjf7FT@8aSOIK$X=Pz6$t4iD$9H*zJm6Fm*a!i_auN^z{oizidC~C^T5b~WmcetbK z?uT;^e)Eeg$SQ&YvDHP1Y>4_*vtiFotcu7LM$c&;cy(iphd}Kk<&7K*!WGPl!O?I(b3sXX}PEP`JWyBMIw=yUlU-es-_gUI50kb@WBTkdf#i$ zKV`TvyMmk&(P$tkrpRQnXmwfwOTixNI_kBcEit|7Em#2cb|Di^Yn`Y9tYj zBbZD)@W4*Io+1RT38T?QK~V)Ji;+Kg{|E8=7kTG4gZ@h98WHkHP?MM5(*JWCisfmLplqW{j2B0f$+Vms?PqJ+QEEa?9`9M z(N}jBHoUzkr_N2rmIuKkshJnCIx5I93WTFE!r>UG8p_IxY3-gxN#$9lUn%t>zAHcQ(%BbZ?Rb7@%Sd4 zoRw@D^~j4eiyw?e6Mv?v>Tm3^S0TQ-y!h?ai(l-`&+V?xFRdUvmnL}Wdn7X|V%Sep znFD*ioxJ=!^6Vz2C+8?GE#_hN5;cVYU#>a1x64Nv_)G#nK zimFQN*|WF#si&U(PCA{5ttBwRfzXk(C_fpGC;$3VDDs_DHaq?A9V037M0n)sVrAj_ z`efKoG|(CmB!S_s0+fFqsXEPtIa6o2V?WVqmb35>GLW zjSXDxzslV7JO-nl0;iorzj%!zj~#DunR4mUwIgc^?4PepAyp;$9~mPh@lt$cQq-ny z?#?FD{Lc@p&zA3cOk31)KN~v|O!oDY)Z18E41oqTszQ#@z{rhJ+B;g%2}aJGJ57

UNj)5@82Mmt*U$~`bXx%B7W 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 diff --git a/WebHostLib/static/static/icons/sc2/magfieldaccelerator.png b/WebHostLib/static/static/icons/sc2/magfieldaccelerator.png deleted file mode 100644 index 0272b4b73892fa735ad850ba22ecaf0646b08a95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11459 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 diff --git a/WebHostLib/static/static/icons/sc2/magrailmunitions.png b/WebHostLib/static/static/icons/sc2/magrailmunitions.png deleted file mode 100644 index ec303498ccdbffc072664429e7a943a128c84025..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19193 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 QF8}}l07*qoM6N<$f)DZ>AOHXW diff --git a/WebHostLib/static/static/icons/sc2/ripwavemissiles.png b/WebHostLib/static/static/icons/sc2/ripwavemissiles.png deleted file mode 100644 index f68e820397652a0e3177a9e03cda1954b1ab12a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13628 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 diff --git a/WebHostLib/static/static/icons/sc2/specialordance.png b/WebHostLib/static/static/icons/sc2/specialordance.png deleted file mode 100644 index 4f7410d7ca9e250b0305fae35b0ccee603b2e5ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12992 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 diff --git a/WebHostLib/static/static/icons/sc2/superstimpack.png b/WebHostLib/static/static/icons/sc2/superstimpack.png deleted file mode 100644 index 0fba8ce5749ad42fe9fa5c86246280aade0e1fa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14901 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 diff --git a/WebHostLib/static/static/icons/sc2/targetingoptics.png b/WebHostLib/static/static/icons/sc2/targetingoptics.png deleted file mode 100644 index 057a40f08e3097f095239cb437618258d58fcb59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8431 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 diff --git a/WebHostLib/static/static/icons/sc2/thorsiegemode.png b/WebHostLib/static/static/icons/sc2/thorsiegemode.png deleted file mode 100644 index a298fb57de5a10de67b9d1c88ad2576884df5967..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11345 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 M07*qoM6N<$f-r-FHvj+t diff --git a/WebHostLib/static/static/icons/sc2/warpjump.png b/WebHostLib/static/static/icons/sc2/warpjump.png deleted file mode 100644 index ff0a7b1af4aaeb9fa6cd112b093c3300d8b46041..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8665 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 N002ovPDHLkV1mnDw5$LC diff --git a/WebHostLib/static/static/icons/sc2/widowmine-deathblossom.png b/WebHostLib/static/static/icons/sc2/widowmine-deathblossom.png deleted file mode 100644 index 7097db05e6c0da7760362b090ad385232f2220b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12946 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 IM6N<$g1}Pepa1{> diff --git a/WebHostLib/static/static/icons/sc2/widowmine.png b/WebHostLib/static/static/icons/sc2/widowmine.png deleted file mode 100644 index 802c49a83d882539becb20df50e6d8a360cc6001..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5671 zcmV+?7TD>DP)EX>4Tx04R}tkv&MmKpe$iTct%Rg6&YmAwzYtAS&XhRVYG*P%E_RU~=gfG-*gu zTpR`0f`cE6RRF*B8G^>Onpui)9@T$_we!cF2S?B&;2?2m4e9tpFljzbi*RvAfDc| zbk6(4QC5}|;&b9LgDyz?$aUG}H_j!8{X8>jWHa-`QDULg#c~(3vY`@B5yur(qkMnP zWrgz=XSG^q?R)YUh6~!tGS_JiBZWmQL4*JqbyQG=g#@h{DJC+spY-q#Iew8`GPx>X z6 zg27-U0>K~zuqc95)PmF$s-m?{`!-$Mb=tLiY~9-VxawS2`}nc5?bfxkYwhanbavMH z)~cw*MtLbO%7|!e6av8zObCet4G=NH^T#b9)o$(cXvLQE`Q(%PKKFHB_my9M=UnGH z=enU^`lVm`rC)BSjEo;Q3y~zQFOJQpuMKGG{SW{5_=)5HIz&1ovHJAM4Sj8V-hGv2 zhDt+vUu7A6kY$}CY*9rLcGweUeg{HGj@~E`wt_u!C0|uwMFPQ$`@AUt@?2t#L10W7k+$#=L z+}9Td<#n?r(w6_j?FYWBkSh`Z{IZ`Y`f&4yYh8;MKj~V$_{raVhkTf52=IWuLW{k@yaw=m zU2;vFoVFUEIKPmHNCi%p9JwNa3(YnxCf(?be|cpAx-1ZF9S7^(+ zjwKT1jIVp&yeAhOSh8YS_jz%CAr_Oan`LrE0;#EE04Oaz{#|nV?rL$^5?U^X(Jofb z`A@ASA>#C%ySCMBes68y`d3ykEhB}dwkB5o<`)F}83^FwXF`we`4W}!h?tU3+yS(>u!@aSzuIQ{&5QP zkF)fDwvahF0RUsI(fpkWD@^*_V_gmni1{*5Vv)Ff@27jsyFS?w4sidhIW#r4u{lS> z;$<%akeZ%B+T_G*zynyb;t`svHeW0+`1R>EFVA`NyAX#RQ^aM_?lJ<5>Qa8D`E2Xn zPxqREq#5@L?yae@jStpu=En=>Ge28RX+a)utzHR0dPXA4e)Cv2%K#4M>$We3Bnx_G}?$4Gxwg8PpSsX+C)!xm?WQ5(9Tnh-T#< z_j}%5x9z4bqi)*L=*P=H+8iQRN{@bi6d>-Yd7~eBaM51&ef%bsRaLzA&RSwsDwaS0 zJO>XHk~uYvr=FS51CJ*oSIV%|+Ss>2!x5VcfVj9=;^JaCuy0@YGips%21y2!S7IQq z#6ZS`Xz%SG7hV90Z!42jjm^FXP@UFldg;q|&*Q!s*#O)(vnSeE^Tsl=rlpfLExlWp zJf(+e0K0bWX7=p+$hfP=k7eAI$jfi`j&l>nB>eui^I`Y>WYwR$z{Sv9eRoQC=#ZM8L1{rAr3HE2l-KObxu);jeZrq$tSjo)EqQhY`eVn5A3Nsirmous zuz^4(fb*0v>ye^Fog_w`BxcfLE0d;z`tB4~y!0-sUV6HlS@WmgLuo-CZFWl!(9^S7 z^75*y02BPx-aUf3=j`e6k?~21Z2#*gV$ zYXz2R^2*TUm7&f^VbYX^6z%&p0Gs!H20*C9AI-kKR{)!V)7eDszWw}a@$+3zVa7~# z&$4QG7&|s>La(hrf9yE&C<(xGz)b<{hK3bR>(Lxb!6(EIIKqizRk&MS1!L1=BcA%N zSzbn49Z@mQaXIY@pQOww8ny;D%@q)z7AaKvkZG- z)!OCh_hhsAy;WDM_he;djm^!?4Lq28us66>J7}uhjyhe5rohOg>Fb3H9{qLupE^b= zB+H7*m@#Kzw_Xt`n58T-qxXV-xO%1YtY%4{Wmusmr%IuWS+aUfPkihYcip*g#qaDKXWZ?Y@4oSx10m z1Obu}v^1aL;^iQL34Xx}mtFftUp7fsp(bbLx_8PWfaJH|TZk%Fj;XetnwlE2va*O8 z8O5RUi-@K1xX%epQculh^SZexBZFDBTuoA9966tsc6a*fLyGR>X=9``4N0c3sO%~e zaa|WMAjoskxzE$?tnbrHBX_Iylh_F9ko3Ezr@GIJhz%=Y^Oj;fu42;1oyXJK#4}Hf zq0ZWbxxvZiog2{~2W&Yv7{_ zU#2F=i5otYR&Vg`Y+g5FIAa|RmEHq7O!!fUA9XaGH3x&|pM8EUx#z&Pe*j{Dva)i1 z^uTa_wp5QIOiY8##V?kw!(1ZgXNbS#j8%NJAu}+FD&J#gH^= zLgwo92cH^HQ&!mQ>F`|mww6Y*^B$hEYu>|CI>lniPsC!$AFsq-J`_y=Txp)qm+yYj;x{M-i4y#^WMoK~;@BX8nGF{JadbcpD*C6hW z5kizagS$%=3W(zG7=tQK$;7lICZ;8!ic=znq?ymHd9$+Q;1j@B&C%D*Vv%^{O`jz< zytiiM?EA7_lt}dW2QLCx51@=zmKhDktBb4^LXeqD*7Ph2N}780Q&g&FVrqO(iYB-4 zs^bF6bgjMGrzn~zGX-~_AFdKW%7_;8+iVHi5%byp(MI$qkE1_%oXZ}eLg%nMYaMpy zVL%JSs3v3*J28vH2j2lOh(+R|Zjal3(@fGQF(a$We`vbD&mjKxmlNPU6ugJ>)Dx4_ zCZ~^A+wJEhc6-xNcLyCIL*ze@1j|G9Ul_UEUQC6~hM&lS&3>69+7^5UrxF}B7jOAI zJOl2>Q1KC3T?gjcQ+Paq$O7%Ov<*akM-i@;AWokwqv7HZ#34#rJ?Clj5%I(6$>e;| z$l48mBO)>kZMl{~frF^4HLbH+EO)lGy50oO#{ixdUK_4V@G9Q98Ik{B9PV)hgp6I- zY%6>9rkSKZzvz-aUs%IsWF=gENd`=hkBeRltOC%TsCZ6yLdYZ-YP~U>@xf5*&9sc3 zI5IpY6?2s^8%h+9;%>gS3ZyfsdNLD7glva8Wfba^QLI?EofYrxz-qAqkTfo->-e)? zsix7H52eq1#H(6w6r)^5Jp-O>1jwuUS!{N>~V>h)KXyr9%;Z8B?7%itx(-f0^^(oX3HH zo0bn-xpWJ8xw>wq#m7aX*Bj8EG60NC89Qbhzz3T)jZGMx*iwDQ2g4a3s;{tYLiS=N zWG|*9rzff5Zmz&-+IZdP=3jc$<9d8xa!N9mvlc98Edc6X6Ocsm zV5BM`Yx5f`1h8TR%P^UJP(*Z~2)~TE&iujn3FCi%!=1j^SC00ddMtGopcrY0ic9Vw z#DfIpNF_li!b1s^$>wg}`hI75xh_9BIiXQ~(H*dKrLgR&cO(%QkVA6%B+7Ga*s69z z#3QtyZ^PBz0{+opc@=r63`@OD2s=Up&>TNQiy{MMXcNAE3hb^HUf;Mw0BoQbe_!zG zaHCX&qP07F*cF>RZQ@WX6IVaO{ybre+41^INac}~X$^?QmpOT=kpPKu=nV%9z*1A* zeJl-8VQ)N5=pZoFSumL`)xb!A_ut<_a&p3CfULWd$vQ0DmvwhCKYjAw|C#bo85XMm zR&r7T#wsKAmB%qxRxp0%qiA#AC4F)_1^GgDwx-gE)8V8^>LgeyX}`L!#8mazE}*K< z2mybK%Sk|RG%Zd8009yuEl%U*8*Y-88f~~1yL#`5~+kJl@h>o<(9zLKP>tekkr-3qQ~YkGBOOA zavCj724u=<1W2L@kSK4C(@)M^FfciD0n!jv??rcs_OV`B2)lK+({A-jN=~4>yeBW8 zdxd2SmiHtOlF}xkKlBVivSfl}NnB{uVzt@`y#kiSVq^TwM+I}f!ZJmq0@E1*7tZw0(Ln@Wv9v}g++t2NGU(^0%xpsC!oN8B7qu^62PwTmF)*So;m8fSw$d0uy z6F)u!i!ry;k}o)8x4E7%2?{TOuy6^cS`(MNf~YPRGOgA+6SCpa1cy`uaG~WA31I@b zVPP@|lcD`=qgVY2ApsdO5Syk{J6d~8i)4uOHHlRESg>d(0YHER#Gy(qoWFprLDzA^ z0W;MYN)=Jj*V_f?yk2{o-SM%eu-FSg6&wF6;CH>Uu$!)=mwhgfvuV4)xXGzVgM5ip zMsmh@rpI4KB;csi;V|it1xKTZ8i}b!0PSZF2pQg^UkL46s~y;E4gintWaY5#L7sCw zu2iWWb-P-;#NwV>uWXnM-Ep1mhDZ7)V|Bra@}n!;THRX#b6cA`5D08(Y5iqqXJ_lb zyVv2ce?IZ9$uAWb72v2hW3NBMsMNcKfcWUA#3YU)CUF$5=JO2n^YM}dOK5gBbK=+u zh727}#Ar2b%{EkNk6=EP-woEhpDx0Bs)85a*-P0MCd$4rF;qU3fPjG*jpo0%w6^W) zHPibKjPezU5=A26XNyEW`1<^6b56bEobz{%bIu8lb52P|M@Qq= zyww1pKO*eR`J(hN+yAtZ^86Bjs`C62wE3kffPzB>m}&(-eJbsNSI2@bY?n}7cy))Og23yN5E#zKL%8nG>6&qe|b^&eP9*Q&{<8E=>zhlGt*xKro-foZ6;C8!pcTT+1EenZH{?JoFe`3jjtxj0;HHXs<(ByD1;2R?Ft!_KFTCEk@ z63sk|**FXMqt$GD-lnjw}j)+lpN@dcV+Z8O7u}P~{G0IL=jI#4;y|1sYj4ONDyuH1H ze0_bx{y)$06^kF1goZBj6^m83HCPLtUDY{rhPn%|KlR0Nif+Y2712p6frUW1j}KyF zwfsseh*fnjQ)X0F8K3QIfZdXJjS{9WNCt)jGu8Lb&Ah^rZ<1Zrp`0SKCZ`B>_9VKt z_$H1P_D)!APUJF4iM7%B^S%h!EqPy38FObGkZr80BWA>9tPQQ?YRp(0S_$%Rzot|!Wg&slvXH=$eHAdTZ~KAcXK#Ba zJbv!er8|ZEl|rtq)5YoAlcc{&dgN;AGN2gv_?D zTd1b{56ed4^9;d~0AJsLp(sYiVyZs`_T!T^>KGRrRkFmC%z+pnx)OSz}o|Y=wn{^BhmUgquT5oB0w|oBly^ZwU z&Z@G@m+Y;IaK9|Wu}_JKy%#Zd#MtrizDP~wVSN08@DUHjan6O^W|Fzxo|WJ0z_s$E z?lq}RW>wvhuu)$ygTRoqUe~oHg~%f!`{S^y#nEWn6d=#mwve+^1m){Gj_vVYM=lB N002ovPDHLkV1hnhB6a`( diff --git a/WebHostLib/static/static/icons/sc2/widowminehidden.png b/WebHostLib/static/static/icons/sc2/widowminehidden.png deleted file mode 100644 index e568742e8a50c5a1084e6d6de4a43309908f49de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12777 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 diff --git a/WebHostLib/static/styles/sc2Tracker.css b/WebHostLib/static/styles/sc2Tracker.css new file mode 100644 index 0000000000..29a719a110 --- /dev/null +++ b/WebHostLib/static/styles/sc2Tracker.css @@ -0,0 +1,160 @@ +#player-tracker-wrapper{ + margin: 0; +} + +#tracker-table td { + vertical-align: top; +} + +.inventory-table-area{ + border: 2px solid #000000; + border-radius: 4px; + padding: 3px 10px 3px 10px; +} + +.inventory-table-area:has(.inventory-table-terran) { + width: 690px; + background-color: #525494; +} + +.inventory-table-area:has(.inventory-table-zerg) { + width: 360px; + background-color: #9d60d2; +} + +.inventory-table-area:has(.inventory-table-protoss) { + width: 400px; + background-color: #d2b260; +} + +#tracker-table .inventory-table td{ + width: 40px; + height: 40px; + text-align: center; + vertical-align: middle; +} + +.inventory-table td.title{ + padding-top: 10px; + height: 20px; + font-family: "JuraBook", monospace; + font-size: 16px; + font-weight: bold; +} + +.inventory-table img{ + height: 100%; + max-width: 40px; + max-height: 40px; + border: 1px solid #000000; + filter: grayscale(100%) contrast(75%) brightness(20%); + background-color: black; +} + +.inventory-table img.acquired{ + filter: none; + background-color: black; +} + +.inventory-table .tint-terran img.acquired { + filter: sepia(100%) saturate(300%) brightness(130%) hue-rotate(120deg) +} + +.inventory-table .tint-protoss img.acquired { + filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(180deg) +} + +.inventory-table .tint-level-1 img.acquired { + filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(60deg) +} + +.inventory-table .tint-level-2 img.acquired { + filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(60deg) hue-rotate(120deg) +} + +.inventory-table .tint-level-3 img.acquired { + filter: sepia(100%) saturate(1000%) brightness(110%) hue-rotate(60deg) hue-rotate(240deg) +} + +.inventory-table div.counted-item { + position: relative; +} + +.inventory-table div.item-count { + width: 160px; + text-align: left; + color: black; + font-family: "JuraBook", monospace; + font-weight: bold; +} + +#location-table{ + border: 2px solid #000000; + border-radius: 4px; + background-color: #87b678; + padding: 10px 3px 3px; + font-family: "JuraBook", monospace; + font-size: 16px; + font-weight: bold; + cursor: default; +} + +#location-table table{ + width: 100%; +} + +#location-table th{ + vertical-align: middle; + text-align: left; + padding-right: 10px; +} + +#location-table td{ + padding-top: 2px; + padding-bottom: 2px; + line-height: 20px; +} + +#location-table td.counter { + text-align: right; + font-size: 14px; +} + +#location-table td.toggle-arrow { + text-align: right; +} + +#location-table tr#Total-header { + font-weight: bold; +} + +#location-table img{ + height: 100%; + max-width: 30px; + max-height: 30px; +} + +#location-table tbody.locations { + font-size: 16px; +} + +#location-table td.location-name { + padding-left: 16px; +} + +#location-table td:has(.location-column) { + vertical-align: top; +} + +#location-table .location-column { + width: 100%; + height: 100%; +} + +#location-table .location-column .spacer { + min-height: 24px; +} + +.hide { + display: none; +} diff --git a/WebHostLib/static/styles/sc2wolTracker.css b/WebHostLib/static/styles/sc2wolTracker.css deleted file mode 100644 index a7d8bd28c4..0000000000 --- a/WebHostLib/static/styles/sc2wolTracker.css +++ /dev/null @@ -1,112 +0,0 @@ -#player-tracker-wrapper{ - margin: 0; -} - -#inventory-table{ - border-top: 2px solid #000000; - border-left: 2px solid #000000; - border-right: 2px solid #000000; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - padding: 3px 3px 10px; - width: 710px; - background-color: #525494; -} - -#inventory-table td{ - width: 40px; - height: 40px; - text-align: center; - vertical-align: middle; -} - -#inventory-table td.title{ - padding-top: 10px; - height: 20px; - font-family: "JuraBook", monospace; - font-size: 16px; - font-weight: bold; -} - -#inventory-table img{ - height: 100%; - max-width: 40px; - max-height: 40px; - border: 1px solid #000000; - filter: grayscale(100%) contrast(75%) brightness(20%); - background-color: black; -} - -#inventory-table img.acquired{ - filter: none; - background-color: black; -} - -#inventory-table div.counted-item { - position: relative; -} - -#inventory-table div.item-count { - text-align: left; - color: black; - font-family: "JuraBook", monospace; - font-weight: bold; -} - -#location-table{ - width: 710px; - border-left: 2px solid #000000; - border-right: 2px solid #000000; - border-bottom: 2px solid #000000; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - background-color: #525494; - padding: 10px 3px 3px; - font-family: "JuraBook", monospace; - font-size: 16px; - font-weight: bold; - cursor: default; -} - -#location-table th{ - vertical-align: middle; - text-align: left; - padding-right: 10px; -} - -#location-table td{ - padding-top: 2px; - padding-bottom: 2px; - line-height: 20px; -} - -#location-table td.counter { - text-align: right; - font-size: 14px; -} - -#location-table td.toggle-arrow { - text-align: right; -} - -#location-table tr#Total-header { - font-weight: bold; -} - -#location-table img{ - height: 100%; - max-width: 30px; - max-height: 30px; -} - -#location-table tbody.locations { - font-size: 16px; -} - -#location-table td.location-name { - padding-left: 16px; -} - -.hide { - display: none; -} diff --git a/WebHostLib/templates/tracker__Starcraft2.html b/WebHostLib/templates/tracker__Starcraft2.html new file mode 100644 index 0000000000..b4252df250 --- /dev/null +++ b/WebHostLib/templates/tracker__Starcraft2.html @@ -0,0 +1,1090 @@ + +{% macro sc2_icon(name) -%} + +{% endmacro -%} +{% macro sc2_progressive_icon(name, url, level) -%} + +{% endmacro -%} +{% macro sc2_progressive_icon_with_custom_name(item_name, url, title) -%} + +{% endmacro -%} +{%+ macro sc2_tint_level(level) %} + tint-level-{{ level }} +{%+ endmacro %} +{% macro sc2_render_area(area) %} + + {{ area }} {{'▼' if area != 'Total'}} + {{ checks_done[area] }} / {{ checks_in_area[area] }} + + + {% for location in location_info[area] %} + + {{ location }} + {{ '✔' if location_info[area][location] else '' }} + + {% endfor %} + +{% endmacro -%} +{% macro sc2_loop_areas(column_index, column_count) %} + {% for area in checks_in_area if checks_in_area[area] > 0 and area != 'Total' %} + {% if loop.index0 < (loop.length / column_count) * (column_index + 1) + and loop.index0 >= (loop.length / column_count) * (column_index) %} + {{ sc2_render_area(area) }} + {% endif %} + {% endfor %} +{% endmacro -%} + + + {{ player_name }}'s Tracker + + + + + + + {# TODO: Replace this with a proper wrapper for each tracker when developing TrackerAPI. #} +

+ +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+

{{ player_name }}'s Starcraft 2 Tracker

+ Starting Resources +
+{{ minerals_count }}
+{{ vespene_count }}
+{{ supply_count }}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Terran +
+ Weapon & Armor Upgrades +
{{ sc2_progressive_icon('Progressive Terran Infantry Weapon', terran_infantry_weapon_url, terran_infantry_weapon_level) }}{{ sc2_progressive_icon('Progressive Terran Infantry Armor', terran_infantry_armor_url, terran_infantry_armor_level) }}{{ sc2_progressive_icon('Progressive Terran Vehicle Weapon', terran_vehicle_weapon_url, terran_vehicle_weapon_level) }}{{ sc2_progressive_icon('Progressive Terran Vehicle Armor', terran_vehicle_armor_url, terran_vehicle_armor_level) }}{{ sc2_progressive_icon('Progressive Terran Ship Weapon', terran_ship_weapon_url, terran_ship_weapon_level) }}{{ sc2_progressive_icon('Progressive Terran Ship Armor', terran_ship_armor_url, terran_ship_armor_level) }}{{ sc2_icon('Ultra-Capacitors') }}{{ sc2_icon('Vanadium Plating') }}
+ Base +
{{ sc2_icon('Bunker') }}{{ sc2_icon('Projectile Accelerator (Bunker)') }}{{ sc2_icon('Neosteel Bunker (Bunker)') }}{{ sc2_icon('Shrike Turret (Bunker)') }}{{ sc2_icon('Fortified Bunker (Bunker)') }}{{ sc2_icon('Missile Turret') }}{{ sc2_icon('Titanium Housing (Missile Turret)') }}{{ sc2_icon('Hellstorm Batteries (Missile Turret)') }}{{ sc2_icon('Tech Reactor') }}{{ sc2_icon('Orbital Depots') }}
{{ sc2_icon('Command Center Reactor') }}{{ sc2_progressive_icon_with_custom_name('Progressive Orbital Command', orbital_command_url, orbital_command_name) }}{{ sc2_icon('Planetary Fortress') }}{{ sc2_progressive_icon_with_custom_name('Progressive Augmented Thrusters (Planetary Fortress)', augmented_thrusters_planetary_fortress_url, augmented_thrusters_planetary_fortress_name) }}{{ sc2_icon('Advanced Targeting (Planetary Fortress)') }}{{ sc2_icon('Advanced Construction (SCV)') }}{{ sc2_icon('Dual-Fusion Welders (SCV)') }}{{ sc2_icon('Micro-Filtering') }}{{ sc2_icon('Automated Refinery') }}
{{ sc2_icon('Sensor Tower') }}{{ sc2_icon('Perdition Turret') }}{{ sc2_icon('Hive Mind Emulator') }}{{ sc2_icon('Psi Disrupter') }}
+ Infantry + + Vehicles +
{{ sc2_icon('Marine') }}{{ sc2_progressive_icon_with_custom_name('Progressive Stimpack (Marine)', stimpack_marine_url, stimpack_marine_name) }}{{ sc2_icon('Combat Shield (Marine)') }}{{ sc2_icon('Laser Targeting System (Marine)') }}{{ sc2_icon('Magrail Munitions (Marine)') }}{{ sc2_icon('Optimized Logistics (Marine)') }}{{ sc2_icon('Hellion') }}{{ sc2_icon('Twin-Linked Flamethrower (Hellion)') }}{{ sc2_icon('Thermite Filaments (Hellion)') }}{{ sc2_icon('Hellbat Aspect (Hellion)') }}{{ sc2_icon('Smart Servos (Hellion)') }}{{ sc2_icon('Optimized Logistics (Hellion)') }}{{ sc2_icon('Jump Jets (Hellion)') }}
{{ sc2_icon('Medic') }}{{ sc2_icon('Advanced Medic Facilities (Medic)') }}{{ sc2_icon('Stabilizer Medpacks (Medic)') }}{{ sc2_icon('Restoration (Medic)') }}{{ sc2_icon('Optical Flare (Medic)') }}{{ sc2_icon('Resource Efficiency (Medic)') }}{{ sc2_icon('Adaptive Medpacks (Medic)') }}{{ sc2_progressive_icon_with_custom_name('Progressive Stimpack (Hellion)', stimpack_hellion_url, stimpack_hellion_name) }}{{ sc2_icon('Infernal Plating (Hellion)') }}
{{ sc2_icon('Nano Projector (Medic)') }}{{ sc2_icon('Vulture') }}{{ sc2_progressive_icon_with_custom_name('Progressive Replenishable Magazine (Vulture)', replenishable_magazine_vulture_url, replenishable_magazine_vulture_name) }}{{ sc2_icon('Ion Thrusters (Vulture)') }}{{ sc2_icon('Auto Launchers (Vulture)') }}{{ sc2_icon('Auto-Repair (Vulture)') }}
{{ sc2_icon('Firebat') }}{{ sc2_icon('Incinerator Gauntlets (Firebat)') }}{{ sc2_icon('Juggernaut Plating (Firebat)') }}{{ sc2_progressive_icon_with_custom_name('Progressive Stimpack (Firebat)', stimpack_firebat_url, stimpack_firebat_name) }}{{ sc2_icon('Resource Efficiency (Firebat)') }}{{ sc2_icon('Infernal Pre-Igniter (Firebat)') }}{{ sc2_icon('Kinetic Foam (Firebat)') }}{{ sc2_icon('Cerberus Mine (Spider Mine)') }}{{ sc2_icon('High Explosive Munition (Spider Mine)') }}
{{ sc2_icon('Nano Projectors (Firebat)') }}{{ sc2_icon('Goliath') }}{{ sc2_icon('Multi-Lock Weapons System (Goliath)') }}{{ sc2_icon('Ares-Class Targeting System (Goliath)') }}{{ sc2_icon('Jump Jets (Goliath)') }}{{ sc2_icon('Shaped Hull (Goliath)') }}{{ sc2_icon('Optimized Logistics (Goliath)') }}{{ sc2_icon('Resource Efficiency (Goliath)') }}
{{ sc2_icon('Marauder') }}{{ sc2_icon('Concussive Shells (Marauder)') }}{{ sc2_icon('Kinetic Foam (Marauder)') }}{{ sc2_progressive_icon_with_custom_name('Progressive Stimpack (Marauder)', stimpack_marauder_url, stimpack_marauder_name) }}{{ sc2_icon('Laser Targeting System (Marauder)') }}{{ sc2_icon('Magrail Munitions (Marauder)') }}{{ sc2_icon('Internal Tech Module (Marauder)') }}{{ sc2_icon('Internal Tech Module (Goliath)') }}
{{ sc2_icon('Juggernaut Plating (Marauder)') }}{{ sc2_icon('Diamondback') }}{{ sc2_progressive_icon_with_custom_name('Progressive Tri-Lithium Power Cell (Diamondback)', trilithium_power_cell_diamondback_url, trilithium_power_cell_diamondback_name) }}{{ sc2_icon('Shaped Hull (Diamondback)') }}{{ sc2_icon('Hyperfluxor (Diamondback)') }}{{ sc2_icon('Burst Capacitors (Diamondback)') }}{{ sc2_icon('Ion Thrusters (Diamondback)') }}{{ sc2_icon('Resource Efficiency (Diamondback)') }}
{{ sc2_icon('Reaper') }}{{ sc2_icon('U-238 Rounds (Reaper)') }}{{ sc2_icon('G-4 Clusterbomb (Reaper)') }}{{ sc2_progressive_icon_with_custom_name('Progressive Stimpack (Reaper)', stimpack_reaper_url, stimpack_reaper_name) }}{{ sc2_icon('Laser Targeting System (Reaper)') }}{{ sc2_icon('Advanced Cloaking Field (Reaper)') }}{{ sc2_icon('Spider Mines (Reaper)') }}{{ sc2_icon('Siege Tank') }}{{ sc2_icon('Maelstrom Rounds (Siege Tank)') }}{{ sc2_icon('Shaped Blast (Siege Tank)') }}{{ sc2_icon('Jump Jets (Siege Tank)') }}{{ sc2_icon('Spider Mines (Siege Tank)') }}{{ sc2_icon('Smart Servos (Siege Tank)') }}{{ sc2_icon('Graduating Range (Siege Tank)') }}
{{ sc2_icon('Combat Drugs (Reaper)') }}{{ sc2_icon('Jet Pack Overdrive (Reaper)') }}{{ sc2_icon('Laser Targeting System (Siege Tank)') }}{{ sc2_icon('Advanced Siege Tech (Siege Tank)') }}{{ sc2_icon('Internal Tech Module (Siege Tank)') }}{{ sc2_icon('Shaped Hull (Siege Tank)') }}{{ sc2_icon('Resource Efficiency (Siege Tank)') }}
{{ sc2_icon('Ghost') }}{{ sc2_icon('Ocular Implants (Ghost)') }}{{ sc2_icon('Crius Suit (Ghost)') }}{{ sc2_icon('EMP Rounds (Ghost)') }}{{ sc2_icon('Lockdown (Ghost)') }}{{ sc2_icon('Resource Efficiency (Ghost)') }}{{ sc2_icon('Thor') }}{{ sc2_icon('330mm Barrage Cannon (Thor)') }}{{ sc2_progressive_icon_with_custom_name('Progressive Immortality Protocol (Thor)', immortality_protocol_thor_url, immortality_protocol_thor_name) }}{{ sc2_progressive_icon_with_custom_name('Progressive High Impact Payload (Thor)', high_impact_payload_thor_url, high_impact_payload_thor_name) }}{{ sc2_icon('Button With a Skull on It (Thor)') }}{{ sc2_icon('Laser Targeting System (Thor)') }}{{ sc2_icon('Large Scale Field Construction (Thor)') }}
{{ sc2_icon('Spectre') }}{{ sc2_icon('Psionic Lash (Spectre)') }}{{ sc2_icon('Nyx-Class Cloaking Module (Spectre)') }}{{ sc2_icon('Impaler Rounds (Spectre)') }}{{ sc2_icon('Resource Efficiency (Spectre)') }}{{ sc2_icon('Predator') }}{{ sc2_icon('Resource Efficiency (Predator)') }}{{ sc2_icon('Cloak (Predator)') }}{{ sc2_icon('Charge (Predator)') }}{{ sc2_icon('Predator\'s Fury (Predator)') }}
{{ sc2_icon('HERC') }}{{ sc2_icon('Juggernaut Plating (HERC)') }}{{ sc2_icon('Kinetic Foam (HERC)') }}{{ sc2_icon('Widow Mine') }}{{ sc2_icon('Drilling Claws (Widow Mine)') }}{{ sc2_icon('Concealment (Widow Mine)') }}{{ sc2_icon('Black Market Launchers (Widow Mine)') }}{{ sc2_icon('Executioner Missiles (Widow Mine)') }}
{{ sc2_icon('Cyclone') }}{{ sc2_icon('Mag-Field Accelerators (Cyclone)') }}{{ sc2_icon('Mag-Field Launchers (Cyclone)') }}{{ sc2_icon('Targeting Optics (Cyclone)') }}{{ sc2_icon('Rapid Fire Launchers (Cyclone)') }}{{ sc2_icon('Resource Efficiency (Cyclone)') }}{{ sc2_icon('Internal Tech Module (Cyclone)') }}
{{ sc2_icon('Warhound') }}{{ sc2_icon('Resource Efficiency (Warhound)') }}{{ sc2_icon('Reinforced Plating (Warhound)') }}
+ Starships +
{{ sc2_icon('Medivac') }}{{ sc2_icon('Rapid Deployment Tube (Medivac)') }}{{ sc2_icon('Advanced Healing AI (Medivac)') }}{{ sc2_icon('Expanded Hull (Medivac)') }}{{ sc2_icon('Afterburners (Medivac)') }}{{ sc2_icon('Scatter Veil (Medivac)') }}{{ sc2_icon('Advanced Cloaking Field (Medivac)') }}{{ sc2_icon('Raven') }}{{ sc2_icon('Bio Mechanical Repair Drone (Raven)') }}{{ sc2_icon('Spider Mines (Raven)') }}{{ sc2_icon('Railgun Turret (Raven)') }}{{ sc2_icon('Hunter-Seeker Weapon (Raven)') }}{{ sc2_icon('Interference Matrix (Raven)') }}{{ sc2_icon('Anti-Armor Missile (Raven)') }}
{{ sc2_icon('Wraith') }}{{ sc2_progressive_icon_with_custom_name('Progressive Tomahawk Power Cells (Wraith)', tomahawk_power_cells_wraith_url, tomahawk_power_cells_wraith_name) }}{{ sc2_icon('Displacement Field (Wraith)') }}{{ sc2_icon('Advanced Laser Technology (Wraith)') }}{{ sc2_icon('Trigger Override (Wraith)') }}{{ sc2_icon('Internal Tech Module (Wraith)') }}{{ sc2_icon('Resource Efficiency (Wraith)') }}{{ sc2_icon('Internal Tech Module (Raven)') }}{{ sc2_icon('Resource Efficiency (Raven)') }}{{ sc2_icon('Durable Materials (Raven)') }}
{{ sc2_icon('Viking') }}{{ sc2_icon('Ripwave Missiles (Viking)') }}{{ sc2_icon('Phobos-Class Weapons System (Viking)') }}{{ sc2_icon('Smart Servos (Viking)') }}{{ sc2_icon('Anti-Mechanical Munition (Viking)') }}{{ sc2_icon('Shredder Rounds (Viking)') }}{{ sc2_icon('W.I.L.D. Missiles (Viking)') }}{{ sc2_icon('Science Vessel') }}{{ sc2_icon('EMP Shockwave (Science Vessel)') }}{{ sc2_icon('Defensive Matrix (Science Vessel)') }}{{ sc2_icon('Improved Nano-Repair (Science Vessel)') }}{{ sc2_icon('Advanced AI Systems (Science Vessel)') }}
{{ sc2_icon('Banshee') }}{{ sc2_progressive_icon_with_custom_name('Progressive Cross-Spectrum Dampeners (Banshee)', crossspectrum_dampeners_banshee_url, crossspectrum_dampeners_banshee_name) }}{{ sc2_icon('Shockwave Missile Battery (Banshee)') }}{{ sc2_icon('Hyperflight Rotors (Banshee)') }}{{ sc2_icon('Laser Targeting System (Banshee)') }}{{ sc2_icon('Internal Tech Module (Banshee)') }}{{ sc2_icon('Shaped Hull (Banshee)') }}{{ sc2_icon('Hercules') }}{{ sc2_icon('Internal Fusion Module (Hercules)') }}{{ sc2_icon('Tactical Jump (Hercules)') }}
{{ sc2_icon('Advanced Targeting Optics (Banshee)') }}{{ sc2_icon('Distortion Blasters (Banshee)') }}{{ sc2_icon('Rocket Barrage (Banshee)') }}{{ sc2_icon('Liberator') }}{{ sc2_icon('Advanced Ballistics (Liberator)') }}{{ sc2_icon('Raid Artillery (Liberator)') }}{{ sc2_icon('Cloak (Liberator)') }}{{ sc2_icon('Laser Targeting System (Liberator)') }}{{ sc2_icon('Optimized Logistics (Liberator)') }}{{ sc2_icon('Smart Servos (Liberator)') }}
{{ sc2_icon('Battlecruiser') }}{{ sc2_progressive_icon('Progressive Missile Pods (Battlecruiser)', missile_pods_battlecruiser_url, missile_pods_battlecruiser_level) }}{{ sc2_progressive_icon_with_custom_name('Progressive Defensive Matrix (Battlecruiser)', defensive_matrix_battlecruiser_url, defensive_matrix_battlecruiser_name) }}{{ sc2_icon('Tactical Jump (Battlecruiser)') }}{{ sc2_icon('Cloak (Battlecruiser)') }}{{ sc2_icon('ATX Laser Battery (Battlecruiser)') }}{{ sc2_icon('Optimized Logistics (Battlecruiser)') }}{{ sc2_icon('Resource Efficiency (Liberator)') }}
{{ sc2_icon('Internal Tech Module (Battlecruiser)') }}{{ sc2_icon('Behemoth Plating (Battlecruiser)') }}{{ sc2_icon('Covert Ops Engines (Battlecruiser)') }}{{ sc2_icon('Valkyrie') }}{{ sc2_icon('Enhanced Cluster Launchers (Valkyrie)') }}{{ sc2_icon('Shaped Hull (Valkyrie)') }}{{ sc2_icon('Flechette Missiles (Valkyrie)') }}{{ sc2_icon('Afterburners (Valkyrie)') }}{{ sc2_icon('Launching Vector Compensator (Valkyrie)') }}{{ sc2_icon('Resource Efficiency (Valkyrie)') }}
+ Mercenaries +
{{ sc2_icon('War Pigs') }}{{ sc2_icon('Devil Dogs') }}{{ sc2_icon('Hammer Securities') }}{{ sc2_icon('Spartan Company') }}{{ sc2_icon('Siege Breakers') }}{{ sc2_icon('Hel\'s Angels') }}{{ sc2_icon('Dusk Wings') }}{{ sc2_icon('Jackson\'s Revenge') }}{{ sc2_icon('Skibi\'s Angels') }}{{ sc2_icon('Death Heads') }}{{ sc2_icon('Winged Nightmares') }}{{ sc2_icon('Midnight Riders') }}{{ sc2_icon('Brynhilds') }}{{ sc2_icon('Jotun') }}
+ General Upgrades +
{{ sc2_progressive_icon('Progressive Fire-Suppression System', firesuppression_system_url, firesuppression_system_level) }}{{ sc2_icon('Orbital Strike') }}{{ sc2_icon('Cellular Reactor') }}{{ sc2_progressive_icon('Progressive Regenerative Bio-Steel', regenerative_biosteel_url, regenerative_biosteel_level) }}{{ sc2_icon('Structure Armor') }}{{ sc2_icon('Hi-Sec Auto Tracking') }}{{ sc2_icon('Advanced Optics') }}{{ sc2_icon('Rogue Forces') }}
+ Nova Equipment +
{{ sc2_icon('C20A Canister Rifle (Nova Weapon)') }}{{ sc2_icon('Hellfire Shotgun (Nova Weapon)') }}{{ sc2_icon('Plasma Rifle (Nova Weapon)') }}{{ sc2_icon('Monomolecular Blade (Nova Weapon)') }}{{ sc2_icon('Blazefire Gunblade (Nova Weapon)') }}{{ sc2_icon('Stim Infusion (Nova Gadget)') }}{{ sc2_icon('Pulse Grenades (Nova Gadget)') }}{{ sc2_icon('Flashbang Grenades (Nova Gadget)') }}{{ sc2_icon('Ionic Force Field (Nova Gadget)') }}{{ sc2_icon('Holo Decoy (Nova Gadget)') }}
{{ sc2_progressive_icon_with_custom_name('Progressive Stealth Suit Module (Nova Suit Module)', stealth_suit_module_nova_suit_module_url, stealth_suit_module_nova_suit_module_name) }}{{ sc2_icon('Energy Suit Module (Nova Suit Module)') }}{{ sc2_icon('Armored Suit Module (Nova Suit Module)') }}{{ sc2_icon('Jump Suit Module (Nova Suit Module)') }}{{ sc2_icon('Ghost Visor (Nova Equipment)') }}{{ sc2_icon('Rangefinder Oculus (Nova Equipment)') }}{{ sc2_icon('Domination (Nova Ability)') }}{{ sc2_icon('Blink (Nova Ability)') }}{{ sc2_icon('Tac Nuke Strike (Nova Ability)') }}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Zerg +
+ Weapon & Armor Upgrades +
{{ sc2_progressive_icon('Progressive Zerg Melee Attack', zerg_melee_attack_url, zerg_melee_attack_level) }}{{ sc2_progressive_icon('Progressive Zerg Missile Attack', zerg_missile_attack_url, zerg_missile_attack_level) }}{{ sc2_progressive_icon('Progressive Zerg Ground Carapace', zerg_ground_carapace_url, zerg_ground_carapace_level) }}{{ sc2_progressive_icon('Progressive Zerg Flyer Attack', zerg_flyer_attack_url, zerg_flyer_attack_level) }}{{ sc2_progressive_icon('Progressive Zerg Flyer Carapace', zerg_flyer_carapace_url, zerg_flyer_carapace_level) }}
+ Base +
{{ sc2_icon('Automated Extractors (Kerrigan Tier 3)') }}{{ sc2_icon('Vespene Efficiency (Kerrigan Tier 5)') }}{{ sc2_icon('Twin Drones (Kerrigan Tier 5)') }}{{ sc2_icon('Improved Overlords (Kerrigan Tier 3)') }}{{ sc2_icon('Ventral Sacs (Overlord)') }}
{{ sc2_icon('Malignant Creep (Kerrigan Tier 5)') }}{{ sc2_icon('Spine Crawler') }}{{ sc2_icon('Spore Crawler') }}
+ Units +
{{ sc2_icon('Zergling') }}{{ sc2_icon('Raptor Strain (Zergling)') }}{{ sc2_icon('Swarmling Strain (Zergling)') }}{{ sc2_icon('Hardened Carapace (Zergling)') }}{{ sc2_icon('Adrenal Overload (Zergling)') }}{{ sc2_icon('Metabolic Boost (Zergling)') }}{{ sc2_icon('Shredding Claws (Zergling)') }}{{ sc2_icon('Zergling Reconstitution (Kerrigan Tier 3)') }}
{{ sc2_icon('Baneling Aspect (Zergling)') }}{{ sc2_icon('Splitter Strain (Baneling)') }}{{ sc2_icon('Hunter Strain (Baneling)') }}{{ sc2_icon('Corrosive Acid (Baneling)') }}{{ sc2_icon('Rupture (Baneling)') }}{{ sc2_icon('Regenerative Acid (Baneling)') }}{{ sc2_icon('Centrifugal Hooks (Baneling)') }}
{{ sc2_icon('Tunneling Jaws (Baneling)') }}{{ sc2_icon('Rapid Metamorph (Baneling)') }}
{{ sc2_icon('Swarm Queen') }}{{ sc2_icon('Spawn Larvae (Swarm Queen)') }}{{ sc2_icon('Deep Tunnel (Swarm Queen)') }}{{ sc2_icon('Organic Carapace (Swarm Queen)') }}{{ sc2_icon('Bio-Mechanical Transfusion (Swarm Queen)') }}{{ sc2_icon('Resource Efficiency (Swarm Queen)') }}{{ sc2_icon('Incubator Chamber (Swarm Queen)') }}
{{ sc2_icon('Roach') }}{{ sc2_icon('Vile Strain (Roach)') }}{{ sc2_icon('Corpser Strain (Roach)') }}{{ sc2_icon('Hydriodic Bile (Roach)') }}{{ sc2_icon('Adaptive Plating (Roach)') }}{{ sc2_icon('Tunneling Claws (Roach)') }}{{ sc2_icon('Glial Reconstitution (Roach)') }}{{ sc2_icon('Organic Carapace (Roach)') }}
{{ sc2_icon('Ravager Aspect (Roach)') }}{{ sc2_icon('Potent Bile (Ravager)') }}{{ sc2_icon('Bloated Bile Ducts (Ravager)') }}{{ sc2_icon('Deep Tunnel (Ravager)') }}
{{ sc2_icon('Hydralisk') }}{{ sc2_icon('Frenzy (Hydralisk)') }}{{ sc2_icon('Ancillary Carapace (Hydralisk)') }}{{ sc2_icon('Grooved Spines (Hydralisk)') }}{{ sc2_icon('Muscular Augments (Hydralisk)') }}{{ sc2_icon('Resource Efficiency (Hydralisk)') }}
{{ sc2_icon('Impaler Aspect (Hydralisk)') }}{{ sc2_icon('Adaptive Talons (Impaler)') }}{{ sc2_icon('Secretion Glands (Impaler)') }}{{ sc2_icon('Hardened Tentacle Spines (Impaler)') }}
{{ sc2_icon('Lurker Aspect (Hydralisk)') }}{{ sc2_icon('Seismic Spines (Lurker)') }}{{ sc2_icon('Adapted Spines (Lurker)') }}
{{ sc2_icon('Aberration') }}
{{ sc2_icon('Swarm Host') }}{{ sc2_icon('Carrion Strain (Swarm Host)') }}{{ sc2_icon('Creeper Strain (Swarm Host)') }}{{ sc2_icon('Burrow (Swarm Host)') }}{{ sc2_icon('Rapid Incubation (Swarm Host)') }}{{ sc2_icon('Pressurized Glands (Swarm Host)') }}{{ sc2_icon('Locust Metabolic Boost (Swarm Host)') }}{{ sc2_icon('Enduring Locusts (Swarm Host)') }}
{{ sc2_icon('Organic Carapace (Swarm Host)') }}{{ sc2_icon('Resource Efficiency (Swarm Host)') }}
{{ sc2_icon('Infestor') }}{{ sc2_icon('Infested Terran (Infestor)') }}{{ sc2_icon('Microbial Shroud (Infestor)') }}
{{ sc2_icon('Defiler') }}
{{ sc2_icon('Ultralisk') }}{{ sc2_icon('Noxious Strain (Ultralisk)') }}{{ sc2_icon('Torrasque Strain (Ultralisk)') }}{{ sc2_icon('Burrow Charge (Ultralisk)') }}{{ sc2_icon('Tissue Assimilation (Ultralisk)') }}{{ sc2_icon('Monarch Blades (Ultralisk)') }}{{ sc2_icon('Anabolic Synthesis (Ultralisk)') }}{{ sc2_icon('Chitinous Plating (Ultralisk)') }}
{{ sc2_icon('Organic Carapace (Ultralisk)') }}{{ sc2_icon('Resource Efficiency (Ultralisk)') }}
{{ sc2_icon('Mutalisk') }}{{ sc2_icon('Rapid Regeneration (Mutalisk)') }}{{ sc2_icon('Sundering Glaive (Mutalisk)') }}{{ sc2_icon('Vicious Glaive (Mutalisk)') }}{{ sc2_icon('Severing Glaive (Mutalisk)') }}{{ sc2_icon('Aerodynamic Glaive Shape (Mutalisk)') }}
{{ sc2_icon('Corruptor') }}{{ sc2_icon('Corruption (Corruptor)') }}{{ sc2_icon('Caustic Spray (Corruptor)') }}
{{ sc2_icon('Brood Lord Aspect (Mutalisk/Corruptor)') }}{{ sc2_icon('Porous Cartilage (Brood Lord)') }}{{ sc2_icon('Evolved Carapace (Brood Lord)') }}{{ sc2_icon('Splitter Mitosis (Brood Lord)') }}{{ sc2_icon('Resource Efficiency (Brood Lord)') }}
{{ sc2_icon('Viper Aspect (Mutalisk/Corruptor)') }}{{ sc2_icon('Parasitic Bomb (Viper)') }}{{ sc2_icon('Paralytic Barbs (Viper)') }}{{ sc2_icon('Virulent Microbes (Viper)') }}
{{ sc2_icon('Guardian Aspect (Mutalisk/Corruptor)') }}{{ sc2_icon('Prolonged Dispersion (Guardian)') }}{{ sc2_icon('Primal Adaptation (Guardian)') }}{{ sc2_icon('Soronan Acid (Guardian)') }}
{{ sc2_icon('Devourer Aspect (Mutalisk/Corruptor)') }}{{ sc2_icon('Corrosive Spray (Devourer)') }}{{ sc2_icon('Gaping Maw (Devourer)') }}{{ sc2_icon('Improved Osmosis (Devourer)') }}{{ sc2_icon('Prescient Spores (Devourer)') }}
{{ sc2_icon('Brood Queen') }}{{ sc2_icon('Fungal Growth (Brood Queen)') }}{{ sc2_icon('Ensnare (Brood Queen)') }}{{ sc2_icon('Enhanced Mitochondria (Brood Queen)') }}
{{ sc2_icon('Scourge') }}{{ sc2_icon('Virulent Spores (Scourge)') }}{{ sc2_icon('Resource Efficiency (Scourge)') }}{{ sc2_icon('Swarm Scourge (Scourge)') }}
+ Mercenaries +
{{ sc2_icon('Infested Medics') }}{{ sc2_icon('Infested Siege Tanks') }}{{ sc2_icon('Infested Banshees') }}
+ Kerrigan +
Level: {{ kerrigan_level }}
{{ sc2_icon('Primal Form (Kerrigan)') }}
{{ sc2_icon('Kinetic Blast (Kerrigan Tier 1)') }}{{ sc2_icon('Heroic Fortitude (Kerrigan Tier 1)') }}{{ sc2_icon('Leaping Strike (Kerrigan Tier 1)') }}{{ sc2_icon('Crushing Grip (Kerrigan Tier 2)') }}{{ sc2_icon('Chain Reaction (Kerrigan Tier 2)') }}{{ sc2_icon('Psionic Shift (Kerrigan Tier 2)') }}
{{ sc2_icon('Wild Mutation (Kerrigan Tier 4)') }}{{ sc2_icon('Spawn Banelings (Kerrigan Tier 4)') }}{{ sc2_icon('Mend (Kerrigan Tier 4)') }}{{ sc2_icon('Infest Broodlings (Kerrigan Tier 6)') }}{{ sc2_icon('Fury (Kerrigan Tier 6)') }}{{ sc2_icon('Ability Efficiency (Kerrigan Tier 6)') }}
{{ sc2_icon('Apocalypse (Kerrigan Tier 7)') }}{{ sc2_icon('Spawn Leviathan (Kerrigan Tier 7)') }}{{ sc2_icon('Drop-Pods (Kerrigan Tier 7)') }}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Protoss +
+ Weapon & Armor Upgrades +
{{ sc2_progressive_icon('Progressive Protoss Ground Weapon', protoss_ground_weapon_url, protoss_ground_weapon_level) }}{{ sc2_progressive_icon('Progressive Protoss Ground Armor', protoss_ground_armor_url, protoss_ground_armor_level) }}{{ sc2_progressive_icon('Progressive Protoss Air Weapon', protoss_air_weapon_url, protoss_air_weapon_level) }}{{ sc2_progressive_icon('Progressive Protoss Air Armor', protoss_air_armor_url, protoss_air_armor_level) }}{{ sc2_progressive_icon('Progressive Protoss Shields', protoss_shields_url, protoss_shields_level) }}{{ sc2_icon('Quatro') }}
+ Base +
{{ sc2_icon('Photon Cannon') }}{{ sc2_icon('Khaydarin Monolith') }}{{ sc2_icon('Shield Battery') }}{{ sc2_icon('Enhanced Targeting') }}{{ sc2_icon('Optimized Ordnance') }}{{ sc2_icon('Khalai Ingenuity') }}{{ sc2_icon('Orbital Assimilators') }}{{ sc2_icon('Amplified Assimilators') }}
{{ sc2_icon('Warp Harmonization') }}{{ sc2_icon('Superior Warp Gates') }}{{ sc2_icon('Nexus Overcharge') }}
+ Gateway +
{{ sc2_icon('Zealot') }}{{ sc2_icon('Centurion') }}{{ sc2_icon('Sentinel') }}{{ sc2_icon('Leg Enhancements (Zealot/Sentinel/Centurion)') }}{{ sc2_icon('Shield Capacity (Zealot/Sentinel/Centurion)') }}
{{ sc2_icon('Supplicant') }}{{ sc2_icon('Blood Shield (Supplicant)') }}{{ sc2_icon('Soul Augmentation (Supplicant)') }}{{ sc2_icon('Shield Regeneration (Supplicant)') }}
{{ sc2_icon('Sentry') }}{{ sc2_icon('Force Field (Sentry)') }}{{ sc2_icon('Hallucination (Sentry)') }}
{{ sc2_icon('Energizer') }}{{ sc2_icon('Reclamation (Energizer)') }}{{ sc2_icon('Forged Chassis (Energizer)') }}{{ sc2_icon('Cloaking Module (Sentry/Energizer/Havoc)') }}{{ sc2_icon('Rapid Recharging (Sentry/Energizer/Havoc/Shield Battery)') }}
{{ sc2_icon('Havoc') }}{{ sc2_icon('Detect Weakness (Havoc)') }}{{ sc2_icon('Bloodshard Resonance (Havoc)') }}
{{ sc2_icon('Stalker') }}{{ sc2_icon('Instigator') }}{{ sc2_icon('Slayer') }}{{ sc2_icon('Disintegrating Particles (Stalker/Instigator/Slayer)') }}{{ sc2_icon('Particle Reflection (Stalker/Instigator/Slayer)') }}
{{ sc2_icon('Dragoon') }}{{ sc2_icon('High Impact Phase Disruptor (Dragoon)') }}{{ sc2_icon('Trillic Compression System (Dragoon)') }}{{ sc2_icon('Singularity Charge (Dragoon)') }}{{ sc2_icon('Enhanced Strider Servos (Dragoon)') }}
{{ sc2_icon('Adept') }}{{ sc2_icon('Shockwave (Adept)') }}{{ sc2_icon('Resonating Glaives (Adept)') }}{{ sc2_icon('Phase Bulwark (Adept)') }}
{{ sc2_icon('High Templar') }}{{ sc2_icon('Signifier') }}{{ sc2_icon('Unshackled Psionic Storm (High Templar/Signifier)') }}{{ sc2_icon('Hallucination (High Templar/Signifier)') }}{{ sc2_icon('Khaydarin Amulet (High Templar/Signifier)') }}{{ sc2_icon('High Archon (Archon)') }}
{{ sc2_icon('Ascendant') }}{{ sc2_icon('Power Overwhelming (Ascendant)') }}{{ sc2_icon('Chaotic Attunement (Ascendant)') }}{{ sc2_icon('Blood Amulet (Ascendant)') }}
{{ sc2_icon('Dark Archon') }}{{ sc2_icon('Feedback (Dark Archon)') }}{{ sc2_icon('Maelstrom (Dark Archon)') }}{{ sc2_icon('Argus Talisman (Dark Archon)') }}
{{ sc2_icon('Dark Templar') }}{{ sc2_icon('Dark Archon Meld (Dark Templar)') }}
{{ sc2_icon('Avenger') }}{{ sc2_icon('Blood Hunter') }}{{ sc2_icon('Shroud of Adun (Dark Templar/Avenger/Blood Hunter)') }}{{ sc2_icon('Shadow Guard Training (Dark Templar/Avenger/Blood Hunter)') }}{{ sc2_icon('Blink (Dark Templar/Avenger/Blood Hunter)') }}{{ sc2_icon('Resource Efficiency (Dark Templar/Avenger/Blood Hunter)') }}
+ Robotics Facility +
{{ sc2_icon('Warp Prism') }}{{ sc2_icon('Gravitic Drive (Warp Prism)') }}{{ sc2_icon('Phase Blaster (Warp Prism)') }}{{ sc2_icon('War Configuration (Warp Prism)') }}
{{ sc2_icon('Immortal') }}{{ sc2_icon('Annihilator') }}{{ sc2_icon('Singularity Charge (Immortal/Annihilator)') }}{{ sc2_icon('Advanced Targeting Mechanics (Immortal/Annihilator)') }}
{{ sc2_icon('Vanguard') }}{{ sc2_icon('Agony Launchers (Vanguard)') }}{{ sc2_icon('Matter Dispersion (Vanguard)') }}
{{ sc2_icon('Colossus') }}{{ sc2_icon('Pacification Protocol (Colossus)') }}
{{ sc2_icon('Wrathwalker') }}{{ sc2_icon('Rapid Power Cycling (Wrathwalker)') }}{{ sc2_icon('Eye of Wrath (Wrathwalker)') }}
{{ sc2_icon('Observer') }}{{ sc2_icon('Gravitic Boosters (Observer)') }}{{ sc2_icon('Sensor Array (Observer)') }}
{{ sc2_icon('Reaver') }}{{ sc2_icon('Scarab Damage (Reaver)') }}{{ sc2_icon('Solarite Payload (Reaver)') }}{{ sc2_icon('Reaver Capacity (Reaver)') }}{{ sc2_icon('Resource Efficiency (Reaver)') }}
{{ sc2_icon('Disruptor') }}
+ Stargate +
{{ sc2_icon('Phoenix') }}{{ sc2_icon('Mirage') }}{{ sc2_icon('Ionic Wavelength Flux (Phoenix/Mirage)') }}{{ sc2_icon('Anion Pulse-Crystals (Phoenix/Mirage)') }}
{{ sc2_icon('Corsair') }}{{ sc2_icon('Stealth Drive (Corsair)') }}{{ sc2_icon('Argus Jewel (Corsair)') }}{{ sc2_icon('Sustaining Disruption (Corsair)') }}{{ sc2_icon('Neutron Shields (Corsair)') }}
{{ sc2_icon('Destroyer') }}{{ sc2_icon('Reforged Bloodshard Core (Destroyer)') }}
{{ sc2_icon('Void Ray') }}{{ sc2_icon('Flux Vanes (Void Ray/Destroyer)') }}
{{ sc2_icon('Carrier') }}{{ sc2_icon('Graviton Catapult (Carrier)') }}{{ sc2_icon('Hull of Past Glories (Carrier)') }}
{{ sc2_icon('Scout') }}{{ sc2_icon('Combat Sensor Array (Scout)') }}{{ sc2_icon('Apial Sensors (Scout)') }}{{ sc2_icon('Gravitic Thrusters (Scout)') }}{{ sc2_icon('Advanced Photon Blasters (Scout)') }}
{{ sc2_icon('Tempest') }}{{ sc2_icon('Tectonic Destabilizers (Tempest)') }}{{ sc2_icon('Quantic Reactor (Tempest)') }}{{ sc2_icon('Gravity Sling (Tempest)') }}
{{ sc2_icon('Mothership') }}
{{ sc2_icon('Arbiter') }}{{ sc2_icon('Chronostatic Reinforcement (Arbiter)') }}{{ sc2_icon('Khaydarin Core (Arbiter)') }}{{ sc2_icon('Spacetime Anchor (Arbiter)') }}{{ sc2_icon('Resource Efficiency (Arbiter)') }}{{ sc2_icon('Enhanced Cloak Field (Arbiter)') }}
{{ sc2_icon('Oracle') }}{{ sc2_icon('Stealth Drive (Oracle)') }}{{ sc2_icon('Stasis Calibration (Oracle)') }}{{ sc2_icon('Temporal Acceleration Beam (Oracle)') }}
+ General Upgrades +
{{ sc2_icon('Matrix Overload') }}{{ sc2_icon('Guardian Shell') }}
+ Spear of Adun +
{{ sc2_icon('Chrono Surge (Spear of Adun Calldown)') }}{{ sc2_progressive_icon_with_custom_name('Progressive Proxy Pylon (Spear of Adun Calldown)', proxy_pylon_spear_of_adun_calldown_url, proxy_pylon_spear_of_adun_calldown_name) }}{{ sc2_icon('Pylon Overcharge (Spear of Adun Calldown)') }}{{ sc2_icon('Mass Recall (Spear of Adun Calldown)') }}{{ sc2_icon('Shield Overcharge (Spear of Adun Calldown)') }}{{ sc2_icon('Deploy Fenix (Spear of Adun Calldown)') }}{{ sc2_icon('Reconstruction Beam (Spear of Adun Auto-Cast)') }}
{{ sc2_icon('Orbital Strike (Spear of Adun Calldown)') }}{{ sc2_icon('Temporal Field (Spear of Adun Calldown)') }}{{ sc2_icon('Solar Lance (Spear of Adun Calldown)') }}{{ sc2_icon('Purifier Beam (Spear of Adun Calldown)') }}{{ sc2_icon('Time Stop (Spear of Adun Calldown)') }}{{ sc2_icon('Solar Bombardment (Spear of Adun Calldown)') }}{{ sc2_icon('Overwatch (Spear of Adun Auto-Cast)') }}
+
+ + + + + + +
+ + {{ sc2_loop_areas(0, 3) }} +
+
+ + {{ sc2_loop_areas(1, 3) }} +
+
+ + {{ sc2_loop_areas(2, 3) }} + + {{ sc2_render_area('Total') }} +
 
+
+
+
+ + diff --git a/WebHostLib/templates/tracker__Starcraft2WingsOfLiberty.html b/WebHostLib/templates/tracker__Starcraft2WingsOfLiberty.html deleted file mode 100644 index c27f690dfd..0000000000 --- a/WebHostLib/templates/tracker__Starcraft2WingsOfLiberty.html +++ /dev/null @@ -1,366 +0,0 @@ - - - - {{ player_name }}'s Tracker - - - - - - - {# TODO: Replace this with a proper wrapper for each tracker when developing TrackerAPI. #} - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Starting Resources -
+{{ minerals_count }}
+{{ vespene_count }}
- Weapon & Armor Upgrades -
- Base -
- Infantry - - Vehicles -
- Starships -
- Mercenaries -
- General Upgrades -
- Protoss Units -
- - {% for area in checks_in_area %} - {% if checks_in_area[area] > 0 %} - - - - - - {% for location in location_info[area] %} - - - - - {% endfor %} - - {% endif %} - {% endfor %} -
{{ area }} {{'▼' if area != 'Total'}}{{ checks_done[area] }} / {{ checks_in_area[area] }}
{{ location }}{{ '✔' if location_info[area][location] else '' }}
-
- - diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py index e8b1ae5b31..c2fdab0ed0 100644 --- a/WebHostLib/tracker.py +++ b/WebHostLib/tracker.py @@ -1553,212 +1553,298 @@ if "ChecksFinder" in network_data_package["games"]: _player_trackers["ChecksFinder"] = render_ChecksFinder_tracker -if "Starcraft 2 Wings of Liberty" in network_data_package["games"]: - def render_Starcraft2WingsOfLiberty_tracker(tracker_data: TrackerData, team: int, player: int) -> str: +if "Starcraft 2" in network_data_package["games"]: + def render_Starcraft2_tracker(tracker_data: TrackerData, team: int, player: int) -> str: SC2WOL_LOC_ID_OFFSET = 1000 + SC2HOTS_LOC_ID_OFFSET = 20000000 # Avoid clashes with The Legend of Zelda + SC2LOTV_LOC_ID_OFFSET = SC2HOTS_LOC_ID_OFFSET + 2000 + SC2NCO_LOC_ID_OFFSET = SC2LOTV_LOC_ID_OFFSET + 2500 + SC2WOL_ITEM_ID_OFFSET = 1000 + SC2HOTS_ITEM_ID_OFFSET = SC2WOL_ITEM_ID_OFFSET + 1000 + SC2LOTV_ITEM_ID_OFFSET = SC2HOTS_ITEM_ID_OFFSET + 1000 + + slot_data = tracker_data.get_slot_data(team, player) + minerals_per_item = slot_data.get("minerals_per_item", 15) + vespene_per_item = slot_data.get("vespene_per_item", 15) + starting_supply_per_item = slot_data.get("starting_supply_per_item", 2) + + github_icon_base_url = "https://matthewmarinets.github.io/ap_sc2_icons/icons/" + organics_icon_base_url = "https://0rganics.org/archipelago/sc2wol/" icons = { "Starting Minerals": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/icons/icon-mineral-protoss.png", "Starting Vespene": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/icons/icon-gas-terran.png", - "Starting Supply": "https://static.wikia.nocookie.net/starcraft/images/d/d3/TerranSupply_SC2_Icon1.gif", + "Starting Supply": github_icon_base_url + "blizzard/icon-supply-terran_nobg.png", - "Infantry Weapons Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryweaponslevel1.png", - "Infantry Weapons Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryweaponslevel2.png", - "Infantry Weapons Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryweaponslevel3.png", - "Infantry Armor Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryarmorlevel1.png", - "Infantry Armor Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryarmorlevel2.png", - "Infantry Armor Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryarmorlevel3.png", - "Vehicle Weapons Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleweaponslevel1.png", - "Vehicle Weapons Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleweaponslevel2.png", - "Vehicle Weapons Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleweaponslevel3.png", - "Vehicle Armor Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleplatinglevel1.png", - "Vehicle Armor Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleplatinglevel2.png", - "Vehicle Armor Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleplatinglevel3.png", - "Ship Weapons Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipweaponslevel1.png", - "Ship Weapons Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipweaponslevel2.png", - "Ship Weapons Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipweaponslevel3.png", - "Ship Armor Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipplatinglevel1.png", - "Ship Armor Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipplatinglevel2.png", - "Ship Armor Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipplatinglevel3.png", + "Terran Infantry Weapons Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryweaponslevel1.png", + "Terran Infantry Weapons Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryweaponslevel2.png", + "Terran Infantry Weapons Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryweaponslevel3.png", + "Terran Infantry Armor Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryarmorlevel1.png", + "Terran Infantry Armor Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryarmorlevel2.png", + "Terran Infantry Armor Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-infantryarmorlevel3.png", + "Terran Vehicle Weapons Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleweaponslevel1.png", + "Terran Vehicle Weapons Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleweaponslevel2.png", + "Terran Vehicle Weapons Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleweaponslevel3.png", + "Terran Vehicle Armor Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleplatinglevel1.png", + "Terran Vehicle Armor Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleplatinglevel2.png", + "Terran Vehicle Armor Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-vehicleplatinglevel3.png", + "Terran Ship Weapons Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipweaponslevel1.png", + "Terran Ship Weapons Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipweaponslevel2.png", + "Terran Ship Weapons Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipweaponslevel3.png", + "Terran Ship Armor Level 1": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipplatinglevel1.png", + "Terran Ship Armor Level 2": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipplatinglevel2.png", + "Terran Ship Armor Level 3": "https://sclegacy.com/images/uploaded/starcraftii_beta/gamefiles/upgrades/btn-upgrade-terran-shipplatinglevel3.png", "Bunker": "https://static.wikia.nocookie.net/starcraft/images/c/c5/Bunker_SC2_Icon1.jpg", "Missile Turret": "https://static.wikia.nocookie.net/starcraft/images/5/5f/MissileTurret_SC2_Icon1.jpg", "Sensor Tower": "https://static.wikia.nocookie.net/starcraft/images/d/d2/SensorTower_SC2_Icon1.jpg", - "Projectile Accelerator (Bunker)": "https://0rganics.org/archipelago/sc2wol/ProjectileAccelerator.png", - "Neosteel Bunker (Bunker)": "https://0rganics.org/archipelago/sc2wol/NeosteelBunker.png", - "Titanium Housing (Missile Turret)": "https://0rganics.org/archipelago/sc2wol/TitaniumHousing.png", - "Hellstorm Batteries (Missile Turret)": "https://0rganics.org/archipelago/sc2wol/HellstormBatteries.png", - "Advanced Construction (SCV)": "https://0rganics.org/archipelago/sc2wol/AdvancedConstruction.png", - "Dual-Fusion Welders (SCV)": "https://0rganics.org/archipelago/sc2wol/Dual-FusionWelders.png", - "Fire-Suppression System (Building)": "https://0rganics.org/archipelago/sc2wol/Fire-SuppressionSystem.png", - "Orbital Command (Building)": "https://0rganics.org/archipelago/sc2wol/OrbitalCommandCampaign.png", + "Projectile Accelerator (Bunker)": github_icon_base_url + "blizzard/btn-upgrade-zerg-stukov-bunkerresearchbundle_05.png", + "Neosteel Bunker (Bunker)": organics_icon_base_url + "NeosteelBunker.png", + "Titanium Housing (Missile Turret)": organics_icon_base_url + "TitaniumHousing.png", + "Hellstorm Batteries (Missile Turret)": github_icon_base_url + "blizzard/btn-ability-stetmann-corruptormissilebarrage.png", + "Advanced Construction (SCV)": github_icon_base_url + "blizzard/btn-ability-mengsk-trooper-advancedconstruction.png", + "Dual-Fusion Welders (SCV)": github_icon_base_url + "blizzard/btn-upgrade-swann-scvdoublerepair.png", + "Fire-Suppression System Level 1": organics_icon_base_url + "Fire-SuppressionSystem.png", + "Fire-Suppression System Level 2": github_icon_base_url + "blizzard/btn-upgrade-swann-firesuppressionsystem.png", + + "Orbital Command": organics_icon_base_url + "OrbitalCommandCampaign.png", + "Planetary Command Module": github_icon_base_url + "original/btn-orbital-fortress.png", + "Lift Off (Planetary Fortress)": github_icon_base_url + "blizzard/btn-ability-terran-liftoff.png", + "Armament Stabilizers (Planetary Fortress)": github_icon_base_url + "blizzard/btn-ability-mengsk-siegetank-flyingtankarmament.png", + "Advanced Targeting (Planetary Fortress)": github_icon_base_url + "blizzard/btn-ability-terran-detectionconedebuff.png", "Marine": "https://static.wikia.nocookie.net/starcraft/images/4/47/Marine_SC2_Icon1.jpg", - "Medic": "https://static.wikia.nocookie.net/starcraft/images/7/74/Medic_SC2_Rend1.jpg", - "Firebat": "https://static.wikia.nocookie.net/starcraft/images/3/3c/Firebat_SC2_Rend1.jpg", + "Medic": github_icon_base_url + "blizzard/btn-unit-terran-medic.png", + "Firebat": github_icon_base_url + "blizzard/btn-unit-terran-firebat.png", "Marauder": "https://static.wikia.nocookie.net/starcraft/images/b/ba/Marauder_SC2_Icon1.jpg", "Reaper": "https://static.wikia.nocookie.net/starcraft/images/7/7d/Reaper_SC2_Icon1.jpg", + "Ghost": "https://static.wikia.nocookie.net/starcraft/images/6/6e/Ghost_SC2_Icon1.jpg", + "Spectre": github_icon_base_url + "original/btn-unit-terran-spectre.png", + "HERC": github_icon_base_url + "blizzard/btn-unit-terran-herc.png", - "Stimpack (Marine)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png", - "Super Stimpack (Marine)": "/static/static/icons/sc2/superstimpack.png", - "Combat Shield (Marine)": "https://0rganics.org/archipelago/sc2wol/CombatShieldCampaign.png", - "Laser Targeting System (Marine)": "/static/static/icons/sc2/lasertargetingsystem.png", - "Magrail Munitions (Marine)": "/static/static/icons/sc2/magrailmunitions.png", - "Optimized Logistics (Marine)": "/static/static/icons/sc2/optimizedlogistics.png", - "Advanced Medic Facilities (Medic)": "https://0rganics.org/archipelago/sc2wol/AdvancedMedicFacilities.png", - "Stabilizer Medpacks (Medic)": "https://0rganics.org/archipelago/sc2wol/StabilizerMedpacks.png", - "Restoration (Medic)": "/static/static/icons/sc2/restoration.png", - "Optical Flare (Medic)": "/static/static/icons/sc2/opticalflare.png", - "Optimized Logistics (Medic)": "/static/static/icons/sc2/optimizedlogistics.png", - "Incinerator Gauntlets (Firebat)": "https://0rganics.org/archipelago/sc2wol/IncineratorGauntlets.png", - "Juggernaut Plating (Firebat)": "https://0rganics.org/archipelago/sc2wol/JuggernautPlating.png", - "Stimpack (Firebat)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png", - "Super Stimpack (Firebat)": "/static/static/icons/sc2/superstimpack.png", - "Optimized Logistics (Firebat)": "/static/static/icons/sc2/optimizedlogistics.png", - "Concussive Shells (Marauder)": "https://0rganics.org/archipelago/sc2wol/ConcussiveShellsCampaign.png", - "Kinetic Foam (Marauder)": "https://0rganics.org/archipelago/sc2wol/KineticFoam.png", - "Stimpack (Marauder)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png", - "Super Stimpack (Marauder)": "/static/static/icons/sc2/superstimpack.png", - "Laser Targeting System (Marauder)": "/static/static/icons/sc2/lasertargetingsystem.png", - "Magrail Munitions (Marauder)": "/static/static/icons/sc2/magrailmunitions.png", - "Internal Tech Module (Marauder)": "/static/static/icons/sc2/internalizedtechmodule.png", - "U-238 Rounds (Reaper)": "https://0rganics.org/archipelago/sc2wol/U-238Rounds.png", - "G-4 Clusterbomb (Reaper)": "https://0rganics.org/archipelago/sc2wol/G-4Clusterbomb.png", - "Stimpack (Reaper)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png", - "Super Stimpack (Reaper)": "/static/static/icons/sc2/superstimpack.png", - "Laser Targeting System (Reaper)": "/static/static/icons/sc2/lasertargetingsystem.png", - "Advanced Cloaking Field (Reaper)": "/static/static/icons/sc2/terran-cloak-color.png", - "Spider Mines (Reaper)": "/static/static/icons/sc2/spidermine.png", - "Combat Drugs (Reaper)": "/static/static/icons/sc2/reapercombatdrugs.png", + "Stimpack (Marine)": github_icon_base_url + "blizzard/btn-ability-terran-stimpack-color.png", + "Super Stimpack (Marine)": github_icon_base_url + "blizzard/btn-upgrade-terran-superstimppack.png", + "Combat Shield (Marine)": github_icon_base_url + "blizzard/btn-techupgrade-terran-combatshield-color.png", + "Laser Targeting System (Marine)": github_icon_base_url + "blizzard/btn-upgrade-terran-lazertargetingsystem.png", + "Magrail Munitions (Marine)": github_icon_base_url + "blizzard/btn-upgrade-terran-magrailmunitions.png", + "Optimized Logistics (Marine)": github_icon_base_url + "blizzard/btn-upgrade-terran-optimizedlogistics.png", + "Advanced Medic Facilities (Medic)": organics_icon_base_url + "AdvancedMedicFacilities.png", + "Stabilizer Medpacks (Medic)": github_icon_base_url + "blizzard/btn-upgrade-raynor-stabilizermedpacks.png", + "Restoration (Medic)": github_icon_base_url + "original/btn-ability-terran-restoration@scbw.png", + "Optical Flare (Medic)": github_icon_base_url + "blizzard/btn-upgrade-protoss-fenix-dragoonsolariteflare.png", + "Resource Efficiency (Medic)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Adaptive Medpacks (Medic)": github_icon_base_url + "blizzard/btn-ability-terran-heal-color.png", + "Nano Projector (Medic)": github_icon_base_url + "blizzard/talent-raynor-level03-firebatmedicrange.png", + "Incinerator Gauntlets (Firebat)": github_icon_base_url + "blizzard/btn-upgrade-raynor-incineratorgauntlets.png", + "Juggernaut Plating (Firebat)": github_icon_base_url + "blizzard/btn-upgrade-raynor-juggernautplating.png", + "Stimpack (Firebat)": github_icon_base_url + "blizzard/btn-ability-terran-stimpack-color.png", + "Super Stimpack (Firebat)": github_icon_base_url + "blizzard/btn-upgrade-terran-superstimppack.png", + "Resource Efficiency (Firebat)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Infernal Pre-Igniter (Firebat)": github_icon_base_url + "blizzard/btn-upgrade-terran-infernalpreigniter.png", + "Kinetic Foam (Firebat)": organics_icon_base_url + "KineticFoam.png", + "Nano Projectors (Firebat)": github_icon_base_url + "blizzard/talent-raynor-level03-firebatmedicrange.png", + "Concussive Shells (Marauder)": github_icon_base_url + "blizzard/btn-ability-terran-punishergrenade-color.png", + "Kinetic Foam (Marauder)": organics_icon_base_url + "KineticFoam.png", + "Stimpack (Marauder)": github_icon_base_url + "blizzard/btn-ability-terran-stimpack-color.png", + "Super Stimpack (Marauder)": github_icon_base_url + "blizzard/btn-upgrade-terran-superstimppack.png", + "Laser Targeting System (Marauder)": github_icon_base_url + "blizzard/btn-upgrade-terran-lazertargetingsystem.png", + "Magrail Munitions (Marauder)": github_icon_base_url + "blizzard/btn-upgrade-terran-magrailmunitions.png", + "Internal Tech Module (Marauder)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Juggernaut Plating (Marauder)": organics_icon_base_url + "JuggernautPlating.png", + "U-238 Rounds (Reaper)": organics_icon_base_url + "U-238Rounds.png", + "G-4 Clusterbomb (Reaper)": github_icon_base_url + "blizzard/btn-upgrade-terran-kd8chargeex3.png", + "Stimpack (Reaper)": github_icon_base_url + "blizzard/btn-ability-terran-stimpack-color.png", + "Super Stimpack (Reaper)": github_icon_base_url + "blizzard/btn-upgrade-terran-superstimppack.png", + "Laser Targeting System (Reaper)": github_icon_base_url + "blizzard/btn-upgrade-terran-lazertargetingsystem.png", + "Advanced Cloaking Field (Reaper)": github_icon_base_url + "original/btn-permacloak-reaper.png", + "Spider Mines (Reaper)": github_icon_base_url + "original/btn-ability-terran-spidermine.png", + "Combat Drugs (Reaper)": github_icon_base_url + "blizzard/btn-upgrade-terran-reapercombatdrugs.png", + "Jet Pack Overdrive (Reaper)": github_icon_base_url + "blizzard/btn-ability-hornerhan-reaper-flightmode.png", + "Ocular Implants (Ghost)": organics_icon_base_url + "OcularImplants.png", + "Crius Suit (Ghost)": github_icon_base_url + "original/btn-permacloak-ghost.png", + "EMP Rounds (Ghost)": github_icon_base_url + "blizzard/btn-ability-terran-emp-color.png", + "Lockdown (Ghost)": github_icon_base_url + "original/btn-abilty-terran-lockdown@scbw.png", + "Resource Efficiency (Ghost)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Psionic Lash (Spectre)": organics_icon_base_url + "PsionicLash.png", + "Nyx-Class Cloaking Module (Spectre)": github_icon_base_url + "original/btn-permacloak-spectre.png", + "Impaler Rounds (Spectre)": github_icon_base_url + "blizzard/btn-techupgrade-terran-impalerrounds.png", + "Resource Efficiency (Spectre)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Juggernaut Plating (HERC)": organics_icon_base_url + "JuggernautPlating.png", + "Kinetic Foam (HERC)": organics_icon_base_url + "KineticFoam.png", "Hellion": "https://static.wikia.nocookie.net/starcraft/images/5/56/Hellion_SC2_Icon1.jpg", - "Vulture": "https://static.wikia.nocookie.net/starcraft/images/d/da/Vulture_WoL.jpg", - "Goliath": "https://static.wikia.nocookie.net/starcraft/images/e/eb/Goliath_WoL.jpg", - "Diamondback": "https://static.wikia.nocookie.net/starcraft/images/a/a6/Diamondback_WoL.jpg", + "Vulture": github_icon_base_url + "blizzard/btn-unit-terran-vulture.png", + "Goliath": github_icon_base_url + "blizzard/btn-unit-terran-goliath.png", + "Diamondback": github_icon_base_url + "blizzard/btn-unit-terran-cobra.png", "Siege Tank": "https://static.wikia.nocookie.net/starcraft/images/5/57/SiegeTank_SC2_Icon1.jpg", + "Thor": "https://static.wikia.nocookie.net/starcraft/images/e/ef/Thor_SC2_Icon1.jpg", + "Predator": github_icon_base_url + "original/btn-unit-terran-predator.png", + "Widow Mine": github_icon_base_url + "blizzard/btn-unit-terran-widowmine.png", + "Cyclone": github_icon_base_url + "blizzard/btn-unit-terran-cyclone.png", + "Warhound": github_icon_base_url + "blizzard/btn-unit-terran-warhound.png", - "Twin-Linked Flamethrower (Hellion)": "https://0rganics.org/archipelago/sc2wol/Twin-LinkedFlamethrower.png", - "Thermite Filaments (Hellion)": "https://0rganics.org/archipelago/sc2wol/ThermiteFilaments.png", - "Hellbat Aspect (Hellion)": "/static/static/icons/sc2/hellionbattlemode.png", - "Smart Servos (Hellion)": "/static/static/icons/sc2/transformationservos.png", - "Optimized Logistics (Hellion)": "/static/static/icons/sc2/optimizedlogistics.png", - "Jump Jets (Hellion)": "/static/static/icons/sc2/jumpjets.png", - "Stimpack (Hellion)": "https://0rganics.org/archipelago/sc2wol/StimpacksCampaign.png", - "Super Stimpack (Hellion)": "/static/static/icons/sc2/superstimpack.png", - "Cerberus Mine (Spider Mine)": "https://0rganics.org/archipelago/sc2wol/CerberusMine.png", - "High Explosive Munition (Spider Mine)": "/static/static/icons/sc2/high-explosive-spidermine.png", - "Replenishable Magazine (Vulture)": "https://0rganics.org/archipelago/sc2wol/ReplenishableMagazine.png", - "Ion Thrusters (Vulture)": "/static/static/icons/sc2/emergencythrusters.png", - "Auto Launchers (Vulture)": "/static/static/icons/sc2/jotunboosters.png", - "Multi-Lock Weapons System (Goliath)": "https://0rganics.org/archipelago/sc2wol/Multi-LockWeaponsSystem.png", - "Ares-Class Targeting System (Goliath)": "https://0rganics.org/archipelago/sc2wol/Ares-ClassTargetingSystem.png", - "Jump Jets (Goliath)": "/static/static/icons/sc2/jumpjets.png", - "Optimized Logistics (Goliath)": "/static/static/icons/sc2/optimizedlogistics.png", - "Tri-Lithium Power Cell (Diamondback)": "https://0rganics.org/archipelago/sc2wol/Tri-LithiumPowerCell.png", - "Shaped Hull (Diamondback)": "https://0rganics.org/archipelago/sc2wol/ShapedHull.png", - "Hyperfluxor (Diamondback)": "/static/static/icons/sc2/hyperfluxor.png", - "Burst Capacitors (Diamondback)": "/static/static/icons/sc2/burstcapacitors.png", - "Optimized Logistics (Diamondback)": "/static/static/icons/sc2/optimizedlogistics.png", - "Maelstrom Rounds (Siege Tank)": "https://0rganics.org/archipelago/sc2wol/MaelstromRounds.png", - "Shaped Blast (Siege Tank)": "https://0rganics.org/archipelago/sc2wol/ShapedBlast.png", - "Jump Jets (Siege Tank)": "/static/static/icons/sc2/jumpjets.png", - "Spider Mines (Siege Tank)": "/static/static/icons/sc2/siegetank-spidermines.png", - "Smart Servos (Siege Tank)": "/static/static/icons/sc2/transformationservos.png", - "Graduating Range (Siege Tank)": "/static/static/icons/sc2/siegetankrange.png", - "Laser Targeting System (Siege Tank)": "/static/static/icons/sc2/lasertargetingsystem.png", - "Advanced Siege Tech (Siege Tank)": "/static/static/icons/sc2/improvedsiegemode.png", - "Internal Tech Module (Siege Tank)": "/static/static/icons/sc2/internalizedtechmodule.png", + "Twin-Linked Flamethrower (Hellion)": github_icon_base_url + "blizzard/btn-upgrade-mengsk-trooper-flamethrower.png", + "Thermite Filaments (Hellion)": github_icon_base_url + "blizzard/btn-upgrade-terran-infernalpreigniter.png", + "Hellbat Aspect (Hellion)": github_icon_base_url + "blizzard/btn-unit-terran-hellionbattlemode.png", + "Smart Servos (Hellion)": github_icon_base_url + "blizzard/btn-upgrade-terran-transformationservos.png", + "Optimized Logistics (Hellion)": github_icon_base_url + "blizzard/btn-upgrade-terran-optimizedlogistics.png", + "Jump Jets (Hellion)": github_icon_base_url + "blizzard/btn-upgrade-terran-jumpjets.png", + "Stimpack (Hellion)": github_icon_base_url + "blizzard/btn-ability-terran-stimpack-color.png", + "Super Stimpack (Hellion)": github_icon_base_url + "blizzard/btn-upgrade-terran-superstimppack.png", + "Infernal Plating (Hellion)": github_icon_base_url + "blizzard/btn-upgrade-swann-hellarmor.png", + "Cerberus Mine (Spider Mine)": github_icon_base_url + "blizzard/btn-upgrade-raynor-cerberusmines.png", + "High Explosive Munition (Spider Mine)": github_icon_base_url + "original/btn-ability-terran-spidermine.png", + "Replenishable Magazine (Vulture)": github_icon_base_url + "blizzard/btn-upgrade-raynor-replenishablemagazine.png", + "Replenishable Magazine (Free) (Vulture)": github_icon_base_url + "blizzard/btn-upgrade-raynor-replenishablemagazine.png", + "Ion Thrusters (Vulture)": github_icon_base_url + "blizzard/btn-ability-terran-emergencythrusters.png", + "Auto Launchers (Vulture)": github_icon_base_url + "blizzard/btn-upgrade-terran-jotunboosters.png", + "Auto-Repair (Vulture)": github_icon_base_url + "blizzard/ui_tipicon_campaign_space01-repair.png", + "Multi-Lock Weapons System (Goliath)": github_icon_base_url + "blizzard/btn-upgrade-swann-multilockweaponsystem.png", + "Ares-Class Targeting System (Goliath)": github_icon_base_url + "blizzard/btn-upgrade-swann-aresclasstargetingsystem.png", + "Jump Jets (Goliath)": github_icon_base_url + "blizzard/btn-upgrade-terran-jumpjets.png", + "Optimized Logistics (Goliath)": github_icon_base_url + "blizzard/btn-upgrade-terran-optimizedlogistics.png", + "Shaped Hull (Goliath)": organics_icon_base_url + "ShapedHull.png", + "Resource Efficiency (Goliath)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Internal Tech Module (Goliath)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Tri-Lithium Power Cell (Diamondback)": github_icon_base_url + "original/btn-upgrade-terran-trilithium-power-cell.png", + "Tungsten Spikes (Diamondback)": github_icon_base_url + "original/btn-upgrade-terran-tungsten-spikes.png", + "Shaped Hull (Diamondback)": organics_icon_base_url + "ShapedHull.png", + "Hyperfluxor (Diamondback)": github_icon_base_url + "blizzard/btn-upgrade-mengsk-engineeringbay-orbitaldrop.png", + "Burst Capacitors (Diamondback)": github_icon_base_url + "blizzard/btn-ability-terran-electricfield.png", + "Ion Thrusters (Diamondback)": github_icon_base_url + "blizzard/btn-ability-terran-emergencythrusters.png", + "Resource Efficiency (Diamondback)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Maelstrom Rounds (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-raynor-maelstromrounds.png", + "Shaped Blast (Siege Tank)": organics_icon_base_url + "ShapedBlast.png", + "Jump Jets (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-terran-jumpjets.png", + "Spider Mines (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-siegetank-spidermines.png", + "Smart Servos (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-terran-transformationservos.png", + "Graduating Range (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-terran-nova-siegetankrange.png", + "Laser Targeting System (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-terran-lazertargetingsystem.png", + "Advanced Siege Tech (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-raynor-improvedsiegemode.png", + "Internal Tech Module (Siege Tank)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Shaped Hull (Siege Tank)": organics_icon_base_url + "ShapedHull.png", + "Resource Efficiency (Siege Tank)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "330mm Barrage Cannon (Thor)": github_icon_base_url + "original/btn-ability-thor-330mm.png", + "Immortality Protocol (Thor)": github_icon_base_url + "blizzard/btn-techupgrade-terran-immortalityprotocol.png", + "Immortality Protocol (Free) (Thor)": github_icon_base_url + "blizzard/btn-techupgrade-terran-immortalityprotocol.png", + "High Impact Payload (Thor)": github_icon_base_url + "blizzard/btn-unit-terran-thorsiegemode.png", + "Smart Servos (Thor)": github_icon_base_url + "blizzard/btn-upgrade-terran-transformationservos.png", + "Button With a Skull on It (Thor)": github_icon_base_url + "blizzard/btn-ability-terran-nuclearstrike-color.png", + "Laser Targeting System (Thor)": github_icon_base_url + "blizzard/btn-upgrade-terran-lazertargetingsystem.png", + "Large Scale Field Construction (Thor)": github_icon_base_url + "blizzard/talent-swann-level12-immortalityprotocol.png", + "Resource Efficiency (Predator)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Cloak (Predator)": github_icon_base_url + "blizzard/btn-ability-terran-cloak-color.png", + "Charge (Predator)": github_icon_base_url + "blizzard/btn-ability-protoss-charge-color.png", + "Predator's Fury (Predator)": github_icon_base_url + "blizzard/btn-ability-protoss-shadowfury.png", + "Drilling Claws (Widow Mine)": github_icon_base_url + "blizzard/btn-upgrade-terran-researchdrillingclaws.png", + "Concealment (Widow Mine)": github_icon_base_url + "blizzard/btn-ability-terran-widowminehidden.png", + "Black Market Launchers (Widow Mine)": github_icon_base_url + "blizzard/btn-ability-hornerhan-widowmine-attackrange.png", + "Executioner Missiles (Widow Mine)": github_icon_base_url + "blizzard/btn-ability-hornerhan-widowmine-deathblossom.png", + "Mag-Field Accelerators (Cyclone)": github_icon_base_url + "blizzard/btn-upgrade-terran-magfieldaccelerator.png", + "Mag-Field Launchers (Cyclone)": github_icon_base_url + "blizzard/btn-upgrade-terran-cyclonerangeupgrade.png", + "Targeting Optics (Cyclone)": github_icon_base_url + "blizzard/btn-upgrade-swann-targetingoptics.png", + "Rapid Fire Launchers (Cyclone)": github_icon_base_url + "blizzard/btn-upgrade-raynor-ripwavemissiles.png", + "Resource Efficiency (Cyclone)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Internal Tech Module (Cyclone)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Resource Efficiency (Warhound)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Reinforced Plating (Warhound)": github_icon_base_url + "original/btn-research-zerg-fortifiedbunker.png", "Medivac": "https://static.wikia.nocookie.net/starcraft/images/d/db/Medivac_SC2_Icon1.jpg", - "Wraith": "https://static.wikia.nocookie.net/starcraft/images/7/75/Wraith_WoL.jpg", + "Wraith": github_icon_base_url + "blizzard/btn-unit-terran-wraith.png", "Viking": "https://static.wikia.nocookie.net/starcraft/images/2/2a/Viking_SC2_Icon1.jpg", "Banshee": "https://static.wikia.nocookie.net/starcraft/images/3/32/Banshee_SC2_Icon1.jpg", "Battlecruiser": "https://static.wikia.nocookie.net/starcraft/images/f/f5/Battlecruiser_SC2_Icon1.jpg", + "Raven": "https://static.wikia.nocookie.net/starcraft/images/1/19/SC2_Lab_Raven_Icon.png", + "Science Vessel": "https://static.wikia.nocookie.net/starcraft/images/c/c3/SC2_Lab_SciVes_Icon.png", + "Hercules": "https://static.wikia.nocookie.net/starcraft/images/4/40/SC2_Lab_Hercules_Icon.png", + "Liberator": github_icon_base_url + "blizzard/btn-unit-terran-liberator.png", + "Valkyrie": github_icon_base_url + "original/btn-unit-terran-valkyrie@scbw.png", - "Rapid Deployment Tube (Medivac)": "https://0rganics.org/archipelago/sc2wol/RapidDeploymentTube.png", - "Advanced Healing AI (Medivac)": "https://0rganics.org/archipelago/sc2wol/AdvancedHealingAI.png", - "Expanded Hull (Medivac)": "/static/static/icons/sc2/neosteelfortifiedarmor.png", - "Afterburners (Medivac)": "/static/static/icons/sc2/medivacemergencythrusters.png", - "Tomahawk Power Cells (Wraith)": "https://0rganics.org/archipelago/sc2wol/TomahawkPowerCells.png", - "Displacement Field (Wraith)": "https://0rganics.org/archipelago/sc2wol/DisplacementField.png", - "Advanced Laser Technology (Wraith)": "/static/static/icons/sc2/improvedburstlaser.png", - "Ripwave Missiles (Viking)": "https://0rganics.org/archipelago/sc2wol/RipwaveMissiles.png", - "Phobos-Class Weapons System (Viking)": "https://0rganics.org/archipelago/sc2wol/Phobos-ClassWeaponsSystem.png", - "Smart Servos (Viking)": "/static/static/icons/sc2/transformationservos.png", - "Magrail Munitions (Viking)": "/static/static/icons/sc2/magrailmunitions.png", - "Cross-Spectrum Dampeners (Banshee)": "/static/static/icons/sc2/crossspectrumdampeners.png", - "Advanced Cross-Spectrum Dampeners (Banshee)": "https://0rganics.org/archipelago/sc2wol/Cross-SpectrumDampeners.png", - "Shockwave Missile Battery (Banshee)": "https://0rganics.org/archipelago/sc2wol/ShockwaveMissileBattery.png", - "Hyperflight Rotors (Banshee)": "/static/static/icons/sc2/hyperflightrotors.png", - "Laser Targeting System (Banshee)": "/static/static/icons/sc2/lasertargetingsystem.png", - "Internal Tech Module (Banshee)": "/static/static/icons/sc2/internalizedtechmodule.png", - "Missile Pods (Battlecruiser)": "https://0rganics.org/archipelago/sc2wol/MissilePods.png", - "Defensive Matrix (Battlecruiser)": "https://0rganics.org/archipelago/sc2wol/DefensiveMatrix.png", - "Tactical Jump (Battlecruiser)": "/static/static/icons/sc2/warpjump.png", - "Cloak (Battlecruiser)": "/static/static/icons/sc2/terran-cloak-color.png", - "ATX Laser Battery (Battlecruiser)": "/static/static/icons/sc2/specialordance.png", - "Optimized Logistics (Battlecruiser)": "/static/static/icons/sc2/optimizedlogistics.png", - "Internal Tech Module (Battlecruiser)": "/static/static/icons/sc2/internalizedtechmodule.png", - - "Ghost": "https://static.wikia.nocookie.net/starcraft/images/6/6e/Ghost_SC2_Icon1.jpg", - "Spectre": "https://static.wikia.nocookie.net/starcraft/images/0/0d/Spectre_WoL.jpg", - "Thor": "https://static.wikia.nocookie.net/starcraft/images/e/ef/Thor_SC2_Icon1.jpg", - - "Widow Mine": "/static/static/icons/sc2/widowmine.png", - "Cyclone": "/static/static/icons/sc2/cyclone.png", - "Liberator": "/static/static/icons/sc2/liberator.png", - "Valkyrie": "/static/static/icons/sc2/valkyrie.png", - - "Ocular Implants (Ghost)": "https://0rganics.org/archipelago/sc2wol/OcularImplants.png", - "Crius Suit (Ghost)": "https://0rganics.org/archipelago/sc2wol/CriusSuit.png", - "EMP Rounds (Ghost)": "/static/static/icons/sc2/terran-emp-color.png", - "Lockdown (Ghost)": "/static/static/icons/sc2/lockdown.png", - "Psionic Lash (Spectre)": "https://0rganics.org/archipelago/sc2wol/PsionicLash.png", - "Nyx-Class Cloaking Module (Spectre)": "https://0rganics.org/archipelago/sc2wol/Nyx-ClassCloakingModule.png", - "Impaler Rounds (Spectre)": "/static/static/icons/sc2/impalerrounds.png", - "330mm Barrage Cannon (Thor)": "https://0rganics.org/archipelago/sc2wol/330mmBarrageCannon.png", - "Immortality Protocol (Thor)": "https://0rganics.org/archipelago/sc2wol/ImmortalityProtocol.png", - "High Impact Payload (Thor)": "/static/static/icons/sc2/thorsiegemode.png", - "Smart Servos (Thor)": "/static/static/icons/sc2/transformationservos.png", - - "Optimized Logistics (Predator)": "/static/static/icons/sc2/optimizedlogistics.png", - "Drilling Claws (Widow Mine)": "/static/static/icons/sc2/drillingclaws.png", - "Concealment (Widow Mine)": "/static/static/icons/sc2/widowminehidden.png", - "Black Market Launchers (Widow Mine)": "/static/static/icons/sc2/widowmine-attackrange.png", - "Executioner Missiles (Widow Mine)": "/static/static/icons/sc2/widowmine-deathblossom.png", - "Mag-Field Accelerators (Cyclone)": "/static/static/icons/sc2/magfieldaccelerator.png", - "Mag-Field Launchers (Cyclone)": "/static/static/icons/sc2/cyclonerangeupgrade.png", - "Targeting Optics (Cyclone)": "/static/static/icons/sc2/targetingoptics.png", - "Rapid Fire Launchers (Cyclone)": "/static/static/icons/sc2/ripwavemissiles.png", - "Bio Mechanical Repair Drone (Raven)": "/static/static/icons/sc2/biomechanicaldrone.png", - "Spider Mines (Raven)": "/static/static/icons/sc2/siegetank-spidermines.png", - "Railgun Turret (Raven)": "/static/static/icons/sc2/autoturretblackops.png", - "Hunter-Seeker Weapon (Raven)": "/static/static/icons/sc2/specialordance.png", - "Interference Matrix (Raven)": "/static/static/icons/sc2/interferencematrix.png", - "Anti-Armor Missile (Raven)": "/static/static/icons/sc2/shreddermissile.png", - "Internal Tech Module (Raven)": "/static/static/icons/sc2/internalizedtechmodule.png", - "EMP Shockwave (Science Vessel)": "/static/static/icons/sc2/staticempblast.png", - "Defensive Matrix (Science Vessel)": "https://0rganics.org/archipelago/sc2wol/DefensiveMatrix.png", - "Advanced Ballistics (Liberator)": "/static/static/icons/sc2/advanceballistics.png", - "Raid Artillery (Liberator)": "/static/static/icons/sc2/terrandefendermodestructureattack.png", - "Cloak (Liberator)": "/static/static/icons/sc2/terran-cloak-color.png", - "Laser Targeting System (Liberator)": "/static/static/icons/sc2/lasertargetingsystem.png", - "Optimized Logistics (Liberator)": "/static/static/icons/sc2/optimizedlogistics.png", - "Enhanced Cluster Launchers (Valkyrie)": "https://0rganics.org/archipelago/sc2wol/HellstormBatteries.png", - "Shaped Hull (Valkyrie)": "https://0rganics.org/archipelago/sc2wol/ShapedHull.png", - "Burst Lasers (Valkyrie)": "/static/static/icons/sc2/improvedburstlaser.png", - "Afterburners (Valkyrie)": "/static/static/icons/sc2/medivacemergencythrusters.png", + "Rapid Deployment Tube (Medivac)": organics_icon_base_url + "RapidDeploymentTube.png", + "Advanced Healing AI (Medivac)": github_icon_base_url + "blizzard/btn-ability-mengsk-medivac-doublehealbeam.png", + "Expanded Hull (Medivac)": github_icon_base_url + "blizzard/btn-upgrade-mengsk-engineeringbay-neosteelfortifiedarmor.png", + "Afterburners (Medivac)": github_icon_base_url + "blizzard/btn-upgrade-terran-medivacemergencythrusters.png", + "Scatter Veil (Medivac)": github_icon_base_url + "blizzard/btn-upgrade-swann-defensivematrix.png", + "Advanced Cloaking Field (Medivac)": github_icon_base_url + "original/btn-permacloak-medivac.png", + "Tomahawk Power Cells (Wraith)": organics_icon_base_url + "TomahawkPowerCells.png", + "Unregistered Cloaking Module (Wraith)": github_icon_base_url + "original/btn-permacloak-wraith.png", + "Trigger Override (Wraith)": github_icon_base_url + "blizzard/btn-ability-hornerhan-wraith-attackspeed.png", + "Internal Tech Module (Wraith)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Resource Efficiency (Wraith)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Displacement Field (Wraith)": github_icon_base_url + "blizzard/btn-upgrade-swann-displacementfield.png", + "Advanced Laser Technology (Wraith)": github_icon_base_url + "blizzard/btn-upgrade-swann-improvedburstlaser.png", + "Ripwave Missiles (Viking)": github_icon_base_url + "blizzard/btn-upgrade-raynor-ripwavemissiles.png", + "Phobos-Class Weapons System (Viking)": github_icon_base_url + "blizzard/btn-upgrade-raynor-phobosclassweaponssystem.png", + "Smart Servos (Viking)": github_icon_base_url + "blizzard/btn-upgrade-terran-transformationservos.png", + "Anti-Mechanical Munition (Viking)": github_icon_base_url + "blizzard/btn-ability-terran-ignorearmor.png", + "Shredder Rounds (Viking)": github_icon_base_url + "blizzard/btn-ability-hornerhan-viking-piercingattacks.png", + "W.I.L.D. Missiles (Viking)": github_icon_base_url + "blizzard/btn-ability-hornerhan-viking-missileupgrade.png", + "Cross-Spectrum Dampeners (Banshee)": github_icon_base_url + "original/btn-banshee-cross-spectrum-dampeners.png", + "Advanced Cross-Spectrum Dampeners (Banshee)": github_icon_base_url + "original/btn-permacloak-banshee.png", + "Shockwave Missile Battery (Banshee)": github_icon_base_url + "blizzard/btn-upgrade-raynor-shockwavemissilebattery.png", + "Hyperflight Rotors (Banshee)": github_icon_base_url + "blizzard/btn-upgrade-terran-hyperflightrotors.png", + "Laser Targeting System (Banshee)": github_icon_base_url + "blizzard/btn-upgrade-terran-lazertargetingsystem.png", + "Internal Tech Module (Banshee)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Shaped Hull (Banshee)": organics_icon_base_url + "ShapedHull.png", + "Advanced Targeting Optics (Banshee)": github_icon_base_url + "blizzard/btn-ability-terran-detectionconedebuff.png", + "Distortion Blasters (Banshee)": github_icon_base_url + "blizzard/btn-techupgrade-terran-cloakdistortionfield.png", + "Rocket Barrage (Banshee)": github_icon_base_url + "blizzard/btn-upgrade-terran-nova-bansheemissilestrik.png", + "Missile Pods (Battlecruiser) Level 1": organics_icon_base_url + "MissilePods.png", + "Missile Pods (Battlecruiser) Level 2": github_icon_base_url + "blizzard/btn-upgrade-terran-nova-bansheemissilestrik.png", + "Defensive Matrix (Battlecruiser)": github_icon_base_url + "blizzard/btn-upgrade-swann-defensivematrix.png", + "Advanced Defensive Matrix (Battlecruiser)": github_icon_base_url + "blizzard/btn-upgrade-swann-defensivematrix.png", + "Tactical Jump (Battlecruiser)": github_icon_base_url + "blizzard/btn-ability-terran-warpjump.png", + "Cloak (Battlecruiser)": github_icon_base_url + "blizzard/btn-ability-terran-cloak-color.png", + "ATX Laser Battery (Battlecruiser)": github_icon_base_url + "blizzard/btn-upgrade-terran-nova-specialordance.png", + "Optimized Logistics (Battlecruiser)": github_icon_base_url + "blizzard/btn-upgrade-terran-optimizedlogistics.png", + "Internal Tech Module (Battlecruiser)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Behemoth Plating (Battlecruiser)": github_icon_base_url + "original/btn-research-zerg-fortifiedbunker.png", + "Covert Ops Engines (Battlecruiser)": github_icon_base_url + "blizzard/btn-ability-terran-emergencythrusters.png", + "Bio Mechanical Repair Drone (Raven)": github_icon_base_url + "blizzard/btn-unit-biomechanicaldrone.png", + "Spider Mines (Raven)": github_icon_base_url + "blizzard/btn-upgrade-siegetank-spidermines.png", + "Railgun Turret (Raven)": github_icon_base_url + "blizzard/btn-unit-terran-autoturretblackops.png", + "Hunter-Seeker Weapon (Raven)": github_icon_base_url + "blizzard/btn-upgrade-terran-nova-specialordance.png", + "Interference Matrix (Raven)": github_icon_base_url + "blizzard/btn-upgrade-terran-interferencematrix.png", + "Anti-Armor Missile (Raven)": github_icon_base_url + "blizzard/btn-ability-terran-shreddermissile-color.png", + "Internal Tech Module (Raven)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Resource Efficiency (Raven)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Durable Materials (Raven)": github_icon_base_url + "blizzard/btn-upgrade-terran-durablematerials.png", + "EMP Shockwave (Science Vessel)": github_icon_base_url + "blizzard/btn-ability-mengsk-ghost-staticempblast.png", + "Defensive Matrix (Science Vessel)": github_icon_base_url + "blizzard/btn-upgrade-swann-defensivematrix.png", + "Improved Nano-Repair (Science Vessel)": github_icon_base_url + "blizzard/btn-upgrade-swann-improvednanorepair.png", + "Advanced AI Systems (Science Vessel)": github_icon_base_url + "blizzard/btn-ability-mengsk-medivac-doublehealbeam.png", + "Internal Fusion Module (Hercules)": github_icon_base_url + "blizzard/btn-upgrade-terran-internalizedtechmodule.png", + "Tactical Jump (Hercules)": github_icon_base_url + "blizzard/btn-ability-terran-hercules-tacticaljump.png", + "Advanced Ballistics (Liberator)": github_icon_base_url + "blizzard/btn-upgrade-terran-advanceballistics.png", + "Raid Artillery (Liberator)": github_icon_base_url + "blizzard/btn-upgrade-terran-nova-terrandefendermodestructureattack.png", + "Cloak (Liberator)": github_icon_base_url + "blizzard/btn-ability-terran-cloak-color.png", + "Laser Targeting System (Liberator)": github_icon_base_url + "blizzard/btn-upgrade-terran-lazertargetingsystem.png", + "Optimized Logistics (Liberator)": github_icon_base_url + "blizzard/btn-upgrade-terran-optimizedlogistics.png", + "Smart Servos (Liberator)": github_icon_base_url + "blizzard/btn-upgrade-terran-transformationservos.png", + "Resource Efficiency (Liberator)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Enhanced Cluster Launchers (Valkyrie)": github_icon_base_url + "blizzard/btn-ability-stetmann-corruptormissilebarrage.png", + "Shaped Hull (Valkyrie)": organics_icon_base_url + "ShapedHull.png", + "Flechette Missiles (Valkyrie)": github_icon_base_url + "blizzard/btn-ability-hornerhan-viking-missileupgrade.png", + "Afterburners (Valkyrie)": github_icon_base_url + "blizzard/btn-upgrade-terran-medivacemergencythrusters.png", + "Launching Vector Compensator (Valkyrie)": github_icon_base_url + "blizzard/btn-ability-terran-emergencythrusters.png", + "Resource Efficiency (Valkyrie)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", "War Pigs": "https://static.wikia.nocookie.net/starcraft/images/e/ed/WarPigs_SC2_Icon1.jpg", "Devil Dogs": "https://static.wikia.nocookie.net/starcraft/images/3/33/DevilDogs_SC2_Icon1.jpg", "Hammer Securities": "https://static.wikia.nocookie.net/starcraft/images/3/3b/HammerSecurity_SC2_Icon1.jpg", "Spartan Company": "https://static.wikia.nocookie.net/starcraft/images/b/be/SpartanCompany_SC2_Icon1.jpg", "Siege Breakers": "https://static.wikia.nocookie.net/starcraft/images/3/31/SiegeBreakers_SC2_Icon1.jpg", - "Hel's Angel": "https://static.wikia.nocookie.net/starcraft/images/6/63/HelsAngels_SC2_Icon1.jpg", + "Hel's Angels": "https://static.wikia.nocookie.net/starcraft/images/6/63/HelsAngels_SC2_Icon1.jpg", "Dusk Wings": "https://static.wikia.nocookie.net/starcraft/images/5/52/DuskWings_SC2_Icon1.jpg", "Jackson's Revenge": "https://static.wikia.nocookie.net/starcraft/images/9/95/JacksonsRevenge_SC2_Icon1.jpg", + "Skibi's Angels": github_icon_base_url + "blizzard/btn-unit-terran-medicelite.png", + "Death Heads": github_icon_base_url + "blizzard/btn-unit-terran-deathhead.png", + "Winged Nightmares": github_icon_base_url + "blizzard/btn-unit-collection-wraith-junker.png", + "Midnight Riders": github_icon_base_url + "blizzard/btn-unit-terran-liberatorblackops.png", + "Brynhilds": github_icon_base_url + "blizzard/btn-unit-collection-vikingfighter-covertops.png", + "Jotun": github_icon_base_url + "blizzard/btn-unit-terran-thormengsk.png", "Ultra-Capacitors": "https://static.wikia.nocookie.net/starcraft/images/2/23/SC2_Lab_Ultra_Capacitors_Icon.png", "Vanadium Plating": "https://static.wikia.nocookie.net/starcraft/images/6/67/SC2_Lab_VanPlating_Icon.png", @@ -1766,8 +1852,6 @@ if "Starcraft 2 Wings of Liberty" in network_data_package["games"]: "Micro-Filtering": "https://static.wikia.nocookie.net/starcraft/images/2/20/SC2_Lab_MicroFilter_Icon.png", "Automated Refinery": "https://static.wikia.nocookie.net/starcraft/images/7/71/SC2_Lab_Auto_Refinery_Icon.png", "Command Center Reactor": "https://static.wikia.nocookie.net/starcraft/images/e/ef/SC2_Lab_CC_Reactor_Icon.png", - "Raven": "https://static.wikia.nocookie.net/starcraft/images/1/19/SC2_Lab_Raven_Icon.png", - "Science Vessel": "https://static.wikia.nocookie.net/starcraft/images/c/c3/SC2_Lab_SciVes_Icon.png", "Tech Reactor": "https://static.wikia.nocookie.net/starcraft/images/c/c5/SC2_Lab_Tech_Reactor_Icon.png", "Orbital Strike": "https://static.wikia.nocookie.net/starcraft/images/d/df/SC2_Lab_Orb_Strike_Icon.png", @@ -1775,23 +1859,372 @@ if "Starcraft 2 Wings of Liberty" in network_data_package["games"]: "Fortified Bunker (Bunker)": "https://static.wikia.nocookie.net/starcraft/images/4/4f/SC2_Lab_FortBunker_Icon.png", "Planetary Fortress": "https://static.wikia.nocookie.net/starcraft/images/0/0b/SC2_Lab_PlanetFortress_Icon.png", "Perdition Turret": "https://static.wikia.nocookie.net/starcraft/images/a/af/SC2_Lab_PerdTurret_Icon.png", - "Predator": "https://static.wikia.nocookie.net/starcraft/images/8/83/SC2_Lab_Predator_Icon.png", - "Hercules": "https://static.wikia.nocookie.net/starcraft/images/4/40/SC2_Lab_Hercules_Icon.png", "Cellular Reactor": "https://static.wikia.nocookie.net/starcraft/images/d/d8/SC2_Lab_CellReactor_Icon.png", - "Regenerative Bio-Steel Level 1": "/static/static/icons/sc2/SC2_Lab_BioSteel_L1.png", - "Regenerative Bio-Steel Level 2": "/static/static/icons/sc2/SC2_Lab_BioSteel_L2.png", + "Regenerative Bio-Steel Level 1": github_icon_base_url + "original/btn-regenerativebiosteel-green.png", + "Regenerative Bio-Steel Level 2": github_icon_base_url + "original/btn-regenerativebiosteel-blue.png", + "Regenerative Bio-Steel Level 3": github_icon_base_url + "blizzard/btn-research-zerg-regenerativebio-steel.png", "Hive Mind Emulator": "https://static.wikia.nocookie.net/starcraft/images/b/bc/SC2_Lab_Hive_Emulator_Icon.png", "Psi Disrupter": "https://static.wikia.nocookie.net/starcraft/images/c/cf/SC2_Lab_Psi_Disruptor_Icon.png", - "Zealot": "https://static.wikia.nocookie.net/starcraft/images/6/6e/Icon_Protoss_Zealot.jpg", + "Structure Armor": github_icon_base_url + "blizzard/btn-upgrade-terran-buildingarmor.png", + "Hi-Sec Auto Tracking": github_icon_base_url + "blizzard/btn-upgrade-terran-hisecautotracking.png", + "Advanced Optics": github_icon_base_url + "blizzard/btn-upgrade-swann-vehiclerangeincrease.png", + "Rogue Forces": github_icon_base_url + "blizzard/btn-unit-terran-tosh.png", + + "Ghost Visor (Nova Equipment)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-ghostvisor.png", + "Rangefinder Oculus (Nova Equipment)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-rangefinderoculus.png", + "Domination (Nova Ability)": github_icon_base_url + "blizzard/btn-ability-nova-domination.png", + "Blink (Nova Ability)": github_icon_base_url + "blizzard/btn-upgrade-nova-blink.png", + "Stealth Suit Module (Nova Suit Module)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-stealthsuit.png", + "Cloak (Nova Suit Module)": github_icon_base_url + "blizzard/btn-ability-terran-cloak-color.png", + "Permanently Cloaked (Nova Suit Module)": github_icon_base_url + "blizzard/btn-upgrade-nova-tacticalstealthsuit.png", + "Energy Suit Module (Nova Suit Module)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-apolloinfantrysuit.png", + "Armored Suit Module (Nova Suit Module)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-blinksuit.png", + "Jump Suit Module (Nova Suit Module)": github_icon_base_url + "blizzard/btn-upgrade-nova-jetpack.png", + "C20A Canister Rifle (Nova Weapon)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-canisterrifle.png", + "Hellfire Shotgun (Nova Weapon)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-shotgun.png", + "Plasma Rifle (Nova Weapon)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-plasmagun.png", + "Monomolecular Blade (Nova Weapon)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-monomolecularblade.png", + "Blazefire Gunblade (Nova Weapon)": github_icon_base_url + "blizzard/btn-upgrade-nova-equipment-gunblade_sword.png", + "Stim Infusion (Nova Gadget)": github_icon_base_url + "blizzard/btn-upgrade-terran-superstimppack.png", + "Pulse Grenades (Nova Gadget)": github_icon_base_url + "blizzard/btn-upgrade-nova-btn-upgrade-nova-pulsegrenade.png", + "Flashbang Grenades (Nova Gadget)": github_icon_base_url + "blizzard/btn-upgrade-nova-btn-upgrade-nova-flashgrenade.png", + "Ionic Force Field (Nova Gadget)": github_icon_base_url + "blizzard/btn-upgrade-terran-nova-personaldefensivematrix.png", + "Holo Decoy (Nova Gadget)": github_icon_base_url + "blizzard/btn-upgrade-nova-holographicdecoy.png", + "Tac Nuke Strike (Nova Ability)": github_icon_base_url + "blizzard/btn-ability-terran-nuclearstrike-color.png", + + "Zerg Melee Attack Level 1": github_icon_base_url + "blizzard/btn-upgrade-zerg-meleeattacks-level1.png", + "Zerg Melee Attack Level 2": github_icon_base_url + "blizzard/btn-upgrade-zerg-meleeattacks-level2.png", + "Zerg Melee Attack Level 3": github_icon_base_url + "blizzard/btn-upgrade-zerg-meleeattacks-level3.png", + "Zerg Missile Attack Level 1": github_icon_base_url + "blizzard/btn-upgrade-zerg-missileattacks-level1.png", + "Zerg Missile Attack Level 2": github_icon_base_url + "blizzard/btn-upgrade-zerg-missileattacks-level2.png", + "Zerg Missile Attack Level 3": github_icon_base_url + "blizzard/btn-upgrade-zerg-missileattacks-level3.png", + "Zerg Ground Carapace Level 1": github_icon_base_url + "blizzard/btn-upgrade-zerg-groundcarapace-level1.png", + "Zerg Ground Carapace Level 2": github_icon_base_url + "blizzard/btn-upgrade-zerg-groundcarapace-level2.png", + "Zerg Ground Carapace Level 3": github_icon_base_url + "blizzard/btn-upgrade-zerg-groundcarapace-level3.png", + "Zerg Flyer Attack Level 1": github_icon_base_url + "blizzard/btn-upgrade-zerg-airattacks-level1.png", + "Zerg Flyer Attack Level 2": github_icon_base_url + "blizzard/btn-upgrade-zerg-airattacks-level2.png", + "Zerg Flyer Attack Level 3": github_icon_base_url + "blizzard/btn-upgrade-zerg-airattacks-level3.png", + "Zerg Flyer Carapace Level 1": github_icon_base_url + "blizzard/btn-upgrade-zerg-flyercarapace-level1.png", + "Zerg Flyer Carapace Level 2": github_icon_base_url + "blizzard/btn-upgrade-zerg-flyercarapace-level2.png", + "Zerg Flyer Carapace Level 3": github_icon_base_url + "blizzard/btn-upgrade-zerg-flyercarapace-level3.png", + + "Automated Extractors (Kerrigan Tier 3)": github_icon_base_url + "blizzard/btn-ability-kerrigan-automatedextractors.png", + "Vespene Efficiency (Kerrigan Tier 5)": github_icon_base_url + "blizzard/btn-ability-kerrigan-vespeneefficiency.png", + "Twin Drones (Kerrigan Tier 5)": github_icon_base_url + "blizzard/btn-ability-kerrigan-twindrones.png", + "Improved Overlords (Kerrigan Tier 3)": github_icon_base_url + "blizzard/btn-ability-kerrigan-improvedoverlords.png", + "Ventral Sacs (Overlord)": github_icon_base_url + "blizzard/btn-upgrade-zerg-ventralsacs.png", + "Malignant Creep (Kerrigan Tier 5)": github_icon_base_url + "blizzard/btn-ability-kerrigan-malignantcreep.png", + + "Spine Crawler": github_icon_base_url + "blizzard/btn-building-zerg-spinecrawler.png", + "Spore Crawler": github_icon_base_url + "blizzard/btn-building-zerg-sporecrawler.png", + + "Zergling": github_icon_base_url + "blizzard/btn-unit-zerg-zergling.png", + "Swarm Queen": github_icon_base_url + "blizzard/btn-unit-zerg-broodqueen.png", + "Roach": github_icon_base_url + "blizzard/btn-unit-zerg-roach.png", + "Hydralisk": github_icon_base_url + "blizzard/btn-unit-zerg-hydralisk.png", + "Aberration": github_icon_base_url + "blizzard/btn-unit-zerg-aberration.png", + "Mutalisk": github_icon_base_url + "blizzard/btn-unit-zerg-mutalisk.png", + "Corruptor": github_icon_base_url + "blizzard/btn-unit-zerg-corruptor.png", + "Swarm Host": github_icon_base_url + "blizzard/btn-unit-zerg-swarmhost.png", + "Infestor": github_icon_base_url + "blizzard/btn-unit-zerg-infestor.png", + "Defiler": github_icon_base_url + "original/btn-unit-zerg-defiler@scbw.png", + "Ultralisk": github_icon_base_url + "blizzard/btn-unit-zerg-ultralisk.png", + "Brood Queen": github_icon_base_url + "blizzard/btn-unit-zerg-classicqueen.png", + "Scourge": github_icon_base_url + "blizzard/btn-unit-zerg-scourge.png", + + "Baneling Aspect (Zergling)": github_icon_base_url + "blizzard/btn-unit-zerg-baneling.png", + "Ravager Aspect (Roach)": github_icon_base_url + "blizzard/btn-unit-zerg-ravager.png", + "Impaler Aspect (Hydralisk)": github_icon_base_url + "blizzard/btn-unit-zerg-impaler.png", + "Lurker Aspect (Hydralisk)": github_icon_base_url + "blizzard/btn-unit-zerg-lurker.png", + "Brood Lord Aspect (Mutalisk/Corruptor)": github_icon_base_url + "blizzard/btn-unit-zerg-broodlord.png", + "Viper Aspect (Mutalisk/Corruptor)": github_icon_base_url + "blizzard/btn-unit-zerg-viper.png", + "Guardian Aspect (Mutalisk/Corruptor)": github_icon_base_url + "blizzard/btn-unit-zerg-primalguardian.png", + "Devourer Aspect (Mutalisk/Corruptor)": github_icon_base_url + "blizzard/btn-unit-zerg-devourerex3.png", + + "Raptor Strain (Zergling)": github_icon_base_url + "blizzard/btn-unit-zerg-zergling-raptor.png", + "Swarmling Strain (Zergling)": github_icon_base_url + "blizzard/btn-unit-zerg-zergling-swarmling.png", + "Hardened Carapace (Zergling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-hardenedcarapace.png", + "Adrenal Overload (Zergling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-adrenaloverload.png", + "Metabolic Boost (Zergling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-hotsmetabolicboost.png", + "Shredding Claws (Zergling)": github_icon_base_url + "blizzard/btn-upgrade-zergling-armorshredding.png", + "Zergling Reconstitution (Kerrigan Tier 3)": github_icon_base_url + "blizzard/btn-ability-kerrigan-zerglingreconstitution.png", + "Splitter Strain (Baneling)": github_icon_base_url + "blizzard/talent-zagara-level14-unlocksplitterling.png", + "Hunter Strain (Baneling)": github_icon_base_url + "blizzard/btn-ability-zerg-cliffjump-baneling.png", + "Corrosive Acid (Baneling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-corrosiveacid.png", + "Rupture (Baneling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-rupture.png", + "Regenerative Acid (Baneling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-regenerativebile.png", + "Centrifugal Hooks (Baneling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-centrifugalhooks.png", + "Tunneling Jaws (Baneling)": github_icon_base_url + "blizzard/btn-upgrade-zerg-tunnelingjaws.png", + "Rapid Metamorph (Baneling)": github_icon_base_url + "blizzard/btn-upgrade-terran-optimizedlogistics.png", + "Spawn Larvae (Swarm Queen)": github_icon_base_url + "blizzard/btn-unit-zerg-larva.png", + "Deep Tunnel (Swarm Queen)": github_icon_base_url + "blizzard/btn-ability-zerg-deeptunnel.png", + "Organic Carapace (Swarm Queen)": github_icon_base_url + "blizzard/btn-upgrade-zerg-organiccarapace.png", + "Bio-Mechanical Transfusion (Swarm Queen)": github_icon_base_url + "blizzard/btn-upgrade-zerg-abathur-biomechanicaltransfusion.png", + "Resource Efficiency (Swarm Queen)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Incubator Chamber (Swarm Queen)": github_icon_base_url + "blizzard/btn-upgrade-zerg-abathur-incubationchamber.png", + "Vile Strain (Roach)": github_icon_base_url + "blizzard/btn-unit-zerg-roach-vile.png", + "Corpser Strain (Roach)": github_icon_base_url + "blizzard/btn-unit-zerg-roach-corpser.png", + "Hydriodic Bile (Roach)": github_icon_base_url + "blizzard/btn-upgrade-zerg-hydriaticacid.png", + "Adaptive Plating (Roach)": github_icon_base_url + "blizzard/btn-upgrade-zerg-adaptivecarapace.png", + "Tunneling Claws (Roach)": github_icon_base_url + "blizzard/btn-upgrade-zerg-hotstunnelingclaws.png", + "Glial Reconstitution (Roach)": github_icon_base_url + "blizzard/btn-upgrade-zerg-glialreconstitution.png", + "Organic Carapace (Roach)": github_icon_base_url + "blizzard/btn-upgrade-zerg-organiccarapace.png", + "Potent Bile (Ravager)": github_icon_base_url + "blizzard/potentbile_coop.png", + "Bloated Bile Ducts (Ravager)": github_icon_base_url + "blizzard/btn-ability-zerg-abathur-corrosivebilelarge.png", + "Deep Tunnel (Ravager)": github_icon_base_url + "blizzard/btn-ability-zerg-deeptunnel.png", + "Frenzy (Hydralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-frenzy.png", + "Ancillary Carapace (Hydralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-ancillaryarmor.png", + "Grooved Spines (Hydralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-hotsgroovedspines.png", + "Muscular Augments (Hydralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-evolvemuscularaugments.png", + "Resource Efficiency (Hydralisk)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Adaptive Talons (Impaler)": github_icon_base_url + "blizzard/btn-upgrade-zerg-adaptivetalons.png", + "Secretion Glands (Impaler)": github_icon_base_url + "blizzard/btn-ability-zerg-creepspread.png", + "Hardened Tentacle Spines (Impaler)": github_icon_base_url + "blizzard/btn-ability-zerg-dehaka-impaler-tenderize.png", + "Seismic Spines (Lurker)": github_icon_base_url + "blizzard/btn-upgrade-kerrigan-seismicspines.png", + "Adapted Spines (Lurker)": github_icon_base_url + "blizzard/btn-upgrade-zerg-groovedspines.png", + "Vicious Glaive (Mutalisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-viciousglaive.png", + "Rapid Regeneration (Mutalisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-rapidregeneration.png", + "Sundering Glaive (Mutalisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-explosiveglaive.png", + "Severing Glaive (Mutalisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-explosiveglaive.png", + "Aerodynamic Glaive Shape (Mutalisk)": github_icon_base_url + "blizzard/btn-ability-dehaka-airbonusdamage.png", + "Corruption (Corruptor)": github_icon_base_url + "blizzard/btn-ability-zerg-causticspray.png", + "Caustic Spray (Corruptor)": github_icon_base_url + "blizzard/btn-ability-zerg-corruption-color.png", + "Porous Cartilage (Brood Lord)": github_icon_base_url + "blizzard/btn-upgrade-kerrigan-broodlordspeed.png", + "Evolved Carapace (Brood Lord)": github_icon_base_url + "blizzard/btn-upgrade-zerg-chitinousplating.png", + "Splitter Mitosis (Brood Lord)": github_icon_base_url + "blizzard/abilityicon_spawnbroodlings_square.png", + "Resource Efficiency (Brood Lord)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Parasitic Bomb (Viper)": github_icon_base_url + "blizzard/btn-ability-zerg-parasiticbomb.png", + "Paralytic Barbs (Viper)": github_icon_base_url + "blizzard/btn-upgrade-zerg-abathur-abduct.png", + "Virulent Microbes (Viper)": github_icon_base_url + "blizzard/btn-upgrade-zerg-abathur-castrange.png", + "Prolonged Dispersion (Guardian)": github_icon_base_url + "blizzard/btn-upgrade-zerg-abathur-prolongeddispersion.png", + "Primal Adaptation (Guardian)": github_icon_base_url + "blizzard/biomassrecovery_coop.png", + "Soronan Acid (Guardian)": github_icon_base_url + "blizzard/btn-upgrade-zerg-abathur-biomass.png", + "Corrosive Spray (Devourer)": github_icon_base_url + "blizzard/btn-upgrade-zerg-abathur-devourer-corrosivespray.png", + "Gaping Maw (Devourer)": github_icon_base_url + "blizzard/btn-ability-zerg-explode-color.png", + "Improved Osmosis (Devourer)": github_icon_base_url + "blizzard/btn-upgrade-zerg-pneumatizedcarapace.png", + "Prescient Spores (Devourer)": github_icon_base_url + "blizzard/btn-upgrade-zerg-airattacks-level2.png", + "Carrion Strain (Swarm Host)": github_icon_base_url + "blizzard/btn-unit-zerg-swarmhost-carrion.png", + "Creeper Strain (Swarm Host)": github_icon_base_url + "blizzard/btn-unit-zerg-swarmhost-creeper.png", + "Burrow (Swarm Host)": github_icon_base_url + "blizzard/btn-ability-zerg-burrow-color.png", + "Rapid Incubation (Swarm Host)": github_icon_base_url + "blizzard/btn-upgrade-zerg-rapidincubation.png", + "Pressurized Glands (Swarm Host)": github_icon_base_url + "blizzard/btn-upgrade-zerg-pressurizedglands.png", + "Locust Metabolic Boost (Swarm Host)": github_icon_base_url + "blizzard/btn-upgrade-zerg-glialreconstitution.png", + "Enduring Locusts (Swarm Host)": github_icon_base_url + "blizzard/btn-upgrade-zerg-evolveincreasedlocustlifetime.png", + "Organic Carapace (Swarm Host)": github_icon_base_url + "blizzard/btn-upgrade-zerg-organiccarapace.png", + "Resource Efficiency (Swarm Host)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Infested Terran (Infestor)": github_icon_base_url + "blizzard/btn-unit-zerg-infestedmarine.png", + "Microbial Shroud (Infestor)": github_icon_base_url + "blizzard/btn-ability-zerg-darkswarm.png", + "Noxious Strain (Ultralisk)": github_icon_base_url + "blizzard/btn-unit-zerg-ultralisk-noxious.png", + "Torrasque Strain (Ultralisk)": github_icon_base_url + "blizzard/btn-unit-zerg-ultralisk-torrasque.png", + "Burrow Charge (Ultralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-burrowcharge.png", + "Tissue Assimilation (Ultralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-tissueassimilation.png", + "Monarch Blades (Ultralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-monarchblades.png", + "Anabolic Synthesis (Ultralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-anabolicsynthesis.png", + "Chitinous Plating (Ultralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-chitinousplating.png", + "Organic Carapace (Ultralisk)": github_icon_base_url + "blizzard/btn-upgrade-zerg-organiccarapace.png", + "Resource Efficiency (Ultralisk)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Fungal Growth (Brood Queen)": github_icon_base_url + "blizzard/btn-upgrade-zerg-stukov-researchqueenfungalgrowth.png", + "Ensnare (Brood Queen)": github_icon_base_url + "blizzard/btn-ability-zerg-fungalgrowth-color.png", + "Enhanced Mitochondria (Brood Queen)": github_icon_base_url + "blizzard/btn-upgrade-zerg-stukov-queenenergyregen.png", + "Virulent Spores (Scourge)": github_icon_base_url + "blizzard/btn-upgrade-zagara-scourgesplashdamage.png", + "Resource Efficiency (Scourge)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Swarm Scourge (Scourge)": github_icon_base_url + "original/btn-upgrade-custom-triple-scourge.png", + + "Infested Medics": github_icon_base_url + "blizzard/btn-unit-terran-medicelite.png", + "Infested Siege Tanks": github_icon_base_url + "original/btn-unit-terran-siegetankmercenary-tank.png", + "Infested Banshees": github_icon_base_url + "original/btn-unit-terran-bansheemercenary.png", + + "Primal Form (Kerrigan)": github_icon_base_url + "blizzard/btn-unit-zerg-kerriganinfested.png", + "Kinetic Blast (Kerrigan Tier 1)": github_icon_base_url + "blizzard/btn-ability-kerrigan-kineticblast.png", + "Heroic Fortitude (Kerrigan Tier 1)": github_icon_base_url + "blizzard/btn-ability-kerrigan-heroicfortitude.png", + "Leaping Strike (Kerrigan Tier 1)": github_icon_base_url + "blizzard/btn-ability-kerrigan-leapingstrike.png", + "Crushing Grip (Kerrigan Tier 2)": github_icon_base_url + "blizzard/btn-ability-swarm-kerrigan-crushinggrip.png", + "Chain Reaction (Kerrigan Tier 2)": github_icon_base_url + "blizzard/btn-ability-swarm-kerrigan-chainreaction.png", + "Psionic Shift (Kerrigan Tier 2)": github_icon_base_url + "blizzard/btn-ability-kerrigan-psychicshift.png", + "Wild Mutation (Kerrigan Tier 4)": github_icon_base_url + "blizzard/btn-ability-kerrigan-wildmutation.png", + "Spawn Banelings (Kerrigan Tier 4)": github_icon_base_url + "blizzard/abilityicon_spawnbanelings_square.png", + "Mend (Kerrigan Tier 4)": github_icon_base_url + "blizzard/btn-ability-zerg-transfusion-color.png", + "Infest Broodlings (Kerrigan Tier 6)": github_icon_base_url + "blizzard/abilityicon_spawnbroodlings_square.png", + "Fury (Kerrigan Tier 6)": github_icon_base_url + "blizzard/btn-ability-kerrigan-fury.png", + "Ability Efficiency (Kerrigan Tier 6)": github_icon_base_url + "blizzard/btn-ability-kerrigan-abilityefficiency.png", + "Apocalypse (Kerrigan Tier 7)": github_icon_base_url + "blizzard/btn-ability-kerrigan-apocalypse.png", + "Spawn Leviathan (Kerrigan Tier 7)": github_icon_base_url + "blizzard/btn-unit-zerg-leviathan.png", + "Drop-Pods (Kerrigan Tier 7)": github_icon_base_url + "blizzard/btn-ability-kerrigan-droppods.png", + + "Protoss Ground Weapon Level 1": github_icon_base_url + "blizzard/btn-upgrade-protoss-groundweaponslevel1.png", + "Protoss Ground Weapon Level 2": github_icon_base_url + "blizzard/btn-upgrade-protoss-groundweaponslevel2.png", + "Protoss Ground Weapon Level 3": github_icon_base_url + "blizzard/btn-upgrade-protoss-groundweaponslevel3.png", + "Protoss Ground Armor Level 1": github_icon_base_url + "blizzard/btn-upgrade-protoss-groundarmorlevel1.png", + "Protoss Ground Armor Level 2": github_icon_base_url + "blizzard/btn-upgrade-protoss-groundarmorlevel2.png", + "Protoss Ground Armor Level 3": github_icon_base_url + "blizzard/btn-upgrade-protoss-groundarmorlevel3.png", + "Protoss Shields Level 1": github_icon_base_url + "blizzard/btn-upgrade-protoss-shieldslevel1.png", + "Protoss Shields Level 2": github_icon_base_url + "blizzard/btn-upgrade-protoss-shieldslevel2.png", + "Protoss Shields Level 3": github_icon_base_url + "blizzard/btn-upgrade-protoss-shieldslevel3.png", + "Protoss Air Weapon Level 1": github_icon_base_url + "blizzard/btn-upgrade-protoss-airweaponslevel1.png", + "Protoss Air Weapon Level 2": github_icon_base_url + "blizzard/btn-upgrade-protoss-airweaponslevel2.png", + "Protoss Air Weapon Level 3": github_icon_base_url + "blizzard/btn-upgrade-protoss-airweaponslevel3.png", + "Protoss Air Armor Level 1": github_icon_base_url + "blizzard/btn-upgrade-protoss-airarmorlevel1.png", + "Protoss Air Armor Level 2": github_icon_base_url + "blizzard/btn-upgrade-protoss-airarmorlevel2.png", + "Protoss Air Armor Level 3": github_icon_base_url + "blizzard/btn-upgrade-protoss-airarmorlevel3.png", + + "Quatro": github_icon_base_url + "blizzard/btn-progression-protoss-fenix-6-forgeresearch.png", + + "Photon Cannon": github_icon_base_url + "blizzard/btn-building-protoss-photoncannon.png", + "Khaydarin Monolith": github_icon_base_url + "blizzard/btn-unit-protoss-khaydarinmonolith.png", + "Shield Battery": github_icon_base_url + "blizzard/btn-building-protoss-shieldbattery.png", + + "Enhanced Targeting": github_icon_base_url + "blizzard/btn-upgrade-karax-turretrange.png", + "Optimized Ordnance": github_icon_base_url + "blizzard/btn-upgrade-karax-turretattackspeed.png", + "Khalai Ingenuity": github_icon_base_url + "blizzard/btn-upgrade-karax-pylonwarpininstantly.png", + "Orbital Assimilators": github_icon_base_url + "blizzard/btn-ability-spearofadun-orbitalassimilator.png", + "Amplified Assimilators": github_icon_base_url + "original/btn-research-terran-microfiltering.png", + "Warp Harmonization": github_icon_base_url + "blizzard/btn-ability-spearofadun-warpharmonization.png", + "Superior Warp Gates": github_icon_base_url + "blizzard/talent-artanis-level03-warpgatecharges.png", + "Nexus Overcharge": github_icon_base_url + "blizzard/btn-ability-spearofadun-nexusovercharge.png", + + "Zealot": github_icon_base_url + "blizzard/btn-unit-protoss-zealot-aiur.png", + "Centurion": github_icon_base_url + "blizzard/btn-unit-protoss-zealot-nerazim.png", + "Sentinel": github_icon_base_url + "blizzard/btn-unit-protoss-zealot-purifier.png", + "Supplicant": github_icon_base_url + "blizzard/btn-unit-protoss-alarak-taldarim-supplicant.png", + "Sentry": github_icon_base_url + "blizzard/btn-unit-protoss-sentry.png", + "Energizer": github_icon_base_url + "blizzard/btn-unit-protoss-sentry-purifier.png", + "Havoc": github_icon_base_url + "blizzard/btn-unit-protoss-sentry-taldarim.png", "Stalker": "https://static.wikia.nocookie.net/starcraft/images/0/0d/Icon_Protoss_Stalker.jpg", + "Instigator": github_icon_base_url + "blizzard/btn-unit-protoss-stalker-purifier.png", + "Slayer": github_icon_base_url + "blizzard/btn-unit-protoss-alarak-taldarim-stalker.png", + "Dragoon": github_icon_base_url + "blizzard/btn-unit-protoss-dragoon-void.png", + "Adept": github_icon_base_url + "blizzard/btn-unit-protoss-adept-purifier.png", "High Templar": "https://static.wikia.nocookie.net/starcraft/images/a/a0/Icon_Protoss_High_Templar.jpg", + "Signifier": github_icon_base_url + "original/btn-unit-protoss-hightemplar-nerazim.png", + "Ascendant": github_icon_base_url + "blizzard/btn-unit-protoss-hightemplar-taldarim.png", + "Dark Archon": github_icon_base_url + "blizzard/talent-vorazun-level05-unlockdarkarchon.png", "Dark Templar": "https://static.wikia.nocookie.net/starcraft/images/9/90/Icon_Protoss_Dark_Templar.jpg", + "Avenger": github_icon_base_url + "blizzard/btn-unit-protoss-darktemplar-aiur.png", + "Blood Hunter": github_icon_base_url + "blizzard/btn-unit-protoss-darktemplar-taldarim.png", + + "Leg Enhancements (Zealot/Sentinel/Centurion)": github_icon_base_url + "blizzard/btn-ability-protoss-charge-color.png", + "Shield Capacity (Zealot/Sentinel/Centurion)": github_icon_base_url + "blizzard/btn-upgrade-protoss-shieldslevel1.png", + "Blood Shield (Supplicant)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-supplicantarmor.png", + "Soul Augmentation (Supplicant)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-supplicantextrashields.png", + "Shield Regeneration (Supplicant)": github_icon_base_url + "blizzard/btn-ability-protoss-voidarmor.png", + "Force Field (Sentry)": github_icon_base_url + "blizzard/btn-ability-protoss-forcefield-color.png", + "Hallucination (Sentry)": github_icon_base_url + "blizzard/btn-ability-protoss-hallucination-color.png", + "Reclamation (Energizer)": github_icon_base_url + "blizzard/btn-ability-protoss-reclamation.png", + "Forged Chassis (Energizer)": github_icon_base_url + "blizzard/btn-upgrade-protoss-groundarmorlevel0.png", + "Detect Weakness (Havoc)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-havoctargetlockbuffed.png", + "Bloodshard Resonance (Havoc)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-rangeincrease.png", + "Cloaking Module (Sentry/Energizer/Havoc)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-permanentcloak.png", + "Rapid Recharging (Sentry/Energizer/Havoc/Shield Battery)": github_icon_base_url + "blizzard/btn-upgrade-karax-energyregen200.png", + "Disintegrating Particles (Stalker/Instigator/Slayer)": github_icon_base_url + "blizzard/btn-ability-protoss-phasedisruptor.png", + "Particle Reflection (Stalker/Instigator/Slayer)": github_icon_base_url + "blizzard/btn-upgrade-protoss-fenix-adeptchampionbounceattack.png", + "High Impact Phase Disruptor (Dragoon)": github_icon_base_url + "blizzard/btn-ability-protoss-phasedisruptor.png", + "Trillic Compression System (Dragoon)": github_icon_base_url + "blizzard/btn-ability-protoss-dragoonchassis.png", + "Singularity Charge (Dragoon)": github_icon_base_url + "blizzard/btn-upgrade-artanis-singularitycharge.png", + "Enhanced Strider Servos (Dragoon)": github_icon_base_url + "blizzard/btn-upgrade-terran-transformationservos.png", + "Shockwave (Adept)": github_icon_base_url + "blizzard/btn-upgrade-protoss-fenix-adept-recochetglaiveupgraded.png", + "Resonating Glaives (Adept)": github_icon_base_url + "blizzard/btn-upgrade-protoss-resonatingglaives.png", + "Phase Bulwark (Adept)": github_icon_base_url + "blizzard/btn-upgrade-protoss-adeptshieldupgrade.png", + "Unshackled Psionic Storm (High Templar/Signifier)": github_icon_base_url + "blizzard/btn-ability-protoss-psistorm.png", + "Hallucination (High Templar/Signifier)": github_icon_base_url + "blizzard/btn-ability-protoss-hallucination-color.png", + "Khaydarin Amulet (High Templar/Signifier)": github_icon_base_url + "blizzard/btn-upgrade-protoss-khaydarinamulet.png", + "High Archon (Archon)": github_icon_base_url + "blizzard/btn-upgrade-artanis-healingpsionicstorm.png", + "Power Overwhelming (Ascendant)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-ascendantspermanentlybetter.png", + "Chaotic Attunement (Ascendant)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-ascendant'spsiorbtravelsfurther.png", + "Blood Amulet (Ascendant)": github_icon_base_url + "blizzard/btn-upgrade-protoss-wrathwalker-chargetimeimproved.png", + "Feedback (Dark Archon)": github_icon_base_url + "blizzard/btn-ability-protoss-feedback-color.png", + "Maelstrom (Dark Archon)": github_icon_base_url + "blizzard/btn-ability-protoss-voidstasis.png", + "Argus Talisman (Dark Archon)": github_icon_base_url + "original/btn-upgrade-protoss-argustalisman@scbw.png", + "Dark Archon Meld (Dark Templar)": github_icon_base_url + "blizzard/talent-vorazun-level05-unlockdarkarchon.png", + "Shroud of Adun (Dark Templar/Avenger/Blood Hunter)": github_icon_base_url + "blizzard/talent-vorazun-level01-shadowstalk.png", + "Shadow Guard Training (Dark Templar/Avenger/Blood Hunter)": github_icon_base_url + "blizzard/btn-ability-terran-heal-color.png", + "Blink (Dark Templar/Avenger/Blood Hunter)": github_icon_base_url + "blizzard/btn-ability-protoss-shadowdash.png", + "Resource Efficiency (Dark Templar/Avenger/Blood Hunter)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + + "Warp Prism": github_icon_base_url + "blizzard/btn-unit-protoss-warpprism.png", "Immortal": "https://static.wikia.nocookie.net/starcraft/images/c/c1/Icon_Protoss_Immortal.jpg", - "Colossus": "https://static.wikia.nocookie.net/starcraft/images/4/40/Icon_Protoss_Colossus.jpg", + "Annihilator": github_icon_base_url + "blizzard/btn-unit-protoss-immortal-nerazim.png", + "Vanguard": github_icon_base_url + "blizzard/btn-unit-protoss-immortal-taldarim.png", + "Colossus": github_icon_base_url + "blizzard/btn-unit-protoss-colossus-purifier.png", + "Wrathwalker": github_icon_base_url + "blizzard/btn-unit-protoss-colossus-taldarim.png", + "Observer": github_icon_base_url + "blizzard/btn-unit-protoss-observer.png", + "Reaver": github_icon_base_url + "blizzard/btn-unit-protoss-reaver.png", + "Disruptor": github_icon_base_url + "blizzard/btn-unit-protoss-disruptor.png", + + "Gravitic Drive (Warp Prism)": github_icon_base_url + "blizzard/btn-upgrade-protoss-graviticdrive.png", + "Phase Blaster (Warp Prism)": github_icon_base_url + "blizzard/btn-upgrade-protoss-airweaponslevel0.png", + "War Configuration (Warp Prism)": github_icon_base_url + "blizzard/btn-upgrade-protoss-alarak-graviticdrive.png", + "Singularity Charge (Immortal/Annihilator)": github_icon_base_url + "blizzard/btn-upgrade-artanis-singularitycharge.png", + "Advanced Targeting Mechanics (Immortal/Annihilator)": github_icon_base_url + "blizzard/btn-ability-terran-detectionconedebuff.png", + "Agony Launchers (Vanguard)": github_icon_base_url + "blizzard/btn-upgrade-protoss-vanguard-aoeradiusincreased.png", + "Matter Dispersion (Vanguard)": github_icon_base_url + "blizzard/btn-ability-terran-detectionconedebuff.png", + "Pacification Protocol (Colossus)": github_icon_base_url + "blizzard/btn-ability-protoss-chargedblast.png", + "Rapid Power Cycling (Wrathwalker)": github_icon_base_url + "blizzard/btn-upgrade-protoss-wrathwalker-chargetimeimproved.png", + "Eye of Wrath (Wrathwalker)": github_icon_base_url + "blizzard/btn-upgrade-protoss-extendedthermallance.png", + "Gravitic Boosters (Observer)": github_icon_base_url + "blizzard/btn-upgrade-protoss-graviticbooster.png", + "Sensor Array (Observer)": github_icon_base_url + "blizzard/btn-ability-zeratul-observer-sensorarray.png", + "Scarab Damage (Reaver)": github_icon_base_url + "blizzard/btn-ability-protoss-scarabshot.png", + "Solarite Payload (Reaver)": github_icon_base_url + "blizzard/btn-upgrade-artanis-scarabsplashradius.png", + "Reaver Capacity (Reaver)": github_icon_base_url + "original/btn-upgrade-protoss-increasedscarabcapacity@scbw.png", + "Resource Efficiency (Reaver)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Phoenix": "https://static.wikia.nocookie.net/starcraft/images/b/b1/Icon_Protoss_Phoenix.jpg", - "Void Ray": "https://static.wikia.nocookie.net/starcraft/images/1/1d/VoidRay_SC2_Rend1.jpg", + "Mirage": github_icon_base_url + "blizzard/btn-unit-protoss-phoenix-purifier.png", + "Corsair": github_icon_base_url + "blizzard/btn-unit-protoss-corsair.png", + "Destroyer": github_icon_base_url + "blizzard/btn-unit-protoss-voidray-taldarim.png", + "Void Ray": github_icon_base_url + "blizzard/btn-unit-protoss-voidray-nerazim.png", "Carrier": "https://static.wikia.nocookie.net/starcraft/images/2/2c/Icon_Protoss_Carrier.jpg", + "Scout": github_icon_base_url + "original/btn-unit-protoss-scout.png", + "Tempest": github_icon_base_url + "blizzard/btn-unit-protoss-tempest-purifier.png", + "Mothership": github_icon_base_url + "blizzard/btn-unit-protoss-mothership-taldarim.png", + "Arbiter": github_icon_base_url + "blizzard/btn-unit-protoss-arbiter.png", + "Oracle": github_icon_base_url + "blizzard/btn-unit-protoss-oracle.png", + + "Ionic Wavelength Flux (Phoenix/Mirage)": github_icon_base_url + "blizzard/btn-upgrade-protoss-airweaponslevel0.png", + "Anion Pulse-Crystals (Phoenix/Mirage)": github_icon_base_url + "blizzard/btn-upgrade-protoss-phoenixrange.png", + "Stealth Drive (Corsair)": github_icon_base_url + "blizzard/btn-upgrade-vorazun-corsairpermanentlycloaked.png", + "Argus Jewel (Corsair)": github_icon_base_url + "blizzard/btn-ability-protoss-stasistrap.png", + "Sustaining Disruption (Corsair)": github_icon_base_url + "blizzard/btn-ability-protoss-disruptionweb.png", + "Neutron Shields (Corsair)": github_icon_base_url + "blizzard/btn-upgrade-protoss-shieldslevel1.png", + "Reforged Bloodshard Core (Destroyer)": github_icon_base_url + "blizzard/btn-amonshardsarmor.png", + "Flux Vanes (Void Ray/Destroyer)": github_icon_base_url + "blizzard/btn-upgrade-protoss-fluxvanes.png", + "Graviton Catapult (Carrier)": github_icon_base_url + "blizzard/btn-upgrade-protoss-gravitoncatapult.png", + "Hull of Past Glories (Carrier)": github_icon_base_url + "blizzard/btn-progression-protoss-fenix-14-colossusandcarrierchampionsresearch.png", + "Combat Sensor Array (Scout)": github_icon_base_url + "blizzard/btn-upgrade-protoss-fenix-scoutchampionrange.png", + "Apial Sensors (Scout)": github_icon_base_url + "blizzard/btn-upgrade-tychus-detection.png", + "Gravitic Thrusters (Scout)": github_icon_base_url + "blizzard/btn-upgrade-protoss-graviticbooster.png", + "Advanced Photon Blasters (Scout)": github_icon_base_url + "blizzard/btn-upgrade-protoss-airweaponslevel3.png", + "Tectonic Destabilizers (Tempest)": github_icon_base_url + "blizzard/btn-ability-protoss-disruptionblast.png", + "Quantic Reactor (Tempest)": github_icon_base_url + "blizzard/btn-upgrade-protoss-researchgravitysling.png", + "Gravity Sling (Tempest)": github_icon_base_url + "blizzard/btn-upgrade-protoss-tectonicdisruptors.png", + "Chronostatic Reinforcement (Arbiter)": github_icon_base_url + "blizzard/btn-upgrade-protoss-airarmorlevel2.png", + "Khaydarin Core (Arbiter)": github_icon_base_url + "blizzard/btn-upgrade-protoss-adeptshieldupgrade.png", + "Spacetime Anchor (Arbiter)": github_icon_base_url + "blizzard/btn-ability-protoss-stasisfield.png", + "Resource Efficiency (Arbiter)": github_icon_base_url + "blizzard/btn-ability-hornerhan-salvagebonus.png", + "Enhanced Cloak Field (Arbiter)": github_icon_base_url + "blizzard/btn-ability-stetmann-stetzonegenerator-speed.png", + "Stealth Drive (Oracle)": github_icon_base_url + "blizzard/btn-upgrade-vorazun-oraclepermanentlycloaked.png", + "Stasis Calibration (Oracle)": github_icon_base_url + "blizzard/btn-ability-protoss-oracle-stasiscalibration.png", + "Temporal Acceleration Beam (Oracle)": github_icon_base_url + "blizzard/btn-ability-protoss-oraclepulsarcannonon.png", + + "Matrix Overload": github_icon_base_url + "blizzard/btn-ability-spearofadun-matrixoverload.png", + "Guardian Shell": github_icon_base_url + "blizzard/btn-ability-spearofadun-guardianshell.png", + + "Chrono Surge (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-chronosurge.png", + "Proxy Pylon (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-deploypylon.png", + "Warp In Reinforcements (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-warpinreinforcements.png", + "Pylon Overcharge (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-protoss-purify.png", + "Orbital Strike (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-orbitalstrike.png", + "Temporal Field (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-temporalfield.png", + "Solar Lance (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-solarlance.png", + "Mass Recall (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-massrecall.png", + "Shield Overcharge (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-shieldovercharge.png", + "Deploy Fenix (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-unit-protoss-fenix.png", + "Purifier Beam (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-purifierbeam.png", + "Time Stop (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-timestop.png", + "Solar Bombardment (Spear of Adun Calldown)": github_icon_base_url + "blizzard/btn-ability-spearofadun-solarbombardment.png", + + "Reconstruction Beam (Spear of Adun Auto-Cast)": github_icon_base_url + "blizzard/btn-ability-spearofadun-reconstructionbeam.png", + "Overwatch (Spear of Adun Auto-Cast)": github_icon_base_url + "blizzard/btn-ability-zeratul-chargedcrystal-psionicwinds.png", "Nothing": "", } @@ -1824,97 +2257,310 @@ if "Starcraft 2 Wings of Liberty" in network_data_package["games"]: "Gates of Hell": range(SC2WOL_LOC_ID_OFFSET + 2600, SC2WOL_LOC_ID_OFFSET + 2700), "Belly of the Beast": range(SC2WOL_LOC_ID_OFFSET + 2700, SC2WOL_LOC_ID_OFFSET + 2800), "Shatter the Sky": range(SC2WOL_LOC_ID_OFFSET + 2800, SC2WOL_LOC_ID_OFFSET + 2900), + "All-In": range(SC2WOL_LOC_ID_OFFSET + 2900, SC2WOL_LOC_ID_OFFSET + 3000), + + "Lab Rat": range(SC2HOTS_LOC_ID_OFFSET + 100, SC2HOTS_LOC_ID_OFFSET + 200), + "Back in the Saddle": range(SC2HOTS_LOC_ID_OFFSET + 200, SC2HOTS_LOC_ID_OFFSET + 300), + "Rendezvous": range(SC2HOTS_LOC_ID_OFFSET + 300, SC2HOTS_LOC_ID_OFFSET + 400), + "Harvest of Screams": range(SC2HOTS_LOC_ID_OFFSET + 400, SC2HOTS_LOC_ID_OFFSET + 500), + "Shoot the Messenger": range(SC2HOTS_LOC_ID_OFFSET + 500, SC2HOTS_LOC_ID_OFFSET + 600), + "Enemy Within": range(SC2HOTS_LOC_ID_OFFSET + 600, SC2HOTS_LOC_ID_OFFSET + 700), + "Domination": range(SC2HOTS_LOC_ID_OFFSET + 700, SC2HOTS_LOC_ID_OFFSET + 800), + "Fire in the Sky": range(SC2HOTS_LOC_ID_OFFSET + 800, SC2HOTS_LOC_ID_OFFSET + 900), + "Old Soldiers": range(SC2HOTS_LOC_ID_OFFSET + 900, SC2HOTS_LOC_ID_OFFSET + 1000), + "Waking the Ancient": range(SC2HOTS_LOC_ID_OFFSET + 1000, SC2HOTS_LOC_ID_OFFSET + 1100), + "The Crucible": range(SC2HOTS_LOC_ID_OFFSET + 1100, SC2HOTS_LOC_ID_OFFSET + 1200), + "Supreme": range(SC2HOTS_LOC_ID_OFFSET + 1200, SC2HOTS_LOC_ID_OFFSET + 1300), + "Infested": range(SC2HOTS_LOC_ID_OFFSET + 1300, SC2HOTS_LOC_ID_OFFSET + 1400), + "Hand of Darkness": range(SC2HOTS_LOC_ID_OFFSET + 1400, SC2HOTS_LOC_ID_OFFSET + 1500), + "Phantoms of the Void": range(SC2HOTS_LOC_ID_OFFSET + 1500, SC2HOTS_LOC_ID_OFFSET + 1600), + "With Friends Like These": range(SC2HOTS_LOC_ID_OFFSET + 1600, SC2HOTS_LOC_ID_OFFSET + 1700), + "Conviction": range(SC2HOTS_LOC_ID_OFFSET + 1700, SC2HOTS_LOC_ID_OFFSET + 1800), + "Planetfall": range(SC2HOTS_LOC_ID_OFFSET + 1800, SC2HOTS_LOC_ID_OFFSET + 1900), + "Death From Above": range(SC2HOTS_LOC_ID_OFFSET + 1900, SC2HOTS_LOC_ID_OFFSET + 2000), + "The Reckoning": range(SC2HOTS_LOC_ID_OFFSET + 2000, SC2HOTS_LOC_ID_OFFSET + 2100), + + "Dark Whispers": range(SC2LOTV_LOC_ID_OFFSET + 100, SC2LOTV_LOC_ID_OFFSET + 200), + "Ghosts in the Fog": range(SC2LOTV_LOC_ID_OFFSET + 200, SC2LOTV_LOC_ID_OFFSET + 300), + "Evil Awoken": range(SC2LOTV_LOC_ID_OFFSET + 300, SC2LOTV_LOC_ID_OFFSET + 400), + + "For Aiur!": range(SC2LOTV_LOC_ID_OFFSET + 400, SC2LOTV_LOC_ID_OFFSET + 500), + "The Growing Shadow": range(SC2LOTV_LOC_ID_OFFSET + 500, SC2LOTV_LOC_ID_OFFSET + 600), + "The Spear of Adun": range(SC2LOTV_LOC_ID_OFFSET + 600, SC2LOTV_LOC_ID_OFFSET + 700), + "Sky Shield": range(SC2LOTV_LOC_ID_OFFSET + 700, SC2LOTV_LOC_ID_OFFSET + 800), + "Brothers in Arms": range(SC2LOTV_LOC_ID_OFFSET + 800, SC2LOTV_LOC_ID_OFFSET + 900), + "Amon's Reach": range(SC2LOTV_LOC_ID_OFFSET + 900, SC2LOTV_LOC_ID_OFFSET + 1000), + "Last Stand": range(SC2LOTV_LOC_ID_OFFSET + 1000, SC2LOTV_LOC_ID_OFFSET + 1100), + "Forbidden Weapon": range(SC2LOTV_LOC_ID_OFFSET + 1100, SC2LOTV_LOC_ID_OFFSET + 1200), + "Temple of Unification": range(SC2LOTV_LOC_ID_OFFSET + 1200, SC2LOTV_LOC_ID_OFFSET + 1300), + "The Infinite Cycle": range(SC2LOTV_LOC_ID_OFFSET + 1300, SC2LOTV_LOC_ID_OFFSET + 1400), + "Harbinger of Oblivion": range(SC2LOTV_LOC_ID_OFFSET + 1400, SC2LOTV_LOC_ID_OFFSET + 1500), + "Unsealing the Past": range(SC2LOTV_LOC_ID_OFFSET + 1500, SC2LOTV_LOC_ID_OFFSET + 1600), + "Purification": range(SC2LOTV_LOC_ID_OFFSET + 1600, SC2LOTV_LOC_ID_OFFSET + 1700), + "Steps of the Rite": range(SC2LOTV_LOC_ID_OFFSET + 1700, SC2LOTV_LOC_ID_OFFSET + 1800), + "Rak'Shir": range(SC2LOTV_LOC_ID_OFFSET + 1800, SC2LOTV_LOC_ID_OFFSET + 1900), + "Templar's Charge": range(SC2LOTV_LOC_ID_OFFSET + 1900, SC2LOTV_LOC_ID_OFFSET + 2000), + "Templar's Return": range(SC2LOTV_LOC_ID_OFFSET + 2000, SC2LOTV_LOC_ID_OFFSET + 2100), + "The Host": range(SC2LOTV_LOC_ID_OFFSET + 2100, SC2LOTV_LOC_ID_OFFSET + 2200), + "Salvation": range(SC2LOTV_LOC_ID_OFFSET + 2200, SC2LOTV_LOC_ID_OFFSET + 2300), + + "Into the Void": range(SC2LOTV_LOC_ID_OFFSET + 2300, SC2LOTV_LOC_ID_OFFSET + 2400), + "The Essence of Eternity": range(SC2LOTV_LOC_ID_OFFSET + 2400, SC2LOTV_LOC_ID_OFFSET + 2500), + "Amon's Fall": range(SC2LOTV_LOC_ID_OFFSET + 2500, SC2LOTV_LOC_ID_OFFSET + 2600), + + "The Escape": range(SC2NCO_LOC_ID_OFFSET + 100, SC2NCO_LOC_ID_OFFSET + 200), + "Sudden Strike": range(SC2NCO_LOC_ID_OFFSET + 200, SC2NCO_LOC_ID_OFFSET + 300), + "Enemy Intelligence": range(SC2NCO_LOC_ID_OFFSET + 300, SC2NCO_LOC_ID_OFFSET + 400), + "Trouble In Paradise": range(SC2NCO_LOC_ID_OFFSET + 400, SC2NCO_LOC_ID_OFFSET + 500), + "Night Terrors": range(SC2NCO_LOC_ID_OFFSET + 500, SC2NCO_LOC_ID_OFFSET + 600), + "Flashpoint": range(SC2NCO_LOC_ID_OFFSET + 600, SC2NCO_LOC_ID_OFFSET + 700), + "In the Enemy's Shadow": range(SC2NCO_LOC_ID_OFFSET + 700, SC2NCO_LOC_ID_OFFSET + 800), + "Dark Skies": range(SC2NCO_LOC_ID_OFFSET + 800, SC2NCO_LOC_ID_OFFSET + 900), + "End Game": range(SC2NCO_LOC_ID_OFFSET + 900, SC2NCO_LOC_ID_OFFSET + 1000), } display_data = {} # Grouped Items grouped_item_ids = { - "Progressive Weapon Upgrade": 107 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Armor Upgrade": 108 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Infantry Upgrade": 109 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Vehicle Upgrade": 110 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Ship Upgrade": 111 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Weapon/Armor Upgrade": 112 + SC2WOL_ITEM_ID_OFFSET + "Progressive Terran Weapon Upgrade": 107 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Armor Upgrade": 108 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Infantry Upgrade": 109 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Vehicle Upgrade": 110 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Ship Upgrade": 111 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Weapon/Armor Upgrade": 112 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Zerg Weapon Upgrade": 105 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Armor Upgrade": 106 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Ground Upgrade": 107 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Flyer Upgrade": 108 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Weapon/Armor Upgrade": 109 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Protoss Weapon Upgrade": 105 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Protoss Armor Upgrade": 106 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Protoss Ground Upgrade": 107 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Protoss Air Upgrade": 108 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Protoss Weapon/Armor Upgrade": 109 + SC2WOL_ITEM_ID_OFFSET, } grouped_item_replacements = { - "Progressive Weapon Upgrade": ["Progressive Infantry Weapon", "Progressive Vehicle Weapon", - "Progressive Ship Weapon"], - "Progressive Armor Upgrade": ["Progressive Infantry Armor", "Progressive Vehicle Armor", - "Progressive Ship Armor"], - "Progressive Infantry Upgrade": ["Progressive Infantry Weapon", "Progressive Infantry Armor"], - "Progressive Vehicle Upgrade": ["Progressive Vehicle Weapon", "Progressive Vehicle Armor"], - "Progressive Ship Upgrade": ["Progressive Ship Weapon", "Progressive Ship Armor"] + "Progressive Terran Weapon Upgrade": ["Progressive Terran Infantry Weapon", + "Progressive Terran Vehicle Weapon", + "Progressive Terran Ship Weapon"], + "Progressive Terran Armor Upgrade": ["Progressive Terran Infantry Armor", + "Progressive Terran Vehicle Armor", + "Progressive Terran Ship Armor"], + "Progressive Terran Infantry Upgrade": ["Progressive Terran Infantry Weapon", + "Progressive Terran Infantry Armor"], + "Progressive Terran Vehicle Upgrade": ["Progressive Terran Vehicle Weapon", + "Progressive Terran Vehicle Armor"], + "Progressive Terran Ship Upgrade": ["Progressive Terran Ship Weapon", "Progressive Terran Ship Armor"], + "Progressive Zerg Weapon Upgrade": ["Progressive Zerg Melee Attack", "Progressive Zerg Missile Attack", + "Progressive Zerg Flyer Attack"], + "Progressive Zerg Armor Upgrade": ["Progressive Zerg Ground Carapace", + "Progressive Zerg Flyer Carapace"], + "Progressive Zerg Ground Upgrade": ["Progressive Zerg Melee Attack", "Progressive Zerg Missile Attack", + "Progressive Zerg Ground Carapace"], + "Progressive Zerg Flyer Upgrade": ["Progressive Zerg Flyer Attack", "Progressive Zerg Flyer Carapace"], + "Progressive Protoss Weapon Upgrade": ["Progressive Protoss Ground Weapon", + "Progressive Protoss Air Weapon"], + "Progressive Protoss Armor Upgrade": ["Progressive Protoss Ground Armor", "Progressive Protoss Shields", + "Progressive Protoss Air Armor"], + "Progressive Protoss Ground Upgrade": ["Progressive Protoss Ground Weapon", + "Progressive Protoss Ground Armor", + "Progressive Protoss Shields"], + "Progressive Protoss Air Upgrade": ["Progressive Protoss Air Weapon", "Progressive Protoss Air Armor", + "Progressive Protoss Shields"] } - grouped_item_replacements["Progressive Weapon/Armor Upgrade"] = grouped_item_replacements[ - "Progressive Weapon Upgrade"] + \ - grouped_item_replacements[ - "Progressive Armor Upgrade"] + grouped_item_replacements["Progressive Terran Weapon/Armor Upgrade"] = \ + grouped_item_replacements["Progressive Terran Weapon Upgrade"] \ + + grouped_item_replacements["Progressive Terran Armor Upgrade"] + grouped_item_replacements["Progressive Zerg Weapon/Armor Upgrade"] = \ + grouped_item_replacements["Progressive Zerg Weapon Upgrade"] \ + + grouped_item_replacements["Progressive Zerg Armor Upgrade"] + grouped_item_replacements["Progressive Protoss Weapon/Armor Upgrade"] = \ + grouped_item_replacements["Progressive Protoss Weapon Upgrade"] \ + + grouped_item_replacements["Progressive Protoss Armor Upgrade"] replacement_item_ids = { - "Progressive Infantry Weapon": 100 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Infantry Armor": 102 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Vehicle Weapon": 103 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Vehicle Armor": 104 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Ship Weapon": 105 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Ship Armor": 106 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Infantry Weapon": 100 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Infantry Armor": 102 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Vehicle Weapon": 103 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Vehicle Armor": 104 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Ship Weapon": 105 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Ship Armor": 106 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Zerg Melee Attack": 100 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Missile Attack": 101 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Ground Carapace": 102 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Flyer Attack": 103 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Flyer Carapace": 104 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Protoss Ground Weapon": 100 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Ground Armor": 101 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Shields": 102 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Air Weapon": 103 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Air Armor": 104 + SC2LOTV_ITEM_ID_OFFSET, } - inventory = tracker_data.get_player_inventory_counts(team, player) + inventory: collections.Counter = tracker_data.get_player_inventory_counts(team, player) for grouped_item_name, grouped_item_id in grouped_item_ids.items(): count: int = inventory[grouped_item_id] if count > 0: for replacement_item in grouped_item_replacements[grouped_item_name]: replacement_id: int = replacement_item_ids[replacement_item] - inventory[replacement_id] = count + if replacement_id not in inventory or count > inventory[replacement_id]: + # If two groups provide the same individual item, maximum is used + # (this behavior is used for Protoss Shields) + inventory[replacement_id] = count # Determine display for progressive items progressive_items = { - "Progressive Infantry Weapon": 100 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Infantry Armor": 102 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Vehicle Weapon": 103 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Vehicle Armor": 104 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Ship Weapon": 105 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Ship Armor": 106 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Stimpack (Marine)": 208 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Stimpack (Firebat)": 226 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Stimpack (Marauder)": 228 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Stimpack (Reaper)": 250 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Stimpack (Hellion)": 259 + SC2WOL_ITEM_ID_OFFSET, - "Progressive High Impact Payload (Thor)": 361 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Cross-Spectrum Dampeners (Banshee)": 316 + SC2WOL_ITEM_ID_OFFSET, - "Progressive Regenerative Bio-Steel": 617 + SC2WOL_ITEM_ID_OFFSET + "Progressive Terran Infantry Weapon": 100 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Infantry Armor": 102 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Vehicle Weapon": 103 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Vehicle Armor": 104 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Ship Weapon": 105 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Terran Ship Armor": 106 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Fire-Suppression System": 206 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Orbital Command": 207 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Stimpack (Marine)": 208 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Stimpack (Firebat)": 226 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Stimpack (Marauder)": 228 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Stimpack (Reaper)": 250 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Stimpack (Hellion)": 259 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Replenishable Magazine (Vulture)": 303 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Tri-Lithium Power Cell (Diamondback)": 306 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Tomahawk Power Cells (Wraith)": 312 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Cross-Spectrum Dampeners (Banshee)": 316 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Missile Pods (Battlecruiser)": 318 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Defensive Matrix (Battlecruiser)": 319 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Immortality Protocol (Thor)": 325 + SC2WOL_ITEM_ID_OFFSET, + "Progressive High Impact Payload (Thor)": 361 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Augmented Thrusters (Planetary Fortress)": 388 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Regenerative Bio-Steel": 617 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Stealth Suit Module (Nova Suit Module)": 904 + SC2WOL_ITEM_ID_OFFSET, + "Progressive Zerg Melee Attack": 100 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Missile Attack": 101 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Ground Carapace": 102 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Flyer Attack": 103 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Zerg Flyer Carapace": 104 + SC2HOTS_ITEM_ID_OFFSET, + "Progressive Protoss Ground Weapon": 100 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Ground Armor": 101 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Shields": 102 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Air Weapon": 103 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Protoss Air Armor": 104 + SC2LOTV_ITEM_ID_OFFSET, + "Progressive Proxy Pylon (Spear of Adun Calldown)": 701 + SC2LOTV_ITEM_ID_OFFSET, } + # Format: L0, L1, L2, L3 progressive_names = { - "Progressive Infantry Weapon": ["Infantry Weapons Level 1", "Infantry Weapons Level 1", - "Infantry Weapons Level 2", "Infantry Weapons Level 3"], - "Progressive Infantry Armor": ["Infantry Armor Level 1", "Infantry Armor Level 1", - "Infantry Armor Level 2", "Infantry Armor Level 3"], - "Progressive Vehicle Weapon": ["Vehicle Weapons Level 1", "Vehicle Weapons Level 1", - "Vehicle Weapons Level 2", "Vehicle Weapons Level 3"], - "Progressive Vehicle Armor": ["Vehicle Armor Level 1", "Vehicle Armor Level 1", - "Vehicle Armor Level 2", "Vehicle Armor Level 3"], - "Progressive Ship Weapon": ["Ship Weapons Level 1", "Ship Weapons Level 1", - "Ship Weapons Level 2", "Ship Weapons Level 3"], - "Progressive Ship Armor": ["Ship Armor Level 1", "Ship Armor Level 1", - "Ship Armor Level 2", "Ship Armor Level 3"], - "Progressive Stimpack (Marine)": ["Stimpack (Marine)", "Stimpack (Marine)", - "Super Stimpack (Marine)"], - "Progressive Stimpack (Firebat)": ["Stimpack (Firebat)", "Stimpack (Firebat)", - "Super Stimpack (Firebat)"], - "Progressive Stimpack (Marauder)": ["Stimpack (Marauder)", "Stimpack (Marauder)", - "Super Stimpack (Marauder)"], - "Progressive Stimpack (Reaper)": ["Stimpack (Reaper)", "Stimpack (Reaper)", - "Super Stimpack (Reaper)"], - "Progressive Stimpack (Hellion)": ["Stimpack (Hellion)", "Stimpack (Hellion)", - "Super Stimpack (Hellion)"], - "Progressive High Impact Payload (Thor)": ["High Impact Payload (Thor)", - "High Impact Payload (Thor)", "Smart Servos (Thor)"], - "Progressive Cross-Spectrum Dampeners (Banshee)": ["Cross-Spectrum Dampeners (Banshee)", - "Cross-Spectrum Dampeners (Banshee)", - "Advanced Cross-Spectrum Dampeners (Banshee)"], - "Progressive Regenerative Bio-Steel": ["Regenerative Bio-Steel Level 1", - "Regenerative Bio-Steel Level 1", - "Regenerative Bio-Steel Level 2"] + "Progressive Terran Infantry Weapon": ["Terran Infantry Weapons Level 1", + "Terran Infantry Weapons Level 1", + "Terran Infantry Weapons Level 2", + "Terran Infantry Weapons Level 3"], + "Progressive Terran Infantry Armor": ["Terran Infantry Armor Level 1", + "Terran Infantry Armor Level 1", + "Terran Infantry Armor Level 2", + "Terran Infantry Armor Level 3"], + "Progressive Terran Vehicle Weapon": ["Terran Vehicle Weapons Level 1", + "Terran Vehicle Weapons Level 1", + "Terran Vehicle Weapons Level 2", + "Terran Vehicle Weapons Level 3"], + "Progressive Terran Vehicle Armor": ["Terran Vehicle Armor Level 1", + "Terran Vehicle Armor Level 1", + "Terran Vehicle Armor Level 2", + "Terran Vehicle Armor Level 3"], + "Progressive Terran Ship Weapon": ["Terran Ship Weapons Level 1", + "Terran Ship Weapons Level 1", + "Terran Ship Weapons Level 2", + "Terran Ship Weapons Level 3"], + "Progressive Terran Ship Armor": ["Terran Ship Armor Level 1", + "Terran Ship Armor Level 1", + "Terran Ship Armor Level 2", + "Terran Ship Armor Level 3"], + "Progressive Fire-Suppression System": ["Fire-Suppression System Level 1", + "Fire-Suppression System Level 1", + "Fire-Suppression System Level 2"], + "Progressive Orbital Command": ["Orbital Command", "Orbital Command", + "Planetary Command Module"], + "Progressive Stimpack (Marine)": ["Stimpack (Marine)", "Stimpack (Marine)", + "Super Stimpack (Marine)"], + "Progressive Stimpack (Firebat)": ["Stimpack (Firebat)", "Stimpack (Firebat)", + "Super Stimpack (Firebat)"], + "Progressive Stimpack (Marauder)": ["Stimpack (Marauder)", "Stimpack (Marauder)", + "Super Stimpack (Marauder)"], + "Progressive Stimpack (Reaper)": ["Stimpack (Reaper)", "Stimpack (Reaper)", + "Super Stimpack (Reaper)"], + "Progressive Stimpack (Hellion)": ["Stimpack (Hellion)", "Stimpack (Hellion)", + "Super Stimpack (Hellion)"], + "Progressive Replenishable Magazine (Vulture)": ["Replenishable Magazine (Vulture)", + "Replenishable Magazine (Vulture)", + "Replenishable Magazine (Free) (Vulture)"], + "Progressive Tri-Lithium Power Cell (Diamondback)": ["Tri-Lithium Power Cell (Diamondback)", + "Tri-Lithium Power Cell (Diamondback)", + "Tungsten Spikes (Diamondback)"], + "Progressive Tomahawk Power Cells (Wraith)": ["Tomahawk Power Cells (Wraith)", + "Tomahawk Power Cells (Wraith)", + "Unregistered Cloaking Module (Wraith)"], + "Progressive Cross-Spectrum Dampeners (Banshee)": ["Cross-Spectrum Dampeners (Banshee)", + "Cross-Spectrum Dampeners (Banshee)", + "Advanced Cross-Spectrum Dampeners (Banshee)"], + "Progressive Missile Pods (Battlecruiser)": ["Missile Pods (Battlecruiser) Level 1", + "Missile Pods (Battlecruiser) Level 1", + "Missile Pods (Battlecruiser) Level 2"], + "Progressive Defensive Matrix (Battlecruiser)": ["Defensive Matrix (Battlecruiser)", + "Defensive Matrix (Battlecruiser)", + "Advanced Defensive Matrix (Battlecruiser)"], + "Progressive Immortality Protocol (Thor)": ["Immortality Protocol (Thor)", + "Immortality Protocol (Thor)", + "Immortality Protocol (Free) (Thor)"], + "Progressive High Impact Payload (Thor)": ["High Impact Payload (Thor)", + "High Impact Payload (Thor)", "Smart Servos (Thor)"], + "Progressive Augmented Thrusters (Planetary Fortress)": ["Lift Off (Planetary Fortress)", + "Lift Off (Planetary Fortress)", + "Armament Stabilizers (Planetary Fortress)"], + "Progressive Regenerative Bio-Steel": ["Regenerative Bio-Steel Level 1", + "Regenerative Bio-Steel Level 1", + "Regenerative Bio-Steel Level 2", + "Regenerative Bio-Steel Level 3"], + "Progressive Stealth Suit Module (Nova Suit Module)": ["Stealth Suit Module (Nova Suit Module)", + "Cloak (Nova Suit Module)", + "Permanently Cloaked (Nova Suit Module)"], + "Progressive Zerg Melee Attack": ["Zerg Melee Attack Level 1", + "Zerg Melee Attack Level 1", + "Zerg Melee Attack Level 2", + "Zerg Melee Attack Level 3"], + "Progressive Zerg Missile Attack": ["Zerg Missile Attack Level 1", + "Zerg Missile Attack Level 1", + "Zerg Missile Attack Level 2", + "Zerg Missile Attack Level 3"], + "Progressive Zerg Ground Carapace": ["Zerg Ground Carapace Level 1", + "Zerg Ground Carapace Level 1", + "Zerg Ground Carapace Level 2", + "Zerg Ground Carapace Level 3"], + "Progressive Zerg Flyer Attack": ["Zerg Flyer Attack Level 1", + "Zerg Flyer Attack Level 1", + "Zerg Flyer Attack Level 2", + "Zerg Flyer Attack Level 3"], + "Progressive Zerg Flyer Carapace": ["Zerg Flyer Carapace Level 1", + "Zerg Flyer Carapace Level 1", + "Zerg Flyer Carapace Level 2", + "Zerg Flyer Carapace Level 3"], + "Progressive Protoss Ground Weapon": ["Protoss Ground Weapon Level 1", + "Protoss Ground Weapon Level 1", + "Protoss Ground Weapon Level 2", + "Protoss Ground Weapon Level 3"], + "Progressive Protoss Ground Armor": ["Protoss Ground Armor Level 1", + "Protoss Ground Armor Level 1", + "Protoss Ground Armor Level 2", + "Protoss Ground Armor Level 3"], + "Progressive Protoss Shields": ["Protoss Shields Level 1", "Protoss Shields Level 1", + "Protoss Shields Level 2", "Protoss Shields Level 3"], + "Progressive Protoss Air Weapon": ["Protoss Air Weapon Level 1", + "Protoss Air Weapon Level 1", + "Protoss Air Weapon Level 2", + "Protoss Air Weapon Level 3"], + "Progressive Protoss Air Armor": ["Protoss Air Armor Level 1", + "Protoss Air Armor Level 1", + "Protoss Air Armor Level 2", + "Protoss Air Armor Level 3"], + "Progressive Proxy Pylon (Spear of Adun Calldown)": ["Proxy Pylon (Spear of Adun Calldown)", + "Proxy Pylon (Spear of Adun Calldown)", + "Warp In Reinforcements (Spear of Adun Calldown)"] } for item_name, item_id in progressive_items.items(): level = min(inventory[item_id], len(progressive_names[item_name]) - 1) @@ -1925,24 +2571,62 @@ if "Starcraft 2 Wings of Liberty" in network_data_package["games"]: .replace("(", "") .replace(")", "")) display_data[base_name + "_level"] = level - display_data[base_name + "_url"] = icons[display_name] + display_data[base_name + "_url"] = icons[display_name] if display_name in icons else "FIXME" display_data[base_name + "_name"] = display_name # Multi-items multi_items = { - "+15 Starting Minerals": 800 + SC2WOL_ITEM_ID_OFFSET, - "+15 Starting Vespene": 801 + SC2WOL_ITEM_ID_OFFSET, - "+2 Starting Supply": 802 + SC2WOL_ITEM_ID_OFFSET + "Additional Starting Minerals": 800 + SC2WOL_ITEM_ID_OFFSET, + "Additional Starting Vespene": 801 + SC2WOL_ITEM_ID_OFFSET, + "Additional Starting Supply": 802 + SC2WOL_ITEM_ID_OFFSET } for item_name, item_id in multi_items.items(): base_name = item_name.split()[-1].lower() count = inventory[item_id] if base_name == "supply": - count = count * 2 - display_data[base_name + "_count"] = count - else: - count = count * 15 - display_data[base_name + "_count"] = count + count = count * starting_supply_per_item + elif base_name == "minerals": + count = count * minerals_per_item + elif base_name == "vespene": + count = count * vespene_per_item + display_data[base_name + "_count"] = count + # Kerrigan level + level_items = { + "1 Kerrigan Level": 509 + SC2HOTS_ITEM_ID_OFFSET, + "2 Kerrigan Levels": 508 + SC2HOTS_ITEM_ID_OFFSET, + "3 Kerrigan Levels": 507 + SC2HOTS_ITEM_ID_OFFSET, + "4 Kerrigan Levels": 506 + SC2HOTS_ITEM_ID_OFFSET, + "5 Kerrigan Levels": 505 + SC2HOTS_ITEM_ID_OFFSET, + "6 Kerrigan Levels": 504 + SC2HOTS_ITEM_ID_OFFSET, + "7 Kerrigan Levels": 503 + SC2HOTS_ITEM_ID_OFFSET, + "8 Kerrigan Levels": 502 + SC2HOTS_ITEM_ID_OFFSET, + "9 Kerrigan Levels": 501 + SC2HOTS_ITEM_ID_OFFSET, + "10 Kerrigan Levels": 500 + SC2HOTS_ITEM_ID_OFFSET, + "14 Kerrigan Levels": 510 + SC2HOTS_ITEM_ID_OFFSET, + "35 Kerrigan Levels": 511 + SC2HOTS_ITEM_ID_OFFSET, + "70 Kerrigan Levels": 512 + SC2HOTS_ITEM_ID_OFFSET, + } + level_amounts = { + "1 Kerrigan Level": 1, + "2 Kerrigan Levels": 2, + "3 Kerrigan Levels": 3, + "4 Kerrigan Levels": 4, + "5 Kerrigan Levels": 5, + "6 Kerrigan Levels": 6, + "7 Kerrigan Levels": 7, + "8 Kerrigan Levels": 8, + "9 Kerrigan Levels": 9, + "10 Kerrigan Levels": 10, + "14 Kerrigan Levels": 14, + "35 Kerrigan Levels": 35, + "70 Kerrigan Levels": 70, + } + kerrigan_level = 0 + for item_name, item_id in level_items.items(): + count = inventory[item_id] + amount = level_amounts[item_name] + kerrigan_level += count * amount + display_data["kerrigan_level"] = kerrigan_level # Victory condition game_state = tracker_data.get_player_client_status(team, player) @@ -1951,7 +2635,7 @@ if "Starcraft 2 Wings of Liberty" in network_data_package["games"]: # Turn location IDs into mission objective counts locations = tracker_data.get_player_locations(team, player) checked_locations = tracker_data.get_player_checked_locations(team, player) - lookup_name = lambda id: tracker_data.location_id_to_name["Starcraft 2 Wings of Liberty"][id] + lookup_name = lambda id: tracker_data.location_id_to_name["Starcraft 2"][id] location_info = {mission_name: {lookup_name(id): (id in checked_locations) for id in mission_locations if id in set(locations)} for mission_name, mission_locations in sc2wol_location_ids.items()} @@ -1963,9 +2647,9 @@ if "Starcraft 2 Wings of Liberty" in network_data_package["games"]: mission_name, mission_locations in sc2wol_location_ids.items()} checks_in_area['Total'] = sum(checks_in_area.values()) - lookup_any_item_id_to_name = tracker_data.item_id_to_name["Starcraft 2 Wings of Liberty"] + lookup_any_item_id_to_name = tracker_data.item_id_to_name["Starcraft 2"] return render_template( - "tracker__Starcraft2WingsOfLiberty.html", + "tracker__Starcraft2.html", inventory=inventory, icons=icons, acquired_items={lookup_any_item_id_to_name[id] for id, count in inventory.items() if count > 0}, @@ -1979,4 +2663,4 @@ if "Starcraft 2 Wings of Liberty" in network_data_package["games"]: **display_data, ) - _player_trackers["Starcraft 2 Wings of Liberty"] = render_Starcraft2WingsOfLiberty_tracker + _player_trackers["Starcraft 2"] = render_Starcraft2_tracker diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index d6730b7308..dea869fb55 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -134,8 +134,8 @@ # Sonic Adventure 2 Battle /worlds/sa2b/ @PoryGone @RaspberrySpace -# Starcraft 2 Wings of Liberty -/worlds/sc2wol/ @Ziktofel +# Starcraft 2 +/worlds/sc2/ @Ziktofel # Super Metroid /worlds/sm/ @lordlou diff --git a/setup.py b/setup.py index 68cab1d5e2..ffb7e02fab 100644 --- a/setup.py +++ b/setup.py @@ -84,7 +84,6 @@ non_apworlds: set = { # LogicMixin is broken before 3.10 import revamp if sys.version_info < (3,10): non_apworlds.add("Hollow Knight") - non_apworlds.add("Starcraft 2 Wings of Liberty") def download_SNI(): print("Updating SNI") diff --git a/test/general/test_items.py b/test/general/test_items.py index 82b6030379..25623d4d83 100644 --- a/test/general/test_items.py +++ b/test/general/test_items.py @@ -23,8 +23,8 @@ class TestBase(unittest.TestCase): {"Pendants", "Crystals"}, "Ocarina of Time": {"medallions", "stones", "rewards", "logic_bottles"}, - "Starcraft 2 Wings of Liberty": - {"Missions"}, + "Starcraft 2": + {"Missions", "WoL Missions"}, } for game_name, world_type in AutoWorldRegister.world_types.items(): with self.subTest(game_name, game_name=game_name): diff --git a/worlds/_sc2common/bot/bot_ai.py b/worlds/_sc2common/bot/bot_ai.py index 79c11a5ad4..b08f8af29a 100644 --- a/worlds/_sc2common/bot/bot_ai.py +++ b/worlds/_sc2common/bot/bot_ai.py @@ -12,6 +12,8 @@ from .position import Point2 from .unit import Unit from .units import Units +from worlds._sc2common.bot import logger + if TYPE_CHECKING: from .game_info import Ramp @@ -310,6 +312,7 @@ class BotAI(BotAIInternal): :param message: :param team_only:""" assert isinstance(message, str), f"{message} is not a string" + logger.debug("Sending message: " + message) await self.client.chat_send(message, team_only) def in_map_bounds(self, pos: Union[Point2, tuple, list]) -> bool: diff --git a/worlds/sc2/Client.py b/worlds/sc2/Client.py new file mode 100644 index 0000000000..fe6efb9c30 --- /dev/null +++ b/worlds/sc2/Client.py @@ -0,0 +1,1631 @@ +from __future__ import annotations + +import asyncio +import copy +import ctypes +import enum +import inspect +import logging +import multiprocessing +import os.path +import re +import sys +import tempfile +import typing +import queue +import zipfile +import io +import random +import concurrent.futures +from pathlib import Path + +# CommonClient import first to trigger ModuleUpdater +from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser +from Utils import init_logging, is_windows, async_start +from worlds.sc2 import ItemNames +from worlds.sc2.ItemGroups import item_name_groups, unlisted_item_name_groups +from worlds.sc2 import Options +from worlds.sc2.Options import ( + MissionOrder, KerriganPrimalStatus, kerrigan_unit_available, KerriganPresence, + GameSpeed, GenericUpgradeItems, GenericUpgradeResearch, ColorChoice, GenericUpgradeMissions, + LocationInclusion, ExtraLocations, MasteryLocations, ChallengeLocations, VanillaLocations, + DisableForcedCamera, SkipCutscenes, GrantStoryTech, GrantStoryLevels, TakeOverAIAllies, RequiredTactics, + SpearOfAdunPresence, SpearOfAdunPresentInNoBuild, SpearOfAdunAutonomouslyCastAbilityPresence, + SpearOfAdunAutonomouslyCastPresentInNoBuild +) + + +if __name__ == "__main__": + init_logging("SC2Client", exception_logger="Client") + +logger = logging.getLogger("Client") +sc2_logger = logging.getLogger("Starcraft2") + +import nest_asyncio +from worlds._sc2common import bot +from worlds._sc2common.bot.data import Race +from worlds._sc2common.bot.main import run_game +from worlds._sc2common.bot.player import Bot +from worlds.sc2.Items import lookup_id_to_name, get_full_item_list, ItemData, type_flaggroups, upgrade_numbers, upgrade_numbers_all +from worlds.sc2.Locations import SC2WOL_LOC_ID_OFFSET, LocationType, SC2HOTS_LOC_ID_OFFSET +from worlds.sc2.MissionTables import lookup_id_to_mission, SC2Campaign, lookup_name_to_mission, \ + lookup_id_to_campaign, MissionConnection, SC2Mission, campaign_mission_table, SC2Race, get_no_build_missions +from worlds.sc2.Regions import MissionInfo + +import colorama +from Options import Option +from NetUtils import ClientStatus, NetworkItem, JSONtoTextParser, JSONMessagePart, add_json_item, add_json_location, add_json_text, JSONTypes +from MultiServer import mark_raw + +pool = concurrent.futures.ThreadPoolExecutor(1) +loop = asyncio.get_event_loop_policy().new_event_loop() +nest_asyncio.apply(loop) +MAX_BONUS: int = 28 +VICTORY_MODULO: int = 100 + +# GitHub repo where the Map/mod data is hosted for /download_data command +DATA_REPO_OWNER = "Ziktofel" +DATA_REPO_NAME = "Archipelago-SC2-data" +DATA_API_VERSION = "API3" + +# Bot controller +CONTROLLER_HEALTH: int = 38281 +CONTROLLER2_HEALTH: int = 38282 + +# Games +STARCRAFT2 = "Starcraft 2" +STARCRAFT2_WOL = "Starcraft 2 Wings of Liberty" + + +# Data version file path. +# This file is used to tell if the downloaded data are outdated +# Associated with /download_data command +def get_metadata_file() -> str: + return os.environ["SC2PATH"] + os.sep + "ArchipelagoSC2Metadata.txt" + + +class ConfigurableOptionType(enum.Enum): + INTEGER = enum.auto() + ENUM = enum.auto() + +class ConfigurableOptionInfo(typing.NamedTuple): + name: str + variable_name: str + option_class: typing.Type[Option] + option_type: ConfigurableOptionType = ConfigurableOptionType.ENUM + can_break_logic: bool = False + + +class ColouredMessage: + def __init__(self, text: str = '') -> None: + self.parts: typing.List[dict] = [] + if text: + self(text) + def __call__(self, text: str) -> 'ColouredMessage': + add_json_text(self.parts, text) + return self + def coloured(self, text: str, colour: str) -> 'ColouredMessage': + add_json_text(self.parts, text, type="color", color=colour) + return self + def location(self, location_id: int, player_id: int = 0) -> 'ColouredMessage': + add_json_location(self.parts, location_id, player_id) + return self + def item(self, item_id: int, player_id: int = 0, flags: int = 0) -> 'ColouredMessage': + add_json_item(self.parts, item_id, player_id, flags) + return self + def player(self, player_id: int) -> 'ColouredMessage': + add_json_text(self.parts, str(player_id), type=JSONTypes.player_id) + return self + def send(self, ctx: SC2Context) -> None: + ctx.on_print_json({"data": self.parts, "cmd": "PrintJSON"}) + + +class StarcraftClientProcessor(ClientCommandProcessor): + ctx: SC2Context + echo_commands = True + + def formatted_print(self, text: str) -> None: + """Prints with kivy formatting to the GUI, and also prints to command-line and to all logs""" + # Note(mm): Bold/underline can help readability, but unfortunately the CommonClient does not filter bold tags from command-line output. + # Regardless, using `on_print_json` to get formatted text in the GUI and output in the command-line and in the logs, + # without having to branch code from CommonClient + self.ctx.on_print_json({"data": [{"text": text}]}) + + def _cmd_difficulty(self, difficulty: str = "") -> bool: + """Overrides the current difficulty set for the world. Takes the argument casual, normal, hard, or brutal""" + options = difficulty.split() + num_options = len(options) + + if num_options > 0: + difficulty_choice = options[0].lower() + if difficulty_choice == "casual": + self.ctx.difficulty_override = 0 + elif difficulty_choice == "normal": + self.ctx.difficulty_override = 1 + elif difficulty_choice == "hard": + self.ctx.difficulty_override = 2 + elif difficulty_choice == "brutal": + self.ctx.difficulty_override = 3 + else: + self.output("Unable to parse difficulty '" + options[0] + "'") + return False + + self.output("Difficulty set to " + options[0]) + return True + + else: + if self.ctx.difficulty == -1: + self.output("Please connect to a seed before checking difficulty.") + else: + current_difficulty = self.ctx.difficulty + if self.ctx.difficulty_override >= 0: + current_difficulty = self.ctx.difficulty_override + self.output("Current difficulty: " + ["Casual", "Normal", "Hard", "Brutal"][current_difficulty]) + self.output("To change the difficulty, add the name of the difficulty after the command.") + return False + + + def _cmd_game_speed(self, game_speed: str = "") -> bool: + """Overrides the current game speed for the world. + Takes the arguments default, slower, slow, normal, fast, faster""" + options = game_speed.split() + num_options = len(options) + + if num_options > 0: + speed_choice = options[0].lower() + if speed_choice == "default": + self.ctx.game_speed_override = 0 + elif speed_choice == "slower": + self.ctx.game_speed_override = 1 + elif speed_choice == "slow": + self.ctx.game_speed_override = 2 + elif speed_choice == "normal": + self.ctx.game_speed_override = 3 + elif speed_choice == "fast": + self.ctx.game_speed_override = 4 + elif speed_choice == "faster": + self.ctx.game_speed_override = 5 + else: + self.output("Unable to parse game speed '" + options[0] + "'") + return False + + self.output("Game speed set to " + options[0]) + return True + + else: + if self.ctx.game_speed == -1: + self.output("Please connect to a seed before checking game speed.") + else: + current_speed = self.ctx.game_speed + if self.ctx.game_speed_override >= 0: + current_speed = self.ctx.game_speed_override + self.output("Current game speed: " + + ["Default", "Slower", "Slow", "Normal", "Fast", "Faster"][current_speed]) + self.output("To change the game speed, add the name of the speed after the command," + " or Default to select based on difficulty.") + return False + + @mark_raw + def _cmd_received(self, filter_search: str = "") -> bool: + """List received items. + Pass in a parameter to filter the search by partial item name or exact item group.""" + # Groups must be matched case-sensitively, so we properly capitalize the search term + # eg. "Spear of Adun" over "Spear Of Adun" or "spear of adun" + # This fails a lot of item name matches, but those should be found by partial name match + formatted_filter_search = " ".join([(part.lower() if len(part) <= 3 else part.lower().capitalize()) for part in filter_search.split()]) + + def item_matches_filter(item_name: str) -> bool: + # The filter can be an exact group name or a partial item name + # Partial item name can be matched case-insensitively + if filter_search.lower() in item_name.lower(): + return True + # The search term should already be formatted as a group name + if formatted_filter_search in item_name_groups and item_name in item_name_groups[formatted_filter_search]: + return True + return False + + items = get_full_item_list() + categorized_items: typing.Dict[SC2Race, typing.List[int]] = {} + parent_to_child: typing.Dict[int, typing.List[int]] = {} + items_received: typing.Dict[int, typing.List[NetworkItem]] = {} + filter_match_count = 0 + for item in self.ctx.items_received: + items_received.setdefault(item.item, []).append(item) + items_received_set = set(items_received) + for item_data in items.values(): + if item_data.parent_item: + parent_to_child.setdefault(items[item_data.parent_item].code, []).append(item_data.code) + else: + categorized_items.setdefault(item_data.race, []).append(item_data.code) + for faction in SC2Race: + has_printed_faction_title = False + def print_faction_title(): + if not has_printed_faction_title: + self.formatted_print(f" [u]{faction.name}[/u] ") + + for item_id in categorized_items[faction]: + item_name = self.ctx.item_names[item_id] + received_child_items = items_received_set.intersection(parent_to_child.get(item_id, [])) + matching_children = [child for child in received_child_items + if item_matches_filter(self.ctx.item_names[child])] + received_items_of_this_type = items_received.get(item_id, []) + item_is_match = item_matches_filter(item_name) + if item_is_match or len(matching_children) > 0: + # Print found item if it or its children match the filter + if item_is_match: + filter_match_count += len(received_items_of_this_type) + for item in received_items_of_this_type: + print_faction_title() + has_printed_faction_title = True + (ColouredMessage('* ').item(item.item, flags=item.flags) + (" from ").location(item.location, self.ctx.slot) + (" by ").player(item.player) + ).send(self.ctx) + + if received_child_items: + # We have this item's children + if len(matching_children) == 0: + # ...but none of them match the filter + continue + + if not received_items_of_this_type: + # We didn't receive the item itself + print_faction_title() + has_printed_faction_title = True + ColouredMessage("- ").coloured(item_name, "black")(" - not obtained").send(self.ctx) + + for child_item in matching_children: + received_items_of_this_type = items_received.get(child_item, []) + for item in received_items_of_this_type: + filter_match_count += len(received_items_of_this_type) + (ColouredMessage(' * ').item(item.item, flags=item.flags) + (" from ").location(item.location, self.ctx.slot) + (" by ").player(item.player) + ).send(self.ctx) + + non_matching_children = len(received_child_items) - len(matching_children) + if non_matching_children > 0: + self.formatted_print(f" + {non_matching_children} child items that don't match the filter") + if filter_search == "": + self.formatted_print(f"[b]Obtained: {len(self.ctx.items_received)} items[/b]") + else: + self.formatted_print(f"[b]Filter \"{filter_search}\" found {filter_match_count} out of {len(self.ctx.items_received)} obtained items[/b]") + return True + + def _cmd_option(self, option_name: str = "", option_value: str = "") -> None: + """Sets a Starcraft game option that can be changed after generation. Use "/option list" to see all options.""" + + LOGIC_WARNING = f" *Note changing this may result in logically unbeatable games*\n" + + options = ( + ConfigurableOptionInfo('kerrigan_presence', 'kerrigan_presence', Options.KerriganPresence, can_break_logic=True), + ConfigurableOptionInfo('soa_presence', 'spear_of_adun_presence', Options.SpearOfAdunPresence, can_break_logic=True), + ConfigurableOptionInfo('soa_in_nobuilds', 'spear_of_adun_present_in_no_build', Options.SpearOfAdunPresentInNoBuild, can_break_logic=True), + ConfigurableOptionInfo('control_ally', 'take_over_ai_allies', Options.TakeOverAIAllies, can_break_logic=True), + ConfigurableOptionInfo('minerals_per_item', 'minerals_per_item', Options.MineralsPerItem, ConfigurableOptionType.INTEGER), + ConfigurableOptionInfo('gas_per_item', 'vespene_per_item', Options.VespenePerItem, ConfigurableOptionType.INTEGER), + ConfigurableOptionInfo('supply_per_item', 'starting_supply_per_item', Options.StartingSupplyPerItem, ConfigurableOptionType.INTEGER), + ConfigurableOptionInfo('no_forced_camera', 'disable_forced_camera', Options.DisableForcedCamera), + ConfigurableOptionInfo('skip_cutscenes', 'skip_cutscenes', Options.SkipCutscenes), + ) + + WARNING_COLOUR = "salmon" + CMD_COLOUR = "slateblue" + boolean_option_map = { + 'y': 'true', 'yes': 'true', 'n': 'false', 'no': 'false', + } + + help_message = ColouredMessage(inspect.cleandoc(""" + Options + -------------------- + """))('\n') + for option in options: + option_help_text = inspect.cleandoc(option.option_class.__doc__ or "No description provided.").split('\n', 1)[0] + help_message.coloured(option.name, CMD_COLOUR)(": " + " | ".join(option.option_class.options) + + f" -- {option_help_text}\n") + if option.can_break_logic: + help_message.coloured(LOGIC_WARNING, WARNING_COLOUR) + help_message("--------------------\nEnter an option without arguments to see its current value.\n") + + if not option_name or option_name == 'list' or option_name == 'help': + help_message.send(self.ctx) + return + for option in options: + if option_name == option.name: + option_value = boolean_option_map.get(option_value, option_value) + if not option_value: + pass + elif option.option_type == ConfigurableOptionType.ENUM and option_value in option.option_class.options: + self.ctx.__dict__[option.variable_name] = option.option_class.options[option_value] + elif option.option_type == ConfigurableOptionType.INTEGER: + try: + self.ctx.__dict__[option.variable_name] = int(option_value, base=0) + except: + self.output(f"{option_value} is not a valid integer") + else: + self.output(f"Unknown option value '{option_value}'") + ColouredMessage(f"{option.name} is '{option.option_class.get_option_name(self.ctx.__dict__[option.variable_name])}'").send(self.ctx) + break + else: + self.output(f"Unknown option '{option_name}'") + help_message.send(self.ctx) + + def _cmd_color(self, faction: str = "", color: str = "") -> None: + """Changes the player color for a given faction.""" + player_colors = [ + "White", "Red", "Blue", "Teal", + "Purple", "Yellow", "Orange", "Green", + "LightPink", "Violet", "LightGrey", "DarkGreen", + "Brown", "LightGreen", "DarkGrey", "Pink", + "Rainbow", "Random", "Default" + ] + var_names = { + 'raynor': 'player_color_raynor', + 'kerrigan': 'player_color_zerg', + 'primal': 'player_color_zerg_primal', + 'protoss': 'player_color_protoss', + 'nova': 'player_color_nova', + } + faction = faction.lower() + if not faction: + for faction_name, key in var_names.items(): + self.output(f"Current player color for {faction_name}: {player_colors[self.ctx.__dict__[key]]}") + self.output("To change your color, add the faction name and color after the command.") + self.output("Available factions: " + ', '.join(var_names)) + self.output("Available colors: " + ', '.join(player_colors)) + return + elif faction not in var_names: + self.output(f"Unknown faction '{faction}'.") + self.output("Available factions: " + ', '.join(var_names)) + return + match_colors = [player_color.lower() for player_color in player_colors] + if not color: + self.output(f"Current player color for {faction}: {player_colors[self.ctx.__dict__[var_names[faction]]]}") + self.output("To change this faction's colors, add the name of the color after the command.") + self.output("Available colors: " + ', '.join(player_colors)) + else: + if color.lower() not in match_colors: + self.output(color + " is not a valid color. Available colors: " + ', '.join(player_colors)) + return + if color.lower() == "random": + color = random.choice(player_colors[:16]) + self.ctx.__dict__[var_names[faction]] = match_colors.index(color.lower()) + self.ctx.pending_color_update = True + self.output(f"Color for {faction} set to " + player_colors[self.ctx.__dict__[var_names[faction]]]) + + def _cmd_disable_mission_check(self) -> bool: + """Disables the check to see if a mission is available to play. Meant for co-op runs where one player can play + the next mission in a chain the other player is doing.""" + self.ctx.missions_unlocked = True + sc2_logger.info("Mission check has been disabled") + return True + + def _cmd_play(self, mission_id: str = "") -> bool: + """Start a Starcraft 2 mission""" + + options = mission_id.split() + num_options = len(options) + + if num_options > 0: + mission_number = int(options[0]) + + self.ctx.play_mission(mission_number) + + else: + sc2_logger.info( + "Mission ID needs to be specified. Use /unfinished or /available to view ids for available missions.") + return False + + return True + + def _cmd_available(self) -> bool: + """Get what missions are currently available to play""" + + request_available_missions(self.ctx) + return True + + def _cmd_unfinished(self) -> bool: + """Get what missions are currently available to play and have not had all locations checked""" + + request_unfinished_missions(self.ctx) + return True + + @mark_raw + def _cmd_set_path(self, path: str = '') -> bool: + """Manually set the SC2 install directory (if the automatic detection fails).""" + if path: + os.environ["SC2PATH"] = path + is_mod_installed_correctly() + return True + else: + sc2_logger.warning("When using set_path, you must type the path to your SC2 install directory.") + return False + + def _cmd_download_data(self) -> bool: + """Download the most recent release of the necessary files for playing SC2 with + Archipelago. Will overwrite existing files.""" + pool.submit(self._download_data) + return True + + @staticmethod + def _download_data() -> bool: + if "SC2PATH" not in os.environ: + check_game_install_path() + + if os.path.exists(get_metadata_file()): + with open(get_metadata_file(), "r") as f: + metadata = f.read() + else: + metadata = None + + tempzip, metadata = download_latest_release_zip( + DATA_REPO_OWNER, DATA_REPO_NAME, DATA_API_VERSION, metadata=metadata, force_download=True) + + if tempzip: + try: + zipfile.ZipFile(tempzip).extractall(path=os.environ["SC2PATH"]) + sc2_logger.info(f"Download complete. Package installed.") + if metadata is not None: + with open(get_metadata_file(), "w") as f: + f.write(metadata) + finally: + os.remove(tempzip) + else: + sc2_logger.warning("Download aborted/failed. Read the log for more information.") + return False + return True + + +class SC2JSONtoTextParser(JSONtoTextParser): + def __init__(self, ctx) -> None: + self.handlers = { + "ItemSend": self._handle_color, + "ItemCheat": self._handle_color, + "Hint": self._handle_color, + } + super().__init__(ctx) + + def _handle_color(self, node: JSONMessagePart) -> str: + codes = node["color"].split(";") + buffer = "".join(self.color_code(code) for code in codes if code in self.color_codes) + return buffer + self._handle_text(node) + '' + + def color_code(self, code: str) -> str: + return '' + + +class SC2Context(CommonContext): + command_processor = StarcraftClientProcessor + game = STARCRAFT2 + items_handling = 0b111 + + def __init__(self, *args, **kwargs) -> None: + super(SC2Context, self).__init__(*args, **kwargs) + self.raw_text_parser = SC2JSONtoTextParser(self) + + self.difficulty = -1 + self.game_speed = -1 + self.disable_forced_camera = 0 + self.skip_cutscenes = 0 + self.all_in_choice = 0 + self.mission_order = 0 + self.player_color_raynor = ColorChoice.option_blue + self.player_color_zerg = ColorChoice.option_orange + self.player_color_zerg_primal = ColorChoice.option_purple + self.player_color_protoss = ColorChoice.option_blue + self.player_color_nova = ColorChoice.option_dark_grey + self.pending_color_update = False + self.kerrigan_presence = 0 + self.kerrigan_primal_status = 0 + self.levels_per_check = 0 + self.checks_per_level = 1 + self.mission_req_table: typing.Dict[SC2Campaign, typing.Dict[str, MissionInfo]] = {} + self.final_mission: int = 29 + self.announcements: queue.Queue = queue.Queue() + self.sc2_run_task: typing.Optional[asyncio.Task] = None + self.missions_unlocked: bool = False # allow launching missions ignoring requirements + self.generic_upgrade_missions = 0 + self.generic_upgrade_research = 0 + self.generic_upgrade_items = 0 + self.location_inclusions: typing.Dict[LocationType, int] = {} + self.plando_locations: typing.List[str] = [] + self.current_tooltip = None + self.last_loc_list = None + self.difficulty_override = -1 + self.game_speed_override = -1 + self.mission_id_to_location_ids: typing.Dict[int, typing.List[int]] = {} + self.last_bot: typing.Optional[ArchipelagoBot] = None + self.slot_data_version = 2 + self.grant_story_tech = 0 + self.required_tactics = RequiredTactics.option_standard + self.take_over_ai_allies = TakeOverAIAllies.option_false + self.spear_of_adun_presence = SpearOfAdunPresence.option_not_present + self.spear_of_adun_present_in_no_build = SpearOfAdunPresentInNoBuild.option_false + self.spear_of_adun_autonomously_cast_ability_presence = SpearOfAdunAutonomouslyCastAbilityPresence.option_not_present + self.spear_of_adun_autonomously_cast_present_in_no_build = SpearOfAdunAutonomouslyCastPresentInNoBuild.option_false + self.minerals_per_item = 15 + self.vespene_per_item = 15 + self.starting_supply_per_item = 2 + self.nova_covert_ops_only = False + self.kerrigan_levels_per_mission_completed = 0 + + async def server_auth(self, password_requested: bool = False) -> None: + self.game = STARCRAFT2 + if password_requested and not self.password: + await super(SC2Context, self).server_auth(password_requested) + await self.get_username() + await self.send_connect() + if self.ui: + self.ui.first_check = True + + def is_legacy_game(self): + return self.game == STARCRAFT2_WOL + + def event_invalid_game(self): + if self.is_legacy_game(): + self.game = STARCRAFT2 + super().event_invalid_game() + else: + self.game = STARCRAFT2_WOL + async_start(self.send_connect()) + + def on_package(self, cmd: str, args: dict) -> None: + if cmd == "Connected": + self.difficulty = args["slot_data"]["game_difficulty"] + self.game_speed = args["slot_data"].get("game_speed", GameSpeed.option_default) + self.disable_forced_camera = args["slot_data"].get("disable_forced_camera", DisableForcedCamera.default) + self.skip_cutscenes = args["slot_data"].get("skip_cutscenes", SkipCutscenes.default) + self.all_in_choice = args["slot_data"]["all_in_map"] + self.slot_data_version = args["slot_data"].get("version", 2) + slot_req_table: dict = args["slot_data"]["mission_req"] + + first_item = list(slot_req_table.keys())[0] + # Maintaining backwards compatibility with older slot data + if first_item in [str(campaign.id) for campaign in SC2Campaign]: + # Multi-campaign + self.mission_req_table = {} + for campaign_id in slot_req_table: + campaign = lookup_id_to_campaign[int(campaign_id)] + self.mission_req_table[campaign] = { + mission: self.parse_mission_info(mission_info) + for mission, mission_info in slot_req_table[campaign_id].items() + } + else: + # Old format + self.mission_req_table = {SC2Campaign.GLOBAL: { + mission: self.parse_mission_info(mission_info) + for mission, mission_info in slot_req_table.items() + } + } + + self.mission_order = args["slot_data"].get("mission_order", MissionOrder.option_vanilla) + self.final_mission = args["slot_data"].get("final_mission", SC2Mission.ALL_IN.id) + self.player_color_raynor = args["slot_data"].get("player_color_terran_raynor", ColorChoice.option_blue) + self.player_color_zerg = args["slot_data"].get("player_color_zerg", ColorChoice.option_orange) + self.player_color_zerg_primal = args["slot_data"].get("player_color_zerg_primal", ColorChoice.option_purple) + self.player_color_protoss = args["slot_data"].get("player_color_protoss", ColorChoice.option_blue) + self.player_color_nova = args["slot_data"].get("player_color_nova", ColorChoice.option_dark_grey) + self.generic_upgrade_missions = args["slot_data"].get("generic_upgrade_missions", GenericUpgradeMissions.default) + self.generic_upgrade_items = args["slot_data"].get("generic_upgrade_items", GenericUpgradeItems.option_individual_items) + self.generic_upgrade_research = args["slot_data"].get("generic_upgrade_research", GenericUpgradeResearch.option_vanilla) + self.kerrigan_presence = args["slot_data"].get("kerrigan_presence", KerriganPresence.option_vanilla) + self.kerrigan_primal_status = args["slot_data"].get("kerrigan_primal_status", KerriganPrimalStatus.option_vanilla) + self.kerrigan_levels_per_mission_completed = args["slot_data"].get("kerrigan_levels_per_mission_completed", 0) + self.kerrigan_levels_per_mission_completed_cap = args["slot_data"].get("kerrigan_levels_per_mission_completed_cap", -1) + self.kerrigan_total_level_cap = args["slot_data"].get("kerrigan_total_level_cap", -1) + self.grant_story_tech = args["slot_data"].get("grant_story_tech", GrantStoryTech.option_false) + self.grant_story_levels = args["slot_data"].get("grant_story_levels", GrantStoryLevels.option_additive) + self.required_tactics = args["slot_data"].get("required_tactics", RequiredTactics.option_standard) + self.take_over_ai_allies = args["slot_data"].get("take_over_ai_allies", TakeOverAIAllies.option_false) + self.spear_of_adun_presence = args["slot_data"].get("spear_of_adun_presence", SpearOfAdunPresence.option_not_present) + self.spear_of_adun_present_in_no_build = args["slot_data"].get("spear_of_adun_present_in_no_build", SpearOfAdunPresentInNoBuild.option_false) + self.spear_of_adun_autonomously_cast_ability_presence = args["slot_data"].get("spear_of_adun_autonomously_cast_ability_presence", SpearOfAdunAutonomouslyCastAbilityPresence.option_not_present) + self.spear_of_adun_autonomously_cast_present_in_no_build = args["slot_data"].get("spear_of_adun_autonomously_cast_present_in_no_build", SpearOfAdunAutonomouslyCastPresentInNoBuild.option_false) + self.minerals_per_item = args["slot_data"].get("minerals_per_item", 15) + self.vespene_per_item = args["slot_data"].get("vespene_per_item", 15) + self.starting_supply_per_item = args["slot_data"].get("starting_supply_per_item", 2) + self.nova_covert_ops_only = args["slot_data"].get("nova_covert_ops_only", False) + + if self.required_tactics == RequiredTactics.option_no_logic: + # Locking Grant Story Tech/Levels if no logic + self.grant_story_tech = GrantStoryTech.option_true + self.grant_story_levels = GrantStoryLevels.option_minimum + + self.location_inclusions = { + LocationType.VICTORY: LocationInclusion.option_enabled, # Victory checks are always enabled + LocationType.VANILLA: args["slot_data"].get("vanilla_locations", VanillaLocations.default), + LocationType.EXTRA: args["slot_data"].get("extra_locations", ExtraLocations.default), + LocationType.CHALLENGE: args["slot_data"].get("challenge_locations", ChallengeLocations.default), + LocationType.MASTERY: args["slot_data"].get("mastery_locations", MasteryLocations.default), + } + self.plando_locations = args["slot_data"].get("plando_locations", []) + + self.build_location_to_mission_mapping() + + # Looks for the required maps and mods for SC2. Runs check_game_install_path. + maps_present = is_mod_installed_correctly() + if os.path.exists(get_metadata_file()): + with open(get_metadata_file(), "r") as f: + current_ver = f.read() + sc2_logger.debug(f"Current version: {current_ver}") + if is_mod_update_available(DATA_REPO_OWNER, DATA_REPO_NAME, DATA_API_VERSION, current_ver): + sc2_logger.info("NOTICE: Update for required files found. Run /download_data to install.") + elif maps_present: + sc2_logger.warning("NOTICE: Your map files may be outdated (version number not found). " + "Run /download_data to update them.") + + @staticmethod + def parse_mission_info(mission_info: dict[str, typing.Any]) -> MissionInfo: + if mission_info.get("id") is not None: + mission_info["mission"] = lookup_id_to_mission[mission_info["id"]] + elif isinstance(mission_info["mission"], int): + mission_info["mission"] = lookup_id_to_mission[mission_info["mission"]] + + return MissionInfo( + **{field: value for field, value in mission_info.items() if field in MissionInfo._fields} + ) + + def find_campaign(self, mission_name: str) -> SC2Campaign: + data = self.mission_req_table + for campaign in data.keys(): + if mission_name in data[campaign].keys(): + return campaign + sc2_logger.info(f"Attempted to find campaign of unknown mission '{mission_name}'; defaulting to GLOBAL") + return SC2Campaign.GLOBAL + + + + def on_print_json(self, args: dict) -> None: + # goes to this world + if "receiving" in args and self.slot_concerns_self(args["receiving"]): + relevant = True + # found in this world + elif "item" in args and self.slot_concerns_self(args["item"].player): + relevant = True + # not related + else: + relevant = False + + if relevant: + self.announcements.put(self.raw_text_parser(copy.deepcopy(args["data"]))) + + super(SC2Context, self).on_print_json(args) + + def run_gui(self) -> None: + from .ClientGui import start_gui + start_gui(self) + + + async def shutdown(self) -> None: + await super(SC2Context, self).shutdown() + if self.last_bot: + self.last_bot.want_close = True + if self.sc2_run_task: + self.sc2_run_task.cancel() + + def play_mission(self, mission_id: int) -> bool: + if self.missions_unlocked or is_mission_available(self, mission_id): + if self.sc2_run_task: + if not self.sc2_run_task.done(): + sc2_logger.warning("Starcraft 2 Client is still running!") + self.sc2_run_task.cancel() # doesn't actually close the game, just stops the python task + if self.slot is None: + sc2_logger.warning("Launching Mission without Archipelago authentication, " + "checks will not be registered to server.") + self.sc2_run_task = asyncio.create_task(starcraft_launch(self, mission_id), + name="Starcraft 2 Launch") + return True + else: + sc2_logger.info( + f"{lookup_id_to_mission[mission_id].mission_name} is not currently unlocked. " + f"Use /unfinished or /available to see what is available.") + return False + + def build_location_to_mission_mapping(self) -> None: + mission_id_to_location_ids: typing.Dict[int, typing.Set[int]] = { + mission_info.mission.id: set() for campaign_mission in self.mission_req_table.values() for mission_info in campaign_mission.values() + } + + for loc in self.server_locations: + offset = SC2WOL_LOC_ID_OFFSET if loc < SC2HOTS_LOC_ID_OFFSET \ + else (SC2HOTS_LOC_ID_OFFSET - SC2Mission.ALL_IN.id * VICTORY_MODULO) + mission_id, objective = divmod(loc - offset, VICTORY_MODULO) + mission_id_to_location_ids[mission_id].add(objective) + self.mission_id_to_location_ids = {mission_id: sorted(objectives) for mission_id, objectives in + mission_id_to_location_ids.items()} + + def locations_for_mission(self, mission_name: str): + mission = lookup_name_to_mission[mission_name] + mission_id: int = mission.id + objectives = self.mission_id_to_location_ids[mission_id] + for objective in objectives: + yield get_location_offset(mission_id) + mission_id * VICTORY_MODULO + objective + + +class CompatItemHolder(typing.NamedTuple): + name: str + quantity: int = 1 + + +async def main(): + multiprocessing.freeze_support() + parser = get_base_parser() + parser.add_argument('--name', default=None, help="Slot Name to connect as.") + args = parser.parse_args() + + ctx = SC2Context(args.connect, args.password) + ctx.auth = args.name + if ctx.server_task is None: + ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop") + + if gui_enabled: + ctx.run_gui() + ctx.run_cli() + + await ctx.exit_event.wait() + + await ctx.shutdown() + +# These items must be given to the player if the game is generated on version 2 +API2_TO_API3_COMPAT_ITEMS: typing.Set[CompatItemHolder] = { + CompatItemHolder(ItemNames.PHOTON_CANNON), + CompatItemHolder(ItemNames.OBSERVER), + CompatItemHolder(ItemNames.WARP_HARMONIZATION), + CompatItemHolder(ItemNames.PROGRESSIVE_PROTOSS_GROUND_WEAPON, 3), + CompatItemHolder(ItemNames.PROGRESSIVE_PROTOSS_GROUND_ARMOR, 3), + CompatItemHolder(ItemNames.PROGRESSIVE_PROTOSS_SHIELDS, 3), + CompatItemHolder(ItemNames.PROGRESSIVE_PROTOSS_AIR_WEAPON, 3), + CompatItemHolder(ItemNames.PROGRESSIVE_PROTOSS_AIR_ARMOR, 3), + CompatItemHolder(ItemNames.PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE, 3) +} + + +def compat_item_to_network_items(compat_item: CompatItemHolder) -> typing.List[NetworkItem]: + item_id = get_full_item_list()[compat_item.name].code + network_item = NetworkItem(item_id, 0, 0, 0) + return compat_item.quantity * [network_item] + + +def calculate_items(ctx: SC2Context) -> typing.Dict[SC2Race, typing.List[int]]: + items = ctx.items_received.copy() + # Items unlocked in API2 by default (Prophecy default items) + if ctx.slot_data_version < 3: + for compat_item in API2_TO_API3_COMPAT_ITEMS: + items.extend(compat_item_to_network_items(compat_item)) + + network_item: NetworkItem + accumulators: typing.Dict[SC2Race, typing.List[int]] = {race: [0 for _ in type_flaggroups[race]] for race in SC2Race} + + # Protoss Shield grouped item specific logic + shields_from_ground_upgrade: int = 0 + shields_from_air_upgrade: int = 0 + + item_list = get_full_item_list() + for network_item in items: + name: str = lookup_id_to_name[network_item.item] + item_data: ItemData = item_list[name] + + # exists exactly once + if item_data.quantity == 1: + accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] |= 1 << item_data.number + + # exists multiple times + elif item_data.type in ["Upgrade", "Progressive Upgrade","Progressive Upgrade 2"]: + flaggroup = type_flaggroups[item_data.race][item_data.type] + + # Generic upgrades apply only to Weapon / Armor upgrades + if item_data.type != "Upgrade" or ctx.generic_upgrade_items == 0: + accumulators[item_data.race][flaggroup] += 1 << item_data.number + else: + if name == ItemNames.PROGRESSIVE_PROTOSS_GROUND_UPGRADE: + shields_from_ground_upgrade += 1 + if name == ItemNames.PROGRESSIVE_PROTOSS_AIR_UPGRADE: + shields_from_air_upgrade += 1 + for bundled_number in upgrade_numbers[item_data.number]: + accumulators[item_data.race][flaggroup] += 1 << bundled_number + + # Regen bio-steel nerf with API3 - undo for older games + if ctx.slot_data_version < 3 and name == ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL: + current_level = (accumulators[item_data.race][flaggroup] >> item_data.number) % 4 + if current_level == 2: + # Switch from level 2 to level 3 for compatibility + accumulators[item_data.race][flaggroup] += 1 << item_data.number + # sum + else: + if name == ItemNames.STARTING_MINERALS: + accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] += ctx.minerals_per_item + elif name == ItemNames.STARTING_VESPENE: + accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] += ctx.vespene_per_item + elif name == ItemNames.STARTING_SUPPLY: + accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] += ctx.starting_supply_per_item + else: + accumulators[item_data.race][type_flaggroups[item_data.race][item_data.type]] += item_data.number + + # Fix Shields from generic upgrades by unit class (Maximum of ground/air upgrades) + if shields_from_ground_upgrade > 0 or shields_from_air_upgrade > 0: + shield_upgrade_level = max(shields_from_ground_upgrade, shields_from_air_upgrade) + shield_upgrade_item = item_list[ItemNames.PROGRESSIVE_PROTOSS_SHIELDS] + for _ in range(0, shield_upgrade_level): + accumulators[shield_upgrade_item.race][type_flaggroups[shield_upgrade_item.race][shield_upgrade_item.type]] += 1 << shield_upgrade_item.number + + # Kerrigan levels per check + accumulators[SC2Race.ZERG][type_flaggroups[SC2Race.ZERG]["Level"]] += (len(ctx.checked_locations) // ctx.checks_per_level) * ctx.levels_per_check + + # Upgrades from completed missions + if ctx.generic_upgrade_missions > 0: + total_missions = sum(len(ctx.mission_req_table[campaign]) for campaign in ctx.mission_req_table) + for race in SC2Race: + if "Upgrade" not in type_flaggroups[race]: + continue + upgrade_flaggroup = type_flaggroups[race]["Upgrade"] + num_missions = ctx.generic_upgrade_missions * total_missions + amounts = [ + num_missions // 100, + 2 * num_missions // 100, + 3 * num_missions // 100 + ] + upgrade_count = 0 + completed = len([id for id in ctx.mission_id_to_location_ids if get_location_offset(id) + VICTORY_MODULO * id in ctx.checked_locations]) + for amount in amounts: + if completed >= amount: + upgrade_count += 1 + # Equivalent to "Progressive Weapon/Armor Upgrade" item + for bundled_number in upgrade_numbers[upgrade_numbers_all[race]]: + accumulators[race][upgrade_flaggroup] += upgrade_count << bundled_number + + return accumulators + + +def calc_difficulty(difficulty: int): + if difficulty == 0: + return 'C' + elif difficulty == 1: + return 'N' + elif difficulty == 2: + return 'H' + elif difficulty == 3: + return 'B' + + return 'X' + + +def get_kerrigan_level(ctx: SC2Context, items: typing.Dict[SC2Race, typing.List[int]], missions_beaten: int) -> int: + item_value = items[SC2Race.ZERG][type_flaggroups[SC2Race.ZERG]["Level"]] + mission_value = missions_beaten * ctx.kerrigan_levels_per_mission_completed + if ctx.kerrigan_levels_per_mission_completed_cap != -1: + mission_value = min(mission_value, ctx.kerrigan_levels_per_mission_completed_cap) + total_value = item_value + mission_value + if ctx.kerrigan_total_level_cap != -1: + total_value = min(total_value, ctx.kerrigan_total_level_cap) + return total_value + + +def calculate_kerrigan_options(ctx: SC2Context) -> int: + options = 0 + + # Bits 0, 1 + # Kerrigan unit available + if ctx.kerrigan_presence in kerrigan_unit_available: + options |= 1 << 0 + + # Bit 2 + # Kerrigan primal status by map + if ctx.kerrigan_primal_status == KerriganPrimalStatus.option_vanilla: + options |= 1 << 2 + + return options + + +def caclulate_soa_options(ctx: SC2Context) -> int: + options = 0 + + # Bits 0, 1 + # SoA Calldowns available + soa_presence_value = 0 + if ctx.spear_of_adun_presence == SpearOfAdunPresence.option_not_present: + soa_presence_value = 0 + elif ctx.spear_of_adun_presence == SpearOfAdunPresence.option_lotv_protoss: + soa_presence_value = 1 + elif ctx.spear_of_adun_presence == SpearOfAdunPresence.option_protoss: + soa_presence_value = 2 + elif ctx.spear_of_adun_presence == SpearOfAdunPresence.option_everywhere: + soa_presence_value = 3 + options |= soa_presence_value << 0 + + # Bit 2 + # SoA Calldowns for no-builds + if ctx.spear_of_adun_present_in_no_build == SpearOfAdunPresentInNoBuild.option_true: + options |= 1 << 2 + + # Bits 3,4 + # Autocasts + soa_autocasts_presence_value = 0 + if ctx.spear_of_adun_autonomously_cast_ability_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_not_present: + soa_autocasts_presence_value = 0 + elif ctx.spear_of_adun_autonomously_cast_ability_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_lotv_protoss: + soa_autocasts_presence_value = 1 + elif ctx.spear_of_adun_autonomously_cast_ability_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_protoss: + soa_autocasts_presence_value = 2 + elif ctx.spear_of_adun_autonomously_cast_ability_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_everywhere: + soa_autocasts_presence_value = 3 + options |= soa_autocasts_presence_value << 3 + + # Bit 5 + # Autocasts in no-builds + if ctx.spear_of_adun_autonomously_cast_present_in_no_build == SpearOfAdunAutonomouslyCastPresentInNoBuild.option_true: + options |= 1 << 5 + + return options + +def kerrigan_primal(ctx: SC2Context, items: typing.Dict[SC2Race, typing.List[int]]) -> bool: + if ctx.kerrigan_primal_status == KerriganPrimalStatus.option_always_zerg: + return True + elif ctx.kerrigan_primal_status == KerriganPrimalStatus.option_always_human: + return False + elif ctx.kerrigan_primal_status == KerriganPrimalStatus.option_level_35: + return items[SC2Race.ZERG][type_flaggroups[SC2Race.ZERG]["Level"]] >= 35 + elif ctx.kerrigan_primal_status == KerriganPrimalStatus.option_half_completion: + total_missions = len(ctx.mission_id_to_location_ids) + completed = len([(mission_id * VICTORY_MODULO + get_location_offset(mission_id)) in ctx.checked_locations + for mission_id in ctx.mission_id_to_location_ids]) + return completed >= (total_missions / 2) + elif ctx.kerrigan_primal_status == KerriganPrimalStatus.option_item: + codes = [item.item for item in ctx.items_received] + return get_full_item_list()[ItemNames.KERRIGAN_PRIMAL_FORM].code in codes + return False + +async def starcraft_launch(ctx: SC2Context, mission_id: int): + sc2_logger.info(f"Launching {lookup_id_to_mission[mission_id].mission_name}. If game does not launch check log file for errors.") + + with DllDirectory(None): + run_game(bot.maps.get(lookup_id_to_mission[mission_id].map_file), [Bot(Race.Terran, ArchipelagoBot(ctx, mission_id), + name="Archipelago", fullscreen=True)], realtime=True) + + +class ArchipelagoBot(bot.bot_ai.BotAI): + __slots__ = [ + 'game_running', + 'mission_completed', + 'boni', + 'setup_done', + 'ctx', + 'mission_id', + 'want_close', + 'can_read_game', + 'last_received_update', + ] + + def __init__(self, ctx: SC2Context, mission_id: int): + self.game_running = False + self.mission_completed = False + self.want_close = False + self.can_read_game = False + self.last_received_update: int = 0 + self.setup_done = False + self.ctx = ctx + self.ctx.last_bot = self + self.mission_id = mission_id + self.boni = [False for _ in range(MAX_BONUS)] + + super(ArchipelagoBot, self).__init__() + + async def on_step(self, iteration: int): + if self.want_close: + self.want_close = False + await self._client.leave() + return + game_state = 0 + if not self.setup_done: + self.setup_done = True + start_items = calculate_items(self.ctx) + missions_beaten = self.missions_beaten_count() + kerrigan_level = get_kerrigan_level(self.ctx, start_items, missions_beaten) + kerrigan_options = calculate_kerrigan_options(self.ctx) + soa_options = caclulate_soa_options(self.ctx) + if self.ctx.difficulty_override >= 0: + difficulty = calc_difficulty(self.ctx.difficulty_override) + else: + difficulty = calc_difficulty(self.ctx.difficulty) + if self.ctx.game_speed_override >= 0: + game_speed = self.ctx.game_speed_override + else: + game_speed = self.ctx.game_speed + await self.chat_send("?SetOptions {} {} {} {} {} {} {} {} {} {} {} {} {}".format( + difficulty, + self.ctx.generic_upgrade_research, + self.ctx.all_in_choice, + game_speed, + self.ctx.disable_forced_camera, + self.ctx.skip_cutscenes, + kerrigan_options, + self.ctx.grant_story_tech, + self.ctx.take_over_ai_allies, + soa_options, + self.ctx.mission_order, + 1 if self.ctx.nova_covert_ops_only else 0, + self.ctx.grant_story_levels + )) + await self.chat_send("?GiveResources {} {} {}".format( + start_items[SC2Race.ANY][0], + start_items[SC2Race.ANY][1], + start_items[SC2Race.ANY][2] + )) + await self.updateTerranTech(start_items) + await self.updateZergTech(start_items, kerrigan_level) + await self.updateProtossTech(start_items) + await self.updateColors() + await self.chat_send("?LoadFinished") + self.last_received_update = len(self.ctx.items_received) + + else: + if self.ctx.pending_color_update: + await self.updateColors() + + if not self.ctx.announcements.empty(): + message = self.ctx.announcements.get(timeout=1) + await self.chat_send("?SendMessage " + message) + self.ctx.announcements.task_done() + + # Archipelago reads the health + controller1_state = 0 + controller2_state = 0 + for unit in self.all_own_units(): + if unit.health_max == CONTROLLER_HEALTH: + controller1_state = int(CONTROLLER_HEALTH - unit.health) + self.can_read_game = True + elif unit.health_max == CONTROLLER2_HEALTH: + controller2_state = int(CONTROLLER2_HEALTH - unit.health) + self.can_read_game = True + game_state = controller1_state + (controller2_state << 15) + + if iteration == 160 and not game_state & 1: + await self.chat_send("?SendMessage Warning: Archipelago unable to connect or has lost connection to " + + "Starcraft 2 (This is likely a map issue)") + + if self.last_received_update < len(self.ctx.items_received): + current_items = calculate_items(self.ctx) + missions_beaten = self.missions_beaten_count() + kerrigan_level = get_kerrigan_level(self.ctx, current_items, missions_beaten) + await self.updateTerranTech(current_items) + await self.updateZergTech(current_items, kerrigan_level) + await self.updateProtossTech(current_items) + self.last_received_update = len(self.ctx.items_received) + + if game_state & 1: + if not self.game_running: + print("Archipelago Connected") + self.game_running = True + + if self.can_read_game: + if game_state & (1 << 1) and not self.mission_completed: + if self.mission_id != self.ctx.final_mission: + print("Mission Completed") + await self.ctx.send_msgs( + [{"cmd": 'LocationChecks', + "locations": [get_location_offset(self.mission_id) + VICTORY_MODULO * self.mission_id]}]) + self.mission_completed = True + else: + print("Game Complete") + await self.ctx.send_msgs([{"cmd": 'StatusUpdate', "status": ClientStatus.CLIENT_GOAL}]) + self.mission_completed = True + + for x, completed in enumerate(self.boni): + if not completed and game_state & (1 << (x + 2)): + await self.ctx.send_msgs( + [{"cmd": 'LocationChecks', + "locations": [get_location_offset(self.mission_id) + VICTORY_MODULO * self.mission_id + x + 1]}]) + self.boni[x] = True + else: + await self.chat_send("?SendMessage LostConnection - Lost connection to game.") + + def missions_beaten_count(self): + return len([location for location in self.ctx.checked_locations if location % VICTORY_MODULO == 0]) + + async def updateColors(self): + await self.chat_send("?SetColor rr " + str(self.ctx.player_color_raynor)) + await self.chat_send("?SetColor ks " + str(self.ctx.player_color_zerg)) + await self.chat_send("?SetColor pz " + str(self.ctx.player_color_zerg_primal)) + await self.chat_send("?SetColor da " + str(self.ctx.player_color_protoss)) + await self.chat_send("?SetColor nova " + str(self.ctx.player_color_nova)) + self.ctx.pending_color_update = False + + async def updateTerranTech(self, current_items): + terran_items = current_items[SC2Race.TERRAN] + await self.chat_send("?GiveTerranTech {} {} {} {} {} {} {} {} {} {} {} {} {} {}".format( + terran_items[0], terran_items[1], terran_items[2], terran_items[3], terran_items[4], + terran_items[5], terran_items[6], terran_items[7], terran_items[8], terran_items[9], terran_items[10], + terran_items[11], terran_items[12], terran_items[13])) + + async def updateZergTech(self, current_items, kerrigan_level): + zerg_items = current_items[SC2Race.ZERG] + kerrigan_primal_by_items = kerrigan_primal(self.ctx, current_items) + kerrigan_primal_bot_value = 1 if kerrigan_primal_by_items else 0 + await self.chat_send("?GiveZergTech {} {} {} {} {} {} {} {} {} {} {} {}".format( + kerrigan_level, kerrigan_primal_bot_value, zerg_items[0], zerg_items[1], zerg_items[2], + zerg_items[3], zerg_items[4], zerg_items[5], zerg_items[6], zerg_items[9], zerg_items[10], zerg_items[11] + )) + + async def updateProtossTech(self, current_items): + protoss_items = current_items[SC2Race.PROTOSS] + await self.chat_send("?GiveProtossTech {} {} {} {} {} {} {} {} {} {}".format( + protoss_items[0], protoss_items[1], protoss_items[2], protoss_items[3], protoss_items[4], + protoss_items[5], protoss_items[6], protoss_items[7], protoss_items[8], protoss_items[9] + )) + + +def request_unfinished_missions(ctx: SC2Context) -> None: + if ctx.mission_req_table: + message = "Unfinished Missions: " + unlocks = initialize_blank_mission_dict(ctx.mission_req_table) + unfinished_locations: typing.Dict[SC2Mission, typing.List[str]] = {} + + _, unfinished_missions = calc_unfinished_missions(ctx, unlocks=unlocks) + + for mission in unfinished_missions: + objectives = set(ctx.locations_for_mission(mission)) + if objectives: + remaining_objectives = objectives.difference(ctx.checked_locations) + unfinished_locations[mission] = [ctx.location_names[location_id] for location_id in remaining_objectives] + else: + unfinished_locations[mission] = [] + + # Removing All-In from location pool + final_mission = lookup_id_to_mission[ctx.final_mission] + if final_mission in unfinished_missions.keys(): + message = f"Final Mission Available: {final_mission}[{ctx.final_mission}]\n" + message + if unfinished_missions[final_mission] == -1: + unfinished_missions.pop(final_mission) + + message += ", ".join(f"{mark_up_mission_name(ctx, mission, unlocks)}[{ctx.mission_req_table[ctx.find_campaign(mission)][mission].mission.id}] " + + mark_up_objectives( + f"[{len(unfinished_missions[mission])}/" + f"{sum(1 for _ in ctx.locations_for_mission(mission))}]", + ctx, unfinished_locations, mission) + for mission in unfinished_missions) + + if ctx.ui: + ctx.ui.log_panels['All'].on_message_markup(message) + ctx.ui.log_panels['Starcraft2'].on_message_markup(message) + else: + sc2_logger.info(message) + else: + sc2_logger.warning("No mission table found, you are likely not connected to a server.") + + +def calc_unfinished_missions(ctx: SC2Context, unlocks: typing.Optional[typing.Dict] = None): + unfinished_missions: typing.List[str] = [] + locations_completed: typing.List[typing.Union[typing.Set[int], typing.Literal[-1]]] = [] + + if not unlocks: + unlocks = initialize_blank_mission_dict(ctx.mission_req_table) + + available_missions = calc_available_missions(ctx, unlocks) + + for name in available_missions: + objectives = set(ctx.locations_for_mission(name)) + if objectives: + objectives_completed = ctx.checked_locations & objectives + if len(objectives_completed) < len(objectives): + unfinished_missions.append(name) + locations_completed.append(objectives_completed) + + else: # infer that this is the final mission as it has no objectives + unfinished_missions.append(name) + locations_completed.append(-1) + + return available_missions, dict(zip(unfinished_missions, locations_completed)) + + +def is_mission_available(ctx: SC2Context, mission_id_to_check: int) -> bool: + unfinished_missions = calc_available_missions(ctx) + + return any(mission_id_to_check == ctx.mission_req_table[ctx.find_campaign(mission)][mission].mission.id for mission in unfinished_missions) + + +def mark_up_mission_name(ctx: SC2Context, mission_name: str, unlock_table: typing.Dict) -> str: + """Checks if the mission is required for game completion and adds '*' to the name to mark that.""" + + campaign = ctx.find_campaign(mission_name) + mission_info = ctx.mission_req_table[campaign][mission_name] + if mission_info.completion_critical: + if ctx.ui: + message = "[color=AF99EF]" + mission_name + "[/color]" + else: + message = "*" + mission_name + "*" + else: + message = mission_name + + if ctx.ui: + campaign_missions = list(ctx.mission_req_table[campaign].keys()) + unlocks: typing.List[str] + index = campaign_missions.index(mission_name) + if index in unlock_table[campaign]: + unlocks = unlock_table[campaign][index] + else: + unlocks = [] + + if len(unlocks) > 0: + pre_message = f"[ref={mission_info.mission.id}|Unlocks: " + pre_message += ", ".join(f"{unlock}({ctx.mission_req_table[ctx.find_campaign(unlock)][unlock].mission.id})" for unlock in unlocks) + pre_message += f"]" + message = pre_message + message + "[/ref]" + + return message + + +def mark_up_objectives(message, ctx, unfinished_locations, mission): + formatted_message = message + + if ctx.ui: + locations = unfinished_locations[mission] + campaign = ctx.find_campaign(mission) + + pre_message = f"[ref={list(ctx.mission_req_table[campaign]).index(mission) + 30}|" + pre_message += "
".join(location for location in locations) + pre_message += f"]" + formatted_message = pre_message + message + "[/ref]" + + return formatted_message + + +def request_available_missions(ctx: SC2Context): + if ctx.mission_req_table: + message = "Available Missions: " + + # Initialize mission unlock table + unlocks = initialize_blank_mission_dict(ctx.mission_req_table) + + missions = calc_available_missions(ctx, unlocks) + message += \ + ", ".join(f"{mark_up_mission_name(ctx, mission, unlocks)}" + f"[{ctx.mission_req_table[ctx.find_campaign(mission)][mission].mission.id}]" + for mission in missions) + + if ctx.ui: + ctx.ui.log_panels['All'].on_message_markup(message) + ctx.ui.log_panels['Starcraft2'].on_message_markup(message) + else: + sc2_logger.info(message) + else: + sc2_logger.warning("No mission table found, you are likely not connected to a server.") + + +def calc_available_missions(ctx: SC2Context, unlocks: typing.Optional[dict] = None) -> typing.List[str]: + available_missions: typing.List[str] = [] + missions_complete = 0 + + # Get number of missions completed + for loc in ctx.checked_locations: + if loc % VICTORY_MODULO == 0: + missions_complete += 1 + + for campaign in ctx.mission_req_table: + # Go through the required missions for each mission and fill up unlock table used later for hover-over tooltips + for mission_name in ctx.mission_req_table[campaign]: + if unlocks: + for unlock in ctx.mission_req_table[campaign][mission_name].required_world: + parsed_unlock = parse_unlock(unlock) + # TODO prophecy-only wants to connect to WoL here + index = parsed_unlock.connect_to - 1 + unlock_mission = list(ctx.mission_req_table[parsed_unlock.campaign])[index] + unlock_campaign = ctx.find_campaign(unlock_mission) + if unlock_campaign in unlocks: + if index not in unlocks[unlock_campaign]: + unlocks[unlock_campaign][index] = list() + unlocks[unlock_campaign][index].append(mission_name) + + if mission_reqs_completed(ctx, mission_name, missions_complete): + available_missions.append(mission_name) + + return available_missions + + +def parse_unlock(unlock: typing.Union[typing.Dict[typing.Literal["connect_to", "campaign"], int], MissionConnection, int]) -> MissionConnection: + if isinstance(unlock, int): + # Legacy + return MissionConnection(unlock) + elif isinstance(unlock, MissionConnection): + return unlock + else: + # Multi-campaign + return MissionConnection(unlock["connect_to"], lookup_id_to_campaign[unlock["campaign"]]) + + +def mission_reqs_completed(ctx: SC2Context, mission_name: str, missions_complete: int) -> bool: + """Returns a bool signifying if the mission has all requirements complete and can be done + + Arguments: + ctx -- instance of SC2Context + locations_to_check -- the mission string name to check + missions_complete -- an int of how many missions have been completed + mission_path -- a list of missions that have already been checked + """ + campaign = ctx.find_campaign(mission_name) + + if len(ctx.mission_req_table[campaign][mission_name].required_world) >= 1: + # A check for when the requirements are being or'd + or_success = False + + # Loop through required missions + for req_mission in ctx.mission_req_table[campaign][mission_name].required_world: + req_success = True + parsed_req_mission = parse_unlock(req_mission) + + # Check if required mission has been completed + mission_id = ctx.mission_req_table[parsed_req_mission.campaign][ + list(ctx.mission_req_table[parsed_req_mission.campaign])[parsed_req_mission.connect_to - 1]].mission.id + if not (mission_id * VICTORY_MODULO + get_location_offset(mission_id)) in ctx.checked_locations: + if not ctx.mission_req_table[campaign][mission_name].or_requirements: + return False + else: + req_success = False + + # Grid-specific logic (to avoid long path checks and infinite recursion) + if ctx.mission_order in (MissionOrder.option_grid, MissionOrder.option_mini_grid, MissionOrder.option_medium_grid): + if req_success: + return True + else: + if parsed_req_mission == ctx.mission_req_table[campaign][mission_name].required_world[-1]: + return False + else: + continue + + # Recursively check required mission to see if it's requirements are met, in case !collect has been done + # Skipping recursive check on Grid settings to speed up checks and avoid infinite recursion + if not mission_reqs_completed(ctx, list(ctx.mission_req_table[parsed_req_mission.campaign])[parsed_req_mission.connect_to - 1], missions_complete): + if not ctx.mission_req_table[campaign][mission_name].or_requirements: + return False + else: + req_success = False + + # If requirement check succeeded mark or as satisfied + if ctx.mission_req_table[campaign][mission_name].or_requirements and req_success: + or_success = True + + if ctx.mission_req_table[campaign][mission_name].or_requirements: + # Return false if or requirements not met + if not or_success: + return False + + # Check number of missions + if missions_complete >= ctx.mission_req_table[campaign][mission_name].number: + return True + else: + return False + else: + return True + + +def initialize_blank_mission_dict(location_table: typing.Dict[SC2Campaign, typing.Dict[str, MissionInfo]]): + unlocks: typing.Dict[SC2Campaign, typing.Dict] = {} + + for mission in list(location_table): + unlocks[mission] = {} + + return unlocks + + +def check_game_install_path() -> bool: + # First thing: go to the default location for ExecuteInfo. + # An exception for Windows is included because it's very difficult to find ~\Documents if the user moved it. + if is_windows: + # The next five lines of utterly inscrutable code are brought to you by copy-paste from Stack Overflow. + # https://stackoverflow.com/questions/6227590/finding-the-users-my-documents-path/30924555# + import ctypes.wintypes + CSIDL_PERSONAL = 5 # My Documents + SHGFP_TYPE_CURRENT = 0 # Get current, not default value + + buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) + ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf) + documentspath: str = buf.value + einfo = str(documentspath / Path("StarCraft II\\ExecuteInfo.txt")) + else: + einfo = str(bot.paths.get_home() / Path(bot.paths.USERPATH[bot.paths.PF])) + + # Check if the file exists. + if os.path.isfile(einfo): + + # Open the file and read it, picking out the latest executable's path. + with open(einfo) as f: + content = f.read() + if content: + search_result = re.search(r" = (.*)Versions", content) + if not search_result: + sc2_logger.warning(f"Found {einfo}, but it was empty. Run SC2 through the Blizzard launcher, " + "then try again.") + return False + base = search_result.group(1) + + if os.path.exists(base): + executable = bot.paths.latest_executeble(Path(base).expanduser() / "Versions") + + # Finally, check the path for an actual executable. + # If we find one, great. Set up the SC2PATH. + if os.path.isfile(executable): + sc2_logger.info(f"Found an SC2 install at {base}!") + sc2_logger.debug(f"Latest executable at {executable}.") + os.environ["SC2PATH"] = base + sc2_logger.debug(f"SC2PATH set to {base}.") + return True + else: + sc2_logger.warning(f"We may have found an SC2 install at {base}, but couldn't find {executable}.") + else: + sc2_logger.warning(f"{einfo} pointed to {base}, but we could not find an SC2 install there.") + else: + sc2_logger.warning(f"Couldn't find {einfo}. Run SC2 through the Blizzard launcher, then try again. " + f"If that fails, please run /set_path with your SC2 install directory.") + return False + + +def is_mod_installed_correctly() -> bool: + """Searches for all required files.""" + if "SC2PATH" not in os.environ: + check_game_install_path() + sc2_path: str = os.environ["SC2PATH"] + mapdir = sc2_path / Path('Maps/ArchipelagoCampaign') + mods = ["ArchipelagoCore", "ArchipelagoPlayer", "ArchipelagoPlayerSuper", "ArchipelagoPatches", + "ArchipelagoTriggers", "ArchipelagoPlayerWoL", "ArchipelagoPlayerHotS", + "ArchipelagoPlayerLotV", "ArchipelagoPlayerLotVPrologue", "ArchipelagoPlayerNCO"] + modfiles = [sc2_path / Path("Mods/" + mod + ".SC2Mod") for mod in mods] + wol_required_maps: typing.List[str] = ["WoL" + os.sep + mission.map_file + ".SC2Map" for mission in SC2Mission + if mission.campaign in (SC2Campaign.WOL, SC2Campaign.PROPHECY)] + hots_required_maps: typing.List[str] = ["HotS" + os.sep + mission.map_file + ".SC2Map" for mission in campaign_mission_table[SC2Campaign.HOTS]] + lotv_required_maps: typing.List[str] = ["LotV" + os.sep + mission.map_file + ".SC2Map" for mission in SC2Mission + if mission.campaign in (SC2Campaign.LOTV, SC2Campaign.PROLOGUE, SC2Campaign.EPILOGUE)] + nco_required_maps: typing.List[str] = ["NCO" + os.sep + mission.map_file + ".SC2Map" for mission in campaign_mission_table[SC2Campaign.NCO]] + required_maps = wol_required_maps + hots_required_maps + lotv_required_maps + nco_required_maps + needs_files = False + + # Check for maps. + missing_maps: typing.List[str] = [] + for mapfile in required_maps: + if not os.path.isfile(mapdir / mapfile): + missing_maps.append(mapfile) + if len(missing_maps) >= 19: + sc2_logger.warning(f"All map files missing from {mapdir}.") + needs_files = True + elif len(missing_maps) > 0: + for map in missing_maps: + sc2_logger.debug(f"Missing {map} from {mapdir}.") + sc2_logger.warning(f"Missing {len(missing_maps)} map files.") + needs_files = True + else: # Must be no maps missing + sc2_logger.info(f"All maps found in {mapdir}.") + + # Check for mods. + for modfile in modfiles: + if os.path.isfile(modfile) or os.path.isdir(modfile): + sc2_logger.info(f"Archipelago mod found at {modfile}.") + else: + sc2_logger.warning(f"Archipelago mod could not be found at {modfile}.") + needs_files = True + + # Final verdict. + if needs_files: + sc2_logger.warning(f"Required files are missing. Run /download_data to acquire them.") + return False + else: + sc2_logger.debug(f"All map/mod files are properly installed.") + return True + + +class DllDirectory: + # Credit to Black Sliver for this code. + # More info: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectoryw + _old: typing.Optional[str] = None + _new: typing.Optional[str] = None + + def __init__(self, new: typing.Optional[str]): + self._new = new + + def __enter__(self): + old = self.get() + if self.set(self._new): + self._old = old + + def __exit__(self, *args): + if self._old is not None: + self.set(self._old) + + @staticmethod + def get() -> typing.Optional[str]: + if sys.platform == "win32": + n = ctypes.windll.kernel32.GetDllDirectoryW(0, None) + buf = ctypes.create_unicode_buffer(n) + ctypes.windll.kernel32.GetDllDirectoryW(n, buf) + return buf.value + # NOTE: other OS may support os.environ["LD_LIBRARY_PATH"], but this fix is windows-specific + return None + + @staticmethod + def set(s: typing.Optional[str]) -> bool: + if sys.platform == "win32": + return ctypes.windll.kernel32.SetDllDirectoryW(s) != 0 + # NOTE: other OS may support os.environ["LD_LIBRARY_PATH"], but this fix is windows-specific + return False + + +def download_latest_release_zip( + owner: str, + repo: str, + api_version: str, + metadata: typing.Optional[str] = None, + force_download=False +) -> typing.Tuple[str, typing.Optional[str]]: + """Downloads the latest release of a GitHub repo to the current directory as a .zip file.""" + import requests + + headers = {"Accept": 'application/vnd.github.v3+json'} + url = f"https://api.github.com/repos/{owner}/{repo}/releases/tags/{api_version}" + + r1 = requests.get(url, headers=headers) + if r1.status_code == 200: + latest_metadata = r1.json() + cleanup_downloaded_metadata(latest_metadata) + latest_metadata = str(latest_metadata) + # sc2_logger.info(f"Latest version: {latest_metadata}.") + else: + sc2_logger.warning(f"Status code: {r1.status_code}") + sc2_logger.warning(f"Failed to reach GitHub. Could not find download link.") + sc2_logger.warning(f"text: {r1.text}") + return "", metadata + + if (force_download is False) and (metadata == latest_metadata): + sc2_logger.info("Latest version already installed.") + return "", metadata + + sc2_logger.info(f"Attempting to download latest version of API version {api_version} of {repo}.") + download_url = r1.json()["assets"][0]["browser_download_url"] + + r2 = requests.get(download_url, headers=headers) + if r2.status_code == 200 and zipfile.is_zipfile(io.BytesIO(r2.content)): + tempdir = tempfile.gettempdir() + file = tempdir + os.sep + f"{repo}.zip" + with open(file, "wb") as fh: + fh.write(r2.content) + sc2_logger.info(f"Successfully downloaded {repo}.zip.") + return file, latest_metadata + else: + sc2_logger.warning(f"Status code: {r2.status_code}") + sc2_logger.warning("Download failed.") + sc2_logger.warning(f"text: {r2.text}") + return "", metadata + + +def cleanup_downloaded_metadata(medatada_json: dict) -> None: + for asset in medatada_json['assets']: + del asset['download_count'] + + +def is_mod_update_available(owner: str, repo: str, api_version: str, metadata: str) -> bool: + import requests + + headers = {"Accept": 'application/vnd.github.v3+json'} + url = f"https://api.github.com/repos/{owner}/{repo}/releases/tags/{api_version}" + + r1 = requests.get(url, headers=headers) + if r1.status_code == 200: + latest_metadata = r1.json() + cleanup_downloaded_metadata(latest_metadata) + latest_metadata = str(latest_metadata) + if metadata != latest_metadata: + return True + else: + return False + + else: + sc2_logger.warning(f"Failed to reach GitHub while checking for updates.") + sc2_logger.warning(f"Status code: {r1.status_code}") + sc2_logger.warning(f"text: {r1.text}") + return False + + +def get_location_offset(mission_id): + return SC2WOL_LOC_ID_OFFSET if mission_id <= SC2Mission.ALL_IN.id \ + else (SC2HOTS_LOC_ID_OFFSET - SC2Mission.ALL_IN.id * VICTORY_MODULO) + + +def launch(): + colorama.init() + asyncio.run(main()) + colorama.deinit() diff --git a/worlds/sc2/ClientGui.py b/worlds/sc2/ClientGui.py new file mode 100644 index 0000000000..167583fd1e --- /dev/null +++ b/worlds/sc2/ClientGui.py @@ -0,0 +1,304 @@ +from typing import * +import asyncio + +from kvui import GameManager, HoverBehavior, ServerToolTip +from kivy.app import App +from kivy.clock import Clock +from kivy.uix.tabbedpanel import TabbedPanelItem +from kivy.uix.gridlayout import GridLayout +from kivy.lang import Builder +from kivy.uix.label import Label +from kivy.uix.button import Button +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.scrollview import ScrollView +from kivy.properties import StringProperty + +from worlds.sc2.Client import SC2Context, calc_unfinished_missions, parse_unlock +from worlds.sc2.MissionTables import lookup_id_to_mission, lookup_name_to_mission, campaign_race_exceptions, \ + SC2Mission, SC2Race, SC2Campaign +from worlds.sc2.Locations import LocationType, lookup_location_id_to_type +from worlds.sc2.Options import LocationInclusion +from worlds.sc2 import SC2World, get_first_mission + + +class HoverableButton(HoverBehavior, Button): + pass + + +class MissionButton(HoverableButton): + tooltip_text = StringProperty("Test") + + def __init__(self, *args, **kwargs): + super(HoverableButton, self).__init__(*args, **kwargs) + self.layout = FloatLayout() + self.popuplabel = ServerToolTip(text=self.text, markup=True) + self.popuplabel.padding = [5, 2, 5, 2] + self.layout.add_widget(self.popuplabel) + + def on_enter(self): + self.popuplabel.text = self.tooltip_text + + if self.ctx.current_tooltip: + App.get_running_app().root.remove_widget(self.ctx.current_tooltip) + + if self.tooltip_text == "": + self.ctx.current_tooltip = None + else: + App.get_running_app().root.add_widget(self.layout) + self.ctx.current_tooltip = self.layout + + def on_leave(self): + self.ctx.ui.clear_tooltip() + + @property + def ctx(self) -> SC2Context: + return App.get_running_app().ctx + +class CampaignScroll(ScrollView): + pass + +class MultiCampaignLayout(GridLayout): + pass + +class CampaignLayout(GridLayout): + pass + +class MissionLayout(GridLayout): + pass + +class MissionCategory(GridLayout): + pass + +class SC2Manager(GameManager): + logging_pairs = [ + ("Client", "Archipelago"), + ("Starcraft2", "Starcraft2"), + ] + base_title = "Archipelago Starcraft 2 Client" + + campaign_panel: Optional[CampaignLayout] = None + last_checked_locations: Set[int] = set() + mission_id_to_button: Dict[int, MissionButton] = {} + launching: Union[bool, int] = False # if int -> mission ID + refresh_from_launching = True + first_check = True + first_mission = "" + ctx: SC2Context + + def __init__(self, ctx) -> None: + super().__init__(ctx) + + def clear_tooltip(self) -> None: + if self.ctx.current_tooltip: + App.get_running_app().root.remove_widget(self.ctx.current_tooltip) + + self.ctx.current_tooltip = None + + def build(self): + container = super().build() + + panel = TabbedPanelItem(text="Starcraft 2 Launcher") + panel.content = CampaignScroll() + self.campaign_panel = MultiCampaignLayout() + panel.content.add_widget(self.campaign_panel) + + self.tabs.add_widget(panel) + + Clock.schedule_interval(self.build_mission_table, 0.5) + + return container + + def build_mission_table(self, dt) -> None: + if (not self.launching and (not self.last_checked_locations == self.ctx.checked_locations or + not self.refresh_from_launching)) or self.first_check: + assert self.campaign_panel is not None + self.refresh_from_launching = True + + self.campaign_panel.clear_widgets() + if self.ctx.mission_req_table: + self.last_checked_locations = self.ctx.checked_locations.copy() + self.first_check = False + self.first_mission = get_first_mission(self.ctx.mission_req_table) + + self.mission_id_to_button = {} + + available_missions, unfinished_missions = calc_unfinished_missions(self.ctx) + + multi_campaign_layout_height = 0 + + for campaign, missions in sorted(self.ctx.mission_req_table.items(), key=lambda item: item[0].id): + categories: Dict[str, List[str]] = {} + + # separate missions into categories + for mission_index in missions: + mission_info = self.ctx.mission_req_table[campaign][mission_index] + if mission_info.category not in categories: + categories[mission_info.category] = [] + + categories[mission_info.category].append(mission_index) + + max_mission_count = max(len(categories[category]) for category in categories) + if max_mission_count == 1: + campaign_layout_height = 115 + else: + campaign_layout_height = (max_mission_count + 2) * 50 + multi_campaign_layout_height += campaign_layout_height + campaign_layout = CampaignLayout(size_hint_y=None, height=campaign_layout_height) + if campaign != SC2Campaign.GLOBAL: + campaign_layout.add_widget( + Label(text=campaign.campaign_name, size_hint_y=None, height=25, outline_width=1) + ) + mission_layout = MissionLayout() + + for category in categories: + category_name_height = 0 + category_spacing = 3 + if category.startswith('_'): + category_display_name = '' + else: + category_display_name = category + category_name_height += 25 + category_spacing = 10 + category_panel = MissionCategory(padding=[category_spacing,6,category_spacing,6]) + category_panel.add_widget( + Label(text=category_display_name, size_hint_y=None, height=category_name_height, outline_width=1)) + + for mission in categories[category]: + text: str = mission + tooltip: str = "" + mission_obj: SC2Mission = lookup_name_to_mission[mission] + mission_id: int = mission_obj.id + mission_data = self.ctx.mission_req_table[campaign][mission] + remaining_locations, plando_locations, remaining_count = self.sort_unfinished_locations(mission) + # Map has uncollected locations + if mission in unfinished_missions: + if self.any_valuable_locations(remaining_locations): + text = f"[color=6495ED]{text}[/color]" + else: + text = f"[color=A0BEF4]{text}[/color]" + elif mission in available_missions: + text = f"[color=FFFFFF]{text}[/color]" + # Map requirements not met + else: + text = f"[color=a9a9a9]{text}[/color]" + tooltip = f"Requires: " + if mission_data.required_world: + tooltip += ", ".join(list(self.ctx.mission_req_table[parse_unlock(req_mission).campaign])[parse_unlock(req_mission).connect_to - 1] for + req_mission in + mission_data.required_world) + + if mission_data.number: + tooltip += " and " + if mission_data.number: + tooltip += f"{self.ctx.mission_req_table[campaign][mission].number} missions completed" + + if mission_id == self.ctx.final_mission: + if mission in available_missions: + text = f"[color=FFBC95]{mission}[/color]" + else: + text = f"[color=D0C0BE]{mission}[/color]" + if tooltip: + tooltip += "\n" + tooltip += "Final Mission" + + if remaining_count > 0: + if tooltip: + tooltip += "\n\n" + tooltip += f"-- Uncollected locations --" + for loctype in LocationType: + if len(remaining_locations[loctype]) > 0: + if loctype == LocationType.VICTORY: + tooltip += f"\n- {remaining_locations[loctype][0]}" + else: + tooltip += f"\n{self.get_location_type_title(loctype)}:\n- " + tooltip += "\n- ".join(remaining_locations[loctype]) + if len(plando_locations) > 0: + tooltip += f"\nPlando:\n- " + tooltip += "\n- ".join(plando_locations) + + MISSION_BUTTON_HEIGHT = 50 + for pad in range(mission_data.ui_vertical_padding): + column_spacer = Label(text='', size_hint_y=None, height=MISSION_BUTTON_HEIGHT) + category_panel.add_widget(column_spacer) + mission_button = MissionButton(text=text, size_hint_y=None, height=MISSION_BUTTON_HEIGHT) + mission_race = mission_obj.race + if mission_race == SC2Race.ANY: + mission_race = mission_obj.campaign.race + race = campaign_race_exceptions.get(mission_obj, mission_race) + racial_colors = { + SC2Race.TERRAN: (0.24, 0.84, 0.68), + SC2Race.ZERG: (1, 0.65, 0.37), + SC2Race.PROTOSS: (0.55, 0.7, 1) + } + if race in racial_colors: + mission_button.background_color = racial_colors[race] + mission_button.tooltip_text = tooltip + mission_button.bind(on_press=self.mission_callback) + self.mission_id_to_button[mission_id] = mission_button + category_panel.add_widget(mission_button) + + category_panel.add_widget(Label(text="")) + mission_layout.add_widget(category_panel) + campaign_layout.add_widget(mission_layout) + self.campaign_panel.add_widget(campaign_layout) + self.campaign_panel.height = multi_campaign_layout_height + + elif self.launching: + assert self.campaign_panel is not None + self.refresh_from_launching = False + + self.campaign_panel.clear_widgets() + self.campaign_panel.add_widget(Label(text="Launching Mission: " + + lookup_id_to_mission[self.launching].mission_name)) + if self.ctx.ui: + self.ctx.ui.clear_tooltip() + + def mission_callback(self, button: MissionButton) -> None: + if not self.launching: + mission_id: int = next(k for k, v in self.mission_id_to_button.items() if v == button) + if self.ctx.play_mission(mission_id): + self.launching = mission_id + Clock.schedule_once(self.finish_launching, 10) + + def finish_launching(self, dt): + self.launching = False + + def sort_unfinished_locations(self, mission_name: str) -> Tuple[Dict[LocationType, List[str]], List[str], int]: + locations: Dict[LocationType, List[str]] = {loctype: [] for loctype in LocationType} + count = 0 + for loc in self.ctx.locations_for_mission(mission_name): + if loc in self.ctx.missing_locations: + count += 1 + locations[lookup_location_id_to_type[loc]].append(self.ctx.location_names[loc]) + + plando_locations = [] + for plando_loc in self.ctx.plando_locations: + for loctype in LocationType: + if plando_loc in locations[loctype]: + locations[loctype].remove(plando_loc) + plando_locations.append(plando_loc) + + return locations, plando_locations, count + + def any_valuable_locations(self, locations: Dict[LocationType, List[str]]) -> bool: + for loctype in LocationType: + if len(locations[loctype]) > 0 and self.ctx.location_inclusions[loctype] == LocationInclusion.option_enabled: + return True + return False + + def get_location_type_title(self, location_type: LocationType) -> str: + title = location_type.name.title().replace("_", " ") + if self.ctx.location_inclusions[location_type] == LocationInclusion.option_disabled: + title += " (Nothing)" + elif self.ctx.location_inclusions[location_type] == LocationInclusion.option_resources: + title += " (Resources)" + else: + title += "" + return title + +def start_gui(context: SC2Context): + context.ui = SC2Manager(context) + context.ui_task = asyncio.create_task(context.ui.async_run(), name="UI") + import pkgutil + data = pkgutil.get_data(SC2World.__module__, "Starcraft2.kv").decode() + Builder.load_string(data) diff --git a/worlds/sc2/ItemGroups.py b/worlds/sc2/ItemGroups.py new file mode 100644 index 0000000000..a77fb920f6 --- /dev/null +++ b/worlds/sc2/ItemGroups.py @@ -0,0 +1,100 @@ +import typing +from . import Items, ItemNames +from .MissionTables import campaign_mission_table, SC2Campaign, SC2Mission + +""" +Item name groups, given to Archipelago and used in YAMLs and /received filtering. +For non-developers the following will be useful: +* Items with a bracket get groups named after the unbracketed part + * eg. "Advanced Healing AI (Medivac)" is accessible as "Advanced Healing AI" + * The exception to this are item names that would be ambiguous (eg. "Resource Efficiency") +* Item flaggroups get unique groups as well as combined groups for numbered flaggroups + * eg. "Unit" contains all units, "Armory" contains "Armory 1" through "Armory 6" + * The best place to look these up is at the bottom of Items.py +* Items that have a parent are grouped together + * eg. "Zergling Items" contains all items that have "Zergling" as a parent + * These groups do NOT contain the parent item + * This currently does not include items with multiple potential parents, like some LotV unit upgrades +* All items are grouped by their race ("Terran", "Protoss", "Zerg", "Any") +* Hand-crafted item groups can be found at the bottom of this file +""" + +item_name_groups: typing.Dict[str, typing.List[str]] = {} + +# Groups for use in world logic +item_name_groups["Missions"] = ["Beat " + mission.mission_name for mission in SC2Mission] +item_name_groups["WoL Missions"] = ["Beat " + mission.mission_name for mission in campaign_mission_table[SC2Campaign.WOL]] + \ + ["Beat " + mission.mission_name for mission in campaign_mission_table[SC2Campaign.PROPHECY]] + +# These item name groups should not show up in documentation +unlisted_item_name_groups = { + "Missions", "WoL Missions" +} + +# Some item names only differ in bracketed parts +# These items are ambiguous for short-hand name groups +bracketless_duplicates: typing.Set[str] +# This is a list of names in ItemNames with bracketed parts removed, for internal use +_shortened_names = [(name[:name.find(' (')] if '(' in name else name) + for name in [ItemNames.__dict__[name] for name in ItemNames.__dir__() if not name.startswith('_')]] +# Remove the first instance of every short-name from the full item list +bracketless_duplicates = set(_shortened_names) +for name in bracketless_duplicates: + _shortened_names.remove(name) +# The remaining short-names are the duplicates +bracketless_duplicates = set(_shortened_names) +del _shortened_names + +# All items get sorted into their data type +for item, data in Items.get_full_item_list().items(): + # Items get assigned to their flaggroup's type + item_name_groups.setdefault(data.type, []).append(item) + # Numbered flaggroups get sorted into an unnumbered group + # Currently supports numbers of one or two digits + if data.type[-2:].strip().isnumeric: + type_group = data.type[:-2].strip() + item_name_groups.setdefault(type_group, []).append(item) + # Flaggroups with numbers are unlisted + unlisted_item_name_groups.add(data.type) + # Items with a bracket get a short-hand name group for ease of use in YAMLs + if '(' in item: + short_name = item[:item.find(' (')] + # Ambiguous short-names are dropped + if short_name not in bracketless_duplicates: + item_name_groups[short_name] = [item] + # Short-name groups are unlisted + unlisted_item_name_groups.add(short_name) + # Items with a parent get assigned to their parent's group + if data.parent_item: + # The parent groups need a special name, otherwise they are ambiguous with the parent + parent_group = f"{data.parent_item} Items" + item_name_groups.setdefault(parent_group, []).append(item) + # Parent groups are unlisted + unlisted_item_name_groups.add(parent_group) + # All items get assigned to their race's group + race_group = data.race.name.capitalize() + item_name_groups.setdefault(race_group, []).append(item) + + +# Hand-made groups +item_name_groups["Aiur"] = [ + ItemNames.ZEALOT, ItemNames.DRAGOON, ItemNames.SENTRY, ItemNames.AVENGER, ItemNames.HIGH_TEMPLAR, + ItemNames.IMMORTAL, ItemNames.REAVER, + ItemNames.PHOENIX, ItemNames.SCOUT, ItemNames.ARBITER, ItemNames.CARRIER, +] +item_name_groups["Nerazim"] = [ + ItemNames.CENTURION, ItemNames.STALKER, ItemNames.DARK_TEMPLAR, ItemNames.SIGNIFIER, ItemNames.DARK_ARCHON, + ItemNames.ANNIHILATOR, + ItemNames.CORSAIR, ItemNames.ORACLE, ItemNames.VOID_RAY, +] +item_name_groups["Tal'Darim"] = [ + ItemNames.SUPPLICANT, ItemNames.SLAYER, ItemNames.HAVOC, ItemNames.BLOOD_HUNTER, ItemNames.ASCENDANT, + ItemNames.VANGUARD, ItemNames.WRATHWALKER, + ItemNames.DESTROYER, ItemNames.MOTHERSHIP, + ItemNames.WARP_PRISM_PHASE_BLASTER, +] +item_name_groups["Purifier"] = [ + ItemNames.SENTINEL, ItemNames.ADEPT, ItemNames.INSTIGATOR, ItemNames.ENERGIZER, + ItemNames.COLOSSUS, ItemNames.DISRUPTOR, + ItemNames.MIRAGE, ItemNames.TEMPEST, +] \ No newline at end of file diff --git a/worlds/sc2/ItemNames.py b/worlds/sc2/ItemNames.py new file mode 100644 index 0000000000..10c7139103 --- /dev/null +++ b/worlds/sc2/ItemNames.py @@ -0,0 +1,661 @@ +""" +A complete collection of Starcraft 2 item names as strings. +Users of this data may make some assumptions about the structure of a name: +* The upgrade for a unit will end with the unit's name in parentheses +* Weapon / armor upgrades may be grouped by a common prefix specified within this file +""" + +# Terran Units +MARINE = "Marine" +MEDIC = "Medic" +FIREBAT = "Firebat" +MARAUDER = "Marauder" +REAPER = "Reaper" +HELLION = "Hellion" +VULTURE = "Vulture" +GOLIATH = "Goliath" +DIAMONDBACK = "Diamondback" +SIEGE_TANK = "Siege Tank" +MEDIVAC = "Medivac" +WRAITH = "Wraith" +VIKING = "Viking" +BANSHEE = "Banshee" +BATTLECRUISER = "Battlecruiser" +GHOST = "Ghost" +SPECTRE = "Spectre" +THOR = "Thor" +RAVEN = "Raven" +SCIENCE_VESSEL = "Science Vessel" +PREDATOR = "Predator" +HERCULES = "Hercules" +# Extended units +LIBERATOR = "Liberator" +VALKYRIE = "Valkyrie" +WIDOW_MINE = "Widow Mine" +CYCLONE = "Cyclone" +HERC = "HERC" +WARHOUND = "Warhound" + +# Terran Buildings +BUNKER = "Bunker" +MISSILE_TURRET = "Missile Turret" +SENSOR_TOWER = "Sensor Tower" +PLANETARY_FORTRESS = "Planetary Fortress" +PERDITION_TURRET = "Perdition Turret" +HIVE_MIND_EMULATOR = "Hive Mind Emulator" +PSI_DISRUPTER = "Psi Disrupter" + +# Terran Weapon / Armor Upgrades +TERRAN_UPGRADE_PREFIX = "Progressive Terran" +TERRAN_INFANTRY_UPGRADE_PREFIX = f"{TERRAN_UPGRADE_PREFIX} Infantry" +TERRAN_VEHICLE_UPGRADE_PREFIX = f"{TERRAN_UPGRADE_PREFIX} Vehicle" +TERRAN_SHIP_UPGRADE_PREFIX = f"{TERRAN_UPGRADE_PREFIX} Ship" + +PROGRESSIVE_TERRAN_INFANTRY_WEAPON = f"{TERRAN_INFANTRY_UPGRADE_PREFIX} Weapon" +PROGRESSIVE_TERRAN_INFANTRY_ARMOR = f"{TERRAN_INFANTRY_UPGRADE_PREFIX} Armor" +PROGRESSIVE_TERRAN_VEHICLE_WEAPON = f"{TERRAN_VEHICLE_UPGRADE_PREFIX} Weapon" +PROGRESSIVE_TERRAN_VEHICLE_ARMOR = f"{TERRAN_VEHICLE_UPGRADE_PREFIX} Armor" +PROGRESSIVE_TERRAN_SHIP_WEAPON = f"{TERRAN_SHIP_UPGRADE_PREFIX} Weapon" +PROGRESSIVE_TERRAN_SHIP_ARMOR = f"{TERRAN_SHIP_UPGRADE_PREFIX} Armor" +PROGRESSIVE_TERRAN_WEAPON_UPGRADE = f"{TERRAN_UPGRADE_PREFIX} Weapon Upgrade" +PROGRESSIVE_TERRAN_ARMOR_UPGRADE = f"{TERRAN_UPGRADE_PREFIX} Armor Upgrade" +PROGRESSIVE_TERRAN_INFANTRY_UPGRADE = f"{TERRAN_INFANTRY_UPGRADE_PREFIX} Upgrade" +PROGRESSIVE_TERRAN_VEHICLE_UPGRADE = f"{TERRAN_VEHICLE_UPGRADE_PREFIX} Upgrade" +PROGRESSIVE_TERRAN_SHIP_UPGRADE = f"{TERRAN_SHIP_UPGRADE_PREFIX} Upgrade" +PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE = f"{TERRAN_UPGRADE_PREFIX} Weapon/Armor Upgrade" + +# Mercenaries +WAR_PIGS = "War Pigs" +DEVIL_DOGS = "Devil Dogs" +HAMMER_SECURITIES = "Hammer Securities" +SPARTAN_COMPANY = "Spartan Company" +SIEGE_BREAKERS = "Siege Breakers" +HELS_ANGELS = "Hel's Angels" +DUSK_WINGS = "Dusk Wings" +JACKSONS_REVENGE = "Jackson's Revenge" +SKIBIS_ANGELS = "Skibi's Angels" +DEATH_HEADS = "Death Heads" +WINGED_NIGHTMARES = "Winged Nightmares" +MIDNIGHT_RIDERS = "Midnight Riders" +BRYNHILDS = "Brynhilds" +JOTUN = "Jotun" + +# Lab / Global +ULTRA_CAPACITORS = "Ultra-Capacitors" +VANADIUM_PLATING = "Vanadium Plating" +ORBITAL_DEPOTS = "Orbital Depots" +MICRO_FILTERING = "Micro-Filtering" +AUTOMATED_REFINERY = "Automated Refinery" +COMMAND_CENTER_REACTOR = "Command Center Reactor" +TECH_REACTOR = "Tech Reactor" +ORBITAL_STRIKE = "Orbital Strike" +CELLULAR_REACTOR = "Cellular Reactor" +PROGRESSIVE_REGENERATIVE_BIO_STEEL = "Progressive Regenerative Bio-Steel" +PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM = "Progressive Fire-Suppression System" +PROGRESSIVE_ORBITAL_COMMAND = "Progressive Orbital Command" +STRUCTURE_ARMOR = "Structure Armor" +HI_SEC_AUTO_TRACKING = "Hi-Sec Auto Tracking" +ADVANCED_OPTICS = "Advanced Optics" +ROGUE_FORCES = "Rogue Forces" + +# Terran Unit Upgrades +BANSHEE_HYPERFLIGHT_ROTORS = "Hyperflight Rotors (Banshee)" +BANSHEE_INTERNAL_TECH_MODULE = "Internal Tech Module (Banshee)" +BANSHEE_LASER_TARGETING_SYSTEM = "Laser Targeting System (Banshee)" +BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS = "Progressive Cross-Spectrum Dampeners (Banshee)" +BANSHEE_SHOCKWAVE_MISSILE_BATTERY = "Shockwave Missile Battery (Banshee)" +BANSHEE_SHAPED_HULL = "Shaped Hull (Banshee)" +BANSHEE_ADVANCED_TARGETING_OPTICS = "Advanced Targeting Optics (Banshee)" +BANSHEE_DISTORTION_BLASTERS = "Distortion Blasters (Banshee)" +BANSHEE_ROCKET_BARRAGE = "Rocket Barrage (Banshee)" +BATTLECRUISER_ATX_LASER_BATTERY = "ATX Laser Battery (Battlecruiser)" +BATTLECRUISER_CLOAK = "Cloak (Battlecruiser)" +BATTLECRUISER_PROGRESSIVE_DEFENSIVE_MATRIX = "Progressive Defensive Matrix (Battlecruiser)" +BATTLECRUISER_INTERNAL_TECH_MODULE = "Internal Tech Module (Battlecruiser)" +BATTLECRUISER_PROGRESSIVE_MISSILE_PODS = "Progressive Missile Pods (Battlecruiser)" +BATTLECRUISER_OPTIMIZED_LOGISTICS = "Optimized Logistics (Battlecruiser)" +BATTLECRUISER_TACTICAL_JUMP = "Tactical Jump (Battlecruiser)" +BATTLECRUISER_BEHEMOTH_PLATING = "Behemoth Plating (Battlecruiser)" +BATTLECRUISER_COVERT_OPS_ENGINES = "Covert Ops Engines (Battlecruiser)" +BUNKER_NEOSTEEL_BUNKER = "Neosteel Bunker (Bunker)" +BUNKER_PROJECTILE_ACCELERATOR = "Projectile Accelerator (Bunker)" +BUNKER_SHRIKE_TURRET = "Shrike Turret (Bunker)" +BUNKER_FORTIFIED_BUNKER = "Fortified Bunker (Bunker)" +CYCLONE_MAG_FIELD_ACCELERATORS = "Mag-Field Accelerators (Cyclone)" +CYCLONE_MAG_FIELD_LAUNCHERS = "Mag-Field Launchers (Cyclone)" +CYCLONE_RAPID_FIRE_LAUNCHERS = "Rapid Fire Launchers (Cyclone)" +CYCLONE_TARGETING_OPTICS = "Targeting Optics (Cyclone)" +CYCLONE_RESOURCE_EFFICIENCY = "Resource Efficiency (Cyclone)" +CYCLONE_INTERNAL_TECH_MODULE = "Internal Tech Module (Cyclone)" +DIAMONDBACK_BURST_CAPACITORS = "Burst Capacitors (Diamondback)" +DIAMONDBACK_HYPERFLUXOR = "Hyperfluxor (Diamondback)" +DIAMONDBACK_RESOURCE_EFFICIENCY = "Resource Efficiency (Diamondback)" +DIAMONDBACK_SHAPED_HULL = "Shaped Hull (Diamondback)" +DIAMONDBACK_PROGRESSIVE_TRI_LITHIUM_POWER_CELL = "Progressive Tri-Lithium Power Cell (Diamondback)" +DIAMONDBACK_ION_THRUSTERS = "Ion Thrusters (Diamondback)" +FIREBAT_INCINERATOR_GAUNTLETS = "Incinerator Gauntlets (Firebat)" +FIREBAT_JUGGERNAUT_PLATING = "Juggernaut Plating (Firebat)" +FIREBAT_RESOURCE_EFFICIENCY = "Resource Efficiency (Firebat)" +FIREBAT_PROGRESSIVE_STIMPACK = "Progressive Stimpack (Firebat)" +FIREBAT_INFERNAL_PRE_IGNITER = "Infernal Pre-Igniter (Firebat)" +FIREBAT_KINETIC_FOAM = "Kinetic Foam (Firebat)" +FIREBAT_NANO_PROJECTORS = "Nano Projectors (Firebat)" +GHOST_CRIUS_SUIT = "Crius Suit (Ghost)" +GHOST_EMP_ROUNDS = "EMP Rounds (Ghost)" +GHOST_LOCKDOWN = "Lockdown (Ghost)" +GHOST_OCULAR_IMPLANTS = "Ocular Implants (Ghost)" +GHOST_RESOURCE_EFFICIENCY = "Resource Efficiency (Ghost)" +GOLIATH_ARES_CLASS_TARGETING_SYSTEM = "Ares-Class Targeting System (Goliath)" +GOLIATH_JUMP_JETS = "Jump Jets (Goliath)" +GOLIATH_MULTI_LOCK_WEAPONS_SYSTEM = "Multi-Lock Weapons System (Goliath)" +GOLIATH_OPTIMIZED_LOGISTICS = "Optimized Logistics (Goliath)" +GOLIATH_SHAPED_HULL = "Shaped Hull (Goliath)" +GOLIATH_RESOURCE_EFFICIENCY = "Resource Efficiency (Goliath)" +GOLIATH_INTERNAL_TECH_MODULE = "Internal Tech Module (Goliath)" +HELLION_HELLBAT_ASPECT = "Hellbat Aspect (Hellion)" +HELLION_JUMP_JETS = "Jump Jets (Hellion)" +HELLION_OPTIMIZED_LOGISTICS = "Optimized Logistics (Hellion)" +HELLION_PROGRESSIVE_STIMPACK = "Progressive Stimpack (Hellion)" +HELLION_SMART_SERVOS = "Smart Servos (Hellion)" +HELLION_THERMITE_FILAMENTS = "Thermite Filaments (Hellion)" +HELLION_TWIN_LINKED_FLAMETHROWER = "Twin-Linked Flamethrower (Hellion)" +HELLION_INFERNAL_PLATING = "Infernal Plating (Hellion)" +HERC_JUGGERNAUT_PLATING = "Juggernaut Plating (HERC)" +HERC_KINETIC_FOAM = "Kinetic Foam (HERC)" +HERC_RESOURCE_EFFICIENCY = "Resource Efficiency (HERC)" +HERCULES_INTERNAL_FUSION_MODULE = "Internal Fusion Module (Hercules)" +HERCULES_TACTICAL_JUMP = "Tactical Jump (Hercules)" +LIBERATOR_ADVANCED_BALLISTICS = "Advanced Ballistics (Liberator)" +LIBERATOR_CLOAK = "Cloak (Liberator)" +LIBERATOR_LASER_TARGETING_SYSTEM = "Laser Targeting System (Liberator)" +LIBERATOR_OPTIMIZED_LOGISTICS = "Optimized Logistics (Liberator)" +LIBERATOR_RAID_ARTILLERY = "Raid Artillery (Liberator)" +LIBERATOR_SMART_SERVOS = "Smart Servos (Liberator)" +LIBERATOR_RESOURCE_EFFICIENCY = "Resource Efficiency (Liberator)" +MARAUDER_CONCUSSIVE_SHELLS = "Concussive Shells (Marauder)" +MARAUDER_INTERNAL_TECH_MODULE = "Internal Tech Module (Marauder)" +MARAUDER_KINETIC_FOAM = "Kinetic Foam (Marauder)" +MARAUDER_LASER_TARGETING_SYSTEM = "Laser Targeting System (Marauder)" +MARAUDER_MAGRAIL_MUNITIONS = "Magrail Munitions (Marauder)" +MARAUDER_PROGRESSIVE_STIMPACK = "Progressive Stimpack (Marauder)" +MARAUDER_JUGGERNAUT_PLATING = "Juggernaut Plating (Marauder)" +MARINE_COMBAT_SHIELD = "Combat Shield (Marine)" +MARINE_LASER_TARGETING_SYSTEM = "Laser Targeting System (Marine)" +MARINE_MAGRAIL_MUNITIONS = "Magrail Munitions (Marine)" +MARINE_OPTIMIZED_LOGISTICS = "Optimized Logistics (Marine)" +MARINE_PROGRESSIVE_STIMPACK = "Progressive Stimpack (Marine)" +MEDIC_ADVANCED_MEDIC_FACILITIES = "Advanced Medic Facilities (Medic)" +MEDIC_OPTICAL_FLARE = "Optical Flare (Medic)" +MEDIC_RESOURCE_EFFICIENCY = "Resource Efficiency (Medic)" +MEDIC_RESTORATION = "Restoration (Medic)" +MEDIC_STABILIZER_MEDPACKS = "Stabilizer Medpacks (Medic)" +MEDIC_ADAPTIVE_MEDPACKS = "Adaptive Medpacks (Medic)" +MEDIC_NANO_PROJECTOR = "Nano Projector (Medic)" +MEDIVAC_ADVANCED_HEALING_AI = "Advanced Healing AI (Medivac)" +MEDIVAC_AFTERBURNERS = "Afterburners (Medivac)" +MEDIVAC_EXPANDED_HULL = "Expanded Hull (Medivac)" +MEDIVAC_RAPID_DEPLOYMENT_TUBE = "Rapid Deployment Tube (Medivac)" +MEDIVAC_SCATTER_VEIL = "Scatter Veil (Medivac)" +MEDIVAC_ADVANCED_CLOAKING_FIELD = "Advanced Cloaking Field (Medivac)" +MISSILE_TURRET_HELLSTORM_BATTERIES = "Hellstorm Batteries (Missile Turret)" +MISSILE_TURRET_TITANIUM_HOUSING = "Titanium Housing (Missile Turret)" +PLANETARY_FORTRESS_PROGRESSIVE_AUGMENTED_THRUSTERS = "Progressive Augmented Thrusters (Planetary Fortress)" +PLANETARY_FORTRESS_ADVANCED_TARGETING = "Advanced Targeting (Planetary Fortress)" +PREDATOR_RESOURCE_EFFICIENCY = "Resource Efficiency (Predator)" +PREDATOR_CLOAK = "Cloak (Predator)" +PREDATOR_CHARGE = "Charge (Predator)" +PREDATOR_PREDATOR_S_FURY = "Predator's Fury (Predator)" +RAVEN_ANTI_ARMOR_MISSILE = "Anti-Armor Missile (Raven)" +RAVEN_BIO_MECHANICAL_REPAIR_DRONE = "Bio Mechanical Repair Drone (Raven)" +RAVEN_HUNTER_SEEKER_WEAPON = "Hunter-Seeker Weapon (Raven)" +RAVEN_INTERFERENCE_MATRIX = "Interference Matrix (Raven)" +RAVEN_INTERNAL_TECH_MODULE = "Internal Tech Module (Raven)" +RAVEN_RAILGUN_TURRET = "Railgun Turret (Raven)" +RAVEN_SPIDER_MINES = "Spider Mines (Raven)" +RAVEN_RESOURCE_EFFICIENCY = "Resource Efficiency (Raven)" +RAVEN_DURABLE_MATERIALS = "Durable Materials (Raven)" +REAPER_ADVANCED_CLOAKING_FIELD = "Advanced Cloaking Field (Reaper)" +REAPER_COMBAT_DRUGS = "Combat Drugs (Reaper)" +REAPER_G4_CLUSTERBOMB = "G-4 Clusterbomb (Reaper)" +REAPER_LASER_TARGETING_SYSTEM = "Laser Targeting System (Reaper)" +REAPER_PROGRESSIVE_STIMPACK = "Progressive Stimpack (Reaper)" +REAPER_SPIDER_MINES = "Spider Mines (Reaper)" +REAPER_U238_ROUNDS = "U-238 Rounds (Reaper)" +REAPER_JET_PACK_OVERDRIVE = "Jet Pack Overdrive (Reaper)" +SCIENCE_VESSEL_DEFENSIVE_MATRIX = "Defensive Matrix (Science Vessel)" +SCIENCE_VESSEL_EMP_SHOCKWAVE = "EMP Shockwave (Science Vessel)" +SCIENCE_VESSEL_IMPROVED_NANO_REPAIR = "Improved Nano-Repair (Science Vessel)" +SCIENCE_VESSEL_ADVANCED_AI_SYSTEMS = "Advanced AI Systems (Science Vessel)" +SCV_ADVANCED_CONSTRUCTION = "Advanced Construction (SCV)" +SCV_DUAL_FUSION_WELDERS = "Dual-Fusion Welders (SCV)" +SCV_HOSTILE_ENVIRONMENT_ADAPTATION = "Hostile Environment Adaptation (SCV)" +SIEGE_TANK_ADVANCED_SIEGE_TECH = "Advanced Siege Tech (Siege Tank)" +SIEGE_TANK_GRADUATING_RANGE = "Graduating Range (Siege Tank)" +SIEGE_TANK_INTERNAL_TECH_MODULE = "Internal Tech Module (Siege Tank)" +SIEGE_TANK_JUMP_JETS = "Jump Jets (Siege Tank)" +SIEGE_TANK_LASER_TARGETING_SYSTEM = "Laser Targeting System (Siege Tank)" +SIEGE_TANK_MAELSTROM_ROUNDS = "Maelstrom Rounds (Siege Tank)" +SIEGE_TANK_SHAPED_BLAST = "Shaped Blast (Siege Tank)" +SIEGE_TANK_SMART_SERVOS = "Smart Servos (Siege Tank)" +SIEGE_TANK_SPIDER_MINES = "Spider Mines (Siege Tank)" +SIEGE_TANK_SHAPED_HULL = "Shaped Hull (Siege Tank)" +SIEGE_TANK_RESOURCE_EFFICIENCY = "Resource Efficiency (Siege Tank)" +SPECTRE_IMPALER_ROUNDS = "Impaler Rounds (Spectre)" +SPECTRE_NYX_CLASS_CLOAKING_MODULE = "Nyx-Class Cloaking Module (Spectre)" +SPECTRE_PSIONIC_LASH = "Psionic Lash (Spectre)" +SPECTRE_RESOURCE_EFFICIENCY = "Resource Efficiency (Spectre)" +SPIDER_MINE_CERBERUS_MINE = "Cerberus Mine (Spider Mine)" +SPIDER_MINE_HIGH_EXPLOSIVE_MUNITION = "High Explosive Munition (Spider Mine)" +THOR_330MM_BARRAGE_CANNON = "330mm Barrage Cannon (Thor)" +THOR_PROGRESSIVE_IMMORTALITY_PROTOCOL = "Progressive Immortality Protocol (Thor)" +THOR_PROGRESSIVE_HIGH_IMPACT_PAYLOAD = "Progressive High Impact Payload (Thor)" +THOR_BUTTON_WITH_A_SKULL_ON_IT = "Button With a Skull on It (Thor)" +THOR_LASER_TARGETING_SYSTEM = "Laser Targeting System (Thor)" +THOR_LARGE_SCALE_FIELD_CONSTRUCTION = "Large Scale Field Construction (Thor)" +VALKYRIE_AFTERBURNERS = "Afterburners (Valkyrie)" +VALKYRIE_FLECHETTE_MISSILES = "Flechette Missiles (Valkyrie)" +VALKYRIE_ENHANCED_CLUSTER_LAUNCHERS = "Enhanced Cluster Launchers (Valkyrie)" +VALKYRIE_SHAPED_HULL = "Shaped Hull (Valkyrie)" +VALKYRIE_LAUNCHING_VECTOR_COMPENSATOR = "Launching Vector Compensator (Valkyrie)" +VALKYRIE_RESOURCE_EFFICIENCY = "Resource Efficiency (Valkyrie)" +VIKING_ANTI_MECHANICAL_MUNITION = "Anti-Mechanical Munition (Viking)" +VIKING_PHOBOS_CLASS_WEAPONS_SYSTEM = "Phobos-Class Weapons System (Viking)" +VIKING_RIPWAVE_MISSILES = "Ripwave Missiles (Viking)" +VIKING_SMART_SERVOS = "Smart Servos (Viking)" +VIKING_SHREDDER_ROUNDS = "Shredder Rounds (Viking)" +VIKING_WILD_MISSILES = "W.I.L.D. Missiles (Viking)" +VULTURE_AUTO_LAUNCHERS = "Auto Launchers (Vulture)" +VULTURE_ION_THRUSTERS = "Ion Thrusters (Vulture)" +VULTURE_PROGRESSIVE_REPLENISHABLE_MAGAZINE = "Progressive Replenishable Magazine (Vulture)" +VULTURE_AUTO_REPAIR = "Auto-Repair (Vulture)" +WARHOUND_RESOURCE_EFFICIENCY = "Resource Efficiency (Warhound)" +WARHOUND_REINFORCED_PLATING = "Reinforced Plating (Warhound)" +WIDOW_MINE_BLACK_MARKET_LAUNCHERS = "Black Market Launchers (Widow Mine)" +WIDOW_MINE_CONCEALMENT = "Concealment (Widow Mine)" +WIDOW_MINE_DRILLING_CLAWS = "Drilling Claws (Widow Mine)" +WIDOW_MINE_EXECUTIONER_MISSILES = "Executioner Missiles (Widow Mine)" +WRAITH_ADVANCED_LASER_TECHNOLOGY = "Advanced Laser Technology (Wraith)" +WRAITH_DISPLACEMENT_FIELD = "Displacement Field (Wraith)" +WRAITH_PROGRESSIVE_TOMAHAWK_POWER_CELLS = "Progressive Tomahawk Power Cells (Wraith)" +WRAITH_TRIGGER_OVERRIDE = "Trigger Override (Wraith)" +WRAITH_INTERNAL_TECH_MODULE = "Internal Tech Module (Wraith)" +WRAITH_RESOURCE_EFFICIENCY = "Resource Efficiency (Wraith)" + +# Nova +NOVA_GHOST_VISOR = "Ghost Visor (Nova Equipment)" +NOVA_RANGEFINDER_OCULUS = "Rangefinder Oculus (Nova Equipment)" +NOVA_DOMINATION = "Domination (Nova Ability)" +NOVA_BLINK = "Blink (Nova Ability)" +NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE = "Progressive Stealth Suit Module (Nova Suit Module)" +NOVA_ENERGY_SUIT_MODULE = "Energy Suit Module (Nova Suit Module)" +NOVA_ARMORED_SUIT_MODULE = "Armored Suit Module (Nova Suit Module)" +NOVA_JUMP_SUIT_MODULE = "Jump Suit Module (Nova Suit Module)" +NOVA_C20A_CANISTER_RIFLE = "C20A Canister Rifle (Nova Weapon)" +NOVA_HELLFIRE_SHOTGUN = "Hellfire Shotgun (Nova Weapon)" +NOVA_PLASMA_RIFLE = "Plasma Rifle (Nova Weapon)" +NOVA_MONOMOLECULAR_BLADE = "Monomolecular Blade (Nova Weapon)" +NOVA_BLAZEFIRE_GUNBLADE = "Blazefire Gunblade (Nova Weapon)" +NOVA_STIM_INFUSION = "Stim Infusion (Nova Gadget)" +NOVA_PULSE_GRENADES = "Pulse Grenades (Nova Gadget)" +NOVA_FLASHBANG_GRENADES = "Flashbang Grenades (Nova Gadget)" +NOVA_IONIC_FORCE_FIELD = "Ionic Force Field (Nova Gadget)" +NOVA_HOLO_DECOY = "Holo Decoy (Nova Gadget)" +NOVA_NUKE = "Tac Nuke Strike (Nova Ability)" + +# Zerg Units +ZERGLING = "Zergling" +SWARM_QUEEN = "Swarm Queen" +ROACH = "Roach" +HYDRALISK = "Hydralisk" +ABERRATION = "Aberration" +MUTALISK = "Mutalisk" +SWARM_HOST = "Swarm Host" +INFESTOR = "Infestor" +ULTRALISK = "Ultralisk" +CORRUPTOR = "Corruptor" +SCOURGE = "Scourge" +BROOD_QUEEN = "Brood Queen" +DEFILER = "Defiler" + +# Zerg Buildings +SPORE_CRAWLER = "Spore Crawler" +SPINE_CRAWLER = "Spine Crawler" + +# Zerg Weapon / Armor Upgrades +ZERG_UPGRADE_PREFIX = "Progressive Zerg" +ZERG_FLYER_UPGRADE_PREFIX = f"{ZERG_UPGRADE_PREFIX} Flyer" + +PROGRESSIVE_ZERG_MELEE_ATTACK = f"{ZERG_UPGRADE_PREFIX} Melee Attack" +PROGRESSIVE_ZERG_MISSILE_ATTACK = f"{ZERG_UPGRADE_PREFIX} Missile Attack" +PROGRESSIVE_ZERG_GROUND_CARAPACE = f"{ZERG_UPGRADE_PREFIX} Ground Carapace" +PROGRESSIVE_ZERG_FLYER_ATTACK = f"{ZERG_FLYER_UPGRADE_PREFIX} Attack" +PROGRESSIVE_ZERG_FLYER_CARAPACE = f"{ZERG_FLYER_UPGRADE_PREFIX} Carapace" +PROGRESSIVE_ZERG_WEAPON_UPGRADE = f"{ZERG_UPGRADE_PREFIX} Weapon Upgrade" +PROGRESSIVE_ZERG_ARMOR_UPGRADE = f"{ZERG_UPGRADE_PREFIX} Armor Upgrade" +PROGRESSIVE_ZERG_GROUND_UPGRADE = f"{ZERG_UPGRADE_PREFIX} Ground Upgrade" +PROGRESSIVE_ZERG_FLYER_UPGRADE = f"{ZERG_FLYER_UPGRADE_PREFIX} Upgrade" +PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE = f"{ZERG_UPGRADE_PREFIX} Weapon/Armor Upgrade" + +# Zerg Unit Upgrades +ZERGLING_HARDENED_CARAPACE = "Hardened Carapace (Zergling)" +ZERGLING_ADRENAL_OVERLOAD = "Adrenal Overload (Zergling)" +ZERGLING_METABOLIC_BOOST = "Metabolic Boost (Zergling)" +ZERGLING_SHREDDING_CLAWS = "Shredding Claws (Zergling)" +ROACH_HYDRIODIC_BILE = "Hydriodic Bile (Roach)" +ROACH_ADAPTIVE_PLATING = "Adaptive Plating (Roach)" +ROACH_TUNNELING_CLAWS = "Tunneling Claws (Roach)" +ROACH_GLIAL_RECONSTITUTION = "Glial Reconstitution (Roach)" +ROACH_ORGANIC_CARAPACE = "Organic Carapace (Roach)" +HYDRALISK_FRENZY = "Frenzy (Hydralisk)" +HYDRALISK_ANCILLARY_CARAPACE = "Ancillary Carapace (Hydralisk)" +HYDRALISK_GROOVED_SPINES = "Grooved Spines (Hydralisk)" +HYDRALISK_MUSCULAR_AUGMENTS = "Muscular Augments (Hydralisk)" +HYDRALISK_RESOURCE_EFFICIENCY = "Resource Efficiency (Hydralisk)" +BANELING_CORROSIVE_ACID = "Corrosive Acid (Baneling)" +BANELING_RUPTURE = "Rupture (Baneling)" +BANELING_REGENERATIVE_ACID = "Regenerative Acid (Baneling)" +BANELING_CENTRIFUGAL_HOOKS = "Centrifugal Hooks (Baneling)" +BANELING_TUNNELING_JAWS = "Tunneling Jaws (Baneling)" +BANELING_RAPID_METAMORPH = "Rapid Metamorph (Baneling)" +MUTALISK_VICIOUS_GLAIVE = "Vicious Glaive (Mutalisk)" +MUTALISK_RAPID_REGENERATION = "Rapid Regeneration (Mutalisk)" +MUTALISK_SUNDERING_GLAIVE = "Sundering Glaive (Mutalisk)" +MUTALISK_SEVERING_GLAIVE = "Severing Glaive (Mutalisk)" +MUTALISK_AERODYNAMIC_GLAIVE_SHAPE = "Aerodynamic Glaive Shape (Mutalisk)" +SWARM_HOST_BURROW = "Burrow (Swarm Host)" +SWARM_HOST_RAPID_INCUBATION = "Rapid Incubation (Swarm Host)" +SWARM_HOST_PRESSURIZED_GLANDS = "Pressurized Glands (Swarm Host)" +SWARM_HOST_LOCUST_METABOLIC_BOOST = "Locust Metabolic Boost (Swarm Host)" +SWARM_HOST_ENDURING_LOCUSTS = "Enduring Locusts (Swarm Host)" +SWARM_HOST_ORGANIC_CARAPACE = "Organic Carapace (Swarm Host)" +SWARM_HOST_RESOURCE_EFFICIENCY = "Resource Efficiency (Swarm Host)" +ULTRALISK_BURROW_CHARGE = "Burrow Charge (Ultralisk)" +ULTRALISK_TISSUE_ASSIMILATION = "Tissue Assimilation (Ultralisk)" +ULTRALISK_MONARCH_BLADES = "Monarch Blades (Ultralisk)" +ULTRALISK_ANABOLIC_SYNTHESIS = "Anabolic Synthesis (Ultralisk)" +ULTRALISK_CHITINOUS_PLATING = "Chitinous Plating (Ultralisk)" +ULTRALISK_ORGANIC_CARAPACE = "Organic Carapace (Ultralisk)" +ULTRALISK_RESOURCE_EFFICIENCY = "Resource Efficiency (Ultralisk)" +CORRUPTOR_CORRUPTION = "Corruption (Corruptor)" +CORRUPTOR_CAUSTIC_SPRAY = "Caustic Spray (Corruptor)" +SCOURGE_VIRULENT_SPORES = "Virulent Spores (Scourge)" +SCOURGE_RESOURCE_EFFICIENCY = "Resource Efficiency (Scourge)" +SCOURGE_SWARM_SCOURGE = "Swarm Scourge (Scourge)" +DEVOURER_CORROSIVE_SPRAY = "Corrosive Spray (Devourer)" +DEVOURER_GAPING_MAW = "Gaping Maw (Devourer)" +DEVOURER_IMPROVED_OSMOSIS = "Improved Osmosis (Devourer)" +DEVOURER_PRESCIENT_SPORES = "Prescient Spores (Devourer)" +GUARDIAN_PROLONGED_DISPERSION = "Prolonged Dispersion (Guardian)" +GUARDIAN_PRIMAL_ADAPTATION = "Primal Adaptation (Guardian)" +GUARDIAN_SORONAN_ACID = "Soronan Acid (Guardian)" +IMPALER_ADAPTIVE_TALONS = "Adaptive Talons (Impaler)" +IMPALER_SECRETION_GLANDS = "Secretion Glands (Impaler)" +IMPALER_HARDENED_TENTACLE_SPINES = "Hardened Tentacle Spines (Impaler)" +LURKER_SEISMIC_SPINES = "Seismic Spines (Lurker)" +LURKER_ADAPTED_SPINES = "Adapted Spines (Lurker)" +RAVAGER_POTENT_BILE = "Potent Bile (Ravager)" +RAVAGER_BLOATED_BILE_DUCTS = "Bloated Bile Ducts (Ravager)" +RAVAGER_DEEP_TUNNEL = "Deep Tunnel (Ravager)" +VIPER_PARASITIC_BOMB = "Parasitic Bomb (Viper)" +VIPER_PARALYTIC_BARBS = "Paralytic Barbs (Viper)" +VIPER_VIRULENT_MICROBES = "Virulent Microbes (Viper)" +BROOD_LORD_POROUS_CARTILAGE = "Porous Cartilage (Brood Lord)" +BROOD_LORD_EVOLVED_CARAPACE = "Evolved Carapace (Brood Lord)" +BROOD_LORD_SPLITTER_MITOSIS = "Splitter Mitosis (Brood Lord)" +BROOD_LORD_RESOURCE_EFFICIENCY = "Resource Efficiency (Brood Lord)" +INFESTOR_INFESTED_TERRAN = "Infested Terran (Infestor)" +INFESTOR_MICROBIAL_SHROUD = "Microbial Shroud (Infestor)" +SWARM_QUEEN_SPAWN_LARVAE = "Spawn Larvae (Swarm Queen)" +SWARM_QUEEN_DEEP_TUNNEL = "Deep Tunnel (Swarm Queen)" +SWARM_QUEEN_ORGANIC_CARAPACE = "Organic Carapace (Swarm Queen)" +SWARM_QUEEN_BIO_MECHANICAL_TRANSFUSION = "Bio-Mechanical Transfusion (Swarm Queen)" +SWARM_QUEEN_RESOURCE_EFFICIENCY = "Resource Efficiency (Swarm Queen)" +SWARM_QUEEN_INCUBATOR_CHAMBER = "Incubator Chamber (Swarm Queen)" +BROOD_QUEEN_FUNGAL_GROWTH = "Fungal Growth (Brood Queen)" +BROOD_QUEEN_ENSNARE = "Ensnare (Brood Queen)" +BROOD_QUEEN_ENHANCED_MITOCHONDRIA = "Enhanced Mitochondria (Brood Queen)" + +# Zerg Strains +ZERGLING_RAPTOR_STRAIN = "Raptor Strain (Zergling)" +ZERGLING_SWARMLING_STRAIN = "Swarmling Strain (Zergling)" +ROACH_VILE_STRAIN = "Vile Strain (Roach)" +ROACH_CORPSER_STRAIN = "Corpser Strain (Roach)" +BANELING_SPLITTER_STRAIN = "Splitter Strain (Baneling)" +BANELING_HUNTER_STRAIN = "Hunter Strain (Baneling)" +SWARM_HOST_CARRION_STRAIN = "Carrion Strain (Swarm Host)" +SWARM_HOST_CREEPER_STRAIN = "Creeper Strain (Swarm Host)" +ULTRALISK_NOXIOUS_STRAIN = "Noxious Strain (Ultralisk)" +ULTRALISK_TORRASQUE_STRAIN = "Torrasque Strain (Ultralisk)" + +# Morphs +ZERGLING_BANELING_ASPECT = "Baneling Aspect (Zergling)" +HYDRALISK_IMPALER_ASPECT = "Impaler Aspect (Hydralisk)" +HYDRALISK_LURKER_ASPECT = "Lurker Aspect (Hydralisk)" +MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT = "Brood Lord Aspect (Mutalisk/Corruptor)" +MUTALISK_CORRUPTOR_VIPER_ASPECT = "Viper Aspect (Mutalisk/Corruptor)" +MUTALISK_CORRUPTOR_GUARDIAN_ASPECT = "Guardian Aspect (Mutalisk/Corruptor)" +MUTALISK_CORRUPTOR_DEVOURER_ASPECT = "Devourer Aspect (Mutalisk/Corruptor)" +ROACH_RAVAGER_ASPECT = "Ravager Aspect (Roach)" + +# Zerg Mercs +INFESTED_MEDICS = "Infested Medics" +INFESTED_SIEGE_TANKS = "Infested Siege Tanks" +INFESTED_BANSHEES = "Infested Banshees" + +# Kerrigan Upgrades +KERRIGAN_KINETIC_BLAST = "Kinetic Blast (Kerrigan Tier 1)" +KERRIGAN_HEROIC_FORTITUDE = "Heroic Fortitude (Kerrigan Tier 1)" +KERRIGAN_LEAPING_STRIKE = "Leaping Strike (Kerrigan Tier 1)" +KERRIGAN_CRUSHING_GRIP = "Crushing Grip (Kerrigan Tier 2)" +KERRIGAN_CHAIN_REACTION = "Chain Reaction (Kerrigan Tier 2)" +KERRIGAN_PSIONIC_SHIFT = "Psionic Shift (Kerrigan Tier 2)" +KERRIGAN_WILD_MUTATION = "Wild Mutation (Kerrigan Tier 4)" +KERRIGAN_SPAWN_BANELINGS = "Spawn Banelings (Kerrigan Tier 4)" +KERRIGAN_MEND = "Mend (Kerrigan Tier 4)" +KERRIGAN_INFEST_BROODLINGS = "Infest Broodlings (Kerrigan Tier 6)" +KERRIGAN_FURY = "Fury (Kerrigan Tier 6)" +KERRIGAN_ABILITY_EFFICIENCY = "Ability Efficiency (Kerrigan Tier 6)" +KERRIGAN_APOCALYPSE = "Apocalypse (Kerrigan Tier 7)" +KERRIGAN_SPAWN_LEVIATHAN = "Spawn Leviathan (Kerrigan Tier 7)" +KERRIGAN_DROP_PODS = "Drop-Pods (Kerrigan Tier 7)" +KERRIGAN_PRIMAL_FORM = "Primal Form (Kerrigan)" + +# Misc Upgrades +KERRIGAN_ZERGLING_RECONSTITUTION = "Zergling Reconstitution (Kerrigan Tier 3)" +KERRIGAN_IMPROVED_OVERLORDS = "Improved Overlords (Kerrigan Tier 3)" +KERRIGAN_AUTOMATED_EXTRACTORS = "Automated Extractors (Kerrigan Tier 3)" +KERRIGAN_TWIN_DRONES = "Twin Drones (Kerrigan Tier 5)" +KERRIGAN_MALIGNANT_CREEP = "Malignant Creep (Kerrigan Tier 5)" +KERRIGAN_VESPENE_EFFICIENCY = "Vespene Efficiency (Kerrigan Tier 5)" +OVERLORD_VENTRAL_SACS = "Ventral Sacs (Overlord)" + +# Kerrigan Levels +KERRIGAN_LEVELS_1 = "1 Kerrigan Level" +KERRIGAN_LEVELS_2 = "2 Kerrigan Levels" +KERRIGAN_LEVELS_3 = "3 Kerrigan Levels" +KERRIGAN_LEVELS_4 = "4 Kerrigan Levels" +KERRIGAN_LEVELS_5 = "5 Kerrigan Levels" +KERRIGAN_LEVELS_6 = "6 Kerrigan Levels" +KERRIGAN_LEVELS_7 = "7 Kerrigan Levels" +KERRIGAN_LEVELS_8 = "8 Kerrigan Levels" +KERRIGAN_LEVELS_9 = "9 Kerrigan Levels" +KERRIGAN_LEVELS_10 = "10 Kerrigan Levels" +KERRIGAN_LEVELS_14 = "14 Kerrigan Levels" +KERRIGAN_LEVELS_35 = "35 Kerrigan Levels" +KERRIGAN_LEVELS_70 = "70 Kerrigan Levels" + +# Protoss Units +ZEALOT = "Zealot" +STALKER = "Stalker" +HIGH_TEMPLAR = "High Templar" +DARK_TEMPLAR = "Dark Templar" +IMMORTAL = "Immortal" +COLOSSUS = "Colossus" +PHOENIX = "Phoenix" +VOID_RAY = "Void Ray" +CARRIER = "Carrier" +OBSERVER = "Observer" +CENTURION = "Centurion" +SENTINEL = "Sentinel" +SUPPLICANT = "Supplicant" +INSTIGATOR = "Instigator" +SLAYER = "Slayer" +SENTRY = "Sentry" +ENERGIZER = "Energizer" +HAVOC = "Havoc" +SIGNIFIER = "Signifier" +ASCENDANT = "Ascendant" +AVENGER = "Avenger" +BLOOD_HUNTER = "Blood Hunter" +DRAGOON = "Dragoon" +DARK_ARCHON = "Dark Archon" +ADEPT = "Adept" +WARP_PRISM = "Warp Prism" +ANNIHILATOR = "Annihilator" +VANGUARD = "Vanguard" +WRATHWALKER = "Wrathwalker" +REAVER = "Reaver" +DISRUPTOR = "Disruptor" +MIRAGE = "Mirage" +CORSAIR = "Corsair" +DESTROYER = "Destroyer" +SCOUT = "Scout" +TEMPEST = "Tempest" +MOTHERSHIP = "Mothership" +ARBITER = "Arbiter" +ORACLE = "Oracle" + +# Upgrades +PROTOSS_UPGRADE_PREFIX = "Progressive Protoss" +PROTOSS_GROUND_UPGRADE_PREFIX = f"{PROTOSS_UPGRADE_PREFIX} Ground" +PROTOSS_AIR_UPGRADE_PREFIX = f"{PROTOSS_UPGRADE_PREFIX} Air" +PROGRESSIVE_PROTOSS_GROUND_WEAPON = f"{PROTOSS_GROUND_UPGRADE_PREFIX} Weapon" +PROGRESSIVE_PROTOSS_GROUND_ARMOR = f"{PROTOSS_GROUND_UPGRADE_PREFIX} Armor" +PROGRESSIVE_PROTOSS_SHIELDS = f"{PROTOSS_UPGRADE_PREFIX} Shields" +PROGRESSIVE_PROTOSS_AIR_WEAPON = f"{PROTOSS_AIR_UPGRADE_PREFIX} Weapon" +PROGRESSIVE_PROTOSS_AIR_ARMOR = f"{PROTOSS_AIR_UPGRADE_PREFIX} Armor" +PROGRESSIVE_PROTOSS_WEAPON_UPGRADE = f"{PROTOSS_UPGRADE_PREFIX} Weapon Upgrade" +PROGRESSIVE_PROTOSS_ARMOR_UPGRADE = f"{PROTOSS_UPGRADE_PREFIX} Armor Upgrade" +PROGRESSIVE_PROTOSS_GROUND_UPGRADE = f"{PROTOSS_GROUND_UPGRADE_PREFIX} Upgrade" +PROGRESSIVE_PROTOSS_AIR_UPGRADE = f"{PROTOSS_AIR_UPGRADE_PREFIX} Upgrade" +PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE = f"{PROTOSS_UPGRADE_PREFIX} Weapon/Armor Upgrade" + +# Buildings +PHOTON_CANNON = "Photon Cannon" +KHAYDARIN_MONOLITH = "Khaydarin Monolith" +SHIELD_BATTERY = "Shield Battery" + +# Unit Upgrades +SUPPLICANT_BLOOD_SHIELD = "Blood Shield (Supplicant)" +SUPPLICANT_SOUL_AUGMENTATION = "Soul Augmentation (Supplicant)" +SUPPLICANT_SHIELD_REGENERATION = "Shield Regeneration (Supplicant)" +ADEPT_SHOCKWAVE = "Shockwave (Adept)" +ADEPT_RESONATING_GLAIVES = "Resonating Glaives (Adept)" +ADEPT_PHASE_BULWARK = "Phase Bulwark (Adept)" +STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES = "Disintegrating Particles (Stalker/Instigator/Slayer)" +STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION = "Particle Reflection (Stalker/Instigator/Slayer)" +DRAGOON_HIGH_IMPACT_PHASE_DISRUPTORS = "High Impact Phase Disruptor (Dragoon)" +DRAGOON_TRILLIC_COMPRESSION_SYSTEM = "Trillic Compression System (Dragoon)" +DRAGOON_SINGULARITY_CHARGE = "Singularity Charge (Dragoon)" +DRAGOON_ENHANCED_STRIDER_SERVOS = "Enhanced Strider Servos (Dragoon)" +SCOUT_COMBAT_SENSOR_ARRAY = "Combat Sensor Array (Scout)" +SCOUT_APIAL_SENSORS = "Apial Sensors (Scout)" +SCOUT_GRAVITIC_THRUSTERS = "Gravitic Thrusters (Scout)" +SCOUT_ADVANCED_PHOTON_BLASTERS = "Advanced Photon Blasters (Scout)" +TEMPEST_TECTONIC_DESTABILIZERS = "Tectonic Destabilizers (Tempest)" +TEMPEST_QUANTIC_REACTOR = "Quantic Reactor (Tempest)" +TEMPEST_GRAVITY_SLING = "Gravity Sling (Tempest)" +PHOENIX_MIRAGE_IONIC_WAVELENGTH_FLUX = "Ionic Wavelength Flux (Phoenix/Mirage)" +PHOENIX_MIRAGE_ANION_PULSE_CRYSTALS = "Anion Pulse-Crystals (Phoenix/Mirage)" +CORSAIR_STEALTH_DRIVE = "Stealth Drive (Corsair)" +CORSAIR_ARGUS_JEWEL = "Argus Jewel (Corsair)" +CORSAIR_SUSTAINING_DISRUPTION = "Sustaining Disruption (Corsair)" +CORSAIR_NEUTRON_SHIELDS = "Neutron Shields (Corsair)" +ORACLE_STEALTH_DRIVE = "Stealth Drive (Oracle)" +ORACLE_STASIS_CALIBRATION = "Stasis Calibration (Oracle)" +ORACLE_TEMPORAL_ACCELERATION_BEAM = "Temporal Acceleration Beam (Oracle)" +ARBITER_CHRONOSTATIC_REINFORCEMENT = "Chronostatic Reinforcement (Arbiter)" +ARBITER_KHAYDARIN_CORE = "Khaydarin Core (Arbiter)" +ARBITER_SPACETIME_ANCHOR = "Spacetime Anchor (Arbiter)" +ARBITER_RESOURCE_EFFICIENCY = "Resource Efficiency (Arbiter)" +ARBITER_ENHANCED_CLOAK_FIELD = "Enhanced Cloak Field (Arbiter)" +CARRIER_GRAVITON_CATAPULT = "Graviton Catapult (Carrier)" +CARRIER_HULL_OF_PAST_GLORIES = "Hull of Past Glories (Carrier)" +VOID_RAY_DESTROYER_FLUX_VANES = "Flux Vanes (Void Ray/Destroyer)" +DESTROYER_REFORGED_BLOODSHARD_CORE = "Reforged Bloodshard Core (Destroyer)" +WARP_PRISM_GRAVITIC_DRIVE = "Gravitic Drive (Warp Prism)" +WARP_PRISM_PHASE_BLASTER = "Phase Blaster (Warp Prism)" +WARP_PRISM_WAR_CONFIGURATION = "War Configuration (Warp Prism)" +OBSERVER_GRAVITIC_BOOSTERS = "Gravitic Boosters (Observer)" +OBSERVER_SENSOR_ARRAY = "Sensor Array (Observer)" +REAVER_SCARAB_DAMAGE = "Scarab Damage (Reaver)" +REAVER_SOLARITE_PAYLOAD = "Solarite Payload (Reaver)" +REAVER_REAVER_CAPACITY = "Reaver Capacity (Reaver)" +REAVER_RESOURCE_EFFICIENCY = "Resource Efficiency (Reaver)" +VANGUARD_AGONY_LAUNCHERS = "Agony Launchers (Vanguard)" +VANGUARD_MATTER_DISPERSION = "Matter Dispersion (Vanguard)" +IMMORTAL_ANNIHILATOR_SINGULARITY_CHARGE = "Singularity Charge (Immortal/Annihilator)" +IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING_MECHANICS = "Advanced Targeting Mechanics (Immortal/Annihilator)" +COLOSSUS_PACIFICATION_PROTOCOL = "Pacification Protocol (Colossus)" +WRATHWALKER_RAPID_POWER_CYCLING = "Rapid Power Cycling (Wrathwalker)" +WRATHWALKER_EYE_OF_WRATH = "Eye of Wrath (Wrathwalker)" +DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHROUD_OF_ADUN = "Shroud of Adun (Dark Templar/Avenger/Blood Hunter)" +DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHADOW_GUARD_TRAINING = "Shadow Guard Training (Dark Templar/Avenger/Blood Hunter)" +DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_BLINK = "Blink (Dark Templar/Avenger/Blood Hunter)" +DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_RESOURCE_EFFICIENCY = "Resource Efficiency (Dark Templar/Avenger/Blood Hunter)" +DARK_TEMPLAR_DARK_ARCHON_MELD = "Dark Archon Meld (Dark Templar)" +HIGH_TEMPLAR_SIGNIFIER_UNSHACKLED_PSIONIC_STORM = "Unshackled Psionic Storm (High Templar/Signifier)" +HIGH_TEMPLAR_SIGNIFIER_HALLUCINATION = "Hallucination (High Templar/Signifier)" +HIGH_TEMPLAR_SIGNIFIER_KHAYDARIN_AMULET = "Khaydarin Amulet (High Templar/Signifier)" +ARCHON_HIGH_ARCHON = "High Archon (Archon)" +DARK_ARCHON_FEEDBACK = "Feedback (Dark Archon)" +DARK_ARCHON_MAELSTROM = "Maelstrom (Dark Archon)" +DARK_ARCHON_ARGUS_TALISMAN = "Argus Talisman (Dark Archon)" +ASCENDANT_POWER_OVERWHELMING = "Power Overwhelming (Ascendant)" +ASCENDANT_CHAOTIC_ATTUNEMENT = "Chaotic Attunement (Ascendant)" +ASCENDANT_BLOOD_AMULET = "Blood Amulet (Ascendant)" +SENTRY_ENERGIZER_HAVOC_CLOAKING_MODULE = "Cloaking Module (Sentry/Energizer/Havoc)" +SENTRY_ENERGIZER_HAVOC_SHIELD_BATTERY_RAPID_RECHARGING = "Rapid Recharging (Sentry/Energizer/Havoc/Shield Battery)" +SENTRY_FORCE_FIELD = "Force Field (Sentry)" +SENTRY_HALLUCINATION = "Hallucination (Sentry)" +ENERGIZER_RECLAMATION = "Reclamation (Energizer)" +ENERGIZER_FORGED_CHASSIS = "Forged Chassis (Energizer)" +HAVOC_DETECT_WEAKNESS = "Detect Weakness (Havoc)" +HAVOC_BLOODSHARD_RESONANCE = "Bloodshard Resonance (Havoc)" +ZEALOT_SENTINEL_CENTURION_LEG_ENHANCEMENTS = "Leg Enhancements (Zealot/Sentinel/Centurion)" +ZEALOT_SENTINEL_CENTURION_SHIELD_CAPACITY = "Shield Capacity (Zealot/Sentinel/Centurion)" + +# Spear Of Adun +SOA_CHRONO_SURGE = "Chrono Surge (Spear of Adun Calldown)" +SOA_PROGRESSIVE_PROXY_PYLON = "Progressive Proxy Pylon (Spear of Adun Calldown)" +SOA_PYLON_OVERCHARGE = "Pylon Overcharge (Spear of Adun Calldown)" +SOA_ORBITAL_STRIKE = "Orbital Strike (Spear of Adun Calldown)" +SOA_TEMPORAL_FIELD = "Temporal Field (Spear of Adun Calldown)" +SOA_SOLAR_LANCE = "Solar Lance (Spear of Adun Calldown)" +SOA_MASS_RECALL = "Mass Recall (Spear of Adun Calldown)" +SOA_SHIELD_OVERCHARGE = "Shield Overcharge (Spear of Adun Calldown)" +SOA_DEPLOY_FENIX = "Deploy Fenix (Spear of Adun Calldown)" +SOA_PURIFIER_BEAM = "Purifier Beam (Spear of Adun Calldown)" +SOA_TIME_STOP = "Time Stop (Spear of Adun Calldown)" +SOA_SOLAR_BOMBARDMENT = "Solar Bombardment (Spear of Adun Calldown)" + +# Generic upgrades +MATRIX_OVERLOAD = "Matrix Overload" +QUATRO = "Quatro" +NEXUS_OVERCHARGE = "Nexus Overcharge" +ORBITAL_ASSIMILATORS = "Orbital Assimilators" +WARP_HARMONIZATION = "Warp Harmonization" +GUARDIAN_SHELL = "Guardian Shell" +RECONSTRUCTION_BEAM = "Reconstruction Beam (Spear of Adun Auto-Cast)" +OVERWATCH = "Overwatch (Spear of Adun Auto-Cast)" +SUPERIOR_WARP_GATES = "Superior Warp Gates" +ENHANCED_TARGETING = "Enhanced Targeting" +OPTIMIZED_ORDNANCE = "Optimized Ordnance" +KHALAI_INGENUITY = "Khalai Ingenuity" +AMPLIFIED_ASSIMILATORS = "Amplified Assimilators" + +# Filler items +STARTING_MINERALS = "Additional Starting Minerals" +STARTING_VESPENE = "Additional Starting Vespene" +STARTING_SUPPLY = "Additional Starting Supply" +NOTHING = "Nothing" diff --git a/worlds/sc2/Items.py b/worlds/sc2/Items.py new file mode 100644 index 0000000000..85fb34e875 --- /dev/null +++ b/worlds/sc2/Items.py @@ -0,0 +1,2553 @@ +import inspect +from pydoc import describe + +from BaseClasses import Item, ItemClassification, MultiWorld +import typing + +from .Options import get_option_value, RequiredTactics +from .MissionTables import SC2Mission, SC2Race, SC2Campaign, campaign_mission_table +from . import ItemNames +from worlds.AutoWorld import World + + +class ItemData(typing.NamedTuple): + code: int + type: str + number: int # Important for bot commands to send the item into the game + race: SC2Race + classification: ItemClassification = ItemClassification.useful + quantity: int = 1 + parent_item: typing.Optional[str] = None + origin: typing.Set[str] = {"wol"} + description: typing.Optional[str] = None + important_for_filtering: bool = False + + def is_important_for_filtering(self): + return self.important_for_filtering \ + or self.classification == ItemClassification.progression \ + or self.classification == ItemClassification.progression_skip_balancing + + +class StarcraftItem(Item): + game: str = "Starcraft 2" + + +def get_full_item_list(): + return item_table + + +SC2WOL_ITEM_ID_OFFSET = 1000 +SC2HOTS_ITEM_ID_OFFSET = SC2WOL_ITEM_ID_OFFSET + 1000 +SC2LOTV_ITEM_ID_OFFSET = SC2HOTS_ITEM_ID_OFFSET + 1000 + +# Descriptions +WEAPON_ARMOR_UPGRADE_NOTE = inspect.cleandoc(""" + Must be researched during the mission if the mission type isn't set to auto-unlock generic upgrades. +""") +LASER_TARGETING_SYSTEMS_DESCRIPTION = "Increases vision by 2 and weapon range by 1." +STIMPACK_SMALL_COST = 10 +STIMPACK_SMALL_HEAL = 30 +STIMPACK_LARGE_COST = 20 +STIMPACK_LARGE_HEAL = 60 +STIMPACK_TEMPLATE = inspect.cleandoc(""" + Level 1: Stimpack: Increases unit movement and attack speed for 15 seconds. Injures the unit for {} life. + Level 2: Super Stimpack: Instead of injuring the unit, heals the unit for {} life instead. +""") +STIMPACK_SMALL_DESCRIPTION = STIMPACK_TEMPLATE.format(STIMPACK_SMALL_COST, STIMPACK_SMALL_HEAL) +STIMPACK_LARGE_DESCRIPTION = STIMPACK_TEMPLATE.format(STIMPACK_LARGE_COST, STIMPACK_LARGE_HEAL) +SMART_SERVOS_DESCRIPTION = "Increases transformation speed between modes." +INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE = "{} can be trained from a {} without an attached Tech Lab." +RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE = "Reduces {} resource and supply cost." +RESOURCE_EFFICIENCY_NO_SUPPLY_DESCRIPTION_TEMPLATE = "Reduces {} resource cost." +CLOAK_DESCRIPTION_TEMPLATE = "Allows {} to use the Cloak ability." + + +# The items are sorted by their IDs. The IDs shall be kept for compatibility with older games. +item_table = { + # WoL + ItemNames.MARINE: + ItemData(0 + SC2WOL_ITEM_ID_OFFSET, "Unit", 0, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="General-purpose infantry."), + ItemNames.MEDIC: + ItemData(1 + SC2WOL_ITEM_ID_OFFSET, "Unit", 1, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Support trooper. Heals nearby biological units."), + ItemNames.FIREBAT: + ItemData(2 + SC2WOL_ITEM_ID_OFFSET, "Unit", 2, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Specialized anti-infantry attacker."), + ItemNames.MARAUDER: + ItemData(3 + SC2WOL_ITEM_ID_OFFSET, "Unit", 3, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Heavy assault infantry."), + ItemNames.REAPER: + ItemData(4 + SC2WOL_ITEM_ID_OFFSET, "Unit", 4, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Raider. Capable of jumping up and down cliffs. Throws explosive mines."), + ItemNames.HELLION: + ItemData(5 + SC2WOL_ITEM_ID_OFFSET, "Unit", 5, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Fast scout. Has a flame attack that damages all enemy units in its line of fire."), + ItemNames.VULTURE: + ItemData(6 + SC2WOL_ITEM_ID_OFFSET, "Unit", 6, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Fast skirmish unit. Can use the Spider Mine ability."), + ItemNames.GOLIATH: + ItemData(7 + SC2WOL_ITEM_ID_OFFSET, "Unit", 7, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Heavy-fire support unit."), + ItemNames.DIAMONDBACK: + ItemData(8 + SC2WOL_ITEM_ID_OFFSET, "Unit", 8, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Fast, high-damage hovertank. Rail Gun can fire while the Diamondback is moving."), + ItemNames.SIEGE_TANK: + ItemData(9 + SC2WOL_ITEM_ID_OFFSET, "Unit", 9, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Heavy tank. Long-range artillery in Siege Mode."), + ItemNames.MEDIVAC: + ItemData(10 + SC2WOL_ITEM_ID_OFFSET, "Unit", 10, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Air transport. Heals nearby biological units."), + ItemNames.WRAITH: + ItemData(11 + SC2WOL_ITEM_ID_OFFSET, "Unit", 11, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Highly mobile flying unit. Excellent at surgical strikes."), + ItemNames.VIKING: + ItemData(12 + SC2WOL_ITEM_ID_OFFSET, "Unit", 12, SC2Race.TERRAN, + classification=ItemClassification.progression, + description=inspect.cleandoc( + """ + Durable support flyer. Loaded with strong anti-capital air missiles. + Can switch into Assault Mode to attack ground units. + """ + )), + ItemNames.BANSHEE: + ItemData(13 + SC2WOL_ITEM_ID_OFFSET, "Unit", 13, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Tactical-strike aircraft."), + ItemNames.BATTLECRUISER: + ItemData(14 + SC2WOL_ITEM_ID_OFFSET, "Unit", 14, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Powerful warship."), + ItemNames.GHOST: + ItemData(15 + SC2WOL_ITEM_ID_OFFSET, "Unit", 15, SC2Race.TERRAN, + classification=ItemClassification.progression, + description=inspect.cleandoc( + """ + Infiltration unit. Can use Snipe and Cloak abilities. Can also call down Tactical Nukes. + """ + )), + ItemNames.SPECTRE: + ItemData(16 + SC2WOL_ITEM_ID_OFFSET, "Unit", 16, SC2Race.TERRAN, + classification=ItemClassification.progression, + description=inspect.cleandoc( + """ + Infiltration unit. Can use Ultrasonic Pulse, Psionic Lash, and Cloak. + Can also call down Tactical Nukes. + """ + )), + ItemNames.THOR: + ItemData(17 + SC2WOL_ITEM_ID_OFFSET, "Unit", 17, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Heavy assault mech."), + # EE units + ItemNames.LIBERATOR: + ItemData(18 + SC2WOL_ITEM_ID_OFFSET, "Unit", 18, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"nco", "ext"}, + description=inspect.cleandoc( + """ + Artillery fighter. Loaded with missiles that deal area damage to enemy air targets. + Can switch into Defender Mode to provide siege support. + """ + )), + ItemNames.VALKYRIE: + ItemData(19 + SC2WOL_ITEM_ID_OFFSET, "Unit", 19, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"bw"}, + description=inspect.cleandoc( + """ + Advanced anti-aircraft fighter. + Able to use cluster missiles that deal area damage to air targets. + """ + )), + ItemNames.WIDOW_MINE: + ItemData(20 + SC2WOL_ITEM_ID_OFFSET, "Unit", 20, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"ext"}, + description=inspect.cleandoc( + """ + Robotic mine. Launches missiles at nearby enemy units while burrowed. + Attacks deal splash damage in a small area around the target. + Widow Mine is revealed when Sentinel Missile is on cooldown. + """ + )), + ItemNames.CYCLONE: + ItemData(21 + SC2WOL_ITEM_ID_OFFSET, "Unit", 21, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"ext"}, + description=inspect.cleandoc( + """ + Mobile assault vehicle. Can use Lock On to quickly fire while moving. + """ + )), + ItemNames.HERC: + ItemData(22 + SC2WOL_ITEM_ID_OFFSET, "Unit", 26, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"ext"}, + description=inspect.cleandoc( + """ + Front-line infantry. Can use Grapple. + """ + )), + ItemNames.WARHOUND: + ItemData(23 + SC2WOL_ITEM_ID_OFFSET, "Unit", 27, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"ext"}, + description=inspect.cleandoc( + """ + Anti-vehicle mech. Haywire missiles do bonus damage to mechanical units. + """ + )), + + # Some other items are moved to Upgrade group because of the way how the bot message is parsed + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_WEAPON: + ItemData(100 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 0, SC2Race.TERRAN, + quantity=3, + description=inspect.cleandoc( + f""" + Increases damage of Terran infantry units. + {WEAPON_ARMOR_UPGRADE_NOTE} + """ + )), + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_ARMOR: + ItemData(102 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 2, SC2Race.TERRAN, + quantity=3, + description=inspect.cleandoc( + f""" + Increases armor of Terran infantry units. + {WEAPON_ARMOR_UPGRADE_NOTE} + """ + )), + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_WEAPON: + ItemData(103 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 4, SC2Race.TERRAN, + quantity=3, + description=inspect.cleandoc( + f""" + Increases damage of Terran vehicle units. + {WEAPON_ARMOR_UPGRADE_NOTE} + """ + )), + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_ARMOR: + ItemData(104 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 6, SC2Race.TERRAN, + quantity=3, + description=inspect.cleandoc( + f""" + Increases armor of Terran vehicle units. + {WEAPON_ARMOR_UPGRADE_NOTE} + """ + )), + ItemNames.PROGRESSIVE_TERRAN_SHIP_WEAPON: + ItemData(105 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 8, SC2Race.TERRAN, + quantity=3, + description=inspect.cleandoc( + f""" + Increases damage of Terran starship units. + {WEAPON_ARMOR_UPGRADE_NOTE} + """ + )), + ItemNames.PROGRESSIVE_TERRAN_SHIP_ARMOR: + ItemData(106 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 10, SC2Race.TERRAN, + quantity=3, + description=inspect.cleandoc( + f""" + Increases armor of Terran starship units. + {WEAPON_ARMOR_UPGRADE_NOTE} + """ + )), + # Upgrade bundle 'number' values are used as indices to get affected 'number's + ItemNames.PROGRESSIVE_TERRAN_WEAPON_UPGRADE: ItemData(107 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 0, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_ARMOR_UPGRADE: ItemData(108 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 1, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_UPGRADE: ItemData(109 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 2, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_UPGRADE: ItemData(110 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 3, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_SHIP_UPGRADE: ItemData(111 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 4, SC2Race.TERRAN, quantity=3), + ItemNames.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE: ItemData(112 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 5, SC2Race.TERRAN, quantity=3), + + # Unit and structure upgrades + ItemNames.BUNKER_PROJECTILE_ACCELERATOR: + ItemData(200 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 0, SC2Race.TERRAN, + parent_item=ItemNames.BUNKER, + description="Increases range of all units in the Bunker by 1."), + ItemNames.BUNKER_NEOSTEEL_BUNKER: + ItemData(201 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 1, SC2Race.TERRAN, + parent_item=ItemNames.BUNKER, + description="Increases the number of Bunker slots by 2."), + ItemNames.MISSILE_TURRET_TITANIUM_HOUSING: + ItemData(202 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 2, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MISSILE_TURRET, + description="Increases Missile Turret life by 75."), + ItemNames.MISSILE_TURRET_HELLSTORM_BATTERIES: + ItemData(203 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 3, SC2Race.TERRAN, + parent_item=ItemNames.MISSILE_TURRET, + description="The Missile Turret unleashes an additional flurry of missiles with each attack."), + ItemNames.SCV_ADVANCED_CONSTRUCTION: + ItemData(204 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 4, SC2Race.TERRAN, + description="Multiple SCVs can construct a structure, reducing its construction time."), + ItemNames.SCV_DUAL_FUSION_WELDERS: + ItemData(205 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 5, SC2Race.TERRAN, + description="SCVs repair twice as fast."), + ItemNames.PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM: + ItemData(206 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 24, SC2Race.TERRAN, + quantity=2, + description=inspect.cleandoc( + """ + Level 1: While on low health, Terran structures are repaired to half health instead of burning down. + Level 2: Terran structures are repaired to full health instead of half health + """ + )), + ItemNames.PROGRESSIVE_ORBITAL_COMMAND: + ItemData(207 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 26, SC2Race.TERRAN, + quantity=2, classification=ItemClassification.progression, + description=inspect.cleandoc( + """ + Level 1: Allows Command Centers to use Scanner Sweep and Calldown: MULE abilities. + Level 2: Orbital Command abilities work even in Planetary Fortress mode. + """ + )), + ItemNames.MARINE_PROGRESSIVE_STIMPACK: + ItemData(208 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 0, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.MARINE, quantity=2, + description=STIMPACK_SMALL_DESCRIPTION), + ItemNames.MARINE_COMBAT_SHIELD: + ItemData(209 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 9, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.MARINE, + description="Increases Marine life by 10."), + ItemNames.MEDIC_ADVANCED_MEDIC_FACILITIES: + ItemData(210 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 10, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Medics", "Barracks")), + ItemNames.MEDIC_STABILIZER_MEDPACKS: + ItemData(211 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 11, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.MEDIC, + description="Increases Medic heal speed. Reduces the amount of energy required for each heal."), + ItemNames.FIREBAT_INCINERATOR_GAUNTLETS: + ItemData(212 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 12, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.FIREBAT, + description="Increases Firebat's damage radius by 40%"), + ItemNames.FIREBAT_JUGGERNAUT_PLATING: + ItemData(213 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 13, SC2Race.TERRAN, + parent_item=ItemNames.FIREBAT, + description="Increases Firebat's armor by 2."), + ItemNames.MARAUDER_CONCUSSIVE_SHELLS: + ItemData(214 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 14, SC2Race.TERRAN, + parent_item=ItemNames.MARAUDER, + description="Marauder attack temporarily slows all units in target area."), + ItemNames.MARAUDER_KINETIC_FOAM: + ItemData(215 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 15, SC2Race.TERRAN, + parent_item=ItemNames.MARAUDER, + description="Increases Marauder life by 25."), + ItemNames.REAPER_U238_ROUNDS: + ItemData(216 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 16, SC2Race.TERRAN, + parent_item=ItemNames.REAPER, + description=inspect.cleandoc( + """ + Increases Reaper pistol attack range by 1. + Reaper pistols do additional 3 damage to Light Armor. + """ + )), + ItemNames.REAPER_G4_CLUSTERBOMB: + ItemData(217 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 17, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.REAPER, + description="Timed explosive that does heavy area damage."), + ItemNames.CYCLONE_MAG_FIELD_ACCELERATORS: + ItemData(218 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 18, SC2Race.TERRAN, + parent_item=ItemNames.CYCLONE, origin={"ext"}, + description="Increases Cyclone Lock On damage"), + ItemNames.CYCLONE_MAG_FIELD_LAUNCHERS: + ItemData(219 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 19, SC2Race.TERRAN, + parent_item=ItemNames.CYCLONE, origin={"ext"}, + description="Increases Cyclone attack range by 2."), + ItemNames.MARINE_LASER_TARGETING_SYSTEM: + ItemData(220 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 8, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MARINE, origin={"nco"}, + description=LASER_TARGETING_SYSTEMS_DESCRIPTION), + ItemNames.MARINE_MAGRAIL_MUNITIONS: + ItemData(221 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 20, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.MARINE, origin={"nco"}, + description="Deals 20 damage to target unit. Autocast on attack with a cooldown."), + ItemNames.MARINE_OPTIMIZED_LOGISTICS: + ItemData(222 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 21, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MARINE, origin={"nco"}, + description="Increases Marine training speed."), + ItemNames.MEDIC_RESTORATION: + ItemData(223 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 22, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"bw"}, + description="Removes negative status effects from target allied unit."), + ItemNames.MEDIC_OPTICAL_FLARE: + ItemData(224 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 23, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"bw"}, + description="Reduces vision range of target enemy unit. Disables detection."), + ItemNames.MEDIC_RESOURCE_EFFICIENCY: + ItemData(225 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 24, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"bw"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Medic")), + ItemNames.FIREBAT_PROGRESSIVE_STIMPACK: + ItemData(226 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 6, SC2Race.TERRAN, + parent_item=ItemNames.FIREBAT, quantity=2, origin={"bw"}, + description=STIMPACK_LARGE_DESCRIPTION), + ItemNames.FIREBAT_RESOURCE_EFFICIENCY: + ItemData(227 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 25, SC2Race.TERRAN, + parent_item=ItemNames.FIREBAT, origin={"bw"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Firebat")), + ItemNames.MARAUDER_PROGRESSIVE_STIMPACK: + ItemData(228 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 8, SC2Race.TERRAN, + parent_item=ItemNames.MARAUDER, quantity=2, origin={"nco"}, + description=STIMPACK_LARGE_DESCRIPTION), + ItemNames.MARAUDER_LASER_TARGETING_SYSTEM: + ItemData(229 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 26, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MARAUDER, origin={"nco"}, + description=LASER_TARGETING_SYSTEMS_DESCRIPTION), + ItemNames.MARAUDER_MAGRAIL_MUNITIONS: + ItemData(230 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 27, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MARAUDER, origin={"nco"}, + description="Deals 20 damage to target unit. Autocast on attack with a cooldown."), + ItemNames.MARAUDER_INTERNAL_TECH_MODULE: + ItemData(231 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 28, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MARAUDER, origin={"nco"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Marauders", "Barracks")), + ItemNames.SCV_HOSTILE_ENVIRONMENT_ADAPTATION: + ItemData(232 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 29, SC2Race.TERRAN, + classification=ItemClassification.filler, origin={"bw"}, + description="Increases SCV life by 15 and attack speed slightly."), + ItemNames.MEDIC_ADAPTIVE_MEDPACKS: + ItemData(233 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 0, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.MEDIC, origin={"ext"}, + description="Allows Medics to heal mechanical and air units."), + ItemNames.MEDIC_NANO_PROJECTOR: + ItemData(234 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 1, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIC, origin={"ext"}, + description="Increases Medic heal range by 2."), + ItemNames.FIREBAT_INFERNAL_PRE_IGNITER: + ItemData(235 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 2, SC2Race.TERRAN, + parent_item=ItemNames.FIREBAT, origin={"bw"}, + description="Firebats do an additional 4 damage to Light Armor."), + ItemNames.FIREBAT_KINETIC_FOAM: + ItemData(236 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 3, SC2Race.TERRAN, + parent_item=ItemNames.FIREBAT, origin={"ext"}, + description="Increases Firebat life by 100."), + ItemNames.FIREBAT_NANO_PROJECTORS: + ItemData(237 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 4, SC2Race.TERRAN, + parent_item=ItemNames.FIREBAT, origin={"ext"}, + description="Increases Firebat attack range by 2"), + ItemNames.MARAUDER_JUGGERNAUT_PLATING: + ItemData(238 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 5, SC2Race.TERRAN, + parent_item=ItemNames.MARAUDER, origin={"ext"}, + description="Increases Marauder's armor by 2."), + ItemNames.REAPER_JET_PACK_OVERDRIVE: + ItemData(239 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 6, SC2Race.TERRAN, + parent_item=ItemNames.REAPER, origin={"ext"}, + description=inspect.cleandoc( + """ + Allows the Reaper to fly for 10 seconds. + While flying, the Reaper can attack air units. + """ + )), + ItemNames.HELLION_INFERNAL_PLATING: + ItemData(240 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 7, SC2Race.TERRAN, + parent_item=ItemNames.HELLION, origin={"ext"}, + description="Increases Hellion and Hellbat armor by 2."), + ItemNames.VULTURE_AUTO_REPAIR: + ItemData(241 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 8, SC2Race.TERRAN, + parent_item=ItemNames.VULTURE, origin={"ext"}, + description="Vultures regenerate life."), + ItemNames.GOLIATH_SHAPED_HULL: + ItemData(242 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 9, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.GOLIATH, origin={"nco", "ext"}, + description="Increases Goliath life by 25."), + ItemNames.GOLIATH_RESOURCE_EFFICIENCY: + ItemData(243 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 10, SC2Race.TERRAN, + parent_item=ItemNames.GOLIATH, origin={"nco", "bw"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Goliath")), + ItemNames.GOLIATH_INTERNAL_TECH_MODULE: + ItemData(244 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 11, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.GOLIATH, origin={"nco", "bw"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Goliaths", "Factory")), + ItemNames.SIEGE_TANK_SHAPED_HULL: + ItemData(245 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 12, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco", "ext"}, + description="Increases Siege Tank life by 25."), + ItemNames.SIEGE_TANK_RESOURCE_EFFICIENCY: + ItemData(246 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 13, SC2Race.TERRAN, + parent_item=ItemNames.SIEGE_TANK, origin={"bw"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Siege Tank")), + ItemNames.PREDATOR_CLOAK: + ItemData(247 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 14, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.PREDATOR, origin={"ext"}, + description=CLOAK_DESCRIPTION_TEMPLATE.format("Predators")), + ItemNames.PREDATOR_CHARGE: + ItemData(248 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 15, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.PREDATOR, origin={"ext"}, + description="Allows Predators to intercept enemy ground units."), + ItemNames.MEDIVAC_SCATTER_VEIL: + ItemData(249 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 16, SC2Race.TERRAN, + parent_item=ItemNames.MEDIVAC, origin={"ext"}, + description="Medivacs get 100 shields."), + ItemNames.REAPER_PROGRESSIVE_STIMPACK: + ItemData(250 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 10, SC2Race.TERRAN, + parent_item=ItemNames.REAPER, quantity=2, origin={"nco"}, + description=STIMPACK_SMALL_DESCRIPTION), + ItemNames.REAPER_LASER_TARGETING_SYSTEM: + ItemData(251 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 17, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.REAPER, origin={"nco"}, + description=LASER_TARGETING_SYSTEMS_DESCRIPTION), + ItemNames.REAPER_ADVANCED_CLOAKING_FIELD: + ItemData(252 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 18, SC2Race.TERRAN, + parent_item=ItemNames.REAPER, origin={"nco"}, + description="Reapers are permanently cloaked."), + ItemNames.REAPER_SPIDER_MINES: + ItemData(253 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 19, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.REAPER, origin={"nco"}, + important_for_filtering=True, + description="Allows Reapers to lay Spider Mines. 3 charges per Reaper."), + ItemNames.REAPER_COMBAT_DRUGS: + ItemData(254 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 20, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.REAPER, origin={"ext"}, + description="Reapers regenerate life while out of combat."), + ItemNames.HELLION_HELLBAT_ASPECT: + ItemData(255 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 21, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.HELLION, origin={"nco"}, + description="Allows Hellions to transform into Hellbats."), + ItemNames.HELLION_SMART_SERVOS: + ItemData(256 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 22, SC2Race.TERRAN, + parent_item=ItemNames.HELLION, origin={"nco"}, + description="Transforms faster between modes. Hellions can attack while moving."), + ItemNames.HELLION_OPTIMIZED_LOGISTICS: + ItemData(257 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 23, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.HELLION, origin={"nco"}, + description="Increases Hellion training speed."), + ItemNames.HELLION_JUMP_JETS: + ItemData(258 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 24, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.HELLION, origin={"nco"}, + description=inspect.cleandoc( + """ + Increases movement speed in Hellion mode. + In Hellbat mode, launches the Hellbat toward enemy ground units and briefly stuns them. + """ + )), + ItemNames.HELLION_PROGRESSIVE_STIMPACK: + ItemData(259 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 12, SC2Race.TERRAN, + parent_item=ItemNames.HELLION, quantity=2, origin={"nco"}, + description=STIMPACK_LARGE_DESCRIPTION), + ItemNames.VULTURE_ION_THRUSTERS: + ItemData(260 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 25, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.VULTURE, origin={"bw"}, + description="Increases Vulture movement speed."), + ItemNames.VULTURE_AUTO_LAUNCHERS: + ItemData(261 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 26, SC2Race.TERRAN, + parent_item=ItemNames.VULTURE, origin={"bw"}, + description="Allows Vultures to attack while moving."), + ItemNames.SPIDER_MINE_HIGH_EXPLOSIVE_MUNITION: + ItemData(262 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 27, SC2Race.TERRAN, + origin={"bw"}, + description="Increases Spider mine damage."), + ItemNames.GOLIATH_JUMP_JETS: + ItemData(263 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 28, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.GOLIATH, origin={"nco"}, + description="Allows Goliaths to jump up and down cliffs."), + ItemNames.GOLIATH_OPTIMIZED_LOGISTICS: + ItemData(264 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 29, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.GOLIATH, origin={"nco"}, + description="Increases Goliath training speed."), + ItemNames.DIAMONDBACK_HYPERFLUXOR: + ItemData(265 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 0, SC2Race.TERRAN, + parent_item=ItemNames.DIAMONDBACK, origin={"ext"}, + description="Increases Diamondback attack speed."), + ItemNames.DIAMONDBACK_BURST_CAPACITORS: + ItemData(266 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 1, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.DIAMONDBACK, origin={"ext"}, + description=inspect.cleandoc( + """ + While not attacking, the Diamondback charges its weapon. + The next attack does 10 additional damage. + """ + )), + ItemNames.DIAMONDBACK_RESOURCE_EFFICIENCY: + ItemData(267 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 2, SC2Race.TERRAN, + parent_item=ItemNames.DIAMONDBACK, origin={"ext"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Diamondback")), + ItemNames.SIEGE_TANK_JUMP_JETS: + ItemData(268 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 3, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}, + description=inspect.cleandoc( + """ + Repositions Siege Tank to a target location. + Can be used in either mode and to jump up and down cliffs. + """ + )), + ItemNames.SIEGE_TANK_SPIDER_MINES: + ItemData(269 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 4, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}, + important_for_filtering=True, + description=inspect.cleandoc( + """ + Allows Siege Tanks to lay Spider Mines. + Lays 3 Spider Mines at once. 3 charges + """ + )), + ItemNames.SIEGE_TANK_SMART_SERVOS: + ItemData(270 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 5, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}, + description=SMART_SERVOS_DESCRIPTION), + ItemNames.SIEGE_TANK_GRADUATING_RANGE: + ItemData(271 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 6, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.SIEGE_TANK, origin={"ext"}, + description=inspect.cleandoc( + """ + Increases the Siege Tank's attack range by 1 every 3 seconds while in Siege Mode, + up to a maximum of 5 additional range. + """ + )), + ItemNames.SIEGE_TANK_LASER_TARGETING_SYSTEM: + ItemData(272 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 7, SC2Race.TERRAN, + parent_item=ItemNames.SIEGE_TANK, origin={"nco"}, + description=LASER_TARGETING_SYSTEMS_DESCRIPTION), + ItemNames.SIEGE_TANK_ADVANCED_SIEGE_TECH: + ItemData(273 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 8, SC2Race.TERRAN, + parent_item=ItemNames.SIEGE_TANK, origin={"ext"}, + description="Siege Tanks gain +3 armor in Siege Mode."), + ItemNames.SIEGE_TANK_INTERNAL_TECH_MODULE: + ItemData(274 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 9, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.SIEGE_TANK, origin={"nco"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Siege Tanks", "Factory")), + ItemNames.PREDATOR_RESOURCE_EFFICIENCY: + ItemData(275 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 10, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.PREDATOR, origin={"ext"}, + description="Decreases Predator resource and supply cost."), + ItemNames.MEDIVAC_EXPANDED_HULL: + ItemData(276 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 11, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC, origin={"ext"}, + description="Increases Medivac cargo space by 4."), + ItemNames.MEDIVAC_AFTERBURNERS: + ItemData(277 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 12, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC, origin={"ext"}, + description="Ability. Temporarily increases the Medivac's movement speed by 70%."), + ItemNames.WRAITH_ADVANCED_LASER_TECHNOLOGY: + ItemData(278 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 13, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.WRAITH, origin={"ext"}, + description=inspect.cleandoc( + """ + Burst Lasers do more damage and can hit both ground and air targets. + Replaces Gemini Missiles weapon. + """ + )), + ItemNames.VIKING_SMART_SERVOS: + ItemData(279 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 14, SC2Race.TERRAN, + parent_item=ItemNames.VIKING, origin={"ext"}, + description=SMART_SERVOS_DESCRIPTION), + ItemNames.VIKING_ANTI_MECHANICAL_MUNITION: + ItemData(280 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 15, SC2Race.TERRAN, + parent_item=ItemNames.VIKING, origin={"ext"}, + description="Increases Viking damage to mechanical units while in Assault Mode."), + ItemNames.DIAMONDBACK_ION_THRUSTERS: + ItemData(281 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 21, SC2Race.TERRAN, + parent_item=ItemNames.DIAMONDBACK, origin={"ext"}, + description="Increases Diamondback movement speed."), + ItemNames.WARHOUND_RESOURCE_EFFICIENCY: + ItemData(282 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 13, SC2Race.TERRAN, + parent_item=ItemNames.WARHOUND, origin={"ext"}, + description=RESOURCE_EFFICIENCY_NO_SUPPLY_DESCRIPTION_TEMPLATE.format("Warhound")), + ItemNames.WARHOUND_REINFORCED_PLATING: + ItemData(283 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 14, SC2Race.TERRAN, + parent_item=ItemNames.WARHOUND, origin={"ext"}, + description="Increases Warhound armor by 2."), + ItemNames.HERC_RESOURCE_EFFICIENCY: + ItemData(284 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 15, SC2Race.TERRAN, + parent_item=ItemNames.HERC, origin={"ext"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("HERC")), + ItemNames.HERC_JUGGERNAUT_PLATING: + ItemData(285 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 16, SC2Race.TERRAN, + parent_item=ItemNames.WARHOUND, origin={"ext"}, + description="Increases HERC armor by 2."), + ItemNames.HERC_KINETIC_FOAM: + ItemData(286 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 17, SC2Race.TERRAN, + parent_item=ItemNames.WARHOUND, origin={"ext"}, + description="Increases HERC life by 50."), + + ItemNames.HELLION_TWIN_LINKED_FLAMETHROWER: + ItemData(300 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 16, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.HELLION, + description="Doubles the width of the Hellion's flame attack."), + ItemNames.HELLION_THERMITE_FILAMENTS: + ItemData(301 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 17, SC2Race.TERRAN, + parent_item=ItemNames.HELLION, + description="Hellions do an additional 10 damage to Light Armor."), + ItemNames.SPIDER_MINE_CERBERUS_MINE: + ItemData(302 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 18, SC2Race.TERRAN, + classification=ItemClassification.filler, + description="Increases trigger and blast radius of Spider Mines."), + ItemNames.VULTURE_PROGRESSIVE_REPLENISHABLE_MAGAZINE: + ItemData(303 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 16, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.VULTURE, quantity=2, + description=inspect.cleandoc( + """ + Level 1: Allows Vultures to replace used Spider Mines. Costs 15 minerals. + Level 2: Replacing used Spider Mines no longer costs minerals. + """ + )), + ItemNames.GOLIATH_MULTI_LOCK_WEAPONS_SYSTEM: + ItemData(304 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 19, SC2Race.TERRAN, + parent_item=ItemNames.GOLIATH, + description="Goliaths can attack both ground and air targets simultaneously."), + ItemNames.GOLIATH_ARES_CLASS_TARGETING_SYSTEM: + ItemData(305 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 20, SC2Race.TERRAN, + parent_item=ItemNames.GOLIATH, + description="Increases Goliath ground attack range by 1 and air by 3."), + ItemNames.DIAMONDBACK_PROGRESSIVE_TRI_LITHIUM_POWER_CELL: + ItemData(306 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade 2", 4, SC2Race.TERRAN, + parent_item=ItemNames.DIAMONDBACK, quantity=2, + description=inspect.cleandoc( + """ + Level 1: Tri-Lithium Power Cell: Increases Diamondback attack range by 1. + Level 2: Tungsten Spikes: Increases Diamondback attack range by 3. + """ + )), + ItemNames.DIAMONDBACK_SHAPED_HULL: + ItemData(307 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 22, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.DIAMONDBACK, + description="Increases Diamondback life by 50."), + ItemNames.SIEGE_TANK_MAELSTROM_ROUNDS: + ItemData(308 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 23, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.SIEGE_TANK, + description="Siege Tanks do an additional 40 damage to the primary target in Siege Mode."), + ItemNames.SIEGE_TANK_SHAPED_BLAST: + ItemData(309 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 24, SC2Race.TERRAN, + parent_item=ItemNames.SIEGE_TANK, + description="Reduces splash damage to friendly targets while in Siege Mode by 75%."), + ItemNames.MEDIVAC_RAPID_DEPLOYMENT_TUBE: + ItemData(310 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 25, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC, + description="Medivacs deploy loaded troops almost instantly."), + ItemNames.MEDIVAC_ADVANCED_HEALING_AI: + ItemData(311 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 26, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.MEDIVAC, + description="Medivacs can heal two targets at once."), + ItemNames.WRAITH_PROGRESSIVE_TOMAHAWK_POWER_CELLS: + ItemData(312 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 18, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.WRAITH, quantity=2, + description=inspect.cleandoc( + """ + Level 1: Tomahawk Power Cells: Increases Wraith starting energy by 100. + Level 2: Unregistered Cloaking Module: Wraiths do not require energy to cloak and remain cloaked. + """ + )), + ItemNames.WRAITH_DISPLACEMENT_FIELD: + ItemData(313 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 27, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.WRAITH, + description="Wraiths evade 20% of incoming attacks while cloaked."), + ItemNames.VIKING_RIPWAVE_MISSILES: + ItemData(314 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 28, SC2Race.TERRAN, + parent_item=ItemNames.VIKING, + description="Vikings do area damage while in Fighter Mode"), + ItemNames.VIKING_PHOBOS_CLASS_WEAPONS_SYSTEM: + ItemData(315 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 29, SC2Race.TERRAN, + parent_item=ItemNames.VIKING, + description="Increases Viking attack range by 1 in Assault mode and 2 in Fighter mode."), + ItemNames.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS: + ItemData(316 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 2, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, quantity=2, + description=inspect.cleandoc( + """ + Level 1: Banshees can remain cloaked twice as long. + Level 2: Banshees do not require energy to cloak and remain cloaked. + """ + )), + ItemNames.BANSHEE_SHOCKWAVE_MISSILE_BATTERY: + ItemData(317 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 0, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.BANSHEE, + description="Banshees do area damage in a straight line."), + ItemNames.BATTLECRUISER_PROGRESSIVE_MISSILE_PODS: + ItemData(318 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade 2", 2, SC2Race.TERRAN, + parent_item=ItemNames.BATTLECRUISER, quantity=2, + description="Spell. Missile Pods do damage to air targets in a target area."), + ItemNames.BATTLECRUISER_PROGRESSIVE_DEFENSIVE_MATRIX: + ItemData(319 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 20, SC2Race.TERRAN, + parent_item=ItemNames.BATTLECRUISER, quantity=2, + description=inspect.cleandoc( + """ + Level 1: Spell. For 20 seconds the Battlecruiser gains a shield that can absorb up to 200 damage. + Level 2: Passive. Battlecruiser gets 200 shields. + """ + )), + ItemNames.GHOST_OCULAR_IMPLANTS: + ItemData(320 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 2, SC2Race.TERRAN, + parent_item=ItemNames.GHOST, + description="Increases Ghost sight range by 3 and attack range by 2."), + ItemNames.GHOST_CRIUS_SUIT: + ItemData(321 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 3, SC2Race.TERRAN, + parent_item=ItemNames.GHOST, + description="Cloak no longer requires energy to activate or maintain."), + ItemNames.SPECTRE_PSIONIC_LASH: + ItemData(322 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 4, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.SPECTRE, + description="Spell. Deals 200 damage to a single target."), + ItemNames.SPECTRE_NYX_CLASS_CLOAKING_MODULE: + ItemData(323 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 5, SC2Race.TERRAN, + parent_item=ItemNames.SPECTRE, + description="Cloak no longer requires energy to activate or maintain."), + ItemNames.THOR_330MM_BARRAGE_CANNON: + ItemData(324 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 6, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.THOR, + description=inspect.cleandoc( + """ + Improves 250mm Strike Cannons ability to deal area damage and stun units in a small area. + Can be also freely aimed on ground. + """ + )), + ItemNames.THOR_PROGRESSIVE_IMMORTALITY_PROTOCOL: + ItemData(325 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 22, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.THOR, quantity=2, + description=inspect.cleandoc(""" + Level 1: Allows destroyed Thors to be reconstructed on the field. Costs Vespene Gas. + Level 2: Thors are automatically reconstructed after falling for free. + """ + )), + ItemNames.LIBERATOR_ADVANCED_BALLISTICS: + ItemData(326 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 7, SC2Race.TERRAN, + parent_item=ItemNames.LIBERATOR, origin={"ext"}, + description="Increases Liberator range by 3 in Defender Mode."), + ItemNames.LIBERATOR_RAID_ARTILLERY: + ItemData(327 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 8, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.LIBERATOR, origin={"nco"}, + description="Allows Liberators to attack structures while in Defender Mode."), + ItemNames.WIDOW_MINE_DRILLING_CLAWS: + ItemData(328 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 9, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.WIDOW_MINE, origin={"ext"}, + description="Allows Widow Mines to burrow and unburrow faster."), + ItemNames.WIDOW_MINE_CONCEALMENT: + ItemData(329 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 10, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.WIDOW_MINE, origin={"ext"}, + description="Burrowed Widow Mines are no longer revealed when the Sentinel Missile is on cooldown."), + ItemNames.MEDIVAC_ADVANCED_CLOAKING_FIELD: + ItemData(330 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 11, SC2Race.TERRAN, + parent_item=ItemNames.MEDIVAC, origin={"ext"}, + description="Medivacs are permanently cloaked."), + ItemNames.WRAITH_TRIGGER_OVERRIDE: + ItemData(331 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 12, SC2Race.TERRAN, + parent_item=ItemNames.WRAITH, origin={"ext"}, + description="Wraith attack speed increases by 10% with each attack, up to a maximum of 100%."), + ItemNames.WRAITH_INTERNAL_TECH_MODULE: + ItemData(332 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 13, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.WRAITH, origin={"bw"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Wraiths", "Starport")), + ItemNames.WRAITH_RESOURCE_EFFICIENCY: + ItemData(333 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 14, SC2Race.TERRAN, + parent_item=ItemNames.WRAITH, origin={"bw"}, + description=RESOURCE_EFFICIENCY_NO_SUPPLY_DESCRIPTION_TEMPLATE.format("Wraith")), + ItemNames.VIKING_SHREDDER_ROUNDS: + ItemData(334 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 15, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.VIKING, origin={"ext"}, + description="Attacks in Assault mode do line splash damage."), + ItemNames.VIKING_WILD_MISSILES: + ItemData(335 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 16, SC2Race.TERRAN, + parent_item=ItemNames.VIKING, origin={"ext"}, + description="Launches 5 rockets at the target unit. Each rocket does 25 (40 vs armored) damage."), + ItemNames.BANSHEE_SHAPED_HULL: + ItemData(336 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 17, SC2Race.TERRAN, + parent_item=ItemNames.BANSHEE, origin={"ext"}, + description="Increases Banshee life by 100."), + ItemNames.BANSHEE_ADVANCED_TARGETING_OPTICS: + ItemData(337 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 18, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.BANSHEE, origin={"ext"}, + description="Increases Banshee attack range by 2 while cloaked."), + ItemNames.BANSHEE_DISTORTION_BLASTERS: + ItemData(338 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 19, SC2Race.TERRAN, + parent_item=ItemNames.BANSHEE, origin={"ext"}, + description="Increases Banshee attack damage by 25% while cloaked."), + ItemNames.BANSHEE_ROCKET_BARRAGE: + ItemData(339 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 20, SC2Race.TERRAN, + parent_item=ItemNames.BANSHEE, origin={"ext"}, + description="Deals 75 damage to enemy ground units in the target area."), + ItemNames.GHOST_RESOURCE_EFFICIENCY: + ItemData(340 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 21, SC2Race.TERRAN, + parent_item=ItemNames.GHOST, origin={"bw"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Ghost")), + ItemNames.SPECTRE_RESOURCE_EFFICIENCY: + ItemData(341 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 22, SC2Race.TERRAN, + parent_item=ItemNames.SPECTRE, origin={"ext"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Spectre")), + ItemNames.THOR_BUTTON_WITH_A_SKULL_ON_IT: + ItemData(342 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 23, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.THOR, origin={"ext"}, + description="Allows Thors to launch nukes."), + ItemNames.THOR_LASER_TARGETING_SYSTEM: + ItemData(343 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 24, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.THOR, origin={"ext"}, + description=LASER_TARGETING_SYSTEMS_DESCRIPTION), + ItemNames.THOR_LARGE_SCALE_FIELD_CONSTRUCTION: + ItemData(344 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 25, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.THOR, origin={"ext"}, + description="Allows Thors to be built by SCVs like a structure."), + ItemNames.RAVEN_RESOURCE_EFFICIENCY: + ItemData(345 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 26, SC2Race.TERRAN, + parent_item=ItemNames.RAVEN, origin={"ext"}, + description=RESOURCE_EFFICIENCY_NO_SUPPLY_DESCRIPTION_TEMPLATE.format("Raven")), + ItemNames.RAVEN_DURABLE_MATERIALS: + ItemData(346 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 27, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.RAVEN, origin={"ext"}, + description="Extends timed life duration of Raven's summoned objects."), + ItemNames.SCIENCE_VESSEL_IMPROVED_NANO_REPAIR: + ItemData(347 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 28, SC2Race.TERRAN, + parent_item=ItemNames.SCIENCE_VESSEL, origin={"ext"}, + description="Nano-Repair no longer requires energy to use."), + ItemNames.SCIENCE_VESSEL_ADVANCED_AI_SYSTEMS: + ItemData(348 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 29, SC2Race.TERRAN, + parent_item=ItemNames.SCIENCE_VESSEL, origin={"ext"}, + description="Science Vessel can use Nano-Repair at two targets at once."), + ItemNames.CYCLONE_RESOURCE_EFFICIENCY: + ItemData(349 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 0, SC2Race.TERRAN, + parent_item=ItemNames.CYCLONE, origin={"ext"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Cyclone")), + ItemNames.BANSHEE_HYPERFLIGHT_ROTORS: + ItemData(350 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 1, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, origin={"ext"}, + description="Increases Banshee movement speed."), + ItemNames.BANSHEE_LASER_TARGETING_SYSTEM: + ItemData(351 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 2, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, origin={"nco"}, + description=LASER_TARGETING_SYSTEMS_DESCRIPTION), + ItemNames.BANSHEE_INTERNAL_TECH_MODULE: + ItemData(352 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 3, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.BANSHEE, origin={"nco"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Banshees", "Starport")), + ItemNames.BATTLECRUISER_TACTICAL_JUMP: + ItemData(353 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 4, SC2Race.TERRAN, + parent_item=ItemNames.BATTLECRUISER, origin={"nco", "ext"}, + description=inspect.cleandoc( + """ + Allows Battlecruisers to warp to a target location anywhere on the map. + """ + )), + ItemNames.BATTLECRUISER_CLOAK: + ItemData(354 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 5, SC2Race.TERRAN, + parent_item=ItemNames.BATTLECRUISER, origin={"nco"}, + description=CLOAK_DESCRIPTION_TEMPLATE.format("Battlecruisers")), + ItemNames.BATTLECRUISER_ATX_LASER_BATTERY: + ItemData(355 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 6, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.BATTLECRUISER, origin={"nco"}, + description=inspect.cleandoc( + """ + Battlecruisers can attack while moving, + do the same damage to both ground and air targets, and fire faster. + """ + )), + ItemNames.BATTLECRUISER_OPTIMIZED_LOGISTICS: + ItemData(356 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 7, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.BATTLECRUISER, origin={"ext"}, + description="Increases Battlecruiser training speed."), + ItemNames.BATTLECRUISER_INTERNAL_TECH_MODULE: + ItemData(357 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 8, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.BATTLECRUISER, origin={"nco"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Battlecruisers", "Starport")), + ItemNames.GHOST_EMP_ROUNDS: + ItemData(358 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 9, SC2Race.TERRAN, + parent_item=ItemNames.GHOST, origin={"ext"}, + description=inspect.cleandoc( + """ + Spell. Does 100 damage to shields and drains all energy from units in the targeted area. + Cloaked units hit by EMP are revealed for a short time. + """ + )), + ItemNames.GHOST_LOCKDOWN: + ItemData(359 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 10, SC2Race.TERRAN, + parent_item=ItemNames.GHOST, origin={"bw"}, + description="Spell. Stuns a target mechanical unit for a long time."), + ItemNames.SPECTRE_IMPALER_ROUNDS: + ItemData(360 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 11, SC2Race.TERRAN, + parent_item=ItemNames.SPECTRE, origin={"ext"}, + description="Spectres do additional damage to armored targets."), + ItemNames.THOR_PROGRESSIVE_HIGH_IMPACT_PAYLOAD: + ItemData(361 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 14, SC2Race.TERRAN, + parent_item=ItemNames.THOR, quantity=2, origin={"ext"}, + description=inspect.cleandoc( + f""" + Level 1: Allows Thors to transform in order to use an alternative air attack. + Level 2: {SMART_SERVOS_DESCRIPTION} + """ + )), + ItemNames.RAVEN_BIO_MECHANICAL_REPAIR_DRONE: + ItemData(363 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 12, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.RAVEN, origin={"nco"}, + description="Spell. Deploys a drone that can heal biological or mechanical units."), + ItemNames.RAVEN_SPIDER_MINES: + ItemData(364 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 13, SC2Race.TERRAN, + parent_item=ItemNames.RAVEN, origin={"nco"}, important_for_filtering=True, + description="Spell. Deploys 3 Spider Mines to a target location."), + ItemNames.RAVEN_RAILGUN_TURRET: + ItemData(365 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 14, SC2Race.TERRAN, + parent_item=ItemNames.RAVEN, origin={"nco"}, + description=inspect.cleandoc( + """ + Spell. Allows Ravens to deploy an advanced Auto-Turret, + that can attack enemy ground units in a straight line. + """ + )), + ItemNames.RAVEN_HUNTER_SEEKER_WEAPON: + ItemData(366 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 15, SC2Race.TERRAN, + classification=ItemClassification.progression, parent_item=ItemNames.RAVEN, origin={"nco"}, + description="Allows Ravens to attack with a Hunter-Seeker weapon."), + ItemNames.RAVEN_INTERFERENCE_MATRIX: + ItemData(367 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 16, SC2Race.TERRAN, + parent_item=ItemNames.RAVEN, origin={"ext"}, + description=inspect.cleandoc( + """ + Spell. Target enemy Mechanical or Psionic unit can't attack or use abilities for a short duration. + """ + )), + ItemNames.RAVEN_ANTI_ARMOR_MISSILE: + ItemData(368 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 17, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.RAVEN, origin={"ext"}, + description="Spell. Decreases target and nearby enemy units armor by 2."), + ItemNames.RAVEN_INTERNAL_TECH_MODULE: + ItemData(369 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 18, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.RAVEN, origin={"nco"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Ravens", "Starport")), + ItemNames.SCIENCE_VESSEL_EMP_SHOCKWAVE: + ItemData(370 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 19, SC2Race.TERRAN, + parent_item=ItemNames.SCIENCE_VESSEL, origin={"bw"}, + description="Spell. Depletes all energy and shields of all units in a target area."), + ItemNames.SCIENCE_VESSEL_DEFENSIVE_MATRIX: + ItemData(371 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 20, SC2Race.TERRAN, + parent_item=ItemNames.SCIENCE_VESSEL, origin={"bw"}, + description=inspect.cleandoc( + """ + Spell. Provides a target unit with a defensive barrier that can absorb up to 250 damage + """ + )), + ItemNames.CYCLONE_TARGETING_OPTICS: + ItemData(372 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 21, SC2Race.TERRAN, + parent_item=ItemNames.CYCLONE, origin={"ext"}, + description="Increases Cyclone Lock On casting range and the range while Locked On."), + ItemNames.CYCLONE_RAPID_FIRE_LAUNCHERS: + ItemData(373 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 22, SC2Race.TERRAN, + parent_item=ItemNames.CYCLONE, origin={"ext"}, + description="The first 12 shots of Lock On are fired more quickly."), + ItemNames.LIBERATOR_CLOAK: + ItemData(374 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 23, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"nco"}, + description=CLOAK_DESCRIPTION_TEMPLATE.format("Liberators")), + ItemNames.LIBERATOR_LASER_TARGETING_SYSTEM: + ItemData(375 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 24, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"ext"}, + description=LASER_TARGETING_SYSTEMS_DESCRIPTION), + ItemNames.LIBERATOR_OPTIMIZED_LOGISTICS: + ItemData(376 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 25, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"nco"}, + description="Increases Liberator training speed."), + ItemNames.WIDOW_MINE_BLACK_MARKET_LAUNCHERS: + ItemData(377 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 26, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.WIDOW_MINE, origin={"ext"}, + description="Increases Widow Mine Sentinel Missile range."), + ItemNames.WIDOW_MINE_EXECUTIONER_MISSILES: + ItemData(378 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 27, SC2Race.TERRAN, + parent_item=ItemNames.WIDOW_MINE, origin={"ext"}, + description=inspect.cleandoc( + """ + Reduces Sentinel Missile cooldown. + When killed, Widow Mines will launch several missiles at random enemy targets. + """ + )), + ItemNames.VALKYRIE_ENHANCED_CLUSTER_LAUNCHERS: + ItemData(379 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 28, + SC2Race.TERRAN, parent_item=ItemNames.VALKYRIE, origin={"ext"}, + description="Valkyries fire 2 additional rockets each volley."), + ItemNames.VALKYRIE_SHAPED_HULL: + ItemData(380 + SC2WOL_ITEM_ID_OFFSET, "Armory 5", 29, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.VALKYRIE, origin={"ext"}, + description="Increases Valkyrie life by 50."), + ItemNames.VALKYRIE_FLECHETTE_MISSILES: + ItemData(381 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 0, SC2Race.TERRAN, + parent_item=ItemNames.VALKYRIE, origin={"ext"}, + description="Equips Valkyries with Air-to-Surface missiles to attack ground units."), + ItemNames.VALKYRIE_AFTERBURNERS: + ItemData(382 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 1, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.VALKYRIE, origin={"ext"}, + description="Ability. Temporarily increases the Valkyries's movement speed by 70%."), + ItemNames.CYCLONE_INTERNAL_TECH_MODULE: + ItemData(383 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 2, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.CYCLONE, origin={"ext"}, + description=INTERNAL_TECH_MODULE_DESCRIPTION_TEMPLATE.format("Cyclones", "Factory")), + ItemNames.LIBERATOR_SMART_SERVOS: + ItemData(384 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 3, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"nco"}, + description=SMART_SERVOS_DESCRIPTION), + ItemNames.LIBERATOR_RESOURCE_EFFICIENCY: + ItemData(385 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 4, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.LIBERATOR, origin={"ext"}, + description=RESOURCE_EFFICIENCY_NO_SUPPLY_DESCRIPTION_TEMPLATE.format("Liberator")), + ItemNames.HERCULES_INTERNAL_FUSION_MODULE: + ItemData(386 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 5, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.HERCULES, origin={"ext"}, + description="Hercules can be trained from a Starport without having a Fusion Core."), + ItemNames.HERCULES_TACTICAL_JUMP: + ItemData(387 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 6, SC2Race.TERRAN, + parent_item=ItemNames.HERCULES, origin={"ext"}, + description=inspect.cleandoc( + """ + Allows Hercules to warp to a target location anywhere on the map. + """ + )), + ItemNames.PLANETARY_FORTRESS_PROGRESSIVE_AUGMENTED_THRUSTERS: + ItemData(388 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 28, SC2Race.TERRAN, + parent_item=ItemNames.PLANETARY_FORTRESS, origin={"ext"}, quantity=2, + description=inspect.cleandoc( + """ + Level 1: Lift Off - Planetary Fortress can lift off. + Level 2: Armament Stabilizers - Planetary Fortress can attack while lifted off. + """ + )), + ItemNames.PLANETARY_FORTRESS_ADVANCED_TARGETING: + ItemData(389 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 7, SC2Race.TERRAN, + parent_item=ItemNames.PLANETARY_FORTRESS, origin={"ext"}, + description="Planetary Fortress can attack air units."), + ItemNames.VALKYRIE_LAUNCHING_VECTOR_COMPENSATOR: + ItemData(390 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 8, SC2Race.TERRAN, + classification=ItemClassification.filler, parent_item=ItemNames.VALKYRIE, origin={"ext"}, + description="Allows Valkyries to shoot air while moving."), + ItemNames.VALKYRIE_RESOURCE_EFFICIENCY: + ItemData(391 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 9, SC2Race.TERRAN, + parent_item=ItemNames.VALKYRIE, origin={"ext"}, + description=RESOURCE_EFFICIENCY_DESCRIPTION_TEMPLATE.format("Valkyrie")), + ItemNames.PREDATOR_PREDATOR_S_FURY: + ItemData(392 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 10, SC2Race.TERRAN, + parent_item=ItemNames.PREDATOR, origin={"ext"}, + description="Predators can use an attack that jumps between targets."), + ItemNames.BATTLECRUISER_BEHEMOTH_PLATING: + ItemData(393 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 11, SC2Race.TERRAN, + parent_item=ItemNames.BATTLECRUISER, origin={"ext"}, + description="Increases Battlecruiser armor by 2."), + ItemNames.BATTLECRUISER_COVERT_OPS_ENGINES: + ItemData(394 + SC2WOL_ITEM_ID_OFFSET, "Armory 6", 12, SC2Race.TERRAN, + parent_item=ItemNames.BATTLECRUISER, origin={"nco"}, + description="Increases Battlecruiser movement speed."), + + #Buildings + ItemNames.BUNKER: + ItemData(400 + SC2WOL_ITEM_ID_OFFSET, "Building", 0, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Defensive structure. Able to load infantry units, giving them +1 range to their attacks."), + ItemNames.MISSILE_TURRET: + ItemData(401 + SC2WOL_ITEM_ID_OFFSET, "Building", 1, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Anti-air defensive structure."), + ItemNames.SENSOR_TOWER: + ItemData(402 + SC2WOL_ITEM_ID_OFFSET, "Building", 2, SC2Race.TERRAN, + description="Reveals locations of enemy units at long range."), + + ItemNames.WAR_PIGS: + ItemData(500 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 0, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Mercenary Marines"), + ItemNames.DEVIL_DOGS: + ItemData(501 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 1, SC2Race.TERRAN, + classification=ItemClassification.filler, + description="Mercenary Firebats"), + ItemNames.HAMMER_SECURITIES: + ItemData(502 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 2, SC2Race.TERRAN, + description="Mercenary Marauders"), + ItemNames.SPARTAN_COMPANY: + ItemData(503 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 3, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Mercenary Goliaths"), + ItemNames.SIEGE_BREAKERS: + ItemData(504 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 4, SC2Race.TERRAN, + description="Mercenary Siege Tanks"), + ItemNames.HELS_ANGELS: + ItemData(505 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 5, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Mercenary Vikings"), + ItemNames.DUSK_WINGS: + ItemData(506 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 6, SC2Race.TERRAN, + description="Mercenary Banshees"), + ItemNames.JACKSONS_REVENGE: + ItemData(507 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 7, SC2Race.TERRAN, + description="Mercenary Battlecruiser"), + ItemNames.SKIBIS_ANGELS: + ItemData(508 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 8, SC2Race.TERRAN, + origin={"ext"}, + description="Mercenary Medics"), + ItemNames.DEATH_HEADS: + ItemData(509 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 9, SC2Race.TERRAN, + origin={"ext"}, + description="Mercenary Reapers"), + ItemNames.WINGED_NIGHTMARES: + ItemData(510 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 10, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"ext"}, + description="Mercenary Wraiths"), + ItemNames.MIDNIGHT_RIDERS: + ItemData(511 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 11, SC2Race.TERRAN, + origin={"ext"}, + description="Mercenary Liberators"), + ItemNames.BRYNHILDS: + ItemData(512 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 12, SC2Race.TERRAN, + classification=ItemClassification.progression, origin={"ext"}, + description="Mercenary Valkyries"), + ItemNames.JOTUN: + ItemData(513 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 13, SC2Race.TERRAN, + origin={"ext"}, + description="Mercenary Thor"), + + ItemNames.ULTRA_CAPACITORS: + ItemData(600 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 0, SC2Race.TERRAN, + description="Increases attack speed of units by 5% per weapon upgrade."), + ItemNames.VANADIUM_PLATING: + ItemData(601 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 1, SC2Race.TERRAN, + description="Increases the life of units by 5% per armor upgrade."), + ItemNames.ORBITAL_DEPOTS: + ItemData(602 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 2, SC2Race.TERRAN, + description="Supply depots are built instantly."), + ItemNames.MICRO_FILTERING: + ItemData(603 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 3, SC2Race.TERRAN, + description="Refineries produce Vespene gas 25% faster."), + ItemNames.AUTOMATED_REFINERY: + ItemData(604 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 4, SC2Race.TERRAN, + description="Eliminates the need for SCVs in vespene gas production."), + ItemNames.COMMAND_CENTER_REACTOR: + ItemData(605 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 5, SC2Race.TERRAN, + description="Command Centers can train two SCVs at once."), + ItemNames.RAVEN: + ItemData(606 + SC2WOL_ITEM_ID_OFFSET, "Unit", 22, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Aerial Caster unit."), + ItemNames.SCIENCE_VESSEL: + ItemData(607 + SC2WOL_ITEM_ID_OFFSET, "Unit", 23, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Aerial Caster unit. Can repair mechanical units."), + ItemNames.TECH_REACTOR: + ItemData(608 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 6, SC2Race.TERRAN, + description="Merges Tech Labs and Reactors into one add on structure to provide both functions."), + ItemNames.ORBITAL_STRIKE: + ItemData(609 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 7, SC2Race.TERRAN, + description="Trained units from Barracks are instantly deployed on rally point."), + ItemNames.BUNKER_SHRIKE_TURRET: + ItemData(610 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 6, SC2Race.TERRAN, + parent_item=ItemNames.BUNKER, + description="Adds an automated turret to Bunkers."), + ItemNames.BUNKER_FORTIFIED_BUNKER: + ItemData(611 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 7, SC2Race.TERRAN, + parent_item=ItemNames.BUNKER, + description="Bunkers have more life."), + ItemNames.PLANETARY_FORTRESS: + ItemData(612 + SC2WOL_ITEM_ID_OFFSET, "Building", 3, SC2Race.TERRAN, + classification=ItemClassification.progression, + description=inspect.cleandoc( + """ + Allows Command Centers to upgrade into a defensive structure with a turret and additional armor. + Planetary Fortresses cannot Lift Off, or cast Orbital Command spells. + """ + )), + ItemNames.PERDITION_TURRET: + ItemData(613 + SC2WOL_ITEM_ID_OFFSET, "Building", 4, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Automated defensive turret. Burrows down while no enemies are nearby."), + ItemNames.PREDATOR: + ItemData(614 + SC2WOL_ITEM_ID_OFFSET, "Unit", 24, SC2Race.TERRAN, + classification=ItemClassification.filler, + description="Anti-infantry specialist that deals area damage with each attack."), + ItemNames.HERCULES: + ItemData(615 + SC2WOL_ITEM_ID_OFFSET, "Unit", 25, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Massive transport ship."), + ItemNames.CELLULAR_REACTOR: + ItemData(616 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 8, SC2Race.TERRAN, + description="All Terran spellcasters get +100 starting and maximum energy."), + ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL: + ItemData(617 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 4, SC2Race.TERRAN, quantity=3, + classification= ItemClassification.progression, + description=inspect.cleandoc( + """ + Allows Terran mechanical units to regenerate health while not in combat. + Each level increases life regeneration speed. + """ + )), + ItemNames.HIVE_MIND_EMULATOR: + ItemData(618 + SC2WOL_ITEM_ID_OFFSET, "Building", 5, SC2Race.TERRAN, + ItemClassification.progression, + description="Defensive structure. Can permanently Mind Control Zerg units."), + ItemNames.PSI_DISRUPTER: + ItemData(619 + SC2WOL_ITEM_ID_OFFSET, "Building", 6, SC2Race.TERRAN, + classification=ItemClassification.progression, + description="Defensive structure. Slows the attack and movement speeds of all nearby Zerg units."), + ItemNames.STRUCTURE_ARMOR: + ItemData(620 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 9, SC2Race.TERRAN, + description="Increases armor of all Terran structures by 2."), + ItemNames.HI_SEC_AUTO_TRACKING: + ItemData(621 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 10, SC2Race.TERRAN, + description="Increases attack range of all Terran structures by 1."), + ItemNames.ADVANCED_OPTICS: + ItemData(622 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 11, SC2Race.TERRAN, + description="Increases attack range of all Terran mechanical units by 1."), + ItemNames.ROGUE_FORCES: + ItemData(623 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 12, SC2Race.TERRAN, + description="Mercenary calldowns are no longer limited by charges."), + + ItemNames.ZEALOT: + ItemData(700 + SC2WOL_ITEM_ID_OFFSET, "Unit", 0, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Powerful melee warrior. Can use the charge ability."), + ItemNames.STALKER: + ItemData(701 + SC2WOL_ITEM_ID_OFFSET, "Unit", 1, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Ranged attack strider. Can use the Blink ability."), + ItemNames.HIGH_TEMPLAR: + ItemData(702 + SC2WOL_ITEM_ID_OFFSET, "Unit", 2, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Potent psionic master. Can use the Feedback and Psionic Storm abilities. Can merge into an Archon."), + ItemNames.DARK_TEMPLAR: + ItemData(703 + SC2WOL_ITEM_ID_OFFSET, "Unit", 3, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Deadly warrior-assassin. Permanently cloaked. Can use the Shadow Fury ability."), + ItemNames.IMMORTAL: + ItemData(704 + SC2WOL_ITEM_ID_OFFSET, "Unit", 4, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Assault strider. Can use Barrier to absorb damage."), + ItemNames.COLOSSUS: + ItemData(705 + SC2WOL_ITEM_ID_OFFSET, "Unit", 5, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Battle strider with a powerful area attack. Can walk up and down cliffs. Attacks set fire to the ground, dealing extra damage to enemies over time."), + ItemNames.PHOENIX: + ItemData(706 + SC2WOL_ITEM_ID_OFFSET, "Unit", 6, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Air superiority starfighter. Can use Graviton Beam and Phasing Armor abilities."), + ItemNames.VOID_RAY: + ItemData(707 + SC2WOL_ITEM_ID_OFFSET, "Unit", 7, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Surgical strike craft. Has the Prismatic Alignment and Prismatic Range abilities."), + ItemNames.CARRIER: + ItemData(708 + SC2WOL_ITEM_ID_OFFSET, "Unit", 8, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"wol", "lotv"}, + description="Capital ship. Builds and launches Interceptors that attack enemy targets. Repair Drones heal nearby mechanical units."), + + # Filler items to fill remaining spots + ItemNames.STARTING_MINERALS: + ItemData(800 + SC2WOL_ITEM_ID_OFFSET, "Minerals", 15, SC2Race.ANY, quantity=0, + classification=ItemClassification.filler, + description="Increases the starting minerals for all missions."), + ItemNames.STARTING_VESPENE: + ItemData(801 + SC2WOL_ITEM_ID_OFFSET, "Vespene", 15, SC2Race.ANY, quantity=0, + classification=ItemClassification.filler, + description="Increases the starting vespene for all missions."), + ItemNames.STARTING_SUPPLY: + ItemData(802 + SC2WOL_ITEM_ID_OFFSET, "Supply", 2, SC2Race.ANY, quantity=0, + classification=ItemClassification.filler, + description="Increases the starting supply for all missions."), + # This item is used to "remove" location from the game. Never placed unless plando'd + ItemNames.NOTHING: + ItemData(803 + SC2WOL_ITEM_ID_OFFSET, "Nothing Group", 2, SC2Race.ANY, quantity=0, + classification=ItemClassification.trap, + description="Does nothing. Used to remove a location from the game."), + + # Nova gear + ItemNames.NOVA_GHOST_VISOR: + ItemData(900 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 0, SC2Race.TERRAN, origin={"nco"}, + description="Reveals the locations of enemy units in the fog of war around Nova. Can detect cloaked units."), + ItemNames.NOVA_RANGEFINDER_OCULUS: + ItemData(901 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 1, SC2Race.TERRAN, origin={"nco"}, + description="Increaases Nova's vision range and non-melee weapon attack range by 2. Also increases range of melee weapons by 1."), + ItemNames.NOVA_DOMINATION: + ItemData(902 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 2, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to mind-control a target enemy unit."), + ItemNames.NOVA_BLINK: + ItemData(903 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 3, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to teleport a short distance and cloak for 10s."), + ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE: + ItemData(904 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade 2", 0, SC2Race.TERRAN, quantity=2, origin={"nco"}, + classification=ItemClassification.progression, + description=inspect.cleandoc( + """ + Level 1: Gives Nova the ability to cloak. + Level 2: Nova is permanently cloaked. + """ + )), + ItemNames.NOVA_ENERGY_SUIT_MODULE: + ItemData(905 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 4, SC2Race.TERRAN, origin={"nco"}, + description="Increases Nova's maximum energy and energy regeneration rate."), + ItemNames.NOVA_ARMORED_SUIT_MODULE: + ItemData(906 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 5, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Increases Nova's health by 100 and armour by 1. Nova also regenerates life quickly out of combat."), + ItemNames.NOVA_JUMP_SUIT_MODULE: + ItemData(907 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 6, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Increases Nova's movement speed and allows her to jump up and down cliffs."), + ItemNames.NOVA_C20A_CANISTER_RIFLE: + ItemData(908 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 7, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Allows Nova to equip the C20A Canister Rifle, which has a ranged attack and allows Nova to cast Snipe."), + ItemNames.NOVA_HELLFIRE_SHOTGUN: + ItemData(909 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 8, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Allows Nova to equip the Hellfire Shotgun, which has a short-range area attack in a cone and allows Nova to cast Penetrating Blast."), + ItemNames.NOVA_PLASMA_RIFLE: + ItemData(910 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 9, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Allows Nova to equip the Plasma Rifle, which has a rapidfire ranged attack and allows Nova to cast Plasma Shot."), + ItemNames.NOVA_MONOMOLECULAR_BLADE: + ItemData(911 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 10, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Allows Nova to equip the Monomolecular Blade, which has a melee attack and allows Nova to cast Dash Attack."), + ItemNames.NOVA_BLAZEFIRE_GUNBLADE: + ItemData(912 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 11, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Allows Nova to equip the Blazefire Gunblade, which has a melee attack and allows Nova to cast Fury of One."), + ItemNames.NOVA_STIM_INFUSION: + ItemData(913 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 12, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to heal herself and temporarily increase her movement and attack speeds."), + ItemNames.NOVA_PULSE_GRENADES: + ItemData(914 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 13, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to throw a grenade dealing large damage in an area."), + ItemNames.NOVA_FLASHBANG_GRENADES: + ItemData(915 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 14, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to throw a grenade to stun enemies and disable detection in a large area."), + ItemNames.NOVA_IONIC_FORCE_FIELD: + ItemData(916 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 15, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to shield herself temporarily."), + ItemNames.NOVA_HOLO_DECOY: + ItemData(917 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 16, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to summon a decoy unit which enemies will prefer to target and takes reduced damage."), + ItemNames.NOVA_NUKE: + ItemData(918 + SC2WOL_ITEM_ID_OFFSET, "Nova Gear", 17, SC2Race.TERRAN, origin={"nco"}, + classification=ItemClassification.progression, + description="Gives Nova the ability to launch tactical nukes built from the Shadow Ops."), + + # HotS + ItemNames.ZERGLING: + ItemData(0 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 0, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Fast inexpensive melee attacker. Hatches in pairs from a single larva. Can morph into a Baneling."), + ItemNames.SWARM_QUEEN: + ItemData(1 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 1, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Ranged support caster. Can use the Spawn Creep Tumor and Rapid Transfusion abilities."), + ItemNames.ROACH: + ItemData(2 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 2, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Durable short ranged attacker. Regenerates life quickly when burrowed."), + ItemNames.HYDRALISK: + ItemData(3 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 3, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="High-damage generalist ranged attacker."), + ItemNames.ZERGLING_BANELING_ASPECT: + ItemData(4 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 5, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Anti-ground suicide unit. Does damage over a small area on death."), + ItemNames.ABERRATION: + ItemData(5 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 5, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Durable melee attacker that deals heavy damage and can walk over other units."), + ItemNames.MUTALISK: + ItemData(6 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 6, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Fragile flying attacker. Attacks bounce between targets."), + ItemNames.SWARM_HOST: + ItemData(7 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 7, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Siege unit that attacks by rooting in place and continually spawning Locusts."), + ItemNames.INFESTOR: + ItemData(8 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 8, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Support caster that can move while burrowed. Can use the Fungal Growth, Parasitic Domination, and Consumption abilities."), + ItemNames.ULTRALISK: + ItemData(9 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 9, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Massive melee attacker. Has an area-damage cleave attack."), + ItemNames.SPORE_CRAWLER: + ItemData(10 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 10, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Anti-air defensive structure that can detect cloaked units."), + ItemNames.SPINE_CRAWLER: + ItemData(11 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 11, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"hots"}, + description="Anti-ground defensive structure."), + ItemNames.CORRUPTOR: + ItemData(12 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 12, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"ext"}, + description="Anti-air flying attacker specializing in taking down enemy capital ships."), + ItemNames.SCOURGE: + ItemData(13 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 13, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"bw", "ext"}, + description="Flying anti-air suicide unit. Hatches in pairs from a single larva."), + ItemNames.BROOD_QUEEN: + ItemData(14 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 4, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"bw", "ext"}, + description="Flying support caster. Can cast the Ocular Symbiote and Spawn Broodlings abilities."), + ItemNames.DEFILER: + ItemData(15 + SC2HOTS_ITEM_ID_OFFSET, "Unit", 14, SC2Race.ZERG, + classification=ItemClassification.progression, origin={"bw"}, + description="Support caster. Can use the Dark Swarm, Consume, and Plague abilities."), + + ItemNames.PROGRESSIVE_ZERG_MELEE_ATTACK: ItemData(100 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 0, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_MISSILE_ATTACK: ItemData(101 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 2, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_GROUND_CARAPACE: ItemData(102 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 4, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_FLYER_ATTACK: ItemData(103 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 6, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_FLYER_CARAPACE: ItemData(104 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 8, SC2Race.ZERG, quantity=3, origin={"hots"}), + # Upgrade bundle 'number' values are used as indices to get affected 'number's + ItemNames.PROGRESSIVE_ZERG_WEAPON_UPGRADE: ItemData(105 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 6, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_ARMOR_UPGRADE: ItemData(106 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 7, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_GROUND_UPGRADE: ItemData(107 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 8, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_FLYER_UPGRADE: ItemData(108 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 9, SC2Race.ZERG, quantity=3, origin={"hots"}), + ItemNames.PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2HOTS_ITEM_ID_OFFSET, "Upgrade", 10, SC2Race.ZERG, quantity=3, origin={"hots"}), + + ItemNames.ZERGLING_HARDENED_CARAPACE: + ItemData(200 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 0, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + origin={"hots"}, description="Increases Zergling health by +10."), + ItemNames.ZERGLING_ADRENAL_OVERLOAD: + ItemData(201 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 1, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + origin={"hots"}, description="Increases Zergling attack speed."), + ItemNames.ZERGLING_METABOLIC_BOOST: + ItemData(202 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 2, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + origin={"hots"}, classification=ItemClassification.filler, + description="Increases Zergling movement speed."), + ItemNames.ROACH_HYDRIODIC_BILE: + ItemData(203 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 3, SC2Race.ZERG, parent_item=ItemNames.ROACH, + origin={"hots"}, description="Roaches deal +8 damage to light targets."), + ItemNames.ROACH_ADAPTIVE_PLATING: + ItemData(204 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 4, SC2Race.ZERG, parent_item=ItemNames.ROACH, + origin={"hots"}, description="Roaches gain +3 armour when their life is below 50%."), + ItemNames.ROACH_TUNNELING_CLAWS: + ItemData(205 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 5, SC2Race.ZERG, parent_item=ItemNames.ROACH, + origin={"hots"}, classification=ItemClassification.filler, + description="Allows Roaches to move while burrowed."), + ItemNames.HYDRALISK_FRENZY: + ItemData(206 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 6, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + origin={"hots"}, + description="Allows Hydralisks to use the Frenzy ability, which increases their attack speed by 50%."), + ItemNames.HYDRALISK_ANCILLARY_CARAPACE: + ItemData(207 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 7, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + origin={"hots"}, classification=ItemClassification.filler, description="Hydralisks gain +20 health."), + ItemNames.HYDRALISK_GROOVED_SPINES: + ItemData(208 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 8, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + origin={"hots"}, description="Hydralisks gain +1 range."), + ItemNames.BANELING_CORROSIVE_ACID: + ItemData(209 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 9, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}, + description="Increases the damage banelings deal to their primary target. Splash damage remains the same."), + ItemNames.BANELING_RUPTURE: + ItemData(210 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 10, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}, + classification=ItemClassification.filler, + description="Increases the splash radius of baneling attacks."), + ItemNames.BANELING_REGENERATIVE_ACID: + ItemData(211 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 11, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}, + classification=ItemClassification.filler, + description="Banelings will heal nearby friendly units when they explode."), + ItemNames.MUTALISK_VICIOUS_GLAIVE: + ItemData(212 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 12, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + origin={"hots"}, description="Mutalisks attacks will bounce an additional 3 times."), + ItemNames.MUTALISK_RAPID_REGENERATION: + ItemData(213 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 13, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + origin={"hots"}, description="Mutalisks will regenerate quickly when out of combat."), + ItemNames.MUTALISK_SUNDERING_GLAIVE: + ItemData(214 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 14, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + origin={"hots"}, description="Mutalisks deal increased damage to their primary target."), + ItemNames.SWARM_HOST_BURROW: + ItemData(215 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 15, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"hots"}, classification=ItemClassification.filler, + description="Allows Swarm Hosts to burrow instead of root to spawn locusts."), + ItemNames.SWARM_HOST_RAPID_INCUBATION: + ItemData(216 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 16, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"hots"}, description="Swarm Hosts will spawn locusts 20% faster."), + ItemNames.SWARM_HOST_PRESSURIZED_GLANDS: + ItemData(217 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 17, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"hots"}, classification=ItemClassification.progression, + description="Allows Swarm Host Locusts to attack air targets."), + ItemNames.ULTRALISK_BURROW_CHARGE: + ItemData(218 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 18, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"hots"}, + description="Allows Ultralisks to burrow and charge at enemy units, knocking back and stunning units when it emerges."), + ItemNames.ULTRALISK_TISSUE_ASSIMILATION: + ItemData(219 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 19, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"hots"}, description="Ultralisks recover health when they deal damage."), + ItemNames.ULTRALISK_MONARCH_BLADES: + ItemData(220 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 20, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"hots"}, description="Ultralisks gain increased splash damage."), + ItemNames.CORRUPTOR_CAUSTIC_SPRAY: + ItemData(221 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 21, SC2Race.ZERG, parent_item=ItemNames.CORRUPTOR, + origin={"ext"}, + description="Allows Corruptors to use the Caustic Spray ability, which deals ramping damage to buildings over time."), + ItemNames.CORRUPTOR_CORRUPTION: + ItemData(222 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 22, SC2Race.ZERG, parent_item=ItemNames.CORRUPTOR, + origin={"ext"}, + description="Allows Corruptors to use the Corruption ability, which causes a target enemy unit to take increased damage."), + ItemNames.SCOURGE_VIRULENT_SPORES: + ItemData(223 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 23, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, + origin={"ext"}, description="Scourge will deal splash damage."), + ItemNames.SCOURGE_RESOURCE_EFFICIENCY: + ItemData(224 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 24, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, + origin={"ext"}, classification=ItemClassification.progression, + description="Reduces the cost of Scourge by 50 gas per egg."), + ItemNames.SCOURGE_SWARM_SCOURGE: + ItemData(225 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 25, SC2Race.ZERG, parent_item=ItemNames.SCOURGE, + origin={"ext"}, description="An extra Scourge will be built from each egg at no additional cost."), + ItemNames.ZERGLING_SHREDDING_CLAWS: + ItemData(226 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 26, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + origin={"ext"}, description="Zergling attacks will temporarily reduce their target's armour to 0."), + ItemNames.ROACH_GLIAL_RECONSTITUTION: + ItemData(227 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 27, SC2Race.ZERG, parent_item=ItemNames.ROACH, + origin={"ext"}, description="Increases Roach movement speed."), + ItemNames.ROACH_ORGANIC_CARAPACE: + ItemData(228 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 28, SC2Race.ZERG, parent_item=ItemNames.ROACH, + origin={"ext"}, description="Increases Roach health by +25."), + ItemNames.HYDRALISK_MUSCULAR_AUGMENTS: + ItemData(229 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 1", 29, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + origin={"bw"}, description="Increases Hydralisk movement speed."), + ItemNames.HYDRALISK_RESOURCE_EFFICIENCY: + ItemData(230 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 0, SC2Race.ZERG, parent_item=ItemNames.HYDRALISK, + origin={"bw"}, description="Reduces Hydralisk resource cost by 25/25 and supply cost by 1."), + ItemNames.BANELING_CENTRIFUGAL_HOOKS: + ItemData(231 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 1, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"ext"}, + description="Increases the movement speed of Banelings."), + ItemNames.BANELING_TUNNELING_JAWS: + ItemData(232 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 2, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"ext"}, + description="Allows Banelings to move while burrowed."), + ItemNames.BANELING_RAPID_METAMORPH: + ItemData(233 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 3, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"ext"}, description="Banelings morph faster."), + ItemNames.MUTALISK_SEVERING_GLAIVE: + ItemData(234 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 4, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + origin={"ext"}, description="Mutalisk bounce attacks will deal full damage."), + ItemNames.MUTALISK_AERODYNAMIC_GLAIVE_SHAPE: + ItemData(235 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 5, SC2Race.ZERG, parent_item=ItemNames.MUTALISK, + origin={"ext"}, description="Increases the attack range of Mutalisks by 2."), + ItemNames.SWARM_HOST_LOCUST_METABOLIC_BOOST: + ItemData(236 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 6, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"ext"}, classification=ItemClassification.filler, + description="Increases Locust movement speed."), + ItemNames.SWARM_HOST_ENDURING_LOCUSTS: + ItemData(237 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 7, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"ext"}, description="Increases the duration of Swarm Hosts' Locusts by 10s."), + ItemNames.SWARM_HOST_ORGANIC_CARAPACE: + ItemData(238 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 8, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"ext"}, description="Increases Swarm Host health by +40."), + ItemNames.SWARM_HOST_RESOURCE_EFFICIENCY: + ItemData(239 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 9, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"ext"}, description="Reduces Swarm Host resource cost by 100/25."), + ItemNames.ULTRALISK_ANABOLIC_SYNTHESIS: + ItemData(240 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 10, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"bw"}, classification=ItemClassification.filler), + ItemNames.ULTRALISK_CHITINOUS_PLATING: + ItemData(241 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 11, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"bw"}), + ItemNames.ULTRALISK_ORGANIC_CARAPACE: + ItemData(242 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 12, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"ext"}), + ItemNames.ULTRALISK_RESOURCE_EFFICIENCY: + ItemData(243 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 13, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"bw"}), + ItemNames.DEVOURER_CORROSIVE_SPRAY: + ItemData(244 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 14, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}), + ItemNames.DEVOURER_GAPING_MAW: + ItemData(245 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 15, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}), + ItemNames.DEVOURER_IMPROVED_OSMOSIS: + ItemData(246 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 16, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}, + classification=ItemClassification.filler), + ItemNames.DEVOURER_PRESCIENT_SPORES: + ItemData(247 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 17, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT, origin={"ext"}), + ItemNames.GUARDIAN_PROLONGED_DISPERSION: + ItemData(248 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 18, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT, origin={"ext"}), + ItemNames.GUARDIAN_PRIMAL_ADAPTATION: + ItemData(249 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 19, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT, origin={"ext"}), + ItemNames.GUARDIAN_SORONAN_ACID: + ItemData(250 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 20, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT, origin={"ext"}), + ItemNames.IMPALER_ADAPTIVE_TALONS: + ItemData(251 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 21, SC2Race.ZERG, + parent_item=ItemNames.HYDRALISK_IMPALER_ASPECT, origin={"ext"}, + classification=ItemClassification.filler), + ItemNames.IMPALER_SECRETION_GLANDS: + ItemData(252 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 22, SC2Race.ZERG, + parent_item=ItemNames.HYDRALISK_IMPALER_ASPECT, origin={"ext"}), + ItemNames.IMPALER_HARDENED_TENTACLE_SPINES: + ItemData(253 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 23, SC2Race.ZERG, + parent_item=ItemNames.HYDRALISK_IMPALER_ASPECT, origin={"ext"}), + ItemNames.LURKER_SEISMIC_SPINES: + ItemData(254 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 24, SC2Race.ZERG, + parent_item=ItemNames.HYDRALISK_LURKER_ASPECT, origin={"ext"}), + ItemNames.LURKER_ADAPTED_SPINES: + ItemData(255 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 25, SC2Race.ZERG, + parent_item=ItemNames.HYDRALISK_LURKER_ASPECT, origin={"ext"}), + ItemNames.RAVAGER_POTENT_BILE: + ItemData(256 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 26, SC2Race.ZERG, + parent_item=ItemNames.ROACH_RAVAGER_ASPECT, origin={"ext"}), + ItemNames.RAVAGER_BLOATED_BILE_DUCTS: + ItemData(257 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 27, SC2Race.ZERG, + parent_item=ItemNames.ROACH_RAVAGER_ASPECT, origin={"ext"}), + ItemNames.RAVAGER_DEEP_TUNNEL: + ItemData(258 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 28, SC2Race.ZERG, + parent_item=ItemNames.ROACH_RAVAGER_ASPECT, origin={"ext"}), + ItemNames.VIPER_PARASITIC_BOMB: + ItemData(259 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 2", 29, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, origin={"ext"}), + ItemNames.VIPER_PARALYTIC_BARBS: + ItemData(260 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 0, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, origin={"ext"}), + ItemNames.VIPER_VIRULENT_MICROBES: + ItemData(261 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 1, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, origin={"ext"}), + ItemNames.BROOD_LORD_POROUS_CARTILAGE: + ItemData(262 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 2, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), + ItemNames.BROOD_LORD_EVOLVED_CARAPACE: + ItemData(263 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 3, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), + ItemNames.BROOD_LORD_SPLITTER_MITOSIS: + ItemData(264 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 4, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), + ItemNames.BROOD_LORD_RESOURCE_EFFICIENCY: + ItemData(265 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 5, SC2Race.ZERG, + parent_item=ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, origin={"ext"}), + ItemNames.INFESTOR_INFESTED_TERRAN: + ItemData(266 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 6, SC2Race.ZERG, parent_item=ItemNames.INFESTOR, + origin={"ext"}), + ItemNames.INFESTOR_MICROBIAL_SHROUD: + ItemData(267 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 7, SC2Race.ZERG, parent_item=ItemNames.INFESTOR, + origin={"ext"}), + ItemNames.SWARM_QUEEN_SPAWN_LARVAE: + ItemData(268 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 8, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + origin={"ext"}), + ItemNames.SWARM_QUEEN_DEEP_TUNNEL: + ItemData(269 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 9, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + origin={"ext"}), + ItemNames.SWARM_QUEEN_ORGANIC_CARAPACE: + ItemData(270 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 10, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + origin={"ext"}, classification=ItemClassification.filler), + ItemNames.SWARM_QUEEN_BIO_MECHANICAL_TRANSFUSION: + ItemData(271 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 11, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + origin={"ext"}), + ItemNames.SWARM_QUEEN_RESOURCE_EFFICIENCY: + ItemData(272 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 12, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + origin={"ext"}), + ItemNames.SWARM_QUEEN_INCUBATOR_CHAMBER: + ItemData(273 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 13, SC2Race.ZERG, parent_item=ItemNames.SWARM_QUEEN, + origin={"ext"}), + ItemNames.BROOD_QUEEN_FUNGAL_GROWTH: + ItemData(274 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 14, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, + origin={"ext"}), + ItemNames.BROOD_QUEEN_ENSNARE: + ItemData(275 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 15, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, + origin={"ext"}), + ItemNames.BROOD_QUEEN_ENHANCED_MITOCHONDRIA: + ItemData(276 + SC2HOTS_ITEM_ID_OFFSET, "Mutation 3", 16, SC2Race.ZERG, parent_item=ItemNames.BROOD_QUEEN, + origin={"ext"}), + + ItemNames.ZERGLING_RAPTOR_STRAIN: + ItemData(300 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 0, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + origin={"hots"}, + description="Allows Zerglings to jump up and down cliffs and leap onto enemies. Also increases Zergling attack damage by 2."), + ItemNames.ZERGLING_SWARMLING_STRAIN: + ItemData(301 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 1, SC2Race.ZERG, parent_item=ItemNames.ZERGLING, + origin={"hots"}, + description="Zerglings will spawn instantly and with an extra Zergling per egg at no additional cost."), + ItemNames.ROACH_VILE_STRAIN: + ItemData(302 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 2, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}, + description="Roach attacks will slow the movement and attack speed of enemies."), + ItemNames.ROACH_CORPSER_STRAIN: + ItemData(303 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 3, SC2Race.ZERG, parent_item=ItemNames.ROACH, origin={"hots"}, + description="Units killed after being attacked by Roaches will spawn 2 Roachlings."), + ItemNames.HYDRALISK_IMPALER_ASPECT: + ItemData(304 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 0, SC2Race.ZERG, origin={"hots"}, + classification=ItemClassification.progression, + description="Allows Hydralisks to morph into Impalers."), + ItemNames.HYDRALISK_LURKER_ASPECT: + ItemData(305 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 1, SC2Race.ZERG, origin={"hots"}, + classification=ItemClassification.progression, description="Allows Hydralisks to morph into Lurkers."), + ItemNames.BANELING_SPLITTER_STRAIN: + ItemData(306 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 6, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}, + description="Banelings will split into two smaller Splitterlings on exploding."), + ItemNames.BANELING_HUNTER_STRAIN: + ItemData(307 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 7, SC2Race.ZERG, + parent_item=ItemNames.ZERGLING_BANELING_ASPECT, origin={"hots"}, + description="Allows Banelings to jump up and down cliffs and leap onto enemies."), + ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT: + ItemData(308 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 2, SC2Race.ZERG, origin={"hots"}, + classification=ItemClassification.progression, + description="Allows Mutalisks and Corruptors to morph into Brood Lords."), + ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT: + ItemData(309 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 3, SC2Race.ZERG, origin={"hots"}, + classification=ItemClassification.progression, + description="Allows Mutalisks and Corruptors to morph into Vipers."), + ItemNames.SWARM_HOST_CARRION_STRAIN: + ItemData(310 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 10, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"hots"}, description="Swarm Hosts will spawn Flying Locusts."), + ItemNames.SWARM_HOST_CREEPER_STRAIN: + ItemData(311 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 11, SC2Race.ZERG, parent_item=ItemNames.SWARM_HOST, + origin={"hots"}, classification=ItemClassification.filler, + description="Allows Swarm Hosts to teleport to any creep on the map in vision. Swarm Hosts will spread creep around them when rooted or burrowed."), + ItemNames.ULTRALISK_NOXIOUS_STRAIN: + ItemData(312 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 12, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"hots"}, classification=ItemClassification.filler, + description="Ultralisks will periodically spread poison, damaging nearby biological enemies."), + ItemNames.ULTRALISK_TORRASQUE_STRAIN: + ItemData(313 + SC2HOTS_ITEM_ID_OFFSET, "Strain", 13, SC2Race.ZERG, parent_item=ItemNames.ULTRALISK, + origin={"hots"}, description="Ultralisks will revive after being killed."), + + ItemNames.KERRIGAN_KINETIC_BLAST: ItemData(400 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 0, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_HEROIC_FORTITUDE: ItemData(401 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 1, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEAPING_STRIKE: ItemData(402 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 2, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_CRUSHING_GRIP: ItemData(403 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 3, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_CHAIN_REACTION: ItemData(404 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 4, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_PSIONIC_SHIFT: ItemData(405 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 5, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_ZERGLING_RECONSTITUTION: ItemData(406 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 0, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.filler), + ItemNames.KERRIGAN_IMPROVED_OVERLORDS: ItemData(407 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 1, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_AUTOMATED_EXTRACTORS: ItemData(408 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 2, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_WILD_MUTATION: ItemData(409 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 6, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_SPAWN_BANELINGS: ItemData(410 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 7, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_MEND: ItemData(411 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 8, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_TWIN_DRONES: ItemData(412 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 3, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_MALIGNANT_CREEP: ItemData(413 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 4, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_VESPENE_EFFICIENCY: ItemData(414 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 5, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_INFEST_BROODLINGS: ItemData(415 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 9, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_FURY: ItemData(416 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 10, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_ABILITY_EFFICIENCY: ItemData(417 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 11, SC2Race.ZERG, origin={"hots"}), + ItemNames.KERRIGAN_APOCALYPSE: ItemData(418 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 12, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_SPAWN_LEVIATHAN: ItemData(419 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 13, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + ItemNames.KERRIGAN_DROP_PODS: ItemData(420 + SC2HOTS_ITEM_ID_OFFSET, "Ability", 14, SC2Race.ZERG, origin={"hots"}, classification=ItemClassification.progression), + # Handled separately from other abilities + ItemNames.KERRIGAN_PRIMAL_FORM: ItemData(421 + SC2HOTS_ITEM_ID_OFFSET, "Primal Form", 0, SC2Race.ZERG, origin={"hots"}), + + ItemNames.KERRIGAN_LEVELS_10: ItemData(500 + SC2HOTS_ITEM_ID_OFFSET, "Level", 10, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_9: ItemData(501 + SC2HOTS_ITEM_ID_OFFSET, "Level", 9, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_8: ItemData(502 + SC2HOTS_ITEM_ID_OFFSET, "Level", 8, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_7: ItemData(503 + SC2HOTS_ITEM_ID_OFFSET, "Level", 7, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_6: ItemData(504 + SC2HOTS_ITEM_ID_OFFSET, "Level", 6, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_5: ItemData(505 + SC2HOTS_ITEM_ID_OFFSET, "Level", 5, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_4: ItemData(506 + SC2HOTS_ITEM_ID_OFFSET, "Level", 4, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_3: ItemData(507 + SC2HOTS_ITEM_ID_OFFSET, "Level", 3, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_2: ItemData(508 + SC2HOTS_ITEM_ID_OFFSET, "Level", 2, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_1: ItemData(509 + SC2HOTS_ITEM_ID_OFFSET, "Level", 1, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression_skip_balancing), + ItemNames.KERRIGAN_LEVELS_14: ItemData(510 + SC2HOTS_ITEM_ID_OFFSET, "Level", 14, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_35: ItemData(511 + SC2HOTS_ITEM_ID_OFFSET, "Level", 35, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + ItemNames.KERRIGAN_LEVELS_70: ItemData(512 + SC2HOTS_ITEM_ID_OFFSET, "Level", 70, SC2Race.ZERG, origin={"hots"}, quantity=0, classification=ItemClassification.progression), + + # Zerg Mercs + ItemNames.INFESTED_MEDICS: ItemData(600 + SC2HOTS_ITEM_ID_OFFSET, "Mercenary", 0, SC2Race.ZERG, origin={"ext"}), + ItemNames.INFESTED_SIEGE_TANKS: ItemData(601 + SC2HOTS_ITEM_ID_OFFSET, "Mercenary", 1, SC2Race.ZERG, origin={"ext"}), + ItemNames.INFESTED_BANSHEES: ItemData(602 + SC2HOTS_ITEM_ID_OFFSET, "Mercenary", 2, SC2Race.ZERG, origin={"ext"}), + + # Misc Upgrades + ItemNames.OVERLORD_VENTRAL_SACS: ItemData(700 + SC2HOTS_ITEM_ID_OFFSET, "Evolution Pit", 6, SC2Race.ZERG, origin={"bw"}), + + # Morphs + ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT: ItemData(800 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 6, SC2Race.ZERG, origin={"bw"}), + ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT: ItemData(801 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 7, SC2Race.ZERG, origin={"bw"}), + ItemNames.ROACH_RAVAGER_ASPECT: ItemData(802 + SC2HOTS_ITEM_ID_OFFSET, "Morph", 8, SC2Race.ZERG, origin={"ext"}), + + + # Protoss Units (those that aren't as items in WoL (Prophecy)) + ItemNames.OBSERVER: ItemData(0 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 9, SC2Race.PROTOSS, + classification=ItemClassification.filler, origin={"wol"}, + description="Flying spy. Cloak renders the unit invisible to enemies without detection."), + ItemNames.CENTURION: ItemData(1 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 10, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Powerful melee warrior. Has the Shadow Charge and Darkcoil abilities."), + ItemNames.SENTINEL: ItemData(2 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 11, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Powerful melee warrior. Has the Charge and Reconstruction abilities."), + ItemNames.SUPPLICANT: ItemData(3 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 12, SC2Race.PROTOSS, + classification=ItemClassification.filler, important_for_filtering=True, origin={"ext"}, + description="Powerful melee warrior. Has powerful damage resistant shields."), + ItemNames.INSTIGATOR: ItemData(4 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 13, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, + description="Ranged support strider. Can store multiple Blink charges."), + ItemNames.SLAYER: ItemData(5 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 14, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, + description="Ranged attack strider. Can use the Phase Blink and Phasing Armor abilities."), + ItemNames.SENTRY: ItemData(6 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 15, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Robotic support unit can use the Guardian Shield ability and restore the shields of nearby Protoss units."), + ItemNames.ENERGIZER: ItemData(7 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 16, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Robotic support unit. Can use the Chrono Beam ability and become stationary to power nearby structures."), + ItemNames.HAVOC: ItemData(8 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 17, SC2Race.PROTOSS, + origin={"lotv"}, important_for_filtering=True, + description="Robotic support unit. Can use the Target Lock and Force Field abilities and increase the range of nearby Protoss units."), + ItemNames.SIGNIFIER: ItemData(9 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 18, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, + description="Potent permanently cloaked psionic master. Can use the Feedback and Crippling Psionic Storm abilities. Can merge into an Archon."), + ItemNames.ASCENDANT: ItemData(10 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 19, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Potent psionic master. Can use the Psionic Orb, Mind Blast, and Sacrifice abilities."), + ItemNames.AVENGER: ItemData(11 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 20, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Deadly warrior-assassin. Permanently cloaked. Recalls to the nearest Dark Shrine upon death."), + ItemNames.BLOOD_HUNTER: ItemData(12 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 21, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Deadly warrior-assassin. Permanently cloaked. Can use the Void Stasis ability."), + ItemNames.DRAGOON: ItemData(13 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 22, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Ranged assault strider. Has enhanced health and damage."), + ItemNames.DARK_ARCHON: ItemData(14 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 23, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Potent psionic master. Can use the Confuse and Mind Control abilities."), + ItemNames.ADEPT: ItemData(15 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 24, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Ranged specialist. Can use the Psionic Transfer ability."), + ItemNames.WARP_PRISM: ItemData(16 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 25, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, + description="Flying transport. Can carry units and become stationary to deploy a power field."), + ItemNames.ANNIHILATOR: ItemData(17 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 26, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Assault Strider. Can use the Shadow Cannon ability to damage air and ground units."), + ItemNames.VANGUARD: ItemData(18 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 27, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Assault Strider. Deals splash damage around the primary target."), + ItemNames.WRATHWALKER: ItemData(19 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 28, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Battle strider with a powerful single target attack. Can walk up and down cliffs."), + ItemNames.REAVER: ItemData(20 + SC2LOTV_ITEM_ID_OFFSET, "Unit", 29, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Area damage siege unit. Builds and launches explosive Scarabs for high burst damage."), + ItemNames.DISRUPTOR: ItemData(21 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 0, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, + description="Robotic disruption unit. Can use the Purification Nova ability to deal heavy area damage."), + ItemNames.MIRAGE: ItemData(22 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 1, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Air superiority starfighter. Can use Graviton Beam and Phasing Armor abilities."), + ItemNames.CORSAIR: ItemData(23 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 2, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Air superiority starfighter. Can use the Disruption Web ability."), + ItemNames.DESTROYER: ItemData(24 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 3, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Area assault craft. Can use the Destruction Beam ability to attack multiple units at once."), + ItemNames.SCOUT: ItemData(25 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 4, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, + description="Versatile high-speed fighter."), + ItemNames.TEMPEST: ItemData(26 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 5, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Siege artillery craft. Attacks from long range. Can use the Disintegration ability."), + ItemNames.MOTHERSHIP: ItemData(27 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 6, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Ultimate Protoss vessel, Can use the Vortex and Mass Recall abilities. Cloaks nearby units and structures."), + ItemNames.ARBITER: ItemData(28 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 7, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="Army support craft. Has the Stasis Field and Recall abilities. Cloaks nearby units."), + ItemNames.ORACLE: ItemData(29 + SC2LOTV_ITEM_ID_OFFSET, "Unit 2", 8, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, + description="Flying caster. Can use the Revelation and Stasis Ward abilities."), + + # Protoss Upgrades + ItemNames.PROGRESSIVE_PROTOSS_GROUND_WEAPON: ItemData(100 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 0, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_GROUND_ARMOR: ItemData(101 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 2, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_SHIELDS: ItemData(102 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 4, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_AIR_WEAPON: ItemData(103 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 6, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_AIR_ARMOR: ItemData(104 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 8, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + # Upgrade bundle 'number' values are used as indices to get affected 'number's + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_UPGRADE: ItemData(105 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 11, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_ARMOR_UPGRADE: ItemData(106 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 12, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_GROUND_UPGRADE: ItemData(107 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 13, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_AIR_UPGRADE: ItemData(108 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 14, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE: ItemData(109 + SC2LOTV_ITEM_ID_OFFSET, "Upgrade", 15, SC2Race.PROTOSS, quantity=3, origin={"wol", "lotv"}), + + # Protoss Buildings + ItemNames.PHOTON_CANNON: ItemData(200 + SC2LOTV_ITEM_ID_OFFSET, "Building", 0, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"wol", "lotv"}), + ItemNames.KHAYDARIN_MONOLITH: ItemData(201 + SC2LOTV_ITEM_ID_OFFSET, "Building", 1, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SHIELD_BATTERY: ItemData(202 + SC2LOTV_ITEM_ID_OFFSET, "Building", 2, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + + # Protoss Unit Upgrades + ItemNames.SUPPLICANT_BLOOD_SHIELD: ItemData(300 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 0, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), + ItemNames.SUPPLICANT_SOUL_AUGMENTATION: ItemData(301 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 1, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), + ItemNames.SUPPLICANT_SHIELD_REGENERATION: ItemData(302 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 2, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SUPPLICANT), + ItemNames.ADEPT_SHOCKWAVE: ItemData(303 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 3, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), + ItemNames.ADEPT_RESONATING_GLAIVES: ItemData(304 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 4, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), + ItemNames.ADEPT_PHASE_BULWARK: ItemData(305 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 5, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ADEPT), + ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES: ItemData(306 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 6, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.progression), + ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION: ItemData(307 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 7, SC2Race.PROTOSS, origin={"ext"}, classification=ItemClassification.progression), + ItemNames.DRAGOON_HIGH_IMPACT_PHASE_DISRUPTORS: ItemData(308 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 8, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DRAGOON), + ItemNames.DRAGOON_TRILLIC_COMPRESSION_SYSTEM: ItemData(309 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 9, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.DRAGOON), + ItemNames.DRAGOON_SINGULARITY_CHARGE: ItemData(310 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 10, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.DRAGOON), + ItemNames.DRAGOON_ENHANCED_STRIDER_SERVOS: ItemData(311 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.DRAGOON), + ItemNames.SCOUT_COMBAT_SENSOR_ARRAY: ItemData(312 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 12, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.SCOUT), + ItemNames.SCOUT_APIAL_SENSORS: ItemData(313 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 13, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.SCOUT), + ItemNames.SCOUT_GRAVITIC_THRUSTERS: ItemData(314 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 14, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.SCOUT), + ItemNames.SCOUT_ADVANCED_PHOTON_BLASTERS: ItemData(315 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 15, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.SCOUT), + ItemNames.TEMPEST_TECTONIC_DESTABILIZERS: ItemData(316 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 16, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.TEMPEST), + ItemNames.TEMPEST_QUANTIC_REACTOR: ItemData(317 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 17, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.TEMPEST), + ItemNames.TEMPEST_GRAVITY_SLING: ItemData(318 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 18, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.TEMPEST), + ItemNames.PHOENIX_MIRAGE_IONIC_WAVELENGTH_FLUX: ItemData(319 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 19, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.PHOENIX_MIRAGE_ANION_PULSE_CRYSTALS: ItemData(320 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 20, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.CORSAIR_STEALTH_DRIVE: ItemData(321 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 21, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.CORSAIR), + ItemNames.CORSAIR_ARGUS_JEWEL: ItemData(322 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 22, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.CORSAIR), + ItemNames.CORSAIR_SUSTAINING_DISRUPTION: ItemData(323 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 23, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.CORSAIR), + ItemNames.CORSAIR_NEUTRON_SHIELDS: ItemData(324 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 24, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.CORSAIR), + ItemNames.ORACLE_STEALTH_DRIVE: ItemData(325 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 25, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), + ItemNames.ORACLE_STASIS_CALIBRATION: ItemData(326 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 26, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), + ItemNames.ORACLE_TEMPORAL_ACCELERATION_BEAM: ItemData(327 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 27, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ORACLE), + ItemNames.ARBITER_CHRONOSTATIC_REINFORCEMENT: ItemData(328 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 28, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_KHAYDARIN_CORE: ItemData(329 + SC2LOTV_ITEM_ID_OFFSET, "Forge 1", 29, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_SPACETIME_ANCHOR: ItemData(330 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 0, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_RESOURCE_EFFICIENCY: ItemData(331 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 1, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.ARBITER_ENHANCED_CLOAK_FIELD: ItemData(332 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 2, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.ARBITER), + ItemNames.CARRIER_GRAVITON_CATAPULT: + ItemData(333 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 3, SC2Race.PROTOSS, origin={"wol"}, + parent_item=ItemNames.CARRIER, + description="Carriers can launch Interceptors more quickly."), + ItemNames.CARRIER_HULL_OF_PAST_GLORIES: + ItemData(334 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 4, SC2Race.PROTOSS, origin={"bw"}, + parent_item=ItemNames.CARRIER, + description="Carriers gain +2 armour."), + ItemNames.VOID_RAY_DESTROYER_FLUX_VANES: + ItemData(335 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 5, SC2Race.PROTOSS, classification=ItemClassification.filler, + origin={"ext"}, + description="Increases Void Ray and Destroyer movement speed."), + ItemNames.DESTROYER_REFORGED_BLOODSHARD_CORE: + ItemData(336 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 6, SC2Race.PROTOSS, origin={"ext"}, + parent_item=ItemNames.DESTROYER, + description="When fully charged, the Destroyer's Destruction Beam weapon does full damage to secondary targets."), + ItemNames.WARP_PRISM_GRAVITIC_DRIVE: + ItemData(337 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 7, SC2Race.PROTOSS, classification=ItemClassification.filler, + origin={"ext"}, parent_item=ItemNames.WARP_PRISM, + description="Increases the movement speed of Warp Prisms."), + ItemNames.WARP_PRISM_PHASE_BLASTER: + ItemData(338 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 8, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"ext"}, parent_item=ItemNames.WARP_PRISM, + description="Equips Warp Prisms with an auto-attack that can hit ground and air targets."), + ItemNames.WARP_PRISM_WAR_CONFIGURATION: ItemData(339 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 9, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.WARP_PRISM), + ItemNames.OBSERVER_GRAVITIC_BOOSTERS: ItemData(340 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 10, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.OBSERVER), + ItemNames.OBSERVER_SENSOR_ARRAY: ItemData(341 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.OBSERVER), + ItemNames.REAVER_SCARAB_DAMAGE: ItemData(342 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 12, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.REAVER), + ItemNames.REAVER_SOLARITE_PAYLOAD: ItemData(343 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 13, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.REAVER), + ItemNames.REAVER_REAVER_CAPACITY: ItemData(344 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 14, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}, parent_item=ItemNames.REAVER), + ItemNames.REAVER_RESOURCE_EFFICIENCY: ItemData(345 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 15, SC2Race.PROTOSS, origin={"bw"}, parent_item=ItemNames.REAVER), + ItemNames.VANGUARD_AGONY_LAUNCHERS: ItemData(346 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 16, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.VANGUARD), + ItemNames.VANGUARD_MATTER_DISPERSION: ItemData(347 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 17, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.VANGUARD), + ItemNames.IMMORTAL_ANNIHILATOR_SINGULARITY_CHARGE: ItemData(348 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 18, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING_MECHANICS: ItemData(349 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 19, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), + ItemNames.COLOSSUS_PACIFICATION_PROTOCOL: ItemData(350 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 20, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.COLOSSUS), + ItemNames.WRATHWALKER_RAPID_POWER_CYCLING: ItemData(351 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 21, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.WRATHWALKER), + ItemNames.WRATHWALKER_EYE_OF_WRATH: ItemData(352 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 22, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.WRATHWALKER), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHROUD_OF_ADUN: ItemData(353 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 23, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_SHADOW_GUARD_TRAINING: ItemData(354 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 24, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_BLINK: ItemData(355 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 25, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"ext"}), + ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_RESOURCE_EFFICIENCY: ItemData(356 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 26, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.DARK_TEMPLAR_DARK_ARCHON_MELD: ItemData(357 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 27, SC2Race.PROTOSS, origin={"bw"}, important_for_filtering=True ,parent_item=ItemNames.DARK_TEMPLAR), + ItemNames.HIGH_TEMPLAR_SIGNIFIER_UNSHACKLED_PSIONIC_STORM: ItemData(358 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 28, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.HIGH_TEMPLAR_SIGNIFIER_HALLUCINATION: ItemData(359 + SC2LOTV_ITEM_ID_OFFSET, "Forge 2", 29, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"bw"}), + ItemNames.HIGH_TEMPLAR_SIGNIFIER_KHAYDARIN_AMULET: ItemData(360 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 0, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.ARCHON_HIGH_ARCHON: ItemData(361 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 1, SC2Race.PROTOSS, origin={"ext"}, important_for_filtering=True), + ItemNames.DARK_ARCHON_FEEDBACK: ItemData(362 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 2, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.DARK_ARCHON_MAELSTROM: ItemData(363 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 3, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.DARK_ARCHON_ARGUS_TALISMAN: ItemData(364 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 4, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.ASCENDANT_POWER_OVERWHELMING: ItemData(365 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 5, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), + ItemNames.ASCENDANT_CHAOTIC_ATTUNEMENT: ItemData(366 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 6, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), + ItemNames.ASCENDANT_BLOOD_AMULET: ItemData(367 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 7, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ASCENDANT), + ItemNames.SENTRY_ENERGIZER_HAVOC_CLOAKING_MODULE: ItemData(368 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 8, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.SENTRY_ENERGIZER_HAVOC_SHIELD_BATTERY_RAPID_RECHARGING: ItemData(369 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 9, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.SENTRY_FORCE_FIELD: ItemData(370 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 10, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SENTRY), + ItemNames.SENTRY_HALLUCINATION: ItemData(371 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 11, SC2Race.PROTOSS, classification=ItemClassification.filler, origin={"ext"}, parent_item=ItemNames.SENTRY), + ItemNames.ENERGIZER_RECLAMATION: ItemData(372 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 12, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ENERGIZER), + ItemNames.ENERGIZER_FORGED_CHASSIS: ItemData(373 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 13, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.ENERGIZER), + ItemNames.HAVOC_DETECT_WEAKNESS: ItemData(374 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 14, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.HAVOC), + ItemNames.HAVOC_BLOODSHARD_RESONANCE: ItemData(375 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 15, SC2Race.PROTOSS, origin={"ext"}, parent_item=ItemNames.HAVOC), + ItemNames.ZEALOT_SENTINEL_CENTURION_LEG_ENHANCEMENTS: ItemData(376 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 16, SC2Race.PROTOSS, origin={"bw"}), + ItemNames.ZEALOT_SENTINEL_CENTURION_SHIELD_CAPACITY: ItemData(377 + SC2LOTV_ITEM_ID_OFFSET, "Forge 3", 17, SC2Race.PROTOSS, origin={"bw"}), + + # SoA Calldown powers + ItemNames.SOA_CHRONO_SURGE: ItemData(700 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 0, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_PROGRESSIVE_PROXY_PYLON: ItemData(701 + SC2LOTV_ITEM_ID_OFFSET, "Progressive Upgrade", 0, SC2Race.PROTOSS, origin={"lotv"}, quantity=2), + ItemNames.SOA_PYLON_OVERCHARGE: ItemData(702 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 1, SC2Race.PROTOSS, origin={"ext"}), + ItemNames.SOA_ORBITAL_STRIKE: ItemData(703 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 2, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_TEMPORAL_FIELD: ItemData(704 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 3, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_SOLAR_LANCE: ItemData(705 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 4, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SOA_MASS_RECALL: ItemData(706 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 5, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_SHIELD_OVERCHARGE: ItemData(707 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 6, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_DEPLOY_FENIX: ItemData(708 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 7, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SOA_PURIFIER_BEAM: ItemData(709 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 8, SC2Race.PROTOSS, origin={"lotv"}), + ItemNames.SOA_TIME_STOP: ItemData(710 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 9, SC2Race.PROTOSS, classification=ItemClassification.progression, origin={"lotv"}), + ItemNames.SOA_SOLAR_BOMBARDMENT: ItemData(711 + SC2LOTV_ITEM_ID_OFFSET, "Spear of Adun", 10, SC2Race.PROTOSS, origin={"lotv"}), + + # Generic Protoss Upgrades + ItemNames.MATRIX_OVERLOAD: + ItemData(800 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 0, SC2Race.PROTOSS, origin={"lotv"}, + description=r"All friendly units gain 25% movement speed and 15% attack speed within a Pylon's power field and for 15 seconds after leaving it."), + ItemNames.QUATRO: + ItemData(801 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 1, SC2Race.PROTOSS, origin={"ext"}, + description="All friendly Protoss units gain the equivalent of their +1 armour, attack, and shield upgrades."), + ItemNames.NEXUS_OVERCHARGE: + ItemData(802 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 2, SC2Race.PROTOSS, origin={"lotv"}, + important_for_filtering=True, description="The Protoss Nexus gains a long-range auto-attack."), + ItemNames.ORBITAL_ASSIMILATORS: + ItemData(803 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 3, SC2Race.PROTOSS, origin={"lotv"}, + description="Assimilators automatically harvest Vespene Gas without the need for Probes."), + ItemNames.WARP_HARMONIZATION: + ItemData(804 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 4, SC2Race.PROTOSS, origin={"lotv"}, + description=r"Stargates and Robotics Facilities can transform to utilize Warp In technology. Warp In cooldowns are 20% faster than original build times."), + ItemNames.GUARDIAN_SHELL: + ItemData(805 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 5, SC2Race.PROTOSS, origin={"lotv"}, + description="The Spear of Adun passively shields friendly Protoss units before death, making them invulnerable for 5 seconds. Each unit can only be shielded once every 60 seconds."), + ItemNames.RECONSTRUCTION_BEAM: + ItemData(806 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 6, SC2Race.PROTOSS, + classification=ItemClassification.progression, origin={"lotv"}, + description="The Spear of Adun will passively heal mechanical units for 5 and non-biological structures for 10 life per second. Up to 3 targets can be repaired at once."), + ItemNames.OVERWATCH: + ItemData(807 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 7, SC2Race.PROTOSS, origin={"ext"}, + description="Once per second, the Spear of Adun will last-hit a damaged enemy unit that is below 50 health."), + ItemNames.SUPERIOR_WARP_GATES: + ItemData(808 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 8, SC2Race.PROTOSS, origin={"ext"}, + description="Protoss Warp Gates can hold up to 3 charges of unit warp-ins."), + ItemNames.ENHANCED_TARGETING: + ItemData(809 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 9, SC2Race.PROTOSS, origin={"ext"}, + description="Protoss defensive structures gain +2 range."), + ItemNames.OPTIMIZED_ORDNANCE: + ItemData(810 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 10, SC2Race.PROTOSS, origin={"ext"}, + description="Increases the attack speed of Protoss defensive structures by 25%."), + ItemNames.KHALAI_INGENUITY: + ItemData(811 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 11, SC2Race.PROTOSS, origin={"ext"}, + description="Pylons, Photon Cannons, Monoliths, and Shield Batteries warp in near-instantly."), + ItemNames.AMPLIFIED_ASSIMILATORS: + ItemData(812 + SC2LOTV_ITEM_ID_OFFSET, "Solarite Core", 12, SC2Race.PROTOSS, origin={"ext"}, + description=r"Assimilators produce Vespene gas 25% faster."), +} + + +def get_item_table(): + return item_table + + +basic_units = { + SC2Race.TERRAN: { + ItemNames.MARINE, + ItemNames.MARAUDER, + ItemNames.GOLIATH, + ItemNames.HELLION, + ItemNames.VULTURE, + ItemNames.WARHOUND, + }, + SC2Race.ZERG: { + ItemNames.ZERGLING, + ItemNames.SWARM_QUEEN, + ItemNames.ROACH, + ItemNames.HYDRALISK, + }, + SC2Race.PROTOSS: { + ItemNames.ZEALOT, + ItemNames.CENTURION, + ItemNames.SENTINEL, + ItemNames.STALKER, + ItemNames.INSTIGATOR, + ItemNames.SLAYER, + ItemNames.DRAGOON, + ItemNames.ADEPT, + } +} + +advanced_basic_units = { + SC2Race.TERRAN: basic_units[SC2Race.TERRAN].union({ + ItemNames.REAPER, + ItemNames.DIAMONDBACK, + ItemNames.VIKING, + ItemNames.SIEGE_TANK, + ItemNames.BANSHEE, + ItemNames.THOR, + ItemNames.BATTLECRUISER, + ItemNames.CYCLONE + }), + SC2Race.ZERG: basic_units[SC2Race.ZERG].union({ + ItemNames.INFESTOR, + ItemNames.ABERRATION, + }), + SC2Race.PROTOSS: basic_units[SC2Race.PROTOSS].union({ + ItemNames.DARK_TEMPLAR, + ItemNames.BLOOD_HUNTER, + ItemNames.AVENGER, + ItemNames.IMMORTAL, + ItemNames.ANNIHILATOR, + ItemNames.VANGUARD, + }) +} + +no_logic_starting_units = { + SC2Race.TERRAN: advanced_basic_units[SC2Race.TERRAN].union({ + ItemNames.FIREBAT, + ItemNames.GHOST, + ItemNames.SPECTRE, + ItemNames.WRAITH, + ItemNames.RAVEN, + ItemNames.PREDATOR, + ItemNames.LIBERATOR, + ItemNames.HERC, + }), + SC2Race.ZERG: advanced_basic_units[SC2Race.ZERG].union({ + ItemNames.ULTRALISK, + ItemNames.SWARM_HOST + }), + SC2Race.PROTOSS: advanced_basic_units[SC2Race.PROTOSS].union({ + ItemNames.CARRIER, + ItemNames.TEMPEST, + ItemNames.VOID_RAY, + ItemNames.DESTROYER, + ItemNames.COLOSSUS, + ItemNames.WRATHWALKER, + ItemNames.SCOUT, + ItemNames.HIGH_TEMPLAR, + ItemNames.SIGNIFIER, + ItemNames.ASCENDANT, + ItemNames.DARK_ARCHON, + ItemNames.SUPPLICANT, + }) +} + +not_balanced_starting_units = { + ItemNames.SIEGE_TANK, + ItemNames.THOR, + ItemNames.BANSHEE, + ItemNames.BATTLECRUISER, + ItemNames.ULTRALISK, + ItemNames.CARRIER, + ItemNames.TEMPEST, +} + + +def get_basic_units(world: World, race: SC2Race) -> typing.Set[str]: + logic_level = get_option_value(world, 'required_tactics') + if logic_level == RequiredTactics.option_no_logic: + return no_logic_starting_units[race] + elif logic_level == RequiredTactics.option_advanced: + return advanced_basic_units[race] + else: + return basic_units[race] + + +# Items that can be placed before resources if not already in +# General upgrades and Mercs +second_pass_placeable_items: typing.Tuple[str, ...] = ( + # Global weapon/armor upgrades + ItemNames.PROGRESSIVE_TERRAN_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_TERRAN_WEAPON_UPGRADE, + ItemNames.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_WEAPON_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_SHIELDS, + # Terran Buildings without upgrades + ItemNames.SENSOR_TOWER, + ItemNames.HIVE_MIND_EMULATOR, + ItemNames.PSI_DISRUPTER, + ItemNames.PERDITION_TURRET, + # Terran units without upgrades + ItemNames.HERC, + ItemNames.WARHOUND, + # General Terran upgrades without any dependencies + ItemNames.SCV_ADVANCED_CONSTRUCTION, + ItemNames.SCV_DUAL_FUSION_WELDERS, + ItemNames.PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM, + ItemNames.PROGRESSIVE_ORBITAL_COMMAND, + ItemNames.ULTRA_CAPACITORS, + ItemNames.VANADIUM_PLATING, + ItemNames.ORBITAL_DEPOTS, + ItemNames.MICRO_FILTERING, + ItemNames.AUTOMATED_REFINERY, + ItemNames.COMMAND_CENTER_REACTOR, + ItemNames.TECH_REACTOR, + ItemNames.CELLULAR_REACTOR, + ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, # Place only L1 + ItemNames.STRUCTURE_ARMOR, + ItemNames.HI_SEC_AUTO_TRACKING, + ItemNames.ADVANCED_OPTICS, + ItemNames.ROGUE_FORCES, + # Mercenaries (All races) + *[item_name for item_name, item_data in get_full_item_list().items() + if item_data.type == "Mercenary"], + # Kerrigan and Nova levels, abilities and generally useful stuff + *[item_name for item_name, item_data in get_full_item_list().items() + if item_data.type in ("Level", "Ability", "Evolution Pit", "Nova Gear")], + ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE, + # Zerg static defenses + ItemNames.SPORE_CRAWLER, + ItemNames.SPINE_CRAWLER, + # Defiler, Aberration (no upgrades) + ItemNames.DEFILER, + ItemNames.ABERRATION, + # Spear of Adun Abilities + ItemNames.SOA_CHRONO_SURGE, + ItemNames.SOA_PROGRESSIVE_PROXY_PYLON, + ItemNames.SOA_PYLON_OVERCHARGE, + ItemNames.SOA_ORBITAL_STRIKE, + ItemNames.SOA_TEMPORAL_FIELD, + ItemNames.SOA_SOLAR_LANCE, + ItemNames.SOA_MASS_RECALL, + ItemNames.SOA_SHIELD_OVERCHARGE, + ItemNames.SOA_DEPLOY_FENIX, + ItemNames.SOA_PURIFIER_BEAM, + ItemNames.SOA_TIME_STOP, + ItemNames.SOA_SOLAR_BOMBARDMENT, + # Protoss generic upgrades + ItemNames.MATRIX_OVERLOAD, + ItemNames.QUATRO, + ItemNames.NEXUS_OVERCHARGE, + ItemNames.ORBITAL_ASSIMILATORS, + ItemNames.WARP_HARMONIZATION, + ItemNames.GUARDIAN_SHELL, + ItemNames.RECONSTRUCTION_BEAM, + ItemNames.OVERWATCH, + ItemNames.SUPERIOR_WARP_GATES, + ItemNames.KHALAI_INGENUITY, + ItemNames.AMPLIFIED_ASSIMILATORS, + # Protoss static defenses + ItemNames.PHOTON_CANNON, + ItemNames.KHAYDARIN_MONOLITH, + ItemNames.SHIELD_BATTERY +) + + +filler_items: typing.Tuple[str, ...] = ( + ItemNames.STARTING_MINERALS, + ItemNames.STARTING_VESPENE, + ItemNames.STARTING_SUPPLY, +) + +# Defense rating table +# Commented defense ratings are handled in LogicMixin +defense_ratings = { + ItemNames.SIEGE_TANK: 5, + # "Maelstrom Rounds": 2, + ItemNames.PLANETARY_FORTRESS: 3, + # Bunker w/ Marine/Marauder: 3, + ItemNames.PERDITION_TURRET: 2, + ItemNames.VULTURE: 1, + ItemNames.BANSHEE: 1, + ItemNames.BATTLECRUISER: 1, + ItemNames.LIBERATOR: 4, + ItemNames.WIDOW_MINE: 1, + # "Concealment (Widow Mine)": 1 +} +zerg_defense_ratings = { + ItemNames.PERDITION_TURRET: 2, + # Bunker w/ Firebat: 2, + ItemNames.LIBERATOR: -2, + ItemNames.HIVE_MIND_EMULATOR: 3, + ItemNames.PSI_DISRUPTER: 3, +} +air_defense_ratings = { + ItemNames.MISSILE_TURRET: 2, +} + +kerrigan_levels = [item_name for item_name, item_data in get_full_item_list().items() + if item_data.type == "Level" and item_data.race == SC2Race.ZERG] + +spider_mine_sources = { + ItemNames.VULTURE, + ItemNames.REAPER_SPIDER_MINES, + ItemNames.SIEGE_TANK_SPIDER_MINES, + ItemNames.RAVEN_SPIDER_MINES, +} + +progressive_if_nco = { + ItemNames.MARINE_PROGRESSIVE_STIMPACK, + ItemNames.FIREBAT_PROGRESSIVE_STIMPACK, + ItemNames.BANSHEE_PROGRESSIVE_CROSS_SPECTRUM_DAMPENERS, + ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, +} + +progressive_if_ext = { + ItemNames.VULTURE_PROGRESSIVE_REPLENISHABLE_MAGAZINE, + ItemNames.WRAITH_PROGRESSIVE_TOMAHAWK_POWER_CELLS, + ItemNames.BATTLECRUISER_PROGRESSIVE_DEFENSIVE_MATRIX, + ItemNames.BATTLECRUISER_PROGRESSIVE_MISSILE_PODS, + ItemNames.THOR_PROGRESSIVE_IMMORTALITY_PROTOCOL, + ItemNames.PROGRESSIVE_FIRE_SUPPRESSION_SYSTEM, + ItemNames.DIAMONDBACK_PROGRESSIVE_TRI_LITHIUM_POWER_CELL +} + +kerrigan_actives: typing.List[typing.Set[str]] = [ + {ItemNames.KERRIGAN_KINETIC_BLAST, ItemNames.KERRIGAN_LEAPING_STRIKE}, + {ItemNames.KERRIGAN_CRUSHING_GRIP, ItemNames.KERRIGAN_PSIONIC_SHIFT}, + set(), + {ItemNames.KERRIGAN_WILD_MUTATION, ItemNames.KERRIGAN_SPAWN_BANELINGS, ItemNames.KERRIGAN_MEND}, + set(), + set(), + {ItemNames.KERRIGAN_APOCALYPSE, ItemNames.KERRIGAN_SPAWN_LEVIATHAN, ItemNames.KERRIGAN_DROP_PODS}, +] + +kerrigan_passives: typing.List[typing.Set[str]] = [ + {ItemNames.KERRIGAN_HEROIC_FORTITUDE}, + {ItemNames.KERRIGAN_CHAIN_REACTION}, + {ItemNames.KERRIGAN_ZERGLING_RECONSTITUTION, ItemNames.KERRIGAN_IMPROVED_OVERLORDS, ItemNames.KERRIGAN_AUTOMATED_EXTRACTORS}, + set(), + {ItemNames.KERRIGAN_TWIN_DRONES, ItemNames.KERRIGAN_MALIGNANT_CREEP, ItemNames.KERRIGAN_VESPENE_EFFICIENCY}, + {ItemNames.KERRIGAN_INFEST_BROODLINGS, ItemNames.KERRIGAN_FURY, ItemNames.KERRIGAN_ABILITY_EFFICIENCY}, + set(), +] + +kerrigan_only_passives = { + ItemNames.KERRIGAN_HEROIC_FORTITUDE, ItemNames.KERRIGAN_CHAIN_REACTION, + ItemNames.KERRIGAN_INFEST_BROODLINGS, ItemNames.KERRIGAN_FURY, ItemNames.KERRIGAN_ABILITY_EFFICIENCY, +} + +spear_of_adun_calldowns = { + ItemNames.SOA_CHRONO_SURGE, + ItemNames.SOA_PROGRESSIVE_PROXY_PYLON, + ItemNames.SOA_PYLON_OVERCHARGE, + ItemNames.SOA_ORBITAL_STRIKE, + ItemNames.SOA_TEMPORAL_FIELD, + ItemNames.SOA_SOLAR_LANCE, + ItemNames.SOA_MASS_RECALL, + ItemNames.SOA_SHIELD_OVERCHARGE, + ItemNames.SOA_DEPLOY_FENIX, + ItemNames.SOA_PURIFIER_BEAM, + ItemNames.SOA_TIME_STOP, + ItemNames.SOA_SOLAR_BOMBARDMENT +} + +spear_of_adun_castable_passives = { + ItemNames.RECONSTRUCTION_BEAM, + ItemNames.OVERWATCH, +} + +nova_equipment = { + *[item_name for item_name, item_data in get_full_item_list().items() + if item_data.type == "Nova Gear"], + ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE +} + +# 'number' values of upgrades for upgrade bundle items +upgrade_numbers = [ + # Terran + {0, 4, 8}, # Weapon + {2, 6, 10}, # Armor + {0, 2}, # Infantry + {4, 6}, # Vehicle + {8, 10}, # Starship + {0, 2, 4, 6, 8, 10}, # All + # Zerg + {0, 2, 6}, # Weapon + {4, 8}, # Armor + {0, 2, 4}, # Ground + {6, 8}, # Flyer + {0, 2, 4, 6, 8}, # All + # Protoss + {0, 6}, # Weapon + {2, 4, 8}, # Armor + {0, 2}, # Ground, Shields are handled specially + {6, 8}, # Air, Shields are handled specially + {0, 2, 4, 6, 8}, # All +] +# 'upgrade_numbers' indices for all upgrades +upgrade_numbers_all = { + SC2Race.TERRAN: 5, + SC2Race.ZERG: 10, + SC2Race.PROTOSS: 15, +} + +# Names of upgrades to be included for different options +upgrade_included_names = [ + { # Individual Items + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_WEAPON, + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_ARMOR, + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_WEAPON, + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_ARMOR, + ItemNames.PROGRESSIVE_TERRAN_SHIP_WEAPON, + ItemNames.PROGRESSIVE_TERRAN_SHIP_ARMOR, + ItemNames.PROGRESSIVE_ZERG_MELEE_ATTACK, + ItemNames.PROGRESSIVE_ZERG_MISSILE_ATTACK, + ItemNames.PROGRESSIVE_ZERG_GROUND_CARAPACE, + ItemNames.PROGRESSIVE_ZERG_FLYER_ATTACK, + ItemNames.PROGRESSIVE_ZERG_FLYER_CARAPACE, + ItemNames.PROGRESSIVE_PROTOSS_GROUND_WEAPON, + ItemNames.PROGRESSIVE_PROTOSS_GROUND_ARMOR, + ItemNames.PROGRESSIVE_PROTOSS_SHIELDS, + ItemNames.PROGRESSIVE_PROTOSS_AIR_WEAPON, + ItemNames.PROGRESSIVE_PROTOSS_AIR_ARMOR, + }, + { # Bundle Weapon And Armor + ItemNames.PROGRESSIVE_TERRAN_WEAPON_UPGRADE, + ItemNames.PROGRESSIVE_TERRAN_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_WEAPON_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_ARMOR_UPGRADE, + }, + { # Bundle Unit Class + ItemNames.PROGRESSIVE_TERRAN_INFANTRY_UPGRADE, + ItemNames.PROGRESSIVE_TERRAN_VEHICLE_UPGRADE, + ItemNames.PROGRESSIVE_TERRAN_SHIP_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_GROUND_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_FLYER_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_GROUND_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_AIR_UPGRADE, + }, + { # Bundle All + ItemNames.PROGRESSIVE_TERRAN_WEAPON_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_ZERG_WEAPON_ARMOR_UPGRADE, + ItemNames.PROGRESSIVE_PROTOSS_WEAPON_ARMOR_UPGRADE, + } +] + +lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in get_full_item_list().items() if + data.code} + +# Map type to expected int +type_flaggroups: typing.Dict[SC2Race, typing.Dict[str, int]] = { + SC2Race.ANY: { + "Minerals": 0, + "Vespene": 1, + "Supply": 2, + "Goal": 3, + "Nothing Group": 4, + }, + SC2Race.TERRAN: { + "Armory 1": 0, + "Armory 2": 1, + "Armory 3": 2, + "Armory 4": 3, + "Armory 5": 4, + "Armory 6": 5, + "Progressive Upgrade": 6, # Unit upgrades that exist multiple times (Stimpack / Super Stimpack) + "Laboratory": 7, + "Upgrade": 8, # Weapon / Armor upgrades + "Unit": 9, + "Building": 10, + "Mercenary": 11, + "Nova Gear": 12, + "Progressive Upgrade 2": 13, + }, + SC2Race.ZERG: { + "Ability": 0, + "Mutation 1": 1, + "Strain": 2, + "Morph": 3, + "Upgrade": 4, + "Mercenary": 5, + "Unit": 6, + "Level": 7, + "Primal Form": 8, + "Evolution Pit": 9, + "Mutation 2": 10, + "Mutation 3": 11 + }, + SC2Race.PROTOSS: { + "Unit": 0, + "Unit 2": 1, + "Upgrade": 2, # Weapon / Armor upgrades + "Building": 3, + "Progressive Upgrade": 4, + "Spear of Adun": 5, + "Solarite Core": 6, + "Forge 1": 7, + "Forge 2": 8, + "Forge 3": 9, + } +} diff --git a/worlds/sc2/Locations.py b/worlds/sc2/Locations.py new file mode 100644 index 0000000000..5a03f22323 --- /dev/null +++ b/worlds/sc2/Locations.py @@ -0,0 +1,1638 @@ +from enum import IntEnum +from typing import List, Tuple, Optional, Callable, NamedTuple, Set, Any +from BaseClasses import MultiWorld +from . import ItemNames +from .Options import get_option_value, kerrigan_unit_available, RequiredTactics, GrantStoryTech, LocationInclusion, \ + EnableHotsMissions +from .Rules import SC2Logic + +from BaseClasses import Location +from worlds.AutoWorld import World + +SC2WOL_LOC_ID_OFFSET = 1000 +SC2HOTS_LOC_ID_OFFSET = 20000000 # Avoid clashes with The Legend of Zelda +SC2LOTV_LOC_ID_OFFSET = SC2HOTS_LOC_ID_OFFSET + 2000 +SC2NCO_LOC_ID_OFFSET = SC2LOTV_LOC_ID_OFFSET + 2500 + + +class SC2Location(Location): + game: str = "Starcraft2" + + +class LocationType(IntEnum): + VICTORY = 0 # Winning a mission + VANILLA = 1 # Objectives that provided metaprogression in the original campaign, along with a few other locations for a balanced experience + EXTRA = 2 # Additional locations based on mission progression, collecting in-mission rewards, etc. that do not significantly increase the challenge. + CHALLENGE = 3 # Challenging objectives, often harder than just completing a mission, and often associated with Achievements + MASTERY = 4 # Extremely challenging objectives often associated with Masteries and Feats of Strength in the original campaign + + +class LocationData(NamedTuple): + region: str + name: str + code: Optional[int] + type: LocationType + rule: Optional[Callable[[Any], bool]] = Location.access_rule + + +def get_location_types(world: World, inclusion_type: LocationInclusion) -> Set[LocationType]: + """ + + :param multiworld: + :param player: + :param inclusion_type: Level of inclusion to check for + :return: A list of location types that match the inclusion type + """ + exclusion_options = [ + ("vanilla_locations", LocationType.VANILLA), + ("extra_locations", LocationType.EXTRA), + ("challenge_locations", LocationType.CHALLENGE), + ("mastery_locations", LocationType.MASTERY) + ] + excluded_location_types = set() + for option_name, location_type in exclusion_options: + if get_option_value(world, option_name) is inclusion_type: + excluded_location_types.add(location_type) + return excluded_location_types + + +def get_plando_locations(world: World) -> List[str]: + """ + + :param multiworld: + :param player: + :return: A list of locations affected by a plando in a world + """ + if world is None: + return [] + plando_locations = [] + for plando_setting in world.multiworld.plando_items[world.player]: + plando_locations += plando_setting.get("locations", []) + plando_setting_location = plando_setting.get("location", None) + if plando_setting_location is not None: + plando_locations.append(plando_setting_location) + + return plando_locations + + +def get_locations(world: Optional[World]) -> Tuple[LocationData, ...]: + # Note: rules which are ended with or True are rules identified as needed later when restricted units is an option + logic_level = get_option_value(world, 'required_tactics') + adv_tactics = logic_level != RequiredTactics.option_standard + kerriganless = get_option_value(world, 'kerrigan_presence') not in kerrigan_unit_available \ + or get_option_value(world, "enable_hots_missions") == EnableHotsMissions.option_false + story_tech_granted = get_option_value(world, "grant_story_tech") == GrantStoryTech.option_true + logic = SC2Logic(world) + player = None if world is None else world.player + location_table: List[LocationData] = [ + # WoL + LocationData("Liberation Day", "Liberation Day: Victory", SC2WOL_LOC_ID_OFFSET + 100, LocationType.VICTORY), + LocationData("Liberation Day", "Liberation Day: First Statue", SC2WOL_LOC_ID_OFFSET + 101, LocationType.VANILLA), + LocationData("Liberation Day", "Liberation Day: Second Statue", SC2WOL_LOC_ID_OFFSET + 102, LocationType.VANILLA), + LocationData("Liberation Day", "Liberation Day: Third Statue", SC2WOL_LOC_ID_OFFSET + 103, LocationType.VANILLA), + LocationData("Liberation Day", "Liberation Day: Fourth Statue", SC2WOL_LOC_ID_OFFSET + 104, LocationType.VANILLA), + LocationData("Liberation Day", "Liberation Day: Fifth Statue", SC2WOL_LOC_ID_OFFSET + 105, LocationType.VANILLA), + LocationData("Liberation Day", "Liberation Day: Sixth Statue", SC2WOL_LOC_ID_OFFSET + 106, LocationType.VANILLA), + LocationData("Liberation Day", "Liberation Day: Special Delivery", SC2WOL_LOC_ID_OFFSET + 107, LocationType.EXTRA), + LocationData("Liberation Day", "Liberation Day: Transport", SC2WOL_LOC_ID_OFFSET + 108, LocationType.EXTRA), + LocationData("The Outlaws", "The Outlaws: Victory", SC2WOL_LOC_ID_OFFSET + 200, LocationType.VICTORY, + lambda state: logic.terran_early_tech(state)), + LocationData("The Outlaws", "The Outlaws: Rebel Base", SC2WOL_LOC_ID_OFFSET + 201, LocationType.VANILLA, + lambda state: logic.terran_early_tech(state)), + LocationData("The Outlaws", "The Outlaws: North Resource Pickups", SC2WOL_LOC_ID_OFFSET + 202, LocationType.EXTRA, + lambda state: logic.terran_early_tech(state)), + LocationData("The Outlaws", "The Outlaws: Bunker", SC2WOL_LOC_ID_OFFSET + 203, LocationType.VANILLA, + lambda state: logic.terran_early_tech(state)), + LocationData("The Outlaws", "The Outlaws: Close Resource Pickups", SC2WOL_LOC_ID_OFFSET + 204, LocationType.EXTRA), + LocationData("Zero Hour", "Zero Hour: Victory", SC2WOL_LOC_ID_OFFSET + 300, LocationType.VICTORY, + lambda state: logic.terran_common_unit(state) and + logic.terran_defense_rating(state, True) >= 2 and + (adv_tactics or logic.terran_basic_anti_air(state))), + LocationData("Zero Hour", "Zero Hour: First Group Rescued", SC2WOL_LOC_ID_OFFSET + 301, LocationType.VANILLA), + LocationData("Zero Hour", "Zero Hour: Second Group Rescued", SC2WOL_LOC_ID_OFFSET + 302, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state)), + LocationData("Zero Hour", "Zero Hour: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 303, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + logic.terran_defense_rating(state, True) >= 2), + LocationData("Zero Hour", "Zero Hour: First Hatchery", SC2WOL_LOC_ID_OFFSET + 304, LocationType.CHALLENGE, + lambda state: logic.terran_competent_comp(state)), + LocationData("Zero Hour", "Zero Hour: Second Hatchery", SC2WOL_LOC_ID_OFFSET + 305, LocationType.CHALLENGE, + lambda state: logic.terran_competent_comp(state)), + LocationData("Zero Hour", "Zero Hour: Third Hatchery", SC2WOL_LOC_ID_OFFSET + 306, LocationType.CHALLENGE, + lambda state: logic.terran_competent_comp(state)), + LocationData("Zero Hour", "Zero Hour: Fourth Hatchery", SC2WOL_LOC_ID_OFFSET + 307, LocationType.CHALLENGE, + lambda state: logic.terran_competent_comp(state)), + LocationData("Zero Hour", "Zero Hour: Ride's on its Way", SC2WOL_LOC_ID_OFFSET + 308, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state)), + LocationData("Zero Hour", "Zero Hour: Hold Just a Little Longer", SC2WOL_LOC_ID_OFFSET + 309, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_defense_rating(state, True) >= 2), + LocationData("Zero Hour", "Zero Hour: Cavalry's on the Way", SC2WOL_LOC_ID_OFFSET + 310, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_defense_rating(state, True) >= 2), + LocationData("Evacuation", "Evacuation: Victory", SC2WOL_LOC_ID_OFFSET + 400, LocationType.VICTORY, + lambda state: logic.terran_early_tech(state) and + (adv_tactics and logic.terran_basic_anti_air(state) + or logic.terran_competent_anti_air(state))), + LocationData("Evacuation", "Evacuation: North Chrysalis", SC2WOL_LOC_ID_OFFSET + 401, LocationType.VANILLA), + LocationData("Evacuation", "Evacuation: West Chrysalis", SC2WOL_LOC_ID_OFFSET + 402, LocationType.VANILLA, + lambda state: logic.terran_early_tech(state)), + LocationData("Evacuation", "Evacuation: East Chrysalis", SC2WOL_LOC_ID_OFFSET + 403, LocationType.VANILLA, + lambda state: logic.terran_early_tech(state)), + LocationData("Evacuation", "Evacuation: Reach Hanson", SC2WOL_LOC_ID_OFFSET + 404, LocationType.EXTRA), + LocationData("Evacuation", "Evacuation: Secret Resource Stash", SC2WOL_LOC_ID_OFFSET + 405, LocationType.EXTRA), + LocationData("Evacuation", "Evacuation: Flawless", SC2WOL_LOC_ID_OFFSET + 406, LocationType.CHALLENGE, + lambda state: logic.terran_early_tech(state) and + logic.terran_defense_rating(state, True, False) >= 2 and + (adv_tactics and logic.terran_basic_anti_air(state) + or logic.terran_competent_anti_air(state))), + LocationData("Outbreak", "Outbreak: Victory", SC2WOL_LOC_ID_OFFSET + 500, LocationType.VICTORY, + lambda state: logic.terran_defense_rating(state, True, False) >= 4 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Outbreak", "Outbreak: Left Infestor", SC2WOL_LOC_ID_OFFSET + 501, LocationType.VANILLA, + lambda state: logic.terran_defense_rating(state, True, False) >= 2 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Outbreak", "Outbreak: Right Infestor", SC2WOL_LOC_ID_OFFSET + 502, LocationType.VANILLA, + lambda state: logic.terran_defense_rating(state, True, False) >= 2 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Outbreak", "Outbreak: North Infested Command Center", SC2WOL_LOC_ID_OFFSET + 503, LocationType.EXTRA, + lambda state: logic.terran_defense_rating(state, True, False) >= 2 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Outbreak", "Outbreak: South Infested Command Center", SC2WOL_LOC_ID_OFFSET + 504, LocationType.EXTRA, + lambda state: logic.terran_defense_rating(state, True, False) >= 2 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Outbreak", "Outbreak: Northwest Bar", SC2WOL_LOC_ID_OFFSET + 505, LocationType.EXTRA, + lambda state: logic.terran_defense_rating(state, True, False) >= 2 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Outbreak", "Outbreak: North Bar", SC2WOL_LOC_ID_OFFSET + 506, LocationType.EXTRA, + lambda state: logic.terran_defense_rating(state, True, False) >= 2 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Outbreak", "Outbreak: South Bar", SC2WOL_LOC_ID_OFFSET + 507, LocationType.EXTRA, + lambda state: logic.terran_defense_rating(state, True, False) >= 2 and + (logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Safe Haven", "Safe Haven: Victory", SC2WOL_LOC_ID_OFFSET + 600, LocationType.VICTORY, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state)), + LocationData("Safe Haven", "Safe Haven: North Nexus", SC2WOL_LOC_ID_OFFSET + 601, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state)), + LocationData("Safe Haven", "Safe Haven: East Nexus", SC2WOL_LOC_ID_OFFSET + 602, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state)), + LocationData("Safe Haven", "Safe Haven: South Nexus", SC2WOL_LOC_ID_OFFSET + 603, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state)), + LocationData("Safe Haven", "Safe Haven: First Terror Fleet", SC2WOL_LOC_ID_OFFSET + 604, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state)), + LocationData("Safe Haven", "Safe Haven: Second Terror Fleet", SC2WOL_LOC_ID_OFFSET + 605, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state)), + LocationData("Safe Haven", "Safe Haven: Third Terror Fleet", SC2WOL_LOC_ID_OFFSET + 606, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state)), + LocationData("Haven's Fall", "Haven's Fall: Victory", SC2WOL_LOC_ID_OFFSET + 700, LocationType.VICTORY, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state) and + logic.terran_defense_rating(state, True) >= 3), + LocationData("Haven's Fall", "Haven's Fall: North Hive", SC2WOL_LOC_ID_OFFSET + 701, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state) and + logic.terran_defense_rating(state, True) >= 3), + LocationData("Haven's Fall", "Haven's Fall: East Hive", SC2WOL_LOC_ID_OFFSET + 702, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state) and + logic.terran_defense_rating(state, True) >= 3), + LocationData("Haven's Fall", "Haven's Fall: South Hive", SC2WOL_LOC_ID_OFFSET + 703, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state) and + logic.terran_defense_rating(state, True) >= 3), + LocationData("Haven's Fall", "Haven's Fall: Northeast Colony Base", SC2WOL_LOC_ID_OFFSET + 704, LocationType.CHALLENGE, + lambda state: logic.terran_respond_to_colony_infestations(state)), + LocationData("Haven's Fall", "Haven's Fall: East Colony Base", SC2WOL_LOC_ID_OFFSET + 705, LocationType.CHALLENGE, + lambda state: logic.terran_respond_to_colony_infestations(state)), + LocationData("Haven's Fall", "Haven's Fall: Middle Colony Base", SC2WOL_LOC_ID_OFFSET + 706, LocationType.CHALLENGE, + lambda state: logic.terran_respond_to_colony_infestations(state)), + LocationData("Haven's Fall", "Haven's Fall: Southeast Colony Base", SC2WOL_LOC_ID_OFFSET + 707, LocationType.CHALLENGE, + lambda state: logic.terran_respond_to_colony_infestations(state)), + LocationData("Haven's Fall", "Haven's Fall: Southwest Colony Base", SC2WOL_LOC_ID_OFFSET + 708, LocationType.CHALLENGE, + lambda state: logic.terran_respond_to_colony_infestations(state)), + LocationData("Haven's Fall", "Haven's Fall: Southwest Gas Pickups", SC2WOL_LOC_ID_OFFSET + 709, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state) and + logic.terran_defense_rating(state, True) >= 3), + LocationData("Haven's Fall", "Haven's Fall: East Gas Pickups", SC2WOL_LOC_ID_OFFSET + 710, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state) and + logic.terran_defense_rating(state, True) >= 3), + LocationData("Haven's Fall", "Haven's Fall: Southeast Gas Pickups", SC2WOL_LOC_ID_OFFSET + 711, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + logic.terran_competent_anti_air(state) and + logic.terran_defense_rating(state, True) >= 3), + LocationData("Smash and Grab", "Smash and Grab: Victory", SC2WOL_LOC_ID_OFFSET + 800, LocationType.VICTORY, + lambda state: logic.terran_common_unit(state) and + (adv_tactics and logic.terran_basic_anti_air(state) + or logic.terran_competent_anti_air(state))), + LocationData("Smash and Grab", "Smash and Grab: First Relic", SC2WOL_LOC_ID_OFFSET + 801, LocationType.VANILLA), + LocationData("Smash and Grab", "Smash and Grab: Second Relic", SC2WOL_LOC_ID_OFFSET + 802, LocationType.VANILLA), + LocationData("Smash and Grab", "Smash and Grab: Third Relic", SC2WOL_LOC_ID_OFFSET + 803, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + (adv_tactics and logic.terran_basic_anti_air(state) + or logic.terran_competent_anti_air(state))), + LocationData("Smash and Grab", "Smash and Grab: Fourth Relic", SC2WOL_LOC_ID_OFFSET + 804, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state) and + (adv_tactics and logic.terran_basic_anti_air(state) + or logic.terran_competent_anti_air(state))), + LocationData("Smash and Grab", "Smash and Grab: First Forcefield Area Busted", SC2WOL_LOC_ID_OFFSET + 805, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + (adv_tactics and logic.terran_basic_anti_air(state) + or logic.terran_competent_anti_air(state))), + LocationData("Smash and Grab", "Smash and Grab: Second Forcefield Area Busted", SC2WOL_LOC_ID_OFFSET + 806, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state) and + (adv_tactics and logic.terran_basic_anti_air(state) + or logic.terran_competent_anti_air(state))), + LocationData("The Dig", "The Dig: Victory", SC2WOL_LOC_ID_OFFSET + 900, LocationType.VICTORY, + lambda state: logic.terran_basic_anti_air(state) + and logic.terran_defense_rating(state, False, True) >= 8 + and logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Dig", "The Dig: Left Relic", SC2WOL_LOC_ID_OFFSET + 901, LocationType.VANILLA, + lambda state: logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Dig", "The Dig: Right Ground Relic", SC2WOL_LOC_ID_OFFSET + 902, LocationType.VANILLA, + lambda state: logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Dig", "The Dig: Right Cliff Relic", SC2WOL_LOC_ID_OFFSET + 903, LocationType.VANILLA, + lambda state: logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Dig", "The Dig: Moebius Base", SC2WOL_LOC_ID_OFFSET + 904, LocationType.EXTRA, + lambda state: logic.marine_medic_upgrade(state) or adv_tactics), + LocationData("The Dig", "The Dig: Door Outer Layer", SC2WOL_LOC_ID_OFFSET + 905, LocationType.EXTRA, + lambda state: logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Dig", "The Dig: Door Thermal Barrier", SC2WOL_LOC_ID_OFFSET + 906, LocationType.EXTRA, + lambda state: logic.terran_basic_anti_air(state) + and logic.terran_defense_rating(state, False, True) >= 8 + and logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Dig", "The Dig: Cutting Through the Core", SC2WOL_LOC_ID_OFFSET + 907, LocationType.EXTRA, + lambda state: logic.terran_basic_anti_air(state) + and logic.terran_defense_rating(state, False, True) >= 8 + and logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Dig", "The Dig: Structure Access Imminent", SC2WOL_LOC_ID_OFFSET + 908, LocationType.EXTRA, + lambda state: logic.terran_basic_anti_air(state) + and logic.terran_defense_rating(state, False, True) >= 8 + and logic.terran_defense_rating(state, False, False) >= 6 + and logic.terran_common_unit(state) + and (logic.marine_medic_upgrade(state) or adv_tactics)), + LocationData("The Moebius Factor", "The Moebius Factor: Victory", SC2WOL_LOC_ID_OFFSET + 1000, LocationType.VICTORY, + lambda state: logic.terran_basic_anti_air(state) and + (logic.terran_air(state) + or state.has_any({ItemNames.MEDIVAC, ItemNames.HERCULES}, player) + and logic.terran_common_unit(state))), + LocationData("The Moebius Factor", "The Moebius Factor: 1st Data Core", SC2WOL_LOC_ID_OFFSET + 1001, LocationType.VANILLA), + LocationData("The Moebius Factor", "The Moebius Factor: 2nd Data Core", SC2WOL_LOC_ID_OFFSET + 1002, LocationType.VANILLA, + lambda state: (logic.terran_air(state) + or state.has_any({ItemNames.MEDIVAC, ItemNames.HERCULES}, player) + and logic.terran_common_unit(state))), + LocationData("The Moebius Factor", "The Moebius Factor: South Rescue", SC2WOL_LOC_ID_OFFSET + 1003, LocationType.EXTRA, + lambda state: logic.terran_can_rescue(state)), + LocationData("The Moebius Factor", "The Moebius Factor: Wall Rescue", SC2WOL_LOC_ID_OFFSET + 1004, LocationType.EXTRA, + lambda state: logic.terran_can_rescue(state)), + LocationData("The Moebius Factor", "The Moebius Factor: Mid Rescue", SC2WOL_LOC_ID_OFFSET + 1005, LocationType.EXTRA, + lambda state: logic.terran_can_rescue(state)), + LocationData("The Moebius Factor", "The Moebius Factor: Nydus Roof Rescue", SC2WOL_LOC_ID_OFFSET + 1006, LocationType.EXTRA, + lambda state: logic.terran_can_rescue(state)), + LocationData("The Moebius Factor", "The Moebius Factor: Alive Inside Rescue", SC2WOL_LOC_ID_OFFSET + 1007, LocationType.EXTRA, + lambda state: logic.terran_can_rescue(state)), + LocationData("The Moebius Factor", "The Moebius Factor: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1008, LocationType.VANILLA, + lambda state: logic.terran_basic_anti_air(state) and + (logic.terran_air(state) + or state.has_any({ItemNames.MEDIVAC, ItemNames.HERCULES}, player) + and logic.terran_common_unit(state))), + LocationData("The Moebius Factor", "The Moebius Factor: 3rd Data Core", SC2WOL_LOC_ID_OFFSET + 1009, LocationType.VANILLA, + lambda state: logic.terran_basic_anti_air(state) and + (logic.terran_air(state) + or state.has_any({ItemNames.MEDIVAC, ItemNames.HERCULES}, player) + and logic.terran_common_unit(state))), + LocationData("Supernova", "Supernova: Victory", SC2WOL_LOC_ID_OFFSET + 1100, LocationType.VICTORY, + lambda state: logic.terran_beats_protoss_deathball(state)), + LocationData("Supernova", "Supernova: West Relic", SC2WOL_LOC_ID_OFFSET + 1101, LocationType.VANILLA), + LocationData("Supernova", "Supernova: North Relic", SC2WOL_LOC_ID_OFFSET + 1102, LocationType.VANILLA), + LocationData("Supernova", "Supernova: South Relic", SC2WOL_LOC_ID_OFFSET + 1103, LocationType.VANILLA, + lambda state: logic.terran_beats_protoss_deathball(state)), + LocationData("Supernova", "Supernova: East Relic", SC2WOL_LOC_ID_OFFSET + 1104, LocationType.VANILLA, + lambda state: logic.terran_beats_protoss_deathball(state)), + LocationData("Supernova", "Supernova: Landing Zone Cleared", SC2WOL_LOC_ID_OFFSET + 1105, LocationType.EXTRA), + LocationData("Supernova", "Supernova: Middle Base", SC2WOL_LOC_ID_OFFSET + 1106, LocationType.EXTRA, + lambda state: logic.terran_beats_protoss_deathball(state)), + LocationData("Supernova", "Supernova: Southeast Base", SC2WOL_LOC_ID_OFFSET + 1107, LocationType.EXTRA, + lambda state: logic.terran_beats_protoss_deathball(state)), + LocationData("Maw of the Void", "Maw of the Void: Victory", SC2WOL_LOC_ID_OFFSET + 1200, LocationType.VICTORY, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Landing Zone Cleared", SC2WOL_LOC_ID_OFFSET + 1201, LocationType.EXTRA), + LocationData("Maw of the Void", "Maw of the Void: Expansion Prisoners", SC2WOL_LOC_ID_OFFSET + 1202, LocationType.VANILLA, + lambda state: adv_tactics or logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: South Close Prisoners", SC2WOL_LOC_ID_OFFSET + 1203, LocationType.VANILLA, + lambda state: adv_tactics or logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: South Far Prisoners", SC2WOL_LOC_ID_OFFSET + 1204, LocationType.VANILLA, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: North Prisoners", SC2WOL_LOC_ID_OFFSET + 1205, LocationType.VANILLA, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Mothership", SC2WOL_LOC_ID_OFFSET + 1206, LocationType.EXTRA, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Expansion Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1207, LocationType.EXTRA, + lambda state: adv_tactics or logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Middle Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1208, LocationType.EXTRA, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Southeast Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1209, LocationType.EXTRA, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Stargate Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1210, LocationType.EXTRA, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Northwest Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1211, LocationType.CHALLENGE, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: West Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1212, LocationType.CHALLENGE, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Maw of the Void", "Maw of the Void: Southwest Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1213, LocationType.CHALLENGE, + lambda state: logic.terran_survives_rip_field(state)), + LocationData("Devil's Playground", "Devil's Playground: Victory", SC2WOL_LOC_ID_OFFSET + 1300, LocationType.VICTORY, + lambda state: adv_tactics or + logic.terran_basic_anti_air(state) and ( + logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Devil's Playground", "Devil's Playground: Tosh's Miners", SC2WOL_LOC_ID_OFFSET + 1301, LocationType.VANILLA), + LocationData("Devil's Playground", "Devil's Playground: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1302, LocationType.VANILLA, + lambda state: adv_tactics or logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player)), + LocationData("Devil's Playground", "Devil's Playground: North Reapers", SC2WOL_LOC_ID_OFFSET + 1303, LocationType.EXTRA), + LocationData("Devil's Playground", "Devil's Playground: Middle Reapers", SC2WOL_LOC_ID_OFFSET + 1304, LocationType.EXTRA, + lambda state: adv_tactics or logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player)), + LocationData("Devil's Playground", "Devil's Playground: Southwest Reapers", SC2WOL_LOC_ID_OFFSET + 1305, LocationType.EXTRA, + lambda state: adv_tactics or logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player)), + LocationData("Devil's Playground", "Devil's Playground: Southeast Reapers", SC2WOL_LOC_ID_OFFSET + 1306, LocationType.EXTRA, + lambda state: adv_tactics or + logic.terran_basic_anti_air(state) and ( + logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Devil's Playground", "Devil's Playground: East Reapers", SC2WOL_LOC_ID_OFFSET + 1307, LocationType.CHALLENGE, + lambda state: logic.terran_basic_anti_air(state) and + (adv_tactics or + logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Devil's Playground", "Devil's Playground: Zerg Cleared", SC2WOL_LOC_ID_OFFSET + 1308, LocationType.CHALLENGE, + lambda state: logic.terran_competent_anti_air(state) and ( + logic.terran_common_unit(state) or state.has(ItemNames.REAPER, player))), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Victory", SC2WOL_LOC_ID_OFFSET + 1400, LocationType.VICTORY, + lambda state: logic.welcome_to_the_jungle_requirement(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Close Relic", SC2WOL_LOC_ID_OFFSET + 1401, LocationType.VANILLA), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: West Relic", SC2WOL_LOC_ID_OFFSET + 1402, LocationType.VANILLA, + lambda state: logic.welcome_to_the_jungle_requirement(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: North-East Relic", SC2WOL_LOC_ID_OFFSET + 1403, LocationType.VANILLA, + lambda state: logic.welcome_to_the_jungle_requirement(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Middle Base", SC2WOL_LOC_ID_OFFSET + 1404, LocationType.EXTRA, + lambda state: logic.welcome_to_the_jungle_requirement(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Main Base", SC2WOL_LOC_ID_OFFSET + 1405, + LocationType.MASTERY, + lambda state: logic.welcome_to_the_jungle_requirement(state) + and logic.terran_beats_protoss_deathball(state) + and logic.terran_base_trasher(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: No Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1406, LocationType.CHALLENGE, + lambda state: logic.welcome_to_the_jungle_requirement(state) + and logic.terran_competent_ground_to_air(state) + and logic.terran_beats_protoss_deathball(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 1 Terrazine Node Sealed", SC2WOL_LOC_ID_OFFSET + 1407, LocationType.CHALLENGE, + lambda state: logic.welcome_to_the_jungle_requirement(state) + and logic.terran_competent_ground_to_air(state) + and logic.terran_beats_protoss_deathball(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 2 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1408, LocationType.CHALLENGE, + lambda state: logic.welcome_to_the_jungle_requirement(state) + and logic.terran_beats_protoss_deathball(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 3 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1409, LocationType.CHALLENGE, + lambda state: logic.welcome_to_the_jungle_requirement(state) + and logic.terran_competent_comp(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 4 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1410, LocationType.EXTRA, + lambda state: logic.welcome_to_the_jungle_requirement(state)), + LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 5 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1411, LocationType.EXTRA, + lambda state: logic.welcome_to_the_jungle_requirement(state)), + LocationData("Breakout", "Breakout: Victory", SC2WOL_LOC_ID_OFFSET + 1500, LocationType.VICTORY), + LocationData("Breakout", "Breakout: Diamondback Prison", SC2WOL_LOC_ID_OFFSET + 1501, LocationType.VANILLA), + LocationData("Breakout", "Breakout: Siege Tank Prison", SC2WOL_LOC_ID_OFFSET + 1502, LocationType.VANILLA), + LocationData("Breakout", "Breakout: First Checkpoint", SC2WOL_LOC_ID_OFFSET + 1503, LocationType.EXTRA), + LocationData("Breakout", "Breakout: Second Checkpoint", SC2WOL_LOC_ID_OFFSET + 1504, LocationType.EXTRA), + LocationData("Ghost of a Chance", "Ghost of a Chance: Victory", SC2WOL_LOC_ID_OFFSET + 1600, LocationType.VICTORY), + LocationData("Ghost of a Chance", "Ghost of a Chance: Terrazine Tank", SC2WOL_LOC_ID_OFFSET + 1601, LocationType.EXTRA), + LocationData("Ghost of a Chance", "Ghost of a Chance: Jorium Stockpile", SC2WOL_LOC_ID_OFFSET + 1602, LocationType.EXTRA), + LocationData("Ghost of a Chance", "Ghost of a Chance: First Island Spectres", SC2WOL_LOC_ID_OFFSET + 1603, LocationType.VANILLA), + LocationData("Ghost of a Chance", "Ghost of a Chance: Second Island Spectres", SC2WOL_LOC_ID_OFFSET + 1604, LocationType.VANILLA), + LocationData("Ghost of a Chance", "Ghost of a Chance: Third Island Spectres", SC2WOL_LOC_ID_OFFSET + 1605, LocationType.VANILLA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Victory", SC2WOL_LOC_ID_OFFSET + 1700, LocationType.VICTORY, + lambda state: logic.great_train_robbery_train_stopper(state) and + logic.terran_basic_anti_air(state)), + LocationData("The Great Train Robbery", "The Great Train Robbery: North Defiler", SC2WOL_LOC_ID_OFFSET + 1701, LocationType.VANILLA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Mid Defiler", SC2WOL_LOC_ID_OFFSET + 1702, LocationType.VANILLA), + LocationData("The Great Train Robbery", "The Great Train Robbery: South Defiler", SC2WOL_LOC_ID_OFFSET + 1703, LocationType.VANILLA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Close Diamondback", SC2WOL_LOC_ID_OFFSET + 1704, LocationType.EXTRA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Northwest Diamondback", SC2WOL_LOC_ID_OFFSET + 1705, LocationType.EXTRA), + LocationData("The Great Train Robbery", "The Great Train Robbery: North Diamondback", SC2WOL_LOC_ID_OFFSET + 1706, LocationType.EXTRA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Northeast Diamondback", SC2WOL_LOC_ID_OFFSET + 1707, LocationType.EXTRA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Southwest Diamondback", SC2WOL_LOC_ID_OFFSET + 1708, LocationType.EXTRA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Southeast Diamondback", SC2WOL_LOC_ID_OFFSET + 1709, LocationType.EXTRA), + LocationData("The Great Train Robbery", "The Great Train Robbery: Kill Team", SC2WOL_LOC_ID_OFFSET + 1710, LocationType.CHALLENGE, + lambda state: (adv_tactics or logic.terran_common_unit(state)) and + logic.great_train_robbery_train_stopper(state) and + logic.terran_basic_anti_air(state)), + LocationData("The Great Train Robbery", "The Great Train Robbery: Flawless", SC2WOL_LOC_ID_OFFSET + 1711, LocationType.CHALLENGE, + lambda state: logic.great_train_robbery_train_stopper(state) and + logic.terran_basic_anti_air(state)), + LocationData("The Great Train Robbery", "The Great Train Robbery: 2 Trains Destroyed", SC2WOL_LOC_ID_OFFSET + 1712, LocationType.EXTRA, + lambda state: logic.great_train_robbery_train_stopper(state)), + LocationData("The Great Train Robbery", "The Great Train Robbery: 4 Trains Destroyed", SC2WOL_LOC_ID_OFFSET + 1713, LocationType.EXTRA, + lambda state: logic.great_train_robbery_train_stopper(state) and + logic.terran_basic_anti_air(state)), + LocationData("The Great Train Robbery", "The Great Train Robbery: 6 Trains Destroyed", SC2WOL_LOC_ID_OFFSET + 1714, LocationType.EXTRA, + lambda state: logic.great_train_robbery_train_stopper(state) and + logic.terran_basic_anti_air(state)), + LocationData("Cutthroat", "Cutthroat: Victory", SC2WOL_LOC_ID_OFFSET + 1800, LocationType.VICTORY, + lambda state: logic.terran_common_unit(state) and + (adv_tactics or logic.terran_basic_anti_air)), + LocationData("Cutthroat", "Cutthroat: Mira Han", SC2WOL_LOC_ID_OFFSET + 1801, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state)), + LocationData("Cutthroat", "Cutthroat: North Relic", SC2WOL_LOC_ID_OFFSET + 1802, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state)), + LocationData("Cutthroat", "Cutthroat: Mid Relic", SC2WOL_LOC_ID_OFFSET + 1803, LocationType.VANILLA), + LocationData("Cutthroat", "Cutthroat: Southwest Relic", SC2WOL_LOC_ID_OFFSET + 1804, LocationType.VANILLA, + lambda state: logic.terran_common_unit(state)), + LocationData("Cutthroat", "Cutthroat: North Command Center", SC2WOL_LOC_ID_OFFSET + 1805, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state)), + LocationData("Cutthroat", "Cutthroat: South Command Center", SC2WOL_LOC_ID_OFFSET + 1806, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state)), + LocationData("Cutthroat", "Cutthroat: West Command Center", SC2WOL_LOC_ID_OFFSET + 1807, LocationType.EXTRA, + lambda state: logic.terran_common_unit(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Victory", SC2WOL_LOC_ID_OFFSET + 1900, LocationType.VICTORY, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Odin", SC2WOL_LOC_ID_OFFSET + 1901, LocationType.EXTRA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Loki", SC2WOL_LOC_ID_OFFSET + 1902, + LocationType.CHALLENGE, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Lab Devourer", SC2WOL_LOC_ID_OFFSET + 1903, LocationType.VANILLA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Engine of Destruction", "Engine of Destruction: North Devourer", SC2WOL_LOC_ID_OFFSET + 1904, LocationType.VANILLA, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Southeast Devourer", SC2WOL_LOC_ID_OFFSET + 1905, LocationType.VANILLA, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Engine of Destruction", "Engine of Destruction: West Base", SC2WOL_LOC_ID_OFFSET + 1906, LocationType.EXTRA, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Northwest Base", SC2WOL_LOC_ID_OFFSET + 1907, LocationType.EXTRA, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Northeast Base", SC2WOL_LOC_ID_OFFSET + 1908, LocationType.EXTRA, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Engine of Destruction", "Engine of Destruction: Southeast Base", SC2WOL_LOC_ID_OFFSET + 1909, LocationType.EXTRA, + lambda state: logic.engine_of_destruction_requirement(state)), + LocationData("Media Blitz", "Media Blitz: Victory", SC2WOL_LOC_ID_OFFSET + 2000, LocationType.VICTORY, + lambda state: logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: Tower 1", SC2WOL_LOC_ID_OFFSET + 2001, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: Tower 2", SC2WOL_LOC_ID_OFFSET + 2002, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: Tower 3", SC2WOL_LOC_ID_OFFSET + 2003, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: Science Facility", SC2WOL_LOC_ID_OFFSET + 2004, LocationType.VANILLA), + LocationData("Media Blitz", "Media Blitz: All Barracks", SC2WOL_LOC_ID_OFFSET + 2005, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: All Factories", SC2WOL_LOC_ID_OFFSET + 2006, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: All Starports", SC2WOL_LOC_ID_OFFSET + 2007, LocationType.EXTRA, + lambda state: adv_tactics or logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: Odin Not Trashed", SC2WOL_LOC_ID_OFFSET + 2008, LocationType.CHALLENGE, + lambda state: logic.terran_competent_comp(state)), + LocationData("Media Blitz", "Media Blitz: Surprise Attack Ends", SC2WOL_LOC_ID_OFFSET + 2009, LocationType.EXTRA), + LocationData("Piercing the Shroud", "Piercing the Shroud: Victory", SC2WOL_LOC_ID_OFFSET + 2100, LocationType.VICTORY, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Piercing the Shroud", "Piercing the Shroud: Holding Cell Relic", SC2WOL_LOC_ID_OFFSET + 2101, LocationType.VANILLA), + LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk Relic", SC2WOL_LOC_ID_OFFSET + 2102, LocationType.VANILLA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Piercing the Shroud", "Piercing the Shroud: First Escape Relic", SC2WOL_LOC_ID_OFFSET + 2103, LocationType.VANILLA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Piercing the Shroud", "Piercing the Shroud: Second Escape Relic", SC2WOL_LOC_ID_OFFSET + 2104, LocationType.VANILLA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk", SC2WOL_LOC_ID_OFFSET + 2105, LocationType.VANILLA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Piercing the Shroud", "Piercing the Shroud: Fusion Reactor", SC2WOL_LOC_ID_OFFSET + 2106, LocationType.EXTRA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Piercing the Shroud", "Piercing the Shroud: Entrance Holding Pen", SC2WOL_LOC_ID_OFFSET + 2107, LocationType.EXTRA), + LocationData("Piercing the Shroud", "Piercing the Shroud: Cargo Bay Warbot", SC2WOL_LOC_ID_OFFSET + 2108, LocationType.EXTRA), + LocationData("Piercing the Shroud", "Piercing the Shroud: Escape Warbot", SC2WOL_LOC_ID_OFFSET + 2109, LocationType.EXTRA, + lambda state: logic.marine_medic_upgrade(state)), + LocationData("Whispers of Doom", "Whispers of Doom: Victory", SC2WOL_LOC_ID_OFFSET + 2200, LocationType.VICTORY), + LocationData("Whispers of Doom", "Whispers of Doom: First Hatchery", SC2WOL_LOC_ID_OFFSET + 2201, LocationType.VANILLA), + LocationData("Whispers of Doom", "Whispers of Doom: Second Hatchery", SC2WOL_LOC_ID_OFFSET + 2202, LocationType.VANILLA), + LocationData("Whispers of Doom", "Whispers of Doom: Third Hatchery", SC2WOL_LOC_ID_OFFSET + 2203, LocationType.VANILLA), + LocationData("Whispers of Doom", "Whispers of Doom: First Prophecy Fragment", SC2WOL_LOC_ID_OFFSET + 2204, LocationType.EXTRA), + LocationData("Whispers of Doom", "Whispers of Doom: Second Prophecy Fragment", SC2WOL_LOC_ID_OFFSET + 2205, LocationType.EXTRA), + LocationData("Whispers of Doom", "Whispers of Doom: Third Prophecy Fragment", SC2WOL_LOC_ID_OFFSET + 2206, LocationType.EXTRA), + LocationData("A Sinister Turn", "A Sinister Turn: Victory", SC2WOL_LOC_ID_OFFSET + 2300, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Robotics Facility", SC2WOL_LOC_ID_OFFSET + 2301, LocationType.VANILLA, + lambda state: adv_tactics or logic.protoss_common_unit(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Dark Shrine", SC2WOL_LOC_ID_OFFSET + 2302, LocationType.VANILLA, + lambda state: adv_tactics or logic.protoss_common_unit(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Templar Archives", SC2WOL_LOC_ID_OFFSET + 2303, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Northeast Base", SC2WOL_LOC_ID_OFFSET + 2304, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Southwest Base", SC2WOL_LOC_ID_OFFSET + 2305, LocationType.CHALLENGE, + lambda state: logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Maar", SC2WOL_LOC_ID_OFFSET + 2306, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Northwest Preserver", SC2WOL_LOC_ID_OFFSET + 2307, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("A Sinister Turn", "A Sinister Turn: Southwest Preserver", SC2WOL_LOC_ID_OFFSET + 2308, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("A Sinister Turn", "A Sinister Turn: East Preserver", SC2WOL_LOC_ID_OFFSET + 2309, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("Echoes of the Future", "Echoes of the Future: Victory", SC2WOL_LOC_ID_OFFSET + 2400, LocationType.VICTORY, + lambda state: adv_tactics and logic.protoss_static_defense(state) or logic.protoss_common_unit(state) and logic.protoss_competent_anti_air(state)), + LocationData("Echoes of the Future", "Echoes of the Future: Close Obelisk", SC2WOL_LOC_ID_OFFSET + 2401, LocationType.VANILLA), + LocationData("Echoes of the Future", "Echoes of the Future: West Obelisk", SC2WOL_LOC_ID_OFFSET + 2402, LocationType.VANILLA, + lambda state: adv_tactics and logic.protoss_static_defense(state) or logic.protoss_common_unit(state)), + LocationData("Echoes of the Future", "Echoes of the Future: Base", SC2WOL_LOC_ID_OFFSET + 2403, LocationType.EXTRA), + LocationData("Echoes of the Future", "Echoes of the Future: Southwest Tendril", SC2WOL_LOC_ID_OFFSET + 2404, LocationType.EXTRA), + LocationData("Echoes of the Future", "Echoes of the Future: Southeast Tendril", SC2WOL_LOC_ID_OFFSET + 2405, LocationType.EXTRA, + lambda state: adv_tactics and logic.protoss_static_defense(state) or logic.protoss_common_unit(state)), + LocationData("Echoes of the Future", "Echoes of the Future: Northeast Tendril", SC2WOL_LOC_ID_OFFSET + 2406, LocationType.EXTRA, + lambda state: adv_tactics and logic.protoss_static_defense(state) or logic.protoss_common_unit(state)), + LocationData("Echoes of the Future", "Echoes of the Future: Northwest Tendril", SC2WOL_LOC_ID_OFFSET + 2407, LocationType.EXTRA, + lambda state: adv_tactics and logic.protoss_static_defense(state) or logic.protoss_common_unit(state)), + LocationData("In Utter Darkness", "In Utter Darkness: Defeat", SC2WOL_LOC_ID_OFFSET + 2500, LocationType.VICTORY), + LocationData("In Utter Darkness", "In Utter Darkness: Protoss Archive", SC2WOL_LOC_ID_OFFSET + 2501, LocationType.VANILLA, + lambda state: logic.last_stand_requirement(state)), + LocationData("In Utter Darkness", "In Utter Darkness: Kills", SC2WOL_LOC_ID_OFFSET + 2502, LocationType.VANILLA, + lambda state: logic.last_stand_requirement(state)), + LocationData("In Utter Darkness", "In Utter Darkness: Urun", SC2WOL_LOC_ID_OFFSET + 2503, LocationType.EXTRA), + LocationData("In Utter Darkness", "In Utter Darkness: Mohandar", SC2WOL_LOC_ID_OFFSET + 2504, LocationType.EXTRA, + lambda state: logic.last_stand_requirement(state)), + LocationData("In Utter Darkness", "In Utter Darkness: Selendis", SC2WOL_LOC_ID_OFFSET + 2505, LocationType.EXTRA, + lambda state: logic.last_stand_requirement(state)), + LocationData("In Utter Darkness", "In Utter Darkness: Artanis", SC2WOL_LOC_ID_OFFSET + 2506, LocationType.EXTRA, + lambda state: logic.last_stand_requirement(state)), + LocationData("Gates of Hell", "Gates of Hell: Victory", SC2WOL_LOC_ID_OFFSET + 2600, LocationType.VICTORY, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: Large Army", SC2WOL_LOC_ID_OFFSET + 2601, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: 2 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2602, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: 4 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2603, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: 6 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2604, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: 8 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2605, LocationType.CHALLENGE, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: Southwest Spore Cannon", SC2WOL_LOC_ID_OFFSET + 2606, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: Northwest Spore Cannon", SC2WOL_LOC_ID_OFFSET + 2607, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: Northeast Spore Cannon", SC2WOL_LOC_ID_OFFSET + 2608, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: East Spore Cannon", SC2WOL_LOC_ID_OFFSET + 2609, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: Southeast Spore Cannon", SC2WOL_LOC_ID_OFFSET + 2610, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Gates of Hell", "Gates of Hell: Expansion Spore Cannon", SC2WOL_LOC_ID_OFFSET + 2611, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state) and + logic.terran_defense_rating(state, True) > 6), + LocationData("Belly of the Beast", "Belly of the Beast: Victory", SC2WOL_LOC_ID_OFFSET + 2700, LocationType.VICTORY), + LocationData("Belly of the Beast", "Belly of the Beast: First Charge", SC2WOL_LOC_ID_OFFSET + 2701, LocationType.EXTRA), + LocationData("Belly of the Beast", "Belly of the Beast: Second Charge", SC2WOL_LOC_ID_OFFSET + 2702, LocationType.EXTRA), + LocationData("Belly of the Beast", "Belly of the Beast: Third Charge", SC2WOL_LOC_ID_OFFSET + 2703, LocationType.EXTRA), + LocationData("Belly of the Beast", "Belly of the Beast: First Group Rescued", SC2WOL_LOC_ID_OFFSET + 2704, LocationType.VANILLA), + LocationData("Belly of the Beast", "Belly of the Beast: Second Group Rescued", SC2WOL_LOC_ID_OFFSET + 2705, LocationType.VANILLA), + LocationData("Belly of the Beast", "Belly of the Beast: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 2706, LocationType.VANILLA), + LocationData("Shatter the Sky", "Shatter the Sky: Victory", SC2WOL_LOC_ID_OFFSET + 2800, LocationType.VICTORY, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: Close Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2801, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: Northwest Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2802, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: Southeast Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2803, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: Southwest Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2804, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: Leviathan", SC2WOL_LOC_ID_OFFSET + 2805, LocationType.VANILLA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: East Hatchery", SC2WOL_LOC_ID_OFFSET + 2806, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: North Hatchery", SC2WOL_LOC_ID_OFFSET + 2807, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state)), + LocationData("Shatter the Sky", "Shatter the Sky: Mid Hatchery", SC2WOL_LOC_ID_OFFSET + 2808, LocationType.EXTRA, + lambda state: logic.terran_competent_comp(state)), + LocationData("All-In", "All-In: Victory", SC2WOL_LOC_ID_OFFSET + 2900, LocationType.VICTORY, + lambda state: logic.all_in_requirement(state)), + LocationData("All-In", "All-In: First Kerrigan Attack", SC2WOL_LOC_ID_OFFSET + 2901, LocationType.EXTRA, + lambda state: logic.all_in_requirement(state)), + LocationData("All-In", "All-In: Second Kerrigan Attack", SC2WOL_LOC_ID_OFFSET + 2902, LocationType.EXTRA, + lambda state: logic.all_in_requirement(state)), + LocationData("All-In", "All-In: Third Kerrigan Attack", SC2WOL_LOC_ID_OFFSET + 2903, LocationType.EXTRA, + lambda state: logic.all_in_requirement(state)), + LocationData("All-In", "All-In: Fourth Kerrigan Attack", SC2WOL_LOC_ID_OFFSET + 2904, LocationType.EXTRA, + lambda state: logic.all_in_requirement(state)), + LocationData("All-In", "All-In: Fifth Kerrigan Attack", SC2WOL_LOC_ID_OFFSET + 2905, LocationType.EXTRA, + lambda state: logic.all_in_requirement(state)), + + # HotS + LocationData("Lab Rat", "Lab Rat: Victory", SC2HOTS_LOC_ID_OFFSET + 100, LocationType.VICTORY, + lambda state: logic.zerg_common_unit(state)), + LocationData("Lab Rat", "Lab Rat: Gather Minerals", SC2HOTS_LOC_ID_OFFSET + 101, LocationType.VANILLA), + LocationData("Lab Rat", "Lab Rat: South Zergling Group", SC2HOTS_LOC_ID_OFFSET + 102, LocationType.VANILLA, + lambda state: adv_tactics or logic.zerg_common_unit(state)), + LocationData("Lab Rat", "Lab Rat: East Zergling Group", SC2HOTS_LOC_ID_OFFSET + 103, LocationType.VANILLA, + lambda state: adv_tactics or logic.zerg_common_unit(state)), + LocationData("Lab Rat", "Lab Rat: West Zergling Group", SC2HOTS_LOC_ID_OFFSET + 104, LocationType.VANILLA, + lambda state: adv_tactics or logic.zerg_common_unit(state)), + LocationData("Lab Rat", "Lab Rat: Hatchery", SC2HOTS_LOC_ID_OFFSET + 105, LocationType.EXTRA), + LocationData("Lab Rat", "Lab Rat: Overlord", SC2HOTS_LOC_ID_OFFSET + 106, LocationType.EXTRA), + LocationData("Lab Rat", "Lab Rat: Gas Turrets", SC2HOTS_LOC_ID_OFFSET + 107, LocationType.EXTRA, + lambda state: adv_tactics or logic.zerg_common_unit(state)), + LocationData("Back in the Saddle", "Back in the Saddle: Victory", SC2HOTS_LOC_ID_OFFSET + 200, LocationType.VICTORY, + lambda state: logic.basic_kerrigan(state) or kerriganless or logic.story_tech_granted), + LocationData("Back in the Saddle", "Back in the Saddle: Defend the Tram", SC2HOTS_LOC_ID_OFFSET + 201, LocationType.EXTRA, + lambda state: logic.basic_kerrigan(state) or kerriganless or logic.story_tech_granted), + LocationData("Back in the Saddle", "Back in the Saddle: Kinetic Blast", SC2HOTS_LOC_ID_OFFSET + 202, LocationType.VANILLA), + LocationData("Back in the Saddle", "Back in the Saddle: Crushing Grip", SC2HOTS_LOC_ID_OFFSET + 203, LocationType.VANILLA), + LocationData("Back in the Saddle", "Back in the Saddle: Reach the Sublevel", SC2HOTS_LOC_ID_OFFSET + 204, LocationType.EXTRA), + LocationData("Back in the Saddle", "Back in the Saddle: Door Section Cleared", SC2HOTS_LOC_ID_OFFSET + 205, LocationType.EXTRA, + lambda state: logic.basic_kerrigan(state) or kerriganless or logic.story_tech_granted), + LocationData("Rendezvous", "Rendezvous: Victory", SC2HOTS_LOC_ID_OFFSET + 300, LocationType.VICTORY, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Rendezvous", "Rendezvous: Right Queen", SC2HOTS_LOC_ID_OFFSET + 301, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Rendezvous", "Rendezvous: Center Queen", SC2HOTS_LOC_ID_OFFSET + 302, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Rendezvous", "Rendezvous: Left Queen", SC2HOTS_LOC_ID_OFFSET + 303, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Rendezvous", "Rendezvous: Hold Out Finished", SC2HOTS_LOC_ID_OFFSET + 304, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Harvest of Screams", "Harvest of Screams: Victory", SC2HOTS_LOC_ID_OFFSET + 400, LocationType.VICTORY, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state)), + LocationData("Harvest of Screams", "Harvest of Screams: First Ursadon Matriarch", SC2HOTS_LOC_ID_OFFSET + 401, LocationType.VANILLA), + LocationData("Harvest of Screams", "Harvest of Screams: North Ursadon Matriarch", SC2HOTS_LOC_ID_OFFSET + 402, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Harvest of Screams", "Harvest of Screams: West Ursadon Matriarch", SC2HOTS_LOC_ID_OFFSET + 403, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Harvest of Screams", "Harvest of Screams: Lost Brood", SC2HOTS_LOC_ID_OFFSET + 404, LocationType.EXTRA), + LocationData("Harvest of Screams", "Harvest of Screams: Northeast Psi-link Spire", SC2HOTS_LOC_ID_OFFSET + 405, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Harvest of Screams", "Harvest of Screams: Northwest Psi-link Spire", SC2HOTS_LOC_ID_OFFSET + 406, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state)), + LocationData("Harvest of Screams", "Harvest of Screams: Southwest Psi-link Spire", SC2HOTS_LOC_ID_OFFSET + 407, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state)), + LocationData("Harvest of Screams", "Harvest of Screams: Nafash", SC2HOTS_LOC_ID_OFFSET + 408, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state)), + LocationData("Shoot the Messenger", "Shoot the Messenger: Victory", SC2HOTS_LOC_ID_OFFSET + 500, LocationType.VICTORY, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Shoot the Messenger", "Shoot the Messenger: East Stasis Chamber", SC2HOTS_LOC_ID_OFFSET + 501, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and logic.zerg_basic_anti_air(state)), + LocationData("Shoot the Messenger", "Shoot the Messenger: Center Stasis Chamber", SC2HOTS_LOC_ID_OFFSET + 502, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) or adv_tactics), + LocationData("Shoot the Messenger", "Shoot the Messenger: West Stasis Chamber", SC2HOTS_LOC_ID_OFFSET + 503, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and logic.zerg_basic_anti_air(state)), + LocationData("Shoot the Messenger", "Shoot the Messenger: Destroy 4 Shuttles", SC2HOTS_LOC_ID_OFFSET + 504, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) and logic.zerg_basic_anti_air(state)), + LocationData("Shoot the Messenger", "Shoot the Messenger: Frozen Expansion", SC2HOTS_LOC_ID_OFFSET + 505, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Shoot the Messenger", "Shoot the Messenger: Southwest Frozen Zerg", SC2HOTS_LOC_ID_OFFSET + 506, LocationType.EXTRA), + LocationData("Shoot the Messenger", "Shoot the Messenger: Southeast Frozen Zerg", SC2HOTS_LOC_ID_OFFSET + 507, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) or adv_tactics), + LocationData("Shoot the Messenger", "Shoot the Messenger: West Frozen Zerg", SC2HOTS_LOC_ID_OFFSET + 508, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) and logic.zerg_basic_anti_air(state)), + LocationData("Shoot the Messenger", "Shoot the Messenger: East Frozen Zerg", SC2HOTS_LOC_ID_OFFSET + 509, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) and logic.zerg_competent_anti_air(state)), + LocationData("Enemy Within", "Enemy Within: Victory", SC2HOTS_LOC_ID_OFFSET + 600, LocationType.VICTORY, + lambda state: logic.zerg_pass_vents(state) + and (logic.story_tech_granted + or state.has_any({ItemNames.ZERGLING_RAPTOR_STRAIN, ItemNames.ROACH, + ItemNames.HYDRALISK, ItemNames.INFESTOR}, player)) + ), + LocationData("Enemy Within", "Enemy Within: Infest Giant Ursadon", SC2HOTS_LOC_ID_OFFSET + 601, LocationType.VANILLA, + lambda state: logic.zerg_pass_vents(state)), + LocationData("Enemy Within", "Enemy Within: First Niadra Evolution", SC2HOTS_LOC_ID_OFFSET + 602, LocationType.VANILLA, + lambda state: logic.zerg_pass_vents(state)), + LocationData("Enemy Within", "Enemy Within: Second Niadra Evolution", SC2HOTS_LOC_ID_OFFSET + 603, LocationType.VANILLA, + lambda state: logic.zerg_pass_vents(state)), + LocationData("Enemy Within", "Enemy Within: Third Niadra Evolution", SC2HOTS_LOC_ID_OFFSET + 604, LocationType.VANILLA, + lambda state: logic.zerg_pass_vents(state)), + LocationData("Enemy Within", "Enemy Within: Warp Drive", SC2HOTS_LOC_ID_OFFSET + 605, LocationType.EXTRA, + lambda state: logic.zerg_pass_vents(state)), + LocationData("Enemy Within", "Enemy Within: Stasis Quadrant", SC2HOTS_LOC_ID_OFFSET + 606, LocationType.EXTRA, + lambda state: logic.zerg_pass_vents(state)), + LocationData("Domination", "Domination: Victory", SC2HOTS_LOC_ID_OFFSET + 700, LocationType.VICTORY, + lambda state: logic.zerg_common_unit(state) and logic.zerg_basic_anti_air(state)), + LocationData("Domination", "Domination: Center Infested Command Center", SC2HOTS_LOC_ID_OFFSET + 701, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Domination", "Domination: North Infested Command Center", SC2HOTS_LOC_ID_OFFSET + 702, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Domination", "Domination: Repel Zagara", SC2HOTS_LOC_ID_OFFSET + 703, LocationType.EXTRA), + LocationData("Domination", "Domination: Close Baneling Nest", SC2HOTS_LOC_ID_OFFSET + 704, LocationType.EXTRA), + LocationData("Domination", "Domination: South Baneling Nest", SC2HOTS_LOC_ID_OFFSET + 705, LocationType.EXTRA, + lambda state: adv_tactics or logic.zerg_common_unit(state)), + LocationData("Domination", "Domination: Southwest Baneling Nest", SC2HOTS_LOC_ID_OFFSET + 706, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Domination", "Domination: Southeast Baneling Nest", SC2HOTS_LOC_ID_OFFSET + 707, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) and logic.zerg_basic_anti_air(state)), + LocationData("Domination", "Domination: North Baneling Nest", SC2HOTS_LOC_ID_OFFSET + 708, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Domination", "Domination: Northeast Baneling Nest", SC2HOTS_LOC_ID_OFFSET + 709, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Fire in the Sky", "Fire in the Sky: Victory", SC2HOTS_LOC_ID_OFFSET + 800, LocationType.VICTORY, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Fire in the Sky", "Fire in the Sky: West Biomass", SC2HOTS_LOC_ID_OFFSET + 801, LocationType.VANILLA), + LocationData("Fire in the Sky", "Fire in the Sky: North Biomass", SC2HOTS_LOC_ID_OFFSET + 802, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Fire in the Sky", "Fire in the Sky: South Biomass", SC2HOTS_LOC_ID_OFFSET + 803, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Fire in the Sky", "Fire in the Sky: Destroy 3 Gorgons", SC2HOTS_LOC_ID_OFFSET + 804, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Fire in the Sky", "Fire in the Sky: Close Zerg Rescue", SC2HOTS_LOC_ID_OFFSET + 805, LocationType.EXTRA), + LocationData("Fire in the Sky", "Fire in the Sky: South Zerg Rescue", SC2HOTS_LOC_ID_OFFSET + 806, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state)), + LocationData("Fire in the Sky", "Fire in the Sky: North Zerg Rescue", SC2HOTS_LOC_ID_OFFSET + 807, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Fire in the Sky", "Fire in the Sky: West Queen Rescue", SC2HOTS_LOC_ID_OFFSET + 808, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Fire in the Sky", "Fire in the Sky: East Queen Rescue", SC2HOTS_LOC_ID_OFFSET + 809, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Old Soldiers", "Old Soldiers: Victory", SC2HOTS_LOC_ID_OFFSET + 900, LocationType.VICTORY, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Old Soldiers", "Old Soldiers: East Science Lab", SC2HOTS_LOC_ID_OFFSET + 901, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Old Soldiers", "Old Soldiers: North Science Lab", SC2HOTS_LOC_ID_OFFSET + 902, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Old Soldiers", "Old Soldiers: Get Nuked", SC2HOTS_LOC_ID_OFFSET + 903, LocationType.EXTRA), + LocationData("Old Soldiers", "Old Soldiers: Entrance Gate", SC2HOTS_LOC_ID_OFFSET + 904, LocationType.EXTRA), + LocationData("Old Soldiers", "Old Soldiers: Citadel Gate", SC2HOTS_LOC_ID_OFFSET + 905, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Old Soldiers", "Old Soldiers: South Expansion", SC2HOTS_LOC_ID_OFFSET + 906, LocationType.EXTRA), + LocationData("Old Soldiers", "Old Soldiers: Rich Mineral Expansion", SC2HOTS_LOC_ID_OFFSET + 907, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Waking the Ancient", "Waking the Ancient: Victory", SC2HOTS_LOC_ID_OFFSET + 1000, LocationType.VICTORY, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Waking the Ancient", "Waking the Ancient: Center Essence Pool", SC2HOTS_LOC_ID_OFFSET + 1001, LocationType.VANILLA), + LocationData("Waking the Ancient", "Waking the Ancient: East Essence Pool", SC2HOTS_LOC_ID_OFFSET + 1002, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + (adv_tactics and logic.zerg_basic_anti_air(state) + or logic.zerg_competent_anti_air(state))), + LocationData("Waking the Ancient", "Waking the Ancient: South Essence Pool", SC2HOTS_LOC_ID_OFFSET + 1003, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + (adv_tactics and logic.zerg_basic_anti_air(state) + or logic.zerg_competent_anti_air(state))), + LocationData("Waking the Ancient", "Waking the Ancient: Finish Feeding", SC2HOTS_LOC_ID_OFFSET + 1004, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Waking the Ancient", "Waking the Ancient: South Proxy Primal Hive", SC2HOTS_LOC_ID_OFFSET + 1005, LocationType.CHALLENGE, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Waking the Ancient", "Waking the Ancient: East Proxy Primal Hive", SC2HOTS_LOC_ID_OFFSET + 1006, LocationType.CHALLENGE, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Waking the Ancient", "Waking the Ancient: South Main Primal Hive", SC2HOTS_LOC_ID_OFFSET + 1007, LocationType.CHALLENGE, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Waking the Ancient", "Waking the Ancient: East Main Primal Hive", SC2HOTS_LOC_ID_OFFSET + 1008, LocationType.CHALLENGE, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_competent_anti_air(state)), + LocationData("The Crucible", "The Crucible: Victory", SC2HOTS_LOC_ID_OFFSET + 1100, LocationType.VICTORY, + lambda state: logic.zerg_competent_defense(state) and + logic.zerg_competent_anti_air(state)), + LocationData("The Crucible", "The Crucible: Tyrannozor", SC2HOTS_LOC_ID_OFFSET + 1101, LocationType.VANILLA, + lambda state: logic.zerg_competent_defense(state) and + logic.zerg_competent_anti_air(state)), + LocationData("The Crucible", "The Crucible: Reach the Pool", SC2HOTS_LOC_ID_OFFSET + 1102, LocationType.VANILLA), + LocationData("The Crucible", "The Crucible: 15 Minutes Remaining", SC2HOTS_LOC_ID_OFFSET + 1103, LocationType.EXTRA, + lambda state: logic.zerg_competent_defense(state) and + logic.zerg_competent_anti_air(state)), + LocationData("The Crucible", "The Crucible: 5 Minutes Remaining", SC2HOTS_LOC_ID_OFFSET + 1104, LocationType.EXTRA, + lambda state: logic.zerg_competent_defense(state) and + logic.zerg_competent_anti_air(state)), + LocationData("The Crucible", "The Crucible: Pincer Attack", SC2HOTS_LOC_ID_OFFSET + 1105, LocationType.EXTRA, + lambda state: logic.zerg_competent_defense(state) and + logic.zerg_competent_anti_air(state)), + LocationData("The Crucible", "The Crucible: Yagdra Claims Brakk's Pack", SC2HOTS_LOC_ID_OFFSET + 1106, LocationType.EXTRA, + lambda state: logic.zerg_competent_defense(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Supreme", "Supreme: Victory", SC2HOTS_LOC_ID_OFFSET + 1200, LocationType.VICTORY, + lambda state: logic.supreme_requirement(state)), + LocationData("Supreme", "Supreme: First Relic", SC2HOTS_LOC_ID_OFFSET + 1201, LocationType.VANILLA, + lambda state: logic.supreme_requirement(state)), + LocationData("Supreme", "Supreme: Second Relic", SC2HOTS_LOC_ID_OFFSET + 1202, LocationType.VANILLA, + lambda state: logic.supreme_requirement(state)), + LocationData("Supreme", "Supreme: Third Relic", SC2HOTS_LOC_ID_OFFSET + 1203, LocationType.VANILLA, + lambda state: logic.supreme_requirement(state)), + LocationData("Supreme", "Supreme: Fourth Relic", SC2HOTS_LOC_ID_OFFSET + 1204, LocationType.VANILLA, + lambda state: logic.supreme_requirement(state)), + LocationData("Supreme", "Supreme: Yagdra", SC2HOTS_LOC_ID_OFFSET + 1205, LocationType.EXTRA, + lambda state: logic.supreme_requirement(state)), + LocationData("Supreme", "Supreme: Kraith", SC2HOTS_LOC_ID_OFFSET + 1206, LocationType.EXTRA, + lambda state: logic.supreme_requirement(state)), + LocationData("Supreme", "Supreme: Slivan", SC2HOTS_LOC_ID_OFFSET + 1207, LocationType.EXTRA, + lambda state: logic.supreme_requirement(state)), + LocationData("Infested", "Infested: Victory", SC2HOTS_LOC_ID_OFFSET + 1300, LocationType.VICTORY, + lambda state: logic.zerg_common_unit(state) and + ((logic.zerg_competent_anti_air(state) and state.has(ItemNames.INFESTOR, player)) or + (adv_tactics and logic.zerg_basic_anti_air(state)))), + LocationData("Infested", "Infested: East Science Facility", SC2HOTS_LOC_ID_OFFSET + 1301, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Infested", "Infested: Center Science Facility", SC2HOTS_LOC_ID_OFFSET + 1302, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Infested", "Infested: West Science Facility", SC2HOTS_LOC_ID_OFFSET + 1303, LocationType.VANILLA, + lambda state: logic.zerg_common_unit(state) and + logic.zerg_basic_anti_air(state) and + logic.spread_creep(state)), + LocationData("Infested", "Infested: First Intro Garrison", SC2HOTS_LOC_ID_OFFSET + 1304, LocationType.EXTRA), + LocationData("Infested", "Infested: Second Intro Garrison", SC2HOTS_LOC_ID_OFFSET + 1305, LocationType.EXTRA), + LocationData("Infested", "Infested: Base Garrison", SC2HOTS_LOC_ID_OFFSET + 1306, LocationType.EXTRA), + LocationData("Infested", "Infested: East Garrison", SC2HOTS_LOC_ID_OFFSET + 1307, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state) + and (adv_tactics or state.has(ItemNames.INFESTOR, player))), + LocationData("Infested", "Infested: Mid Garrison", SC2HOTS_LOC_ID_OFFSET + 1308, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state) + and (adv_tactics or state.has(ItemNames.INFESTOR, player))), + LocationData("Infested", "Infested: North Garrison", SC2HOTS_LOC_ID_OFFSET + 1309, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state) + and (adv_tactics or state.has(ItemNames.INFESTOR, player))), + LocationData("Infested", "Infested: Close Southwest Garrison", SC2HOTS_LOC_ID_OFFSET + 1310, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state) + and (adv_tactics or state.has(ItemNames.INFESTOR, player))), + LocationData("Infested", "Infested: Far Southwest Garrison", SC2HOTS_LOC_ID_OFFSET + 1311, LocationType.EXTRA, + lambda state: logic.zerg_common_unit(state) + and logic.zerg_basic_anti_air(state) + and (adv_tactics or state.has(ItemNames.INFESTOR, player))), + LocationData("Hand of Darkness", "Hand of Darkness: Victory", SC2HOTS_LOC_ID_OFFSET + 1400, LocationType.VICTORY, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: North Brutalisk", SC2HOTS_LOC_ID_OFFSET + 1401, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: South Brutalisk", SC2HOTS_LOC_ID_OFFSET + 1402, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: Kill 1 Hybrid", SC2HOTS_LOC_ID_OFFSET + 1403, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: Kill 2 Hybrid", SC2HOTS_LOC_ID_OFFSET + 1404, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: Kill 3 Hybrid", SC2HOTS_LOC_ID_OFFSET + 1405, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: Kill 4 Hybrid", SC2HOTS_LOC_ID_OFFSET + 1406, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: Kill 5 Hybrid", SC2HOTS_LOC_ID_OFFSET + 1407, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: Kill 6 Hybrid", SC2HOTS_LOC_ID_OFFSET + 1408, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Hand of Darkness", "Hand of Darkness: Kill 7 Hybrid", SC2HOTS_LOC_ID_OFFSET + 1409, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_basic_anti_air(state)), + LocationData("Phantoms of the Void", "Phantoms of the Void: Victory", SC2HOTS_LOC_ID_OFFSET + 1500, LocationType.VICTORY, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("Phantoms of the Void", "Phantoms of the Void: Northwest Crystal", SC2HOTS_LOC_ID_OFFSET + 1501, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("Phantoms of the Void", "Phantoms of the Void: Northeast Crystal", SC2HOTS_LOC_ID_OFFSET + 1502, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("Phantoms of the Void", "Phantoms of the Void: South Crystal", SC2HOTS_LOC_ID_OFFSET + 1503, LocationType.VANILLA), + LocationData("Phantoms of the Void", "Phantoms of the Void: Base Established", SC2HOTS_LOC_ID_OFFSET + 1504, LocationType.EXTRA), + LocationData("Phantoms of the Void", "Phantoms of the Void: Close Temple", SC2HOTS_LOC_ID_OFFSET + 1505, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("Phantoms of the Void", "Phantoms of the Void: Mid Temple", SC2HOTS_LOC_ID_OFFSET + 1506, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("Phantoms of the Void", "Phantoms of the Void: Southeast Temple", SC2HOTS_LOC_ID_OFFSET + 1507, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("Phantoms of the Void", "Phantoms of the Void: Northeast Temple", SC2HOTS_LOC_ID_OFFSET + 1508, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("Phantoms of the Void", "Phantoms of the Void: Northwest Temple", SC2HOTS_LOC_ID_OFFSET + 1509, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + (logic.zerg_competent_anti_air(state) or adv_tactics)), + LocationData("With Friends Like These", "With Friends Like These: Victory", SC2HOTS_LOC_ID_OFFSET + 1600, LocationType.VICTORY), + LocationData("With Friends Like These", "With Friends Like These: Pirate Capital Ship", SC2HOTS_LOC_ID_OFFSET + 1601, LocationType.VANILLA), + LocationData("With Friends Like These", "With Friends Like These: First Mineral Patch", SC2HOTS_LOC_ID_OFFSET + 1602, LocationType.VANILLA), + LocationData("With Friends Like These", "With Friends Like These: Second Mineral Patch", SC2HOTS_LOC_ID_OFFSET + 1603, LocationType.VANILLA), + LocationData("With Friends Like These", "With Friends Like These: Third Mineral Patch", SC2HOTS_LOC_ID_OFFSET + 1604, LocationType.VANILLA), + LocationData("Conviction", "Conviction: Victory", SC2HOTS_LOC_ID_OFFSET + 1700, LocationType.VICTORY, + lambda state: logic.two_kerrigan_actives(state) and + (logic.basic_kerrigan(state) or logic.story_tech_granted) or kerriganless), + LocationData("Conviction", "Conviction: First Secret Documents", SC2HOTS_LOC_ID_OFFSET + 1701, LocationType.VANILLA, + lambda state: logic.two_kerrigan_actives(state) or kerriganless), + LocationData("Conviction", "Conviction: Second Secret Documents", SC2HOTS_LOC_ID_OFFSET + 1702, LocationType.VANILLA, + lambda state: logic.two_kerrigan_actives(state) and + (logic.basic_kerrigan(state) or logic.story_tech_granted) or kerriganless), + LocationData("Conviction", "Conviction: Power Coupling", SC2HOTS_LOC_ID_OFFSET + 1703, LocationType.EXTRA, + lambda state: logic.two_kerrigan_actives(state) or kerriganless), + LocationData("Conviction", "Conviction: Door Blasted", SC2HOTS_LOC_ID_OFFSET + 1704, LocationType.EXTRA, + lambda state: logic.two_kerrigan_actives(state) or kerriganless), + LocationData("Planetfall", "Planetfall: Victory", SC2HOTS_LOC_ID_OFFSET + 1800, LocationType.VICTORY, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: East Gate", SC2HOTS_LOC_ID_OFFSET + 1801, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: Northwest Gate", SC2HOTS_LOC_ID_OFFSET + 1802, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: North Gate", SC2HOTS_LOC_ID_OFFSET + 1803, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: 1 Bile Launcher Deployed", SC2HOTS_LOC_ID_OFFSET + 1804, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: 2 Bile Launchers Deployed", SC2HOTS_LOC_ID_OFFSET + 1805, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: 3 Bile Launchers Deployed", SC2HOTS_LOC_ID_OFFSET + 1806, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: 4 Bile Launchers Deployed", SC2HOTS_LOC_ID_OFFSET + 1807, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: 5 Bile Launchers Deployed", SC2HOTS_LOC_ID_OFFSET + 1808, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: Sons of Korhal", SC2HOTS_LOC_ID_OFFSET + 1809, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: Night Wolves", SC2HOTS_LOC_ID_OFFSET + 1810, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: West Expansion", SC2HOTS_LOC_ID_OFFSET + 1811, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Planetfall", "Planetfall: Mid Expansion", SC2HOTS_LOC_ID_OFFSET + 1812, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Death From Above", "Death From Above: Victory", SC2HOTS_LOC_ID_OFFSET + 1900, LocationType.VICTORY, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Death From Above", "Death From Above: First Power Link", SC2HOTS_LOC_ID_OFFSET + 1901, LocationType.VANILLA), + LocationData("Death From Above", "Death From Above: Second Power Link", SC2HOTS_LOC_ID_OFFSET + 1902, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Death From Above", "Death From Above: Third Power Link", SC2HOTS_LOC_ID_OFFSET + 1903, LocationType.VANILLA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Death From Above", "Death From Above: Expansion Command Center", SC2HOTS_LOC_ID_OFFSET + 1904, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("Death From Above", "Death From Above: Main Path Command Center", SC2HOTS_LOC_ID_OFFSET + 1905, LocationType.EXTRA, + lambda state: logic.zerg_competent_comp(state) and + logic.zerg_competent_anti_air(state)), + LocationData("The Reckoning", "The Reckoning: Victory", SC2HOTS_LOC_ID_OFFSET + 2000, LocationType.VICTORY, + lambda state: logic.the_reckoning_requirement(state)), + LocationData("The Reckoning", "The Reckoning: South Lane", SC2HOTS_LOC_ID_OFFSET + 2001, LocationType.VANILLA, + lambda state: logic.the_reckoning_requirement(state)), + LocationData("The Reckoning", "The Reckoning: North Lane", SC2HOTS_LOC_ID_OFFSET + 2002, LocationType.VANILLA, + lambda state: logic.the_reckoning_requirement(state)), + LocationData("The Reckoning", "The Reckoning: East Lane", SC2HOTS_LOC_ID_OFFSET + 2003, LocationType.VANILLA, + lambda state: logic.the_reckoning_requirement(state)), + LocationData("The Reckoning", "The Reckoning: Odin", SC2HOTS_LOC_ID_OFFSET + 2004, LocationType.EXTRA, + lambda state: logic.the_reckoning_requirement(state)), + + # LotV Prologue + LocationData("Dark Whispers", "Dark Whispers: Victory", SC2LOTV_LOC_ID_OFFSET + 100, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_basic_anti_air(state)), + LocationData("Dark Whispers", "Dark Whispers: First Prisoner Group", SC2LOTV_LOC_ID_OFFSET + 101, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_basic_anti_air(state)), + LocationData("Dark Whispers", "Dark Whispers: Second Prisoner Group", SC2LOTV_LOC_ID_OFFSET + 102, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_basic_anti_air(state)), + LocationData("Dark Whispers", "Dark Whispers: First Pylon", SC2LOTV_LOC_ID_OFFSET + 103, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_basic_anti_air(state)), + LocationData("Dark Whispers", "Dark Whispers: Second Pylon", SC2LOTV_LOC_ID_OFFSET + 104, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_basic_anti_air(state)), + LocationData("Ghosts in the Fog", "Ghosts in the Fog: Victory", SC2LOTV_LOC_ID_OFFSET + 200, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Ghosts in the Fog", "Ghosts in the Fog: South Rock Formation", SC2LOTV_LOC_ID_OFFSET + 201, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Ghosts in the Fog", "Ghosts in the Fog: West Rock Formation", SC2LOTV_LOC_ID_OFFSET + 202, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Ghosts in the Fog", "Ghosts in the Fog: East Rock Formation", SC2LOTV_LOC_ID_OFFSET + 203, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) \ + and logic.protoss_anti_armor_anti_air(state) \ + and logic.protoss_can_attack_behind_chasm(state)), + LocationData("Evil Awoken", "Evil Awoken: Victory", SC2LOTV_LOC_ID_OFFSET + 300, LocationType.VICTORY, + lambda state: adv_tactics or logic.protoss_stalker_upgrade(state)), + LocationData("Evil Awoken", "Evil Awoken: Temple Investigated", SC2LOTV_LOC_ID_OFFSET + 301, LocationType.EXTRA), + LocationData("Evil Awoken", "Evil Awoken: Void Catalyst", SC2LOTV_LOC_ID_OFFSET + 302, LocationType.EXTRA), + LocationData("Evil Awoken", "Evil Awoken: First Particle Cannon", SC2LOTV_LOC_ID_OFFSET + 303, LocationType.VANILLA), + LocationData("Evil Awoken", "Evil Awoken: Second Particle Cannon", SC2LOTV_LOC_ID_OFFSET + 304, LocationType.VANILLA), + LocationData("Evil Awoken", "Evil Awoken: Third Particle Cannon", SC2LOTV_LOC_ID_OFFSET + 305, LocationType.VANILLA), + + + # LotV + LocationData("For Aiur!", "For Aiur!: Victory", SC2LOTV_LOC_ID_OFFSET + 400, LocationType.VICTORY), + LocationData("For Aiur!", "For Aiur!: Southwest Hive", SC2LOTV_LOC_ID_OFFSET + 401, LocationType.VANILLA), + LocationData("For Aiur!", "For Aiur!: Northwest Hive", SC2LOTV_LOC_ID_OFFSET + 402, LocationType.VANILLA), + LocationData("For Aiur!", "For Aiur!: Northeast Hive", SC2LOTV_LOC_ID_OFFSET + 403, LocationType.VANILLA), + LocationData("For Aiur!", "For Aiur!: East Hive", SC2LOTV_LOC_ID_OFFSET + 404, LocationType.VANILLA), + LocationData("For Aiur!", "For Aiur!: West Conduit", SC2LOTV_LOC_ID_OFFSET + 405, LocationType.EXTRA), + LocationData("For Aiur!", "For Aiur!: Middle Conduit", SC2LOTV_LOC_ID_OFFSET + 406, LocationType.EXTRA), + LocationData("For Aiur!", "For Aiur!: Northeast Conduit", SC2LOTV_LOC_ID_OFFSET + 407, LocationType.EXTRA), + LocationData("The Growing Shadow", "The Growing Shadow: Victory", SC2LOTV_LOC_ID_OFFSET + 500, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("The Growing Shadow", "The Growing Shadow: Close Pylon", SC2LOTV_LOC_ID_OFFSET + 501, LocationType.VANILLA), + LocationData("The Growing Shadow", "The Growing Shadow: East Pylon", SC2LOTV_LOC_ID_OFFSET + 502, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("The Growing Shadow", "The Growing Shadow: West Pylon", SC2LOTV_LOC_ID_OFFSET + 503, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("The Growing Shadow", "The Growing Shadow: Nexus", SC2LOTV_LOC_ID_OFFSET + 504, LocationType.EXTRA), + LocationData("The Growing Shadow", "The Growing Shadow: Templar Base", SC2LOTV_LOC_ID_OFFSET + 505, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("The Spear of Adun", "The Spear of Adun: Victory", SC2LOTV_LOC_ID_OFFSET + 600, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("The Spear of Adun", "The Spear of Adun: Close Warp Gate", SC2LOTV_LOC_ID_OFFSET + 601, LocationType.VANILLA), + LocationData("The Spear of Adun", "The Spear of Adun: West Warp Gate", SC2LOTV_LOC_ID_OFFSET + 602, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("The Spear of Adun", "The Spear of Adun: North Warp Gate", SC2LOTV_LOC_ID_OFFSET + 603, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("The Spear of Adun", "The Spear of Adun: North Power Cell", SC2LOTV_LOC_ID_OFFSET + 604, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("The Spear of Adun", "The Spear of Adun: East Power Cell", SC2LOTV_LOC_ID_OFFSET + 605, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("The Spear of Adun", "The Spear of Adun: South Power Cell", SC2LOTV_LOC_ID_OFFSET + 606, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("The Spear of Adun", "The Spear of Adun: Southeast Power Cell", SC2LOTV_LOC_ID_OFFSET + 607, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: Victory", SC2LOTV_LOC_ID_OFFSET + 700, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: Mid EMP Scrambler", SC2LOTV_LOC_ID_OFFSET + 701, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: Southeast EMP Scrambler", SC2LOTV_LOC_ID_OFFSET + 702, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: North EMP Scrambler", SC2LOTV_LOC_ID_OFFSET + 703, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: Mid Stabilizer", SC2LOTV_LOC_ID_OFFSET + 704, LocationType.EXTRA), + LocationData("Sky Shield", "Sky Shield: Southwest Stabilizer", SC2LOTV_LOC_ID_OFFSET + 705, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: Northwest Stabilizer", SC2LOTV_LOC_ID_OFFSET + 706, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: Northeast Stabilizer", SC2LOTV_LOC_ID_OFFSET + 707, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: Southeast Stabilizer", SC2LOTV_LOC_ID_OFFSET + 708, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: West Raynor Base", SC2LOTV_LOC_ID_OFFSET + 709, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Sky Shield", "Sky Shield: East Raynor Base", SC2LOTV_LOC_ID_OFFSET + 710, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_basic_anti_air(state)), + LocationData("Brothers in Arms", "Brothers in Arms: Victory", SC2LOTV_LOC_ID_OFFSET + 800, LocationType.VICTORY, + lambda state: logic.brothers_in_arms_requirement(state)), + LocationData("Brothers in Arms", "Brothers in Arms: Mid Science Facility", SC2LOTV_LOC_ID_OFFSET + 801, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) or logic.take_over_ai_allies), + LocationData("Brothers in Arms", "Brothers in Arms: North Science Facility", SC2LOTV_LOC_ID_OFFSET + 802, LocationType.VANILLA, + lambda state: logic.brothers_in_arms_requirement(state) + or logic.take_over_ai_allies + and logic.advanced_tactics + and ( + logic.terran_common_unit(state) + or logic.protoss_common_unit(state) + ) + ), + LocationData("Brothers in Arms", "Brothers in Arms: South Science Facility", SC2LOTV_LOC_ID_OFFSET + 803, LocationType.VANILLA, + lambda state: logic.brothers_in_arms_requirement(state)), + LocationData("Amon's Reach", "Amon's Reach: Victory", SC2LOTV_LOC_ID_OFFSET + 900, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Amon's Reach", "Amon's Reach: Close Solarite Reserve", SC2LOTV_LOC_ID_OFFSET + 901, LocationType.VANILLA), + LocationData("Amon's Reach", "Amon's Reach: North Solarite Reserve", SC2LOTV_LOC_ID_OFFSET + 902, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Amon's Reach", "Amon's Reach: East Solarite Reserve", SC2LOTV_LOC_ID_OFFSET + 903, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Amon's Reach", "Amon's Reach: West Launch Bay", SC2LOTV_LOC_ID_OFFSET + 904, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Amon's Reach", "Amon's Reach: South Launch Bay", SC2LOTV_LOC_ID_OFFSET + 905, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Amon's Reach", "Amon's Reach: Northwest Launch Bay", SC2LOTV_LOC_ID_OFFSET + 906, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Amon's Reach", "Amon's Reach: East Launch Bay", SC2LOTV_LOC_ID_OFFSET + 907, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Last Stand", "Last Stand: Victory", SC2LOTV_LOC_ID_OFFSET + 1000, LocationType.VICTORY, + lambda state: logic.last_stand_requirement(state)), + LocationData("Last Stand", "Last Stand: West Zenith Stone", SC2LOTV_LOC_ID_OFFSET + 1001, LocationType.VANILLA, + lambda state: logic.last_stand_requirement(state)), + LocationData("Last Stand", "Last Stand: North Zenith Stone", SC2LOTV_LOC_ID_OFFSET + 1002, LocationType.VANILLA, + lambda state: logic.last_stand_requirement(state)), + LocationData("Last Stand", "Last Stand: East Zenith Stone", SC2LOTV_LOC_ID_OFFSET + 1003, LocationType.VANILLA, + lambda state: logic.last_stand_requirement(state)), + LocationData("Last Stand", "Last Stand: 1 Billion Zerg", SC2LOTV_LOC_ID_OFFSET + 1004, LocationType.EXTRA, + lambda state: logic.last_stand_requirement(state)), + LocationData("Last Stand", "Last Stand: 1.5 Billion Zerg", SC2LOTV_LOC_ID_OFFSET + 1005, LocationType.VANILLA, + lambda state: logic.last_stand_requirement(state) and ( + state.has_all({ItemNames.KHAYDARIN_MONOLITH, ItemNames.PHOTON_CANNON, ItemNames.SHIELD_BATTERY}, player) + or state.has_any({ItemNames.SOA_SOLAR_LANCE, ItemNames.SOA_DEPLOY_FENIX}, player) + )), + LocationData("Forbidden Weapon", "Forbidden Weapon: Victory", SC2LOTV_LOC_ID_OFFSET + 1100, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Forbidden Weapon", "Forbidden Weapon: South Solarite", SC2LOTV_LOC_ID_OFFSET + 1101, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Forbidden Weapon", "Forbidden Weapon: North Solarite", SC2LOTV_LOC_ID_OFFSET + 1102, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Forbidden Weapon", "Forbidden Weapon: Northwest Solarite", SC2LOTV_LOC_ID_OFFSET + 1103, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Temple of Unification", "Temple of Unification: Victory", SC2LOTV_LOC_ID_OFFSET + 1200, LocationType.VICTORY, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Temple of Unification", "Temple of Unification: Mid Celestial Lock", SC2LOTV_LOC_ID_OFFSET + 1201, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Temple of Unification", "Temple of Unification: West Celestial Lock", SC2LOTV_LOC_ID_OFFSET + 1202, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Temple of Unification", "Temple of Unification: South Celestial Lock", SC2LOTV_LOC_ID_OFFSET + 1203, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Temple of Unification", "Temple of Unification: East Celestial Lock", SC2LOTV_LOC_ID_OFFSET + 1204, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Temple of Unification", "Temple of Unification: North Celestial Lock", SC2LOTV_LOC_ID_OFFSET + 1205, LocationType.EXTRA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("Temple of Unification", "Temple of Unification: Titanic Warp Prism", SC2LOTV_LOC_ID_OFFSET + 1206, LocationType.VANILLA, + lambda state: logic.protoss_common_unit(state) + and logic.protoss_anti_armor_anti_air(state)), + LocationData("The Infinite Cycle", "The Infinite Cycle: Victory", SC2LOTV_LOC_ID_OFFSET + 1300, LocationType.VICTORY, + lambda state: logic.the_infinite_cycle_requirement(state)), + LocationData("The Infinite Cycle", "The Infinite Cycle: First Hall of Revelation", SC2LOTV_LOC_ID_OFFSET + 1301, LocationType.EXTRA, + lambda state: logic.the_infinite_cycle_requirement(state)), + LocationData("The Infinite Cycle", "The Infinite Cycle: Second Hall of Revelation", SC2LOTV_LOC_ID_OFFSET + 1302, LocationType.EXTRA, + lambda state: logic.the_infinite_cycle_requirement(state)), + LocationData("The Infinite Cycle", "The Infinite Cycle: First Xel'Naga Device", SC2LOTV_LOC_ID_OFFSET + 1303, LocationType.VANILLA, + lambda state: logic.the_infinite_cycle_requirement(state)), + LocationData("The Infinite Cycle", "The Infinite Cycle: Second Xel'Naga Device", SC2LOTV_LOC_ID_OFFSET + 1304, LocationType.VANILLA, + lambda state: logic.the_infinite_cycle_requirement(state)), + LocationData("The Infinite Cycle", "The Infinite Cycle: Third Xel'Naga Device", SC2LOTV_LOC_ID_OFFSET + 1305, LocationType.VANILLA, + lambda state: logic.the_infinite_cycle_requirement(state)), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: Victory", SC2LOTV_LOC_ID_OFFSET + 1400, LocationType.VICTORY, + lambda state: logic.harbinger_of_oblivion_requirement(state)), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: Artanis", SC2LOTV_LOC_ID_OFFSET + 1401, LocationType.EXTRA), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: Northwest Void Crystal", SC2LOTV_LOC_ID_OFFSET + 1402, LocationType.EXTRA, + lambda state: logic.harbinger_of_oblivion_requirement(state)), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: Northeast Void Crystal", SC2LOTV_LOC_ID_OFFSET + 1403, LocationType.EXTRA, + lambda state: logic.harbinger_of_oblivion_requirement(state)), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: Southwest Void Crystal", SC2LOTV_LOC_ID_OFFSET + 1404, LocationType.EXTRA, + lambda state: logic.harbinger_of_oblivion_requirement(state)), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: Southeast Void Crystal", SC2LOTV_LOC_ID_OFFSET + 1405, LocationType.EXTRA, + lambda state: logic.harbinger_of_oblivion_requirement(state)), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: South Xel'Naga Vessel", SC2LOTV_LOC_ID_OFFSET + 1406, LocationType.VANILLA), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: Mid Xel'Naga Vessel", SC2LOTV_LOC_ID_OFFSET + 1407, LocationType.VANILLA, + lambda state: logic.harbinger_of_oblivion_requirement(state)), + LocationData("Harbinger of Oblivion", "Harbinger of Oblivion: North Xel'Naga Vessel", SC2LOTV_LOC_ID_OFFSET + 1408, LocationType.VANILLA, + lambda state: logic.harbinger_of_oblivion_requirement(state)), + LocationData("Unsealing the Past", "Unsealing the Past: Victory", SC2LOTV_LOC_ID_OFFSET + 1500, LocationType.VICTORY, + lambda state: logic.protoss_basic_splash(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Unsealing the Past", "Unsealing the Past: Zerg Cleared", SC2LOTV_LOC_ID_OFFSET + 1501, LocationType.EXTRA), + LocationData("Unsealing the Past", "Unsealing the Past: First Stasis Lock", SC2LOTV_LOC_ID_OFFSET + 1502, LocationType.EXTRA, + lambda state: logic.advanced_tactics \ + or logic.protoss_basic_splash(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Unsealing the Past", "Unsealing the Past: Second Stasis Lock", SC2LOTV_LOC_ID_OFFSET + 1503, LocationType.EXTRA, + lambda state: logic.protoss_basic_splash(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Unsealing the Past", "Unsealing the Past: Third Stasis Lock", SC2LOTV_LOC_ID_OFFSET + 1504, LocationType.EXTRA, + lambda state: logic.protoss_basic_splash(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Unsealing the Past", "Unsealing the Past: Fourth Stasis Lock", SC2LOTV_LOC_ID_OFFSET + 1505, LocationType.EXTRA, + lambda state: logic.protoss_basic_splash(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Unsealing the Past", "Unsealing the Past: South Power Core", SC2LOTV_LOC_ID_OFFSET + 1506, LocationType.VANILLA, + lambda state: logic.protoss_basic_splash(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Unsealing the Past", "Unsealing the Past: East Power Core", SC2LOTV_LOC_ID_OFFSET + 1507, LocationType.VANILLA, + lambda state: logic.protoss_basic_splash(state) + and logic.protoss_anti_light_anti_air(state)), + LocationData("Purification", "Purification: Victory", SC2LOTV_LOC_ID_OFFSET + 1600, LocationType.VICTORY, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: North Sector: West Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1601, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: North Sector: Northeast Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1602, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: North Sector: Southeast Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1603, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: South Sector: West Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1604, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: South Sector: North Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1605, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: South Sector: East Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1606, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: West Sector: West Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1607, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: West Sector: Mid Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1608, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: West Sector: East Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1609, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: East Sector: North Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1610, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: East Sector: West Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1611, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: East Sector: South Null Circuit", SC2LOTV_LOC_ID_OFFSET + 1612, LocationType.EXTRA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Purification", "Purification: Purifier Warden", SC2LOTV_LOC_ID_OFFSET + 1613, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Steps of the Rite", "Steps of the Rite: Victory", SC2LOTV_LOC_ID_OFFSET + 1700, LocationType.VICTORY, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Steps of the Rite", "Steps of the Rite: First Terrazine Fog", SC2LOTV_LOC_ID_OFFSET + 1701, LocationType.EXTRA, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Steps of the Rite", "Steps of the Rite: Southwest Guardian", SC2LOTV_LOC_ID_OFFSET + 1702, LocationType.EXTRA, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Steps of the Rite", "Steps of the Rite: West Guardian", SC2LOTV_LOC_ID_OFFSET + 1703, LocationType.EXTRA, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Steps of the Rite", "Steps of the Rite: Northwest Guardian", SC2LOTV_LOC_ID_OFFSET + 1704, LocationType.EXTRA, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Steps of the Rite", "Steps of the Rite: Northeast Guardian", SC2LOTV_LOC_ID_OFFSET + 1705, LocationType.EXTRA, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Steps of the Rite", "Steps of the Rite: North Mothership", SC2LOTV_LOC_ID_OFFSET + 1706, LocationType.VANILLA, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Steps of the Rite", "Steps of the Rite: South Mothership", SC2LOTV_LOC_ID_OFFSET + 1707, LocationType.VANILLA, + lambda state: logic.steps_of_the_rite_requirement(state)), + LocationData("Rak'Shir", "Rak'Shir: Victory", SC2LOTV_LOC_ID_OFFSET + 1800, LocationType.VICTORY, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Rak'Shir", "Rak'Shir: North Slayn Elemental", SC2LOTV_LOC_ID_OFFSET + 1801, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Rak'Shir", "Rak'Shir: Southwest Slayn Elemental", SC2LOTV_LOC_ID_OFFSET + 1802, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Rak'Shir", "Rak'Shir: East Slayn Elemental", SC2LOTV_LOC_ID_OFFSET + 1803, LocationType.VANILLA, + lambda state: logic.protoss_competent_comp(state)), + LocationData("Templar's Charge", "Templar's Charge: Victory", SC2LOTV_LOC_ID_OFFSET + 1900, LocationType.VICTORY, + lambda state: logic.templars_charge_requirement(state)), + LocationData("Templar's Charge", "Templar's Charge: Northwest Power Core", SC2LOTV_LOC_ID_OFFSET + 1901, LocationType.EXTRA, + lambda state: logic.templars_charge_requirement(state)), + LocationData("Templar's Charge", "Templar's Charge: Northeast Power Core", SC2LOTV_LOC_ID_OFFSET + 1902, LocationType.EXTRA, + lambda state: logic.templars_charge_requirement(state)), + LocationData("Templar's Charge", "Templar's Charge: Southeast Power Core", SC2LOTV_LOC_ID_OFFSET + 1903, LocationType.EXTRA, + lambda state: logic.templars_charge_requirement(state)), + LocationData("Templar's Charge", "Templar's Charge: West Hybrid Statis Chamber", SC2LOTV_LOC_ID_OFFSET + 1904, LocationType.VANILLA, + lambda state: logic.templars_charge_requirement(state)), + LocationData("Templar's Charge", "Templar's Charge: Southeast Hybrid Statis Chamber", SC2LOTV_LOC_ID_OFFSET + 1905, LocationType.VANILLA, + lambda state: logic.protoss_fleet(state)), + LocationData("Templar's Return", "Templar's Return: Victory", SC2LOTV_LOC_ID_OFFSET + 2000, LocationType.VICTORY, + lambda state: logic.templars_return_requirement(state)), + LocationData("Templar's Return", "Templar's Return: Citadel: First Gate", SC2LOTV_LOC_ID_OFFSET + 2001, LocationType.EXTRA), + LocationData("Templar's Return", "Templar's Return: Citadel: Second Gate", SC2LOTV_LOC_ID_OFFSET + 2002, LocationType.EXTRA), + LocationData("Templar's Return", "Templar's Return: Citadel: Power Structure", SC2LOTV_LOC_ID_OFFSET + 2003, LocationType.VANILLA), + LocationData("Templar's Return", "Templar's Return: Temple Grounds: Gather Army", SC2LOTV_LOC_ID_OFFSET + 2004, LocationType.VANILLA, + lambda state: logic.templars_return_requirement(state)), + LocationData("Templar's Return", "Templar's Return: Temple Grounds: Power Structure", SC2LOTV_LOC_ID_OFFSET + 2005, LocationType.VANILLA, + lambda state: logic.templars_return_requirement(state)), + LocationData("Templar's Return", "Templar's Return: Caverns: Purifier", SC2LOTV_LOC_ID_OFFSET + 2006, LocationType.EXTRA, + lambda state: logic.templars_return_requirement(state)), + LocationData("Templar's Return", "Templar's Return: Caverns: Dark Templar", SC2LOTV_LOC_ID_OFFSET + 2007, LocationType.EXTRA, + lambda state: logic.templars_return_requirement(state)), + LocationData("The Host", "The Host: Victory", SC2LOTV_LOC_ID_OFFSET + 2100, LocationType.VICTORY, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: Southeast Void Shard", SC2LOTV_LOC_ID_OFFSET + 2101, LocationType.VICTORY, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: South Void Shard", SC2LOTV_LOC_ID_OFFSET + 2102, LocationType.EXTRA, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: Southwest Void Shard", SC2LOTV_LOC_ID_OFFSET + 2103, LocationType.EXTRA, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: North Void Shard", SC2LOTV_LOC_ID_OFFSET + 2104, LocationType.EXTRA, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: Northwest Void Shard", SC2LOTV_LOC_ID_OFFSET + 2105, LocationType.EXTRA, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: Nerazim Warp in Zone", SC2LOTV_LOC_ID_OFFSET + 2106, LocationType.VANILLA, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: Tal'darim Warp in Zone", SC2LOTV_LOC_ID_OFFSET + 2107, LocationType.VANILLA, + lambda state: logic.the_host_requirement(state)), + LocationData("The Host", "The Host: Purifier Warp in Zone", SC2LOTV_LOC_ID_OFFSET + 2108, LocationType.VANILLA, + lambda state: logic.the_host_requirement(state)), + LocationData("Salvation", "Salvation: Victory", SC2LOTV_LOC_ID_OFFSET + 2200, LocationType.VICTORY, + lambda state: logic.salvation_requirement(state)), + LocationData("Salvation", "Salvation: Fabrication Matrix", SC2LOTV_LOC_ID_OFFSET + 2201, LocationType.EXTRA, + lambda state: logic.salvation_requirement(state)), + LocationData("Salvation", "Salvation: Assault Cluster", SC2LOTV_LOC_ID_OFFSET + 2202, LocationType.EXTRA, + lambda state: logic.salvation_requirement(state)), + LocationData("Salvation", "Salvation: Hull Breach", SC2LOTV_LOC_ID_OFFSET + 2203, LocationType.EXTRA, + lambda state: logic.salvation_requirement(state)), + LocationData("Salvation", "Salvation: Core Critical", SC2LOTV_LOC_ID_OFFSET + 2204, LocationType.EXTRA, + lambda state: logic.salvation_requirement(state)), + + # Epilogue + LocationData("Into the Void", "Into the Void: Victory", SC2LOTV_LOC_ID_OFFSET + 2300, LocationType.VICTORY, + lambda state: logic.into_the_void_requirement(state)), + LocationData("Into the Void", "Into the Void: Corruption Source", SC2LOTV_LOC_ID_OFFSET + 2301, LocationType.EXTRA), + LocationData("Into the Void", "Into the Void: Southwest Forward Position", SC2LOTV_LOC_ID_OFFSET + 2302, LocationType.VANILLA, + lambda state: logic.into_the_void_requirement(state)), + LocationData("Into the Void", "Into the Void: Northwest Forward Position", SC2LOTV_LOC_ID_OFFSET + 2303, LocationType.VANILLA, + lambda state: logic.into_the_void_requirement(state)), + LocationData("Into the Void", "Into the Void: Southeast Forward Position", SC2LOTV_LOC_ID_OFFSET + 2304, LocationType.VANILLA, + lambda state: logic.into_the_void_requirement(state)), + LocationData("Into the Void", "Into the Void: Northeast Forward Position", SC2LOTV_LOC_ID_OFFSET + 2305, LocationType.VANILLA), + LocationData("The Essence of Eternity", "The Essence of Eternity: Victory", SC2LOTV_LOC_ID_OFFSET + 2400, LocationType.VICTORY, + lambda state: logic.essence_of_eternity_requirement(state)), + LocationData("The Essence of Eternity", "The Essence of Eternity: Void Trashers", SC2LOTV_LOC_ID_OFFSET + 2401, LocationType.EXTRA), + LocationData("Amon's Fall", "Amon's Fall: Victory", SC2LOTV_LOC_ID_OFFSET + 2500, LocationType.VICTORY, + lambda state: logic.amons_fall_requirement(state)), + + # Nova Covert Ops + LocationData("The Escape", "The Escape: Victory", SC2NCO_LOC_ID_OFFSET + 100, LocationType.VICTORY, + lambda state: logic.the_escape_requirement(state)), + LocationData("The Escape", "The Escape: Rifle", SC2NCO_LOC_ID_OFFSET + 101, LocationType.VANILLA, + lambda state: logic.the_escape_first_stage_requirement(state)), + LocationData("The Escape", "The Escape: Grenades", SC2NCO_LOC_ID_OFFSET + 102, LocationType.VANILLA, + lambda state: logic.the_escape_first_stage_requirement(state)), + LocationData("The Escape", "The Escape: Agent Delta", SC2NCO_LOC_ID_OFFSET + 103, LocationType.VANILLA, + lambda state: logic.the_escape_requirement(state)), + LocationData("The Escape", "The Escape: Agent Pierce", SC2NCO_LOC_ID_OFFSET + 104, LocationType.VANILLA, + lambda state: logic.the_escape_requirement(state)), + LocationData("The Escape", "The Escape: Agent Stone", SC2NCO_LOC_ID_OFFSET + 105, LocationType.VANILLA, + lambda state: logic.the_escape_requirement(state)), + LocationData("Sudden Strike", "Sudden Strike: Victory", SC2NCO_LOC_ID_OFFSET + 200, LocationType.VICTORY, + lambda state: logic.sudden_strike_can_reach_objectives(state)), + LocationData("Sudden Strike", "Sudden Strike: Research Center", SC2NCO_LOC_ID_OFFSET + 201, LocationType.VANILLA, + lambda state: logic.sudden_strike_can_reach_objectives(state)), + LocationData("Sudden Strike", "Sudden Strike: Weaponry Labs", SC2NCO_LOC_ID_OFFSET + 202, LocationType.VANILLA, + lambda state: logic.sudden_strike_requirement(state)), + LocationData("Sudden Strike", "Sudden Strike: Brutalisk", SC2NCO_LOC_ID_OFFSET + 203, LocationType.EXTRA, + lambda state: logic.sudden_strike_requirement(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: Victory", SC2NCO_LOC_ID_OFFSET + 300, LocationType.VICTORY, + lambda state: logic.enemy_intelligence_third_stage_requirement(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: West Garrison", SC2NCO_LOC_ID_OFFSET + 301, LocationType.EXTRA, + lambda state: logic.enemy_intelligence_first_stage_requirement(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: Close Garrison", SC2NCO_LOC_ID_OFFSET + 302, LocationType.EXTRA, + lambda state: logic.enemy_intelligence_first_stage_requirement(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: Northeast Garrison", SC2NCO_LOC_ID_OFFSET + 303, LocationType.EXTRA, + lambda state: logic.enemy_intelligence_first_stage_requirement(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: Southeast Garrison", SC2NCO_LOC_ID_OFFSET + 304, LocationType.EXTRA, + lambda state: logic.enemy_intelligence_first_stage_requirement(state) + and logic.enemy_intelligence_cliff_garrison(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: South Garrison", SC2NCO_LOC_ID_OFFSET + 305, LocationType.EXTRA, + lambda state: logic.enemy_intelligence_first_stage_requirement(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: All Garrisons", SC2NCO_LOC_ID_OFFSET + 306, LocationType.VANILLA, + lambda state: logic.enemy_intelligence_first_stage_requirement(state) + and logic.enemy_intelligence_cliff_garrison(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: Forces Rescued", SC2NCO_LOC_ID_OFFSET + 307, LocationType.VANILLA, + lambda state: logic.enemy_intelligence_first_stage_requirement(state)), + LocationData("Enemy Intelligence", "Enemy Intelligence: Communications Hub", SC2NCO_LOC_ID_OFFSET + 308, LocationType.VANILLA, + lambda state: logic.enemy_intelligence_second_stage_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: Victory", SC2NCO_LOC_ID_OFFSET + 400, LocationType.VICTORY, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: North Base: West Hatchery", SC2NCO_LOC_ID_OFFSET + 401, LocationType.VANILLA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: North Base: North Hatchery", SC2NCO_LOC_ID_OFFSET + 402, LocationType.VANILLA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: North Base: East Hatchery", SC2NCO_LOC_ID_OFFSET + 403, LocationType.VANILLA), + LocationData("Trouble In Paradise", "Trouble In Paradise: South Base: Northwest Hatchery", SC2NCO_LOC_ID_OFFSET + 404, LocationType.VANILLA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: South Base: Southwest Hatchery", SC2NCO_LOC_ID_OFFSET + 405, LocationType.VANILLA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: South Base: East Hatchery", SC2NCO_LOC_ID_OFFSET + 406, LocationType.VANILLA), + LocationData("Trouble In Paradise", "Trouble In Paradise: North Shield Projector", SC2NCO_LOC_ID_OFFSET + 407, LocationType.EXTRA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: East Shield Projector", SC2NCO_LOC_ID_OFFSET + 408, LocationType.EXTRA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: South Shield Projector", SC2NCO_LOC_ID_OFFSET + 409, LocationType.EXTRA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: West Shield Projector", SC2NCO_LOC_ID_OFFSET + 410, LocationType.EXTRA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Trouble In Paradise", "Trouble In Paradise: Fleet Beacon", SC2NCO_LOC_ID_OFFSET + 411, LocationType.VANILLA, + lambda state: logic.trouble_in_paradise_requirement(state)), + LocationData("Night Terrors", "Night Terrors: Victory", SC2NCO_LOC_ID_OFFSET + 500, LocationType.VICTORY, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: 1 Terrazine Node Collected", SC2NCO_LOC_ID_OFFSET + 501, LocationType.EXTRA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: 2 Terrazine Nodes Collected", SC2NCO_LOC_ID_OFFSET + 502, LocationType.EXTRA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: 3 Terrazine Nodes Collected", SC2NCO_LOC_ID_OFFSET + 503, LocationType.EXTRA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: 4 Terrazine Nodes Collected", SC2NCO_LOC_ID_OFFSET + 504, LocationType.EXTRA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: 5 Terrazine Nodes Collected", SC2NCO_LOC_ID_OFFSET + 505, LocationType.EXTRA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: HERC Outpost", SC2NCO_LOC_ID_OFFSET + 506, LocationType.VANILLA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: Umojan Mine", SC2NCO_LOC_ID_OFFSET + 507, LocationType.EXTRA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: Blightbringer", SC2NCO_LOC_ID_OFFSET + 508, LocationType.VANILLA, + lambda state: logic.night_terrors_requirement(state) + and logic.nova_ranged_weapon(state) + and state.has_any( + {ItemNames.NOVA_HELLFIRE_SHOTGUN, ItemNames.NOVA_PULSE_GRENADES, ItemNames.NOVA_STIM_INFUSION, + ItemNames.NOVA_HOLO_DECOY}, player)), + LocationData("Night Terrors", "Night Terrors: Science Facility", SC2NCO_LOC_ID_OFFSET + 509, LocationType.EXTRA, + lambda state: logic.night_terrors_requirement(state)), + LocationData("Night Terrors", "Night Terrors: Eradicators", SC2NCO_LOC_ID_OFFSET + 510, LocationType.VANILLA, + lambda state: logic.night_terrors_requirement(state) + and logic.nova_any_weapon(state)), + LocationData("Flashpoint", "Flashpoint: Victory", SC2NCO_LOC_ID_OFFSET + 600, LocationType.VICTORY, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Close North Evidence Coordinates", SC2NCO_LOC_ID_OFFSET + 601, LocationType.EXTRA, + lambda state: state.has_any( + {ItemNames.LIBERATOR_RAID_ARTILLERY, ItemNames.RAVEN_HUNTER_SEEKER_WEAPON}, player) + or logic.terran_common_unit(state)), + LocationData("Flashpoint", "Flashpoint: Close East Evidence Coordinates", SC2NCO_LOC_ID_OFFSET + 602, LocationType.EXTRA, + lambda state: state.has_any( + {ItemNames.LIBERATOR_RAID_ARTILLERY, ItemNames.RAVEN_HUNTER_SEEKER_WEAPON}, player) + or logic.terran_common_unit(state)), + LocationData("Flashpoint", "Flashpoint: Far North Evidence Coordinates", SC2NCO_LOC_ID_OFFSET + 603, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Far East Evidence Coordinates", SC2NCO_LOC_ID_OFFSET + 604, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Experimental Weapon", SC2NCO_LOC_ID_OFFSET + 605, LocationType.VANILLA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Northwest Subway Entrance", SC2NCO_LOC_ID_OFFSET + 606, LocationType.VANILLA, + lambda state: state.has_any( + {ItemNames.LIBERATOR_RAID_ARTILLERY, ItemNames.RAVEN_HUNTER_SEEKER_WEAPON}, player) + and logic.terran_common_unit(state) + or logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Southeast Subway Entrance", SC2NCO_LOC_ID_OFFSET + 607, LocationType.VANILLA, + lambda state: state.has_any( + {ItemNames.LIBERATOR_RAID_ARTILLERY, ItemNames.RAVEN_HUNTER_SEEKER_WEAPON}, player) + and logic.terran_common_unit(state) + or logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Northeast Subway Entrance", SC2NCO_LOC_ID_OFFSET + 608, LocationType.VANILLA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Expansion Hatchery", SC2NCO_LOC_ID_OFFSET + 609, LocationType.EXTRA, + lambda state: state.has(ItemNames.LIBERATOR_RAID_ARTILLERY, player) and logic.terran_common_unit(state) + or logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Baneling Spawns", SC2NCO_LOC_ID_OFFSET + 610, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Mutalisk Spawns", SC2NCO_LOC_ID_OFFSET + 611, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Nydus Worm Spawns", SC2NCO_LOC_ID_OFFSET + 612, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Lurker Spawns", SC2NCO_LOC_ID_OFFSET + 613, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Brood Lord Spawns", SC2NCO_LOC_ID_OFFSET + 614, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("Flashpoint", "Flashpoint: Ultralisk Spawns", SC2NCO_LOC_ID_OFFSET + 615, LocationType.EXTRA, + lambda state: logic.flashpoint_far_requirement(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Victory", SC2NCO_LOC_ID_OFFSET + 700, LocationType.VICTORY, + lambda state: logic.enemy_shadow_victory(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Sewers: Domination Visor", SC2NCO_LOC_ID_OFFSET + 701, LocationType.VANILLA, + lambda state: logic.enemy_shadow_domination(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Sewers: Resupply Crate", SC2NCO_LOC_ID_OFFSET + 702, LocationType.EXTRA, + lambda state: logic.enemy_shadow_first_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Sewers: Facility Access", SC2NCO_LOC_ID_OFFSET + 703, LocationType.VANILLA, + lambda state: logic.enemy_shadow_first_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: Northwest Door Lock", SC2NCO_LOC_ID_OFFSET + 704, LocationType.VANILLA, + lambda state: logic.enemy_shadow_door_controls(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: Southeast Door Lock", SC2NCO_LOC_ID_OFFSET + 705, LocationType.VANILLA, + lambda state: logic.enemy_shadow_door_controls(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: Blazefire Gunblade", SC2NCO_LOC_ID_OFFSET + 706, LocationType.VANILLA, + lambda state: logic.enemy_shadow_second_stage(state) + and (story_tech_granted + or state.has(ItemNames.NOVA_BLINK, player) + or (adv_tactics and state.has_all({ItemNames.NOVA_DOMINATION, ItemNames.NOVA_HOLO_DECOY, ItemNames.NOVA_JUMP_SUIT_MODULE}, player)) + ) + ), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: Blink Suit", SC2NCO_LOC_ID_OFFSET + 707, LocationType.VANILLA, + lambda state: logic.enemy_shadow_second_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: Advanced Weaponry", SC2NCO_LOC_ID_OFFSET + 708, LocationType.VANILLA, + lambda state: logic.enemy_shadow_second_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: Entrance Resupply Crate", SC2NCO_LOC_ID_OFFSET + 709, LocationType.EXTRA, + lambda state: logic.enemy_shadow_first_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: West Resupply Crate", SC2NCO_LOC_ID_OFFSET + 710, LocationType.EXTRA, + lambda state: logic.enemy_shadow_second_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: North Resupply Crate", SC2NCO_LOC_ID_OFFSET + 711, LocationType.EXTRA, + lambda state: logic.enemy_shadow_second_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: East Resupply Crate", SC2NCO_LOC_ID_OFFSET + 712, LocationType.EXTRA, + lambda state: logic.enemy_shadow_second_stage(state)), + LocationData("In the Enemy's Shadow", "In the Enemy's Shadow: Facility: South Resupply Crate", SC2NCO_LOC_ID_OFFSET + 713, LocationType.EXTRA, + lambda state: logic.enemy_shadow_second_stage(state)), + LocationData("Dark Skies", "Dark Skies: Victory", SC2NCO_LOC_ID_OFFSET + 800, LocationType.VICTORY, + lambda state: logic.dark_skies_requirement(state)), + LocationData("Dark Skies", "Dark Skies: First Squadron of Dominion Fleet", SC2NCO_LOC_ID_OFFSET + 801, LocationType.EXTRA, + lambda state: logic.dark_skies_requirement(state)), + LocationData("Dark Skies", "Dark Skies: Remainder of Dominion Fleet", SC2NCO_LOC_ID_OFFSET + 802, LocationType.EXTRA, + lambda state: logic.dark_skies_requirement(state)), + LocationData("Dark Skies", "Dark Skies: Ji'nara", SC2NCO_LOC_ID_OFFSET + 803, LocationType.EXTRA, + lambda state: logic.dark_skies_requirement(state)), + LocationData("Dark Skies", "Dark Skies: Science Facility", SC2NCO_LOC_ID_OFFSET + 804, LocationType.VANILLA, + lambda state: logic.dark_skies_requirement(state)), + LocationData("End Game", "End Game: Victory", SC2NCO_LOC_ID_OFFSET + 900, LocationType.VICTORY, + lambda state: logic.end_game_requirement(state) and logic.nova_any_weapon(state)), + LocationData("End Game", "End Game: Xanthos", SC2NCO_LOC_ID_OFFSET + 901, LocationType.VANILLA, + lambda state: logic.end_game_requirement(state)), + ] + + beat_events = [] + # Filtering out excluded locations + if world is not None: + excluded_location_types = get_location_types(world, LocationInclusion.option_disabled) + plando_locations = get_plando_locations(world) + exclude_locations = get_option_value(world, "exclude_locations") + location_table = [location for location in location_table + if (LocationType is LocationType.VICTORY or location.name not in exclude_locations) + and location.type not in excluded_location_types + or location.name in plando_locations] + for i, location_data in enumerate(location_table): + # Removing all item-based logic on No Logic + if logic_level == RequiredTactics.option_no_logic: + location_data = location_data._replace(rule=Location.access_rule) + location_table[i] = location_data + # Generating Beat event locations + if location_data.name.endswith((": Victory", ": Defeat")): + beat_events.append( + location_data._replace(name="Beat " + location_data.name.rsplit(": ", 1)[0], code=None) + ) + return tuple(location_table + beat_events) + +lookup_location_id_to_type = {loc.code: loc.type for loc in get_locations(None) if loc.code is not None} \ No newline at end of file diff --git a/worlds/sc2/MissionTables.py b/worlds/sc2/MissionTables.py new file mode 100644 index 0000000000..99b6448aff --- /dev/null +++ b/worlds/sc2/MissionTables.py @@ -0,0 +1,737 @@ +from typing import NamedTuple, Dict, List, Set, Union, Literal, Iterable, Callable +from enum import IntEnum, Enum + + +class SC2Race(IntEnum): + ANY = 0 + TERRAN = 1 + ZERG = 2 + PROTOSS = 3 + + +class MissionPools(IntEnum): + STARTER = 0 + EASY = 1 + MEDIUM = 2 + HARD = 3 + VERY_HARD = 4 + FINAL = 5 + + +class SC2CampaignGoalPriority(IntEnum): + """ + Campaign's priority to goal election + """ + NONE = 0 + MINI_CAMPAIGN = 1 # A goal shouldn't be in a mini-campaign if there's at least one 'big' campaign + HARD = 2 # A campaign ending with a hard mission + VERY_HARD = 3 # A campaign ending with a very hard mission + EPILOGUE = 4 # Epilogue shall be always preferred as the goal if present + + +class SC2Campaign(Enum): + + def __new__(cls, *args, **kwargs): + value = len(cls.__members__) + 1 + obj = object.__new__(cls) + obj._value_ = value + return obj + + def __init__(self, campaign_id: int, name: str, goal_priority: SC2CampaignGoalPriority, race: SC2Race): + self.id = campaign_id + self.campaign_name = name + self.goal_priority = goal_priority + self.race = race + + GLOBAL = 0, "Global", SC2CampaignGoalPriority.NONE, SC2Race.ANY + WOL = 1, "Wings of Liberty", SC2CampaignGoalPriority.VERY_HARD, SC2Race.TERRAN + PROPHECY = 2, "Prophecy", SC2CampaignGoalPriority.MINI_CAMPAIGN, SC2Race.PROTOSS + HOTS = 3, "Heart of the Swarm", SC2CampaignGoalPriority.HARD, SC2Race.ZERG + PROLOGUE = 4, "Whispers of Oblivion (Legacy of the Void: Prologue)", SC2CampaignGoalPriority.MINI_CAMPAIGN, SC2Race.PROTOSS + LOTV = 5, "Legacy of the Void", SC2CampaignGoalPriority.VERY_HARD, SC2Race.PROTOSS + EPILOGUE = 6, "Into the Void (Legacy of the Void: Epilogue)", SC2CampaignGoalPriority.EPILOGUE, SC2Race.ANY + NCO = 7, "Nova Covert Ops", SC2CampaignGoalPriority.HARD, SC2Race.TERRAN + + +class SC2Mission(Enum): + + def __new__(cls, *args, **kwargs): + value = len(cls.__members__) + 1 + obj = object.__new__(cls) + obj._value_ = value + return obj + + def __init__(self, mission_id: int, name: str, campaign: SC2Campaign, area: str, race: SC2Race, pool: MissionPools, map_file: str, build: bool = True): + self.id = mission_id + self.mission_name = name + self.campaign = campaign + self.area = area + self.race = race + self.pool = pool + self.map_file = map_file + self.build = build + + # Wings of Liberty + LIBERATION_DAY = 1, "Liberation Day", SC2Campaign.WOL, "Mar Sara", SC2Race.ANY, MissionPools.STARTER, "ap_liberation_day", False + THE_OUTLAWS = 2, "The Outlaws", SC2Campaign.WOL, "Mar Sara", SC2Race.TERRAN, MissionPools.EASY, "ap_the_outlaws" + ZERO_HOUR = 3, "Zero Hour", SC2Campaign.WOL, "Mar Sara", SC2Race.TERRAN, MissionPools.EASY, "ap_zero_hour" + EVACUATION = 4, "Evacuation", SC2Campaign.WOL, "Colonist", SC2Race.TERRAN, MissionPools.EASY, "ap_evacuation" + OUTBREAK = 5, "Outbreak", SC2Campaign.WOL, "Colonist", SC2Race.TERRAN, MissionPools.EASY, "ap_outbreak" + SAFE_HAVEN = 6, "Safe Haven", SC2Campaign.WOL, "Colonist", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_safe_haven" + HAVENS_FALL = 7, "Haven's Fall", SC2Campaign.WOL, "Colonist", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_havens_fall" + SMASH_AND_GRAB = 8, "Smash and Grab", SC2Campaign.WOL, "Artifact", SC2Race.TERRAN, MissionPools.EASY, "ap_smash_and_grab" + THE_DIG = 9, "The Dig", SC2Campaign.WOL, "Artifact", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_the_dig" + THE_MOEBIUS_FACTOR = 10, "The Moebius Factor", SC2Campaign.WOL, "Artifact", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_the_moebius_factor" + SUPERNOVA = 11, "Supernova", SC2Campaign.WOL, "Artifact", SC2Race.TERRAN, MissionPools.HARD, "ap_supernova" + MAW_OF_THE_VOID = 12, "Maw of the Void", SC2Campaign.WOL, "Artifact", SC2Race.TERRAN, MissionPools.HARD, "ap_maw_of_the_void" + DEVILS_PLAYGROUND = 13, "Devil's Playground", SC2Campaign.WOL, "Covert", SC2Race.TERRAN, MissionPools.EASY, "ap_devils_playground" + WELCOME_TO_THE_JUNGLE = 14, "Welcome to the Jungle", SC2Campaign.WOL, "Covert", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_welcome_to_the_jungle" + BREAKOUT = 15, "Breakout", SC2Campaign.WOL, "Covert", SC2Race.ANY, MissionPools.STARTER, "ap_breakout", False + GHOST_OF_A_CHANCE = 16, "Ghost of a Chance", SC2Campaign.WOL, "Covert", SC2Race.ANY, MissionPools.STARTER, "ap_ghost_of_a_chance", False + THE_GREAT_TRAIN_ROBBERY = 17, "The Great Train Robbery", SC2Campaign.WOL, "Rebellion", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_the_great_train_robbery" + CUTTHROAT = 18, "Cutthroat", SC2Campaign.WOL, "Rebellion", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_cutthroat" + ENGINE_OF_DESTRUCTION = 19, "Engine of Destruction", SC2Campaign.WOL, "Rebellion", SC2Race.TERRAN, MissionPools.HARD, "ap_engine_of_destruction" + MEDIA_BLITZ = 20, "Media Blitz", SC2Campaign.WOL, "Rebellion", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_media_blitz" + PIERCING_OF_THE_SHROUD = 21, "Piercing the Shroud", SC2Campaign.WOL, "Rebellion", SC2Race.TERRAN, MissionPools.STARTER, "ap_piercing_the_shroud", False + GATES_OF_HELL = 26, "Gates of Hell", SC2Campaign.WOL, "Char", SC2Race.TERRAN, MissionPools.HARD, "ap_gates_of_hell" + BELLY_OF_THE_BEAST = 27, "Belly of the Beast", SC2Campaign.WOL, "Char", SC2Race.ANY, MissionPools.STARTER, "ap_belly_of_the_beast", False + SHATTER_THE_SKY = 28, "Shatter the Sky", SC2Campaign.WOL, "Char", SC2Race.TERRAN, MissionPools.HARD, "ap_shatter_the_sky" + ALL_IN = 29, "All-In", SC2Campaign.WOL, "Char", SC2Race.TERRAN, MissionPools.VERY_HARD, "ap_all_in" + + # Prophecy + WHISPERS_OF_DOOM = 22, "Whispers of Doom", SC2Campaign.PROPHECY, "_1", SC2Race.ANY, MissionPools.STARTER, "ap_whispers_of_doom", False + A_SINISTER_TURN = 23, "A Sinister Turn", SC2Campaign.PROPHECY, "_2", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_a_sinister_turn" + ECHOES_OF_THE_FUTURE = 24, "Echoes of the Future", SC2Campaign.PROPHECY, "_3", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_echoes_of_the_future" + IN_UTTER_DARKNESS = 25, "In Utter Darkness", SC2Campaign.PROPHECY, "_4", SC2Race.PROTOSS, MissionPools.HARD, "ap_in_utter_darkness" + + # Heart of the Swarm + LAB_RAT = 30, "Lab Rat", SC2Campaign.HOTS, "Umoja", SC2Race.ZERG, MissionPools.STARTER, "ap_lab_rat" + BACK_IN_THE_SADDLE = 31, "Back in the Saddle", SC2Campaign.HOTS, "Umoja", SC2Race.ANY, MissionPools.STARTER, "ap_back_in_the_saddle", False + RENDEZVOUS = 32, "Rendezvous", SC2Campaign.HOTS, "Umoja", SC2Race.ZERG, MissionPools.EASY, "ap_rendezvous" + HARVEST_OF_SCREAMS = 33, "Harvest of Screams", SC2Campaign.HOTS, "Kaldir", SC2Race.ZERG, MissionPools.EASY, "ap_harvest_of_screams" + SHOOT_THE_MESSENGER = 34, "Shoot the Messenger", SC2Campaign.HOTS, "Kaldir", SC2Race.ZERG, MissionPools.EASY, "ap_shoot_the_messenger" + ENEMY_WITHIN = 35, "Enemy Within", SC2Campaign.HOTS, "Kaldir", SC2Race.ANY, MissionPools.EASY, "ap_enemy_within", False + DOMINATION = 36, "Domination", SC2Campaign.HOTS, "Char", SC2Race.ZERG, MissionPools.EASY, "ap_domination" + FIRE_IN_THE_SKY = 37, "Fire in the Sky", SC2Campaign.HOTS, "Char", SC2Race.ZERG, MissionPools.MEDIUM, "ap_fire_in_the_sky" + OLD_SOLDIERS = 38, "Old Soldiers", SC2Campaign.HOTS, "Char", SC2Race.ZERG, MissionPools.MEDIUM, "ap_old_soldiers" + WAKING_THE_ANCIENT = 39, "Waking the Ancient", SC2Campaign.HOTS, "Zerus", SC2Race.ZERG, MissionPools.MEDIUM, "ap_waking_the_ancient" + THE_CRUCIBLE = 40, "The Crucible", SC2Campaign.HOTS, "Zerus", SC2Race.ZERG, MissionPools.MEDIUM, "ap_the_crucible" + SUPREME = 41, "Supreme", SC2Campaign.HOTS, "Zerus", SC2Race.ANY, MissionPools.MEDIUM, "ap_supreme", False + INFESTED = 42, "Infested", SC2Campaign.HOTS, "Skygeirr Station", SC2Race.ZERG, MissionPools.MEDIUM, "ap_infested" + HAND_OF_DARKNESS = 43, "Hand of Darkness", SC2Campaign.HOTS, "Skygeirr Station", SC2Race.ZERG, MissionPools.HARD, "ap_hand_of_darkness" + PHANTOMS_OF_THE_VOID = 44, "Phantoms of the Void", SC2Campaign.HOTS, "Skygeirr Station", SC2Race.ZERG, MissionPools.HARD, "ap_phantoms_of_the_void" + WITH_FRIENDS_LIKE_THESE = 45, "With Friends Like These", SC2Campaign.HOTS, "Dominion Space", SC2Race.ANY, MissionPools.STARTER, "ap_with_friends_like_these", False + CONVICTION = 46, "Conviction", SC2Campaign.HOTS, "Dominion Space", SC2Race.ANY, MissionPools.MEDIUM, "ap_conviction", False + PLANETFALL = 47, "Planetfall", SC2Campaign.HOTS, "Korhal", SC2Race.ZERG, MissionPools.HARD, "ap_planetfall" + DEATH_FROM_ABOVE = 48, "Death From Above", SC2Campaign.HOTS, "Korhal", SC2Race.ZERG, MissionPools.HARD, "ap_death_from_above" + THE_RECKONING = 49, "The Reckoning", SC2Campaign.HOTS, "Korhal", SC2Race.ZERG, MissionPools.HARD, "ap_the_reckoning" + + # Prologue + DARK_WHISPERS = 50, "Dark Whispers", SC2Campaign.PROLOGUE, "_1", SC2Race.PROTOSS, MissionPools.EASY, "ap_dark_whispers" + GHOSTS_IN_THE_FOG = 51, "Ghosts in the Fog", SC2Campaign.PROLOGUE, "_2", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_ghosts_in_the_fog" + EVIL_AWOKEN = 52, "Evil Awoken", SC2Campaign.PROLOGUE, "_3", SC2Race.PROTOSS, MissionPools.STARTER, "ap_evil_awoken", False + + # LotV + FOR_AIUR = 53, "For Aiur!", SC2Campaign.LOTV, "Aiur", SC2Race.ANY, MissionPools.STARTER, "ap_for_aiur", False + THE_GROWING_SHADOW = 54, "The Growing Shadow", SC2Campaign.LOTV, "Aiur", SC2Race.PROTOSS, MissionPools.EASY, "ap_the_growing_shadow" + THE_SPEAR_OF_ADUN = 55, "The Spear of Adun", SC2Campaign.LOTV, "Aiur", SC2Race.PROTOSS, MissionPools.EASY, "ap_the_spear_of_adun" + SKY_SHIELD = 56, "Sky Shield", SC2Campaign.LOTV, "Korhal", SC2Race.PROTOSS, MissionPools.EASY, "ap_sky_shield" + BROTHERS_IN_ARMS = 57, "Brothers in Arms", SC2Campaign.LOTV, "Korhal", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_brothers_in_arms" + AMON_S_REACH = 58, "Amon's Reach", SC2Campaign.LOTV, "Shakuras", SC2Race.PROTOSS, MissionPools.EASY, "ap_amon_s_reach" + LAST_STAND = 59, "Last Stand", SC2Campaign.LOTV, "Shakuras", SC2Race.PROTOSS, MissionPools.HARD, "ap_last_stand" + FORBIDDEN_WEAPON = 60, "Forbidden Weapon", SC2Campaign.LOTV, "Purifier", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_forbidden_weapon" + TEMPLE_OF_UNIFICATION = 61, "Temple of Unification", SC2Campaign.LOTV, "Ulnar", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_temple_of_unification" + THE_INFINITE_CYCLE = 62, "The Infinite Cycle", SC2Campaign.LOTV, "Ulnar", SC2Race.ANY, MissionPools.HARD, "ap_the_infinite_cycle", False + HARBINGER_OF_OBLIVION = 63, "Harbinger of Oblivion", SC2Campaign.LOTV, "Ulnar", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_harbinger_of_oblivion" + UNSEALING_THE_PAST = 64, "Unsealing the Past", SC2Campaign.LOTV, "Purifier", SC2Race.PROTOSS, MissionPools.MEDIUM, "ap_unsealing_the_past" + PURIFICATION = 65, "Purification", SC2Campaign.LOTV, "Purifier", SC2Race.PROTOSS, MissionPools.HARD, "ap_purification" + STEPS_OF_THE_RITE = 66, "Steps of the Rite", SC2Campaign.LOTV, "Tal'darim", SC2Race.PROTOSS, MissionPools.HARD, "ap_steps_of_the_rite" + RAK_SHIR = 67, "Rak'Shir", SC2Campaign.LOTV, "Tal'darim", SC2Race.PROTOSS, MissionPools.HARD, "ap_rak_shir" + TEMPLAR_S_CHARGE = 68, "Templar's Charge", SC2Campaign.LOTV, "Moebius", SC2Race.PROTOSS, MissionPools.HARD, "ap_templar_s_charge" + TEMPLAR_S_RETURN = 69, "Templar's Return", SC2Campaign.LOTV, "Return to Aiur", SC2Race.PROTOSS, MissionPools.EASY, "ap_templar_s_return", False + THE_HOST = 70, "The Host", SC2Campaign.LOTV, "Return to Aiur", SC2Race.PROTOSS, MissionPools.HARD, "ap_the_host", + SALVATION = 71, "Salvation", SC2Campaign.LOTV, "Return to Aiur", SC2Race.PROTOSS, MissionPools.VERY_HARD, "ap_salvation" + + # Epilogue + INTO_THE_VOID = 72, "Into the Void", SC2Campaign.EPILOGUE, "_1", SC2Race.PROTOSS, MissionPools.VERY_HARD, "ap_into_the_void" + THE_ESSENCE_OF_ETERNITY = 73, "The Essence of Eternity", SC2Campaign.EPILOGUE, "_2", SC2Race.TERRAN, MissionPools.VERY_HARD, "ap_the_essence_of_eternity" + AMON_S_FALL = 74, "Amon's Fall", SC2Campaign.EPILOGUE, "_3", SC2Race.ZERG, MissionPools.VERY_HARD, "ap_amon_s_fall" + + # Nova Covert Ops + THE_ESCAPE = 75, "The Escape", SC2Campaign.NCO, "_1", SC2Race.ANY, MissionPools.MEDIUM, "ap_the_escape", False + SUDDEN_STRIKE = 76, "Sudden Strike", SC2Campaign.NCO, "_1", SC2Race.TERRAN, MissionPools.EASY, "ap_sudden_strike" + ENEMY_INTELLIGENCE = 77, "Enemy Intelligence", SC2Campaign.NCO, "_1", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_enemy_intelligence" + TROUBLE_IN_PARADISE = 78, "Trouble In Paradise", SC2Campaign.NCO, "_2", SC2Race.TERRAN, MissionPools.HARD, "ap_trouble_in_paradise" + NIGHT_TERRORS = 79, "Night Terrors", SC2Campaign.NCO, "_2", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_night_terrors" + FLASHPOINT = 80, "Flashpoint", SC2Campaign.NCO, "_2", SC2Race.TERRAN, MissionPools.HARD, "ap_flashpoint" + IN_THE_ENEMY_S_SHADOW = 81, "In the Enemy's Shadow", SC2Campaign.NCO, "_3", SC2Race.TERRAN, MissionPools.MEDIUM, "ap_in_the_enemy_s_shadow", False + DARK_SKIES = 82, "Dark Skies", SC2Campaign.NCO, "_3", SC2Race.TERRAN, MissionPools.HARD, "ap_dark_skies" + END_GAME = 83, "End Game", SC2Campaign.NCO, "_3", SC2Race.TERRAN, MissionPools.VERY_HARD, "ap_end_game" + + +class MissionConnection: + campaign: SC2Campaign + connect_to: int # -1 connects to Menu + + def __init__(self, connect_to, campaign = SC2Campaign.GLOBAL): + self.campaign = campaign + self.connect_to = connect_to + + def _asdict(self): + return { + "campaign": self.campaign.id, + "connect_to": self.connect_to + } + + +class MissionInfo(NamedTuple): + mission: SC2Mission + required_world: List[Union[MissionConnection, Dict[Literal["campaign", "connect_to"], int]]] + category: str + number: int = 0 # number of worlds need beaten + completion_critical: bool = False # missions needed to beat game + or_requirements: bool = False # true if the requirements should be or-ed instead of and-ed + ui_vertical_padding: int = 0 + + +class FillMission(NamedTuple): + type: MissionPools + connect_to: List[MissionConnection] + category: str + number: int = 0 # number of worlds need beaten + completion_critical: bool = False # missions needed to beat game + or_requirements: bool = False # true if the requirements should be or-ed instead of and-ed + removal_priority: int = 0 # how many missions missing from the pool required to remove this mission + + + +def vanilla_shuffle_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.WOL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.WOL)], "Colonist"), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.WOL)], "Colonist"), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.WOL)], "Colonist", number=7), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.WOL)], "Colonist", number=7, removal_priority=1), + FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.WOL)], "Artifact", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(7, SC2Campaign.WOL)], "Artifact", number=8, completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(8, SC2Campaign.WOL)], "Artifact", number=11, completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(9, SC2Campaign.WOL)], "Artifact", number=14, completion_critical=True, removal_priority=7), + FillMission(MissionPools.HARD, [MissionConnection(10, SC2Campaign.WOL)], "Artifact", completion_critical=True, removal_priority=6), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.WOL)], "Covert", number=4), + FillMission(MissionPools.MEDIUM, [MissionConnection(12, SC2Campaign.WOL)], "Covert"), + FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.WOL)], "Covert", number=8, removal_priority=3), + FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.WOL)], "Covert", number=8, removal_priority=2), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.WOL)], "Rebellion", number=6), + FillMission(MissionPools.HARD, [MissionConnection(16, SC2Campaign.WOL)], "Rebellion"), + FillMission(MissionPools.HARD, [MissionConnection(17, SC2Campaign.WOL)], "Rebellion"), + FillMission(MissionPools.HARD, [MissionConnection(18, SC2Campaign.WOL)], "Rebellion", removal_priority=8), + FillMission(MissionPools.HARD, [MissionConnection(19, SC2Campaign.WOL)], "Rebellion", removal_priority=5), + FillMission(MissionPools.HARD, [MissionConnection(11, SC2Campaign.WOL)], "Char", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(21, SC2Campaign.WOL)], "Char", completion_critical=True, removal_priority=4), + FillMission(MissionPools.HARD, [MissionConnection(21, SC2Campaign.WOL)], "Char", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(22, SC2Campaign.WOL), MissionConnection(23, SC2Campaign.WOL)], "Char", completion_critical=True, or_requirements=True) + ], + SC2Campaign.PROPHECY: [ + FillMission(MissionPools.MEDIUM, [MissionConnection(8, SC2Campaign.WOL)], "_1"), + FillMission(MissionPools.HARD, [MissionConnection(0, SC2Campaign.PROPHECY)], "_2", removal_priority=2), + FillMission(MissionPools.HARD, [MissionConnection(1, SC2Campaign.PROPHECY)], "_3", removal_priority=1), + FillMission(MissionPools.FINAL, [MissionConnection(2, SC2Campaign.PROPHECY)], "_4"), + ], + SC2Campaign.HOTS: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.HOTS)], "Umoja", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.HOTS)], "Umoja", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.HOTS)], "Umoja", completion_critical=True, removal_priority=1), + FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.HOTS)], "Kaldir", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.HOTS)], "Kaldir", completion_critical=True, removal_priority=2), + FillMission(MissionPools.MEDIUM, [MissionConnection(4, SC2Campaign.HOTS)], "Kaldir", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(2, SC2Campaign.HOTS)], "Char", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(6, SC2Campaign.HOTS)], "Char", completion_critical=True, removal_priority=3), + FillMission(MissionPools.MEDIUM, [MissionConnection(7, SC2Campaign.HOTS)], "Char", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS), MissionConnection(8, SC2Campaign.HOTS)], "Zerus", completion_critical=True, or_requirements=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(9, SC2Campaign.HOTS)], "Zerus", completion_critical=True, removal_priority=4), + FillMission(MissionPools.MEDIUM, [MissionConnection(10, SC2Campaign.HOTS)], "Zerus", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS), MissionConnection(8, SC2Campaign.HOTS), MissionConnection(11, SC2Campaign.HOTS)], "Skygeirr Station", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(12, SC2Campaign.HOTS)], "Skygeirr Station", completion_critical=True, removal_priority=5), + FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.HOTS)], "Skygeirr Station", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS), MissionConnection(8, SC2Campaign.HOTS), MissionConnection(11, SC2Campaign.HOTS)], "Dominion Space", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(15, SC2Campaign.HOTS)], "Dominion Space", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(14, SC2Campaign.HOTS), MissionConnection(16, SC2Campaign.HOTS)], "Korhal", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(17, SC2Campaign.HOTS)], "Korhal", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(18, SC2Campaign.HOTS)], "Korhal", completion_critical=True), + ], + SC2Campaign.PROLOGUE: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.PROLOGUE)], "_1"), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.PROLOGUE)], "_2", removal_priority=1), + FillMission(MissionPools.FINAL, [MissionConnection(1, SC2Campaign.PROLOGUE)], "_3") + ], + SC2Campaign.LOTV: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.LOTV)], "Aiur", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.LOTV)], "Aiur", completion_critical=True, removal_priority=3), + FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.LOTV)], "Aiur", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.LOTV)], "Korhal", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.LOTV)], "Korhal", completion_critical=True, removal_priority=7), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.LOTV)], "Shakuras", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.LOTV)], "Shakuras", completion_critical=True, removal_priority=6), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.LOTV), MissionConnection(6, SC2Campaign.LOTV)], "Purifier", completion_critical=True, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.LOTV), MissionConnection(6, SC2Campaign.LOTV), MissionConnection(7, SC2Campaign.LOTV)], "Ulnar", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(8, SC2Campaign.LOTV)], "Ulnar", completion_critical=True, removal_priority=1), + FillMission(MissionPools.HARD, [MissionConnection(9, SC2Campaign.LOTV)], "Ulnar", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(10, SC2Campaign.LOTV)], "Purifier", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(11, SC2Campaign.LOTV)], "Purifier", completion_critical=True, removal_priority=5), + FillMission(MissionPools.HARD, [MissionConnection(10, SC2Campaign.LOTV)], "Tal'darim", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(13, SC2Campaign.LOTV)], "Tal'darim", completion_critical=True, removal_priority=4), + FillMission(MissionPools.HARD, [MissionConnection(12, SC2Campaign.LOTV), MissionConnection(14, SC2Campaign.LOTV)], "Moebius", completion_critical=True, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(12, SC2Campaign.LOTV), MissionConnection(14, SC2Campaign.LOTV), MissionConnection(15, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(16, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True, removal_priority=2), + FillMission(MissionPools.FINAL, [MissionConnection(17, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), + ], + SC2Campaign.EPILOGUE: [ + FillMission(MissionPools.VERY_HARD, [MissionConnection(24, SC2Campaign.WOL), MissionConnection(19, SC2Campaign.HOTS), MissionConnection(18, SC2Campaign.LOTV)], "_1", completion_critical=True), + FillMission(MissionPools.VERY_HARD, [MissionConnection(0, SC2Campaign.EPILOGUE)], "_2", completion_critical=True, removal_priority=1), + FillMission(MissionPools.FINAL, [MissionConnection(1, SC2Campaign.EPILOGUE)], "_3", completion_critical=True), + ], + SC2Campaign.NCO: [ + FillMission(MissionPools.EASY, [MissionConnection(-1, SC2Campaign.NCO)], "_1", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.NCO)], "_1", completion_critical=True, removal_priority=6), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.NCO)], "_1", completion_critical=True, removal_priority=5), + FillMission(MissionPools.HARD, [MissionConnection(2, SC2Campaign.NCO)], "_2", completion_critical=True, removal_priority=7), + FillMission(MissionPools.HARD, [MissionConnection(3, SC2Campaign.NCO)], "_2", completion_critical=True, removal_priority=4), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.NCO)], "_2", completion_critical=True, removal_priority=3), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.NCO)], "_3", completion_critical=True, removal_priority=2), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.NCO)], "_3", completion_critical=True, removal_priority=1), + FillMission(MissionPools.FINAL, [MissionConnection(7, SC2Campaign.NCO)], "_3", completion_critical=True), + ] + } + + +def mini_campaign_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.WOL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.WOL)], "Mar Sara", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.WOL)], "Colonist"), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.WOL)], "Colonist"), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.WOL)], "Artifact", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.WOL)], "Artifact", number=4, completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.WOL)], "Artifact", number=8, completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.WOL)], "Covert", number=2), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.WOL)], "Covert"), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.WOL)], "Rebellion", number=3), + FillMission(MissionPools.HARD, [MissionConnection(8, SC2Campaign.WOL)], "Rebellion"), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.WOL)], "Char", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.WOL)], "Char", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(10, SC2Campaign.WOL), MissionConnection(11, SC2Campaign.WOL)], "Char", completion_critical=True, or_requirements=True) + ], + SC2Campaign.PROPHECY: [ + FillMission(MissionPools.MEDIUM, [MissionConnection(4, SC2Campaign.WOL)], "_1"), + FillMission(MissionPools.FINAL, [MissionConnection(0, SC2Campaign.PROPHECY)], "_2"), + ], + SC2Campaign.HOTS: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.HOTS)], "Umoja", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.HOTS)], "Kaldir"), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.HOTS)], "Kaldir"), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.HOTS)], "Char"), + FillMission(MissionPools.MEDIUM, [MissionConnection(3, SC2Campaign.HOTS)], "Char"), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.HOTS)], "Zerus", number=3), + FillMission(MissionPools.MEDIUM, [MissionConnection(5, SC2Campaign.HOTS)], "Zerus"), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.HOTS)], "Skygeirr Station", number=5), + FillMission(MissionPools.HARD, [MissionConnection(7, SC2Campaign.HOTS)], "Skygeirr Station"), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.HOTS)], "Dominion Space", number=5), + FillMission(MissionPools.HARD, [MissionConnection(9, SC2Campaign.HOTS)], "Dominion Space"), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.HOTS)], "Korhal", completion_critical=True, number=8), + FillMission(MissionPools.FINAL, [MissionConnection(11, SC2Campaign.HOTS)], "Korhal", completion_critical=True), + ], + SC2Campaign.PROLOGUE: [ + FillMission(MissionPools.EASY, [MissionConnection(-1, SC2Campaign.PROLOGUE)], "_1"), + FillMission(MissionPools.FINAL, [MissionConnection(0, SC2Campaign.PROLOGUE)], "_2") + ], + SC2Campaign.LOTV: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1, SC2Campaign.LOTV)], "Aiur",completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0, SC2Campaign.LOTV)], "Aiur", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(1, SC2Campaign.LOTV)], "Korhal", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.LOTV)], "Shakuras", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(2, SC2Campaign.LOTV), MissionConnection(3, SC2Campaign.LOTV)], "Purifier", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.LOTV)], "Purifier", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(4, SC2Campaign.LOTV)], "Ulnar", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(6, SC2Campaign.LOTV)], "Tal'darim", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(5, SC2Campaign.LOTV), MissionConnection(7, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(8, SC2Campaign.LOTV)], "Return to Aiur", completion_critical=True), + ], + SC2Campaign.EPILOGUE: [ + FillMission(MissionPools.VERY_HARD, [MissionConnection(12, SC2Campaign.WOL), MissionConnection(12, SC2Campaign.HOTS), MissionConnection(9, SC2Campaign.LOTV)], "_1", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(0, SC2Campaign.EPILOGUE)], "_2", completion_critical=True), + ], + SC2Campaign.NCO: [ + FillMission(MissionPools.EASY, [MissionConnection(-1, SC2Campaign.NCO)], "_1", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0, SC2Campaign.NCO)], "_1", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(1, SC2Campaign.NCO)], "_2", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(2, SC2Campaign.NCO)], "_3", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(3, SC2Campaign.NCO)], "_3", completion_critical=True), + ] + } + + +def gauntlet_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.GLOBAL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1)], "I", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0)], "II", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(1)], "III", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(2)], "IV", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(3)], "V", completion_critical=True), + FillMission(MissionPools.HARD, [MissionConnection(4)], "VI", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(5)], "Final", completion_critical=True) + ] + } + + +def mini_gauntlet_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.GLOBAL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1)], "I", completion_critical=True), + FillMission(MissionPools.EASY, [MissionConnection(0)], "II", completion_critical=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(1)], "III", completion_critical=True), + FillMission(MissionPools.FINAL, [MissionConnection(2)], "Final", completion_critical=True) + ] + } + + +def grid_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.GLOBAL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1)], "_1"), + FillMission(MissionPools.EASY, [MissionConnection(0)], "_1"), + FillMission(MissionPools.MEDIUM, [MissionConnection(1), MissionConnection(6), MissionConnection( 3)], "_1", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(2), MissionConnection(7)], "_1", or_requirements=True), + FillMission(MissionPools.EASY, [MissionConnection(0)], "_2"), + FillMission(MissionPools.MEDIUM, [MissionConnection(1), MissionConnection(4)], "_2", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(2), MissionConnection(5), MissionConnection(10), MissionConnection(7)], "_2", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(3), MissionConnection(6), MissionConnection(11)], "_2", or_requirements=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(4), MissionConnection(9), MissionConnection(12)], "_3", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(5), MissionConnection(8), MissionConnection(10), MissionConnection(13)], "_3", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(6), MissionConnection(9), MissionConnection(11), MissionConnection(14)], "_3", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(7), MissionConnection(10)], "_3", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(8), MissionConnection(13)], "_4", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(9), MissionConnection(12), MissionConnection(14)], "_4", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(10), MissionConnection(13)], "_4", or_requirements=True), + FillMission(MissionPools.FINAL, [MissionConnection(11), MissionConnection(14)], "_4", or_requirements=True) + ] + } + +def mini_grid_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.GLOBAL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1)], "_1"), + FillMission(MissionPools.EASY, [MissionConnection(0)], "_1"), + FillMission(MissionPools.MEDIUM, [MissionConnection(1), MissionConnection(5)], "_1", or_requirements=True), + FillMission(MissionPools.EASY, [MissionConnection(0)], "_2"), + FillMission(MissionPools.MEDIUM, [MissionConnection(1), MissionConnection(3)], "_2", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(2), MissionConnection(4)], "_2", or_requirements=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(3), MissionConnection(7)], "_3", or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(4), MissionConnection(6)], "_3", or_requirements=True), + FillMission(MissionPools.FINAL, [MissionConnection(5), MissionConnection(7)], "_3", or_requirements=True) + ] + } + +def tiny_grid_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.GLOBAL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1)], "_1"), + FillMission(MissionPools.MEDIUM, [MissionConnection(0)], "_1"), + FillMission(MissionPools.EASY, [MissionConnection(0)], "_2"), + FillMission(MissionPools.FINAL, [MissionConnection(1), MissionConnection(2)], "_2", or_requirements=True), + ] + } + +def blitz_order() -> Dict[SC2Campaign, List[FillMission]]: + return { + SC2Campaign.GLOBAL: [ + FillMission(MissionPools.STARTER, [MissionConnection(-1)], "I"), + FillMission(MissionPools.EASY, [MissionConnection(-1)], "I"), + FillMission(MissionPools.MEDIUM, [MissionConnection(0), MissionConnection(1)], "II", number=1, or_requirements=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0), MissionConnection(1)], "II", number=1, or_requirements=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0), MissionConnection(1)], "III", number=2, or_requirements=True), + FillMission(MissionPools.MEDIUM, [MissionConnection(0), MissionConnection(1)], "III", number=2, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "IV", number=3, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "IV", number=3, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "V", number=4, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "V", number=4, or_requirements=True), + FillMission(MissionPools.HARD, [MissionConnection(0), MissionConnection(1)], "Final", number=5, or_requirements=True), + FillMission(MissionPools.FINAL, [MissionConnection(0), MissionConnection(1)], "Final", number=5, or_requirements=True) + ] + } + + +mission_orders: List[Callable[[], Dict[SC2Campaign, List[FillMission]]]] = [ + vanilla_shuffle_order, + vanilla_shuffle_order, + mini_campaign_order, + grid_order, + mini_grid_order, + blitz_order, + gauntlet_order, + mini_gauntlet_order, + tiny_grid_order +] + + +vanilla_mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]] = { + SC2Campaign.WOL: { + SC2Mission.LIBERATION_DAY.mission_name: MissionInfo(SC2Mission.LIBERATION_DAY, [], SC2Mission.LIBERATION_DAY.area, completion_critical=True), + SC2Mission.THE_OUTLAWS.mission_name: MissionInfo(SC2Mission.THE_OUTLAWS, [MissionConnection(1, SC2Campaign.WOL)], SC2Mission.THE_OUTLAWS.area, completion_critical=True), + SC2Mission.ZERO_HOUR.mission_name: MissionInfo(SC2Mission.ZERO_HOUR, [MissionConnection(2, SC2Campaign.WOL)], SC2Mission.ZERO_HOUR.area, completion_critical=True), + SC2Mission.EVACUATION.mission_name: MissionInfo(SC2Mission.EVACUATION, [MissionConnection(3, SC2Campaign.WOL)], SC2Mission.EVACUATION.area), + SC2Mission.OUTBREAK.mission_name: MissionInfo(SC2Mission.OUTBREAK, [MissionConnection(4, SC2Campaign.WOL)], SC2Mission.OUTBREAK.area), + SC2Mission.SAFE_HAVEN.mission_name: MissionInfo(SC2Mission.SAFE_HAVEN, [MissionConnection(5, SC2Campaign.WOL)], SC2Mission.SAFE_HAVEN.area, number=7), + SC2Mission.HAVENS_FALL.mission_name: MissionInfo(SC2Mission.HAVENS_FALL, [MissionConnection(5, SC2Campaign.WOL)], SC2Mission.HAVENS_FALL.area, number=7), + SC2Mission.SMASH_AND_GRAB.mission_name: MissionInfo(SC2Mission.SMASH_AND_GRAB, [MissionConnection(3, SC2Campaign.WOL)], SC2Mission.SMASH_AND_GRAB.area, completion_critical=True), + SC2Mission.THE_DIG.mission_name: MissionInfo(SC2Mission.THE_DIG, [MissionConnection(8, SC2Campaign.WOL)], SC2Mission.THE_DIG.area, number=8, completion_critical=True), + SC2Mission.THE_MOEBIUS_FACTOR.mission_name: MissionInfo(SC2Mission.THE_MOEBIUS_FACTOR, [MissionConnection(9, SC2Campaign.WOL)], SC2Mission.THE_MOEBIUS_FACTOR.area, number=11, completion_critical=True), + SC2Mission.SUPERNOVA.mission_name: MissionInfo(SC2Mission.SUPERNOVA, [MissionConnection(10, SC2Campaign.WOL)], SC2Mission.SUPERNOVA.area, number=14, completion_critical=True), + SC2Mission.MAW_OF_THE_VOID.mission_name: MissionInfo(SC2Mission.MAW_OF_THE_VOID, [MissionConnection(11, SC2Campaign.WOL)], SC2Mission.MAW_OF_THE_VOID.area, completion_critical=True), + SC2Mission.DEVILS_PLAYGROUND.mission_name: MissionInfo(SC2Mission.DEVILS_PLAYGROUND, [MissionConnection(3, SC2Campaign.WOL)], SC2Mission.DEVILS_PLAYGROUND.area, number=4), + SC2Mission.WELCOME_TO_THE_JUNGLE.mission_name: MissionInfo(SC2Mission.WELCOME_TO_THE_JUNGLE, [MissionConnection(13, SC2Campaign.WOL)], SC2Mission.WELCOME_TO_THE_JUNGLE.area), + SC2Mission.BREAKOUT.mission_name: MissionInfo(SC2Mission.BREAKOUT, [MissionConnection(14, SC2Campaign.WOL)], SC2Mission.BREAKOUT.area, number=8), + SC2Mission.GHOST_OF_A_CHANCE.mission_name: MissionInfo(SC2Mission.GHOST_OF_A_CHANCE, [MissionConnection(14, SC2Campaign.WOL)], SC2Mission.GHOST_OF_A_CHANCE.area, number=8), + SC2Mission.THE_GREAT_TRAIN_ROBBERY.mission_name: MissionInfo(SC2Mission.THE_GREAT_TRAIN_ROBBERY, [MissionConnection(3, SC2Campaign.WOL)], SC2Mission.THE_GREAT_TRAIN_ROBBERY.area, number=6), + SC2Mission.CUTTHROAT.mission_name: MissionInfo(SC2Mission.CUTTHROAT, [MissionConnection(17, SC2Campaign.WOL)], SC2Mission.THE_GREAT_TRAIN_ROBBERY.area), + SC2Mission.ENGINE_OF_DESTRUCTION.mission_name: MissionInfo(SC2Mission.ENGINE_OF_DESTRUCTION, [MissionConnection(18, SC2Campaign.WOL)], SC2Mission.ENGINE_OF_DESTRUCTION.area), + SC2Mission.MEDIA_BLITZ.mission_name: MissionInfo(SC2Mission.MEDIA_BLITZ, [MissionConnection(19, SC2Campaign.WOL)], SC2Mission.MEDIA_BLITZ.area), + SC2Mission.PIERCING_OF_THE_SHROUD.mission_name: MissionInfo(SC2Mission.PIERCING_OF_THE_SHROUD, [MissionConnection(20, SC2Campaign.WOL)], SC2Mission.PIERCING_OF_THE_SHROUD.area), + SC2Mission.GATES_OF_HELL.mission_name: MissionInfo(SC2Mission.GATES_OF_HELL, [MissionConnection(12, SC2Campaign.WOL)], SC2Mission.GATES_OF_HELL.area, completion_critical=True), + SC2Mission.BELLY_OF_THE_BEAST.mission_name: MissionInfo(SC2Mission.BELLY_OF_THE_BEAST, [MissionConnection(22, SC2Campaign.WOL)], SC2Mission.BELLY_OF_THE_BEAST.area, completion_critical=True), + SC2Mission.SHATTER_THE_SKY.mission_name: MissionInfo(SC2Mission.SHATTER_THE_SKY, [MissionConnection(22, SC2Campaign.WOL)], SC2Mission.SHATTER_THE_SKY.area, completion_critical=True), + SC2Mission.ALL_IN.mission_name: MissionInfo(SC2Mission.ALL_IN, [MissionConnection(23, SC2Campaign.WOL), MissionConnection(24, SC2Campaign.WOL)], SC2Mission.ALL_IN.area, or_requirements=True, completion_critical=True) + }, + SC2Campaign.PROPHECY: { + SC2Mission.WHISPERS_OF_DOOM.mission_name: MissionInfo(SC2Mission.WHISPERS_OF_DOOM, [MissionConnection(9, SC2Campaign.WOL)], SC2Mission.WHISPERS_OF_DOOM.area), + SC2Mission.A_SINISTER_TURN.mission_name: MissionInfo(SC2Mission.A_SINISTER_TURN, [MissionConnection(1, SC2Campaign.PROPHECY)], SC2Mission.A_SINISTER_TURN.area), + SC2Mission.ECHOES_OF_THE_FUTURE.mission_name: MissionInfo(SC2Mission.ECHOES_OF_THE_FUTURE, [MissionConnection(2, SC2Campaign.PROPHECY)], SC2Mission.ECHOES_OF_THE_FUTURE.area), + SC2Mission.IN_UTTER_DARKNESS.mission_name: MissionInfo(SC2Mission.IN_UTTER_DARKNESS, [MissionConnection(3, SC2Campaign.PROPHECY)], SC2Mission.IN_UTTER_DARKNESS.area) + }, + SC2Campaign.HOTS: { + SC2Mission.LAB_RAT.mission_name: MissionInfo(SC2Mission.LAB_RAT, [], SC2Mission.LAB_RAT.area, completion_critical=True), + SC2Mission.BACK_IN_THE_SADDLE.mission_name: MissionInfo(SC2Mission.BACK_IN_THE_SADDLE, [MissionConnection(1, SC2Campaign.HOTS)], SC2Mission.BACK_IN_THE_SADDLE.area, completion_critical=True), + SC2Mission.RENDEZVOUS.mission_name: MissionInfo(SC2Mission.RENDEZVOUS, [MissionConnection(2, SC2Campaign.HOTS)], SC2Mission.RENDEZVOUS.area, completion_critical=True), + SC2Mission.HARVEST_OF_SCREAMS.mission_name: MissionInfo(SC2Mission.HARVEST_OF_SCREAMS, [MissionConnection(3, SC2Campaign.HOTS)], SC2Mission.HARVEST_OF_SCREAMS.area), + SC2Mission.SHOOT_THE_MESSENGER.mission_name: MissionInfo(SC2Mission.SHOOT_THE_MESSENGER, [MissionConnection(4, SC2Campaign.HOTS)], SC2Mission.SHOOT_THE_MESSENGER.area), + SC2Mission.ENEMY_WITHIN.mission_name: MissionInfo(SC2Mission.ENEMY_WITHIN, [MissionConnection(5, SC2Campaign.HOTS)], SC2Mission.ENEMY_WITHIN.area), + SC2Mission.DOMINATION.mission_name: MissionInfo(SC2Mission.DOMINATION, [MissionConnection(3, SC2Campaign.HOTS)], SC2Mission.DOMINATION.area), + SC2Mission.FIRE_IN_THE_SKY.mission_name: MissionInfo(SC2Mission.FIRE_IN_THE_SKY, [MissionConnection(7, SC2Campaign.HOTS)], SC2Mission.FIRE_IN_THE_SKY.area), + SC2Mission.OLD_SOLDIERS.mission_name: MissionInfo(SC2Mission.OLD_SOLDIERS, [MissionConnection(8, SC2Campaign.HOTS)], SC2Mission.OLD_SOLDIERS.area), + SC2Mission.WAKING_THE_ANCIENT.mission_name: MissionInfo(SC2Mission.WAKING_THE_ANCIENT, [MissionConnection(6, SC2Campaign.HOTS), MissionConnection(9, SC2Campaign.HOTS)], SC2Mission.WAKING_THE_ANCIENT.area, completion_critical=True, or_requirements=True), + SC2Mission.THE_CRUCIBLE.mission_name: MissionInfo(SC2Mission.THE_CRUCIBLE, [MissionConnection(10, SC2Campaign.HOTS)], SC2Mission.THE_CRUCIBLE.area, completion_critical=True), + SC2Mission.SUPREME.mission_name: MissionInfo(SC2Mission.SUPREME, [MissionConnection(11, SC2Campaign.HOTS)], SC2Mission.SUPREME.area, completion_critical=True), + SC2Mission.INFESTED.mission_name: MissionInfo(SC2Mission.INFESTED, [MissionConnection(6, SC2Campaign.HOTS), MissionConnection(9, SC2Campaign.HOTS), MissionConnection(12, SC2Campaign.HOTS)], SC2Mission.INFESTED.area), + SC2Mission.HAND_OF_DARKNESS.mission_name: MissionInfo(SC2Mission.HAND_OF_DARKNESS, [MissionConnection(13, SC2Campaign.HOTS)], SC2Mission.HAND_OF_DARKNESS.area), + SC2Mission.PHANTOMS_OF_THE_VOID.mission_name: MissionInfo(SC2Mission.PHANTOMS_OF_THE_VOID, [MissionConnection(14, SC2Campaign.HOTS)], SC2Mission.PHANTOMS_OF_THE_VOID.area), + SC2Mission.WITH_FRIENDS_LIKE_THESE.mission_name: MissionInfo(SC2Mission.WITH_FRIENDS_LIKE_THESE, [MissionConnection(6, SC2Campaign.HOTS), MissionConnection(9, SC2Campaign.HOTS), MissionConnection(12, SC2Campaign.HOTS)], SC2Mission.WITH_FRIENDS_LIKE_THESE.area), + SC2Mission.CONVICTION.mission_name: MissionInfo(SC2Mission.CONVICTION, [MissionConnection(16, SC2Campaign.HOTS)], SC2Mission.CONVICTION.area), + SC2Mission.PLANETFALL.mission_name: MissionInfo(SC2Mission.PLANETFALL, [MissionConnection(15, SC2Campaign.HOTS), MissionConnection(17, SC2Campaign.HOTS)], SC2Mission.PLANETFALL.area, completion_critical=True), + SC2Mission.DEATH_FROM_ABOVE.mission_name: MissionInfo(SC2Mission.DEATH_FROM_ABOVE, [MissionConnection(18, SC2Campaign.HOTS)], SC2Mission.DEATH_FROM_ABOVE.area, completion_critical=True), + SC2Mission.THE_RECKONING.mission_name: MissionInfo(SC2Mission.THE_RECKONING, [MissionConnection(19, SC2Campaign.HOTS)], SC2Mission.THE_RECKONING.area, completion_critical=True), + }, + SC2Campaign.PROLOGUE: { + SC2Mission.DARK_WHISPERS.mission_name: MissionInfo(SC2Mission.DARK_WHISPERS, [], SC2Mission.DARK_WHISPERS.area), + SC2Mission.GHOSTS_IN_THE_FOG.mission_name: MissionInfo(SC2Mission.GHOSTS_IN_THE_FOG, [MissionConnection(1, SC2Campaign.PROLOGUE)], SC2Mission.GHOSTS_IN_THE_FOG.area), + SC2Mission.EVIL_AWOKEN.mission_name: MissionInfo(SC2Mission.EVIL_AWOKEN, [MissionConnection(2, SC2Campaign.PROLOGUE)], SC2Mission.EVIL_AWOKEN.area) + }, + SC2Campaign.LOTV: { + SC2Mission.FOR_AIUR.mission_name: MissionInfo(SC2Mission.FOR_AIUR, [], SC2Mission.FOR_AIUR.area, completion_critical=True), + SC2Mission.THE_GROWING_SHADOW.mission_name: MissionInfo(SC2Mission.THE_GROWING_SHADOW, [MissionConnection(1, SC2Campaign.LOTV)], SC2Mission.THE_GROWING_SHADOW.area, completion_critical=True), + SC2Mission.THE_SPEAR_OF_ADUN.mission_name: MissionInfo(SC2Mission.THE_SPEAR_OF_ADUN, [MissionConnection(2, SC2Campaign.LOTV)], SC2Mission.THE_SPEAR_OF_ADUN.area, completion_critical=True), + SC2Mission.SKY_SHIELD.mission_name: MissionInfo(SC2Mission.SKY_SHIELD, [MissionConnection(3, SC2Campaign.LOTV)], SC2Mission.SKY_SHIELD.area, completion_critical=True), + SC2Mission.BROTHERS_IN_ARMS.mission_name: MissionInfo(SC2Mission.BROTHERS_IN_ARMS, [MissionConnection(4, SC2Campaign.LOTV)], SC2Mission.BROTHERS_IN_ARMS.area, completion_critical=True), + SC2Mission.AMON_S_REACH.mission_name: MissionInfo(SC2Mission.AMON_S_REACH, [MissionConnection(3, SC2Campaign.LOTV)], SC2Mission.AMON_S_REACH.area, completion_critical=True), + SC2Mission.LAST_STAND.mission_name: MissionInfo(SC2Mission.LAST_STAND, [MissionConnection(6, SC2Campaign.LOTV)], SC2Mission.LAST_STAND.area, completion_critical=True), + SC2Mission.FORBIDDEN_WEAPON.mission_name: MissionInfo(SC2Mission.FORBIDDEN_WEAPON, [MissionConnection(5, SC2Campaign.LOTV), MissionConnection(7, SC2Campaign.LOTV)], SC2Mission.FORBIDDEN_WEAPON.area, completion_critical=True, or_requirements=True), + SC2Mission.TEMPLE_OF_UNIFICATION.mission_name: MissionInfo(SC2Mission.TEMPLE_OF_UNIFICATION, [MissionConnection(5, SC2Campaign.LOTV), MissionConnection(7, SC2Campaign.LOTV), MissionConnection(8, SC2Campaign.LOTV)], SC2Mission.TEMPLE_OF_UNIFICATION.area, completion_critical=True), + SC2Mission.THE_INFINITE_CYCLE.mission_name: MissionInfo(SC2Mission.THE_INFINITE_CYCLE, [MissionConnection(9, SC2Campaign.LOTV)], SC2Mission.THE_INFINITE_CYCLE.area, completion_critical=True), + SC2Mission.HARBINGER_OF_OBLIVION.mission_name: MissionInfo(SC2Mission.HARBINGER_OF_OBLIVION, [MissionConnection(10, SC2Campaign.LOTV)], SC2Mission.HARBINGER_OF_OBLIVION.area, completion_critical=True), + SC2Mission.UNSEALING_THE_PAST.mission_name: MissionInfo(SC2Mission.UNSEALING_THE_PAST, [MissionConnection(11, SC2Campaign.LOTV)], SC2Mission.UNSEALING_THE_PAST.area, completion_critical=True), + SC2Mission.PURIFICATION.mission_name: MissionInfo(SC2Mission.PURIFICATION, [MissionConnection(12, SC2Campaign.LOTV)], SC2Mission.PURIFICATION.area, completion_critical=True), + SC2Mission.STEPS_OF_THE_RITE.mission_name: MissionInfo(SC2Mission.STEPS_OF_THE_RITE, [MissionConnection(11, SC2Campaign.LOTV)], SC2Mission.STEPS_OF_THE_RITE.area, completion_critical=True), + SC2Mission.RAK_SHIR.mission_name: MissionInfo(SC2Mission.RAK_SHIR, [MissionConnection(14, SC2Campaign.LOTV)], SC2Mission.RAK_SHIR.area, completion_critical=True), + SC2Mission.TEMPLAR_S_CHARGE.mission_name: MissionInfo(SC2Mission.TEMPLAR_S_CHARGE, [MissionConnection(13, SC2Campaign.LOTV), MissionConnection(15, SC2Campaign.LOTV)], SC2Mission.TEMPLAR_S_CHARGE.area, completion_critical=True, or_requirements=True), + SC2Mission.TEMPLAR_S_RETURN.mission_name: MissionInfo(SC2Mission.TEMPLAR_S_RETURN, [MissionConnection(13, SC2Campaign.LOTV), MissionConnection(15, SC2Campaign.LOTV), MissionConnection(16, SC2Campaign.LOTV)], SC2Mission.TEMPLAR_S_RETURN.area, completion_critical=True), + SC2Mission.THE_HOST.mission_name: MissionInfo(SC2Mission.THE_HOST, [MissionConnection(17, SC2Campaign.LOTV)], SC2Mission.THE_HOST.area, completion_critical=True), + SC2Mission.SALVATION.mission_name: MissionInfo(SC2Mission.SALVATION, [MissionConnection(18, SC2Campaign.LOTV)], SC2Mission.SALVATION.area, completion_critical=True), + }, + SC2Campaign.EPILOGUE: { + SC2Mission.INTO_THE_VOID.mission_name: MissionInfo(SC2Mission.INTO_THE_VOID, [MissionConnection(25, SC2Campaign.WOL), MissionConnection(20, SC2Campaign.HOTS), MissionConnection(19, SC2Campaign.LOTV)], SC2Mission.INTO_THE_VOID.area, completion_critical=True), + SC2Mission.THE_ESSENCE_OF_ETERNITY.mission_name: MissionInfo(SC2Mission.THE_ESSENCE_OF_ETERNITY, [MissionConnection(1, SC2Campaign.EPILOGUE)], SC2Mission.THE_ESSENCE_OF_ETERNITY.area, completion_critical=True), + SC2Mission.AMON_S_FALL.mission_name: MissionInfo(SC2Mission.AMON_S_FALL, [MissionConnection(2, SC2Campaign.EPILOGUE)], SC2Mission.AMON_S_FALL.area, completion_critical=True), + }, + SC2Campaign.NCO: { + SC2Mission.THE_ESCAPE.mission_name: MissionInfo(SC2Mission.THE_ESCAPE, [], SC2Mission.THE_ESCAPE.area, completion_critical=True), + SC2Mission.SUDDEN_STRIKE.mission_name: MissionInfo(SC2Mission.SUDDEN_STRIKE, [MissionConnection(1, SC2Campaign.NCO)], SC2Mission.SUDDEN_STRIKE.area, completion_critical=True), + SC2Mission.ENEMY_INTELLIGENCE.mission_name: MissionInfo(SC2Mission.ENEMY_INTELLIGENCE, [MissionConnection(2, SC2Campaign.NCO)], SC2Mission.ENEMY_INTELLIGENCE.area, completion_critical=True), + SC2Mission.TROUBLE_IN_PARADISE.mission_name: MissionInfo(SC2Mission.TROUBLE_IN_PARADISE, [MissionConnection(3, SC2Campaign.NCO)], SC2Mission.TROUBLE_IN_PARADISE.area, completion_critical=True), + SC2Mission.NIGHT_TERRORS.mission_name: MissionInfo(SC2Mission.NIGHT_TERRORS, [MissionConnection(4, SC2Campaign.NCO)], SC2Mission.NIGHT_TERRORS.area, completion_critical=True), + SC2Mission.FLASHPOINT.mission_name: MissionInfo(SC2Mission.FLASHPOINT, [MissionConnection(5, SC2Campaign.NCO)], SC2Mission.FLASHPOINT.area, completion_critical=True), + SC2Mission.IN_THE_ENEMY_S_SHADOW.mission_name: MissionInfo(SC2Mission.IN_THE_ENEMY_S_SHADOW, [MissionConnection(6, SC2Campaign.NCO)], SC2Mission.IN_THE_ENEMY_S_SHADOW.area, completion_critical=True), + SC2Mission.DARK_SKIES.mission_name: MissionInfo(SC2Mission.DARK_SKIES, [MissionConnection(7, SC2Campaign.NCO)], SC2Mission.DARK_SKIES.area, completion_critical=True), + SC2Mission.END_GAME.mission_name: MissionInfo(SC2Mission.END_GAME, [MissionConnection(8, SC2Campaign.NCO)], SC2Mission.END_GAME.area, completion_critical=True), + } +} + +lookup_id_to_mission: Dict[int, SC2Mission] = { + mission.id: mission for mission in SC2Mission +} + +lookup_name_to_mission: Dict[str, SC2Mission] = { + mission.mission_name: mission for mission in SC2Mission +} + +lookup_id_to_campaign: Dict[int, SC2Campaign] = { + campaign.id: campaign for campaign in SC2Campaign +} + + +campaign_mission_table: Dict[SC2Campaign, Set[SC2Mission]] = { + campaign: set() for campaign in SC2Campaign +} +for mission in SC2Mission: + campaign_mission_table[mission.campaign].add(mission) + + +def get_campaign_difficulty(campaign: SC2Campaign, excluded_missions: Iterable[SC2Mission] = ()) -> MissionPools: + """ + + :param campaign: + :param excluded_missions: + :return: Campaign's the most difficult non-excluded mission + """ + excluded_mission_set = set(excluded_missions) + included_missions = campaign_mission_table[campaign].difference(excluded_mission_set) + return max([mission.pool for mission in included_missions]) + + +def get_campaign_goal_priority(campaign: SC2Campaign, excluded_missions: Iterable[SC2Mission] = ()) -> SC2CampaignGoalPriority: + """ + Gets a modified campaign goal priority. + If all the campaign's goal missions are excluded, it's ineligible to have the goal + If the campaign's very hard missions are excluded, the priority is lowered to hard + :param campaign: + :param excluded_missions: + :return: + """ + if excluded_missions is None: + return campaign.goal_priority + else: + goal_missions = set(get_campaign_potential_goal_missions(campaign)) + excluded_mission_set = set(excluded_missions) + remaining_goals = goal_missions.difference(excluded_mission_set) + if remaining_goals == set(): + # All potential goals are excluded, the campaign can't be a goal + return SC2CampaignGoalPriority.NONE + elif campaign.goal_priority == SC2CampaignGoalPriority.VERY_HARD: + # Check if a very hard campaign doesn't get rid of it's last very hard mission + difficulty = get_campaign_difficulty(campaign, excluded_missions) + if difficulty == MissionPools.VERY_HARD: + return SC2CampaignGoalPriority.VERY_HARD + else: + return SC2CampaignGoalPriority.HARD + else: + return campaign.goal_priority + + +class SC2CampaignGoal(NamedTuple): + mission: SC2Mission + location: str + + +campaign_final_mission_locations: Dict[SC2Campaign, SC2CampaignGoal] = { + SC2Campaign.WOL: SC2CampaignGoal(SC2Mission.ALL_IN, "All-In: Victory"), + SC2Campaign.PROPHECY: SC2CampaignGoal(SC2Mission.IN_UTTER_DARKNESS, "In Utter Darkness: Kills"), + SC2Campaign.HOTS: None, + SC2Campaign.PROLOGUE: SC2CampaignGoal(SC2Mission.EVIL_AWOKEN, "Evil Awoken: Victory"), + SC2Campaign.LOTV: SC2CampaignGoal(SC2Mission.SALVATION, "Salvation: Victory"), + SC2Campaign.EPILOGUE: None, + SC2Campaign.NCO: None, +} + +campaign_alt_final_mission_locations: Dict[SC2Campaign, Dict[SC2Mission, str]] = { + SC2Campaign.WOL: { + SC2Mission.MAW_OF_THE_VOID: "Maw of the Void: Victory", + SC2Mission.ENGINE_OF_DESTRUCTION: "Engine of Destruction: Victory", + SC2Mission.SUPERNOVA: "Supernova: Victory", + SC2Mission.GATES_OF_HELL: "Gates of Hell: Victory", + SC2Mission.SHATTER_THE_SKY: "Shatter the Sky: Victory" + }, + SC2Campaign.PROPHECY: None, + SC2Campaign.HOTS: { + SC2Mission.THE_RECKONING: "The Reckoning: Victory", + SC2Mission.THE_CRUCIBLE: "The Crucible: Victory", + SC2Mission.HAND_OF_DARKNESS: "Hand of Darkness: Victory", + SC2Mission.PHANTOMS_OF_THE_VOID: "Phantoms of the Void: Victory", + SC2Mission.PLANETFALL: "Planetfall: Victory", + SC2Mission.DEATH_FROM_ABOVE: "Death From Above: Victory" + }, + SC2Campaign.PROLOGUE: { + SC2Mission.GHOSTS_IN_THE_FOG: "Ghosts in the Fog: Victory" + }, + SC2Campaign.LOTV: { + SC2Mission.THE_HOST: "The Host: Victory", + SC2Mission.TEMPLAR_S_CHARGE: "Templar's Charge: Victory" + }, + SC2Campaign.EPILOGUE: { + SC2Mission.AMON_S_FALL: "Amon's Fall: Victory", + SC2Mission.INTO_THE_VOID: "Into the Void: Victory", + SC2Mission.THE_ESSENCE_OF_ETERNITY: "The Essence of Eternity: Victory", + }, + SC2Campaign.NCO: { + SC2Mission.END_GAME: "End Game: Victory", + SC2Mission.FLASHPOINT: "Flashpoint: Victory", + SC2Mission.DARK_SKIES: "Dark Skies: Victory", + SC2Mission.NIGHT_TERRORS: "Night Terrors: Victory", + SC2Mission.TROUBLE_IN_PARADISE: "Trouble In Paradise: Victory" + } +} + +campaign_race_exceptions: Dict[SC2Mission, SC2Race] = { + SC2Mission.WITH_FRIENDS_LIKE_THESE: SC2Race.TERRAN +} + + +def get_goal_location(mission: SC2Mission) -> Union[str, None]: + """ + + :param mission: + :return: Goal location assigned to the goal mission + """ + campaign = mission.campaign + primary_campaign_goal = campaign_final_mission_locations[campaign] + if primary_campaign_goal is not None: + if primary_campaign_goal.mission == mission: + return primary_campaign_goal.location + + campaign_alt_goals = campaign_alt_final_mission_locations[campaign] + if campaign_alt_goals is not None: + return campaign_alt_goals.get(mission) + + return None + + +def get_campaign_potential_goal_missions(campaign: SC2Campaign) -> List[SC2Mission]: + """ + + :param campaign: + :return: All missions that can be the campaign's goal + """ + missions: List[SC2Mission] = list() + primary_goal_mission = campaign_final_mission_locations[campaign] + if primary_goal_mission is not None: + missions.append(primary_goal_mission.mission) + alt_goal_locations = campaign_alt_final_mission_locations[campaign] + if alt_goal_locations is not None: + for mission in alt_goal_locations.keys(): + missions.append(mission) + + return missions + + +def get_no_build_missions() -> List[SC2Mission]: + return [mission for mission in SC2Mission if not mission.build] diff --git a/worlds/sc2/Options.py b/worlds/sc2/Options.py new file mode 100644 index 0000000000..88febb7096 --- /dev/null +++ b/worlds/sc2/Options.py @@ -0,0 +1,908 @@ +from dataclasses import dataclass, fields, Field +from typing import FrozenSet, Union, Set + +from Options import Choice, Toggle, DefaultOnToggle, ItemSet, OptionSet, Range, PerGameCommonOptions +from .MissionTables import SC2Campaign, SC2Mission, lookup_name_to_mission, MissionPools, get_no_build_missions, \ + campaign_mission_table +from worlds.AutoWorld import World + + +class GameDifficulty(Choice): + """ + The difficulty of the campaign, affects enemy AI, starting units, and game speed. + + For those unfamiliar with the Archipelago randomizer, the recommended settings are one difficulty level + lower than the vanilla game + """ + display_name = "Game Difficulty" + option_casual = 0 + option_normal = 1 + option_hard = 2 + option_brutal = 3 + default = 1 + + +class GameSpeed(Choice): + """Optional setting to override difficulty-based game speed.""" + display_name = "Game Speed" + option_default = 0 + option_slower = 1 + option_slow = 2 + option_normal = 3 + option_fast = 4 + option_faster = 5 + default = option_default + + +class DisableForcedCamera(Toggle): + """ + Prevents the game from moving or locking the camera without the player's consent. + """ + display_name = "Disable Forced Camera Movement" + + +class SkipCutscenes(Toggle): + """ + Skips all cutscenes and prevents dialog from blocking progress. + """ + display_name = "Skip Cutscenes" + + +class AllInMap(Choice): + """Determines what version of All-In (WoL final map) that will be generated for the campaign.""" + display_name = "All In Map" + option_ground = 0 + option_air = 1 + + +class MissionOrder(Choice): + """ + Determines the order the missions are played in. The last three mission orders end in a random mission. + Vanilla (83 total if all campaigns enabled): Keeps the standard mission order and branching from the vanilla Campaigns. + Vanilla Shuffled (83 total if all campaigns enabled): Keeps same branching paths from the vanilla Campaigns but randomizes the order of missions within. + Mini Campaign (47 total if all campaigns enabled): Shorter version of the campaign with randomized missions and optional branches. + Medium Grid (16): A 4x4 grid of random missions. Start at the top-left and forge a path towards bottom-right mission to win. + Mini Grid (9): A 3x3 version of Grid. Complete the bottom-right mission to win. + Blitz (12): 12 random missions that open up very quickly. Complete the bottom-right mission to win. + Gauntlet (7): Linear series of 7 random missions to complete the campaign. + Mini Gauntlet (4): Linear series of 4 random missions to complete the campaign. + Tiny Grid (4): A 2x2 version of Grid. Complete the bottom-right mission to win. + Grid (variable): A grid that will resize to use all non-excluded missions. Corners may be omitted to make the grid more square. Complete the bottom-right mission to win. + """ + display_name = "Mission Order" + option_vanilla = 0 + option_vanilla_shuffled = 1 + option_mini_campaign = 2 + option_medium_grid = 3 + option_mini_grid = 4 + option_blitz = 5 + option_gauntlet = 6 + option_mini_gauntlet = 7 + option_tiny_grid = 8 + option_grid = 9 + + +class MaximumCampaignSize(Range): + """ + Sets an upper bound on how many missions to include when a variable-size mission order is selected. + If a set-size mission order is selected, does nothing. + """ + display_name = "Maximum Campaign Size" + range_start = 1 + range_end = 83 + default = 83 + + +class GridTwoStartPositions(Toggle): + """ + If turned on and 'grid' mission order is selected, removes a mission from the starting + corner sets the adjacent two missions as the starter missions. + """ + display_name = "Start with two unlocked missions on grid" + default = Toggle.option_false + + +class ColorChoice(Choice): + option_white = 0 + option_red = 1 + option_blue = 2 + option_teal = 3 + option_purple = 4 + option_yellow = 5 + option_orange = 6 + option_green = 7 + option_light_pink = 8 + option_violet = 9 + option_light_grey = 10 + option_dark_green = 11 + option_brown = 12 + option_light_green = 13 + option_dark_grey = 14 + option_pink = 15 + option_rainbow = 16 + option_default = 17 + default = option_default + + +class PlayerColorTerranRaynor(ColorChoice): + """Determines in-game team color for playable Raynor's Raiders (Terran) factions.""" + display_name = "Terran Player Color (Raynor)" + + +class PlayerColorProtoss(ColorChoice): + """Determines in-game team color for playable Protoss factions.""" + display_name = "Protoss Player Color" + + +class PlayerColorZerg(ColorChoice): + """Determines in-game team color for playable Zerg factions before Kerrigan becomes Primal Kerrigan.""" + display_name = "Zerg Player Color" + + +class PlayerColorZergPrimal(ColorChoice): + """Determines in-game team color for playable Zerg factions after Kerrigan becomes Primal Kerrigan.""" + display_name = "Zerg Player Color (Primal)" + + +class EnableWolMissions(DefaultOnToggle): + """ + Enables missions from main Wings of Liberty campaign. + """ + display_name = "Enable Wings of Liberty missions" + + +class EnableProphecyMissions(DefaultOnToggle): + """ + Enables missions from Prophecy mini-campaign. + """ + display_name = "Enable Prophecy missions" + + +class EnableHotsMissions(DefaultOnToggle): + """ + Enables missions from Heart of the Swarm campaign. + """ + display_name = "Enable Heart of the Swarm missions" + + +class EnableLotVPrologueMissions(DefaultOnToggle): + """ + Enables missions from Prologue campaign. + """ + display_name = "Enable Prologue (Legacy of the Void) missions" + + +class EnableLotVMissions(DefaultOnToggle): + """ + Enables missions from Legacy of the Void campaign. + """ + display_name = "Enable Legacy of the Void (main campaign) missions" + + +class EnableEpilogueMissions(DefaultOnToggle): + """ + Enables missions from Epilogue campaign. + These missions are considered very hard. + + Enabling Wings of Liberty, Heart of the Swarm and Legacy of the Void is strongly recommended in order to play Epilogue. + Not recommended for short mission orders. + See also: Exclude Very Hard Missions + """ + display_name = "Enable Epilogue missions" + + +class EnableNCOMissions(DefaultOnToggle): + """ + Enables missions from Nova Covert Ops campaign. + + Note: For best gameplay experience it's recommended to also enable Wings of Liberty campaign. + """ + display_name = "Enable Nova Covert Ops missions" + + +class ShuffleCampaigns(DefaultOnToggle): + """ + Shuffles the missions between campaigns if enabled. + Only available for Vanilla Shuffled and Mini Campaign mission order + """ + display_name = "Shuffle Campaigns" + + +class ShuffleNoBuild(DefaultOnToggle): + """ + Determines if the no-build missions are included in the shuffle. + If turned off, the no-build missions will not appear. Has no effect for Vanilla mission order. + """ + display_name = "Shuffle No-Build Missions" + + +class StarterUnit(Choice): + """ + Unlocks a random unit at the start of the game. + + Off: No units are provided, the first unit must be obtained from the randomizer + Balanced: A unit that doesn't give the player too much power early on is given + Any Starter Unit: Any starter unit can be given + """ + display_name = "Starter Unit" + option_off = 0 + option_balanced = 1 + option_any_starter_unit = 2 + + +class RequiredTactics(Choice): + """ + Determines the maximum tactical difficulty of the world (separate from mission difficulty). Higher settings + increase randomness. + + Standard: All missions can be completed with good micro and macro. + Advanced: Completing missions may require relying on starting units and micro-heavy units. + No Logic: Units and upgrades may be placed anywhere. LIKELY TO RENDER THE RUN IMPOSSIBLE ON HARDER DIFFICULTIES! + Locks Grant Story Tech option to true. + """ + display_name = "Required Tactics" + option_standard = 0 + option_advanced = 1 + option_no_logic = 2 + + +class GenericUpgradeMissions(Range): + """Determines the percentage of missions in the mission order that must be completed before + level 1 of all weapon and armor upgrades is unlocked. Level 2 upgrades require double the amount of missions, + and level 3 requires triple the amount. The required amounts are always rounded down. + If set to 0, upgrades are instead added to the item pool and must be found to be used.""" + display_name = "Generic Upgrade Missions" + range_start = 0 + range_end = 100 + default = 0 + + +class GenericUpgradeResearch(Choice): + """Determines how weapon and armor upgrades affect missions once unlocked. + + Vanilla: Upgrades must be researched as normal. + Auto In No-Build: In No-Build missions, upgrades are automatically researched. + In all other missions, upgrades must be researched as normal. + Auto In Build: In No-Build missions, upgrades are unavailable as normal. + In all other missions, upgrades are automatically researched. + Always Auto: Upgrades are automatically researched in all missions.""" + display_name = "Generic Upgrade Research" + option_vanilla = 0 + option_auto_in_no_build = 1 + option_auto_in_build = 2 + option_always_auto = 3 + + +class GenericUpgradeItems(Choice): + """Determines how weapon and armor upgrades are split into items. All options produce 3 levels of each item. + Does nothing if upgrades are unlocked by completed mission counts. + + Individual Items: All weapon and armor upgrades are each an item, + resulting in 18 total upgrade items for Terran and 15 total items for Zerg and Protoss each. + Bundle Weapon And Armor: All types of weapon upgrades are one item per race, + and all types of armor upgrades are one item per race, + resulting in 18 total items. + Bundle Unit Class: Weapon and armor upgrades are merged, + but upgrades are bundled separately for each race: + Infantry, Vehicle, and Starship upgrades for Terran (9 items), + Ground and Flyer upgrades for Zerg (6 items), + Ground and Air upgrades for Protoss (6 items), + resulting in 21 total items. + Bundle All: All weapon and armor upgrades are one item per race, + resulting in 9 total items.""" + display_name = "Generic Upgrade Items" + option_individual_items = 0 + option_bundle_weapon_and_armor = 1 + option_bundle_unit_class = 2 + option_bundle_all = 3 + + +class NovaCovertOpsItems(Toggle): + """ + If turned on, the equipment upgrades from Nova Covert Ops may be present in the world. + + If Nova Covert Ops campaign is enabled, this option is locked to be turned on. + """ + display_name = "Nova Covert Ops Items" + default = Toggle.option_true + + +class BroodWarItems(Toggle): + """If turned on, returning items from StarCraft: Brood War may appear in the world.""" + display_name = "Brood War Items" + default = Toggle.option_true + + +class ExtendedItems(Toggle): + """If turned on, original items that did not appear in Campaign mode may appear in the world.""" + display_name = "Extended Items" + default = Toggle.option_true + + +# Current maximum number of upgrades for a unit +MAX_UPGRADES_OPTION = 12 + + +class EnsureGenericItems(Range): + """ + Specifies a minimum percentage of the generic item pool that will be present for the slot. + The generic item pool is the pool of all generically useful items after all exclusions. + Generically-useful items include: Worker upgrades, Building upgrades, economy upgrades, + Mercenaries, Kerrigan levels and abilities, and Spear of Adun abilities + Increasing this percentage will make units less common. + """ + display_name = "Ensure Generic Items" + range_start = 0 + range_end = 100 + default = 25 + + +class MinNumberOfUpgrades(Range): + """ + Set a minimum to the number of upgrades a unit/structure can have. + Note that most units have 4 or 6 upgrades. + If a unit has fewer upgrades than the minimum, it will have all of its upgrades. + + Doesn't affect shared unit upgrades. + """ + display_name = "Minimum number of upgrades per unit/structure" + range_start = 0 + range_end = MAX_UPGRADES_OPTION + default = 2 + + +class MaxNumberOfUpgrades(Range): + """ + Set a maximum to the number of upgrades a unit/structure can have. -1 is used to define unlimited. + Note that most unit have 4 to 6 upgrades. + + Doesn't affect shared unit upgrades. + """ + display_name = "Maximum number of upgrades per unit/structure" + range_start = -1 + range_end = MAX_UPGRADES_OPTION + default = -1 + + +class KerriganPresence(Choice): + """ + Determines whether Kerrigan is playable outside of missions that require her. + + Vanilla: Kerrigan is playable as normal, appears in the same missions as in vanilla game. + Not Present: Kerrigan is not playable, unless the mission requires her to be present. Other hero units stay playable, + and locations normally requiring Kerrigan can be checked by any unit. + Kerrigan level items, active abilities and passive abilities affecting her will not appear. + In missions where the Kerrigan unit is required, story abilities are given in same way as Grant Story Tech is set to true + Not Present And No Passives: In addition to the above, Kerrigan's passive abilities affecting other units (such as Twin Drones) will not appear. + + Note: Always set to "Not Present" if Heart of the Swarm campaign is disabled. + """ + display_name = "Kerrigan Presence" + option_vanilla = 0 + option_not_present = 1 + option_not_present_and_no_passives = 2 + + +class KerriganLevelsPerMissionCompleted(Range): + """ + Determines how many levels Kerrigan gains when a mission is beaten. + + NOTE: Setting this too low can result in generation failures if The Infinite Cycle or Supreme are in the mission pool. + """ + display_name = "Levels Per Mission Beaten" + range_start = 0 + range_end = 20 + default = 0 + + +class KerriganLevelsPerMissionCompletedCap(Range): + """ + Limits how many total levels Kerrigan can gain from beating missions. This does not affect levels gained from items. + Set to -1 to disable this limit. + + NOTE: The following missions have these level requirements: + Supreme: 35 + The Infinite Cycle: 70 + See Grant Story Levels for more details. + """ + display_name = "Levels Per Mission Beaten Cap" + range_start = -1 + range_end = 140 + default = -1 + + +class KerriganLevelItemSum(Range): + """ + Determines the sum of the level items in the world. This does not affect levels gained from beating missions. + + NOTE: The following missions have these level requirements: + Supreme: 35 + The Infinite Cycle: 70 + See Grant Story Levels for more details. + """ + display_name = "Kerrigan Level Item Sum" + range_start = 0 + range_end = 140 + default = 70 + + +class KerriganLevelItemDistribution(Choice): + """Determines the amount and size of Kerrigan level items. + + Vanilla: Uses the distribution in the vanilla campaign. + This entails 32 individual levels and 6 packs of varying sizes. + This distribution always adds up to 70, ignoring the Level Item Sum setting. + Smooth: Uses a custom, condensed distribution of 10 items between sizes 4 and 10, + intended to fit more levels into settings with little room for filler while keeping some variance in level gains. + This distribution always adds up to 70, ignoring the Level Item Sum setting. + Size 70: Uses items worth 70 levels each. + Size 35: Uses items worth 35 levels each. + Size 14: Uses items worth 14 levels each. + Size 10: Uses items worth 10 levels each. + Size 7: Uses items worth 7 levels each. + Size 5: Uses items worth 5 levels each. + Size 2: Uses items worth 2 level eachs. + Size 1: Uses individual levels. As there are not enough locations in the game for this distribution, + this will result in a greatly reduced total level, and is likely to remove many other items.""" + display_name = "Kerrigan Level Item Distribution" + option_vanilla = 0 + option_smooth = 1 + option_size_70 = 2 + option_size_35 = 3 + option_size_14 = 4 + option_size_10 = 5 + option_size_7 = 6 + option_size_5 = 7 + option_size_2 = 8 + option_size_1 = 9 + default = option_smooth + + +class KerriganTotalLevelCap(Range): + """ + Limits how many total levels Kerrigan can gain from any source. Depending on your other settings, + there may be more levels available in the world, but they will not affect Kerrigan. + Set to -1 to disable this limit. + + NOTE: The following missions have these level requirements: + Supreme: 35 + The Infinite Cycle: 70 + See Grant Story Levels for more details. + """ + display_name = "Total Level Cap" + range_start = -1 + range_end = 140 + default = -1 + + +class StartPrimaryAbilities(Range): + """Number of Primary Abilities (Kerrigan Tier 1, 2, and 4) to start the game with. + If set to 4, a Tier 7 ability is also included.""" + display_name = "Starting Primary Abilities" + range_start = 0 + range_end = 4 + default = 0 + + +class KerriganPrimalStatus(Choice): + """Determines when Kerrigan appears in her Primal Zerg form. + This greatly increases her energy regeneration. + + Vanilla: Kerrigan is human in missions that canonically appear before The Crucible, + and zerg thereafter. + Always Zerg: Kerrigan is always zerg. + Always Human: Kerrigan is always human. + Level 35: Kerrigan is human until reaching level 35, and zerg thereafter. + Half Completion: Kerrigan is human until half of the missions in the world are completed, + and zerg thereafter. + Item: Kerrigan's Primal Form is an item. She is human until it is found, and zerg thereafter.""" + display_name = "Kerrigan Primal Status" + option_vanilla = 0 + option_always_zerg = 1 + option_always_human = 2 + option_level_35 = 3 + option_half_completion = 4 + option_item = 5 + + +class SpearOfAdunPresence(Choice): + """ + Determines in which missions Spear of Adun calldowns will be available. + Affects only abilities used from Spear of Adun top menu. + + Not Present: Spear of Adun calldowns are unavailable. + LotV Protoss: Spear of Adun calldowns are only available in LotV main campaign + Protoss: Spear od Adun calldowns are available in any Protoss mission + Everywhere: Spear od Adun calldowns are available in any mission of any race + """ + display_name = "Spear of Adun Presence" + option_not_present = 0 + option_lotv_protoss = 1 + option_protoss = 2 + option_everywhere = 3 + default = option_lotv_protoss + + # Fix case + @classmethod + def get_option_name(cls, value: int) -> str: + if value == SpearOfAdunPresence.option_lotv_protoss: + return "LotV Protoss" + else: + return super().get_option_name(value) + + +class SpearOfAdunPresentInNoBuild(Toggle): + """ + Determines if Spear of Adun calldowns are available in no-build missions. + + If turned on, Spear of Adun calldown powers are available in missions specified under "Spear of Adun Presence". + If turned off, Spear of Adun calldown powers are unavailable in all no-build missions + """ + display_name = "Spear of Adun Present in No-Build" + + +class SpearOfAdunAutonomouslyCastAbilityPresence(Choice): + """ + Determines availability of Spear of Adun powers, that are autonomously cast. + Affects abilities like Reconstruction Beam or Overwatch + + Not Presents: Autocasts are not available. + LotV Protoss: Spear of Adun autocasts are only available in LotV main campaign + Protoss: Spear od Adun autocasts are available in any Protoss mission + Everywhere: Spear od Adun autocasts are available in any mission of any race + """ + display_name = "Spear of Adun Autonomously Cast Powers Presence" + option_not_present = 0 + option_lotv_protoss = 1 + option_protoss = 2 + option_everywhere = 3 + default = option_lotv_protoss + + # Fix case + @classmethod + def get_option_name(cls, value: int) -> str: + if value == SpearOfAdunPresence.option_lotv_protoss: + return "LotV Protoss" + else: + return super().get_option_name(value) + + +class SpearOfAdunAutonomouslyCastPresentInNoBuild(Toggle): + """ + Determines if Spear of Adun autocasts are available in no-build missions. + + If turned on, Spear of Adun autocasts are available in missions specified under "Spear of Adun Autonomously Cast Powers Presence". + If turned off, Spear of Adun autocasts are unavailable in all no-build missions + """ + display_name = "Spear of Adun Autonomously Cast Powers Present in No-Build" + + +class GrantStoryTech(Toggle): + """ + If set true, grants special tech required for story mission completion for duration of the mission. + Otherwise, you need to find these tech by a normal means as items. + Affects story missions like Back in the Saddle and Supreme + + Locked to true if Required Tactics is set to no logic. + """ + display_name = "Grant Story Tech" + + +class GrantStoryLevels(Choice): + """ + If enabled, grants Kerrigan the required minimum levels for the following missions: + Supreme: 35 + The Infinite Cycle: 70 + The bonus levels only apply during the listed missions, and can exceed the Total Level Cap. + + If disabled, either of these missions is included, and there are not enough levels in the world, generation may fail. + To prevent this, either increase the amount of levels in the world, or enable this option. + + If disabled and Required Tactics is set to no logic, this option is forced to Minimum. + + Disabled: Kerrigan does not get bonus levels for these missions, + instead the levels must be gained from items or beating missions. + Additive: Kerrigan gains bonus levels equal to the mission's required level. + Minimum: Kerrigan is either at her real level, or at the mission's required level, + depending on which is higher. + """ + display_name = "Grant Story Levels" + option_disabled = 0 + option_additive = 1 + option_minimum = 2 + default = option_minimum + + +class TakeOverAIAllies(Toggle): + """ + On maps supporting this feature allows you to take control over an AI Ally. + """ + display_name = "Take Over AI Allies" + + +class LockedItems(ItemSet): + """Guarantees that these items will be unlockable""" + display_name = "Locked Items" + + +class ExcludedItems(ItemSet): + """Guarantees that these items will not be unlockable""" + display_name = "Excluded Items" + + +class ExcludedMissions(OptionSet): + """Guarantees that these missions will not appear in the campaign + Doesn't apply to vanilla mission order. + It may be impossible to build a valid campaign if too many missions are excluded.""" + display_name = "Excluded Missions" + valid_keys = {mission.mission_name for mission in SC2Mission} + + +class ExcludeVeryHardMissions(Choice): + """ + Excludes Very Hard missions outside of Epilogue campaign (All-In, Salvation, and all Epilogue missions are considered Very Hard). + Doesn't apply to "Vanilla" mission order. + + Default: Not excluded for mission orders "Vanilla Shuffled" or "Grid" with Maximum Campaign Size >= 20, + excluded for any other order + Yes: Non-Epilogue Very Hard missions are excluded and won't be generated + No: Non-Epilogue Very Hard missions can appear normally. Not recommended for too short mission orders. + + See also: Excluded Missions, Enable Epilogue Missions, Maximum Campaign Size + """ + display_name = "Exclude Very Hard Missions" + option_default = 0 + option_true = 1 + option_false = 2 + + @classmethod + def get_option_name(cls, value): + return ["Default", "Yes", "No"][int(value)] + + +class LocationInclusion(Choice): + option_enabled = 0 + option_resources = 1 + option_disabled = 2 + + +class VanillaLocations(LocationInclusion): + """ + Enables or disables item rewards for completing vanilla objectives. + Vanilla objectives are bonus objectives from the vanilla game, + along with some additional objectives to balance the missions. + Enable these locations for a balanced experience. + + Enabled: All locations fitting into this do their normal rewards + Resources: Forces these locations to contain Starting Resources + Disabled: Removes item rewards from these locations. + + Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. + See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) + """ + display_name = "Vanilla Locations" + + +class ExtraLocations(LocationInclusion): + """ + Enables or disables item rewards for mission progress and minor objectives. + This includes mandatory mission objectives, + collecting reinforcements and resource pickups, + destroying structures, and overcoming minor challenges. + Enables these locations to add more checks and items to your world. + + Enabled: All locations fitting into this do their normal rewards + Resources: Forces these locations to contain Starting Resources + Disabled: Removes item rewards from these locations. + + Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. + See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) + """ + display_name = "Extra Locations" + + +class ChallengeLocations(LocationInclusion): + """ + Enables or disables item rewards for completing challenge tasks. + Challenges are tasks that are more difficult than completing the mission, and are often based on achievements. + You might be required to visit the same mission later after getting stronger in order to finish these tasks. + Enable these locations to increase the difficulty of completing the multiworld. + + Enabled: All locations fitting into this do their normal rewards + Resources: Forces these locations to contain Starting Resources + Disabled: Removes item rewards from these locations. + + Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. + See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) + """ + display_name = "Challenge Locations" + + +class MasteryLocations(LocationInclusion): + """ + Enables or disables item rewards for overcoming especially difficult challenges. + These challenges are often based on Mastery achievements and Feats of Strength. + Enable these locations to add the most difficult checks to the world. + + Enabled: All locations fitting into this do their normal rewards + Resources: Forces these locations to contain Starting Resources + Disabled: Removes item rewards from these locations. + + Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. + See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) + """ + display_name = "Mastery Locations" + + +class MineralsPerItem(Range): + """ + Configures how many minerals are given per resource item. + """ + display_name = "Minerals Per Item" + range_start = 0 + range_end = 500 + default = 25 + + +class VespenePerItem(Range): + """ + Configures how much vespene gas is given per resource item. + """ + display_name = "Vespene Per Item" + range_start = 0 + range_end = 500 + default = 25 + + +class StartingSupplyPerItem(Range): + """ + Configures how much starting supply per is given per item. + """ + display_name = "Starting Supply Per Item" + range_start = 0 + range_end = 200 + default = 5 + + +@dataclass +class Starcraft2Options(PerGameCommonOptions): + game_difficulty: GameDifficulty + game_speed: GameSpeed + disable_forced_camera: DisableForcedCamera + skip_cutscenes: SkipCutscenes + all_in_map: AllInMap + mission_order: MissionOrder + maximum_campaign_size: MaximumCampaignSize + grid_two_start_positions: GridTwoStartPositions + player_color_terran_raynor: PlayerColorTerranRaynor + player_color_protoss: PlayerColorProtoss + player_color_zerg: PlayerColorZerg + player_color_zerg_primal: PlayerColorZergPrimal + enable_wol_missions: EnableWolMissions + enable_prophecy_missions: EnableProphecyMissions + enable_hots_missions: EnableHotsMissions + enable_lotv_prologue_missions: EnableLotVPrologueMissions + enable_lotv_missions: EnableLotVMissions + enable_epilogue_missions: EnableEpilogueMissions + enable_nco_missions: EnableNCOMissions + shuffle_campaigns: ShuffleCampaigns + shuffle_no_build: ShuffleNoBuild + starter_unit: StarterUnit + required_tactics: RequiredTactics + ensure_generic_items: EnsureGenericItems + min_number_of_upgrades: MinNumberOfUpgrades + max_number_of_upgrades: MaxNumberOfUpgrades + generic_upgrade_missions: GenericUpgradeMissions + generic_upgrade_research: GenericUpgradeResearch + generic_upgrade_items: GenericUpgradeItems + kerrigan_presence: KerriganPresence + kerrigan_levels_per_mission_completed: KerriganLevelsPerMissionCompleted + kerrigan_levels_per_mission_completed_cap: KerriganLevelsPerMissionCompletedCap + kerrigan_level_item_sum: KerriganLevelItemSum + kerrigan_level_item_distribution: KerriganLevelItemDistribution + kerrigan_total_level_cap: KerriganTotalLevelCap + start_primary_abilities: StartPrimaryAbilities + kerrigan_primal_status: KerriganPrimalStatus + spear_of_adun_presence: SpearOfAdunPresence + spear_of_adun_present_in_no_build: SpearOfAdunPresentInNoBuild + spear_of_adun_autonomously_cast_ability_presence: SpearOfAdunAutonomouslyCastAbilityPresence + spear_of_adun_autonomously_cast_present_in_no_build: SpearOfAdunAutonomouslyCastPresentInNoBuild + grant_story_tech: GrantStoryTech + grant_story_levels: GrantStoryLevels + take_over_ai_allies: TakeOverAIAllies + locked_items: LockedItems + excluded_items: ExcludedItems + excluded_missions: ExcludedMissions + exclude_very_hard_missions: ExcludeVeryHardMissions + nco_items: NovaCovertOpsItems + bw_items: BroodWarItems + ext_items: ExtendedItems + vanilla_locations: VanillaLocations + extra_locations: ExtraLocations + challenge_locations: ChallengeLocations + mastery_locations: MasteryLocations + minerals_per_item: MineralsPerItem + vespene_per_item: VespenePerItem + starting_supply_per_item: StartingSupplyPerItem + + +def get_option_value(world: World, name: str) -> Union[int, FrozenSet]: + if world is None: + field: Field = [class_field for class_field in fields(Starcraft2Options) if class_field.name == name][0] + return field.type.default + + player_option = getattr(world.options, name) + + return player_option.value + + +def get_enabled_campaigns(world: World) -> Set[SC2Campaign]: + enabled_campaigns = set() + if get_option_value(world, "enable_wol_missions"): + enabled_campaigns.add(SC2Campaign.WOL) + if get_option_value(world, "enable_prophecy_missions"): + enabled_campaigns.add(SC2Campaign.PROPHECY) + if get_option_value(world, "enable_hots_missions"): + enabled_campaigns.add(SC2Campaign.HOTS) + if get_option_value(world, "enable_lotv_prologue_missions"): + enabled_campaigns.add(SC2Campaign.PROLOGUE) + if get_option_value(world, "enable_lotv_missions"): + enabled_campaigns.add(SC2Campaign.LOTV) + if get_option_value(world, "enable_epilogue_missions"): + enabled_campaigns.add(SC2Campaign.EPILOGUE) + if get_option_value(world, "enable_nco_missions"): + enabled_campaigns.add(SC2Campaign.NCO) + return enabled_campaigns + + +def get_disabled_campaigns(world: World) -> Set[SC2Campaign]: + all_campaigns = set(SC2Campaign) + enabled_campaigns = get_enabled_campaigns(world) + disabled_campaigns = all_campaigns.difference(enabled_campaigns) + disabled_campaigns.remove(SC2Campaign.GLOBAL) + return disabled_campaigns + + +def get_excluded_missions(world: World) -> Set[SC2Mission]: + mission_order_type = get_option_value(world, "mission_order") + excluded_mission_names = get_option_value(world, "excluded_missions") + shuffle_no_build = get_option_value(world, "shuffle_no_build") + disabled_campaigns = get_disabled_campaigns(world) + + excluded_missions: Set[SC2Mission] = set([lookup_name_to_mission[name] for name in excluded_mission_names]) + + # Excluding Very Hard missions depending on options + if (get_option_value(world, "exclude_very_hard_missions") == ExcludeVeryHardMissions.option_true + ) or ( + get_option_value(world, "exclude_very_hard_missions") == ExcludeVeryHardMissions.option_default + and ( + mission_order_type not in [MissionOrder.option_vanilla_shuffled, MissionOrder.option_grid] + or ( + mission_order_type == MissionOrder.option_grid + and get_option_value(world, "maximum_campaign_size") < 20 + ) + ) + ): + excluded_missions = excluded_missions.union( + [mission for mission in SC2Mission if + mission.pool == MissionPools.VERY_HARD and mission.campaign != SC2Campaign.EPILOGUE] + ) + # Omitting No-Build missions if not shuffling no-build + if not shuffle_no_build: + excluded_missions = excluded_missions.union(get_no_build_missions()) + # Omitting missions not in enabled campaigns + for campaign in disabled_campaigns: + excluded_missions = excluded_missions.union(campaign_mission_table[campaign]) + + return excluded_missions + + +campaign_depending_orders = [ + MissionOrder.option_vanilla, + MissionOrder.option_vanilla_shuffled, + MissionOrder.option_mini_campaign +] + +kerrigan_unit_available = [ + KerriganPresence.option_vanilla, +] \ No newline at end of file diff --git a/worlds/sc2/PoolFilter.py b/worlds/sc2/PoolFilter.py new file mode 100644 index 0000000000..5f8151ed39 --- /dev/null +++ b/worlds/sc2/PoolFilter.py @@ -0,0 +1,595 @@ +from typing import Callable, Dict, List, Set, Union, Tuple +from BaseClasses import Item, Location +from .Items import get_full_item_list, spider_mine_sources, second_pass_placeable_items, progressive_if_nco, \ + progressive_if_ext, spear_of_adun_calldowns, spear_of_adun_castable_passives, nova_equipment +from .MissionTables import mission_orders, MissionInfo, MissionPools, \ + get_campaign_goal_priority, campaign_final_mission_locations, campaign_alt_final_mission_locations, \ + SC2Campaign, SC2Race, SC2CampaignGoalPriority, SC2Mission +from .Options import get_option_value, MissionOrder, \ + get_enabled_campaigns, get_disabled_campaigns, RequiredTactics, kerrigan_unit_available, GrantStoryTech, \ + TakeOverAIAllies, SpearOfAdunPresence, SpearOfAdunAutonomouslyCastAbilityPresence, campaign_depending_orders, \ + ShuffleCampaigns, get_excluded_missions, ShuffleNoBuild, ExtraLocations, GrantStoryLevels +from . import ItemNames +from worlds.AutoWorld import World + +# Items with associated upgrades +UPGRADABLE_ITEMS = {item.parent_item for item in get_full_item_list().values() if item.parent_item} + +BARRACKS_UNITS = { + ItemNames.MARINE, ItemNames.MEDIC, ItemNames.FIREBAT, ItemNames.MARAUDER, + ItemNames.REAPER, ItemNames.GHOST, ItemNames.SPECTRE, ItemNames.HERC, +} +FACTORY_UNITS = { + ItemNames.HELLION, ItemNames.VULTURE, ItemNames.GOLIATH, ItemNames.DIAMONDBACK, + ItemNames.SIEGE_TANK, ItemNames.THOR, ItemNames.PREDATOR, ItemNames.WIDOW_MINE, + ItemNames.CYCLONE, ItemNames.WARHOUND, +} +STARPORT_UNITS = { + ItemNames.MEDIVAC, ItemNames.WRAITH, ItemNames.VIKING, ItemNames.BANSHEE, + ItemNames.BATTLECRUISER, ItemNames.HERCULES, ItemNames.SCIENCE_VESSEL, ItemNames.RAVEN, + ItemNames.LIBERATOR, ItemNames.VALKYRIE, +} + + +def filter_missions(world: World) -> Dict[MissionPools, List[SC2Mission]]: + + """ + Returns a semi-randomly pruned tuple of no-build, easy, medium, and hard mission sets + """ + world: World = world + mission_order_type = get_option_value(world, "mission_order") + shuffle_no_build = get_option_value(world, "shuffle_no_build") + enabled_campaigns = get_enabled_campaigns(world) + grant_story_tech = get_option_value(world, "grant_story_tech") == GrantStoryTech.option_true + grant_story_levels = get_option_value(world, "grant_story_levels") != GrantStoryLevels.option_disabled + extra_locations = get_option_value(world, "extra_locations") + excluded_missions: Set[SC2Mission] = get_excluded_missions(world) + mission_pools: Dict[MissionPools, List[SC2Mission]] = {} + for mission in SC2Mission: + if not mission_pools.get(mission.pool): + mission_pools[mission.pool] = list() + mission_pools[mission.pool].append(mission) + # A bit of safeguard: + for mission_pool in MissionPools: + if not mission_pools.get(mission_pool): + mission_pools[mission_pool] = [] + + if mission_order_type == MissionOrder.option_vanilla: + # Vanilla uses the entire mission pool + goal_priorities: Dict[SC2Campaign, SC2CampaignGoalPriority] = {campaign: get_campaign_goal_priority(campaign) for campaign in enabled_campaigns} + goal_level = max(goal_priorities.values()) + candidate_campaigns = [campaign for campaign, goal_priority in goal_priorities.items() if goal_priority == goal_level] + goal_campaign = world.random.choice(candidate_campaigns) + if campaign_final_mission_locations[goal_campaign] is not None: + mission_pools[MissionPools.FINAL] = [campaign_final_mission_locations[goal_campaign].mission] + else: + mission_pools[MissionPools.FINAL] = [list(campaign_alt_final_mission_locations[goal_campaign].keys())[0]] + remove_final_mission_from_other_pools(mission_pools) + return mission_pools + + # Finding the goal map + goal_priorities = {campaign: get_campaign_goal_priority(campaign, excluded_missions) for campaign in enabled_campaigns} + goal_level = max(goal_priorities.values()) + candidate_campaigns = [campaign for campaign, goal_priority in goal_priorities.items() if goal_priority == goal_level] + goal_campaign = world.random.choice(candidate_campaigns) + primary_goal = campaign_final_mission_locations[goal_campaign] + if primary_goal is None or primary_goal.mission in excluded_missions: + # No primary goal or its mission is excluded + candidate_missions = list(campaign_alt_final_mission_locations[goal_campaign].keys()) + candidate_missions = [mission for mission in candidate_missions if mission not in excluded_missions] + if len(candidate_missions) == 0: + raise Exception("There are no valid goal missions. Please exclude fewer missions.") + goal_mission = world.random.choice(candidate_missions) + else: + goal_mission = primary_goal.mission + + # Excluding missions + for difficulty, mission_pool in mission_pools.items(): + mission_pools[difficulty] = [mission for mission in mission_pool if mission not in excluded_missions] + mission_pools[MissionPools.FINAL] = [goal_mission] + + # Mission pool changes + adv_tactics = get_option_value(world, "required_tactics") != RequiredTactics.option_standard + + def move_mission(mission: SC2Mission, current_pool, new_pool): + if mission in mission_pools[current_pool]: + mission_pools[current_pool].remove(mission) + mission_pools[new_pool].append(mission) + # WoL + if shuffle_no_build == ShuffleNoBuild.option_false or adv_tactics: + # Replacing No Build missions with Easy missions + # WoL + move_mission(SC2Mission.ZERO_HOUR, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.EVACUATION, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.DEVILS_PLAYGROUND, MissionPools.EASY, MissionPools.STARTER) + # LotV + move_mission(SC2Mission.THE_GROWING_SHADOW, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.THE_SPEAR_OF_ADUN, MissionPools.EASY, MissionPools.STARTER) + if extra_locations == ExtraLocations.option_enabled: + move_mission(SC2Mission.SKY_SHIELD, MissionPools.EASY, MissionPools.STARTER) + # Pushing this to Easy + move_mission(SC2Mission.THE_GREAT_TRAIN_ROBBERY, MissionPools.MEDIUM, MissionPools.EASY) + if shuffle_no_build == ShuffleNoBuild.option_false: + # Pushing Outbreak to Normal, as it cannot be placed as the second mission on Build-Only + move_mission(SC2Mission.OUTBREAK, MissionPools.EASY, MissionPools.MEDIUM) + # Pushing extra Normal missions to Easy + move_mission(SC2Mission.ECHOES_OF_THE_FUTURE, MissionPools.MEDIUM, MissionPools.EASY) + move_mission(SC2Mission.CUTTHROAT, MissionPools.MEDIUM, MissionPools.EASY) + # Additional changes on Advanced Tactics + if adv_tactics: + # WoL + move_mission(SC2Mission.THE_GREAT_TRAIN_ROBBERY, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.SMASH_AND_GRAB, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.THE_MOEBIUS_FACTOR, MissionPools.MEDIUM, MissionPools.EASY) + move_mission(SC2Mission.WELCOME_TO_THE_JUNGLE, MissionPools.MEDIUM, MissionPools.EASY) + move_mission(SC2Mission.ENGINE_OF_DESTRUCTION, MissionPools.HARD, MissionPools.MEDIUM) + # LotV + move_mission(SC2Mission.AMON_S_REACH, MissionPools.EASY, MissionPools.STARTER) + # Prophecy needs to be adjusted on tiny grid + if enabled_campaigns == {SC2Campaign.PROPHECY} and mission_order_type == MissionOrder.option_tiny_grid: + move_mission(SC2Mission.A_SINISTER_TURN, MissionPools.MEDIUM, MissionPools.EASY) + # Prologue's only valid starter is the goal mission + if enabled_campaigns == {SC2Campaign.PROLOGUE} \ + or mission_order_type in campaign_depending_orders \ + and get_option_value(world, "shuffle_campaigns") == ShuffleCampaigns.option_false: + move_mission(SC2Mission.DARK_WHISPERS, MissionPools.EASY, MissionPools.STARTER) + # HotS + kerriganless = get_option_value(world, "kerrigan_presence") not in kerrigan_unit_available \ + or SC2Campaign.HOTS not in enabled_campaigns + if adv_tactics: + # Medium -> Easy + for mission in (SC2Mission.FIRE_IN_THE_SKY, SC2Mission.WAKING_THE_ANCIENT, SC2Mission.CONVICTION): + move_mission(mission, MissionPools.MEDIUM, MissionPools.EASY) + # Hard -> Medium + move_mission(SC2Mission.PHANTOMS_OF_THE_VOID, MissionPools.HARD, MissionPools.MEDIUM) + if not kerriganless: + # Additional starter mission assuming player starts with minimal anti-air + move_mission(SC2Mission.WAKING_THE_ANCIENT, MissionPools.EASY, MissionPools.STARTER) + if grant_story_tech: + # Additional starter mission if player is granted story tech + move_mission(SC2Mission.ENEMY_WITHIN, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.TEMPLAR_S_RETURN, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.THE_ESCAPE, MissionPools.MEDIUM, MissionPools.STARTER) + move_mission(SC2Mission.IN_THE_ENEMY_S_SHADOW, MissionPools.MEDIUM, MissionPools.STARTER) + if (grant_story_tech and grant_story_levels) or kerriganless: + # The player has, all the stuff he needs, provided under these settings + move_mission(SC2Mission.SUPREME, MissionPools.MEDIUM, MissionPools.STARTER) + move_mission(SC2Mission.THE_INFINITE_CYCLE, MissionPools.HARD, MissionPools.STARTER) + if get_option_value(world, "take_over_ai_allies") == TakeOverAIAllies.option_true: + move_mission(SC2Mission.HARBINGER_OF_OBLIVION, MissionPools.MEDIUM, MissionPools.STARTER) + if len(mission_pools[MissionPools.STARTER]) < 2 and not kerriganless or adv_tactics: + # Conditionally moving Easy missions to Starter + move_mission(SC2Mission.HARVEST_OF_SCREAMS, MissionPools.EASY, MissionPools.STARTER) + move_mission(SC2Mission.DOMINATION, MissionPools.EASY, MissionPools.STARTER) + if len(mission_pools[MissionPools.STARTER]) < 2: + move_mission(SC2Mission.TEMPLAR_S_RETURN, MissionPools.EASY, MissionPools.STARTER) + if len(mission_pools[MissionPools.STARTER]) + len(mission_pools[MissionPools.EASY]) < 2: + # Flashpoint needs just a few items at start but competent comp at the end + move_mission(SC2Mission.FLASHPOINT, MissionPools.HARD, MissionPools.EASY) + + remove_final_mission_from_other_pools(mission_pools) + return mission_pools + + +def remove_final_mission_from_other_pools(mission_pools: Dict[MissionPools, List[SC2Mission]]): + final_missions = mission_pools[MissionPools.FINAL] + for pool, missions in mission_pools.items(): + if pool == MissionPools.FINAL: + continue + for final_mission in final_missions: + while final_mission in missions: + missions.remove(final_mission) + + +def get_item_upgrades(inventory: List[Item], parent_item: Union[Item, str]) -> List[Item]: + item_name = parent_item.name if isinstance(parent_item, Item) else parent_item + return [ + inv_item for inv_item in inventory + if get_full_item_list()[inv_item.name].parent_item == item_name + ] + + +def get_item_quantity(item: Item, world: World): + if (not get_option_value(world, "nco_items")) \ + and SC2Campaign.NCO in get_disabled_campaigns(world) \ + and item.name in progressive_if_nco: + return 1 + if (not get_option_value(world, "ext_items")) \ + and item.name in progressive_if_ext: + return 1 + return get_full_item_list()[item.name].quantity + + +def copy_item(item: Item): + return Item(item.name, item.classification, item.code, item.player) + + +def num_missions(world: World) -> int: + mission_order_type = get_option_value(world, "mission_order") + if mission_order_type != MissionOrder.option_grid: + mission_order = mission_orders[mission_order_type]() + misssions = [mission for campaign in mission_order for mission in mission_order[campaign]] + return len(misssions) - 1 # Menu + else: + mission_pools = filter_missions(world) + return sum(len(pool) for _, pool in mission_pools.items()) + + +class ValidInventory: + + def has(self, item: str, player: int): + return item in self.logical_inventory + + def has_any(self, items: Set[str], player: int): + return any(item in self.logical_inventory for item in items) + + def has_all(self, items: Set[str], player: int): + return all(item in self.logical_inventory for item in items) + + def has_group(self, item_group: str, player: int, count: int = 1): + return False # Deliberately fails here, as item pooling is not aware about mission layout + + def count_group(self, item_name_group: str, player: int) -> int: + return 0 # For item filtering assume no missions are beaten + + def count(self, item: str, player: int) -> int: + return len([inventory_item for inventory_item in self.logical_inventory if inventory_item == item]) + + def has_units_per_structure(self) -> bool: + return len(BARRACKS_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure and \ + len(FACTORY_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure and \ + len(STARPORT_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure + + def generate_reduced_inventory(self, inventory_size: int, mission_requirements: List[Tuple[str, Callable]]) -> List[Item]: + """Attempts to generate a reduced inventory that can fulfill the mission requirements.""" + inventory = list(self.item_pool) + locked_items = list(self.locked_items) + item_list = get_full_item_list() + self.logical_inventory = [ + item.name for item in inventory + locked_items + self.existing_items + if item_list[item.name].is_important_for_filtering() # Track all Progression items and those with complex rules for filtering + ] + requirements = mission_requirements + parent_items = self.item_children.keys() + parent_lookup = {child: parent for parent, children in self.item_children.items() for child in children} + minimum_upgrades = get_option_value(self.world, "min_number_of_upgrades") + + def attempt_removal(item: Item) -> bool: + inventory.remove(item) + # Only run logic checks when removing logic items + if item.name in self.logical_inventory: + self.logical_inventory.remove(item.name) + if not all(requirement(self) for (_, requirement) in mission_requirements): + # If item cannot be removed, lock or revert + self.logical_inventory.append(item.name) + for _ in range(get_item_quantity(item, self.world)): + locked_items.append(copy_item(item)) + return False + return True + + # Limit the maximum number of upgrades + maxNbUpgrade = get_option_value(self.world, "max_number_of_upgrades") + if maxNbUpgrade != -1: + unit_avail_upgrades = {} + # Needed to take into account locked/existing items + unit_nb_upgrades = {} + for item in inventory: + cItem = item_list[item.name] + if item.name in UPGRADABLE_ITEMS and item.name not in unit_avail_upgrades: + unit_avail_upgrades[item.name] = [] + unit_nb_upgrades[item.name] = 0 + elif cItem.parent_item is not None: + if cItem.parent_item not in unit_avail_upgrades: + unit_avail_upgrades[cItem.parent_item] = [item] + unit_nb_upgrades[cItem.parent_item] = 1 + else: + unit_avail_upgrades[cItem.parent_item].append(item) + unit_nb_upgrades[cItem.parent_item] += 1 + # For those two categories, we count them but dont include them in removal + for item in locked_items + self.existing_items: + cItem = item_list[item.name] + if item.name in UPGRADABLE_ITEMS and item.name not in unit_avail_upgrades: + unit_avail_upgrades[item.name] = [] + unit_nb_upgrades[item.name] = 0 + elif cItem.parent_item is not None: + if cItem.parent_item not in unit_avail_upgrades: + unit_nb_upgrades[cItem.parent_item] = 1 + else: + unit_nb_upgrades[cItem.parent_item] += 1 + # Making sure that the upgrades being removed is random + shuffled_unit_upgrade_list = list(unit_avail_upgrades.keys()) + self.world.random.shuffle(shuffled_unit_upgrade_list) + for unit in shuffled_unit_upgrade_list: + while (unit_nb_upgrades[unit] > maxNbUpgrade) \ + and (len(unit_avail_upgrades[unit]) > 0): + itemCandidate = self.world.random.choice(unit_avail_upgrades[unit]) + success = attempt_removal(itemCandidate) + # Whatever it succeed to remove the iventory or it fails and thus + # lock it, the upgrade is no longer available for removal + unit_avail_upgrades[unit].remove(itemCandidate) + if success: + unit_nb_upgrades[unit] -= 1 + + # Locking minimum upgrades for items that have already been locked/placed when minimum required + if minimum_upgrades > 0: + known_items = self.existing_items + locked_items + known_parents = [item for item in known_items if item in parent_items] + for parent in known_parents: + child_items = self.item_children[parent] + removable_upgrades = [item for item in inventory if item in child_items] + locked_upgrade_count = sum(1 if item in child_items else 0 for item in known_items) + self.world.random.shuffle(removable_upgrades) + while len(removable_upgrades) > 0 and locked_upgrade_count < minimum_upgrades: + item_to_lock = removable_upgrades.pop() + inventory.remove(item_to_lock) + locked_items.append(copy_item(item_to_lock)) + locked_upgrade_count += 1 + + if self.min_units_per_structure > 0 and self.has_units_per_structure(): + requirements.append(("Minimum units per structure", lambda state: state.has_units_per_structure())) + + # Determining if the full-size inventory can complete campaign + failed_locations: List[str] = [location for (location, requirement) in requirements if not requirement(self)] + if len(failed_locations) > 0: + raise Exception(f"Too many items excluded - couldn't satisfy access rules for the following locations:\n{failed_locations}") + + # Optionally locking generic items + generic_items = [item for item in inventory if item.name in second_pass_placeable_items] + reserved_generic_percent = get_option_value(self.world, "ensure_generic_items") / 100 + reserved_generic_amount = int(len(generic_items) * reserved_generic_percent) + removable_generic_items = [] + self.world.random.shuffle(generic_items) + for item in generic_items[:reserved_generic_amount]: + locked_items.append(copy_item(item)) + inventory.remove(item) + if item.name not in self.logical_inventory and item.name not in self.locked_items: + removable_generic_items.append(item) + + # Main cull process + unused_items = [] # Reusable items for the second pass + while len(inventory) + len(locked_items) > inventory_size: + if len(inventory) == 0: + # There are more items than locations and all of them are already locked due to YAML or logic. + # First, drop non-logic generic items to free up space + while len(removable_generic_items) > 0 and len(locked_items) > inventory_size: + removed_item = removable_generic_items.pop() + locked_items.remove(removed_item) + # If there still isn't enough space, push locked items into start inventory + self.world.random.shuffle(locked_items) + while len(locked_items) > inventory_size: + item: Item = locked_items.pop() + self.multiworld.push_precollected(item) + break + # Select random item from removable items + item = self.world.random.choice(inventory) + # Do not remove item if it would drop upgrades below minimum + if minimum_upgrades > 0: + parent_item = parent_lookup.get(item, None) + if parent_item: + count = sum(1 if item in self.item_children[parent_item] else 0 for item in inventory + locked_items) + if count <= minimum_upgrades: + if parent_item in inventory: + # Attempt to remove parent instead, if possible + item = parent_item + else: + # Lock remaining upgrades + for item in self.item_children[parent_item]: + if item in inventory: + inventory.remove(item) + locked_items.append(copy_item(item)) + continue + + # Drop child items when removing a parent + if item in parent_items: + items_to_remove = [item for item in self.item_children[item] if item in inventory] + success = attempt_removal(item) + if success: + while len(items_to_remove) > 0: + item_to_remove = items_to_remove.pop() + if item_to_remove not in inventory: + continue + attempt_removal(item_to_remove) + else: + # Unimportant upgrades may be added again in the second pass + if attempt_removal(item): + unused_items.append(item.name) + + # Removing extra dependencies + # WoL + logical_inventory_set = set(self.logical_inventory) + if not spider_mine_sources & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Spider Mine)")] + if not BARRACKS_UNITS & logical_inventory_set: + inventory = [item for item in inventory if + not (item.name.startswith(ItemNames.TERRAN_INFANTRY_UPGRADE_PREFIX) or item.name == ItemNames.ORBITAL_STRIKE)] + if not FACTORY_UNITS & logical_inventory_set: + inventory = [item for item in inventory if not item.name.startswith(ItemNames.TERRAN_VEHICLE_UPGRADE_PREFIX)] + if not STARPORT_UNITS & logical_inventory_set: + inventory = [item for item in inventory if not item.name.startswith(ItemNames.TERRAN_SHIP_UPGRADE_PREFIX)] + # HotS + # Baneling without sources => remove Baneling and upgrades + if (ItemNames.ZERGLING_BANELING_ASPECT in self.logical_inventory + and ItemNames.ZERGLING not in self.logical_inventory + and ItemNames.KERRIGAN_SPAWN_BANELINGS not in self.logical_inventory + ): + inventory = [item for item in inventory if item.name != ItemNames.ZERGLING_BANELING_ASPECT] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.ZERGLING_BANELING_ASPECT] + # Spawn Banelings without Zergling => remove Baneling unit, keep upgrades except macro ones + if (ItemNames.ZERGLING_BANELING_ASPECT in self.logical_inventory + and ItemNames.ZERGLING not in self.logical_inventory + and ItemNames.KERRIGAN_SPAWN_BANELINGS in self.logical_inventory + ): + inventory = [item for item in inventory if item.name != ItemNames.ZERGLING_BANELING_ASPECT] + inventory = [item for item in inventory if item.name != ItemNames.BANELING_RAPID_METAMORPH] + if not {ItemNames.MUTALISK, ItemNames.CORRUPTOR, ItemNames.SCOURGE} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.startswith(ItemNames.ZERG_FLYER_UPGRADE_PREFIX)] + locked_items = [item for item in locked_items if not item.name.startswith(ItemNames.ZERG_FLYER_UPGRADE_PREFIX)] + # T3 items removal rules - remove morph and its upgrades if the basic unit isn't in + if not {ItemNames.MUTALISK, ItemNames.CORRUPTOR} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Mutalisk/Corruptor)")] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.MUTALISK_CORRUPTOR_GUARDIAN_ASPECT] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.MUTALISK_CORRUPTOR_DEVOURER_ASPECT] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT] + if ItemNames.ROACH not in logical_inventory_set: + inventory = [item for item in inventory if item.name != ItemNames.ROACH_RAVAGER_ASPECT] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.ROACH_RAVAGER_ASPECT] + if ItemNames.HYDRALISK not in logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Hydralisk)")] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.HYDRALISK_LURKER_ASPECT] + inventory = [item for item in inventory if item_list[item.name].parent_item != ItemNames.HYDRALISK_IMPALER_ASPECT] + # LotV + # Shared unit upgrades between several units + if not {ItemNames.STALKER, ItemNames.INSTIGATOR, ItemNames.SLAYER} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Stalker/Instigator/Slayer)")] + if not {ItemNames.PHOENIX, ItemNames.MIRAGE} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Phoenix/Mirage)")] + if not {ItemNames.VOID_RAY, ItemNames.DESTROYER} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Void Ray/Destroyer)")] + if not {ItemNames.IMMORTAL, ItemNames.ANNIHILATOR} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Immortal/Annihilator)")] + if not {ItemNames.DARK_TEMPLAR, ItemNames.AVENGER, ItemNames.BLOOD_HUNTER} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Dark Templar/Avenger/Blood Hunter)")] + if not {ItemNames.HIGH_TEMPLAR, ItemNames.SIGNIFIER, ItemNames.ASCENDANT, ItemNames.DARK_TEMPLAR} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Archon)")] + logical_inventory_set.difference_update([item_name for item_name in logical_inventory_set if item_name.endswith("(Archon)")]) + if not {ItemNames.HIGH_TEMPLAR, ItemNames.SIGNIFIER, ItemNames.ARCHON_HIGH_ARCHON} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(High Templar/Signifier)")] + if ItemNames.SUPPLICANT not in logical_inventory_set: + inventory = [item for item in inventory if item.name != ItemNames.ASCENDANT_POWER_OVERWHELMING] + if not {ItemNames.DARK_ARCHON, ItemNames.DARK_TEMPLAR_DARK_ARCHON_MELD} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Dark Archon)")] + if not {ItemNames.SENTRY, ItemNames.ENERGIZER, ItemNames.HAVOC} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Sentry/Energizer/Havoc)")] + if not {ItemNames.SENTRY, ItemNames.ENERGIZER, ItemNames.HAVOC, ItemNames.SHIELD_BATTERY} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Sentry/Energizer/Havoc/Shield Battery)")] + if not {ItemNames.ZEALOT, ItemNames.CENTURION, ItemNames.SENTINEL} & logical_inventory_set: + inventory = [item for item in inventory if not item.name.endswith("(Zealot/Sentinel/Centurion)")] + # Static defense upgrades only if static defense present + if not {ItemNames.PHOTON_CANNON, ItemNames.KHAYDARIN_MONOLITH, ItemNames.NEXUS_OVERCHARGE, ItemNames.SHIELD_BATTERY} & logical_inventory_set: + inventory = [item for item in inventory if item.name != ItemNames.ENHANCED_TARGETING] + if not {ItemNames.PHOTON_CANNON, ItemNames.KHAYDARIN_MONOLITH, ItemNames.NEXUS_OVERCHARGE} & logical_inventory_set: + inventory = [item for item in inventory if item.name != ItemNames.OPTIMIZED_ORDNANCE] + + # Cull finished, adding locked items back into inventory + inventory += locked_items + + # Replacing empty space with generically useful items + replacement_items = [item for item in self.item_pool + if (item not in inventory + and item not in self.locked_items + and ( + item.name in second_pass_placeable_items + or item.name in unused_items))] + self.world.random.shuffle(replacement_items) + while len(inventory) < inventory_size and len(replacement_items) > 0: + item = replacement_items.pop() + inventory.append(item) + + return inventory + + def __init__(self, world: World , + item_pool: List[Item], existing_items: List[Item], locked_items: List[Item], + used_races: Set[SC2Race], nova_equipment_used: bool): + self.multiworld = world.multiworld + self.player = world.player + self.world: World = world + self.logical_inventory = list() + self.locked_items = locked_items[:] + self.existing_items = existing_items + soa_presence = get_option_value(world, "spear_of_adun_presence") + soa_autocast_presence = get_option_value(world, "spear_of_adun_autonomously_cast_ability_presence") + # Initial filter of item pool + self.item_pool = [] + item_quantities: dict[str, int] = dict() + # Inventory restrictiveness based on number of missions with checks + mission_count = num_missions(world) + self.min_units_per_structure = int(mission_count / 7) + min_upgrades = 1 if mission_count < 10 else 2 + for item in item_pool: + item_info = get_full_item_list()[item.name] + if item_info.race != SC2Race.ANY and item_info.race not in used_races: + if soa_presence == SpearOfAdunPresence.option_everywhere \ + and item.name in spear_of_adun_calldowns: + # Add SoA powers regardless of used races as it's present everywhere + self.item_pool.append(item) + if soa_autocast_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_everywhere \ + and item.name in spear_of_adun_castable_passives: + self.item_pool.append(item) + # Drop any item belonging to a race not used in the campaign + continue + if item.name in nova_equipment and not nova_equipment_used: + # Drop Nova equipment if there's no NCO mission generated + continue + if item_info.type == "Upgrade": + # Locking upgrades based on mission duration + if item.name not in item_quantities: + item_quantities[item.name] = 0 + item_quantities[item.name] += 1 + if item_quantities[item.name] <= min_upgrades: + self.locked_items.append(item) + else: + self.item_pool.append(item) + elif item_info.type == "Goal": + self.locked_items.append(item) + else: + self.item_pool.append(item) + self.item_children: Dict[Item, List[Item]] = dict() + for item in self.item_pool + locked_items + existing_items: + if item.name in UPGRADABLE_ITEMS: + self.item_children[item] = get_item_upgrades(self.item_pool, item) + + +def filter_items(world: World, mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], location_cache: List[Location], + item_pool: List[Item], existing_items: List[Item], locked_items: List[Item]) -> List[Item]: + """ + Returns a semi-randomly pruned set of items based on number of available locations. + The returned inventory must be capable of logically accessing every location in the world. + """ + open_locations = [location for location in location_cache if location.item is None] + inventory_size = len(open_locations) + used_races = get_used_races(mission_req_table, world) + nova_equipment_used = is_nova_equipment_used(mission_req_table) + mission_requirements = [(location.name, location.access_rule) for location in location_cache] + valid_inventory = ValidInventory(world, item_pool, existing_items, locked_items, used_races, nova_equipment_used) + + valid_items = valid_inventory.generate_reduced_inventory(inventory_size, mission_requirements) + return valid_items + + +def get_used_races(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], world: World) -> Set[SC2Race]: + grant_story_tech = get_option_value(world, "grant_story_tech") + take_over_ai_allies = get_option_value(world, "take_over_ai_allies") + kerrigan_presence = get_option_value(world, "kerrigan_presence") \ + and SC2Campaign.HOTS in get_enabled_campaigns(world) + missions = missions_in_mission_table(mission_req_table) + + # By missions + races = set([mission.race for mission in missions]) + + # Conditionally logic-less no-builds (They're set to SC2Race.ANY): + if grant_story_tech == GrantStoryTech.option_false: + if SC2Mission.ENEMY_WITHIN in missions: + # Zerg units need to be unlocked + races.add(SC2Race.ZERG) + if kerrigan_presence in kerrigan_unit_available \ + and not missions.isdisjoint({SC2Mission.BACK_IN_THE_SADDLE, SC2Mission.SUPREME, SC2Mission.CONVICTION, SC2Mission.THE_INFINITE_CYCLE}): + # You need some Kerrigan abilities (they're granted if Kerriganless or story tech granted) + races.add(SC2Race.ZERG) + + # If you take over the AI Ally, you need to have its race stuff + if take_over_ai_allies == TakeOverAIAllies.option_true \ + and not missions.isdisjoint({SC2Mission.THE_RECKONING}): + # Jimmy in The Reckoning + races.add(SC2Race.TERRAN) + + return races + +def is_nova_equipment_used(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> bool: + missions = missions_in_mission_table(mission_req_table) + return any([mission.campaign == SC2Campaign.NCO for mission in missions]) + + +def missions_in_mission_table(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> Set[SC2Mission]: + return set([mission.mission for campaign_missions in mission_req_table.values() for mission in + campaign_missions.values()]) diff --git a/worlds/sc2/Regions.py b/worlds/sc2/Regions.py new file mode 100644 index 0000000000..e6c001b186 --- /dev/null +++ b/worlds/sc2/Regions.py @@ -0,0 +1,691 @@ +from typing import List, Dict, Tuple, Optional, Callable, NamedTuple, Union +import math + +from BaseClasses import MultiWorld, Region, Entrance, Location, CollectionState +from .Locations import LocationData +from .Options import get_option_value, MissionOrder, get_enabled_campaigns, campaign_depending_orders, \ + GridTwoStartPositions +from .MissionTables import MissionInfo, mission_orders, vanilla_mission_req_table, \ + MissionPools, SC2Campaign, get_goal_location, SC2Mission, MissionConnection +from .PoolFilter import filter_missions +from worlds.AutoWorld import World + + +class SC2MissionSlot(NamedTuple): + campaign: SC2Campaign + slot: Union[MissionPools, SC2Mission, None] + + +def create_regions( + world: World, locations: Tuple[LocationData, ...], location_cache: List[Location] +) -> Tuple[Dict[SC2Campaign, Dict[str, MissionInfo]], int, str]: + """ + Creates region connections by calling the multiworld's `connect()` methods + Returns a 3-tuple containing: + * dict[SC2Campaign, Dict[str, MissionInfo]] mapping a campaign and mission name to its data + * int The number of missions in the world + * str The name of the goal location + """ + mission_order_type: int = get_option_value(world, "mission_order") + + if mission_order_type == MissionOrder.option_vanilla: + return create_vanilla_regions(world, locations, location_cache) + elif mission_order_type == MissionOrder.option_grid: + return create_grid_regions(world, locations, location_cache) + else: + return create_structured_regions(world, locations, location_cache, mission_order_type) + +def create_vanilla_regions( + world: World, + locations: Tuple[LocationData, ...], + location_cache: List[Location], +) -> Tuple[Dict[SC2Campaign, Dict[str, MissionInfo]], int, str]: + locations_per_region = get_locations_per_region(locations) + regions = [create_region(world, locations_per_region, location_cache, "Menu")] + + mission_pools: Dict[MissionPools, List[SC2Mission]] = filter_missions(world) + final_mission = mission_pools[MissionPools.FINAL][0] + + enabled_campaigns = get_enabled_campaigns(world) + names: Dict[str, int] = {} + + # Generating all regions and locations for each enabled campaign + for campaign in enabled_campaigns: + for region_name in vanilla_mission_req_table[campaign].keys(): + regions.append(create_region(world, locations_per_region, location_cache, region_name)) + world.multiworld.regions += regions + vanilla_mission_reqs = {campaign: missions for campaign, missions in vanilla_mission_req_table.items() if campaign in enabled_campaigns} + + def wol_cleared_missions(state: CollectionState, mission_count: int) -> bool: + return state.has_group("WoL Missions", world.player, mission_count) + + player: int = world.player + if SC2Campaign.WOL in enabled_campaigns: + connect(world, names, 'Menu', 'Liberation Day') + connect(world, names, 'Liberation Day', 'The Outlaws', + lambda state: state.has("Beat Liberation Day", player)) + connect(world, names, 'The Outlaws', 'Zero Hour', + lambda state: state.has("Beat The Outlaws", player)) + connect(world, names, 'Zero Hour', 'Evacuation', + lambda state: state.has("Beat Zero Hour", player)) + connect(world, names, 'Evacuation', 'Outbreak', + lambda state: state.has("Beat Evacuation", player)) + connect(world, names, "Outbreak", "Safe Haven", + lambda state: wol_cleared_missions(state, 7) and state.has("Beat Outbreak", player)) + connect(world, names, "Outbreak", "Haven's Fall", + lambda state: wol_cleared_missions(state, 7) and state.has("Beat Outbreak", player)) + connect(world, names, 'Zero Hour', 'Smash and Grab', + lambda state: state.has("Beat Zero Hour", player)) + connect(world, names, 'Smash and Grab', 'The Dig', + lambda state: wol_cleared_missions(state, 8) and state.has("Beat Smash and Grab", player)) + connect(world, names, 'The Dig', 'The Moebius Factor', + lambda state: wol_cleared_missions(state, 11) and state.has("Beat The Dig", player)) + connect(world, names, 'The Moebius Factor', 'Supernova', + lambda state: wol_cleared_missions(state, 14) and state.has("Beat The Moebius Factor", player)) + connect(world, names, 'Supernova', 'Maw of the Void', + lambda state: state.has("Beat Supernova", player)) + connect(world, names, 'Zero Hour', "Devil's Playground", + lambda state: wol_cleared_missions(state, 4) and state.has("Beat Zero Hour", player)) + connect(world, names, "Devil's Playground", 'Welcome to the Jungle', + lambda state: state.has("Beat Devil's Playground", player)) + connect(world, names, "Welcome to the Jungle", 'Breakout', + lambda state: wol_cleared_missions(state, 8) and state.has("Beat Welcome to the Jungle", player)) + connect(world, names, "Welcome to the Jungle", 'Ghost of a Chance', + lambda state: wol_cleared_missions(state, 8) and state.has("Beat Welcome to the Jungle", player)) + connect(world, names, "Zero Hour", 'The Great Train Robbery', + lambda state: wol_cleared_missions(state, 6) and state.has("Beat Zero Hour", player)) + connect(world, names, 'The Great Train Robbery', 'Cutthroat', + lambda state: state.has("Beat The Great Train Robbery", player)) + connect(world, names, 'Cutthroat', 'Engine of Destruction', + lambda state: state.has("Beat Cutthroat", player)) + connect(world, names, 'Engine of Destruction', 'Media Blitz', + lambda state: state.has("Beat Engine of Destruction", player)) + connect(world, names, 'Media Blitz', 'Piercing the Shroud', + lambda state: state.has("Beat Media Blitz", player)) + connect(world, names, 'Maw of the Void', 'Gates of Hell', + lambda state: state.has("Beat Maw of the Void", player)) + connect(world, names, 'Gates of Hell', 'Belly of the Beast', + lambda state: state.has("Beat Gates of Hell", player)) + connect(world, names, 'Gates of Hell', 'Shatter the Sky', + lambda state: state.has("Beat Gates of Hell", player)) + connect(world, names, 'Gates of Hell', 'All-In', + lambda state: state.has('Beat Gates of Hell', player) and ( + state.has('Beat Shatter the Sky', player) or state.has('Beat Belly of the Beast', player))) + + if SC2Campaign.PROPHECY in enabled_campaigns: + if SC2Campaign.WOL in enabled_campaigns: + connect(world, names, 'The Dig', 'Whispers of Doom', + lambda state: state.has("Beat The Dig", player)), + else: + vanilla_mission_reqs[SC2Campaign.PROPHECY] = vanilla_mission_reqs[SC2Campaign.PROPHECY].copy() + vanilla_mission_reqs[SC2Campaign.PROPHECY][SC2Mission.WHISPERS_OF_DOOM.mission_name] = MissionInfo( + SC2Mission.WHISPERS_OF_DOOM, [], SC2Mission.WHISPERS_OF_DOOM.area) + connect(world, names, 'Menu', 'Whispers of Doom'), + connect(world, names, 'Whispers of Doom', 'A Sinister Turn', + lambda state: state.has("Beat Whispers of Doom", player)) + connect(world, names, 'A Sinister Turn', 'Echoes of the Future', + lambda state: state.has("Beat A Sinister Turn", player)) + connect(world, names, 'Echoes of the Future', 'In Utter Darkness', + lambda state: state.has("Beat Echoes of the Future", player)) + + if SC2Campaign.HOTS in enabled_campaigns: + connect(world, names, 'Menu', 'Lab Rat'), + connect(world, names, 'Lab Rat', 'Back in the Saddle', + lambda state: state.has("Beat Lab Rat", player)), + connect(world, names, 'Back in the Saddle', 'Rendezvous', + lambda state: state.has("Beat Back in the Saddle", player)), + connect(world, names, 'Rendezvous', 'Harvest of Screams', + lambda state: state.has("Beat Rendezvous", player)), + connect(world, names, 'Harvest of Screams', 'Shoot the Messenger', + lambda state: state.has("Beat Harvest of Screams", player)), + connect(world, names, 'Shoot the Messenger', 'Enemy Within', + lambda state: state.has("Beat Shoot the Messenger", player)), + connect(world, names, 'Rendezvous', 'Domination', + lambda state: state.has("Beat Rendezvous", player)), + connect(world, names, 'Domination', 'Fire in the Sky', + lambda state: state.has("Beat Domination", player)), + connect(world, names, 'Fire in the Sky', 'Old Soldiers', + lambda state: state.has("Beat Fire in the Sky", player)), + connect(world, names, 'Old Soldiers', 'Waking the Ancient', + lambda state: state.has("Beat Old Soldiers", player)), + connect(world, names, 'Enemy Within', 'Waking the Ancient', + lambda state: state.has("Beat Enemy Within", player)), + connect(world, names, 'Waking the Ancient', 'The Crucible', + lambda state: state.has("Beat Waking the Ancient", player)), + connect(world, names, 'The Crucible', 'Supreme', + lambda state: state.has("Beat The Crucible", player)), + connect(world, names, 'Supreme', 'Infested', + lambda state: state.has("Beat Supreme", player) and + state.has("Beat Old Soldiers", player) and + state.has("Beat Enemy Within", player)), + connect(world, names, 'Infested', 'Hand of Darkness', + lambda state: state.has("Beat Infested", player)), + connect(world, names, 'Hand of Darkness', 'Phantoms of the Void', + lambda state: state.has("Beat Hand of Darkness", player)), + connect(world, names, 'Supreme', 'With Friends Like These', + lambda state: state.has("Beat Supreme", player) and + state.has("Beat Old Soldiers", player) and + state.has("Beat Enemy Within", player)), + connect(world, names, 'With Friends Like These', 'Conviction', + lambda state: state.has("Beat With Friends Like These", player)), + connect(world, names, 'Conviction', 'Planetfall', + lambda state: state.has("Beat Conviction", player) and + state.has("Beat Phantoms of the Void", player)), + connect(world, names, 'Planetfall', 'Death From Above', + lambda state: state.has("Beat Planetfall", player)), + connect(world, names, 'Death From Above', 'The Reckoning', + lambda state: state.has("Beat Death From Above", player)), + + if SC2Campaign.PROLOGUE in enabled_campaigns: + connect(world, names, "Menu", "Dark Whispers") + connect(world, names, "Dark Whispers", "Ghosts in the Fog", + lambda state: state.has("Beat Dark Whispers", player)) + connect(world, names, "Dark Whispers", "Evil Awoken", + lambda state: state.has("Beat Ghosts in the Fog", player)) + + if SC2Campaign.LOTV in enabled_campaigns: + connect(world, names, "Menu", "For Aiur!") + connect(world, names, "For Aiur!", "The Growing Shadow", + lambda state: state.has("Beat For Aiur!", player)), + connect(world, names, "The Growing Shadow", "The Spear of Adun", + lambda state: state.has("Beat The Growing Shadow", player)), + connect(world, names, "The Spear of Adun", "Sky Shield", + lambda state: state.has("Beat The Spear of Adun", player)), + connect(world, names, "Sky Shield", "Brothers in Arms", + lambda state: state.has("Beat Sky Shield", player)), + connect(world, names, "Brothers in Arms", "Forbidden Weapon", + lambda state: state.has("Beat Brothers in Arms", player)), + connect(world, names, "The Spear of Adun", "Amon's Reach", + lambda state: state.has("Beat The Spear of Adun", player)), + connect(world, names, "Amon's Reach", "Last Stand", + lambda state: state.has("Beat Amon's Reach", player)), + connect(world, names, "Last Stand", "Forbidden Weapon", + lambda state: state.has("Beat Last Stand", player)), + connect(world, names, "Forbidden Weapon", "Temple of Unification", + lambda state: state.has("Beat Brothers in Arms", player) + and state.has("Beat Last Stand", player) + and state.has("Beat Forbidden Weapon", player)), + connect(world, names, "Temple of Unification", "The Infinite Cycle", + lambda state: state.has("Beat Temple of Unification", player)), + connect(world, names, "The Infinite Cycle", "Harbinger of Oblivion", + lambda state: state.has("Beat The Infinite Cycle", player)), + connect(world, names, "Harbinger of Oblivion", "Unsealing the Past", + lambda state: state.has("Beat Harbinger of Oblivion", player)), + connect(world, names, "Unsealing the Past", "Purification", + lambda state: state.has("Beat Unsealing the Past", player)), + connect(world, names, "Purification", "Templar's Charge", + lambda state: state.has("Beat Purification", player)), + connect(world, names, "Harbinger of Oblivion", "Steps of the Rite", + lambda state: state.has("Beat Harbinger of Oblivion", player)), + connect(world, names, "Steps of the Rite", "Rak'Shir", + lambda state: state.has("Beat Steps of the Rite", player)), + connect(world, names, "Rak'Shir", "Templar's Charge", + lambda state: state.has("Beat Rak'Shir", player)), + connect(world, names, "Templar's Charge", "Templar's Return", + lambda state: state.has("Beat Purification", player) + and state.has("Beat Rak'Shir", player) + and state.has("Beat Templar's Charge", player)), + connect(world, names, "Templar's Return", "The Host", + lambda state: state.has("Beat Templar's Return", player)), + connect(world, names, "The Host", "Salvation", + lambda state: state.has("Beat The Host", player)), + + if SC2Campaign.EPILOGUE in enabled_campaigns: + # TODO: Make this aware about excluded campaigns + connect(world, names, "Salvation", "Into the Void", + lambda state: state.has("Beat Salvation", player) + and state.has("Beat The Reckoning", player) + and state.has("Beat All-In", player)), + connect(world, names, "Into the Void", "The Essence of Eternity", + lambda state: state.has("Beat Into the Void", player)), + connect(world, names, "The Essence of Eternity", "Amon's Fall", + lambda state: state.has("Beat The Essence of Eternity", player)), + + if SC2Campaign.NCO in enabled_campaigns: + connect(world, names, "Menu", "The Escape") + connect(world, names, "The Escape", "Sudden Strike", + lambda state: state.has("Beat The Escape", player)) + connect(world, names, "Sudden Strike", "Enemy Intelligence", + lambda state: state.has("Beat Sudden Strike", player)) + connect(world, names, "Enemy Intelligence", "Trouble In Paradise", + lambda state: state.has("Beat Enemy Intelligence", player)) + connect(world, names, "Trouble In Paradise", "Night Terrors", + lambda state: state.has("Beat Evacuation", player)) + connect(world, names, "Night Terrors", "Flashpoint", + lambda state: state.has("Beat Night Terrors", player)) + connect(world, names, "Flashpoint", "In the Enemy's Shadow", + lambda state: state.has("Beat Flashpoint", player)) + connect(world, names, "In the Enemy's Shadow", "Dark Skies", + lambda state: state.has("Beat In the Enemy's Shadow", player)) + connect(world, names, "Dark Skies", "End Game", + lambda state: state.has("Beat Dark Skies", player)) + + goal_location = get_goal_location(final_mission) + assert goal_location, f"Unable to find a goal location for mission {final_mission}" + setup_final_location(goal_location, location_cache) + + return (vanilla_mission_reqs, final_mission.id, goal_location) + + +def create_grid_regions( + world: World, + locations: Tuple[LocationData, ...], + location_cache: List[Location], +) -> Tuple[Dict[SC2Campaign, Dict[str, MissionInfo]], int, str]: + locations_per_region = get_locations_per_region(locations) + + mission_pools = filter_missions(world) + final_mission = mission_pools[MissionPools.FINAL][0] + + mission_pool = [mission for mission_pool in mission_pools.values() for mission in mission_pool] + + num_missions = min(len(mission_pool), get_option_value(world, "maximum_campaign_size")) + remove_top_left: bool = get_option_value(world, "grid_two_start_positions") == GridTwoStartPositions.option_true + + regions = [create_region(world, locations_per_region, location_cache, "Menu")] + names: Dict[str, int] = {} + missions: Dict[Tuple[int, int], SC2Mission] = {} + + grid_size_x, grid_size_y, num_corners_to_remove = get_grid_dimensions(num_missions + remove_top_left) + # pick missions in order along concentric diagonals + # each diagonal will have the same difficulty + # this keeps long sides from possibly stealing lower-difficulty missions from future columns + num_diagonals = grid_size_x + grid_size_y - 1 + diagonal_difficulty = MissionPools.STARTER + missions_to_add = mission_pools[MissionPools.STARTER] + for diagonal in range(num_diagonals): + if diagonal == num_diagonals - 1: + diagonal_difficulty = MissionPools.FINAL + grid_coords = (grid_size_x-1, grid_size_y-1) + missions[grid_coords] = final_mission + break + if diagonal == 0 and remove_top_left: + continue + diagonal_length = min(diagonal + 1, num_diagonals - diagonal, grid_size_x, grid_size_y) + if len(missions_to_add) < diagonal_length: + raise Exception(f"There are not enough {diagonal_difficulty.name} missions to fill the campaign. Please exclude fewer missions.") + for i in range(diagonal_length): + # (0,0) + (0,1)*diagonal + (1,-1)*i + (1,-1)*max(diagonal - grid_size_y + 1, 0) + grid_coords = (i + max(diagonal - grid_size_y + 1, 0), diagonal - i - max(diagonal - grid_size_y + 1, 0)) + if grid_coords == (grid_size_x - 1, 0) and num_corners_to_remove >= 2: + pass + elif grid_coords == (0, grid_size_y - 1) and num_corners_to_remove >= 1: + pass + else: + mission_index = world.random.randint(0, len(missions_to_add) - 1) + missions[grid_coords] = missions_to_add.pop(mission_index) + + if diagonal_difficulty < MissionPools.VERY_HARD: + diagonal_difficulty = MissionPools(diagonal_difficulty.value + 1) + missions_to_add.extend(mission_pools[diagonal_difficulty]) + + # Generating regions and locations from selected missions + for x in range(grid_size_x): + for y in range(grid_size_y): + if missions.get((x, y)): + regions.append(create_region(world, locations_per_region, location_cache, missions[(x, y)].mission_name)) + world.multiworld.regions += regions + + # This pattern is horrifying, why are we using the dict as an ordered dict??? + slot_map: Dict[Tuple[int, int], int] = {} + for index, coords in enumerate(missions): + slot_map[coords] = index + 1 + + mission_req_table: Dict[str, MissionInfo] = {} + for coords, mission in missions.items(): + prepend_vertical = 0 + if not mission: + continue + connections: List[MissionConnection] = [] + if coords == (0, 0) or (remove_top_left and sum(coords) == 1): + # Connect to the "Menu" starting region + connect(world, names, "Menu", mission.mission_name) + else: + for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)): + connected_coords = (coords[0] + dx, coords[1] + dy) + if connected_coords in missions: + # connections.append(missions[connected_coords]) + connections.append(MissionConnection(slot_map[connected_coords])) + connect(world, names, missions[connected_coords].mission_name, mission.mission_name, + make_grid_connect_rule(missions, connected_coords, world.player), + ) + if coords[1] == 1 and not missions.get((coords[0], 0)): + prepend_vertical = 1 + mission_req_table[mission.mission_name] = MissionInfo( + mission, + connections, + category=f'_{coords[0] + 1}', + or_requirements=True, + ui_vertical_padding=prepend_vertical, + ) + + final_mission_id = final_mission.id + # Changing the completion condition for alternate final missions into an event + final_location = get_goal_location(final_mission) + setup_final_location(final_location, location_cache) + + return {SC2Campaign.GLOBAL: mission_req_table}, final_mission_id, final_location + + +def make_grid_connect_rule( + missions: Dict[Tuple[int, int], SC2Mission], + connected_coords: Tuple[int, int], + player: int +) -> Callable[[CollectionState], bool]: + return lambda state: state.has(f"Beat {missions[connected_coords].mission_name}", player) + + +def create_structured_regions( + world: World, + locations: Tuple[LocationData, ...], + location_cache: List[Location], + mission_order_type: int, +) -> Tuple[Dict[SC2Campaign, Dict[str, MissionInfo]], int, str]: + locations_per_region = get_locations_per_region(locations) + + mission_order = mission_orders[mission_order_type]() + enabled_campaigns = get_enabled_campaigns(world) + shuffle_campaigns = get_option_value(world, "shuffle_campaigns") + + mission_pools: Dict[MissionPools, List[SC2Mission]] = filter_missions(world) + final_mission = mission_pools[MissionPools.FINAL][0] + + regions = [create_region(world, locations_per_region, location_cache, "Menu")] + + names: Dict[str, int] = {} + + mission_slots: List[SC2MissionSlot] = [] + mission_pool = [mission for mission_pool in mission_pools.values() for mission in mission_pool] + + if mission_order_type in campaign_depending_orders: + # Do slot removal per campaign + for campaign in enabled_campaigns: + campaign_mission_pool = [mission for mission in mission_pool if mission.campaign == campaign] + campaign_mission_pool_size = len(campaign_mission_pool) + + removals = len(mission_order[campaign]) - campaign_mission_pool_size + + for mission in mission_order[campaign]: + # Removing extra missions if mission pool is too small + if 0 < mission.removal_priority <= removals: + mission_slots.append(SC2MissionSlot(campaign, None)) + elif mission.type == MissionPools.FINAL: + if campaign == final_mission.campaign: + # Campaign is elected to be goal + mission_slots.append(SC2MissionSlot(campaign, final_mission)) + else: + # Not the goal, find the most difficult mission in the pool and set the difficulty + campaign_difficulty = max(mission.pool for mission in campaign_mission_pool) + mission_slots.append(SC2MissionSlot(campaign, campaign_difficulty)) + else: + mission_slots.append(SC2MissionSlot(campaign, mission.type)) + else: + order = mission_order[SC2Campaign.GLOBAL] + # Determining if missions must be removed + mission_pool_size = sum(len(mission_pool) for mission_pool in mission_pools.values()) + removals = len(order) - mission_pool_size + + # Initial fill out of mission list and marking All-In mission + for mission in order: + # Removing extra missions if mission pool is too small + if 0 < mission.removal_priority <= removals: + mission_slots.append(SC2MissionSlot(SC2Campaign.GLOBAL, None)) + elif mission.type == MissionPools.FINAL: + mission_slots.append(SC2MissionSlot(SC2Campaign.GLOBAL, final_mission)) + else: + mission_slots.append(SC2MissionSlot(SC2Campaign.GLOBAL, mission.type)) + + no_build_slots = [] + easy_slots = [] + medium_slots = [] + hard_slots = [] + very_hard_slots = [] + + # Search through missions to find slots needed to fill + for i in range(len(mission_slots)): + mission_slot = mission_slots[i] + if mission_slot is None: + continue + if isinstance(mission_slot, SC2MissionSlot): + if mission_slot.slot is None: + continue + if mission_slot.slot == MissionPools.STARTER: + no_build_slots.append(i) + elif mission_slot.slot == MissionPools.EASY: + easy_slots.append(i) + elif mission_slot.slot == MissionPools.MEDIUM: + medium_slots.append(i) + elif mission_slot.slot == MissionPools.HARD: + hard_slots.append(i) + elif mission_slot.slot == MissionPools.VERY_HARD: + very_hard_slots.append(i) + + def pick_mission(slot): + if shuffle_campaigns or mission_order_type not in campaign_depending_orders: + # Pick a mission from any campaign + filler = world.random.randint(0, len(missions_to_add) - 1) + mission = missions_to_add.pop(filler) + slot_campaign = mission_slots[slot].campaign + mission_slots[slot] = SC2MissionSlot(slot_campaign, mission) + else: + # Pick a mission from required campaign + slot_campaign = mission_slots[slot].campaign + campaign_mission_candidates = [mission for mission in missions_to_add if mission.campaign == slot_campaign] + mission = world.random.choice(campaign_mission_candidates) + missions_to_add.remove(mission) + mission_slots[slot] = SC2MissionSlot(slot_campaign, mission) + + # Add no_build missions to the pool and fill in no_build slots + missions_to_add: List[SC2Mission] = mission_pools[MissionPools.STARTER] + if len(no_build_slots) > len(missions_to_add): + raise Exception("There are no valid No-Build missions. Please exclude fewer missions.") + for slot in no_build_slots: + pick_mission(slot) + + # Add easy missions into pool and fill in easy slots + missions_to_add = missions_to_add + mission_pools[MissionPools.EASY] + if len(easy_slots) > len(missions_to_add): + raise Exception("There are not enough Easy missions to fill the campaign. Please exclude fewer missions.") + for slot in easy_slots: + pick_mission(slot) + + # Add medium missions into pool and fill in medium slots + missions_to_add = missions_to_add + mission_pools[MissionPools.MEDIUM] + if len(medium_slots) > len(missions_to_add): + raise Exception("There are not enough Easy and Medium missions to fill the campaign. Please exclude fewer missions.") + for slot in medium_slots: + pick_mission(slot) + + # Add hard missions into pool and fill in hard slots + missions_to_add = missions_to_add + mission_pools[MissionPools.HARD] + if len(hard_slots) > len(missions_to_add): + raise Exception("There are not enough missions to fill the campaign. Please exclude fewer missions.") + for slot in hard_slots: + pick_mission(slot) + + # Add very hard missions into pool and fill in very hard slots + missions_to_add = missions_to_add + mission_pools[MissionPools.VERY_HARD] + if len(very_hard_slots) > len(missions_to_add): + raise Exception("There are not enough missions to fill the campaign. Please exclude fewer missions.") + for slot in very_hard_slots: + pick_mission(slot) + + # Generating regions and locations from selected missions + for mission_slot in mission_slots: + if isinstance(mission_slot.slot, SC2Mission): + regions.append(create_region(world, locations_per_region, location_cache, mission_slot.slot.mission_name)) + world.multiworld.regions += regions + + campaigns: List[SC2Campaign] + if mission_order_type in campaign_depending_orders: + campaigns = list(enabled_campaigns) + else: + campaigns = [SC2Campaign.GLOBAL] + + mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]] = {} + campaign_mission_slots: Dict[SC2Campaign, List[SC2MissionSlot]] = \ + { + campaign: [mission_slot for mission_slot in mission_slots if campaign == mission_slot.campaign] + for campaign in campaigns + } + + slot_map: Dict[SC2Campaign, List[int]] = dict() + + for campaign in campaigns: + mission_req_table.update({campaign: dict()}) + + # Mapping original mission slots to shifted mission slots when missions are removed + slot_map[campaign] = [] + slot_offset = 0 + for position, mission in enumerate(campaign_mission_slots[campaign]): + slot_map[campaign].append(position - slot_offset + 1) + if mission is None or mission.slot is None: + slot_offset += 1 + + def build_connection_rule(mission_names: List[str], missions_req: int) -> Callable: + player = world.player + if len(mission_names) > 1: + return lambda state: state.has_all({f"Beat {name}" for name in mission_names}, player) \ + and state.has_group("Missions", player, missions_req) + else: + return lambda state: state.has(f"Beat {mission_names[0]}", player) \ + and state.has_group("Missions", player, missions_req) + + for campaign in campaigns: + # Loop through missions to create requirements table and connect regions + for i, mission in enumerate(campaign_mission_slots[campaign]): + if mission is None or mission.slot is None: + continue + connections: List[MissionConnection] = [] + all_connections: List[SC2MissionSlot] = [] + connection: MissionConnection + for connection in mission_order[campaign][i].connect_to: + if connection.connect_to == -1: + continue + # If mission normally connects to an excluded campaign, connect to menu instead + if connection.campaign not in campaign_mission_slots: + connection.connect_to = -1 + continue + while campaign_mission_slots[connection.campaign][connection.connect_to].slot is None: + connection.connect_to -= 1 + all_connections.append(campaign_mission_slots[connection.campaign][connection.connect_to]) + for connection in mission_order[campaign][i].connect_to: + if connection.connect_to == -1: + connect(world, names, "Menu", mission.slot.mission_name) + else: + required_mission = campaign_mission_slots[connection.campaign][connection.connect_to] + if ((required_mission is None or required_mission.slot is None) + and not mission_order[campaign][i].completion_critical): # Drop non-critical null slots + continue + while required_mission is None or required_mission.slot is None: # Substituting null slot with prior slot + connection.connect_to -= 1 + required_mission = campaign_mission_slots[connection.campaign][connection.connect_to] + required_missions = [required_mission] if mission_order[campaign][i].or_requirements else all_connections + if isinstance(required_mission.slot, SC2Mission): + required_mission_name = required_mission.slot.mission_name + required_missions_names = [mission.slot.mission_name for mission in required_missions] + connect(world, names, required_mission_name, mission.slot.mission_name, + build_connection_rule(required_missions_names, mission_order[campaign][i].number)) + connections.append(MissionConnection(slot_map[connection.campaign][connection.connect_to], connection.campaign)) + + mission_req_table[campaign].update({mission.slot.mission_name: MissionInfo( + mission.slot, connections, mission_order[campaign][i].category, + number=mission_order[campaign][i].number, + completion_critical=mission_order[campaign][i].completion_critical, + or_requirements=mission_order[campaign][i].or_requirements)}) + + final_mission_id = final_mission.id + # Changing the completion condition for alternate final missions into an event + final_location = get_goal_location(final_mission) + setup_final_location(final_location, location_cache) + + return mission_req_table, final_mission_id, final_location + + +def setup_final_location(final_location, location_cache): + # Final location should be near the end of the cache + for i in range(len(location_cache) - 1, -1, -1): + if location_cache[i].name == final_location: + location_cache[i].address = None + break + + +def create_location(player: int, location_data: LocationData, region: Region, + location_cache: List[Location]) -> Location: + location = Location(player, location_data.name, location_data.code, region) + location.access_rule = location_data.rule + + location_cache.append(location) + + return location + + +def create_region(world: World, locations_per_region: Dict[str, List[LocationData]], + location_cache: List[Location], name: str) -> Region: + region = Region(name, world.player, world.multiworld) + + if name in locations_per_region: + for location_data in locations_per_region[name]: + location = create_location(world.player, location_data, region, location_cache) + region.locations.append(location) + + return region + + +def connect(world: World, used_names: Dict[str, int], source: str, target: str, + rule: Optional[Callable] = None): + source_region = world.get_region(source) + target_region = world.get_region(target) + + 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) + + +def get_locations_per_region(locations: Tuple[LocationData, ...]) -> Dict[str, List[LocationData]]: + per_region: Dict[str, List[LocationData]] = {} + + for location in locations: + per_region.setdefault(location.region, []).append(location) + + return per_region + + +def get_factors(number: int) -> Tuple[int, int]: + """ + Simple factorization into pairs of numbers (x, y) using a sieve method. + Returns the factorization that is most square, i.e. where x + y is minimized. + Factor order is such that x <= y. + """ + assert number > 0 + for divisor in range(math.floor(math.sqrt(number)), 1, -1): + quotient = number // divisor + if quotient * divisor == number: + return divisor, quotient + return 1, number + + +def get_grid_dimensions(size: int) -> Tuple[int, int, int]: + """ + Get the dimensions of a grid mission order from the number of missions, int the format (x, y, error). + * Error will always be 0, 1, or 2, so the missions can be removed from the corners that aren't the start or end. + * Dimensions are chosen such that x <= y, as buttons in the UI are wider than they are tall. + * Dimensions are chosen to be maximally square. That is, x + y + error is minimized. + * If multiple options of the same rating are possible, the one with the larger error is chosen, + as it will appear more square. Compare 3x11 to 5x7-2 for an example of this. + """ + dimension_candidates: List[Tuple[int, int, int]] = [(*get_factors(size + x), x) for x in (2, 1, 0)] + best_dimension = min(dimension_candidates, key=sum) + return best_dimension + diff --git a/worlds/sc2/Rules.py b/worlds/sc2/Rules.py new file mode 100644 index 0000000000..8b9097ea1d --- /dev/null +++ b/worlds/sc2/Rules.py @@ -0,0 +1,952 @@ +from typing import Set + +from BaseClasses import CollectionState +from .Options import get_option_value, RequiredTactics, kerrigan_unit_available, AllInMap, \ + GrantStoryTech, GrantStoryLevels, TakeOverAIAllies, SpearOfAdunAutonomouslyCastAbilityPresence, \ + get_enabled_campaigns, MissionOrder +from .Items import get_basic_units, defense_ratings, zerg_defense_ratings, kerrigan_actives, air_defense_ratings, \ + kerrigan_levels, get_full_item_list +from .MissionTables import SC2Race, SC2Campaign +from . import ItemNames +from worlds.AutoWorld import World + + +class SC2Logic: + + def lock_any_item(self, state: CollectionState, items: Set[str]) -> bool: + """ + Guarantees that at least one of these items will remain in the world. Doesn't affect placement. + Needed for cases when the dynamic pool filtering could remove all the item prerequisites + :param state: + :param items: + :return: + """ + return self.is_item_placement(state) \ + or state.has_any(items, self.player) + + def is_item_placement(self, state): + """ + Tells if it's item placement or item pool filter + :param state: + :return: True for item placement, False for pool filter + """ + # has_group with count = 0 is always true for item placement and always false for SC2 item filtering + return state.has_group("Missions", self.player, 0) + + # WoL + def terran_common_unit(self, state: CollectionState) -> bool: + return state.has_any(self.basic_terran_units, self.player) + + def terran_early_tech(self, state: CollectionState): + """ + Basic combat unit that can be deployed quickly from mission start + :param state + :return: + """ + return ( + state.has_any({ItemNames.MARINE, ItemNames.FIREBAT, ItemNames.MARAUDER, ItemNames.REAPER, ItemNames.HELLION}, self.player) + or (self.advanced_tactics and state.has_any({ItemNames.GOLIATH, ItemNames.DIAMONDBACK, ItemNames.VIKING, ItemNames.BANSHEE}, self.player)) + ) + + def terran_air(self, state: CollectionState) -> bool: + """ + Air units or drops on advanced tactics + :param state: + :return: + """ + return (state.has_any({ItemNames.VIKING, ItemNames.WRAITH, ItemNames.BANSHEE, ItemNames.BATTLECRUISER}, self.player) or self.advanced_tactics + and state.has_any({ItemNames.HERCULES, ItemNames.MEDIVAC}, self.player) and self.terran_common_unit(state) + ) + + def terran_air_anti_air(self, state: CollectionState) -> bool: + """ + Air-to-air + :param state: + :return: + """ + return ( + state.has(ItemNames.VIKING, self.player) + or state.has_all({ItemNames.WRAITH, ItemNames.WRAITH_ADVANCED_LASER_TECHNOLOGY}, self.player) + or state.has_all({ItemNames.BATTLECRUISER, ItemNames.BATTLECRUISER_ATX_LASER_BATTERY}, self.player) + or self.advanced_tactics and state.has_any({ItemNames.WRAITH, ItemNames.VALKYRIE, ItemNames.BATTLECRUISER}, self.player) + ) + + def terran_competent_ground_to_air(self, state: CollectionState) -> bool: + """ + Ground-to-air + :param state: + :return: + """ + return ( + state.has(ItemNames.GOLIATH, self.player) + or state.has(ItemNames.MARINE, self.player) and self.terran_bio_heal(state) + or self.advanced_tactics and state.has(ItemNames.CYCLONE, self.player) + ) + + def terran_competent_anti_air(self, state: CollectionState) -> bool: + """ + Good AA unit + :param state: + :return: + """ + return ( + self.terran_competent_ground_to_air(state) + or self.terran_air_anti_air(state) + ) + + def welcome_to_the_jungle_requirement(self, state: CollectionState) -> bool: + """ + Welcome to the Jungle requirements - able to deal with Scouts, Void Rays, Zealots and Stalkers + :param state: + :return: + """ + return ( + self.terran_common_unit(state) + and self.terran_competent_ground_to_air(state) + ) or ( + self.advanced_tactics + and state.has_any({ItemNames.MARINE, ItemNames.VULTURE}, self.player) + and self.terran_air_anti_air(state) + ) + + def terran_basic_anti_air(self, state: CollectionState) -> bool: + """ + Basic AA to deal with few air units + :param state: + :return: + """ + return ( + state.has_any({ + ItemNames.MISSILE_TURRET, ItemNames.THOR, ItemNames.WAR_PIGS, ItemNames.SPARTAN_COMPANY, + ItemNames.HELS_ANGELS, ItemNames.BATTLECRUISER, ItemNames.MARINE, ItemNames.WRAITH, + ItemNames.VALKYRIE, ItemNames.CYCLONE, ItemNames.WINGED_NIGHTMARES, ItemNames.BRYNHILDS + }, self.player) + or self.terran_competent_anti_air(state) + or self.advanced_tactics and state.has_any({ItemNames.GHOST, ItemNames.SPECTRE, ItemNames.WIDOW_MINE, ItemNames.LIBERATOR}, self.player) + ) + + def terran_defense_rating(self, state: CollectionState, zerg_enemy: bool, air_enemy: bool = True) -> int: + """ + Ability to handle defensive missions + :param state: + :param zerg_enemy: + :param air_enemy: + :return: + """ + defense_score = sum((defense_ratings[item] for item in defense_ratings if state.has(item, self.player))) + # Manned Bunker + if state.has_any({ItemNames.MARINE, ItemNames.MARAUDER}, self.player) and state.has(ItemNames.BUNKER, self.player): + defense_score += 3 + elif zerg_enemy and state.has(ItemNames.FIREBAT, self.player) and state.has(ItemNames.BUNKER, self.player): + defense_score += 2 + # Siege Tank upgrades + if state.has_all({ItemNames.SIEGE_TANK, ItemNames.SIEGE_TANK_MAELSTROM_ROUNDS}, self.player): + defense_score += 2 + if state.has_all({ItemNames.SIEGE_TANK, ItemNames.SIEGE_TANK_GRADUATING_RANGE}, self.player): + defense_score += 1 + # Widow Mine upgrade + if state.has_all({ItemNames.WIDOW_MINE, ItemNames.WIDOW_MINE_CONCEALMENT}, self.player): + defense_score += 1 + # Viking with splash + if state.has_all({ItemNames.VIKING, ItemNames.VIKING_SHREDDER_ROUNDS}, self.player): + defense_score += 2 + + # General enemy-based rules + if zerg_enemy: + defense_score += sum((zerg_defense_ratings[item] for item in zerg_defense_ratings if state.has(item, self.player))) + if air_enemy: + defense_score += sum((air_defense_ratings[item] for item in air_defense_ratings if state.has(item, self.player))) + if air_enemy and zerg_enemy and state.has(ItemNames.VALKYRIE, self.player): + # Valkyries shred mass Mutas, most common air enemy that's massed in these cases + defense_score += 2 + # Advanced Tactics bumps defense rating requirements down by 2 + if self.advanced_tactics: + defense_score += 2 + return defense_score + + def terran_competent_comp(self, state: CollectionState) -> bool: + """ + Ability to deal with most of hard missions + :param state: + :return: + """ + return ( + ( + (state.has_any({ItemNames.MARINE, ItemNames.MARAUDER}, self.player) and self.terran_bio_heal(state)) + or state.has_any({ItemNames.THOR, ItemNames.BANSHEE, ItemNames.SIEGE_TANK}, self.player) + or state.has_all({ItemNames.LIBERATOR, ItemNames.LIBERATOR_RAID_ARTILLERY}, self.player) + ) + and self.terran_competent_anti_air(state) + ) or ( + state.has(ItemNames.BATTLECRUISER, self.player) and self.terran_common_unit(state) + ) + + def great_train_robbery_train_stopper(self, state: CollectionState) -> bool: + """ + Ability to deal with trains (moving target with a lot of HP) + :param state: + :return: + """ + return ( + state.has_any({ItemNames.SIEGE_TANK, ItemNames.DIAMONDBACK, ItemNames.MARAUDER, ItemNames.CYCLONE, ItemNames.BANSHEE}, self.player) + or self.advanced_tactics + and ( + state.has_all({ItemNames.REAPER, ItemNames.REAPER_G4_CLUSTERBOMB}, self.player) + or state.has_all({ItemNames.SPECTRE, ItemNames.SPECTRE_PSIONIC_LASH}, self.player) + or state.has_any({ItemNames.VULTURE, ItemNames.LIBERATOR}, self.player) + ) + ) + + def terran_can_rescue(self, state) -> bool: + """ + Rescuing in The Moebius Factor + :param state: + :return: + """ + return state.has_any({ItemNames.MEDIVAC, ItemNames.HERCULES, ItemNames.RAVEN, ItemNames.VIKING}, self.player) or self.advanced_tactics + + def terran_beats_protoss_deathball(self, state: CollectionState) -> bool: + """ + Ability to deal with Immortals, Colossi with some air support + :param state: + :return: + """ + return ( + ( + state.has_any({ItemNames.BANSHEE, ItemNames.BATTLECRUISER}, self.player) + or state.has_all({ItemNames.LIBERATOR, ItemNames.LIBERATOR_RAID_ARTILLERY}, self.player) + ) and self.terran_competent_anti_air(state) + or self.terran_competent_comp(state) and self.terran_air_anti_air(state) + ) + + def marine_medic_upgrade(self, state: CollectionState) -> bool: + """ + Infantry upgrade to infantry-only no-build segments + :param state: + :return: + """ + return state.has_any({ + ItemNames.MARINE_COMBAT_SHIELD, ItemNames.MARINE_MAGRAIL_MUNITIONS, ItemNames.MEDIC_STABILIZER_MEDPACKS + }, self.player) \ + or (state.count(ItemNames.MARINE_PROGRESSIVE_STIMPACK, self.player) >= 2 + and state.has_group("Missions", self.player, 1)) + + def terran_survives_rip_field(self, state: CollectionState) -> bool: + """ + Ability to deal with large areas with environment damage + :param state: + :return: + """ + return (state.has(ItemNames.BATTLECRUISER, self.player) + or self.terran_air(state) and self.terran_competent_anti_air(state) and self.terran_sustainable_mech_heal(state)) + + def terran_sustainable_mech_heal(self, state: CollectionState) -> bool: + """ + Can heal mech units without spending resources + :param state: + :return: + """ + return state.has(ItemNames.SCIENCE_VESSEL, self.player) \ + or state.has_all({ItemNames.MEDIC, ItemNames.MEDIC_ADAPTIVE_MEDPACKS}, self.player) \ + or state.count(ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, self.player) >= 3 \ + or (self.advanced_tactics + and ( + state.has_all({ItemNames.RAVEN, ItemNames.RAVEN_BIO_MECHANICAL_REPAIR_DRONE}, self.player) + or state.count(ItemNames.PROGRESSIVE_REGENERATIVE_BIO_STEEL, self.player) >= 2) + ) + + def terran_bio_heal(self, state: CollectionState) -> bool: + """ + Ability to heal bio units + :param state: + :return: + """ + return state.has_any({ItemNames.MEDIC, ItemNames.MEDIVAC}, self.player) \ + or self.advanced_tactics and state.has_all({ItemNames.RAVEN, ItemNames.RAVEN_BIO_MECHANICAL_REPAIR_DRONE}, self.player) + + def terran_base_trasher(self, state: CollectionState) -> bool: + """ + Can attack heavily defended bases + :param state: + :return: + """ + return state.has(ItemNames.SIEGE_TANK, self.player) \ + or state.has_all({ItemNames.BATTLECRUISER, ItemNames.BATTLECRUISER_ATX_LASER_BATTERY}, self.player) \ + or state.has_all({ItemNames.LIBERATOR, ItemNames.LIBERATOR_RAID_ARTILLERY}, self.player) \ + or (self.advanced_tactics + and ((state.has_all({ItemNames.RAVEN, ItemNames.RAVEN_HUNTER_SEEKER_WEAPON}, self.player) + or self.can_nuke(state)) + and ( + state.has_all({ItemNames.VIKING, ItemNames.VIKING_SHREDDER_ROUNDS}, self.player) + or state.has_all({ItemNames.BANSHEE, ItemNames.BANSHEE_SHOCKWAVE_MISSILE_BATTERY}, self.player)) + ) + ) + + def terran_mobile_detector(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.RAVEN, ItemNames.SCIENCE_VESSEL, ItemNames.PROGRESSIVE_ORBITAL_COMMAND}, self.player) + + def can_nuke(self, state: CollectionState) -> bool: + """ + Ability to launch nukes + :param state: + :return: + """ + return (self.advanced_tactics + and (state.has_any({ItemNames.GHOST, ItemNames.SPECTRE}, self.player) + or state.has_all({ItemNames.THOR, ItemNames.THOR_BUTTON_WITH_A_SKULL_ON_IT}, self.player))) + + def terran_respond_to_colony_infestations(self, state: CollectionState) -> bool: + """ + Can deal quickly with Brood Lords and Mutas in Haven's Fall and being able to progress the mission + :param state: + :return: + """ + return ( + self.terran_common_unit(state) + and self.terran_competent_anti_air(state) + and ( + self.terran_air_anti_air(state) + or state.has_any({ItemNames.BATTLECRUISER, ItemNames.VALKYRIE}, self.player) + ) + and self.terran_defense_rating(state, True) >= 3 + ) + + def engine_of_destruction_requirement(self, state: CollectionState): + return self.marine_medic_upgrade(state) \ + and ( + self.terran_competent_anti_air(state) + and self.terran_common_unit(state) or state.has(ItemNames.WRAITH, self.player) + ) + + def all_in_requirement(self, state: CollectionState): + """ + All-in + :param state: + :return: + """ + beats_kerrigan = state.has_any({ItemNames.MARINE, ItemNames.BANSHEE, ItemNames.GHOST}, self.player) or self.advanced_tactics + if get_option_value(self.world, 'all_in_map') == AllInMap.option_ground: + # Ground + defense_rating = self.terran_defense_rating(state, True, False) + if state.has_any({ItemNames.BATTLECRUISER, ItemNames.BANSHEE}, self.player): + defense_rating += 2 + return defense_rating >= 13 and beats_kerrigan + else: + # Air + defense_rating = self.terran_defense_rating(state, True, True) + return defense_rating >= 9 and beats_kerrigan \ + and state.has_any({ItemNames.VIKING, ItemNames.BATTLECRUISER, ItemNames.VALKYRIE}, self.player) \ + and state.has_any({ItemNames.HIVE_MIND_EMULATOR, ItemNames.PSI_DISRUPTER, ItemNames.MISSILE_TURRET}, self.player) + + # HotS + def zerg_common_unit(self, state: CollectionState) -> bool: + return state.has_any(self.basic_zerg_units, self.player) + + def zerg_competent_anti_air(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.HYDRALISK, ItemNames.MUTALISK, ItemNames.CORRUPTOR, ItemNames.BROOD_QUEEN}, self.player) \ + or state.has_all({ItemNames.SWARM_HOST, ItemNames.SWARM_HOST_PRESSURIZED_GLANDS}, self.player) \ + or state.has_all({ItemNames.SCOURGE, ItemNames.SCOURGE_RESOURCE_EFFICIENCY}, self.player) \ + or (self.advanced_tactics and state.has(ItemNames.INFESTOR, self.player)) + + def zerg_basic_anti_air(self, state: CollectionState) -> bool: + return self.zerg_competent_anti_air(state) or self.kerrigan_unit_available in kerrigan_unit_available or \ + state.has_any({ItemNames.SWARM_QUEEN, ItemNames.SCOURGE}, self.player) or (self.advanced_tactics and state.has(ItemNames.SPORE_CRAWLER, self.player)) + + def morph_brood_lord(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.MUTALISK, ItemNames.CORRUPTOR}, self.player) \ + and state.has(ItemNames.MUTALISK_CORRUPTOR_BROOD_LORD_ASPECT, self.player) + + def morph_viper(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.MUTALISK, ItemNames.CORRUPTOR}, self.player) \ + and state.has(ItemNames.MUTALISK_CORRUPTOR_VIPER_ASPECT, self.player) + + def morph_impaler_or_lurker(self, state: CollectionState) -> bool: + return state.has(ItemNames.HYDRALISK, self.player) and state.has_any({ItemNames.HYDRALISK_IMPALER_ASPECT, ItemNames.HYDRALISK_LURKER_ASPECT}, self.player) + + def zerg_competent_comp(self, state: CollectionState) -> bool: + advanced = self.advanced_tactics + core_unit = state.has_any({ItemNames.ROACH, ItemNames.ABERRATION, ItemNames.ZERGLING}, self.player) + support_unit = state.has_any({ItemNames.SWARM_QUEEN, ItemNames.HYDRALISK}, self.player) \ + or self.morph_brood_lord(state) \ + or advanced and (state.has_any({ItemNames.INFESTOR, ItemNames.DEFILER}, self.player) or self.morph_viper(state)) + if core_unit and support_unit: + return True + vespene_unit = state.has_any({ItemNames.ULTRALISK, ItemNames.ABERRATION}, self.player) \ + or advanced and self.morph_viper(state) + return vespene_unit and state.has_any({ItemNames.ZERGLING, ItemNames.SWARM_QUEEN}, self.player) + + def spread_creep(self, state: CollectionState) -> bool: + return self.advanced_tactics or state.has(ItemNames.SWARM_QUEEN, self.player) + + def zerg_competent_defense(self, state: CollectionState) -> bool: + return ( + self.zerg_common_unit(state) + and ( + ( + state.has(ItemNames.SWARM_HOST, self.player) + or self.morph_brood_lord(state) + or self.morph_impaler_or_lurker(state) + ) or ( + self.advanced_tactics + and (self.morph_viper(state) + or state.has(ItemNames.SPINE_CRAWLER, self.player)) + ) + ) + ) + + def basic_kerrigan(self, state: CollectionState) -> bool: + # One active ability that can be used to defeat enemies directly on Standard + if not self.advanced_tactics and \ + not state.has_any({ItemNames.KERRIGAN_KINETIC_BLAST, ItemNames.KERRIGAN_LEAPING_STRIKE, + ItemNames.KERRIGAN_CRUSHING_GRIP, ItemNames.KERRIGAN_PSIONIC_SHIFT, + ItemNames.KERRIGAN_SPAWN_BANELINGS}, self.player): + return False + # Two non-ultimate abilities + count = 0 + for item in (ItemNames.KERRIGAN_KINETIC_BLAST, ItemNames.KERRIGAN_LEAPING_STRIKE, ItemNames.KERRIGAN_HEROIC_FORTITUDE, + ItemNames.KERRIGAN_CHAIN_REACTION, ItemNames.KERRIGAN_CRUSHING_GRIP, ItemNames.KERRIGAN_PSIONIC_SHIFT, + ItemNames.KERRIGAN_SPAWN_BANELINGS, ItemNames.KERRIGAN_INFEST_BROODLINGS, ItemNames.KERRIGAN_FURY): + if state.has(item, self.player): + count += 1 + if count >= 2: + return True + return False + + def two_kerrigan_actives(self, state: CollectionState) -> bool: + count = 0 + for i in range(7): + if state.has_any(kerrigan_actives[i], self.player): + count += 1 + return count >= 2 + + def zerg_pass_vents(self, state: CollectionState) -> bool: + return self.story_tech_granted \ + or state.has_any({ItemNames.ZERGLING, ItemNames.HYDRALISK, ItemNames.ROACH}, self.player) \ + or (self.advanced_tactics and state.has(ItemNames.INFESTOR, self.player)) + + def supreme_requirement(self, state: CollectionState) -> bool: + return self.story_tech_granted \ + or not self.kerrigan_unit_available \ + or ( + state.has_all({ItemNames.KERRIGAN_LEAPING_STRIKE, ItemNames.KERRIGAN_MEND}, self.player) + and self.kerrigan_levels(state, 35) + ) + + def kerrigan_levels(self, state: CollectionState, target: int) -> bool: + if self.story_levels_granted or not self.kerrigan_unit_available: + return True # Levels are granted + if self.kerrigan_levels_per_mission_completed > 0 \ + and self.kerrigan_levels_per_mission_completed_cap > 0 \ + and not self.is_item_placement(state): + # Levels can be granted from mission completion. + # Item pool filtering isn't aware of missions beaten. Assume that missions beaten will fulfill this rule. + return True + # Levels from missions beaten + levels = self.kerrigan_levels_per_mission_completed * state.count_group("Missions", self.player) + if self.kerrigan_levels_per_mission_completed_cap != -1: + levels = min(levels, self.kerrigan_levels_per_mission_completed_cap) + # Levels from items + for kerrigan_level_item in kerrigan_levels: + level_amount = get_full_item_list()[kerrigan_level_item].number + item_count = state.count(kerrigan_level_item, self.player) + levels += item_count * level_amount + # Total level cap + if self.kerrigan_total_level_cap != -1: + levels = min(levels, self.kerrigan_total_level_cap) + + return levels >= target + + + def the_reckoning_requirement(self, state: CollectionState) -> bool: + if self.take_over_ai_allies: + return self.terran_competent_comp(state) \ + and self.zerg_competent_comp(state) \ + and (self.zerg_competent_anti_air(state) + or self.terran_competent_anti_air(state)) + else: + return self.zerg_competent_comp(state) \ + and self.zerg_competent_anti_air(state) + + # LotV + + def protoss_common_unit(self, state: CollectionState) -> bool: + return state.has_any(self.basic_protoss_units, self.player) + + def protoss_basic_anti_air(self, state: CollectionState) -> bool: + return self.protoss_competent_anti_air(state) \ + or state.has_any({ItemNames.PHOENIX, ItemNames.MIRAGE, ItemNames.CORSAIR, ItemNames.CARRIER, ItemNames.SCOUT, + ItemNames.DARK_ARCHON, ItemNames.WRATHWALKER, ItemNames.MOTHERSHIP}, self.player) \ + or state.has_all({ItemNames.WARP_PRISM, ItemNames.WARP_PRISM_PHASE_BLASTER}, self.player) \ + or self.advanced_tactics and state.has_any( + {ItemNames.HIGH_TEMPLAR, ItemNames.SIGNIFIER, ItemNames.ASCENDANT, ItemNames.DARK_TEMPLAR, + ItemNames.SENTRY, ItemNames.ENERGIZER}, self.player) + + def protoss_anti_armor_anti_air(self, state: CollectionState) -> bool: + return self.protoss_competent_anti_air(state) \ + or state.has_any({ItemNames.SCOUT, ItemNames.WRATHWALKER}, self.player) \ + or (state.has_any({ItemNames.IMMORTAL, ItemNames.ANNIHILATOR}, self.player) + and state.has(ItemNames.IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING_MECHANICS, self.player)) + + def protoss_anti_light_anti_air(self, state: CollectionState) -> bool: + return self.protoss_competent_anti_air(state) \ + or state.has_any({ItemNames.PHOENIX, ItemNames.MIRAGE, ItemNames.CORSAIR, ItemNames.CARRIER}, self.player) + + def protoss_competent_anti_air(self, state: CollectionState) -> bool: + return state.has_any( + {ItemNames.STALKER, ItemNames.SLAYER, ItemNames.INSTIGATOR, ItemNames.DRAGOON, ItemNames.ADEPT, + ItemNames.VOID_RAY, ItemNames.DESTROYER, ItemNames.TEMPEST}, self.player) \ + or (state.has_any({ItemNames.PHOENIX, ItemNames.MIRAGE, ItemNames.CORSAIR, ItemNames.CARRIER}, self.player) + and state.has_any({ItemNames.SCOUT, ItemNames.WRATHWALKER}, self.player)) \ + or (self.advanced_tactics + and state.has_any({ItemNames.IMMORTAL, ItemNames.ANNIHILATOR}, self.player) + and state.has(ItemNames.IMMORTAL_ANNIHILATOR_ADVANCED_TARGETING_MECHANICS, self.player)) + + def protoss_has_blink(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.STALKER, ItemNames.INSTIGATOR, ItemNames.SLAYER}, self.player) \ + or ( + state.has(ItemNames.DARK_TEMPLAR_AVENGER_BLOOD_HUNTER_BLINK, self.player) + and state.has_any({ItemNames.DARK_TEMPLAR, ItemNames.BLOOD_HUNTER, ItemNames.AVENGER}, self.player) + ) + + def protoss_can_attack_behind_chasm(self, state: CollectionState) -> bool: + return state.has_any( + {ItemNames.SCOUT, ItemNames.TEMPEST, + ItemNames.CARRIER, ItemNames.VOID_RAY, ItemNames.DESTROYER, ItemNames.MOTHERSHIP}, self.player) \ + or self.protoss_has_blink(state) \ + or (state.has(ItemNames.WARP_PRISM, self.player) + and (self.protoss_common_unit(state) or state.has(ItemNames.WARP_PRISM_PHASE_BLASTER, self.player))) \ + or (self.advanced_tactics + and state.has_any({ItemNames.ORACLE, ItemNames.ARBITER}, self.player)) + + def protoss_fleet(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.CARRIER, ItemNames.TEMPEST, ItemNames.VOID_RAY, ItemNames.DESTROYER}, self.player) + + def templars_return_requirement(self, state: CollectionState) -> bool: + return self.story_tech_granted \ + or ( + state.has_any({ItemNames.IMMORTAL, ItemNames.ANNIHILATOR}, self.player) + and state.has_any({ItemNames.COLOSSUS, ItemNames.VANGUARD, ItemNames.REAVER, ItemNames.DARK_TEMPLAR}, self.player) + and state.has_any({ItemNames.SENTRY, ItemNames.HIGH_TEMPLAR}, self.player) + ) + + def brothers_in_arms_requirement(self, state: CollectionState) -> bool: + return ( + self.protoss_common_unit(state) + and self.protoss_anti_armor_anti_air(state) + and self.protoss_hybrid_counter(state) + ) or ( + self.take_over_ai_allies + and ( + self.terran_common_unit(state) + or self.protoss_common_unit(state) + ) + and ( + self.terran_competent_anti_air(state) + or self.protoss_anti_armor_anti_air(state) + ) + and ( + self.protoss_hybrid_counter(state) + or state.has_any({ItemNames.BATTLECRUISER, ItemNames.LIBERATOR, ItemNames.SIEGE_TANK}, self.player) + or state.has_all({ItemNames.SPECTRE, ItemNames.SPECTRE_PSIONIC_LASH}, self.player) + or (state.has(ItemNames.IMMORTAL, self.player) + and state.has_any({ItemNames.MARINE, ItemNames.MARAUDER}, self.player) + and self.terran_bio_heal(state)) + ) + ) + + def protoss_hybrid_counter(self, state: CollectionState) -> bool: + """ + Ground Hybrids + """ + return state.has_any( + {ItemNames.ANNIHILATOR, ItemNames.ASCENDANT, ItemNames.TEMPEST, ItemNames.CARRIER, ItemNames.VOID_RAY, + ItemNames.WRATHWALKER, ItemNames.VANGUARD}, self.player) \ + or (state.has(ItemNames.IMMORTAL, self.player) or self.advanced_tactics) and state.has_any( + {ItemNames.STALKER, ItemNames.DRAGOON, ItemNames.ADEPT, ItemNames.INSTIGATOR, ItemNames.SLAYER}, self.player) + + def the_infinite_cycle_requirement(self, state: CollectionState) -> bool: + return self.story_tech_granted \ + or not self.kerrigan_unit_available \ + or ( + self.two_kerrigan_actives(state) + and self.basic_kerrigan(state) + and self.kerrigan_levels(state, 70) + ) + + def protoss_basic_splash(self, state: CollectionState) -> bool: + return state.has_any( + {ItemNames.ZEALOT, ItemNames.COLOSSUS, ItemNames.VANGUARD, ItemNames.HIGH_TEMPLAR, ItemNames.SIGNIFIER, + ItemNames.DARK_TEMPLAR, ItemNames.REAVER, ItemNames.ASCENDANT}, self.player) + + def protoss_static_defense(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.PHOTON_CANNON, ItemNames.KHAYDARIN_MONOLITH}, self.player) + + def last_stand_requirement(self, state: CollectionState) -> bool: + return self.protoss_common_unit(state) \ + and self.protoss_competent_anti_air(state) \ + and self.protoss_static_defense(state) \ + and ( + self.advanced_tactics + or self.protoss_basic_splash(state) + ) + + def harbinger_of_oblivion_requirement(self, state: CollectionState) -> bool: + return self.protoss_anti_armor_anti_air(state) and ( + self.take_over_ai_allies + or ( + self.protoss_common_unit(state) + and self.protoss_hybrid_counter(state) + ) + ) + + def protoss_competent_comp(self, state: CollectionState) -> bool: + return self.protoss_common_unit(state) \ + and self.protoss_competent_anti_air(state) \ + and self.protoss_hybrid_counter(state) \ + and self.protoss_basic_splash(state) + + def protoss_stalker_upgrade(self, state: CollectionState) -> bool: + return ( + state.has_any( + { + ItemNames.STALKER_INSTIGATOR_SLAYER_DISINTEGRATING_PARTICLES, + ItemNames.STALKER_INSTIGATOR_SLAYER_PARTICLE_REFLECTION + }, self.player) + and self.lock_any_item(state, {ItemNames.STALKER, ItemNames.INSTIGATOR, ItemNames.SLAYER}) + ) + + def steps_of_the_rite_requirement(self, state: CollectionState) -> bool: + return self.protoss_competent_comp(state) \ + or ( + self.protoss_common_unit(state) + and self.protoss_competent_anti_air(state) + and self.protoss_static_defense(state) + ) + + def protoss_heal(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.CARRIER, ItemNames.SENTRY, ItemNames.SHIELD_BATTERY, ItemNames.RECONSTRUCTION_BEAM}, self.player) + + def templars_charge_requirement(self, state: CollectionState) -> bool: + return self.protoss_heal(state) \ + and self.protoss_anti_armor_anti_air(state) \ + and ( + self.protoss_fleet(state) + or (self.advanced_tactics + and self.protoss_competent_comp(state) + ) + ) + + def the_host_requirement(self, state: CollectionState) -> bool: + return (self.protoss_fleet(state) + and self.protoss_static_defense(state) + ) or ( + self.protoss_competent_comp(state) + and state.has(ItemNames.SOA_TIME_STOP, self.player) + ) + + def salvation_requirement(self, state: CollectionState) -> bool: + return [ + self.protoss_competent_comp(state), + self.protoss_fleet(state), + self.protoss_static_defense(state) + ].count(True) >= 2 + + def into_the_void_requirement(self, state: CollectionState) -> bool: + return self.protoss_competent_comp(state) \ + or ( + self.take_over_ai_allies + and ( + state.has(ItemNames.BATTLECRUISER, self.player) + or ( + state.has(ItemNames.ULTRALISK, self.player) + and self.protoss_competent_anti_air(state) + ) + ) + ) + + def essence_of_eternity_requirement(self, state: CollectionState) -> bool: + defense_score = self.terran_defense_rating(state, False, True) + if self.take_over_ai_allies and self.protoss_static_defense(state): + defense_score += 2 + return defense_score >= 10 \ + and ( + self.terran_competent_anti_air(state) + or self.take_over_ai_allies + and self.protoss_competent_anti_air(state) + ) \ + and ( + state.has(ItemNames.BATTLECRUISER, self.player) + or (state.has(ItemNames.BANSHEE, self.player) and state.has_any({ItemNames.VIKING, ItemNames.VALKYRIE}, + self.player)) + or self.take_over_ai_allies and self.protoss_fleet(state) + ) \ + and state.has_any({ItemNames.SIEGE_TANK, ItemNames.LIBERATOR}, self.player) + + def amons_fall_requirement(self, state: CollectionState) -> bool: + if self.take_over_ai_allies: + return ( + ( + state.has_any({ItemNames.BATTLECRUISER, ItemNames.CARRIER}, self.player) + ) + or (state.has(ItemNames.ULTRALISK, self.player) + and self.protoss_competent_anti_air(state) + and ( + state.has_any({ItemNames.LIBERATOR, ItemNames.BANSHEE, ItemNames.VALKYRIE, ItemNames.VIKING}, self.player) + or state.has_all({ItemNames.WRAITH, ItemNames.WRAITH_ADVANCED_LASER_TECHNOLOGY}, self.player) + or self.protoss_fleet(state) + ) + and (self.terran_sustainable_mech_heal(state) + or (self.spear_of_adun_autonomously_cast_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_everywhere + and state.has(ItemNames.RECONSTRUCTION_BEAM, self.player)) + ) + ) + ) \ + and self.terran_competent_anti_air(state) \ + and self.protoss_competent_comp(state) \ + and self.zerg_competent_comp(state) + else: + return state.has(ItemNames.MUTALISK, self.player) and self.zerg_competent_comp(state) + + def nova_any_weapon(self, state: CollectionState) -> bool: + return state.has_any( + {ItemNames.NOVA_C20A_CANISTER_RIFLE, ItemNames.NOVA_HELLFIRE_SHOTGUN, ItemNames.NOVA_PLASMA_RIFLE, + ItemNames.NOVA_MONOMOLECULAR_BLADE, ItemNames.NOVA_BLAZEFIRE_GUNBLADE}, self.player) + + def nova_ranged_weapon(self, state: CollectionState) -> bool: + return state.has_any( + {ItemNames.NOVA_C20A_CANISTER_RIFLE, ItemNames.NOVA_HELLFIRE_SHOTGUN, ItemNames.NOVA_PLASMA_RIFLE}, + self.player) + + def nova_splash(self, state: CollectionState) -> bool: + return state.has_any({ + ItemNames.NOVA_HELLFIRE_SHOTGUN, ItemNames.NOVA_BLAZEFIRE_GUNBLADE, ItemNames.NOVA_PULSE_GRENADES + }, self.player) \ + or self.advanced_tactics and state.has_any( + {ItemNames.NOVA_PLASMA_RIFLE, ItemNames.NOVA_MONOMOLECULAR_BLADE}, self.player) + + def nova_dash(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.NOVA_MONOMOLECULAR_BLADE, ItemNames.NOVA_BLINK}, self.player) + + def nova_full_stealth(self, state: CollectionState) -> bool: + return state.count(ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE, self.player) >= 2 + + def nova_heal(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.NOVA_ARMORED_SUIT_MODULE, ItemNames.NOVA_STIM_INFUSION}, self.player) + + def nova_escape_assist(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.NOVA_BLINK, ItemNames.NOVA_HOLO_DECOY, ItemNames.NOVA_IONIC_FORCE_FIELD}, self.player) + + def the_escape_stuff_granted(self) -> bool: + """ + The NCO first mission requires having too much stuff first before actually able to do anything + :return: + """ + return self.story_tech_granted \ + or (self.mission_order == MissionOrder.option_vanilla and self.enabled_campaigns == {SC2Campaign.NCO}) + + def the_escape_first_stage_requirement(self, state: CollectionState) -> bool: + return self.the_escape_stuff_granted() \ + or (self.nova_ranged_weapon(state) and (self.nova_full_stealth(state) or self.nova_heal(state))) + + def the_escape_requirement(self, state: CollectionState) -> bool: + return self.the_escape_first_stage_requirement(state) \ + and (self.the_escape_stuff_granted() or self.nova_splash(state)) + + def terran_cliffjumper(self, state: CollectionState) -> bool: + return state.has(ItemNames.REAPER, self.player) \ + or state.has_all({ItemNames.GOLIATH, ItemNames.GOLIATH_JUMP_JETS}, self.player) \ + or state.has_all({ItemNames.SIEGE_TANK, ItemNames.SIEGE_TANK_JUMP_JETS}, self.player) + + def terran_able_to_snipe_defiler(self, state: CollectionState) -> bool: + return state.has_all({ItemNames.NOVA_JUMP_SUIT_MODULE, ItemNames.NOVA_C20A_CANISTER_RIFLE}, self.player) \ + or state.has_all({ItemNames.SIEGE_TANK, ItemNames.SIEGE_TANK_MAELSTROM_ROUNDS, ItemNames.SIEGE_TANK_JUMP_JETS}, self.player) + + def sudden_strike_requirement(self, state: CollectionState) -> bool: + return self.sudden_strike_can_reach_objectives(state) \ + and self.terran_able_to_snipe_defiler(state) \ + and state.has_any({ItemNames.SIEGE_TANK, ItemNames.VULTURE}, self.player) \ + and self.nova_splash(state) \ + and (self.terran_defense_rating(state, True, False) >= 2 + or state.has(ItemNames.NOVA_JUMP_SUIT_MODULE, self.player)) + + def sudden_strike_can_reach_objectives(self, state: CollectionState) -> bool: + return self.terran_cliffjumper(state) \ + or state.has_any({ItemNames.BANSHEE, ItemNames.VIKING}, self.player) \ + or ( + self.advanced_tactics + and state.has(ItemNames.MEDIVAC, self.player) + and state.has_any({ItemNames.MARINE, ItemNames.MARAUDER, ItemNames.VULTURE, ItemNames.HELLION, + ItemNames.GOLIATH}, self.player) + ) + + def enemy_intelligence_garrisonable_unit(self, state: CollectionState) -> bool: + """ + Has unit usable as a Garrison in Enemy Intelligence + :param state: + :return: + """ + return state.has_any( + {ItemNames.MARINE, ItemNames.REAPER, ItemNames.MARAUDER, ItemNames.GHOST, ItemNames.SPECTRE, + ItemNames.HELLION, ItemNames.GOLIATH, ItemNames.WARHOUND, ItemNames.DIAMONDBACK, ItemNames.VIKING}, + self.player) + + def enemy_intelligence_cliff_garrison(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.REAPER, ItemNames.VIKING, ItemNames.MEDIVAC, ItemNames.HERCULES}, self.player) \ + or state.has_all({ItemNames.GOLIATH, ItemNames.GOLIATH_JUMP_JETS}, self.player) \ + or self.advanced_tactics and state.has_any({ItemNames.HELS_ANGELS, ItemNames.BRYNHILDS}, self.player) + + def enemy_intelligence_first_stage_requirement(self, state: CollectionState) -> bool: + return self.enemy_intelligence_garrisonable_unit(state) \ + and (self.terran_competent_comp(state) + or ( + self.terran_common_unit(state) + and self.terran_competent_anti_air(state) + and state.has(ItemNames.NOVA_NUKE, self.player) + ) + ) \ + and self.terran_defense_rating(state, True, True) >= 5 + + def enemy_intelligence_second_stage_requirement(self, state: CollectionState) -> bool: + return self.enemy_intelligence_first_stage_requirement(state) \ + and self.enemy_intelligence_cliff_garrison(state) \ + and ( + self.story_tech_granted + or ( + self.nova_any_weapon(state) + and ( + self.nova_full_stealth(state) + or (self.nova_heal(state) + and self.nova_splash(state) + and self.nova_ranged_weapon(state)) + ) + ) + ) + + def enemy_intelligence_third_stage_requirement(self, state: CollectionState) -> bool: + return self.enemy_intelligence_second_stage_requirement(state) \ + and ( + self.story_tech_granted + or ( + state.has(ItemNames.NOVA_PROGRESSIVE_STEALTH_SUIT_MODULE, self.player) + and self.nova_dash(state) + ) + ) + + def trouble_in_paradise_requirement(self, state: CollectionState) -> bool: + return self.nova_any_weapon(state) \ + and self.nova_splash(state) \ + and self.terran_beats_protoss_deathball(state) \ + and self.terran_defense_rating(state, True, True) >= 7 + + def night_terrors_requirement(self, state: CollectionState) -> bool: + return self.terran_common_unit(state) \ + and self.terran_competent_anti_air(state) \ + and ( + # These can handle the waves of infested, even volatile ones + state.has(ItemNames.SIEGE_TANK, self.player) + or state.has_all({ItemNames.VIKING, ItemNames.VIKING_SHREDDER_ROUNDS}, self.player) + or ( + ( + # Regular infesteds + state.has(ItemNames.FIREBAT, self.player) + or state.has_all({ItemNames.HELLION, ItemNames.HELLION_HELLBAT_ASPECT}, self.player) + or ( + self.advanced_tactics + and state.has_any({ItemNames.PERDITION_TURRET, ItemNames.PLANETARY_FORTRESS}, self.player) + ) + ) + and self.terran_bio_heal(state) + and ( + # Volatile infesteds + state.has(ItemNames.LIBERATOR, self.player) + or ( + self.advanced_tactics + and state.has_any({ItemNames.HERC, ItemNames.VULTURE}, self.player) + ) + ) + ) + ) + + def flashpoint_far_requirement(self, state: CollectionState) -> bool: + return self.terran_competent_comp(state) \ + and self.terran_mobile_detector(state) \ + and self.terran_defense_rating(state, True, False) >= 6 + + def enemy_shadow_tripwires_tool(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.NOVA_FLASHBANG_GRENADES, ItemNames.NOVA_BLINK, ItemNames.NOVA_DOMINATION}, + self.player) + + def enemy_shadow_door_unlocks_tool(self, state: CollectionState) -> bool: + return state.has_any({ItemNames.NOVA_DOMINATION, ItemNames.NOVA_BLINK, ItemNames.NOVA_JUMP_SUIT_MODULE}, + self.player) + + def enemy_shadow_domination(self, state: CollectionState) -> bool: + return self.story_tech_granted \ + or (self.nova_ranged_weapon(state) + and (self.nova_full_stealth(state) + or state.has(ItemNames.NOVA_JUMP_SUIT_MODULE, self.player) + or (self.nova_heal(state) and self.nova_splash(state)) + ) + ) + + def enemy_shadow_first_stage(self, state: CollectionState) -> bool: + return self.enemy_shadow_domination(state) \ + and (self.story_tech_granted + or ((self.nova_full_stealth(state) and self.enemy_shadow_tripwires_tool(state)) + or (self.nova_heal(state) and self.nova_splash(state)) + ) + ) + + def enemy_shadow_second_stage(self, state: CollectionState) -> bool: + return self.enemy_shadow_first_stage(state) \ + and (self.story_tech_granted + or self.nova_splash(state) + or self.nova_heal(state) + or self.nova_escape_assist(state) + ) + + def enemy_shadow_door_controls(self, state: CollectionState) -> bool: + return self.enemy_shadow_second_stage(state) \ + and (self.story_tech_granted or self.enemy_shadow_door_unlocks_tool(state)) + + def enemy_shadow_victory(self, state: CollectionState) -> bool: + return self.enemy_shadow_door_controls(state) \ + and (self.story_tech_granted or self.nova_heal(state)) + + def dark_skies_requirement(self, state: CollectionState) -> bool: + return self.terran_common_unit(state) \ + and self.terran_beats_protoss_deathball(state) \ + and self.terran_defense_rating(state, False, True) >= 8 + + def end_game_requirement(self, state: CollectionState) -> bool: + return self.terran_competent_comp(state) \ + and self.terran_mobile_detector(state) \ + and ( + state.has_any({ItemNames.BATTLECRUISER, ItemNames.LIBERATOR, ItemNames.BANSHEE}, self.player) + or state.has_all({ItemNames.WRAITH, ItemNames.WRAITH_ADVANCED_LASER_TECHNOLOGY}, self.player) + ) \ + and (state.has_any({ItemNames.BATTLECRUISER, ItemNames.VIKING, ItemNames.LIBERATOR}, self.player) + or (self.advanced_tactics + and state.has_all({ItemNames.RAVEN, ItemNames.RAVEN_HUNTER_SEEKER_WEAPON}, self.player) + ) + ) + + def __init__(self, world: World): + self.world: World = world + self.player = None if world is None else world.player + self.logic_level = get_option_value(world, 'required_tactics') + self.advanced_tactics = self.logic_level != RequiredTactics.option_standard + self.take_over_ai_allies = get_option_value(world, "take_over_ai_allies") == TakeOverAIAllies.option_true + self.kerrigan_unit_available = get_option_value(world, 'kerrigan_presence') in kerrigan_unit_available \ + and SC2Campaign.HOTS in get_enabled_campaigns(world) + self.kerrigan_levels_per_mission_completed = get_option_value(world, "kerrigan_levels_per_mission_completed") + self.kerrigan_levels_per_mission_completed_cap = get_option_value(world, "kerrigan_levels_per_mission_completed_cap") + self.kerrigan_total_level_cap = get_option_value(world, "kerrigan_total_level_cap") + self.story_tech_granted = get_option_value(world, "grant_story_tech") == GrantStoryTech.option_true + self.story_levels_granted = get_option_value(world, "grant_story_levels") != GrantStoryLevels.option_disabled + self.basic_terran_units = get_basic_units(world, SC2Race.TERRAN) + self.basic_zerg_units = get_basic_units(world, SC2Race.ZERG) + self.basic_protoss_units = get_basic_units(world, SC2Race.PROTOSS) + self.spear_of_adun_autonomously_cast_presence = get_option_value(world, "spear_of_adun_autonomously_cast_ability_presence") + self.enabled_campaigns = get_enabled_campaigns(world) + self.mission_order = get_option_value(world, "mission_order") diff --git a/worlds/sc2/Starcraft2.kv b/worlds/sc2/Starcraft2.kv new file mode 100644 index 0000000000..6b112c2f00 --- /dev/null +++ b/worlds/sc2/Starcraft2.kv @@ -0,0 +1,28 @@ + + scroll_type: ["content", "bars"] + bar_width: dp(12) + effect_cls: "ScrollEffect" + + + cols: 1 + size_hint_y: None + height: self.minimum_height + 15 + padding: [5,0,dp(12),0] + +: + cols: 1 + +: + rows: 1 + +: + cols: 1 + spacing: [0,5] + +: + text_size: self.size + markup: True + halign: 'center' + valign: 'middle' + padding: [5,0,5,0] + outline_width: 1 diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py new file mode 100644 index 0000000000..fffa618d26 --- /dev/null +++ b/worlds/sc2/__init__.py @@ -0,0 +1,482 @@ +import typing +from dataclasses import fields + +from typing import List, Set, Iterable, Sequence, Dict, Callable, Union +from math import floor, ceil +from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification +from worlds.AutoWorld import WebWorld, World +from . import ItemNames +from .Items import StarcraftItem, filler_items, get_item_table, get_full_item_list, \ + get_basic_units, ItemData, upgrade_included_names, progressive_if_nco, kerrigan_actives, kerrigan_passives, \ + kerrigan_only_passives, progressive_if_ext, not_balanced_starting_units, spear_of_adun_calldowns, \ + spear_of_adun_castable_passives, nova_equipment +from .ItemGroups import item_name_groups +from .Locations import get_locations, LocationType, get_location_types, get_plando_locations +from .Regions import create_regions +from .Options import get_option_value, LocationInclusion, KerriganLevelItemDistribution, \ + KerriganPresence, KerriganPrimalStatus, RequiredTactics, kerrigan_unit_available, StarterUnit, SpearOfAdunPresence, \ + get_enabled_campaigns, SpearOfAdunAutonomouslyCastAbilityPresence, Starcraft2Options +from .PoolFilter import filter_items, get_item_upgrades, UPGRADABLE_ITEMS, missions_in_mission_table, get_used_races +from .MissionTables import MissionInfo, SC2Campaign, lookup_name_to_mission, SC2Mission, \ + SC2Race + + +class Starcraft2WebWorld(WebWorld): + setup = Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Starcraft 2 randomizer connected to an Archipelago Multiworld", + "English", + "setup_en.md", + "setup/en", + ["TheCondor", "Phaneros"] + ) + + tutorials = [setup] + + +class SC2World(World): + """ + StarCraft II is a science fiction real-time strategy video game developed and published by Blizzard Entertainment. + Play as one of three factions across four campaigns in a battle for supremacy of the Koprulu Sector. + """ + + game = "Starcraft 2" + web = Starcraft2WebWorld() + data_version = 6 + + item_name_to_id = {name: data.code for name, data in get_full_item_list().items()} + location_name_to_id = {location.name: location.code for location in get_locations(None)} + options_dataclass = Starcraft2Options + options: Starcraft2Options + + item_name_groups = item_name_groups + locked_locations: typing.List[str] + location_cache: typing.List[Location] + mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]] = {} + final_mission_id: int + victory_item: str + required_client_version = 0, 4, 5 + + def __init__(self, multiworld: MultiWorld, player: int): + super(SC2World, self).__init__(multiworld, player) + self.location_cache = [] + self.locked_locations = [] + + def create_item(self, name: str) -> Item: + data = get_full_item_list()[name] + return StarcraftItem(name, data.classification, data.code, self.player) + + def create_regions(self): + self.mission_req_table, self.final_mission_id, self.victory_item = create_regions( + self, get_locations(self), self.location_cache + ) + + def create_items(self): + setup_events(self.player, self.locked_locations, self.location_cache) + + excluded_items = get_excluded_items(self) + + starter_items = assign_starter_items(self, excluded_items, self.locked_locations, self.location_cache) + + fill_resource_locations(self, self.locked_locations, self.location_cache) + + pool = get_item_pool(self, self.mission_req_table, starter_items, excluded_items, self.location_cache) + + fill_item_pool_with_dummy_items(self, self.locked_locations, self.location_cache, pool) + + self.multiworld.itempool += pool + + def set_rules(self): + self.multiworld.completion_condition[self.player] = lambda state: state.has(self.victory_item, self.player) + + def get_filler_item_name(self) -> str: + return self.random.choice(filler_items) + + def fill_slot_data(self): + slot_data = {} + for option_name in [field.name for field in fields(Starcraft2Options)]: + option = get_option_value(self, option_name) + if type(option) in {str, int}: + slot_data[option_name] = int(option) + slot_req_table = {} + + # Serialize data + for campaign in self.mission_req_table: + slot_req_table[campaign.id] = {} + for mission in self.mission_req_table[campaign]: + slot_req_table[campaign.id][mission] = self.mission_req_table[campaign][mission]._asdict() + # Replace mission objects with mission IDs + slot_req_table[campaign.id][mission]["mission"] = slot_req_table[campaign.id][mission]["mission"].id + + for index in range(len(slot_req_table[campaign.id][mission]["required_world"])): + # TODO this is a band-aid, sometimes the mission_req_table already contains dicts + # as far as I can tell it's related to having multiple vanilla mission orders + if not isinstance(slot_req_table[campaign.id][mission]["required_world"][index], dict): + slot_req_table[campaign.id][mission]["required_world"][index] = slot_req_table[campaign.id][mission]["required_world"][index]._asdict() + + enabled_campaigns = get_enabled_campaigns(self) + slot_data["plando_locations"] = get_plando_locations(self) + slot_data["nova_covert_ops_only"] = (enabled_campaigns == {SC2Campaign.NCO}) + slot_data["mission_req"] = slot_req_table + slot_data["final_mission"] = self.final_mission_id + slot_data["version"] = 3 + + if SC2Campaign.HOTS not in enabled_campaigns: + slot_data["kerrigan_presence"] = KerriganPresence.option_not_present + return slot_data + + +def setup_events(player: int, locked_locations: typing.List[str], location_cache: typing.List[Location]): + for location in location_cache: + if location.address is None: + item = Item(location.name, ItemClassification.progression, None, player) + + locked_locations.append(location.name) + + location.place_locked_item(item) + + +def get_excluded_items(world: World) -> Set[str]: + excluded_items: Set[str] = set(get_option_value(world, 'excluded_items')) + for item in world.multiworld.precollected_items[world.player]: + excluded_items.add(item.name) + locked_items: Set[str] = set(get_option_value(world, 'locked_items')) + # Starter items are also excluded items + starter_items: Set[str] = set(get_option_value(world, 'start_inventory')) + item_table = get_full_item_list() + soa_presence = get_option_value(world, "spear_of_adun_presence") + soa_autocast_presence = get_option_value(world, "spear_of_adun_autonomously_cast_ability_presence") + enabled_campaigns = get_enabled_campaigns(world) + + # Ensure no item is both guaranteed and excluded + invalid_items = excluded_items.intersection(locked_items) + invalid_count = len(invalid_items) + # Don't count starter items that can appear multiple times + invalid_count -= len([item for item in starter_items.intersection(locked_items) if item_table[item].quantity != 1]) + if invalid_count > 0: + raise Exception(f"{invalid_count} item{'s are' if invalid_count > 1 else ' is'} both locked and excluded from generation. Please adjust your excluded items and locked items.") + + def smart_exclude(item_choices: Set[str], choices_to_keep: int): + expected_choices = len(item_choices) + if expected_choices == 0: + return + item_choices = set(item_choices) + starter_choices = item_choices.intersection(starter_items) + excluded_choices = item_choices.intersection(excluded_items) + item_choices.difference_update(excluded_choices) + item_choices.difference_update(locked_items) + candidates = sorted(item_choices) + exclude_amount = min(expected_choices - choices_to_keep - len(excluded_choices) + len(starter_choices), len(candidates)) + if exclude_amount > 0: + excluded_items.update(world.random.sample(candidates, exclude_amount)) + + # Nova gear exclusion if NCO not in campaigns + if SC2Campaign.NCO not in enabled_campaigns: + excluded_items = excluded_items.union(nova_equipment) + + kerrigan_presence = get_option_value(world, "kerrigan_presence") + # Exclude Primal Form item if option is not set or Kerrigan is unavailable + if get_option_value(world, "kerrigan_primal_status") != KerriganPrimalStatus.option_item or \ + (kerrigan_presence in {KerriganPresence.option_not_present, KerriganPresence.option_not_present_and_no_passives}): + excluded_items.add(ItemNames.KERRIGAN_PRIMAL_FORM) + + # no Kerrigan & remove all passives => remove all abilities + if kerrigan_presence == KerriganPresence.option_not_present_and_no_passives: + for tier in range(7): + smart_exclude(kerrigan_actives[tier].union(kerrigan_passives[tier]), 0) + else: + # no Kerrigan, but keep non-Kerrigan passives + if kerrigan_presence == KerriganPresence.option_not_present: + smart_exclude(kerrigan_only_passives, 0) + for tier in range(7): + smart_exclude(kerrigan_actives[tier], 0) + + # SOA exclusion, other cases are handled by generic race logic + if (soa_presence == SpearOfAdunPresence.option_lotv_protoss and SC2Campaign.LOTV not in enabled_campaigns) \ + or soa_presence == SpearOfAdunPresence.option_not_present: + excluded_items.update(spear_of_adun_calldowns) + if (soa_autocast_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_lotv_protoss \ + and SC2Campaign.LOTV not in enabled_campaigns) \ + or soa_autocast_presence == SpearOfAdunAutonomouslyCastAbilityPresence.option_not_present: + excluded_items.update(spear_of_adun_castable_passives) + + return excluded_items + + +def assign_starter_items(world: World, excluded_items: Set[str], locked_locations: List[str], location_cache: typing.List[Location]) -> List[Item]: + starter_items: List[Item] = [] + non_local_items = get_option_value(world, "non_local_items") + starter_unit = get_option_value(world, "starter_unit") + enabled_campaigns = get_enabled_campaigns(world) + first_mission = get_first_mission(world.mission_req_table) + # Ensuring that first mission is completable + if starter_unit == StarterUnit.option_off: + starter_mission_locations = [location.name for location in location_cache + if location.parent_region.name == first_mission + and location.access_rule == Location.access_rule] + if not starter_mission_locations: + # Force early unit if first mission is impossible without one + starter_unit = StarterUnit.option_any_starter_unit + + if starter_unit != StarterUnit.option_off: + first_race = lookup_name_to_mission[first_mission].race + + if first_race == SC2Race.ANY: + # If the first mission is a logic-less no-build + mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]] = world.mission_req_table + races = get_used_races(mission_req_table, world) + races.remove(SC2Race.ANY) + if lookup_name_to_mission[first_mission].race in races: + # The campaign's race is in (At least one mission that's not logic-less no-build exists) + first_race = lookup_name_to_mission[first_mission].campaign.race + elif len(races) > 0: + # The campaign only has logic-less no-build missions. Find any other valid race + first_race = world.random.choice(list(races)) + + if first_race != SC2Race.ANY: + # The race of the early unit has been chosen + basic_units = get_basic_units(world, first_race) + if starter_unit == StarterUnit.option_balanced: + basic_units = basic_units.difference(not_balanced_starting_units) + if first_mission == SC2Mission.DARK_WHISPERS.mission_name: + # Special case - you don't have a logicless location but need an AA + basic_units = basic_units.difference( + {ItemNames.ZEALOT, ItemNames.CENTURION, ItemNames.SENTINEL, ItemNames.BLOOD_HUNTER, + ItemNames.AVENGER, ItemNames.IMMORTAL, ItemNames.ANNIHILATOR, ItemNames.VANGUARD}) + if first_mission == SC2Mission.SUDDEN_STRIKE.mission_name: + # Special case - cliffjumpers + basic_units = {ItemNames.REAPER, ItemNames.GOLIATH, ItemNames.SIEGE_TANK, ItemNames.VIKING, ItemNames.BANSHEE} + local_basic_unit = sorted(item for item in basic_units if item not in non_local_items and item not in excluded_items) + if not local_basic_unit: + # Drop non_local_items constraint + local_basic_unit = sorted(item for item in basic_units if item not in excluded_items) + if not local_basic_unit: + raise Exception("Early Unit: At least one basic unit must be included") + + unit: Item = add_starter_item(world, excluded_items, local_basic_unit) + starter_items.append(unit) + + # NCO-only specific rules + if first_mission == SC2Mission.SUDDEN_STRIKE.mission_name: + support_item: Union[str, None] = None + if unit.name == ItemNames.REAPER: + support_item = ItemNames.REAPER_SPIDER_MINES + elif unit.name == ItemNames.GOLIATH: + support_item = ItemNames.GOLIATH_JUMP_JETS + elif unit.name == ItemNames.SIEGE_TANK: + support_item = ItemNames.SIEGE_TANK_JUMP_JETS + elif unit.name == ItemNames.VIKING: + support_item = ItemNames.VIKING_SMART_SERVOS + if support_item is not None: + starter_items.append(add_starter_item(world, excluded_items, [support_item])) + starter_items.append(add_starter_item(world, excluded_items, [ItemNames.NOVA_JUMP_SUIT_MODULE])) + starter_items.append( + add_starter_item(world, excluded_items, + [ + ItemNames.NOVA_HELLFIRE_SHOTGUN, + ItemNames.NOVA_PLASMA_RIFLE, + ItemNames.NOVA_PULSE_GRENADES + ])) + if enabled_campaigns == {SC2Campaign.NCO}: + starter_items.append(add_starter_item(world, excluded_items, [ItemNames.LIBERATOR_RAID_ARTILLERY])) + + starter_abilities = get_option_value(world, 'start_primary_abilities') + assert isinstance(starter_abilities, int) + if starter_abilities: + ability_count = starter_abilities + ability_tiers = [0, 1, 3] + world.random.shuffle(ability_tiers) + if ability_count > 3: + ability_tiers.append(6) + for tier in ability_tiers: + abilities = kerrigan_actives[tier].union(kerrigan_passives[tier]).difference(excluded_items, non_local_items) + if not abilities: + abilities = kerrigan_actives[tier].union(kerrigan_passives[tier]).difference(excluded_items) + if abilities: + ability_count -= 1 + starter_items.append(add_starter_item(world, excluded_items, list(abilities))) + if ability_count == 0: + break + + return starter_items + + +def get_first_mission(mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]]) -> str: + # The first world should also be the starting world + campaigns = mission_req_table.keys() + lowest_id = min([campaign.id for campaign in campaigns]) + first_campaign = [campaign for campaign in campaigns if campaign.id == lowest_id][0] + first_mission = list(mission_req_table[first_campaign])[0] + return first_mission + + +def add_starter_item(world: World, excluded_items: Set[str], item_list: Sequence[str]) -> Item: + + item_name = world.random.choice(sorted(item_list)) + + excluded_items.add(item_name) + + item = create_item_with_correct_settings(world.player, item_name) + + world.multiworld.push_precollected(item) + + return item + + +def get_item_pool(world: World, mission_req_table: Dict[SC2Campaign, Dict[str, MissionInfo]], + starter_items: List[Item], excluded_items: Set[str], location_cache: List[Location]) -> List[Item]: + pool: List[Item] = [] + + # For the future: goal items like Artifact Shards go here + locked_items = [] + + # YAML items + yaml_locked_items = get_option_value(world, 'locked_items') + assert not isinstance(yaml_locked_items, int) + + # Adjust generic upgrade availability based on options + include_upgrades = get_option_value(world, 'generic_upgrade_missions') == 0 + upgrade_items = get_option_value(world, 'generic_upgrade_items') + assert isinstance(upgrade_items, int) + + # Include items from outside main campaigns + item_sets = {'wol', 'hots', 'lotv'} + if get_option_value(world, 'nco_items') \ + or SC2Campaign.NCO in get_enabled_campaigns(world): + item_sets.add('nco') + if get_option_value(world, 'bw_items'): + item_sets.add('bw') + if get_option_value(world, 'ext_items'): + item_sets.add('ext') + + def allowed_quantity(name: str, data: ItemData) -> int: + if name in excluded_items \ + or data.type == "Upgrade" and (not include_upgrades or name not in upgrade_included_names[upgrade_items]) \ + or not data.origin.intersection(item_sets): + return 0 + elif name in progressive_if_nco and 'nco' not in item_sets: + return 1 + elif name in progressive_if_ext and 'ext' not in item_sets: + return 1 + else: + return data.quantity + + for name, data in get_item_table().items(): + for _ in range(allowed_quantity(name, data)): + item = create_item_with_correct_settings(world.player, name) + if name in yaml_locked_items: + locked_items.append(item) + else: + pool.append(item) + + existing_items = starter_items + [item for item in world.multiworld.precollected_items[world.player] if item not in starter_items] + existing_names = [item.name for item in existing_items] + + # Check the parent item integrity, exclude items + pool[:] = [item for item in pool if pool_contains_parent(item, pool + locked_items + existing_items)] + + # Removing upgrades for excluded items + for item_name in excluded_items: + if item_name in existing_names: + continue + invalid_upgrades = get_item_upgrades(pool, item_name) + for invalid_upgrade in invalid_upgrades: + pool.remove(invalid_upgrade) + + fill_pool_with_kerrigan_levels(world, pool) + filtered_pool = filter_items(world, mission_req_table, location_cache, pool, existing_items, locked_items) + return filtered_pool + + +def fill_item_pool_with_dummy_items(self: SC2World, locked_locations: List[str], + location_cache: List[Location], pool: List[Item]): + for _ in range(len(location_cache) - len(locked_locations) - len(pool)): + item = create_item_with_correct_settings(self.player, self.get_filler_item_name()) + pool.append(item) + + +def create_item_with_correct_settings(player: int, name: str) -> Item: + data = get_full_item_list()[name] + + item = Item(name, data.classification, data.code, player) + + return item + + +def pool_contains_parent(item: Item, pool: Iterable[Item]): + item_data = get_full_item_list().get(item.name) + if item_data.parent_item is None: + # The item has not associated parent, the item is valid + return True + parent_item = item_data.parent_item + # Check if the pool contains the parent item + return parent_item in [pool_item.name for pool_item in pool] + + +def fill_resource_locations(world: World, locked_locations: List[str], location_cache: List[Location]): + """ + Filters the locations in the world using a trash or Nothing item + :param multiworld: + :param player: + :param locked_locations: + :param location_cache: + :return: + """ + open_locations = [location for location in location_cache if location.item is None] + plando_locations = get_plando_locations(world) + resource_location_types = get_location_types(world, LocationInclusion.option_resources) + location_data = {sc2_location.name: sc2_location for sc2_location in get_locations(world)} + for location in open_locations: + # Go through the locations that aren't locked yet (early unit, etc) + if location.name not in plando_locations: + # The location is not plando'd + sc2_location = location_data[location.name] + if sc2_location.type in resource_location_types: + item_name = world.random.choice(filler_items) + item = create_item_with_correct_settings(world.player, item_name) + location.place_locked_item(item) + locked_locations.append(location.name) + + +def place_exclusion_item(item_name, location, locked_locations, player): + item = create_item_with_correct_settings(player, item_name) + location.place_locked_item(item) + locked_locations.append(location.name) + + +def fill_pool_with_kerrigan_levels(world: World, item_pool: List[Item]): + total_levels = get_option_value(world, "kerrigan_level_item_sum") + if get_option_value(world, "kerrigan_presence") not in kerrigan_unit_available \ + or total_levels == 0 \ + or SC2Campaign.HOTS not in get_enabled_campaigns(world): + return + + def add_kerrigan_level_items(level_amount: int, item_amount: int): + name = f"{level_amount} Kerrigan Level" + if level_amount > 1: + name += "s" + for _ in range(item_amount): + item_pool.append(create_item_with_correct_settings(world.player, name)) + + sizes = [70, 35, 14, 10, 7, 5, 2, 1] + option = get_option_value(world, "kerrigan_level_item_distribution") + + assert isinstance(option, int) + assert isinstance(total_levels, int) + + if option in (KerriganLevelItemDistribution.option_vanilla, KerriganLevelItemDistribution.option_smooth): + distribution = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + if option == KerriganLevelItemDistribution.option_vanilla: + distribution = [32, 0, 0, 1, 3, 0, 0, 0, 1, 1] + else: # Smooth + distribution = [0, 0, 0, 1, 1, 2, 2, 2, 1, 1] + for tier in range(len(distribution)): + add_kerrigan_level_items(tier + 1, distribution[tier]) + else: + size = sizes[option - 2] + round_func: Callable[[float], int] = round + if total_levels > 70: + round_func = floor + else: + round_func = ceil + add_kerrigan_level_items(size, round_func(float(total_levels) / size)) diff --git a/worlds/sc2/docs/contributors.md b/worlds/sc2/docs/contributors.md new file mode 100644 index 0000000000..5b62466d7e --- /dev/null +++ b/worlds/sc2/docs/contributors.md @@ -0,0 +1,42 @@ +# Contributors +Contibutors are listed with preferred or Discord names first, with github usernames prepended with an `@` + +## Update 2024.0 +### Code Changes +* Ziktofel (@Ziktofel) +* Salzkorn (@Salzkorn) +* EnvyDragon (@EnvyDragon) +* Phanerus (@MatthewMarinets) +* Madi Sylveon (@MadiMadsen) +* Magnemania (@Magnemania) +* Subsourian (@Subsourian) +* Hopop (@hopop201) +* Alice Voltaire (@AliceVoltaire) +* Genderdruid (@ArchonofFail) +* CrazedCollie (@FoxOfWar) + +### Additional Beta testing and bug reports +* Varcklen (@Varcklen) +* BicolourSnake (@Bicoloursnake) +* @NobleXenon +* Severencir (@Severencir) +* neocerber (@neocerber) +* Mati (@Matiya-star) +* Ixzine +* sweetox +* 8thDaughterOfFrost +* The M8 +* Berserker (@Berserker66) +* KaitoKid +* Sheen +* ProfBytes +* IncoherentOrange +* eudaimonistic +* Figment + +## Older versions +Not all contributors to older versions of Archipelago Starcraft 2 are known. + +TheCondor (@TheCondor07) is the original maintainer of the project. Other known contributors include: +* soldieroforder +* Berserker (@Berserker66) diff --git a/worlds/sc2/docs/en_Starcraft 2.md b/worlds/sc2/docs/en_Starcraft 2.md new file mode 100644 index 0000000000..43a7da89f2 --- /dev/null +++ b/worlds/sc2/docs/en_Starcraft 2.md @@ -0,0 +1,64 @@ +# Starcraft 2 Wings of Liberty + +## What does randomization do to this game? + +The following unlocks are randomized as items: +1. Your ability to build any non-worker unit. +2. Unit specific upgrades including some combinations not available in the vanilla campaigns, such as both strain choices simultaneously for Zerg and every Spear of Adun upgrade simultaneously for Protoss! +3. Your ability to get the generic unit upgrades, such as attack and armour upgrades. +4. Other miscellaneous upgrades such as laboratory upgrades and mercenaries for Terran, Kerrigan levels and upgrades for Zerg, and Spear of Adun upgrades for Protoss. +5. Small boosts to your starting mineral, vespene gas, and supply totals on each mission. + +You find items by making progress in these categories: +* Completing missions +* Completing bonus objectives (like by gathering lab research material in Wings of Liberty) +* Reaching milestones in the mission, such as completing part of a main objective +* Completing challenges based on achievements in the base game, such as clearing all Zerg on Devil's Playground + +Except for mission completion, these categories can be disabled in the game's settings. For instance, you can disable getting items for reaching required milestones. + +When you receive items, they will immediately become available, even during a mission, and you will be +notified via a text box in the top-right corner of the game screen. Item unlocks are also logged in the Archipelago client. + +Missions are launched through the Starcraft 2 Archipelago client, through the Starcraft 2 Launcher tab. The between mission segments on the Hyperion, the Leviathan, and the Spear of Adun are not included. Additionally, metaprogression currencies such as credits and Solarite are not used. + +## What is the goal of this game when randomized? + +The goal is to beat the final mission in the mission order. The yaml configuration file controls the mission order and how missions are shuffled. + +## What non-randomized changes are there from vanilla Starcraft 2? + +1. Some missions have more vespene geysers available to allow a wider variety of units. +2. Many new units and upgrades have been added as items, coming from co-op, melee, later campaigns, later expansions, brood war, and original ideas. +3. Higher-tech production structures, including Factories, Starports, Robotics Facilities, and Stargates, no longer have tech requirements. +4. Zerg missions have been adjusted to give the player a starting Lair where they would only have Hatcheries. +5. Upgrades with a downside have had the downside removed, such as automated refineries costing more or tech reactors taking longer to build. +6. Unit collision within the vents in Enemy Within has been adjusted to allow larger units to travel through them without getting stuck in odd places. +7. Several vanilla bugs have been fixed. + +## Which of my items can be in another player's world? + +By default, any of StarCraft 2's items (specified above) can be in another player's world. See the +[Advanced YAML Guide](https://archipelago.gg/tutorial/Archipelago/advanced_settings/en) +for more information on how to change this. + +## Unique Local Commands + +The following commands are only available when using the Starcraft 2 Client to play with Archipelago. You can list them any time in the client with `/help`. + +* `/download_data` Download the most recent release of the necessary files for playing SC2 with Archipelago. Will overwrite existing files +* `/difficulty [difficulty]` Overrides the difficulty set for the world. + * Options: casual, normal, hard, brutal +* `/game_speed [game_speed]` Overrides the game speed for the world + * Options: default, slower, slow, normal, fast, faster +* `/color [faction] [color]` Changes your color for one of your playable factions. + * Faction options: raynor, kerrigan, primal, protoss, nova + * Color options: white, red, blue, teal, purple, yellow, orange, green, lightpink, violet, lightgrey, darkgreen, brown, lightgreen, darkgrey, pink, rainbow, random, default +* `/option [option_name] [option_value]` Sets an option normally controlled by your yaml after generation. + * Run without arguments to list all options. + * Options pertain to automatic cutscene skipping, Kerrigan presence, Spear of Adun presence, starting resource amounts, controlling AI allies, etc. +* `/disable_mission_check` Disables the check to see if a mission is available to play. Meant for co-op runs where one player can play the next mission in a chain the other player is doing. +* `/play [mission_id]` Starts a Starcraft 2 mission based off of the mission_id provided +* `/available` Get what missions are currently available to play +* `/unfinished` Get what missions are currently available to play and have not had all locations checked +* `/set_path [path]` Manually set the SC2 install directory (if the automatic detection fails) diff --git a/worlds/sc2wol/docs/setup_en.md b/worlds/sc2/docs/setup_en.md similarity index 50% rename from worlds/sc2wol/docs/setup_en.md rename to worlds/sc2/docs/setup_en.md index 9bfeb3d235..10881e149c 100644 --- a/worlds/sc2wol/docs/setup_en.md +++ b/worlds/sc2/docs/setup_en.md @@ -7,12 +7,10 @@ to obtain a config file for StarCraft 2. - [StarCraft 2](https://starcraft2.com/en-us/) - [The most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases) -- [StarCraft 2 AP Maps and Data](https://github.com/Ziktofel/Archipelago-SC2-data/releases) ## How do I install this randomizer? -1. Install StarCraft 2 and Archipelago using the first two links above. (The StarCraft 2 client for Archipelago is - included by default.) +1. Install StarCraft 2 and Archipelago using the links above. The StarCraft 2 Archipelago client is downloaded by the Archipelago installer. - Linux users should also follow the instructions found at the bottom of this page (["Running in Linux"](#running-in-linux)). 2. Run ArchipelagoStarcraft2Client.exe. @@ -21,25 +19,66 @@ to obtain a config file for StarCraft 2. ## Where do I get a config file (aka "YAML") for this game? -The [Player Settings](https://archipelago.gg/games/Starcraft%202%20Wings%20of%20Liberty/player-settings) page on this -website allows you to choose your personal settings for the randomizer and download them into a config file. Remember -the name you type in the `Player Name` box; that's the "slot name" the client will ask you for when you attempt to -connect! +Yaml files are configuration files that tell Archipelago how you'd like your game to be randomized, even if you're only using default options. +When you're setting up a multiworld, every world needs its own yaml file. -### And why do I need a config file? +There are three basic ways to get a yaml: +* You can go to the [Player Options](https://archipelago.gg/games/Starcraft%202/player-options) page, set your options in the GUI, and export the yaml. +* You can generate a template, either by downloading it from the [Player Options](https://archipelago.gg/games/Starcraft%202/player-options) page or by generating it from the Launcher (ArchipelagoLauncher.exe). The template includes descriptions of each option, you just have to edit it in your text editor of choice. +* You can ask someone else to share their yaml to use it for yourself or adjust it as you wish. -Config files tell Archipelago how you'd like your game to be randomized, even if you're only using default settings. -When you're setting up a multiworld, every world needs its own config file. -Check out [Creating a YAML](https://archipelago.gg/tutorial/Archipelago/setup/en#creating-a-yaml) for more information. +Remember the name you enter in the options page or in the yaml file, you'll need it to connect later! + +Note that the basic Player Options page doesn't allow you to change all advanced options, such as excluding particular units or upgrades. Go through the [Weighted Options](https://archipelago.gg/weighted-options) page for that. + +Check out [Creating a YAML](https://archipelago.gg/tutorial/Archipelago/setup/en#creating-a-yaml) for more game-agnostic information. + +### Common yaml questions +#### How do I know I set my yaml up correctly? + +The simplest way to check is to test it out. Save your yaml to the Players/ folder within your Archipelago installation and run ArchipelagoGenerate.exe. You should see a new .zip file within the output/ folder of your Archipelago installation if things worked correctly. It's advisable to run ArchipelagoGenerate through a terminal so that you can see the printout, which will include any errors and the precise output file name if it's successful. If you don't like terminals, you can also check the log file in the logs/ folder. + +#### What does Progression Balancing do? + +For Starcraft 2, not much. It's an Archipelago-wide option meant to shift required items earlier in the playthrough, but Starcraft 2 tends to be much more open in what items you can use. As such, this adjustment isn't very noticeable. It can also increase generation times, so we generally recommend turning it off. + +#### How do I specify items in a list, like in excluded items? + +You can look up the syntax for yaml collections in the [YAML specification](https://yaml.org/spec/1.2.2/#21-collections). For lists, every item goes on its own line, started with a hyphen: + +```yaml +excluded_items: + - Battlecruiser + - Drop-Pods (Kerrigan Tier 7) +``` + +An empty list is just a matching pair of square brackets: `[]`. That's the default value in the template, which should let you know to use this syntax. + +#### How do I specify items for the starting inventory? + +The starting inventory is a YAML mapping rather than a list, which associates an item with the amount you start with. The syntax looks like the item name, followed by a colon, then a whitespace character, and then the value: + +```yaml +start_inventory: + Micro-Filtering: 1 + Additional Starting Vespene: 5 +``` + +An empty mapping is just a matching pair of curly braces: `{}`. That's the default value in the template, which should let you know to use this syntax. + +#### How do I know the exact names of items? + +You can look up a complete list if item names in the [Icon Repository](https://matthewmarinets.github.io/ap_sc2_icons/). ## How do I join a MultiWorld game? 1. Run ArchipelagoStarcraft2Client.exe. - macOS users should instead follow the instructions found at ["Running in macOS"](#running-in-macos) for this step only. 2. Type `/connect [server ip]`. -3. Type your slot name and the server's password when prompted. -4. Once connected, switch to the 'StarCraft 2 Launcher' tab in the client. There, you can see every mission. By default, - only 'Liberation Day' will be available at the beginning. Just click on a mission to start it! + - If you're running through the website, the server IP should be displayed near the top of the room page. +3. Type your slot name from your YAML when prompted. +4. If the server has a password, enter that when prompted. +5. Once connected, switch to the 'StarCraft 2 Launcher' tab in the client. There, you can see all the missions in your world. Unreachable missions will have greyed-out text. Just click on an available mission to start it! ## The game isn't launching when I try to start a mission. diff --git a/worlds/sc2wol/requirements.txt b/worlds/sc2/requirements.txt similarity index 100% rename from worlds/sc2wol/requirements.txt rename to worlds/sc2/requirements.txt diff --git a/worlds/sc2/test/__init__.py b/worlds/sc2/test/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/worlds/sc2/test/test_Regions.py b/worlds/sc2/test/test_Regions.py new file mode 100644 index 0000000000..c268b65da9 --- /dev/null +++ b/worlds/sc2/test/test_Regions.py @@ -0,0 +1,41 @@ +import unittest +from .test_base import Sc2TestBase +from .. import Regions +from .. import Options, MissionTables + +class TestGridsizes(unittest.TestCase): + def test_grid_sizes_meet_specs(self): + self.assertTupleEqual((1, 2, 0), Regions.get_grid_dimensions(2)) + self.assertTupleEqual((1, 3, 0), Regions.get_grid_dimensions(3)) + self.assertTupleEqual((2, 2, 0), Regions.get_grid_dimensions(4)) + self.assertTupleEqual((2, 3, 1), Regions.get_grid_dimensions(5)) + self.assertTupleEqual((2, 4, 1), Regions.get_grid_dimensions(7)) + self.assertTupleEqual((2, 4, 0), Regions.get_grid_dimensions(8)) + self.assertTupleEqual((3, 3, 0), Regions.get_grid_dimensions(9)) + self.assertTupleEqual((2, 5, 0), Regions.get_grid_dimensions(10)) + self.assertTupleEqual((3, 4, 1), Regions.get_grid_dimensions(11)) + self.assertTupleEqual((3, 4, 0), Regions.get_grid_dimensions(12)) + self.assertTupleEqual((3, 5, 0), Regions.get_grid_dimensions(15)) + self.assertTupleEqual((4, 4, 0), Regions.get_grid_dimensions(16)) + self.assertTupleEqual((4, 6, 0), Regions.get_grid_dimensions(24)) + self.assertTupleEqual((5, 5, 0), Regions.get_grid_dimensions(25)) + self.assertTupleEqual((5, 6, 1), Regions.get_grid_dimensions(29)) + self.assertTupleEqual((5, 7, 2), Regions.get_grid_dimensions(33)) + + +class TestGridGeneration(Sc2TestBase): + options = { + "mission_order": Options.MissionOrder.option_grid, + "excluded_missions": [MissionTables.SC2Mission.ZERO_HOUR.mission_name,], + "enable_hots_missions": False, + "enable_prophecy_missions": True, + "enable_lotv_prologue_missions": False, + "enable_lotv_missions": False, + "enable_epilogue_missions": False, + "enable_nco_missions": False + } + + def test_size_matches_exclusions(self): + self.assertNotIn(MissionTables.SC2Mission.ZERO_HOUR.mission_name, self.multiworld.regions) + # WoL has 29 missions. -1 for Zero Hour being excluded, +1 for the automatically-added menu location + self.assertEqual(len(self.multiworld.regions), 29) diff --git a/worlds/sc2/test/test_base.py b/worlds/sc2/test/test_base.py new file mode 100644 index 0000000000..28529e37ed --- /dev/null +++ b/worlds/sc2/test/test_base.py @@ -0,0 +1,11 @@ +from typing import * + +from test.TestBase import WorldTestBase +from .. import SC2World +from .. import Client + +class Sc2TestBase(WorldTestBase): + game = Client.SC2Context.game + world: SC2World + player: ClassVar[int] = 1 + skip_long_tests: bool = True diff --git a/worlds/sc2/test/test_options.py b/worlds/sc2/test/test_options.py new file mode 100644 index 0000000000..30d21f3969 --- /dev/null +++ b/worlds/sc2/test/test_options.py @@ -0,0 +1,7 @@ +import unittest +from .test_base import Sc2TestBase +from .. import Options, MissionTables + +class TestOptions(unittest.TestCase): + def test_campaign_size_option_max_matches_number_of_missions(self): + self.assertEqual(Options.MaximumCampaignSize.range_end, len(MissionTables.SC2Mission)) diff --git a/worlds/sc2wol/Client.py b/worlds/sc2wol/Client.py deleted file mode 100644 index 83b7b62d29..0000000000 --- a/worlds/sc2wol/Client.py +++ /dev/null @@ -1,1222 +0,0 @@ -from __future__ import annotations - -import asyncio -import copy -import ctypes -import logging -import multiprocessing -import os.path -import re -import sys -import tempfile -import typing -import queue -import zipfile -import io -import random -import concurrent.futures -from pathlib import Path - -# CommonClient import first to trigger ModuleUpdater -from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser -from Utils import init_logging, is_windows - -if __name__ == "__main__": - init_logging("SC2Client", exception_logger="Client") - -logger = logging.getLogger("Client") -sc2_logger = logging.getLogger("Starcraft2") - -import nest_asyncio -from worlds._sc2common import bot -from worlds._sc2common.bot.data import Race -from worlds._sc2common.bot.main import run_game -from worlds._sc2common.bot.player import Bot -from worlds.sc2wol import SC2WoLWorld -from worlds.sc2wol.Items import lookup_id_to_name, get_full_item_list, ItemData, type_flaggroups, upgrade_numbers -from worlds.sc2wol.Locations import SC2WOL_LOC_ID_OFFSET -from worlds.sc2wol.MissionTables import lookup_id_to_mission -from worlds.sc2wol.Regions import MissionInfo - -import colorama -from NetUtils import ClientStatus, NetworkItem, RawJSONtoTextParser, JSONtoTextParser, JSONMessagePart -from MultiServer import mark_raw - -pool = concurrent.futures.ThreadPoolExecutor(1) -loop = asyncio.get_event_loop_policy().new_event_loop() -nest_asyncio.apply(loop) -max_bonus: int = 13 -victory_modulo: int = 100 - -# GitHub repo where the Map/mod data is hosted for /download_data command -DATA_REPO_OWNER = "Ziktofel" -DATA_REPO_NAME = "Archipelago-SC2-data" -DATA_API_VERSION = "API2" - - -# Data version file path. -# This file is used to tell if the downloaded data are outdated -# Associated with /download_data command -def get_metadata_file(): - return os.environ["SC2PATH"] + os.sep + "ArchipelagoSC2Metadata.txt" - - -class StarcraftClientProcessor(ClientCommandProcessor): - ctx: SC2Context - - def _cmd_difficulty(self, difficulty: str = "") -> bool: - """Overrides the current difficulty set for the world. Takes the argument casual, normal, hard, or brutal""" - options = difficulty.split() - num_options = len(options) - - if num_options > 0: - difficulty_choice = options[0].lower() - if difficulty_choice == "casual": - self.ctx.difficulty_override = 0 - elif difficulty_choice == "normal": - self.ctx.difficulty_override = 1 - elif difficulty_choice == "hard": - self.ctx.difficulty_override = 2 - elif difficulty_choice == "brutal": - self.ctx.difficulty_override = 3 - else: - self.output("Unable to parse difficulty '" + options[0] + "'") - return False - - self.output("Difficulty set to " + options[0]) - return True - - else: - if self.ctx.difficulty == -1: - self.output("Please connect to a seed before checking difficulty.") - else: - current_difficulty = self.ctx.difficulty - if self.ctx.difficulty_override >= 0: - current_difficulty = self.ctx.difficulty_override - self.output("Current difficulty: " + ["Casual", "Normal", "Hard", "Brutal"][current_difficulty]) - self.output("To change the difficulty, add the name of the difficulty after the command.") - return False - - - def _cmd_game_speed(self, game_speed: str = "") -> bool: - """Overrides the current game speed for the world. - Takes the arguments default, slower, slow, normal, fast, faster""" - options = game_speed.split() - num_options = len(options) - - if num_options > 0: - speed_choice = options[0].lower() - if speed_choice == "default": - self.ctx.game_speed_override = 0 - elif speed_choice == "slower": - self.ctx.game_speed_override = 1 - elif speed_choice == "slow": - self.ctx.game_speed_override = 2 - elif speed_choice == "normal": - self.ctx.game_speed_override = 3 - elif speed_choice == "fast": - self.ctx.game_speed_override = 4 - elif speed_choice == "faster": - self.ctx.game_speed_override = 5 - else: - self.output("Unable to parse game speed '" + options[0] + "'") - return False - - self.output("Game speed set to " + options[0]) - return True - - else: - if self.ctx.game_speed == -1: - self.output("Please connect to a seed before checking game speed.") - else: - current_speed = self.ctx.game_speed - if self.ctx.game_speed_override >= 0: - current_speed = self.ctx.game_speed_override - self.output("Current game speed: " - + ["Default", "Slower", "Slow", "Normal", "Fast", "Faster"][current_speed]) - self.output("To change the game speed, add the name of the speed after the command," - " or Default to select based on difficulty.") - return False - - def _cmd_color(self, color: str = "") -> bool: - player_colors = [ - "White", "Red", "Blue", "Teal", - "Purple", "Yellow", "Orange", "Green", - "LightPink", "Violet", "LightGrey", "DarkGreen", - "Brown", "LightGreen", "DarkGrey", "Pink", - "Rainbow", "Random", "Default" - ] - match_colors = [player_color.lower() for player_color in player_colors] - if color: - if color.lower() not in match_colors: - self.output(color + " is not a valid color. Available colors: " + ', '.join(player_colors)) - return False - if color.lower() == "random": - color = random.choice(player_colors[:16]) - self.ctx.player_color = match_colors.index(color.lower()) - self.output("Color set to " + player_colors[self.ctx.player_color]) - else: - self.output("Current player color: " + player_colors[self.ctx.player_color]) - self.output("To change your colors, add the name of the color after the command.") - self.output("Available colors: " + ', '.join(player_colors)) - - def _cmd_disable_mission_check(self) -> bool: - """Disables the check to see if a mission is available to play. Meant for co-op runs where one player can play - the next mission in a chain the other player is doing.""" - self.ctx.missions_unlocked = True - sc2_logger.info("Mission check has been disabled") - return True - - def _cmd_play(self, mission_id: str = "") -> bool: - """Start a Starcraft 2 mission""" - - options = mission_id.split() - num_options = len(options) - - if num_options > 0: - mission_number = int(options[0]) - - self.ctx.play_mission(mission_number) - - else: - sc2_logger.info( - "Mission ID needs to be specified. Use /unfinished or /available to view ids for available missions.") - return False - - return True - - def _cmd_available(self) -> bool: - """Get what missions are currently available to play""" - - request_available_missions(self.ctx) - return True - - def _cmd_unfinished(self) -> bool: - """Get what missions are currently available to play and have not had all locations checked""" - - request_unfinished_missions(self.ctx) - return True - - @mark_raw - def _cmd_set_path(self, path: str = '') -> bool: - """Manually set the SC2 install directory (if the automatic detection fails).""" - if path: - os.environ["SC2PATH"] = path - is_mod_installed_correctly() - return True - else: - sc2_logger.warning("When using set_path, you must type the path to your SC2 install directory.") - return False - - def _cmd_download_data(self) -> bool: - """Download the most recent release of the necessary files for playing SC2 with - Archipelago. Will overwrite existing files.""" - pool.submit(self._download_data) - return True - - @staticmethod - def _download_data() -> bool: - if "SC2PATH" not in os.environ: - check_game_install_path() - - if os.path.exists(get_metadata_file()): - with open(get_metadata_file(), "r") as f: - metadata = f.read() - else: - metadata = None - - tempzip, metadata = download_latest_release_zip(DATA_REPO_OWNER, DATA_REPO_NAME, DATA_API_VERSION, - metadata=metadata, force_download=True) - - if tempzip != '': - try: - zipfile.ZipFile(tempzip).extractall(path=os.environ["SC2PATH"]) - sc2_logger.info(f"Download complete. Package installed.") - with open(get_metadata_file(), "w") as f: - f.write(metadata) - finally: - os.remove(tempzip) - else: - sc2_logger.warning("Download aborted/failed. Read the log for more information.") - return False - return True - - -class SC2JSONtoTextParser(JSONtoTextParser): - def __init__(self, ctx): - self.handlers = { - "ItemSend": self._handle_color, - "ItemCheat": self._handle_color, - "Hint": self._handle_color, - } - super().__init__(ctx) - - def _handle_color(self, node: JSONMessagePart): - codes = node["color"].split(";") - buffer = "".join(self.color_code(code) for code in codes if code in self.color_codes) - return buffer + self._handle_text(node) + '
' - - def color_code(self, code: str): - return '' - - -class SC2Context(CommonContext): - command_processor = StarcraftClientProcessor - game = "Starcraft 2 Wings of Liberty" - items_handling = 0b111 - difficulty = -1 - game_speed = -1 - all_in_choice = 0 - mission_order = 0 - player_color = 2 - mission_req_table: typing.Dict[str, MissionInfo] = {} - final_mission: int = 29 - announcements = queue.Queue() - sc2_run_task: typing.Optional[asyncio.Task] = None - missions_unlocked: bool = False # allow launching missions ignoring requirements - generic_upgrade_missions = 0 - generic_upgrade_research = 0 - generic_upgrade_items = 0 - current_tooltip = None - last_loc_list = None - difficulty_override = -1 - game_speed_override = -1 - mission_id_to_location_ids: typing.Dict[int, typing.List[int]] = {} - last_bot: typing.Optional[ArchipelagoBot] = None - - def __init__(self, *args, **kwargs): - super(SC2Context, self).__init__(*args, **kwargs) - self.raw_text_parser = SC2JSONtoTextParser(self) - - async def server_auth(self, password_requested: bool = False): - if password_requested and not self.password: - await super(SC2Context, self).server_auth(password_requested) - await self.get_username() - await self.send_connect() - if self.ui: - self.ui.first_check = True - - def on_package(self, cmd: str, args: dict): - if cmd in {"Connected"}: - self.difficulty = args["slot_data"]["game_difficulty"] - if "game_speed" in args["slot_data"]: - self.game_speed = args["slot_data"]["game_speed"] - else: - self.game_speed = 0 - self.all_in_choice = args["slot_data"]["all_in_map"] - slot_req_table = args["slot_data"]["mission_req"] - # Maintaining backwards compatibility with older slot data - self.mission_req_table = { - mission: MissionInfo( - **{field: value for field, value in mission_info.items() if field in MissionInfo._fields} - ) - for mission, mission_info in slot_req_table.items() - } - self.mission_order = args["slot_data"].get("mission_order", 0) - self.final_mission = args["slot_data"].get("final_mission", 29) - self.player_color = args["slot_data"].get("player_color", 2) - self.generic_upgrade_missions = args["slot_data"].get("generic_upgrade_missions", 0) - self.generic_upgrade_items = args["slot_data"].get("generic_upgrade_items", 0) - self.generic_upgrade_research = args["slot_data"].get("generic_upgrade_research", 0) - - self.build_location_to_mission_mapping() - - # Looks for the required maps and mods for SC2. Runs check_game_install_path. - maps_present = is_mod_installed_correctly() - if os.path.exists(get_metadata_file()): - with open(get_metadata_file(), "r") as f: - current_ver = f.read() - sc2_logger.debug(f"Current version: {current_ver}") - if is_mod_update_available(DATA_REPO_OWNER, DATA_REPO_NAME, DATA_API_VERSION, current_ver): - sc2_logger.info("NOTICE: Update for required files found. Run /download_data to install.") - elif maps_present: - sc2_logger.warning("NOTICE: Your map files may be outdated (version number not found). " - "Run /download_data to update them.") - - - def on_print_json(self, args: dict): - # goes to this world - if "receiving" in args and self.slot_concerns_self(args["receiving"]): - relevant = True - # found in this world - elif "item" in args and self.slot_concerns_self(args["item"].player): - relevant = True - # not related - else: - relevant = False - - if relevant: - self.announcements.put(self.raw_text_parser(copy.deepcopy(args["data"]))) - - super(SC2Context, self).on_print_json(args) - - def run_gui(self): - from kvui import GameManager, HoverBehavior, ServerToolTip - from kivy.app import App - from kivy.clock import Clock - from kivy.uix.tabbedpanel import TabbedPanelItem - from kivy.uix.gridlayout import GridLayout - from kivy.lang import Builder - from kivy.uix.label import Label - from kivy.uix.button import Button - from kivy.uix.floatlayout import FloatLayout - from kivy.properties import StringProperty - - class HoverableButton(HoverBehavior, Button): - pass - - class MissionButton(HoverableButton): - tooltip_text = StringProperty("Test") - ctx: SC2Context - - def __init__(self, *args, **kwargs): - super(HoverableButton, self).__init__(*args, **kwargs) - self.layout = FloatLayout() - self.popuplabel = ServerToolTip(text=self.text) - self.layout.add_widget(self.popuplabel) - - def on_enter(self): - self.popuplabel.text = self.tooltip_text - - if self.ctx.current_tooltip: - App.get_running_app().root.remove_widget(self.ctx.current_tooltip) - - if self.tooltip_text == "": - self.ctx.current_tooltip = None - else: - App.get_running_app().root.add_widget(self.layout) - self.ctx.current_tooltip = self.layout - - def on_leave(self): - self.ctx.ui.clear_tooltip() - - @property - def ctx(self) -> CommonContext: - return App.get_running_app().ctx - - class MissionLayout(GridLayout): - pass - - class MissionCategory(GridLayout): - pass - - class SC2Manager(GameManager): - logging_pairs = [ - ("Client", "Archipelago"), - ("Starcraft2", "Starcraft2"), - ] - base_title = "Archipelago Starcraft 2 Client" - - mission_panel = None - last_checked_locations = {} - mission_id_to_button = {} - launching: typing.Union[bool, int] = False # if int -> mission ID - refresh_from_launching = True - first_check = True - ctx: SC2Context - - def __init__(self, ctx): - super().__init__(ctx) - - def clear_tooltip(self): - if self.ctx.current_tooltip: - App.get_running_app().root.remove_widget(self.ctx.current_tooltip) - - self.ctx.current_tooltip = None - - def build(self): - container = super().build() - - panel = TabbedPanelItem(text="Starcraft 2 Launcher") - self.mission_panel = panel.content = MissionLayout() - - self.tabs.add_widget(panel) - - Clock.schedule_interval(self.build_mission_table, 0.5) - - return container - - def build_mission_table(self, dt): - if (not self.launching and (not self.last_checked_locations == self.ctx.checked_locations or - not self.refresh_from_launching)) or self.first_check: - self.refresh_from_launching = True - - self.mission_panel.clear_widgets() - if self.ctx.mission_req_table: - self.last_checked_locations = self.ctx.checked_locations.copy() - self.first_check = False - - self.mission_id_to_button = {} - categories = {} - available_missions, unfinished_missions = calc_unfinished_missions(self.ctx) - - # separate missions into categories - for mission in self.ctx.mission_req_table: - if not self.ctx.mission_req_table[mission].category in categories: - categories[self.ctx.mission_req_table[mission].category] = [] - - categories[self.ctx.mission_req_table[mission].category].append(mission) - - for category in categories: - category_panel = MissionCategory() - if category.startswith('_'): - category_display_name = '' - else: - category_display_name = category - category_panel.add_widget( - Label(text=category_display_name, size_hint_y=None, height=50, outline_width=1)) - - for mission in categories[category]: - text: str = mission - tooltip: str = "" - mission_id: int = self.ctx.mission_req_table[mission].id - # Map has uncollected locations - if mission in unfinished_missions: - text = f"[color=6495ED]{text}[/color]" - elif mission in available_missions: - text = f"[color=FFFFFF]{text}[/color]" - # Map requirements not met - else: - text = f"[color=a9a9a9]{text}[/color]" - tooltip = f"Requires: " - if self.ctx.mission_req_table[mission].required_world: - tooltip += ", ".join(list(self.ctx.mission_req_table)[req_mission - 1] for - req_mission in - self.ctx.mission_req_table[mission].required_world) - - if self.ctx.mission_req_table[mission].number: - tooltip += " and " - if self.ctx.mission_req_table[mission].number: - tooltip += f"{self.ctx.mission_req_table[mission].number} missions completed" - remaining_location_names: typing.List[str] = [ - self.ctx.location_names[loc] for loc in self.ctx.locations_for_mission(mission) - if loc in self.ctx.missing_locations] - - if mission_id == self.ctx.final_mission: - if mission in available_missions: - text = f"[color=FFBC95]{mission}[/color]" - else: - text = f"[color=D0C0BE]{mission}[/color]" - if tooltip: - tooltip += "\n" - tooltip += "Final Mission" - - if remaining_location_names: - if tooltip: - tooltip += "\n" - tooltip += f"Uncollected locations:\n" - tooltip += "\n".join(remaining_location_names) - - mission_button = MissionButton(text=text, size_hint_y=None, height=50) - mission_button.tooltip_text = tooltip - mission_button.bind(on_press=self.mission_callback) - self.mission_id_to_button[mission_id] = mission_button - category_panel.add_widget(mission_button) - - category_panel.add_widget(Label(text="")) - self.mission_panel.add_widget(category_panel) - - elif self.launching: - self.refresh_from_launching = False - - self.mission_panel.clear_widgets() - self.mission_panel.add_widget(Label(text="Launching Mission: " + - lookup_id_to_mission[self.launching])) - if self.ctx.ui: - self.ctx.ui.clear_tooltip() - - def mission_callback(self, button): - if not self.launching: - mission_id: int = next(k for k, v in self.mission_id_to_button.items() if v == button) - if self.ctx.play_mission(mission_id): - self.launching = mission_id - Clock.schedule_once(self.finish_launching, 10) - - def finish_launching(self, dt): - self.launching = False - - self.ui = SC2Manager(self) - self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") - import pkgutil - data = pkgutil.get_data(SC2WoLWorld.__module__, "Starcraft2.kv").decode() - Builder.load_string(data) - - async def shutdown(self): - await super(SC2Context, self).shutdown() - if self.last_bot: - self.last_bot.want_close = True - if self.sc2_run_task: - self.sc2_run_task.cancel() - - def play_mission(self, mission_id: int) -> bool: - if self.missions_unlocked or \ - is_mission_available(self, mission_id): - if self.sc2_run_task: - if not self.sc2_run_task.done(): - sc2_logger.warning("Starcraft 2 Client is still running!") - self.sc2_run_task.cancel() # doesn't actually close the game, just stops the python task - if self.slot is None: - sc2_logger.warning("Launching Mission without Archipelago authentication, " - "checks will not be registered to server.") - self.sc2_run_task = asyncio.create_task(starcraft_launch(self, mission_id), - name="Starcraft 2 Launch") - return True - else: - sc2_logger.info( - f"{lookup_id_to_mission[mission_id]} is not currently unlocked. " - f"Use /unfinished or /available to see what is available.") - return False - - def build_location_to_mission_mapping(self): - mission_id_to_location_ids: typing.Dict[int, typing.Set[int]] = { - mission_info.id: set() for mission_info in self.mission_req_table.values() - } - - for loc in self.server_locations: - mission_id, objective = divmod(loc - SC2WOL_LOC_ID_OFFSET, victory_modulo) - mission_id_to_location_ids[mission_id].add(objective) - self.mission_id_to_location_ids = {mission_id: sorted(objectives) for mission_id, objectives in - mission_id_to_location_ids.items()} - - def locations_for_mission(self, mission: str): - mission_id: int = self.mission_req_table[mission].id - objectives = self.mission_id_to_location_ids[self.mission_req_table[mission].id] - for objective in objectives: - yield SC2WOL_LOC_ID_OFFSET + mission_id * 100 + objective - - -async def main(): - multiprocessing.freeze_support() - parser = get_base_parser() - parser.add_argument('--name', default=None, help="Slot Name to connect as.") - args = parser.parse_args() - - ctx = SC2Context(args.connect, args.password) - ctx.auth = args.name - if ctx.server_task is None: - ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop") - - if gui_enabled: - ctx.run_gui() - ctx.run_cli() - - await ctx.exit_event.wait() - - await ctx.shutdown() - - -maps_table = [ - "ap_liberation_day", "ap_the_outlaws", "ap_zero_hour", - "ap_evacuation", "ap_outbreak", "ap_safe_haven", "ap_havens_fall", - "ap_smash_and_grab", "ap_the_dig", "ap_the_moebius_factor", "ap_supernova", "ap_maw_of_the_void", - "ap_devils_playground", "ap_welcome_to_the_jungle", "ap_breakout", "ap_ghost_of_a_chance", - "ap_the_great_train_robbery", "ap_cutthroat", "ap_engine_of_destruction", "ap_media_blitz", "ap_piercing_the_shroud", - "ap_whispers_of_doom", "ap_a_sinister_turn", "ap_echoes_of_the_future", "ap_in_utter_darkness", - "ap_gates_of_hell", "ap_belly_of_the_beast", "ap_shatter_the_sky", "ap_all_in" -] - -wol_default_categories = [ - "Mar Sara", "Mar Sara", "Mar Sara", "Colonist", "Colonist", "Colonist", "Colonist", - "Artifact", "Artifact", "Artifact", "Artifact", "Artifact", "Covert", "Covert", "Covert", "Covert", - "Rebellion", "Rebellion", "Rebellion", "Rebellion", "Rebellion", "Prophecy", "Prophecy", "Prophecy", "Prophecy", - "Char", "Char", "Char", "Char" -] -wol_default_category_names = [ - "Mar Sara", "Colonist", "Artifact", "Covert", "Rebellion", "Prophecy", "Char" -] - - -def calculate_items(ctx: SC2Context) -> typing.List[int]: - items = ctx.items_received - network_item: NetworkItem - accumulators: typing.List[int] = [0 for _ in type_flaggroups] - - for network_item in items: - name: str = lookup_id_to_name[network_item.item] - item_data: ItemData = get_full_item_list()[name] - - # exists exactly once - if item_data.quantity == 1: - accumulators[type_flaggroups[item_data.type]] |= 1 << item_data.number - - # exists multiple times - elif item_data.type == "Upgrade" or item_data.type == "Progressive Upgrade": - flaggroup = type_flaggroups[item_data.type] - - # Generic upgrades apply only to Weapon / Armor upgrades - if item_data.type != "Upgrade" or ctx.generic_upgrade_items == 0: - accumulators[flaggroup] += 1 << item_data.number - else: - for bundled_number in upgrade_numbers[item_data.number]: - accumulators[flaggroup] += 1 << bundled_number - - # sum - else: - accumulators[type_flaggroups[item_data.type]] += item_data.number - - # Upgrades from completed missions - if ctx.generic_upgrade_missions > 0: - upgrade_flaggroup = type_flaggroups["Upgrade"] - num_missions = ctx.generic_upgrade_missions * len(ctx.mission_req_table) - amounts = [ - num_missions // 100, - 2 * num_missions // 100, - 3 * num_missions // 100 - ] - upgrade_count = 0 - completed = len([id for id in ctx.mission_id_to_location_ids if SC2WOL_LOC_ID_OFFSET + victory_modulo * id in ctx.checked_locations]) - for amount in amounts: - if completed >= amount: - upgrade_count += 1 - # Equivalent to "Progressive Weapon/Armor Upgrade" item - for bundled_number in upgrade_numbers[5]: - accumulators[upgrade_flaggroup] += upgrade_count << bundled_number - - return accumulators - - -def calc_difficulty(difficulty): - if difficulty == 0: - return 'C' - elif difficulty == 1: - return 'N' - elif difficulty == 2: - return 'H' - elif difficulty == 3: - return 'B' - - return 'X' - - -async def starcraft_launch(ctx: SC2Context, mission_id: int): - sc2_logger.info(f"Launching {lookup_id_to_mission[mission_id]}. If game does not launch check log file for errors.") - - with DllDirectory(None): - run_game(bot.maps.get(maps_table[mission_id - 1]), [Bot(Race.Terran, ArchipelagoBot(ctx, mission_id), - name="Archipelago", fullscreen=True)], realtime=True) - - -class ArchipelagoBot(bot.bot_ai.BotAI): - game_running: bool = False - mission_completed: bool = False - boni: typing.List[bool] - setup_done: bool - ctx: SC2Context - mission_id: int - want_close: bool = False - can_read_game = False - last_received_update: int = 0 - - def __init__(self, ctx: SC2Context, mission_id): - self.setup_done = False - self.ctx = ctx - self.ctx.last_bot = self - self.mission_id = mission_id - self.boni = [False for _ in range(max_bonus)] - - super(ArchipelagoBot, self).__init__() - - async def on_step(self, iteration: int): - if self.want_close: - self.want_close = False - await self._client.leave() - return - game_state = 0 - if not self.setup_done: - self.setup_done = True - start_items = calculate_items(self.ctx) - if self.ctx.difficulty_override >= 0: - difficulty = calc_difficulty(self.ctx.difficulty_override) - else: - difficulty = calc_difficulty(self.ctx.difficulty) - if self.ctx.game_speed_override >= 0: - game_speed = self.ctx.game_speed_override - else: - game_speed = self.ctx.game_speed - await self.chat_send("?SetOptions {} {} {} {}".format( - difficulty, - self.ctx.generic_upgrade_research, - self.ctx.all_in_choice, - game_speed - )) - await self.chat_send("?GiveResources {} {} {}".format( - start_items[8], - start_items[9], - start_items[10] - )) - await self.chat_send("?GiveTerranTech {} {} {} {} {} {} {} {} {} {}".format( - start_items[0], start_items[1], start_items[2], start_items[3], start_items[4], - start_items[5], start_items[6], start_items[12], start_items[13], start_items[14])) - await self.chat_send("?GiveProtossTech {}".format(start_items[7])) - await self.chat_send("?SetColor rr " + str(self.ctx.player_color)) # TODO: Add faction color options - await self.chat_send("?LoadFinished") - self.last_received_update = len(self.ctx.items_received) - - else: - if not self.ctx.announcements.empty(): - message = self.ctx.announcements.get(timeout=1) - await self.chat_send("?SendMessage " + message) - self.ctx.announcements.task_done() - - # Archipelago reads the health - for unit in self.all_own_units(): - if unit.health_max == 38281: - game_state = int(38281 - unit.health) - self.can_read_game = True - - if iteration == 160 and not game_state & 1: - await self.chat_send("?SendMessage Warning: Archipelago unable to connect or has lost connection to " + - "Starcraft 2 (This is likely a map issue)") - - if self.last_received_update < len(self.ctx.items_received): - current_items = calculate_items(self.ctx) - await self.chat_send("?GiveTerranTech {} {} {} {} {} {} {} {} {} {}".format( - current_items[0], current_items[1], current_items[2], current_items[3], current_items[4], - current_items[5], current_items[6], current_items[12], current_items[13], current_items[14])) - await self.chat_send("?GiveProtossTech {}".format(current_items[7])) - self.last_received_update = len(self.ctx.items_received) - - if game_state & 1: - if not self.game_running: - print("Archipelago Connected") - self.game_running = True - - if self.can_read_game: - if game_state & (1 << 1) and not self.mission_completed: - if self.mission_id != self.ctx.final_mission: - print("Mission Completed") - await self.ctx.send_msgs( - [{"cmd": 'LocationChecks', - "locations": [SC2WOL_LOC_ID_OFFSET + victory_modulo * self.mission_id]}]) - self.mission_completed = True - else: - print("Game Complete") - await self.ctx.send_msgs([{"cmd": 'StatusUpdate', "status": ClientStatus.CLIENT_GOAL}]) - self.mission_completed = True - - for x, completed in enumerate(self.boni): - if not completed and game_state & (1 << (x + 2)): - await self.ctx.send_msgs( - [{"cmd": 'LocationChecks', - "locations": [SC2WOL_LOC_ID_OFFSET + victory_modulo * self.mission_id + x + 1]}]) - self.boni[x] = True - - else: - await self.chat_send("?SendMessage LostConnection - Lost connection to game.") - - -def request_unfinished_missions(ctx: SC2Context): - if ctx.mission_req_table: - message = "Unfinished Missions: " - unlocks = initialize_blank_mission_dict(ctx.mission_req_table) - unfinished_locations = initialize_blank_mission_dict(ctx.mission_req_table) - - _, unfinished_missions = calc_unfinished_missions(ctx, unlocks=unlocks) - - # Removing All-In from location pool - final_mission = lookup_id_to_mission[ctx.final_mission] - if final_mission in unfinished_missions.keys(): - message = f"Final Mission Available: {final_mission}[{ctx.final_mission}]\n" + message - if unfinished_missions[final_mission] == -1: - unfinished_missions.pop(final_mission) - - message += ", ".join(f"{mark_up_mission_name(ctx, mission, unlocks)}[{ctx.mission_req_table[mission].id}] " + - mark_up_objectives( - f"[{len(unfinished_missions[mission])}/" - f"{sum(1 for _ in ctx.locations_for_mission(mission))}]", - ctx, unfinished_locations, mission) - for mission in unfinished_missions) - - if ctx.ui: - ctx.ui.log_panels['All'].on_message_markup(message) - ctx.ui.log_panels['Starcraft2'].on_message_markup(message) - else: - sc2_logger.info(message) - else: - sc2_logger.warning("No mission table found, you are likely not connected to a server.") - - -def calc_unfinished_missions(ctx: SC2Context, unlocks=None): - unfinished_missions = [] - locations_completed = [] - - if not unlocks: - unlocks = initialize_blank_mission_dict(ctx.mission_req_table) - - available_missions = calc_available_missions(ctx, unlocks) - - for name in available_missions: - objectives = set(ctx.locations_for_mission(name)) - if objectives: - objectives_completed = ctx.checked_locations & objectives - if len(objectives_completed) < len(objectives): - unfinished_missions.append(name) - locations_completed.append(objectives_completed) - - else: # infer that this is the final mission as it has no objectives - unfinished_missions.append(name) - locations_completed.append(-1) - - return available_missions, dict(zip(unfinished_missions, locations_completed)) - - -def is_mission_available(ctx: SC2Context, mission_id_to_check): - unfinished_missions = calc_available_missions(ctx) - - return any(mission_id_to_check == ctx.mission_req_table[mission].id for mission in unfinished_missions) - - -def mark_up_mission_name(ctx: SC2Context, mission, unlock_table): - """Checks if the mission is required for game completion and adds '*' to the name to mark that.""" - - if ctx.mission_req_table[mission].completion_critical: - if ctx.ui: - message = "[color=AF99EF]" + mission + "[/color]" - else: - message = "*" + mission + "*" - else: - message = mission - - if ctx.ui: - unlocks = unlock_table[mission] - - if len(unlocks) > 0: - pre_message = f"[ref={list(ctx.mission_req_table).index(mission)}|Unlocks: " - pre_message += ", ".join(f"{unlock}({ctx.mission_req_table[unlock].id})" for unlock in unlocks) - pre_message += f"]" - message = pre_message + message + "[/ref]" - - return message - - -def mark_up_objectives(message, ctx, unfinished_locations, mission): - formatted_message = message - - if ctx.ui: - locations = unfinished_locations[mission] - - pre_message = f"[ref={list(ctx.mission_req_table).index(mission) + 30}|" - pre_message += "
".join(location for location in locations) - pre_message += f"]" - formatted_message = pre_message + message + "[/ref]" - - return formatted_message - - -def request_available_missions(ctx: SC2Context): - if ctx.mission_req_table: - message = "Available Missions: " - - # Initialize mission unlock table - unlocks = initialize_blank_mission_dict(ctx.mission_req_table) - - missions = calc_available_missions(ctx, unlocks) - message += \ - ", ".join(f"{mark_up_mission_name(ctx, mission, unlocks)}" - f"[{ctx.mission_req_table[mission].id}]" - for mission in missions) - - if ctx.ui: - ctx.ui.log_panels['All'].on_message_markup(message) - ctx.ui.log_panels['Starcraft2'].on_message_markup(message) - else: - sc2_logger.info(message) - else: - sc2_logger.warning("No mission table found, you are likely not connected to a server.") - - -def calc_available_missions(ctx: SC2Context, unlocks=None): - available_missions = [] - missions_complete = 0 - - # Get number of missions completed - for loc in ctx.checked_locations: - if loc % victory_modulo == 0: - missions_complete += 1 - - for name in ctx.mission_req_table: - # Go through the required missions for each mission and fill up unlock table used later for hover-over tooltips - if unlocks: - for unlock in ctx.mission_req_table[name].required_world: - unlocks[list(ctx.mission_req_table)[unlock - 1]].append(name) - - if mission_reqs_completed(ctx, name, missions_complete): - available_missions.append(name) - - return available_missions - - -def mission_reqs_completed(ctx: SC2Context, mission_name: str, missions_complete: int): - """Returns a bool signifying if the mission has all requirements complete and can be done - - Arguments: - ctx -- instance of SC2Context - locations_to_check -- the mission string name to check - missions_complete -- an int of how many missions have been completed - mission_path -- a list of missions that have already been checked -""" - if len(ctx.mission_req_table[mission_name].required_world) >= 1: - # A check for when the requirements are being or'd - or_success = False - - # Loop through required missions - for req_mission in ctx.mission_req_table[mission_name].required_world: - req_success = True - - # Check if required mission has been completed - if not (ctx.mission_req_table[list(ctx.mission_req_table)[req_mission - 1]].id * - victory_modulo + SC2WOL_LOC_ID_OFFSET) in ctx.checked_locations: - if not ctx.mission_req_table[mission_name].or_requirements: - return False - else: - req_success = False - - # Grid-specific logic (to avoid long path checks and infinite recursion) - if ctx.mission_order in (3, 4): - if req_success: - return True - else: - if req_mission is ctx.mission_req_table[mission_name].required_world[-1]: - return False - else: - continue - - # Recursively check required mission to see if it's requirements are met, in case !collect has been done - # Skipping recursive check on Grid settings to speed up checks and avoid infinite recursion - if not mission_reqs_completed(ctx, list(ctx.mission_req_table)[req_mission - 1], missions_complete): - if not ctx.mission_req_table[mission_name].or_requirements: - return False - else: - req_success = False - - # If requirement check succeeded mark or as satisfied - if ctx.mission_req_table[mission_name].or_requirements and req_success: - or_success = True - - if ctx.mission_req_table[mission_name].or_requirements: - # Return false if or requirements not met - if not or_success: - return False - - # Check number of missions - if missions_complete >= ctx.mission_req_table[mission_name].number: - return True - else: - return False - else: - return True - - -def initialize_blank_mission_dict(location_table): - unlocks = {} - - for mission in list(location_table): - unlocks[mission] = [] - - return unlocks - - -def check_game_install_path() -> bool: - # First thing: go to the default location for ExecuteInfo. - # An exception for Windows is included because it's very difficult to find ~\Documents if the user moved it. - if is_windows: - # The next five lines of utterly inscrutable code are brought to you by copy-paste from Stack Overflow. - # https://stackoverflow.com/questions/6227590/finding-the-users-my-documents-path/30924555# - import ctypes.wintypes - CSIDL_PERSONAL = 5 # My Documents - SHGFP_TYPE_CURRENT = 0 # Get current, not default value - - buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) - ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf) - documentspath = buf.value - einfo = str(documentspath / Path("StarCraft II\\ExecuteInfo.txt")) - else: - einfo = str(bot.paths.get_home() / Path(bot.paths.USERPATH[bot.paths.PF])) - - # Check if the file exists. - if os.path.isfile(einfo): - - # Open the file and read it, picking out the latest executable's path. - with open(einfo) as f: - content = f.read() - if content: - try: - base = re.search(r" = (.*)Versions", content).group(1) - except AttributeError: - sc2_logger.warning(f"Found {einfo}, but it was empty. Run SC2 through the Blizzard launcher, then " - f"try again.") - return False - if os.path.exists(base): - executable = bot.paths.latest_executeble(Path(base).expanduser() / "Versions") - - # Finally, check the path for an actual executable. - # If we find one, great. Set up the SC2PATH. - if os.path.isfile(executable): - sc2_logger.info(f"Found an SC2 install at {base}!") - sc2_logger.debug(f"Latest executable at {executable}.") - os.environ["SC2PATH"] = base - sc2_logger.debug(f"SC2PATH set to {base}.") - return True - else: - sc2_logger.warning(f"We may have found an SC2 install at {base}, but couldn't find {executable}.") - else: - sc2_logger.warning(f"{einfo} pointed to {base}, but we could not find an SC2 install there.") - else: - sc2_logger.warning(f"Couldn't find {einfo}. Run SC2 through the Blizzard launcher, then try again. " - f"If that fails, please run /set_path with your SC2 install directory.") - return False - - -def is_mod_installed_correctly() -> bool: - """Searches for all required files.""" - if "SC2PATH" not in os.environ: - check_game_install_path() - - mapdir = os.environ['SC2PATH'] / Path('Maps/ArchipelagoCampaign') - mods = ["ArchipelagoCore", "ArchipelagoPlayer", "ArchipelagoPlayerWoL", "ArchipelagoTriggers"] - modfiles = [os.environ["SC2PATH"] / Path("Mods/" + mod + ".SC2Mod") for mod in mods] - wol_required_maps = ["WoL" + os.sep + map_name + ".SC2Map" for map_name in maps_table] - needs_files = False - - # Check for maps. - missing_maps = [] - for mapfile in wol_required_maps: - if not os.path.isfile(mapdir / mapfile): - missing_maps.append(mapfile) - if len(missing_maps) >= 19: - sc2_logger.warning(f"All map files missing from {mapdir}.") - needs_files = True - elif len(missing_maps) > 0: - for map in missing_maps: - sc2_logger.debug(f"Missing {map} from {mapdir}.") - sc2_logger.warning(f"Missing {len(missing_maps)} map files.") - needs_files = True - else: # Must be no maps missing - sc2_logger.info(f"All maps found in {mapdir}.") - - # Check for mods. - for modfile in modfiles: - if os.path.isfile(modfile) or os.path.isdir(modfile): - sc2_logger.info(f"Archipelago mod found at {modfile}.") - else: - sc2_logger.warning(f"Archipelago mod could not be found at {modfile}.") - needs_files = True - - # Final verdict. - if needs_files: - sc2_logger.warning(f"Required files are missing. Run /download_data to acquire them.") - return False - else: - sc2_logger.debug(f"All map/mod files are properly installed.") - return True - - -class DllDirectory: - # Credit to Black Sliver for this code. - # More info: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectoryw - _old: typing.Optional[str] = None - _new: typing.Optional[str] = None - - def __init__(self, new: typing.Optional[str]): - self._new = new - - def __enter__(self): - old = self.get() - if self.set(self._new): - self._old = old - - def __exit__(self, *args): - if self._old is not None: - self.set(self._old) - - @staticmethod - def get() -> typing.Optional[str]: - if sys.platform == "win32": - n = ctypes.windll.kernel32.GetDllDirectoryW(0, None) - buf = ctypes.create_unicode_buffer(n) - ctypes.windll.kernel32.GetDllDirectoryW(n, buf) - return buf.value - # NOTE: other OS may support os.environ["LD_LIBRARY_PATH"], but this fix is windows-specific - return None - - @staticmethod - def set(s: typing.Optional[str]) -> bool: - if sys.platform == "win32": - return ctypes.windll.kernel32.SetDllDirectoryW(s) != 0 - # NOTE: other OS may support os.environ["LD_LIBRARY_PATH"], but this fix is windows-specific - return False - - -def download_latest_release_zip(owner: str, repo: str, api_version: str, metadata: str = None, force_download=False) -> (str, str): - """Downloads the latest release of a GitHub repo to the current directory as a .zip file.""" - import requests - - headers = {"Accept": 'application/vnd.github.v3+json'} - url = f"https://api.github.com/repos/{owner}/{repo}/releases/tags/{api_version}" - - r1 = requests.get(url, headers=headers) - if r1.status_code == 200: - latest_metadata = r1.json() - cleanup_downloaded_metadata(latest_metadata) - latest_metadata = str(latest_metadata) - # sc2_logger.info(f"Latest version: {latest_metadata}.") - else: - sc2_logger.warning(f"Status code: {r1.status_code}") - sc2_logger.warning(f"Failed to reach GitHub. Could not find download link.") - sc2_logger.warning(f"text: {r1.text}") - return "", metadata - - if (force_download is False) and (metadata == latest_metadata): - sc2_logger.info("Latest version already installed.") - return "", metadata - - sc2_logger.info(f"Attempting to download latest version of API version {api_version} of {repo}.") - download_url = r1.json()["assets"][0]["browser_download_url"] - - r2 = requests.get(download_url, headers=headers) - if r2.status_code == 200 and zipfile.is_zipfile(io.BytesIO(r2.content)): - tempdir = tempfile.gettempdir() - file = tempdir + os.sep + f"{repo}.zip" - with open(file, "wb") as fh: - fh.write(r2.content) - sc2_logger.info(f"Successfully downloaded {repo}.zip.") - return file, latest_metadata - else: - sc2_logger.warning(f"Status code: {r2.status_code}") - sc2_logger.warning("Download failed.") - sc2_logger.warning(f"text: {r2.text}") - return "", metadata - - -def cleanup_downloaded_metadata(medatada_json): - for asset in medatada_json['assets']: - del asset['download_count'] - - -def is_mod_update_available(owner: str, repo: str, api_version: str, metadata: str) -> bool: - import requests - - headers = {"Accept": 'application/vnd.github.v3+json'} - url = f"https://api.github.com/repos/{owner}/{repo}/releases/tags/{api_version}" - - r1 = requests.get(url, headers=headers) - if r1.status_code == 200: - latest_metadata = r1.json() - cleanup_downloaded_metadata(latest_metadata) - latest_metadata = str(latest_metadata) - if metadata != latest_metadata: - return True - else: - return False - - else: - sc2_logger.warning(f"Failed to reach GitHub while checking for updates.") - sc2_logger.warning(f"Status code: {r1.status_code}") - sc2_logger.warning(f"text: {r1.text}") - return False - - -def launch(): - colorama.init() - asyncio.run(main()) - colorama.deinit() diff --git a/worlds/sc2wol/Items.py b/worlds/sc2wol/Items.py deleted file mode 100644 index 971a75375f..0000000000 --- a/worlds/sc2wol/Items.py +++ /dev/null @@ -1,421 +0,0 @@ -from BaseClasses import Item, ItemClassification, MultiWorld -import typing - -from .Options import get_option_value -from .MissionTables import vanilla_mission_req_table - - -class ItemData(typing.NamedTuple): - code: typing.Optional[int] - type: typing.Optional[str] - number: typing.Optional[int] - classification: ItemClassification = ItemClassification.useful - quantity: int = 1 - parent_item: str = None - origin: typing.Set[str] = {"wol"} - - -class StarcraftWoLItem(Item): - game: str = "Starcraft 2 Wings of Liberty" - - -def get_full_item_list(): - return item_table - - -SC2WOL_ITEM_ID_OFFSET = 1000 - -item_table = { - "Marine": ItemData(0 + SC2WOL_ITEM_ID_OFFSET, "Unit", 0, classification=ItemClassification.progression), - "Medic": ItemData(1 + SC2WOL_ITEM_ID_OFFSET, "Unit", 1, classification=ItemClassification.progression), - "Firebat": ItemData(2 + SC2WOL_ITEM_ID_OFFSET, "Unit", 2, classification=ItemClassification.progression), - "Marauder": ItemData(3 + SC2WOL_ITEM_ID_OFFSET, "Unit", 3, classification=ItemClassification.progression), - "Reaper": ItemData(4 + SC2WOL_ITEM_ID_OFFSET, "Unit", 4, classification=ItemClassification.progression), - "Hellion": ItemData(5 + SC2WOL_ITEM_ID_OFFSET, "Unit", 5, classification=ItemClassification.progression), - "Vulture": ItemData(6 + SC2WOL_ITEM_ID_OFFSET, "Unit", 6, classification=ItemClassification.progression), - "Goliath": ItemData(7 + SC2WOL_ITEM_ID_OFFSET, "Unit", 7, classification=ItemClassification.progression), - "Diamondback": ItemData(8 + SC2WOL_ITEM_ID_OFFSET, "Unit", 8, classification=ItemClassification.progression), - "Siege Tank": ItemData(9 + SC2WOL_ITEM_ID_OFFSET, "Unit", 9, classification=ItemClassification.progression), - "Medivac": ItemData(10 + SC2WOL_ITEM_ID_OFFSET, "Unit", 10, classification=ItemClassification.progression), - "Wraith": ItemData(11 + SC2WOL_ITEM_ID_OFFSET, "Unit", 11, classification=ItemClassification.progression), - "Viking": ItemData(12 + SC2WOL_ITEM_ID_OFFSET, "Unit", 12, classification=ItemClassification.progression), - "Banshee": ItemData(13 + SC2WOL_ITEM_ID_OFFSET, "Unit", 13, classification=ItemClassification.progression), - "Battlecruiser": ItemData(14 + SC2WOL_ITEM_ID_OFFSET, "Unit", 14, classification=ItemClassification.progression), - "Ghost": ItemData(15 + SC2WOL_ITEM_ID_OFFSET, "Unit", 15, classification=ItemClassification.progression), - "Spectre": ItemData(16 + SC2WOL_ITEM_ID_OFFSET, "Unit", 16, classification=ItemClassification.progression), - "Thor": ItemData(17 + SC2WOL_ITEM_ID_OFFSET, "Unit", 17, classification=ItemClassification.progression), - # EE units - "Liberator": ItemData(18 + SC2WOL_ITEM_ID_OFFSET, "Unit", 18, classification=ItemClassification.progression, origin={"nco", "ext"}), - "Valkyrie": ItemData(19 + SC2WOL_ITEM_ID_OFFSET, "Unit", 19, classification=ItemClassification.progression, origin={"bw"}), - "Widow Mine": ItemData(20 + SC2WOL_ITEM_ID_OFFSET, "Unit", 20, classification=ItemClassification.progression, origin={"ext"}), - "Cyclone": ItemData(21 + SC2WOL_ITEM_ID_OFFSET, "Unit", 21, classification=ItemClassification.progression, origin={"ext"}), - - # Some other items are moved to Upgrade group because of the way how the bot message is parsed - "Progressive Infantry Weapon": ItemData(100 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 0, quantity=3), - "Progressive Infantry Armor": ItemData(102 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 2, quantity=3), - "Progressive Vehicle Weapon": ItemData(103 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 4, quantity=3), - "Progressive Vehicle Armor": ItemData(104 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 6, quantity=3), - "Progressive Ship Weapon": ItemData(105 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 8, quantity=3), - "Progressive Ship Armor": ItemData(106 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 10, quantity=3), - # Upgrade bundle 'number' values are used as indices to get affected 'number's - "Progressive Weapon Upgrade": ItemData(107 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 0, quantity=3), - "Progressive Armor Upgrade": ItemData(108 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 1, quantity=3), - "Progressive Infantry Upgrade": ItemData(109 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 2, quantity=3), - "Progressive Vehicle Upgrade": ItemData(110 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 3, quantity=3), - "Progressive Ship Upgrade": ItemData(111 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 4, quantity=3), - "Progressive Weapon/Armor Upgrade": ItemData(112 + SC2WOL_ITEM_ID_OFFSET, "Upgrade", 5, quantity=3), - - "Projectile Accelerator (Bunker)": ItemData(200 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 0, parent_item="Bunker"), - "Neosteel Bunker (Bunker)": ItemData(201 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 1, parent_item="Bunker"), - "Titanium Housing (Missile Turret)": ItemData(202 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 2, classification=ItemClassification.filler, parent_item="Missile Turret"), - "Hellstorm Batteries (Missile Turret)": ItemData(203 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 3, parent_item="Missile Turret"), - "Advanced Construction (SCV)": ItemData(204 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 4), - "Dual-Fusion Welders (SCV)": ItemData(205 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 5), - "Fire-Suppression System (Building)": ItemData(206 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 6), - "Orbital Command (Building)": ItemData(207 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 7), - "Progressive Stimpack (Marine)": ItemData(208 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 0, parent_item="Marine", quantity=2), - "Combat Shield (Marine)": ItemData(209 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 9, classification=ItemClassification.progression, parent_item="Marine"), - "Advanced Medic Facilities (Medic)": ItemData(210 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 10, classification=ItemClassification.filler, parent_item="Medic"), - "Stabilizer Medpacks (Medic)": ItemData(211 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 11, classification=ItemClassification.progression, parent_item="Medic"), - "Incinerator Gauntlets (Firebat)": ItemData(212 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 12, classification=ItemClassification.filler, parent_item="Firebat"), - "Juggernaut Plating (Firebat)": ItemData(213 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 13, parent_item="Firebat"), - "Concussive Shells (Marauder)": ItemData(214 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 14, parent_item="Marauder"), - "Kinetic Foam (Marauder)": ItemData(215 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 15, parent_item="Marauder"), - "U-238 Rounds (Reaper)": ItemData(216 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 16, parent_item="Reaper"), - "G-4 Clusterbomb (Reaper)": ItemData(217 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 17, classification=ItemClassification.progression, parent_item="Reaper"), - # Items from EE - "Mag-Field Accelerators (Cyclone)": ItemData(218 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 18, parent_item="Cyclone", origin={"ext"}), - "Mag-Field Launchers (Cyclone)": ItemData(219 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 19, parent_item="Cyclone", origin={"ext"}), - # Items from new mod - "Laser Targeting System (Marine)": ItemData(220 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 8, classification=ItemClassification.filler, parent_item="Marine", origin={"nco"}), # Freed slot from Stimpack - "Magrail Munitions (Marine)": ItemData(221 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 20, parent_item="Marine", origin={"nco"}), - "Optimized Logistics (Marine)": ItemData(222 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 21, classification=ItemClassification.filler, parent_item="Marine", origin={"nco"}), - "Restoration (Medic)": ItemData(223 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 22, classification=ItemClassification.filler, parent_item="Medic", origin={"bw"}), - "Optical Flare (Medic)": ItemData(224 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 23, classification=ItemClassification.filler, parent_item="Medic", origin={"bw"}), - "Optimized Logistics (Medic)": ItemData(225 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 24, classification=ItemClassification.filler, parent_item="Medic", origin={"bw"}), - "Progressive Stimpack (Firebat)": ItemData(226 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 6, parent_item="Firebat", quantity=2, origin={"bw"}), - "Optimized Logistics (Firebat)": ItemData(227 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 25, parent_item="Firebat", origin={"bw"}), - "Progressive Stimpack (Marauder)": ItemData(228 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 8, parent_item="Marauder", quantity=2, origin={"nco"}), - "Laser Targeting System (Marauder)": ItemData(229 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 26, classification=ItemClassification.filler, parent_item="Marauder", origin={"nco"}), - "Magrail Munitions (Marauder)": ItemData(230 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 27, classification=ItemClassification.filler, parent_item="Marauder", origin={"nco"}), - "Internal Tech Module (Marauder)": ItemData(231 + SC2WOL_ITEM_ID_OFFSET, "Armory 1", 28, classification=ItemClassification.filler, parent_item="Marauder", origin={"nco"}), - - # Items from new mod - "Progressive Stimpack (Reaper)": ItemData(250 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 10, parent_item="Reaper", quantity=2, origin={"nco"}), - "Laser Targeting System (Reaper)": ItemData(251 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 0, classification=ItemClassification.filler, parent_item="Reaper", origin={"nco"}), - "Advanced Cloaking Field (Reaper)": ItemData(252 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 1, parent_item="Reaper", origin={"nco"}), - "Spider Mines (Reaper)": ItemData(253 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 2, classification=ItemClassification.filler, parent_item="Reaper", origin={"nco"}), - "Combat Drugs (Reaper)": ItemData(254 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 3, classification=ItemClassification.filler, parent_item="Reaper", origin={"ext"}), - "Hellbat Aspect (Hellion)": ItemData(255 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 4, parent_item="Hellion", origin={"nco"}), - "Smart Servos (Hellion)": ItemData(256 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 5, parent_item="Hellion", origin={"nco"}), - "Optimized Logistics (Hellion)": ItemData(257 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 6, classification=ItemClassification.filler, parent_item="Hellion", origin={"nco"}), - "Jump Jets (Hellion)": ItemData(258 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 7, classification=ItemClassification.filler, parent_item="Hellion", origin={"nco"}), - "Progressive Stimpack (Hellion)": ItemData(259 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 12, parent_item="Hellion", quantity=2, origin={"nco"}), - "Ion Thrusters (Vulture)": ItemData(260 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 8, classification=ItemClassification.filler, parent_item="Vulture", origin={"bw"}), - "Auto Launchers (Vulture)": ItemData(261 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 9, parent_item="Vulture", origin={"bw"}), - "High Explosive Munition (Spider Mine)": ItemData(262 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 10, origin={"bw"}), - "Jump Jets (Goliath)": ItemData(263 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 11, classification=ItemClassification.filler, parent_item="Goliath", origin={"nco"}), - "Optimized Logistics (Goliath)": ItemData(264 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 12, classification=ItemClassification.filler, parent_item="Goliath", origin={"nco"}), - "Hyperfluxor (Diamondback)": ItemData(265 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 13, parent_item="Diamondback", origin={"ext"}), - "Burst Capacitors (Diamondback)": ItemData(266 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 14, classification=ItemClassification.filler, parent_item="Diamondback", origin={"ext"}), - "Optimized Logistics (Diamondback)": ItemData(267 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 15, parent_item="Diamondback", origin={"ext"}), - "Jump Jets (Siege Tank)": ItemData(268 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 16, parent_item="Siege Tank", origin={"nco"}), - "Spider Mines (Siege Tank)": ItemData(269 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 17, classification=ItemClassification.filler, parent_item="Siege Tank", origin={"nco"}), - "Smart Servos (Siege Tank)": ItemData(270 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 18, classification=ItemClassification.filler, parent_item="Siege Tank", origin={"nco"}), - "Graduating Range (Siege Tank)": ItemData(271 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 19, classification=ItemClassification.progression, parent_item="Siege Tank", origin={"ext"}), - "Laser Targeting System (Siege Tank)": ItemData(272 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 20, parent_item="Siege Tank", origin={"nco"}), - "Advanced Siege Tech (Siege Tank)": ItemData(273 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 21, parent_item="Siege Tank", origin={"ext"}), - "Internal Tech Module (Siege Tank)": ItemData(274 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 22, classification=ItemClassification.filler, parent_item="Siege Tank", origin={"nco"}), - "Optimized Logistics (Predator)": ItemData(275 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 23, classification=ItemClassification.filler, parent_item="Predator", origin={"ext"}), - "Expanded Hull (Medivac)": ItemData(276 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 24, classification=ItemClassification.filler, parent_item="Medivac", origin={"ext"}), - "Afterburners (Medivac)": ItemData(277 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 25, classification=ItemClassification.filler, parent_item="Medivac", origin={"ext"}), - "Advanced Laser Technology (Wraith)": ItemData(278 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 26, classification=ItemClassification.progression, parent_item="Wraith", origin={"ext"}), - "Smart Servos (Viking)": ItemData(279 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 27, parent_item="Viking", origin={"ext"}), - "Magrail Munitions (Viking)": ItemData(280 + SC2WOL_ITEM_ID_OFFSET, "Armory 3", 28, parent_item="Viking", origin={"ext"}), - - "Twin-Linked Flamethrower (Hellion)": ItemData(300 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 0, classification=ItemClassification.filler, parent_item="Hellion"), - "Thermite Filaments (Hellion)": ItemData(301 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 1, parent_item="Hellion"), - "Cerberus Mine (Spider Mine)": ItemData(302 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 2, classification=ItemClassification.filler), - "Replenishable Magazine (Vulture)": ItemData(303 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 3, classification=ItemClassification.filler, parent_item="Vulture"), - "Multi-Lock Weapons System (Goliath)": ItemData(304 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 4, parent_item="Goliath"), - "Ares-Class Targeting System (Goliath)": ItemData(305 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 5, parent_item="Goliath"), - "Tri-Lithium Power Cell (Diamondback)": ItemData(306 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 6, classification=ItemClassification.filler, parent_item="Diamondback"), - "Shaped Hull (Diamondback)": ItemData(307 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 7, classification=ItemClassification.filler, parent_item="Diamondback"), - "Maelstrom Rounds (Siege Tank)": ItemData(308 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 8, classification=ItemClassification.progression, parent_item="Siege Tank"), - "Shaped Blast (Siege Tank)": ItemData(309 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 9, parent_item="Siege Tank"), - "Rapid Deployment Tube (Medivac)": ItemData(310 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 10, classification=ItemClassification.filler, parent_item="Medivac"), - "Advanced Healing AI (Medivac)": ItemData(311 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 11, classification=ItemClassification.filler, parent_item="Medivac"), - "Tomahawk Power Cells (Wraith)": ItemData(312 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 12, classification=ItemClassification.filler, parent_item="Wraith"), - "Displacement Field (Wraith)": ItemData(313 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 13, classification=ItemClassification.filler, parent_item="Wraith"), - "Ripwave Missiles (Viking)": ItemData(314 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 14, parent_item="Viking"), - "Phobos-Class Weapons System (Viking)": ItemData(315 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 15, parent_item="Viking"), - "Progressive Cross-Spectrum Dampeners (Banshee)": ItemData(316 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 2, classification=ItemClassification.filler, parent_item="Banshee", quantity=2), - "Shockwave Missile Battery (Banshee)": ItemData(317 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 17, parent_item="Banshee"), - "Missile Pods (Battlecruiser)": ItemData(318 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 18, classification=ItemClassification.filler, parent_item="Battlecruiser"), - "Defensive Matrix (Battlecruiser)": ItemData(319 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 19, classification=ItemClassification.filler, parent_item="Battlecruiser"), - "Ocular Implants (Ghost)": ItemData(320 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 20, parent_item="Ghost"), - "Crius Suit (Ghost)": ItemData(321 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 21, parent_item="Ghost"), - "Psionic Lash (Spectre)": ItemData(322 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 22, classification=ItemClassification.progression, parent_item="Spectre"), - "Nyx-Class Cloaking Module (Spectre)": ItemData(323 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 23, parent_item="Spectre"), - "330mm Barrage Cannon (Thor)": ItemData(324 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 24, classification=ItemClassification.filler, parent_item="Thor"), - "Immortality Protocol (Thor)": ItemData(325 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 25, classification=ItemClassification.filler, parent_item="Thor"), - # Items from EE - "Advanced Ballistics (Liberator)": ItemData(326 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 26, parent_item="Liberator", origin={"ext"}), - "Raid Artillery (Liberator)": ItemData(327 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 27, classification=ItemClassification.progression, parent_item="Liberator", origin={"nco"}), - "Drilling Claws (Widow Mine)": ItemData(328 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 28, classification=ItemClassification.filler, parent_item="Widow Mine", origin={"ext"}), - "Concealment (Widow Mine)": ItemData(329 + SC2WOL_ITEM_ID_OFFSET, "Armory 2", 29, classification=ItemClassification.progression, parent_item="Widow Mine", origin={"ext"}), - - #Items from new mod - "Hyperflight Rotors (Banshee)": ItemData(350 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 0, classification=ItemClassification.filler, parent_item="Banshee", origin={"ext"}), - "Laser Targeting System (Banshee)": ItemData(351 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 1, classification=ItemClassification.filler, parent_item="Banshee", origin={"nco"}), - "Internal Tech Module (Banshee)": ItemData(352 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 2, classification=ItemClassification.filler, parent_item="Banshee", origin={"nco"}), - "Tactical Jump (Battlecruiser)": ItemData(353 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 3, parent_item="Battlecruiser", origin={"nco", "ext"}), - "Cloak (Battlecruiser)": ItemData(354 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 4, parent_item="Battlecruiser", origin={"nco"}), - "ATX Laser Battery (Battlecruiser)": ItemData(355 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 5, classification=ItemClassification.progression, parent_item="Battlecruiser", origin={"nco"}), - "Optimized Logistics (Battlecruiser)": ItemData(356 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 6, classification=ItemClassification.filler, parent_item="Battlecruiser", origin={"ext"}), - "Internal Tech Module (Battlecruiser)": ItemData(357 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 7, classification=ItemClassification.filler, parent_item="Battlecruiser", origin={"nco"}), - "EMP Rounds (Ghost)": ItemData(358 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 8, parent_item="Ghost", origin={"ext"}), - "Lockdown (Ghost)": ItemData(359 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 9, parent_item="Ghost", origin={"bw"}), - "Impaler Rounds (Spectre)": ItemData(360 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 10, parent_item="Spectre", origin={"ext"}), - "Progressive High Impact Payload (Thor)": ItemData(361 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 14, parent_item="Thor", quantity=2, origin={"ext"}), # L2 is Smart Servos - "Bio Mechanical Repair Drone (Raven)": ItemData(363 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 13, parent_item="Raven", origin={"nco"}), - "Spider Mines (Raven)": ItemData(364 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 14, parent_item="Raven", origin={"nco"}), - "Railgun Turret (Raven)": ItemData(365 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 15, parent_item="Raven", origin={"nco"}), - "Hunter-Seeker Weapon (Raven)": ItemData(366 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 16, parent_item="Raven", origin={"nco"}), - "Interference Matrix (Raven)": ItemData(367 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 17, parent_item="Raven", origin={"ext"}), - "Anti-Armor Missile (Raven)": ItemData(368 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 18, classification=ItemClassification.filler, parent_item="Raven", origin={"ext"}), - "Internal Tech Module (Raven)": ItemData(369 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 19, classification=ItemClassification.filler, parent_item="Raven", origin={"nco"}), - "EMP Shockwave (Science Vessel)": ItemData(370 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 20, parent_item="Science Vessel", origin={"bw"}), - "Defensive Matrix (Science Vessel)": ItemData(371 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 21, parent_item="Science Vessel", origin={"bw"}), - "Targeting Optics (Cyclone)": ItemData(372 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 22, parent_item="Cyclone", origin={"ext"}), - "Rapid Fire Launchers (Cyclone)": ItemData(373 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 23, parent_item="Cyclone", origin={"ext"}), - "Cloak (Liberator)": ItemData(374 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 24, classification=ItemClassification.filler, parent_item="Liberator", origin={"nco"}), - "Laser Targeting System (Liberator)": ItemData(375 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 25, classification=ItemClassification.filler, parent_item="Liberator", origin={"ext"}), - "Optimized Logistics (Liberator)": ItemData(376 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 26, classification=ItemClassification.filler, parent_item="Liberator", origin={"nco"}), - "Black Market Launchers (Widow Mine)": ItemData(377 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 27, classification=ItemClassification.filler, parent_item="Widow Mine", origin={"ext"}), - "Executioner Missiles (Widow Mine)": ItemData(378 + SC2WOL_ITEM_ID_OFFSET, "Armory 4", 28, parent_item="Widow Mine", origin={"ext"}), - - # Just lazy to create a new group for one unit - "Enhanced Cluster Launchers (Valkyrie)": ItemData(379 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 17, parent_item="Valkyrie", origin={"ext"}), - "Shaped Hull (Valkyrie)": ItemData(380 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 20, classification=ItemClassification.filler, parent_item="Valkyrie", origin={"ext"}), - "Burst Lasers (Valkyrie)": ItemData(381 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 21, parent_item="Valkyrie", origin={"ext"}), - "Afterburners (Valkyrie)": ItemData(382 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 22, classification=ItemClassification.filler, parent_item="Valkyrie", origin={"ext"}), - - "Bunker": ItemData(400 + SC2WOL_ITEM_ID_OFFSET, "Building", 0, classification=ItemClassification.progression), - "Missile Turret": ItemData(401 + SC2WOL_ITEM_ID_OFFSET, "Building", 1, classification=ItemClassification.progression), - "Sensor Tower": ItemData(402 + SC2WOL_ITEM_ID_OFFSET, "Building", 2), - - "War Pigs": ItemData(500 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 0, classification=ItemClassification.progression), - "Devil Dogs": ItemData(501 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 1, classification=ItemClassification.filler), - "Hammer Securities": ItemData(502 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 2), - "Spartan Company": ItemData(503 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 3, classification=ItemClassification.progression), - "Siege Breakers": ItemData(504 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 4), - "Hel's Angel": ItemData(505 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 5, classification=ItemClassification.progression), - "Dusk Wings": ItemData(506 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 6), - "Jackson's Revenge": ItemData(507 + SC2WOL_ITEM_ID_OFFSET, "Mercenary", 7), - - "Ultra-Capacitors": ItemData(600 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 0), - "Vanadium Plating": ItemData(601 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 1), - "Orbital Depots": ItemData(602 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 2), - "Micro-Filtering": ItemData(603 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 3), - "Automated Refinery": ItemData(604 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 4), - "Command Center Reactor": ItemData(605 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 5), - "Raven": ItemData(606 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 6), - "Science Vessel": ItemData(607 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 7, classification=ItemClassification.progression), - "Tech Reactor": ItemData(608 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 8), - "Orbital Strike": ItemData(609 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 9), - "Shrike Turret (Bunker)": ItemData(610 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 10, parent_item="Bunker"), - "Fortified Bunker (Bunker)": ItemData(611 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 11, parent_item="Bunker"), - "Planetary Fortress": ItemData(612 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 12, classification=ItemClassification.progression), - "Perdition Turret": ItemData(613 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 13, classification=ItemClassification.progression), - "Predator": ItemData(614 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 14, classification=ItemClassification.filler), - "Hercules": ItemData(615 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 15, classification=ItemClassification.progression), - "Cellular Reactor": ItemData(616 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 16), - "Progressive Regenerative Bio-Steel": ItemData(617 + SC2WOL_ITEM_ID_OFFSET, "Progressive Upgrade", 4, quantity=2), - "Hive Mind Emulator": ItemData(618 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 18, ItemClassification.progression), - "Psi Disrupter": ItemData(619 + SC2WOL_ITEM_ID_OFFSET, "Laboratory", 19, classification=ItemClassification.progression), - - "Zealot": ItemData(700 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 0, classification=ItemClassification.progression), - "Stalker": ItemData(701 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 1, classification=ItemClassification.progression), - "High Templar": ItemData(702 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 2, classification=ItemClassification.progression), - "Dark Templar": ItemData(703 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 3, classification=ItemClassification.progression), - "Immortal": ItemData(704 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 4, classification=ItemClassification.progression), - "Colossus": ItemData(705 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 5), - "Phoenix": ItemData(706 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 6, classification=ItemClassification.filler), - "Void Ray": ItemData(707 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 7, classification=ItemClassification.progression), - "Carrier": ItemData(708 + SC2WOL_ITEM_ID_OFFSET, "Protoss", 8, classification=ItemClassification.progression), - - # Filler items to fill remaining spots - "+15 Starting Minerals": ItemData(800 + SC2WOL_ITEM_ID_OFFSET, "Minerals", 15, quantity=0, classification=ItemClassification.filler), - "+15 Starting Vespene": ItemData(801 + SC2WOL_ITEM_ID_OFFSET, "Vespene", 15, quantity=0, classification=ItemClassification.filler), - # This Filler item isn't placed by the generator yet unless plando'd - "+2 Starting Supply": ItemData(802 + SC2WOL_ITEM_ID_OFFSET, "Supply", 2, quantity=0, classification=ItemClassification.filler), - # This item is used to "remove" location from the game. Never placed unless plando'd - "Nothing": ItemData(803 + SC2WOL_ITEM_ID_OFFSET, "Nothing Group", 2, quantity=0, classification=ItemClassification.trap), - - # "Keystone Piece": ItemData(850 + SC2WOL_ITEM_ID_OFFSET, "Goal", 0, quantity=0, classification=ItemClassification.progression_skip_balancing) -} - -def get_item_table(multiworld: MultiWorld, player: int): - return item_table - -basic_units = { - 'Marine', - 'Marauder', - 'Goliath', - 'Hellion', - 'Vulture' -} - -advanced_basic_units = basic_units.union({ - 'Reaper', - 'Diamondback', - 'Viking' -}) - - -def get_basic_units(multiworld: MultiWorld, player: int) -> typing.Set[str]: - if get_option_value(multiworld, player, 'required_tactics') > 0: - return advanced_basic_units - else: - return basic_units - - -item_name_groups = {} -for item, data in get_full_item_list().items(): - item_name_groups.setdefault(data.type, []).append(item) - if data.type in ("Armory 1", "Armory 2") and '(' in item: - short_name = item[:item.find(' (')] - item_name_groups[short_name] = [item] -item_name_groups["Missions"] = ["Beat " + mission_name for mission_name in vanilla_mission_req_table] - - -# Items that can be placed before resources if not already in -# General upgrades and Mercs -second_pass_placeable_items: typing.Tuple[str, ...] = ( - # Buildings without upgrades - "Sensor Tower", - "Hive Mind Emulator", - "Psi Disrupter", - "Perdition Turret", - # General upgrades without any dependencies - "Advanced Construction (SCV)", - "Dual-Fusion Welders (SCV)", - "Fire-Suppression System (Building)", - "Orbital Command (Building)", - "Ultra-Capacitors", - "Vanadium Plating", - "Orbital Depots", - "Micro-Filtering", - "Automated Refinery", - "Command Center Reactor", - "Tech Reactor", - "Planetary Fortress", - "Cellular Reactor", - "Progressive Regenerative Bio-Steel", # Place only L1 - # Mercenaries - "War Pigs", - "Devil Dogs", - "Hammer Securities", - "Spartan Company", - "Siege Breakers", - "Hel's Angel", - "Dusk Wings", - "Jackson's Revenge" -) - - -filler_items: typing.Tuple[str, ...] = ( - '+15 Starting Minerals', - '+15 Starting Vespene' -) - -# Defense rating table -# Commented defense ratings are handled in LogicMixin -defense_ratings = { - "Siege Tank": 5, - # "Maelstrom Rounds": 2, - "Planetary Fortress": 3, - # Bunker w/ Marine/Marauder: 3, - "Perdition Turret": 2, - "Missile Turret": 2, - "Vulture": 2, - "Liberator": 2, - "Widow Mine": 2 - # "Concealment (Widow Mine)": 1 -} -zerg_defense_ratings = { - "Perdition Turret": 2, - # Bunker w/ Firebat: 2, - "Hive Mind Emulator": 3, - "Psi Disruptor": 3 -} - -spider_mine_sources = { - "Vulture", - "Spider Mines (Reaper)", - "Spider Mines (Siege Tank)", - "Spider Mines (Raven)" -} - -progressive_if_nco = { - "Progressive Stimpack (Marine)", - "Progressive Stimpack (Firebat)", - "Progressive Cross-Spectrum Dampeners (Banshee)", - "Progressive Regenerative Bio-Steel" -} - -# 'number' values of upgrades for upgrade bundle items -upgrade_numbers = [ - {0, 4, 8}, # Weapon - {2, 6, 10}, # Armor - {0, 2}, # Infantry - {4, 6}, # Vehicle - {8, 10}, # Starship - {0, 2, 4, 6, 8, 10} # All -] -# Names of upgrades to be included for different options -upgrade_included_names = [ - { # Individual Items - "Progressive Infantry Weapon", - "Progressive Infantry Armor", - "Progressive Vehicle Weapon", - "Progressive Vehicle Armor", - "Progressive Ship Weapon", - "Progressive Ship Armor" - }, - { # Bundle Weapon And Armor - "Progressive Weapon Upgrade", - "Progressive Armor Upgrade" - }, - { # Bundle Unit Class - "Progressive Infantry Upgrade", - "Progressive Vehicle Upgrade", - "Progressive Starship Upgrade" - }, - { # Bundle All - "Progressive Weapon/Armor Upgrade" - } -] - -lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in get_full_item_list().items() if - data.code} -# Map type to expected int -type_flaggroups: typing.Dict[str, int] = { - "Unit": 0, - "Upgrade": 1, # Weapon / Armor upgrades - "Armory 1": 2, # Unit upgrades - "Armory 2": 3, # Unit upgrades - "Building": 4, - "Mercenary": 5, - "Laboratory": 6, - "Protoss": 7, - "Minerals": 8, - "Vespene": 9, - "Supply": 10, - "Goal": 11, - "Armory 3": 12, # Unit upgrades - "Armory 4": 13, # Unit upgrades - "Progressive Upgrade": 14, # Unit upgrades that exist multiple times (Stimpack / Super Stimpack) - "Nothing Group": 15 -} diff --git a/worlds/sc2wol/Locations.py b/worlds/sc2wol/Locations.py deleted file mode 100644 index fba7051337..0000000000 --- a/worlds/sc2wol/Locations.py +++ /dev/null @@ -1,516 +0,0 @@ -from enum import IntEnum -from typing import List, Tuple, Optional, Callable, NamedTuple -from BaseClasses import MultiWorld -from .Options import get_option_value - -from BaseClasses import Location - -SC2WOL_LOC_ID_OFFSET = 1000 - - -class SC2WoLLocation(Location): - game: str = "Starcraft2WoL" - - -class LocationType(IntEnum): - VICTORY = 0 # Winning a mission - MISSION_PROGRESS = 1 # All tasks done for progressing the mission normally towards victory. All cleaning of expansion bases falls here - BONUS = 2 # Bonus objective, getting a campaign or mission bonus in vanilla (credits, research, bonus units or resources) - CHALLENGE = 3 # Challenging objectives, often harder than just completing a mission - OPTIONAL_BOSS = 4 # Any boss that's not required to win the mission. All Brutalisks, Loki, etc. - -class LocationData(NamedTuple): - region: str - name: str - code: Optional[int] - type: LocationType - rule: Callable = lambda state: True - - -def get_locations(multiworld: Optional[MultiWorld], player: Optional[int]) -> Tuple[LocationData, ...]: - # Note: rules which are ended with or True are rules identified as needed later when restricted units is an option - logic_level = get_option_value(multiworld, player, 'required_tactics') - location_table: List[LocationData] = [ - LocationData("Liberation Day", "Liberation Day: Victory", SC2WOL_LOC_ID_OFFSET + 100, LocationType.VICTORY), - LocationData("Liberation Day", "Liberation Day: First Statue", SC2WOL_LOC_ID_OFFSET + 101, LocationType.BONUS), - LocationData("Liberation Day", "Liberation Day: Second Statue", SC2WOL_LOC_ID_OFFSET + 102, LocationType.BONUS), - LocationData("Liberation Day", "Liberation Day: Third Statue", SC2WOL_LOC_ID_OFFSET + 103, LocationType.BONUS), - LocationData("Liberation Day", "Liberation Day: Fourth Statue", SC2WOL_LOC_ID_OFFSET + 104, LocationType.BONUS), - LocationData("Liberation Day", "Liberation Day: Fifth Statue", SC2WOL_LOC_ID_OFFSET + 105, LocationType.BONUS), - LocationData("Liberation Day", "Liberation Day: Sixth Statue", SC2WOL_LOC_ID_OFFSET + 106, LocationType.BONUS), - LocationData("Liberation Day", "Liberation Day: Special Delivery", SC2WOL_LOC_ID_OFFSET + 107, LocationType.MISSION_PROGRESS), - LocationData("The Outlaws", "The Outlaws: Victory", SC2WOL_LOC_ID_OFFSET + 200, LocationType.VICTORY, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("The Outlaws", "The Outlaws: Rebel Base", SC2WOL_LOC_ID_OFFSET + 201, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("The Outlaws", "The Outlaws: North Resource Pickups", SC2WOL_LOC_ID_OFFSET + 202, LocationType.BONUS), - LocationData("The Outlaws", "The Outlaws: Bunker", SC2WOL_LOC_ID_OFFSET + 203, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Zero Hour", "Zero Hour: Victory", SC2WOL_LOC_ID_OFFSET + 300, LocationType.VICTORY, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) >= 2 and - (logic_level > 0 or state._sc2wol_has_anti_air(multiworld, player))), - LocationData("Zero Hour", "Zero Hour: First Group Rescued", SC2WOL_LOC_ID_OFFSET + 301, LocationType.BONUS), - LocationData("Zero Hour", "Zero Hour: Second Group Rescued", SC2WOL_LOC_ID_OFFSET + 302, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Zero Hour", "Zero Hour: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 303, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) >= 2), - LocationData("Zero Hour", "Zero Hour: First Hatchery", SC2WOL_LOC_ID_OFFSET + 304, LocationType.CHALLENGE, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Zero Hour", "Zero Hour: Second Hatchery", SC2WOL_LOC_ID_OFFSET + 305, LocationType.CHALLENGE, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Zero Hour", "Zero Hour: Third Hatchery", SC2WOL_LOC_ID_OFFSET + 306, LocationType.CHALLENGE, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Zero Hour", "Zero Hour: Fourth Hatchery", SC2WOL_LOC_ID_OFFSET + 307, LocationType.CHALLENGE, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Evacuation", "Evacuation: Victory", SC2WOL_LOC_ID_OFFSET + 400, LocationType.VICTORY, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) - or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("Evacuation", "Evacuation: North Chrysalis", SC2WOL_LOC_ID_OFFSET + 401, LocationType.BONUS), - LocationData("Evacuation", "Evacuation: West Chrysalis", SC2WOL_LOC_ID_OFFSET + 402, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Evacuation", "Evacuation: East Chrysalis", SC2WOL_LOC_ID_OFFSET + 403, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Evacuation", "Evacuation: Reach Hanson", SC2WOL_LOC_ID_OFFSET + 404, LocationType.MISSION_PROGRESS), - LocationData("Evacuation", "Evacuation: Secret Resource Stash", SC2WOL_LOC_ID_OFFSET + 405, LocationType.BONUS), - LocationData("Evacuation", "Evacuation: Flawless", SC2WOL_LOC_ID_OFFSET + 406, LocationType.CHALLENGE, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) - or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("Outbreak", "Outbreak: Victory", SC2WOL_LOC_ID_OFFSET + 500, LocationType.VICTORY, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 4 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Outbreak", "Outbreak: Left Infestor", SC2WOL_LOC_ID_OFFSET + 501, LocationType.BONUS, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Outbreak", "Outbreak: Right Infestor", SC2WOL_LOC_ID_OFFSET + 502, LocationType.BONUS, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Outbreak", "Outbreak: North Infested Command Center", SC2WOL_LOC_ID_OFFSET + 503, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Outbreak", "Outbreak: South Infested Command Center", SC2WOL_LOC_ID_OFFSET + 504, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Outbreak", "Outbreak: Northwest Bar", SC2WOL_LOC_ID_OFFSET + 505, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Outbreak", "Outbreak: North Bar", SC2WOL_LOC_ID_OFFSET + 506, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Outbreak", "Outbreak: South Bar", SC2WOL_LOC_ID_OFFSET + 507, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_defense_rating(multiworld, player, True, False) >= 2 and - (state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Safe Haven", "Safe Haven: Victory", SC2WOL_LOC_ID_OFFSET + 600, LocationType.VICTORY, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player)), - LocationData("Safe Haven", "Safe Haven: North Nexus", SC2WOL_LOC_ID_OFFSET + 601, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player)), - LocationData("Safe Haven", "Safe Haven: East Nexus", SC2WOL_LOC_ID_OFFSET + 602, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player)), - LocationData("Safe Haven", "Safe Haven: South Nexus", SC2WOL_LOC_ID_OFFSET + 603, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player)), - LocationData("Safe Haven", "Safe Haven: First Terror Fleet", SC2WOL_LOC_ID_OFFSET + 604, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player)), - LocationData("Safe Haven", "Safe Haven: Second Terror Fleet", SC2WOL_LOC_ID_OFFSET + 605, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player)), - LocationData("Safe Haven", "Safe Haven: Third Terror Fleet", SC2WOL_LOC_ID_OFFSET + 606, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player)), - LocationData("Haven's Fall", "Haven's Fall: Victory", SC2WOL_LOC_ID_OFFSET + 700, LocationType.VICTORY, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) >= 3), - LocationData("Haven's Fall", "Haven's Fall: North Hive", SC2WOL_LOC_ID_OFFSET + 701, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) >= 3), - LocationData("Haven's Fall", "Haven's Fall: East Hive", SC2WOL_LOC_ID_OFFSET + 702, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) >= 3), - LocationData("Haven's Fall", "Haven's Fall: South Hive", SC2WOL_LOC_ID_OFFSET + 703, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) >= 3), - LocationData("Haven's Fall", "Haven's Fall: Northeast Colony Base", SC2WOL_LOC_ID_OFFSET + 704, LocationType.CHALLENGE, - lambda state: state._sc2wol_can_respond_to_colony_infestations), - LocationData("Haven's Fall", "Haven's Fall: East Colony Base", SC2WOL_LOC_ID_OFFSET + 705, LocationType.CHALLENGE, - lambda state: state._sc2wol_can_respond_to_colony_infestations), - LocationData("Haven's Fall", "Haven's Fall: Middle Colony Base", SC2WOL_LOC_ID_OFFSET + 706, LocationType.CHALLENGE, - lambda state: state._sc2wol_can_respond_to_colony_infestations), - LocationData("Haven's Fall", "Haven's Fall: Southeast Colony Base", SC2WOL_LOC_ID_OFFSET + 707, LocationType.CHALLENGE, - lambda state: state._sc2wol_can_respond_to_colony_infestations), - LocationData("Haven's Fall", "Haven's Fall: Southwest Colony Base", SC2WOL_LOC_ID_OFFSET + 708, LocationType.CHALLENGE, - lambda state: state._sc2wol_can_respond_to_colony_infestations), - LocationData("Smash and Grab", "Smash and Grab: Victory", SC2WOL_LOC_ID_OFFSET + 800, LocationType.VICTORY, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) - or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("Smash and Grab", "Smash and Grab: First Relic", SC2WOL_LOC_ID_OFFSET + 801, LocationType.BONUS), - LocationData("Smash and Grab", "Smash and Grab: Second Relic", SC2WOL_LOC_ID_OFFSET + 802, LocationType.BONUS), - LocationData("Smash and Grab", "Smash and Grab: Third Relic", SC2WOL_LOC_ID_OFFSET + 803, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) - or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("Smash and Grab", "Smash and Grab: Fourth Relic", SC2WOL_LOC_ID_OFFSET + 804, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) - or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("Smash and Grab", "Smash and Grab: First Forcefield Area Busted", SC2WOL_LOC_ID_OFFSET + 805, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) - or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("Smash and Grab", "Smash and Grab: Second Forcefield Area Busted", SC2WOL_LOC_ID_OFFSET + 806, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - (logic_level > 0 and state._sc2wol_has_anti_air(multiworld, player) - or state._sc2wol_has_competent_anti_air(multiworld, player))), - LocationData("The Dig", "The Dig: Victory", SC2WOL_LOC_ID_OFFSET + 900, LocationType.VICTORY, - lambda state: state._sc2wol_has_anti_air(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, False) >= 7), - LocationData("The Dig", "The Dig: Left Relic", SC2WOL_LOC_ID_OFFSET + 901, LocationType.BONUS, - lambda state: state._sc2wol_defense_rating(multiworld, player, False) >= 5), - LocationData("The Dig", "The Dig: Right Ground Relic", SC2WOL_LOC_ID_OFFSET + 902, LocationType.BONUS, - lambda state: state._sc2wol_defense_rating(multiworld, player, False) >= 5), - LocationData("The Dig", "The Dig: Right Cliff Relic", SC2WOL_LOC_ID_OFFSET + 903, LocationType.BONUS, - lambda state: state._sc2wol_defense_rating(multiworld, player, False) >= 5), - LocationData("The Dig", "The Dig: Moebius Base", SC2WOL_LOC_ID_OFFSET + 904, LocationType.MISSION_PROGRESS), - LocationData("The Moebius Factor", "The Moebius Factor: Victory", SC2WOL_LOC_ID_OFFSET + 1000, LocationType.VICTORY, - lambda state: state._sc2wol_has_anti_air(multiworld, player) and - (state._sc2wol_has_air(multiworld, player) - or state.has_any({'Medivac', 'Hercules'}, player) - and state._sc2wol_has_common_unit(multiworld, player))), - LocationData("The Moebius Factor", "The Moebius Factor: 1st Data Core", SC2WOL_LOC_ID_OFFSET + 1001, LocationType.MISSION_PROGRESS), - LocationData("The Moebius Factor", "The Moebius Factor: 2nd Data Core", SC2WOL_LOC_ID_OFFSET + 1002, LocationType.MISSION_PROGRESS, - lambda state: (state._sc2wol_has_air(multiworld, player) - or state.has_any({'Medivac', 'Hercules'}, player) - and state._sc2wol_has_common_unit(multiworld, player))), - LocationData("The Moebius Factor", "The Moebius Factor: South Rescue", SC2WOL_LOC_ID_OFFSET + 1003, LocationType.BONUS, - lambda state: state._sc2wol_able_to_rescue(multiworld, player)), - LocationData("The Moebius Factor", "The Moebius Factor: Wall Rescue", SC2WOL_LOC_ID_OFFSET + 1004, LocationType.BONUS, - lambda state: state._sc2wol_able_to_rescue(multiworld, player)), - LocationData("The Moebius Factor", "The Moebius Factor: Mid Rescue", SC2WOL_LOC_ID_OFFSET + 1005, LocationType.BONUS, - lambda state: state._sc2wol_able_to_rescue(multiworld, player)), - LocationData("The Moebius Factor", "The Moebius Factor: Nydus Roof Rescue", SC2WOL_LOC_ID_OFFSET + 1006, LocationType.BONUS, - lambda state: state._sc2wol_able_to_rescue(multiworld, player)), - LocationData("The Moebius Factor", "The Moebius Factor: Alive Inside Rescue", SC2WOL_LOC_ID_OFFSET + 1007, LocationType.BONUS, - lambda state: state._sc2wol_able_to_rescue(multiworld, player)), - LocationData("The Moebius Factor", "The Moebius Factor: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1008, LocationType.OPTIONAL_BOSS, - lambda state: state._sc2wol_has_anti_air(multiworld, player) and - (state._sc2wol_has_air(multiworld, player) - or state.has_any({'Medivac', 'Hercules'}, player) - and state._sc2wol_has_common_unit(multiworld, player))), - LocationData("The Moebius Factor", "The Moebius Factor: 3rd Data Core", SC2WOL_LOC_ID_OFFSET + 1009, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_anti_air(multiworld, player) and - (state._sc2wol_has_air(multiworld, player) - or state.has_any({'Medivac', 'Hercules'}, player) - and state._sc2wol_has_common_unit(multiworld, player))), - LocationData("Supernova", "Supernova: Victory", SC2WOL_LOC_ID_OFFSET + 1100, LocationType.VICTORY, - lambda state: state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Supernova", "Supernova: West Relic", SC2WOL_LOC_ID_OFFSET + 1101, LocationType.BONUS), - LocationData("Supernova", "Supernova: North Relic", SC2WOL_LOC_ID_OFFSET + 1102, LocationType.BONUS), - LocationData("Supernova", "Supernova: South Relic", SC2WOL_LOC_ID_OFFSET + 1103, LocationType.BONUS, - lambda state: state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Supernova", "Supernova: East Relic", SC2WOL_LOC_ID_OFFSET + 1104, LocationType.BONUS, - lambda state: state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Supernova", "Supernova: Landing Zone Cleared", SC2WOL_LOC_ID_OFFSET + 1105, LocationType.MISSION_PROGRESS), - LocationData("Supernova", "Supernova: Middle Base", SC2WOL_LOC_ID_OFFSET + 1106, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Supernova", "Supernova: Southeast Base", SC2WOL_LOC_ID_OFFSET + 1107, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Victory", SC2WOL_LOC_ID_OFFSET + 1200, LocationType.VICTORY, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Landing Zone Cleared", SC2WOL_LOC_ID_OFFSET + 1201, LocationType.MISSION_PROGRESS), - LocationData("Maw of the Void", "Maw of the Void: Expansion Prisoners", SC2WOL_LOC_ID_OFFSET + 1202, LocationType.BONUS, - lambda state: logic_level > 0 or state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: South Close Prisoners", SC2WOL_LOC_ID_OFFSET + 1203, LocationType.BONUS, - lambda state: logic_level > 0 or state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: South Far Prisoners", SC2WOL_LOC_ID_OFFSET + 1204, LocationType.BONUS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: North Prisoners", SC2WOL_LOC_ID_OFFSET + 1205, LocationType.BONUS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Mothership", SC2WOL_LOC_ID_OFFSET + 1206, LocationType.OPTIONAL_BOSS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Expansion Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1207, LocationType.MISSION_PROGRESS, - lambda state: logic_level > 0 or state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Middle Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1208, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Southeast Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1209, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Stargate Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1210, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Northwest Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1211, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: West Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1212, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Maw of the Void", "Maw of the Void: Southwest Rip Field Generator", SC2WOL_LOC_ID_OFFSET + 1213, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_survives_rip_field(multiworld, player)), - LocationData("Devil's Playground", "Devil's Playground: Victory", SC2WOL_LOC_ID_OFFSET + 1300, LocationType.VICTORY, - lambda state: logic_level > 0 or - state._sc2wol_has_anti_air(multiworld, player) and ( - state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Devil's Playground", "Devil's Playground: Tosh's Miners", SC2WOL_LOC_ID_OFFSET + 1301, LocationType.BONUS), - LocationData("Devil's Playground", "Devil's Playground: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1302, LocationType.OPTIONAL_BOSS, - lambda state: logic_level > 0 or state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player)), - LocationData("Devil's Playground", "Devil's Playground: North Reapers", SC2WOL_LOC_ID_OFFSET + 1303, LocationType.BONUS), - LocationData("Devil's Playground", "Devil's Playground: Middle Reapers", SC2WOL_LOC_ID_OFFSET + 1304, LocationType.BONUS, - lambda state: logic_level > 0 or state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player)), - LocationData("Devil's Playground", "Devil's Playground: Southwest Reapers", SC2WOL_LOC_ID_OFFSET + 1305, LocationType.BONUS, - lambda state: logic_level > 0 or state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player)), - LocationData("Devil's Playground", "Devil's Playground: Southeast Reapers", SC2WOL_LOC_ID_OFFSET + 1306, LocationType.BONUS, - lambda state: logic_level > 0 or - state._sc2wol_has_anti_air(multiworld, player) and ( - state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Devil's Playground", "Devil's Playground: East Reapers", SC2WOL_LOC_ID_OFFSET + 1307, LocationType.BONUS, - lambda state: state._sc2wol_has_anti_air(multiworld, player) and - (logic_level > 0 or - state._sc2wol_has_common_unit(multiworld, player) or state.has("Reaper", player))), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Victory", SC2WOL_LOC_ID_OFFSET + 1400, LocationType.VICTORY, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Close Relic", SC2WOL_LOC_ID_OFFSET + 1401, LocationType.BONUS), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: West Relic", SC2WOL_LOC_ID_OFFSET + 1402, LocationType.BONUS, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: North-East Relic", SC2WOL_LOC_ID_OFFSET + 1403, LocationType.BONUS, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Middle Base", SC2WOL_LOC_ID_OFFSET + 1404, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Main Base", SC2WOL_LOC_ID_OFFSET + 1405, LocationType.CHALLENGE, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player) - and state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: No Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1406, LocationType.CHALLENGE, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player) - and state._sc2wol_has_competent_ground_to_air(multiworld, player) - and state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 1 Terrazine Node Sealed", SC2WOL_LOC_ID_OFFSET + 1407, LocationType.CHALLENGE, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player) - and state._sc2wol_has_competent_ground_to_air(multiworld, player) - and state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 2 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1408, LocationType.CHALLENGE, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player) - and state._sc2wol_beats_protoss_deathball(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 3 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1409, LocationType.CHALLENGE, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player) - and state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 4 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1410, LocationType.CHALLENGE, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player)), - LocationData("Welcome to the Jungle", "Welcome to the Jungle: Up to 5 Terrazine Nodes Sealed", SC2WOL_LOC_ID_OFFSET + 1411, LocationType.CHALLENGE, - lambda state: state._sc2wol_welcome_to_the_jungle_requirement(multiworld, player)), - LocationData("Breakout", "Breakout: Victory", SC2WOL_LOC_ID_OFFSET + 1500, LocationType.VICTORY), - LocationData("Breakout", "Breakout: Diamondback Prison", SC2WOL_LOC_ID_OFFSET + 1501, LocationType.BONUS), - LocationData("Breakout", "Breakout: Siege Tank Prison", SC2WOL_LOC_ID_OFFSET + 1502, LocationType.BONUS), - LocationData("Breakout", "Breakout: First Checkpoint", SC2WOL_LOC_ID_OFFSET + 1503, LocationType.MISSION_PROGRESS), - LocationData("Breakout", "Breakout: Second Checkpoint", SC2WOL_LOC_ID_OFFSET + 1504, LocationType.MISSION_PROGRESS), - LocationData("Ghost of a Chance", "Ghost of a Chance: Victory", SC2WOL_LOC_ID_OFFSET + 1600, LocationType.VICTORY), - LocationData("Ghost of a Chance", "Ghost of a Chance: Terrazine Tank", SC2WOL_LOC_ID_OFFSET + 1601, LocationType.MISSION_PROGRESS), - LocationData("Ghost of a Chance", "Ghost of a Chance: Jorium Stockpile", SC2WOL_LOC_ID_OFFSET + 1602, LocationType.MISSION_PROGRESS), - LocationData("Ghost of a Chance", "Ghost of a Chance: First Island Spectres", SC2WOL_LOC_ID_OFFSET + 1603, LocationType.BONUS), - LocationData("Ghost of a Chance", "Ghost of a Chance: Second Island Spectres", SC2WOL_LOC_ID_OFFSET + 1604, LocationType.BONUS), - LocationData("Ghost of a Chance", "Ghost of a Chance: Third Island Spectres", SC2WOL_LOC_ID_OFFSET + 1605, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Victory", SC2WOL_LOC_ID_OFFSET + 1700, LocationType.VICTORY, - lambda state: state._sc2wol_has_train_killers(multiworld, player) and - state._sc2wol_has_anti_air(multiworld, player)), - LocationData("The Great Train Robbery", "The Great Train Robbery: North Defiler", SC2WOL_LOC_ID_OFFSET + 1701, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Mid Defiler", SC2WOL_LOC_ID_OFFSET + 1702, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: South Defiler", SC2WOL_LOC_ID_OFFSET + 1703, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Close Diamondback", SC2WOL_LOC_ID_OFFSET + 1704, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Northwest Diamondback", SC2WOL_LOC_ID_OFFSET + 1705, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: North Diamondback", SC2WOL_LOC_ID_OFFSET + 1706, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Northeast Diamondback", SC2WOL_LOC_ID_OFFSET + 1707, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Southwest Diamondback", SC2WOL_LOC_ID_OFFSET + 1708, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Southeast Diamondback", SC2WOL_LOC_ID_OFFSET + 1709, LocationType.BONUS), - LocationData("The Great Train Robbery", "The Great Train Robbery: Kill Team", SC2WOL_LOC_ID_OFFSET + 1710, LocationType.CHALLENGE, - lambda state: (logic_level > 0 or state._sc2wol_has_common_unit(multiworld, player)) and - state._sc2wol_has_train_killers(multiworld, player) and - state._sc2wol_has_anti_air(multiworld, player)), - LocationData("Cutthroat", "Cutthroat: Victory", SC2WOL_LOC_ID_OFFSET + 1800, LocationType.VICTORY, - lambda state: state._sc2wol_has_common_unit(multiworld, player) and - (logic_level > 0 or state._sc2wol_has_anti_air)), - LocationData("Cutthroat", "Cutthroat: Mira Han", SC2WOL_LOC_ID_OFFSET + 1801, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Cutthroat", "Cutthroat: North Relic", SC2WOL_LOC_ID_OFFSET + 1802, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Cutthroat", "Cutthroat: Mid Relic", SC2WOL_LOC_ID_OFFSET + 1803, LocationType.BONUS), - LocationData("Cutthroat", "Cutthroat: Southwest Relic", SC2WOL_LOC_ID_OFFSET + 1804, LocationType.BONUS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Cutthroat", "Cutthroat: North Command Center", SC2WOL_LOC_ID_OFFSET + 1805, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Cutthroat", "Cutthroat: South Command Center", SC2WOL_LOC_ID_OFFSET + 1806, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Cutthroat", "Cutthroat: West Command Center", SC2WOL_LOC_ID_OFFSET + 1807, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_common_unit(multiworld, player)), - LocationData("Engine of Destruction", "Engine of Destruction: Victory", SC2WOL_LOC_ID_OFFSET + 1900, LocationType.VICTORY, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Engine of Destruction: Odin", SC2WOL_LOC_ID_OFFSET + 1901, LocationType.MISSION_PROGRESS), - LocationData("Engine of Destruction", "Engine of Destruction: Loki", SC2WOL_LOC_ID_OFFSET + 1902, LocationType.OPTIONAL_BOSS, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Engine of Destruction: Lab Devourer", SC2WOL_LOC_ID_OFFSET + 1903, LocationType.BONUS), - LocationData("Engine of Destruction", "Engine of Destruction: North Devourer", SC2WOL_LOC_ID_OFFSET + 1904, LocationType.BONUS, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Engine of Destruction: Southeast Devourer", SC2WOL_LOC_ID_OFFSET + 1905, LocationType.BONUS, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Engine of Destruction: West Base", SC2WOL_LOC_ID_OFFSET + 1906, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Engine of Destruction: Northwest Base", SC2WOL_LOC_ID_OFFSET + 1907, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Engine of Destruction: Northeast Base", SC2WOL_LOC_ID_OFFSET + 1908, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Engine of Destruction: Southeast Base", SC2WOL_LOC_ID_OFFSET + 1909, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_anti_air(multiworld, player) and - state._sc2wol_has_common_unit(multiworld, player) or state.has('Wraith', player)), - LocationData("Media Blitz", "Media Blitz: Victory", SC2WOL_LOC_ID_OFFSET + 2000, LocationType.VICTORY, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Media Blitz", "Media Blitz: Tower 1", SC2WOL_LOC_ID_OFFSET + 2001, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Media Blitz", "Media Blitz: Tower 2", SC2WOL_LOC_ID_OFFSET + 2002, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Media Blitz", "Media Blitz: Tower 3", SC2WOL_LOC_ID_OFFSET + 2003, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Media Blitz", "Media Blitz: Science Facility", SC2WOL_LOC_ID_OFFSET + 2004, LocationType.BONUS), - LocationData("Media Blitz", "Media Blitz: All Barracks", SC2WOL_LOC_ID_OFFSET + 2005, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Media Blitz", "Media Blitz: All Factories", SC2WOL_LOC_ID_OFFSET + 2006, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Media Blitz", "Media Blitz: All Starports", SC2WOL_LOC_ID_OFFSET + 2007, LocationType.MISSION_PROGRESS, - lambda state: logic_level > 0 or state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Media Blitz", "Media Blitz: Odin Not Trashed", SC2WOL_LOC_ID_OFFSET + 2008, LocationType.CHALLENGE, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Piercing the Shroud", "Piercing the Shroud: Victory", SC2WOL_LOC_ID_OFFSET + 2100, LocationType.VICTORY, - lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)), - LocationData("Piercing the Shroud", "Piercing the Shroud: Holding Cell Relic", SC2WOL_LOC_ID_OFFSET + 2101, LocationType.BONUS), - LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk Relic", SC2WOL_LOC_ID_OFFSET + 2102, LocationType.BONUS, - lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)), - LocationData("Piercing the Shroud", "Piercing the Shroud: First Escape Relic", SC2WOL_LOC_ID_OFFSET + 2103,LocationType.BONUS, - lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)), - LocationData("Piercing the Shroud", "Piercing the Shroud: Second Escape Relic", SC2WOL_LOC_ID_OFFSET + 2104, LocationType.BONUS, - lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)), - LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk", SC2WOL_LOC_ID_OFFSET + 2105, LocationType.OPTIONAL_BOSS, - lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)), - LocationData("Piercing the Shroud", "Piercing the Shroud: Fusion Reactor", SC2WOL_LOC_ID_OFFSET + 2106, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_mm_upgrade(multiworld, player)), - LocationData("Whispers of Doom", "Whispers of Doom: Victory", SC2WOL_LOC_ID_OFFSET + 2200, LocationType.VICTORY), - LocationData("Whispers of Doom", "Whispers of Doom: First Hatchery", SC2WOL_LOC_ID_OFFSET + 2201, LocationType.BONUS), - LocationData("Whispers of Doom", "Whispers of Doom: Second Hatchery", SC2WOL_LOC_ID_OFFSET + 2202, LocationType.BONUS), - LocationData("Whispers of Doom", "Whispers of Doom: Third Hatchery", SC2WOL_LOC_ID_OFFSET + 2203, LocationType.BONUS), - LocationData("Whispers of Doom", "Whispers of Doom: First Prophecy Fragment", SC2WOL_LOC_ID_OFFSET + 2204, LocationType.MISSION_PROGRESS), - LocationData("Whispers of Doom", "Whispers of Doom: Second Prophecy Fragment", SC2WOL_LOC_ID_OFFSET + 2205, LocationType.MISSION_PROGRESS), - LocationData("Whispers of Doom", "Whispers of Doom: Third Prophecy Fragment", SC2WOL_LOC_ID_OFFSET + 2206, LocationType.MISSION_PROGRESS), - LocationData("A Sinister Turn", "A Sinister Turn: Victory", SC2WOL_LOC_ID_OFFSET + 2300, LocationType.VICTORY, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Robotics Facility", SC2WOL_LOC_ID_OFFSET + 2301, LocationType.BONUS, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Dark Shrine", SC2WOL_LOC_ID_OFFSET + 2302, LocationType.BONUS, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Templar Archives", SC2WOL_LOC_ID_OFFSET + 2303, LocationType.BONUS, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Northeast Base", SC2WOL_LOC_ID_OFFSET + 2304, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Southwest Base", SC2WOL_LOC_ID_OFFSET + 2305, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Maar", SC2WOL_LOC_ID_OFFSET + 2306, LocationType.MISSION_PROGRESS, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Northwest Preserver", SC2WOL_LOC_ID_OFFSET + 2307, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: Southwest Preserver", SC2WOL_LOC_ID_OFFSET + 2308, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("A Sinister Turn", "A Sinister Turn: East Preserver", SC2WOL_LOC_ID_OFFSET + 2309, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("Echoes of the Future", "Echoes of the Future: Victory", SC2WOL_LOC_ID_OFFSET + 2400, LocationType.VICTORY, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("Echoes of the Future", "Echoes of the Future: Close Obelisk", SC2WOL_LOC_ID_OFFSET + 2401, LocationType.BONUS), - LocationData("Echoes of the Future", "Echoes of the Future: West Obelisk", SC2WOL_LOC_ID_OFFSET + 2402, LocationType.BONUS, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("Echoes of the Future", "Echoes of the Future: Base", SC2WOL_LOC_ID_OFFSET + 2403, LocationType.MISSION_PROGRESS), - LocationData("Echoes of the Future", "Echoes of the Future: Southwest Tendril", SC2WOL_LOC_ID_OFFSET + 2404, LocationType.MISSION_PROGRESS), - LocationData("Echoes of the Future", "Echoes of the Future: Southeast Tendril", SC2WOL_LOC_ID_OFFSET + 2405, LocationType.MISSION_PROGRESS, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("Echoes of the Future", "Echoes of the Future: Northeast Tendril", SC2WOL_LOC_ID_OFFSET + 2406, LocationType.MISSION_PROGRESS, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("Echoes of the Future", "Echoes of the Future: Northwest Tendril", SC2WOL_LOC_ID_OFFSET + 2407, LocationType.MISSION_PROGRESS, - lambda state: logic_level > 0 or state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("In Utter Darkness", "In Utter Darkness: Defeat", SC2WOL_LOC_ID_OFFSET + 2500, LocationType.VICTORY), - LocationData("In Utter Darkness", "In Utter Darkness: Protoss Archive", SC2WOL_LOC_ID_OFFSET + 2501, LocationType.BONUS, - lambda state: state._sc2wol_has_protoss_medium_units(multiworld, player)), - LocationData("In Utter Darkness", "In Utter Darkness: Kills", SC2WOL_LOC_ID_OFFSET + 2502, LocationType.CHALLENGE, - lambda state: state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("In Utter Darkness", "In Utter Darkness: Urun", SC2WOL_LOC_ID_OFFSET + 2503, LocationType.MISSION_PROGRESS), - LocationData("In Utter Darkness", "In Utter Darkness: Mohandar", SC2WOL_LOC_ID_OFFSET + 2504, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("In Utter Darkness", "In Utter Darkness: Selendis", SC2WOL_LOC_ID_OFFSET + 2505, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("In Utter Darkness", "In Utter Darkness: Artanis", SC2WOL_LOC_ID_OFFSET + 2506, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_protoss_common_units(multiworld, player)), - LocationData("Gates of Hell", "Gates of Hell: Victory", SC2WOL_LOC_ID_OFFSET + 2600, LocationType.VICTORY, - lambda state: state._sc2wol_has_competent_comp(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) > 6), - LocationData("Gates of Hell", "Gates of Hell: Large Army", SC2WOL_LOC_ID_OFFSET + 2601, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) > 6), - LocationData("Gates of Hell", "Gates of Hell: 2 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2602, LocationType.BONUS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) > 6), - LocationData("Gates of Hell", "Gates of Hell: 4 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2603, LocationType.BONUS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) > 6), - LocationData("Gates of Hell", "Gates of Hell: 6 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2604, LocationType.BONUS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) > 6), - LocationData("Gates of Hell", "Gates of Hell: 8 Drop Pods", SC2WOL_LOC_ID_OFFSET + 2605, LocationType.BONUS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player) and - state._sc2wol_defense_rating(multiworld, player, True) > 6), - LocationData("Belly of the Beast", "Belly of the Beast: Victory", SC2WOL_LOC_ID_OFFSET + 2700, LocationType.VICTORY), - LocationData("Belly of the Beast", "Belly of the Beast: First Charge", SC2WOL_LOC_ID_OFFSET + 2701, LocationType.MISSION_PROGRESS), - LocationData("Belly of the Beast", "Belly of the Beast: Second Charge", SC2WOL_LOC_ID_OFFSET + 2702, LocationType.MISSION_PROGRESS), - LocationData("Belly of the Beast", "Belly of the Beast: Third Charge", SC2WOL_LOC_ID_OFFSET + 2703, LocationType.MISSION_PROGRESS), - LocationData("Belly of the Beast", "Belly of the Beast: First Group Rescued", SC2WOL_LOC_ID_OFFSET + 2704, LocationType.BONUS), - LocationData("Belly of the Beast", "Belly of the Beast: Second Group Rescued", SC2WOL_LOC_ID_OFFSET + 2705, LocationType.BONUS), - LocationData("Belly of the Beast", "Belly of the Beast: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 2706, LocationType.BONUS), - LocationData("Shatter the Sky", "Shatter the Sky: Victory", SC2WOL_LOC_ID_OFFSET + 2800, LocationType.VICTORY, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: Close Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2801, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: Northwest Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2802, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: Southeast Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2803, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: Southwest Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2804, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: Leviathan", SC2WOL_LOC_ID_OFFSET + 2805, LocationType.OPTIONAL_BOSS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: East Hatchery", SC2WOL_LOC_ID_OFFSET + 2806, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: North Hatchery", SC2WOL_LOC_ID_OFFSET + 2807, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("Shatter the Sky", "Shatter the Sky: Mid Hatchery", SC2WOL_LOC_ID_OFFSET + 2808, LocationType.MISSION_PROGRESS, - lambda state: state._sc2wol_has_competent_comp(multiworld, player)), - LocationData("All-In", "All-In: Victory", None, LocationType.VICTORY, - lambda state: state._sc2wol_final_mission_requirements(multiworld, player)) - ] - - beat_events = [] - - for i, location_data in enumerate(location_table): - # Removing all item-based logic on No Logic - if logic_level == 2: - location_data = location_data._replace(rule=Location.access_rule) - location_table[i] = location_data - # Generating Beat event locations - if location_data.name.endswith((": Victory", ": Defeat")): - beat_events.append( - location_data._replace(name="Beat " + location_data.name.rsplit(": ", 1)[0], code=None) - ) - return tuple(location_table + beat_events) diff --git a/worlds/sc2wol/LogicMixin.py b/worlds/sc2wol/LogicMixin.py deleted file mode 100644 index 112302beb2..0000000000 --- a/worlds/sc2wol/LogicMixin.py +++ /dev/null @@ -1,148 +0,0 @@ -from BaseClasses import MultiWorld -from worlds.AutoWorld import LogicMixin -from .Options import get_option_value -from .Items import get_basic_units, defense_ratings, zerg_defense_ratings - - -class SC2WoLLogic(LogicMixin): - def _sc2wol_has_common_unit(self, multiworld: MultiWorld, player: int) -> bool: - return self.has_any(get_basic_units(multiworld, player), player) - - def _sc2wol_has_air(self, multiworld: MultiWorld, player: int) -> bool: - return self.has_any({'Viking', 'Wraith', 'Banshee', 'Battlecruiser'}, player) or get_option_value(multiworld, player, 'required_tactics') > 0 \ - and self.has_any({'Hercules', 'Medivac'}, player) and self._sc2wol_has_common_unit(multiworld, player) - - def _sc2wol_has_air_anti_air(self, multiworld: MultiWorld, player: int) -> bool: - return self.has('Viking', player) \ - or self.has_all({'Wraith', 'Advanced Laser Technology (Wraith)'}, player) \ - or self.has_all({'Battlecruiser', 'ATX Laser Battery (Battlecruiser)'}, player) \ - or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has_any({'Wraith', 'Valkyrie', 'Battlecruiser'}, player) - - def _sc2wol_has_competent_ground_to_air(self, multiworld: MultiWorld, player: int) -> bool: - return self.has('Goliath', player) \ - or self.has('Marine', player) and self.has_any({'Medic', 'Medivac'}, player) \ - or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has('Cyclone', player) - - def _sc2wol_has_competent_anti_air(self, multiworld: MultiWorld, player: int) -> bool: - return self._sc2wol_has_competent_ground_to_air(multiworld, player) \ - or self._sc2wol_has_air_anti_air(multiworld, player) - - def _sc2wol_welcome_to_the_jungle_requirement(self, multiworld: MultiWorld, player: int) -> bool: - return ( - self._sc2wol_has_common_unit(multiworld, player) - and self._sc2wol_has_competent_ground_to_air(multiworld, player) - ) or ( - get_option_value(multiworld, player, 'required_tactics') > 0 - and self.has_any({'Marine', 'Vulture'}, player) - and self._sc2wol_has_air_anti_air(multiworld, player) - ) - - def _sc2wol_has_anti_air(self, multiworld: MultiWorld, player: int) -> bool: - return self.has_any({'Missile Turret', 'Thor', 'War Pigs', 'Spartan Company', "Hel's Angel", 'Battlecruiser', 'Marine', 'Wraith', 'Valkyrie', 'Cyclone'}, player) \ - or self._sc2wol_has_competent_anti_air(multiworld, player) \ - or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has_any({'Ghost', 'Spectre', 'Widow Mine', 'Liberator'}, player) - - def _sc2wol_defense_rating(self, multiworld: MultiWorld, player: int, zerg_enemy: bool, air_enemy: bool = True) -> bool: - defense_score = sum((defense_ratings[item] for item in defense_ratings if self.has(item, player))) - if self.has_any({'Marine', 'Marauder'}, player) and self.has('Bunker', player): - defense_score += 3 - if self.has_all({'Siege Tank', 'Maelstrom Rounds (Siege Tank)'}, player): - defense_score += 2 - if self.has_all({'Siege Tank', 'Graduating Range (Siege Tank)'}, player): - defense_score += 1 - if self.has_all({'Widow Mine', 'Concealment (Widow Mine)'}, player): - defense_score += 1 - if zerg_enemy: - defense_score += sum((zerg_defense_ratings[item] for item in zerg_defense_ratings if self.has(item, player))) - if self.has('Firebat', player) and self.has('Bunker', player): - defense_score += 2 - if not air_enemy and self.has('Missile Turret', player): - defense_score -= defense_ratings['Missile Turret'] - # Advanced Tactics bumps defense rating requirements down by 2 - if get_option_value(multiworld, player, 'required_tactics') > 0: - defense_score += 2 - return defense_score - - def _sc2wol_has_competent_comp(self, multiworld: MultiWorld, player: int) -> bool: - return \ - ( - ( - self.has_any({'Marine', 'Marauder'}, player) and self.has_any({'Medivac', 'Medic'}, player) - or self.has_any({'Thor', 'Banshee', 'Siege Tank'}, player) - or self.has_all({'Liberator', 'Raid Artillery (Liberator)'}, player) - ) and self._sc2wol_has_competent_anti_air(multiworld, player) - ) \ - or \ - ( - self.has('Battlecruiser', player) and self._sc2wol_has_common_unit(multiworld, player) - ) - - def _sc2wol_has_train_killers(self, multiworld: MultiWorld, player: int) -> bool: - return ( - self.has_any({'Siege Tank', 'Diamondback', 'Marauder', 'Cyclone'}, player) - or get_option_value(multiworld, player, 'required_tactics') > 0 - and ( - self.has_all({'Reaper', "G-4 Clusterbomb"}, player) - or self.has_all({'Spectre', 'Psionic Lash'}, player) - or self.has_any({'Vulture', 'Liberator'}, player) - ) - ) - - def _sc2wol_able_to_rescue(self, multiworld: MultiWorld, player: int) -> bool: - return self.has_any({'Medivac', 'Hercules', 'Raven', 'Viking'}, player) or get_option_value(multiworld, player, 'required_tactics') > 0 - - def _sc2wol_has_protoss_common_units(self, multiworld: MultiWorld, player: int) -> bool: - return self.has_any({'Zealot', 'Immortal', 'Stalker', 'Dark Templar'}, player) \ - or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has('High Templar', player) - - def _sc2wol_has_protoss_medium_units(self, multiworld: MultiWorld, player: int) -> bool: - return self._sc2wol_has_protoss_common_units(multiworld, player) and \ - self.has_any({'Stalker', 'Void Ray', 'Carrier'}, player) \ - or get_option_value(multiworld, player, 'required_tactics') > 0 and self.has('Dark Templar', player) - - def _sc2wol_beats_protoss_deathball(self, multiworld: MultiWorld, player: int) -> bool: - return (self.has_any({'Banshee', 'Battlecruiser'}, player) or - self.has_all({'Liberator', 'Raid Artillery (Liberator)'}, player)) \ - and self._sc2wol_has_competent_anti_air(multiworld, player) or \ - self._sc2wol_has_competent_comp(multiworld, player) and self._sc2wol_has_air_anti_air(multiworld, player) - - def _sc2wol_has_mm_upgrade(self, multiworld: MultiWorld, player: int) -> bool: - return self.has_any({"Combat Shield (Marine)", "Stabilizer Medpacks (Medic)"}, player) - - def _sc2wol_survives_rip_field(self, multiworld: MultiWorld, player: int) -> bool: - return self.has("Battlecruiser", player) or \ - self._sc2wol_has_air(multiworld, player) and \ - self._sc2wol_has_competent_anti_air(multiworld, player) and \ - self.has("Science Vessel", player) - - def _sc2wol_has_nukes(self, multiworld: MultiWorld, player: int) -> bool: - return get_option_value(multiworld, player, 'required_tactics') > 0 and self.has_any({'Ghost', 'Spectre'}, player) - - def _sc2wol_can_respond_to_colony_infestations(self, multiworld: MultiWorld, player: int) -> bool: - return self._sc2wol_has_common_unit(multiworld, player) \ - and self._sc2wol_has_competent_anti_air(multiworld, player) \ - and \ - ( - self._sc2wol_has_air_anti_air(multiworld, player) or - self.has_any({'Battlecruiser', 'Valkyrie'}), player - ) \ - and \ - self._sc2wol_defense_rating(multiworld, player, True) >= 3 - - def _sc2wol_final_mission_requirements(self, multiworld: MultiWorld, player: int): - beats_kerrigan = self.has_any({'Marine', 'Banshee', 'Ghost'}, player) or get_option_value(multiworld, player, 'required_tactics') > 0 - if get_option_value(multiworld, player, 'all_in_map') == 0: - # Ground - defense_rating = self._sc2wol_defense_rating(multiworld, player, True, False) - if self.has_any({'Battlecruiser', 'Banshee'}, player): - defense_rating += 3 - return defense_rating >= 12 and beats_kerrigan - else: - # Air - defense_rating = self._sc2wol_defense_rating(multiworld, player, True, True) - return defense_rating >= 8 and beats_kerrigan \ - and self.has_any({'Viking', 'Battlecruiser', 'Valkyrie'}, player) \ - and self.has_any({'Hive Mind Emulator', 'Psi Disruptor', 'Missile Turret'}, player) - - def _sc2wol_cleared_missions(self, multiworld: MultiWorld, player: int, mission_count: int) -> bool: - return self.has_group("Missions", player, mission_count) diff --git a/worlds/sc2wol/MissionTables.py b/worlds/sc2wol/MissionTables.py deleted file mode 100644 index 298cd7a978..0000000000 --- a/worlds/sc2wol/MissionTables.py +++ /dev/null @@ -1,230 +0,0 @@ -from typing import NamedTuple, Dict, List -from enum import IntEnum - -no_build_regions_list = ["Liberation Day", "Breakout", "Ghost of a Chance", "Piercing the Shroud", "Whispers of Doom", - "Belly of the Beast"] -easy_regions_list = ["The Outlaws", "Zero Hour", "Evacuation", "Outbreak", "Smash and Grab", "Devil's Playground"] -medium_regions_list = ["Safe Haven", "Haven's Fall", "The Dig", "The Moebius Factor", "Supernova", - "Welcome to the Jungle", "The Great Train Robbery", "Cutthroat", "Media Blitz", - "A Sinister Turn", "Echoes of the Future"] -hard_regions_list = ["Maw of the Void", "Engine of Destruction", "In Utter Darkness", "Gates of Hell", - "Shatter the Sky"] - - -class MissionPools(IntEnum): - STARTER = 0 - EASY = 1 - MEDIUM = 2 - HARD = 3 - FINAL = 4 - - -class MissionInfo(NamedTuple): - id: int - required_world: List[int] - category: str - number: int = 0 # number of worlds need beaten - completion_critical: bool = False # missions needed to beat game - or_requirements: bool = False # true if the requirements should be or-ed instead of and-ed - - -class FillMission(NamedTuple): - type: int - connect_to: List[int] # -1 connects to Menu - category: str - number: int = 0 # number of worlds need beaten - completion_critical: bool = False # missions needed to beat game - or_requirements: bool = False # true if the requirements should be or-ed instead of and-ed - removal_priority: int = 0 # how many missions missing from the pool required to remove this mission - - -vanilla_shuffle_order = [ - FillMission(MissionPools.STARTER, [-1], "Mar Sara", completion_critical=True), - FillMission(MissionPools.EASY, [0], "Mar Sara", completion_critical=True), - FillMission(MissionPools.EASY, [1], "Mar Sara", completion_critical=True), - FillMission(MissionPools.EASY, [2], "Colonist"), - FillMission(MissionPools.MEDIUM, [3], "Colonist"), - FillMission(MissionPools.HARD, [4], "Colonist", number=7), - FillMission(MissionPools.HARD, [4], "Colonist", number=7, removal_priority=1), - FillMission(MissionPools.EASY, [2], "Artifact", completion_critical=True), - FillMission(MissionPools.MEDIUM, [7], "Artifact", number=8, completion_critical=True), - FillMission(MissionPools.HARD, [8], "Artifact", number=11, completion_critical=True), - FillMission(MissionPools.HARD, [9], "Artifact", number=14, completion_critical=True, removal_priority=11), - FillMission(MissionPools.HARD, [10], "Artifact", completion_critical=True, removal_priority=10), - FillMission(MissionPools.MEDIUM, [2], "Covert", number=4), - FillMission(MissionPools.MEDIUM, [12], "Covert"), - FillMission(MissionPools.HARD, [13], "Covert", number=8, removal_priority=3), - FillMission(MissionPools.HARD, [13], "Covert", number=8, removal_priority=2), - FillMission(MissionPools.MEDIUM, [2], "Rebellion", number=6), - FillMission(MissionPools.HARD, [16], "Rebellion"), - FillMission(MissionPools.HARD, [17], "Rebellion"), - FillMission(MissionPools.HARD, [18], "Rebellion", removal_priority=12), - FillMission(MissionPools.HARD, [19], "Rebellion", removal_priority=5), - FillMission(MissionPools.MEDIUM, [8], "Prophecy", removal_priority=9), - FillMission(MissionPools.HARD, [21], "Prophecy", removal_priority=8), - FillMission(MissionPools.HARD, [22], "Prophecy", removal_priority=7), - FillMission(MissionPools.HARD, [23], "Prophecy", removal_priority=6), - FillMission(MissionPools.HARD, [11], "Char", completion_critical=True), - FillMission(MissionPools.HARD, [25], "Char", completion_critical=True, removal_priority=4), - FillMission(MissionPools.HARD, [25], "Char", completion_critical=True), - FillMission(MissionPools.FINAL, [26, 27], "Char", completion_critical=True, or_requirements=True) -] - -mini_campaign_order = [ - FillMission(MissionPools.STARTER, [-1], "Mar Sara", completion_critical=True), - FillMission(MissionPools.EASY, [0], "Colonist"), - FillMission(MissionPools.MEDIUM, [1], "Colonist"), - FillMission(MissionPools.EASY, [0], "Artifact", completion_critical=True), - FillMission(MissionPools.MEDIUM, [3], "Artifact", number=4, completion_critical=True), - FillMission(MissionPools.HARD, [4], "Artifact", number=8, completion_critical=True), - FillMission(MissionPools.MEDIUM, [0], "Covert", number=2), - FillMission(MissionPools.HARD, [6], "Covert"), - FillMission(MissionPools.MEDIUM, [0], "Rebellion", number=3), - FillMission(MissionPools.HARD, [8], "Rebellion"), - FillMission(MissionPools.MEDIUM, [4], "Prophecy"), - FillMission(MissionPools.HARD, [10], "Prophecy"), - FillMission(MissionPools.HARD, [5], "Char", completion_critical=True), - FillMission(MissionPools.HARD, [5], "Char", completion_critical=True), - FillMission(MissionPools.FINAL, [12, 13], "Char", completion_critical=True, or_requirements=True) -] - -gauntlet_order = [ - FillMission(MissionPools.STARTER, [-1], "I", completion_critical=True), - FillMission(MissionPools.EASY, [0], "II", completion_critical=True), - FillMission(MissionPools.EASY, [1], "III", completion_critical=True), - FillMission(MissionPools.MEDIUM, [2], "IV", completion_critical=True), - FillMission(MissionPools.MEDIUM, [3], "V", completion_critical=True), - FillMission(MissionPools.HARD, [4], "VI", completion_critical=True), - FillMission(MissionPools.FINAL, [5], "Final", completion_critical=True) -] - -mini_gauntlet_order = [ - FillMission(MissionPools.STARTER, [-1], "I", completion_critical=True), - FillMission(MissionPools.EASY, [0], "II", completion_critical=True), - FillMission(MissionPools.MEDIUM, [1], "III", completion_critical=True), - FillMission(MissionPools.FINAL, [2], "Final", completion_critical=True) -] - -grid_order = [ - FillMission(MissionPools.STARTER, [-1], "_1"), - FillMission(MissionPools.EASY, [0], "_1"), - FillMission(MissionPools.MEDIUM, [1, 6, 3], "_1", or_requirements=True), - FillMission(MissionPools.HARD, [2, 7], "_1", or_requirements=True), - FillMission(MissionPools.EASY, [0], "_2"), - FillMission(MissionPools.MEDIUM, [1, 4], "_2", or_requirements=True), - FillMission(MissionPools.HARD, [2, 5, 10, 7], "_2", or_requirements=True), - FillMission(MissionPools.HARD, [3, 6, 11], "_2", or_requirements=True), - FillMission(MissionPools.MEDIUM, [4, 9, 12], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [5, 8, 10, 13], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [6, 9, 11, 14], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [7, 10], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [8, 13], "_4", or_requirements=True), - FillMission(MissionPools.HARD, [9, 12, 14], "_4", or_requirements=True), - FillMission(MissionPools.HARD, [10, 13], "_4", or_requirements=True), - FillMission(MissionPools.FINAL, [11, 14], "_4", or_requirements=True) -] - -mini_grid_order = [ - FillMission(MissionPools.STARTER, [-1], "_1"), - FillMission(MissionPools.EASY, [0], "_1"), - FillMission(MissionPools.MEDIUM, [1, 5], "_1", or_requirements=True), - FillMission(MissionPools.EASY, [0], "_2"), - FillMission(MissionPools.MEDIUM, [1, 3], "_2", or_requirements=True), - FillMission(MissionPools.HARD, [2, 4], "_2", or_requirements=True), - FillMission(MissionPools.MEDIUM, [3, 7], "_3", or_requirements=True), - FillMission(MissionPools.HARD, [4, 6], "_3", or_requirements=True), - FillMission(MissionPools.FINAL, [5, 7], "_3", or_requirements=True) -] - -tiny_grid_order = [ - FillMission(MissionPools.STARTER, [-1], "_1"), - FillMission(MissionPools.MEDIUM, [0], "_1"), - FillMission(MissionPools.EASY, [0], "_2"), - FillMission(MissionPools.FINAL, [1, 2], "_2", or_requirements=True), -] - -blitz_order = [ - FillMission(MissionPools.STARTER, [-1], "I"), - FillMission(MissionPools.EASY, [-1], "I"), - FillMission(MissionPools.MEDIUM, [0, 1], "II", number=1, or_requirements=True), - FillMission(MissionPools.MEDIUM, [0, 1], "II", number=1, or_requirements=True), - FillMission(MissionPools.MEDIUM, [0, 1], "III", number=2, or_requirements=True), - FillMission(MissionPools.MEDIUM, [0, 1], "III", number=2, or_requirements=True), - FillMission(MissionPools.HARD, [0, 1], "IV", number=3, or_requirements=True), - FillMission(MissionPools.HARD, [0, 1], "IV", number=3, or_requirements=True), - FillMission(MissionPools.HARD, [0, 1], "V", number=4, or_requirements=True), - FillMission(MissionPools.HARD, [0, 1], "V", number=4, or_requirements=True), - FillMission(MissionPools.HARD, [0, 1], "Final", number=5, or_requirements=True), - FillMission(MissionPools.FINAL, [0, 1], "Final", number=5, or_requirements=True) -] - -mission_orders = [ - vanilla_shuffle_order, - vanilla_shuffle_order, - mini_campaign_order, - grid_order, - mini_grid_order, - blitz_order, - gauntlet_order, - mini_gauntlet_order, - tiny_grid_order -] - - -vanilla_mission_req_table = { - "Liberation Day": MissionInfo(1, [], "Mar Sara", completion_critical=True), - "The Outlaws": MissionInfo(2, [1], "Mar Sara", completion_critical=True), - "Zero Hour": MissionInfo(3, [2], "Mar Sara", completion_critical=True), - "Evacuation": MissionInfo(4, [3], "Colonist"), - "Outbreak": MissionInfo(5, [4], "Colonist"), - "Safe Haven": MissionInfo(6, [5], "Colonist", number=7), - "Haven's Fall": MissionInfo(7, [5], "Colonist", number=7), - "Smash and Grab": MissionInfo(8, [3], "Artifact", completion_critical=True), - "The Dig": MissionInfo(9, [8], "Artifact", number=8, completion_critical=True), - "The Moebius Factor": MissionInfo(10, [9], "Artifact", number=11, completion_critical=True), - "Supernova": MissionInfo(11, [10], "Artifact", number=14, completion_critical=True), - "Maw of the Void": MissionInfo(12, [11], "Artifact", completion_critical=True), - "Devil's Playground": MissionInfo(13, [3], "Covert", number=4), - "Welcome to the Jungle": MissionInfo(14, [13], "Covert"), - "Breakout": MissionInfo(15, [14], "Covert", number=8), - "Ghost of a Chance": MissionInfo(16, [14], "Covert", number=8), - "The Great Train Robbery": MissionInfo(17, [3], "Rebellion", number=6), - "Cutthroat": MissionInfo(18, [17], "Rebellion"), - "Engine of Destruction": MissionInfo(19, [18], "Rebellion"), - "Media Blitz": MissionInfo(20, [19], "Rebellion"), - "Piercing the Shroud": MissionInfo(21, [20], "Rebellion"), - "Whispers of Doom": MissionInfo(22, [9], "Prophecy"), - "A Sinister Turn": MissionInfo(23, [22], "Prophecy"), - "Echoes of the Future": MissionInfo(24, [23], "Prophecy"), - "In Utter Darkness": MissionInfo(25, [24], "Prophecy"), - "Gates of Hell": MissionInfo(26, [12], "Char", completion_critical=True), - "Belly of the Beast": MissionInfo(27, [26], "Char", completion_critical=True), - "Shatter the Sky": MissionInfo(28, [26], "Char", completion_critical=True), - "All-In": MissionInfo(29, [27, 28], "Char", completion_critical=True, or_requirements=True) -} - -lookup_id_to_mission: Dict[int, str] = { - data.id: mission_name for mission_name, data in vanilla_mission_req_table.items() if data.id} - -starting_mission_locations = { - "Liberation Day": "Liberation Day: Victory", - "Breakout": "Breakout: Victory", - "Ghost of a Chance": "Ghost of a Chance: Victory", - "Piercing the Shroud": "Piercing the Shroud: Victory", - "Whispers of Doom": "Whispers of Doom: Victory", - "Belly of the Beast": "Belly of the Beast: Victory", - "Zero Hour": "Zero Hour: First Group Rescued", - "Evacuation": "Evacuation: Reach Hanson", - "Devil's Playground": "Devil's Playground: Tosh's Miners", - "Smash and Grab": "Smash and Grab: First Relic", - "The Great Train Robbery": "The Great Train Robbery: North Defiler" -} - - -alt_final_mission_locations = { - "Maw of the Void": "Maw of the Void: Victory", - "Engine of Destruction": "Engine of Destruction: Victory", - "Supernova": "Supernova: Victory", - "Gates of Hell": "Gates of Hell: Victory", - "Shatter the Sky": "Shatter the Sky: Victory" -} \ No newline at end of file diff --git a/worlds/sc2wol/Options.py b/worlds/sc2wol/Options.py deleted file mode 100644 index e4b6a74066..0000000000 --- a/worlds/sc2wol/Options.py +++ /dev/null @@ -1,362 +0,0 @@ -from typing import Dict, FrozenSet, Union -from BaseClasses import MultiWorld -from Options import Choice, Option, Toggle, DefaultOnToggle, ItemSet, OptionSet, Range -from .MissionTables import vanilla_mission_req_table - -ORDER_VANILLA = 0 -ORDER_VANILLA_SHUFFLED = 1 - -class GameDifficulty(Choice): - """ - The difficulty of the campaign, affects enemy AI, starting units, and game speed. - - For those unfamiliar with the Archipelago randomizer, the recommended settings are one difficulty level - lower than the vanilla game - """ - display_name = "Game Difficulty" - option_casual = 0 - option_normal = 1 - option_hard = 2 - option_brutal = 3 - default = 1 - -class GameSpeed(Choice): - """Optional setting to override difficulty-based game speed.""" - display_name = "Game Speed" - option_default = 0 - option_slower = 1 - option_slow = 2 - option_normal = 3 - option_fast = 4 - option_faster = 5 - default = option_default - -class FinalMap(Choice): - """ - Determines if the final map and goal of the campaign. - All in: You need to beat All-in map - Random Hard: A random hard mission is selected as a goal. - Beat this mission in order to complete the game. - All-in map won't be in the campaign - - Vanilla mission order always ends with All in mission! - - Warning: Using All-in with a short mission order (7 or fewer missions) is not recommended, - as there might not be enough locations to place all the required items, - any excess required items will be placed into the player's starting inventory! - - This option is short-lived. It may be changed in the future - """ - display_name = "Final Map" - option_all_in = 0 - option_random_hard = 1 - -class AllInMap(Choice): - """Determines what version of All-In (final map) that will be generated for the campaign.""" - display_name = "All In Map" - option_ground = 0 - option_air = 1 - - -class MissionOrder(Choice): - """ - Determines the order the missions are played in. The last three mission orders end in a random mission. - Vanilla (29): Keeps the standard mission order and branching from the WoL Campaign. - Vanilla Shuffled (29): Keeps same branching paths from the WoL Campaign but randomizes the order of missions within. - Mini Campaign (15): Shorter version of the campaign with randomized missions and optional branches. - Grid (16): A 4x4 grid of random missions. Start at the top-left and forge a path towards bottom-right mission to win. - Mini Grid (9): A 3x3 version of Grid. Complete the bottom-right mission to win. - Blitz (12): 12 random missions that open up very quickly. Complete the bottom-right mission to win. - Gauntlet (7): Linear series of 7 random missions to complete the campaign. - Mini Gauntlet (4): Linear series of 4 random missions to complete the campaign. - Tiny Grid (4): A 2x2 version of Grid. Complete the bottom-right mission to win. - """ - display_name = "Mission Order" - option_vanilla = 0 - option_vanilla_shuffled = 1 - option_mini_campaign = 2 - option_grid = 3 - option_mini_grid = 4 - option_blitz = 5 - option_gauntlet = 6 - option_mini_gauntlet = 7 - option_tiny_grid = 8 - - -class PlayerColor(Choice): - """Determines in-game team color.""" - display_name = "Player Color" - option_white = 0 - option_red = 1 - option_blue = 2 - option_teal = 3 - option_purple = 4 - option_yellow = 5 - option_orange = 6 - option_green = 7 - option_light_pink = 8 - option_violet = 9 - option_light_grey = 10 - option_dark_green = 11 - option_brown = 12 - option_light_green = 13 - option_dark_grey = 14 - option_pink = 15 - option_rainbow = 16 - option_default = 17 - default = option_default - - -class ShuffleProtoss(DefaultOnToggle): - """Determines if the 3 protoss missions are included in the shuffle if Vanilla mission order is not enabled. - If turned off, the 3 protoss missions will not appear and Protoss units are removed from the pool.""" - display_name = "Shuffle Protoss Missions" - - -class ShuffleNoBuild(DefaultOnToggle): - """Determines if the 5 no-build missions are included in the shuffle if Vanilla mission order is not enabled. - If turned off, the 5 no-build missions will not appear.""" - display_name = "Shuffle No-Build Missions" - - -class EarlyUnit(DefaultOnToggle): - """ - Guarantees that the first mission will contain a unit. - - Each mission available to be the first mission has a pre-defined location where the unit should spawn. - This location gets overriden over any exclusion. It's guaranteed to be reachable with an empty inventory. - """ - display_name = "Early Unit" - - -class RequiredTactics(Choice): - """Determines the maximum tactical difficulty of the seed (separate from mission difficulty). Higher settings - increase randomness. - - Standard: All missions can be completed with good micro and macro. - Advanced: Completing missions may require relying on starting units and micro-heavy units. - No Logic: Units and upgrades may be placed anywhere. LIKELY TO RENDER THE RUN IMPOSSIBLE ON HARDER DIFFICULTIES!""" - display_name = "Required Tactics" - option_standard = 0 - option_advanced = 1 - option_no_logic = 2 - - -class UnitsAlwaysHaveUpgrades(DefaultOnToggle): - """ - If turned on, all upgrades will be present for each unit and structure in the seed. - This usually results in fewer units. - - See also: Max Number of Upgrades - """ - display_name = "Units Always Have Upgrades" - - -class GenericUpgradeMissions(Range): - """Determines the percentage of missions in the mission order that must be completed before - level 1 of all weapon and armor upgrades is unlocked. Level 2 upgrades require double the amount of missions, - and level 3 requires triple the amount. The required amounts are always rounded down. - If set to 0, upgrades are instead added to the item pool and must be found to be used.""" - display_name = "Generic Upgrade Missions" - range_start = 0 - range_end = 100 - default = 0 - - -class GenericUpgradeResearch(Choice): - """Determines how weapon and armor upgrades affect missions once unlocked. - - Vanilla: Upgrades must be researched as normal. - Auto In No-Build: In No-Build missions, upgrades are automatically researched. - In all other missions, upgrades must be researched as normal. - Auto In Build: In No-Build missions, upgrades are unavailable as normal. - In all other missions, upgrades are automatically researched. - Always Auto: Upgrades are automatically researched in all missions.""" - display_name = "Generic Upgrade Research" - option_vanilla = 0 - option_auto_in_no_build = 1 - option_auto_in_build = 2 - option_always_auto = 3 - - -class GenericUpgradeItems(Choice): - """Determines how weapon and armor upgrades are split into items. All options produce 3 levels of each item. - Does nothing if upgrades are unlocked by completed mission counts. - - Individual Items: All weapon and armor upgrades are each an item, - resulting in 18 total upgrade items. - Bundle Weapon And Armor: All types of weapon upgrades are one item, - and all types of armor upgrades are one item, - resulting in 6 total items. - Bundle Unit Class: Weapon and armor upgrades are merged, - but Infantry, Vehicle, and Starship upgrades are bundled separately, - resulting in 9 total items. - Bundle All: All weapon and armor upgrades are one item, - resulting in 3 total items.""" - display_name = "Generic Upgrade Items" - option_individual_items = 0 - option_bundle_weapon_and_armor = 1 - option_bundle_unit_class = 2 - option_bundle_all = 3 - - -class NovaCovertOpsItems(Toggle): - """If turned on, the equipment upgrades from Nova Covert Ops may be present in the world.""" - display_name = "Nova Covert Ops Items" - default = Toggle.option_true - - -class BroodWarItems(Toggle): - """If turned on, returning items from StarCraft: Brood War may appear in the world.""" - display_name = "Brood War Items" - default = Toggle.option_true - - -class ExtendedItems(Toggle): - """If turned on, original items that did not appear in Campaign mode may appear in the world.""" - display_name = "Extended Items" - default = Toggle.option_true - - -class MaxNumberOfUpgrades(Range): - """ - Set a maximum to the number of upgrades a unit/structure can have. -1 is used to define unlimited. - Note that most unit have 4 or 6 upgrades. - - If used with Units Always Have Upgrades, each unit has this given amount of upgrades (if there enough upgrades exist) - - See also: Units Always Have Upgrades - """ - display_name = "Maximum number of upgrades per unit/structure" - range_start = -1 - # Do not know the maximum, but it is less than 123! - range_end = 123 - default = -1 - - -class LockedItems(ItemSet): - """Guarantees that these items will be unlockable""" - display_name = "Locked Items" - - -class ExcludedItems(ItemSet): - """Guarantees that these items will not be unlockable""" - display_name = "Excluded Items" - - -class ExcludedMissions(OptionSet): - """Guarantees that these missions will not appear in the campaign - Doesn't apply to vanilla mission order. - It may be impossible to build a valid campaign if too many missions are excluded.""" - display_name = "Excluded Missions" - valid_keys = {mission_name for mission_name in vanilla_mission_req_table.keys() if mission_name != 'All-In'} - - -class LocationInclusion(Choice): - option_enabled = 0 - option_trash = 1 - option_nothing = 2 - - -class MissionProgressLocations(LocationInclusion): - """ - Enables or disables item rewards for progressing (not finishing) a mission. - Progressing a mission is usually a task of completing or progressing into a main objective. - Clearing an expansion base also counts here. - - Enabled: All locations fitting into this do their normal rewards - Trash: Forces a trash item in - Nothing: No rewards for this type of tasks, effectively disabling such locations - - Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. - See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) - """ - display_name = "Mission Progress Locations" - - -class BonusLocations(LocationInclusion): - """ - Enables or disables item rewards for completing bonus tasks. - Bonus tasks are those giving you a campaign-wide or mission-wide bonus in vanilla game: - Research, credits, bonus units or resources, etc. - - Enabled: All locations fitting into this do their normal rewards - Trash: Forces a trash item in - Nothing: No rewards for this type of tasks, effectively disabling such locations - - Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. - See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) - """ - display_name = "Bonus Locations" - - -class ChallengeLocations(LocationInclusion): - """ - Enables or disables item rewards for completing challenge tasks. - Challenges are tasks that have usually higher requirements to be completed - than to complete the mission they're in successfully. - You might be required to visit the same mission later when getting stronger in order to finish these tasks. - - Enabled: All locations fitting into this do their normal rewards - Trash: Forces a trash item in - Nothing: No rewards for this type of tasks, effectively disabling such locations - - Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. - See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) - """ - display_name = "Challenge Locations" - - -class OptionalBossLocations(LocationInclusion): - """ - Enables or disables item rewards for defeating optional bosses. - An optional boss is any boss that's not required to kill in order to finish the mission successfully. - All Brutalisks, Loki, etc. belongs here. - - Enabled: All locations fitting into this do their normal rewards - Trash: Forces a trash item in - Nothing: No rewards for this type of tasks, effectively disabling such locations - - Note: Individual locations subject to plando are always enabled, so the plando can be placed properly. - See also: Excluded Locations, Item Plando (https://archipelago.gg/tutorial/Archipelago/plando/en#item-plando) - """ - display_name = "Optional Boss Locations" - - -# noinspection PyTypeChecker -sc2wol_options: Dict[str, Option] = { - "game_difficulty": GameDifficulty, - "game_speed": GameSpeed, - "all_in_map": AllInMap, - "final_map": FinalMap, - "mission_order": MissionOrder, - "player_color": PlayerColor, - "shuffle_protoss": ShuffleProtoss, - "shuffle_no_build": ShuffleNoBuild, - "early_unit": EarlyUnit, - "required_tactics": RequiredTactics, - "units_always_have_upgrades": UnitsAlwaysHaveUpgrades, - "max_number_of_upgrades": MaxNumberOfUpgrades, - "generic_upgrade_missions": GenericUpgradeMissions, - "generic_upgrade_research": GenericUpgradeResearch, - "generic_upgrade_items": GenericUpgradeItems, - "locked_items": LockedItems, - "excluded_items": ExcludedItems, - "excluded_missions": ExcludedMissions, - "nco_items": NovaCovertOpsItems, - "bw_items": BroodWarItems, - "ext_items": ExtendedItems, - "mission_progress_locations": MissionProgressLocations, - "bonus_locations": BonusLocations, - "challenge_locations": ChallengeLocations, - "optional_boss_locations": OptionalBossLocations -} - - -def get_option_value(multiworld: MultiWorld, player: int, name: str) -> Union[int, FrozenSet]: - if multiworld is None: - return sc2wol_options[name].default - - player_option = getattr(multiworld, name)[player] - - return player_option.value diff --git a/worlds/sc2wol/PoolFilter.py b/worlds/sc2wol/PoolFilter.py deleted file mode 100644 index 23422a3d1e..0000000000 --- a/worlds/sc2wol/PoolFilter.py +++ /dev/null @@ -1,367 +0,0 @@ -from typing import Callable, Dict, List, Set -from BaseClasses import MultiWorld, ItemClassification, Item, Location -from .Items import get_full_item_list, spider_mine_sources, second_pass_placeable_items, filler_items, \ - progressive_if_nco -from .MissionTables import no_build_regions_list, easy_regions_list, medium_regions_list, hard_regions_list,\ - mission_orders, MissionInfo, alt_final_mission_locations, MissionPools -from .Options import get_option_value, MissionOrder, FinalMap, MissionProgressLocations, LocationInclusion -from .LogicMixin import SC2WoLLogic - -# Items with associated upgrades -UPGRADABLE_ITEMS = [ - "Marine", "Medic", "Firebat", "Marauder", "Reaper", "Ghost", "Spectre", - "Hellion", "Vulture", "Goliath", "Diamondback", "Siege Tank", "Thor", "Predator", "Widow Mine", "Cyclone", - "Medivac", "Wraith", "Viking", "Banshee", "Battlecruiser", "Raven", "Science Vessel", "Liberator", "Valkyrie", - "Bunker", "Missile Turret" -] - -BARRACKS_UNITS = {"Marine", "Medic", "Firebat", "Marauder", "Reaper", "Ghost", "Spectre"} -FACTORY_UNITS = {"Hellion", "Vulture", "Goliath", "Diamondback", "Siege Tank", "Thor", "Predator", "Widow Mine", "Cyclone"} -STARPORT_UNITS = {"Medivac", "Wraith", "Viking", "Banshee", "Battlecruiser", "Hercules", "Science Vessel", "Raven", "Liberator", "Valkyrie"} - -PROTOSS_REGIONS = {"A Sinister Turn", "Echoes of the Future", "In Utter Darkness"} - - -def filter_missions(multiworld: MultiWorld, player: int) -> Dict[int, List[str]]: - """ - Returns a semi-randomly pruned tuple of no-build, easy, medium, and hard mission sets - """ - - mission_order_type = get_option_value(multiworld, player, "mission_order") - shuffle_no_build = get_option_value(multiworld, player, "shuffle_no_build") - shuffle_protoss = get_option_value(multiworld, player, "shuffle_protoss") - excluded_missions = get_option_value(multiworld, player, "excluded_missions") - final_map = get_option_value(multiworld, player, "final_map") - mission_pools = { - MissionPools.STARTER: no_build_regions_list[:], - MissionPools.EASY: easy_regions_list[:], - MissionPools.MEDIUM: medium_regions_list[:], - MissionPools.HARD: hard_regions_list[:], - MissionPools.FINAL: [] - } - if mission_order_type == MissionOrder.option_vanilla: - # Vanilla uses the entire mission pool - mission_pools[MissionPools.FINAL] = ['All-In'] - return mission_pools - # Omitting No-Build missions if not shuffling no-build - if not shuffle_no_build: - excluded_missions = excluded_missions.union(no_build_regions_list) - # Omitting Protoss missions if not shuffling protoss - if not shuffle_protoss: - excluded_missions = excluded_missions.union(PROTOSS_REGIONS) - # Replacing All-In with alternate ending depending on option - if final_map == FinalMap.option_random_hard: - final_mission = multiworld.random.choice([mission for mission in alt_final_mission_locations.keys() if mission not in excluded_missions]) - excluded_missions.add(final_mission) - else: - final_mission = 'All-In' - # Excluding missions - for difficulty, mission_pool in mission_pools.items(): - mission_pools[difficulty] = [mission for mission in mission_pool if mission not in excluded_missions] - mission_pools[MissionPools.FINAL].append(final_mission) - # Mission pool changes on Build-Only - if not get_option_value(multiworld, player, 'shuffle_no_build'): - def move_mission(mission_name, current_pool, new_pool): - if mission_name in mission_pools[current_pool]: - mission_pools[current_pool].remove(mission_name) - mission_pools[new_pool].append(mission_name) - # Replacing No Build missions with Easy missions - move_mission("Zero Hour", MissionPools.EASY, MissionPools.STARTER) - move_mission("Evacuation", MissionPools.EASY, MissionPools.STARTER) - move_mission("Devil's Playground", MissionPools.EASY, MissionPools.STARTER) - # Pushing Outbreak to Normal, as it cannot be placed as the second mission on Build-Only - move_mission("Outbreak", MissionPools.EASY, MissionPools.MEDIUM) - # Pushing extra Normal missions to Easy - move_mission("The Great Train Robbery", MissionPools.MEDIUM, MissionPools.EASY) - move_mission("Echoes of the Future", MissionPools.MEDIUM, MissionPools.EASY) - move_mission("Cutthroat", MissionPools.MEDIUM, MissionPools.EASY) - # Additional changes on Advanced Tactics - if get_option_value(multiworld, player, "required_tactics") > 0: - move_mission("The Great Train Robbery", MissionPools.EASY, MissionPools.STARTER) - move_mission("Smash and Grab", MissionPools.EASY, MissionPools.STARTER) - move_mission("Moebius Factor", MissionPools.MEDIUM, MissionPools.EASY) - move_mission("Welcome to the Jungle", MissionPools.MEDIUM, MissionPools.EASY) - move_mission("Engine of Destruction", MissionPools.HARD, MissionPools.MEDIUM) - - return mission_pools - - -def get_item_upgrades(inventory: List[Item], parent_item: Item or str): - item_name = parent_item.name if isinstance(parent_item, Item) else parent_item - return [ - inv_item for inv_item in inventory - if get_full_item_list()[inv_item.name].parent_item == item_name - ] - - -def get_item_quantity(item: Item, multiworld: MultiWorld, player: int): - if (not get_option_value(multiworld, player, "nco_items")) \ - and item.name in progressive_if_nco: - return 1 - return get_full_item_list()[item.name].quantity - - -def copy_item(item: Item): - return Item(item.name, item.classification, item.code, item.player) - - -class ValidInventory: - - def has(self, item: str, player: int): - return item in self.logical_inventory - - def has_any(self, items: Set[str], player: int): - return any(item in self.logical_inventory for item in items) - - def has_all(self, items: Set[str], player: int): - return all(item in self.logical_inventory for item in items) - - def has_units_per_structure(self) -> bool: - return len(BARRACKS_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure and \ - len(FACTORY_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure and \ - len(STARPORT_UNITS.intersection(self.logical_inventory)) > self.min_units_per_structure - - def generate_reduced_inventory(self, inventory_size: int, mission_requirements: List[Callable]) -> List[Item]: - """Attempts to generate a reduced inventory that can fulfill the mission requirements.""" - inventory = list(self.item_pool) - locked_items = list(self.locked_items) - self.logical_inventory = { - item.name for item in inventory + locked_items + self.existing_items - if item.classification in (ItemClassification.progression, ItemClassification.progression_skip_balancing) - } - requirements = mission_requirements - cascade_keys = self.cascade_removal_map.keys() - units_always_have_upgrades = get_option_value(self.multiworld, self.player, "units_always_have_upgrades") - - def attempt_removal(item: Item) -> bool: - # If item can be removed and has associated items, remove them as well - inventory.remove(item) - # Only run logic checks when removing logic items - if item.name in self.logical_inventory: - self.logical_inventory.remove(item.name) - if not all(requirement(self) for requirement in requirements): - # If item cannot be removed, lock or revert - self.logical_inventory.add(item.name) - for _ in range(get_item_quantity(item, self.multiworld, self.player)): - locked_items.append(copy_item(item)) - return False - return True - - # Limit the maximum number of upgrades - maxUpgrad = get_option_value(self.multiworld, self.player, - "max_number_of_upgrades") - if maxUpgrad != -1: - unit_avail_upgrades = {} - # Needed to take into account locked/existing items - unit_nb_upgrades = {} - for item in inventory: - cItem = get_full_item_list()[item.name] - if cItem.type in UPGRADABLE_ITEMS and item.name not in unit_avail_upgrades: - unit_avail_upgrades[item.name] = [] - unit_nb_upgrades[item.name] = 0 - elif cItem.parent_item is not None: - if cItem.parent_item not in unit_avail_upgrades: - unit_avail_upgrades[cItem.parent_item] = [item] - unit_nb_upgrades[cItem.parent_item] = 1 - else: - unit_avail_upgrades[cItem.parent_item].append(item) - unit_nb_upgrades[cItem.parent_item] += 1 - # For those two categories, we count them but dont include them in removal - for item in locked_items + self.existing_items: - cItem = get_full_item_list()[item.name] - if cItem.type in UPGRADABLE_ITEMS and item.name not in unit_avail_upgrades: - unit_avail_upgrades[item.name] = [] - unit_nb_upgrades[item.name] = 0 - elif cItem.parent_item is not None: - if cItem.parent_item not in unit_avail_upgrades: - unit_nb_upgrades[cItem.parent_item] = 1 - else: - unit_nb_upgrades[cItem.parent_item] += 1 - # Making sure that the upgrades being removed is random - # Currently, only for combat shield vs Stabilizer Medpacks... - shuffled_unit_upgrade_list = list(unit_avail_upgrades.keys()) - self.multiworld.random.shuffle(shuffled_unit_upgrade_list) - for unit in shuffled_unit_upgrade_list: - while (unit_nb_upgrades[unit] > maxUpgrad) \ - and (len(unit_avail_upgrades[unit]) > 0): - itemCandidate = self.multiworld.random.choice(unit_avail_upgrades[unit]) - _ = attempt_removal(itemCandidate) - # Whatever it succeed to remove the iventory or it fails and thus - # lock it, the upgrade is no longer available for removal - unit_avail_upgrades[unit].remove(itemCandidate) - unit_nb_upgrades[unit] -= 1 - - # Locking associated items for items that have already been placed when units_always_have_upgrades is on - if units_always_have_upgrades: - existing_items = set(self.existing_items[:] + locked_items) - while existing_items: - existing_item = existing_items.pop() - items_to_lock = self.cascade_removal_map.get(existing_item, [existing_item]) - if get_full_item_list()[existing_item.name].type != "Upgrade": - # Don't process general upgrades, they may have been pre-locked per-level - for item in items_to_lock: - if item in inventory: - item_quantity = inventory.count(item) - # Unit upgrades, lock all levels - for _ in range(item_quantity): - inventory.remove(item) - if item not in locked_items: - # Lock all the associated items if not already locked - for _ in range(item_quantity): - locked_items.append(copy_item(item)) - if item in existing_items: - existing_items.remove(item) - - if self.min_units_per_structure > 0 and self.has_units_per_structure(): - requirements.append(lambda state: state.has_units_per_structure()) - - # Determining if the full-size inventory can complete campaign - if not all(requirement(self) for requirement in requirements): - raise Exception("Too many items excluded - campaign is impossible to complete.") - - while len(inventory) + len(locked_items) > inventory_size: - if len(inventory) == 0: - # There are more items than locations and all of them are already locked due to YAML or logic. - # Random items from locked ones will go to starting items - self.multiworld.random.shuffle(locked_items) - while len(locked_items) > inventory_size: - item: Item = locked_items.pop() - self.multiworld.push_precollected(item) - break - # Select random item from removable items - item = self.multiworld.random.choice(inventory) - # Cascade removals to associated items - if item in cascade_keys: - items_to_remove = self.cascade_removal_map[item] - transient_items = [] - cascade_failure = False - while len(items_to_remove) > 0: - item_to_remove = items_to_remove.pop() - transient_items.append(item_to_remove) - if item_to_remove not in inventory: - if units_always_have_upgrades and item_to_remove in locked_items: - cascade_failure = True - break - else: - continue - success = attempt_removal(item_to_remove) - if not success and units_always_have_upgrades: - cascade_failure = True - transient_items += items_to_remove - break - # Lock all associated items if any of them cannot be removed on Units Always Have Upgrades - if cascade_failure: - for transient_item in transient_items: - if transient_item in inventory: - for _ in range(inventory.count(transient_item)): - inventory.remove(transient_item) - if transient_item not in locked_items: - for _ in range(get_item_quantity(transient_item, self.multiworld, self.player)): - locked_items.append(copy_item(transient_item)) - if transient_item.classification in (ItemClassification.progression, ItemClassification.progression_skip_balancing): - self.logical_inventory.add(transient_item.name) - else: - attempt_removal(item) - - if not spider_mine_sources & self.logical_inventory: - inventory = [item for item in inventory if not item.name.endswith("(Spider Mine)")] - if not BARRACKS_UNITS & self.logical_inventory: - inventory = [item for item in inventory if - not (item.name.startswith("Progressive Infantry") or item.name == "Orbital Strike")] - if not FACTORY_UNITS & self.logical_inventory: - inventory = [item for item in inventory if not item.name.startswith("Progressive Vehicle")] - if not STARPORT_UNITS & self.logical_inventory: - inventory = [item for item in inventory if not item.name.startswith("Progressive Ship")] - - # Cull finished, adding locked items back into inventory - inventory += locked_items - - # Replacing empty space with generically useful items - replacement_items = [item for item in self.item_pool - if (item not in inventory - and item not in self.locked_items - and item.name in second_pass_placeable_items)] - self.multiworld.random.shuffle(replacement_items) - while len(inventory) < inventory_size and len(replacement_items) > 0: - item = replacement_items.pop() - inventory.append(item) - - return inventory - - def _read_logic(self): - self._sc2wol_has_common_unit = lambda world, player: SC2WoLLogic._sc2wol_has_common_unit(self, world, player) - self._sc2wol_has_air = lambda world, player: SC2WoLLogic._sc2wol_has_air(self, world, player) - self._sc2wol_has_air_anti_air = lambda world, player: SC2WoLLogic._sc2wol_has_air_anti_air(self, world, player) - self._sc2wol_has_competent_anti_air = lambda world, player: SC2WoLLogic._sc2wol_has_competent_anti_air(self, world, player) - self._sc2wol_has_competent_ground_to_air = lambda world, player: SC2WoLLogic._sc2wol_has_competent_ground_to_air(self, world, player) - self._sc2wol_has_anti_air = lambda world, player: SC2WoLLogic._sc2wol_has_anti_air(self, world, player) - self._sc2wol_defense_rating = lambda world, player, zerg_enemy, air_enemy=False: SC2WoLLogic._sc2wol_defense_rating(self, world, player, zerg_enemy, air_enemy) - self._sc2wol_has_competent_comp = lambda world, player: SC2WoLLogic._sc2wol_has_competent_comp(self, world, player) - self._sc2wol_has_train_killers = lambda world, player: SC2WoLLogic._sc2wol_has_train_killers(self, world, player) - self._sc2wol_able_to_rescue = lambda world, player: SC2WoLLogic._sc2wol_able_to_rescue(self, world, player) - self._sc2wol_beats_protoss_deathball = lambda world, player: SC2WoLLogic._sc2wol_beats_protoss_deathball(self, world, player) - self._sc2wol_survives_rip_field = lambda world, player: SC2WoLLogic._sc2wol_survives_rip_field(self, world, player) - self._sc2wol_has_protoss_common_units = lambda world, player: SC2WoLLogic._sc2wol_has_protoss_common_units(self, world, player) - self._sc2wol_has_protoss_medium_units = lambda world, player: SC2WoLLogic._sc2wol_has_protoss_medium_units(self, world, player) - self._sc2wol_has_mm_upgrade = lambda world, player: SC2WoLLogic._sc2wol_has_mm_upgrade(self, world, player) - self._sc2wol_welcome_to_the_jungle_requirement = lambda world, player: SC2WoLLogic._sc2wol_welcome_to_the_jungle_requirement(self, world, player) - self._sc2wol_can_respond_to_colony_infestations = lambda world, player: SC2WoLLogic._sc2wol_can_respond_to_colony_infestations(self, world, player) - self._sc2wol_final_mission_requirements = lambda world, player: SC2WoLLogic._sc2wol_final_mission_requirements(self, world, player) - - def __init__(self, multiworld: MultiWorld, player: int, - item_pool: List[Item], existing_items: List[Item], locked_items: List[Item], - has_protoss: bool): - self.multiworld = multiworld - self.player = player - self.logical_inventory = set() - self.locked_items = locked_items[:] - self.existing_items = existing_items - self._read_logic() - # Initial filter of item pool - self.item_pool = [] - item_quantities: dict[str, int] = dict() - # Inventory restrictiveness based on number of missions with checks - mission_order_type = get_option_value(self.multiworld, self.player, "mission_order") - mission_count = len(mission_orders[mission_order_type]) - 1 - self.min_units_per_structure = int(mission_count / 7) - min_upgrades = 1 if mission_count < 10 else 2 - for item in item_pool: - item_info = get_full_item_list()[item.name] - if item_info.type == "Upgrade": - # Locking upgrades based on mission duration - if item.name not in item_quantities: - item_quantities[item.name] = 0 - item_quantities[item.name] += 1 - if item_quantities[item.name] < min_upgrades: - self.locked_items.append(item) - else: - self.item_pool.append(item) - elif item_info.type == "Goal": - locked_items.append(item) - elif item_info.type != "Protoss" or has_protoss: - self.item_pool.append(item) - self.cascade_removal_map: Dict[Item, List[Item]] = dict() - for item in self.item_pool + locked_items + existing_items: - if item.name in UPGRADABLE_ITEMS: - upgrades = get_item_upgrades(self.item_pool, item) - associated_items = [*upgrades, item] - self.cascade_removal_map[item] = associated_items - if get_option_value(multiworld, player, "units_always_have_upgrades"): - for upgrade in upgrades: - self.cascade_removal_map[upgrade] = associated_items - - -def filter_items(multiworld: MultiWorld, player: int, mission_req_table: Dict[str, MissionInfo], location_cache: List[Location], - item_pool: List[Item], existing_items: List[Item], locked_items: List[Item]) -> List[Item]: - """ - Returns a semi-randomly pruned set of items based on number of available locations. - The returned inventory must be capable of logically accessing every location in the world. - """ - open_locations = [location for location in location_cache if location.item is None] - inventory_size = len(open_locations) - has_protoss = bool(PROTOSS_REGIONS.intersection(mission_req_table.keys())) - mission_requirements = [location.access_rule for location in location_cache] - valid_inventory = ValidInventory(multiworld, player, item_pool, existing_items, locked_items, has_protoss) - - valid_items = valid_inventory.generate_reduced_inventory(inventory_size, mission_requirements) - return valid_items diff --git a/worlds/sc2wol/Regions.py b/worlds/sc2wol/Regions.py deleted file mode 100644 index f588ce7e98..0000000000 --- a/worlds/sc2wol/Regions.py +++ /dev/null @@ -1,313 +0,0 @@ -from typing import List, Set, Dict, Tuple, Optional, Callable -from BaseClasses import MultiWorld, Region, Entrance, Location -from .Locations import LocationData -from .Options import get_option_value, MissionOrder -from .MissionTables import MissionInfo, mission_orders, vanilla_mission_req_table, alt_final_mission_locations, \ - MissionPools, vanilla_shuffle_order -from .PoolFilter import filter_missions - -PROPHECY_CHAIN_MISSION_COUNT = 4 - -VANILLA_SHUFFLED_FIRST_PROPHECY_MISSION = 21 - -def create_regions(multiworld: MultiWorld, player: int, locations: Tuple[LocationData, ...], location_cache: List[Location])\ - -> Tuple[Dict[str, MissionInfo], int, str]: - locations_per_region = get_locations_per_region(locations) - - mission_order_type = get_option_value(multiworld, player, "mission_order") - mission_order = mission_orders[mission_order_type] - - mission_pools = filter_missions(multiworld, player) - - regions = [create_region(multiworld, player, locations_per_region, location_cache, "Menu")] - - names: Dict[str, int] = {} - - if mission_order_type == MissionOrder.option_vanilla: - - # Generating all regions and locations - for region_name in vanilla_mission_req_table.keys(): - regions.append(create_region(multiworld, player, locations_per_region, location_cache, region_name)) - multiworld.regions += regions - - connect(multiworld, player, names, 'Menu', 'Liberation Day'), - connect(multiworld, player, names, 'Liberation Day', 'The Outlaws', - lambda state: state.has("Beat Liberation Day", player)), - connect(multiworld, player, names, 'The Outlaws', 'Zero Hour', - lambda state: state.has("Beat The Outlaws", player)), - connect(multiworld, player, names, 'Zero Hour', 'Evacuation', - lambda state: state.has("Beat Zero Hour", player)), - connect(multiworld, player, names, 'Evacuation', 'Outbreak', - lambda state: state.has("Beat Evacuation", player)), - connect(multiworld, player, names, "Outbreak", "Safe Haven", - lambda state: state._sc2wol_cleared_missions(multiworld, player, 7) and - state.has("Beat Outbreak", player)), - connect(multiworld, player, names, "Outbreak", "Haven's Fall", - lambda state: state._sc2wol_cleared_missions(multiworld, player, 7) and - state.has("Beat Outbreak", player)), - connect(multiworld, player, names, 'Zero Hour', 'Smash and Grab', - lambda state: state.has("Beat Zero Hour", player)), - connect(multiworld, player, names, 'Smash and Grab', 'The Dig', - lambda state: state._sc2wol_cleared_missions(multiworld, player, 8) and - state.has("Beat Smash and Grab", player)), - connect(multiworld, player, names, 'The Dig', 'The Moebius Factor', - lambda state: state._sc2wol_cleared_missions(multiworld, player, 11) and - state.has("Beat The Dig", player)), - connect(multiworld, player, names, 'The Moebius Factor', 'Supernova', - lambda state: state._sc2wol_cleared_missions(multiworld, player, 14) and - state.has("Beat The Moebius Factor", player)), - connect(multiworld, player, names, 'Supernova', 'Maw of the Void', - lambda state: state.has("Beat Supernova", player)), - connect(multiworld, player, names, 'Zero Hour', "Devil's Playground", - lambda state: state._sc2wol_cleared_missions(multiworld, player, 4) and - state.has("Beat Zero Hour", player)), - connect(multiworld, player, names, "Devil's Playground", 'Welcome to the Jungle', - lambda state: state.has("Beat Devil's Playground", player)), - connect(multiworld, player, names, "Welcome to the Jungle", 'Breakout', - lambda state: state._sc2wol_cleared_missions(multiworld, player, 8) and - state.has("Beat Welcome to the Jungle", player)), - connect(multiworld, player, names, "Welcome to the Jungle", 'Ghost of a Chance', - lambda state: state._sc2wol_cleared_missions(multiworld, player, 8) and - state.has("Beat Welcome to the Jungle", player)), - connect(multiworld, player, names, "Zero Hour", 'The Great Train Robbery', - lambda state: state._sc2wol_cleared_missions(multiworld, player, 6) and - state.has("Beat Zero Hour", player)), - connect(multiworld, player, names, 'The Great Train Robbery', 'Cutthroat', - lambda state: state.has("Beat The Great Train Robbery", player)), - connect(multiworld, player, names, 'Cutthroat', 'Engine of Destruction', - lambda state: state.has("Beat Cutthroat", player)), - connect(multiworld, player, names, 'Engine of Destruction', 'Media Blitz', - lambda state: state.has("Beat Engine of Destruction", player)), - connect(multiworld, player, names, 'Media Blitz', 'Piercing the Shroud', - lambda state: state.has("Beat Media Blitz", player)), - connect(multiworld, player, names, 'The Dig', 'Whispers of Doom', - lambda state: state.has("Beat The Dig", player)), - connect(multiworld, player, names, 'Whispers of Doom', 'A Sinister Turn', - lambda state: state.has("Beat Whispers of Doom", player)), - connect(multiworld, player, names, 'A Sinister Turn', 'Echoes of the Future', - lambda state: state.has("Beat A Sinister Turn", player)), - connect(multiworld, player, names, 'Echoes of the Future', 'In Utter Darkness', - lambda state: state.has("Beat Echoes of the Future", player)), - connect(multiworld, player, names, 'Maw of the Void', 'Gates of Hell', - lambda state: state.has("Beat Maw of the Void", player)), - connect(multiworld, player, names, 'Gates of Hell', 'Belly of the Beast', - lambda state: state.has("Beat Gates of Hell", player)), - connect(multiworld, player, names, 'Gates of Hell', 'Shatter the Sky', - lambda state: state.has("Beat Gates of Hell", player)), - connect(multiworld, player, names, 'Gates of Hell', 'All-In', - lambda state: state.has('Beat Gates of Hell', player) and ( - state.has('Beat Shatter the Sky', player) or state.has('Beat Belly of the Beast', player))) - - return vanilla_mission_req_table, 29, 'All-In: Victory' - - else: - missions = [] - - remove_prophecy = mission_order_type == 1 and not get_option_value(multiworld, player, "shuffle_protoss") - - final_mission = mission_pools[MissionPools.FINAL][0] - - # Determining if missions must be removed - mission_pool_size = sum(len(mission_pool) for mission_pool in mission_pools.values()) - removals = len(mission_order) - mission_pool_size - # Removing entire Prophecy chain on vanilla shuffled when not shuffling protoss - if remove_prophecy: - removals -= PROPHECY_CHAIN_MISSION_COUNT - - # Initial fill out of mission list and marking all-in mission - for mission in mission_order: - # Removing extra missions if mission pool is too small - # Also handle lower removal priority than Prophecy - if 0 < mission.removal_priority <= removals or mission.category == 'Prophecy' and remove_prophecy \ - or (remove_prophecy and mission_order_type == MissionOrder.option_vanilla_shuffled - and mission.removal_priority > vanilla_shuffle_order[ - VANILLA_SHUFFLED_FIRST_PROPHECY_MISSION].removal_priority - and 0 < mission.removal_priority <= removals + PROPHECY_CHAIN_MISSION_COUNT): - missions.append(None) - elif mission.type == MissionPools.FINAL: - missions.append(final_mission) - else: - missions.append(mission.type) - - no_build_slots = [] - easy_slots = [] - medium_slots = [] - hard_slots = [] - - # Search through missions to find slots needed to fill - for i in range(len(missions)): - if missions[i] is None: - continue - if missions[i] == MissionPools.STARTER: - no_build_slots.append(i) - elif missions[i] == MissionPools.EASY: - easy_slots.append(i) - elif missions[i] == MissionPools.MEDIUM: - medium_slots.append(i) - elif missions[i] == MissionPools.HARD: - hard_slots.append(i) - - # Add no_build missions to the pool and fill in no_build slots - missions_to_add = mission_pools[MissionPools.STARTER] - if len(no_build_slots) > len(missions_to_add): - raise Exception("There are no valid No-Build missions. Please exclude fewer missions.") - for slot in no_build_slots: - filler = multiworld.random.randint(0, len(missions_to_add) - 1) - - missions[slot] = missions_to_add.pop(filler) - - # Add easy missions into pool and fill in easy slots - missions_to_add = missions_to_add + mission_pools[MissionPools.EASY] - if len(easy_slots) > len(missions_to_add): - raise Exception("There are not enough Easy missions to fill the campaign. Please exclude fewer missions.") - for slot in easy_slots: - filler = multiworld.random.randint(0, len(missions_to_add) - 1) - - missions[slot] = missions_to_add.pop(filler) - - # Add medium missions into pool and fill in medium slots - missions_to_add = missions_to_add + mission_pools[MissionPools.MEDIUM] - if len(medium_slots) > len(missions_to_add): - raise Exception("There are not enough Easy and Medium missions to fill the campaign. Please exclude fewer missions.") - for slot in medium_slots: - filler = multiworld.random.randint(0, len(missions_to_add) - 1) - - missions[slot] = missions_to_add.pop(filler) - - # Add hard missions into pool and fill in hard slots - missions_to_add = missions_to_add + mission_pools[MissionPools.HARD] - if len(hard_slots) > len(missions_to_add): - raise Exception("There are not enough missions to fill the campaign. Please exclude fewer missions.") - for slot in hard_slots: - filler = multiworld.random.randint(0, len(missions_to_add) - 1) - - missions[slot] = missions_to_add.pop(filler) - - # Generating regions and locations from selected missions - for region_name in missions: - regions.append(create_region(multiworld, player, locations_per_region, location_cache, region_name)) - multiworld.regions += regions - - # Mapping original mission slots to shifted mission slots when missions are removed - slot_map = [] - slot_offset = 0 - for position, mission in enumerate(missions): - slot_map.append(position - slot_offset + 1) - if mission is None: - slot_offset += 1 - - # Loop through missions to create requirements table and connect regions - # TODO: Handle 'and' connections - mission_req_table = {} - - def build_connection_rule(mission_names: List[str], missions_req: int) -> Callable: - if len(mission_names) > 1: - return lambda state: state.has_all({f"Beat {name}" for name in mission_names}, player) and \ - state._sc2wol_cleared_missions(multiworld, player, missions_req) - else: - return lambda state: state.has(f"Beat {mission_names[0]}", player) and \ - state._sc2wol_cleared_missions(multiworld, player, missions_req) - - for i, mission in enumerate(missions): - if mission is None: - continue - connections = [] - all_connections = [] - for connection in mission_order[i].connect_to: - if connection == -1: - continue - while missions[connection] is None: - connection -= 1 - all_connections.append(missions[connection]) - for connection in mission_order[i].connect_to: - required_mission = missions[connection] - if connection == -1: - connect(multiworld, player, names, "Menu", mission) - else: - if required_mission is None and not mission_order[i].completion_critical: # Drop non-critical null slots - continue - while required_mission is None: # Substituting null slot with prior slot - connection -= 1 - required_mission = missions[connection] - required_missions = [required_mission] if mission_order[i].or_requirements else all_connections - connect(multiworld, player, names, required_mission, mission, - build_connection_rule(required_missions, mission_order[i].number)) - connections.append(slot_map[connection]) - - mission_req_table.update({mission: MissionInfo( - vanilla_mission_req_table[mission].id, connections, mission_order[i].category, - number=mission_order[i].number, - completion_critical=mission_order[i].completion_critical, - or_requirements=mission_order[i].or_requirements)}) - - final_mission_id = vanilla_mission_req_table[final_mission].id - - # Changing the completion condition for alternate final missions into an event - if final_mission != 'All-In': - final_location = alt_final_mission_locations[final_mission] - # Final location should be near the end of the cache - for i in range(len(location_cache) - 1, -1, -1): - if location_cache[i].name == final_location: - location_cache[i].locked = True - location_cache[i].event = True - location_cache[i].address = None - break - else: - final_location = 'All-In: Victory' - - return mission_req_table, final_mission_id, final_location - -def create_location(player: int, location_data: LocationData, region: Region, - location_cache: List[Location]) -> Location: - location = Location(player, location_data.name, location_data.code, region) - location.access_rule = location_data.rule - - if id is None: - location.event = True - location.locked = True - - location_cache.append(location) - - return location - - -def create_region(multiworld: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]], - location_cache: List[Location], name: str) -> Region: - region = Region(name, player, multiworld) - - if name in locations_per_region: - for location_data in locations_per_region[name]: - location = create_location(player, location_data, region, location_cache) - region.locations.append(location) - - return region - - -def connect(world: MultiWorld, player: int, used_names: Dict[str, int], source: str, target: str, - rule: Optional[Callable] = None): - sourceRegion = world.get_region(source, player) - targetRegion = world.get_region(target, 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(player, name, sourceRegion) - - if rule: - connection.access_rule = rule - - sourceRegion.exits.append(connection) - connection.connect(targetRegion) - - -def get_locations_per_region(locations: Tuple[LocationData, ...]) -> Dict[str, List[LocationData]]: - per_region: Dict[str, List[LocationData]] = {} - - for location in locations: - per_region.setdefault(location.region, []).append(location) - - return per_region diff --git a/worlds/sc2wol/Starcraft2.kv b/worlds/sc2wol/Starcraft2.kv deleted file mode 100644 index f0785b89e4..0000000000 --- a/worlds/sc2wol/Starcraft2.kv +++ /dev/null @@ -1,16 +0,0 @@ -: - rows: 1 - -: - cols: 1 - padding: [10,5,10,5] - spacing: [0,5] - -: - text_size: self.size - markup: True - halign: 'center' - valign: 'middle' - padding: [5,0,5,0] - markup: True - outline_width: 1 diff --git a/worlds/sc2wol/__init__.py b/worlds/sc2wol/__init__.py deleted file mode 100644 index 5c487f8fee..0000000000 --- a/worlds/sc2wol/__init__.py +++ /dev/null @@ -1,324 +0,0 @@ -import typing - -from typing import List, Set, Tuple, Dict -from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification -from worlds.AutoWorld import WebWorld, World -from .Items import StarcraftWoLItem, filler_items, item_name_groups, get_item_table, get_full_item_list, \ - get_basic_units, ItemData, upgrade_included_names, progressive_if_nco -from .Locations import get_locations, LocationType -from .Regions import create_regions -from .Options import sc2wol_options, get_option_value, LocationInclusion -from .LogicMixin import SC2WoLLogic -from .PoolFilter import filter_missions, filter_items, get_item_upgrades -from .MissionTables import starting_mission_locations, MissionInfo - - -class Starcraft2WoLWebWorld(WebWorld): - setup = Tutorial( - "Multiworld Setup Guide", - "A guide to setting up the Starcraft 2 randomizer connected to an Archipelago Multiworld", - "English", - "setup_en.md", - "setup/en", - ["TheCondor"] - ) - - tutorials = [setup] - - -class SC2WoLWorld(World): - """ - StarCraft II: Wings of Liberty is a science fiction real-time strategy video game developed and published by Blizzard Entertainment. - Command Raynor's Raiders in collecting pieces of the Keystone in order to stop the zerg threat posed by the Queen of Blades. - """ - - game = "Starcraft 2 Wings of Liberty" - web = Starcraft2WoLWebWorld() - data_version = 5 - - item_name_to_id = {name: data.code for name, data in get_full_item_list().items()} - location_name_to_id = {location.name: location.code for location in get_locations(None, None)} - option_definitions = sc2wol_options - - item_name_groups = item_name_groups - locked_locations: typing.List[str] - location_cache: typing.List[Location] - mission_req_table = {} - final_mission_id: int - victory_item: str - required_client_version = 0, 4, 3 - - def __init__(self, multiworld: MultiWorld, player: int): - super(SC2WoLWorld, self).__init__(multiworld, player) - self.location_cache = [] - self.locked_locations = [] - - def create_item(self, name: str) -> Item: - data = get_full_item_list()[name] - return StarcraftWoLItem(name, data.classification, data.code, self.player) - - def create_regions(self): - self.mission_req_table, self.final_mission_id, self.victory_item = create_regions( - self.multiworld, self.player, get_locations(self.multiworld, self.player), self.location_cache - ) - - def create_items(self): - setup_events(self.player, self.locked_locations, self.location_cache) - - excluded_items = get_excluded_items(self.multiworld, self.player) - - starter_items = assign_starter_items(self.multiworld, self.player, excluded_items, self.locked_locations) - - filter_locations(self.multiworld, self.player, self.locked_locations, self.location_cache) - - pool = get_item_pool(self.multiworld, self.player, self.mission_req_table, starter_items, excluded_items, self.location_cache) - - fill_item_pool_with_dummy_items(self, self.multiworld, self.player, self.locked_locations, self.location_cache, pool) - - self.multiworld.itempool += pool - - def set_rules(self): - self.multiworld.completion_condition[self.player] = lambda state: state.has(self.victory_item, self.player) - - def get_filler_item_name(self) -> str: - return self.multiworld.random.choice(filler_items) - - def fill_slot_data(self): - slot_data = {} - for option_name in sc2wol_options: - option = getattr(self.multiworld, option_name)[self.player] - if type(option.value) in {str, int}: - slot_data[option_name] = int(option.value) - slot_req_table = {} - for mission in self.mission_req_table: - slot_req_table[mission] = self.mission_req_table[mission]._asdict() - - slot_data["mission_req"] = slot_req_table - slot_data["final_mission"] = self.final_mission_id - return slot_data - - -def setup_events(player: int, locked_locations: typing.List[str], location_cache: typing.List[Location]): - for location in location_cache: - if location.address is None: - item = Item(location.name, ItemClassification.progression, None, player) - - locked_locations.append(location.name) - - location.place_locked_item(item) - - -def get_excluded_items(multiworld: MultiWorld, player: int) -> Set[str]: - excluded_items: Set[str] = set() - - for item in multiworld.precollected_items[player]: - excluded_items.add(item.name) - - excluded_items_option = getattr(multiworld, 'excluded_items', []) - - excluded_items.update(excluded_items_option[player].value) - - return excluded_items - - -def assign_starter_items(multiworld: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str]) -> List[Item]: - non_local_items = multiworld.non_local_items[player].value - if get_option_value(multiworld, player, "early_unit"): - local_basic_unit = sorted(item for item in get_basic_units(multiworld, player) if item not in non_local_items and item not in excluded_items) - if not local_basic_unit: - raise Exception("At least one basic unit must be local") - - # The first world should also be the starting world - first_mission = list(multiworld.worlds[player].mission_req_table)[0] - if first_mission in starting_mission_locations: - first_location = starting_mission_locations[first_mission] - elif first_mission == "In Utter Darkness": - first_location = first_mission + ": Defeat" - else: - first_location = first_mission + ": Victory" - - return [assign_starter_item(multiworld, player, excluded_items, locked_locations, first_location, local_basic_unit)] - else: - return [] - - -def assign_starter_item(multiworld: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str], - location: str, item_list: Tuple[str, ...]) -> Item: - - item_name = multiworld.random.choice(item_list) - - excluded_items.add(item_name) - - item = create_item_with_correct_settings(player, item_name) - - multiworld.get_location(location, player).place_locked_item(item) - - locked_locations.append(location) - - return item - - -def get_item_pool(multiworld: MultiWorld, player: int, mission_req_table: Dict[str, MissionInfo], - starter_items: List[Item], excluded_items: Set[str], location_cache: List[Location]) -> List[Item]: - pool: List[Item] = [] - - # For the future: goal items like Artifact Shards go here - locked_items = [] - - # YAML items - yaml_locked_items = get_option_value(multiworld, player, 'locked_items') - - # Adjust generic upgrade availability based on options - include_upgrades = get_option_value(multiworld, player, 'generic_upgrade_missions') == 0 - upgrade_items = get_option_value(multiworld, player, 'generic_upgrade_items') - - # Include items from outside Wings of Liberty - item_sets = {'wol'} - if get_option_value(multiworld, player, 'nco_items'): - item_sets.add('nco') - if get_option_value(multiworld, player, 'bw_items'): - item_sets.add('bw') - if get_option_value(multiworld, player, 'ext_items'): - item_sets.add('ext') - - def allowed_quantity(name: str, data: ItemData) -> int: - if name in excluded_items \ - or data.type == "Upgrade" and (not include_upgrades or name not in upgrade_included_names[upgrade_items]) \ - or not data.origin.intersection(item_sets): - return 0 - elif name in progressive_if_nco and 'nco' not in item_sets: - return 1 - else: - return data.quantity - - for name, data in get_item_table(multiworld, player).items(): - for i in range(allowed_quantity(name, data)): - item = create_item_with_correct_settings(player, name) - if name in yaml_locked_items: - locked_items.append(item) - else: - pool.append(item) - - existing_items = starter_items + [item for item in multiworld.precollected_items[player]] - existing_names = [item.name for item in existing_items] - - # Check the parent item integrity, exclude items - pool[:] = [item for item in pool if pool_contains_parent(item, pool + locked_items + existing_items)] - - # Removing upgrades for excluded items - for item_name in excluded_items: - if item_name in existing_names: - continue - invalid_upgrades = get_item_upgrades(pool, item_name) - for invalid_upgrade in invalid_upgrades: - pool.remove(invalid_upgrade) - - filtered_pool = filter_items(multiworld, player, mission_req_table, location_cache, pool, existing_items, locked_items) - return filtered_pool - - -def fill_item_pool_with_dummy_items(self: SC2WoLWorld, multiworld: MultiWorld, player: int, locked_locations: List[str], - location_cache: List[Location], pool: List[Item]): - for _ in range(len(location_cache) - len(locked_locations) - len(pool)): - item = create_item_with_correct_settings(player, self.get_filler_item_name()) - pool.append(item) - - -def create_item_with_correct_settings(player: int, name: str) -> Item: - data = get_full_item_list()[name] - - item = Item(name, data.classification, data.code, player) - - return item - - -def pool_contains_parent(item: Item, pool: [Item]): - item_data = get_full_item_list().get(item.name) - if item_data.parent_item is None: - # The item has not associated parent, the item is valid - return True - parent_item = item_data.parent_item - # Check if the pool contains the parent item - return parent_item in [pool_item.name for pool_item in pool] - - -def filter_locations(multiworld: MultiWorld, player, locked_locations: List[str], location_cache: List[Location]): - """ - Filters the locations in the world using a trash or Nothing item - :param multiworld: - :param player: - :param locked_locations: - :param location_cache: - :return: - """ - open_locations = [location for location in location_cache if location.item is None] - plando_locations = get_plando_locations(multiworld, player) - mission_progress_locations = get_option_value(multiworld, player, "mission_progress_locations") - bonus_locations = get_option_value(multiworld, player, "bonus_locations") - challenge_locations = get_option_value(multiworld, player, "challenge_locations") - optional_boss_locations = get_option_value(multiworld, player, "optional_boss_locations") - location_data = get_locations(multiworld, player) - for location in open_locations: - # Go through the locations that aren't locked yet (early unit, etc) - if location.name not in plando_locations: - # The location is not plando'd - sc2_location = [sc2_location for sc2_location in location_data if sc2_location.name == location.name][0] - location_type = sc2_location.type - - if location_type == LocationType.MISSION_PROGRESS \ - and mission_progress_locations != LocationInclusion.option_enabled: - item_name = get_exclusion_item(multiworld, mission_progress_locations) - place_exclusion_item(item_name, location, locked_locations, player) - - if location_type == LocationType.BONUS \ - and bonus_locations != LocationInclusion.option_enabled: - item_name = get_exclusion_item(multiworld, bonus_locations) - place_exclusion_item(item_name, location, locked_locations, player) - - if location_type == LocationType.CHALLENGE \ - and challenge_locations != LocationInclusion.option_enabled: - item_name = get_exclusion_item(multiworld, challenge_locations) - place_exclusion_item(item_name, location, locked_locations, player) - - if location_type == LocationType.OPTIONAL_BOSS \ - and optional_boss_locations != LocationInclusion.option_enabled: - item_name = get_exclusion_item(multiworld, optional_boss_locations) - place_exclusion_item(item_name, location, locked_locations, player) - - -def place_exclusion_item(item_name, location, locked_locations, player): - item = create_item_with_correct_settings(player, item_name) - location.place_locked_item(item) - locked_locations.append(location.name) - - -def get_exclusion_item(multiworld: MultiWorld, option) -> str: - """ - Gets the exclusion item according to settings (trash/nothing) - :param multiworld: - :param option: - :return: Item used for location exclusion - """ - if option == LocationInclusion.option_nothing: - return "Nothing" - elif option == LocationInclusion.option_trash: - index = multiworld.random.randint(0, len(filler_items) - 1) - return filler_items[index] - raise Exception(f"Unsupported option type: {option}") - - -def get_plando_locations(multiworld: MultiWorld, player) -> List[str]: - """ - - :param multiworld: - :param player: - :return: A list of locations affected by a plando in a world - """ - plando_locations = [] - for plando_setting in multiworld.plando_items[player]: - plando_locations += plando_setting.get("locations", []) - plando_setting_location = plando_setting.get("location", None) - if plando_setting_location is not None: - plando_locations.append(plando_setting_location) - - return plando_locations diff --git a/worlds/sc2wol/docs/en_Starcraft 2 Wings of Liberty.md b/worlds/sc2wol/docs/en_Starcraft 2 Wings of Liberty.md deleted file mode 100644 index 18bda64784..0000000000 --- a/worlds/sc2wol/docs/en_Starcraft 2 Wings of Liberty.md +++ /dev/null @@ -1,54 +0,0 @@ -# Starcraft 2 Wings of Liberty - -## What does randomization do to this game? - -The following unlocks are randomized as items: -1. Your ability to build any non-worker unit (including Marines!). -2. Your ability to upgrade infantry weapons, infantry armor, vehicle weapons, etc. -3. All armory upgrades -4. All laboratory upgrades -5. All mercenaries -6. Small boosts to your starting mineral and vespene gas totals on each mission - -You find items by making progress in bonus objectives (like by rescuing allies in 'Zero Hour') and by completing -missions. When you receive items, they will immediately become available, even during a mission, and you will be -notified via a text box in the top-right corner of the game screen. (The text client for StarCraft 2 also records all -items in all worlds.) - -Missions are launched only through the text client. The Hyperion is never visited. Additionally, credits are not used. - -## What is the goal of this game when randomized? - -The goal is to beat the final mission: 'All In'. The config file determines which variant you must complete. - -## What non-randomized changes are there from vanilla Starcraft 2? - -1. Some missions have more vespene geysers available to allow a wider variety of units. -2. Starports no longer require Factories in order to be built. -3. In 'A Sinister Turn' and 'Echoes of the Future', you can research Protoss air weapon/armor upgrades. - -## Which of my items can be in another player's world? - -By default, any of StarCraft 2's items (specified above) can be in another player's world. See the -[Advanced YAML Guide](https://archipelago.gg/tutorial/Archipelago/advanced_settings/en) -for more information on how to change this. - -## Unique Local Commands - -The following commands are only available when using the Starcraft 2 Client to play with Archipelago. - -- `/difficulty [difficulty]` Overrides the difficulty set for the world. - - Options: casual, normal, hard, brutal -- `/game_speed [game_speed]` Overrides the game speed for the world - - Options: default, slower, slow, normal, fast, faster -- `/color [color]` Changes your color (Currently has no effect) - - Options: white, red, blue, teal, purple, yellow, orange, green, lightpink, violet, lightgrey, darkgreen, brown, - lightgreen, darkgrey, pink, rainbow, random, default -- `/disable_mission_check` Disables the check to see if a mission is available to play. Meant for co-op runs where one - player can play the next mission in a chain the other player is doing. -- `/play [mission_id]` Starts a Starcraft 2 mission based off of the mission_id provided -- `/available` Get what missions are currently available to play -- `/unfinished` Get what missions are currently available to play and have not had all locations checked -- `/set_path [path]` Menually set the SC2 install directory (if the automatic detection fails) -- `/download_data` Download the most recent release of the necassry files for playing SC2 with Archipelago. Will - overwrite existing files From 2a8784ef7200a8c5586257dd5c4592608cedacfe Mon Sep 17 00:00:00 2001 From: Nicholas Brochu Date: Fri, 15 Mar 2024 12:35:37 -0400 Subject: [PATCH 146/166] Zork Grand Inquisitor: Implement New Game (#2539) Adds Archipelago support for Zork Grand Inquisitor, the 1997 point-and-click PC adventure game. The client (based on `CommonClient`), on top of its regular Archipelago duties, fully handles the randomization of the game and the monitoring / modification of the game state. No game modding needed at all; the player is ready to play an Archipelago seed if they can play the vanilla game through ScummVM. The "reverse engineering" (there's likely a better term for this...) of the game is my own original work and I included an MIT license at the root of my world directory. A PopTracker pack was also created to help people learn the game: https://github.com/SerpentAI/ZorkGrandInquisitorAPTracker --- README.md | 1 + docs/CODEOWNERS | 3 + worlds/zork_grand_inquisitor/LICENSE | 21 + worlds/zork_grand_inquisitor/__init__.py | 17 + worlds/zork_grand_inquisitor/client.py | 188 ++ worlds/zork_grand_inquisitor/data/__init__.py | 0 .../data/entrance_rule_data.py | 419 +++ .../zork_grand_inquisitor/data/item_data.py | 792 +++++ .../data/location_data.py | 1535 +++++++++ ...missable_location_grant_conditions_data.py | 200 ++ .../zork_grand_inquisitor/data/region_data.py | 183 ++ worlds/zork_grand_inquisitor/data_funcs.py | 247 ++ .../docs/en_Zork Grand Inquisitor.md | 102 + worlds/zork_grand_inquisitor/docs/setup_en.md | 42 + worlds/zork_grand_inquisitor/enums.py | 350 ++ .../zork_grand_inquisitor/game_controller.py | 1388 ++++++++ .../game_state_manager.py | 370 +++ worlds/zork_grand_inquisitor/options.py | 61 + worlds/zork_grand_inquisitor/requirements.txt | 1 + worlds/zork_grand_inquisitor/test/__init__.py | 5 + .../zork_grand_inquisitor/test/test_access.py | 2927 +++++++++++++++++ .../test/test_data_funcs.py | 132 + .../test/test_locations.py | 49 + worlds/zork_grand_inquisitor/world.py | 206 ++ 24 files changed, 9239 insertions(+) create mode 100644 worlds/zork_grand_inquisitor/LICENSE create mode 100644 worlds/zork_grand_inquisitor/__init__.py create mode 100644 worlds/zork_grand_inquisitor/client.py create mode 100644 worlds/zork_grand_inquisitor/data/__init__.py create mode 100644 worlds/zork_grand_inquisitor/data/entrance_rule_data.py create mode 100644 worlds/zork_grand_inquisitor/data/item_data.py create mode 100644 worlds/zork_grand_inquisitor/data/location_data.py create mode 100644 worlds/zork_grand_inquisitor/data/missable_location_grant_conditions_data.py create mode 100644 worlds/zork_grand_inquisitor/data/region_data.py create mode 100644 worlds/zork_grand_inquisitor/data_funcs.py create mode 100644 worlds/zork_grand_inquisitor/docs/en_Zork Grand Inquisitor.md create mode 100644 worlds/zork_grand_inquisitor/docs/setup_en.md create mode 100644 worlds/zork_grand_inquisitor/enums.py create mode 100644 worlds/zork_grand_inquisitor/game_controller.py create mode 100644 worlds/zork_grand_inquisitor/game_state_manager.py create mode 100644 worlds/zork_grand_inquisitor/options.py create mode 100644 worlds/zork_grand_inquisitor/requirements.txt create mode 100644 worlds/zork_grand_inquisitor/test/__init__.py create mode 100644 worlds/zork_grand_inquisitor/test/test_access.py create mode 100644 worlds/zork_grand_inquisitor/test/test_data_funcs.py create mode 100644 worlds/zork_grand_inquisitor/test/test_locations.py create mode 100644 worlds/zork_grand_inquisitor/world.py diff --git a/README.md b/README.md index d914f86016..975f0ce75a 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ Currently, the following games are supported: * TUNIC * Kirby's Dream Land 3 * Celeste 64 +* Zork Grand Inquisitor 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 dea869fb55..90b1dabb6d 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -188,6 +188,9 @@ # Zillion /worlds/zillion/ @beauxq +# Zork Grand Inquisitor +/worlds/zork_grand_inquisitor/ @nbrochu + ################################## ## Disabled Unmaintained Worlds ## ################################## diff --git a/worlds/zork_grand_inquisitor/LICENSE b/worlds/zork_grand_inquisitor/LICENSE new file mode 100644 index 0000000000..a94ca6bf91 --- /dev/null +++ b/worlds/zork_grand_inquisitor/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Serpent.AI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/worlds/zork_grand_inquisitor/__init__.py b/worlds/zork_grand_inquisitor/__init__.py new file mode 100644 index 0000000000..4da257e47b --- /dev/null +++ b/worlds/zork_grand_inquisitor/__init__.py @@ -0,0 +1,17 @@ +import worlds.LauncherComponents as LauncherComponents + +from .world import ZorkGrandInquisitorWorld + + +def launch_client() -> None: + from .client import main + LauncherComponents.launch_subprocess(main, name="ZorkGrandInquisitorClient") + + +LauncherComponents.components.append( + LauncherComponents.Component( + "Zork Grand Inquisitor Client", + func=launch_client, + component_type=LauncherComponents.Type.CLIENT + ) +) diff --git a/worlds/zork_grand_inquisitor/client.py b/worlds/zork_grand_inquisitor/client.py new file mode 100644 index 0000000000..11d6b7f8f1 --- /dev/null +++ b/worlds/zork_grand_inquisitor/client.py @@ -0,0 +1,188 @@ +import asyncio + +import CommonClient +import NetUtils +import Utils + +from typing import Any, Dict, List, Optional, Set, Tuple + +from .data_funcs import item_names_to_id, location_names_to_id, id_to_items, id_to_locations, id_to_goals +from .enums import ZorkGrandInquisitorItems, ZorkGrandInquisitorLocations +from .game_controller import GameController + + +class ZorkGrandInquisitorCommandProcessor(CommonClient.ClientCommandProcessor): + def _cmd_zork(self) -> None: + """Attach to an open Zork Grand Inquisitor process.""" + result: bool = self.ctx.game_controller.open_process_handle() + + if result: + self.ctx.process_attached_at_least_once = True + self.output("Successfully attached to Zork Grand Inquisitor process.") + else: + self.output("Failed to attach to Zork Grand Inquisitor process.") + + def _cmd_brog(self) -> None: + """List received Brog items.""" + self.ctx.game_controller.list_received_brog_items() + + def _cmd_griff(self) -> None: + """List received Griff items.""" + self.ctx.game_controller.list_received_griff_items() + + def _cmd_lucy(self) -> None: + """List received Lucy items.""" + self.ctx.game_controller.list_received_lucy_items() + + def _cmd_hotspots(self) -> None: + """List received Hotspots.""" + self.ctx.game_controller.list_received_hotspots() + + +class ZorkGrandInquisitorContext(CommonClient.CommonContext): + tags: Set[str] = {"AP"} + game: str = "Zork Grand Inquisitor" + command_processor: CommonClient.ClientCommandProcessor = ZorkGrandInquisitorCommandProcessor + items_handling: int = 0b111 + want_slot_data: bool = True + + item_name_to_id: Dict[str, int] = item_names_to_id() + location_name_to_id: Dict[str, int] = location_names_to_id() + + id_to_items: Dict[int, ZorkGrandInquisitorItems] = id_to_items() + id_to_locations: Dict[int, ZorkGrandInquisitorLocations] = id_to_locations() + + game_controller: GameController + + controller_task: Optional[asyncio.Task] + + process_attached_at_least_once: bool + can_display_process_message: bool + + def __init__(self, server_address: Optional[str], password: Optional[str]) -> None: + super().__init__(server_address, password) + + self.game_controller = GameController(logger=CommonClient.logger) + + self.controller_task = None + + self.process_attached_at_least_once = False + self.can_display_process_message = True + + def run_gui(self) -> None: + from kvui import GameManager + + class TextManager(GameManager): + logging_pairs: List[Tuple[str, str]] = [("Client", "Archipelago")] + base_title: str = "Archipelago Zork Grand Inquisitor Client" + + self.ui = TextManager(self) + self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") + + async def server_auth(self, password_requested: bool = False): + if password_requested and not self.password: + await super().server_auth(password_requested) + + await self.get_username() + await self.send_connect() + + def on_package(self, cmd: str, _args: Any) -> None: + if cmd == "Connected": + self.game = self.slot_info[self.slot].game + + # Options + self.game_controller.option_goal = id_to_goals()[_args["slot_data"]["goal"]] + self.game_controller.option_deathsanity = _args["slot_data"]["deathsanity"] == 1 + + self.game_controller.option_grant_missable_location_checks = ( + _args["slot_data"]["grant_missable_location_checks"] == 1 + ) + + async def controller(self): + while not self.exit_event.is_set(): + await asyncio.sleep(0.1) + + # Enqueue Received Item Delta + network_item: NetUtils.NetworkItem + for network_item in self.items_received: + item: ZorkGrandInquisitorItems = self.id_to_items[network_item.item] + + if item not in self.game_controller.received_items: + if item not in self.game_controller.received_items_queue: + self.game_controller.received_items_queue.append(item) + + # Game Controller Update + if self.game_controller.is_process_running(): + self.game_controller.update() + self.can_display_process_message = True + else: + process_message: str + + if self.process_attached_at_least_once: + process_message = ( + "Lost connection to Zork Grand Inquisitor process. Please restart the game and use the /zork " + "command to reattach." + ) + else: + process_message = ( + "Please use the /zork command to attach to a running Zork Grand Inquisitor process." + ) + + if self.can_display_process_message: + CommonClient.logger.info(process_message) + self.can_display_process_message = False + + # Send Checked Locations + checked_location_ids: List[int] = list() + + while len(self.game_controller.completed_locations_queue) > 0: + location: ZorkGrandInquisitorLocations = self.game_controller.completed_locations_queue.popleft() + location_id: int = self.location_name_to_id[location.value] + + checked_location_ids.append(location_id) + + await self.send_msgs([ + { + "cmd": "LocationChecks", + "locations": checked_location_ids + } + ]) + + # Check for Goal Completion + if self.game_controller.goal_completed: + await self.send_msgs([ + { + "cmd": "StatusUpdate", + "status": CommonClient.ClientStatus.CLIENT_GOAL + } + ]) + + +def main() -> None: + Utils.init_logging("ZorkGrandInquisitorClient", exception_logger="Client") + + async def _main(): + ctx: ZorkGrandInquisitorContext = ZorkGrandInquisitorContext(None, None) + + ctx.server_task = asyncio.create_task(CommonClient.server_loop(ctx), name="server loop") + ctx.controller_task = asyncio.create_task(ctx.controller(), name="ZorkGrandInquisitorController") + + if CommonClient.gui_enabled: + ctx.run_gui() + + ctx.run_cli() + + await ctx.exit_event.wait() + await ctx.shutdown() + + import colorama + + colorama.init() + + asyncio.run(_main()) + + colorama.deinit() + + +if __name__ == "__main__": + main() diff --git a/worlds/zork_grand_inquisitor/data/__init__.py b/worlds/zork_grand_inquisitor/data/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/worlds/zork_grand_inquisitor/data/entrance_rule_data.py b/worlds/zork_grand_inquisitor/data/entrance_rule_data.py new file mode 100644 index 0000000000..f48be5eb6b --- /dev/null +++ b/worlds/zork_grand_inquisitor/data/entrance_rule_data.py @@ -0,0 +1,419 @@ +from typing import Dict, Tuple, Union + +from ..enums import ZorkGrandInquisitorEvents, ZorkGrandInquisitorItems, ZorkGrandInquisitorRegions + + +entrance_rule_data: Dict[ + Tuple[ + ZorkGrandInquisitorRegions, + ZorkGrandInquisitorRegions, + ], + Union[ + Tuple[ + Tuple[ + Union[ + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorRegions, + ], + ..., + ], + ..., + ], + None, + ], +] = { + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.DM_LAIR): ( + ( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE, + ), + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR, + ), + ), + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.GUE_TECH): ( + ( + ZorkGrandInquisitorItems.SPELL_REZROV, + ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR, + ), + ), + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH, + ), + ), + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.HADES_SHORE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES, + ), + ), + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.PORT_FOOZLE): None, + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB, + ), + ), + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): ( + ( + ZorkGrandInquisitorItems.SUBWAY_TOKEN, + ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT, + ), + ), + (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY, + ), + ), + (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.CROSSROADS): None, + (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR): ( + ( + ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR, + ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD, + ), + ), + (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH, + ), + ), + (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.HADES_SHORE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES, + ), + ), + (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB, + ), + ), + (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY, + ), + ), + (ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.DM_LAIR): None, + (ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.WALKING_CASTLE): ( + ( + ZorkGrandInquisitorItems.HOTSPOT_BLINDS, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL, + ), + ), + (ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.WHITE_HOUSE): ( + ( + ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR, + ZorkGrandInquisitorItems.SPELL_NARWILE, + ZorkGrandInquisitorEvents.KNOWS_YASTARD, + ), + ), + (ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON): ( + ( + ZorkGrandInquisitorItems.TOTEM_GRIFF, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW, + ), + ), + (ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, ZorkGrandInquisitorRegions.HADES_BEYOND_GATES): None, + (ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO): None, + (ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, ZorkGrandInquisitorRegions.ENDGAME): ( + ( + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, + ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, + ZorkGrandInquisitorRegions.WHITE_HOUSE, + ZorkGrandInquisitorItems.TOTEM_BROG, # Needed here since White House is not broken down in 2 regions + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, + ZorkGrandInquisitorItems.BROGS_PLANK, + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE, + ), + ), + (ZorkGrandInquisitorRegions.GUE_TECH, ZorkGrandInquisitorRegions.CROSSROADS): None, + (ZorkGrandInquisitorRegions.GUE_TECH, ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY): ( + ( + ZorkGrandInquisitorItems.SPELL_IGRAM, + ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS, + ), + ), + (ZorkGrandInquisitorRegions.GUE_TECH, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( + (ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR,), + ), + (ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, ZorkGrandInquisitorRegions.GUE_TECH): None, + (ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( + ( + ZorkGrandInquisitorItems.STUDENT_ID, + ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE, + ), + ), + (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.CROSSROADS): ( + (ZorkGrandInquisitorItems.MAP,), + ), + (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.DM_LAIR): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR, + ), + ), + (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.GUE_TECH): None, + (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.HADES_SHORE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES, + ), + ), + (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB, + ), + ), + (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY, + ), + ), + (ZorkGrandInquisitorRegions.HADES, ZorkGrandInquisitorRegions.HADES_BEYOND_GATES): ( + ( + ZorkGrandInquisitorEvents.KNOWS_SNAVIG, + ZorkGrandInquisitorItems.TOTEM_BROG, # Visually hiding this totem is tied to owning it; no choice + ), + ), + (ZorkGrandInquisitorRegions.HADES, ZorkGrandInquisitorRegions.HADES_SHORE): ( + (ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS,), + ), + (ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO): ( + ( + ZorkGrandInquisitorItems.SPELL_NARWILE, + ZorkGrandInquisitorEvents.KNOWS_YASTARD, + ), + ), + (ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, ZorkGrandInquisitorRegions.HADES): None, + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.CROSSROADS): ( + (ZorkGrandInquisitorItems.MAP,), + ), + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.DM_LAIR): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR, + ), + ), + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH, + ), + ), + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.HADES): ( + ( + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER, + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS, + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ), + ), + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB, + ), + ), + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): None, + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM): ( + (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM,), + ), + (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( + (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY,), + ), + (ZorkGrandInquisitorRegions.MENU, ZorkGrandInquisitorRegions.PORT_FOOZLE): None, + (ZorkGrandInquisitorRegions.MONASTERY, ZorkGrandInquisitorRegions.HADES_SHORE): ( + ( + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH, + ), + ), + (ZorkGrandInquisitorRegions.MONASTERY, ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT): ( + ( + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH, + ), + ), + (ZorkGrandInquisitorRegions.MONASTERY, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): None, + (ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, ZorkGrandInquisitorRegions.MONASTERY): None, + (ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST): ( + ( + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER, + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT, + ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER, + ZorkGrandInquisitorItems.SPELL_NARWILE, + ZorkGrandInquisitorEvents.KNOWS_YASTARD, + ), + ), + (ZorkGrandInquisitorRegions.PORT_FOOZLE, ZorkGrandInquisitorRegions.CROSSROADS): ( + ( + ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE, + ZorkGrandInquisitorItems.ROPE, + ZorkGrandInquisitorItems.HOTSPOT_WELL, + ), + ), + (ZorkGrandInquisitorRegions.PORT_FOOZLE, ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP): ( + ( + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, + ), + ), + (ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP, ZorkGrandInquisitorRegions.PORT_FOOZLE): None, + (ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT): None, + (ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN): ( + ( + ZorkGrandInquisitorItems.TOTEM_LUCY, + ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR, + ), + ), + (ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, ZorkGrandInquisitorRegions.ENDGAME): ( + ( + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, + ZorkGrandInquisitorRegions.WHITE_HOUSE, + ZorkGrandInquisitorItems.TOTEM_BROG, # Needed here since White House is not broken down in 2 regions + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, + ZorkGrandInquisitorItems.BROGS_PLANK, + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE, + ), + ), + (ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST): None, + (ZorkGrandInquisitorRegions.SPELL_LAB, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): None, + (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.CROSSROADS): ( + (ZorkGrandInquisitorItems.MAP,), + ), + (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.DM_LAIR): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR, + ), + ), + (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH, + ), + ), + (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY): None, + (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.HADES_SHORE): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES, + ), + ), + (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.SPELL_LAB): ( + ( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE, + ZorkGrandInquisitorEvents.DAM_DESTROYED, + ZorkGrandInquisitorItems.SPELL_GOLGATEM, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM, + ), + ), + (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( + ( + ZorkGrandInquisitorItems.MAP, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY, + ), + ), + (ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.CROSSROADS): None, + (ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.HADES_SHORE): ( + ( + ZorkGrandInquisitorItems.SPELL_KENDALL, + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES, + ), + ), + (ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM): ( + ( + ZorkGrandInquisitorItems.SPELL_KENDALL, + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM, + ), + ), + (ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( + ( + ZorkGrandInquisitorItems.SPELL_KENDALL, + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY, + ), + ), + (ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, ZorkGrandInquisitorRegions.HADES_SHORE): ( + (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES,), + ), + (ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): None, + (ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( + (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY,), + ), + (ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.HADES_SHORE): ( + (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES,), + ), + (ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.MONASTERY): ( + ( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorEvents.ROPE_GLORFABLE, + ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT, + ), + ), + (ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): None, + (ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM): ( + (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM,), + ), + (ZorkGrandInquisitorRegions.WALKING_CASTLE, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR): None, + (ZorkGrandInquisitorRegions.WHITE_HOUSE, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR): None, + (ZorkGrandInquisitorRegions.WHITE_HOUSE, ZorkGrandInquisitorRegions.ENDGAME): ( + ( + ZorkGrandInquisitorItems.TOTEM_BROG, # Needed here since White House is not broken down in 2 regions + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, + ZorkGrandInquisitorItems.BROGS_PLANK, + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE, + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, + ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, + ), + ), +} diff --git a/worlds/zork_grand_inquisitor/data/item_data.py b/worlds/zork_grand_inquisitor/data/item_data.py new file mode 100644 index 0000000000..c312bbce3d --- /dev/null +++ b/worlds/zork_grand_inquisitor/data/item_data.py @@ -0,0 +1,792 @@ +from typing import Dict, NamedTuple, Optional, Tuple, Union + +from BaseClasses import ItemClassification + +from ..enums import ZorkGrandInquisitorItems, ZorkGrandInquisitorTags + + +class ZorkGrandInquisitorItemData(NamedTuple): + statemap_keys: Optional[Tuple[int, ...]] + archipelago_id: Optional[int] + classification: ItemClassification + tags: Tuple[ZorkGrandInquisitorTags, ...] + maximum_quantity: Optional[int] = 1 + + +ITEM_OFFSET = 9758067000 + +item_data: Dict[ZorkGrandInquisitorItems, ZorkGrandInquisitorItemData] = { + # Inventory Items + ZorkGrandInquisitorItems.BROGS_BICKERING_TORCH: ZorkGrandInquisitorItemData( + statemap_keys=(67,), # Extinguished = 103 + archipelago_id=ITEM_OFFSET + 0, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH: ZorkGrandInquisitorItemData( + statemap_keys=(68,), # Extinguished = 104 + archipelago_id=ITEM_OFFSET + 1, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.BROGS_GRUE_EGG: ZorkGrandInquisitorItemData( + statemap_keys=(70,), # Boiled = 71 + archipelago_id=ITEM_OFFSET + 2, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.BROGS_PLANK: ZorkGrandInquisitorItemData( + statemap_keys=(69,), + archipelago_id=ITEM_OFFSET + 3, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.FLATHEADIA_FUDGE: ZorkGrandInquisitorItemData( + statemap_keys=(54,), + archipelago_id=ITEM_OFFSET + 4, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP: ZorkGrandInquisitorItemData( + statemap_keys=(86,), + archipelago_id=ITEM_OFFSET + 5, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH: ZorkGrandInquisitorItemData( + statemap_keys=(84,), + archipelago_id=ITEM_OFFSET + 6, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT: ZorkGrandInquisitorItemData( + statemap_keys=(9,), + archipelago_id=ITEM_OFFSET + 7, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN: ZorkGrandInquisitorItemData( + statemap_keys=(16,), + archipelago_id=ITEM_OFFSET + 8, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.HAMMER: ZorkGrandInquisitorItemData( + statemap_keys=(23,), + archipelago_id=ITEM_OFFSET + 9, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.HUNGUS_LARD: ZorkGrandInquisitorItemData( + statemap_keys=(55,), + archipelago_id=ITEM_OFFSET + 10, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.JAR_OF_HOTBUGS: ZorkGrandInquisitorItemData( + statemap_keys=(56,), + archipelago_id=ITEM_OFFSET + 11, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.LANTERN: ZorkGrandInquisitorItemData( + statemap_keys=(4,), + archipelago_id=ITEM_OFFSET + 12, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER: ZorkGrandInquisitorItemData( + statemap_keys=(88,), + archipelago_id=ITEM_OFFSET + 13, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1: ZorkGrandInquisitorItemData( + statemap_keys=(116,), # With fly = 120 + archipelago_id=ITEM_OFFSET + 14, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2: ZorkGrandInquisitorItemData( + statemap_keys=(117,), # With fly = 121 + archipelago_id=ITEM_OFFSET + 15, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3: ZorkGrandInquisitorItemData( + statemap_keys=(118,), # With fly = 122 + archipelago_id=ITEM_OFFSET + 16, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4: ZorkGrandInquisitorItemData( + statemap_keys=(119,), # With fly = 123 + archipelago_id=ITEM_OFFSET + 17, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.MAP: ZorkGrandInquisitorItemData( + statemap_keys=(6,), + archipelago_id=ITEM_OFFSET + 18, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.MEAD_LIGHT: ZorkGrandInquisitorItemData( + statemap_keys=(2,), + archipelago_id=ITEM_OFFSET + 19, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.MOSS_OF_MAREILON: ZorkGrandInquisitorItemData( + statemap_keys=(57,), + archipelago_id=ITEM_OFFSET + 20, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.MUG: ZorkGrandInquisitorItemData( + statemap_keys=(35,), + archipelago_id=ITEM_OFFSET + 21, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.OLD_SCRATCH_CARD: ZorkGrandInquisitorItemData( + statemap_keys=(17,), + archipelago_id=ITEM_OFFSET + 22, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.PERMA_SUCK_MACHINE: ZorkGrandInquisitorItemData( + statemap_keys=(36,), + archipelago_id=ITEM_OFFSET + 23, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER: ZorkGrandInquisitorItemData( + statemap_keys=(3,), + archipelago_id=ITEM_OFFSET + 24, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS: ZorkGrandInquisitorItemData( + statemap_keys=(5827,), + archipelago_id=ITEM_OFFSET + 25, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.PROZORK_TABLET: ZorkGrandInquisitorItemData( + statemap_keys=(65,), + archipelago_id=ITEM_OFFSET + 26, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB: ZorkGrandInquisitorItemData( + statemap_keys=(53,), + archipelago_id=ITEM_OFFSET + 27, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.ROPE: ZorkGrandInquisitorItemData( + statemap_keys=(83,), + archipelago_id=ITEM_OFFSET + 28, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS: ZorkGrandInquisitorItemData( + statemap_keys=(101,), # SNA = 41 + archipelago_id=ITEM_OFFSET + 29, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV: ZorkGrandInquisitorItemData( + statemap_keys=(102,), # VIG = 48 + archipelago_id=ITEM_OFFSET + 30, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.SHOVEL: ZorkGrandInquisitorItemData( + statemap_keys=(49,), + archipelago_id=ITEM_OFFSET + 31, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.SNAPDRAGON: ZorkGrandInquisitorItemData( + statemap_keys=(50,), + archipelago_id=ITEM_OFFSET + 32, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.STUDENT_ID: ZorkGrandInquisitorItemData( + statemap_keys=(39,), + archipelago_id=ITEM_OFFSET + 33, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.SUBWAY_TOKEN: ZorkGrandInquisitorItemData( + statemap_keys=(20,), + archipelago_id=ITEM_OFFSET + 34, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.SWORD: ZorkGrandInquisitorItemData( + statemap_keys=(21,), + archipelago_id=ITEM_OFFSET + 35, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.ZIMDOR_SCROLL: ZorkGrandInquisitorItemData( + statemap_keys=(25,), + archipelago_id=ITEM_OFFSET + 36, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + ZorkGrandInquisitorItems.ZORK_ROCKS: ZorkGrandInquisitorItemData( + statemap_keys=(37,), + archipelago_id=ITEM_OFFSET + 37, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), + ), + # Hotspots + ZorkGrandInquisitorItems.HOTSPOT_666_MAILBOX: ZorkGrandInquisitorItemData( + statemap_keys=(9116,), + archipelago_id=ITEM_OFFSET + 100 + 0, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS: ZorkGrandInquisitorItemData( + statemap_keys=(15434, 15436, 15438, 15440), + archipelago_id=ITEM_OFFSET + 100 + 1, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX: ZorkGrandInquisitorItemData( + statemap_keys=(12096,), + archipelago_id=ITEM_OFFSET + 100 + 2, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_BLINDS: ZorkGrandInquisitorItemData( + statemap_keys=(4799,), + archipelago_id=ITEM_OFFSET + 100 + 3, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS: ZorkGrandInquisitorItemData( + statemap_keys=(12691, 12692, 12693, 12694, 12695, 12696, 12697, 12698, 12699, 12700, 12701), + archipelago_id=ITEM_OFFSET + 100 + 4, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(12702,), + archipelago_id=ITEM_OFFSET + 100 + 5, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_VACUUM_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(12909,), + archipelago_id=ITEM_OFFSET + 100 + 6, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_CHANGE_MACHINE_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(12900,), + archipelago_id=ITEM_OFFSET + 100 + 7, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR: ZorkGrandInquisitorItemData( + statemap_keys=(5010,), + archipelago_id=ITEM_OFFSET + 100 + 8, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(9539,), + archipelago_id=ITEM_OFFSET + 100 + 9, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER: ZorkGrandInquisitorItemData( + statemap_keys=(19712,), + archipelago_id=ITEM_OFFSET + 100 + 10, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT: ZorkGrandInquisitorItemData( + statemap_keys=(2586,), + archipelago_id=ITEM_OFFSET + 100 + 11, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_DENTED_LOCKER: ZorkGrandInquisitorItemData( + statemap_keys=(11878,), + archipelago_id=ITEM_OFFSET + 100 + 12, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_DIRT_MOUND: ZorkGrandInquisitorItemData( + statemap_keys=(11751,), + archipelago_id=ITEM_OFFSET + 100 + 13, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH: ZorkGrandInquisitorItemData( + statemap_keys=(15147, 15153), + archipelago_id=ITEM_OFFSET + 100 + 14, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW: ZorkGrandInquisitorItemData( + statemap_keys=(1705,), + archipelago_id=ITEM_OFFSET + 100 + 15, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS: ZorkGrandInquisitorItemData( + statemap_keys=(1425, 1426), + archipelago_id=ITEM_OFFSET + 100 + 16, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE: ZorkGrandInquisitorItemData( + statemap_keys=(13106,), + archipelago_id=ITEM_OFFSET + 100 + 17, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS: ZorkGrandInquisitorItemData( + statemap_keys=(13219, 13220, 13221, 13222), + archipelago_id=ITEM_OFFSET + 100 + 18, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS: ZorkGrandInquisitorItemData( + statemap_keys=(14327, 14332, 14337, 14342), + archipelago_id=ITEM_OFFSET + 100 + 19, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(12528,), + archipelago_id=ITEM_OFFSET + 100 + 20, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS: ZorkGrandInquisitorItemData( + statemap_keys=(12523, 12524, 12525), + archipelago_id=ITEM_OFFSET + 100 + 21, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_GLASS_CASE: ZorkGrandInquisitorItemData( + statemap_keys=(13002,), + archipelago_id=ITEM_OFFSET + 100 + 22, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL: ZorkGrandInquisitorItemData( + statemap_keys=(10726,), + archipelago_id=ITEM_OFFSET + 100 + 23, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR: ZorkGrandInquisitorItemData( + statemap_keys=(12280,), + archipelago_id=ITEM_OFFSET + 100 + 24, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_GRASS: ZorkGrandInquisitorItemData( + statemap_keys=( + 17694, + 17695, + 17696, + 17697, + 18200, + 17703, + 17704, + 17705, + 17710, + 17711, + 17712, + 17713, + 17714, + 17715, + 17716, + 17722, + 17723, + 17724, + 17725, + 17726, + 17727 + ), + archipelago_id=ITEM_OFFSET + 100 + 25, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS: ZorkGrandInquisitorItemData( + statemap_keys=(8448, 8449, 8450, 8451, 8452, 8453, 8454, 8455, 8456, 8457, 8458, 8459), + archipelago_id=ITEM_OFFSET + 100 + 26, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER: ZorkGrandInquisitorItemData( + statemap_keys=(8446,), + archipelago_id=ITEM_OFFSET + 100 + 27, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_HARRY: ZorkGrandInquisitorItemData( + statemap_keys=(4260,), + archipelago_id=ITEM_OFFSET + 100 + 28, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY: ZorkGrandInquisitorItemData( + statemap_keys=(18026,), + archipelago_id=ITEM_OFFSET + 100 + 29, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH: ZorkGrandInquisitorItemData( + statemap_keys=(17623,), + archipelago_id=ITEM_OFFSET + 100 + 30, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR: ZorkGrandInquisitorItemData( + statemap_keys=(13140,), + archipelago_id=ITEM_OFFSET + 100 + 31, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR: ZorkGrandInquisitorItemData( + statemap_keys=(10441,), + archipelago_id=ITEM_OFFSET + 100 + 32, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS: ZorkGrandInquisitorItemData( + statemap_keys=(19632, 19627), + archipelago_id=ITEM_OFFSET + 100 + 33, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR: ZorkGrandInquisitorItemData( + statemap_keys=(3025,), + archipelago_id=ITEM_OFFSET + 100 + 34, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG: ZorkGrandInquisitorItemData( + statemap_keys=(3036,), + archipelago_id=ITEM_OFFSET + 100 + 35, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_MIRROR: ZorkGrandInquisitorItemData( + statemap_keys=(5031,), + archipelago_id=ITEM_OFFSET + 100 + 36, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT: ZorkGrandInquisitorItemData( + statemap_keys=(13597,), + archipelago_id=ITEM_OFFSET + 100 + 37, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_MOSSY_GRATE: ZorkGrandInquisitorItemData( + statemap_keys=(13390,), + archipelago_id=ITEM_OFFSET + 100 + 38, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR: ZorkGrandInquisitorItemData( + statemap_keys=(2455, 2447), + archipelago_id=ITEM_OFFSET + 100 + 39, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS: ZorkGrandInquisitorItemData( + statemap_keys=(12389, 12390), + archipelago_id=ITEM_OFFSET + 100 + 40, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE: ZorkGrandInquisitorItemData( + statemap_keys=(4302,), + archipelago_id=ITEM_OFFSET + 100 + 41, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE: ZorkGrandInquisitorItemData( + statemap_keys=(16383, 16384), + archipelago_id=ITEM_OFFSET + 100 + 42, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE: ZorkGrandInquisitorItemData( + statemap_keys=(2769,), + archipelago_id=ITEM_OFFSET + 100 + 43, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON: ZorkGrandInquisitorItemData( + statemap_keys=(4149,), + archipelago_id=ITEM_OFFSET + 100 + 44, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_BUTTONS: ZorkGrandInquisitorItemData( + statemap_keys=(12584, 12585, 12586, 12587), + archipelago_id=ITEM_OFFSET + 100 + 45, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_COIN_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(12574,), + archipelago_id=ITEM_OFFSET + 100 + 46, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SOUVENIR_COIN_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(13412,), + archipelago_id=ITEM_OFFSET + 100 + 47, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER: ZorkGrandInquisitorItemData( + statemap_keys=(12170,), + archipelago_id=ITEM_OFFSET + 100 + 48, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM: ZorkGrandInquisitorItemData( + statemap_keys=(16382,), + archipelago_id=ITEM_OFFSET + 100 + 49, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM: ZorkGrandInquisitorItemData( + statemap_keys=(4209,), + archipelago_id=ITEM_OFFSET + 100 + 50, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE: ZorkGrandInquisitorItemData( + statemap_keys=(11973,), + archipelago_id=ITEM_OFFSET + 100 + 51, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT: ZorkGrandInquisitorItemData( + statemap_keys=(13168,), + archipelago_id=ITEM_OFFSET + 100 + 52, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY: ZorkGrandInquisitorItemData( + statemap_keys=(15396,), + archipelago_id=ITEM_OFFSET + 100 + 53, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH: ZorkGrandInquisitorItemData( + statemap_keys=(9706,), + archipelago_id=ITEM_OFFSET + 100 + 54, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS: ZorkGrandInquisitorItemData( + statemap_keys=(9728, 9729, 9730), + archipelago_id=ITEM_OFFSET + 100 + 55, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + ZorkGrandInquisitorItems.HOTSPOT_WELL: ZorkGrandInquisitorItemData( + statemap_keys=(10314,), + archipelago_id=ITEM_OFFSET + 100 + 56, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.HOTSPOT,), + ), + # Spells + ZorkGrandInquisitorItems.SPELL_GLORF: ZorkGrandInquisitorItemData( + statemap_keys=(202,), + archipelago_id=ITEM_OFFSET + 200 + 0, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + ZorkGrandInquisitorItems.SPELL_GOLGATEM: ZorkGrandInquisitorItemData( + statemap_keys=(192,), + archipelago_id=ITEM_OFFSET + 200 + 1, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + ZorkGrandInquisitorItems.SPELL_IGRAM: ZorkGrandInquisitorItemData( + statemap_keys=(199,), + archipelago_id=ITEM_OFFSET + 200 + 2, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + ZorkGrandInquisitorItems.SPELL_KENDALL: ZorkGrandInquisitorItemData( + statemap_keys=(196,), + archipelago_id=ITEM_OFFSET + 200 + 3, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + ZorkGrandInquisitorItems.SPELL_NARWILE: ZorkGrandInquisitorItemData( + statemap_keys=(197,), + archipelago_id=ITEM_OFFSET + 200 + 4, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + ZorkGrandInquisitorItems.SPELL_REZROV: ZorkGrandInquisitorItemData( + statemap_keys=(195,), + archipelago_id=ITEM_OFFSET + 200 + 5, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + ZorkGrandInquisitorItems.SPELL_THROCK: ZorkGrandInquisitorItemData( + statemap_keys=(200,), + archipelago_id=ITEM_OFFSET + 200 + 6, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + ZorkGrandInquisitorItems.SPELL_VOXAM: ZorkGrandInquisitorItemData( + statemap_keys=(191,), + archipelago_id=ITEM_OFFSET + 200 + 7, + classification=ItemClassification.useful, + tags=(ZorkGrandInquisitorTags.SPELL,), + ), + # Subway Destinations + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM: ZorkGrandInquisitorItemData( + statemap_keys=(13757, 13297, 13486, 13625), + archipelago_id=ITEM_OFFSET + 300 + 0, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SUBWAY_DESTINATION,), + ), + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES: ZorkGrandInquisitorItemData( + statemap_keys=(13758, 13309, 13498, 13637), + archipelago_id=ITEM_OFFSET + 300 + 1, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SUBWAY_DESTINATION,), + ), + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY: ZorkGrandInquisitorItemData( + statemap_keys=(13759, 13316, 13505, 13644), + archipelago_id=ITEM_OFFSET + 300 + 2, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.SUBWAY_DESTINATION,), + ), + # Teleporter Destinations + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR: ZorkGrandInquisitorItemData( + statemap_keys=(2203,), + archipelago_id=ITEM_OFFSET + 400 + 0, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), + ), + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH: ZorkGrandInquisitorItemData( + statemap_keys=(7132,), + archipelago_id=ITEM_OFFSET + 400 + 1, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), + ), + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES: ZorkGrandInquisitorItemData( + statemap_keys=(7119,), + archipelago_id=ITEM_OFFSET + 400 + 2, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), + ), + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY: ZorkGrandInquisitorItemData( + statemap_keys=(7148,), + archipelago_id=ITEM_OFFSET + 400 + 3, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), + ), + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB: ZorkGrandInquisitorItemData( + statemap_keys=(16545,), + archipelago_id=ITEM_OFFSET + 400 + 4, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), + ), + # Totemizer Destinations + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION: ZorkGrandInquisitorItemData( + statemap_keys=(9660,), + archipelago_id=ITEM_OFFSET + 500 + 0, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,), + ), + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_INFINITY: ZorkGrandInquisitorItemData( + statemap_keys=(9666,), + archipelago_id=ITEM_OFFSET + 500 + 1, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,), + ), + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL: ZorkGrandInquisitorItemData( + statemap_keys=(9668,), + archipelago_id=ITEM_OFFSET + 500 + 2, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,), + ), + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_SURFACE_OF_MERZ: ZorkGrandInquisitorItemData( + statemap_keys=(9662,), + archipelago_id=ITEM_OFFSET + 500 + 3, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,), + ), + # Totems + ZorkGrandInquisitorItems.TOTEM_BROG: ZorkGrandInquisitorItemData( + statemap_keys=(4853,), + archipelago_id=ITEM_OFFSET + 600 + 0, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TOTEM,), + ), + ZorkGrandInquisitorItems.TOTEM_GRIFF: ZorkGrandInquisitorItemData( + statemap_keys=(4315,), + archipelago_id=ITEM_OFFSET + 600 + 1, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TOTEM,), + ), + ZorkGrandInquisitorItems.TOTEM_LUCY: ZorkGrandInquisitorItemData( + statemap_keys=(5223,), + archipelago_id=ITEM_OFFSET + 600 + 2, + classification=ItemClassification.progression, + tags=(ZorkGrandInquisitorTags.TOTEM,), + ), + # Filler + ZorkGrandInquisitorItems.FILLER_INQUISITION_PROPAGANDA_FLYER: ZorkGrandInquisitorItemData( + statemap_keys=None, + archipelago_id=ITEM_OFFSET + 700 + 0, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.FILLER,), + maximum_quantity=None, + ), + ZorkGrandInquisitorItems.FILLER_UNREADABLE_SPELL_SCROLL: ZorkGrandInquisitorItemData( + statemap_keys=None, + archipelago_id=ITEM_OFFSET + 700 + 1, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.FILLER,), + maximum_quantity=None, + ), + ZorkGrandInquisitorItems.FILLER_MAGIC_CONTRABAND: ZorkGrandInquisitorItemData( + statemap_keys=None, + archipelago_id=ITEM_OFFSET + 700 + 2, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.FILLER,), + maximum_quantity=None, + ), + ZorkGrandInquisitorItems.FILLER_FROBOZZ_ELECTRIC_GADGET: ZorkGrandInquisitorItemData( + statemap_keys=None, + archipelago_id=ITEM_OFFSET + 700 + 3, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.FILLER,), + maximum_quantity=None, + ), + ZorkGrandInquisitorItems.FILLER_NONSENSICAL_INQUISITION_PAPERWORK: ZorkGrandInquisitorItemData( + statemap_keys=None, + archipelago_id=ITEM_OFFSET + 700 + 4, + classification=ItemClassification.filler, + tags=(ZorkGrandInquisitorTags.FILLER,), + maximum_quantity=None, + ), +} diff --git a/worlds/zork_grand_inquisitor/data/location_data.py b/worlds/zork_grand_inquisitor/data/location_data.py new file mode 100644 index 0000000000..8b4e57392d --- /dev/null +++ b/worlds/zork_grand_inquisitor/data/location_data.py @@ -0,0 +1,1535 @@ +from typing import Dict, NamedTuple, Optional, Tuple, Union + +from ..enums import ( + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorLocations, + ZorkGrandInquisitorRegions, + ZorkGrandInquisitorTags, +) + + +class ZorkGrandInquisitorLocationData(NamedTuple): + game_state_trigger: Optional[ + Tuple[ + Union[ + Tuple[str, str], + Tuple[int, int], + Tuple[int, Tuple[int, ...]], + ], + ..., + ] + ] + archipelago_id: Optional[int] + region: ZorkGrandInquisitorRegions + tags: Optional[Tuple[ZorkGrandInquisitorTags, ...]] = None + requirements: Optional[ + Tuple[ + Union[ + Union[ + ZorkGrandInquisitorItems, + ZorkGrandInquisitorEvents, + ], + Tuple[ + Union[ + ZorkGrandInquisitorItems, + ZorkGrandInquisitorEvents, + ], + ..., + ], + ], + ..., + ] + ] = None + event_item_name: Optional[str] = None + + +LOCATION_OFFSET = 9758067000 + +location_data: Dict[ + Union[ZorkGrandInquisitorLocations, ZorkGrandInquisitorEvents], ZorkGrandInquisitorLocationData +] = { + ZorkGrandInquisitorLocations.ALARM_SYSTEM_IS_DOWN: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tr2m"),), + archipelago_id=LOCATION_OFFSET + 0, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.ARREST_THE_VANDAL: ZorkGrandInquisitorLocationData( + game_state_trigger=((10789, 1),), + archipelago_id=LOCATION_OFFSET + 1, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, + ), + ), + ZorkGrandInquisitorLocations.ARTIFACTS_EXPLAINED: ZorkGrandInquisitorLocationData( + game_state_trigger=((11787, 1), (11788, 1), (11789, 1)), + archipelago_id=LOCATION_OFFSET + 2, + region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER: ZorkGrandInquisitorLocationData( + game_state_trigger=((8929, 1),), + archipelago_id=LOCATION_OFFSET + 3, + region=ZorkGrandInquisitorRegions.HADES, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.KNOWS_OBIDIL,), + ), + ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE: ZorkGrandInquisitorLocationData( + game_state_trigger=((9124, 1),), + archipelago_id=LOCATION_OFFSET + 4, + region=ZorkGrandInquisitorRegions.HADES, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE, + ZorkGrandInquisitorItems.HOTSPOT_666_MAILBOX, + ), + ), + ZorkGrandInquisitorLocations.A_SMALLWAY: ZorkGrandInquisitorLocationData( + game_state_trigger=((11777, 1),), + archipelago_id=LOCATION_OFFSET + 5, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS, + ZorkGrandInquisitorItems.SPELL_IGRAM, + ), + ), + ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY: ZorkGrandInquisitorLocationData( + game_state_trigger=((13278, 1),), + archipelago_id=LOCATION_OFFSET + 6, + region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_MOSSY_GRATE, + ZorkGrandInquisitorItems.SPELL_THROCK, + ), + ), + ZorkGrandInquisitorLocations.BEBURTT_DEMYSTIFIED: ZorkGrandInquisitorLocationData( + game_state_trigger=((16315, 1),), + archipelago_id=LOCATION_OFFSET + 7, + region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE, + ZorkGrandInquisitorItems.SPELL_KENDALL, + ), + ), + ZorkGrandInquisitorLocations.BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "th3x"),), + archipelago_id=LOCATION_OFFSET + 8, + region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE,), + ), + ZorkGrandInquisitorLocations.BOING_BOING_BOING: ZorkGrandInquisitorLocationData( + game_state_trigger=((4220, 1),), + archipelago_id=LOCATION_OFFSET + 9, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.HAMMER, + ZorkGrandInquisitorItems.SNAPDRAGON, + ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM, + ), + ), + ZorkGrandInquisitorLocations.BONK: ZorkGrandInquisitorLocationData( + game_state_trigger=((19491, 1),), + archipelago_id=LOCATION_OFFSET + 10, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.HAMMER, + ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON, + ), + ), + ZorkGrandInquisitorLocations.BRAVE_SOULS_WANTED: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "us2g"),), + archipelago_id=LOCATION_OFFSET + 11, + region=ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.BROG_DO_GOOD: ZorkGrandInquisitorLocationData( + game_state_trigger=((2644, 1),), + archipelago_id=LOCATION_OFFSET + 12, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_BROG, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ) + ), + ZorkGrandInquisitorLocations.BROG_EAT_ROCKS: ZorkGrandInquisitorLocationData( + game_state_trigger=((2629, 1),), + archipelago_id=LOCATION_OFFSET + 13, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_BROG, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ) + ), + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB: ZorkGrandInquisitorLocationData( + game_state_trigger=((2650, 1),), + archipelago_id=LOCATION_OFFSET + 14, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_BROG, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ) + ), + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME: ZorkGrandInquisitorLocationData( + game_state_trigger=((15715, 1),), + archipelago_id=LOCATION_OFFSET + 15, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_BROG, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ZorkGrandInquisitorItems.BROGS_PLANK, + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE, + ) + ), + ZorkGrandInquisitorLocations.CASTLE_WATCHING_A_FIELD_GUIDE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "dv1t"),), + archipelago_id=LOCATION_OFFSET + 16, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.CAVES_NOTES: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "th3y"),), + archipelago_id=LOCATION_OFFSET + 17, + region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE,), + ), + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS: ZorkGrandInquisitorLocationData( + game_state_trigger=((9543, 1),), + archipelago_id=LOCATION_OFFSET + 18, + region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.CRISIS_AVERTED: ZorkGrandInquisitorLocationData( + game_state_trigger=((11769, 1),), + archipelago_id=LOCATION_OFFSET + 19, + region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED, + ZorkGrandInquisitorItems.SPELL_IGRAM, + ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS, + ZorkGrandInquisitorItems.HOTSPOT_DENTED_LOCKER, + ), + ), + ZorkGrandInquisitorLocations.CUT_THAT_OUT_YOU_LITTLE_CREEP: ZorkGrandInquisitorLocationData( + game_state_trigger=((19350, 1),), + archipelago_id=LOCATION_OFFSET + 20, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER: ZorkGrandInquisitorLocationData( + game_state_trigger=((17632, 1),), + archipelago_id=LOCATION_OFFSET + 21, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_BLINDS, + ZorkGrandInquisitorItems.SPELL_GOLGATEM, + ), + ), + ZorkGrandInquisitorLocations.DESPERATELY_SEEKING_TUTOR: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tr2q"),), + archipelago_id=LOCATION_OFFSET + 22, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "hp5e"), (8919, 2), (9, 100)), + archipelago_id=LOCATION_OFFSET + 23, + region=ZorkGrandInquisitorRegions.HADES, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.SWORD,), + ), + ZorkGrandInquisitorLocations.DOOOOOOWN: ZorkGrandInquisitorLocationData( + game_state_trigger=((3619, 3600),), + archipelago_id=LOCATION_OFFSET + 24, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_GRIFF, + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, + ), + ), + ZorkGrandInquisitorLocations.DOWN: ZorkGrandInquisitorLocationData( + game_state_trigger=((3619, 5300),), + archipelago_id=LOCATION_OFFSET + 25, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_LUCY, + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, + ), + ), + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL: ZorkGrandInquisitorLocationData( + game_state_trigger=((9216, 1),), + archipelago_id=LOCATION_OFFSET + 26, + region=ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.SPELL_NARWILE,), + ), + ZorkGrandInquisitorLocations.DUNCE_LOCKER: ZorkGrandInquisitorLocationData( + game_state_trigger=((11851, 1),), + archipelago_id=LOCATION_OFFSET + 27, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, + ), + ), + ZorkGrandInquisitorLocations.EGGPLANTS: ZorkGrandInquisitorLocationData( + game_state_trigger=((3816, 11000),), + archipelago_id=LOCATION_OFFSET + 28, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.ELSEWHERE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "pc1e"),), + archipelago_id=LOCATION_OFFSET + 29, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.EMERGENCY_MAGICATRONIC_MESSAGE: ZorkGrandInquisitorLocationData( + game_state_trigger=((11784, 1),), + archipelago_id=LOCATION_OFFSET + 30, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + ), + ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP: ZorkGrandInquisitorLocationData( + game_state_trigger=((13743, 1),), + archipelago_id=LOCATION_OFFSET + 31, + region=ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.SPELL_KENDALL,), + ), + ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA: ZorkGrandInquisitorLocationData( + game_state_trigger=((16368, 1),), + archipelago_id=LOCATION_OFFSET + 32, + region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.SPELL_IGRAM,), + ), + ZorkGrandInquisitorLocations.FIRE_FIRE: ZorkGrandInquisitorLocationData( + game_state_trigger=((10277, 1),), + archipelago_id=LOCATION_OFFSET + 33, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, + ), + ), + ZorkGrandInquisitorLocations.FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "ue1h"),), + archipelago_id=LOCATION_OFFSET + 34, + region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON: ZorkGrandInquisitorLocationData( + game_state_trigger=((4222, 1),), + archipelago_id=LOCATION_OFFSET + 35, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SPELL_THROCK, + ZorkGrandInquisitorItems.SNAPDRAGON, + ZorkGrandInquisitorItems.HAMMER, + ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM, + ), + ), + ZorkGrandInquisitorLocations.FROBUARY_3_UNDERGROUNDHOG_DAY: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "dw2g"),), + archipelago_id=LOCATION_OFFSET + 36, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.GETTING_SOME_CHANGE: ZorkGrandInquisitorLocationData( + game_state_trigger=((12892, 1),), + archipelago_id=LOCATION_OFFSET + 37, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.ZORKMID_BILL_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_CHANGE_MACHINE_SLOT, + ), + ), + ZorkGrandInquisitorLocations.GO_AWAY: ZorkGrandInquisitorLocationData( + game_state_trigger=((10654, 1),), + archipelago_id=LOCATION_OFFSET + 38, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.GUE_TECH_DEANS_LIST: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tr2k"),), + archipelago_id=LOCATION_OFFSET + 39, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.GUE_TECH_ENTRANCE_EXAM: ZorkGrandInquisitorLocationData( + game_state_trigger=((11082, 1), (11307, 1), (11536, 1)), + archipelago_id=LOCATION_OFFSET + 40, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.GUE_TECH_HEALTH_MEMO: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tr2j"),), + archipelago_id=LOCATION_OFFSET + 41, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.GUE_TECH_MAGEMEISTERS: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tr2n"),), + archipelago_id=LOCATION_OFFSET + 42, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY: ZorkGrandInquisitorLocationData( + game_state_trigger=((8443, 1),), + archipelago_id=LOCATION_OFFSET + 43, + region=ZorkGrandInquisitorRegions.HADES_SHORE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER, + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS, + ) + ), + ZorkGrandInquisitorLocations.HELLO_THIS_IS_SHONA_FROM_GURTH_PUBLISHING: ZorkGrandInquisitorLocationData( + game_state_trigger=((4698, 1),), + archipelago_id=LOCATION_OFFSET + 44, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE: ZorkGrandInquisitorLocationData( + game_state_trigger=((10421, 1),), + archipelago_id=LOCATION_OFFSET + 45, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH, + ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER, + ), + ), + ZorkGrandInquisitorLocations.HEY_FREE_DIRT: ZorkGrandInquisitorLocationData( + game_state_trigger=((11747, 1),), + archipelago_id=LOCATION_OFFSET + 46, + region=ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_DIRT_MOUND, + ZorkGrandInquisitorItems.SHOVEL, + ), + ), + ZorkGrandInquisitorLocations.HI_MY_NAME_IS_DOUG: ZorkGrandInquisitorLocationData( + game_state_trigger=((4698, 2),), + archipelago_id=LOCATION_OFFSET + 47, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "mt2h"),), + archipelago_id=LOCATION_OFFSET + 48, + region=ZorkGrandInquisitorRegions.MONASTERY, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.HOLD_ON_FOR_AN_IMPORTANT_MESSAGE: ZorkGrandInquisitorLocationData( + game_state_trigger=((4698, 5),), + archipelago_id=LOCATION_OFFSET + 49, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.HOW_TO_HYPNOTIZE_YOURSELF: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "uh1e"),), + archipelago_id=LOCATION_OFFSET + 50, + region=ZorkGrandInquisitorRegions.HADES_SHORE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.HOW_TO_WIN_AT_DOUBLE_FANUCCI: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "th3s"),), + archipelago_id=LOCATION_OFFSET + 51, + region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE,), + ), + ZorkGrandInquisitorLocations.IMBUE_BEBURTT: ZorkGrandInquisitorLocationData( + game_state_trigger=((194, 1),), + archipelago_id=LOCATION_OFFSET + 52, + region=ZorkGrandInquisitorRegions.SPELL_LAB, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, + ), + ), + ZorkGrandInquisitorLocations.IM_COMPLETELY_NUDE: ZorkGrandInquisitorLocationData( + game_state_trigger=((19344, 1),), + archipelago_id=LOCATION_OFFSET + 53, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE: ZorkGrandInquisitorLocationData( + game_state_trigger=((13060, 1),), + archipelago_id=LOCATION_OFFSET + 54, + region=ZorkGrandInquisitorRegions.CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE, + ), + ), + ZorkGrandInquisitorLocations.INVISIBLE_FLOWERS: ZorkGrandInquisitorLocationData( + game_state_trigger=((12967, 1),), + archipelago_id=LOCATION_OFFSET + 55, + region=ZorkGrandInquisitorRegions.CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.SPELL_IGRAM,), + ), + ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE: ZorkGrandInquisitorLocationData( + game_state_trigger=((12931, 1),), + archipelago_id=LOCATION_OFFSET + 56, + region=ZorkGrandInquisitorRegions.CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HAMMER, + ZorkGrandInquisitorItems.HOTSPOT_GLASS_CASE, + ), + ), + ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST: ZorkGrandInquisitorLocationData( + game_state_trigger=((13062, 1),), + archipelago_id=LOCATION_OFFSET + 57, + region=ZorkGrandInquisitorRegions.CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SPELL_REZROV, + ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR, + ), + ), + ZorkGrandInquisitorLocations.ITS_ONE_OF_THOSE_ADVENTURERS_AGAIN: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "pe3j"),), + archipelago_id=LOCATION_OFFSET + 58, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY: ZorkGrandInquisitorLocationData( + game_state_trigger=((3816, 1008),), + archipelago_id=LOCATION_OFFSET + 59, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.SPELL_THROCK, + ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON, + ), + ), + ZorkGrandInquisitorLocations.I_DONT_WANT_NO_TROUBLE: ZorkGrandInquisitorLocationData( + game_state_trigger=((10694, 1),), + archipelago_id=LOCATION_OFFSET + 60, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE: ZorkGrandInquisitorLocationData( + game_state_trigger=((9637, 1),), + archipelago_id=LOCATION_OFFSET + 61, + region=ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorEvents.ROPE_GLORFABLE, + ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT, + ), + ), + ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE: ZorkGrandInquisitorLocationData( + game_state_trigger=((16374, 1),), + archipelago_id=LOCATION_OFFSET + 62, + region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE, + ZorkGrandInquisitorEvents.DAM_DESTROYED, + ZorkGrandInquisitorItems.SPELL_GOLGATEM, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM, + ), + ), + ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tp1e"), (9, 87), (1011, 1)), + archipelago_id=LOCATION_OFFSET + 63, + region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS,), + ), + ZorkGrandInquisitorLocations.LIT_SUNFLOWERS: ZorkGrandInquisitorLocationData( + game_state_trigger=((4129, 1),), + archipelago_id=LOCATION_OFFSET + 64, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.SPELL_THROCK,), + ), + ZorkGrandInquisitorLocations.MAGIC_FOREVER: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "pc1e"), (10304, 1), (5221, 1)), + archipelago_id=LOCATION_OFFSET + 65, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE, + ZorkGrandInquisitorItems.ROPE, + ZorkGrandInquisitorItems.HOTSPOT_WELL, + ), + ), + ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL: ZorkGrandInquisitorLocationData( + game_state_trigger=((2498, (1, 2)),), + archipelago_id=LOCATION_OFFSET + 66, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + (ZorkGrandInquisitorItems.TOTEM_GRIFF, ZorkGrandInquisitorItems.TOTEM_LUCY), + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR, + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, + ), + ), + ZorkGrandInquisitorLocations.MAKE_LOVE_NOT_WAR: ZorkGrandInquisitorLocationData( + game_state_trigger=((8623, 21),), + archipelago_id=LOCATION_OFFSET + 67, + region=ZorkGrandInquisitorRegions.HADES_SHORE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.CHARON_CALLED, + ZorkGrandInquisitorItems.SWORD, + ), + ), + ZorkGrandInquisitorLocations.MEAD_LIGHT: ZorkGrandInquisitorLocationData( + game_state_trigger=((10485, 1),), + archipelago_id=LOCATION_OFFSET + 68, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.MEAD_LIGHT, + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR, + ), + ), + ZorkGrandInquisitorLocations.MIKES_PANTS: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tr2p"),), + archipelago_id=LOCATION_OFFSET + 69, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED: ZorkGrandInquisitorLocationData( + game_state_trigger=((4217, 1),), + archipelago_id=LOCATION_OFFSET + 70, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.HAMMER, + ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM, + ), + ), + ZorkGrandInquisitorLocations.NATIONAL_TREASURE: ZorkGrandInquisitorLocationData( + game_state_trigger=((14318, 1),), + archipelago_id=LOCATION_OFFSET + 71, + region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SPELL_REZROV, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS, + ), + ), + ZorkGrandInquisitorLocations.NATURAL_AND_SUPERNATURAL_CREATURES_OF_QUENDOR: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "dv1p"),), + archipelago_id=LOCATION_OFFSET + 72, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO: ZorkGrandInquisitorLocationData( + game_state_trigger=((12706, 1),), + archipelago_id=LOCATION_OFFSET + 73, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, + ), + ), + ZorkGrandInquisitorLocations.NOTHIN_LIKE_A_GOOD_STOGIE: ZorkGrandInquisitorLocationData( + game_state_trigger=((4237, 1),), + archipelago_id=LOCATION_OFFSET + 74, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY, + ), + ), + ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT: ZorkGrandInquisitorLocationData( + game_state_trigger=((8935, 1),), + archipelago_id=LOCATION_OFFSET + 75, + region=ZorkGrandInquisitorRegions.HADES, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.KNOWS_SNAVIG,), + ), + ZorkGrandInquisitorLocations.NO_AUTOGRAPHS: ZorkGrandInquisitorLocationData( + game_state_trigger=((10476, 1),), + archipelago_id=LOCATION_OFFSET + 76, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR,), + ), + ZorkGrandInquisitorLocations.NO_BONDAGE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "pe2e"), (10262, 2), (15150, 83)), + archipelago_id=LOCATION_OFFSET + 77, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.ROPE, + ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH, + ), + ), + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP: ZorkGrandInquisitorLocationData( + game_state_trigger=((12164, 1),), + archipelago_id=LOCATION_OFFSET + 78, + region=ZorkGrandInquisitorRegions.SPELL_LAB, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, + ), + ), + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON: ZorkGrandInquisitorLocationData( + game_state_trigger=((1300, 1),), + archipelago_id=LOCATION_OFFSET + 79, + region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, + ), + ), + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS: ZorkGrandInquisitorLocationData( + game_state_trigger=((2448, 1),), + archipelago_id=LOCATION_OFFSET + 80, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_BROG, + ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR, + ), + ), + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU: ZorkGrandInquisitorLocationData( + game_state_trigger=((4869, 1),), + archipelago_id=LOCATION_OFFSET + 81, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.FLATHEADIA_FUDGE, + ZorkGrandInquisitorItems.HUNGUS_LARD, + ZorkGrandInquisitorItems.JAR_OF_HOTBUGS, + ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB, + ZorkGrandInquisitorItems.MOSS_OF_MAREILON, + ZorkGrandInquisitorItems.MUG, + ), + ), + ZorkGrandInquisitorLocations.OLD_SCRATCH_WINNER: ZorkGrandInquisitorLocationData( + game_state_trigger=((4512, 32),), + archipelago_id=LOCATION_OFFSET + 82, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, # This can be done anywhere if the item requirement is met + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.OLD_SCRATCH_CARD,), + ), + ZorkGrandInquisitorLocations.ONLY_YOU_CAN_PREVENT_FOOZLE_FIRES: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "pe5n"),), + archipelago_id=LOCATION_OFFSET + 83, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL: ZorkGrandInquisitorLocationData( + game_state_trigger=((8730, 1),), + archipelago_id=LOCATION_OFFSET + 84, + region=ZorkGrandInquisitorRegions.HADES, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.KNOWS_SNAVIG,), + ), + ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES: ZorkGrandInquisitorLocationData( + game_state_trigger=((4241, 1),), + archipelago_id=LOCATION_OFFSET + 85, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HUNGUS_LARD, + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE, + ), + ), + ZorkGrandInquisitorLocations.PERMASEAL: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "mt1g"),), + archipelago_id=LOCATION_OFFSET + 86, + region=ZorkGrandInquisitorRegions.MONASTERY, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.PLANETFALL: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "pp1j"),), + archipelago_id=LOCATION_OFFSET + 87, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.PLEASE_DONT_THROCK_THE_GRASS: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "te1g"),), + archipelago_id=LOCATION_OFFSET + 88, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL: ZorkGrandInquisitorLocationData( + game_state_trigger=((9404, 1),), + archipelago_id=LOCATION_OFFSET + 89, + region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER, + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT, + ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER, + ZorkGrandInquisitorItems.SPELL_NARWILE, + ), + ), + ZorkGrandInquisitorLocations.PROZORKED: ZorkGrandInquisitorLocationData( + game_state_trigger=((4115, 1),), + archipelago_id=LOCATION_OFFSET + 90, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.PROZORK_TABLET, + ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON, + ), + ), + ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG: ZorkGrandInquisitorLocationData( + game_state_trigger=((4512, 98),), + archipelago_id=LOCATION_OFFSET + 91, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS, + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV, + ZorkGrandInquisitorItems.HOTSPOT_MIRROR, + ), + ), + ZorkGrandInquisitorLocations.RESTOCKED_ON_GRUESDAY: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tr2h"),), + archipelago_id=LOCATION_OFFSET + 92, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.RIGHT_HELLO_YES_UH_THIS_IS_SNEFFLE: ZorkGrandInquisitorLocationData( + game_state_trigger=((4698, 3),), + archipelago_id=LOCATION_OFFSET + 93, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.RIGHT_UH_SORRY_ITS_ME_AGAIN_SNEFFLE: ZorkGrandInquisitorLocationData( + game_state_trigger=((4698, 4),), + archipelago_id=LOCATION_OFFSET + 94, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED: ZorkGrandInquisitorLocationData( + game_state_trigger=((201, 1),), + archipelago_id=LOCATION_OFFSET + 95, + region=ZorkGrandInquisitorRegions.SPELL_LAB, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, + ), + ), + ZorkGrandInquisitorLocations.SOUVENIR: ZorkGrandInquisitorLocationData( + game_state_trigger=((13408, 1),), + archipelago_id=LOCATION_OFFSET + 96, + region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_SOUVENIR_COIN_SLOT, + ), + ), + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL: ZorkGrandInquisitorLocationData( + game_state_trigger=((9719, 1),), + archipelago_id=LOCATION_OFFSET + 97, + region=ZorkGrandInquisitorRegions.MONASTERY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS, + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH, + ), + ), + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER: ZorkGrandInquisitorLocationData( + game_state_trigger=((14511, 1), (14524, 5)), + archipelago_id=LOCATION_OFFSET + 98, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, + ), + ), + ZorkGrandInquisitorLocations.SUCKING_ROCKS: ZorkGrandInquisitorLocationData( + game_state_trigger=((12859, 1),), + archipelago_id=LOCATION_OFFSET + 99, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE, + ZorkGrandInquisitorItems.PERMA_SUCK_MACHINE, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_VACUUM_SLOT, + ), + ), + ZorkGrandInquisitorLocations.TALK_TO_ME_GRAND_INQUISITOR: ZorkGrandInquisitorLocationData( + game_state_trigger=((10299, 1),), + archipelago_id=LOCATION_OFFSET + 100, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL,), + ), + ZorkGrandInquisitorLocations.TAMING_YOUR_SNAPDRAGON: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "dv1h"),), + archipelago_id=LOCATION_OFFSET + 101, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS: ZorkGrandInquisitorLocationData( + game_state_trigger=((1311, 1), (1312, 1)), + archipelago_id=LOCATION_OFFSET + 102, + region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, + ), + ), + ZorkGrandInquisitorLocations.THATS_A_ROPE: ZorkGrandInquisitorLocationData( + game_state_trigger=((10486, 1),), + archipelago_id=LOCATION_OFFSET + 103, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.ROPE, + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR, + ), + ), + ZorkGrandInquisitorLocations.THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS: ZorkGrandInquisitorLocationData( + game_state_trigger=((13805, 1),), + archipelago_id=LOCATION_OFFSET + 104, + region=ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + ), + ZorkGrandInquisitorLocations.THATS_STILL_A_ROPE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "tp1e"), (9, 83), (1011, 1)), + archipelago_id=LOCATION_OFFSET + 105, + region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorEvents.ROPE_GLORFABLE,), + ), + ZorkGrandInquisitorLocations.THATS_THE_SPIRIT: ZorkGrandInquisitorLocationData( + game_state_trigger=((10341, 95),), + archipelago_id=LOCATION_OFFSET + 106, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS,), + ), + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE: ZorkGrandInquisitorLocationData( + game_state_trigger=((9459, 1),), + archipelago_id=LOCATION_OFFSET + 107, + region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE: ZorkGrandInquisitorLocationData( + game_state_trigger=((9473, 1),), + archipelago_id=LOCATION_OFFSET + 108, + region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO: ZorkGrandInquisitorLocationData( + game_state_trigger=((9520, 1),), + archipelago_id=LOCATION_OFFSET + 109, + region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "me1j"),), + archipelago_id=LOCATION_OFFSET + 110, + region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND: ZorkGrandInquisitorLocationData( + game_state_trigger=((13167, 1),), + archipelago_id=LOCATION_OFFSET + 111, + region=ZorkGrandInquisitorRegions.CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SUBWAY_TOKEN, + ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT, + ), + ), + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "cd60"), (1524, 1)), + archipelago_id=LOCATION_OFFSET + 112, + region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.TOTEM_LUCY,), + ), + ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED: ZorkGrandInquisitorLocationData( + game_state_trigger=((4219, 1),), + archipelago_id=LOCATION_OFFSET + 113, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HAMMER, + ZorkGrandInquisitorItems.SPELL_THROCK, + ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM, + ), + ), + ZorkGrandInquisitorLocations.TIME_TRAVEL_FOR_DUMMIES: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "th3z"),), + archipelago_id=LOCATION_OFFSET + 114, + region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE,), + ), + ZorkGrandInquisitorLocations.TOTEMIZED_DAILY_BILLBOARD: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "px1h"),), + archipelago_id=LOCATION_OFFSET + 115, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "cd60"), (1520, 1)), + archipelago_id=LOCATION_OFFSET + 116, + region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.TOTEM_BROG,), + ), + ZorkGrandInquisitorLocations.UMBRELLA_FLOWERS: ZorkGrandInquisitorLocationData( + game_state_trigger=((12926, 1),), + archipelago_id=LOCATION_OFFSET + 117, + region=ZorkGrandInquisitorRegions.CROSSROADS, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorEvents.KNOWS_BEBURTT,), + ), + ZorkGrandInquisitorLocations.UP: ZorkGrandInquisitorLocationData( + game_state_trigger=((3619, 5200),), + archipelago_id=LOCATION_OFFSET + 118, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_LUCY, + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, + ), + ), + ZorkGrandInquisitorLocations.USELESS_BUT_FUN: ZorkGrandInquisitorLocationData( + game_state_trigger=((14321, 1),), + archipelago_id=LOCATION_OFFSET + 119, + region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=(ZorkGrandInquisitorItems.SPELL_GOLGATEM,), + ), + ZorkGrandInquisitorLocations.UUUUUP: ZorkGrandInquisitorLocationData( + game_state_trigger=((3619, 3500),), + archipelago_id=LOCATION_OFFSET + 120, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_GRIFF, + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, + ), + ), + ZorkGrandInquisitorLocations.VOYAGE_OF_CAPTAIN_ZAHAB: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "uh1h"),), + archipelago_id=LOCATION_OFFSET + 121, + region=ZorkGrandInquisitorRegions.HADES_SHORE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO: ZorkGrandInquisitorLocationData( + game_state_trigger=((4034, 1),), + archipelago_id=LOCATION_OFFSET + 122, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR, + ZorkGrandInquisitorItems.MEAD_LIGHT, + ZorkGrandInquisitorItems.ZIMDOR_SCROLL, + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH, + ), + ), + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE: ZorkGrandInquisitorLocationData( + game_state_trigger=((2461, 1),), + archipelago_id=LOCATION_OFFSET + 123, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.TOTEM_GRIFF, + ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR, + ), + ), + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER: ZorkGrandInquisitorLocationData( + game_state_trigger=((15472, 1),), + archipelago_id=LOCATION_OFFSET + 124, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, + ), + ), + ZorkGrandInquisitorLocations.WHAT_ARE_YOU_STUPID: ZorkGrandInquisitorLocationData( + game_state_trigger=((10484, 1),), + archipelago_id=LOCATION_OFFSET + 125, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER, + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR, + ), + ), + ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL: ZorkGrandInquisitorLocationData( + game_state_trigger=((4983, 1),), + archipelago_id=LOCATION_OFFSET + 126, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR, + ZorkGrandInquisitorItems.SPELL_NARWILE, + ), + ), + ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "dc10"), (1596, 1)), + archipelago_id=LOCATION_OFFSET + 127, + region=ZorkGrandInquisitorRegions.WALKING_CASTLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "dm2g"),), + archipelago_id=LOCATION_OFFSET + 128, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.HOTSPOT_MIRROR,), + ), + ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "dg4e"), (4266, 1), (9, 21), (4035, 1)), + archipelago_id=LOCATION_OFFSET + 129, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_HARRY, + ), + ), + ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER: ZorkGrandInquisitorLocationData( + game_state_trigger=((16405, 1),), + archipelago_id=LOCATION_OFFSET + 130, + region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.SPELL_REZROV,), + ), + ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS: ZorkGrandInquisitorLocationData( + game_state_trigger=((16342, 1),), + archipelago_id=LOCATION_OFFSET + 131, + region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + tags=(ZorkGrandInquisitorTags.CORE,), + requirements=( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE, + ), + ), + ZorkGrandInquisitorLocations.YOU_ONE_OF_THEM_AGITATORS_AINT_YA: ZorkGrandInquisitorLocationData( + game_state_trigger=((10586, 1),), + archipelago_id=LOCATION_OFFSET + 132, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE,), + ), + ZorkGrandInquisitorLocations.YOU_WANT_A_PIECE_OF_ME_DOCK_BOY: ZorkGrandInquisitorLocationData( + game_state_trigger=((15151, 1),), + archipelago_id=LOCATION_OFFSET + 133, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH,), + ), + # Deathsanity + ZorkGrandInquisitorLocations.DEATH_ARRESTED_WITH_JACK: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 1)), + archipelago_id=LOCATION_OFFSET + 200 + 0, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, + ), + ), + ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 20)), + archipelago_id=LOCATION_OFFSET + 200 + 1, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.SWORD, + ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE, + ), + ), + ZorkGrandInquisitorLocations.DEATH_CLIMBED_OUT_OF_THE_WELL: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 21)), + archipelago_id=LOCATION_OFFSET + 200 + 2, + region=ZorkGrandInquisitorRegions.CROSSROADS, + tags=(ZorkGrandInquisitorTags.DEATHSANITY,), + ), + ZorkGrandInquisitorLocations.DEATH_EATEN_BY_A_GRUE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 18)), + archipelago_id=LOCATION_OFFSET + 200 + 3, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.ROPE, + ZorkGrandInquisitorItems.HOTSPOT_WELL, + ), + ), + ZorkGrandInquisitorLocations.DEATH_JUMPED_IN_BOTTOMLESS_PIT: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 3)), + archipelago_id=LOCATION_OFFSET + 200 + 4, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.DEATHSANITY,), + ), + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 37)), + archipelago_id=LOCATION_OFFSET + 200 + 5, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, + ), + ), + ZorkGrandInquisitorLocations.DEATH_LOST_SOUL_TO_OLD_SCRATCH: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 23)), + archipelago_id=LOCATION_OFFSET + 200 + 6, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorItems.OLD_SCRATCH_CARD,), + ), + ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 29)), + archipelago_id=LOCATION_OFFSET + 200 + 7, + region=ZorkGrandInquisitorRegions.DM_LAIR, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.HUNGUS_LARD, + ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE, + ), + ), + ZorkGrandInquisitorLocations.DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 30)), + archipelago_id=LOCATION_OFFSET + 200 + 8, + region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + ), + ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 4)), + archipelago_id=LOCATION_OFFSET + 200 + 9, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.SPELL_IGRAM, + ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS, + ), + ), + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 11)), + archipelago_id=LOCATION_OFFSET + 200 + 10, + region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=( + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, + ), + ), + ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 34)), + archipelago_id=LOCATION_OFFSET + 200 + 11, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.DEATHSANITY,), + requirements=( + ZorkGrandInquisitorItems.SPELL_THROCK, + ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_GRASS, + ), + ), + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, (9, 32, 33))), + archipelago_id=LOCATION_OFFSET + 200 + 12, + region=ZorkGrandInquisitorRegions.MONASTERY, + tags=(ZorkGrandInquisitorTags.DEATHSANITY,), + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH, + ), + ), + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, (5, 6, 7, 8, 13))), + archipelago_id=LOCATION_OFFSET + 200 + 13, + region=ZorkGrandInquisitorRegions.MONASTERY, + tags=(ZorkGrandInquisitorTags.DEATHSANITY,), + requirements=(ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH,), + ), + ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 10)), + archipelago_id=LOCATION_OFFSET + 200 + 14, + region=ZorkGrandInquisitorRegions.HADES, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorEvents.KNOWS_SNAVIG,), + ), + ZorkGrandInquisitorLocations.DEATH_ZORK_ROCKS_EXPLODED: ZorkGrandInquisitorLocationData( + game_state_trigger=(("location", "gjde"), (2201, 19)), + archipelago_id=LOCATION_OFFSET + 200 + 15, + region=ZorkGrandInquisitorRegions.GUE_TECH, + tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), + requirements=(ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED,), + ), + # Events + ZorkGrandInquisitorEvents.CHARON_CALLED: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.HADES_SHORE, + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER, + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS, + ), + event_item_name=ZorkGrandInquisitorEvents.CHARON_CALLED.value, + ), + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + requirements=( + ZorkGrandInquisitorItems.LANTERN, + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR, + ), + event_item_name=ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE.value, + ), + ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.GUE_TECH, + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, + ), + event_item_name=ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, + ), + ZorkGrandInquisitorEvents.DAM_DESTROYED: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + requirements=( + ZorkGrandInquisitorItems.SPELL_REZROV, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS, + ), + event_item_name=ZorkGrandInquisitorEvents.DAM_DESTROYED.value, + ), + ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.DM_LAIR, + requirements=( + ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR, + ZorkGrandInquisitorItems.MEAD_LIGHT, + ZorkGrandInquisitorItems.ZIMDOR_SCROLL, + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH, + ), + event_item_name=ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, + ), + ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.DM_LAIR, + requirements=( + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY, + ), + event_item_name=ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR.value, + ), + ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.GUE_TECH, + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, + ), + event_item_name=ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, + ), + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.GUE_TECH, + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT, + ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS, + ), + event_item_name=ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, + ), + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + requirements=( + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS, + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV, + ZorkGrandInquisitorItems.HOTSPOT_MIRROR, + ), + event_item_name=ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, + ), + ZorkGrandInquisitorEvents.KNOWS_BEBURTT: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.SPELL_LAB, + requirements=( + ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, + ), + event_item_name=ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ), + ZorkGrandInquisitorEvents.KNOWS_OBIDIL: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.SPELL_LAB, + requirements=( + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, + ), + event_item_name=ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ), + ZorkGrandInquisitorEvents.KNOWS_SNAVIG: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.SPELL_LAB, + requirements=( + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, + ), + event_item_name=ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ), + ZorkGrandInquisitorEvents.KNOWS_YASTARD: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + requirements=( + ZorkGrandInquisitorItems.FLATHEADIA_FUDGE, + ZorkGrandInquisitorItems.HUNGUS_LARD, + ZorkGrandInquisitorItems.JAR_OF_HOTBUGS, + ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB, + ZorkGrandInquisitorItems.MOSS_OF_MAREILON, + ZorkGrandInquisitorItems.MUG, + ), + event_item_name=ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ), + ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + requirements=( + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, + ), + event_item_name=ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE.value, + ), + ZorkGrandInquisitorEvents.ROPE_GLORFABLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.CROSSROADS, + requirements=(ZorkGrandInquisitorItems.SPELL_GLORF,), + event_item_name=ZorkGrandInquisitorEvents.ROPE_GLORFABLE.value, + ), + ZorkGrandInquisitorEvents.VICTORY: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.ENDGAME, + event_item_name=ZorkGrandInquisitorEvents.VICTORY.value, + ), + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.WHITE_HOUSE, + requirements=( + (ZorkGrandInquisitorItems.TOTEM_GRIFF, ZorkGrandInquisitorItems.TOTEM_LUCY), + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, + ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR, + ), + event_item_name=ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, + ), + ZorkGrandInquisitorEvents.ZORKMID_BILL_ACCESSIBLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.PORT_FOOZLE, + requirements=(ZorkGrandInquisitorItems.OLD_SCRATCH_CARD,), + event_item_name=ZorkGrandInquisitorEvents.ZORKMID_BILL_ACCESSIBLE.value, + ), + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.GUE_TECH, + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_COIN_SLOT, + ZorkGrandInquisitorItems.ZORK_ROCKS, + ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_BUTTONS, + ), + event_item_name=ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, + ), + ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE: ZorkGrandInquisitorLocationData( + game_state_trigger=None, + archipelago_id=None, + region=ZorkGrandInquisitorRegions.GUE_TECH, + requirements=( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, + ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, + ), + event_item_name=ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, + ), +} diff --git a/worlds/zork_grand_inquisitor/data/missable_location_grant_conditions_data.py b/worlds/zork_grand_inquisitor/data/missable_location_grant_conditions_data.py new file mode 100644 index 0000000000..ef6eacb78c --- /dev/null +++ b/worlds/zork_grand_inquisitor/data/missable_location_grant_conditions_data.py @@ -0,0 +1,200 @@ +from typing import Dict, NamedTuple, Optional, Tuple + +from ..enums import ZorkGrandInquisitorItems, ZorkGrandInquisitorLocations + + +class ZorkGrandInquisitorMissableLocationGrantConditionsData(NamedTuple): + location_condition: ZorkGrandInquisitorLocations + item_conditions: Optional[Tuple[ZorkGrandInquisitorItems, ...]] + + +missable_location_grant_conditions_data: Dict[ + ZorkGrandInquisitorLocations, ZorkGrandInquisitorMissableLocationGrantConditionsData +] = { + ZorkGrandInquisitorLocations.BOING_BOING_BOING: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.BONK: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.PROZORKED, + item_conditions=(ZorkGrandInquisitorItems.HAMMER,), + ) + , + ZorkGrandInquisitorLocations.DEATH_ARRESTED_WITH_JACK: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.ARREST_THE_VANDAL, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_EATEN_BY_A_GRUE: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.MAGIC_FOREVER, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_LOST_SOUL_TO_OLD_SCRATCH: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.OLD_SCRATCH_WINNER, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.A_SMALLWAY, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.THAR_SHE_BLOWS, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DEATH_ZORK_ROCKS_EXPLODED: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.CRISIS_AVERTED, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE, + item_conditions=(ZorkGrandInquisitorItems.SPELL_GOLGATEM,), + ) + , + ZorkGrandInquisitorLocations.EMERGENCY_MAGICATRONIC_MESSAGE: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.ARTIFACTS_EXPLAINED, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, + item_conditions=(ZorkGrandInquisitorItems.SPELL_IGRAM,), + ) + , + ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.PROZORKED, + item_conditions=(ZorkGrandInquisitorItems.SPELL_THROCK,), + ) + , + ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, + item_conditions=(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS,), + ) + , + ZorkGrandInquisitorLocations.MEAD_LIGHT: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, + item_conditions=(ZorkGrandInquisitorItems.MEAD_LIGHT,), + ) + , + ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.NO_AUTOGRAPHS: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.NO_BONDAGE: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE, + item_conditions=(ZorkGrandInquisitorItems.ROPE,), + ) + , + ZorkGrandInquisitorLocations.TALK_TO_ME_GRAND_INQUISITOR: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.THATS_A_ROPE: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, + item_conditions=(ZorkGrandInquisitorItems.ROPE,), + ) + , + ZorkGrandInquisitorLocations.THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.THATS_STILL_A_ROPE: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, + item_conditions=(ZorkGrandInquisitorItems.SPELL_GLORF,), + ) + , + ZorkGrandInquisitorLocations.WHAT_ARE_YOU_STUPID: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, + item_conditions=(ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER,), + ) + , + ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG, + item_conditions=None, + ) + , + ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO, + item_conditions=(ZorkGrandInquisitorItems.SWORD, ZorkGrandInquisitorItems.HOTSPOT_HARRY), + ) + , + ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, + item_conditions=(ZorkGrandInquisitorItems.SPELL_REZROV,), + ) + , + ZorkGrandInquisitorLocations.YOU_WANT_A_PIECE_OF_ME_DOCK_BOY: + ZorkGrandInquisitorMissableLocationGrantConditionsData( + location_condition=ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE, + item_conditions=None, + ) + , +} diff --git a/worlds/zork_grand_inquisitor/data/region_data.py b/worlds/zork_grand_inquisitor/data/region_data.py new file mode 100644 index 0000000000..1aed160f30 --- /dev/null +++ b/worlds/zork_grand_inquisitor/data/region_data.py @@ -0,0 +1,183 @@ +from typing import Dict, NamedTuple, Optional, Tuple + +from ..enums import ZorkGrandInquisitorRegions + + +class ZorkGrandInquisitorRegionData(NamedTuple): + exits: Optional[Tuple[ZorkGrandInquisitorRegions, ...]] + + +region_data: Dict[ZorkGrandInquisitorRegions, ZorkGrandInquisitorRegionData] = { + ZorkGrandInquisitorRegions.CROSSROADS: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.DM_LAIR, + ZorkGrandInquisitorRegions.GUE_TECH, + ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.PORT_FOOZLE, + ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.DM_LAIR: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.CROSSROADS, + ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.DM_LAIR, + ZorkGrandInquisitorRegions.WALKING_CASTLE, + ZorkGrandInquisitorRegions.WHITE_HOUSE, + ) + ), + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, + ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, + ) + ), + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, + ZorkGrandInquisitorRegions.ENDGAME, + ) + ), + ZorkGrandInquisitorRegions.ENDGAME: ZorkGrandInquisitorRegionData(exits=None), + ZorkGrandInquisitorRegions.GUE_TECH: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.CROSSROADS, + ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, + ) + ), + ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.GUE_TECH, + ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + ) + ), + ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.CROSSROADS, + ZorkGrandInquisitorRegions.DM_LAIR, + ZorkGrandInquisitorRegions.GUE_TECH, + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.HADES: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, + ZorkGrandInquisitorRegions.HADES_SHORE, + ) + ), + ZorkGrandInquisitorRegions.HADES_BEYOND_GATES: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, + ZorkGrandInquisitorRegions.HADES, + ) + ), + ZorkGrandInquisitorRegions.HADES_SHORE: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.CROSSROADS, + ZorkGrandInquisitorRegions.DM_LAIR, + ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, + ZorkGrandInquisitorRegions.HADES, + ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, + ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, + ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.MENU: ZorkGrandInquisitorRegionData( + exits=(ZorkGrandInquisitorRegions.PORT_FOOZLE,) + ), + ZorkGrandInquisitorRegions.MONASTERY: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.MONASTERY, + ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, + ) + ), + ZorkGrandInquisitorRegions.PORT_FOOZLE: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.CROSSROADS, + ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP, + ) + ), + ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP: ZorkGrandInquisitorRegionData( + exits=(ZorkGrandInquisitorRegions.PORT_FOOZLE,) + ), + ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, + ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, + ) + ), + ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.ENDGAME, + ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, + ) + ), + ZorkGrandInquisitorRegions.SPELL_LAB: ZorkGrandInquisitorRegionData( + exits=(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE,) + ), + ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.CROSSROADS, + ZorkGrandInquisitorRegions.DM_LAIR, + ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, + ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.SPELL_LAB, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.CROSSROADS, + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, + ) + ), + ZorkGrandInquisitorRegions.SUBWAY_MONASTERY: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.HADES_SHORE, + ZorkGrandInquisitorRegions.MONASTERY, + ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, + ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, + ) + ), + ZorkGrandInquisitorRegions.WALKING_CASTLE: ZorkGrandInquisitorRegionData( + exits=(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR,) + ), + ZorkGrandInquisitorRegions.WHITE_HOUSE: ZorkGrandInquisitorRegionData( + exits=( + ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, + ZorkGrandInquisitorRegions.ENDGAME, + ) + ), +} diff --git a/worlds/zork_grand_inquisitor/data_funcs.py b/worlds/zork_grand_inquisitor/data_funcs.py new file mode 100644 index 0000000000..9ea806e8aa --- /dev/null +++ b/worlds/zork_grand_inquisitor/data_funcs.py @@ -0,0 +1,247 @@ +from typing import Dict, Set, Tuple, Union + +from .data.entrance_rule_data import entrance_rule_data +from .data.item_data import item_data, ZorkGrandInquisitorItemData +from .data.location_data import location_data, ZorkGrandInquisitorLocationData + +from .enums import ( + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorGoals, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorLocations, + ZorkGrandInquisitorRegions, + ZorkGrandInquisitorTags, +) + + +def item_names_to_id() -> Dict[str, int]: + return {item.value: data.archipelago_id for item, data in item_data.items()} + + +def item_names_to_item() -> Dict[str, ZorkGrandInquisitorItems]: + return {item.value: item for item in item_data} + + +def location_names_to_id() -> Dict[str, int]: + return { + location.value: data.archipelago_id + for location, data in location_data.items() + if data.archipelago_id is not None + } + + +def location_names_to_location() -> Dict[str, ZorkGrandInquisitorLocations]: + return { + location.value: location + for location, data in location_data.items() + if data.archipelago_id is not None + } + + +def id_to_goals() -> Dict[int, ZorkGrandInquisitorGoals]: + return {goal.value: goal for goal in ZorkGrandInquisitorGoals} + + +def id_to_items() -> Dict[int, ZorkGrandInquisitorItems]: + return {data.archipelago_id: item for item, data in item_data.items()} + + +def id_to_locations() -> Dict[int, ZorkGrandInquisitorLocations]: + return { + data.archipelago_id: location + for location, data in location_data.items() + if data.archipelago_id is not None + } + + +def item_groups() -> Dict[str, Set[str]]: + groups: Dict[str, Set[str]] = dict() + + item: ZorkGrandInquisitorItems + data: ZorkGrandInquisitorItemData + for item, data in item_data.items(): + if data.tags is not None: + for tag in data.tags: + groups.setdefault(tag.value, set()).add(item.value) + + return {k: v for k, v in groups.items() if len(v)} + + +def items_with_tag(tag: ZorkGrandInquisitorTags) -> Set[ZorkGrandInquisitorItems]: + items: Set[ZorkGrandInquisitorItems] = set() + + item: ZorkGrandInquisitorItems + data: ZorkGrandInquisitorItemData + for item, data in item_data.items(): + if data.tags is not None and tag in data.tags: + items.add(item) + + return items + + +def game_id_to_items() -> Dict[int, ZorkGrandInquisitorItems]: + mapping: Dict[int, ZorkGrandInquisitorItems] = dict() + + item: ZorkGrandInquisitorItems + data: ZorkGrandInquisitorItemData + for item, data in item_data.items(): + if data.statemap_keys is not None: + for key in data.statemap_keys: + mapping[key] = item + + return mapping + + +def location_groups() -> Dict[str, Set[str]]: + groups: Dict[str, Set[str]] = dict() + + tag: ZorkGrandInquisitorTags + for tag in ZorkGrandInquisitorTags: + groups[tag.value] = set() + + location: ZorkGrandInquisitorLocations + data: ZorkGrandInquisitorLocationData + for location, data in location_data.items(): + if data.tags is not None: + for tag in data.tags: + groups[tag.value].add(location.value) + + return {k: v for k, v in groups.items() if len(v)} + + +def locations_by_region(include_deathsanity: bool = False) -> Dict[ + ZorkGrandInquisitorRegions, Set[ZorkGrandInquisitorLocations] +]: + mapping: Dict[ZorkGrandInquisitorRegions, Set[ZorkGrandInquisitorLocations]] = dict() + + region: ZorkGrandInquisitorRegions + for region in ZorkGrandInquisitorRegions: + mapping[region] = set() + + location: ZorkGrandInquisitorLocations + data: ZorkGrandInquisitorLocationData + for location, data in location_data.items(): + if not include_deathsanity and ZorkGrandInquisitorTags.DEATHSANITY in ( + data.tags or tuple() + ): + continue + + mapping[data.region].add(location) + + return mapping + + +def locations_with_tag(tag: ZorkGrandInquisitorTags) -> Set[ZorkGrandInquisitorLocations]: + location: ZorkGrandInquisitorLocations + data: ZorkGrandInquisitorLocationData + + return {location for location, data in location_data.items() if data.tags is not None and tag in data.tags} + + +def location_access_rule_for(location: ZorkGrandInquisitorLocations, player: int) -> str: + data: ZorkGrandInquisitorLocationData = location_data[location] + + if data.requirements is None: + return "lambda state: True" + + lambda_string: str = "lambda state: " + + i: int + requirement: Union[ + Tuple[ + Union[ + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ], + ..., + ], + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems + ] + + for i, requirement in enumerate(data.requirements): + if isinstance(requirement, tuple): + lambda_string += "(" + + ii: int + sub_requirement: Union[ZorkGrandInquisitorEvents, ZorkGrandInquisitorItems] + for ii, sub_requirement in enumerate(requirement): + lambda_string += f"state.has(\"{sub_requirement.value}\", {player})" + + if ii < len(requirement) - 1: + lambda_string += " or " + + lambda_string += ")" + else: + lambda_string += f"state.has(\"{requirement.value}\", {player})" + + if i < len(data.requirements) - 1: + lambda_string += " and " + + return lambda_string + + +def entrance_access_rule_for( + region_origin: ZorkGrandInquisitorRegions, + region_destination: ZorkGrandInquisitorRegions, + player: int +) -> str: + data: Union[ + Tuple[ + Tuple[ + Union[ + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorRegions, + ], + ..., + ], + ..., + ], + None, + ] = entrance_rule_data[(region_origin, region_destination)] + + if data is None: + return "lambda state: True" + + lambda_string: str = "lambda state: " + + i: int + requirement_group: Tuple[ + Union[ + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorRegions, + ], + ..., + ] + for i, requirement_group in enumerate(data): + lambda_string += "(" + + ii: int + requirement: Union[ + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorRegions, + ] + for ii, requirement in enumerate(requirement_group): + requirement_type: Union[ + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorRegions, + ] = type(requirement) + + if requirement_type in (ZorkGrandInquisitorEvents, ZorkGrandInquisitorItems): + lambda_string += f"state.has(\"{requirement.value}\", {player})" + elif requirement_type == ZorkGrandInquisitorRegions: + lambda_string += f"state.can_reach(\"{requirement.value}\", \"Region\", {player})" + + if ii < len(requirement_group) - 1: + lambda_string += " and " + + lambda_string += ")" + + if i < len(data) - 1: + lambda_string += " or " + + return lambda_string diff --git a/worlds/zork_grand_inquisitor/docs/en_Zork Grand Inquisitor.md b/worlds/zork_grand_inquisitor/docs/en_Zork Grand Inquisitor.md new file mode 100644 index 0000000000..d5821914be --- /dev/null +++ b/worlds/zork_grand_inquisitor/docs/en_Zork Grand Inquisitor.md @@ -0,0 +1,102 @@ +# Zork Grand Inquisitor + +## 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 +configuration file. + +## Is a tracker available for this game? + +Yes! You can download the latest PopTracker pack for Zork Grand Inquisitor [here](https://github.com/SerpentAI/ZorkGrandInquisitorAPTracker/releases/latest). + +## What does randomization do to this game? + +A majority of inventory items you can normally pick up are completely removed from the game (e.g. the lantern won't be +in the crate, the mead won't be at the fish market, etc.). Instead, these items will be distributed in the multiworld. +This means that you can expect to access areas and be in a position to solve certain puzzles in a completely different +order than you normally would. + +Subway, teleporter and totemizer destinations are initially locked and need to be unlocked by receiving the +corresponding item in the multiworld. This alone enables creative routing in a game that would otherwise be rather +linear. The Crossroads destination is always unlocked for both the subway and teleporter to prevent softlocks. Until you +receive your first totemizer destination, it will be locked to Newark, New Jersey. + +Important hotspots are also randomized. This means that you will be unable to interact with certain objects until you +receive the corresponding item in the multiworld. This can be a bit confusing at first, but it adds depth to the +randomization and makes the game more interesting to play. + +You can travel back to the surface without dying by looking inside the bucket. This will work as long as the rope is +still attached to the well. + +Attempting to cast VOXAM will teleport you back to the Crossroads. Fast Travel! + +## What item types are distributed in the multiworld? + +- Inventory items +- Pouch of Zorkmids +- Spells +- Totems +- Subway destinations +- Teleporter destinations +- Totemizer destinations +- Hotspots (with option to start with the items enabling them instead if you prefer not playing with the randomization + of hotspots) + +## When the player receives an item, what happens? + +- **Inventory items**: Directly added to the player's inventory. +- **Pouch of Zorkmids**: Appears on the inventory screen. The player can then pick up Zorkmid coins from it. +- **Spells**: Learned and directly added to the spell book. +- **Totems**: Appears on the inventory screen. +- **Subway destinations**: The destination button on the subway map becomes functional. +- **Teleporter destinations**: The destination can show up on the teleporter screen. +- **Totemizer destinations**: The destination button on the panel becomes functional. +- **Hotspots**: The hotspot becomes interactable. + +## What is considered a location check in Zork Grand Inquisitor? + +- Solving puzzles +- Accessing certain areas for the first time +- Triggering certain interactions, even if they aren't puzzles per se +- Dying in unique ways (Optional; Deathsanity option) + +## The location check names are fun but don't always convey well what's needed to unlock them. Is there a guide? + +Yes! You can find a complete guide for the location checks [here](https://gist.github.com/nbrochu/f7bed7a1fef4e2beb67ad6ddbf18b970). + +## What is the victory condition? + +Victory is achieved when the 3 artifacts of magic are retrieved and placed inside the walking castle. + +## Can I use the save system without a problem? + +Absolutely! The save system is fully supported (and its use is in fact strongly encouraged!). You can save and load your +game as you normally would and the client will automatically sync your items and hotspots with what you should have in +that game state. + +Depending on how your game progresses, there's a chance that certain location checks might become missable. This +presents an excellent opportunity to utilize the save system. Simply make it a habit to save before undertaking +irreversible actions, ensuring you can revert to a previous state if necessary. If you prefer not to depend on the save +system for accessing missable location checks, there's an option to automatically unlock them as they become +unavailable. + +## Unique Local Commands +The following commands are only available when using the Zork Grand Inquisitor Client to play the game with Archipelago. + +- `/zork` Attempts to attach to a running instance of Zork Grand Inquisitor. If successful, the client will then be able + to read and control the state of the game. +- `/brog` Lists received items for Brog. +- `/griff` Lists received items for Griff. +- `/lucy` Lists received items for Lucy. +- `/hotspots` Lists received hotspots. + +## Known issues + +- You will get a second rope right after using GLORF (one in your inventory and one on your cursor). This is a harmless + side effect that will go away after you store it in your inventory as duplicates are actively removed. +- After climbing up to the Monastery for the first time, a rope will forever remain in place in the vent. When you come + back to the Monastery, you will be able to climb up without needing to combine the sword and rope again. However, when + arriving at the top, you will receive a duplicate sword on a rope. This is a harmless side effect that will go away + after you store it in your inventory as duplicates are actively removed. +- Since the client is reading and manipulating the game's memory, rare game crashes can happen. If you encounter one, + simply restart the game, load your latest save and use the `/zork` command again in the client. Nothing will be lost. diff --git a/worlds/zork_grand_inquisitor/docs/setup_en.md b/worlds/zork_grand_inquisitor/docs/setup_en.md new file mode 100644 index 0000000000..f9078c6d39 --- /dev/null +++ b/worlds/zork_grand_inquisitor/docs/setup_en.md @@ -0,0 +1,42 @@ +# Zork Grand Inquisitor Randomizer Setup Guide + +## Requirements + +- Windows OS (Hard required. Client is using memory reading / writing through Win32 API) +- A copy of Zork Grand Inquisitor. Only the GOG version is supported. The Steam version can work with some tinkering but + is not officially supported. +- ScummVM 2.7.1 64-bit (Important: Will not work with any other version. [Direct Download](https://downloads.scummvm.org/frs/scummvm/2.7.1/scummvm-2.7.1-win32-x86_64.zip)) +- Archipelago 0.4.4+ + +## Game Setup Instructions + +No game modding is required to play Zork Grand Inquisitor with Archipelago. The client does all the work by attaching to +the game process and reading and manipulating the game state in real-time. + +This being said, the game does need to be played through ScummVM 2.7.1, so some configuration is required around that. + +### GOG + +- Open the directory where you installed Zork Grand Inquisitor. You should see a `Launch Zork Grand Inquisitor` + shortcut. +- Open the `scummvm` directory. Delete the entire contents of that directory. +- Still inside the `scummvm` directory, unzip the contents of the ScummVM 2.7.1 zip file you downloaded earlier. +- Go back to the directory where you installed Zork Grand Inquisitor. +- Verify that the game still launches when using the `Launch Zork Grand Inquisitor` shortcut. +- Your game is now ready to be played with Archipelago. From now on, you can use the `Launch Zork Grand Inquisitor` + shortcut to launch the game. + +## Joining a Multiworld Game + +- Launch Zork Grand Inquisitor and start a new game. +- Open the Archipelago Launcher and click `Zork Grand Inquisitor Client`. +- Using the `Zork Grand Inquisitor Client`: + - Enter the room's hostname and port number (e.g. `archipelago.gg:54321`) in the top box and press `Connect`. + - Input your player name at the bottom when prompted and press `Enter`. + - You should now be connected to the Archipelago room. + - Next, input `/zork` at the bottom and press `Enter`. This will attach the client to the game process. + - If the command is successful, you are now ready to play Zork Grand Inquisitor with Archipelago. + +## Continuing a Multiworld Game + +- Perform the same steps as above, but instead of starting a new game, load your latest save file. diff --git a/worlds/zork_grand_inquisitor/enums.py b/worlds/zork_grand_inquisitor/enums.py new file mode 100644 index 0000000000..ecbb38a949 --- /dev/null +++ b/worlds/zork_grand_inquisitor/enums.py @@ -0,0 +1,350 @@ +import enum + + +class ZorkGrandInquisitorEvents(enum.Enum): + CHARON_CALLED = "Event: Charon Called" + CIGAR_ACCESSIBLE = "Event: Cigar Accessible" + DALBOZ_LOCKER_OPENABLE = "Event: Dalboz Locker Openable" + DAM_DESTROYED = "Event: Dam Destroyed" + DOOR_DRANK_MEAD = "Event: Door Drank Mead" + DOOR_SMOKED_CIGAR = "Event: Door Smoked Cigar" + DUNCE_LOCKER_OPENABLE = "Event: Dunce Locker Openable" + HAS_REPAIRABLE_OBIDIL = "Event: Has Repairable OBIDIL" + HAS_REPAIRABLE_SNAVIG = "Event: Has Repairable SNAVIG" + KNOWS_BEBURTT = "Event: Knows BEBURTT" + KNOWS_OBIDIL = "Event: Knows OBIDIL" + KNOWS_SNAVIG = "Event: Knows SNAVIG" + KNOWS_YASTARD = "Event: Knows YASTARD" + LANTERN_DALBOZ_ACCESSIBLE = "Event: Lantern (Dalboz) Accessible" + ROPE_GLORFABLE = "Event: Rope GLORFable" + VICTORY = "Victory" + WHITE_HOUSE_LETTER_MAILABLE = "Event: White House Letter Mailable" + ZORKMID_BILL_ACCESSIBLE = "Event: 500 Zorkmid Bill Accessible" + ZORK_ROCKS_ACTIVATED = "Event: Zork Rocks Activated" + ZORK_ROCKS_SUCKABLE = "Event: Zork Rocks Suckable" + + +class ZorkGrandInquisitorGoals(enum.Enum): + THREE_ARTIFACTS = 0 + + +class ZorkGrandInquisitorItems(enum.Enum): + BROGS_BICKERING_TORCH = "Brog's Bickering Torch" + BROGS_FLICKERING_TORCH = "Brog's Flickering Torch" + BROGS_GRUE_EGG = "Brog's Grue Egg" + BROGS_PLANK = "Brog's Plank" + FILLER_FROBOZZ_ELECTRIC_GADGET = "Frobozz Electric Gadget" + FILLER_INQUISITION_PROPAGANDA_FLYER = "Inquisition Propaganda Flyer" + FILLER_MAGIC_CONTRABAND = "Magic Contraband" + FILLER_NONSENSICAL_INQUISITION_PAPERWORK = "Nonsensical Inquisition Paperwork" + FILLER_UNREADABLE_SPELL_SCROLL = "Unreadable Spell Scroll" + FLATHEADIA_FUDGE = "Flatheadia Fudge" + GRIFFS_AIR_PUMP = "Griff's Air Pump" + GRIFFS_DRAGON_TOOTH = "Griff's Dragon Tooth" + GRIFFS_INFLATABLE_RAFT = "Griff's Inflatable Raft" + GRIFFS_INFLATABLE_SEA_CAPTAIN = "Griff's Inflatable Sea Captain" + HAMMER = "Hammer" + HOTSPOT_666_MAILBOX = "Hotspot: 666 Mailbox" + HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS = "Hotspot: Alpine's Quandry Card Slots" + HOTSPOT_BLANK_SCROLL_BOX = "Hotspot: Blank Scroll Box" + HOTSPOT_BLINDS = "Hotspot: Blinds" + HOTSPOT_CANDY_MACHINE_BUTTONS = "Hotspot: Candy Machine Buttons" + HOTSPOT_CANDY_MACHINE_COIN_SLOT = "Hotspot: Candy Machine Coin Slot" + HOTSPOT_CANDY_MACHINE_VACUUM_SLOT = "Hotspot: Candy Machine Vacuum Slot" + HOTSPOT_CHANGE_MACHINE_SLOT = "Hotspot: Change Machine Slot" + HOTSPOT_CLOSET_DOOR = "Hotspot: Closet Door" + HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT = "Hotspot: Closing the Time Tunnels Hammer Slot" + HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER = "Hotspot: Closing the Time Tunnels Lever" + HOTSPOT_COOKING_POT = "Hotspot: Cooking Pot" + HOTSPOT_DENTED_LOCKER = "Hotspot: Dented Locker" + HOTSPOT_DIRT_MOUND = "Hotspot: Dirt Mound" + HOTSPOT_DOCK_WINCH = "Hotspot: Dock Winch" + HOTSPOT_DRAGON_CLAW = "Hotspot: Dragon Claw" + HOTSPOT_DRAGON_NOSTRILS = "Hotspot: Dragon Nostrils" + HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE = "Hotspot: Dungeon Master's Lair Entrance" + HOTSPOT_FLOOD_CONTROL_BUTTONS = "Hotspot: Flood Control Buttons" + HOTSPOT_FLOOD_CONTROL_DOORS = "Hotspot: Flood Control Doors" + HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT = "Hotspot: Frozen Treat Machine Coin Slot" + HOTSPOT_FROZEN_TREAT_MACHINE_DOORS = "Hotspot: Frozen Treat Machine Doors" + HOTSPOT_GLASS_CASE = "Hotspot: Glass Case" + HOTSPOT_GRAND_INQUISITOR_DOLL = "Hotspot: Grand Inquisitor Doll" + HOTSPOT_GUE_TECH_DOOR = "Hotspot: GUE Tech Door" + HOTSPOT_GUE_TECH_GRASS = "Hotspot: GUE Tech Grass" + HOTSPOT_HADES_PHONE_BUTTONS = "Hotspot: Hades Phone Buttons" + HOTSPOT_HADES_PHONE_RECEIVER = "Hotspot: Hades Phone Receiver" + HOTSPOT_HARRY = "Hotspot: Harry" + HOTSPOT_HARRYS_ASHTRAY = "Hotspot: Harry's Ashtray" + HOTSPOT_HARRYS_BIRD_BATH = "Hotspot: Harry's Bird Bath" + HOTSPOT_IN_MAGIC_WE_TRUST_DOOR = "Hotspot: In Magic We Trust Door" + HOTSPOT_JACKS_DOOR = "Hotspot: Jack's Door" + HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS = "Hotspot: Loudspeaker Volume Buttons" + HOTSPOT_MAILBOX_DOOR = "Hotspot: Mailbox Door" + HOTSPOT_MAILBOX_FLAG = "Hotspot: Mailbox Flag" + HOTSPOT_MIRROR = "Hotspot: Mirror" + HOTSPOT_MONASTERY_VENT = "Hotspot: Monastery Vent" + HOTSPOT_MOSSY_GRATE = "Hotspot: Mossy Grate" + HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR = "Hotspot: Port Foozle Past Tavern Door" + HOTSPOT_PURPLE_WORDS = "Hotspot: Purple Words" + HOTSPOT_QUELBEE_HIVE = "Hotspot: Quelbee Hive" + HOTSPOT_ROPE_BRIDGE = "Hotspot: Rope Bridge" + HOTSPOT_SKULL_CAGE = "Hotspot: Skull Cage" + HOTSPOT_SNAPDRAGON = "Hotspot: Snapdragon" + HOTSPOT_SODA_MACHINE_BUTTONS = "Hotspot: Soda Machine Buttons" + HOTSPOT_SODA_MACHINE_COIN_SLOT = "Hotspot: Soda Machine Coin Slot" + HOTSPOT_SOUVENIR_COIN_SLOT = "Hotspot: Souvenir Coin Slot" + HOTSPOT_SPELL_CHECKER = "Hotspot: Spell Checker" + HOTSPOT_SPELL_LAB_CHASM = "Hotspot: Spell Lab Chasm" + HOTSPOT_SPRING_MUSHROOM = "Hotspot: Spring Mushroom" + HOTSPOT_STUDENT_ID_MACHINE = "Hotspot: Student ID Machine" + HOTSPOT_SUBWAY_TOKEN_SLOT = "Hotspot: Subway Token Slot" + HOTSPOT_TAVERN_FLY = "Hotspot: Tavern Fly" + HOTSPOT_TOTEMIZER_SWITCH = "Hotspot: Totemizer Switch" + HOTSPOT_TOTEMIZER_WHEELS = "Hotspot: Totemizer Wheels" + HOTSPOT_WELL = "Hotspot: Well" + HUNGUS_LARD = "Hungus Lard" + JAR_OF_HOTBUGS = "Jar of Hotbugs" + LANTERN = "Lantern" + LARGE_TELEGRAPH_HAMMER = "Large Telegraph Hammer" + LUCYS_PLAYING_CARD_1 = "Lucy's Playing Card: 1 Pip" + LUCYS_PLAYING_CARD_2 = "Lucy's Playing Card: 2 Pips" + LUCYS_PLAYING_CARD_3 = "Lucy's Playing Card: 3 Pips" + LUCYS_PLAYING_CARD_4 = "Lucy's Playing Card: 4 Pips" + MAP = "Map" + MEAD_LIGHT = "Mead Light" + MOSS_OF_MAREILON = "Moss of Mareilon" + MUG = "Mug" + OLD_SCRATCH_CARD = "Old Scratch Card" + PERMA_SUCK_MACHINE = "Perma-Suck Machine" + PLASTIC_SIX_PACK_HOLDER = "Plastic Six-Pack Holder" + POUCH_OF_ZORKMIDS = "Pouch of Zorkmids" + PROZORK_TABLET = "Prozork Tablet" + QUELBEE_HONEYCOMB = "Quelbee Honeycomb" + ROPE = "Rope" + SCROLL_FRAGMENT_ANS = "Scroll Fragment: ANS" + SCROLL_FRAGMENT_GIV = "Scroll Fragment: GIV" + SHOVEL = "Shovel" + SNAPDRAGON = "Snapdragon" + SPELL_GLORF = "Spell: GLORF" + SPELL_GOLGATEM = "Spell: GOLGATEM" + SPELL_IGRAM = "Spell: IGRAM" + SPELL_KENDALL = "Spell: KENDALL" + SPELL_NARWILE = "Spell: NARWILE" + SPELL_REZROV = "Spell: REZROV" + SPELL_THROCK = "Spell: THROCK" + SPELL_VOXAM = "Spell: VOXAM" + STUDENT_ID = "Student ID" + SUBWAY_DESTINATION_FLOOD_CONTROL_DAM = "Subway Destination: Flood Control Dam #3" + SUBWAY_DESTINATION_HADES = "Subway Destination: Hades" + SUBWAY_DESTINATION_MONASTERY = "Subway Destination: Monastery" + SUBWAY_TOKEN = "Subway Token" + SWORD = "Sword" + TELEPORTER_DESTINATION_DM_LAIR = "Teleporter Destination: Dungeon Master's Lair" + TELEPORTER_DESTINATION_GUE_TECH = "Teleporter Destination: GUE Tech" + TELEPORTER_DESTINATION_HADES = "Teleporter Destination: Hades" + TELEPORTER_DESTINATION_MONASTERY = "Teleporter Destination: Monastery Station" + TELEPORTER_DESTINATION_SPELL_LAB = "Teleporter Destination: Spell Lab" + TOTEM_BROG = "Totem: Brog" + TOTEM_GRIFF = "Totem: Griff" + TOTEM_LUCY = "Totem: Lucy" + TOTEMIZER_DESTINATION_HALL_OF_INQUISITION = "Totemizer Destination: Hall of Inquisition" + TOTEMIZER_DESTINATION_INFINITY = "Totemizer Destination: Infinity" + TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL = "Totemizer Destination: Straight to Hell" + TOTEMIZER_DESTINATION_SURFACE_OF_MERZ = "Totemizer Destination: Surface of Merz" + ZIMDOR_SCROLL = "ZIMDOR Scroll" + ZORK_ROCKS = "Zork Rocks" + + +class ZorkGrandInquisitorLocations(enum.Enum): + ALARM_SYSTEM_IS_DOWN = "Alarm System is Down" + ARREST_THE_VANDAL = "Arrest the Vandal!" + ARTIFACTS_EXPLAINED = "Artifacts, Explained" + A_BIG_FAT_SASSY_2_HEADED_MONSTER = "A Big, Fat, SASSY 2-Headed Monster" + A_LETTER_FROM_THE_WHITE_HOUSE = "A Letter from the White House" + A_SMALLWAY = "A Smallway" + BEAUTIFUL_THATS_PLENTY = "Beautiful, That's Plenty!" + BEBURTT_DEMYSTIFIED = "BEBURTT, Demystified" + BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES = "Better Spell Manufacturing in Under 10 Minutes" + BOING_BOING_BOING = "Boing, Boing, Boing" + BONK = "Bonk!" + BRAVE_SOULS_WANTED = "Brave Souls Wanted" + BROG_DO_GOOD = "Brog Do Good!" + BROG_EAT_ROCKS = "Brog Eat Rocks" + BROG_KNOW_DUMB_THAT_DUMB = "Brog Know Dumb. That Dumb" + BROG_MUCH_BETTER_AT_THIS_GAME = "Brog Much Better at This Game" + CASTLE_WATCHING_A_FIELD_GUIDE = "Castle Watching: A Field Guide" + CAVES_NOTES = "Cave's Notes" + CLOSING_THE_TIME_TUNNELS = "Closing the Time Tunnels" + CRISIS_AVERTED = "Crisis Averted" + CUT_THAT_OUT_YOU_LITTLE_CREEP = "Cut That Out You Little Creep!" + DEATH_ARRESTED_WITH_JACK = "Death: Arrested With Jack" + DEATH_ATTACKED_THE_QUELBEES = "Death: Attacked the Quelbees" + DEATH_CLIMBED_OUT_OF_THE_WELL = "Death: Climbed Out of the Well" + DEATH_EATEN_BY_A_GRUE = "Death: Eaten by a Grue" + DEATH_JUMPED_IN_BOTTOMLESS_PIT = "Death: Jumped in Bottomless Pit" + DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER = "Death: Lost Game of Strip Grue, Fire, Water" + DEATH_LOST_SOUL_TO_OLD_SCRATCH = "Death: Lost Soul to Old Scratch" + DEATH_OUTSMARTED_BY_THE_QUELBEES = "Death: Outsmarted by the Quelbees" + DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD = "Death: Sliced up by the Invisible Guard" + DEATH_STEPPED_INTO_THE_INFINITE = "Death: Step Into the Infinite" + DEATH_SWALLOWED_BY_A_DRAGON = "Death: Swallowed by a Dragon" + DEATH_THROCKED_THE_GRASS = "Death: THROCKed the Grass" + DEATH_TOTEMIZED = "Death: Totemized?" + DEATH_TOTEMIZED_PERMANENTLY = "Death: Totemized... Permanently" + DEATH_YOURE_NOT_CHARON = "Death: You're Not Charon!?" + DEATH_ZORK_ROCKS_EXPLODED = "Death: Zork Rocks Exploded" + DENIED_BY_THE_LAKE_MONSTER = "Denied by the Lake Monster" + DESPERATELY_SEEKING_TUTOR = "Desperately Seeking Tutor" + DONT_EVEN_START_WITH_US_SPARKY = "Don't Even Start With Us, Sparky" + DOOOOOOWN = "Doooooown" + DOWN = "Down" + DRAGON_ARCHIPELAGO_TIME_TUNNEL = "Dragon Archipelago Time Tunnel" + DUNCE_LOCKER = "Dunce Locker" + EGGPLANTS = "Eggplants" + ELSEWHERE = "Elsewhere" + EMERGENCY_MAGICATRONIC_MESSAGE = "Emergency Magicatronic Message" + ENJOY_YOUR_TRIP = "Enjoy Your Trip!" + FAT_LOT_OF_GOOD_THATLL_DO_YA = "Fat Lot of Good That'll Do Ya" + FIRE_FIRE = "Fire! Fire!" + FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE = "Flood Control Dam #3: The Not Remotely Boring Tale" + FLYING_SNAPDRAGON = "Flying Snapdragon" + FROBUARY_3_UNDERGROUNDHOG_DAY = "Frobruary 3 - Undergroundhog Day" + GETTING_SOME_CHANGE = "Getting Some Change" + GO_AWAY = "GO AWAY!" + GUE_TECH_DEANS_LIST = "GUE Tech Dean's List" + GUE_TECH_ENTRANCE_EXAM = "GUE Tech Entrance Exam" + GUE_TECH_HEALTH_MEMO = "GUE Tech Health Memo" + GUE_TECH_MAGEMEISTERS = "GUE Tech Magemeisters" + HAVE_A_HELL_OF_A_DAY = "Have a Hell of a Day!" + HELLO_THIS_IS_SHONA_FROM_GURTH_PUBLISHING = "Hello, This is Shona from Gurth Publishing" + HELP_ME_CANT_BREATHE = "Help... Me. Can't... Breathe" + HEY_FREE_DIRT = "Hey, Free Dirt!" + HI_MY_NAME_IS_DOUG = "Hi, My Name is Doug" + HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING = "Hmmm. Informative. Yet Deeply Disturbing" + HOLD_ON_FOR_AN_IMPORTANT_MESSAGE = "Hold on for an Important Message" + HOW_TO_HYPNOTIZE_YOURSELF = "How to Hypnotize Yourself" + HOW_TO_WIN_AT_DOUBLE_FANUCCI = "How to Win at Double Fanucci" + IMBUE_BEBURTT = "Imbue BEBURTT" + IM_COMPLETELY_NUDE = "I'm Completely Nude" + INTO_THE_FOLIAGE = "Into the Foliage" + INVISIBLE_FLOWERS = "Invisible Flowers" + IN_CASE_OF_ADVENTURE = "In Case of Adventure, Break Glass!" + IN_MAGIC_WE_TRUST = "In Magic We Trust" + ITS_ONE_OF_THOSE_ADVENTURERS_AGAIN = "It's One of Those Adventurers Again..." + I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY = "I Don't Think You Would've Wanted That to Work Anyway" + I_DONT_WANT_NO_TROUBLE = "I Don't Want No Trouble!" + I_HOPE_YOU_CAN_CLIMB_UP_THERE = "I Hope You Can Climb Up There With All This Junk" + I_LIKE_YOUR_STYLE = "I Like Your Style!" + I_SPIT_ON_YOUR_FILTHY_COINAGE = "I Spit on Your Filthy Coinage" + LIT_SUNFLOWERS = "Lit Sunflowers" + MAGIC_FOREVER = "Magic Forever!" + MAILED_IT_TO_HELL = "Mailed it to Hell" + MAKE_LOVE_NOT_WAR = "Make Love, Not War" + MEAD_LIGHT = "Mead Light?" + MIKES_PANTS = "Mike's Pants" + MUSHROOM_HAMMERED = "Mushroom, Hammered" + NATIONAL_TREASURE = "300 Year Old National Treasure" + NATURAL_AND_SUPERNATURAL_CREATURES_OF_QUENDOR = "Natural and Supernatural Creatures of Quendor" + NOOOOOOOOOOOOO = "NOOOOOOOOOOOOO!" + NOTHIN_LIKE_A_GOOD_STOGIE = "Nothin' Like a Good Stogie" + NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT = "Now You Look Like Us, Which is an Improvement" + NO_AUTOGRAPHS = "No Autographs" + NO_BONDAGE = "No Bondage" + OBIDIL_DRIED_UP = "OBIDIL, Dried Up" + OH_DEAR_GOD_ITS_A_DRAGON = "Oh Dear God, It's a Dragon!" + OH_VERY_FUNNY_GUYS = "Oh, Very Funny Guys" + OH_WOW_TALK_ABOUT_DEJA_VU = "Oh, Wow! Talk About Deja Vu" + OLD_SCRATCH_WINNER = "Old Scratch Winner!" + ONLY_YOU_CAN_PREVENT_FOOZLE_FIRES = "Only You Can Prevent Foozle Fires" + OPEN_THE_GATES_OF_HELL = "Open the Gates of Hell" + OUTSMART_THE_QUELBEES = "Outsmart the Quelbees" + PERMASEAL = "PermaSeal" + PLANETFALL = "Planetfall" + PLEASE_DONT_THROCK_THE_GRASS = "Please Don't THROCK the Grass" + PORT_FOOZLE_TIME_TUNNEL = "Port Foozle Time Tunnel" + PROZORKED = "Prozorked" + REASSEMBLE_SNAVIG = "Reassemble SNAVIG" + RESTOCKED_ON_GRUESDAY = "Restocked on Gruesday" + RIGHT_HELLO_YES_UH_THIS_IS_SNEFFLE = "Right. Hello. Yes. Uh, This is Sneffle" + RIGHT_UH_SORRY_ITS_ME_AGAIN_SNEFFLE = "Right. Uh, Sorry. It's Me Again. Sneffle" + SNAVIG_REPAIRED = "SNAVIG, Repaired" + SOUVENIR = "Souvenir" + STRAIGHT_TO_HELL = "Straight to Hell" + STRIP_GRUE_FIRE_WATER = "Strip Grue, Fire, Water" + SUCKING_ROCKS = "Sucking Rocks" + TALK_TO_ME_GRAND_INQUISITOR = "Talk to Me Grand Inquisitor" + TAMING_YOUR_SNAPDRAGON = "Taming Your Snapdragon" + THAR_SHE_BLOWS = "Thar She Blows!" + THATS_A_ROPE = "That's a Rope" + THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS = "That's it! Just Keep Hitting Those Buttons" + THATS_STILL_A_ROPE = "That's Still a Rope" + THATS_THE_SPIRIT = "That's the Spirit!" + THE_ALCHEMICAL_DEBACLE = "The Alchemical Debacle" + THE_ENDLESS_FIRE = "The Endless Fire" + THE_FLATHEADIAN_FUDGE_FIASCO = "The Flatheadian Fudge Fiasco" + THE_PERILS_OF_MAGIC = "The Perils of Magic" + THE_UNDERGROUND_UNDERGROUND = "The Underground Underground" + THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE = "This Doesn't Look Anything Like the Brochure" + THROCKED_MUSHROOM_HAMMERED = "THROCKed Mushroom, Hammered" + TIME_TRAVEL_FOR_DUMMIES = "Time Travel for Dummies" + TOTEMIZED_DAILY_BILLBOARD = "Totemized Daily Billboard Functioning Correctly" + UH_OH_BROG_CANT_SWIM = "Uh-Oh. Brog Can't Swim" + UMBRELLA_FLOWERS = "Umbrella Flowers" + UP = "Up" + USELESS_BUT_FUN = "Useless, But Fun" + UUUUUP = "Uuuuup" + VOYAGE_OF_CAPTAIN_ZAHAB = "Voyage of Captain Zahab" + WANT_SOME_RYE_COURSE_YA_DO = "Want Some Rye? Course Ya Do!" + WE_DONT_SERVE_YOUR_KIND_HERE = "We Don't Serve Your Kind Here" + WE_GOT_A_HIGH_ROLLER = "We Got a High Roller!" + WHAT_ARE_YOU_STUPID = "What Are You, Stupid?" + WHITE_HOUSE_TIME_TUNNEL = "White House Time Tunnel" + WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE = "Wow! I've Never Gone Inside Him Before!" + YAD_GOHDNUORGREDNU_3_YRAUBORF = "yaD gohdnuorgrednU - 3 yrauborF" + YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY = "Your Puny Weapons Don't Phase Me, Baby!" + YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER = "You Don't Go Messing With a Man's Zipper" + YOU_GAINED_86_EXPERIENCE_POINTS = "You Gained 86 Experience Points" + YOU_ONE_OF_THEM_AGITATORS_AINT_YA = "You One of Them Agitators, Ain't Ya?" + YOU_WANT_A_PIECE_OF_ME_DOCK_BOY = "You Want a Piece of Me, Dock Boy? or Girl" + + +class ZorkGrandInquisitorRegions(enum.Enum): + CROSSROADS = "Crossroads" + DM_LAIR = "Dungeon Master's Lair" + DM_LAIR_INTERIOR = "Dungeon Master's Lair - Interior" + DRAGON_ARCHIPELAGO = "Dragon Archipelago" + DRAGON_ARCHIPELAGO_DRAGON = "Dragon Archipelago - Dragon" + ENDGAME = "Endgame" + GUE_TECH = "GUE Tech" + GUE_TECH_HALLWAY = "GUE Tech - Hallway" + GUE_TECH_OUTSIDE = "GUE Tech - Outside" + HADES = "Hades" + HADES_BEYOND_GATES = "Hades - Beyond Gates" + HADES_SHORE = "Hades - Shore" + MENU = "Menu" + MONASTERY = "Monastery" + MONASTERY_EXHIBIT = "Monastery - Exhibit" + PORT_FOOZLE = "Port Foozle" + PORT_FOOZLE_JACKS_SHOP = "Port Foozle - Jack's Shop" + PORT_FOOZLE_PAST = "Port Foozle Past" + PORT_FOOZLE_PAST_TAVERN = "Port Foozle Past - Tavern" + SPELL_LAB = "Spell Lab" + SPELL_LAB_BRIDGE = "Spell Lab - Bridge" + SUBWAY_CROSSROADS = "Subway Platform - Crossroads" + SUBWAY_FLOOD_CONTROL_DAM = "Subway Platform - Flood Control Dam #3" + SUBWAY_MONASTERY = "Subway Platform - Monastery" + WALKING_CASTLE = "Walking Castle" + WHITE_HOUSE = "White House" + + +class ZorkGrandInquisitorTags(enum.Enum): + CORE = "Core" + DEATHSANITY = "Deathsanity" + FILLER = "Filler" + HOTSPOT = "Hotspot" + INVENTORY_ITEM = "Inventory Item" + MISSABLE = "Missable" + SPELL = "Spell" + SUBWAY_DESTINATION = "Subway Destination" + TELEPORTER_DESTINATION = "Teleporter Destination" + TOTEMIZER_DESTINATION = "Totemizer Destination" + TOTEM = "Totem" diff --git a/worlds/zork_grand_inquisitor/game_controller.py b/worlds/zork_grand_inquisitor/game_controller.py new file mode 100644 index 0000000000..7a60a14608 --- /dev/null +++ b/worlds/zork_grand_inquisitor/game_controller.py @@ -0,0 +1,1388 @@ +import collections +import functools +import logging + +from typing import Dict, Optional, Set, Tuple, Union + +from .data.item_data import item_data, ZorkGrandInquisitorItemData +from .data.location_data import location_data, ZorkGrandInquisitorLocationData + +from .data.missable_location_grant_conditions_data import ( + missable_location_grant_conditions_data, + ZorkGrandInquisitorMissableLocationGrantConditionsData, +) + +from .data_funcs import game_id_to_items, items_with_tag, locations_with_tag + +from .enums import ( + ZorkGrandInquisitorGoals, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorLocations, + ZorkGrandInquisitorTags, +) + +from .game_state_manager import GameStateManager + + +class GameController: + logger: Optional[logging.Logger] + + game_state_manager: GameStateManager + + received_items: Set[ZorkGrandInquisitorItems] + completed_locations: Set[ZorkGrandInquisitorLocations] + + completed_locations_queue: collections.deque + received_items_queue: collections.deque + + all_hotspot_items: Set[ZorkGrandInquisitorItems] + + game_id_to_items: Dict[int, ZorkGrandInquisitorItems] + + possible_inventory_items: Set[ZorkGrandInquisitorItems] + + available_inventory_slots: Set[int] + + goal_completed: bool + + option_goal: Optional[ZorkGrandInquisitorGoals] + option_deathsanity: Optional[bool] + option_grant_missable_location_checks: Optional[bool] + + def __init__(self, logger=None) -> None: + self.logger = logger + + self.game_state_manager = GameStateManager() + + self.received_items = set() + self.completed_locations = set() + + self.completed_locations_queue = collections.deque() + self.received_items_queue = collections.deque() + + self.all_hotspot_items = ( + items_with_tag(ZorkGrandInquisitorTags.HOTSPOT) + | items_with_tag(ZorkGrandInquisitorTags.SUBWAY_DESTINATION) + | items_with_tag(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION) + ) + + self.game_id_to_items = game_id_to_items() + + self.possible_inventory_items = ( + items_with_tag(ZorkGrandInquisitorTags.INVENTORY_ITEM) + | items_with_tag(ZorkGrandInquisitorTags.SPELL) + | items_with_tag(ZorkGrandInquisitorTags.TOTEM) + ) + + self.available_inventory_slots = set() + + self.goal_completed = False + + self.option_goal = None + self.option_deathsanity = None + self.option_grant_missable_location_checks = None + + @functools.cached_property + def brog_items(self) -> Set[ZorkGrandInquisitorItems]: + return { + ZorkGrandInquisitorItems.BROGS_BICKERING_TORCH, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG, + ZorkGrandInquisitorItems.BROGS_PLANK, + } + + @functools.cached_property + def griff_items(self) -> Set[ZorkGrandInquisitorItems]: + return { + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, + } + + @functools.cached_property + def lucy_items(self) -> Set[ZorkGrandInquisitorItems]: + return { + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, + } + + @property + def totem_items(self) -> Set[ZorkGrandInquisitorItems]: + return self.brog_items | self.griff_items | self.lucy_items + + @functools.cached_property + def missable_locations(self) -> Set[ZorkGrandInquisitorLocations]: + return locations_with_tag(ZorkGrandInquisitorTags.MISSABLE) + + def log(self, message) -> None: + if self.logger: + self.logger.info(message) + + def log_debug(self, message) -> None: + if self.logger: + self.logger.debug(message) + + def open_process_handle(self) -> bool: + return self.game_state_manager.open_process_handle() + + def close_process_handle(self) -> bool: + return self.game_state_manager.close_process_handle() + + def is_process_running(self) -> bool: + return self.game_state_manager.is_process_running + + def list_received_brog_items(self) -> None: + self.log("Received Brog Items:") + + self._process_received_items() + received_brog_items: Set[ZorkGrandInquisitorItems] = self.received_items & self.brog_items + + if not len(received_brog_items): + self.log(" Nothing") + return + + for item in sorted(i.value for i in received_brog_items): + self.log(f" {item}") + + def list_received_griff_items(self) -> None: + self.log("Received Griff Items:") + + self._process_received_items() + received_griff_items: Set[ZorkGrandInquisitorItems] = self.received_items & self.griff_items + + if not len(received_griff_items): + self.log(" Nothing") + return + + for item in sorted(i.value for i in received_griff_items): + self.log(f" {item}") + + def list_received_lucy_items(self) -> None: + self.log("Received Lucy Items:") + + self._process_received_items() + received_lucy_items: Set[ZorkGrandInquisitorItems] = self.received_items & self.lucy_items + + if not len(received_lucy_items): + self.log(" Nothing") + return + + for item in sorted(i.value for i in received_lucy_items): + self.log(f" {item}") + + def list_received_hotspots(self) -> None: + self.log("Received Hotspots:") + + self._process_received_items() + + hotspot_items: Set[ZorkGrandInquisitorItems] = items_with_tag(ZorkGrandInquisitorTags.HOTSPOT) + received_hotspots: Set[ZorkGrandInquisitorItems] = self.received_items & hotspot_items + + if not len(received_hotspots): + self.log(" Nothing") + return + + for item in sorted(i.value for i in received_hotspots): + self.log(f" {item}") + + def update(self) -> None: + if self.game_state_manager.is_process_still_running(): + try: + self.game_state_manager.refresh_game_location() + + self._apply_permanent_game_state() + self._apply_conditional_game_state() + + self._apply_permanent_game_flags() + + self._check_for_completed_locations() + + if self.option_grant_missable_location_checks: + self._check_for_missable_locations_to_grant() + + self._process_received_items() + + self._manage_hotspots() + self._manage_items() + + self._apply_conditional_teleports() + + self._check_for_victory() + except Exception as e: + self.log_debug(e) + + def _apply_permanent_game_state(self) -> None: + self._write_game_state_value_for(10934, 1) # Rope Taken + self._write_game_state_value_for(10418, 1) # Mead Light Taken + self._write_game_state_value_for(10275, 0) # Lantern in Crate + self._write_game_state_value_for(13929, 1) # Great Underground Door Open + self._write_game_state_value_for(13968, 1) # Subway Token Taken + self._write_game_state_value_for(12930, 1) # Hammer Taken + self._write_game_state_value_for(12935, 1) # Griff Totem Taken + self._write_game_state_value_for(12948, 1) # ZIMDOR Scroll Taken + self._write_game_state_value_for(4058, 1) # Shovel Taken + self._write_game_state_value_for(4059, 1) # THROCK Scroll Taken + self._write_game_state_value_for(11758, 1) # KENDALL Scroll Taken + self._write_game_state_value_for(16959, 1) # Old Scratch Card Taken + self._write_game_state_value_for(12840, 0) # Zork Rocks in Perma-Suck Machine + self._write_game_state_value_for(11886, 1) # Student ID Taken + self._write_game_state_value_for(16279, 1) # Prozork Tablet Taken + self._write_game_state_value_for(13260, 1) # GOLGATEM Scroll Taken + self._write_game_state_value_for(4834, 1) # Flatheadia Fudge Taken + self._write_game_state_value_for(4746, 1) # Jar of Hotbugs Taken + self._write_game_state_value_for(4755, 1) # Hungus Lard Taken + self._write_game_state_value_for(4758, 1) # Mug Taken + self._write_game_state_value_for(3716, 1) # NARWILE Scroll Taken + self._write_game_state_value_for(17147, 1) # Lucy Totem Taken + self._write_game_state_value_for(9818, 1) # Middle Telegraph Hammer Taken + self._write_game_state_value_for(3766, 0) # ANS Scroll in Window + self._write_game_state_value_for(4980, 0) # ANS Scroll in Window + self._write_game_state_value_for(3768, 0) # GIV Scroll in Window + self._write_game_state_value_for(4978, 0) # GIV Scroll in Window + self._write_game_state_value_for(3765, 0) # SNA Scroll in Window + self._write_game_state_value_for(4979, 0) # SNA Scroll in Window + self._write_game_state_value_for(3767, 0) # VIG Scroll in Window + self._write_game_state_value_for(4977, 0) # VIG Scroll in Window + self._write_game_state_value_for(15065, 1) # Brog's Bickering Torch Taken + self._write_game_state_value_for(15088, 1) # Brog's Flickering Torch Taken + self._write_game_state_value_for(2628, 4) # Brog's Grue Eggs Taken + self._write_game_state_value_for(2971, 1) # Brog's Plank Taken + self._write_game_state_value_for(1340, 1) # Griff's Inflatable Sea Captain Taken + self._write_game_state_value_for(1341, 1) # Griff's Inflatable Raft Taken + self._write_game_state_value_for(1477, 1) # Griff's Air Pump Taken + self._write_game_state_value_for(1814, 1) # Griff's Dragon Tooth Taken + self._write_game_state_value_for(15403, 0) # Lucy's Cards Taken + self._write_game_state_value_for(15404, 1) # Lucy's Cards Taken + self._write_game_state_value_for(15405, 4) # Lucy's Cards Taken + self._write_game_state_value_for(5222, 1) # User Has Spell Book + self._write_game_state_value_for(13930, 1) # Skip Well Cutscenes + self._write_game_state_value_for(19057, 1) # Skip Well Cutscenes + self._write_game_state_value_for(13934, 1) # Skip Well Cutscenes + self._write_game_state_value_for(13935, 1) # Skip Well Cutscenes + self._write_game_state_value_for(13384, 1) # Skip Meanwhile... Cutscene + self._write_game_state_value_for(8620, 1) # First Coin Paid to Charon + self._write_game_state_value_for(8731, 1) # First Coin Paid to Charon + + def _apply_conditional_game_state(self): + # Can teleport to Dungeon Master's Lair + if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR): + self._write_game_state_value_for(2203, 1) + else: + self._write_game_state_value_for(2203, 0) + + # Can teleport to GUE Tech + if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH): + self._write_game_state_value_for(7132, 1) + else: + self._write_game_state_value_for(7132, 0) + + # Can Teleport to Spell Lab + if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB): + self._write_game_state_value_for(16545, 1) + else: + self._write_game_state_value_for(16545, 0) + + # Can Teleport to Hades + if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES): + self._write_game_state_value_for(7119, 1) + else: + self._write_game_state_value_for(7119, 0) + + # Can Teleport to Monastery Station + if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY): + self._write_game_state_value_for(7148, 1) + else: + self._write_game_state_value_for(7148, 0) + + # Initial Totemizer Destination + should_force_initial_totemizer_destination: bool = True + + if self._player_has(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION): + should_force_initial_totemizer_destination = False + elif self._player_has(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL): + should_force_initial_totemizer_destination = False + elif self._player_has(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_INFINITY): + should_force_initial_totemizer_destination = False + elif self._player_has(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_SURFACE_OF_MERZ): + should_force_initial_totemizer_destination = False + + if should_force_initial_totemizer_destination: + self._write_game_state_value_for(9617, 2) + + # Pouch of Zorkmids + if self._player_has(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS): + self._write_game_state_value_for(5827, 1) + else: + self._write_game_state_value_for(5827, 0) + + # Brog Torches + if self._player_is_brog() and self._player_has(ZorkGrandInquisitorItems.BROGS_BICKERING_TORCH): + self._write_game_state_value_for(10999, 1) + else: + self._write_game_state_value_for(10999, 0) + + if self._player_is_brog() and self._player_has(ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH): + self._write_game_state_value_for(10998, 1) + else: + self._write_game_state_value_for(10998, 0) + + # Monastery Rope + if ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE in self.completed_locations: + self._write_game_state_value_for(9637, 1) + + def _apply_permanent_game_flags(self) -> None: + self._write_game_flags_value_for(9437, 2) # Monastery Exhibit Door to Outside + self._write_game_flags_value_for(3074, 2) # White House Door + self._write_game_flags_value_for(13005, 2) # Map + self._write_game_flags_value_for(13006, 2) # Sword + self._write_game_flags_value_for(13007, 2) # Sword + self._write_game_flags_value_for(13389, 2) # Moss of Mareilon + self._write_game_flags_value_for(4301, 2) # Quelbee Honeycomb + self._write_game_flags_value_for(12895, 2) # Change Machine Money + self._write_game_flags_value_for(4150, 2) # Prozorked Snapdragon + self._write_game_flags_value_for(13413, 2) # Letter Opener + self._write_game_flags_value_for(15403, 2) # Lucy's Cards + + def _check_for_completed_locations(self) -> None: + location: ZorkGrandInquisitorLocations + data: ZorkGrandInquisitorLocationData + for location, data in location_data.items(): + if location in self.completed_locations or not isinstance( + location, ZorkGrandInquisitorLocations + ): + continue + + is_location_completed: bool = True + + trigger: [Union[str, int]] + value: Union[str, int, Tuple[int, ...]] + for trigger, value in data.game_state_trigger: + if trigger == "location": + if not self._player_is_at(value): + is_location_completed = False + break + elif isinstance(trigger, int): + if isinstance(value, int): + if self._read_game_state_value_for(trigger) != value: + is_location_completed = False + break + elif isinstance(value, tuple): + if self._read_game_state_value_for(trigger) not in value: + is_location_completed = False + break + else: + is_location_completed = False + break + else: + is_location_completed = False + break + + if is_location_completed: + self.completed_locations.add(location) + self.completed_locations_queue.append(location) + + def _check_for_missable_locations_to_grant(self) -> None: + missable_location: ZorkGrandInquisitorLocations + for missable_location in self.missable_locations: + if missable_location in self.completed_locations: + continue + + data: ZorkGrandInquisitorLocationData = location_data[missable_location] + + if ZorkGrandInquisitorTags.DEATHSANITY in data.tags and not self.option_deathsanity: + continue + + condition_data: ZorkGrandInquisitorMissableLocationGrantConditionsData = ( + missable_location_grant_conditions_data.get(missable_location) + ) + + if condition_data is None: + self.log_debug(f"Missable Location {missable_location.value} has no grant conditions") + continue + + if condition_data.location_condition in self.completed_locations: + grant_location: bool = True + + item: ZorkGrandInquisitorItems + for item in condition_data.item_conditions or tuple(): + if self._player_doesnt_have(item): + grant_location = False + break + + if grant_location: + self.completed_locations_queue.append(missable_location) + + def _process_received_items(self) -> None: + while len(self.received_items_queue) > 0: + item: ZorkGrandInquisitorItems = self.received_items_queue.popleft() + data: ZorkGrandInquisitorItemData = item_data[item] + + if ZorkGrandInquisitorTags.FILLER in data.tags: + continue + + self.received_items.add(item) + + def _manage_hotspots(self) -> None: + hotspot_item: ZorkGrandInquisitorItems + for hotspot_item in self.all_hotspot_items: + data: ZorkGrandInquisitorItemData = item_data[hotspot_item] + + if hotspot_item not in self.received_items: + key: int + for key in data.statemap_keys: + self._write_game_flags_value_for(key, 2) + else: + if hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_666_MAILBOX: + if self.game_state_manager.game_location == "hp5g": + if self._read_game_state_value_for(9113) == 0: + self._write_game_flags_value_for(9116, 0) + else: + self._write_game_flags_value_for(9116, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS: + if self.game_state_manager.game_location == "qb2g": + if self._read_game_state_value_for(15433) == 0: + self._write_game_flags_value_for(15434, 0) + else: + self._write_game_flags_value_for(15434, 2) + + if self._read_game_state_value_for(15435) == 0: + self._write_game_flags_value_for(15436, 0) + else: + self._write_game_flags_value_for(15436, 2) + + if self._read_game_state_value_for(15437) == 0: + self._write_game_flags_value_for(15438, 0) + else: + self._write_game_flags_value_for(15438, 2) + + if self._read_game_state_value_for(15439) == 0: + self._write_game_flags_value_for(15440, 0) + else: + self._write_game_flags_value_for(15440, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX: + if self.game_state_manager.game_location == "tp2g": + if self._read_game_state_value_for(12095) == 1: + self._write_game_flags_value_for(9115, 2) + else: + self._write_game_flags_value_for(9115, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_BLINDS: + if self.game_state_manager.game_location == "dv1e": + if self._read_game_state_value_for(4743) == 0: + self._write_game_flags_value_for(4799, 0) + else: + self._write_game_flags_value_for(4799, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS: + if self.game_state_manager.game_location == "tr5g": + key: int + for key in data.statemap_keys: + self._write_game_flags_value_for(key, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT: + if self.game_state_manager.game_location == "tr5g": + self._write_game_flags_value_for(12702, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_VACUUM_SLOT: + if self.game_state_manager.game_location == "tr5m": + self._write_game_flags_value_for(12909, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CHANGE_MACHINE_SLOT: + if self.game_state_manager.game_location == "tr5j": + if self._read_game_state_value_for(12892) == 0: + self._write_game_flags_value_for(12900, 0) + else: + self._write_game_flags_value_for(12900, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR: + if self.game_state_manager.game_location == "dw1e": + if self._read_game_state_value_for(4983) == 0: + self._write_game_flags_value_for(5010, 0) + else: + self._write_game_flags_value_for(5010, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT: + if self.game_state_manager.game_location == "me2j": + if self._read_game_state_value_for(9491) == 2: + self._write_game_flags_value_for(9539, 0) + else: + self._write_game_flags_value_for(9539, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER: + if self.game_state_manager.game_location == "me2j": + if self._read_game_state_value_for(9546) == 2 or self._read_game_state_value_for(9419) == 1: + self._write_game_flags_value_for(19712, 2) + else: + self._write_game_flags_value_for(19712, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT: + if self.game_state_manager.game_location == "sg1f": + self._write_game_flags_value_for(2586, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DENTED_LOCKER: + if self.game_state_manager.game_location == "th3j": + five_is_open: bool = self._read_game_state_value_for(11847) == 1 + six_is_open: bool = self._read_game_state_value_for(11840) == 1 + seven_is_open: bool = self._read_game_state_value_for(11841) == 1 + eight_is_open: bool = self._read_game_state_value_for(11848) == 1 + + rocks_in_six: bool = self._read_game_state_value_for(11769) == 1 + six_blasted: bool = self._read_game_state_value_for(11770) == 1 + + if five_is_open or six_is_open or seven_is_open or eight_is_open or rocks_in_six or six_blasted: + self._write_game_flags_value_for(11878, 2) + else: + self._write_game_flags_value_for(11878, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DIRT_MOUND: + if self.game_state_manager.game_location == "te5e": + if self._read_game_state_value_for(11747) == 0: + self._write_game_flags_value_for(11751, 0) + else: + self._write_game_flags_value_for(11751, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH: + if self.game_state_manager.game_location == "pe2e": + self._write_game_flags_value_for(15147, 0) + self._write_game_flags_value_for(15153, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW: + if self.game_state_manager.game_location == "cd70": + self._write_game_flags_value_for(1705, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS: + if self.game_state_manager.game_location == "cd3h": + raft_in_left: bool = self._read_game_state_value_for(1301) == 1 + raft_in_right: bool = self._read_game_state_value_for(1304) == 1 + raft_inflated: bool = self._read_game_state_value_for(1379) == 1 + + captain_in_left: bool = self._read_game_state_value_for(1374) == 1 + captain_in_right: bool = self._read_game_state_value_for(1381) == 1 + captain_inflated: bool = self._read_game_state_value_for(1378) == 1 + + left_inflated: bool = (raft_in_left and raft_inflated) or (captain_in_left and captain_inflated) + + right_inflated: bool = (raft_in_right and raft_inflated) or ( + captain_in_right and captain_inflated + ) + + if left_inflated: + self._write_game_flags_value_for(1425, 2) + else: + self._write_game_flags_value_for(1425, 0) + + if right_inflated: + self._write_game_flags_value_for(1426, 2) + else: + self._write_game_flags_value_for(1426, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE: + if self.game_state_manager.game_location == "uc3e": + if self._read_game_state_value_for(13060) == 0: + self._write_game_flags_value_for(13106, 0) + else: + self._write_game_flags_value_for(13106, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS: + if self.game_state_manager.game_location == "ue1e": + if self._read_game_state_value_for(14318) == 0: + self._write_game_flags_value_for(13219, 0) + self._write_game_flags_value_for(13220, 0) + self._write_game_flags_value_for(13221, 0) + self._write_game_flags_value_for(13222, 0) + else: + self._write_game_flags_value_for(13219, 2) + self._write_game_flags_value_for(13220, 2) + self._write_game_flags_value_for(13221, 2) + self._write_game_flags_value_for(13222, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS: + if self.game_state_manager.game_location == "ue1e": + if self._read_game_state_value_for(14318) == 0: + self._write_game_flags_value_for(14327, 0) + self._write_game_flags_value_for(14332, 0) + self._write_game_flags_value_for(14337, 0) + self._write_game_flags_value_for(14342, 0) + else: + self._write_game_flags_value_for(14327, 2) + self._write_game_flags_value_for(14332, 2) + self._write_game_flags_value_for(14337, 2) + self._write_game_flags_value_for(14342, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT: + if self.game_state_manager.game_location == "tr5e": + self._write_game_flags_value_for(12528, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS: + if self.game_state_manager.game_location == "tr5e": + if self._read_game_state_value_for(12220) == 0: + self._write_game_flags_value_for(12523, 2) + self._write_game_flags_value_for(12524, 2) + self._write_game_flags_value_for(12525, 2) + else: + self._write_game_flags_value_for(12523, 0) + self._write_game_flags_value_for(12524, 0) + self._write_game_flags_value_for(12525, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_GLASS_CASE: + if self.game_state_manager.game_location == "uc1g": + if self._read_game_state_value_for(12931) == 1 or self._read_game_state_value_for(12929) == 1: + self._write_game_flags_value_for(13002, 2) + else: + self._write_game_flags_value_for(13002, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL: + if self.game_state_manager.game_location == "pe5e": + if self._read_game_state_value_for(10277) == 0: + self._write_game_flags_value_for(10726, 0) + else: + self._write_game_flags_value_for(10726, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR: + if self.game_state_manager.game_location == "tr1k": + if self._read_game_state_value_for(12212) == 0: + self._write_game_flags_value_for(12280, 0) + else: + self._write_game_flags_value_for(12280, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_GRASS: + if self.game_state_manager.game_location in ("te10", "te1g", "te20", "te30", "te40"): + key: int + for key in data.statemap_keys: + self._write_game_flags_value_for(key, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS: + if self.game_state_manager.game_location == "hp1e": + if self._read_game_state_value_for(8431) == 1: + key: int + for key in data.statemap_keys: + self._write_game_flags_value_for(key, 0) + else: + key: int + for key in data.statemap_keys: + self._write_game_flags_value_for(key, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER: + if self.game_state_manager.game_location == "hp1e": + if self._read_game_state_value_for(8431) == 1: + self._write_game_flags_value_for(8446, 2) + else: + self._write_game_flags_value_for(8446, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HARRY: + if self.game_state_manager.game_location == "dg4e": + if self._read_game_state_value_for(4237) == 1 and self._read_game_state_value_for(4034) == 1: + self._write_game_flags_value_for(4260, 2) + else: + self._write_game_flags_value_for(4260, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY: + if self.game_state_manager.game_location == "dg4h": + if self._read_game_state_value_for(4279) == 1: + self._write_game_flags_value_for(18026, 2) + else: + self._write_game_flags_value_for(18026, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH: + if self.game_state_manager.game_location == "dg4g": + if self._read_game_state_value_for(4034) == 1: + self._write_game_flags_value_for(17623, 2) + else: + self._write_game_flags_value_for(17623, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR: + if self.game_state_manager.game_location == "uc4e": + if self._read_game_state_value_for(13062) == 1: + self._write_game_flags_value_for(13140, 2) + else: + self._write_game_flags_value_for(13140, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR: + if self.game_state_manager.game_location == "pe1e": + if self._read_game_state_value_for(10451) == 1: + self._write_game_flags_value_for(10441, 2) + else: + self._write_game_flags_value_for(10441, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS: + if self.game_state_manager.game_location == "pe2j": + self._write_game_flags_value_for(19632, 0) + self._write_game_flags_value_for(19627, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR: + if self.game_state_manager.game_location == "sw4e": + if self._read_game_state_value_for(2989) == 1: + self._write_game_flags_value_for(3025, 2) + else: + self._write_game_flags_value_for(3025, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG: + if self.game_state_manager.game_location == "sw4e": + self._write_game_flags_value_for(3036, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MIRROR: + if self.game_state_manager.game_location == "dw1f": + self._write_game_flags_value_for(5031, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT: + if self.game_state_manager.game_location == "um1e": + if self._read_game_state_value_for(9637) == 0: + self._write_game_flags_value_for(13597, 0) + else: + self._write_game_flags_value_for(13597, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MOSSY_GRATE: + if self.game_state_manager.game_location == "ue2g": + if self._read_game_state_value_for(13278) == 0: + self._write_game_flags_value_for(13390, 0) + else: + self._write_game_flags_value_for(13390, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR: + if self.game_state_manager.game_location == "qe1e": + if self._player_is_brog(): + self._write_game_flags_value_for(2447, 0) + elif self._player_is_griff(): + self._write_game_flags_value_for(2455, 0) + elif self._player_is_lucy(): + if self._read_game_state_value_for(2457) == 0: + self._write_game_flags_value_for(2455, 0) + else: + self._write_game_flags_value_for(2455, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS: + if self.game_state_manager.game_location == "tr3h": + if self._read_game_state_value_for(11777) == 1: + self._write_game_flags_value_for(12389, 2) + else: + self._write_game_flags_value_for(12389, 0) + + self._write_game_state_value_for(12390, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE: + if self.game_state_manager.game_location == "dg4f": + if self._read_game_state_value_for(4241) == 1: + self._write_game_flags_value_for(4302, 2) + else: + self._write_game_flags_value_for(4302, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE: + if self.game_state_manager.game_location == "tp1e": + if self._read_game_state_value_for(16342) == 1: + self._write_game_flags_value_for(16383, 2) + self._write_game_flags_value_for(16384, 2) + else: + self._write_game_flags_value_for(16383, 0) + self._write_game_flags_value_for(16384, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE: + if self.game_state_manager.game_location == "sg6e": + if self._read_game_state_value_for(15715) == 1: + self._write_game_flags_value_for(2769, 2) + else: + self._write_game_flags_value_for(2769, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON: + if self.game_state_manager.game_location == "dg2f": + if self._read_game_state_value_for(4114) == 1 or self._read_game_state_value_for(4115) == 1: + self._write_game_flags_value_for(4149, 2) + else: + self._write_game_flags_value_for(4149, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_BUTTONS: + if self.game_state_manager.game_location == "tr5f": + self._write_game_flags_value_for(12584, 0) + self._write_game_flags_value_for(12585, 0) + self._write_game_flags_value_for(12586, 0) + self._write_game_flags_value_for(12587, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_COIN_SLOT: + if self.game_state_manager.game_location == "tr5f": + self._write_game_flags_value_for(12574, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SOUVENIR_COIN_SLOT: + if self.game_state_manager.game_location == "ue2j": + if self._read_game_state_value_for(13408) == 1: + self._write_game_flags_value_for(13412, 2) + else: + self._write_game_flags_value_for(13412, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER: + if self.game_state_manager.game_location == "tp4g": + self._write_game_flags_value_for(12170, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM: + if self.game_state_manager.game_location == "tp1e": + if self._read_game_state_value_for(16342) == 1 and self._read_game_state_value_for(16374) == 0: + self._write_game_flags_value_for(16382, 0) + else: + self._write_game_flags_value_for(16382, 2) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM: + if self.game_state_manager.game_location == "dg3e": + self._write_game_flags_value_for(4209, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE: + if self.game_state_manager.game_location == "th3r": + self._write_game_flags_value_for(11973, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT: + if self.game_state_manager.game_location == "uc6e": + self._write_game_flags_value_for(13168, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY: + if self.game_state_manager.game_location == "qb2e": + if self._read_game_state_value_for(15395) == 1: + self._write_game_flags_value_for(15396, 2) + else: + self._write_game_flags_value_for(15396, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH: + if self.game_state_manager.game_location == "mt2e": + self._write_game_flags_value_for(9706, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS: + if self.game_state_manager.game_location == "mt2g": + self._write_game_flags_value_for(9728, 0) + self._write_game_flags_value_for(9729, 0) + self._write_game_flags_value_for(9730, 0) + elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_WELL: + if self.game_state_manager.game_location == "pc1e": + self._write_game_flags_value_for(10314, 0) + elif hotspot_item == ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM: + if self.game_state_manager.game_location == "us2e": + self._write_game_flags_value_for(13757, 0) + elif self.game_state_manager.game_location == "ue2e": + self._write_game_flags_value_for(13297, 0) + elif self.game_state_manager.game_location == "uh2e": + self._write_game_flags_value_for(13486, 0) + elif self.game_state_manager.game_location == "um2e": + self._write_game_flags_value_for(13625, 0) + elif hotspot_item == ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES: + if self.game_state_manager.game_location == "us2e": + self._write_game_flags_value_for(13758, 0) + elif self.game_state_manager.game_location == "ue2e": + self._write_game_flags_value_for(13309, 0) + elif self.game_state_manager.game_location == "uh2e": + self._write_game_flags_value_for(13498, 0) + elif self.game_state_manager.game_location == "um2e": + self._write_game_flags_value_for(13637, 0) + elif hotspot_item == ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY: + if self.game_state_manager.game_location == "us2e": + self._write_game_flags_value_for(13759, 0) + elif self.game_state_manager.game_location == "ue2e": + self._write_game_flags_value_for(13316, 0) + elif self.game_state_manager.game_location == "uh2e": + self._write_game_flags_value_for(13505, 0) + elif self.game_state_manager.game_location == "um2e": + self._write_game_flags_value_for(13644, 0) + elif hotspot_item == ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION: + if self.game_state_manager.game_location == "mt1f": + self._write_game_flags_value_for(9660, 0) + elif hotspot_item == ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_INFINITY: + if self.game_state_manager.game_location == "mt1f": + self._write_game_flags_value_for(9666, 0) + elif hotspot_item == ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL: + if self.game_state_manager.game_location == "mt1f": + self._write_game_flags_value_for(9668, 0) + elif hotspot_item == ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_SURFACE_OF_MERZ: + if self.game_state_manager.game_location == "mt1f": + self._write_game_flags_value_for(9662, 0) + + def _manage_items(self) -> None: + if self._player_is_afgncaap(): + self.available_inventory_slots = self._determine_available_inventory_slots() + + received_inventory_items: Set[ZorkGrandInquisitorItems] + received_inventory_items = self.received_items & self.possible_inventory_items + + received_inventory_items = self._filter_received_inventory_items(received_inventory_items) + elif self._player_is_totem(): + self.available_inventory_slots = self._determine_available_inventory_slots(is_totem=True) + + received_inventory_items: Set[ZorkGrandInquisitorItems] + + if self._player_is_brog(): + received_inventory_items = self.received_items & self.brog_items + received_inventory_items = self._filter_received_brog_inventory_items(received_inventory_items) + elif self._player_is_griff(): + received_inventory_items = self.received_items & self.griff_items + received_inventory_items = self._filter_received_griff_inventory_items(received_inventory_items) + elif self._player_is_lucy(): + received_inventory_items = self.received_items & self.lucy_items + received_inventory_items = self._filter_received_lucy_inventory_items(received_inventory_items) + else: + return None + else: + return None + + game_state_inventory_items: Set[ZorkGrandInquisitorItems] = self._determine_game_state_inventory() + + inventory_items_to_remove: Set[ZorkGrandInquisitorItems] + inventory_items_to_remove = game_state_inventory_items - received_inventory_items + + inventory_items_to_add: Set[ZorkGrandInquisitorItems] + inventory_items_to_add = received_inventory_items - game_state_inventory_items + + item: ZorkGrandInquisitorItems + for item in inventory_items_to_remove: + self._remove_from_inventory(item) + + item: ZorkGrandInquisitorItems + for item in inventory_items_to_add: + self._add_to_inventory(item) + + # Item Deduplication (Just in Case) + seen_items: Set[int] = set() + + i: int + for i in range(151, 171): + item: int = self._read_game_state_value_for(i) + + if item in seen_items: + self._write_game_state_value_for(i, 0) + else: + seen_items.add(item) + + def _apply_conditional_teleports(self) -> None: + if self._player_is_at("uw1x"): + self.game_state_manager.set_game_location("uw10", 0) + + if self._player_is_at("uw1k") and self._read_game_state_value_for(13938) == 0: + self.game_state_manager.set_game_location("pc10", 250) + + if self._player_is_at("ue1q"): + self.game_state_manager.set_game_location("ue1e", 0) + + if self._player_is_at("ej10"): + self.game_state_manager.set_game_location("uc10", 1200) + + if self._read_game_state_value_for(9) == 224: + self._write_game_state_value_for(9, 0) + self.game_state_manager.set_game_location("uc10", 1200) + + def _check_for_victory(self) -> None: + if self.option_goal == ZorkGrandInquisitorGoals.THREE_ARTIFACTS: + coconut_is_placed = self._read_game_state_value_for(2200) == 1 + cube_is_placed = self._read_game_state_value_for(2322) == 1 + skull_is_placed = self._read_game_state_value_for(2321) == 1 + + self.goal_completed = coconut_is_placed and cube_is_placed and skull_is_placed + + def _determine_game_state_inventory(self) -> Set[ZorkGrandInquisitorItems]: + game_state_inventory: Set[ZorkGrandInquisitorItems] = set() + + # Item on Cursor + item_on_cursor: int = self._read_game_state_value_for(9) + + if item_on_cursor != 0: + if item_on_cursor in self.game_id_to_items: + game_state_inventory.add(self.game_id_to_items[item_on_cursor]) + + # Item in Inspector + item_in_inspector: int = self._read_game_state_value_for(4512) + + if item_in_inspector != 0: + if item_in_inspector in self.game_id_to_items: + game_state_inventory.add(self.game_id_to_items[item_in_inspector]) + + # Items in Inventory Slots + i: int + for i in range(151, 171): + if self._read_game_state_value_for(i) != 0: + if self._read_game_state_value_for(i) in self.game_id_to_items: + game_state_inventory.add( + self.game_id_to_items[self._read_game_state_value_for(i)] + ) + + # Pouch of Zorkmids + if self._read_game_state_value_for(5827) == 1: + game_state_inventory.add(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS) + + # Spells + i: int + for i in range(191, 203): + if self._read_game_state_value_for(i) == 1: + if i in self.game_id_to_items: + game_state_inventory.add(self.game_id_to_items[i]) + + # Totems + if self._read_game_state_value_for(4853) == 1: + game_state_inventory.add(ZorkGrandInquisitorItems.TOTEM_BROG) + + if self._read_game_state_value_for(4315) == 1: + game_state_inventory.add(ZorkGrandInquisitorItems.TOTEM_GRIFF) + + if self._read_game_state_value_for(5223) == 1: + game_state_inventory.add(ZorkGrandInquisitorItems.TOTEM_LUCY) + + return game_state_inventory + + def _add_to_inventory(self, item: ZorkGrandInquisitorItems) -> None: + if item == ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS: + return None + + data: ZorkGrandInquisitorItemData = item_data[item] + + if ZorkGrandInquisitorTags.INVENTORY_ITEM in data.tags: + if len(self.available_inventory_slots): # Inventory slot overflow protection + inventory_slot: int = self.available_inventory_slots.pop() + self._write_game_state_value_for(inventory_slot, data.statemap_keys[0]) + elif ZorkGrandInquisitorTags.SPELL in data.tags: + self._write_game_state_value_for(data.statemap_keys[0], 1) + elif ZorkGrandInquisitorTags.TOTEM in data.tags: + self._write_game_state_value_for(data.statemap_keys[0], 1) + + def _remove_from_inventory(self, item: ZorkGrandInquisitorItems) -> None: + if item == ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS: + return None + + data: ZorkGrandInquisitorItemData = item_data[item] + + if ZorkGrandInquisitorTags.INVENTORY_ITEM in data.tags: + inventory_slot: Optional[int] = self._inventory_slot_for(item) + + if inventory_slot is None: + return None + + self._write_game_state_value_for(inventory_slot, 0) + + if inventory_slot != 9: + self.available_inventory_slots.add(inventory_slot) + elif ZorkGrandInquisitorTags.SPELL in data.tags: + self._write_game_state_value_for(data.statemap_keys[0], 0) + elif ZorkGrandInquisitorTags.TOTEM in data.tags: + self._write_game_state_value_for(data.statemap_keys[0], 0) + + def _determine_available_inventory_slots(self, is_totem: bool = False) -> Set[int]: + available_inventory_slots: Set[int] = set() + + inventory_slot_range_end: int = 171 + + if is_totem: + if self._player_is_brog(): + inventory_slot_range_end = 161 + elif self._player_is_griff(): + inventory_slot_range_end = 160 + elif self._player_is_lucy(): + inventory_slot_range_end = 157 + + i: int + for i in range(151, inventory_slot_range_end): + if self._read_game_state_value_for(i) == 0: + available_inventory_slots.add(i) + + return available_inventory_slots + + def _inventory_slot_for(self, item) -> Optional[int]: + data: ZorkGrandInquisitorItemData = item_data[item] + + if ZorkGrandInquisitorTags.INVENTORY_ITEM in data.tags: + i: int + for i in range(151, 171): + if self._read_game_state_value_for(i) == data.statemap_keys[0]: + return i + + if self._read_game_state_value_for(9) == data.statemap_keys[0]: + return 9 + + if self._read_game_state_value_for(4512) == data.statemap_keys[0]: + return 4512 + + return None + + def _filter_received_inventory_items( + self, received_inventory_items: Set[ZorkGrandInquisitorItems] + ) -> Set[ZorkGrandInquisitorItems]: + to_filter_inventory_items: Set[ZorkGrandInquisitorItems] = self.totem_items + + inventory_item_values: Set[int] = set() + + i: int + for i in range(151, 171): + inventory_item_values.add(self._read_game_state_value_for(i)) + + cursor_item_value: int = self._read_game_state_value_for(9) + inspector_item_value: int = self._read_game_state_value_for(4512) + + inventory_item_values.add(cursor_item_value) + inventory_item_values.add(inspector_item_value) + + item: ZorkGrandInquisitorItems + for item in received_inventory_items: + if item == ZorkGrandInquisitorItems.FLATHEADIA_FUDGE: + if self._read_game_state_value_for(4766) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(4869) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.HUNGUS_LARD: + if self._read_game_state_value_for(4870) == 1: + to_filter_inventory_items.add(item) + elif ( + self._read_game_state_value_for(4244) == 1 + and self._read_game_state_value_for(4309) == 0 + ): + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.JAR_OF_HOTBUGS: + if self._read_game_state_value_for(4750) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(4869) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.LANTERN: + if self._read_game_state_value_for(10477) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(5221) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER: + if self._read_game_state_value_for(9491) == 3: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.MAP: + if self._read_game_state_value_for(16618) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.MEAD_LIGHT: + if 105 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(17620) > 0: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(4034) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.MOSS_OF_MAREILON: + if self._read_game_state_value_for(4763) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(4869) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.MUG: + if self._read_game_state_value_for(4772) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(4869) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.OLD_SCRATCH_CARD: + if 32 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(12892) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.PERMA_SUCK_MACHINE: + if self._read_game_state_value_for(12218) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER: + if self._read_game_state_value_for(15150) == 3: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(10421) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.PROZORK_TABLET: + if self._read_game_state_value_for(4115) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB: + if self._read_game_state_value_for(4769) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(4869) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.ROPE: + if 22 in inventory_item_values: + to_filter_inventory_items.add(item) + elif 111 in inventory_item_values: + to_filter_inventory_items.add(item) + elif ( + self._read_game_state_value_for(10304) == 1 + and not self._read_game_state_value_for(13938) == 1 + ): + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15150) == 83: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS: + if 41 in inventory_item_values: + to_filter_inventory_items.add(item) + elif 98 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(201) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV: + if 48 in inventory_item_values: + to_filter_inventory_items.add(item) + elif 98 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(201) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.SNAPDRAGON: + if self._read_game_state_value_for(4199) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.STUDENT_ID: + if self._read_game_state_value_for(11838) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.SUBWAY_TOKEN: + if self._read_game_state_value_for(13167) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.SWORD: + if 22 in inventory_item_values: + to_filter_inventory_items.add(item) + elif 100 in inventory_item_values: + to_filter_inventory_items.add(item) + elif 111 in inventory_item_values: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.ZIMDOR_SCROLL: + if 105 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(17620) == 3: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(4034) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.ZORK_ROCKS: + if self._read_game_state_value_for(12486) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(12487) == 1: + to_filter_inventory_items.add(item) + elif 52 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(11769) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(11840) == 1: + to_filter_inventory_items.add(item) + + return received_inventory_items - to_filter_inventory_items + + def _filter_received_brog_inventory_items( + self, received_inventory_items: Set[ZorkGrandInquisitorItems] + ) -> Set[ZorkGrandInquisitorItems]: + to_filter_inventory_items: Set[ZorkGrandInquisitorItems] = set() + + inventory_item_values: Set[int] = set() + + i: int + for i in range(151, 161): + inventory_item_values.add(self._read_game_state_value_for(i)) + + cursor_item_value: int = self._read_game_state_value_for(9) + inspector_item_value: int = self._read_game_state_value_for(2194) + + inventory_item_values.add(cursor_item_value) + inventory_item_values.add(inspector_item_value) + + item: ZorkGrandInquisitorItems + for item in received_inventory_items: + if item == ZorkGrandInquisitorItems.BROGS_BICKERING_TORCH: + if 103 in inventory_item_values: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH: + if 104 in inventory_item_values: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.BROGS_GRUE_EGG: + if self._read_game_state_value_for(2577) == 1: + to_filter_inventory_items.add(item) + elif 71 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(2641) == 1: + to_filter_inventory_items.add(item) + + return received_inventory_items - to_filter_inventory_items + + def _filter_received_griff_inventory_items( + self, received_inventory_items: Set[ZorkGrandInquisitorItems] + ) -> Set[ZorkGrandInquisitorItems]: + to_filter_inventory_items: Set[ZorkGrandInquisitorItems] = set() + + inventory_item_values: Set[int] = set() + + i: int + for i in range(151, 160): + inventory_item_values.add(self._read_game_state_value_for(i)) + + cursor_item_value: int = self._read_game_state_value_for(9) + inspector_item_value: int = self._read_game_state_value_for(4512) + + inventory_item_values.add(cursor_item_value) + inventory_item_values.add(inspector_item_value) + + item: ZorkGrandInquisitorItems + for item in received_inventory_items: + if item == ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT: + if self._read_game_state_value_for(1301) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(1304) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(16562) == 1: + to_filter_inventory_items.add(item) + if item == ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN: + if self._read_game_state_value_for(1374) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(1381) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(16562) == 1: + to_filter_inventory_items.add(item) + + return received_inventory_items - to_filter_inventory_items + + def _filter_received_lucy_inventory_items( + self, received_inventory_items: Set[ZorkGrandInquisitorItems] + ) -> Set[ZorkGrandInquisitorItems]: + to_filter_inventory_items: Set[ZorkGrandInquisitorItems] = set() + + inventory_item_values: Set[int] = set() + + i: int + for i in range(151, 157): + inventory_item_values.add(self._read_game_state_value_for(i)) + + cursor_item_value: int = self._read_game_state_value_for(9) + inspector_item_value: int = self._read_game_state_value_for(2198) + + inventory_item_values.add(cursor_item_value) + inventory_item_values.add(inspector_item_value) + + item: ZorkGrandInquisitorItems + for item in received_inventory_items: + if item == ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1: + if 120 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15433) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15435) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15437) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15439) == 1: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15472) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2: + if 121 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15433) == 2: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15435) == 2: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15437) == 2: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15439) == 2: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15472) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3: + if 122 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15433) == 3: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15435) == 3: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15437) == 3: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15439) == 3: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15472) == 1: + to_filter_inventory_items.add(item) + elif item == ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4: + if 123 in inventory_item_values: + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15433) in (4, 5): + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15435) in (4, 5): + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15437) in (4, 5): + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15439) in (4, 5): + to_filter_inventory_items.add(item) + elif self._read_game_state_value_for(15472) == 1: + to_filter_inventory_items.add(item) + + return received_inventory_items - to_filter_inventory_items + + def _read_game_state_value_for(self, key: int) -> Optional[int]: + try: + return self.game_state_manager.read_game_state_value_for(key) + except Exception as e: + self.log_debug(f"Exception: {e} while trying to read game state key '{key}'") + raise e + + def _write_game_state_value_for(self, key: int, value: int) -> Optional[bool]: + try: + return self.game_state_manager.write_game_state_value_for(key, value) + except Exception as e: + self.log_debug(f"Exception: {e} while trying to write '{key} = {value}' to game state") + raise e + + def _read_game_flags_value_for(self, key: int) -> Optional[int]: + try: + return self.game_state_manager.read_game_flags_value_for(key) + except Exception as e: + self.log_debug(f"Exception: {e} while trying to read game flags key '{key}'") + raise e + + def _write_game_flags_value_for(self, key: int, value: int) -> Optional[bool]: + try: + return self.game_state_manager.write_game_flags_value_for(key, value) + except Exception as e: + self.log_debug(f"Exception: {e} while trying to write '{key} = {value}' to game flags") + raise e + + def _player_has(self, item: ZorkGrandInquisitorItems) -> bool: + return item in self.received_items + + def _player_doesnt_have(self, item: ZorkGrandInquisitorItems) -> bool: + return item not in self.received_items + + def _player_is_at(self, game_location: str) -> bool: + return self.game_state_manager.game_location == game_location + + def _player_is_afgncaap(self) -> bool: + return self._read_game_state_value_for(1596) == 1 + + def _player_is_totem(self) -> bool: + return self._player_is_brog() or self._player_is_griff() or self._player_is_lucy() + + def _player_is_brog(self) -> bool: + return self._read_game_state_value_for(1520) == 1 + + def _player_is_griff(self) -> bool: + return self._read_game_state_value_for(1296) == 1 + + def _player_is_lucy(self) -> bool: + return self._read_game_state_value_for(1524) == 1 diff --git a/worlds/zork_grand_inquisitor/game_state_manager.py b/worlds/zork_grand_inquisitor/game_state_manager.py new file mode 100644 index 0000000000..25b35969bf --- /dev/null +++ b/worlds/zork_grand_inquisitor/game_state_manager.py @@ -0,0 +1,370 @@ +from typing import Optional, Tuple + +from pymem import Pymem +from pymem.process import close_handle + + +class GameStateManager: + process_name = "scummvm.exe" + + process: Optional[Pymem] + is_process_running: bool + + script_manager_struct_address: int + render_manager_struct_address: int + + game_location: Optional[str] + game_location_offset: Optional[int] + + def __init__(self) -> None: + self.process = None + self.is_process_running = False + + self.script_manager_struct_address = 0x0 + self.render_manager_struct_address = 0x0 + + self.game_location = None + self.game_location_offset = None + + @property + def game_state_storage_pointer_address(self) -> int: + return self.script_manager_struct_address + 0x88 + + @property + def game_state_storage_address(self) -> int: + return self.process.read_longlong(self.game_state_storage_pointer_address) + + @property + def game_state_hashmap_size_address(self) -> int: + return self.script_manager_struct_address + 0x90 + + @property + def game_state_key_count_address(self) -> int: + return self.script_manager_struct_address + 0x94 + + @property + def game_state_deleted_key_count_address(self) -> int: + return self.script_manager_struct_address + 0x98 + + @property + def game_flags_storage_pointer_address(self) -> int: + return self.script_manager_struct_address + 0x120 + + @property + def game_flags_storage_address(self) -> int: + return self.process.read_longlong(self.game_flags_storage_pointer_address) + + @property + def game_flags_hashmap_size_address(self) -> int: + return self.script_manager_struct_address + 0x128 + + @property + def game_flags_key_count_address(self) -> int: + return self.script_manager_struct_address + 0x12C + + @property + def game_flags_deleted_key_count_address(self) -> int: + return self.script_manager_struct_address + 0x130 + + @property + def current_location_address(self) -> int: + return self.script_manager_struct_address + 0x400 + + @property + def current_location_offset_address(self) -> int: + return self.script_manager_struct_address + 0x404 + + @property + def next_location_address(self) -> int: + return self.script_manager_struct_address + 0x408 + + @property + def next_location_offset_address(self) -> int: + return self.script_manager_struct_address + 0x40C + + @property + def panorama_reversed_address(self) -> int: + return self.render_manager_struct_address + 0x1C + + def open_process_handle(self) -> bool: + try: + self.process = Pymem(self.process_name) + self.is_process_running = True + + self.script_manager_struct_address = self._resolve_address(0x5276600, (0xC8, 0x0)) + self.render_manager_struct_address = self._resolve_address(0x5276600, (0xD0, 0x120)) + except Exception: + return False + + return True + + def close_process_handle(self) -> bool: + if close_handle(self.process.process_handle): + self.is_process_running = False + self.process = None + + self.script_manager_struct_address = 0x0 + self.render_manager_struct_address = 0x0 + + return True + + return False + + def is_process_still_running(self) -> bool: + try: + self.process.read_int(self.process.base_address) + except Exception: + self.is_process_running = False + self.process = None + + self.script_manager_struct_address = 0x0 + self.render_manager_struct_address = 0x0 + + return False + + return True + + def read_game_state_value_for(self, key: int) -> Optional[int]: + return self.read_statemap_value_for(key, scope="game_state") + + def read_game_flags_value_for(self, key: int) -> Optional[int]: + return self.read_statemap_value_for(key, scope="game_flags") + + def read_statemap_value_for(self, key: int, scope: str = "game_state") -> Optional[int]: + if self.is_process_running: + offset: int + + address: int + address_value: int + + if scope == "game_state": + offset = self._get_game_state_address_read_offset_for(key) + + address = self.game_state_storage_address + offset + address_value = self.process.read_longlong(address) + elif scope == "game_flags": + offset = self._get_game_flags_address_read_offset_for(key) + + address = self.game_flags_storage_address + offset + address_value = self.process.read_longlong(address) + else: + raise ValueError(f"Invalid scope: {scope}") + + if address_value == 0: + return 0 + + statemap_value: int = self.process.read_int(address_value + 0x0) + statemap_key: int = self.process.read_int(address_value + 0x4) + + assert statemap_key == key + + return statemap_value + + return None + + def write_game_state_value_for(self, key: int, value: int) -> Optional[bool]: + return self.write_statemap_value_for(key, value, scope="game_state") + + def write_game_flags_value_for(self, key: int, value: int) -> Optional[bool]: + return self.write_statemap_value_for(key, value, scope="game_flags") + + def write_statemap_value_for(self, key: int, value: int, scope: str = "game_state") -> Optional[bool]: + if self.is_process_running: + offset: int + is_existing_node: bool + is_reused_dummy_node: bool + + key_count_address: int + deleted_key_count_address: int + + storage_address: int + + if scope == "game_state": + offset, is_existing_node, is_reused_dummy_node = self._get_game_state_address_write_offset_for(key) + + key_count_address = self.game_state_key_count_address + deleted_key_count_address = self.game_state_deleted_key_count_address + + storage_address = self.game_state_storage_address + elif scope == "game_flags": + offset, is_existing_node, is_reused_dummy_node = self._get_game_flags_address_write_offset_for(key) + + key_count_address = self.game_flags_key_count_address + deleted_key_count_address = self.game_flags_deleted_key_count_address + + storage_address = self.game_flags_storage_address + else: + raise ValueError(f"Invalid scope: {scope}") + + statemap_key_count: int = self.process.read_int(key_count_address) + statemap_deleted_key_count: int = self.process.read_int(deleted_key_count_address) + + if value == 0: + if not is_existing_node: + return False + + self.process.write_longlong(storage_address + offset, 1) + + self.process.write_int(key_count_address, statemap_key_count - 1) + self.process.write_int(deleted_key_count_address, statemap_deleted_key_count + 1) + else: + if is_existing_node: + address_value: int = self.process.read_longlong(storage_address + offset) + self.process.write_int(address_value + 0x0, value) + else: + write_address: int = self.process.allocate(0x8) + + self.process.write_int(write_address + 0x0, value) + self.process.write_int(write_address + 0x4, key) + + self.process.write_longlong(storage_address + offset, write_address) + + self.process.write_int(key_count_address, statemap_key_count + 1) + + if is_reused_dummy_node: + self.process.write_int(deleted_key_count_address, statemap_deleted_key_count - 1) + + return True + + return None + + def refresh_game_location(self) -> Optional[bool]: + if self.is_process_running: + game_location_bytes: bytes = self.process.read_bytes(self.current_location_address, 4) + + self.game_location = game_location_bytes.decode("ascii") + self.game_location_offset = self.process.read_int(self.current_location_offset_address) + + return True + + return None + + def set_game_location(self, game_location: str, offset: int) -> Optional[bool]: + if self.is_process_running: + game_location_bytes: bytes = game_location.encode("ascii") + + self.process.write_bytes(self.next_location_address, game_location_bytes, 4) + self.process.write_int(self.next_location_offset_address, offset) + + return True + + return None + + def set_panorama_reversed(self, is_reversed: bool) -> Optional[bool]: + if self.is_process_running: + self.process.write_int(self.panorama_reversed_address, 1 if is_reversed else 0) + + return True + + return None + + def _resolve_address(self, base_offset: int, offsets: Tuple[int, ...]): + address: int = self.process.read_longlong(self.process.base_address + base_offset) + + for offset in offsets[:-1]: + address = self.process.read_longlong(address + offset) + + return address + offsets[-1] + + def _get_game_state_address_read_offset_for(self, key: int): + return self._get_statemap_address_read_offset_for(key, scope="game_state") + + def _get_game_flags_address_read_offset_for(self, key: int): + return self._get_statemap_address_read_offset_for(key, scope="game_flags") + + def _get_statemap_address_read_offset_for(self, key: int, scope: str = "game_state") -> int: + hashmap_size_address: int + storage_address: int + + if scope == "game_state": + hashmap_size_address = self.game_state_hashmap_size_address + storage_address = self.game_state_storage_address + elif scope == "game_flags": + hashmap_size_address = self.game_flags_hashmap_size_address + storage_address = self.game_flags_storage_address + else: + raise ValueError(f"Invalid scope: {scope}") + + statemap_hashmap_size: int = self.process.read_int(hashmap_size_address) + + perturb: int = key + perturb_shift: int = 0x5 + + index: int = key & statemap_hashmap_size + offset: int = index * 0x8 + + while True: + offset_value: int = self.process.read_longlong(storage_address + offset) + + if offset_value == 0: # Null Pointer + break + elif offset_value == 1: # Dummy Node + pass + elif offset_value > 1: # Existing Node + if self.process.read_int(offset_value + 0x4) == key: + break + + index = ((0x5 * index) + perturb + 0x1) & statemap_hashmap_size + offset = index * 0x8 + + perturb >>= perturb_shift + + return offset + + def _get_game_state_address_write_offset_for(self, key: int) -> Tuple[int, bool, bool]: + return self._get_statemap_address_write_offset_for(key, scope="game_state") + + def _get_game_flags_address_write_offset_for(self, key: int) -> Tuple[int, bool, bool]: + return self._get_statemap_address_write_offset_for(key, scope="game_flags") + + def _get_statemap_address_write_offset_for(self, key: int, scope: str = "game_state") -> Tuple[int, bool, bool]: + hashmap_size_address: int + storage_address: int + + if scope == "game_state": + hashmap_size_address = self.game_state_hashmap_size_address + storage_address = self.game_state_storage_address + elif scope == "game_flags": + hashmap_size_address = self.game_flags_hashmap_size_address + storage_address = self.game_flags_storage_address + else: + raise ValueError(f"Invalid scope: {scope}") + + statemap_hashmap_size: int = self.process.read_int(hashmap_size_address) + + perturb: int = key + perturb_shift: int = 0x5 + + index: int = key & statemap_hashmap_size + offset: int = index * 0x8 + + node_found: bool = False + + dummy_node_found: bool = False + dummy_node_offset: Optional[int] = None + + while True: + offset_value: int = self.process.read_longlong(storage_address + offset) + + if offset_value == 0: # Null Pointer + break + elif offset_value == 1: # Dummy Node + if not dummy_node_found: + dummy_node_offset = offset + dummy_node_found = True + elif offset_value > 1: # Existing Node + if self.process.read_int(offset_value + 0x4) == key: + node_found = True + break + + index = ((0x5 * index) + perturb + 0x1) & statemap_hashmap_size + offset = index * 0x8 + + perturb >>= perturb_shift + + if not node_found and dummy_node_found: # We should reuse the dummy node + return dummy_node_offset, False, True + elif not node_found and not dummy_node_found: # We should allocate a new node + return offset, False, False + + return offset, True, False # We should update the existing node diff --git a/worlds/zork_grand_inquisitor/options.py b/worlds/zork_grand_inquisitor/options.py new file mode 100644 index 0000000000..f064151999 --- /dev/null +++ b/worlds/zork_grand_inquisitor/options.py @@ -0,0 +1,61 @@ +from dataclasses import dataclass + +from Options import Choice, DefaultOnToggle, PerGameCommonOptions, Toggle + + +class Goal(Choice): + """ + Determines the victory condition + + Three Artifacts: Retrieve the three artifacts of magic and place them in the walking castle + """ + display_name: str = "Goal" + + default: int = 0 + option_three_artifacts: int = 0 + + +class QuickPortFoozle(DefaultOnToggle): + """If true, the items needed to go down the well will be found in early locations for a smoother early game""" + + display_name: str = "Quick Port Foozle" + + +class StartWithHotspotItems(DefaultOnToggle): + """ + If true, the player will be given all the hotspot items at the start of the game, effectively removing the need + to enable the important hotspots in the game before interacting with them. Recommended for beginners + + Note: The spots these hotspot items would have occupied in the item pool will instead be filled with junk items. + Expect a higher volume of filler items if you enable this option + """ + + display_name: str = "Start with Hotspot Items" + + +class Deathsanity(Toggle): + """If true, adds 16 player death locations to the world""" + + display_name: str = "Deathsanity" + + +class GrantMissableLocationChecks(Toggle): + """ + If true, performing an irreversible action will grant the locations checks that would have become unobtainable as a + result of that action when you meet the item requirements + + Otherwise, the player is expected to potentially have to use the save system to reach those location checks. If you + don't like the idea of rarely having to reload an earlier save to get a location check, make sure this option is + enabled + """ + + display_name: str = "Grant Missable Checks" + + +@dataclass +class ZorkGrandInquisitorOptions(PerGameCommonOptions): + goal: Goal + quick_port_foozle: QuickPortFoozle + start_with_hotspot_items: StartWithHotspotItems + deathsanity: Deathsanity + grant_missable_location_checks: GrantMissableLocationChecks diff --git a/worlds/zork_grand_inquisitor/requirements.txt b/worlds/zork_grand_inquisitor/requirements.txt new file mode 100644 index 0000000000..fe25267f67 --- /dev/null +++ b/worlds/zork_grand_inquisitor/requirements.txt @@ -0,0 +1 @@ +Pymem>=1.13.0 \ No newline at end of file diff --git a/worlds/zork_grand_inquisitor/test/__init__.py b/worlds/zork_grand_inquisitor/test/__init__.py new file mode 100644 index 0000000000..c8ceda43a7 --- /dev/null +++ b/worlds/zork_grand_inquisitor/test/__init__.py @@ -0,0 +1,5 @@ +from test.bases import WorldTestBase + + +class ZorkGrandInquisitorTestBase(WorldTestBase): + game = "Zork Grand Inquisitor" diff --git a/worlds/zork_grand_inquisitor/test/test_access.py b/worlds/zork_grand_inquisitor/test/test_access.py new file mode 100644 index 0000000000..63a5f8c9ab --- /dev/null +++ b/worlds/zork_grand_inquisitor/test/test_access.py @@ -0,0 +1,2927 @@ +from typing import List + +from . import ZorkGrandInquisitorTestBase + +from ..enums import ( + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorLocations, + ZorkGrandInquisitorRegions, +) + + +class AccessTestRegions(ZorkGrandInquisitorTestBase): + options = { + "start_with_hotspot_items": "false", + } + + def test_access_crossroads_to_dm_lair_sword(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + def test_access_crossroads_to_dm_lair_teleporter(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + def test_access_crossroads_to_gue_tech(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_REZROV.value, + ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) + + def test_access_crossroads_to_gue_tech_outside(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + def test_access_crossroads_to_hades_shore(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_crossroads_to_port_foozle(self) -> None: + self._go_to_crossroads() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE.value)) + + def test_access_crossroads_to_spell_lab_bridge(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + def test_access_crossroads_to_subway_crossroads(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SUBWAY_TOKEN.value, + ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) + + def test_access_crossroads_to_subway_monastery(self) -> None: + self._go_to_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_dm_lair_to_crossroads(self) -> None: + self._go_to_dm_lair() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + def test_access_dm_lair_to_dm_lair_interior(self) -> None: + self._go_to_dm_lair() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY.value, + ZorkGrandInquisitorItems.MEAD_LIGHT.value, + ZorkGrandInquisitorItems.ZIMDOR_SCROLL.value, + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR.value)) + + def test_access_dm_lair_to_gue_tech_outside(self) -> None: + self._go_to_dm_lair() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) + + def test_access_dm_lair_to_hades_shore(self) -> None: + self._go_to_dm_lair() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_dm_lair_to_spell_lab_bridge(self) -> None: + self._go_to_dm_lair() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + def test_access_dm_lair_to_subway_monastery(self) -> None: + self._go_to_dm_lair() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_dm_lair_interior_to_dm_lair(self) -> None: + self._go_to_dm_lair_interior() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + def test_access_dm_lair_interior_to_walking_castle(self) -> None: + self._go_to_dm_lair_interior() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.WALKING_CASTLE.value)) + + self._obtain_obidil() + + self.collect_by_name(ZorkGrandInquisitorItems.HOTSPOT_BLINDS.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.WALKING_CASTLE.value)) + + def test_access_dm_lair_interior_to_white_house(self) -> None: + self._go_to_dm_lair_interior() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.WHITE_HOUSE.value)) + + self._obtain_yastard() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR.value, + ZorkGrandInquisitorItems.SPELL_NARWILE.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.WHITE_HOUSE.value)) + + def test_access_dragon_archipelago_to_dragon_archipelago_dragon(self) -> None: + self._go_to_dragon_archipelago() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEM_GRIFF.value, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON.value)) + + def test_access_dragon_archipelago_to_hades_beyond_gates(self) -> None: + self._go_to_dragon_archipelago() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_BEYOND_GATES.value)) + + def test_access_dragon_archipelago_dragon_to_dragon_archipelago(self) -> None: + self._go_to_dragon_archipelago_dragon() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO.value)) + + def test_access_dragon_archipelago_dragon_to_endgame(self) -> None: + self._go_to_dragon_archipelago_dragon() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP.value, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT.value, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN.value, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS.value, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH.value, + ) + ) + + self._go_to_port_foozle_past_tavern() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4.value, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY.value, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS.value, + ) + ) + + self._go_to_white_house() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEM_BROG.value, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH.value, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG.value, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT.value, + ZorkGrandInquisitorItems.BROGS_PLANK.value, + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) + + def test_access_gue_tech_to_crossroads(self) -> None: + self._go_to_gue_tech() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + def test_access_gue_tech_to_gue_tech_hallway(self) -> None: + self._go_to_gue_tech() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_IGRAM.value, + ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY.value)) + + def test_access_gue_tech_to_gue_tech_outside(self) -> None: + self._go_to_gue_tech() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + self.collect_by_name(ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + def test_access_gue_tech_hallway_to_gue_tech(self) -> None: + self._go_to_gue_tech_hallway() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) + + def test_access_gue_tech_hallway_to_spell_lab_bridge(self) -> None: + self._go_to_gue_tech_hallway() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.STUDENT_ID.value, + ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + def test_access_gue_tech_outside_to_crossroads(self) -> None: + self._go_to_gue_tech_outside() + + # Direct connection requires the map but indirect connection is free + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + def test_access_gue_tech_outside_to_dm_lair(self) -> None: + self._go_to_gue_tech_outside() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + def test_access_gue_tech_outside_to_gue_tech(self) -> None: + self._go_to_gue_tech_outside() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) + + def test_access_gue_tech_outside_to_hades_shore(self) -> None: + self._go_to_gue_tech_outside() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_gue_tech_outside_to_spell_lab_bridge(self) -> None: + self._go_to_gue_tech_outside() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + def test_access_gue_tech_outside_to_subway_monastery(self) -> None: + self._go_to_gue_tech_outside() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_hades_to_hades_beyond_gates(self) -> None: + self._go_to_hades() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_BEYOND_GATES.value)) + + self._obtain_snavig() + + self.collect_by_name(ZorkGrandInquisitorItems.TOTEM_BROG.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_BEYOND_GATES.value)) + + def test_access_hades_to_hades_shore(self) -> None: + self._go_to_hades() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_hades_beyond_gates_to_dragon_archipelago(self) -> None: + self._go_to_hades_beyond_gates() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO.value)) + + self._obtain_yastard() + + self.collect_by_name(ZorkGrandInquisitorItems.SPELL_NARWILE.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO.value)) + + def test_access_hades_beyond_gates_to_hades(self) -> None: + self._go_to_hades_beyond_gates() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES.value)) + + def test_access_hades_shore_to_crossroads(self) -> None: + self._go_to_hades_shore() + + # Direct connection requires the map but indirect connection is free + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + def test_access_hades_shore_to_dm_lair(self) -> None: + self._go_to_hades_shore() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + def test_access_hades_shore_to_gue_tech_outside(self) -> None: + self._go_to_hades_shore() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + def test_access_hades_shore_to_hades(self) -> None: + self._go_to_hades_shore() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER.value, + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS.value, + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES.value)) + + def test_access_hades_shore_to_spell_lab_bridge(self) -> None: + self._go_to_hades_shore() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + def test_access_hades_shore_to_subway_crossroads(self) -> None: + self._go_to_hades_shore() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) + + def test_access_hades_shore_to_subway_flood_control_dam(self) -> None: + self._go_to_hades_shore() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value)) + + self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value)) + + def test_access_hades_shore_to_subway_monastery(self) -> None: + self._go_to_hades_shore() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_monastery_to_hades_shore(self) -> None: + self._go_to_monastery() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL.value, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS.value, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_monastery_to_monastery_exhibit(self) -> None: + self._go_to_monastery() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION.value, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS.value, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT.value)) + + def test_access_monastery_to_subway_monastery(self) -> None: + self._go_to_monastery() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_monastery_exhibit_to_monastery(self) -> None: + self._go_to_monastery_exhibit() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY.value)) + + def test_access_monastery_exhibit_to_port_foozle_past(self) -> None: + self._go_to_monastery_exhibit() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST.value)) + + self._obtain_yastard() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER.value, + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT.value, + ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER.value, + ZorkGrandInquisitorItems.SPELL_NARWILE.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST.value)) + + def test_access_port_foozle_to_crossroads(self) -> None: + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value, + ZorkGrandInquisitorItems.LANTERN.value, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value, + ZorkGrandInquisitorItems.ROPE.value, + ZorkGrandInquisitorItems.HOTSPOT_WELL.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + def test_access_port_foozle_to_port_foozle_jacks_shop(self) -> None: + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value, + ZorkGrandInquisitorItems.LANTERN.value, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP.value)) + + def test_access_port_foozle_jacks_shop_to_port_foozle(self) -> None: + self._go_to_port_foozle_jacks_shop() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE.value)) + + def test_access_port_foozle_past_to_monastery_exhibit(self) -> None: + self._go_to_port_foozle_past() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT.value)) + + def test_access_port_foozle_past_to_port_foozle_past_tavern(self) -> None: + self._go_to_port_foozle_past() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEM_LUCY.value, + ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN.value)) + + def test_access_port_foozle_past_tavern_to_endgame(self) -> None: + self._go_to_port_foozle_past_tavern() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4.value, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY.value, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS.value, + ) + ) + + self._go_to_dragon_archipelago_dragon() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP.value, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT.value, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN.value, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS.value, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH.value, + ) + ) + + self._go_to_white_house() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEM_BROG.value, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH.value, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG.value, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT.value, + ZorkGrandInquisitorItems.BROGS_PLANK.value, + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) + + def test_access_port_foozle_past_tavern_to_port_foozle_past(self) -> None: + self._go_to_port_foozle_past_tavern() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST.value)) + + def test_access_spell_lab_to_spell_lab_bridge(self) -> None: + self._go_to_spell_lab() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) + + def test_access_spell_lab_bridge_to_crossroads(self) -> None: + self._go_to_spell_lab_bridge() + + # Direct connection requires the map but indirect connection is free + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + def test_access_spell_lab_bridge_to_dm_lair(self) -> None: + self._go_to_spell_lab_bridge() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) + + def test_access_spell_lab_bridge_to_gue_tech_outside(self) -> None: + self._go_to_spell_lab_bridge() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) + + def test_access_spell_lab_bridge_to_gue_tech_hallway(self) -> None: + self._go_to_spell_lab_bridge() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY.value)) + + def test_access_spell_lab_bridge_to_hades_shore(self) -> None: + self._go_to_spell_lab_bridge() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_spell_lab_bridge_to_spell_lab(self) -> None: + self._go_to_spell_lab_bridge() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB.value)) + + self._go_to_subway_flood_control_dam() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_REZROV.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value, + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value, + ZorkGrandInquisitorItems.SPELL_GOLGATEM.value, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB.value)) + + def test_access_spell_lab_bridge_to_subway_monastery(self) -> None: + self._go_to_spell_lab_bridge() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_subway_crossroads_to_crossroads(self) -> None: + self._go_to_subway_crossroads() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) + + def test_access_subway_crossroads_to_hades_shore(self) -> None: + self._go_to_subway_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_KENDALL.value, + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_subway_crossroads_to_subway_flood_control_dam(self) -> None: + self._go_to_subway_crossroads() + + self.assertFalse( + self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value) + ) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_KENDALL.value, + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value, + ) + ) + + self.assertTrue( + self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value) + ) + + def test_access_subway_crossroads_to_subway_monastery(self) -> None: + self._go_to_subway_crossroads() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_KENDALL.value, + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_subway_flood_control_dam_to_hades_shore(self) -> None: + self._go_to_subway_flood_control_dam() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_subway_flood_control_dam_to_subway_crossroads(self) -> None: + self._go_to_subway_flood_control_dam() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) + + def test_access_subway_flood_control_dam_to_subway_monastery(self) -> None: + self._go_to_subway_flood_control_dam() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) + + def test_access_subway_monastery_to_hades_shore(self) -> None: + self._go_to_subway_monastery() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES.value) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) + + def test_access_subway_monastery_to_monastery(self) -> None: + self._go_to_subway_monastery() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.SPELL_GLORF.value, + ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY.value)) + + def test_access_subway_monastery_to_subway_crossroads(self) -> None: + self._go_to_subway_monastery() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) + + def test_access_subway_monastery_to_subway_flood_control_dam(self) -> None: + self._go_to_subway_monastery() + + self.assertFalse( + self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value) + ) + + self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value) + + self.assertTrue( + self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value) + ) + + def test_access_walking_castle_to_dm_lair_interior(self) -> None: + self._go_to_walking_castle() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR.value)) + + def test_access_white_house_to_dm_lair_interior(self) -> None: + self._go_to_white_house() + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR.value)) + + def test_access_white_house_to_endgame(self) -> None: + self._go_to_white_house() + + self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEM_BROG.value, + ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH.value, + ZorkGrandInquisitorItems.BROGS_GRUE_EGG.value, + ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT.value, + ZorkGrandInquisitorItems.BROGS_PLANK.value, + ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE.value, + ) + ) + + self._go_to_dragon_archipelago_dragon() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP.value, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT.value, + ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN.value, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS.value, + ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH.value, + ) + ) + + self._go_to_port_foozle_past_tavern() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3.value, + ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4.value, + ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY.value, + ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS.value, + ) + ) + + self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) + + def _go_to_crossroads(self) -> None: + self.collect_by_name( + ( + ZorkGrandInquisitorItems.LANTERN.value, + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value, + ZorkGrandInquisitorItems.ROPE.value, + ZorkGrandInquisitorItems.HOTSPOT_WELL.value, + ) + ) + + def _go_to_dm_lair(self) -> None: + self._go_to_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE.value, + ) + ) + + def _go_to_dm_lair_interior(self) -> None: + self._go_to_dm_lair() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY.value, + ZorkGrandInquisitorItems.MEAD_LIGHT.value, + ZorkGrandInquisitorItems.ZIMDOR_SCROLL.value, + ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH.value, + ) + ) + + def _go_to_dragon_archipelago(self) -> None: + self._go_to_hades_beyond_gates() + self._obtain_yastard() + + self.collect_by_name(ZorkGrandInquisitorItems.SPELL_NARWILE.value) + + def _go_to_dragon_archipelago_dragon(self) -> None: + self._go_to_dragon_archipelago() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEM_GRIFF.value, + ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW.value, + ) + ) + + def _go_to_gue_tech(self) -> None: + self._go_to_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_REZROV.value, + ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR.value, + ) + ) + + def _go_to_gue_tech_hallway(self) -> None: + self._go_to_gue_tech() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_IGRAM.value, + ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS.value, + ) + ) + + def _go_to_gue_tech_outside(self) -> None: + self._go_to_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, + ) + ) + + def _go_to_hades(self) -> None: + self._go_to_hades_shore() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER.value, + ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS.value, + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS.value, + ) + ) + + def _go_to_hades_beyond_gates(self) -> None: + self._go_to_hades() + self._obtain_snavig() + + self.collect_by_name(ZorkGrandInquisitorItems.TOTEM_BROG.value) + + def _go_to_hades_shore(self) -> None: + self._go_to_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, + ) + ) + + def _go_to_monastery(self) -> None: + self._go_to_subway_monastery() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.SPELL_GLORF.value, + ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT.value, + ) + ) + + def _go_to_monastery_exhibit(self) -> None: + self._go_to_monastery() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION.value, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS.value, + ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH.value, + ) + ) + + def _go_to_port_foozle_jacks_shop(self) -> None: + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value, + ZorkGrandInquisitorItems.LANTERN.value, + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value, + ) + ) + + def _go_to_port_foozle_past(self) -> None: + self._go_to_monastery_exhibit() + + self._obtain_yastard() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER.value, + ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT.value, + ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER.value, + ZorkGrandInquisitorItems.SPELL_NARWILE.value, + ) + ) + + def _go_to_port_foozle_past_tavern(self) -> None: + self._go_to_port_foozle_past() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.TOTEM_LUCY.value, + ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR.value, + ) + ) + + def _go_to_spell_lab(self) -> None: + self._go_to_subway_flood_control_dam() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_REZROV.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value, + ) + ) + + self._go_to_spell_lab_bridge() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value, + ZorkGrandInquisitorItems.SPELL_GOLGATEM.value, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value, + ) + ) + + def _go_to_spell_lab_bridge(self) -> None: + self._go_to_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, + ) + ) + + def _go_to_subway_crossroads(self) -> None: + self._go_to_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SUBWAY_TOKEN.value, + ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT.value, + ) + ) + + def _go_to_subway_flood_control_dam(self) -> None: + self._go_to_subway_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_KENDALL.value, + ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value, + ) + ) + + def _go_to_subway_monastery(self) -> None: + self._go_to_crossroads() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.MAP.value, + ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, + ) + ) + + def _go_to_white_house(self) -> None: + self._go_to_dm_lair_interior() + + self._obtain_yastard() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR.value, + ZorkGrandInquisitorItems.SPELL_NARWILE.value, + ) + ) + + def _go_to_walking_castle(self) -> None: + self._go_to_dm_lair_interior() + + self._obtain_obidil() + self.collect_by_name(ZorkGrandInquisitorItems.HOTSPOT_BLINDS.value) + + def _obtain_obidil(self) -> None: + self._go_to_crossroads() + self._go_to_gue_tech() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS.value, + ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT.value, + ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS.value, + ) + ) + + self._go_to_subway_flood_control_dam() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_REZROV.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value, + ) + ) + + self._go_to_spell_lab_bridge() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value, + ZorkGrandInquisitorItems.SPELL_GOLGATEM.value, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER.value, + ) + ) + + def _obtain_snavig(self) -> None: + self._go_to_crossroads() + self._go_to_dm_lair_interior() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS.value, + ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV.value, + ZorkGrandInquisitorItems.HOTSPOT_MIRROR.value, + ) + ) + + self._go_to_subway_flood_control_dam() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SPELL_REZROV.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value, + ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value, + ) + ) + + self._go_to_spell_lab_bridge() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.SWORD.value, + ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value, + ZorkGrandInquisitorItems.SPELL_GOLGATEM.value, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value, + ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER.value, + ) + ) + + def _obtain_yastard(self) -> None: + self._go_to_crossroads() + self._go_to_dm_lair_interior() + + self.collect_by_name( + ( + ZorkGrandInquisitorItems.FLATHEADIA_FUDGE.value, + ZorkGrandInquisitorItems.HUNGUS_LARD.value, + ZorkGrandInquisitorItems.JAR_OF_HOTBUGS.value, + ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB.value, + ZorkGrandInquisitorItems.MOSS_OF_MAREILON.value, + ZorkGrandInquisitorItems.MUG.value, + ) + ) + + +class AccessTestLocations(ZorkGrandInquisitorTestBase): + options = { + "deathsanity": "true", + "start_with_hotspot_items": "false", + } + + def test_access_locations_requiring_brogs_flickering_torch(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH.value,)] + ) + + def test_access_locations_requiring_brogs_grue_egg(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.BROGS_GRUE_EGG.value,)] + ) + + def test_access_locations_requiring_brogs_plank(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.BROGS_PLANK.value,)] + ) + + def test_access_locations_requiring_flatheadia_fudge(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.FLATHEADIA_FUDGE.value,)] + ) + + def test_access_locations_requiring_griffs_air_pump(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP.value,)] + ) + + def test_access_locations_requiring_griffs_dragon_tooth(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH.value,)] + ) + + def test_access_locations_requiring_griffs_inflatable_raft(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT.value,)] + ) + + def test_access_locations_requiring_griffs_inflatable_sea_captain(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN.value,)] + ) + + def test_access_locations_requiring_hammer(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, + ZorkGrandInquisitorLocations.BONK.value, + ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, + ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE.value, + ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED.value, + ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HAMMER.value,)] + ) + + def test_access_locations_requiring_hungus_lard(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HUNGUS_LARD.value,)] + ) + + def test_access_locations_requiring_jar_of_hotbugs(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.JAR_OF_HOTBUGS.value,)] + ) + + def test_access_locations_requiring_lantern(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.LANTERN.value,)] + ) + + def test_access_locations_requiring_large_telegraph_hammer(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER.value,)] + ) + + def test_access_locations_requiring_lucys_playing_cards(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1.value,)] + ) + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2.value,)] + ) + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3.value,)] + ) + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4.value,)] + ) + + def test_access_locations_requiring_map(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.MAP.value,)] + ) + + def test_access_locations_requiring_mead_light(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.MEAD_LIGHT.value, + ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, + ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.MEAD_LIGHT.value,)] + ) + + def test_access_locations_requiring_moss_of_mareilon(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.MOSS_OF_MAREILON.value,)] + ) + + def test_access_locations_requiring_mug(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.MUG.value,)] + ) + + def test_access_locations_requiring_old_scratch_card(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_LOST_SOUL_TO_OLD_SCRATCH.value, + ZorkGrandInquisitorLocations.OLD_SCRATCH_WINNER.value, + ZorkGrandInquisitorEvents.ZORKMID_BILL_ACCESSIBLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.OLD_SCRATCH_CARD.value,)] + ) + + def test_access_locations_requiring_perma_suck_machine(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.SUCKING_ROCKS.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.PERMA_SUCK_MACHINE.value,)] + ) + + def test_access_locations_requiring_plastic_six_pack_holder(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE.value, + ZorkGrandInquisitorLocations.WHAT_ARE_YOU_STUPID.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER.value,)] + ) + + def test_access_locations_requiring_pouch_of_zorkmids(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, + ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, + ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, + ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE.value, + ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, + ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, + ZorkGrandInquisitorLocations.SOUVENIR.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, + ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS.value,)] + ) + + def test_access_locations_requiring_prozork_tablet(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.PROZORKED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.PROZORK_TABLET.value,)] + ) + + def test_access_locations_requiring_quelbee_honeycomb(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB.value,)] + ) + + def test_access_locations_requiring_rope(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.ALARM_SYSTEM_IS_DOWN.value, + ZorkGrandInquisitorLocations.ARTIFACTS_EXPLAINED.value, + ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, + ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, + ZorkGrandInquisitorLocations.A_SMALLWAY.value, + ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, + ZorkGrandInquisitorLocations.BEBURTT_DEMYSTIFIED.value, + ZorkGrandInquisitorLocations.BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES.value, + ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, + ZorkGrandInquisitorLocations.BONK.value, + ZorkGrandInquisitorLocations.BRAVE_SOULS_WANTED.value, + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorLocations.CASTLE_WATCHING_A_FIELD_GUIDE.value, + ZorkGrandInquisitorLocations.CAVES_NOTES.value, + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, + ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, + ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.DEATH_CLIMBED_OUT_OF_THE_WELL.value, + ZorkGrandInquisitorLocations.DEATH_EATEN_BY_A_GRUE.value, + ZorkGrandInquisitorLocations.DEATH_JUMPED_IN_BOTTOMLESS_PIT.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD.value, + ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, + ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, + ZorkGrandInquisitorLocations.DEATH_ZORK_ROCKS_EXPLODED.value, + ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER.value, + ZorkGrandInquisitorLocations.DESPERATELY_SEEKING_TUTOR.value, + ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, + ZorkGrandInquisitorLocations.DOOOOOOWN.value, + ZorkGrandInquisitorLocations.DOWN.value, + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, + ZorkGrandInquisitorLocations.EGGPLANTS.value, + ZorkGrandInquisitorLocations.EMERGENCY_MAGICATRONIC_MESSAGE.value, + ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP.value, + ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA.value, + ZorkGrandInquisitorLocations.FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE.value, + ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, + ZorkGrandInquisitorLocations.FROBUARY_3_UNDERGROUNDHOG_DAY.value, + ZorkGrandInquisitorLocations.GETTING_SOME_CHANGE.value, + ZorkGrandInquisitorLocations.GUE_TECH_DEANS_LIST.value, + ZorkGrandInquisitorLocations.GUE_TECH_ENTRANCE_EXAM.value, + ZorkGrandInquisitorLocations.GUE_TECH_HEALTH_MEMO.value, + ZorkGrandInquisitorLocations.GUE_TECH_MAGEMEISTERS.value, + ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY.value, + ZorkGrandInquisitorLocations.HELLO_THIS_IS_SHONA_FROM_GURTH_PUBLISHING.value, + ZorkGrandInquisitorLocations.HEY_FREE_DIRT.value, + ZorkGrandInquisitorLocations.HI_MY_NAME_IS_DOUG.value, + ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING.value, + ZorkGrandInquisitorLocations.HOLD_ON_FOR_AN_IMPORTANT_MESSAGE.value, + ZorkGrandInquisitorLocations.HOW_TO_HYPNOTIZE_YOURSELF.value, + ZorkGrandInquisitorLocations.HOW_TO_WIN_AT_DOUBLE_FANUCCI.value, + ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY.value, + ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE.value, + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE.value, + ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE.value, + ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST.value, + ZorkGrandInquisitorLocations.INVISIBLE_FLOWERS.value, + ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE.value, + ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, + ZorkGrandInquisitorLocations.LIT_SUNFLOWERS.value, + ZorkGrandInquisitorLocations.MAGIC_FOREVER.value, + ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, + ZorkGrandInquisitorLocations.MAKE_LOVE_NOT_WAR.value, + ZorkGrandInquisitorLocations.MIKES_PANTS.value, + ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED.value, + ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, + ZorkGrandInquisitorLocations.NATURAL_AND_SUPERNATURAL_CREATURES_OF_QUENDOR.value, + ZorkGrandInquisitorLocations.NO_BONDAGE.value, + ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, + ZorkGrandInquisitorLocations.NOTHIN_LIKE_A_GOOD_STOGIE.value, + ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, + ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.PERMASEAL.value, + ZorkGrandInquisitorLocations.PLEASE_DONT_THROCK_THE_GRASS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.PROZORKED.value, + ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, + ZorkGrandInquisitorLocations.RESTOCKED_ON_GRUESDAY.value, + ZorkGrandInquisitorLocations.RIGHT_HELLO_YES_UH_THIS_IS_SNEFFLE.value, + ZorkGrandInquisitorLocations.RIGHT_UH_SORRY_ITS_ME_AGAIN_SNEFFLE.value, + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, + ZorkGrandInquisitorLocations.SOUVENIR.value, + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.SUCKING_ROCKS.value, + ZorkGrandInquisitorLocations.TAMING_YOUR_SNAPDRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.THATS_A_ROPE.value, + ZorkGrandInquisitorLocations.THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS.value, + ZorkGrandInquisitorLocations.THATS_STILL_A_ROPE.value, + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, + ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, + ZorkGrandInquisitorLocations.TIME_TRAVEL_FOR_DUMMIES.value, + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, + ZorkGrandInquisitorLocations.UMBRELLA_FLOWERS.value, + ZorkGrandInquisitorLocations.UP.value, + ZorkGrandInquisitorLocations.USELESS_BUT_FUN.value, + ZorkGrandInquisitorLocations.UUUUUP.value, + ZorkGrandInquisitorLocations.VOYAGE_OF_CAPTAIN_ZAHAB.value, + ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE.value, + ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF.value, + ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER.value, + ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS.value, + ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY.value, + ZorkGrandInquisitorEvents.CHARON_CALLED.value, + ZorkGrandInquisitorEvents.DAM_DESTROYED.value, + ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, + ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR.value, + ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ZorkGrandInquisitorEvents.ROPE_GLORFABLE.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.ROPE.value,)] + ) + + def test_access_locations_requiring_scroll_fragment_ans(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS.value,)] + ) + + def test_access_locations_requiring_scroll_fragment_giv(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV.value,)] + ) + + def test_access_locations_requiring_shovel(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.HEY_FREE_DIRT.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SHOVEL.value,)] + ) + + def test_access_locations_requiring_snapdragon(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, + ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SNAPDRAGON.value,)] + ) + + def test_access_locations_requiring_student_id(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.STUDENT_ID.value,)] + ) + + def test_access_locations_requiring_subway_token(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SUBWAY_TOKEN.value,)] + ) + + def test_access_locations_requiring_sword(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, + ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, + ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, + ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING.value, + ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE.value, + ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE.value, + ZorkGrandInquisitorLocations.MAKE_LOVE_NOT_WAR.value, + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.PERMASEAL.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS.value, + ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SWORD.value,)] + ) + + def test_access_locations_requiring_zimdor_scroll(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, + ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.ZIMDOR_SCROLL.value,)] + ) + + def test_access_locations_requiring_zork_rocks(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.ZORK_ROCKS.value,)] + ) + + def test_access_locations_requiring_hotspot_666_mailbox(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_666_MAILBOX.value,)] + ) + + def test_access_locations_requiring_hotspot_alpines_quandry_card_slots(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS.value,)] + ) + + def test_access_locations_requiring_hotspot_blank_scroll_box(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX.value,)] + ) + + def test_access_locations_requiring_hotspot_blinds(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER.value, + ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_BLINDS.value,)] + ) + + def test_access_locations_requiring_hotspot_candy_machine_buttons(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, + ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, + ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS.value,)] + ) + + def test_access_locations_requiring_hotspot_candy_machine_coin_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, + ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, + ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_candy_machine_vacuum_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.SUCKING_ROCKS.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_VACUUM_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_change_machine_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.GETTING_SOME_CHANGE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_CHANGE_MACHINE_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_closet_door(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorLocations.DOOOOOOWN.value, + ZorkGrandInquisitorLocations.DOWN.value, + ZorkGrandInquisitorLocations.UP.value, + ZorkGrandInquisitorLocations.UUUUUP.value, + ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, + ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR.value,)] + ) + + def test_access_locations_requiring_hotspot_closing_the_time_tunnels_hammer_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_closing_the_time_tunnels_lever(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER.value,)] + ) + + def test_access_locations_requiring_hotspot_cooking_pot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT.value,)] + ) + + def test_access_locations_requiring_hotspot_dented_locker(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_DENTED_LOCKER.value,)] + ) + + def test_access_locations_requiring_hotspot_dirt_mound(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.HEY_FREE_DIRT.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_DIRT_MOUND.value,)] + ) + + def test_access_locations_requiring_hotspot_dock_winch(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE.value, + ZorkGrandInquisitorLocations.NO_BONDAGE.value, + ZorkGrandInquisitorLocations.YOU_WANT_A_PIECE_OF_ME_DOCK_BOY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH.value,)] + ) + + def test_access_locations_requiring_hotspot_dragon_claw(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW.value,)] + ) + + def test_access_locations_requiring_hotspot_dragon_nostrils(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS.value,)] + ) + + def test_access_locations_requiring_hotspot_dungeon_masters_lair_entrance(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE.value,)] + ) + + def test_access_locations_requiring_hotspot_flood_control_buttons(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, + ZorkGrandInquisitorEvents.DAM_DESTROYED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value,)] + ) + + def test_access_locations_requiring_hotspot_flood_control_doors(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, + ZorkGrandInquisitorEvents.DAM_DESTROYED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value,)] + ) + + def test_access_locations_requiring_hotspot_frozen_treat_machine_coin_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_frozen_treat_machine_doors(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS.value,)] + ) + + def test_access_locations_requiring_hotspot_glass_case(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_GLASS_CASE.value,)] + ) + + def test_access_locations_requiring_hotspot_grand_inquisitor_doll(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.ARREST_THE_VANDAL.value, + ZorkGrandInquisitorLocations.DEATH_ARRESTED_WITH_JACK.value, + ZorkGrandInquisitorLocations.FIRE_FIRE.value, + ZorkGrandInquisitorLocations.PLANETFALL.value, + ZorkGrandInquisitorLocations.TALK_TO_ME_GRAND_INQUISITOR.value, + ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value,)] + ) + + def test_access_locations_requiring_hotspot_gue_tech_door(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR.value,)] + ) + + def test_access_locations_requiring_hotspot_gue_tech_grass(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_GRASS.value,)] + ) + + def test_access_locations_requiring_hotspot_hades_phone_buttons(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, + ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, + ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY.value, + ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, + ZorkGrandInquisitorEvents.CHARON_CALLED.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS.value,)] + ) + + def test_access_locations_requiring_hotspot_hades_phone_receiver(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, + ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, + ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY.value, + ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, + ZorkGrandInquisitorEvents.CHARON_CALLED.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER.value,)] + ) + + def test_access_locations_requiring_hotspot_harry(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_HARRY.value,)] + ) + + def test_access_locations_requiring_hotspot_harrys_ashtray(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.NOTHIN_LIKE_A_GOOD_STOGIE.value, + ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY.value,)] + ) + + def test_access_locations_requiring_hotspot_harrys_bird_bath(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, + ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH.value,)] + ) + + def test_access_locations_requiring_hotspot_in_magic_we_trust_door(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR.value,)] + ) + + def test_access_locations_requiring_hotspot_jacks_door(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.MEAD_LIGHT.value, + ZorkGrandInquisitorLocations.NO_AUTOGRAPHS.value, + ZorkGrandInquisitorLocations.THATS_A_ROPE.value, + ZorkGrandInquisitorLocations.WHAT_ARE_YOU_STUPID.value, + ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value,)] + ) + + def test_access_locations_requiring_hotspot_loudspeaker_volume_buttons(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.THATS_THE_SPIRIT.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS.value,)] + ) + + def test_access_locations_requiring_hotspot_mailbox_door(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR.value,)] + ) + + def test_access_locations_requiring_hotspot_mailbox_flag(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DOOOOOOWN.value, + ZorkGrandInquisitorLocations.DOWN.value, + ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, + ZorkGrandInquisitorLocations.UP.value, + ZorkGrandInquisitorLocations.UUUUUP.value, + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG.value,)] + ) + + def test_access_locations_requiring_hotspot_mirror(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, + ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_MIRROR.value,)] + ) + + def test_access_locations_requiring_hotspot_monastery_vent(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, + ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING.value, + ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.PERMASEAL.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT.value,)] + ) + + def test_access_locations_requiring_hotspot_mossy_grate(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_MOSSY_GRATE.value,)] + ) + + def test_access_locations_requiring_hotspot_port_foozle_past_tavern_door(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR.value,)] + ) + + def test_access_locations_requiring_hotspot_purple_words(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.A_SMALLWAY.value, + ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, + ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS.value,)] + ) + + def test_access_locations_requiring_hotspot_quelbee_hive(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE.value,)] + ) + + def test_access_locations_requiring_hotspot_rope_bridge(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, + ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value,)] + ) + + def test_access_locations_requiring_hotspot_skull_cage(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE.value,)] + ) + + def test_access_locations_requiring_hotspot_snapdragon(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BONK.value, + ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY.value, + ZorkGrandInquisitorLocations.PROZORKED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON.value,)] + ) + + def test_access_locations_requiring_hotspot_soda_machine_buttons(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_BUTTONS.value,)] + ) + + def test_access_locations_requiring_hotspot_soda_machine_coin_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_COIN_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_souvenir_coin_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.SOUVENIR.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SOUVENIR_COIN_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_spell_checker(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER.value,)] + ) + + def test_access_locations_requiring_hotspot_spell_lab_chasm(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value,)] + ) + + def test_access_locations_requiring_hotspot_spring_mushroom(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, + ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, + ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED.value, + ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM.value,)] + ) + + def test_access_locations_requiring_hotspot_student_id_machine(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE.value,)] + ) + + def test_access_locations_requiring_hotspot_subway_token_slot(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT.value,)] + ) + + def test_access_locations_requiring_hotspot_tavern_fly(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY.value,)] + ) + + def test_access_locations_requiring_hotspot_totemizer_switch(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH.value,)] + ) + + def test_access_locations_requiring_hotspot_totemizer_wheels(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS.value,)] + ) + + def test_access_locations_requiring_hotspot_well(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.ALARM_SYSTEM_IS_DOWN.value, + ZorkGrandInquisitorLocations.ARTIFACTS_EXPLAINED.value, + ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, + ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, + ZorkGrandInquisitorLocations.A_SMALLWAY.value, + ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, + ZorkGrandInquisitorLocations.BEBURTT_DEMYSTIFIED.value, + ZorkGrandInquisitorLocations.BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES.value, + ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, + ZorkGrandInquisitorLocations.BONK.value, + ZorkGrandInquisitorLocations.BRAVE_SOULS_WANTED.value, + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorLocations.CASTLE_WATCHING_A_FIELD_GUIDE.value, + ZorkGrandInquisitorLocations.CAVES_NOTES.value, + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, + ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, + ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.DEATH_CLIMBED_OUT_OF_THE_WELL.value, + ZorkGrandInquisitorLocations.DEATH_EATEN_BY_A_GRUE.value, + ZorkGrandInquisitorLocations.DEATH_JUMPED_IN_BOTTOMLESS_PIT.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD.value, + ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, + ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, + ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, + ZorkGrandInquisitorLocations.DEATH_ZORK_ROCKS_EXPLODED.value, + ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER.value, + ZorkGrandInquisitorLocations.DESPERATELY_SEEKING_TUTOR.value, + ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, + ZorkGrandInquisitorLocations.DOOOOOOWN.value, + ZorkGrandInquisitorLocations.DOWN.value, + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, + ZorkGrandInquisitorLocations.EGGPLANTS.value, + ZorkGrandInquisitorLocations.EMERGENCY_MAGICATRONIC_MESSAGE.value, + ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP.value, + ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA.value, + ZorkGrandInquisitorLocations.FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE.value, + ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, + ZorkGrandInquisitorLocations.FROBUARY_3_UNDERGROUNDHOG_DAY.value, + ZorkGrandInquisitorLocations.GETTING_SOME_CHANGE.value, + ZorkGrandInquisitorLocations.GUE_TECH_DEANS_LIST.value, + ZorkGrandInquisitorLocations.GUE_TECH_ENTRANCE_EXAM.value, + ZorkGrandInquisitorLocations.GUE_TECH_HEALTH_MEMO.value, + ZorkGrandInquisitorLocations.GUE_TECH_MAGEMEISTERS.value, + ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY.value, + ZorkGrandInquisitorLocations.HELLO_THIS_IS_SHONA_FROM_GURTH_PUBLISHING.value, + ZorkGrandInquisitorLocations.HEY_FREE_DIRT.value, + ZorkGrandInquisitorLocations.HI_MY_NAME_IS_DOUG.value, + ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING.value, + ZorkGrandInquisitorLocations.HOLD_ON_FOR_AN_IMPORTANT_MESSAGE.value, + ZorkGrandInquisitorLocations.HOW_TO_HYPNOTIZE_YOURSELF.value, + ZorkGrandInquisitorLocations.HOW_TO_WIN_AT_DOUBLE_FANUCCI.value, + ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY.value, + ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE.value, + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE.value, + ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE.value, + ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST.value, + ZorkGrandInquisitorLocations.INVISIBLE_FLOWERS.value, + ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE.value, + ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, + ZorkGrandInquisitorLocations.LIT_SUNFLOWERS.value, + ZorkGrandInquisitorLocations.MAGIC_FOREVER.value, + ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, + ZorkGrandInquisitorLocations.MAKE_LOVE_NOT_WAR.value, + ZorkGrandInquisitorLocations.MIKES_PANTS.value, + ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED.value, + ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, + ZorkGrandInquisitorLocations.NATURAL_AND_SUPERNATURAL_CREATURES_OF_QUENDOR.value, + ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, + ZorkGrandInquisitorLocations.NOTHIN_LIKE_A_GOOD_STOGIE.value, + ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, + ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, + ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, + ZorkGrandInquisitorLocations.PERMASEAL.value, + ZorkGrandInquisitorLocations.PLEASE_DONT_THROCK_THE_GRASS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.PROZORKED.value, + ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, + ZorkGrandInquisitorLocations.RESTOCKED_ON_GRUESDAY.value, + ZorkGrandInquisitorLocations.RIGHT_HELLO_YES_UH_THIS_IS_SNEFFLE.value, + ZorkGrandInquisitorLocations.RIGHT_UH_SORRY_ITS_ME_AGAIN_SNEFFLE.value, + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, + ZorkGrandInquisitorLocations.SOUVENIR.value, + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.SUCKING_ROCKS.value, + ZorkGrandInquisitorLocations.TAMING_YOUR_SNAPDRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS.value, + ZorkGrandInquisitorLocations.THATS_STILL_A_ROPE.value, + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, + ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, + ZorkGrandInquisitorLocations.TIME_TRAVEL_FOR_DUMMIES.value, + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, + ZorkGrandInquisitorLocations.UMBRELLA_FLOWERS.value, + ZorkGrandInquisitorLocations.UP.value, + ZorkGrandInquisitorLocations.USELESS_BUT_FUN.value, + ZorkGrandInquisitorLocations.UUUUUP.value, + ZorkGrandInquisitorLocations.VOYAGE_OF_CAPTAIN_ZAHAB.value, + ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE.value, + ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF.value, + ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER.value, + ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS.value, + ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY.value, + ZorkGrandInquisitorEvents.CHARON_CALLED.value, + ZorkGrandInquisitorEvents.DAM_DESTROYED.value, + ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, + ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR.value, + ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, + ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, + ZorkGrandInquisitorEvents.ROPE_GLORFABLE.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, + ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.HOTSPOT_WELL.value,)] + ) + + def test_access_locations_requiring_spell_glorf(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorEvents.ROPE_GLORFABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SPELL_GLORF.value,)] + ) + + def test_access_locations_requiring_spell_golgatem(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER.value, + ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, + ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, + ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, + ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, + ZorkGrandInquisitorLocations.USELESS_BUT_FUN.value, + ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, + ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, + ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SPELL_GOLGATEM.value,)] + ) + + def test_access_locations_requiring_spell_igram(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.A_SMALLWAY.value, + ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, + ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE.value, + ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA.value, + ZorkGrandInquisitorLocations.INVISIBLE_FLOWERS.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SPELL_IGRAM.value,)] + ) + + def test_access_locations_requiring_spell_kendall(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BEBURTT_DEMYSTIFIED.value, + ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SPELL_KENDALL.value,)] + ) + + def test_access_locations_requiring_spell_narwile(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DOOOOOOWN.value, + ZorkGrandInquisitorLocations.DOWN.value, + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, + ZorkGrandInquisitorLocations.UP.value, + ZorkGrandInquisitorLocations.UUUUUP.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SPELL_NARWILE.value,)] + ) + + def test_access_locations_requiring_spell_rezrov(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST.value, + ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, + ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER.value, + ZorkGrandInquisitorEvents.DAM_DESTROYED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SPELL_REZROV.value,)] + ) + + def test_access_locations_requiring_spell_throck(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, + ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS.value, + ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, + ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY.value, + ZorkGrandInquisitorLocations.LIT_SUNFLOWERS.value, + ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SPELL_THROCK.value,)] + ) + + def test_access_locations_requiring_subway_destination_flood_control_dam(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, + ZorkGrandInquisitorLocations.FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE.value, + ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, + ZorkGrandInquisitorLocations.SOUVENIR.value, + ZorkGrandInquisitorLocations.USELESS_BUT_FUN.value, + ZorkGrandInquisitorEvents.DAM_DESTROYED.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value,)] + ) + + def test_access_locations_requiring_subway_destination_hades(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES.value,)] + ) + + def test_access_locations_requiring_subway_destination_monastery(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY.value,)] + ) + + def test_access_locations_requiring_teleporter_destination_dm_lair(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value,)] + ) + + def test_access_locations_requiring_teleporter_destination_gue_tech(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value,)] + ) + + def test_access_locations_requiring_teleporter_destination_hades(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value,)] + ) + + def test_access_locations_requiring_teleporter_destination_monastery(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value,)] + ) + + def test_access_locations_requiring_teleporter_destination_spell_lab(self) -> None: + locations: List[str] = list() + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value,)] + ) + + def test_access_locations_requiring_totemizer_destination_hall_of_inquisition(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, + ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, + ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, + ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION.value,)] + ) + + def test_access_locations_requiring_totemizer_destination_straight_to_hell(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL.value,)] + ) + + def test_access_locations_requiring_totem_brog(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, + ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, + ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, + ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TOTEM_BROG.value,)] + ) + + def test_access_locations_requiring_totem_griff(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, + ZorkGrandInquisitorLocations.DOOOOOOWN.value, + ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, + ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, + ZorkGrandInquisitorLocations.UUUUUP.value, + ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TOTEM_GRIFF.value,)] + ) + + def test_access_locations_requiring_totem_lucy(self) -> None: + locations: List[str] = [ + ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.DOWN.value, + ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, + ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, + ZorkGrandInquisitorLocations.UP.value, + ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, + ZorkGrandInquisitorEvents.VICTORY.value, + ] + + self.assertAccessDependency( + locations, [(ZorkGrandInquisitorItems.TOTEM_LUCY.value,)] + ) diff --git a/worlds/zork_grand_inquisitor/test/test_data_funcs.py b/worlds/zork_grand_inquisitor/test/test_data_funcs.py new file mode 100644 index 0000000000..9d8d5a4ba3 --- /dev/null +++ b/worlds/zork_grand_inquisitor/test/test_data_funcs.py @@ -0,0 +1,132 @@ +import unittest + +from ..data_funcs import location_access_rule_for, entrance_access_rule_for +from ..enums import ZorkGrandInquisitorLocations, ZorkGrandInquisitorRegions + + +class DataFuncsTest(unittest.TestCase): + def test_location_access_rule_for(self) -> None: + # No Requirements + self.assertEqual( + "lambda state: True", + location_access_rule_for(ZorkGrandInquisitorLocations.ALARM_SYSTEM_IS_DOWN, 1), + ) + + # Single Item Requirement + self.assertEqual( + 'lambda state: state.has("Sword", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY, 1), + ) + + self.assertEqual( + 'lambda state: state.has("Spell: NARWILE", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL, 1), + ) + + # Single Event Requirement + self.assertEqual( + 'lambda state: state.has("Event: Knows OBIDIL", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER, 1), + ) + + self.assertEqual( + 'lambda state: state.has("Event: Dunce Locker Openable", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES, 1), + ) + + # Multiple Item Requirements + self.assertEqual( + 'lambda state: state.has("Hotspot: Purple Words", 1) and state.has("Spell: IGRAM", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.A_SMALLWAY, 1), + ) + + self.assertEqual( + 'lambda state: state.has("Hotspot: Mossy Grate", 1) and state.has("Spell: THROCK", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY, 1), + ) + + # Multiple Item Requirements OR + self.assertEqual( + 'lambda state: (state.has("Totem: Griff", 1) or state.has("Totem: Lucy", 1)) and state.has("Hotspot: Mailbox Door", 1) and state.has("Hotspot: Mailbox Flag", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL, 1), + ) + + # Multiple Mixed Requirements + self.assertEqual( + 'lambda state: state.has("Event: Cigar Accessible", 1) and state.has("Hotspot: Grand Inquisitor Doll", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.ARREST_THE_VANDAL, 1), + ) + + self.assertEqual( + 'lambda state: state.has("Sword", 1) and state.has("Event: Rope GLORFable", 1) and state.has("Hotspot: Monastery Vent", 1)', + location_access_rule_for(ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE, 1), + ) + + def test_entrance_access_rule_for(self) -> None: + # No Requirements + self.assertEqual( + "lambda state: True", + entrance_access_rule_for( + ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.PORT_FOOZLE, 1 + ), + ) + + self.assertEqual( + "lambda state: True", + entrance_access_rule_for( + ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.CROSSROADS, 1 + ), + ) + + # Single Requirement + self.assertEqual( + 'lambda state: (state.has("Map", 1))', + entrance_access_rule_for( + ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.CROSSROADS, 1 + ), + ) + + self.assertEqual( + 'lambda state: (state.has("Map", 1))', + entrance_access_rule_for( + ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.CROSSROADS, 1 + ), + ) + + # Multiple Requirements AND + self.assertEqual( + 'lambda state: (state.has("Spell: REZROV", 1) and state.has("Hotspot: In Magic We Trust Door", 1))', + entrance_access_rule_for( + ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.GUE_TECH, 1 + ), + ) + + self.assertEqual( + 'lambda state: (state.has("Event: Door Smoked Cigar", 1) and state.has("Event: Door Drank Mead", 1))', + entrance_access_rule_for( + ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, 1 + ), + ) + + self.assertEqual( + 'lambda state: (state.has("Hotspot: Closet Door", 1) and state.has("Spell: NARWILE", 1) and state.has("Event: Knows YASTARD", 1))', + entrance_access_rule_for( + ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.WHITE_HOUSE, 1 + ), + ) + + # Multiple Requirements AND + OR + self.assertEqual( + 'lambda state: (state.has("Sword", 1) and state.has("Hotspot: Dungeon Master\'s Lair Entrance", 1)) or (state.has("Map", 1) and state.has("Teleporter Destination: Dungeon Master\'s Lair", 1))', + entrance_access_rule_for( + ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.DM_LAIR, 1 + ), + ) + + # Multiple Requirements Regions + self.assertEqual( + 'lambda state: (state.has("Griff\'s Air Pump", 1) and state.has("Griff\'s Inflatable Raft", 1) and state.has("Griff\'s Inflatable Sea Captain", 1) and state.has("Hotspot: Dragon Nostrils", 1) and state.has("Griff\'s Dragon Tooth", 1) and state.can_reach("Port Foozle Past - Tavern", "Region", 1) and state.has("Lucy\'s Playing Card: 1 Pip", 1) and state.has("Lucy\'s Playing Card: 2 Pips", 1) and state.has("Lucy\'s Playing Card: 3 Pips", 1) and state.has("Lucy\'s Playing Card: 4 Pips", 1) and state.has("Hotspot: Tavern Fly", 1) and state.has("Hotspot: Alpine\'s Quandry Card Slots", 1) and state.can_reach("White House", "Region", 1) and state.has("Totem: Brog", 1) and state.has("Brog\'s Flickering Torch", 1) and state.has("Brog\'s Grue Egg", 1) and state.has("Hotspot: Cooking Pot", 1) and state.has("Brog\'s Plank", 1) and state.has("Hotspot: Skull Cage", 1))', + entrance_access_rule_for( + ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, ZorkGrandInquisitorRegions.ENDGAME, 1 + ), + ) diff --git a/worlds/zork_grand_inquisitor/test/test_locations.py b/worlds/zork_grand_inquisitor/test/test_locations.py new file mode 100644 index 0000000000..fa576dd510 --- /dev/null +++ b/worlds/zork_grand_inquisitor/test/test_locations.py @@ -0,0 +1,49 @@ +from typing import Dict, Set + +from . import ZorkGrandInquisitorTestBase + +from ..data_funcs import location_names_to_location, locations_with_tag +from ..enums import ZorkGrandInquisitorLocations, ZorkGrandInquisitorTags + + +class LocationsTestNoDeathsanity(ZorkGrandInquisitorTestBase): + options = { + "deathsanity": "false", + } + + def test_correct_locations_exist(self) -> None: + expected_locations: Set[ZorkGrandInquisitorLocations] = locations_with_tag( + ZorkGrandInquisitorTags.CORE + ) + + self._assert_expected_locations_exist(expected_locations) + + def _assert_expected_locations_exist(self, expected_locations: Set[ZorkGrandInquisitorLocations]) -> None: + location_name_to_location: Dict[str, ZorkGrandInquisitorLocations] = location_names_to_location() + + for location_object in self.multiworld.get_locations(1): + location: ZorkGrandInquisitorLocations = location_name_to_location.get( + location_object.name + ) + + if location is None: + continue + + self.assertIn(location, expected_locations) + + expected_locations.remove(location) + + self.assertEqual(0, len(expected_locations)) + + +class LocationsTestDeathsanity(LocationsTestNoDeathsanity): + options = { + "deathsanity": "true", + } + + def test_correct_locations_exist(self) -> None: + expected_locations: Set[ZorkGrandInquisitorLocations] = ( + locations_with_tag(ZorkGrandInquisitorTags.CORE) | locations_with_tag(ZorkGrandInquisitorTags.DEATHSANITY) + ) + + self._assert_expected_locations_exist(expected_locations) diff --git a/worlds/zork_grand_inquisitor/world.py b/worlds/zork_grand_inquisitor/world.py new file mode 100644 index 0000000000..2dc634e47d --- /dev/null +++ b/worlds/zork_grand_inquisitor/world.py @@ -0,0 +1,206 @@ +from typing import Any, Dict, List, Set, Tuple + +from BaseClasses import Item, ItemClassification, Location, Region, Tutorial + +from worlds.AutoWorld import WebWorld, World + +from .data.item_data import item_data, ZorkGrandInquisitorItemData +from .data.location_data import location_data, ZorkGrandInquisitorLocationData +from .data.region_data import region_data + +from .data_funcs import ( + item_names_to_id, + item_names_to_item, + location_names_to_id, + item_groups, + items_with_tag, + location_groups, + locations_by_region, + location_access_rule_for, + entrance_access_rule_for, +) + +from .enums import ( + ZorkGrandInquisitorEvents, + ZorkGrandInquisitorItems, + ZorkGrandInquisitorLocations, + ZorkGrandInquisitorRegions, + ZorkGrandInquisitorTags, +) + +from .options import ZorkGrandInquisitorOptions + + +class ZorkGrandInquisitorItem(Item): + game = "Zork Grand Inquisitor" + + +class ZorkGrandInquisitorLocation(Location): + game = "Zork Grand Inquisitor" + + +class ZorkGrandInquisitorWebWorld(WebWorld): + theme: str = "stone" + + tutorials: List[Tutorial] = [ + Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Zork Grand Inquisitor randomizer connected to an Archipelago Multiworld", + "English", + "setup_en.md", + "setup/en", + ["Serpent.AI"], + ) + ] + + +class ZorkGrandInquisitorWorld(World): + """ + Zork: Grand Inquisitor is a 1997 point-and-click adventure game for PC. + Magic has been banned from the great Underground Empire of Zork. By edict of the Grand Inquisitor Mir Yannick, the + Empire has been sealed off and the practice of mystic arts declared punishable by "Totemization" (a very bad thing). + The only way to restore magic to the kingdom is to find three hidden artifacts: The Coconut of Quendor, The Cube of + Foundation, and The Skull of Yoruk. + """ + + options_dataclass = ZorkGrandInquisitorOptions + options: ZorkGrandInquisitorOptions + + game = "Zork Grand Inquisitor" + + item_name_to_id = item_names_to_id() + location_name_to_id = location_names_to_id() + + item_name_groups = item_groups() + location_name_groups = location_groups() + + required_client_version: Tuple[int, int, int] = (0, 4, 4) + + web = ZorkGrandInquisitorWebWorld() + + item_name_to_item: Dict[str, ZorkGrandInquisitorItems] = item_names_to_item() + + def create_regions(self) -> None: + deathsanity: bool = bool(self.options.deathsanity) + + region_mapping: Dict[ZorkGrandInquisitorRegions, Region] = dict() + + region_enum_item: ZorkGrandInquisitorRegions + for region_enum_item in region_data.keys(): + region_mapping[region_enum_item] = Region(region_enum_item.value, self.player, self.multiworld) + + region_locations_mapping: Dict[ZorkGrandInquisitorRegions, Set[ZorkGrandInquisitorLocations]] + region_locations_mapping = locations_by_region(include_deathsanity=deathsanity) + + region_enum_item: ZorkGrandInquisitorRegions + region: Region + for region_enum_item, region in region_mapping.items(): + regions_locations: Set[ZorkGrandInquisitorLocations] = region_locations_mapping[region_enum_item] + + # Locations + location_enum_item: ZorkGrandInquisitorLocations + for location_enum_item in regions_locations: + data: ZorkGrandInquisitorLocationData = location_data[location_enum_item] + + location: ZorkGrandInquisitorLocation = ZorkGrandInquisitorLocation( + self.player, + location_enum_item.value, + data.archipelago_id, + region_mapping[data.region], + ) + + location.event = isinstance(location_enum_item, ZorkGrandInquisitorEvents) + + if location.event: + location.place_locked_item( + ZorkGrandInquisitorItem( + data.event_item_name, + ItemClassification.progression, + None, + self.player, + ) + ) + + location_access_rule: str = location_access_rule_for(location_enum_item, self.player) + + if location_access_rule != "lambda state: True": + location.access_rule = eval(location_access_rule) + + region.locations.append(location) + + # Connections + region_exit: ZorkGrandInquisitorRegions + for region_exit in region_data[region_enum_item].exits or tuple(): + entrance_access_rule: str = entrance_access_rule_for(region_enum_item, region_exit, self.player) + + if entrance_access_rule == "lambda state: True": + region.connect(region_mapping[region_exit]) + else: + region.connect(region_mapping[region_exit], rule=eval(entrance_access_rule)) + + self.multiworld.regions.append(region) + + def create_items(self) -> None: + quick_port_foozle: bool = bool(self.options.quick_port_foozle) + start_with_hotspot_items: bool = bool(self.options.start_with_hotspot_items) + + item_pool: List[ZorkGrandInquisitorItem] = list() + + item: ZorkGrandInquisitorItems + data: ZorkGrandInquisitorItemData + for item, data in item_data.items(): + tags: Tuple[ZorkGrandInquisitorTags, ...] = data.tags or tuple() + + if ZorkGrandInquisitorTags.FILLER in tags: + continue + elif ZorkGrandInquisitorTags.HOTSPOT in tags and start_with_hotspot_items: + continue + + item_pool.append(self.create_item(item.value)) + + total_locations: int = len(self.multiworld.get_unfilled_locations(self.player)) + item_pool += [self.create_filler() for _ in range(total_locations - len(item_pool))] + + self.multiworld.itempool += item_pool + + if quick_port_foozle: + self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.ROPE.value] = 1 + self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.LANTERN.value] = 1 + + if not start_with_hotspot_items: + self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.HOTSPOT_WELL.value] = 1 + self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value] = 1 + + self.multiworld.early_items[self.player][ + ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value + ] = 1 + + if start_with_hotspot_items: + item: ZorkGrandInquisitorItems + for item in items_with_tag(ZorkGrandInquisitorTags.HOTSPOT): + self.multiworld.push_precollected(self.create_item(item.value)) + + def create_item(self, name: str) -> ZorkGrandInquisitorItem: + data: ZorkGrandInquisitorItemData = item_data[self.item_name_to_item[name]] + + return ZorkGrandInquisitorItem( + name, + data.classification, + data.archipelago_id, + self.player, + ) + + def generate_basic(self) -> None: + self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player) + + def fill_slot_data(self) -> Dict[str, Any]: + return self.options.as_dict( + "goal", + "quick_port_foozle", + "start_with_hotspot_items", + "deathsanity", + "grant_missable_location_checks", + ) + + def get_filler_item_name(self) -> str: + return self.random.choice(list(self.item_name_groups["Filler"])) From e6198585c8860f07b07b76d157a711555f16a636 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Fri, 15 Mar 2024 12:52:05 -0400 Subject: [PATCH 147/166] TUNIC: Implement support for connection plando (#2864) --- worlds/tunic/__init__.py | 2 +- worlds/tunic/docs/en_TUNIC.md | 20 ++- worlds/tunic/er_data.py | 6 - worlds/tunic/er_rules.py | 13 -- worlds/tunic/er_scripts.py | 218 +++++++++++++++++-------------- worlds/tunic/options.py | 10 +- worlds/tunic/test/test_access.py | 2 +- 7 files changed, 150 insertions(+), 121 deletions(-) diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index b10ccd43af..c4b1bbec8e 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -226,7 +226,7 @@ class TunicWorld(World): "logic_rules": self.options.logic_rules.value, "lanternless": self.options.lanternless.value, "maskless": self.options.maskless.value, - "entrance_rando": self.options.entrance_rando.value, + "entrance_rando": bool(self.options.entrance_rando.value), "Hexagon Quest Prayer": self.ability_unlocks["Pages 24-25 (Prayer)"], "Hexagon Quest Holy Cross": self.ability_unlocks["Pages 42-43 (Holy Cross)"], "Hexagon Quest Icebolt": self.ability_unlocks["Pages 52-53 (Icebolt)"], diff --git a/worlds/tunic/docs/en_TUNIC.md b/worlds/tunic/docs/en_TUNIC.md index 1204f2ef4c..5921d0ed09 100644 --- a/worlds/tunic/docs/en_TUNIC.md +++ b/worlds/tunic/docs/en_TUNIC.md @@ -67,4 +67,22 @@ For the Entrance Randomizer: Bombs, consumables (non-bomb ones), weapons, melee weapons (stick and sword), keys, hexagons, offerings, hero relics, cards, golden treasures, money, pages, and abilities (the three ability pages). There are also a few groups being used for singular items: laurels, orb, dagger, magic rod, holy cross, prayer, icebolt, and progressive sword. ## What location groups are there? -Holy cross (for all holy cross checks), fairies (for the two fairy checks), well (for the coin well checks), and shop. Additionally, for checks that do not fall into the above categories, the name of the region is the name of the location group. \ No newline at end of file +Holy cross (for all holy cross checks), fairies (for the two fairy checks), well (for the coin well checks), and shop. Additionally, for checks that do not fall into the above categories, the name of the region is the name of the location group. + +## Is Connection Plando supported? +Yes. The host needs to enable it in their `host.yaml`, and the player's yaml needs to contain a plando_connections block. +Example: +``` +plando_connections: + - entrance: Stick House Entrance + exit: Stick House Exit + - entrance: Special Shop Exit + exit: Stairs to Top of the Mountain +``` +Notes: +- The Entrance Randomizer option must be enabled for it to work. +- The `direction` field is not supported. Connections are always coupled. +- For a list of entrance names, check `er_data.py` in the TUNIC world folder or generate a game with the Entrance Randomizer option enabled and check the spoiler log. +- There is no limit to the number of Shops hard-coded into place. +- If you have more than one shop in a scene, you may be wrong warped when exiting a shop. +- If you have a shop in every scene, and you have an odd number of shops, it will error out. diff --git a/worlds/tunic/er_data.py b/worlds/tunic/er_data.py index 7678d77fe0..8d8db426f6 100644 --- a/worlds/tunic/er_data.py +++ b/worlds/tunic/er_data.py @@ -682,12 +682,6 @@ tunic_er_regions: Dict[str, RegionInfo] = { "Hero Relic - Library": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats, hint=Hint.region), "Hero Relic - Swamp": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats, hint=Hint.region), "Purgatory": RegionInfo("Purgatory"), - "Shop Entrance 1": RegionInfo("Shop", dead_end=DeadEnd.all_cats), - "Shop Entrance 2": RegionInfo("Shop", dead_end=DeadEnd.all_cats), - "Shop Entrance 3": RegionInfo("Shop", dead_end=DeadEnd.all_cats), - "Shop Entrance 4": RegionInfo("Shop", dead_end=DeadEnd.all_cats), - "Shop Entrance 5": RegionInfo("Shop", dead_end=DeadEnd.all_cats), - "Shop Entrance 6": RegionInfo("Shop", dead_end=DeadEnd.all_cats), "Shop": RegionInfo("Shop", dead_end=DeadEnd.all_cats), "Spirit Arena": RegionInfo("Spirit Arena", dead_end=DeadEnd.all_cats, hint=Hint.region), "Spirit Arena Victory": RegionInfo("Spirit Arena", dead_end=DeadEnd.all_cats) diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index a7d0543c3f..fec6635422 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -619,19 +619,6 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re connecting_region=regions["Far Shore"]) # Misc - regions["Shop Entrance 1"].connect( - connecting_region=regions["Shop"]) - regions["Shop Entrance 2"].connect( - connecting_region=regions["Shop"]) - regions["Shop Entrance 3"].connect( - connecting_region=regions["Shop"]) - regions["Shop Entrance 4"].connect( - connecting_region=regions["Shop"]) - regions["Shop Entrance 5"].connect( - connecting_region=regions["Shop"]) - regions["Shop Entrance 6"].connect( - connecting_region=regions["Shop"]) - regions["Spirit Arena"].connect( connecting_region=regions["Spirit Arena Victory"], rule=lambda state: (state.has(gold_hexagon, player, world.options.hexagon_goal.value) if diff --git a/worlds/tunic/er_scripts.py b/worlds/tunic/er_scripts.py index d2b854f5df..291cd7b331 100644 --- a/worlds/tunic/er_scripts.py +++ b/worlds/tunic/er_scripts.py @@ -5,6 +5,7 @@ from .er_data import Portal, tunic_er_regions, portal_mapping, hallway_helper, h dependent_regions_restricted, dependent_regions_nmg, dependent_regions_ur from .er_rules import set_er_region_rules from worlds.generic import PlandoConnection +from random import Random if TYPE_CHECKING: from . import TunicWorld @@ -185,9 +186,14 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: portal_pairs: Dict[Portal, Portal] = {} dead_ends: List[Portal] = [] two_plus: List[Portal] = [] - plando_connections: List[PlandoConnection] = [] - fixed_shop = False logic_rules = world.options.logic_rules.value + player_name = world.multiworld.get_player_name(world.player) + + shop_scenes: Set[str] = set() + shop_count = 6 + if world.options.fixed_shop.value: + shop_count = 1 + shop_scenes.add("Overworld Redux") if not logic_rules: dependent_regions = dependent_regions_restricted @@ -215,19 +221,17 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: start_region = "Overworld" connected_regions.update(add_dependent_regions(start_region, logic_rules)) + plando_connections = world.multiworld.plando_connections[world.player] + # universal tracker support stuff, don't need to care about region dependency if hasattr(world.multiworld, "re_gen_passthrough"): if "TUNIC" in world.multiworld.re_gen_passthrough: + plando_connections.clear() # universal tracker stuff, won't do anything in normal gen for portal1, portal2 in world.multiworld.re_gen_passthrough["TUNIC"]["Entrance Rando"].items(): portal_name1 = "" portal_name2 = "" - # skip this if 10 fairies laurels location is on, it can be handled normally - if portal1 == "Overworld Redux, Waterfall_" and portal2 == "Waterfall, Overworld Redux_" \ - and world.options.laurels_location == "10_fairies": - continue - for portal in portal_mapping: if portal.scene_destination() == portal1: portal_name1 = portal.name @@ -240,9 +244,78 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: portal_name2 = "Shop Portal" plando_connections.append(PlandoConnection(portal_name1, portal_name2, "both")) + non_dead_end_regions = set() + for region_name, region_info in tunic_er_regions.items(): + if not region_info.dead_end: + non_dead_end_regions.add(region_name) + elif region_info.dead_end == 2 and logic_rules: + non_dead_end_regions.add(region_name) + if plando_connections: - portal_pairs, dependent_regions, dead_ends, two_plus = \ - create_plando_connections(plando_connections, dependent_regions, dead_ends, two_plus) + for connection in plando_connections: + p_entrance = connection.entrance + p_exit = connection.exit + + if p_entrance.startswith("Shop"): + p_entrance = p_exit + p_exit = "Shop Portal" + + portal1 = None + portal2 = None + + # search two_plus for both at once + for portal in two_plus: + if p_entrance == portal.name: + portal1 = portal + if p_exit == portal.name: + portal2 = portal + + # search dead_ends individually since we can't really remove items from two_plus during the loop + if not portal1: + for portal in dead_ends: + if p_entrance == portal.name: + portal1 = portal + break + if not portal1: + raise Exception(f"Could not find entrance named {p_entrance} for " + f"plando connections in {player_name}'s YAML.") + dead_ends.remove(portal1) + else: + two_plus.remove(portal1) + + if not portal2: + for portal in dead_ends: + if p_exit == portal.name: + portal2 = portal + break + if p_exit in ["Shop Portal", "Shop"]: + portal2 = Portal(name="Shop Portal", region=f"Shop", + destination="Previous Region_") + shop_count -= 1 + if shop_count < 0: + shop_count += 2 + for p in portal_mapping: + if p.name == p_entrance: + shop_scenes.add(p.scene()) + break + else: + if not portal2: + raise Exception(f"Could not find entrance named {p_exit} for " + f"plando connections in {player_name}'s YAML.") + dead_ends.remove(portal2) + else: + two_plus.remove(portal2) + + portal_pairs[portal1] = portal2 + + # update dependent regions based on the plando'd connections, to ensure the portals connect well, logically + for origins, destinations in dependent_regions.items(): + if portal1.region in origins: + if portal2.region in non_dead_end_regions: + destinations.append(portal2.region) + if portal2.region in origins: + if portal1.region in non_dead_end_regions: + destinations.append(portal1.region) # if we have plando connections, our connected regions may change somewhat while True: @@ -255,7 +328,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: # need to plando fairy cave, or it could end up laurels locked # fix this later to be random after adding some item logic to dependent regions - if world.options.laurels_location == "10_fairies": + if world.options.laurels_location == "10_fairies" and not hasattr(world.multiworld, "re_gen_passthrough"): portal1 = None portal2 = None for portal in two_plus: @@ -266,41 +339,59 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: if portal.scene_destination() == "Waterfall, Overworld Redux_": portal2 = portal break + if not portal1: + raise Exception(f"Failed to do Laurels Location at 10 Fairies option. " + f"Did {player_name} plando connection the Secret Gathering Place Entrance?") + if not portal2: + raise Exception(f"Failed to do Laurels Location at 10 Fairies option. " + f"Did {player_name} plando connection the Secret Gathering Place Exit?") portal_pairs[portal1] = portal2 two_plus.remove(portal1) dead_ends.remove(portal2) if world.options.fixed_shop and not hasattr(world.multiworld, "re_gen_passthrough"): - fixed_shop = True portal1 = None for portal in two_plus: if portal.scene_destination() == "Overworld Redux, Windmill_": portal1 = portal break - portal2 = Portal(name="Shop Portal", region=f"Shop Entrance 2", destination="Previous Region_") + portal2 = Portal(name="Shop Portal", region="Shop", destination="Previous Region_") + if not portal1: + raise Exception(f"Failed to do Fixed Shop option. " + f"Did {player_name} plando connection the Windmill Shop entrance?") portal_pairs[portal1] = portal2 two_plus.remove(portal1) + random_object: Random = world.random + if world.options.entrance_rando.value != 1: + random_object = Random(world.options.entrance_rando.value) # we want to start by making sure every region is accessible - non_dead_end_regions = set() - for region_name, region_info in tunic_er_regions.items(): - if not region_info.dead_end: - non_dead_end_regions.add(region_name) - elif region_info.dead_end == 2 and logic_rules: - non_dead_end_regions.add(region_name) - - world.random.shuffle(two_plus) + random_object.shuffle(two_plus) check_success = 0 portal1 = None portal2 = None + previous_conn_num = 0 + fail_count = 0 while len(connected_regions) < len(non_dead_end_regions): + # if the connected regions length stays unchanged for too long, it's stuck in a loop + # should, hopefully, only ever occur if someone plandos connections poorly + if hasattr(world.multiworld, "re_gen_passthrough"): + break + if previous_conn_num == len(connected_regions): + fail_count += 1 + if fail_count >= 500: + raise Exception(f"Failed to pair regions. Check plando connections for {player_name} for loops.") + else: + fail_count = 0 + previous_conn_num = len(connected_regions) + # find a portal in an inaccessible region if check_success == 0: for portal in two_plus: if portal.region in connected_regions: # if there's risk of self-locking, start over if gate_before_switch(portal, two_plus): - world.random.shuffle(two_plus) + random_object.shuffle(two_plus) break portal1 = portal two_plus.remove(portal) @@ -313,7 +404,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: if portal.region not in connected_regions: # if there's risk of self-locking, shuffle and try again if gate_before_switch(portal, two_plus): - world.random.shuffle(two_plus) + random_object.shuffle(two_plus) break portal2 = portal two_plus.remove(portal) @@ -325,16 +416,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: connected_regions.update(add_dependent_regions(portal2.region, logic_rules)) portal_pairs[portal1] = portal2 check_success = 0 - world.random.shuffle(two_plus) - - # add 6 shops, connect them to unique scenes - # this is due to a limitation in Tunic -- you wrong warp if there's multiple shops - shop_scenes: Set[str] = set() - shop_count = 6 - - if fixed_shop: - shop_count = 1 - shop_scenes.add("Overworld Redux") + random_object.shuffle(two_plus) # for universal tracker, we want to skip shop gen if hasattr(world.multiworld, "re_gen_passthrough"): @@ -350,13 +432,15 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: two_plus.remove(portal) break if portal1 is None: - raise Exception("Too many shops in the pool, or something else went wrong") - portal2 = Portal(name="Shop Portal", region=f"Shop Entrance {i + 1}", destination="Previous Region_") + raise Exception("Too many shops in the pool, or something else went wrong.") + portal2 = Portal(name="Shop Portal", region="Shop", destination="Previous Region_") portal_pairs[portal1] = portal2 # connect dead ends to random non-dead ends # none of the key events are in dead ends, so we don't need to do gate_before_switch while len(dead_ends) > 0: + if hasattr(world.multiworld, "re_gen_passthrough"): + break portal1 = two_plus.pop() portal2 = dead_ends.pop() portal_pairs[portal1] = portal2 @@ -364,6 +448,8 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: # then randomly connect the remaining portals to each other # every region is accessible, so gate_before_switch is not necessary while len(two_plus) > 1: + if hasattr(world.multiworld, "re_gen_passthrough"): + break portal1 = two_plus.pop() portal2 = two_plus.pop() portal_pairs[portal1] = portal2 @@ -381,7 +467,7 @@ def create_randomized_entrances(portal_pairs: Dict[Portal, Portal], regions: Dic region2 = regions[portal2.region] region1.connect(region2, f"{portal1.name} -> {portal2.name}") # prevent the logic from thinking you can get to any shop-connected region from the shop - if portal2.name != "Shop": + if not portal2.name.startswith("Shop"): region2.connect(region1, f"{portal2.name} -> {portal1.name}") @@ -507,65 +593,3 @@ def gate_before_switch(check_portal: Portal, two_plus: List[Portal]) -> bool: # false means you're good to place the portal return False - - -# this is for making the connections themselves -def create_plando_connections(plando_connections: List[PlandoConnection], - dependent_regions: Dict[Tuple[str, ...], List[str]], dead_ends: List[Portal], - two_plus: List[Portal]) \ - -> Tuple[Dict[Portal, Portal], Dict[Tuple[str, ...], List[str]], List[Portal], List[Portal]]: - - portal_pairs: Dict[Portal, Portal] = {} - shop_num = 1 - for connection in plando_connections: - p_entrance = connection.entrance - p_exit = connection.exit - - portal1 = None - portal2 = None - - # search two_plus for both at once - for portal in two_plus: - if p_entrance == portal.name: - portal1 = portal - if p_exit == portal.name: - portal2 = portal - - # search dead_ends individually since we can't really remove items from two_plus during the loop - if not portal1: - for portal in dead_ends: - if p_entrance == portal.name: - portal1 = portal - break - dead_ends.remove(portal1) - else: - two_plus.remove(portal1) - - if not portal2: - for portal in dead_ends: - if p_exit == portal.name: - portal2 = portal - break - if p_exit == "Shop Portal": - portal2 = Portal(name="Shop Portal", region=f"Shop Entrance {shop_num}", destination="Previous Region_") - shop_num += 1 - else: - dead_ends.remove(portal2) - else: - two_plus.remove(portal2) - - if not portal1: - raise Exception("could not find entrance named " + p_entrance + " for Tunic player's plando") - if not portal2: - raise Exception("could not find entrance named " + p_exit + " for Tunic player's plando") - - portal_pairs[portal1] = portal2 - - # update dependent regions based on the plando'd connections, to make sure the portals connect well, logically - for origins, destinations in dependent_regions.items(): - if portal1.region in origins: - destinations.append(portal2.region) - if portal2.region in origins: - destinations.append(portal1.region) - - return portal_pairs, dependent_regions, dead_ends, two_plus diff --git a/worlds/tunic/options.py b/worlds/tunic/options.py index ee42b1cfc4..779e632326 100644 --- a/worlds/tunic/options.py +++ b/worlds/tunic/options.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from Options import DefaultOnToggle, Toggle, StartInventoryPool, Choice, Range, PerGameCommonOptions +from Options import DefaultOnToggle, Toggle, StartInventoryPool, Choice, Range, TextChoice, PerGameCommonOptions class SwordProgression(DefaultOnToggle): @@ -104,11 +104,17 @@ class ExtraHexagonPercentage(Range): default = 50 -class EntranceRando(Toggle): +class EntranceRando(TextChoice): """Randomize the connections between scenes. + You can choose a custom seed by editing this option. A small, very lost fox on a big adventure.""" internal_name = "entrance_rando" display_name = "Entrance Rando" + alias_false = 0 + option_no = 0 + alias_true = 1 + option_yes = 1 + default = 0 class FixedShop(Toggle): diff --git a/worlds/tunic/test/test_access.py b/worlds/tunic/test/test_access.py index d74858bd27..1c4f06d504 100644 --- a/worlds/tunic/test/test_access.py +++ b/worlds/tunic/test/test_access.py @@ -59,7 +59,7 @@ class TestNormalGoal(TunicTestBase): class TestER(TunicTestBase): - options = {options.EntranceRando.internal_name: options.EntranceRando.option_true, + options = {options.EntranceRando.internal_name: options.EntranceRando.option_yes, options.AbilityShuffling.internal_name: options.AbilityShuffling.option_true, options.HexagonQuest.internal_name: options.HexagonQuest.option_false} From 9efc7bae406821d5b0ebfd2c81a32c3c57e7ea70 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:54:21 +0100 Subject: [PATCH 148/166] The Witness: Add junk hint for Zork: Grand Inquisitor (#2961) --- worlds/witness/hints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/worlds/witness/hints.py b/worlds/witness/hints.py index 0c84dbc94d..6ebf8eeec0 100644 --- a/worlds/witness/hints.py +++ b/worlds/witness/hints.py @@ -72,6 +72,7 @@ joke_hints = [ "Have you tried Wargroove?\nI'm glad that for every abandoned series, enough people are yearning for its return that one of them will know how to code.", "Have you tried The Witness?\nOh. I guess you already have. Thanks for playing!", "Have you tried Zillion?\nMe neither. But it looks fun. So, let's try something new together?", + "Have you tried Zork: Grand Inquisitor?\nThis 1997 game uses Z-Vision technology to simulate 3D environments.\nCome on, I know you wanna find out what \"Z-Vision\" is.", "Quaternions break my brain", "Eclipse has nothing, but you should do it anyway.", From 8a8263fa61a6f996667ac2fc4472384c83cfb59d Mon Sep 17 00:00:00 2001 From: PoryGone <98504756+PoryGone@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:02:25 -0400 Subject: [PATCH 149/166] SMW: Increment Required Client Version (#2962) --- worlds/smw/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/smw/__init__.py b/worlds/smw/__init__.py index 1916108102..875491a8d0 100644 --- a/worlds/smw/__init__.py +++ b/worlds/smw/__init__.py @@ -58,7 +58,7 @@ class SMWWorld(World): options: SMWOptions topology_present = False - required_client_version = (0, 4, 4) + required_client_version = (0, 4, 5) item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = all_locations From 94650a02de62956eee8e7e41f61e8a41506b5842 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Tue, 19 Mar 2024 17:08:29 -0500 Subject: [PATCH 150/166] Core: implement APProcedurePatch and APTokenMixin (#2536) * initial work on procedure patch * more flexibility load default procedure for version 5 patches add args for procedure add default extension for tokens and bsdiff allow specifying additional required extensions for generation * pushing current changes to go fix tloz bug * move tokens into a separate inheritable class * forgot the commit to remove token from ProcedurePatch * further cleaning from bad commit * start on docstrings * further work on docstrings and typing * improve docstrings * fix incorrect docstring * cleanup * clean defaults and docstring * define interface that has only the bare minimum required for `Patch.create_rom_file` * change to dictionary.get * remove unnecessary if statement * update to explicitly check for procedure, restore compatible version and manual override * Update Files.py * remove struct uses * ensure returning bytes, add token type checking * Apply suggestions from code review Co-authored-by: Doug Hoskisson * pep8 --------- Co-authored-by: beauxq Co-authored-by: Doug Hoskisson --- worlds/Files.py | 282 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 247 insertions(+), 35 deletions(-) diff --git a/worlds/Files.py b/worlds/Files.py index b2ecb9afb8..6fee582c87 100644 --- a/worlds/Files.py +++ b/worlds/Files.py @@ -3,10 +3,11 @@ from __future__ import annotations import abc import json import zipfile +from enum import IntEnum import os import threading -from typing import ClassVar, Dict, List, Literal, Tuple, Any, Optional, Union, BinaryIO +from typing import ClassVar, Dict, List, Literal, Tuple, Any, Optional, Union, BinaryIO, overload import bsdiff4 @@ -38,6 +39,32 @@ class AutoPatchRegister(abc.ABCMeta): return None +class AutoPatchExtensionRegister(abc.ABCMeta): + extension_types: ClassVar[Dict[str, AutoPatchExtensionRegister]] = {} + required_extensions: List[str] = [] + + def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoPatchExtensionRegister: + # construct class + new_class = super().__new__(mcs, name, bases, dct) + if "game" in dct: + AutoPatchExtensionRegister.extension_types[dct["game"]] = new_class + return new_class + + @staticmethod + def get_handler(game: str) -> Union[AutoPatchExtensionRegister, List[AutoPatchExtensionRegister]]: + handler = AutoPatchExtensionRegister.extension_types.get(game, APPatchExtension) + if handler.required_extensions: + handlers = [handler] + for required in handler.required_extensions: + ext = AutoPatchExtensionRegister.extension_types.get(required) + if not ext: + raise NotImplementedError(f"No handler for {required}.") + handlers.append(ext) + return handlers + else: + return handler + + container_version: int = 6 @@ -157,27 +184,14 @@ class APAutoPatchInterface(APPatch, abc.ABC, metaclass=AutoPatchRegister): """ create the output file with the file name `target` """ -class APDeltaPatch(APAutoPatchInterface): - """An implementation of `APAutoPatchInterface` that additionally - has delta.bsdiff4 containing a delta patch to get the desired file.""" - +class APProcedurePatch(APAutoPatchInterface): + """ + An APPatch that defines a procedure to produce the desired file. + """ hash: Optional[str] # base checksum of source file - patch_file_ending: str = "" - delta: Optional[bytes] = None source_data: bytes - procedure = None # delete this line when APPP is added - - def __init__(self, *args: Any, patched_path: str = "", **kwargs: Any) -> None: - self.patched_path = patched_path - super(APDeltaPatch, self).__init__(*args, **kwargs) - - def get_manifest(self) -> Dict[str, Any]: - manifest = super(APDeltaPatch, self).get_manifest() - manifest["base_checksum"] = self.hash - manifest["result_file_ending"] = self.result_file_ending - manifest["patch_file_ending"] = self.patch_file_ending - manifest["compatible_version"] = 5 # delete this line when APPP is added - return manifest + patch_file_ending: str = "" + files: Dict[str, bytes] = {} @classmethod def get_source_data(cls) -> bytes: @@ -190,21 +204,219 @@ class APDeltaPatch(APAutoPatchInterface): cls.source_data = cls.get_source_data() return cls.source_data - def write_contents(self, opened_zipfile: zipfile.ZipFile): - super(APDeltaPatch, self).write_contents(opened_zipfile) - # write Delta - opened_zipfile.writestr("delta.bsdiff4", - bsdiff4.diff(self.get_source_data_with_cache(), open(self.patched_path, "rb").read()), - compress_type=zipfile.ZIP_STORED) # bsdiff4 is a format with integrated compression + def __init__(self, *args: Any, **kwargs: Any): + super(APProcedurePatch, self).__init__(*args, **kwargs) - def read_contents(self, opened_zipfile: zipfile.ZipFile): - super(APDeltaPatch, self).read_contents(opened_zipfile) - self.delta = opened_zipfile.read("delta.bsdiff4") + def get_manifest(self) -> Dict[str, Any]: + manifest = super(APProcedurePatch, self).get_manifest() + manifest["base_checksum"] = self.hash + manifest["result_file_ending"] = self.result_file_ending + manifest["patch_file_ending"] = self.patch_file_ending + manifest["procedure"] = self.procedure + if self.procedure == APDeltaPatch.procedure: + manifest["compatible_version"] = 5 + return manifest - def patch(self, target: str): - """Base + Delta -> Patched""" - if not self.delta: + def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None: + super(APProcedurePatch, self).read_contents(opened_zipfile) + with opened_zipfile.open("archipelago.json", "r") as f: + manifest = json.load(f) + if "procedure" not in manifest: + # support patching files made before moving to procedures + self.procedure = [("apply_bsdiff4", ["delta.bsdiff4"])] + else: + self.procedure = manifest["procedure"] + for file in opened_zipfile.namelist(): + if file not in ["archipelago.json"]: + self.files[file] = opened_zipfile.read(file) + + def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None: + super(APProcedurePatch, self).write_contents(opened_zipfile) + for file in self.files: + opened_zipfile.writestr(file, self.files[file], + compress_type=zipfile.ZIP_STORED if file.endswith(".bsdiff4") else None) + + def get_file(self, file: str) -> bytes: + """ Retrieves a file from the patch container.""" + if file not in self.files: self.read() - result = bsdiff4.patch(self.get_source_data_with_cache(), self.delta) - with open(target, "wb") as f: - f.write(result) + return self.files[file] + + def write_file(self, file_name: str, file: bytes) -> None: + """ Writes a file to the patch container, to be retrieved upon patching. """ + self.files[file_name] = file + + def patch(self, target: str) -> None: + self.read() + base_data = self.get_source_data_with_cache() + patch_extender = AutoPatchExtensionRegister.get_handler(self.game) + assert not isinstance(self.procedure, str), f"{type(self)} must define procedures" + for step, args in self.procedure: + if isinstance(patch_extender, list): + extension = next((item for item in [getattr(extender, step, None) for extender in patch_extender] + if item is not None), None) + else: + extension = getattr(patch_extender, step, None) + if extension is not None: + base_data = extension(self, base_data, *args) + else: + raise NotImplementedError(f"Unknown procedure {step} for {self.game}.") + with open(target, 'wb') as f: + f.write(base_data) + + +class APDeltaPatch(APProcedurePatch): + """An APProcedurePatch that additionally has delta.bsdiff4 + containing a delta patch to get the desired file, often a rom.""" + + procedure = [ + ("apply_bsdiff4", ["delta.bsdiff4"]) + ] + + def __init__(self, *args: Any, patched_path: str = "", **kwargs: Any) -> None: + super(APDeltaPatch, self).__init__(*args, **kwargs) + self.patched_path = patched_path + + def write_contents(self, opened_zipfile: zipfile.ZipFile): + self.write_file("delta.bsdiff4", + bsdiff4.diff(self.get_source_data_with_cache(), open(self.patched_path, "rb").read())) + super(APDeltaPatch, self).write_contents(opened_zipfile) + + +class APTokenTypes(IntEnum): + WRITE = 0 + COPY = 1 + RLE = 2 + AND_8 = 3 + OR_8 = 4 + XOR_8 = 5 + + +class APTokenMixin: + """ + A class that defines functions for generating a token binary, for use in patches. + """ + tokens: List[ + Tuple[APTokenTypes, int, Union[ + bytes, # WRITE + Tuple[int, int], # COPY, RLE + int # AND_8, OR_8, XOR_8 + ]]] = [] + + def get_token_binary(self) -> bytes: + """ + Returns the token binary created from stored tokens. + :return: A bytes object representing the token data. + """ + data = bytearray() + data.extend(len(self.tokens).to_bytes(4, "little")) + for token_type, offset, args in self.tokens: + data.append(token_type) + data.extend(offset.to_bytes(4, "little")) + if token_type in [APTokenTypes.AND_8, APTokenTypes.OR_8, APTokenTypes.XOR_8]: + assert isinstance(args, int), f"Arguments to AND/OR/XOR must be of type int, not {type(args)}" + data.extend(int.to_bytes(1, 4, "little")) + data.append(args) + elif token_type in [APTokenTypes.COPY, APTokenTypes.RLE]: + assert isinstance(args, tuple), f"Arguments to COPY/RLE must be of type tuple, not {type(args)}" + data.extend(int.to_bytes(4, 4, "little")) + data.extend(args[0].to_bytes(4, "little")) + data.extend(args[1].to_bytes(4, "little")) + elif token_type == APTokenTypes.WRITE: + assert isinstance(args, bytes), f"Arguments to WRITE must be of type bytes, not {type(args)}" + data.extend(len(args).to_bytes(4, "little")) + data.extend(args) + else: + raise ValueError(f"Unknown token type {token_type}") + return bytes(data) + + @overload + def write_token(self, + token_type: Literal[APTokenTypes.AND_8, APTokenTypes.OR_8, APTokenTypes.XOR_8], + offset: int, + data: int) -> None: + ... + + @overload + def write_token(self, + token_type: Literal[APTokenTypes.COPY, APTokenTypes.RLE], + offset: int, + data: Tuple[int, int]) -> None: + ... + + @overload + def write_token(self, + token_type: Literal[APTokenTypes.WRITE], + offset: int, + data: bytes) -> None: + ... + + def write_token(self, token_type: APTokenTypes, offset: int, data: Union[bytes, Tuple[int, int], int]): + """ + Stores a token to be used by patching. + """ + self.tokens.append((token_type, offset, data)) + + +class APPatchExtension(metaclass=AutoPatchExtensionRegister): + """Class that defines patch extension functions for a given game. + Patch extension functions must have the following two arguments in the following order: + + caller: APProcedurePatch (used to retrieve files from the patch container) + + rom: bytes (the data to patch) + + Further arguments are passed in from the procedure as defined. + + Patch extension functions must return the changed bytes. + """ + game: str + required_extensions: List[str] = [] + + @staticmethod + def apply_bsdiff4(caller: APProcedurePatch, rom: bytes, patch: str): + """Applies the given bsdiff4 from the patch onto the current file.""" + return bsdiff4.patch(rom, caller.get_file(patch)) + + @staticmethod + def apply_tokens(caller: APProcedurePatch, rom: bytes, token_file: str) -> bytes: + """Applies the given token file from the patch onto the current file.""" + token_data = caller.get_file(token_file) + rom_data = bytearray(rom) + token_count = int.from_bytes(token_data[0:4], "little") + bpr = 4 + for _ in range(token_count): + token_type = token_data[bpr:bpr + 1][0] + offset = int.from_bytes(token_data[bpr + 1:bpr + 5], "little") + size = int.from_bytes(token_data[bpr + 5:bpr + 9], "little") + data = token_data[bpr + 9:bpr + 9 + size] + if token_type in [APTokenTypes.AND_8, APTokenTypes.OR_8, APTokenTypes.XOR_8]: + arg = data[0] + if token_type == APTokenTypes.AND_8: + rom_data[offset] = rom_data[offset] & arg + elif token_type == APTokenTypes.OR_8: + rom_data[offset] = rom_data[offset] | arg + else: + rom_data[offset] = rom_data[offset] ^ arg + elif token_type in [APTokenTypes.COPY, APTokenTypes.RLE]: + length = int.from_bytes(data[:4], "little") + value = int.from_bytes(data[4:], "little") + if token_type == APTokenTypes.COPY: + rom_data[offset: offset + length] = rom_data[value: value + length] + else: + rom_data[offset: offset + length] = bytes([value] * length) + else: + rom_data[offset:offset + len(data)] = data + bpr += 9 + size + return bytes(rom_data) + + @staticmethod + def calc_snes_crc(caller: APProcedurePatch, rom: bytes): + """Calculates and applies a valid CRC for the SNES rom header.""" + rom_data = bytearray(rom) + if len(rom) < 0x8000: + raise Exception("Tried to calculate SNES CRC on file too small to be a SNES ROM.") + crc = (sum(rom_data[:0x7FDC] + rom_data[0x7FE0:]) + 0x01FE) & 0xFFFF + inv = crc ^ 0xFFFF + rom_data[0x7FDC:0x7FE0] = [inv & 0xFF, (inv >> 8) & 0xFF, crc & 0xFF, (crc >> 8) & 0xFF] + return bytes(rom_data) From d0a9d0e2d1df641668f4f806b45f9577e69229f6 Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Wed, 20 Mar 2024 06:43:13 -0600 Subject: [PATCH 151/166] Pokemon Emerald: Bump required client version (#2963) --- worlds/pokemon_emerald/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/pokemon_emerald/__init__.py b/worlds/pokemon_emerald/__init__.py index c17fd1bc19..384bec9f45 100644 --- a/worlds/pokemon_emerald/__init__.py +++ b/worlds/pokemon_emerald/__init__.py @@ -87,7 +87,7 @@ class PokemonEmeraldWorld(World): location_name_groups = LOCATION_GROUPS data_version = 2 - required_client_version = (0, 4, 3) + required_client_version = (0, 4, 5) badge_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] hm_shuffle_info: Optional[List[Tuple[PokemonEmeraldLocation, PokemonEmeraldItem]]] From 6f64bb98693556ac2635791381cc9651c365b324 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Wed, 20 Mar 2024 08:46:31 -0400 Subject: [PATCH 152/166] Noita: Remove newline from option description so it doesn't look bad on webhost (#2969) --- worlds/noita/options.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/worlds/noita/options.py b/worlds/noita/options.py index 7d987571a5..2c99e9dd2f 100644 --- a/worlds/noita/options.py +++ b/worlds/noita/options.py @@ -78,8 +78,7 @@ class VictoryCondition(Choice): class ExtraOrbs(Range): - """Add extra orbs to your item pool, to prevent you from needing to wait as long - for the last orb you need for your victory condition. + """Add extra orbs to your item pool, to prevent you from needing to wait as long for the last orb you need for your victory condition. Extra orbs received past your victory condition's amount will be received as hearts instead. Can be turned on for the Greed Ending goal, but will only really make it harder.""" display_name = "Extra Orbs" From 8f7b63a787a0ef05625ae2fad1768251aced0c87 Mon Sep 17 00:00:00 2001 From: TheLX5 Date: Wed, 20 Mar 2024 05:56:04 -0700 Subject: [PATCH 153/166] SMW: Blocksanity logic fixes (#2988) --- worlds/smw/Regions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/worlds/smw/Regions.py b/worlds/smw/Regions.py index 2f8a128a56..2496049874 100644 --- a/worlds/smw/Regions.py +++ b/worlds/smw/Regions.py @@ -975,7 +975,7 @@ def create_regions(world: World, active_locations): add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_yellow_block_2, lambda state: state.has(ItemName.yellow_switch_palace, player)) add_location_to_region(multiworld, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_vine_block_1, - lambda state:( ((state.has(ItemName.mario_climb, player) and state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.yoshi_activate, player)))) + lambda state:( ((state.has(ItemName.mario_carry, player) and state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_spin_jump, player))) or (state.has(ItemName.yoshi_activate, player)))) add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_coin_block_1, lambda state: state.has(ItemName.mario_swim, player)) add_location_to_region(multiworld, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_coin_block_2, @@ -1118,7 +1118,7 @@ def create_regions(world: World, active_locations): add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_castle_region, LocationName.chocolate_castle_yellow_block_2, lambda state: (state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.yellow_switch_palace, player))) add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_castle_region, LocationName.chocolate_castle_green_block_1, - lambda state: (state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.yellow_switch_palace, player))) + lambda state: (state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.green_switch_palace, player))) add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress_powerup_block_1) add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress_powerup_block_2) add_location_to_region(multiworld, player, active_locations, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress_coin_block_1) @@ -1468,7 +1468,7 @@ def create_regions(world: World, active_locations): add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_9) add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_coin_block_10) add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_green_block_1, - lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.mario_carry, player))) + lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.mario_swim, player))) add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_powerup_block_1, lambda state: state.has(ItemName.mario_swim, player)) add_location_to_region(multiworld, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_invis_coin_block_1, @@ -1762,7 +1762,7 @@ def create_regions(world: World, active_locations): add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_green_block_7, lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player))) add_location_to_region(multiworld, player, active_locations, LocationName.star_road_4_region, LocationName.star_road_4_key_block_1, - lambda state:( ((state.has(ItemName.mario_climb, player) and state.has(ItemName.mario_carry, player))) or ((state.has(ItemName.green_switch_palace, player) and state.has(ItemName.red_switch_palace, player) and state.has(ItemName.mario_climb, player))))) + lambda state:( ((state.has(ItemName.mario_carry, player) and state.has(ItemName.yoshi_activate, player))) or ((state.has(ItemName.green_switch_palace, player) and state.has(ItemName.red_switch_palace, player) and state.has(ItemName.mario_carry, player))))) add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_directional_coin_block_1) add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_life_block_1, lambda state: state.has(ItemName.p_switch, player)) From fcaaa197a19a3be03965c504ca78dd2c21ce1f84 Mon Sep 17 00:00:00 2001 From: TheLX5 Date: Wed, 20 Mar 2024 05:56:19 -0700 Subject: [PATCH 154/166] SMW: Fixes for Bowser being defeatable on Egg Hunt and CI2 DC room access (#2981) --- worlds/smw/Client.py | 13 ++++++++----- worlds/smw/Rom.py | 37 +++++++++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/worlds/smw/Client.py b/worlds/smw/Client.py index eb9b4ec3d3..33a74b3dc8 100644 --- a/worlds/smw/Client.py +++ b/worlds/smw/Client.py @@ -65,11 +65,12 @@ SMW_RECV_PROGRESS_ADDR = WRAM_START + 0x01F2B SMW_BLOCKSANITY_BLOCK_COUNT = 582 -SMW_GOAL_LEVELS = [0x28, 0x31, 0x32] -SMW_INVALID_MARIO_STATES = [0x05, 0x06, 0x0A, 0x0C, 0x0D] -SMW_BAD_TEXT_BOX_LEVELS = [0x00, 0x26, 0x02, 0x4B] -SMW_BOSS_STATES = [0x80, 0xC0, 0xC1] -SMW_UNCOLLECTABLE_LEVELS = [0x25, 0x07, 0x0B, 0x40, 0x0E, 0x1F, 0x20, 0x1B, 0x1A, 0x35, 0x34, 0x31, 0x32] +SMW_GOAL_LEVELS = [0x28, 0x31, 0x32] +SMW_INVALID_MARIO_STATES = [0x05, 0x06, 0x0A, 0x0C, 0x0D] +SMW_BAD_TEXT_BOX_LEVELS = [0x00, 0x26, 0x02, 0x4B] +SMW_BOSS_STATES = [0x80, 0xC0, 0xC1] +SMW_UNCOLLECTABLE_LEVELS = [0x25, 0x07, 0x0B, 0x40, 0x0E, 0x1F, 0x20, 0x1B, 0x1A, 0x35, 0x34, 0x31, 0x32] +SMW_UNCOLLECTABLE_DRAGON_COINS = [0x24] class SMWSNIClient(SNIClient): @@ -604,6 +605,8 @@ class SMWSNIClient(SNIClient): if level_data[1] == 2: # Dragon Coins Check + if level_data[0] in SMW_UNCOLLECTABLE_DRAGON_COINS: + continue progress_byte = (level_data[0] // 8) progress_bit = 7 - (level_data[0] % 8) diff --git a/worlds/smw/Rom.py b/worlds/smw/Rom.py index 66226d5036..36078d4622 100644 --- a/worlds/smw/Rom.py +++ b/worlds/smw/Rom.py @@ -587,18 +587,17 @@ def handle_yoshi_box(rom): def handle_bowser_damage(rom): - rom.write_bytes(0x1A509, bytearray([0x20, 0x50, 0xBC])) # JSR $03BC50 + rom.write_bytes(0x1A509, bytearray([0x5C, 0x50, 0xBC, 0x03])) # JML $03BC50 BOWSER_BALLS_SUB_ADDR = 0x01BC50 - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x00, bytearray([0x08])) # PHP - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x01, bytearray([0xAD, 0x48, 0x0F])) # LDA $F48 - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x04, bytearray([0xCF, 0xA1, 0xBF, 0x03])) # CMP $03BFA1 - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x08, bytearray([0x90, 0x06])) # BCC +0x06 - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0A, bytearray([0x28])) # PLP - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0B, bytearray([0xEE, 0xB8, 0x14])) # INC $14B8 - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0E, bytearray([0x80, 0x01])) # BRA +0x01 - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x10, bytearray([0x28])) # PLP - rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x11, bytearray([0x60])) # RTS + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0000, bytearray([0xAF, 0xA0, 0xBF, 0x03])) # bowser_infinite_balls: lda.l goal_setting + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0004, bytearray([0xD0, 0x0C])) # bne .nope + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0006, bytearray([0xAD, 0x48, 0x0F])) # lda $0F48 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0009, bytearray([0xCF, 0xA1, 0xBF, 0x03])) # cmp.l required_bosses_setting + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x000D, bytearray([0x90, 0x03])) # bcc .nope + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x000F, bytearray([0xEE, 0xB8, 0x14])) # inc $14B8 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0012, bytearray([0xAD, 0xB8, 0x14])) # .nope lda $14B8 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0015, bytearray([0x5C, 0x0F, 0xA5, 0x03])) # jml $03A50F return @@ -2729,6 +2728,22 @@ def handle_uncompressed_player_gfx(rom): ]) rom.write_bytes(0x87F80, vram_targets) + +def handle_chocolate_island_2(rom): + FIX_CHOCOISLAND2_ADDR = 0x87200 + rom.write_bytes(0x2DB3E, bytearray([0x5C, 0x00, 0xF2, 0x10])) # jml fix_choco_island_2 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x0000, bytearray([0xAD, 0x33, 0x1F])) # fix_choco_island_2 lda $1F2F+$04 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x0003, bytearray([0x29, 0x08])) # and #$08 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x0005, bytearray([0xD0, 0x0D])) # bne .dc_room + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x0007, bytearray([0xAD, 0x22, 0x14])) # lda $1422 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x000A, bytearray([0xC9, 0x04])) # cmp #$04 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x000C, bytearray([0xF0, 0x06])) # beq .dc_room + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x000E, bytearray([0xA2, 0x02])) # .rex_room ldx #$02 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x0010, bytearray([0x5C, 0x49, 0xDB, 0x05])) # jml $05DB49 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x0014, bytearray([0xA2, 0x00])) # .dc_room ldx #$00 + rom.write_bytes(FIX_CHOCOISLAND2_ADDR + 0x0016, bytearray([0x5C, 0x49, 0xDB, 0x05])) # jml $05DB49 + + def decompress_gfx(compressed_graphics): # This code decompresses graphics in LC_LZ2 format in order to be able to swap player and yoshi's graphics with ease. decompressed_gfx = bytearray([]) @@ -3050,6 +3065,8 @@ def patch_rom(world: World, rom, player, active_level_dict): rom.write_bytes(0x09C13, bytearray([0x7E, 0x7E, 0x7F, 0x7F])) rom.write_byte(0x3F425, 0x32) + handle_chocolate_island_2(rom) + handle_ability_code(rom) handle_yoshi_box(rom) From 183ca35bbaf6c805fdb53396d21d0cba34f9cc5e Mon Sep 17 00:00:00 2001 From: qwint Date: Wed, 20 Mar 2024 08:39:37 -0500 Subject: [PATCH 155/166] CommonClient: Port Casting Bug (#2975) --- CommonClient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CommonClient.py b/CommonClient.py index b6f8e43b18..085a48a4b7 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -643,13 +643,13 @@ async def server_loop(ctx: CommonContext, address: typing.Optional[str] = None) ctx.username = server_url.username if server_url.password: ctx.password = server_url.password - port = server_url.port or 38281 def reconnect_hint() -> str: return ", type /connect to reconnect" if ctx.server_address else "" logger.info(f'Connecting to Archipelago server at {address}') try: + port = server_url.port or 38281 # raises ValueError if invalid socket = await websockets.connect(address, port=port, ping_timeout=None, ping_interval=None, ssl=get_ssl_context() if address.startswith("wss://") else None) if ctx.ui is not None: From e9620bea777ff1008a09c24a70bf523c94f22c29 Mon Sep 17 00:00:00 2001 From: Magnemania <89949176+Magnemania@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:56:00 -0400 Subject: [PATCH 156/166] SM64: Goal Logic and Hint Bugfixes (#2886) --- worlds/sm64ex/Regions.py | 57 ++++++++++++++++++++++++++------------- worlds/sm64ex/Rules.py | 15 ++++++----- worlds/sm64ex/__init__.py | 24 ++++++++++++----- 3 files changed, 64 insertions(+), 32 deletions(-) diff --git a/worlds/sm64ex/Regions.py b/worlds/sm64ex/Regions.py index 8c2d32e401..a493281ec3 100644 --- a/worlds/sm64ex/Regions.py +++ b/worlds/sm64ex/Regions.py @@ -36,6 +36,11 @@ class SM64Levels(int, Enum): BOWSER_IN_THE_FIRE_SEA = 191 WING_MARIO_OVER_THE_RAINBOW = 311 + +class SM64Region(Region): + subregions: typing.List[Region] = [] + + # sm64paintings is a dict of entrances, format LEVEL | AREA sm64_level_to_paintings: typing.Dict[SM64Levels, str] = { SM64Levels.BOB_OMB_BATTLEFIELD: "Bob-omb Battlefield", @@ -81,14 +86,16 @@ def create_regions(world: MultiWorld, player: int): regBoB = create_region("Bob-omb Battlefield", player, world) create_locs(regBoB, "BoB: Big Bob-Omb on the Summit", "BoB: Footrace with Koopa The Quick", "BoB: Mario Wings to the Sky", "BoB: Behind Chain Chomp's Gate", "BoB: Bob-omb Buddy") - create_subregion(regBoB, "BoB: Island", "BoB: Shoot to the Island in the Sky", "BoB: Find the 8 Red Coins") + bob_island = create_subregion(regBoB, "BoB: Island", "BoB: Shoot to the Island in the Sky", "BoB: Find the 8 Red Coins") + regBoB.subregions = [bob_island] if (world.EnableCoinStars[player].value): create_locs(regBoB, "BoB: 100 Coins") regWhomp = create_region("Whomp's Fortress", player, world) create_locs(regWhomp, "WF: Chip Off Whomp's Block", "WF: Shoot into the Wild Blue", "WF: Red Coins on the Floating Isle", "WF: Fall onto the Caged Island", "WF: Blast Away the Wall") - create_subregion(regWhomp, "WF: Tower", "WF: To the Top of the Fortress", "WF: Bob-omb Buddy") + wf_tower = create_subregion(regWhomp, "WF: Tower", "WF: To the Top of the Fortress", "WF: Bob-omb Buddy") + regWhomp.subregions = [wf_tower] if (world.EnableCoinStars[player].value): create_locs(regWhomp, "WF: 100 Coins") @@ -96,6 +103,7 @@ def create_regions(world: MultiWorld, player: int): create_locs(regJRB, "JRB: Plunder in the Sunken Ship", "JRB: Can the Eel Come Out to Play?", "JRB: Treasure of the Ocean Cave", "JRB: Blast to the Stone Pillar", "JRB: Through the Jet Stream", "JRB: Bob-omb Buddy") jrb_upper = create_subregion(regJRB, 'JRB: Upper', "JRB: Red Coins on the Ship Afloat") + regJRB.subregions = [jrb_upper] if (world.EnableCoinStars[player].value): create_locs(jrb_upper, "JRB: 100 Coins") @@ -108,7 +116,8 @@ def create_regions(world: MultiWorld, player: int): create_locs(regBBH, "BBH: Go on a Ghost Hunt", "BBH: Ride Big Boo's Merry-Go-Round", "BBH: Secret of the Haunted Books", "BBH: Seek the 8 Red Coins") bbh_third_floor = create_subregion(regBBH, "BBH: Third Floor", "BBH: Eye to Eye in the Secret Room") - create_subregion(bbh_third_floor, "BBH: Roof", "BBH: Big Boo's Balcony", "BBH: 1Up Block Top of Mansion") + bbh_roof = create_subregion(bbh_third_floor, "BBH: Roof", "BBH: Big Boo's Balcony", "BBH: 1Up Block Top of Mansion") + regBBH.subregions = [bbh_third_floor, bbh_roof] if (world.EnableCoinStars[player].value): create_locs(regBBH, "BBH: 100 Coins") @@ -130,22 +139,26 @@ def create_regions(world: MultiWorld, player: int): create_locs(regHMC, "HMC: Swimming Beast in the Cavern", "HMC: Metal-Head Mario Can Move!", "HMC: Watch for Rolling Rocks", "HMC: Navigating the Toxic Maze","HMC: 1Up Block Past Rolling Rocks") hmc_red_coin_area = create_subregion(regHMC, "HMC: Red Coin Area", "HMC: Elevate for 8 Red Coins") - create_subregion(regHMC, "HMC: Pit Islands", "HMC: A-Maze-Ing Emergency Exit", "HMC: 1Up Block above Pit") + hmc_pit_islands = create_subregion(regHMC, "HMC: Pit Islands", "HMC: A-Maze-Ing Emergency Exit", "HMC: 1Up Block above Pit") + regHMC.subregions = [hmc_red_coin_area, hmc_pit_islands] if (world.EnableCoinStars[player].value): create_locs(hmc_red_coin_area, "HMC: 100 Coins") regLLL = create_region("Lethal Lava Land", player, world) create_locs(regLLL, "LLL: Boil the Big Bully", "LLL: Bully the Bullies", "LLL: 8-Coin Puzzle with 15 Pieces", "LLL: Red-Hot Log Rolling") - create_subregion(regLLL, "LLL: Upper Volcano", "LLL: Hot-Foot-It into the Volcano", "LLL: Elevator Tour in the Volcano") + lll_upper_volcano = create_subregion(regLLL, "LLL: Upper Volcano", "LLL: Hot-Foot-It into the Volcano", "LLL: Elevator Tour in the Volcano") + regLLL.subregions = [lll_upper_volcano] if (world.EnableCoinStars[player].value): create_locs(regLLL, "LLL: 100 Coins") regSSL = create_region("Shifting Sand Land", player, world) - create_locs(regSSL, "SSL: In the Talons of the Big Bird", "SSL: Shining Atop the Pyramid", "SSL: Inside the Ancient Pyramid", + create_locs(regSSL, "SSL: In the Talons of the Big Bird", "SSL: Shining Atop the Pyramid", "SSL: Free Flying for 8 Red Coins", "SSL: Bob-omb Buddy", "SSL: 1Up Block Outside Pyramid", "SSL: 1Up Block Pyramid Left Path", "SSL: 1Up Block Pyramid Back") - create_subregion(regSSL, "SSL: Upper Pyramid", "SSL: Stand Tall on the Four Pillars", "SSL: Pyramid Puzzle") + ssl_upper_pyramid = create_subregion(regSSL, "SSL: Upper Pyramid", "SSL: Inside the Ancient Pyramid", + "SSL: Stand Tall on the Four Pillars", "SSL: Pyramid Puzzle") + regSSL.subregions = [ssl_upper_pyramid] if (world.EnableCoinStars[player].value): create_locs(regSSL, "SSL: 100 Coins") @@ -153,6 +166,7 @@ def create_regions(world: MultiWorld, player: int): create_locs(regDDD, "DDD: Board Bowser's Sub", "DDD: Chests in the Current", "DDD: Through the Jet Stream", "DDD: The Manta Ray's Reward", "DDD: Collect the Caps...") ddd_moving_poles = create_subregion(regDDD, "DDD: Moving Poles", "DDD: Pole-Jumping for Red Coins") + regDDD.subregions = [ddd_moving_poles] if (world.EnableCoinStars[player].value): create_locs(ddd_moving_poles, "DDD: 100 Coins") @@ -163,7 +177,8 @@ def create_regions(world: MultiWorld, player: int): create_default_locs(regVCutM, locVCutM_table) regBitFS = create_region("Bowser in the Fire Sea", player, world) - create_subregion(regBitFS, "BitFS: Upper", *locBitFS_table.keys()) + bitfs_upper = create_subregion(regBitFS, "BitFS: Upper", *locBitFS_table.keys()) + regBitFS.subregions = [bitfs_upper] create_region("Second Floor", player, world) @@ -176,7 +191,8 @@ def create_regions(world: MultiWorld, player: int): create_locs(regWDW, "WDW: Express Elevator--Hurry Up!") wdw_top = create_subregion(regWDW, "WDW: Top", "WDW: Shocking Arrow Lifts!", "WDW: Top o' the Town", "WDW: Secrets in the Shallows & Sky", "WDW: Bob-omb Buddy") - create_subregion(regWDW, "WDW: Downtown", "WDW: Go to Town for Red Coins", "WDW: Quick Race Through Downtown!", "WDW: 1Up Block in Downtown") + wdw_downtown = create_subregion(regWDW, "WDW: Downtown", "WDW: Go to Town for Red Coins", "WDW: Quick Race Through Downtown!", "WDW: 1Up Block in Downtown") + regWDW.subregions = [wdw_top, wdw_downtown] if (world.EnableCoinStars[player].value): create_locs(wdw_top, "WDW: 100 Coins") @@ -185,17 +201,19 @@ def create_regions(world: MultiWorld, player: int): "TTM: Bob-omb Buddy", "TTM: 1Up Block on Red Mushroom") ttm_top = create_subregion(ttm_middle, "TTM: Top", "TTM: Scale the Mountain", "TTM: Mystery of the Monkey Cage", "TTM: Mysterious Mountainside", "TTM: Breathtaking View from Bridge") + regTTM.subregions = [ttm_middle, ttm_top] if (world.EnableCoinStars[player].value): create_locs(ttm_top, "TTM: 100 Coins") create_region("Tiny-Huge Island (Huge)", player, world) create_region("Tiny-Huge Island (Tiny)", player, world) regTHI = create_region("Tiny-Huge Island", player, world) - create_locs(regTHI, "THI: The Tip Top of the Huge Island", "THI: 1Up Block THI Small near Start") - thi_pipes = create_subregion(regTHI, "THI: Pipes", "THI: Pluck the Piranha Flower", "THI: Rematch with Koopa the Quick", + create_locs(regTHI, "THI: 1Up Block THI Small near Start") + thi_pipes = create_subregion(regTHI, "THI: Pipes", "THI: The Tip Top of the Huge Island", "THI: Pluck the Piranha Flower", "THI: Rematch with Koopa the Quick", "THI: Five Itty Bitty Secrets", "THI: Wiggler's Red Coins", "THI: Bob-omb Buddy", "THI: 1Up Block THI Large near Start", "THI: 1Up Block Windy Area") thi_large_top = create_subregion(thi_pipes, "THI: Large Top", "THI: Make Wiggler Squirm") + regTHI.subregions = [thi_pipes, thi_large_top] if (world.EnableCoinStars[player].value): create_locs(thi_large_top, "THI: 100 Coins") @@ -206,6 +224,7 @@ def create_regions(world: MultiWorld, player: int): ttc_lower = create_subregion(regTTC, "TTC: Lower", "TTC: Roll into the Cage", "TTC: Get a Hand", "TTC: 1Up Block Midway Up") ttc_upper = create_subregion(ttc_lower, "TTC: Upper", "TTC: Timed Jumps on Moving Bars", "TTC: The Pit and the Pendulums") ttc_top = create_subregion(ttc_upper, "TTC: Top", "TTC: Stomp on the Thwomp", "TTC: 1Up Block at the Top") + regTTC.subregions = [ttc_lower, ttc_upper, ttc_top] if (world.EnableCoinStars[player].value): create_locs(ttc_top, "TTC: 100 Coins") @@ -213,8 +232,9 @@ def create_regions(world: MultiWorld, player: int): create_locs(regRR, "RR: Swingin' in the Breeze", "RR: Tricky Triangles!", "RR: 1Up Block Top of Red Coin Maze", "RR: 1Up Block Under Fly Guy", "RR: Bob-omb Buddy") rr_maze = create_subregion(regRR, "RR: Maze", "RR: Coins Amassed in a Maze") - create_subregion(regRR, "RR: Cruiser", "RR: Cruiser Crossing the Rainbow", "RR: Somewhere Over the Rainbow") - create_subregion(regRR, "RR: House", "RR: The Big House in the Sky", "RR: 1Up Block On House in the Sky") + rr_cruiser = create_subregion(regRR, "RR: Cruiser", "RR: Cruiser Crossing the Rainbow", "RR: Somewhere Over the Rainbow") + rr_house = create_subregion(regRR, "RR: House", "RR: The Big House in the Sky", "RR: 1Up Block On House in the Sky") + regRR.subregions = [rr_maze, rr_cruiser, rr_house] if (world.EnableCoinStars[player].value): create_locs(rr_maze, "RR: 100 Coins") @@ -223,7 +243,8 @@ def create_regions(world: MultiWorld, player: int): regBitS = create_region("Bowser in the Sky", player, world) create_locs(regBitS, "Bowser in the Sky 1Up Block") - create_subregion(regBitS, "BitS: Top", "Bowser in the Sky Red Coins") + bits_top = create_subregion(regBitS, "BitS: Top", "Bowser in the Sky Red Coins") + regBitS.subregions = [bits_top] def connect_regions(world: MultiWorld, player: int, source: str, target: str, rule=None): @@ -232,14 +253,14 @@ def connect_regions(world: MultiWorld, player: int, source: str, target: str, ru sourceRegion.connect(targetRegion, rule=rule) -def create_region(name: str, player: int, world: MultiWorld) -> Region: - region = Region(name, player, world) +def create_region(name: str, player: int, world: MultiWorld) -> SM64Region: + region = SM64Region(name, player, world) world.regions.append(region) return region -def create_subregion(source_region: Region, name: str, *locs: str) -> Region: - region = Region(name, source_region.player, source_region.multiworld) +def create_subregion(source_region: Region, name: str, *locs: str) -> SM64Region: + region = SM64Region(name, source_region.player, source_region.multiworld) connection = Entrance(source_region.player, name, source_region) source_region.exits.append(connection) connection.connect(region) diff --git a/worlds/sm64ex/Rules.py b/worlds/sm64ex/Rules.py index f2b8e0bcdf..cc2b52f0f1 100644 --- a/worlds/sm64ex/Rules.py +++ b/worlds/sm64ex/Rules.py @@ -107,9 +107,9 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move connect_regions(world, player, "Second Floor", "Third Floor", lambda state: state.has("Power Star", player, star_costs["SecondFloorDoorCost"])) - connect_regions(world, player, "Third Floor", randomized_entrances_s["Tick Tock Clock"]) - connect_regions(world, player, "Third Floor", randomized_entrances_s["Rainbow Ride"]) - connect_regions(world, player, "Third Floor", randomized_entrances_s["Wing Mario over the Rainbow"]) + connect_regions(world, player, "Third Floor", randomized_entrances_s["Tick Tock Clock"], rf.build_rule("LG/TJ/SF/BF/WK")) + connect_regions(world, player, "Third Floor", randomized_entrances_s["Rainbow Ride"], rf.build_rule("TJ/SF/BF")) + connect_regions(world, player, "Third Floor", randomized_entrances_s["Wing Mario over the Rainbow"], rf.build_rule("TJ/SF/BF")) connect_regions(world, player, "Third Floor", "Bowser in the Sky", lambda state: state.has("Power Star", player, star_costs["StarsToFinish"])) # Course Rules @@ -146,7 +146,7 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move rf.assign_rule("LLL: Upper Volcano", "CL") # Shifting Sand Land rf.assign_rule("SSL: Upper Pyramid", "CL & TJ/BF/SF/LG | MOVELESS") - rf.assign_rule("SSL: Free Flying for 8 Red Coins", "TJ/SF/BF & TJ+WC | TJ/SF/BF & CAPLESS | MOVELESS") + rf.assign_rule("SSL: Free Flying for 8 Red Coins", "TJ/SF/BF & TJ+WC | TJ/SF/BF & CAPLESS | MOVELESS & CAPLESS") # Dire, Dire Docks rf.assign_rule("DDD: Moving Poles", "CL & {{Bowser in the Fire Sea Key}} | TJ+DV+LG+WK & MOVELESS") rf.assign_rule("DDD: Through the Jet Stream", "MC | CAPLESS") @@ -165,6 +165,7 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move rf.assign_rule("TTM: Top", "MOVELESS & TJ | LJ/DV & LG/KK | MOVELESS & WK & SF/LG | MOVELESS & KK/DV") rf.assign_rule("TTM: Blast to the Lonely Mushroom", "CANN | CANNLESS & LJ | MOVELESS & CANNLESS") # Tiny-Huge Island + rf.assign_rule("THI: 1Up Block THI Small near Start", "NAR | {THI: Pipes}") rf.assign_rule("THI: Pipes", "NAR | LJ/TJ/DV/LG | MOVELESS & BF/SF/KK") rf.assign_rule("THI: Large Top", "NAR | LJ/TJ/DV | MOVELESS") rf.assign_rule("THI: Wiggler's Red Coins", "WK") @@ -225,11 +226,11 @@ def set_rules(world, player: int, area_connections: dict, star_costs: dict, move world.completion_condition[player] = lambda state: state.can_reach("BitS: Top", 'Region', player) if world.CompletionType[player] == "last_bowser_stage": - world.completion_condition[player] = lambda state: state.can_reach("Bowser in the Sky", 'Region', player) + world.completion_condition[player] = lambda state: state.can_reach("BitS: Top", 'Region', player) elif world.CompletionType[player] == "all_bowser_stages": world.completion_condition[player] = lambda state: state.can_reach("Bowser in the Dark World", 'Region', player) and \ - state.can_reach("Bowser in the Fire Sea", 'Region', player) and \ - state.can_reach("Bowser in the Sky", 'Region', player) + state.can_reach("BitFS: Upper", 'Region', player) and \ + state.can_reach("BitS: Top", 'Region', player) class RuleFactory: diff --git a/worlds/sm64ex/__init__.py b/worlds/sm64ex/__init__.py index e54a4b7a91..e6a6e42c76 100644 --- a/worlds/sm64ex/__init__.py +++ b/worlds/sm64ex/__init__.py @@ -5,8 +5,8 @@ from .Items import item_table, action_item_table, cannon_item_table, SM64Item from .Locations import location_table, SM64Location from .Options import sm64_options from .Rules import set_rules -from .Regions import create_regions, sm64_level_to_entrances -from BaseClasses import Item, Tutorial, ItemClassification +from .Regions import create_regions, sm64_level_to_entrances, SM64Levels +from BaseClasses import Item, Tutorial, ItemClassification, Region from ..AutoWorld import World, WebWorld @@ -200,11 +200,21 @@ class SM64World(World): with open(os.path.join(output_directory, filename), 'w') as f: json.dump(data, f) - def modify_multidata(self, multidata): + def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]): if self.topology_present: er_hint_data = {} for entrance, destination in self.area_connections.items(): - region = self.multiworld.get_region(sm64_level_to_entrances[destination], self.player) - for location in region.locations: - er_hint_data[location.address] = sm64_level_to_entrances[entrance] - multidata['er_hint_data'][self.player] = er_hint_data + regions = [self.multiworld.get_region(sm64_level_to_entrances[destination], self.player)] + if regions[0].name == "Tiny-Huge Island (Huge)": + # Special rules for Tiny-Huge Island's dual entrances + reverse_area_connections = {destination: entrance for entrance, destination in self.area_connections.items()} + entrance_name = sm64_level_to_entrances[reverse_area_connections[SM64Levels.TINY_HUGE_ISLAND_HUGE]] \ + + ' or ' + sm64_level_to_entrances[reverse_area_connections[SM64Levels.TINY_HUGE_ISLAND_TINY]] + regions[0] = self.multiworld.get_region("Tiny-Huge Island", self.player) + else: + entrance_name = sm64_level_to_entrances[entrance] + regions += regions[0].subregions + for region in regions: + for location in region.locations: + er_hint_data[location.address] = entrance_name + hint_data[self.player] = er_hint_data From 32315776ac0ac1a714eb9d58688c479e2038c658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Bolduc?= <16137441+Jouramie@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:57:45 -0400 Subject: [PATCH 157/166] Stardew Valley: Fix extended family legendary fishes being locations with fishsanity set to exclude legendary (#2967) --- worlds/stardew_valley/locations.py | 7 ++++--- worlds/stardew_valley/options.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/worlds/stardew_valley/locations.py b/worlds/stardew_valley/locations.py index 3bd1cf21e3..103b3bd960 100644 --- a/worlds/stardew_valley/locations.py +++ b/worlds/stardew_valley/locations.py @@ -6,7 +6,7 @@ from typing import Optional, Dict, Protocol, List, FrozenSet, Iterable from . import data from .bundles.bundle_room import BundleRoom -from .data.fish_data import legendary_fish, special_fish, get_fish_for_mods +from .data.fish_data import special_fish, get_fish_for_mods from .data.museum_data import all_museum_items from .data.villagers_data import get_villagers_for_mods from .mods.mod_data import ModNames @@ -206,7 +206,8 @@ def extend_fishsanity_locations(randomized_locations: List[LocationData], option if fishsanity == Fishsanity.option_none: return elif fishsanity == Fishsanity.option_legendaries: - randomized_locations.extend(location_table[f"{prefix}{legendary.name}"] for legendary in legendary_fish) + fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in active_fish if fish.legendary] + randomized_locations.extend(filter_disabled_locations(options, fish_locations)) elif fishsanity == Fishsanity.option_special: randomized_locations.extend(location_table[f"{prefix}{special.name}"] for special in special_fish) elif fishsanity == Fishsanity.option_randomized: @@ -216,7 +217,7 @@ def extend_fishsanity_locations(randomized_locations: List[LocationData], option fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in active_fish] randomized_locations.extend(filter_disabled_locations(options, fish_locations)) elif fishsanity == Fishsanity.option_exclude_legendaries: - fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in active_fish if fish not in legendary_fish] + fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in active_fish if not fish.legendary] randomized_locations.extend(filter_disabled_locations(options, fish_locations)) elif fishsanity == Fishsanity.option_exclude_hard_fish: fish_locations = [location_table[f"{prefix}{fish.name}"] for fish in active_fish if fish.difficulty < 80] diff --git a/worlds/stardew_valley/options.py b/worlds/stardew_valley/options.py index c2d239d274..634de45285 100644 --- a/worlds/stardew_valley/options.py +++ b/worlds/stardew_valley/options.py @@ -356,7 +356,7 @@ class QuestLocations(NamedRange): class Fishsanity(Choice): """Locations for catching a fish the first time? None: There are no locations for catching fish - Legendaries: Each of the 5 legendary fish are checks + Legendaries: Each of the 5 legendary fish are checks, plus the extended family if qi board is turned on Special: A curated selection of strong fish are checks Randomized: A random selection of fish are checks All: Every single fish in the game is a location that contains an item. Pairs well with the Master Angler Goal From db02e9d2aabc0f4c1302ac761b3f5547ef00c7c5 Mon Sep 17 00:00:00 2001 From: LiquidCat64 <74896918+LiquidCat64@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:03:25 -0600 Subject: [PATCH 158/166] Castlevania 64: Implement New Game (#2472) --- README.md | 1 + docs/CODEOWNERS | 3 + inno_setup.iss | 5 + worlds/cv64/__init__.py | 327 +++ worlds/cv64/aesthetics.py | 666 ++++++ worlds/cv64/client.py | 207 ++ worlds/cv64/data/APLogo-LICENSE.txt | 3 + worlds/cv64/data/ap_icons.bin | Bin 0 -> 4666 bytes worlds/cv64/data/ename.py | 71 + worlds/cv64/data/iname.py | 49 + worlds/cv64/data/lname.py | 479 +++++ worlds/cv64/data/patches.py | 2865 +++++++++++++++++++++++++ worlds/cv64/data/rname.py | 63 + worlds/cv64/docs/en_Castlevania 64.md | 148 ++ worlds/cv64/docs/obscure_checks.md | 429 ++++ worlds/cv64/docs/setup_en.md | 63 + worlds/cv64/entrances.py | 149 ++ worlds/cv64/items.py | 214 ++ worlds/cv64/locations.py | 699 ++++++ worlds/cv64/lzkn64.py | 266 +++ worlds/cv64/options.py | 490 +++++ worlds/cv64/regions.py | 517 +++++ worlds/cv64/rom.py | 959 +++++++++ worlds/cv64/rules.py | 103 + worlds/cv64/src/drop_sub_weapon.c | 69 + worlds/cv64/src/print.c | 116 + worlds/cv64/src/print_text_ovl.c | 26 + worlds/cv64/stages.py | 490 +++++ worlds/cv64/test/__init__.py | 6 + worlds/cv64/test/test_access.py | 250 +++ worlds/cv64/text.py | 95 + 31 files changed, 9828 insertions(+) create mode 100644 worlds/cv64/__init__.py create mode 100644 worlds/cv64/aesthetics.py create mode 100644 worlds/cv64/client.py create mode 100644 worlds/cv64/data/APLogo-LICENSE.txt create mode 100644 worlds/cv64/data/ap_icons.bin create mode 100644 worlds/cv64/data/ename.py create mode 100644 worlds/cv64/data/iname.py create mode 100644 worlds/cv64/data/lname.py create mode 100644 worlds/cv64/data/patches.py create mode 100644 worlds/cv64/data/rname.py create mode 100644 worlds/cv64/docs/en_Castlevania 64.md create mode 100644 worlds/cv64/docs/obscure_checks.md create mode 100644 worlds/cv64/docs/setup_en.md create mode 100644 worlds/cv64/entrances.py create mode 100644 worlds/cv64/items.py create mode 100644 worlds/cv64/locations.py create mode 100644 worlds/cv64/lzkn64.py create mode 100644 worlds/cv64/options.py create mode 100644 worlds/cv64/regions.py create mode 100644 worlds/cv64/rom.py create mode 100644 worlds/cv64/rules.py create mode 100644 worlds/cv64/src/drop_sub_weapon.c create mode 100644 worlds/cv64/src/print.c create mode 100644 worlds/cv64/src/print_text_ovl.c create mode 100644 worlds/cv64/stages.py create mode 100644 worlds/cv64/test/__init__.py create mode 100644 worlds/cv64/test/test_access.py create mode 100644 worlds/cv64/text.py diff --git a/README.md b/README.md index 975f0ce75a..2c0c164b53 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ Currently, the following games are supported: * Kirby's Dream Land 3 * Celeste 64 * Zork Grand Inquisitor +* Castlevania 64 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 90b1dabb6d..9c801f04af 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -28,6 +28,9 @@ # Bumper Stickers /worlds/bumpstik/ @FelicitusNeko +# Castlevania 64 +/worlds/cv64/ @LiquidCat64 + # Celeste 64 /worlds/celeste64/ @PoryGone diff --git a/inno_setup.iss b/inno_setup.iss index 5a6d608306..9f4c9d1678 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -169,6 +169,11 @@ Root: HKCR; Subkey: "{#MyAppName}pkmnepatch"; ValueData: "Ar Root: HKCR; Subkey: "{#MyAppName}pkmnepatch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}pkmnepatch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: ".apcv64"; ValueData: "{#MyAppName}cv64patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}cv64patch"; ValueData: "Archipelago Castlevania 64 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}cv64patch\DefaultIcon"; ValueData: "{app}\ArchipelagoBizHawkClient.exe,0"; ValueType: string; ValueName: ""; +Root: HKCR; Subkey: "{#MyAppName}cv64patch\shell\open\command"; ValueData: """{app}\ArchipelagoBizHawkClient.exe"" ""%1"""; ValueType: string; ValueName: ""; + Root: HKCR; Subkey: ".apladx"; ValueData: "{#MyAppName}ladxpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}ladxpatch"; ValueData: "Archipelago Links Awakening DX Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}ladxpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoLinksAwakeningClient.exe,0"; ValueType: string; ValueName: ""; diff --git a/worlds/cv64/__init__.py b/worlds/cv64/__init__.py new file mode 100644 index 0000000000..ca4697bce8 --- /dev/null +++ b/worlds/cv64/__init__.py @@ -0,0 +1,327 @@ +import os +import typing +import settings +import base64 +import logging + +from BaseClasses import Item, Region, MultiWorld, Tutorial, ItemClassification +from .items import CV64Item, filler_item_names, get_item_info, get_item_names_to_ids, get_item_counts +from .locations import CV64Location, get_location_info, verify_locations, get_location_names_to_ids, base_id +from .entrances import verify_entrances, get_warp_entrances +from .options import CV64Options, CharacterStages, DraculasCondition, SubWeaponShuffle +from .stages import get_locations_from_stage, get_normal_stage_exits, vanilla_stage_order, \ + shuffle_stages, generate_warps, get_region_names +from .regions import get_region_info +from .rules import CV64Rules +from .data import iname, rname, ename +from ..AutoWorld import WebWorld, World +from .aesthetics import randomize_lighting, shuffle_sub_weapons, rom_empty_breakables_flags, rom_sub_weapon_flags, \ + randomize_music, get_start_inventory_data, get_location_data, randomize_shop_prices, get_loading_zone_bytes, \ + get_countdown_numbers +from .rom import LocalRom, patch_rom, get_base_rom_path, CV64DeltaPatch +from .client import Castlevania64Client + + +class CV64Settings(settings.Group): + class RomFile(settings.UserFilePath): + """File name of the CV64 US 1.0 rom""" + copy_to = "Castlevania (USA).z64" + description = "CV64 (US 1.0) ROM File" + md5s = [CV64DeltaPatch.hash] + + rom_file: RomFile = RomFile(RomFile.copy_to) + + +class CV64Web(WebWorld): + theme = "stone" + + tutorials = [Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Archipleago Castlevania 64 randomizer on your computer and connecting it to a " + "multiworld.", + "English", + "setup_en.md", + "setup/en", + ["Liquid Cat"] + )] + + +class CV64World(World): + """ + Castlevania for the Nintendo 64 is the first 3D game in the franchise. As either whip-wielding Belmont descendant + Reinhardt Schneider or powerful sorceress Carrie Fernandez, brave many terrifying traps and foes as you make your + way to Dracula's chamber and stop his rule of terror! + """ + game = "Castlevania 64" + item_name_groups = { + "Bomb": {iname.magical_nitro, iname.mandragora}, + "Ingredient": {iname.magical_nitro, iname.mandragora}, + } + location_name_groups = {stage: set(get_locations_from_stage(stage)) for stage in vanilla_stage_order} + options_dataclass = CV64Options + options: CV64Options + settings: typing.ClassVar[CV64Settings] + topology_present = True + data_version = 1 + + item_name_to_id = get_item_names_to_ids() + location_name_to_id = get_location_names_to_ids() + + active_stage_exits: typing.Dict[str, typing.Dict] + active_stage_list: typing.List[str] + active_warp_list: typing.List[str] + + # Default values to possibly be updated in generate_early + reinhardt_stages: bool = True + carrie_stages: bool = True + branching_stages: bool = False + starting_stage: str = rname.forest_of_silence + total_s1s: int = 7 + s1s_per_warp: int = 1 + total_s2s: int = 0 + required_s2s: int = 0 + drac_condition: int = 0 + + auth: bytearray + + web = CV64Web() + + @classmethod + def stage_assert_generate(cls, multiworld: MultiWorld) -> None: + rom_file = get_base_rom_path() + if not os.path.exists(rom_file): + raise FileNotFoundError(rom_file) + + def generate_early(self) -> None: + # Generate the player's unique authentication + self.auth = bytearray(self.multiworld.random.getrandbits(8) for _ in range(16)) + + self.total_s1s = self.options.total_special1s.value + self.s1s_per_warp = self.options.special1s_per_warp.value + self.drac_condition = self.options.draculas_condition.value + + # If there are more S1s needed to unlock the whole warp menu than there are S1s in total, drop S1s per warp to + # something manageable. + if self.s1s_per_warp * 7 > self.total_s1s: + self.s1s_per_warp = self.total_s1s // 7 + logging.warning(f"[{self.multiworld.player_name[self.player]}] Too many required Special1s " + f"({self.options.special1s_per_warp.value * 7}) for Special1s Per Warp setting: " + f"{self.options.special1s_per_warp.value} with Total Special1s setting: " + f"{self.options.total_special1s.value}. Lowering Special1s Per Warp to: " + f"{self.s1s_per_warp}") + self.options.special1s_per_warp.value = self.s1s_per_warp + + # Set the total and required Special2s to 1 if the drac condition is the Crystal, to the specified YAML numbers + # if it's Specials, or to 0 if it's None or Bosses. The boss totals will be figured out later. + if self.drac_condition == DraculasCondition.option_crystal: + self.total_s2s = 1 + self.required_s2s = 1 + elif self.drac_condition == DraculasCondition.option_specials: + self.total_s2s = self.options.total_special2s.value + self.required_s2s = int(self.options.percent_special2s_required.value / 100 * self.total_s2s) + + # Enable/disable character stages and branching paths accordingly + if self.options.character_stages == CharacterStages.option_reinhardt_only: + self.carrie_stages = False + elif self.options.character_stages == CharacterStages.option_carrie_only: + self.reinhardt_stages = False + elif self.options.character_stages == CharacterStages.option_both: + self.branching_stages = True + + self.active_stage_exits = get_normal_stage_exits(self) + + stage_1_blacklist = [] + + # Prevent Clock Tower from being Stage 1 if more than 4 S1s are needed to warp out of it. + if self.s1s_per_warp > 4 and not self.options.multi_hit_breakables: + stage_1_blacklist.append(rname.clock_tower) + + # Shuffle the stages if the option is on. + if self.options.stage_shuffle: + self.active_stage_exits, self.starting_stage, self.active_stage_list = \ + shuffle_stages(self, stage_1_blacklist) + else: + self.active_stage_list = [stage for stage in vanilla_stage_order if stage in self.active_stage_exits] + + # Create a list of warps from the active stage list. They are in a random order by default and will never + # include the starting stage. + self.active_warp_list = generate_warps(self) + + def create_regions(self) -> None: + # Add the Menu Region. + created_regions = [Region("Menu", self.player, self.multiworld)] + + # Add every stage Region by checking to see if that stage is active. + created_regions.extend([Region(name, self.player, self.multiworld) + for name in get_region_names(self.active_stage_exits)]) + + # Add the Renon's shop Region if shopsanity is on. + if self.options.shopsanity: + created_regions.append(Region(rname.renon, self.player, self.multiworld)) + + # Add the Dracula's chamber (the end) Region. + created_regions.append(Region(rname.ck_drac_chamber, self.player, self.multiworld)) + + # Set up the Regions correctly. + self.multiworld.regions.extend(created_regions) + + # Add the warp Entrances to the Menu Region (the one always at the start of the Region list). + created_regions[0].add_exits(get_warp_entrances(self.active_warp_list)) + + for reg in created_regions: + + # Add the Entrances to all the Regions. + ent_names = get_region_info(reg.name, "entrances") + if ent_names is not None: + reg.add_exits(verify_entrances(self.options, ent_names, self.active_stage_exits)) + + # Add the Locations to all the Regions. + loc_names = get_region_info(reg.name, "locations") + if loc_names is None: + continue + verified_locs, events = verify_locations(self.options, loc_names) + reg.add_locations(verified_locs, CV64Location) + + # Place event Items on all of their associated Locations. + for event_loc, event_item in events.items(): + self.get_location(event_loc).place_locked_item(self.create_item(event_item, "progression")) + # If we're looking at a boss kill trophy, increment the total S2s and, if we're not already at the + # set number of required bosses, the total required number. This way, we can prevent gen failures + # should the player set more bosses required than there are total. + if event_item == iname.trophy: + self.total_s2s += 1 + if self.required_s2s < self.options.bosses_required.value: + self.required_s2s += 1 + + # If Dracula's Condition is Bosses and there are less calculated required S2s than the value specified by the + # player (meaning there weren't enough bosses to reach the player's setting), throw a warning and lower the + # option value. + if self.options.draculas_condition == DraculasCondition.option_bosses and self.required_s2s < \ + self.options.bosses_required.value: + logging.warning(f"[{self.multiworld.player_name[self.player]}] Not enough bosses for Bosses Required " + f"setting: {self.options.bosses_required.value}. Lowering to: {self.required_s2s}") + self.options.bosses_required.value = self.required_s2s + + def create_item(self, name: str, force_classification: typing.Optional[str] = None) -> Item: + if force_classification is not None: + classification = getattr(ItemClassification, force_classification) + else: + classification = getattr(ItemClassification, get_item_info(name, "default classification")) + + code = get_item_info(name, "code") + if code is not None: + code += base_id + + created_item = CV64Item(name, classification, code, self.player) + + return created_item + + def create_items(self) -> None: + item_counts = get_item_counts(self) + + # Set up the items correctly + self.multiworld.itempool += [self.create_item(item, classification) for classification in item_counts for item + in item_counts[classification] for _ in range(item_counts[classification][item])] + + def set_rules(self) -> None: + # Set all the Entrance rules properly. + CV64Rules(self).set_cv64_rules() + + def pre_fill(self) -> None: + # If we need more Special1s to warp out of Sphere 1 than there are locations available, then AP's fill + # algorithm may try placing the Special1s anyway despite placing the stage's single key always being an option. + # To get around this problem in the fill algorithm, the keys will be forced early in these situations to ensure + # the algorithm will pick them over the Special1s. + if self.starting_stage == rname.tower_of_science: + if self.s1s_per_warp > 3: + self.multiworld.local_early_items[self.player][iname.science_key2] = 1 + elif self.starting_stage == rname.clock_tower: + if (self.s1s_per_warp > 2 and not self.options.multi_hit_breakables) or \ + (self.s1s_per_warp > 8 and self.options.multi_hit_breakables): + self.multiworld.local_early_items[self.player][iname.clocktower_key1] = 1 + elif self.starting_stage == rname.castle_wall: + if self.s1s_per_warp > 5 and not self.options.hard_logic and \ + not self.options.multi_hit_breakables: + self.multiworld.local_early_items[self.player][iname.left_tower_key] = 1 + + def generate_output(self, output_directory: str) -> None: + active_locations = self.multiworld.get_locations(self.player) + + # Location data and shop names, descriptions, and colors + offset_data, shop_name_list, shop_colors_list, shop_desc_list = \ + get_location_data(self, active_locations) + # Shop prices + if self.options.shop_prices: + offset_data.update(randomize_shop_prices(self)) + # Map lighting + if self.options.map_lighting: + offset_data.update(randomize_lighting(self)) + # Sub-weapons + if self.options.sub_weapon_shuffle == SubWeaponShuffle.option_own_pool: + offset_data.update(shuffle_sub_weapons(self)) + elif self.options.sub_weapon_shuffle == SubWeaponShuffle.option_anywhere: + offset_data.update(rom_sub_weapon_flags) + # Empty breakables + if self.options.empty_breakables: + offset_data.update(rom_empty_breakables_flags) + # Music + if self.options.background_music: + offset_data.update(randomize_music(self)) + # Loading zones + offset_data.update(get_loading_zone_bytes(self.options, self.starting_stage, self.active_stage_exits)) + # Countdown + if self.options.countdown: + offset_data.update(get_countdown_numbers(self.options, active_locations)) + # Start Inventory + offset_data.update(get_start_inventory_data(self.player, self.options, + self.multiworld.precollected_items[self.player])) + + cv64_rom = LocalRom(get_base_rom_path()) + + rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.z64") + + patch_rom(self, cv64_rom, offset_data, shop_name_list, shop_desc_list, shop_colors_list, active_locations) + + cv64_rom.write_to_file(rompath) + + patch = CV64DeltaPatch(os.path.splitext(rompath)[0] + CV64DeltaPatch.patch_file_ending, player=self.player, + player_name=self.multiworld.player_name[self.player], patched_path=rompath) + patch.write() + os.unlink(rompath) + + def get_filler_item_name(self) -> str: + return self.random.choice(filler_item_names) + + def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]): + # Attach each location's stage's position to its hint information if Stage Shuffle is on. + if not self.options.stage_shuffle: + return + + stage_pos_data = {} + for loc in list(self.multiworld.get_locations(self.player)): + stage = get_region_info(loc.parent_region.name, "stage") + if stage is not None and loc.address is not None: + num = str(self.active_stage_exits[stage]["position"]).zfill(2) + path = self.active_stage_exits[stage]["path"] + stage_pos_data[loc.address] = f"Stage {num}" + if path != " ": + stage_pos_data[loc.address] += path + hint_data[self.player] = stage_pos_data + + def modify_multidata(self, multidata: typing.Dict[str, typing.Any]): + # Put the player's unique authentication in connect_names. + multidata["connect_names"][base64.b64encode(self.auth).decode("ascii")] = \ + multidata["connect_names"][self.multiworld.player_name[self.player]] + + def write_spoiler(self, spoiler_handle: typing.TextIO) -> None: + # Write the stage order to the spoiler log + spoiler_handle.write(f"\nCastlevania 64 stage & warp orders for {self.multiworld.player_name[self.player]}:\n") + for stage in self.active_stage_list: + num = str(self.active_stage_exits[stage]["position"]).zfill(2) + path = self.active_stage_exits[stage]["path"] + spoiler_handle.writelines(f"Stage {num}{path}:\t{stage}\n") + + # Write the warp order to the spoiler log + spoiler_handle.writelines(f"\nStart :\t{self.active_stage_list[0]}\n") + for i in range(1, len(self.active_warp_list)): + spoiler_handle.writelines(f"Warp {i}:\t{self.active_warp_list[i]}\n") diff --git a/worlds/cv64/aesthetics.py b/worlds/cv64/aesthetics.py new file mode 100644 index 0000000000..cbf2728c82 --- /dev/null +++ b/worlds/cv64/aesthetics.py @@ -0,0 +1,666 @@ +import logging + +from BaseClasses import ItemClassification, Location, Item +from .data import iname, rname +from .options import CV64Options, BackgroundMusic, Countdown, IceTrapAppearance, InvisibleItems, CharacterStages +from .stages import vanilla_stage_order, get_stage_info +from .locations import get_location_info, base_id +from .regions import get_region_info +from .items import get_item_info, item_info + +from typing import TYPE_CHECKING, Dict, List, Tuple, Union, Iterable + +if TYPE_CHECKING: + from . import CV64World + +rom_sub_weapon_offsets = { + 0x10C6EB: (0x10, rname.forest_of_silence), # Forest + 0x10C6F3: (0x0F, rname.forest_of_silence), + 0x10C6FB: (0x0E, rname.forest_of_silence), + 0x10C703: (0x0D, rname.forest_of_silence), + + 0x10C81F: (0x0F, rname.castle_wall), # Castle Wall + 0x10C827: (0x10, rname.castle_wall), + 0x10C82F: (0x0E, rname.castle_wall), + 0x7F9A0F: (0x0D, rname.castle_wall), + + 0x83A5D9: (0x0E, rname.villa), # Villa + 0x83A5E5: (0x0D, rname.villa), + 0x83A5F1: (0x0F, rname.villa), + 0xBFC903: (0x10, rname.villa), + 0x10C987: (0x10, rname.villa), + 0x10C98F: (0x0D, rname.villa), + 0x10C997: (0x0F, rname.villa), + 0x10CF73: (0x10, rname.villa), + + 0x10CA57: (0x0D, rname.tunnel), # Tunnel + 0x10CA5F: (0x0E, rname.tunnel), + 0x10CA67: (0x10, rname.tunnel), + 0x10CA6F: (0x0D, rname.tunnel), + 0x10CA77: (0x0F, rname.tunnel), + 0x10CA7F: (0x0E, rname.tunnel), + + 0x10CBC7: (0x0E, rname.castle_center), # Castle Center + 0x10CC0F: (0x0D, rname.castle_center), + 0x10CC5B: (0x0F, rname.castle_center), + + 0x10CD3F: (0x0E, rname.tower_of_execution), # Character towers + 0x10CD65: (0x0D, rname.tower_of_execution), + 0x10CE2B: (0x0E, rname.tower_of_science), + 0x10CE83: (0x10, rname.duel_tower), + + 0x10CF8B: (0x0F, rname.room_of_clocks), # Room of Clocks + 0x10CF93: (0x0D, rname.room_of_clocks), + + 0x99BC5A: (0x0D, rname.clock_tower), # Clock Tower + 0x10CECB: (0x10, rname.clock_tower), + 0x10CED3: (0x0F, rname.clock_tower), + 0x10CEDB: (0x0E, rname.clock_tower), + 0x10CEE3: (0x0D, rname.clock_tower), +} + +rom_sub_weapon_flags = { + 0x10C6EC: 0x0200FF04, # Forest of Silence + 0x10C6FC: 0x0400FF04, + 0x10C6F4: 0x0800FF04, + 0x10C704: 0x4000FF04, + + 0x10C831: 0x08, # Castle Wall + 0x10C829: 0x10, + 0x10C821: 0x20, + 0xBFCA97: 0x04, + + # Villa + 0xBFC926: 0xFF04, + 0xBFC93A: 0x80, + 0xBFC93F: 0x01, + 0xBFC943: 0x40, + 0xBFC947: 0x80, + 0x10C989: 0x10, + 0x10C991: 0x20, + 0x10C999: 0x40, + 0x10CF77: 0x80, + + 0x10CA58: 0x4000FF0E, # Tunnel + 0x10CA6B: 0x80, + 0x10CA60: 0x1000FF05, + 0x10CA70: 0x2000FF05, + 0x10CA78: 0x4000FF05, + 0x10CA80: 0x8000FF05, + + 0x10CBCA: 0x02, # Castle Center + 0x10CC10: 0x80, + 0x10CC5C: 0x40, + + 0x10CE86: 0x01, # Duel Tower + 0x10CD43: 0x02, # Tower of Execution + 0x10CE2E: 0x20, # Tower of Science + + 0x10CF8E: 0x04, # Room of Clocks + 0x10CF96: 0x08, + + 0x10CECE: 0x08, # Clock Tower + 0x10CED6: 0x10, + 0x10CEE6: 0x20, + 0x10CEDE: 0x80, +} + +rom_empty_breakables_flags = { + 0x10C74D: 0x40FF05, # Forest of Silence + 0x10C765: 0x20FF0E, + 0x10C774: 0x0800FF0E, + 0x10C755: 0x80FF05, + 0x10C784: 0x0100FF0E, + 0x10C73C: 0x0200FF0E, + + 0x10C8D0: 0x0400FF0E, # Villa foyer + + 0x10CF9F: 0x08, # Room of Clocks flags + 0x10CFA7: 0x01, + 0xBFCB6F: 0x04, # Room of Clocks candle property IDs + 0xBFCB73: 0x05, +} + +rom_axe_cross_lower_values = { + 0x6: [0x7C7F97, 0x07], # Forest + 0x8: [0x7C7FA6, 0xF9], + + 0x30: [0x83A60A, 0x71], # Villa hallway + 0x27: [0x83A617, 0x26], + 0x2C: [0x83A624, 0x6E], + + 0x16C: [0x850FE6, 0x07], # Villa maze + + 0x10A: [0x8C44D3, 0x08], # CC factory floor + 0x109: [0x8C44E1, 0x08], + + 0x74: [0x8DF77C, 0x07], # CC invention area + 0x60: [0x90FD37, 0x43], + 0x55: [0xBFCC2B, 0x43], + 0x65: [0x90FBA1, 0x51], + 0x64: [0x90FBAD, 0x50], + 0x61: [0x90FE56, 0x43] +} + +rom_looping_music_fade_ins = { + 0x10: None, + 0x11: None, + 0x12: None, + 0x13: None, + 0x14: None, + 0x15: None, + 0x16: 0x17, + 0x18: 0x19, + 0x1A: 0x1B, + 0x21: 0x75, + 0x27: None, + 0x2E: 0x23, + 0x39: None, + 0x45: 0x63, + 0x56: None, + 0x57: 0x58, + 0x59: None, + 0x5A: None, + 0x5B: 0x5C, + 0x5D: None, + 0x5E: None, + 0x5F: None, + 0x60: 0x61, + 0x62: None, + 0x64: None, + 0x65: None, + 0x66: None, + 0x68: None, + 0x69: None, + 0x6D: 0x78, + 0x6E: None, + 0x6F: None, + 0x73: None, + 0x74: None, + 0x77: None, + 0x79: None +} + +music_sfx_ids = [0x1C, 0x4B, 0x4C, 0x4D, 0x4E, 0x55, 0x6C, 0x76] + +renon_item_dialogue = { + 0x02: "More Sub-weapon uses!\n" + "Just what you need!", + 0x03: "Galamoth told me it's\n" + "a heart in other times.", + 0x04: "Who needs Warp Rooms\n" + "when you have these?", + 0x05: "I was told to safeguard\n" + "this, but I dunno why.", + 0x06: "Fresh off a Behemoth!\n" + "Those cows are weird.", + 0x07: "Preserved with special\n" + " wall-based methods.", + 0x08: "Don't tell Geneva\n" + "about this...", + 0x09: "If this existed in 1094,\n" + "that whip wouldn't...", + 0x0A: "For when some lizard\n" + "brain spits on your ego.", + 0x0C: "It'd be a shame if you\n" + "lost it immediately...", + 0x10C: "No consequences should\n" + "you perish with this!", + 0x0D: "Arthur was far better\n" + "with it than you!", + 0x0E: "Night Creatures handle\n" + "with care!", + 0x0F: "Some may call it a\n" + "\"Banshee Boomerang.\"", + 0x10: "No weapon triangle\n" + "advantages with this.", + 0x12: "It looks sus? Trust me," + "my wares are genuine.", + 0x15: "This non-volatile kind\n" + "is safe to handle.", + 0x16: "If you can soul-wield,\n" + "they have a good one!", + 0x17: "Calls the morning sun\n" + "to vanquish the night.", + 0x18: "1 on-demand horrible\n" + "night. Devils love it!", + 0x1A: "Want to study here?\n" + "It will cost you.", + 0x1B: "\"Let them eat cake!\"\n" + "Said no princess ever.", + 0x1C: "Why do I suspect this\n" + "was a toilet room?", + 0x1D: "When you see Coller,\n" + "tell him I said hi!", + 0x1E: "Atomic number is 29\n" + "and weight is 63.546.", + 0x1F: "One torture per pay!\n" + "Who will it be?", + 0x20: "Being here feels like\n" + "time is slowing down.", + 0x21: "Only one thing beind\n" + "this. Do you dare?", + 0x22: "The key 2 Science!\n" + "Both halves of it!", + 0x23: "This warehouse can\n" + "be yours for a fee.", + 0x24: "Long road ahead if you\n" + "don't have the others.", + 0x25: "Will you get the curse\n" + "of eternal burning?", + 0x26: "What's beyond time?\n" + "Find out your", + 0x27: "Want to take out a\n" + "loan? By all means!", + 0x28: "The bag is green,\n" + "so it must be lucky!", + 0x29: "(Does this fool realize?)\n" + "Oh, sorry.", + "prog": "They will absolutely\n" + "need it in time!", + "useful": "Now, this would be\n" + "useful to send...", + "common": "Every last little bit\n" + "helps, right?", + "trap": "I'll teach this fool\n" + " a lesson for a price!", + "dlc coin": "1 coin out of... wha!?\n" + "You imp, why I oughta!" +} + + +def randomize_lighting(world: "CV64World") -> Dict[int, int]: + """Generates randomized data for the map lighting table.""" + randomized_lighting = {} + for entry in range(67): + for sub_entry in range(19): + if sub_entry not in [3, 7, 11, 15] and entry != 4: + # The fourth entry in the lighting table affects the lighting on some item pickups; skip it + randomized_lighting[0x1091A0 + (entry * 28) + sub_entry] = \ + world.random.randint(0, 255) + return randomized_lighting + + +def shuffle_sub_weapons(world: "CV64World") -> Dict[int, int]: + """Shuffles the sub-weapons amongst themselves.""" + sub_weapon_dict = {offset: rom_sub_weapon_offsets[offset][0] for offset in rom_sub_weapon_offsets if + rom_sub_weapon_offsets[offset][1] in world.active_stage_exits} + + # Remove the one 3HB sub-weapon in Tower of Execution if 3HBs are not shuffled. + if not world.options.multi_hit_breakables and 0x10CD65 in sub_weapon_dict: + del (sub_weapon_dict[0x10CD65]) + + sub_bytes = list(sub_weapon_dict.values()) + world.random.shuffle(sub_bytes) + return dict(zip(sub_weapon_dict, sub_bytes)) + + +def randomize_music(world: "CV64World") -> Dict[int, int]: + """Generates randomized or disabled data for all the music in the game.""" + music_array = bytearray(0x7A) + for number in music_sfx_ids: + music_array[number] = number + if world.options.background_music == BackgroundMusic.option_randomized: + looping_songs = [] + non_looping_songs = [] + fade_in_songs = {} + # Create shuffle-able lists of all the looping, non-looping, and fade-in track IDs + for i in range(0x10, len(music_array)): + if i not in rom_looping_music_fade_ins.keys() and i not in rom_looping_music_fade_ins.values() and \ + i != 0x72: # Credits song is blacklisted + non_looping_songs.append(i) + elif i in rom_looping_music_fade_ins.keys(): + looping_songs.append(i) + elif i in rom_looping_music_fade_ins.values(): + fade_in_songs[i] = i + # Shuffle the looping songs + rando_looping_songs = looping_songs.copy() + world.random.shuffle(rando_looping_songs) + looping_songs = dict(zip(looping_songs, rando_looping_songs)) + # Shuffle the non-looping songs + rando_non_looping_songs = non_looping_songs.copy() + world.random.shuffle(rando_non_looping_songs) + non_looping_songs = dict(zip(non_looping_songs, rando_non_looping_songs)) + non_looping_songs[0x72] = 0x72 + # Figure out the new fade-in songs if applicable + for vanilla_song in looping_songs: + if rom_looping_music_fade_ins[vanilla_song]: + if rom_looping_music_fade_ins[looping_songs[vanilla_song]]: + fade_in_songs[rom_looping_music_fade_ins[vanilla_song]] = rom_looping_music_fade_ins[ + looping_songs[vanilla_song]] + else: + fade_in_songs[rom_looping_music_fade_ins[vanilla_song]] = looping_songs[vanilla_song] + # Build the new music array + for i in range(0x10, len(music_array)): + if i in looping_songs.keys(): + music_array[i] = looping_songs[i] + elif i in non_looping_songs.keys(): + music_array[i] = non_looping_songs[i] + else: + music_array[i] = fade_in_songs[i] + del (music_array[0x00: 0x10]) + + # Convert the music array into a data dict + music_offsets = {} + for i in range(len(music_array)): + music_offsets[0xBFCD30 + i] = music_array[i] + + return music_offsets + + +def randomize_shop_prices(world: "CV64World") -> Dict[int, int]: + """Randomize the shop prices based on the minimum and maximum values chosen. + The minimum price will adjust if it's higher than the max.""" + min_price = world.options.minimum_gold_price.value + max_price = world.options.maximum_gold_price.value + + if min_price > max_price: + min_price = world.random.randint(0, max_price) + logging.warning(f"[{world.multiworld.player_name[world.player]}] The Minimum Gold Price " + f"({world.options.minimum_gold_price.value * 100}) is higher than the " + f"Maximum Gold Price ({max_price * 100}). Lowering the minimum to: {min_price * 100}") + world.options.minimum_gold_price.value = min_price + + shop_price_list = [world.random.randint(min_price * 100, max_price * 100) for _ in range(7)] + + # Convert the price list into a data dict. Which offset it starts from depends on how many bytes it takes up. + price_dict = {} + for i in range(len(shop_price_list)): + if shop_price_list[i] <= 0xFF: + price_dict[0x103D6E + (i*12)] = 0 + price_dict[0x103D6F + (i*12)] = shop_price_list[i] + elif shop_price_list[i] <= 0xFFFF: + price_dict[0x103D6E + (i*12)] = shop_price_list[i] + else: + price_dict[0x103D6D + (i*12)] = shop_price_list[i] + + return price_dict + + +def get_countdown_numbers(options: CV64Options, active_locations: Iterable[Location]) -> Dict[int, int]: + """Figures out which Countdown numbers to increase for each Location after verifying the Item on the Location should + increase a number. + + First, check the location's info to see if it has a countdown number override. + If not, then figure it out based on the parent region's stage's position in the vanilla stage order. + If the parent region is not part of any stage (as is the case for Renon's shop), skip the location entirely.""" + countdown_list = [0 for _ in range(15)] + for loc in active_locations: + if loc.address is not None and (options.countdown == Countdown.option_all_locations or + (options.countdown == Countdown.option_majors + and loc.item.advancement)): + + countdown_number = get_location_info(loc.name, "countdown") + + if countdown_number is None: + stage = get_region_info(loc.parent_region.name, "stage") + if stage is not None: + countdown_number = vanilla_stage_order.index(stage) + + if countdown_number is not None: + countdown_list[countdown_number] += 1 + + # Convert the Countdown list into a data dict + countdown_dict = {} + for i in range(len(countdown_list)): + countdown_dict[0xBFD818 + i] = countdown_list[i] + + return countdown_dict + + +def get_location_data(world: "CV64World", active_locations: Iterable[Location]) \ + -> Tuple[Dict[int, int], List[str], List[bytearray], List[List[Union[int, str, None]]]]: + """Gets ALL the item data to go into the ROM. Item data consists of two bytes: the first dictates the appearance of + the item, the second determines what the item actually is when picked up. All items from other worlds will be AP + items that do nothing when picked up other than set their flag, and their appearance will depend on whether it's + another CV64 player's item and, if so, what item it is in their game. Ice Traps can assume the form of any item that + is progression, non-progression, or either depending on the player's settings. + + Appearance does not matter if it's one of the two NPC-given items (from either Vincent or Heinrich Meyer). For + Renon's shop items, a list containing the shop item names, descriptions, and colors will be returned alongside the + regular data.""" + + # Figure out the list of possible Ice Trap appearances to use based on the settings, first and foremost. + if world.options.ice_trap_appearance == IceTrapAppearance.option_major_only: + allowed_classifications = ["progression", "progression skip balancing"] + elif world.options.ice_trap_appearance == IceTrapAppearance.option_junk_only: + allowed_classifications = ["filler", "useful"] + else: + allowed_classifications = ["progression", "progression skip balancing", "filler", "useful"] + + trap_appearances = [] + for item in item_info: + if item_info[item]["default classification"] in allowed_classifications and item != "Ice Trap" and \ + get_item_info(item, "code") is not None: + trap_appearances.append(item) + + shop_name_list = [] + shop_desc_list = [] + shop_colors_list = [] + + location_bytes = {} + + for loc in active_locations: + # If the Location is an event, skip it. + if loc.address is None: + continue + + loc_type = get_location_info(loc.name, "type") + + # Figure out the item ID bytes to put in each Location here. Write the item itself if either it's the player's + # very own, or it belongs to an Item Link that the player is a part of. + if loc.item.player == world.player or (loc.item.player in world.multiworld.groups and + world.player in world.multiworld.groups[loc.item.player]['players']): + if loc_type not in ["npc", "shop"] and get_item_info(loc.item.name, "pickup actor id") is not None: + location_bytes[get_location_info(loc.name, "offset")] = get_item_info(loc.item.name, "pickup actor id") + else: + location_bytes[get_location_info(loc.name, "offset")] = get_item_info(loc.item.name, "code") + else: + # Make the item the unused Wooden Stake - our multiworld item. + location_bytes[get_location_info(loc.name, "offset")] = 0x11 + + # Figure out the item's appearance. If it's a CV64 player's item, change the multiworld item's model to + # match what it is. Otherwise, change it to an Archipelago progress or not progress icon. The model "change" + # has to be applied to even local items because this is how the game knows to count it on the Countdown. + if loc.item.game == "Castlevania 64": + location_bytes[get_location_info(loc.name, "offset") - 1] = get_item_info(loc.item.name, "code") + elif loc.item.advancement: + location_bytes[get_location_info(loc.name, "offset") - 1] = 0x11 # Wooden Stakes are majors + else: + location_bytes[get_location_info(loc.name, "offset") - 1] = 0x12 # Roses are minors + + # If it's a PermaUp, change the item's model to a big PowerUp no matter what. + if loc.item.game == "Castlevania 64" and loc.item.code == 0x10C + base_id: + location_bytes[get_location_info(loc.name, "offset") - 1] = 0x0B + + # If it's an Ice Trap, change its model to one of the appearances we determined before. + # Unless it's an NPC item, in which case use the Ice Trap's regular ID so that it won't decrement the majors + # Countdown due to how I set up the NPC items to work. + if loc.item.game == "Castlevania 64" and loc.item.code == 0x12 + base_id: + if loc_type == "npc": + location_bytes[get_location_info(loc.name, "offset") - 1] = 0x12 + else: + location_bytes[get_location_info(loc.name, "offset") - 1] = \ + get_item_info(world.random.choice(trap_appearances), "code") + # If we chose a PermaUp as our trap appearance, change it to its actual in-game ID of 0x0B. + if location_bytes[get_location_info(loc.name, "offset") - 1] == 0x10C: + location_bytes[get_location_info(loc.name, "offset") - 1] = 0x0B + + # Apply the invisibility variable depending on the "invisible items" setting. + if (world.options.invisible_items == InvisibleItems.option_vanilla and loc_type == "inv") or \ + (world.options.invisible_items == InvisibleItems.option_hide_all and loc_type not in ["npc", "shop"]): + location_bytes[get_location_info(loc.name, "offset") - 1] += 0x80 + elif world.options.invisible_items == InvisibleItems.option_chance and loc_type not in ["npc", "shop"]: + invisible = world.random.randint(0, 1) + if invisible: + location_bytes[get_location_info(loc.name, "offset") - 1] += 0x80 + + # If it's an Axe or Cross in a higher freestanding location, lower it into grab range. + # KCEK made these spawn 3.2 units higher for some reason. + if loc.address & 0xFFF in rom_axe_cross_lower_values and loc.item.code & 0xFF in [0x0F, 0x10]: + location_bytes[rom_axe_cross_lower_values[loc.address & 0xFFF][0]] = \ + rom_axe_cross_lower_values[loc.address & 0xFFF][1] + + # Figure out the list of shop names, descriptions, and text colors here. + if loc.parent_region.name != rname.renon: + continue + + shop_name = loc.item.name + if len(shop_name) > 18: + shop_name = shop_name[0:18] + shop_name_list.append(shop_name) + + if loc.item.player == world.player: + shop_desc_list.append([get_item_info(loc.item.name, "code"), None]) + elif loc.item.game == "Castlevania 64": + shop_desc_list.append([get_item_info(loc.item.name, "code"), + world.multiworld.get_player_name(loc.item.player)]) + else: + if loc.item.game == "DLCQuest" and loc.item.name in ["DLC Quest: Coin Bundle", + "Live Freemium or Die: Coin Bundle"]: + if getattr(world.multiworld.worlds[loc.item.player].options, "coinbundlequantity") == 1: + shop_desc_list.append(["dlc coin", world.multiworld.get_player_name(loc.item.player)]) + shop_colors_list.append(get_item_text_color(loc)) + continue + + if loc.item.advancement: + shop_desc_list.append(["prog", world.multiworld.get_player_name(loc.item.player)]) + elif loc.item.classification == ItemClassification.useful: + shop_desc_list.append(["useful", world.multiworld.get_player_name(loc.item.player)]) + elif loc.item.classification == ItemClassification.trap: + shop_desc_list.append(["trap", world.multiworld.get_player_name(loc.item.player)]) + else: + shop_desc_list.append(["common", world.multiworld.get_player_name(loc.item.player)]) + + shop_colors_list.append(get_item_text_color(loc)) + + return location_bytes, shop_name_list, shop_colors_list, shop_desc_list + + +def get_loading_zone_bytes(options: CV64Options, starting_stage: str, + active_stage_exits: Dict[str, Dict[str, Union[str, int, None]]]) -> Dict[int, int]: + """Figure out all the bytes for loading zones and map transitions based on which stages are where in the exit data. + The same data was used earlier in figuring out the logic. Map transitions consist of two major components: which map + to send the player to, and which spot within the map to spawn the player at.""" + + # Write the byte for the starting stage to send the player to after the intro narration. + loading_zone_bytes = {0xB73308: get_stage_info(starting_stage, "start map id")} + + for stage in active_stage_exits: + + # Start loading zones + # If the start zone is the start of the line, have it simply refresh the map. + if active_stage_exits[stage]["prev"] == "Menu": + loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = 0xFF + loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] = 0x00 + elif active_stage_exits[stage]["prev"]: + loading_zone_bytes[get_stage_info(stage, "startzone map offset")] = \ + get_stage_info(active_stage_exits[stage]["prev"], "end map id") + loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] = \ + get_stage_info(active_stage_exits[stage]["prev"], "end spawn id") + + # Change CC's end-spawn ID to put you at Carrie's exit if appropriate + if active_stage_exits[stage]["prev"] == rname.castle_center: + if options.character_stages == CharacterStages.option_carrie_only or \ + active_stage_exits[rname.castle_center]["alt"] == stage: + loading_zone_bytes[get_stage_info(stage, "startzone spawn offset")] += 1 + + # End loading zones + if active_stage_exits[stage]["next"]: + loading_zone_bytes[get_stage_info(stage, "endzone map offset")] = \ + get_stage_info(active_stage_exits[stage]["next"], "start map id") + loading_zone_bytes[get_stage_info(stage, "endzone spawn offset")] = \ + get_stage_info(active_stage_exits[stage]["next"], "start spawn id") + + # Alternate end loading zones + if active_stage_exits[stage]["alt"]: + loading_zone_bytes[get_stage_info(stage, "altzone map offset")] = \ + get_stage_info(active_stage_exits[stage]["alt"], "start map id") + loading_zone_bytes[get_stage_info(stage, "altzone spawn offset")] = \ + get_stage_info(active_stage_exits[stage]["alt"], "start spawn id") + + return loading_zone_bytes + + +def get_start_inventory_data(player: int, options: CV64Options, precollected_items: List[Item]) -> Dict[int, int]: + """Calculate and return the starting inventory values. Not every Item goes into the menu inventory, so everything + has to be handled appropriately.""" + start_inventory_data = {0xBFD867: 0, # Jewels + 0xBFD87B: 0, # PowerUps + 0xBFD883: 0, # Sub-weapon + 0xBFD88B: 0} # Ice Traps + + inventory_items_array = [0 for _ in range(35)] + total_money = 0 + + items_max = 10 + + # Raise the items max if Increase Item Limit is enabled. + if options.increase_item_limit: + items_max = 99 + + for item in precollected_items: + if item.player != player: + continue + + inventory_offset = get_item_info(item.name, "inventory offset") + sub_equip_id = get_item_info(item.name, "sub equip id") + # Starting inventory items + if inventory_offset is not None: + inventory_items_array[inventory_offset] += 1 + if inventory_items_array[inventory_offset] > items_max and "Special" not in item.name: + inventory_items_array[inventory_offset] = items_max + if item.name == iname.permaup: + if inventory_items_array[inventory_offset] > 2: + inventory_items_array[inventory_offset] = 2 + # Starting sub-weapon + elif sub_equip_id is not None: + start_inventory_data[0xBFD883] = sub_equip_id + # Starting PowerUps + elif item.name == iname.powerup: + start_inventory_data[0xBFD87B] += 1 + if start_inventory_data[0xBFD87B] > 2: + start_inventory_data[0xBFD87B] = 2 + # Starting Gold + elif "GOLD" in item.name: + total_money += int(item.name[0:4]) + if total_money > 99999: + total_money = 99999 + # Starting Jewels + elif "jewel" in item.name: + if "L" in item.name: + start_inventory_data[0xBFD867] += 10 + else: + start_inventory_data[0xBFD867] += 5 + if start_inventory_data[0xBFD867] > 99: + start_inventory_data[0xBFD867] = 99 + # Starting Ice Traps + else: + start_inventory_data[0xBFD88B] += 1 + if start_inventory_data[0xBFD88B] > 0xFF: + start_inventory_data[0xBFD88B] = 0xFF + + # Convert the inventory items into data. + for i in range(len(inventory_items_array)): + start_inventory_data[0xBFE518 + i] = inventory_items_array[i] + + # Convert the starting money into data. Which offset it starts from depends on how many bytes it takes up. + if total_money <= 0xFF: + start_inventory_data[0xBFE517] = total_money + elif total_money <= 0xFFFF: + start_inventory_data[0xBFE516] = total_money + else: + start_inventory_data[0xBFE515] = total_money + + return start_inventory_data + + +def get_item_text_color(loc: Location) -> bytearray: + if loc.item.advancement: + return bytearray([0xA2, 0x0C]) + elif loc.item.classification == ItemClassification.useful: + return bytearray([0xA2, 0x0A]) + elif loc.item.classification == ItemClassification.trap: + return bytearray([0xA2, 0x0B]) + else: + return bytearray([0xA2, 0x02]) diff --git a/worlds/cv64/client.py b/worlds/cv64/client.py new file mode 100644 index 0000000000..ff9c79f578 --- /dev/null +++ b/worlds/cv64/client.py @@ -0,0 +1,207 @@ +from typing import TYPE_CHECKING, Set +from .locations import base_id +from .text import cv64_text_wrap, cv64_string_to_bytearray + +from NetUtils import ClientStatus +import worlds._bizhawk as bizhawk +import base64 +from worlds._bizhawk.client import BizHawkClient + +if TYPE_CHECKING: + from worlds._bizhawk.context import BizHawkClientContext + + +class Castlevania64Client(BizHawkClient): + game = "Castlevania 64" + system = "N64" + patch_suffix = ".apcv64" + self_induced_death = False + received_deathlinks = 0 + death_causes = [] + currently_shopping = False + local_checked_locations: Set[int] + + def __init__(self) -> None: + super().__init__() + self.local_checked_locations = set() + + async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: + from CommonClient import logger + + try: + # Check ROM name/patch version + game_names = await bizhawk.read(ctx.bizhawk_ctx, [(0x20, 0x14, "ROM"), (0xBFBFD0, 12, "ROM")]) + if game_names[0].decode("ascii") != "CASTLEVANIA ": + return False + if game_names[1] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00': + logger.info("ERROR: You appear to be running an unpatched version of Castlevania 64. " + "You need to generate a patch file and use it to create a patched ROM.") + return False + if game_names[1].decode("ascii") != "ARCHIPELAGO1": + logger.info("ERROR: The patch file used to create this ROM is not compatible with " + "this client. Double check your client version against the version being " + "used by the generator.") + return False + except UnicodeDecodeError: + return False + except bizhawk.RequestFailedError: + return False # Should verify on the next pass + + ctx.game = self.game + ctx.items_handling = 0b001 + ctx.want_slot_data = False + ctx.watcher_timeout = 0.125 + return True + + async def set_auth(self, ctx: "BizHawkClientContext") -> None: + auth_raw = (await bizhawk.read(ctx.bizhawk_ctx, [(0xBFBFE0, 16, "ROM")]))[0] + ctx.auth = base64.b64encode(auth_raw).decode("utf-8") + + def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None: + if cmd != "Bounced": + return + if "tags" not in args: + return + if "DeathLink" in args["tags"] and args["data"]["source"] != ctx.slot_info[ctx.slot].name: + self.received_deathlinks += 1 + if "cause" in args["data"]: + cause = args["data"]["cause"] + if len(cause) > 88: + cause = cause[0x00:0x89] + else: + cause = f"{args['data']['source']} killed you!" + self.death_causes.append(cause) + + async def game_watcher(self, ctx: "BizHawkClientContext") -> None: + + try: + read_state = await bizhawk.read(ctx.bizhawk_ctx, [(0x342084, 4, "RDRAM"), + (0x389BDE, 6, "RDRAM"), + (0x389BE4, 224, "RDRAM"), + (0x389EFB, 1, "RDRAM"), + (0x389EEF, 1, "RDRAM"), + (0xBFBFDE, 2, "ROM")]) + + game_state = int.from_bytes(read_state[0], "big") + save_struct = read_state[2] + written_deathlinks = int.from_bytes(bytearray(read_state[1][4:6]), "big") + deathlink_induced_death = int.from_bytes(bytearray(read_state[1][0:1]), "big") + cutscene_value = int.from_bytes(read_state[3], "big") + current_menu = int.from_bytes(read_state[4], "big") + num_received_items = int.from_bytes(bytearray(save_struct[0xDA:0xDC]), "big") + rom_flags = int.from_bytes(read_state[5], "big") + + # Make sure we are in the Gameplay or Credits states before detecting sent locations and/or DeathLinks. + # If we are in any other state, such as the Game Over state, set self_induced_death to false, so we can once + # again send a DeathLink once we are back in the Gameplay state. + if game_state not in [0x00000002, 0x0000000B]: + self.self_induced_death = False + return + + # Enable DeathLink if the bit for it is set in our ROM flags. + if "DeathLink" not in ctx.tags and rom_flags & 0x0100: + await ctx.update_death_link(True) + + # Scout the Renon shop locations if the shopsanity flag is written in the ROM. + if rom_flags & 0x0001 and ctx.locations_info == {}: + await ctx.send_msgs([{ + "cmd": "LocationScouts", + "locations": [base_id + i for i in range(0x1C8, 0x1CF)], + "create_as_hint": 0 + }]) + + # Send a DeathLink if we died on our own independently of receiving another one. + if "DeathLink" in ctx.tags and save_struct[0xA4] & 0x80 and not self.self_induced_death and not \ + deathlink_induced_death: + self.self_induced_death = True + if save_struct[0xA4] & 0x08: + # Special death message for dying while having the Vamp status. + await ctx.send_death(f"{ctx.player_names[ctx.slot]} became a vampire and drank your blood!") + else: + await ctx.send_death(f"{ctx.player_names[ctx.slot]} perished. Dracula has won!") + + # Write any DeathLinks received along with the corresponding death cause starting with the oldest. + # To minimize Bizhawk Write jank, the DeathLink write will be prioritized over the item received one. + if self.received_deathlinks and not self.self_induced_death and not written_deathlinks: + death_text, num_lines = cv64_text_wrap(self.death_causes[0], 96) + await bizhawk.write(ctx.bizhawk_ctx, [(0x389BE3, [0x01], "RDRAM"), + (0x389BDF, [0x11], "RDRAM"), + (0x18BF98, bytearray([0xA2, 0x0B]) + + cv64_string_to_bytearray(death_text, False), "RDRAM"), + (0x18C097, [num_lines], "RDRAM")]) + self.received_deathlinks -= 1 + del self.death_causes[0] + else: + # If the game hasn't received all items yet, the received item struct doesn't contain an item, the + # current number of received items still matches what we read before, and there are no open text boxes, + # then fill it with the next item and write the "item from player" text in its buffer. The game will + # increment the number of received items on its own. + if num_received_items < len(ctx.items_received): + next_item = ctx.items_received[num_received_items] + if next_item.flags & 0b001: + text_color = bytearray([0xA2, 0x0C]) + elif next_item.flags & 0b010: + text_color = bytearray([0xA2, 0x0A]) + elif next_item.flags & 0b100: + text_color = bytearray([0xA2, 0x0B]) + else: + text_color = bytearray([0xA2, 0x02]) + received_text, num_lines = cv64_text_wrap(f"{ctx.item_names[next_item.item]}\n" + f"from {ctx.player_names[next_item.player]}", 96) + await bizhawk.guarded_write(ctx.bizhawk_ctx, + [(0x389BE1, [next_item.item & 0xFF], "RDRAM"), + (0x18C0A8, text_color + cv64_string_to_bytearray(received_text, False), + "RDRAM"), + (0x18C1A7, [num_lines], "RDRAM")], + [(0x389BE1, [0x00], "RDRAM"), # Remote item reward buffer + (0x389CBE, save_struct[0xDA:0xDC], "RDRAM"), # Received items + (0x342891, [0x02], "RDRAM")]) # Textbox state + + flag_bytes = bytearray(save_struct[0x00:0x44]) + bytearray(save_struct[0x90:0x9F]) + locs_to_send = set() + + # Check for set location flags. + for byte_i, byte in enumerate(flag_bytes): + for i in range(8): + and_value = 0x80 >> i + if byte & and_value != 0: + flag_id = byte_i * 8 + i + + location_id = flag_id + base_id + if location_id in ctx.server_locations: + locs_to_send.add(location_id) + + # Send locations if there are any to send. + if locs_to_send != self.local_checked_locations: + self.local_checked_locations = locs_to_send + + if locs_to_send is not None: + await ctx.send_msgs([{ + "cmd": "LocationChecks", + "locations": list(locs_to_send) + }]) + + # Check the menu value to see if we are in Renon's shop, and set currently_shopping to True if we are. + if current_menu == 0xA: + self.currently_shopping = True + + # If we are currently shopping, and the current menu value is 0 (meaning we just left the shop), hint the + # un-bought shop locations that have progression. + if current_menu == 0 and self.currently_shopping: + await ctx.send_msgs([{ + "cmd": "LocationScouts", + "locations": [loc for loc, n_item in ctx.locations_info.items() if n_item.flags & 0b001], + "create_as_hint": 2 + }]) + self.currently_shopping = False + + # Send game clear if we're in either any ending cutscene or the credits state. + if not ctx.finished_game and (0x26 <= int(cutscene_value) <= 0x2E or game_state == 0x0000000B): + await ctx.send_msgs([{ + "cmd": "StatusUpdate", + "status": ClientStatus.CLIENT_GOAL + }]) + + except bizhawk.RequestFailedError: + # Exit handler and return to main loop to reconnect. + pass diff --git a/worlds/cv64/data/APLogo-LICENSE.txt b/worlds/cv64/data/APLogo-LICENSE.txt new file mode 100644 index 0000000000..69d1e3ecd1 --- /dev/null +++ b/worlds/cv64/data/APLogo-LICENSE.txt @@ -0,0 +1,3 @@ +The Archipelago Logo is © 2022 by Krista Corkos and Christopher Wilson is licensed under Attribution-NonCommercial 4.0 International. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/4.0/ + +Logo modified by Liquid Cat to fit artstyle and uses within the mod. diff --git a/worlds/cv64/data/ap_icons.bin b/worlds/cv64/data/ap_icons.bin new file mode 100644 index 0000000000000000000000000000000000000000..0a4129ac409ceb3a7126a8f04fe793d27bb0d2bc GIT binary patch literal 4666 zcmeH}ze`(D6vt1}@M2rrS`jg+CS8O=|AA5{k#5DgW1&>U(p7XQ9i*#&fRlrW&<+lT z?sh12u*AWkbr8j1+d)Xv;7~9*J>PqBa$kNupP-$*2XgLv_kPa#-t%6P)1M872m5Gf zxGxyZ5oJdy$lVNxazT(B$`U2BKS{>{_+b9)o_Y93N$-tBh5$isF9AGn%sSJ%8UxBPnk$v{ z%z8%eF_*{7<6N;-+p|@j+@9RFJ@aGpV_S{y6Eo;yzL*DTW;@M;p3Y2XK$>X|@a?oH zkn#Gd8s?q!{8%(^G<-DAm9TbxujdE1dPj|UtLn8ctu3wj^Sk<2?A-9E9=OdFZ-`*$ zhPUcjedUOa7&vV^jX1ckF~GJyA4I`D9je>swwbMWvZ}(g)aP2YAI%)iY_8PjqB$P} z`Yk8C@^NXLq&^qsF^``eI&e}S18n^4b`CH$?B4l%>r+Z}EkOfB2SlU&BTv5X{Qm2{ zpF1Lfq!IZhP0a!=P>F6i5hc3e`kIr$kt=+Qxh8BtuNIEQr?0+!{-YtaDn4DPkmWfZ zkPWi_2kYriZ%Q;Maqdd%GtnpfM372KpcDlGB`FmojdMC==UyZ3#W|}v{8_Vrx$Il) zKihnC9Z@qKmwgNBaM`znzjc>=3px+^d32`#;QD=v-?vcW|1*Aar>55 z-gh4Ivd8LM!n~*Pmbl-IQ~Q=M-}zhQVz`fPeT&xJ*Uw#tU(n_p|M=bd4xb%Sgzq_e z_AR03*Ate7aL-}=OQUab5u$QyT$G`E-x94ntjgE3rPdMW4%2bx))$;l-!l8)fAuZ@ E08)QCxc~qF literal 0 HcmV?d00001 diff --git a/worlds/cv64/data/ename.py b/worlds/cv64/data/ename.py new file mode 100644 index 0000000000..26cd7151de --- /dev/null +++ b/worlds/cv64/data/ename.py @@ -0,0 +1,71 @@ +forest_dbridge_gate = "Descending bridge gate" +forest_werewolf_gate = "Werewolf gate" +forest_end = "Dracula's drawbridge" + +cw_portcullis_c = "Central portcullis" +cw_lt_skip = "Do Left Tower Skip" +cw_lt_door = "Left Tower door" +cw_end = "End portcullis" + +villa_dog_gates = "Front dog gates" +villa_snipe_dogs = "Orb snipe the dogs" +villa_to_storeroom = "To Storeroom door" +villa_to_archives = "To Archives door" +villa_renon = "Villa contract" +villa_to_maze = "To maze gate" +villa_from_storeroom = "From Storeroom door" +villa_from_maze = "From maze gate" +villa_servant_door = "Servants' door" +villa_copper_door = "Copper door" +villa_copper_skip = "Get Copper Skip" +villa_bridge_door = "From bridge door" +villa_end_r = "Villa Reinhardt (daytime) exit" +villa_end_c = "Villa Carrie (nighttime) exit" + +tunnel_start_renon = "Tunnel start contract" +tunnel_gondolas = "Gondola ride" +tunnel_end_renon = "Tunnel end contract" +tunnel_end = "End Tunnel door" + +uw_final_waterfall = "Final waterfall" +uw_waterfall_skip = "Do Waterfall Skip" +uw_renon = "Underground Waterway contract" +uw_end = "End Waterway door" + +cc_tc_door = "Torture Chamber door" +cc_renon = "Castle Center contract" +cc_lower_wall = "Lower sealed cracked wall" +cc_upper_wall = "Upper cracked wall" +cc_elevator = "Activate crystal and ride elevator" +cc_exit_r = "Castle Center Reinhardt (Medusa Head) exit" +cc_exit_c = "Castle Center Carrie (Ghost) exit" + +dt_start = "Duel Tower start passage" +dt_end = "Duel Tower end passage" + +toe_start = "Tower of Execution start passage" +toe_gate = "Execution gate" +toe_gate_skip = "Just jump past the gate from above, bro!" +toe_end = "Tower of Execution end staircase" + +tosci_start = "Tower of Science start passage" +tosci_key1_door = "Science Door 1" +tosci_to_key2_door = "To Science Door 2" +tosci_from_key2_door = "From Science Door 2" +tosci_key3_door = "Science Door 3" +tosci_end = "Tower of Science end passage" + +tosor_start = "Tower of Sorcery start passage" +tosor_end = "Tower of Sorcery end passage" + +roc_gate = "Defeat boss gate" + +ct_to_door1 = "To Clocktower Door 1" +ct_from_door1 = "From Clocktower Door 1" +ct_to_door2 = "To Clocktower Door 2" +ct_from_door2 = "From Clocktower Door 2" +ct_renon = "Clock Tower contract" +ct_door_3 = "Clocktower Door 3" + +ck_slope_jump = "Slope Jump to boss tower" +ck_drac_door = "Dracula's door" diff --git a/worlds/cv64/data/iname.py b/worlds/cv64/data/iname.py new file mode 100644 index 0000000000..9b9e225ca5 --- /dev/null +++ b/worlds/cv64/data/iname.py @@ -0,0 +1,49 @@ +# Items +white_jewel = "White jewel" +special_one = "Special1" +special_two = "Special2" +red_jewel_s = "Red jewel(S)" +red_jewel_l = "Red jewel(L)" +roast_chicken = "Roast chicken" +roast_beef = "Roast beef" +healing_kit = "Healing kit" +purifying = "Purifying" +cure_ampoule = "Cure ampoule" +pot_pourri = "pot-pourri" +powerup = "PowerUp" +permaup = "PermaUp" +holy_water = "Holy water" +cross = "Cross" +axe = "Axe" +knife = "Knife" +wooden_stake = "Wooden stake" +rose = "Rose" +ice_trap = "Ice Trap" +the_contract = "The contract" +engagement_ring = "engagement ring" +magical_nitro = "Magical Nitro" +mandragora = "Mandragora" +sun_card = "Sun card" +moon_card = "Moon card" +incandescent_gaze = "Incandescent gaze" +five_hundred_gold = "500 GOLD" +three_hundred_gold = "300 GOLD" +one_hundred_gold = "100 GOLD" +archives_key = "Archives Key" +left_tower_key = "Left Tower Key" +storeroom_key = "Storeroom Key" +garden_key = "Garden Key" +copper_key = "Copper Key" +chamber_key = "Chamber Key" +execution_key = "Execution Key" +science_key1 = "Science Key1" +science_key2 = "Science Key2" +science_key3 = "Science Key3" +clocktower_key1 = "Clocktower Key1" +clocktower_key2 = "Clocktower Key2" +clocktower_key3 = "Clocktower Key3" + +trophy = "Trophy" +crystal = "Crystal" + +victory = "The Count Downed" diff --git a/worlds/cv64/data/lname.py b/worlds/cv64/data/lname.py new file mode 100644 index 0000000000..09db86b380 --- /dev/null +++ b/worlds/cv64/data/lname.py @@ -0,0 +1,479 @@ +# Forest of Silence main locations +forest_pillars_right = "Forest of Silence: Grab practice pillars - Right" +forest_pillars_top = "Forest of Silence: Grab practice pillars - Top" +forest_king_skeleton = "Forest of Silence: King Skeleton's bridge" +forest_lgaz_in = "Forest of Silence: Moon gazebo inside" +forest_lgaz_top = "Forest of Silence: Moon gazebo roof" +forest_hgaz_in = "Forest of Silence: Sun gazebo inside" +forest_hgaz_top = "Forest of Silence: Sun gazebo roof" +forest_weretiger_sw = "Forest of Silence: Were-tiger switch" +forest_weretiger_gate = "Forest of Silence: Dirge maiden gate" +forest_dirge_tomb_u = "Forest of Silence: Dirge maiden crypt - Upper" +forest_dirge_plaque = "Forest of Silence: Dirge maiden pedestal plaque" +forest_corpse_save = "Forest of Silence: Tri-corpse save junction" +forest_dbridge_wall = "Forest of Silence: Descending bridge wall side" +forest_dbridge_sw = "Forest of Silence: Descending bridge switch side" +forest_dbridge_gate_r = "Forest of Silence: Tri-corpse gate - Right" +forest_dbridge_tomb_uf = "Forest of Silence: Three-crypt plaza main path crypt - Upper-front" +forest_bface_tomb_lf = "Forest of Silence: Three-crypt plaza back-facing crypt - Lower-front" +forest_bface_tomb_u = "Forest of Silence: Three-crypt plaza back-facing crypt - Upper" +forest_ibridge = "Forest of Silence: Invisible bridge platform" +forest_werewolf_tomb_r = "Forest of Silence: Werewolf crypt - Right" +forest_werewolf_plaque = "Forest of Silence: Werewolf statue plaque" +forest_werewolf_tree = "Forest of Silence: Werewolf path near tree" +forest_final_sw = "Forest of Silence: Three-crypt plaza switch" + +# Forest of Silence empty breakables +forest_dirge_tomb_l = "Forest of Silence: Dirge maiden crypt - Lower" +forest_dbridge_tomb_l = "Forest of Silence: Three-crypt plaza main path crypt - Lower" +forest_dbridge_tomb_ur = "Forest of Silence: Three-crypt plaza main path crypt - Upper-rear" +forest_bface_tomb_lr = "Forest of Silence: Three-crypt plaza back-facing crypt - Lower-rear" +forest_werewolf_tomb_lf = "Forest of Silence: Werewolf crypt - Left-front" +forest_werewolf_tomb_lr = "Forest of Silence: Werewolf crypt - Left-rear" + +# Forest of Silence 3-hit breakables +forest_dirge_rock1 = "Forest of Silence: Dirge maiden rock - Item 1" +forest_dirge_rock2 = "Forest of Silence: Dirge maiden rock - Item 2" +forest_dirge_rock3 = "Forest of Silence: Dirge maiden rock - Item 3" +forest_dirge_rock4 = "Forest of Silence: Dirge maiden rock - Item 4" +forest_dirge_rock5 = "Forest of Silence: Dirge maiden rock - Item 5" +forest_bridge_rock1 = "Forest of Silence: Bat archway rock - Item 1" +forest_bridge_rock2 = "Forest of Silence: Bat archway rock - Item 2" +forest_bridge_rock3 = "Forest of Silence: Bat archway rock - Item 3" +forest_bridge_rock4 = "Forest of Silence: Bat archway rock - Item 4" + +# Forest of Silence sub-weapons +forest_pillars_left = "Forest of Silence: Grab practice pillars - Left" +forest_dirge_ped = "Forest of Silence: Dirge maiden pedestal" +forest_dbridge_gate_l = "Forest of Silence: Tri-corpse gate - Left" +forest_werewolf_island = "Forest of Silence: Werewolf path island switch platforms" + + +# Castle Wall main locations +cw_ground_middle = "Castle Wall: Ground gatehouse - Middle" +cw_rrampart = "Castle Wall: Central rampart near right tower" +cw_lrampart = "Castle Wall: Central rampart near left tower" +cw_dragon_sw = "Castle Wall: White Dragons switch door" +cw_drac_sw = "Castle Wall: Dracula cutscene switch door" +cw_shelf_visible = "Castle Wall: Sandbag shelf - Visible" +cw_shelf_sandbags = "Castle Wall: Sandbag shelf - Invisible" + +# Castle Wall towers main locations +cwr_bottom = "Castle Wall: Above bottom right tower door" +cwl_bottom = "Castle Wall: Above bottom left tower door" +cwl_bridge = "Castle Wall: Left tower child ledge" + +# Castle Wall 3-hit breakables +cw_save_slab1 = "Castle Wall: Central rampart savepoint slab - Item 1" +cw_save_slab2 = "Castle Wall: Central rampart savepoint slab - Item 2" +cw_save_slab3 = "Castle Wall: Central rampart savepoint slab - Item 3" +cw_save_slab4 = "Castle Wall: Central rampart savepoint slab - Item 4" +cw_save_slab5 = "Castle Wall: Central rampart savepoint slab - Item 5" +cw_drac_slab1 = "Castle Wall: Dracula cutscene switch slab - Item 1" +cw_drac_slab2 = "Castle Wall: Dracula cutscene switch slab - Item 2" +cw_drac_slab3 = "Castle Wall: Dracula cutscene switch slab - Item 3" +cw_drac_slab4 = "Castle Wall: Dracula cutscene switch slab - Item 4" +cw_drac_slab5 = "Castle Wall: Dracula cutscene switch slab - Item 5" + +# Castle Wall sub-weapons +cw_ground_left = "Castle Wall: Ground gatehouse - Left" +cw_ground_right = "Castle Wall: Ground gatehouse - Right" +cw_shelf_torch = "Castle Wall: Sandbag shelf floor torch" +cw_pillar = "Castle Wall: Central rampart broken pillar" + + +# Villa front yard main locations +villafy_outer_gate_l = "Villa: Outer front gate - Left" +villafy_outer_gate_r = "Villa: Outer front gate - Right" +villafy_inner_gate = "Villa: Inner front gate dog food" +villafy_dog_platform = "Villa: Outer front gate platform" +villafy_gate_marker = "Villa: Front yard cross grave near gates" +villafy_villa_marker = "Villa: Front yard cross grave near porch" +villafy_tombstone = "Villa: Front yard visitor's tombstone" +villafy_fountain_fl = "Villa: Midnight fountain - Front-left" +villafy_fountain_fr = "Villa: Midnight fountain - Front-right" +villafy_fountain_ml = "Villa: Midnight fountain - Middle-left" +villafy_fountain_mr = "Villa: Midnight fountain - Middle-right" +villafy_fountain_rl = "Villa: Midnight fountain - Rear-left" +villafy_fountain_rr = "Villa: Midnight fountain - Rear-right" + +# Villa foyer main locations +villafo_sofa = "Villa: Foyer sofa" +villafo_pot_r = "Villa: Foyer upper-right pot" +villafo_pot_l = "Villa: Foyer upper-left pot" +villafo_rear_r = "Villa: Foyer lower level - Rear-right" +villafo_rear_l = "Villa: Foyer lower level - Rear-left" +villafo_mid_l = "Villa: Foyer lower level - Middle-left" +villafo_front_r = "Villa: Foyer lower level - Front-right" +villafo_front_l = "Villa: Foyer lower level - Front-left" +villafo_serv_ent = "Villa: Servants' entrance" + +# Villa empty breakables +villafo_mid_r = "Villa: Foyer lower level - Middle-right" + +# Villa 3-hit breakables +villafo_chandelier1 = "Villa: Foyer chandelier - Item 1" +villafo_chandelier2 = "Villa: Foyer chandelier - Item 2" +villafo_chandelier3 = "Villa: Foyer chandelier - Item 3" +villafo_chandelier4 = "Villa: Foyer chandelier - Item 4" +villafo_chandelier5 = "Villa: Foyer chandelier - Item 5" + +# Villa living area main locations +villala_hallway_stairs = "Villa: Rose garden staircase bottom" +villala_bedroom_chairs = "Villa: Bedroom near chairs" +villala_bedroom_bed = "Villa: Bedroom near bed" +villala_vincent = "Villa: Vincent" +villala_slivingroom_table = "Villa: Mary's room table" +villala_storeroom_l = "Villa: Storeroom - Left" +villala_storeroom_r = "Villa: Storeroom - Right" +villala_storeroom_s = "Villa: Storeroom statue" +villala_diningroom_roses = "Villa: Dining room rose vase" +villala_archives_table = "Villa: Archives table" +villala_archives_rear = "Villa: Archives rear corner" +villala_llivingroom_lion = "Villa: Living room lion head" +villala_llivingroom_pot_r = "Villa: Living room - Right pot" +villala_llivingroom_pot_l = "Villa: Living room - Left pot" +villala_llivingroom_light = "Villa: Living room ceiling light" +villala_llivingroom_painting = "Villa: Living room clawed painting" +villala_exit_knight = "Villa: Maze garden exit knight" + +# Villa maze main locations +villam_malus_torch = "Villa: Front maze garden - Malus start torch" +villam_malus_bush = "Villa: Front maze garden - Malus's hiding bush" +villam_frankieturf_r = "Villa: Front maze garden - Frankie's right dead-end" +villam_frankieturf_l = "Villa: Front maze garden - Frankie's left dead-end" +villam_frankieturf_ru = "Villa: Front maze garden - Frankie's right dead-end urn" +villam_fgarden_f = "Villa: Rear maze garden - Iron Thorn Fenced area - Front" +villam_fgarden_mf = "Villa: Rear maze garden - Iron Thorn Fenced area - Mid-front" +villam_fgarden_mr = "Villa: Rear maze garden - Iron Thorn Fenced area - Mid-rear" +villam_fgarden_r = "Villa: Rear maze garden - Iron Thorn Fenced area - Rear" +villam_rplatform_de = "Villa: Rear maze garden - Viewing platform dead-end" +villam_exit_de = "Villa: Rear maze garden - Past-exit dead-end" +villam_serv_path = "Villa: Servants' path small alcove" +villam_crypt_ent = "Villa: Crypt entrance" +villam_crypt_upstream = "Villa: Crypt bridge upstream" + +# Villa crypt main locations +villac_ent_l = "Villa: Crypt - Left from entrance" +villac_ent_r = "Villa: Crypt - Right from entrance" +villac_wall_l = "Villa: Crypt - Left wall" +villac_wall_r = "Villa: Crypt - Right wall" +villac_coffin_r = "Villa: Crypt - Right of coffin" + +# Villa sub-weapons +villala_hallway_l = "Villa: Hallway near rose garden stairs - Left" +villala_hallway_r = "Villa: Hallway near rose garden stairs - Right" +villala_slivingroom_mirror = "Villa: Mary's room corner" +villala_archives_entrance = "Villa: Archives near entrance" +villam_fplatform = "Villa: Front maze garden - Viewing platform" +villam_rplatform = "Villa: Rear maze garden - Viewing platform" +villac_coffin_l = "Villa: Crypt - Left of coffin" + + +# Tunnel main locations +tunnel_landing = "Tunnel: Landing point" +tunnel_landing_rc = "Tunnel: Landing point rock crusher" +tunnel_stone_alcove_l = "Tunnel: Stepping stone alcove - Left" +tunnel_twin_arrows = "Tunnel: Twin arrow signs" +tunnel_lonesome_bucket = "Tunnel: Near lonesome bucket" +tunnel_lbucket_quag = "Tunnel: Lonesome bucket poison pit" +tunnel_lbucket_albert = "Tunnel: Lonesome bucket-Albert junction" +tunnel_albert_camp = "Tunnel: Albert's campsite" +tunnel_albert_quag = "Tunnel: Albert's poison pit" +tunnel_gondola_rc_sdoor_r = "Tunnel: Gondola rock crusher sun door - Right" +tunnel_gondola_rc_sdoor_m = "Tunnel: Gondola rock crusher sun door - Middle" +tunnel_gondola_rc = "Tunnel: Gondola rock crusher" +tunnel_rgondola_station = "Tunnel: Red gondola station" +tunnel_gondola_transfer = "Tunnel: Gondola transfer point" +tunnel_corpse_bucket_quag = "Tunnel: Corpse bucket poison pit" +tunnel_corpse_bucket_mdoor_r = "Tunnel: Corpse bucket moon door - Right" +tunnel_shovel_quag_start = "Tunnel: Shovel poison pit start" +tunnel_exit_quag_start = "Tunnel: Exit door poison pit start" +tunnel_shovel_quag_end = "Tunnel: Shovel poison pit end" +tunnel_exit_quag_end = "Tunnel: Exit door poison pit end" +tunnel_shovel = "Tunnel: Shovel" +tunnel_shovel_save = "Tunnel: Shovel zone save junction" +tunnel_shovel_mdoor_l = "Tunnel: Shovel zone moon door - Left" +tunnel_shovel_sdoor_l = "Tunnel: Shovel zone sun door - Left" +tunnel_shovel_sdoor_m = "Tunnel: Shovel zone sun door - Middle" + +# Tunnel 3-hit breakables +tunnel_arrows_rock1 = "Tunnel: Twin arrow signs rock - Item 1" +tunnel_arrows_rock2 = "Tunnel: Twin arrow signs rock - Item 2" +tunnel_arrows_rock3 = "Tunnel: Twin arrow signs rock - Item 3" +tunnel_arrows_rock4 = "Tunnel: Twin arrow signs rock - Item 4" +tunnel_arrows_rock5 = "Tunnel: Twin arrow signs rock - Item 5" +tunnel_bucket_quag_rock1 = "Tunnel: Lonesome bucket poison pit rock - Item 1" +tunnel_bucket_quag_rock2 = "Tunnel: Lonesome bucket poison pit rock - Item 2" +tunnel_bucket_quag_rock3 = "Tunnel: Lonesome bucket poison pit rock - Item 3" + +# Tunnel sub-weapons +tunnel_stone_alcove_r = "Tunnel: Stepping stone alcove - Right" +tunnel_lbucket_mdoor_l = "Tunnel: Lonesome bucket moon door" +tunnel_gondola_rc_sdoor_l = "Tunnel: Gondola rock crusher sun door - Left" +tunnel_corpse_bucket_mdoor_l = "Tunnel: Corpse bucket moon door - Left" +tunnel_shovel_mdoor_r = "Tunnel: Shovel zone moon door - Right" +tunnel_shovel_sdoor_r = "Tunnel: Shovel zone sun door - Right" + + +# Underground Waterway main locations +uw_near_ent = "Underground Waterway: Near entrance corridor" +uw_across_ent = "Underground Waterway: Across from entrance" +uw_poison_parkour = "Underground Waterway: Across poison parkour ledges" +uw_waterfall_alcove = "Underground Waterway: Waterfall alcove ledge" +uw_carrie1 = "Underground Waterway: Carrie crawlspace corridor - First left" +uw_carrie2 = "Underground Waterway: Carrie crawlspace corridor - Second left" +uw_bricks_save = "Underground Waterway: Brick platforms save corridor" +uw_above_skel_ledge = "Underground Waterway: Above skeleton crusher ledge" + +# Underground Waterway 3-hit breakables +uw_first_ledge1 = "Underground Waterway: First poison parkour ledge - Item 1" +uw_first_ledge2 = "Underground Waterway: First poison parkour ledge - Item 2" +uw_first_ledge3 = "Underground Waterway: First poison parkour ledge - Item 3" +uw_first_ledge4 = "Underground Waterway: First poison parkour ledge - Item 4" +uw_first_ledge5 = "Underground Waterway: First poison parkour ledge - Item 5" +uw_first_ledge6 = "Underground Waterway: First poison parkour ledge - Item 6" +uw_in_skel_ledge1 = "Underground Waterway: Inside skeleton crusher ledge - Item 1" +uw_in_skel_ledge2 = "Underground Waterway: Inside skeleton crusher ledge - Item 2" +uw_in_skel_ledge3 = "Underground Waterway: Inside skeleton crusher ledge - Item 3" + + +# Castle Center basement main locations +ccb_skel_hallway_ent = "Castle Center: Entrance hallway" +ccb_skel_hallway_jun = "Castle Center: Basement hallway junction" +ccb_skel_hallway_tc = "Castle Center: Torture chamber hallway" +ccb_behemoth_l_ff = "Castle Center: Behemoth arena - Left far-front torch" +ccb_behemoth_l_mf = "Castle Center: Behemoth arena - Left mid-front torch" +ccb_behemoth_l_mr = "Castle Center: Behemoth arena - Left mid-rear torch" +ccb_behemoth_l_fr = "Castle Center: Behemoth arena - Left far-rear torch" +ccb_behemoth_r_ff = "Castle Center: Behemoth arena - Right far-front torch" +ccb_behemoth_r_mf = "Castle Center: Behemoth arena - Right mid-front torch" +ccb_behemoth_r_mr = "Castle Center: Behemoth arena - Right mid-rear torch" +ccb_behemoth_r_fr = "Castle Center: Behemoth arena - Right far-rear torch" +ccb_mandrag_shelf_l = "Castle Center: Mandragora shelf - Left" +ccb_mandrag_shelf_r = "Castle Center: Mandragora shelf - Right" +ccb_torture_rack = "Castle Center: Torture chamber instrument rack" +ccb_torture_rafters = "Castle Center: Torture chamber rafters" + +# Castle Center elevator room main locations +ccelv_near_machine = "Castle Center: Near elevator room machine" +ccelv_atop_machine = "Castle Center: Atop elevator room machine" +ccelv_pipes = "Castle Center: Elevator pipe device" +ccelv_staircase = "Castle Center: Elevator room staircase" + +# Castle Center factory floor main locations +ccff_redcarpet_knight = "Castle Center: Red carpet hall knight" +ccff_gears_side = "Castle Center: Gear room side" +ccff_gears_mid = "Castle Center: Gear room center" +ccff_gears_corner = "Castle Center: Gear room corner" +ccff_lizard_knight = "Castle Center: Lizard locker knight" +ccff_lizard_pit = "Castle Center: Lizard locker room near pit" +ccff_lizard_corner = "Castle Center: Lizard locker room corner" + +# Castle Center lizard lab main locations +ccll_brokenstairs_floor = "Castle Center: Broken staircase floor" +ccll_brokenstairs_knight = "Castle Center: Broken staircase knight" +ccll_brokenstairs_save = "Castle Center: Above broken staircase savepoint" +ccll_glassknight_l = "Castle Center: Stained Glass Knight room - Left" +ccll_glassknight_r = "Castle Center: Stained Glass Knight room - Right" +ccll_butlers_door = "Castle Center: Butler bros. room near door" +ccll_butlers_side = "Castle Center: Butler bros. room inner" +ccll_cwhall_butlerflames_past = "Castle Center: Past butler room flamethrowers" +ccll_cwhall_flamethrower = "Castle Center: Inside cracked wall hallway flamethrower" +ccll_cwhall_cwflames = "Castle Center: Past upper cracked wall flamethrowers" +ccll_cwhall_wall = "Castle Center: Inside upper cracked wall" +ccll_heinrich = "Castle Center: Heinrich Meyer" + +# Castle Center library main locations +ccl_bookcase = "Castle Center: Library bookshelf" + +# Castle Center invention area main locations +ccia_nitro_crates = "Castle Center: Nitro room crates" +ccia_nitro_shelf_h = "Castle Center: Magical Nitro shelf - Heinrich side" +ccia_nitro_shelf_i = "Castle Center: Magical Nitro shelf - Invention side" +ccia_nitrohall_torch = "Castle Center: Past nitro room flamethrowers" +ccia_nitrohall_flamethrower = "Castle Center: Inside nitro hallway flamethrower" +ccia_inventions_crusher = "Castle Center: Invention room spike crusher door" +ccia_inventions_maids = "Castle Center: Invention room maid sisters door" +ccia_inventions_round = "Castle Center: Invention room round machine" +ccia_inventions_famicart = "Castle Center: Invention room giant Famicart" +ccia_inventions_zeppelin = "Castle Center: Invention room zeppelin" +ccia_maids_outer = "Castle Center: Maid sisters room outer table" +ccia_maids_inner = "Castle Center: Maid sisters room inner table" +ccia_maids_vase = "Castle Center: Maid sisters room vase" +ccia_stairs_knight = "Castle Center: Hell Knight landing corner knight" + +# Castle Center sub-weapons +ccb_skel_hallway_ba = "Castle Center: Behemoth arena hallway" +ccelv_switch = "Castle Center: Near elevator switch" +ccff_lizard_near_knight = "Castle Center: Near lizard locker knight" + +# Castle Center lizard lockers +ccff_lizard_locker_nfr = "Castle Center: Far-right near-side lizard locker" +ccff_lizard_locker_nmr = "Castle Center: Mid-right near-side lizard locker" +ccff_lizard_locker_nml = "Castle Center: Mid-left near-side lizard locker" +ccff_lizard_locker_nfl = "Castle Center: Far-left near-side lizard locker" +ccff_lizard_locker_fl = "Castle Center: Left far-side lizard locker" +ccff_lizard_locker_fr = "Castle Center: Right far-side lizard locker" + +# Castle Center 3-hit breakables +ccb_behemoth_crate1 = "Castle Center: Behemoth arena crate - Item 1" +ccb_behemoth_crate2 = "Castle Center: Behemoth arena crate - Item 2" +ccb_behemoth_crate3 = "Castle Center: Behemoth arena crate - Item 3" +ccb_behemoth_crate4 = "Castle Center: Behemoth arena crate - Item 4" +ccb_behemoth_crate5 = "Castle Center: Behemoth arena crate - Item 5" +ccelv_stand1 = "Castle Center: Elevator room unoccupied statue stand - Item 1" +ccelv_stand2 = "Castle Center: Elevator room unoccupied statue stand - Item 2" +ccelv_stand3 = "Castle Center: Elevator room unoccupied statue stand - Item 3" +ccff_lizard_slab1 = "Castle Center: Lizard locker room slab - Item 1" +ccff_lizard_slab2 = "Castle Center: Lizard locker room slab - Item 2" +ccff_lizard_slab3 = "Castle Center: Lizard locker room slab - Item 3" +ccff_lizard_slab4 = "Castle Center: Lizard locker room slab - Item 4" + + +# Duel Tower main locations +dt_stones_start = "Duel Tower: Stepping stone path start" +dt_werebull_arena = "Duel Tower: Above Were-bull arena" +dt_ibridge_l = "Duel Tower: Invisible bridge balcony - Left" +dt_ibridge_r = "Duel Tower: Invisible bridge balcony - Right" + +# Duel Tower sub-weapons +dt_stones_end = "Duel Tower: Stepping stone path end" + + +# Tower of Execution main locations +toe_midsavespikes_r = "Tower of Execution: Past mid-savepoint spikes - Right" +toe_midsavespikes_l = "Tower of Execution: Past mid-savepoint spikes - Left" +toe_elec_grate = "Tower of Execution: Electric grate ledge" +toe_ibridge = "Tower of Execution: Invisible bridge ledge" +toe_top = "Tower of Execution: Guillotine tower top level" +toe_keygate_l = "Tower of Execution: Key gate alcove - Left" + +# Tower of Execution 3-hit breakables +toe_ledge1 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 1" +toe_ledge2 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 2" +toe_ledge3 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 3" +toe_ledge4 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 4" +toe_ledge5 = "Tower of Execution: Pre-mid-savepoint platforms ledge - Item 5" + +# Tower of Execution sub-weapons +toe_keygate_r = "Tower of Execution: Key gate alcove - Right" + + +# Tower of Science main locations +tosci_elevator = "Tower of Science: Elevator hallway" +tosci_plain_sr = "Tower of Science: Plain sight side room" +tosci_stairs_sr = "Tower of Science: Staircase side room" +tosci_three_door_hall = "Tower of Science: Pick-a-door hallway locked middle room" +tosci_ibridge_t = "Tower of Science: Invisible bridge platform torch" +tosci_conveyor_sr = "Tower of Science: Spiky conveyor side room" +tosci_exit = "Tower of Science: Exit hallway" +tosci_key3_r = "Tower of Science: Locked Key3 room - Right" +tosci_key3_l = "Tower of Science: Locked Key3 room - Left" + +# Tower of Science 3-hit breakables +tosci_ibridge_b1 = "Tower of Science: Invisible bridge platform crate - Item 1" +tosci_ibridge_b2 = "Tower of Science: Invisible bridge platform crate - Item 2" +tosci_ibridge_b3 = "Tower of Science: Invisible bridge platform crate - Item 3" +tosci_ibridge_b4 = "Tower of Science: Invisible bridge platform crate - Item 4" +tosci_ibridge_b5 = "Tower of Science: Invisible bridge platform crate - Item 5" +tosci_ibridge_b6 = "Tower of Science: Invisible bridge platform crate - Item 6" + +# Tower of Science sub-weapons +tosci_key3_m = "Tower of Science: Locked Key3 room - Middle" + + +# Tower of Sorcery main locations +tosor_stained_tower = "Tower of Sorcery: Stained glass tower" +tosor_savepoint = "Tower of Sorcery: Mid-savepoint platform" +tosor_trickshot = "Tower of Sorcery: Trick shot from mid-savepoint platform" +tosor_yellow_bubble = "Tower of Sorcery: Above yellow bubble" +tosor_blue_platforms = "Tower of Sorcery: Above tiny blue platforms start" +tosor_side_isle = "Tower of Sorcery: Lone red platform side island" +tosor_ibridge = "Tower of Sorcery: Invisible bridge platform" + +# Room of Clocks main locations +roc_ent_l = "Room of Clocks: Left from entrance hallway" +roc_cont_r = "Room of Clocks: Right of Contract" +roc_ent_r = "Room of Clocks: Right from entrance hallway" + +# Room of Clocks sub-weapons +roc_elev_l = "Room of Clocks: Left of elevator hallway" +roc_elev_r = "Room of Clocks: Right of elevator hallway" + +# Room of Clocks empty breakables +roc_cont_l = "Room of Clocks: Left of Contract" +roc_exit = "Room of Clocks: Left of exit" + +# Clock Tower main locations +ct_gearclimb_corner = "Clock Tower: Gear climb room corner" +ct_gearclimb_side = "Clock Tower: Gear climb room side" +ct_bp_chasm_fl = "Clock Tower: Bone Pillar chasm room - Front-left" +ct_bp_chasm_fr = "Clock Tower: Bone Pillar chasm room - Front-right" +ct_bp_chasm_k = "Clock Tower: Bone Pillar chasm room key alcove" +ct_finalroom_platform = "Clock Tower: Final room key ledge" + +# Clock Tower 3-hit breakables +ct_gearclimb_battery_slab1 = "Clock Tower: Gear climb room beneath battery slab - Item 1" +ct_gearclimb_battery_slab2 = "Clock Tower: Gear climb room beneath battery slab - Item 2" +ct_gearclimb_battery_slab3 = "Clock Tower: Gear climb room beneath battery slab - Item 3" +ct_gearclimb_door_slab1 = "Clock Tower: Gear climb room beneath door slab - Item 1" +ct_gearclimb_door_slab2 = "Clock Tower: Gear climb room beneath door slab - Item 2" +ct_gearclimb_door_slab3 = "Clock Tower: Gear climb room beneath door slab - Item 3" +ct_finalroom_door_slab1 = "Clock Tower: Final room entrance slab - Item 1" +ct_finalroom_door_slab2 = "Clock Tower: Final room entrance slab - Item 2" +ct_finalroom_renon_slab1 = "Clock Tower: Renon's final offers slab - Item 1" +ct_finalroom_renon_slab2 = "Clock Tower: Renon's final offers slab - Item 2" +ct_finalroom_renon_slab3 = "Clock Tower: Renon's final offers slab - Item 3" +ct_finalroom_renon_slab4 = "Clock Tower: Renon's final offers slab - Item 4" +ct_finalroom_renon_slab5 = "Clock Tower: Renon's final offers slab - Item 5" +ct_finalroom_renon_slab6 = "Clock Tower: Renon's final offers slab - Item 6" +ct_finalroom_renon_slab7 = "Clock Tower: Renon's final offers slab - Item 7" +ct_finalroom_renon_slab8 = "Clock Tower: Renon's final offers slab - Item 8" + +# Clock Tower sub-weapons +ct_bp_chasm_rl = "Clock Tower: Bone Pillar chasm room - Rear-left" +ct_finalroom_fr = "Clock Tower: Final room floor - front-right" +ct_finalroom_fl = "Clock Tower: Final room floor - front-left" +ct_finalroom_rr = "Clock Tower: Final room floor - rear-right" +ct_finalroom_rl = "Clock Tower: Final room floor - rear-left" + + +# Castle Keep main locations +ck_flame_l = "Castle Keep: Left Dracula door flame" +ck_flame_r = "Castle Keep: Right Dracula door flame" +ck_behind_drac = "Castle Keep: Behind Dracula's chamber" +ck_cube = "Castle Keep: Dracula's floating cube" + + +# Renon's shop locations +renon1 = "Renon's shop: Roast Chicken purchase" +renon2 = "Renon's shop: Roast Beef purchase" +renon3 = "Renon's shop: Healing Kit purchase" +renon4 = "Renon's shop: Purifying purchase" +renon5 = "Renon's shop: Cure Ampoule purchase" +renon6 = "Renon's shop: Sun Card purchase" +renon7 = "Renon's shop: Moon Card purchase" + + +# Events +forest_boss_one = "Forest of Silence: King Skeleton 1" +forest_boss_two = "Forest of Silence: Were-tiger" +forest_boss_three = "Forest of Silence: King Skeleton 2" +cw_boss = "Castle Wall: Bone Dragons" +villa_boss_one = "Villa: J. A. Oldrey" +villa_boss_two = "Villa: Undead Maiden" +uw_boss = "Underground Waterway: Lizard-man trio" +cc_boss_one = "Castle Center: Behemoth" +cc_boss_two = "Castle Center: Rosa/Camilla" +dt_boss_one = "Duel Tower: Were-jaguar" +dt_boss_two = "Duel Tower: Werewolf" +dt_boss_three = "Duel Tower: Were-bull" +dt_boss_four = "Duel Tower: Were-tiger" +roc_boss = "Room of Clocks: Death/Actrise" +ck_boss_one = "Castle Keep: Renon" +ck_boss_two = "Castle Keep: Vincent" + +cc_behind_the_seal = "Castle Center: Behind the seal" + +the_end = "Dracula" diff --git a/worlds/cv64/data/patches.py b/worlds/cv64/data/patches.py new file mode 100644 index 0000000000..4c46703638 --- /dev/null +++ b/worlds/cv64/data/patches.py @@ -0,0 +1,2865 @@ +normal_door_hook = [ + 0x00862021, # ADDU A0, A0, A2 + 0x80849C60, # LB A0, 0x9C60 (A0) + 0x0C0FF174, # JAL 0x803FC5D0 + 0x308900FF # ANDI T1, A0, 0x00FF +] + +normal_door_code = [ + 0x00024080, # SLL T0, V0, 2 + 0x3C048039, # LUI A0, 0x8039 + 0x00882021, # ADDU A0, A0, T0 + 0x8C849BE4, # LW A0, 0x9BE4 (A0) + 0x8C6A0008, # LW T2, 0x0008 (V1) + 0x008A5824, # AND T3, A0, T2 + 0x11600003, # BEQZ T3, [forward 0x03] + 0x00000000, # NOP + 0x24020003, # ADDIU V0, R0, 0x0003 + 0x27FF006C, # ADDIU RA, RA, 0x006C + 0x03E00008 # JR RA +] + +ct_door_hook = [ + 0x0C0FF182, # JAL 0x803FC608 + 0x00000000, # NOP + 0x315900FF # ANDI T9, T2, 0x00FF +] + +ct_door_code = [ + 0x3C0A8039, # LUI T2, 0x8039 + 0x8D429BF8, # LW V0, 0x9BF8 (T2) + 0x01465021, # ADDU T2, T2, A2 + 0x814A9C60, # LB T2, 0x9C60 (T2) + 0x00495824, # AND T3, V0, T1 + 0x55600001, # BNEZL T3, [forward 0x01] + 0x27FF0010, # ADDIU RA, RA, 0x0010 + 0x03E00008 # JR RA +] + +stage_select_overwrite = [ + # Replacement for the "wipe world state" function when using the warp menu. Now it's the "Special1 jewel checker" + # to see how many destinations can be selected on it with the current count. + 0x8FA60018, # LW A2, 0x0018 (SP) + 0xA0606437, # SB R0, 0x6437 (V1) + 0x10000029, # B [forward 0x29] + 0x00000000, # NOP + 0x3C0A8039, # LUI T2, 0x8039 + 0x254A9C4B, # ADDIU T2, T2, 0x9C4B + 0x814B0000, # LB T3, 0x0000 (T2) + 0x240C000A, # ADDIU T4, R0, 0x000A + 0x016C001B, # DIVU T3, T4 + 0x00003012, # MFLO A2 + 0x24C60001, # ADDIU A2, A2, 0x0001 + 0x28CA0009, # SLTI T2, A2, 0x0009 + 0x51400001, # BEQZL T2, 0x8012AC7C + 0x24060008, # ADDIU A2, R0, 0x0008 + 0x3C0A800D, # LUI T2, 0x800D + 0x914A5E20, # LBU T2, 0x5E20 (T2) + 0x314A0040, # ANDI T2, T2, 0x0040 + 0x11400003, # BEQZ T2, [forward 0x03] + 0x240BFFFE, # ADDIU T3, R0, 0xFFFE + 0x3C0C8034, # LUI T4, 0x8034 + 0xAD8B2084, # SW T3, 0x2084 (T4) + 0x03200008, # JR T9 + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP +] + +custom_code_loader = [ + # On boot, when the company logos show up, this will trigger and load most of the custom ASM data in this module + # off from ROM offsets 0xBFC000-0xBFFFFF and into the 803FC000-803FFFFF range in RAM. + 0x3C080C10, # LUI T0, 0x0C10 + 0x2508F1C0, # ADDIU T0, T0, 0xF1C0 + 0x3C098000, # LUI T1, 0x8000 + 0xAD282438, # SW T0, 0x2438 (T1) + 0x3C088040, # LUI T0, 0x8040 + 0x9108C000, # ADDIU T0, 0xC000 (T0) + 0x15000007, # BNEZ T0, [forward 0x07] + 0x3C0400C0, # LUI A0, 0x00C0 + 0x2484C000, # ADDIU A0, A0, 0xC000 + 0x3C058040, # LUI A1, 0x8040 + 0x24A5C000, # ADDIU A1, A1, 0xC000 + 0x24064000, # ADDIU A2, R0, 0x4000 + 0x08005DFB, # J 0x800177EC + 0x00000000, # NOP + 0x03E00008 # JR RA +] + +remote_item_giver = [ + # The essential multiworld function. Every frame wherein the player is in control and not looking at a text box, + # this thing will check some bytes in RAM to see if an item or DeathLink has been received and trigger the right + # functions accordingly to either reward items or kill the player. + + # Primary checks + 0x3C088034, # LUI T0, 0x8034 + 0x9509244A, # LHU T1, 0x244A (T0) + 0x3C088039, # LUI T0, 0x8039 + 0x910A9EFB, # LBU T2, 0x9EFF (T0) + 0x012A4821, # ADDU T1, T1, T2 + 0x910A9EFF, # LBU T2, 0x9EFF (T0) + 0x012A4821, # ADDU T1, T1, T2 + 0x910A9CCF, # LBU T2, 0x9CCF (T0) + 0x012A4821, # ADDU T1, T1, T2 + 0x910A9EEF, # LBU T2, 0x9EEF (T0) + 0x012A4821, # ADDU T1, T1, T2 + 0x910A9CD3, # LBU T2, 0x9CD3 (T0) + 0x012A4821, # ADDU T1, T1, T2 + 0x3C088038, # LUI T0, 0x8038 + 0x910A7ADD, # LBU T2, 0x7ADD (T0) + 0x012A4821, # ADDU T1, T1, T2 + 0x3C0B8039, # LUI T3, 0x8039 + 0x916A9BE0, # LBU T2, 0x9BE0 (T3) + 0x012A4821, # ADDU T1, T1, T2 + 0x11200006, # BEQZ T1, [forward 0x06] + 0x00000000, # NOP + 0x11400002, # BEQZ T2, [forward 0x02] + 0x254AFFFF, # ADDIU T2, T2, 0xFFFF + 0xA16A9BE0, # SB T2, 0x9BE0 (T3) + 0x03E00008, # JR RA + 0x00000000, # NOP + # Item-specific checks + 0x3C088034, # LUI T0, 0x8034 + 0x91082891, # LBU T0, 0x2891 (T0) + 0x24090002, # ADDIU T1, R0, 0x0002 + 0x15090012, # BNE T0, T1, [forward 0x12] + 0x00000000, # NOP + 0x256B9BDF, # ADDIU T3, T3, 0x9BDF + 0x91640000, # LBU A0, 0x0000 (T3) + 0x14800003, # BNEZ A0, [forward 0x03] + 0x00000000, # NOP + 0x10000005, # B [forward 0x05] + 0x256B0002, # ADDIU T3, T3, 0x0002 + 0x2409000F, # ADDIU T1, R0, 0x000F + 0xA1690001, # SB T1, 0x0001 (T3) + 0x080FF8DD, # J 0x803FE374 + 0xA1600000, # SB R0, 0x0000 (T3) + 0x91640000, # LBU A0, 0x0000 (T3) + 0x14800002, # BNEZ A0, [forward 0x02] + 0x00000000, # NOP + 0x10000003, # B [forward 0x03] + 0x2409000F, # ADDIU T1, R0, 0x000F + 0x080FF864, # J 0x803FE190 + 0xA169FFFF, # SB T1, 0xFFFF (T3) + # DeathLink-specific checks + 0x3C0B8039, # LUI T3, 0x8039 + 0x256B9BE1, # ADDIU T3, T3, 0x9BE1 + 0x91640002, # LBU A0, 0x0002 (T3) + 0x14800002, # BNEZ A0, [forward 0x02] + 0x916900A7, # LBU T1, 0x00A7 (T3) + 0x080FF9C0, # J 0x803FE700 + 0x312A0080, # ANDI T2, T1, 0x0080 + 0x11400002, # BEQZ T2, [forward 0x02] + 0x00000000, # NOP + 0x03E00008, # JR RA + 0x35290080, # ORI T1, T1, 0x0080 + 0xA16900A7, # SB T1, 0x00A7 (T3) + 0x2484FFFF, # ADDIU A0, A0, 0xFFFF + 0x24080001, # ADDIU T0, R0, 0x0001 + 0x03E00008, # JR RA + 0xA168FFFD, # SB T0, 0xFFFD (T3) +] + +deathlink_nitro_edition = [ + # Alternative to the end of the above DeathLink-specific checks that kills the player with the Nitro explosion + # instead of the normal death. + 0x91690043, # LBU T1, 0x0043 (T3) + 0x080FF9C0, # J 0x803FE700 + 0x3C088034, # LUI T0, 0x8034 + 0x91082BFE, # LBU T0, 0x2BFE (T0) + 0x11000002, # BEQZ T0, [forward 0x02] + 0x00000000, # NOP + 0x03E00008, # JR RA + 0x35290080, # ORI T1, T1, 0x0080 + 0xA1690043, # SB T1, 0x0043 (T3) + 0x2484FFFF, # ADDIU A0, A0, 0xFFFF + 0x24080001, # ADDIU T0, R0, 0x0001 + 0x03E00008, # JR RA + 0xA168FFFD, # SB T0, 0xFFFD (T3) +] + +nitro_fall_killer = [ + # Custom code to force the instant fall death if at a high enough falling speed after getting killed by the Nitro + # explosion, since the game doesn't run the checks for the fall death after getting hit by said explosion and could + # result in a softlock when getting blown into an abyss. + 0x3C0C8035, # LUI T4, 0x8035 + 0x918807E2, # LBU T0, 0x07E2 (T4) + 0x2409000C, # ADDIU T1, R0, 0x000C + 0x15090006, # BNE T0, T1, [forward 0x06] + 0x3C098035, # LUI T1, 0x8035 + 0x91290810, # LBU T1, 0x0810 (T1) + 0x240A00C1, # ADDIU T2, R0, 0x00C1 + 0x152A0002, # BNE T1, T2, [forward 0x02] + 0x240B0001, # ADDIU T3, R0, 0x0001 + 0xA18B07E2, # SB T3, 0x07E2 (T4) + 0x03E00008 # JR RA +] + +deathlink_counter_decrementer = [ + # Decrements the DeathLink counter if it's above zero upon loading a previous state. Checking this number will be + # how the client will tell if a player's cause of death was something in-game or a DeathLink (and send a DeathLink + # to the server if it was the former). Also resets the remote item values to 00 so the player's received items don't + # get mucked up in-game. + 0x3C088039, # LUI T0, 0x8039 + 0x91099BE3, # LBU T1, 0x9BE3 (T0) + 0x11200002, # BEQZ T1, 0x803FC154 + 0x2529FFFF, # ADDIU T1, T1, 0xFFFF + 0xA1099BE3, # SB T1, 0x9BE3 + 0x240900FF, # ADDIU T1, R0, 0x00FF + 0xA1099BE0, # SB T1, 0x9BE0 (T0) + 0xA1009BDF, # SB R0, 0x9BDF (T0) + 0xA1009BE1, # SB R0, 0x9BE1 (T0) + 0x91099BDE, # LBU T1, 0x9BDE (T0) + 0x55200001, # BNEZL T1, [forward 0x01] + 0x24090000, # ADDIU T1, R0, 0x0000 + 0xA1099BDE, # SB T1, 0x9BDE (T0) + 0x91099C24, # LBU T1, 0x9C24 (T0) + 0x312A0080, # ANDI T2, T1, 0x0080 + 0x55400001, # BNEZL T2, [forward 0x01] + 0x3129007F, # ANDI T1, T1, 0x007F + 0x03E00008, # JR RA + 0xA1099C24 # SB T1, 0x9C24 (T0) +] + +death_flag_unsetter = [ + # Un-sets the Death status bitflag when overwriting the "Restart this stage" state and sets health to full if it's + # empty. This is to ensure DeathLinked players won't get trapped in a perpetual death loop for eternity should they + # receive one right before transitioning to a different stage. + 0x3C048039, # LUI A0, 0x8039 + 0x90889C88, # LBU T0, 0x9C88 (A0) + 0x31090080, # ANDI T1, T0, 0x0080 + 0x01094023, # SUBU T0, T0, T1 + 0x908A9C3F, # LBU T2, 0x9C3F (A0) + 0x24090064, # ADDIU T1, R0, 0x0064 + 0x51400001, # BEQZL T2, [forward 0x01] + 0xA0899C3F, # SB T1, 0x9C3F (A0) + 0x08006DAE, # J 0x8001B6B8 + 0xA0889C88 # SB T0, 0x9C88 (A0) +] + +warp_menu_opener = [ + # Enables opening the Stage Select menu by pausing while holding Z + R when not in a boss fight, the castle + # crumbling sequence following Fake Dracula, or Renon's arena (in the few seconds after his health bar vanishes). + 0x3C08800D, # LUI T0, 0x800D + 0x85095E20, # LH T1, 0x5E20 (T0) + 0x24083010, # ADDIU T0, R0, 0x3010 + 0x15090011, # BNE T0, T1, [forward 0x11] + 0x3C088035, # LUI T0, 0x8035 + 0x9108F7D8, # LBU T0, 0xF7D8 (T0) + 0x24090020, # ADDIU T1, R0, 0x0020 + 0x1109000D, # BEQ T0, T1, [forward 0x0D] + 0x3C088039, # LUI T0, 0x8039 + 0x91099BFA, # LBU T1, 0x9BFA (T0) + 0x31290001, # ANDI T1, T1, 0x0001 + 0x15200009, # BNEZ T1, [forward 0x09] + 0x8D099EE0, # LW T1, 0x9EE0 + 0x3C0A001B, # LUI T2, 0x001B + 0x254A0003, # ADDIU T2, T2, 0x0003 + 0x112A0005, # BEQ T1, T2, [forward 0x05] + 0x3C098034, # LUI T1, 0x8034 + 0xA1009BE1, # SB R0, 0x9BE1 (T0) + 0x2408FFFC, # ADDIU T0, R0, 0xFFFC + 0x0804DA70, # J 0x80136960 + 0xAD282084, # SW T0, 0x2084 (T1) + 0x0804DA70, # J 0x80136960 + 0xA44E6436 # SH T6, 0x6436 (V0) +] + +give_subweapon_stopper = [ + # Extension to "give subweapon" function to not change the player's weapon if the received item is a Stake or Rose. + # Can also increment the Ice Trap counter if getting a Rose or jump to prev_subweapon_dropper if applicable. + 0x24090011, # ADDIU T1, R0, 0x0011 + 0x11240009, # BEQ T1, A0, [forward 0x09] + 0x24090012, # ADDIU T1, R0, 0x0012 + 0x11240003, # BEQ T1, A0, [forward 0x03] + 0x9465618A, # LHU A1, 0x618A (V1) + 0xA46D618A, # SH T5, 0x618A (V1) + 0x0804F0BF, # J 0x8013C2FC + 0x3C098039, # LUI T1, 0x8039 + 0x912A9BE2, # LBU T2, 0x9BE2 (T1) + 0x254A0001, # ADDIU T2, T2, 0x0001 + 0xA12A9BE2, # SB T2, 0x9BE2 (T1) + 0x0804F0BF, # J 0x8013C2FC +] + +give_powerup_stopper = [ + # Extension to "give PowerUp" function to not increase the player's PowerUp count beyond 2 + 0x240D0002, # ADDIU T5, R0, 0x0002 + 0x556D0001, # BNEL T3, T5, [forward 0x01] + 0xA46C6234, # SH T4, 0x6234 (V1) + 0x0804F0BF # J 0x8013C2FC +] + +npc_item_hack = [ + # Hack to make NPC items show item textboxes when received (and decrease the Countdown if applicable). + 0x3C098039, # LUI T1, 0x8039 + 0x001F5602, # SRL T2, RA, 24 + 0x240B0080, # ADDIU T3, R0, 0x0080 + 0x114B001F, # BEQ T2, T3, [forward 0x1F] + 0x240A001A, # ADDIU T2, R0, 0x001A + 0x27BD0020, # ADDIU SP, SP, 0x20 + 0x15440004, # BNE T2, A0, [forward 0x04] + 0x240B0029, # ADDIU T3, R0, 0x0029 + 0x34199464, # ORI T9, R0, 0x9464 + 0x10000004, # B [forward 0x04] + 0x240C0002, # ADDIU T4, R0, 0x0002 + 0x3419DA64, # ORI T9, R0, 0xDA64 + 0x240B0002, # ADDIU T3, R0, 0x0002 + 0x240C000E, # ADDIU T4, R0, 0x000E + 0x012C7021, # ADDU T6, T1, T4 + 0x316C00FF, # ANDI T4, T3, 0x00FF + 0x000B5A02, # SRL T3, T3, 8 + 0x91CA9CA4, # LBU T2, 0x9CA4 (T6) + 0x3C0D8040, # LUI T5, 0x8040 + 0x256FFFFF, # ADDIU T7, T3, 0xFFFF + 0x01AF6821, # ADDU T5, T5, T7 + 0x91B8D71C, # LBU T8, 0xD71C (T5) + 0x29EF0019, # SLTI T7, T7, 0x0019 + 0x51E00001, # BEQZL T7, [forward 0x01] + 0x91B8D71F, # LBU T8, 0xD71F (T5) + 0x13000002, # BEQZ T8, [forward 0x02] + 0x254AFFFF, # ADDIU T2, T2, 0xFFFF + 0xA1CA9CA4, # SB T2, 0x9CA4 (T6) + 0xA12C9BDF, # SB T4, 0x9BDF (T1) + 0x3C0400BB, # LUI A0, 0x00BB + 0x00992025, # OR A0, A0, T9 + 0x3C058019, # LUI A1, 0x8019 + 0x24A5BF98, # ADDIU A1, A1, 0xBF98 + 0x08005DFB, # J 0x800177EC + 0x24060100, # ADDIU A2, R0, 0x0100 + 0x0804EFFD, # J 0x8013BFF4 + 0xAFBF0014 # SW RA, 0x0014 (SP) +] + +overlay_modifiers = [ + # Whenever a compressed overlay gets decompressed and mapped in the 0F or 0E domains, this thing will check the + # number ID in the T0 register to tell which one it is and overwrite some instructions in it on-the-fly accordingly + # to said number before it runs. Confirmed to NOT be a foolproof solution on console and Simple64; the instructions + # may not be properly overwritten on the first execution of the overlay. + + # Prevent being able to throw Nitro into the Hazardous Waste Disposals + 0x3C0A2402, # LUI T2, 0x2402 + 0x254A0001, # ADDIU T2, T2, 0x0001 + 0x24090023, # ADDIU T1, R0, 0x0023 + 0x15090003, # BNE T0, T1, [forward 0x03] + 0x00000000, # NOP + 0x03200008, # JR T9 + 0xAF2A01D4, # SW T2, 0x01D4 (T9) + # Make it so nothing can be taken from the Nitro or Mandragora shelves through the textboxes + 0x24090022, # ADDIU T1, R0, 0x0022 + 0x11090002, # BEQ T0, T1, [forward 0x02] + 0x24090021, # ADDIU T1, R0, 0x0021 + 0x15090003, # BNE T0, T1, [forward 0x03] + 0x254AFFFF, # ADDIU T2, T2, 0xFFFF + 0x03200008, # JR T9 + 0xAF2A0194, # SW T2, 0x0194 (T9) + # Fix to allow placing both bomb components at a cracked wall at once while having multiple copies of each, and + # prevent placing them at the downstairs crack altogether until the seal is removed. Also enables placing both in + # one interaction. + 0x24090024, # ADDIU T1, R0, 0x0024 + 0x15090012, # BNE T0, T1, [forward 0x12] + 0x240A0040, # ADDIU T2, R0, 0x0040 + 0x240BC338, # ADDIU T3, R0, 0xC338 + 0x240CC3D4, # ADDIU T4, R0, 0xC3D4 + 0x240DC38C, # ADDIU T5, R0, 0xC38C + 0xA32A030F, # SB T2, 0x030F (T9) + 0xA72B0312, # SH T3, 0x0312 (T9) + 0xA32A033F, # SB T2, 0x033F (T9) + 0xA72B0342, # SH T3, 0x0342 (T9) + 0xA32A03E3, # SB T2, 0x03E3 (T9) + 0xA72C03E6, # SH T4, 0x03E6 (T9) + 0xA32A039F, # SB T2, 0x039F (T9) + 0xA72D03A2, # SH T5, 0x03A2 (T9) + 0xA32A03CB, # SB T2, 0x03CB (T9) + 0xA72D03CE, # SH T5, 0x03CE (T9) + 0xA32A05CF, # SB T2, 0x05CF (T9) + 0x240EE074, # ADDIU T6, R0, 0xE074 + 0xA72E05D2, # SH T6, 0x05D2 (T9) + 0x03200008, # JR T9 + # Disable the costume and Hard Mode flag checks so that pressing Up on the Player Select screen will always allow + # the characters' alternate costumes to be used as well as Hard Mode being selectable without creating save data. + 0x2409012E, # ADDIU T1, R0, 0x012E + 0x1509000A, # BNE T0, T1, [forward 0x0A] + 0x3C0A3C0B, # LUI T2, 0x3C0B + 0x254A8000, # ADDIU T2, T2, 0x8000 + 0x240B240E, # ADDIU T3, R0, 0x240E + 0x240C240F, # ADDIU T4, R0, 0x240F + 0x240D0024, # ADDIU T5, R0, 0x0024 + 0xAF2A0C78, # SW T2, 0x0C78 (T9) + 0xA72B0CA0, # SH T3, 0x0CA0 (T9) + 0xA72C0CDC, # SH T4, 0x0CDC (T9) + 0xA32D0168, # SB T5, 0x0024 (T9) + 0x03200008, # JR T9 + # Overwrite instructions in the Forest end cutscene script to store a spawn position ID instead of a cutscene ID. + 0x2409002E, # ADDIU T1, R0, 0x002E + 0x15090005, # BNE T0, T1, [forward 0x05] + 0x3C0AA058, # LUI T2, 0xA058 + 0x254A642B, # ADDIU T2, T2, 0x642B + 0xAF2A0D88, # SW T2, 0x0D88 (T9) + 0xAF200D98, # SW R0, 0x0D98 (T9) + 0x03200008, # JR T9 + # Disable the rapid flashing effect in the CC planetarium cutscene to ensure it won't trigger seizures. + 0x2409003E, # ADDIU T1, R0, 0x003E + 0x1509000C, # BNE T0, T1, [forward 0x0C] + 0x00000000, # NOP + 0xAF200C5C, # SW R0, 0x0C5C + 0xAF200CD0, # SW R0, 0x0CD0 + 0xAF200C64, # SW R0, 0x0C64 + 0xAF200C74, # SW R0, 0x0C74 + 0xAF200C80, # SW R0, 0x0C80 + 0xAF200C88, # SW R0, 0x0C88 + 0xAF200C90, # SW R0, 0x0C90 + 0xAF200C9C, # SW R0, 0x0C9C + 0xAF200CB4, # SW R0, 0x0CB4 + 0xAF200CC8, # SW R0, 0x0CC8 + 0x03200008, # JR T9 + 0x24090134, # ADDIU T1, R0, 0x0134 + 0x15090005, # BNE T0, T1, [forward 0x05] + 0x340B8040, # ORI T3, R0, 0x8040 + 0x340CDD20, # ORI T4, R0, 0xDD20 + 0xA72B1D1E, # SH T3, 0x1D1E (T9) + 0xA72C1D22, # SH T4, 0x1D22 (T9) + 0x03200008, # JR T9 + # Make the Ice Trap model check branch properly + 0x24090125, # ADDIU T1, R0, 0x0125 + 0x15090003, # BNE T0, T1, [forward 0x03] + 0x3C0B3C19, # LUI T3, 0x3C19 + 0x356B803F, # ORI T3, T3, 0x803F + 0xAF2B04D0, # SW T3, 0x04D0 (T9) + 0x03200008 # JR T9 +] + +double_component_checker = [ + # When checking to see if a bomb component can be placed at a cracked wall, this will run if the code lands at the + # "no need to set 2" outcome to see if the other can be set. + + # Mandragora checker + 0x10400007, # BEQZ V0, [forward 0x07] + 0x3C0A8039, # LUI T2, 0x8039 + 0x31098000, # ANDI T1, T0, 0x8000 + 0x15200008, # BNEZ T1, [forward 0x08] + 0x91499C5D, # LBU T1, 0x9C5D (T2) + 0x11200006, # BEQZ T1, 0x80183938 + 0x00000000, # NOP + 0x10000007, # B [forward 0x07] + 0x31E90100, # ANDI T1, T7, 0x0100 + 0x15200002, # BNEZ T1, [forward 0x02] + 0x91499C5D, # LBU T1, 0x9C5D (T2) + 0x15200003, # BNEZ T1, [forward 0x03] + 0x3C198000, # LUI T9, 0x8000 + 0x27391590, # ADDIU T9, T9, 0x1590 + 0x03200008, # JR T9 + 0x24090001, # ADDIU T1, R0, 0x0001 + 0xA4E9004C, # SH T1, 0x004C (A3) + 0x3C190E00, # LUI T9, 0x0E00 + 0x273903E0, # ADDIU T9, T9, 0x03E0 + 0x03200008, # JR T9 + 0x00000000, # NOP + # Nitro checker + 0x10400007, # BEQZ V0, [forward 0x07] + 0x3C0A8039, # LUI T2, 0x8039 + 0x31694000, # ANDI T1, T3, 0x4000 + 0x15200008, # BNEZ T1, [forward 0x08] + 0x91499C5C, # LBU T1, 0x9C5C + 0x11200006, # BEQZ T1, [forward 0x06] + 0x00000000, # NOP + 0x1000FFF4, # B [backward 0x0B] + 0x914F9C18, # LBU T7, 0x9C18 (T2) + 0x31E90002, # ANDI T1, T7, 0x0002 + 0x1520FFEC, # BNEZ T1, [backward 0x13] + 0x91499C5C, # LBU T1, 0x9C5C (T2) + 0x1520FFEF, # BNEZ T1, [backward 0x15] + 0x00000000, # NOP + 0x1000FFE8, # B [backward 0x17] + 0x00000000, # NOP +] + +downstairs_seal_checker = [ + # This will run specifically for the downstairs crack to see if the seal has been removed before then deciding to + # let the player set the bomb components or not. An anti-dick measure, since there is a limited number of each + # component per world. + 0x14400004, # BNEZ V0, [forward 0x04] + 0x3C0A8039, # LUI T2, 0x8039 + 0x914A9C18, # LBU T2, 0x9C18 (T2) + 0x314A0001, # ANDI T2, T2, 0x0001 + 0x11400003, # BEQZ T2, [forward 0x03] + 0x3C198000, # LUI T9, 0x8000 + 0x27391448, # ADDIU T9, T9, 0x1448 + 0x03200008, # JR T9 + 0x3C190E00, # LUI T9, 0x0E00 + 0x273902B4, # ADDIU T9, T9, 0x02B4 + 0x03200008, # JR T9 + 0x00000000, # NOP +] + +map_data_modifiers = [ + # Overwrites the map data table on-the-fly after it loads and before the game reads it to load objects. Good for + # changing anything that is part of a compression chain in the ROM data, including some freestanding item IDs. + # Also jumps to the function that overwrites the "Restart this stage" data if entering through the back of a level. + + 0x08006DAA, # J 0x8001B6A8 + 0x00000000, # NOP + # Demo checker (if we're in a title demo, don't do any of this) + 0x3C028034, # LUI V0, 0x8034 + 0x9449244A, # LHU T1, 0x244A (V0) + 0x11200002, # BEQZ T1, [forward 0x02] + # Zero checker (if there are zeroes in the word at 0x8034244A, where the entity list address is stored, don't do + # any of this either) + 0x8C422B00, # LW V0, 0x2B00 (V0) + 0x03E00008, # JR RA + 0x00000000, # NOP + 0x14400002, # BNEZ V0, [forward 0x02] + 0x00000000, # NOP + 0x03E00008, # JR RA + 0x3C088039, # LUI T0, 0x8039 + 0x91199EE3, # LBU T9, 0x9EE3 (T0) + 0x91089EE1, # LBU T0, 0x9EE1 (T0) + # Forest of Silence (replaces 1 invisible chicken) + 0x15000006, # BNEZ T0, [forward 0x06] + 0x340A0001, # ORI T2, R0, 0x0001 <- Werewolf plaque + 0xA44A01C8, # SH T2, 0x01C8 (V0) + 0x24090001, # ADDIU T1, R0, 0x0001 + 0x1139FFED, # BEQ T1, T9, [backward 0x12] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Villa front yard (replaces 1 moneybag and 2 beefs) + 0x24090003, # ADDIU T1, R0, 0x0003 + 0x15090008, # BNE T0, T1, [forward 0x08] + 0x340A0001, # ORI T2, R0, 0x0001 <- Fountain FL + 0x340B0001, # ORI T3, R0, 0x0001 <- Fountain RL + 0x340C001F, # ORI T4, R0, 0x0001 <- Dog food gate + 0xA44A0058, # SH T2, 0x0058 (V0) + 0xA44B0038, # SH T3, 0x0038 (V0) + 0xA44C0068, # SH T4, 0x0068 (V0) + 0x03E00008, # JR RA + 0x00000000, # NOP + # Villa living area (Replaces 1 chicken, 1 knife, and 3 invisible Purifyings and assigns flags to the sub-weapons) + 0x24090005, # ADDIU T1, R0, 0x0005 + 0x15090025, # BNE T0, T1, [forward 0x25] + 0x340B0010, # ORI T3, R0, 0x0001 <- Hallway axe + 0xA44B00B8, # SH T3, 0x00B8 (V0) + 0x340A0001, # ORI T2, R0, 0x0001 <- Storeroom R + 0x340B0010, # ORI T3, R0, 0x0001 <- Hallway knife + 0x340C0001, # ORI T4, R0, 0x0001 <- Living Room painting + 0x340D0001, # ORI T5, R0, 0x0001 <- Dining Room vase + 0x340E0001, # ORI T6, R0, 0x0001 <- Archives table + 0xA44A0078, # SH T2, 0x0078 (V0) + 0xA44B00C8, # SH T3, 0x00C8 (V0) + 0xA44C0108, # SH T4, 0x0108 (V0) + 0xA44D0128, # SH T5, 0x0128 (V0) + 0xA44E0138, # SH T6, 0x0138 (V0) + 0x340A0000, # ORI T2, R0, 0x0000 <- Sub-weapons left flag half + 0xA44A009C, # SH T2, 0x009C (V0) + 0xA44A00AC, # SH T2, 0x00AC (V0) + 0xA44A00BC, # SH T2, 0x00BC (V0) + 0xA44A00CC, # SH T2, 0x00CC (V0) + 0x340A0000, # ORI T2, R0, 0x0000 <- Sub-weapons right flag halves + 0x240B0000, # ADDIU T3, R0, 0x0000 + 0x240C0000, # ADDIU T4, R0, 0x0000 + 0x240D0000, # ADDIU T5, R0, 0x0000 + 0xA44A00CA, # SH T2, 0x00CA (V0) + 0xA44B00BA, # SH T3, 0x00BA (V0) + 0xA44C009A, # SH T4, 0x009A (V0) + 0xA44D00AA, # SH T5, 0x00AA (V0) + 0x340A0001, # ORI T2, R0, 0x0001 <- Near bed + 0x340B0010, # ORI T3, R0, 0x0001 <- Storeroom L + 0x340C0001, # ORI T4, R0, 0x0001 <- Storeroom statue + 0x340D0001, # ORI T5, R0, 0x0001 <- Exit knight + 0x340E0001, # ORI T6, R0, 0x0001 <- Sitting room table + 0xA44A0048, # SH T2, 0x0078 (V0) + 0xA44B0088, # SH T3, 0x00C8 (V0) + 0xA44C00D8, # SH T4, 0x0108 (V0) + 0xA44D00F8, # SH T5, 0x0128 (V0) + 0xA44E0118, # SH T6, 0x0138 (V0) + 0x03E00008, # JR RA + 0x00000000, # NOP + # Tunnel (replaces 1 invisible Cure Ampoule) + 0x24090007, # ADDIU T1, R0, 0x0007 + 0x1509000A, # BNE T0, T1, [forward 0x0A] + 0x340A0001, # ORI T2, R0, 0x0001 <- Twin arrow signs + 0xA44A0268, # SH T2, 0x0268 (V0) + 0x340A0001, # ORI T2, R0, 0x0001 <- Bucket + 0xA44A0258, # SH T2, 0x0258 (V0) + 0x240B0005, # ADDIU T3, R0, 0x0005 + 0xA04B0150, # SB T3, 0x0150 (V0) + 0x24090011, # ADDIU T1, R0, 0x0011 + 0x1139FFB0, # BEQ T1, T9, [backward 0x50] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Castle Center factory floor (replaces 1 moneybag, 1 jewel, and gives every lizard man coffin item a unique flag) + 0x2409000B, # ADDIU T1, R0, 0x000B + 0x15090016, # BNE T0, T1, [forward 0x16] + 0x340A001A, # ORI T2, R0, 0x001A <- Lizard coffin nearside mid-right + 0x340B0003, # ORI T3, R0, 0x0003 <- Lizard coffin nearside mid-left + 0xA44A00C8, # SH T2, 0x00C8 (V0) + 0xA44B00D8, # SH T3, 0x00D8 (V0) + 0x240A1000, # ADDIU T2, R0, 0x1000 + 0x240B2000, # ADDIU T3, R0, 0x2000 + 0x240C0400, # ADDIU T4, R0, 0x0400 + 0x240D0800, # ADDIU T5, R0, 0x0800 + 0x240E0200, # ADDIU T6, R0, 0x0200 + 0x240F0100, # ADDIU T7, R0, 0x0100 + 0xA44A009A, # SH T2, 0x009A (V0) + 0xA44B00AA, # SH T3, 0x00AA (V0) + 0xA44C00CA, # SH T4, 0x00CA (V0) + 0xA44D00BA, # SH T5, 0x00BA (V0) + 0xA44E00DA, # SH T6, 0x00DA (V0) + 0xA44F00EA, # SH T7, 0x00EA (V0) + 0x340A0017, # ORI T2, R0, 0x0017 <- Lizard coffin nearside mid-right + 0x340B000C, # ORI T3, R0, 0x000C <- Lizard coffin nearside mid-left + 0xA44A00A8, # SH T2, 0x00C8 (V0) + 0xA44B00E8, # SH T3, 0x00D8 (V0) + 0x03E00008, # JR RA + 0x00000000, # NOP + # Duel Tower (replaces a flame on top of a rotating lion pillar with a White Jewel on the invisible bridge ledge) + 0x24090013, # ADDIU T1, R0, 0x0013 + 0x1509000F, # BNE T0, T1, [forward 0x0F] + 0x3C0A00B9, # LUI T2, 0x00BB + 0x254A012B, # ADDIU T2, T2, 0x012B + 0x3C0BFE2A, # LUI T3, 0xFE2A + 0x256B0027, # ADDIU T3, T3, 0x0027 + 0x3C0C0001, # LUI T4, 0x0001 + 0x3C0D0022, # LUI T5, 0x0022 + 0x25AD0100, # ADDIU T5, T5, 0x0100 + 0xAC4A0A80, # SW T2, 0x0AE0 (V0) + 0xAC4B0A84, # SW T3, 0x0AE4 (V0) + 0xAC4C0A88, # SW T4, 0x0AE8 (V0) + 0xAC4D0A8C, # SW T5, 0x0AEC (V0) + 0x24090001, # ADDIU T1, R0, 0x0001 + 0x1139FF87, # BEQ T1, T9, [backward 0x77] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Castle Keep outside (replaces 1 invisible Healing Kit and gives both invisible Healing Kits pickup flags) + 0x24090014, # ADDIU T1, R0, 0x0014 + 0x1509000A, # BNE T0, T1, [forward 0x0A] + 0x340A0001, # ORI T2, R0, 0x0001 <- Right flame + 0xA44A0058, # SH T2, 0x0058 (V0) + 0x240A0001, # ADDIU T2, R0, 0x0001 + 0x240B0002, # ADDIU T3, R0, 0x0002 + 0xA44A004A, # SH T2, 0x004A (V0) + 0xA44B005A, # SH T3, 0x005A (V0) + 0x24090002, # ADDIU T1, R0, 0x0002 + 0x1139FF7B, # BEQ T0, T1, [backward 0x74] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Castle Wall main area (sets a flag for the freestanding Holy Water if applicable and the "beginning of stage" + # state if entered from the rear) + 0x24090002, # ADDIU T1, R0, 0x0002 + 0x15090006, # BNE T0, T1, [forward 0x06] + 0x24090000, # ADDIU T1, R0, 0x0000 + 0xA049009B, # SB T1, 0x009B (V0) + 0x24090010, # ADDIU T1, R0, 0x0010 + 0x1139FF73, # BEQ T1, T9, [backward 0x8D] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Villa vampire crypt (sets the "beginning of stage" state if entered from the rear, as well as the "can warp here" + # flag if arriving for the first time) + 0x2409001A, # ADDIU T1, R0, 0x001A + 0x15090008, # BNE T0, T1, [forward 0x08] + 0x3C0A8039, # LUI T2, 0x8039 + 0x914B9C1C, # LBU T3, 0x9C1C (T2) + 0x356B0001, # ORI T3, T3, 0x0001 + 0xA14B9C1C, # SB T3, 0x9C1C (T2) + 0x24090003, # ADDIU T1, R0, 0x0003 + 0x1139FF69, # BEQ T1, T9, [backward 0x98] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Underground Waterway (sets the "beginning of stage" state if entered from the rear) + 0x24090008, # ADDIU T1, R0, 0x0008 + 0x15090004, # BNE T0, T1, [forward 0x04] + 0x24090001, # ADDIU T1, R0, 0x0001 + 0x1139FF63, # BEQ T1, T9, [backward 0x9F] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Castle Center elevator top (sets the "beginning of stage" state if entered from either rear, as well as the "can + # warp here" flag if arriving for the first time) + 0x2409000F, # ADDIU T1, R0, 0x000F + 0x1509000A, # BNE T0, T1, [forward 0x0A] + 0x3C0A8039, # LUI T2, 0x8039 + 0x914B9C1C, # LBU T3, 0x9C1C (T2) + 0x356B0002, # ORI T3, T3, 0x0002 + 0xA14B9C1C, # SB T3, 0x9C1C (T2) + 0x24090002, # ADDIU T1, R0, 0x0002 + 0x1139FF59, # BEQ T1, T9, [backward 0xAA] + 0x24090003, # ADDIU T1, R0, 0x0003 + 0x1139FF57, # BEQ T1, T9, [backward 0xAC] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Tower of Execution (sets the "beginning of stage" state if entered from the rear) + 0x24090010, # ADDIU T1, R0, 0x0010 + 0x15090004, # BNE T0, T1, [forward 0x10] + 0x24090012, # ADDIU T1, R0, 0x0012 + 0x1139FF51, # BEQ T1, T9, [backward 0xAF] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Tower of Sorcery (sets the "beginning of stage" state if entered from the rear) + 0x24090011, # ADDIU T1, R0, 0x0011 + 0x15090004, # BNE T0, T1, [forward 0x04] + 0x24090013, # ADDIU T1, R0, 0x0013 + 0x1139FF4B, # BEQ T1, T9, [backward 0xBA] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Tower of Science (sets the "beginning of stage" state if entered from the rear) + 0x24090012, # ADDIU T1, R0, 0x0012 + 0x15090004, # BNE T0, T1, [forward 0x04] + 0x24090004, # ADDIU T1, R0, 0x0004 + 0x1139FF45, # BEQ T1, T9, [backward 0xC1] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Room of Clocks (changes 2 candle settings if applicable and sets the "begging of stage" state if spawning at end) + 0x2409001B, # ADDIU T1, R0, 0x001B + 0x15090008, # BNE T0, T1, [forward 0x08] + 0x24090006, # ADDIU T1, R0, 0x0006 + 0x240A0006, # ADDIU T2, R0, 0x0006 + 0xA0490059, # SB T1, 0x0059 (V0) + 0xA04A0069, # SB T2, 0x0069 (V0) + 0x24090014, # ADDIU T1, R0, 0x0014 + 0x1139FF3B, # BEQ T1, T9, [backward 0xCC] + 0x00000000, # NOP + 0x03E00008, # JR RA + # Castle Center basement (changes 2 non-pickup-able Mandragoras into 2 real items and moves the torture shelf item + # forward slightly if it's turned visible) + 0x24090009, # ADDIU T1, R0, 0x0009 + 0x15090011, # BNE T0, T1, [forward 0x11] + 0x3409FFFC, # ORI T1, R0, 0xFFFC + 0xA44907C0, # SH T1, 0x07C0 (V0) + 0xA44907D0, # SH T1, 0x07D0 (V0) + 0x240A0027, # ADDIU T2, R0, 0x0027 + 0xA44A07C6, # SH T2, 0x07C6 (V0) + 0xA44A07D6, # SH T2, 0x07D6 (V0) + 0x340B0001, # ORI T3, R0, 0x0001 <- Right Mandragora + 0x340C0001, # ORI T4, R0, 0x0001 <- Left Mandragora + 0xA44B07C8, # SH T3, 0x07C8 (V0) + 0xA44C07D8, # SH T4, 0x07D8 (V0) + 0x240D00F5, # ADDIU T5, R0, 0x00F5 + 0xA04D06D1, # SB T5, 0x06D1 (V0) + 0x24090040, # ADDIU T1, R0, 0x0040 + 0x240A0080, # ADDIU T2, R0, 0x0080 + 0xA04907CA, # SB T1, 0x07CA (V0) + 0xA04A07DA, # SB T2, 0x07DA (V0) + 0x03E00008, # JR RA + # Castle Center nitro area (changes 2 non-pickup-able Nitros into 2 real items) + 0x2409000E, # ADDIU T1, R0, 0x000E + 0x15090015, # BNE T0, T1, [forward 0x15] + 0x240900C0, # ADDIU T1, R0, 0x00C0 + 0x240A00CE, # ADDIU T2, R0, 0x00CE + 0xA0490471, # SB T1, 0x0471 (V0) + 0xA04A04A1, # SB T2, 0x04A1 (V0) + 0x24090027, # ADDIU T1, R0, 0x0027 + 0xA4490476, # SH T1, 0x0476 (V0) + 0xA44904A6, # SH T1, 0x04A6 (V0) + 0x340A0001, # ORI T2, R0, 0x0001 <- Invention-side shelf + 0x340B0001, # ORI T3, R0, 0x0001 <- Heinrich-side shelf + 0xA44A0478, # SH T2, 0x0478 (V0) + 0xA44B04A8, # SH T3, 0x04A8 (V0) + 0x24090080, # ADDIU T1, R0, 0x0080 + 0xA049047A, # SB T1, 0x047A (V0) + 0xA440047C, # SH R0, 0x047C (V0) + 0x240A0400, # ADDIU T2, R0, 0x0400 + 0x340BFF05, # ORI T3, R0, 0xFF05 + 0xA44A04AA, # SH T2, 0x04AA (V0) + 0xA44B04AC, # SH T3, 0x04AC (V0) + 0x24090046, # ADDIU T1, R0, 0x0046 + 0xA04904A3, # SB T1, 0x04A3 (V0) + 0x03E00008, # JR RA + # Fan meeting room (sets "beginning of stage" flag) + 0x24090019, # ADDIU T1, R0, 0x0019 + 0x1109FF0D, # BEQ T1, T9, [backward 0xFB] + 0x00000000, # NOP + 0x03E00008, # JR RA +] + +renon_cutscene_checker = [ + # Prevents Renon's departure/pre-fight cutscene from playing if the player is either in the escape sequence or both + # did not spend the required 30K to fight him and lacks the required Special2s to fight Dracula. + 0x15810002, # BNE T4, AT, [forward 0x02] + 0x00000000, # NOP + 0x08049EB3, # J 0x80127ACC + 0x24090016, # ADDIU T1, R0, 0x0016 + 0x11C90002, # BEQ T6, T1, [forward 0x02] + 0x00000000, # NOP + 0x08049ECA, # J 0x80127B28 + 0x24190000, # ADDIU T9, R0, 0x0000 + 0x8C696208, # LW T1, 0x6208 (V1) + 0x292A7531, # SLTI T2, T1, 0x7531 + 0x51400001, # BEQZL T2, [forward 0x01] + 0x24190001, # ADDIU T9, R0, 0x0001 + 0x3C0B8013, # LUI T3, 0x8013 + 0x916BAC9F, # LBU T3, 0xAC9F (T3) + 0x906C6194, # LBU T4, 0x6194 (V1) + 0x018B502A, # SLT T2, T4, T3 + 0x51400001, # BEQZL T2, [forward 0x01] + 0x24190001, # ADDIU T9, R0, 0x0001 + 0x90696142, # LBU T1, 0x6142 (V1) + 0x31290002, # ANDI T1, T1, 0x0002 + 0x55200001, # BNEZL T1, [forward 0x01] + 0x24190000, # ADDIU T9, R0, 0x0000 + 0x17200003, # BNEZ T9, [forward 0x03] + 0x00000000, # NOP + 0x08049ECC, # J 0x80127B30 + 0x00000000, # NOP + 0x08049ECA # J 0x80127B28 +] + +renon_cutscene_checker_jr = [ + # Like renon_cutscene_checker, but without the checks for the Special2 and spent money counters. Inserted instead if + # the player chooses to guarantee or disable the Renon fight on their YAML. + 0x15810002, # BNE T4, AT, [forward 0x02] + 0x00000000, # NOP + 0x08049EB3, # J 0x80127ACC + 0x24090016, # ADDIU T1, R0, 0x0016 + 0x11C90002, # BEQ T6, T1, [forward 0x02] + 0x00000000, # NOP + 0x08049ECA, # J 0x80127B28 + 0x24190001, # ADDIU T9, R0, 0x0001 + 0x90696142, # LBU T1, 0x6142 (V1) + 0x31290002, # ANDI T1, T1, 0x0002 + 0x55200001, # BNEZL T1, [forward 0x01] + 0x24190000, # ADDIU T9, R0, 0x0000 + 0x17200003, # BNEZ T9, [forward 0x03] + 0x00000000, # NOP + 0x08049ECC, # J 0x80127B30 + 0x00000000, # NOP + 0x08049ECA # J 0x80127B28 +] + +ck_door_music_player = [ + # Plays Castle Keep's song if you spawn in front of Dracula's door (teleporting via the warp menu) and haven't + # started the escape sequence yet. + 0x17010002, # BNE T8, AT, [forward 0x02] + 0x00000000, # NOP + 0x08063DF9, # J 0x8018F7E4 + 0x240A0000, # ADDIU T2, R0, 0x0000 + 0x3C088039, # LUI T0, 0x8039 + 0x91089BFA, # LBU T0, 0x9BFA (T0) + 0x31080002, # ANDI T0, T0, 0x0002 + 0x51090001, # BEQL T0, T1, [forward 0x01] + 0x254A0001, # ADDIU T2, T2, 0x0001 + 0x24080003, # ADDIU T0, R0, 0x0003 + 0x51180001, # BEQL T0, T8, [forward 0x01] + 0x254A0001, # ADDIU T2, T2, 0x0001 + 0x240B0002, # ADDIU T3, R0, 0x0002 + 0x114B0002, # BEQ T2, T3, [forward 0x02] + 0x00000000, # NOP + 0x08063DFD, # J 0x8018F7F4 + 0x00000000, # NOP + 0x08063DF9 # J 0x8018F7E4 +] + +dracula_door_text_redirector = [ + # Switches the standard pointer to the map text with one to a custom message for Dracula's chamber door if the + # current scene is Castle Keep exterior (Scene 0x14). + 0x3C088039, # LUI T0, 0x8039 + 0x91089EE1, # LBU T0, 0x9EE1 (T0) + 0x24090014, # ADDIU T1, R0, 0x0014 + 0x15090006, # BNE T0, T1, [forward 0x06] + 0x3C088014, # LUI T0, 0x8014 + 0x2508B9F4, # ADDIU T0, T0, 0xB9F4 + 0x151F0003, # BNE T0, RA, [forward 0x03] + 0x00000000, # NOP + 0x3C028040, # LUI V0, 0x8040 + 0x2442CC48, # ADDIU V0, V0, 0xCC48 + 0x03E00008 # JR RA +] + +coffin_time_checker = [ + # When entering the Villa coffin, this will check to see whether it's day or night and send you to either the Tunnel + # or Underground Waterway level slot accordingly regardless of which character you are + 0x28490006, # SLTI T1, V0, 0x0006 + 0x15200005, # BNEZ T1, [forward 0x05] + 0x28490012, # SLTI T1, V0, 0x0012 + 0x11200003, # BEQZ T1, [forward 0x03] + 0x00000000, # NOP + 0x08055AEB, # J 0x80156BAC + 0x00000000, # NOP + 0x08055AED # J 0x80156BB4 +] + +werebull_flag_unsetter = [ + # This will un-set Were-bull's defeat flag in Duel Tower after beating him so that the check above his arena can + # still be acquired later, if it hasn't been acquired already. This is the only check in the entire game that can be + # permanently missed even with the ability to return to levels. + 0x3C0E0400, # LUI T6, 0x0400 + 0x15CF0006, # BNE T6, T7, [forward 0x06] + 0x00187402, # SRL T6, T8, 16 + 0x31CE2000, # ANDI T6, T6, 0x2000 + 0x15C00003, # BNEZ T6, [forward 0x03] + 0x3C0E0020, # LUI T6, 0x0020 + 0x014E5025, # OR T2, T2, T6 + 0xAC4A613C, # SW T2, 0x613C (V0) + 0x03200008 # JR T9 +] + +werebull_flag_unsetter_special2_electric_boogaloo = [ + # Like werebull_flag_unsetter, but with the added feature of awarding a Special2 after determining the player isn't + # trying to beat Were-bull twice! This will be inserted over the former if the goal is set to boss hunt. + 0x3C0E0400, # LUI T6, 0x0400 + 0x15CF0008, # BNE T6, T7, [forward 0x06] + 0x00187402, # SRL T6, T8, 16 + 0x31CE2000, # ANDI T6, T6, 0x2000 + 0x15C00005, # BNEZ T6, [forward 0x05] + 0x3C0E0020, # LUI T6, 0x0020 + 0x014EC024, # AND T8, T2, T6 + 0x014E5025, # OR T2, T2, T6 + 0xAC4A613C, # SW T2, 0x613C (V0) + 0x17000003, # BNEZ T8, [forward 0x03] + 0x3C188039, # LUI T8, 0x8039 + 0x240E0005, # ADDIU T6, R0, 0x0005 + 0xA30E9BDF, # SB T6, 0x9BDF (T8) + 0x03200008 # JR T9 +] + +werebull_flag_pickup_setter = [ + # Checks to see if an item being picked up is the one on top of Were-bull's arena. If it is, then it'll check to see + # if our makeshift "Were-bull defeated once" flag and, if it is, set Were-bull's arena flag proper, so it'll + # permanently stay down. + 0x3C088038, # LUI T0, 0x8038 + 0x25083AC8, # ADDIU T0, T0, 0x3AC8 + 0x15020007, # BNE T0, V0, [forward 0x07] + 0x3C082000, # LUI T0, 0x2000 + 0x15040005, # BNE T0, A0, [forward 0x05] + 0x9449612C, # LHU T1, 0x612C (V0) + 0x31290020, # ANDI T1, T1, 0x0020 + 0x11200002, # BEQZ T1, [forward 0x02] + 0x3C0A0400, # LUI T2, 0x0400 + 0x014D6825, # OR T5, T2, T5 + 0xAC4D612C, # SW T5, 0x612C (V0) + 0x03E00008 # JR RA +] + +boss_special2_giver = [ + # Enables the rewarding of Special2s upon the vanishing of a boss's health bar when defeating it. + + # Also sets a flag in the case of the Castle Wall White Dragons' health bar going away. Their defeat flag in vanilla + # is tied to hitting the lever after killing them, so this alternate flag is used to track them for the "All Bosses" + # goal in the event someone kills them and then warps out opting to not be a Konami pachinko champ. + 0x3C118035, # LUI S1, 0x8035 + 0x962DF834, # LHU T5, 0xF834 (S1) + 0x240E3F73, # ADDIU T6, R0, 0x3F73 + 0x15AE0012, # BNE T5, T6, [forward 0x12] + 0x3C118039, # LUI S1, 0x8039 + 0x922D9EE1, # LBU T5, 0x9EE1 (S1) + 0x240E0013, # ADDIU T6, R0, 0x0013 + 0x11AE000E, # BEQ T5, T6, [forward 0x0E] + 0x922F9BFA, # LBU T7, 0x9BFA (S1) + 0x31EF0001, # ANDI T7, T7, 0x0001 + 0x15E0000B, # BNEZ T7, [forward 0x0B] + 0x240E0002, # ADDIU T6, R0, 0x0002 + 0x15AE0006, # BNE T5, T6, [forward 0x06] + 0x00000000, # NOP + 0x862F9BF4, # LH T7, 0x9BF4 (S1) + 0x31ED0080, # ANDI T5, T7, 0x0080 + 0x15A00005, # BNEZ T5, [forward 0x05] + 0x35EF0080, # ORI T7, T7, 0x0080 + 0xA62F9BF4, # SH T7, 0x9BF4 (S1) + 0x240D0005, # ADDIU T5, R0, 0x0005 + 0xA22D9BDF, # SB T5, 0x9BDF (S1) + 0xA22D9BE0, # SB T5, 0x9BE0 (S1) + 0x03E00008 # JR RA +] + +boss_goal_checker = [ + # Checks each boss flag to see if every boss with a health meter has been defeated and puts 0x0004 in V0 to + # disallow opening Dracula's door if not all have been. + 0x3C0A8039, # LUI T2, 0x8039 + 0x954B9BF4, # LHU T3, 0x9BF4 (T2) + 0x316D0BA0, # ANDI T5, T3, 0x0BA0 + 0x914B9BFB, # LBU T3, 0x9BFB (T2) + 0x000B6182, # SRL T4, T3, 6 + 0x11800010, # BEQZ T4, [forward 0x10] + 0x240C00C0, # ADDIU T4, R0, 0x00C0 + 0x01AC6821, # ADDU T5, T5, T4 + 0x914B9BFD, # LBU T3, 0x9BFD (T2) + 0x316C0020, # ANDI T4, T3, 0x0020 + 0x01AC6821, # ADDU T5, T5, T4 + 0x914B9BFE, # LBU T3, 0x9BFE (T2) + 0x316C0010, # ANDI T4, T3, 0x0010 + 0x01AC6821, # ADDU T5, T5, T4 + 0x914B9C18, # LBU T3, 0x9C18 (T2) + 0x316C0010, # ANDI T4, T3, 0x0010 + 0x01AC6821, # ADDU T5, T5, T4 + 0x914B9C1B, # LBU T3, 0x9C1B (T2) + 0x000B6102, # SRL T4, T3, 4 + 0x11800005, # BEQZ T4, [forward 0x05] + 0x240C0050, # ADDIU T4, R0, 0x0050 + 0x01AC6821, # ADDU T5, T5, T4 + 0x240E0CF0, # ADDIU T6, R0, 0x0CF0 + 0x55CD0001, # BNEL T6, T5, [forward 0x01] + 0x24020004, # ADDIU V0, R0, 0x0004 + 0x03E00008 # JR RA +] + +special_goal_checker = [ + # Checks the Special2 counter to see if the specified threshold has been reached and puts 0x0001 in V0 to disallow + # opening Dracula's door if it hasn't been. + 0x3C0A8039, # LUI T2, 0x8039 + 0x914B9C4C, # LBU T3, 0x9C4C (T2) + 0x296A001E, # SLTI T2, T3, 0x001E + 0x55400001, # BNEZL T2, 0x8012AC8C + 0x24020001, # ADDIU V0, R0, 0x0001 + 0x03E00008 # JR RA +] + +warp_menu_rewrite = [ + # Rewrite to the warp menu code to ensure each option can have its own scene ID, spawn ID, and fade color. + # Start Warp + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x1000001F, # B [forward 0x1F] + 0x3C0F8000, # LUI T7, 0x8000 + # Warp 1 + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x1000001B, # B [forward 0x1B] + 0x3C0F8040, # LUI T7, 0x8040 + # Warp 2 + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x10000017, # B [forward 0x17] + 0x3C0F8080, # LUI T7, 0x8080 + # Warp 3 + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x10000013, # B [forward 0x13] + 0x3C0F0080, # LUI T7, 0x0080 + # Warp 4 + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x3C0F0080, # LUI T7, 0x0080 + 0x1000000E, # B [forward 0x0E] + 0x25EF8000, # ADDIU T7, T7, 0x8000 + # Warp 5 + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x1000000A, # B [forward 0x0A] + 0x340F8000, # ORI T7, R0, 0x8000 + # Warp 6 + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x3C0F8000, # LUI T7, 0x8000 + 0x10000005, # B [forward 0x05] + 0x35EF8000, # ORI T7, T7, 0x8000 + # Warp 7 + 0x3C0E0000, # LUI T6, 0x0000 + 0x25CE0000, # ADDIU T6, T6, 0x0000 + 0x3C0F8040, # LUI T7, 0x8040 + 0x35EF8000, # ORI T7, T7, 0x8000 + # Warp Crypt + 0x3C18800D, # LUI T8, 0x800D + 0x97185E20, # LHU T8, 0x5E20 (T8) + 0x24192000, # ADDIU T9, R0, 0x2000 + 0x17190009, # BNE T8, T9, [forward 0x09] + 0x3C088039, # LUI T0, 0x8039 + 0x91089C1C, # LBU T0, 0x9C1C (T0) + 0x31080001, # ANDI T0, T0, 0x0001 + 0x1100000F, # BEQZ T0, [forward 0x0F] + 0x00000000, # NOP + 0x3C0E001A, # LUI T6, 0x001A + 0x25CE0003, # ADDIU T6, T6, 0x0003 + 0x1000000B, # B [forward 0x0B] + 0x240F0000, # ADDIU T7, R0, 0x0000 + # Warp Elevator + 0x24190010, # ADDIU T9, R0, 0x0010 + 0x17190008, # BNE T8, T9, [forward 0x08] + 0x91089C1C, # LBU T0, 0x9C1C (T0) + 0x31080002, # ANDI T0, T0, 0x0002 + 0x11000005, # BEQZ T0, [forward 0x05] + 0x00000000, # NOP + 0x3C0E000F, # LUI T6, 0x000F + 0x25CE0001, # ADDIU T6, T6, 0x0001 + 0x3C0F8080, # LUI T7, 0x8080 + 0x35EF8000, # ORI T7, T7, 0x8000 + # All + 0xAC6E6428, # SW T6, 0x6428 (V1) + 0xAC6F642C, # SW T7, 0x642C (V1) + 0x2402001E, # ADDIU V0, R0, 0x001E + 0xA4626430, # SH V0, 0x6430 (V1) + 0xA4626432, # SH V0, 0x6432 (V1) +] + +warp_pointer_table = [ + # Changed pointer table addresses to go with the warp menu rewrite + 0x8012AD74, + 0x8012AD84, + 0x8012AD94, + 0x8012ADA4, + 0x8012ADB4, + 0x8012ADC8, + 0x8012ADD8, + 0x8012ADEC, +] + +spawn_coordinates_extension = [ + # Checks if the 0x10 bit is set in the spawn ID and references the below list of custom spawn coordinates if it is. + 0x316A0010, # ANDI T2, T3, 0x0010 + 0x11400003, # BEQZ T2, [forward 0x03] + 0x8CD90008, # LW T9, 0x0008 (A2) + 0x3C198040, # LUI T9, 0x8040 + 0x2739C2CC, # ADDIU T9, T9, 0xC2CC + 0x08054A83, # J 0x80152A0C + 0x00000000, # NOP + 0x00000000, # NOP + + # Castle Wall end: 10 + # player camera focus point + # x = 0xFFFF 0xFFFF 0xFFFF + # y = 0x0003 0x0012 0x000D + # z = 0xFFF3 0xEDFF 0xFFF3 + # r = 0xC000 + 0x0000FFFF, + 0x0003FFF3, + 0xC000FFFF, + 0x0012FFED, + 0xFFFF000D, + 0xFFF30000, + + # Tunnel end: 11 + # player camera focus point + # x = 0x0088 0x0087 0x0088 + # y = 0x01D6 0x01F1 0x01E5 + # z = 0xF803 0xF7D2 0xF803 + # r = 0xC000 + 0x008801D6, + 0xF803C000, + 0x008701F1, + 0xF7D20088, + 0x01E5F803, + + # Tower of Execution end: 12 + # player camera focus point + # x = 0x00AC 0x00EC 0x00AC + # y = 0x0154 0x0183 0x0160 + # z = 0xFE8F 0xFE8F 0xFE8F + # r = 0x8000 + 0x000000AC, + 0x0154FE8F, + 0x800000EC, + 0x0183FE8F, + 0x00AC0160, + 0xFE8F0000, + + # Tower of Sorcery end: 13 + # player camera focus point + # x = 0xFEB0 0xFE60 0xFEB0 + # y = 0x0348 0x036D 0x0358 + # z = 0xFEFB 0xFEFB 0xFEFB + # r = 0x0000 + 0xFEB00348, + 0xFEFB0000, + 0xFE60036D, + 0xFEFBFEB0, + 0x0358FEFB, + + # Room of Clocks end: 14 + # player camera focus point + # x = 0x01B1 0x01BE 0x01B1 + # y = 0x0006 0x001B 0x0015 + # z = 0xFFCD 0xFFCD 0xFFCD + # r = 0x8000 + 0x000001B1, + 0x0006FFCD, + 0x800001BE, + 0x001BFFCD, + 0x01B10015, + 0xFFCD0000, + + # Duel Tower savepoint: 15 + # player camera focus point + # x = 0x00B9 0x00B9 0x00B9 + # y = 0x012B 0x0150 0x0138 + # z = 0xFE20 0xFE92 0xFE20 + # r = 0xC000 + 0x00B9012B, + 0xFE20C000, + 0x00B90150, + 0xFE9200B9, + 0x0138FE20 +] + +waterway_end_coordinates = [ + # Underground Waterway end: 01 + # player camera focus point + # x = 0x0397 0x03A1 0x0397 + # y = 0xFFC4 0xFFDC 0xFFD3 + # z = 0xFDB9 0xFDB8 0xFDB9 + # r = 0x8000 + 0x00000397, + 0xFFC4FDB9, + 0x800003A1, + 0xFFDCFDB8, + 0x0397FFD3, + 0xFDB90000 +] + +continue_cursor_start_checker = [ + # This is used to improve the Game Over screen's "Continue" menu by starting the cursor on whichever checkpoint + # is most recent instead of always on "Previously saved". If a menu has a cursor start value of 0xFF in its text + # data, this will read the byte at 0x80389BC0 to determine which option to start the cursor on. + 0x8208001C, # LB T0, 0x001C(S0) + 0x05010003, # BGEZ T0, [forward 0x03] + 0x3C098039, # LUI T1, 0x8039 + 0x81289BC0, # LB T0, 0x9BC0 (T1) + 0xA208001C, # SB T0, 0x001C (S0) + 0x03E00008 # JR RA +] + +savepoint_cursor_updater = [ + # Sets the value at 0x80389BC0 to 0x00 after saving to let the Game Over screen's "Continue" menu know to start the + # cursor on "Previously saved" as well as updates the entrance variable for B warping. It then jumps to + # deathlink_counter_decrementer in the event we're loading a save from the Game Over screen. + 0x3C088039, # LUI T0, 0x8039 + 0x91099C95, # LBU T1, 0x9C95 (T0) + 0x000948C0, # SLL T1, T1, 3 + 0x3C0A8018, # LUI T2, 0x8018 + 0x01495021, # ADDU T2, T2, T1 + 0x914B17CF, # LBU T3, 0x17CF (T2) + 0xA10B9EE3, # SB T3, 0x9EE3 (T0) + 0xA1009BC0, # SB R0, 0x9BC0 (T0) + 0x080FF8F0 # J 0x803FE3C0 +] + +stage_start_cursor_updater = [ + # Sets the value at 0x80389BC0 to 0x01 after entering a stage to let the Game Over screen's "Continue" menu know to + # start the cursor on "Restart this stage". + 0x3C088039, # LUI T0, 0x8039 + 0x24090001, # ADDIU T1, R0, 0x0001 + 0xA1099BC0, # SB T1, 0x9BC0 (T0) + 0x03E00008 # JR RA +] + +elevator_flag_checker = [ + # Prevents the top elevator in Castle Center from activating if the bottom elevator switch is not turned on. + 0x3C088039, # LUI T0, 0x8039 + 0x91089C07, # LBU T0, 0x9C07 (T0) + 0x31080002, # ANDI T0, T0, 0x0002 + 0x15000002, # BNEZ T0, [forward 0x02] + 0x848E004C, # LH T6, 0x004C (A0) + 0x240E0000, # ADDIU T6, R0, 0x0000 + 0x03E00008 # JR RA +] + +crystal_special2_giver = [ + # Gives a Special2 upon activating the big crystal in CC basement. + 0x3C098039, # LUI T1, 0x8039 + 0x24190005, # ADDIU T9, R0, 0x0005 + 0xA1399BDF, # SB T9, 0x9BDF (T1) + 0x03E00008, # JR RA + 0x3C198000 # LUI T9, 0x8000 +] + +boss_save_stopper = [ + # Prevents usage of a White Jewel if in a boss fight. Important for the lizard-man trio in Waterway as escaping + # their fight by saving/reloading can render a Special2 permanently missable. + 0x24080001, # ADDIU T0, R0, 0x0001 + 0x15030005, # BNE T0, V1, [forward 0x05] + 0x3C088035, # LUI T0, 0x8035 + 0x9108F7D8, # LBU T0, 0xF7D8 (T0) + 0x24090020, # ADDIU T1, R0, 0x0020 + 0x51090001, # BEQL T0, T1, [forward 0x01] + 0x24020000, # ADDIU V0, R0, 0x0000 + 0x03E00008 # JR RA +] + +music_modifier = [ + # Uses the ID of a song about to be played to pull a switcheroo by grabbing a new ID from a custom table to play + # instead. A hacky way to circumvent song IDs in the compressed overlays' "play song" function calls, but it works! + 0xAFBF001C, # SW RA, 0x001C (SP) + 0x0C004A6B, # JAL 0x800129AC + 0x44800000, # MTC1 R0, F0 + 0x10400003, # BEQZ V0, [forward 0x03] + 0x3C088040, # LUI T0, 0x8040 + 0x01044821, # ADDU T1, T0, A0 + 0x9124CD20, # LBU A0, 0xCD20 (T1) + 0x08004E64 # J 0x80013990 +] + +music_comparer_modifier = [ + # The same as music_modifier, but for the function that compares the "song to play" ID with the one that's currently + # playing. This will ensure the randomized music doesn't reset when going through a loading zone in Villa or CC. + 0x3C088040, # LUI T0, 0x8040 + 0x01044821, # ADDU T1, T0, A0 + 0x9124CD20, # LBU A0, 0xCD20 (T1) + 0x08004A60, # J 0x80012980 +] + +item_customizer = [ + # Allows changing an item's appearance settings and visibility independent of what it actually is as well as setting + # its bitflag literally anywhere in the save file by changing things in the item actor's data as it's being created + # for the below three functions to then utilize. + 0x03205825, # OR T3, T9, R0 + 0x000B5A02, # SRL T3, T3, 8 + 0x316C0080, # ANDI T4, T3, 0x0080 + 0xA0CC0041, # SB T4, 0x0041 (A2) + 0x016C5823, # SUBU T3, T3, T4 + 0xA0CB0040, # SB T3, 0x0040 (A2) + 0x333900FF, # ANDI T9, T9, 0x00FF + 0xA4D90038, # SH T9, 0x0038 (A2) + 0x8CCD0058, # LW T5, 0x0058 (A2) + 0x31ACFF00, # ANDI T4, T5, 0xFF00 + 0x340EFF00, # ORI T6, R0, 0xFF00 + 0x158E000A, # BNE T4, T6, [forward 0x0A] + 0x31AC00FF, # ANDI T4, T5, 0x00FF + 0x240E0002, # ADDIU T6, R0, 0x0002 + 0x018E001B, # DIVU T4, T6 + 0x00006010, # MFHI T4 + 0x000D5C02, # SRL T3, T5, 16 + 0x51800001, # BEQZL T4, [forward 0x01] + 0x000B5C00, # SLL T3, T3, 16 + 0x00006012, # MFLO T4 + 0xA0CC0055, # SB T4, 0x0055 (A2) + 0xACCB0058, # SW T3, 0x0058 (A2) + 0x080494E5, # J 0x80125394 + 0x032A0019 # MULTU T9, T2 +] + +item_appearance_switcher = [ + # Determines an item's model appearance by checking to see if a different item appearance ID was written in a + # specific spot in the actor's data; if one wasn't, then the appearance value will be grabbed from the item's entry + # in the item property table like normal instead. + 0x92080040, # LBU T0, 0x0040 (S0) + 0x55000001, # BNEZL T0, T1, [forward 0x01] + 0x01002025, # OR A0, T0, R0 + 0x03E00008, # JR RA + 0xAFA70024 # SW A3, 0x0024 (SP) +] + +item_model_visibility_switcher = [ + # If 80 is written one byte ahead of the appearance switch value in the item's actor data, parse 0C00 to the + # function that checks if an item should be invisible or not. Otherwise, grab that setting from the item property + # table like normal. + 0x920B0041, # LBU T3, 0x0041 (S0) + 0x316E0080, # ANDI T6, T3, 0x0080 + 0x11C00003, # BEQZ T6, [forward 0x03] + 0x240D0C00, # ADDIU T5, R0, 0x0C00 + 0x03E00008, # JR RA + 0x00000000, # NOP + 0x03E00008, # JR RA + 0x958D0004 # LHU T5, 0x0004 (T4) +] + +item_shine_visibility_switcher = [ + # Same as the above, but for item shines instead of the model. + 0x920B0041, # LBU T3, 0x0041 (S0) + 0x31690080, # ANDI T1, T3, 0x0080 + 0x11200003, # BEQZ T1, [forward 0x03] + 0x00000000, # NOP + 0x03E00008, # JR RA + 0x240C0C00, # ADDIU T4, R0, 0x0C00 + 0x03E00008, # JR RA + 0x958CA908 # LHU T4, 0xA908 (T4) +] + +three_hit_item_flags_setter = [ + # As the function to create items from the 3HB item lists iterates through said item lists, this will pass unique + # flag values to each item when calling the "create item instance" function by right-shifting said flag by a number + # of bits depending on which item in the list it is. Unlike the vanilla game which always puts flags of 0x00000000 + # on each of these. + 0x8DC80008, # LW T0, 0x0008 (T6) + 0x240A0000, # ADDIU T2, R0, 0x0000 + 0x00084C02, # SRL T1, T0, 16 + 0x3108FFFF, # ANDI T0, T0, 0xFFFF + 0x00094842, # SRL T1, T1, 1 + 0x15200003, # BNEZ T1, [forward 0x03] + 0x00000000, # NOP + 0x34098000, # ORI T1, R0, 0x8000 + 0x25080001, # ADDIU T0, T0, 0x0001 + 0x0154582A, # SLT T3, T2, S4 + 0x1560FFF9, # BNEZ T3, [backward 0x07] + 0x254A0001, # ADDIU T2, T2, 0x0001 + 0x00094C00, # SLL T1, T1, 16 + 0x01094025, # OR T0, T0, T1 + 0x0805971E, # J 0x80165C78 + 0xAFA80010 # SW T0, 0x0010 (SP) +] + +chandelier_item_flags_setter = [ + # Same as the above, but for the unique function made specifically and ONLY for the Villa foyer chandelier's item + # list. KCEK, why the heck did you have to do this!? + 0x8F280014, # LW T0, 0x0014 (T9) + 0x240A0000, # ADDIU T2, R0, 0x0000 + 0x00084C02, # SRL T1, T0, 16 + 0x3108FFFF, # ANDI T0, T0, 0xFFFF + 0x00094842, # SRL T1, T1, 1 + 0x15200003, # BNEZ T1, [forward 0x03] + 0x00000000, # NOP + 0x34098000, # ORI T1, R0, 0x8000 + 0x25080001, # ADDIU T0, T0, 0x0001 + 0x0155582A, # SLT T3, T2, S5 + 0x1560FFF9, # BNEZ T3, [backward 0x07] + 0x254A0001, # ADDIU T2, T2, 0x0001 + 0x00094C00, # SLL T1, T1, 16 + 0x01094025, # OR T0, T0, T1 + 0x0805971E, # J 0x80165C78 + 0xAFA80010 # SW T0, 0x0010 (SP) +] + +prev_subweapon_spawn_checker = [ + # When picking up a sub-weapon this will check to see if it's different from the one the player already had (if they + # did have one) and jump to prev_subweapon_dropper, which will spawn a subweapon actor of what they had before + # directly behind them. + 0x322F3031, # Previous sub-weapon bytes + 0x10A00009, # BEQZ A1, [forward 0x09] + 0x00000000, # NOP + 0x10AD0007, # BEQ A1, T5, [forward 0x07] + 0x3C088040, # LUI T0, 0x8040 + 0x01054021, # ADDU T0, T0, A1 + 0x0C0FF418, # JAL 0x803FD060 + 0x9104CFC3, # LBU A0, 0xCFC3 (T0) + 0x2484FF9C, # ADDIU A0, A0, 0xFF9C + 0x3C088039, # LUI T0, 0x8039 + 0xAD049BD4, # SW A0, 0x9BD4 (T0) + 0x0804F0BF, # J 0x8013C2FC + 0x24020001 # ADDIU V0, R0, 0x0001 +] + +prev_subweapon_fall_checker = [ + # Checks to see if a pointer to a previous sub-weapon drop actor spawned by prev_subweapon_dropper is in 80389BD4 + # and calls the function in prev_subweapon_dropper to lower the weapon closer to the ground on the next frame if a + # pointer exists and its actor ID is 0x0027. Once it hits the ground or despawns, the connection to the actor will + # be severed by 0-ing out the pointer. + 0x3C088039, # LUI T0, 0x8039 + 0x8D049BD4, # LW A0, 0x9BD4 (T0) + 0x10800008, # BEQZ A0, [forward 0x08] + 0x00000000, # NOP + 0x84890000, # LH T1, 0x0000 (A0) + 0x240A0027, # ADDIU T2, R0, 0x0027 + 0x152A0004, # BNE T1, T2, [forward 0x04] + 0x00000000, # NOP + 0x0C0FF452, # JAL 0x803FD148 + 0x00000000, # NOP + 0x50400001, # BEQZL V0, [forward 0x01] + 0xAD009BD4, # SW R0, 0x9BD4 (T0) + 0x080FF40F # J 0x803FD03C +] + +prev_subweapon_dropper = [ + # Spawns a pickup actor of the sub-weapon the player had before picking up a new one behind them at their current + # position like in other CVs. This will enable them to pick it back up again if they still want it. + # Courtesy of Moisés; see derp.c in the src folder for the C source code. + 0x27BDFFC8, + 0xAFBF001C, + 0xAFA40038, + 0xAFB00018, + 0x0C0006B4, + 0x2404016C, + 0x00402025, + 0x0C000660, + 0x24050027, + 0x1040002B, + 0x00408025, + 0x3C048035, + 0x848409DE, + 0x00042023, + 0x0C0230D4, + 0x3084FFFF, + 0x44822000, + 0x3C018040, + 0xC428D370, + 0x468021A0, + 0x3C048035, + 0x848409DE, + 0x00042023, + 0x46083282, + 0x3084FFFF, + 0x0C01FFAC, + 0xE7AA0024, + 0x44828000, + 0x3C018040, + 0xC424D374, + 0x468084A0, + 0x27A40024, + 0x00802825, + 0x3C064100, + 0x46049182, + 0x0C004562, + 0xE7A6002C, + 0x3C058035, + 0x24A509D0, + 0x26040064, + 0x0C004530, + 0x27A60024, + 0x3C018035, + 0xC42809D4, + 0x3C0140A0, + 0x44815000, + 0x00000000, + 0x460A4400, + 0xE6100068, + 0xC6120068, + 0xE6120034, + 0x8FAE0038, + 0xA60E0038, + 0x8FBF001C, + 0x8FB00018, + 0x27BD0038, + 0x03E00008, + 0x00000000, + 0x3C068040, + 0x24C6D368, + 0x90CE0000, + 0x27BDFFE8, + 0xAFBF0014, + 0x15C00027, + 0x00802825, + 0x240400DB, + 0x0C0006B4, + 0xAFA50018, + 0x44802000, + 0x3C038040, + 0x2463D364, + 0x3C068040, + 0x24C6D368, + 0x8FA50018, + 0x1040000A, + 0xE4640000, + 0x8C4F0024, + 0x3C013F80, + 0x44814000, + 0xC5E60044, + 0xC4700000, + 0x3C018040, + 0x46083280, + 0x460A8480, + 0xE432D364, + 0x94A20038, + 0x2401000F, + 0x24180001, + 0x10410006, + 0x24010010, + 0x10410004, + 0x2401002F, + 0x10410002, + 0x24010030, + 0x14410005, + 0x3C014040, + 0x44813000, + 0xC4640000, + 0x46062200, + 0xE4680000, + 0xA0D80000, + 0x10000023, + 0x24020001, + 0x3C038040, + 0x2463D364, + 0xC4600000, + 0xC4A20068, + 0x3C038039, + 0x24639BD0, + 0x4600103E, + 0x00001025, + 0x45000006, + 0x00000000, + 0x44808000, + 0xE4A00068, + 0xA0C00000, + 0x10000014, + 0xE4700000, + 0x3C038039, + 0x24639BD0, + 0x3C018019, + 0xC42AC870, + 0xC4600000, + 0x460A003C, + 0x00000000, + 0x45000006, + 0x3C018019, + 0xC432C878, + 0x46120100, + 0xE4640000, + 0xC4600000, + 0xC4A20068, + 0x46001181, + 0x24020001, + 0xE4A60068, + 0xC4A80068, + 0xE4A80034, + 0x8FBF0014, + 0x27BD0018, + 0x03E00008, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x0000001B, + 0x060048E0, + 0x40000000, + 0x06AEFFD3, + 0x06004B30, + 0x40000000, + 0x00000000, + 0x06004CB8, + 0x0000031A, + 0x002C0000, + 0x060059B8, + 0x40000248, + 0xFFB50186, + 0x06005B68, + 0xC00001DF, + 0x00000000, + 0x06005C88, + 0x80000149, + 0x00000000, + 0x06005DC0, + 0xC0000248, + 0xFFB5FE7B, + 0x06005F70, + 0xC00001E0, + 0x00000000, + 0x06006090, + 0x8000014A, + 0x00000000, + 0x06007D28, + 0x4000010E, + 0xFFF100A5, + 0x06007F60, + 0xC0000275, + 0x00000000, + 0x06008208, + 0x800002B2, + 0x00000000, + 0x060083B0, + 0xC000010D, + 0xFFF2FF5C, + 0x060085E8, + 0xC0000275, + 0x00000000, + 0x06008890, + 0x800002B2, + 0x00000000, + 0x3D4CCCCD, + 0x3FC00000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0xB8000100, + 0xB8000100, +] + +subweapon_surface_checker = [ + # During the process of remotely giving an item received via multiworld, this will check to see if the item being + # received is a subweapon and, if it is, wait until the player is not above an abyss or instant kill surface before + # giving it. This is to ensure dropped previous subweapons won't land somewhere inaccessible. + 0x2408000D, # ADDIU T0, R0, 0x000D + 0x11040006, # BEQ T0, A0, [forward 0x06] + 0x2409000E, # ADDIU T1, R0, 0x000E + 0x11240004, # BEQ T1, A0, [forward 0x04] + 0x2408000F, # ADDIU T0, R0, 0x000F + 0x11040002, # BEQ T0, A0, [forward 0x02] + 0x24090010, # ADDIU T1, R0, 0x0010 + 0x1524000B, # BNE T1, A0, [forward 0x0B] + 0x3C0A800D, # LUI T2, 0x800D + 0x8D4A7B5C, # LW T2, 0x7B5C (T2) + 0x1140000E, # BEQZ T2, [forward 0x0E] + 0x00000000, # NOP + 0x914A0001, # LBU T2, 0x0001 (T2) + 0x240800A2, # ADDIU T0, R0, 0x00A2 + 0x110A000A, # BEQ T0, T2, [forward 0x0A] + 0x24090092, # ADDIU T1, R0, 0x0092 + 0x112A0008, # BEQ T1, T2, [forward 0x08] + 0x24080080, # ADDIU T0, R0, 0x0080 + 0x110A0006, # BEQ T0, T2, [forward 0x06] + 0x956C00DD, # LHU T4, 0x00DD (T3) + 0xA1600000, # SB R0, 0x0000 (T3) + 0x258C0001, # ADDIU T4, T4, 0x0001 + 0x080FF8D0, # J 0x803FE340 + 0xA56C00DD, # SH T4, 0x00DD (T3) + 0x00000000, # NOP + 0x03E00008 # JR RA +] + +countdown_number_displayer = [ + # Displays a number below the HUD clock of however many items are left to find in whichever stage the player is in. + # Which number in the save file to display depends on which map the player is currently on. It can track either + # items marked progression only or all locations in the stage. + # Courtesy of Moisés; see print_text_ovl.c in the src folder for the C source code. + 0x27BDFFD8, + 0xAFBF0024, + 0x00002025, + 0x0C000360, + 0x2405000C, + 0x3C038040, + 0x3C198034, + 0x2463D6D0, + 0x37392814, + 0x240E0002, + 0x3C0F0860, + 0x24180014, + 0xAC620000, + 0xAFB80018, + 0xAFAF0014, + 0xAFAE0010, + 0xAFB9001C, + 0x00002025, + 0x00402825, + 0x2406001E, + 0x0C0FF55D, + 0x24070028, + 0x8FBF0024, + 0x3C018040, + 0xAC22D6D4, + 0x03E00008, + 0x27BD0028, + 0x27BDFFE0, + 0xAFA40020, + 0x93AE0023, + 0x3C058039, + 0xAFBF001C, + 0x3C048040, + 0x3C068040, + 0x240F0014, + 0x00AE2821, + 0x90A59CA4, + 0xAFAF0010, + 0x8CC6D6D0, + 0x8C84D6D4, + 0x0C0FF58A, + 0x24070002, + 0x8FBF001C, + 0x27BD0020, + 0x03E00008, + 0x00000000, + 0x00000000, + 0x00000000, + 0x90820000, + 0x00001825, + 0x50400008, + 0xA4A00000, + 0xA4A20000, + 0x90820001, + 0x24840001, + 0x24A50002, + 0x1440FFFB, + 0x24630001, + 0xA4A00000, + 0x03E00008, + 0x00601025, + 0x27BDFFD8, + 0xAFBF0024, + 0xAFB0001C, + 0xAFA40028, + 0xAFA5002C, + 0xAFB10020, + 0xAFA60030, + 0xAFA70034, + 0x00008025, + 0x24050064, + 0x0C000360, + 0x00002025, + 0x8FA40040, + 0x00408825, + 0x3C05800A, + 0x10800004, + 0x8FA6003C, + 0x0C04B2E2, + 0x8CA5B450, + 0x00408025, + 0x5200001A, + 0x8FBF0024, + 0x12200017, + 0x8FAE0028, + 0x11C00015, + 0x02002025, + 0x97A5002E, + 0x97A60032, + 0x0C04B33F, + 0x24070001, + 0x02002025, + 0x83A50037, + 0x87A6003A, + 0x00003825, + 0x0C04B345, + 0xAFA00010, + 0x8FA40028, + 0x0C0FF51C, + 0x02202825, + 0x0C006CF0, + 0x02202025, + 0x02002025, + 0x02202825, + 0x00003025, + 0x0C04B34E, + 0x00003825, + 0x8FBF0024, + 0x02001025, + 0x8FB0001C, + 0x8FB10020, + 0x03E00008, + 0x27BD0028, + 0x27BDFFD8, + 0x8FAE0044, + 0xAFB00020, + 0xAFBF0024, + 0xAFA40028, + 0xAFA5002C, + 0xAFA60030, + 0xAFA70034, + 0x11C00007, + 0x00008025, + 0x3C05800A, + 0x8CA5B450, + 0x01C02025, + 0x0C04B2E2, + 0x8FA6003C, + 0x00408025, + 0x12000017, + 0x8FAF002C, + 0x11E00015, + 0x02002025, + 0x97A50032, + 0x97A60036, + 0x0C04B33F, + 0x24070001, + 0x02002025, + 0x24050001, + 0x24060064, + 0x00003825, + 0x0C04B345, + 0xAFA00010, + 0x8FA40028, + 0x8FA5002C, + 0x93A6003B, + 0x0C04B5BD, + 0x8FA70040, + 0x02002025, + 0x8FA5002C, + 0x00003025, + 0x0C04B34E, + 0x00003825, + 0x8FBF0024, + 0x02001025, + 0x8FB00020, + 0x03E00008, + 0x27BD0028, + 0x27BDFFE8, + 0xAFBF0014, + 0xAFA40018, + 0xAFA5001C, + 0xAFA60020, + 0x10C0000B, + 0xAFA70024, + 0x00A02025, + 0x00C02825, + 0x93A60027, + 0x0C04B5BD, + 0x8FA70028, + 0x8FA20018, + 0x3C010100, + 0x8C4F0000, + 0x01E1C025, + 0xAC580000, + 0x8FBF0014, + 0x27BD0018, + 0x03E00008, + 0x00000000, + 0xAFA50004, + 0x1080000E, + 0x30A500FF, + 0x24010001, + 0x54A10008, + 0x8C980000, + 0x8C8E0000, + 0x3C017FFF, + 0x3421FFFF, + 0x01C17824, + 0x03E00008, + 0xAC8F0000, + 0x8C980000, + 0x3C018000, + 0x0301C825, + 0xAC990000, + 0x03E00008, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000 +] + +countdown_number_manager = [ + # Tables and code for managing things about the Countdown number at the appropriate times. + 0x00010102, # Map ID offset table start + 0x02020D03, + 0x04050505, + 0x0E0E0E05, + 0x07090806, + 0x0C0C000B, + 0x0C050D0A, + 0x00000000, # Table end + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000001, # Major identifiers table start + 0x01000000, + 0x00000000, + 0x00000000, + 0x01000000, + 0x01010000, + 0x00010101, + 0x01010101, + 0x01010101, + 0x01010000, + 0x00000000, # Table end + # Decrements the counter upon picking up an item if the counter should be decremented. + 0x90E80039, # LBU T0, 0x0039 (A3) + 0x240B0011, # ADDIU T3, R0, 0x0011 + 0x110B0002, # BEQ T0, T3, [forward 0x02] + 0x90EA0040, # LBU T2, 0x0040 (A3) + 0x2548FFFF, # ADDIU T0, T2, 0xFFFF + 0x3C098040, # LUI T1, 0x8040 + 0x01284821, # ADDIU T1, T1, T0 + 0x9129D71C, # LBU T1, 0xD71C (T1) + 0x11200009, # BEQZ T1, [forward 0x09] + 0x3C088039, # LUI T0, 0x8039 + 0x91099EE1, # LBU T1, 0x9EE1 (T0) + 0x3C0A8040, # LUI T2, 0x8040 + 0x01495021, # ADDU T2, T2, T1 + 0x914AD6DC, # LBU T2, 0xD6DC (T2) + 0x010A4021, # ADDU T0, T0, T2 + 0x91099CA4, # LBU T1, 0x9CA4 (T0) + 0x2529FFFF, # ADDIU T1, T1, 0xFFFF + 0xA1099CA4, # SB T1, 0x9CA4 (T0) + 0x03E00008, # JR RA + 0x00000000, # NOP + # Moves the number to/from its pause menu position when pausing/un-pausing. + 0x3C088040, # LUI T0, 0x8040 + 0x8D08D6D4, # LW T0, 0xD6D4 + 0x11000009, # BEQZ T0, [forward 0x09] + 0x92090000, # LBU T1, 0x0000 (S0) + 0x14200004, # BNEZ AT, [forward 0x04] + 0x3C0A0033, # LUI T2, 0x0033 + 0x254A001F, # ADDIU T2, T2, 0x001F + 0x03E00008, # JR RA + 0xAD0A0014, # SW T2, 0x0014 (T0) + 0x3C0A00D4, # LUI T2, 0x00D4 + 0x254A003C, # ADDIU T2, T2, 0x003C + 0xAD0A0014, # SW T2, 0x0014 (T0) + 0x03E00008, # JR RA + 0x00000000, # NOP + # Hides the number when going into a cutscene or the Options menu. + 0x3C048040, # LUI A0, 0x8040 + 0x8C84D6D4, # LW A0, 0xD6D4 (A0) + 0x0C0FF59F, # JAL 0x803FD67C + 0x24050000, # ADDIU A1, R0, 0x0000 + 0x0804DFE0, # J 0x80137FB0 + 0x3C048000, # LUI A0, 0x8000 + 0x00000000, # NOP + # Un-hides the number when leaving a cutscene or the Options menu. + 0x3C048040, # LUI A0, 0x8040 + 0x8C84D6D4, # LW A0, 0xD6D4 (A0) + 0x0C0FF59F, # JAL 0x803FD67C + 0x24050001, # ADDIU A1, R0, 0x0000 + 0x0804DFFA, # J 0x8013 + 0x3C047FFF, # LUI A0, 0x7FFFF + 0x00000000, # NOP + # Kills the last map's pointer to the Countdown stuff. + 0x3C088040, # LUI T0, 0x8040 + 0xFD00D6D0, # SD R0, 0xD6D0 (T0) + 0x03E00008 # JR RA +] + +new_game_extras = [ + # Upon starting a new game, this will write anything extra to the save file data that the run should have at the + # start. The initial Countdown numbers begin here. + 0x24080000, # ADDIU T0, R0, 0x0000 + 0x24090010, # ADDIU T1, R0, 0x0010 + 0x11090008, # BEQ T0, T1, [forward 0x08] + 0x3C0A8040, # LUI T2, 0x8040 + 0x01485021, # ADDU T2, T2, T0 + 0x8D4AD818, # LW T2, 0xD818 (T2) + 0x3C0B8039, # LUI T3, 0x8039 + 0x01685821, # ADDU T3, T3, T0 + 0xAD6A9CA4, # SW T2, 0x9CA4 (T3) + 0x1000FFF8, # B [backward 0x08] + 0x25080004, # ADDIU T0, T0, 0x0004 + # start_inventory begins here + 0x3C088039, # LUI T0, 0x8039 + 0x91099C27, # LBU T1, 0x9C27 (T0) + 0x31290010, # ANDI T1, T1, 0x0010 + 0x15200005, # BNEZ T1, [forward 0x05] + 0x24090000, # ADDIU T1, R0, 0x0000 <- Starting jewels + 0xA1099C49, # SB T1, 0x9C49 + 0x3C0A8040, # LUI T2, 0x8040 + 0x8D4BE514, # LW T3, 0xE514 (T2) <- Starting money + 0xAD0B9C44, # SW T3, 0x9C44 (T0) + 0x24090000, # ADDIU T1, R0, 0x0000 <- Starting PowerUps + 0xA1099CED, # SB T1, 0x9CED (T0) + 0x24090000, # ADDIU T1, R0, 0x0000 <- Starting sub-weapon + 0xA1099C43, # SB T1, 0x9C43 (T0) + 0x24090000, # ADDIU T1, R0, 0x0000 <- Starting Ice Traps + 0xA1099BE2, # SB T1, 0x9BE2 (T0) + 0x240C0000, # ADDIU T4, R0, 0x0000 + 0x240D0022, # ADDIU T5, R0, 0x0022 + 0x11AC0007, # BEQ T5, T4, [forward 0x07] + 0x3C0A8040, # LUI T2, 0x8040 + 0x014C5021, # ADDU T2, T2, T4 + 0x814AE518, # LB T2, 0xE518 <- Starting inventory items + 0x25080001, # ADDIU T0, T0, 0x0001 + 0xA10A9C4A, # SB T2, 0x9C4A (T0) + 0x1000FFF9, # B [backward 0x07] + 0x258C0001, # ADDIU T4, T4, 0x0001 + 0x03E00008 # JR RA +] + +shopsanity_stuff = [ + # Everything related to shopsanity. + # Flag table (in bytes) start + 0x80402010, + 0x08000000, + 0x00000000, + 0x00000000, + 0x00040200, + # Replacement item table (in halfwords) start + 0x00030003, + 0x00030003, + 0x00030000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000003, + 0x00030000, + # Switches the vanilla item being bought with the randomized one, if its flag is un-set, and sets its flag. + 0x3C088040, # LUI T0, 0x8040 + 0x01044021, # ADDU T0, T0, A0 + 0x9109D8CA, # LBU T1, 0xD8CA (T0) + 0x3C0B8039, # LUI T3, 0x8039 + 0x916A9C1D, # LBU T2, 0x9C1D (T3) + 0x01496024, # AND T4, T2, T1 + 0x15800005, # BNEZ T4, [forward 0x05] + 0x01495025, # OR T2, T2, T1 + 0xA16A9C1D, # SB T2, 0x9C1D (T3) + 0x01044021, # ADDU T0, T0, A0 + 0x9504D8D8, # LHU A0, 0xD8D8 (T0) + 0x308400FF, # ANDI A0, A0, 0x00FF + 0x0804EFFB, # J 0x8013BFEC + 0x00000000, # NOP + # Switches the vanilla item model on the buy menu with the randomized item if the randomized item isn't purchased. + 0x3C088040, # LUI T0, 0x8040 + 0x01044021, # ADDU T0, T0, A0 + 0x9109D8CA, # LBU T1, 0xD8CA (T0) + 0x3C0B8039, # LUI T3, 0x8039 + 0x916A9C1D, # LBU T2, 0x9C1D (T3) + 0x01495024, # AND T2, T2, T1 + 0x15400005, # BNEZ T2, [forward 0x05] + 0x01044021, # ADDU T0, T0, A0 + 0x9504D8D8, # LHU A0, 0xD8D8 (T0) + 0x00046202, # SRL T4, A0, 8 + 0x55800001, # BNEZL T4, [forward 0x01] + 0x01802021, # ADDU A0, T4, R0 + 0x0804F180, # J 0x8013C600 + 0x00000000, # NOP + # Replacement item names table start. + 0x00010203, + 0x04000000, + 0x00000000, + 0x00000000, + 0x00050600, + 0x00000000, + # Switches the vanilla item name in the shop menu with the randomized item if the randomized item isn't purchased. + 0x3C088040, # LUI T0, 0x8040 + 0x01064021, # ADDU T0, T0, A2 + 0x9109D8CA, # LBU T1, 0xD8CA (T0) + 0x3C0B8039, # LUI T3, 0x8039 + 0x916A9C1D, # LBU T2, 0x9C1D (T3) + 0x01495024, # AND T2, T2, T1 + 0x15400004, # BNEZ T2, [forward 0x04] + 0x00000000, # NOP + 0x9105D976, # LBU A1, 0xD976 (T0) + 0x3C048001, # LUI A0, 8001 + 0x3484A100, # ORI A0, A0, 0xA100 + 0x0804B39F, # J 0x8012CE7C + 0x00000000, # NOP + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + # Displays "Not purchased." if the selected randomized item is nor purchased, or the current holding amount of that + # slot's vanilla item if it is. + 0x3C0C8040, # LUI T4, 0x8040 + 0x018B6021, # ADDU T4, T4, T3 + 0x918DD8CA, # LBU T5, 0xD8CA (T4) + 0x3C0E8039, # LUI T6, 0x8039 + 0x91D89C1D, # LBU T8, 0x9C1D (T6) + 0x030DC024, # AND T8, T8, T5 + 0x13000003, # BEQZ T8, [forward 0x03] + 0x00000000, # NOP + 0x0804E819, # J 0x8013A064 + 0x00000000, # NOP + 0x0804E852, # J 0x8013A148 + 0x820F0061, # LB T7, 0x0061 (S0) + 0x00000000, # NOP + # Displays a custom item description if the selected randomized item is not purchased. + 0x3C088040, # LUI T0, 0x8040 + 0x01054021, # ADDU T0, T0, A1 + 0x9109D8D0, # LBU T1, 0xD8D0 (T0) + 0x3C0A8039, # LUI T2, 0x8039 + 0x914B9C1D, # LBU T3, 0x9C1D (T2) + 0x01695824, # AND T3, T3, T1 + 0x15600003, # BNEZ T3, [forward 0x03] + 0x00000000, # NOP + 0x3C048002, # LUI A0, 0x8002 + 0x24849C00, # ADDIU A0, A0, 0x9C00 + 0x0804B39F # J 0x8012CE7C +] + +special_sound_notifs = [ + # Plays a distinct sound whenever you get enough Special1s to unlock a new location or enough Special2s to unlock + # Dracula's door. + 0x3C088013, # LUI A0, 0x8013 + 0x9108AC9F, # LBU T0, 0xAC57 (T0) + 0x3C098039, # LUI T1, 0x8039 + 0x91299C4C, # LBU T1, 0x9C4B (T1) + 0x15090003, # BNE T0, T1, [forward 0x03] + 0x00000000, # NOP + 0x0C004FAB, # JAL 0x80013EAC + 0x24040162, # ADDIU A0, R0, 0x0162 + 0x0804F0BF, # J 0x8013C2FC + 0x00000000, # NOP + 0x3C088013, # LUI T0, 0x8013 + 0x9108AC57, # LBU T0, 0xAC57 (T0) + 0x3C098039, # LUI T1, 0x8039 + 0x91299C4B, # LBU T1, 0x9C4B (T1) + 0x0128001B, # DIVU T1, T0 + 0x00005010, # MFHI + 0x15400006, # BNEZ T2, [forward 0x06] + 0x00005812, # MFLO T3 + 0x296C0008, # SLTI T4, T3, 0x0008 + 0x11800003, # BEQZ T4, [forward 0x03] + 0x00000000, # NOP + 0x0C004FAB, # JAL 0x80013EAC + 0x2404019E, # ADDIU A0, R0, 0x019E + 0x0804F0BF # J 0x8013C2FC +] + +map_text_redirector = [ + # Checks for Map Texts 06 or 08 if in the Forest or Castle Wall Main maps respectively and redirects the text + # pointer to a blank string, skipping all the yes/no prompt text for pulling levers. + 0x0002FFFF, # Dummy text string + 0x3C0B8039, # LUI T3, 0x8039 + 0x91689EE1, # LBU T0, 0x9EE1 (T3) + 0x1100000F, # BEQZ T0, [forward 0x0F] + 0x24090006, # ADDIU T1, R0, 0x0006 + 0x240A0002, # ADDIU T2, R0, 0x0002 + 0x110A000C, # BEQ T0, T2, [forward 0x0C] + 0x24090008, # ADDIU T1, R0, 0x0008 + 0x240A0009, # ADDIU T2, R0, 0x0009 + 0x110A0009, # BEQ T0, T2, [forward 0x09] + 0x24090004, # ADDIU T1, R0, 0x0004 + 0x240A000A, # ADDIU T2, R0, 0x000A + 0x110A0006, # BEQ T0, T2, [forward 0x06] + 0x24090001, # ADDIU T1, R0, 0x0001 + 0x240A000C, # ADDIU T2, R0, 0x000C + 0x110A0003, # BEQ T0, T2, [forward 0x03] + 0x2409000C, # ADDIU T1, R0, 0x000C + 0x10000008, # B 0x803FDB34 + 0x00000000, # NOP + 0x15250006, # BNE T1, A1, [forward 0x06] + 0x00000000, # NOP + 0x3C04803F, # LUI A0, 0x803F + 0x3484DACC, # ORI A0, A0, 0xDACC + 0x24050000, # ADDIU A1, R0, 0x0000 + 0x0804B39F, # J 0x8012CE7C + 0x00000000, # NOP + # Redirects to a custom message if you try placing the bomb ingredients at the bottom CC crack before deactivating + # the seal. + 0x24090009, # ADDIU T1, R0, 0x0009 + 0x15090009, # BNE T0, T1, [forward 0x09] + 0x240A0002, # ADDIU T2, R0, 0x0002 + 0x15450007, # BNE T2, A1, [forward 0x07] + 0x916A9C18, # LBU T2, 0x9C18 (T3) + 0x314A0001, # ANDI T2, T2, 0x0001 + 0x15400004, # BNEZ T2, [forward 0x04] + 0x00000000, # NOP + 0x3C04803F, # LUI A0, 0x803F + 0x3484DBAC, # ORI A0, A0, 0xDBAC + 0x24050000, # ADDIU A1, R0, 0x0000 + 0x0804B39F, # J 0x8012CE7C + 0x00000000, # NOP + # Checks for Map Texts 02 or 00 if in the Villa hallway or CC lizard lab maps respectively and redirects the text + # pointer to a blank string, skipping all the NPC dialogue mandatory for checks. + 0x3C088039, # LUI T0, 0x8039 + 0x91089EE1, # LBU T0, 0x9EE1 (T0) + 0x240A0005, # ADDIU T2, R0, 0x0005 + 0x110A0006, # BEQ T0, T2, [forward 0x06] + 0x24090002, # ADDIU T1, R0, 0x0002 + 0x240A000C, # ADDIU T2, R0, 0x000C + 0x110A0003, # BEQ T0, T2, [forward 0x03] + 0x24090000, # ADDIU T1, R0, 0x0000 + 0x0804B39F, # J 0x8012CE7C + 0x00000000, # NOP + 0x15250004, # BNE T1, A1, [forward 0x04] + 0x00000000, # NOP + 0x3C04803F, # LUI A0, 0x803F + 0x3484DACC, # ORI A0, A0, 0xDACC + 0x24050000, # ADDIU A1, R0, 0x0000 + 0x0804B39F # J 0x8012CE7C +] + +special_descriptions_redirector = [ + # Redirects the menu description when looking at the Special1 and 2 items to different, custom strings that tell + # how many are needed per warp and to fight Dracula respectively, and how many there are of both in the whole seed. + 0x240A0003, # ADDIU T2, R0, 0x0003 + 0x10AA0005, # BEQ A1, T2, [forward 0x05] + 0x240A0004, # ADDIU T2, R0, 0x0004 + 0x10AA0003, # BEQ A1, T2, [forward 0x03] + 0x00000000, # NOP + 0x0804B39F, # J 0x8012CE7C + 0x00000000, # NOP + 0x3C04803F, # LUI A0, 0x803F + 0x3484E53C, # ORI A0, A0, 0xE53C + 0x24A5FFFD, # ADDIU A1, A1, 0xFFFD + 0x0804B39F # J 0x8012CE7C +] + +forest_cw_villa_intro_cs_player = [ + # Plays the Forest, Castle Wall, or Villa intro cutscene after transitioning to a different map if the map being + # transitioned to is the start of their levels respectively. Gets around the fact that they have to be set on the + # previous loading zone for them to play normally. + 0x3C088039, # LUI T0, 0x8039 + 0x8D099EE0, # LW T1, 0x9EE0 (T0) + 0x1120000B, # BEQZ T1 T1, [forward 0x0B] + 0x240B0000, # ADDIU T3, R0, 0x0000 + 0x3C0A0002, # LUI T2, 0x0002 + 0x112A0008, # BEQ T1, T2, [forward 0x08] + 0x240B0007, # ADDIU T3, R0, 0x0007 + 0x254A0007, # ADDIU T2, T2, 0x0007 + 0x112A0005, # BEQ T1, T2, [forward 0x05] + 0x3C0A0003, # LUI T2, 0x0003 + 0x112A0003, # BEQ T1, T2, [forward 0x03] + 0x240B0003, # ADDIU T3, R0, 0x0003 + 0x08005FAA, # J 0x80017EA8 + 0x00000000, # NOP + 0x010B6021, # ADDU T4, T0, T3 + 0x918D9C08, # LBU T5, 0x9C08 (T4) + 0x31AF0001, # ANDI T7, T5, 0x0001 + 0x15E00009, # BNEZ T7, [forward 0x09] + 0x240E0009, # ADDIU T6, R0, 0x0009 + 0x3C180003, # LUI T8, 0x0003 + 0x57090001, # BNEL T8, T1, [forward 0x01] + 0x240E0004, # ADDIU T6, R0, 0x0004 + 0x15200003, # BNEZ T1, [forward 0x03] + 0x240F0001, # ADDIU T7, R0, 0x0001 + 0xA18F9C08, # SB T7, 0x9C08 (T4) + 0x240E003C, # ADDIU T6, R0, 0x003C + 0xA10E9EFF, # SB T6, 0x9EFF (T0) + 0x08005FAA # J 0x80017EA8 +] + +map_id_refresher = [ + # After transitioning to a different map, if this detects the map ID being transitioned to as FF, it will write back + # the past map ID so that the map will reset. Useful for thngs like getting around a bug wherein the camera fixes in + # place if you enter a loading zone that doesn't actually change the map, which can happen in a seed that gives you + # any character tower stage at the very start. + 0x240800FF, # ADDIU T0, R0, 0x00FF + 0x110E0003, # BEQ T0, T6, [forward 0x03] + 0x00000000, # NOP + 0x03E00008, # JR RA + 0xA44E61D8, # SH T6, 0x61D8 + 0x904961D9, # LBU T1, 0x61D9 + 0xA0496429, # SB T1, 0x6429 + 0x03E00008 # JR RA +] + +character_changer = [ + # Changes the character being controlled if the player is holding L while loading into a map by swapping the + # character ID. + 0x3C08800D, # LUI T0, 0x800D + 0x910B5E21, # LBU T3, 0x5E21 (T0) + 0x31680020, # ANDI T0, T3, 0x0020 + 0x3C0A8039, # LUI T2, 0x8039 + 0x1100000B, # BEQZ T0, [forward 0x0B] + 0x91499C3D, # LBU T1, 0x9C3D (T2) + 0x11200005, # BEQZ T1, [forward 0x05] + 0x24080000, # ADDIU T0, R0, 0x0000 + 0xA1489C3D, # SB T0, 0x9C3D (T2) + 0x25080001, # ADDIU T0, T0, 0x0001 + 0xA1489BC2, # SB T0, 0x9BC2 (T2) + 0x10000004, # B [forward 0x04] + 0x24080001, # ADDIU T0, R0, 0x0001 + 0xA1489C3D, # SB T0, 0x9C3D (T2) + 0x25080001, # ADDIU T0, T0, 0x0001 + 0xA1489BC2, # SB T0, 0x9BC2 (T2) + # Changes the alternate costume variables if the player is holding C-up. + 0x31680008, # ANDI T0, T3, 0x0008 + 0x11000009, # BEQZ T0, [forward 0x09] + 0x91499C24, # LBU T1, 0x9C24 (T2) + 0x312B0040, # ANDI T3, T1, 0x0040 + 0x2528FFC0, # ADDIU T0, T1, 0xFFC0 + 0x15600003, # BNEZ T3, [forward 0x03] + 0x240C0000, # ADDIU T4, R0, 0x0000 + 0x25280040, # ADDIU T0, T1, 0x0040 + 0x240C0001, # ADDIU T4, R0, 0x0001 + 0xA1489C24, # SB T0, 0x9C24 (T2) + 0xA14C9CEE, # SB T4, 0x9CEE (T2) + 0x080062AA, # J 0x80018AA8 + 0x00000000, # NOP + # Plays the attack sound of the character being changed into to indicate the change was successful. + 0x3C088039, # LUI T0, 0x8039 + 0x91099BC2, # LBU T1, 0x9BC2 (T0) + 0xA1009BC2, # SB R0, 0x9BC2 (T0) + 0xA1009BC1, # SB R0, 0x9BC1 (T0) + 0x11200006, # BEQZ T1, [forward 0x06] + 0x2529FFFF, # ADDIU T1, T1, 0xFFFF + 0x240402F6, # ADDIU A0, R0, 0x02F6 + 0x55200001, # BNEZL T1, [forward 0x01] + 0x240402F8, # ADDIU A0, R0, 0x02F8 + 0x08004FAB, # J 0x80013EAC + 0x00000000, # NOP + 0x03E00008 # JR RA +] + +panther_dash = [ + # Changes various movement parameters when holding C-right so the player will move way faster. + # Increases movement speed and speeds up the running animation. + 0x3C08800D, # LUI T0, 0x800D + 0x91085E21, # LBU T0, 0x5E21 (T0) + 0x31080001, # ANDI T0, T0, 0x0001 + 0x24093FEA, # ADDIU T1, R0, 0x3FEA + 0x11000004, # BEQZ T0, [forward 0x04] + 0x240B0010, # ADDIU T3, R0, 0x0010 + 0x3C073F20, # LUI A3, 0x3F20 + 0x240940AA, # ADDIU T1, R0, 0x40AA + 0x240B000A, # ADDIU T3, R0, 0x000A + 0x3C0C8035, # LUI T4, 0x8035 + 0xA18B07AE, # SB T3, 0x07AE (T4) + 0xA18B07C2, # SB T3, 0x07C2 (T4) + 0x3C0A8034, # LUI T2, 0x8034 + 0x03200008, # JR T9 + 0xA5492BD8, # SH T1, 0x2BD8 (T2) + 0x00000000, # NOP + # Increases the turning speed so that handling is better. + 0x3C08800D, # LUI T0, 0x800D + 0x91085E21, # LBU T0, 0x5E21 (T0) + 0x31080001, # ANDI T0, T0, 0x0001 + 0x11000002, # BEQZ T0, [forward 0x02] + 0x240A00D9, # ADDIU T2, R0, 0x00D9 + 0x240A00F0, # ADDIU T2, R0, 0x00F0 + 0x3C0B8039, # LUI T3, 0x8039 + 0x916B9C3D, # LBU T3, 0x9C3D (T3) + 0x11600003, # BEQZ T3, [forward 0x03] + 0xD428DD58, # LDC1 F8, 0xDD58 (AT) + 0x03E00008, # JR RA + 0xA02ADD59, # SB T2, 0xDD59 (AT) + 0xD428D798, # LDC1 F8, 0xD798 (AT) + 0x03E00008, # JR RA + 0xA02AD799, # SB T2, 0xD799 (AT) + 0x00000000, # NOP + # Increases crouch-walking x and z speed. + 0x3C08800D, # LUI T0, 0x800D + 0x91085E21, # LBU T0, 0x5E21 (T0) + 0x31080001, # ANDI T0, T0, 0x0001 + 0x11000002, # BEQZ T0, [forward 0x02] + 0x240A00C5, # ADDIU T2, R0, 0x00C5 + 0x240A00F8, # ADDIU T2, R0, 0x00F8 + 0x3C0B8039, # LUI T3, 0x8039 + 0x916B9C3D, # LBU T3, 0x9C3D (T3) + 0x15600005, # BNEZ T3, [forward 0x05] + 0x00000000, # NOP + 0xA02AD801, # SB T2, 0xD801 (AT) + 0xA02AD809, # SB T2, 0xD809 (AT) + 0x03E00008, # JR RA + 0xD430D800, # LDC1 F16, 0xD800 (AT) + 0xA02ADDC1, # SB T2, 0xDDC1 (AT) + 0xA02ADDC9, # SB T2, 0xDDC9 (AT) + 0x03E00008, # JR RA + 0xD430DDC0 # LDC1 F16, 0xDDC0 (AT) +] + +panther_jump_preventer = [ + # Optional hack to prevent jumping while moving at the increased panther dash speed as a way to prevent logic + # sequence breaks that would otherwise be impossible without it. Such sequence breaks are never considered in logic + # either way. + + # Decreases a "can running jump" value by 1 per frame unless it's at 0, or while in the sliding state. When the + # player lets go of C-right, their running speed should have returned to a normal amount by the time it hits 0. + 0x9208007F, # LBU T0, 0x007F (S0) + 0x24090008, # ADDIU T1, R0, 0x0008 + 0x11090005, # BEQ T0, T1, [forward 0x05] + 0x3C088039, # LUI T0, 0x8039 + 0x91099BC1, # LBU T1, 0x9BC1 (T0) + 0x11200002, # BEQZ T1, [forward 0x02] + 0x2529FFFF, # ADDIU T1, T1, 0xFFFF + 0xA1099BC1, # SB T1, 0x9BC1 (T0) + 0x080FF413, # J 0x803FD04C + 0x00000000, # NOP + # Increases the "can running jump" value by 2 per frame while panther dashing unless it's at 8 or higher, at which + # point the player should be at the max panther dash speed. + 0x00074402, # SRL T0, A3, 16 + 0x29083F7F, # SLTI T0, T0, 0x3F7F + 0x11000006, # BEQZ T0, [forward 0x06] + 0x3C098039, # LUI T1, 0x8039 + 0x912A9BC1, # LBU T2, 0x9BC1 (T1) + 0x254A0002, # ADDIU T2, T2, 0x0002 + 0x294B0008, # SLTI T3, T2, 0x0008 + 0x55600001, # BNEZL T3, [forward 0x01] + 0xA12A9BC1, # SB T2, 0x9BC1 (T1) + 0x03200008, # JR T9 + 0x00000000, # NOP + # Makes running jumps only work while the "can running jump" value is at 0. Otherwise, their state won't change. + 0x3C010001, # LUI AT, 0x0001 + 0x3C088039, # LUI T0, 0x8039 + 0x91089BC1, # LBU T0, 0x9BC1 (T0) + 0x55000001, # BNEZL T0, [forward 0x01] + 0x3C010000, # LUI AT, 0x0000 + 0x03E00008 # JR RA +] + +gondola_skipper = [ + # Upon stepping on one of the gondolas in Tunnel to activate it, this will instantly teleport you to the other end + # of the gondola course depending on which one activated, skipping the entire 3-minute wait to get there. + 0x3C088039, # LUI T0, 0x8039 + 0x240900FF, # ADDIU T1, R0, 0x00FF + 0xA1099EE1, # SB T1, 0x9EE1 (T0) + 0x31EA0020, # ANDI T2, T7, 0x0020 + 0x3C0C3080, # LUI T4, 0x3080 + 0x358C9700, # ORI T4, T4, 0x9700 + 0x154B0003, # BNE T2, T3, [forward 0x03] + 0x24090002, # ADDIU T1, R0, 0x0002 + 0x24090003, # ADDIU T1, R0, 0x0003 + 0x3C0C7A00, # LUI T4, 0x7A00 + 0xA1099EE3, # SB T1, 0x9EE3 (T0) + 0xAD0C9EE4, # SW T4, 0x9EE4 (T0) + 0x3C0D0010, # LUI T5, 0x0010 + 0x25AD0010, # ADDIU T5, T5, 0x0010 + 0xAD0D9EE8, # SW T5, 0x9EE8 (T0) + 0x08063E68 # J 0x8018F9A0 +] + +mandragora_with_nitro_setter = [ + # When setting a Nitro, if Mandragora is in the inventory too and the wall's "Mandragora set" flag is not set, this + # will automatically subtract a Mandragora from the inventory and set its flag so the wall can be blown up in just + # one interaction instead of two. + 0x3C088039, # LUI T0, 0x8039 + 0x81099EE1, # LB T1, 0x9EE1 (T0) + 0x240A000C, # ADDIU T2, R0, 0x000C + 0x112A000E, # BEQ T1, T2, [forward 0x0E] + 0x81099C18, # LB T1, 0x9C18 (T0) + 0x31290002, # ANDI T1, T1, 0x0002 + 0x11200009, # BEQZ T1, [forward 0x09] + 0x91099C5D, # LBU T1, 0x9C5D (T0) + 0x11200007, # BEQZ T1, [forward 0x07] + 0x910B9C1A, # LBU T3, 0x9C1A (T0) + 0x316A0001, # ANDI T2, T3, 0x0001 + 0x15400004, # BNEZ T2, [forward 0x04] + 0x2529FFFF, # ADDIU T1, T1, 0xFFFF + 0xA1099C5D, # SB T1, 0x9C5D (T0) + 0x356B0001, # ORI T3, T3, 0x0001 + 0xA10B9C1A, # SB T3, 0x9C1A (T0) + 0x08000512, # J 0x80001448 + 0x00000000, # NOP + 0x810B9BF2, # LB T3, 0x9BF2 (T0) + 0x31690040, # ANDI T1, T3, 0x0040 + 0x11200008, # BEQZ T1, [forward 0x08] + 0x91099C5D, # LBU T1, 0x9C5D (T0) + 0x11200006, # BEQZ T1, [forward 0x06] + 0x316A0080, # ANDI T2, T3, 0x0080 + 0x15400004, # BNEZ T2, 0x803FE0E8 + 0x2529FFFF, # ADDIU T1, T1, 0xFFFF + 0xA1099C5D, # SB T1, 0x9C5D (T0) + 0x356B0080, # ORI T3, T3, 0x0080 + 0xA10B9BF2, # SB T3, 0x9BF2 (T0) + 0x08000512 # J 0x80001448 +] + +ambience_silencer = [ + # Silences all map-specific ambience when loading into a different map, so we don't have to live with, say, Tower of + # Science/Clock Tower machinery noises everywhere until either resetting, dying, or going into a map that is + # normally set up to disable said noises. + 0x3C088039, # LUI T0, 0x8039 + 0x91089EE1, # LBU T0, 0x9EE1 (T0) + 0x24090012, # ADDIU T1, R0, 0x0012 + 0x11090003, # BEQ T0, T1, [forward 0x03] + 0x00000000, # NOP + 0x0C004FAB, # JAL 0x80013EAC + 0x3404818C, # ORI A0, R0, 0x818C + 0x0C004FAB, # JAL 0x80013EAC + 0x34048134, # ORI A0, R0, 0x8134 + 0x0C004FAB, # JAL 0x80013EAC + 0x34048135, # ORI A0, R0, 0x8135 + 0x0C004FAB, # JAL 0x80013EAC + 0x34048136, # ORI A0, R0, 0x8136 + 0x08054987, # J 0x8015261C + 0x00000000, # NOP + # Plays the fan ambience when loading into the fan meeting room if this detects the active character's cutscene flag + # here already being set. + 0x3C088039, # LUI T0, 0x8039 + 0x91099EE1, # LBU T1, 0x9EE1 (T0) + 0x240A0019, # ADDIU T2, R0, 0x0019 + 0x152A000A, # BNE T1, T2, [forward 0x0A] + 0x910B9BFE, # LBU T3, 0x9BFE (T0) + 0x910C9C3D, # LBU T4, 0x9C3D (T0) + 0x240D0001, # ADDIU T5, R0, 0x0001 + 0x55800001, # BNEZL T4, [forward 0x01] + 0x240D0002, # ADDIU T5, R0, 0x0002 + 0x016D7024, # AND T6, T3, T5 + 0x11C00003, # BEQZ T6, [forward 0x03] + 0x00000000, # NOP + 0x0C0052B4, # JAL 0x80014AD0 + 0x34040169, # ORI A0, R0, 0x0169 + 0x0805581C # J 0x80156070 +] + +coffin_cutscene_skipper = [ + # Kills the normally-unskippable "Found a hidden path" cutscene at the end of Villa if this detects, in the current + # module in the modules array, the cutscene's module number of 0x205C and the "skip" value 0f 0x01 normally set by + # all cutscenes upon pressing Start. + 0x10A0000B, # BEQZ A1, [forward 0x0B] + 0x00000000, # NOP + 0x94A80000, # LHU T0, 0x0000 (A1) + 0x2409205C, # ADDIU T1, R0, 0x205C + 0x15090007, # BNE T0, T1, [forward 0x07] + 0x90AA0070, # LBU T2, 0x0070 (A1) + 0x11400005, # BEQZ T2, [forward 0x05] + 0x90AB0009, # LBU T3, 0x0009 (A1) + 0x240C0003, # ADDIU T4, R0, 0x0003 + 0x156C0002, # BNE T3, T4, [forward 0x02] + 0x240B0004, # ADDIU T3, R0, 0x0004 + 0xA0AB0009, # SB T3, 0x0009 (A1) + 0x03E00008 # JR RA +] + +multiworld_item_name_loader = [ + # When picking up an item from another world, this will load from ROM the custom message for that item explaining + # in the item textbox what the item is and who it's for. The flag index it calculates determines from what part of + # the ROM to load the item name from. If the item being picked up is a white jewel or a contract, it will always + # load from a part of the ROM that has nothing in it to ensure their set "flag" values don't yield unintended names. + 0x3C088040, # LUI T0, 0x8040 + 0xAD03E238, # SW V1, 0xE238 (T0) + 0x92080039, # LBU T0, 0x0039 (S0) + 0x11000003, # BEQZ T0, [forward 0x03] + 0x24090012, # ADDIU T1, R0, 0x0012 + 0x15090003, # BNE T0, T1, [forward 0x03] + 0x24080000, # ADDIU T0, R0, 0x0000 + 0x10000010, # B [forward 0x10] + 0x24080000, # ADDIU T0, R0, 0x0000 + 0x920C0055, # LBU T4, 0x0055 (S0) + 0x8E090058, # LW T1, 0x0058 (S0) + 0x1120000C, # BEQZ T1, [forward 0x0C] + 0x298A0011, # SLTI T2, T4, 0x0011 + 0x51400001, # BEQZL T2, [forward 0x01] + 0x258CFFED, # ADDIU T4, T4, 0xFFED + 0x240A0000, # ADDIU T2, R0, 0x0000 + 0x00094840, # SLL T1, T1, 1 + 0x5520FFFE, # BNEZL T1, [backward 0x02] + 0x254A0001, # ADDIU T2, T2, 0x0001 + 0x240B0020, # ADDIU T3, R0, 0x0020 + 0x018B0019, # MULTU T4, T3 + 0x00004812, # MFLO T1 + 0x012A4021, # ADDU T0, T1, T2 + 0x00084200, # SLL T0, T0, 8 + 0x3C0400BB, # LUI A0, 0x00BB + 0x24847164, # ADDIU A0, A0, 0x7164 + 0x00882020, # ADD A0, A0, T0 + 0x3C058018, # LUI A1, 0x8018 + 0x34A5BF98, # ORI A1, A1, 0xBF98 + 0x0C005DFB, # JAL 0x800177EC + 0x24060100, # ADDIU A2, R0, 0x0100 + 0x3C088040, # LUI T0, 0x8040 + 0x8D03E238, # LW V1, 0xE238 (T0) + 0x3C1F8012, # LUI RA, 0x8012 + 0x27FF5BA4, # ADDIU RA, RA, 0x5BA4 + 0x0804EF54, # J 0x8013BD50 + 0x94640002, # LHU A0, 0x0002 (V1) + # Changes the Y screen position of the textbox depending on how many line breaks there are. + 0x3C088019, # LUI T0, 0x8019 + 0x9108C097, # LBU T0, 0xC097 (T0) + 0x11000005, # BEQZ T0, [forward 0x05] + 0x2508FFFF, # ADDIU T0, T0, 0xFFFF + 0x11000003, # BEQZ T0, [forward 0x03] + 0x00000000, # NOP + 0x1000FFFC, # B [backward 0x04] + 0x24C6FFF1, # ADDIU A2, A2, 0xFFF1 + 0x0804B33F, # J 0x8012CCFC + # Changes the length and number of lines on the textbox if there's a multiworld message in the buffer. + 0x3C088019, # LUI T0, 0x8019 + 0x9108C097, # LBU T0, 0xC097 (T0) + 0x11000003, # BEQZ T0, [forward 0x03] + 0x00000000, # NOP + 0x00082821, # ADDU A1, R0, T0 + 0x240600B6, # ADDIU A2, R0, 0x00B6 + 0x0804B345, # J 0x8012CD14 + 0x00000000, # NOP + # Redirects the text to the multiworld message buffer if a message exists in it. + 0x3C088019, # LUI T0, 0x8019 + 0x9108C097, # LBU T0, 0xC097 (T0) + 0x11000004, # BEQZ T0, [forward 0x04] + 0x00000000, # NOP + 0x3C048018, # LUI A0, 0x8018 + 0x3484BF98, # ORI A0, A0, 0xBF98 + 0x24050000, # ADDIU A1, R0, 0x0000 + 0x0804B39F, # J 0x8012CE7C + # Copy the "item from player" text when being given an item through the multiworld via the game's copy function. + 0x00000000, # NOP + 0x00000000, # NOP + 0x00000000, # NOP + 0x3C088040, # LUI T0, 0x8040 + 0xAD1FE33C, # SW RA, 0xE33C (T0) + 0xA104E338, # SB A0, 0xE338 (T0) + 0x3C048019, # LUI A0, 0x8019 + 0x2484C0A8, # ADDIU A0, A0, 0xC0A8 + 0x3C058019, # LUI A1, 0x8019 + 0x24A5BF98, # ADDIU A1, A1, 0xBF98 + 0x0C000234, # JAL 0x800008D0 + 0x24060100, # ADDIU A2, R0, 0x0100 + 0x3C088040, # LUI T0, 0x8040 + 0x8D1FE33C, # LW RA, 0xE33C (T0) + 0x0804EDCE, # J 0x8013B738 + 0x9104E338, # LBU A0, 0xE338 (T0) + 0x00000000, # NOP + # Neuters the multiworld item text buffer if giving a non-multiworld item through the in-game remote item rewarder + # byte before then jumping to item_prepareTextbox. + 0x24080011, # ADDIU T0, R0, 0x0011 + 0x10880004, # BEQ A0, T0, [forward 0x04] + 0x24080012, # ADDIU T0, R0, 0x0012 + 0x10880002, # BEQ A0, T0, [forward 0x02] + 0x3C088019, # LUI T0, 0x8019 + 0xA100C097, # SB R0, 0xC097 (T0) + 0x0804EDCE # J 0x8013B738 +] + +ice_trap_initializer = [ + # During a map load, creates the module that allows the ice block model to appear while in the frozen state if not + # on the intro narration map (map 0x16). + 0x3C088039, # LUI T0, 0x8039 + 0x91089EE1, # LBU T0, 0x9EE1 (T0) + 0x24090016, # ADDIU T1, R0, 0x0016 + 0x11090004, # BEQ T0, T1, [forward 0x04] + 0x3C048034, # LUI A0, 0x8034 + 0x24842ACC, # ADDIU A0, A0, 0x2ACC + 0x08000660, # J 0x80001980 + 0x24052125, # ADDIU A1, R0, 0x2125 + 0x03E00008 # JR RA +] + +the_deep_freezer = [ + # Writes 000C0000 into the player state to freeze the player on the spot if Ice Traps have been received, writes the + # Ice Trap code into the pointer value (0x20B8, which is also Camilla's boss code),and decrements the Ice Traps + # remaining counter. All after verifying the player is in a "safe" state to be frozen in. + 0x3C0B8039, # LUI T3, 0x8039 + 0x91699BE2, # LBU T3, 0x9BE2 (T0) + 0x11200015, # BEQZ T1, [forward 0x15] + 0x3C088034, # LUI T0, 0x8034 + 0x910827A9, # LBU T0, 0x27A9 (T0) + 0x240A0005, # ADDIU T2, R0, 0x0005 + 0x110A0011, # BEQ T0, T2, [forward 0x11] + 0x240A000C, # ADDIU T2, R0, 0x000C + 0x110A000F, # BEQ T0, T2, [forward 0x0F] + 0x240A0002, # ADDIU T2, R0, 0x0002 + 0x110A000D, # BEQ T0, T2, [forward 0x0D] + 0x240A0008, # ADDIU T2, R0, 0x0008 + 0x110A000B, # BEQ T0, T2, [forward 0x0B] + 0x2529FFFF, # ADDIU T1, T1, 0xFFFF + 0xA1699BE2, # SB T1, 0x9BE2 (T3) + 0x3C088034, # LUI T0, 0x8034 + 0x3C09000C, # LUI T1, 0x000C + 0xAD0927A8, # SW T1, 0x27A8 (T0) + 0x240C20B8, # ADDIU T4, R0, 0x20B8 + 0xA56C9E6E, # SH T4, 0x9E6E (T3) + 0x8D0927C8, # LW T1, 0x27C8 (T0) + 0x912A0048, # LBU T2, 0x0068 (T1) + 0x314A007F, # ANDI T2, T2, 0x007F + 0xA12A0048, # SB T2, 0x0068 (T1) + 0x03E00008 # JR RA +] + +freeze_verifier = [ + # Verifies for the ice chunk module that a freeze should spawn the ice model. The player must be in the frozen state + # (0x000C) and 0x20B8 must be in either the freeze pointer value or the current boss ID (Camilla's); otherwise, we + # weill assume that the freeze happened due to a vampire grab or Actrise shard tornado and not spawn the ice chunk. + 0x8C4E000C, # LW T6, 0x000C (V0) + 0x00803025, # OR A2, A0, R0 + 0x8DC30008, # LW V1, 0x0008 (T6) + 0x3C088039, # LUI T0, 0x8039 + 0x240920B8, # ADDIU T1, R0, 0x20B8 + 0x950A9E72, # LHU T2, 0x9E72 (T0) + 0x3C0C8034, # LUI T4, 0x8034 + 0x918C27A9, # LBU T4, 0x27A9 (T4) + 0x240D000C, # ADDIU T5, R0, 0x000C + 0x158D0004, # BNE T4, T5, [forward 0x04] + 0x3C0B0F00, # LUI T3, 0x0F00 + 0x112A0005, # BEQ T1, T2, [forward 0x05] + 0x950A9E78, # LHU T2, 0x9E78 (T0) + 0x112A0003, # BEQ T1, T2, [forward 0x03] + 0x357996A0, # ORI T9, T3, 0x96A0 + 0x03200008, # JR T9 + 0x00000000, # NOP + 0x35799640, # ORI T9, T3, 0x9640 + 0x03200008, # JR T9 +] + +countdown_extra_safety_check = [ + # Checks to see if the multiworld message is a red flashing trap before then truly deciding to decrement the + # Countdown number. This was a VERY last minute thing I caught, since Ice Traps for other CV64 players can take the + # appearance of majors with no other way of the game knowing. + 0x3C0B8019, # LUI T3, 0x8019 + 0x956BBF98, # LHU T3, 0xBF98 (T3) + 0x240C0000, # ADDIU T4, R0, 0x0000 + 0x358CA20B, # ORI T4, T4, 0xA20B + 0x556C0001, # BNEL T3, T4, [forward 0x01] + 0xA1099CA4, # SB T1, 0x9CA4 (T0) + 0x03E00008 # JR RA +] + +countdown_demo_hider = [ + # Hides the Countdown number if we are not in the Gameplay state (state 2), which would happen if we were in the + # Demo state (state 9). This is to ensure the demo maps' number is not peep-able before starting a run proper, for + # the sake of preventing a marginal unfair advantage. Otherwise, updates the number once per frame. + 0x3C088039, # LUI T0, 0x8039 + 0x91089EE1, # LBU T0, 0x9EE1 (T0) + 0x3C098040, # LUI T1, 0x8040 + 0x01284821, # ADDU T1, T1, T0 + 0x0C0FF507, # JAL 0x803FD41C + 0x9124D6DC, # LBU A0, 0xD6DC (T1) + 0x3C088034, # LUI T0, 0x8034 + 0x91092087, # LBU T0, 0x2087 (T0) + 0x240A0002, # ADDIU T2, R0, 0x0002 + 0x112A0003, # BEQ T1, T2, [forward 0x03] + 0x3C048040, # LUI A0, 0x8040 + 0x8C84D6D4, # LW A0, 0xD6D4 (A0) + 0x0C0FF59F, # JAL 0x803FD67C + 0x24050000, # ADDIU A1, R0, 0x0000 + 0x080FF411, # J 0x803FD044 +] + +item_drop_spin_corrector = [ + # Corrects how far AP-placed items drop and how fast they spin based on what appearance they take. + + # Pickup actor ID table for the item appearance IDs to reference. + 0x01020304, + 0x05060708, + 0x090A0B0C, + 0x100D0E0F, + 0x11121314, + 0x15161718, + 0x191D1E1F, + 0x20212223, + 0x24252627, + 0x28291A1B, + 0x1C000000, + 0x00000000, + # Makes AP-placed items in 1-hit breakables drop to their correct, dev-intended height depending on what appearance + # we gave it. Primarily intended for the Axe and the Cross to ensure they don't land half buried in the ground. + 0x000C4202, # SRL T0, T4, 8 + 0x318C00FF, # ANDI T4, T4, 0x00FF + 0x11000003, # BEQZ T0, [forward 0x03] + 0x3C098040, # LUI T1, 0x8040 + 0x01284821, # ADDU T1, T1, T0 + 0x912CE7DB, # LBU T4, 0xE7D8 + 0x03E00008, # JR RA + 0xAC600000, # SW R0, 0x0000 (V1) + 0x00000000, # NOP + # Makes items with changed appearances spin at their correct speed. Unless it's a local Ice Trap, wherein it will + # instead spin at the speed it isn't supposed to. + 0x920B0040, # LBU T3, 0x0040 (S0) + 0x1160000D, # BEQZ T3, [forward 0x0D] + 0x3C0C8040, # LUI T4, 0x8040 + 0x016C6021, # ADDU T4, T3, T4 + 0x918CE7DB, # LBU T4, 0xE7DB (T4) + 0x258CFFFF, # ADDIU T4, T4, 0xFFFF + 0x240D0011, # ADDIU T5, R0, 0x0011 + 0x154D0006, # BNE T2, T5, [forward 0x06] + 0x29AE0006, # SLTI T6, T5, 0x0006 + 0x240A0001, # ADDIU T2, R0, 0x0001 + 0x55C00001, # BNEZL T6, [forward 0x01] + 0x240A0007, # ADDIU T2, R0, 0x0007 + 0x10000002, # B [forward 0x02] + 0x00000000, # NOP + 0x258A0000, # ADDIU T2, T4, 0x0000 + 0x08049648, # J 0x80125920 + 0x3C028017, # LUI V0, 0x8017 + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + # Makes AP-placed items in 3-hit breakables drop to their correct, dev-intended height depending on what appearance + # we gave it. + 0x00184202, # SRL T0, T8, 8 + 0x331800FF, # ANDI T8, T8, 0x00FF + 0x11000003, # BEQZ T0, [forward 0x03] + 0x3C098040, # LUI T1, 0x8040 + 0x01284821, # ADDU T1, T1, T0 + 0x9138E7DB, # LBU T8, 0xE7D8 + 0x03E00008, # JR RA + 0xAC60FFD8, # SW R0, 0xFFD8 (V1) + 0x00000000, + # Makes AP-placed items in the Villa chandelier drop to their correct, dev-intended height depending on what + # appearance we gave it. (why must this singular breakable be such a problem child with its own code? :/) + 0x000D4202, # SRL T0, T5, 8 + 0x31AD00FF, # ANDI T5, T5, 0x00FF + 0x11000003, # BEQZ T0, [forward 0x03] + 0x3C098040, # LUI T1, 0x8040 + 0x01284821, # ADDU T1, T1, T0 + 0x912DE7DB, # LBU T5, 0xE7D8 + 0x03E00008, # JR RA + 0xAC60FFD8, # SW R0, 0xFFD8 (V1) +] + +big_tosser = [ + # Makes every hit the player takes that does not immobilize them send them flying backwards with the power of + # Behemoth's charge. + 0x3C0A8038, # LUI T2, 0x8038 + 0x914A7D7E, # LBU T2, 0x7D7E (T2) + 0x314A0020, # ANDI T2, T2, 0x0020 + 0x1540000D, # BEQZ T2, [forward 0x0D] + 0x3C0A800E, # LUI T2, 0x800E + 0x954B8290, # LHU T3, 0x8290 (T2) + 0x356B2000, # ORI T3, T3, 0x2000 + 0xA54B8290, # SH T3, 0x8290 (T2) + 0x3C0C8035, # LUI T4, 0x8035 + 0x958C09DE, # LHU T4, 0x09DE (T4) + 0x258C8000, # ADDIU T4, T4, 0x8000 + 0x3C0D8039, # LUI T5, 0x8039 + 0xA5AC9CF0, # SH T4, 0x9CF0 (T5) + 0x3C0C4160, # LUI T4, 0x4160 + 0xADAC9CF4, # SW T4, 0x9CF4 (T5) + 0x3C0C4040, # LUI T4, 0x4040 + 0xADAC9CF8, # SW T4, 0x9CF8 (T5) + 0x03E00008, # JR RA + 0x8C680048, # LW T0, 0x0048 (V1) + 0x00000000, + 0x00000000, + # Allows pressing A while getting launched to cancel all XZ momentum. Useful for saving oneself from getting + # launched into an instant death trap. + 0x3C088038, # LUI T0, 0x8038 + 0x91087D80, # LBU T0, 0x7D80 (T0) + 0x31090080, # ANDI T1, T0, 0x0080 + 0x11200009, # BEQZ T1, [forward 0x09] + 0x3C088035, # LUI T0, 0x8035 + 0x8D0A079C, # LW T2, 0x079C (T0) + 0x3C0B000C, # LUI T3, 0x000C + 0x256B4000, # ADDIU T3, T3, 0x4000 + 0x014B5024, # AND T2, T2, T3 + 0x154B0003, # BNE T2, T3, [forward 0x03] + 0x00000000, # NOP + 0xAD00080C, # SW R0, 0x080C (T0) + 0xAD000814, # SW R0, 0x0814 (T0) + 0x03200008 # JR T9 +] diff --git a/worlds/cv64/data/rname.py b/worlds/cv64/data/rname.py new file mode 100644 index 0000000000..851ee618af --- /dev/null +++ b/worlds/cv64/data/rname.py @@ -0,0 +1,63 @@ +forest_of_silence = "Forest of Silence" +forest_start = "Forest of Silence: first half" +forest_mid = "Forest of Silence: second half" +forest_end = "Forest of Silence: end area" + +castle_wall = "Castle Wall" +cw_start = "Castle Wall: main area" +cw_exit = "Castle Wall: exit room" +cw_ltower = "Castle Wall: left tower" + +villa = "Villa" +villa_start = "Villa: dog gates" +villa_main = "Villa: main interior" +villa_storeroom = "Villa: storeroom" +villa_archives = "Villa: archives" +villa_maze = "Villa: maze" +villa_servants = "Villa: servants entrance" +villa_crypt = "Villa: crypt" + +tunnel = "Tunnel" +tunnel_start = "Tunnel: first half" +tunnel_end = "Tunnel: second half" + +underground_waterway = "Underground Waterway" +uw_main = "Underground Waterway: main area" +uw_end = "Underground Waterway: end" + +castle_center = "Castle Center" +cc_main = "Castle Center: main area" +cc_crystal = "Castle Center: big crystal" +cc_torture_chamber = "Castle Center: torture chamber" +cc_library = "Castle Center: library" +cc_elev_top = "Castle Center: elevator top" + +duel_tower = "Duel Tower" +dt_main = "Duel Tower" + +tower_of_sorcery = "Tower of Sorcery" +tosor_main = "Tower of Sorcery" + +tower_of_execution = "Tower of Execution" +toe_main = "Tower of Execution: main area" +toe_ledge = "Tower of Execution: gated ledge" + +tower_of_science = "Tower of Science" +tosci_start = "Tower of Science: turret lab" +tosci_three_doors = "Tower of Science: locked key1 room" +tosci_conveyors = "Tower of Science: spiky conveyors" +tosci_key3 = "Tower of Science: locked key3 room" + +room_of_clocks = "Room of Clocks" +roc_main = "Room of Clocks" + +clock_tower = "Clock Tower" +ct_start = "Clock Tower: start" +ct_middle = "Clock Tower: middle" +ct_end = "Clock Tower: end" + +castle_keep = "Castle Keep" +ck_main = "Castle Keep: exterior" +ck_drac_chamber = "Castle Keep: Dracula's chamber" + +renon = "Renon's shop" diff --git a/worlds/cv64/docs/en_Castlevania 64.md b/worlds/cv64/docs/en_Castlevania 64.md new file mode 100644 index 0000000000..5fe85555c4 --- /dev/null +++ b/worlds/cv64/docs/en_Castlevania 64.md @@ -0,0 +1,148 @@ +# Castlevania 64 + +## Where is the settings page? + +The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +config file. + +## What does randomization do to this game? + +All items that you would normally pick up throughout the game, be it from candles, breakables, or sitting out, have been +moved around. This includes the key items that the player would normally need to find to progress in some stages, which can +now be found outside their own stages, so returning to previously-visited stages will very likely be necessary (see: [How do +I jump to a different stage?](#how-do-i-jump-to-a-different-stage?)). The positions of the stages can optionally be randomized +too, so you may start out in Duel Tower and get Forest of Silence as your penultimate stage before Castle Keep, amongst +many other possibilities. + +## How do I jump to a different stage? + +Instant travel to an earlier or later stage is made possible through the Warp Menu, a major addition to the game that can +be pulled up while not in a boss fight by pressing START while holding Z and R. By finding Special1 jewels (the item that +unlocks Hard Mode in vanilla Castlevania 64), more destinations become available to be selected on this menu. The destinations +on the list are randomized per seed and the first one, which requires no Special1s to select, will always be your starting +area. + +NOTE: Regardless of which option on the menu you are currently highlighting, you can hold Z or R while making your selection +to return to Villa's crypt or Castle Center's top elevator room respectively, provided you've already been to that place at +least once. This can make checking out both character stages at the start of a route divergence far less of a hassle. + +## Can I do everything as one character? + +Yes! The Villa end-of-level coffin has had its behavior modified so that which character stage slot it sends you to +depends on the time of day, and likewise both bridges at the top of Castle Center's elevator are intact so both exits are +reachable regardless of who you are. With these changes in game behavior, every stage can be accessed by any character +in a singular run. + +NOTE: By holding L while loading into a map (this can even be done while cancelling out of the Warp Menu), you can swap to +the other character you are not playing as, and/or hold C-Up to swap to and from the characters' alternate costumes. Unless +you have Carrie Logic enabled, and you are not playing as her, switching should never be necessary. + +## What is the goal of Castlevania 64 when randomized? + +Make it to Castle Keep, enter Dracula's chamber, and defeat him to trigger an ending and complete your goal. Whether you +get your character's good or bad ending does **not** matter; the goal will send regardless. Options exist to force a specific +ending for those who prefer a specific one. + +Dracula's chamber's entrance door is initially locked until whichever of the following objectives that was specified on your +YAML under `draculas_condition` is completed: +- `crystal`: Activate the big crystal in the basement of Castle Center. Doing this entails finding two Magical Nitros and +two Mandragoras to blow up both cracked walls (see: [How does the Nitro transport work in this?](#how-does-the-nitro-transport-work-in-this?)). +Behemoth and Rosa/Camilla do **NOT** have to be defeated. +- `bosses`: Kill bosses with visible health meters to earn Trophies. The number of Trophies required can be specified under +`bosses_required`. +- `special2s`: Find enough Special2 jewels (the item that normally unlocks alternate costumes) that are shuffled in the +regular item pool. The total amount and percent needed can be specified under `total_special2s` and `percent_special2s_required` respectively. + +If `none` was specified, then there is no objective. Dracula's chamber door is unlocked from the start, and you merely have to reach it. + +## What items and locations get shuffled? + +Inventory items, jewels, moneybags, and PowerUps are all placed in the item pool by default. Randomizing Sub-weapons is optional, +and they can be shuffled in their own separate pool or in the main item pool. An optional hack can be enabled to make your +old sub-weapon drop behind you when you receive a different one, so you can pick it up again if you still want it. Location +checks by default include freestanding items, items from one-hit breakables, and the very few items given through NPC text. Additional +locations that can be toggled are: +- Objects that break in three hits. +- Sub-weapon locations if they have been shuffled anywhere. +- Seven items sold by the shopkeeper Renon. +- The two items beyond the crawlspace in Waterway that normally require Carrie, if Carrie Logic is on. +- The six items inside the Lizard-man generators in Castle Center that open randomly to spawn Lizard-men. These are particularly annoying! + +## How does the Nitro transport work in this? + +Two Magical Nitros and two Mandragoras are placed into the item pool for blowing up the cracked walls in Castle Center +and two randomized items are placed on both of their shelves. The randomized Magical Nitro will **NOT** kill you upon landing +or taking damage, so don't panic when you receive one! Hazardous Waste Dispoal bins are disabled and the basement crack with +a seal will not let you set anything at it until said seal is removed so none of the limited ingredients can be wasted. + +In short, Nitro is still in, explode-y business is not! Unless you turn on explosive DeathLink, that is... + +## Which items can be in another player's world? + +Any of the items which can be shuffled may also be placed into another player's world. The exception is if sub-weapons +are shuffled in their own pool, in which case they will only appear in your world in sub-weapon spots. + +## What does another world's item look like in Castlevania 64? + +An item belonging to another world will show up as that item if it's from another Castlevania 64 world, or one of two +Archipelago logo icons if it's from a different game entirely. If the icon is big and has an orange arrow in the top-right +corner, it is a progression item for that world; definitely get these! Otherwise, if it's small and with no arrow, it is +either filler, useful, or a trap. + +When you pick up someone else's item, you will not receive anything and the item textbox will show up to announce what you +found and who it was for. The color of the text will tell you its classification: +- Light brown-ish: Common +- White/Yellow: Useful +- Yellow/Green: Progression +- Yellow/Red: Trap + +## When the player receives an item, what happens? + +A textbox containing the name of the item and the player who sent it will appear, and they will get it. +Just like the textbox that appears when sending an item, the color of the text will tell you its classification. + +NOTE: You can press B to close the item textbox instantly and get through your item queue quicker. + +## What tricks and glitches should I know for Hard Logic? + +The following tricks always have a chance to be required: +- Left Tower Skip in Castle Wall +- Copper Door Skip in Villa (both characters have their own methods for this) +- Waterfall Skip if you travel backwards into Underground Waterway +- Slope Jump to Room of Clocks from Castle Keep +- Jump to the gated ledge from the level above in Tower of Execution + +Enabling Carrie Logic will also expect the following: + +- Orb-sniping dogs through the front gates in Villa + +Library Skip is **NOT** logically expected on any setting. The basement hallway crack will always logically expect two Nitros +and two Mandragoras even with Hard Logic on due to the possibility of wasting a pair on the upper wall, after managing +to skip past it. And plus, the RNG manip may not even be possible after picking up all the items in the Nitro room. + +## What are the item name groups? +The groups you can use for Castlevania 64 are `bomb` and `ingredient`, both of which will hint randomly for either a +Magical Nitro or Mandragora. + +## What are the location name groups? +In Castlevania 64, every location that is specific to a stage is part of a location group under that stage's name. +So if you want to exclude all of, say, Duel Tower, you can do so by just excluding "Duel Tower" as a whole. + +## I'm stuck and/or I can't find this hinted location...is there a map tracker? +At the moment, no map tracker exists. [Here](https://github.com/ArchipelagoMW/Archipelago/tree/main/worlds/cv64/docs/obscure_checks.md) +is a list of many checks that someone could very likely miss, with instructions on how to find them. See if the check you +are missing is on there and if it isn't, or you still can't find it, reach out in the [Archipelago Discord server](https://discord.gg/archipelago) +to inquire about having the list updated if you think it should be. + +If you are new to this randomizer, it is strongly recommended to play with the Countdown option enabled to at least give you a general +idea of where you should be looking if you get completely stuck. It can track the total number of unchecked locations in the +area you are currently in, or the total remaining majors. + +## Why does the game stop working when I sit on the title screen for too long? +This is an issue that existed with Castlevania 64 on mupen64plus way back in 2017, and BizHawk never updated their +mupen64plus core since it was fixed way back then. This is a Castlevania 64 in general problem that happens even with the +vanilla ROM, so there's not much that can done about it besides opening an issue to them (which [has been done](https://github.com/TASEmulators/BizHawk/issues/3670)) +and hoping they update their mupen64plus core one day... + +## How the f*** do I set Nitro/Mandragora? +(>) diff --git a/worlds/cv64/docs/obscure_checks.md b/worlds/cv64/docs/obscure_checks.md new file mode 100644 index 0000000000..4aafc2db1c --- /dev/null +++ b/worlds/cv64/docs/obscure_checks.md @@ -0,0 +1,429 @@ +# Obscure locations in the AP Castlevania 64 randomizer + + + +## Forest of Silence + +#### Invisible bridge platform +A square platform floating off to the side of the broken bridge between the three-crypt and Werewolf areas. There's an +invisible bridge connecting it with the middle piece on the broken bridge that you can cross over to reach it. This is +where you normally get the Special1 in vanilla that unlocks Hard Mode. + +### Invisible Items +#### Dirge maiden pedestal plaque +This plaque in question can be found on the statue pedestal with a torch on it in the area right after the first switch gate, +near the cliff where the breakable rock can be found. The plaque reads "A maiden sings a dirge" if you check it after you +pick up the item there, hence the name of all the locations in this area. + +#### Werewolf statue plaque +The plaque on the statue pedestal in the area inhabited by the Werewolf. Reading this plaque after picking up the item +says it's "the lady who blesses and restores." + +### 3-Hit Breakables +#### Dirge maiden pedestal rock +This rock can be found near the cliff behind the empty above-mentioned dirge maiden pedestal. Normally has a ton of money +in vanilla, contains 5 checks in rando. + +#### Bat archway rock +After the broken bridge containing the invisible pathway to the Special1 in vanilla, this rock is off to the side in front +of the gate frame with a swarm of bats that come at you, before the Werewolf's territory. Contains 4 checks. If you are new +to speedrunning the vanilla game and haven't yet learned the RNG manip strats, this is a guranteed spot to find a PowerUp at. + + + +## Castle Wall +#### Above bottom right/left tower door +These checks are located on top of the bottom doorways inside the both the Left and Right Tower. You have to drop from above +to reach them. In the case of the left tower, it's probably easiest to wait on the green platform directly above it until it flips. + +#### Left tower child ledge +When you reach the bridge of four rotating green platforms, look towards the pillar in the center of the room (hold C-up to +enter first person view), and you'll see this. There's an invisible bridge between the rotating platforms and the tiny ledge +that you can use to get to and from it. In Legacy of Darkness, it is on this ledge where one of Henry's children is found. + +### Invisible Items +#### Sandbag shelf - Left +If you thought the PowerUp on this shelf in vanilla CV64 was the only thing here, then you'd be wrong! Hidden inside the +sandbags near the item is another item you can pick up before subsequent checks on this spot yield "only sand and gravel". +Legacy took this item out entirely, interestingly enough. + +### 3-Hit Breakables +#### Upper rampart savepoint slab +After killing the two White Dragons and flipping their switch, drop down onto this platform from the top, and you'll find +it near the White Jewel. Contains 5 checks that are all normally Red Jewels in vanilla, making it an excellent place to +fuel up at if you're not doing Left Tower Skip. Just be careful of the infinitely spawning skeletons! + +#### Dracula switch slab +Located behind the door that you come out of at the top of the left tower where you encounter Totally Real Dracula in a +cutscene. Contains 5 checks that are all normally money; take note of all these money spots if you're plaing vanilla and +plan on trying to trigger the Renon fight. + + + +## Villa +#### Outer front gate platform +From the start of the level, turn right, and you'll see a platform with a torch above a torch on the ground. This upper torch +is reachable via an invisible platform that you can grab and pull yourself up onto. The PAL version and onwards removed +this secret entirely, interestingly enough. + +#### Front yard cross grave near gates/porch +In the Villa's front yard area are two cross-shaped grave markers that are actually 1-hit breakables just like torches. +They contain a check each. + +#### Midnight fountain +At exactly midnight (0:00 on the pause screen), a pillar in the fountain's base will rise to give you access to the six +checks on the upper parts of the fountain. If you're playing with Disable Time Requirements enabled, this pillar will be +raised regardless of the current time. + +#### Vincent +Vincent has a check that he will give to you by speaking to him after triggering the Rosa cutscene at 3 AM in the rose +garden. With Disable Time Requirements enabled, the Rosa cutscene will trigger at any time. + +#### Living room ceiling light +In the rectangular living room with ghosts and flying skulls that come at you, there are two yellow lights on the ceiling +and one red light between them. The red light can be broken for a check; just jump directly below it and use your c-left +attack to hit it. + +#### Front maze garden - Frankie's right dead-end urn +When you first enter the maze, before going to trigger the Malus cutscene, go forward, right at the one-way door, then right +at the T-junction, and you'll reach a dead-end where the Gardner is just going about his business. The urn on the left +at this dead-end can be broken for a check; it's the ONLY urn in the entire maze that can be broken like this. + +#### Crypt bridge upstream +After unlocking the Copper Door, follow the stream all the way past the bridge to end up at this torch. +I see many people miss this one. + +### Invisible Items +#### Front yard visitor's tombstone +The tombstone closest to the Villa building itself, in the top-right corner if approaching from the gates. If you are +familiar with the puzzle here in Cornell's quest in Legacy, it's the tombstone prepared for "anybody else who drops by +to visit". + +#### Foyer sofa +The first sofa in the foyer, on the upper floor to the right. + +#### Mary's room table +The table closer to the mirror on the right in the small room adjacent to the bedroom, where Mary would normally be found +in Cornell's story in Legacy. + +#### Dining room rose vase +The vase of roses in the dining room that a rose falls out of in the cutscene here to warn Reinhardt/Carrie of the vampire +villager. + +#### Living room clawed painting +The painting with claw marks on it above the fireplace in the middle of the living room. + +#### Living room lion head +The lion head on the left wall of the living room (if you entered from one of the doors to the main hallway). + +#### Maze garden exit knight +The suit of armor in the stairs room before the Maze Garden, where Renon normally introduces himself. + +#### Storeroom statue +The weird statue in the back of the Storeroom. If you check it again after taking its item, the game questions why would +someone make something like it. + +#### Archives table +The table in the middle of the Archives. In Legacy, this is where Oldrey's diary normally sits if you are playing Cornell. + +#### Malus's hiding bush +The bush that Reinhardt/Carrie find Malus hiding in at the start of the Maze Garden chase sequence. + +### 3-Hit Breakables +#### Foyer chandelier +The big chandelier above the foyer can be broken for 5 assorted items, all of which become checks in rando with the multi +hits setting on. This is the only 3-hit breakable in the entire stage.
+ +Here's a fun fact about the chandelier: for some reason, KCEK made this thing a completely separate object from every other +3-hit breakable in the game, complete with its own distinct code that I had to modify entirely separately as I was making +this rando to make the 3-hit breakable setting feasible! What fun! + + + +## Tunnel +#### Stepping stone alcove +After the first save following the initial Spider Women encounter, take the first right you see, and you'll arrive back at +the poison river where there's a second set of stepping stones similar to the one you just jumped across earlier. Jump on +these to find the secret alcove containing one or two checks depending on whether sub-weapons are randomized anywhere or not. + +### Sun/Moon Doors + +In total, there are six of these throughout the entire stage. One of them you are required to open in order to leave the stage, +while the other five lead to optional side rooms containing items. These are all very skippable in the vanilla game, but in a +rando context, it is obviously *very* important you learn where all of them are for when the day comes that a Hookshot +lands behind one! If it helps, two of them are before the gondolas, while all the rest are after them. + +#### Lonesome bucket moon door +After you ride up on the second elevator, turn left at the first split path you see, and you will find, as I called it, the +"Lonesome bucket". Keep moving forward past this, and you will arrive at the moon door. The only thing of value, beside a shop +point, is a sub-weapon location. So if you don't have sub-weapons shuffled anywhere, you can very much skip this one. + +#### Gondola rock crusher sun door +Once you get past the first poison pit that you are literally required to platform over, go forward at the next junction +instead of left (going left is progress and will take you to the rock crusher right before the gondolas). This door notably +hides two Roast Beefs normally, making it probably the most worthwhile one to visit in vanilla. + +#### Corpse bucket moon door +After the poison pit immediately following the gondola ride, you will arrive at a bucket surrounded by corpses (hence the name). +Go left here, and you will arrive at this door. + +#### Shovel zone moon door +On the straight path to the end-of-level sun door are two separate poison pits on the right that you can platform over. +Both of these lead to and from the same optional area, the "shovel zone" as I call it due to the random shovel you can find +here. Follow the path near the shovel that leads away from both poison pits, and you'll arrive at a junction with a save jewel. +Go straight on at this junction to arrive at this moon door. This particular one is more notable in Legacy of Darkness as it +contains one of the locations of Henry's children. + +#### Shovel zone sun door +Same as the above moon door, but go left at the save jewel junction instead of straight. + +### Invisible Items +#### Twin arrow signs +From the save point after the stepping stones following the initial Spider Women encounter, travel forward until you reach a +T-junction with two arrow signs at it. The right-pointing sign here contains an item on its post. + +#### Near lonesome bucket +After riding the first upwards elevator following turning left at the twin arrow signs, you'll arrive at the lonesome bucket +area, with said bucket being found if you turn left at the first opportunity after said elevator. The item here is not +found *in* the bucket, but rather on a completely unmarked spot some meters from it. This had to have been a mistake, +seeing as Legacy moved it to actually be in the bucket. + +#### Shovel +Can be found by taking either platforming course on the right side of the straightaway to the end after the gondolas. +This entire zone is noteable for the fact that there's no reason for Reinhardt to come here in either game; it's only ever +required for Henry to rescue one of his children. + +### 3-Hit Breakables +#### Twin arrow signs rock +Turn right at the twin arrow signs junction, and you'll find this rock at the dead-end by the river. It contains a bunch of +healing and status items that translate into 5 rando checks. + +#### Lonesome bucket poison pit rock +Near the lonesome bucket is the start of a platforming course over poison water that connects near Albert's campsite...which +you could reach anyway just by traveling forward at the prior junction instead of left. So what's the point of this poison +pit, then? Look out into the middle of it, and you'll see this rock on a tiny island out in the middle of it. If you choose +to take the hard way here, your reward will be three meat checks. + + + +## Underground Waterway +#### Carrie Crawlspace +This is located shortly after the corridor following the ledges that let you reach the first waterfall's source alcove. +Notably, only Carrie is able to crouch and go through this, making these the only checks in the *entire* game that are +hard impossible without being a specific character. So if you have Carrie Logic on and your character is Reinhardt, you'll +have to hold L while loading into a map to change to Carrie just for this one secret. If Carrie Logic is off, then these +locations will not be added and you can just skip them entirely. + +### 3-Hit Breakables +#### First poison parkour ledge +Near the start of the level is a series of ledges you can climb onto and platform across to reach a corner with a lantern +that you can normally get a Cure Ampoule from. The first of these ledges can be broken for an assortment of 6 things. + +#### Inside skeleton crusher ledge +To the left of the hallway entrance leading to the third switch is a long shimmy-able ledge that you can grab onto and shimmy +for a whole eternity (I implemented a setting JUST to make shimmying this ledge faster!) to get to a couple stand on-able ledges, +one of which has a lantern above it containing a check. This ledge can be broken for 3 chickens. I'd highly suggest bringing +Holy Water for this because otherwise you're forced to break it from the other, lower ledge that's here. And this ledge +will drop endless crawling skeletons on you as long as you're on it. + + + +## Castle Center +#### Atop elevator room machine +In the elevator room, right from the entrance coming in from the vampire triplets' room, is a machine that you can press +C-Right on to get dialog reading "An enormous machine." There's a torch on top of this machine that you can reach by +climbing onto the slanted part of the walls in the room. + +#### Heinrich Meyer +The friendly lizard-man who normally gives you the Chamber Key in vanilla has a check for you just like Vincent. +Yes, he has a name! And you'd best not forget it! + +#### Torture chamber rafters +A check can be found in the rafters in the room with the Mandragora shelf. Get onto and jump off the giant scythe or the +torture instrument shelf to make it up there. It's less annoying to do without Mandragora since having it will cause ghosts to +infinitely spawn in here. + +### Invisible Items +#### Red carpet hall knight +The suit of armor in the red carpet hallway after the bottom elevator room, directly next to the door leading into the +Lizard Locker Room. + +#### Lizard locker knight +The suit of armor in the Lizard Locker Room itself, directly across from the door connecting to the red carpet hallway. + +#### Broken staircase knight +The suit of armor in the broken staircase room following the Lizard Locker Room. + +#### Inside cracked wall hallway flamethrower +In the upper cracked wall hallway, it is in the lower flamethrower that is part of the pair between the Butler Bros. Room +and the main part of the hallway. + +#### Nitro room crates +The wall of crates in the Nitro room on Heinrich Meyer's side. This is notable for being one of the very rare Healing Kits +that you can get for free in vanilla. + +#### Hell Knight landing corner knight +The inactive suit of armor in the corner of the room before the Maid Sisters' Room, which also contains an active knight. + +#### Maid sisters room vase +The lone vase in the vampire Maid Sisters' Room, directly across from the door leading to the Hell Knight Landing. +Yes, you are actually supposed to *check* this with C-right to get its item; not break it like you did to the pots in +the Villa earlier! + +#### Invention room giant Famicart +The giant square-shaped thing in one corner of the invention room that looks vaguely like a massive video game cartridge. +A Famicom cartridge, perhaps? + +#### Invention room round machine +The brown circular machine in the invention room, close to the middle of the wall on the side of the Spike Crusher Room. + +#### Inside nitro hallway flamethrower +The lower flamethrower in the hallway between the Nitro room from the Spike Crusher Room, near the two doors to said rooms. + +#### Torture chamber instrument rack +The shelf full of torture instruments in the torture chamber, to the right of the Mandragora shelf. + +### 3-Hit Breakables +#### Behemoth arena crate +This large crate can be found in the back-right corner of Behemoth's arena and is pretty hard to miss. Break it to get 5 +moneybags-turned-checks. + +#### Elevator room unoccupied statue stand +In the bottom elevator room is a statue on a stand that will cry literal Bloody Tears if you get near it. On the opposite +side of the room from this, near the enormous machine, is a stand much like the one the aforementioned statue is on only +this one is completely vacant. This stand can be broken for 3 roast beefs-turned checks. + +#### Lizard locker room slab +In the Lizard Locker Room, on top of the second locker from the side of the room with the door to the red carpet hallway, +is a metallic box-like slab thingy that can be broken normally for 4 status items. This 3HB is notable for being one of two +funny ones in the game that does NOT set a flag when you break it in vanilla, meaning you can keep breaking it over and +over again for infinite Purifyings and Cure Ampoules! + +### The Lizard Lockers +If you turned on the Lizard Locker Items setting, then hoo boy, you are in for a FUN =) time! Inside each one of the six Lizard +Lockers is a check, and the way you get these checks is by camping near each of the lockers as you defeat said Lizards, +praying that one will emerge from it and cause it to open to give you a chance to grab it. It is *completely* luck-based, +you have a 1-in-6 (around 16%) chance per Lizard-man that emerges, and you have to repeat this process six times for each +check inside each locker. You can open and cancel the warp menu to make things easier, but other than that, enjoy playing +with the Lizards! + + + +## Duel Tower +#### Invisible bridge balcony +Located between Werewolf and Were-bull's arenas. Use an invisible bridge to reach it; it starts at the highest platform +on the same wall as it that appears after defeating Werewolf. The balcony contains two checks and a save point that I +added specifically for this rando to make the level less frustrating. + +#### Above Were-bull arena +The only check that can be permanently missed in the vanilla game depending on the order you do things, not counting any +points of no return. Between Werewolf and Were-bull's arenas is an alternate path that you can take downward and around +to avoid Were-bull completely and get on top of his still-raised arena, so you can reach this. In the rando, I set it up so +that his arena will go back to being raised if you leave the area and come back, and if you get the item later his arena flag +will be set then. If you're playing with Dracula's bosses condition, then you can only get one Special2 off of him the first +time you beat him and then none more after that. + + + +## Tower of Execution +#### Invisible bridge ledge +There are two ways to reach this one; use the invisible bridge that starts at the walkway above the entrance, or jump to +it from the Execution Key gate alcove. Collecting this Special2 in vanilla unlocks Reinhardt's alternate costume. + +#### Guillotine tower top level +This iron maiden is strategically placed in such a way that you will very likely miss it if you aren't looking carefully for +it, so I am including it here. When you make it to the top floor of the level, as you approach the tower for the final time, +look on the opposite side of it from where you approach it, and you will find this. The laggiest check in the whole game! +I'd dare someone to find some check in some other Archipelago game that lags harder than this. + +### 3-Hit Breakables +#### Pre-mid-savepoint platforms ledge +Here's a really weird one that even I never found about until well after I finished the 3HB setting and moved on to deving +other things, and I'd honestly be shocked if ANYONE knew about outside the context of this rando! This square-shaped +platform can be found right before the second set of expanding and retracting wall platforms, leading up to the mid-save +point, after going towards the central tower structure for the second time on the second floor. Breaking it will drop an +assortment of 5 items, one of which is notable for being the ONE sub-weapon that drops from a 3HB. This meant I had to really +change how things work to account for sub-weapons being in 3HBs! + + + +## Tower of Science +#### Invisible bridge platform +Following the hallway with a save jewel beyond the Science Key2 door, look to your right, and you'll see this. Mind the +gap separating the invisible bridge from the solid ground of the bottom part of this section! + +### 3-Hit Breakables +#### Invisible bridge platform crate +Near the candle on the above-mentioned invisible bridge platform is a small metallic crate. Break it for a total of 6 +checks, which in vanilla are 2 chickens, moneybags, and jewels. + + + +## Tower of Sorcery +#### Trick shot from mid-savepoint platform +From the platform with the save jewel, look back towards the vanishing red platforms that you had to conquer to get up +there, and you'll see a breakable diamond floating high above a solid platform before it. An ultra-precise orb shot from +Carrie can hit it to reveal the check, so you can then go down and get it. If you are playing as Reinhardt, you'll have +to use a sub-weapon. Any sub-weapon that's not Holy Water will work. Sub-weapons and the necessary jewels can both be +farmed off the local Icemen if it really comes down to it. + +#### Above yellow bubble +Directly above the yellow bubble that you break to raise the middle yellow large platform. Jump off the final red platform +in the series of red platforms right after the save point when said platform bobs all the way up, and you'll be able to +hit this diamond with good timing. + +#### Above tiny blue platforms start +Above the large platform after the yellow ones from whence you can reach the first of the series of tiny blue platforms. +This diamond is low enough that you can reach it by simply jumping straight up to it. + +#### Invisible bridge platform +Located at the very end of the stage, off to the side. Use the invisible bridge to reach it. The Special2 that unlocks +Carrie's costume can be normally found here (or Special3, depending on if you are playing the PAL version or not). + + + +## Clock Tower +All the normal items here and in Room of Clocks are self-explanatory, but the 3HBs in Clock Tower are a whoooooooole 'nother story. So buckle up... + +### 3-Hit Breakables +#### Gear climb room battery underside slab +In the first room, on the side you initially climb up, go up until you find a battery-like object that you can stand on +as a platform. From the platform right after this, you can hit this 3HB that can be found on the underside of the structure +in the corner, above the structure before the first set of gears that you initially start the climb on. 3 chickens/checks +can be gotten out of this one. + +#### Gear climb room door underside slab +Now THIS one can be very annoying to get, doubly so if you're playing as Reinhardt, triply so if you're playing Hard Mode +on top of that because you will also have to deal with invincible, bone-throwing red skeletons here! This slab can be +found on the underside of the platform in the first room with the door leading out into the second area and drops 3 +beefs/checks when you break it. Carrie is small enough to crouch under the platform and shoot the thing a few times +without *too* much hassle, but the only way I know of for Reinhardt to get it is to hang him off the side of the gear +and pull him up once he gets moved under the platform. Getting him to then face the breakable and whip it without falling +off due to the gear's rotation is next-to-impossible, I find, so the safest method to breaking it as him is to just rush +it with his sword, go back up, repeat 2 more times. Pray the aftermentioned Hard Mode skellies don't decide to cause *too* +much trouble during all of this! + +#### Final room entrance slab +Simply next to the entrance when you come into the third and final room from the intended way. Drops 2 moneybags-turned-checks, +which funnily normally have their item IDs shared with the other 3HB in this room's items, so I had to separate those for +the rando. + +#### Renon's final offers slab +At the top of the final room, on a platform near the Renon contract that would normally be the very last in the game. +This 3HB drops a whopping 8 items, more than any other 3HB in the entire game, and 6 of those are all moneybags. They +*really* shower you in gold in preparation for the finale, huh? + + + +## Castle Keep +#### Behind Dracula's chamber/Dracula's floating cube +This game continues the CV tradition of having a hidden secret around Dracula's chamber that you can get helpful things +from before the final battle begins. Jump onto the torch ledges and use the thin walkway to reach the backside of Dracula's +chamber where these can both be found. The floating cube, in question, can be reached with an invisible bridge. The other +candle here is noteworthy for being the sole jewel candle in vanilla that doesn't set a flag, meaning you can keep going +back down and up the stairs to farm infinite sub-weapon ammo for Dracula like in old school Castlevania! + +### Invisible Items +#### Left/Right Dracula door flame +Inside the torches on either side of the door to Dracula's chamber. Similar to the above-mentioned jewel torch, these do +not set flags. So you can get infinite healing kits for free by constantly going down and back up! \ No newline at end of file diff --git a/worlds/cv64/docs/setup_en.md b/worlds/cv64/docs/setup_en.md new file mode 100644 index 0000000000..6065b142c8 --- /dev/null +++ b/worlds/cv64/docs/setup_en.md @@ -0,0 +1,63 @@ +# Castlevania 64 Setup Guide + +## Required Software + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) +- A Castlevania 64 ROM of the US 1.0 version specifically. The Archipelago community cannot provide this. +- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) 2.7 or later + +### Configuring BizHawk + +Once you have installed BizHawk, open `EmuHawk.exe` and change the following settings: + +- If you're using BizHawk 2.7 or 2.8, go to `Config > Customize`. On the Advanced tab, switch the Lua Core from +`NLua+KopiLua` to `Lua+LuaInterface`, then restart EmuHawk. (If you're using BizHawk 2.9, you can skip this step.) +- Under `Config > Customize`, check the "Run in background" option to prevent disconnecting from the client while you're +tabbed out of EmuHawk. +- Open a `.z64` file in EmuHawk and go to `Config > Controllers…` to configure your inputs. If you can't click +`Controllers…`, load any `.z64` ROM first. +- Consider clearing keybinds in `Config > Hotkeys…` if you don't intend to use them. Select the keybind and press Esc to +clear it. +- All non-Japanese versions of the N64 Castlevanias require a Controller Pak to save game data. To enable this, while +you still have the `.z64` ROM loaded, go to `N64 > Controller Settings...`, click the dropdown by `Controller 1`, and +click `Memory Card`. You must then restart EmuHawk for it to take effect. +- After enabling the `Memory Card` setting, next time you boot up your Castlevania 64 ROM, you will see the +No "CASTLEVANIA" Note Found screen. Pick `Create "CASTLEVANIA" Note Now > Yes` to create save data and enable saving at +the White Jewels. + + +## Generating and Patching a Game + +1. Create your settings file (YAML). You can make one on the +[Castlevania 64 settings page](../../../games/Castlevania 64/player-settings). +2. Follow the general Archipelago instructions for [generating a game](../../Archipelago/setup/en#generating-a-game). +This will generate an output file for you. Your patch file will have the `.apcv64` file extension. +3. Open `ArchipelagoLauncher.exe` +4. Select "Open Patch" on the left side and select your patch file. +5. If this is your first time patching, you will be prompted to locate your vanilla ROM. +6. A patched `.z64` file will be created in the same place as the patch file. +7. On your first time opening a patch with BizHawk Client, you will also be asked to locate `EmuHawk.exe` in your +BizHawk install. + +If you're playing a single-player seed, and you don't care about hints, you can stop here, close the client, and load +the patched ROM in any emulator or EverDrive of your choice. However, for multiworlds and other Archipelago features, +continue below using BizHawk as your emulator. + +## Connecting to a Server + +By default, opening a patch file will do steps 1-5 below for you automatically. Even so, keep them in your memory just +in case you have to close and reopen a window mid-game for some reason. + +1. Castlevania 64 uses Archipelago's BizHawk Client. If the client isn't still open from when you patched your game, +you can re-open it from the launcher. +2. Ensure EmuHawk is running the patched ROM. +3. In EmuHawk, go to `Tools > Lua Console`. This window must stay open while playing. +4. In the Lua Console window, go to `Script > Open Script…`. +5. Navigate to your Archipelago install folder and open `data/lua/connector_bizhawk_generic.lua`. +6. The emulator may freeze every few seconds until it manages to connect to the client. This is expected. The BizHawk +Client window should indicate that it connected and recognized Castlevania 64. +7. To connect the client to the server, enter your room's address and port (e.g. `archipelago.gg:38281`) into the +top text field of the client and click Connect. + +You should now be able to receive and send items. You'll need to do these steps every time you want to reconnect. It is +perfectly safe to make progress offline; everything will re-sync when you reconnect. diff --git a/worlds/cv64/entrances.py b/worlds/cv64/entrances.py new file mode 100644 index 0000000000..74537f9244 --- /dev/null +++ b/worlds/cv64/entrances.py @@ -0,0 +1,149 @@ +from .data import ename, iname, rname +from .stages import get_stage_info +from .options import CV64Options + +from typing import Dict, List, Tuple, Union + +# # # KEY # # # +# "connection" = The name of the Region the Entrance connects into. If it's a Tuple[str, str], we take the stage in +# active_stage_exits given in the second string and then the stage given in that stage's slot given in +# the first string, and take the start or end Region of that stage. +# "rule" = What rule should be applied to the Entrance during set_rules, as defined in self.rules in the CV64Rules class +# definition in rules.py. +# "add conds" = A list of player options conditions that must be satisfied for the Entrance to be added. Can be of +# varying length depending on how many conditions need to be satisfied. In the add_conds dict's tuples, +# the first element is the name of the option, the second is the option value to check for, and the third +# is a boolean for whether we are evaluating for the option value or not. +entrance_info = { + # Forest of Silence + ename.forest_dbridge_gate: {"connection": rname.forest_mid}, + ename.forest_werewolf_gate: {"connection": rname.forest_end}, + ename.forest_end: {"connection": ("next", rname.forest_of_silence)}, + # Castle Wall + ename.cw_portcullis_c: {"connection": rname.cw_exit}, + ename.cw_lt_skip: {"connection": ("next", rname.castle_wall), "add conds": ["hard"]}, + ename.cw_lt_door: {"connection": rname.cw_ltower, "rule": iname.left_tower_key}, + ename.cw_end: {"connection": ("next", rname.castle_wall)}, + # Villa + ename.villa_dog_gates: {"connection": rname.villa_main}, + ename.villa_snipe_dogs: {"connection": rname.villa_start, "add conds": ["carrie", "hard"]}, + ename.villa_to_storeroom: {"connection": rname.villa_storeroom, "rule": iname.storeroom_key}, + ename.villa_to_archives: {"connection": rname.villa_archives, "rule": iname.archives_key}, + ename.villa_renon: {"connection": rname.renon, "add conds": ["shopsanity"]}, + ename.villa_to_maze: {"connection": rname.villa_maze, "rule": iname.garden_key}, + ename.villa_from_storeroom: {"connection": rname.villa_main, "rule": iname.storeroom_key}, + ename.villa_from_maze: {"connection": rname.villa_servants, "rule": iname.garden_key}, + ename.villa_servant_door: {"connection": rname.villa_main}, + ename.villa_copper_door: {"connection": rname.villa_crypt, "rule": iname.copper_key, + "add conds": ["not hard"]}, + ename.villa_copper_skip: {"connection": rname.villa_crypt, "add conds": ["hard"]}, + ename.villa_bridge_door: {"connection": rname.villa_maze}, + ename.villa_end_r: {"connection": ("next", rname.villa)}, + ename.villa_end_c: {"connection": ("alt", rname.villa)}, + # Tunnel + ename.tunnel_start_renon: {"connection": rname.renon, "add conds": ["shopsanity"]}, + ename.tunnel_gondolas: {"connection": rname.tunnel_end}, + ename.tunnel_end_renon: {"connection": rname.renon, "add conds": ["shopsanity"]}, + ename.tunnel_end: {"connection": ("next", rname.tunnel)}, + # Underground Waterway + ename.uw_renon: {"connection": rname.renon, "add conds": ["shopsanity"]}, + ename.uw_final_waterfall: {"connection": rname.uw_end}, + ename.uw_waterfall_skip: {"connection": rname.uw_main, "add conds": ["hard"]}, + ename.uw_end: {"connection": ("next", rname.underground_waterway)}, + # Castle Center + ename.cc_tc_door: {"connection": rname.cc_torture_chamber, "rule": iname.chamber_key}, + ename.cc_renon: {"connection": rname.renon, "add conds": ["shopsanity"]}, + ename.cc_lower_wall: {"connection": rname.cc_crystal, "rule": "Bomb 2"}, + ename.cc_upper_wall: {"connection": rname.cc_library, "rule": "Bomb 1"}, + ename.cc_elevator: {"connection": rname.cc_elev_top}, + ename.cc_exit_r: {"connection": ("next", rname.castle_center)}, + ename.cc_exit_c: {"connection": ("alt", rname.castle_center)}, + # Duel Tower + ename.dt_start: {"connection": ("prev", rname.duel_tower)}, + ename.dt_end: {"connection": ("next", rname.duel_tower)}, + # Tower of Execution + ename.toe_start: {"connection": ("prev", rname.tower_of_execution)}, + ename.toe_gate: {"connection": rname.toe_ledge, "rule": iname.execution_key, + "add conds": ["not hard"]}, + ename.toe_gate_skip: {"connection": rname.toe_ledge, "add conds": ["hard"]}, + ename.toe_end: {"connection": ("next", rname.tower_of_execution)}, + # Tower of Science + ename.tosci_start: {"connection": ("prev", rname.tower_of_science)}, + ename.tosci_key1_door: {"connection": rname.tosci_three_doors, "rule": iname.science_key1}, + ename.tosci_to_key2_door: {"connection": rname.tosci_conveyors, "rule": iname.science_key2}, + ename.tosci_from_key2_door: {"connection": rname.tosci_start, "rule": iname.science_key2}, + ename.tosci_key3_door: {"connection": rname.tosci_key3, "rule": iname.science_key3}, + ename.tosci_end: {"connection": ("next", rname.tower_of_science)}, + # Tower of Sorcery + ename.tosor_start: {"connection": ("prev", rname.tower_of_sorcery)}, + ename.tosor_end: {"connection": ("next", rname.tower_of_sorcery)}, + # Room of Clocks + ename.roc_gate: {"connection": ("next", rname.room_of_clocks)}, + # Clock Tower + ename.ct_to_door1: {"connection": rname.ct_middle, "rule": iname.clocktower_key1}, + ename.ct_from_door1: {"connection": rname.ct_start, "rule": iname.clocktower_key1}, + ename.ct_to_door2: {"connection": rname.ct_end, "rule": iname.clocktower_key2}, + ename.ct_from_door2: {"connection": rname.ct_middle, "rule": iname.clocktower_key2}, + ename.ct_renon: {"connection": rname.renon, "add conds": ["shopsanity"]}, + ename.ct_door_3: {"connection": ("next", rname.clock_tower), "rule": iname.clocktower_key3}, + # Castle Keep + ename.ck_slope_jump: {"connection": rname.roc_main, "add conds": ["hard"]}, + ename.ck_drac_door: {"connection": rname.ck_drac_chamber, "rule": "Dracula"} +} + +add_conds = {"carrie": ("carrie_logic", True, True), + "hard": ("hard_logic", True, True), + "not hard": ("hard_logic", False, True), + "shopsanity": ("shopsanity", True, True)} + +stage_connection_types = {"prev": "end region", + "next": "start region", + "alt": "start region"} + + +def get_entrance_info(entrance: str, info: str) -> Union[str, Tuple[str, str], List[str], None]: + return entrance_info[entrance].get(info, None) + + +def get_warp_entrances(active_warp_list: List[str]) -> Dict[str, str]: + # Create the starting stage Entrance. + warp_entrances = {get_stage_info(active_warp_list[0], "start region"): "Start stage"} + + # Create the warp Entrances. + for i in range(1, len(active_warp_list)): + mid_stage_region = get_stage_info(active_warp_list[i], "mid region") + warp_entrances.update({mid_stage_region: f"Warp {i}"}) + + return warp_entrances + + +def verify_entrances(options: CV64Options, entrances: List[str], + active_stage_exits: Dict[str, Dict[str, Union[str, int, None]]]) -> Dict[str, str]: + verified_entrances = {} + + for ent_name in entrances: + ent_add_conds = get_entrance_info(ent_name, "add conds") + + # Check any options that might be associated with the Entrance before adding it. + add_it = True + if ent_add_conds is not None: + for cond in ent_add_conds: + if not ((getattr(options, add_conds[cond][0]).value == add_conds[cond][1]) == add_conds[cond][2]): + add_it = False + + if not add_it: + continue + + # Add the Entrance to the verified Entrances if the above check passes. + connection = get_entrance_info(ent_name, "connection") + + # If the Entrance is a connection to a different stage, get the corresponding other stage Region. + if isinstance(connection, tuple): + connecting_stage = active_stage_exits[connection[1]][connection[0]] + # Stages that lead backwards at the beginning of the line will appear leading to "Menu". + if connecting_stage in ["Menu", None]: + continue + connection = get_stage_info(connecting_stage, stage_connection_types[connection[0]]) + verified_entrances.update({connection: ent_name}) + + return verified_entrances diff --git a/worlds/cv64/items.py b/worlds/cv64/items.py new file mode 100644 index 0000000000..d40f5d53cb --- /dev/null +++ b/worlds/cv64/items.py @@ -0,0 +1,214 @@ +from BaseClasses import Item +from .data import iname +from .locations import base_id, get_location_info +from .options import DraculasCondition, SpareKeys + +from typing import TYPE_CHECKING, Dict, Union + +if TYPE_CHECKING: + from . import CV64World + +import math + + +class CV64Item(Item): + game: str = "Castlevania 64" + + +# # # KEY # # # +# "code" = The unique part of the Item's AP code attribute, as well as the value to call the in-game "prepare item +# textbox" function with to give the Item in-game. Add this + base_id to get the actual AP code. +# "default classification" = The AP Item Classification that gets assigned to instances of that Item in create_item +# by default, unless I deliberately override it (as is the case for some Special1s). +# "inventory offset" = What offset from the start of the in-game inventory array (beginning at 0x80389C4B) stores the +# current count for that Item. Used for start inventory purposes. +# "pickup actor id" = The ID for the Item's in-game Item pickup actor. If it's not in the Item's data dict, it's the +# same as the Item's code. This is what gets written in the ROM to replace non-NPC/shop items. +# "sub equip id" = For sub-weapons specifically, this is the number to put in the game's "current sub-weapon" value to +# indicate the player currently having that weapon. Used for start inventory purposes. +item_info = { + # White jewel + iname.red_jewel_s: {"code": 0x02, "default classification": "filler"}, + iname.red_jewel_l: {"code": 0x03, "default classification": "filler"}, + iname.special_one: {"code": 0x04, "default classification": "progression_skip_balancing", + "inventory offset": 0}, + iname.special_two: {"code": 0x05, "default classification": "progression_skip_balancing", + "inventory offset": 1}, + iname.roast_chicken: {"code": 0x06, "default classification": "filler", "inventory offset": 2}, + iname.roast_beef: {"code": 0x07, "default classification": "filler", "inventory offset": 3}, + iname.healing_kit: {"code": 0x08, "default classification": "useful", "inventory offset": 4}, + iname.purifying: {"code": 0x09, "default classification": "filler", "inventory offset": 5}, + iname.cure_ampoule: {"code": 0x0A, "default classification": "filler", "inventory offset": 6}, + # pot-pourri + iname.powerup: {"code": 0x0C, "default classification": "filler"}, + iname.permaup: {"code": 0x10C, "default classification": "useful", "pickup actor id": 0x0C, + "inventory offset": 8}, + iname.knife: {"code": 0x0D, "default classification": "filler", "pickup actor id": 0x10, + "sub equip id": 1}, + iname.holy_water: {"code": 0x0E, "default classification": "filler", "pickup actor id": 0x0D, + "sub equip id": 2}, + iname.cross: {"code": 0x0F, "default classification": "filler", "pickup actor id": 0x0E, + "sub equip id": 3}, + iname.axe: {"code": 0x10, "default classification": "filler", "pickup actor id": 0x0F, + "sub equip id": 4}, + # Wooden stake (AP item) + iname.ice_trap: {"code": 0x12, "default classification": "trap"}, + # The contract + # engagement ring + iname.magical_nitro: {"code": 0x15, "default classification": "progression", "inventory offset": 17}, + iname.mandragora: {"code": 0x16, "default classification": "progression", "inventory offset": 18}, + iname.sun_card: {"code": 0x17, "default classification": "filler", "inventory offset": 19}, + iname.moon_card: {"code": 0x18, "default classification": "filler", "inventory offset": 20}, + # Incandescent gaze + iname.archives_key: {"code": 0x1A, "default classification": "progression", "pickup actor id": 0x1D, + "inventory offset": 22}, + iname.left_tower_key: {"code": 0x1B, "default classification": "progression", "pickup actor id": 0x1E, + "inventory offset": 23}, + iname.storeroom_key: {"code": 0x1C, "default classification": "progression", "pickup actor id": 0x1F, + "inventory offset": 24}, + iname.garden_key: {"code": 0x1D, "default classification": "progression", "pickup actor id": 0x20, + "inventory offset": 25}, + iname.copper_key: {"code": 0x1E, "default classification": "progression", "pickup actor id": 0x21, + "inventory offset": 26}, + iname.chamber_key: {"code": 0x1F, "default classification": "progression", "pickup actor id": 0x22, + "inventory offset": 27}, + iname.execution_key: {"code": 0x20, "default classification": "progression", "pickup actor id": 0x23, + "inventory offset": 28}, + iname.science_key1: {"code": 0x21, "default classification": "progression", "pickup actor id": 0x24, + "inventory offset": 29}, + iname.science_key2: {"code": 0x22, "default classification": "progression", "pickup actor id": 0x25, + "inventory offset": 30}, + iname.science_key3: {"code": 0x23, "default classification": "progression", "pickup actor id": 0x26, + "inventory offset": 31}, + iname.clocktower_key1: {"code": 0x24, "default classification": "progression", "pickup actor id": 0x27, + "inventory offset": 32}, + iname.clocktower_key2: {"code": 0x25, "default classification": "progression", "pickup actor id": 0x28, + "inventory offset": 33}, + iname.clocktower_key3: {"code": 0x26, "default classification": "progression", "pickup actor id": 0x29, + "inventory offset": 34}, + iname.five_hundred_gold: {"code": 0x27, "default classification": "filler", "pickup actor id": 0x1A}, + iname.three_hundred_gold: {"code": 0x28, "default classification": "filler", "pickup actor id": 0x1B}, + iname.one_hundred_gold: {"code": 0x29, "default classification": "filler", "pickup actor id": 0x1C}, + iname.crystal: {"default classification": "progression"}, + iname.trophy: {"default classification": "progression"}, + iname.victory: {"default classification": "progression"} +} + +filler_item_names = [iname.red_jewel_s, iname.red_jewel_l, iname.five_hundred_gold, iname.three_hundred_gold, + iname.one_hundred_gold] + + +def get_item_info(item: str, info: str) -> Union[str, int, None]: + return item_info[item].get(info, None) + + +def get_item_names_to_ids() -> Dict[str, int]: + return {name: get_item_info(name, "code")+base_id for name in item_info if get_item_info(name, "code") is not None} + + +def get_item_counts(world: "CV64World") -> Dict[str, Dict[str, int]]: + + active_locations = world.multiworld.get_unfilled_locations(world.player) + + item_counts = { + "progression": {}, + "progression_skip_balancing": {}, + "useful": {}, + "filler": {}, + "trap": {} + } + total_items = 0 + extras_count = 0 + + # Get from each location its vanilla item and add it to the default item counts. + for loc in active_locations: + if loc.address is None: + continue + + if world.options.hard_item_pool and get_location_info(loc.name, "hard item") is not None: + item_to_add = get_location_info(loc.name, "hard item") + else: + item_to_add = get_location_info(loc.name, "normal item") + + classification = get_item_info(item_to_add, "default classification") + + if item_to_add not in item_counts[classification]: + item_counts[classification][item_to_add] = 1 + else: + item_counts[classification][item_to_add] += 1 + total_items += 1 + + # Replace all but 2 PowerUps with junk if Permanent PowerUps is on and mark those two PowerUps as Useful. + if world.options.permanent_powerups: + for i in range(item_counts["filler"][iname.powerup] - 2): + item_counts["filler"][world.get_filler_item_name()] += 1 + del(item_counts["filler"][iname.powerup]) + item_counts["useful"][iname.permaup] = 2 + + # Add the total Special1s. + item_counts["progression_skip_balancing"][iname.special_one] = world.options.total_special1s.value + extras_count += world.options.total_special1s.value + + # Add the total Special2s if Dracula's Condition is Special2s. + if world.options.draculas_condition == DraculasCondition.option_specials: + item_counts["progression_skip_balancing"][iname.special_two] = world.options.total_special2s.value + extras_count += world.options.total_special2s.value + + # Determine the extra key counts if applicable. Doing this before moving Special1s will ensure only the keys and + # bomb components are affected by this. + for key in item_counts["progression"]: + spare_keys = 0 + if world.options.spare_keys == SpareKeys.option_on: + spare_keys = item_counts["progression"][key] + elif world.options.spare_keys == SpareKeys.option_chance: + if item_counts["progression"][key] > 0: + for i in range(item_counts["progression"][key]): + spare_keys += world.random.randint(0, 1) + item_counts["progression"][key] += spare_keys + extras_count += spare_keys + + # Move the total number of Special1s needed to warp everywhere to normal progression balancing if S1s per warp is + # 3 or lower. + if world.s1s_per_warp <= 3: + item_counts["progression_skip_balancing"][iname.special_one] -= world.s1s_per_warp * 7 + item_counts["progression"][iname.special_one] = world.s1s_per_warp * 7 + + # Determine the total amounts of replaceable filler and non-filler junk. + total_filler_junk = 0 + total_non_filler_junk = 0 + for junk in item_counts["filler"]: + if junk in filler_item_names: + total_filler_junk += item_counts["filler"][junk] + else: + total_non_filler_junk += item_counts["filler"][junk] + + # Subtract from the filler counts total number of "extra" items we've added. get_filler_item_name() filler will be + # subtracted from first until we run out of that, at which point we'll start subtracting from the rest. At this + # moment, non-filler item name filler cannot run out no matter the settings, so I haven't bothered adding handling + # for when it does yet. + available_filler_junk = filler_item_names.copy() + for i in range(extras_count): + if total_filler_junk > 0: + total_filler_junk -= 1 + item_to_subtract = world.random.choice(available_filler_junk) + else: + total_non_filler_junk -= 1 + item_to_subtract = world.random.choice(list(item_counts["filler"].keys())) + + item_counts["filler"][item_to_subtract] -= 1 + if item_counts["filler"][item_to_subtract] == 0: + del(item_counts["filler"][item_to_subtract]) + if item_to_subtract in available_filler_junk: + available_filler_junk.remove(item_to_subtract) + + # Determine the Ice Trap count by taking a certain % of the total filler remaining at this point. + item_counts["trap"][iname.ice_trap] = math.floor((total_filler_junk + total_non_filler_junk) * + (world.options.ice_trap_percentage.value / 100.0)) + for i in range(item_counts["trap"][iname.ice_trap]): + # Subtract the remaining filler after determining the ice trap count. + item_to_subtract = world.random.choice(list(item_counts["filler"].keys())) + item_counts["filler"][item_to_subtract] -= 1 + if item_counts["filler"][item_to_subtract] == 0: + del (item_counts["filler"][item_to_subtract]) + + return item_counts diff --git a/worlds/cv64/locations.py b/worlds/cv64/locations.py new file mode 100644 index 0000000000..264f2f7c0b --- /dev/null +++ b/worlds/cv64/locations.py @@ -0,0 +1,699 @@ +from BaseClasses import Location +from .data import lname, iname +from .options import CV64Options, SubWeaponShuffle, DraculasCondition, RenonFightCondition, VincentFightCondition + +from typing import Dict, Optional, Union, List, Tuple + +base_id = 0xC64000 + + +class CV64Location(Location): + game: str = "Castlevania 64" + + +# # # KEY # # # +# "code" = The unique part of the Location's AP code attribute, as well as the in-game bitflag index starting from +# 0x80389BE4 that indicates the Location has been checked. Add this + base_id to get the actual AP code. +# "offset" = The offset in the ROM to overwrite to change the Item on that Location. +# "normal item" = The Item normally there in vanilla on most difficulties in most versions of the game. Used to +# determine the World's Item counts by checking what Locations are active. +# "hard item" = The Item normally there in Hard Mode in the PAL version of CV64 specifically. Used instead of the +# normal Item when the hard Item pool is enabled if it's in the Location's data dict. +# "add conds" = A list of player options conditions that must be satisfied for the Location to be added. Can be of +# varying length depending on how many conditions need to be satisfied. In the add_conds dict's tuples, +# the first element is the name of the option, the second is the option value to check for, and the third +# is a boolean for whether we are evaluating for the option value or not. +# "event" = What event Item to place on that Location, for Locations that are events specifically. +# "countdown" = What Countdown number in the array of Countdown numbers that Location contributes to. For the most part, +# this is figured out by taking that Location's corresponding stage's postion in the vanilla stage order, +# but there are some exceptions made for Locations in parts of Villa and Castle Center that split off into +# their own numbers. +# "type" = Anything special about this Location in-game, whether it be NPC-given, invisible, etc. +location_info = { + # Forest of Silence + lname.forest_pillars_right: {"code": 0x1C, "offset": 0x10C67B, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s}, + lname.forest_pillars_left: {"code": 0x46, "offset": 0x10C6EB, "normal item": iname.knife, + "add conds": ["sub"]}, + lname.forest_pillars_top: {"code": 0x13, "offset": 0x10C71B, "normal item": iname.roast_beef, + "hard item": iname.red_jewel_l}, + lname.forest_boss_one: {"event": iname.trophy, "add conds": ["boss"]}, + lname.forest_king_skeleton: {"code": 0xC, "offset": 0x10C6BB, "normal item": iname.five_hundred_gold}, + lname.forest_lgaz_in: {"code": 0x1A, "offset": 0x10C68B, "normal item": iname.moon_card}, + lname.forest_lgaz_top: {"code": 0x19, "offset": 0x10C693, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s}, + lname.forest_hgaz_in: {"code": 0xB, "offset": 0x10C6C3, "normal item": iname.sun_card}, + lname.forest_hgaz_top: {"code": 0x3, "offset": 0x10C6E3, "normal item": iname.roast_chicken, + "hard item": iname.five_hundred_gold}, + lname.forest_weretiger_sw: {"code": 0xA, "offset": 0x10C6CB, "normal item": iname.five_hundred_gold}, + lname.forest_boss_two: {"event": iname.trophy, "add conds": ["boss"]}, + lname.forest_weretiger_gate: {"code": 0x7, "offset": 0x10C683, "normal item": iname.powerup}, + lname.forest_dirge_tomb_l: {"code": 0x59, "offset": 0x10C74B, "normal item": iname.one_hundred_gold, + "add conds": ["empty"]}, + lname.forest_dirge_tomb_u: {"code": 0x8, "offset": 0x10C743, "normal item": iname.one_hundred_gold}, + lname.forest_dirge_plaque: {"code": 0x6, "offset": 0x7C7F9D, "normal item": iname.roast_chicken, + "hard item": iname.one_hundred_gold, "type": "inv"}, + lname.forest_dirge_ped: {"code": 0x45, "offset": 0x10C6FB, "normal item": iname.cross, + "add conds": ["sub"]}, + lname.forest_dirge_rock1: {"code": 0x221, "offset": 0x10C791, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.forest_dirge_rock2: {"code": 0x222, "offset": 0x10C793, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.forest_dirge_rock3: {"code": 0x223, "offset": 0x10C795, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.forest_dirge_rock4: {"code": 0x224, "offset": 0x10C797, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.forest_dirge_rock5: {"code": 0x225, "offset": 0x10C799, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.forest_corpse_save: {"code": 0xF, "offset": 0x10C6A3, "normal item": iname.red_jewel_s}, + lname.forest_dbridge_wall: {"code": 0x18, "offset": 0x10C69B, "normal item": iname.red_jewel_s}, + lname.forest_dbridge_sw: {"code": 0x9, "offset": 0x10C6D3, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold}, + lname.forest_dbridge_gate_l: {"code": 0x44, "offset": 0x10C6F3, "normal item": iname.axe, "add conds": ["sub"]}, + lname.forest_dbridge_gate_r: {"code": 0xE, "offset": 0x10C6AB, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s}, + lname.forest_dbridge_tomb_l: {"code": 0xEA, "offset": 0x10C763, "normal item": iname.three_hundred_gold, + "add conds": ["empty"]}, + lname.forest_dbridge_tomb_ur: {"code": 0xE4, "offset": 0x10C773, "normal item": iname.three_hundred_gold, + "add conds": ["empty"]}, + lname.forest_dbridge_tomb_uf: {"code": 0x1B, "offset": 0x10C76B, "normal item": iname.red_jewel_s}, + lname.forest_bface_tomb_lf: {"code": 0x10, "offset": 0x10C75B, "normal item": iname.roast_chicken}, + lname.forest_bface_tomb_lr: {"code": 0x58, "offset": 0x10C753, "normal item": iname.three_hundred_gold, + "add conds": ["empty"]}, + lname.forest_bface_tomb_u: {"code": 0x1E, "offset": 0x10C77B, "normal item": iname.one_hundred_gold}, + lname.forest_ibridge: {"code": 0x2, "offset": 0x10C713, "normal item": iname.one_hundred_gold}, + lname.forest_bridge_rock1: {"code": 0x227, "offset": 0x10C79D, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + lname.forest_bridge_rock2: {"code": 0x228, "offset": 0x10C79F, "normal item": iname.five_hundred_gold, + "hard item": iname.three_hundred_gold, "add conds": ["3hb"]}, + lname.forest_bridge_rock3: {"code": 0x229, "offset": 0x10C7A1, "normal item": iname.powerup, + "hard item": iname.three_hundred_gold, "add conds": ["3hb"]}, + lname.forest_bridge_rock4: {"code": 0x22A, "offset": 0x10C7A3, "normal item": iname.roast_chicken, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.forest_werewolf_tomb_lf: {"code": 0xE7, "offset": 0x10C783, "normal item": iname.one_hundred_gold, + "add conds": ["empty"]}, + lname.forest_werewolf_tomb_lr: {"code": 0xE6, "offset": 0x10C73B, "normal item": iname.three_hundred_gold, + "add conds": ["empty"]}, + lname.forest_werewolf_tomb_r: {"code": 0x4, "offset": 0x10C733, "normal item": iname.sun_card}, + lname.forest_werewolf_plaque: {"code": 0x1, "offset": 0xBFC8AF, "normal item": iname.roast_chicken, + "type": "inv"}, + lname.forest_werewolf_tree: {"code": 0xD, "offset": 0x10C6B3, "normal item": iname.red_jewel_s}, + lname.forest_werewolf_island: {"code": 0x41, "offset": 0x10C703, "normal item": iname.holy_water, + "add conds": ["sub"]}, + lname.forest_final_sw: {"code": 0x12, "offset": 0x10C72B, "normal item": iname.roast_beef}, + lname.forest_boss_three: {"event": iname.trophy, "add conds": ["boss"]}, + + # Castle Wall + lname.cwr_bottom: {"code": 0x1DD, "offset": 0x10C7E7, "normal item": iname.sun_card, + "hard item": iname.one_hundred_gold}, + lname.cw_dragon_sw: {"code": 0x153, "offset": 0x10C817, "normal item": iname.roast_chicken}, + lname.cw_boss: {"event": iname.trophy, "add conds": ["boss"]}, + lname.cw_save_slab1: {"code": 0x22C, "offset": 0x10C84D, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + lname.cw_save_slab2: {"code": 0x22D, "offset": 0x10C84F, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.cw_save_slab3: {"code": 0x22E, "offset": 0x10C851, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.cw_save_slab4: {"code": 0x22F, "offset": 0x10C853, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.cw_save_slab5: {"code": 0x230, "offset": 0x10C855, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.cw_rrampart: {"code": 0x156, "offset": 0x10C7FF, "normal item": iname.five_hundred_gold}, + lname.cw_lrampart: {"code": 0x155, "offset": 0x10C807, "normal item": iname.moon_card, + "hard item": iname.one_hundred_gold}, + lname.cw_pillar: {"code": 0x14D, "offset": 0x7F9A0F, "normal item": iname.holy_water, "add conds": ["sub"]}, + lname.cw_shelf_visible: {"code": 0x158, "offset": 0x7F99A9, "normal item": iname.powerup}, + lname.cw_shelf_sandbags: {"code": 0x14E, "offset": 0x7F9A3E, "normal item": iname.five_hundred_gold, "type": "inv"}, + lname.cw_shelf_torch: {"code": 0x14C, "offset": 0x10C82F, "normal item": iname.cross, "add conds": ["sub"]}, + lname.cw_ground_left: {"code": 0x14B, "offset": 0x10C827, "normal item": iname.knife, "add conds": ["sub"]}, + lname.cw_ground_middle: {"code": 0x159, "offset": 0x10C7F7, "normal item": iname.left_tower_key}, + lname.cw_ground_right: {"code": 0x14A, "offset": 0x10C81F, "normal item": iname.axe, "add conds": ["sub"]}, + lname.cwl_bottom: {"code": 0x1DE, "offset": 0x10C7DF, "normal item": iname.moon_card}, + lname.cwl_bridge: {"code": 0x1DC, "offset": 0x10C7EF, "normal item": iname.roast_beef}, + lname.cw_drac_sw: {"code": 0x154, "offset": 0x10C80F, "normal item": iname.roast_chicken, + "hard item": iname.one_hundred_gold}, + lname.cw_drac_slab1: {"code": 0x232, "offset": 0x10C859, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.cw_drac_slab2: {"code": 0x233, "offset": 0x10C85B, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.cw_drac_slab3: {"code": 0x234, "offset": 0x10C85D, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.cw_drac_slab4: {"code": 0x235, "offset": 0x10C85F, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.cw_drac_slab5: {"code": 0x236, "offset": 0x10C861, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + # Villa + lname.villafy_outer_gate_l: {"code": 0x133, "offset": 0x10C87F, "normal item": iname.red_jewel_l}, + lname.villafy_outer_gate_r: {"code": 0x132, "offset": 0x10C887, "normal item": iname.red_jewel_l}, + lname.villafy_dog_platform: {"code": 0x134, "offset": 0x10C89F, "normal item": iname.red_jewel_l}, + lname.villafy_inner_gate: {"code": 0x138, "offset": 0xBFC8D7, "normal item": iname.roast_beef}, + lname.villafy_gate_marker: {"code": 0x131, "offset": 0x10C8A7, "normal item": iname.powerup, + "hard item": iname.one_hundred_gold}, + lname.villafy_villa_marker: {"code": 0x13E, "offset": 0x10C897, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold}, + lname.villafy_tombstone: {"code": 0x12F, "offset": 0x8099CC, "normal item": iname.moon_card, + "type": "inv"}, + lname.villafy_fountain_fl: {"code": 0x139, "offset": 0xBFC8CF, "normal item": iname.five_hundred_gold}, + lname.villafy_fountain_fr: {"code": 0x130, "offset": 0x80997D, "normal item": iname.purifying}, + lname.villafy_fountain_ml: {"code": 0x13A, "offset": 0x809956, "normal item": iname.sun_card}, + lname.villafy_fountain_mr: {"code": 0x13D, "offset": 0x80992D, "normal item": iname.moon_card}, + lname.villafy_fountain_rl: {"code": 0x13B, "offset": 0xBFC8D3, "normal item": iname.roast_beef, + "hard item": iname.five_hundred_gold}, + lname.villafy_fountain_rr: {"code": 0x13C, "offset": 0x80993C, "normal item": iname.five_hundred_gold}, + lname.villafo_front_r: {"code": 0x3D, "offset": 0x10C8E7, "normal item": iname.red_jewel_l, + "hard item": iname.five_hundred_gold}, + lname.villafo_front_l: {"code": 0x3B, "offset": 0x10C8DF, "normal item": iname.red_jewel_s}, + lname.villafo_mid_l: {"code": 0x3C, "offset": 0x10C8D7, "normal item": iname.red_jewel_s}, + lname.villafo_mid_r: {"code": 0xE5, "offset": 0x10C8CF, "normal item": iname.three_hundred_gold, + "add conds": ["empty"]}, + lname.villafo_rear_r: {"code": 0x38, "offset": 0x10C8C7, "normal item": iname.red_jewel_s}, + lname.villafo_rear_l: {"code": 0x39, "offset": 0x10C8BF, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s}, + lname.villafo_pot_r: {"code": 0x2E, "offset": 0x10C8AF, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s}, + lname.villafo_pot_l: {"code": 0x2F, "offset": 0x10C8B7, "normal item": iname.red_jewel_s}, + lname.villafo_sofa: {"code": 0x2D, "offset": 0x81F07C, "normal item": iname.purifying, + "type": "inv"}, + lname.villafo_chandelier1: {"code": 0x27D, "offset": 0x10C8F5, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + lname.villafo_chandelier2: {"code": 0x27E, "offset": 0x10C8F7, "normal item": iname.purifying, + "add conds": ["3hb"]}, + lname.villafo_chandelier3: {"code": 0x27F, "offset": 0x10C8F9, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.villafo_chandelier4: {"code": 0x280, "offset": 0x10C8FB, "normal item": iname.cure_ampoule, + "add conds": ["3hb"]}, + lname.villafo_chandelier5: {"code": 0x281, "offset": 0x10C8FD, "normal item": iname.roast_chicken, + "add conds": ["3hb"]}, + lname.villala_hallway_stairs: {"code": 0x34, "offset": 0x10C927, "normal item": iname.red_jewel_l}, + lname.villala_hallway_l: {"code": 0x40, "offset": 0xBFC903, "normal item": iname.knife, + "add conds": ["sub"]}, + lname.villala_hallway_r: {"code": 0x4F, "offset": 0xBFC8F7, "normal item": iname.axe, + "add conds": ["sub"]}, + lname.villala_bedroom_chairs: {"code": 0x33, "offset": 0x83A588, "normal item": iname.purifying, + "hard item": iname.three_hundred_gold}, + lname.villala_bedroom_bed: {"code": 0x32, "offset": 0xBFC95B, "normal item": iname.red_jewel_l, + "hard item": iname.three_hundred_gold}, + lname.villala_vincent: {"code": 0x23, "offset": 0xBFE42F, "normal item": iname.archives_key, + "type": "npc"}, + lname.villala_slivingroom_table: {"code": 0x2B, "offset": 0xBFC96B, "normal item": iname.five_hundred_gold, + "type": "inv"}, + lname.villala_slivingroom_mirror: {"code": 0x49, "offset": 0x83A5D9, "normal item": iname.cross, + "add conds": ["sub"]}, + lname.villala_diningroom_roses: {"code": 0x2A, "offset": 0xBFC90B, "normal item": iname.purifying, + "hard item": iname.three_hundred_gold, "type": "inv"}, + lname.villala_llivingroom_pot_r: {"code": 0x26, "offset": 0x10C90F, "normal item": iname.storeroom_key}, + lname.villala_llivingroom_pot_l: {"code": 0x25, "offset": 0x10C917, "normal item": iname.roast_chicken}, + lname.villala_llivingroom_painting: {"code": 0x2C, "offset": 0xBFC907, "normal item": iname.purifying, + "hard item": iname.one_hundred_gold, "type": "inv"}, + lname.villala_llivingroom_light: {"code": 0x28, "offset": 0x10C91F, "normal item": iname.purifying}, + lname.villala_llivingroom_lion: {"code": 0x30, "offset": 0x83A610, "normal item": iname.roast_chicken, + "hard item": iname.five_hundred_gold, "type": "inv"}, + lname.villala_exit_knight: {"code": 0x27, "offset": 0xBFC967, "normal item": iname.purifying, + "type": "inv"}, + lname.villala_storeroom_l: {"code": 0x36, "offset": 0xBFC95F, "normal item": iname.roast_beef}, + lname.villala_storeroom_r: {"code": 0x37, "offset": 0xBFC8FF, "normal item": iname.roast_chicken, + "hard item": iname.five_hundred_gold}, + lname.villala_storeroom_s: {"code": 0x31, "offset": 0xBFC963, "normal item": iname.purifying, + "hard item": iname.one_hundred_gold, "type": "inv"}, + lname.villala_archives_entrance: {"code": 0x48, "offset": 0x83A5E5, "normal item": iname.holy_water, + "add conds": ["sub"]}, + lname.villala_archives_table: {"code": 0x29, "offset": 0xBFC90F, "normal item": iname.purifying, + "type": "inv"}, + lname.villala_archives_rear: {"code": 0x24, "offset": 0x83A5B1, "normal item": iname.garden_key}, + lname.villam_malus_torch: {"code": 0x173, "offset": 0x10C967, "normal item": iname.red_jewel_s, + "countdown": 13}, + lname.villam_malus_bush: {"code": 0x16C, "offset": 0x850FEC, "normal item": iname.roast_chicken, + "type": "inv", "countdown": 13}, + lname.villam_fplatform: {"code": 0x16B, "offset": 0x10C987, "normal item": iname.knife, + "add conds": ["sub"], "countdown": 13}, + lname.villam_frankieturf_l: {"code": 0x177, "offset": 0x10C947, "normal item": iname.three_hundred_gold, + "countdown": 13}, + lname.villam_frankieturf_r: {"code": 0x16A, "offset": 0x10C98F, "normal item": iname.holy_water, + "add conds": ["sub"], "countdown": 13}, + lname.villam_frankieturf_ru: {"code": 0x16E, "offset": 0x10C9A7, "normal item": iname.red_jewel_s, + "countdown": 13}, + lname.villam_fgarden_f: {"code": 0x172, "offset": 0x10C96F, "normal item": iname.red_jewel_s, + "countdown": 13}, + lname.villam_fgarden_mf: {"code": 0x171, "offset": 0x10C977, "normal item": iname.red_jewel_s, + "countdown": 13}, + lname.villam_fgarden_mr: {"code": 0x174, "offset": 0x10C95F, "normal item": iname.roast_chicken, + "countdown": 13}, + lname.villam_fgarden_r: {"code": 0x170, "offset": 0x10C97F, "normal item": iname.red_jewel_l, + "countdown": 13}, + lname.villam_rplatform: {"code": 0x169, "offset": 0x10C997, "normal item": iname.axe, + "add conds": ["sub"], "countdown": 13}, + lname.villam_rplatform_de: {"code": 0x176, "offset": 0x10C94F, "normal item": iname.five_hundred_gold, + "countdown": 13}, + lname.villam_exit_de: {"code": 0x175, "offset": 0x10C957, "normal item": iname.three_hundred_gold, + "countdown": 13}, + lname.villam_serv_path: {"code": 0x17A, "offset": 0x10C92F, "normal item": iname.copper_key, + "countdown": 13}, + lname.villafo_serv_ent: {"code": 0x3E, "offset": 0x10C8EF, "normal item": iname.roast_chicken}, + lname.villam_crypt_ent: {"code": 0x178, "offset": 0x10C93F, "normal item": iname.purifying, + "countdown": 13}, + lname.villam_crypt_upstream: {"code": 0x179, "offset": 0x10C937, "normal item": iname.roast_beef, + "countdown": 13}, + lname.villac_ent_l: {"code": 0xC9, "offset": 0x10CF4B, "normal item": iname.red_jewel_s, + "countdown": 13}, + lname.villac_ent_r: {"code": 0xC0, "offset": 0x10CF63, "normal item": iname.five_hundred_gold, + "countdown": 13}, + lname.villac_wall_l: {"code": 0xC2, "offset": 0x10CF6B, "normal item": iname.roast_chicken, + "countdown": 13}, + lname.villac_wall_r: {"code": 0xC1, "offset": 0x10CF5B, "normal item": iname.red_jewel_l, + "countdown": 13}, + lname.villac_coffin_l: {"code": 0xD8, "offset": 0x10CF73, "normal item": iname.knife, + "add conds": ["sub"], "countdown": 13}, + lname.villac_coffin_r: {"code": 0xC8, "offset": 0x10CF53, "normal item": iname.red_jewel_s, + "countdown": 13}, + lname.villa_boss_one: {"event": iname.trophy, "add conds": ["boss"]}, + lname.villa_boss_two: {"event": iname.trophy, "add conds": ["boss"]}, + # Tunnel + lname.tunnel_landing: {"code": 0x197, "offset": 0x10C9AF, "normal item": iname.red_jewel_l, + "hard item": iname.one_hundred_gold}, + lname.tunnel_landing_rc: {"code": 0x196, "offset": 0x10C9B7, "normal item": iname.red_jewel_s, + "hard item": iname.one_hundred_gold}, + lname.tunnel_stone_alcove_r: {"code": 0xE1, "offset": 0x10CA57, "normal item": iname.holy_water, + "add conds": ["sub"]}, + lname.tunnel_stone_alcove_l: {"code": 0x187, "offset": 0x10CA9F, "normal item": iname.roast_beef, + "hard item": iname.roast_chicken}, + lname.tunnel_twin_arrows: {"code": 0x195, "offset": 0xBFC993, "normal item": iname.cure_ampoule, + "type": "inv"}, + lname.tunnel_arrows_rock1: {"code": 0x238, "offset": 0x10CABD, "normal item": iname.purifying, + "add conds": ["3hb"]}, + lname.tunnel_arrows_rock2: {"code": 0x239, "offset": 0x10CABF, "normal item": iname.purifying, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.tunnel_arrows_rock3: {"code": 0x23A, "offset": 0x10CAC1, "normal item": iname.cure_ampoule, + "add conds": ["3hb"]}, + lname.tunnel_arrows_rock4: {"code": 0x23B, "offset": 0x10CAC3, "normal item": iname.cure_ampoule, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.tunnel_arrows_rock5: {"code": 0x23C, "offset": 0x10CAC5, "normal item": iname.roast_chicken, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.tunnel_lonesome_bucket: {"code": 0x189, "offset": 0xBFC99B, "normal item": iname.cure_ampoule, + "type": "inv"}, + lname.tunnel_lbucket_mdoor_l: {"code": 0x198, "offset": 0x10CA67, "normal item": iname.knife, + "add conds": ["sub"]}, + lname.tunnel_lbucket_quag: {"code": 0x191, "offset": 0x10C9DF, "normal item": iname.red_jewel_l}, + lname.tunnel_bucket_quag_rock1: {"code": 0x23E, "offset": 0x10CAC9, "normal item": iname.roast_beef, + "hard item": iname.roast_chicken, "add conds": ["3hb"]}, + lname.tunnel_bucket_quag_rock2: {"code": 0x23F, "offset": 0x10CACB, "normal item": iname.roast_beef, + "hard item": iname.roast_chicken, "add conds": ["3hb"]}, + lname.tunnel_bucket_quag_rock3: {"code": 0x240, "offset": 0x10CACD, "normal item": iname.roast_beef, + "hard item": iname.roast_chicken, "add conds": ["3hb"]}, + lname.tunnel_lbucket_albert: {"code": 0x190, "offset": 0x10C9E7, "normal item": iname.red_jewel_s}, + lname.tunnel_albert_camp: {"code": 0x192, "offset": 0x10C9D7, "normal item": iname.red_jewel_s}, + lname.tunnel_albert_quag: {"code": 0x193, "offset": 0x10C9CF, "normal item": iname.red_jewel_l}, + lname.tunnel_gondola_rc_sdoor_l: {"code": 0x53, "offset": 0x10CA5F, "normal item": iname.cross, + "add conds": ["sub"]}, + lname.tunnel_gondola_rc_sdoor_m: {"code": 0x19E, "offset": 0x10CAA7, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold}, + lname.tunnel_gondola_rc_sdoor_r: {"code": 0x188, "offset": 0x10CA27, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold}, + lname.tunnel_gondola_rc: {"code": 0x19C, "offset": 0x10CAB7, "normal item": iname.powerup}, + lname.tunnel_rgondola_station: {"code": 0x194, "offset": 0x10C9C7, "normal item": iname.red_jewel_s}, + lname.tunnel_gondola_transfer: {"code": 0x186, "offset": 0x10CA2F, "normal item": iname.five_hundred_gold}, + lname.tunnel_corpse_bucket_quag: {"code": 0x18E, "offset": 0x10C9F7, "normal item": iname.red_jewel_s}, + lname.tunnel_corpse_bucket_mdoor_l: {"code": 0x52, "offset": 0x10CA6F, "normal item": iname.holy_water, + "add conds": ["sub"]}, + lname.tunnel_corpse_bucket_mdoor_r: {"code": 0x185, "offset": 0x10CA37, "normal item": iname.sun_card, + "hard item": iname.one_hundred_gold}, + lname.tunnel_shovel_quag_start: {"code": 0x18D, "offset": 0x10C9FF, "normal item": iname.red_jewel_l}, + lname.tunnel_exit_quag_start: {"code": 0x18C, "offset": 0x10CA07, "normal item": iname.red_jewel_l}, + lname.tunnel_shovel_quag_end: {"code": 0x18B, "offset": 0x10CA0F, "normal item": iname.red_jewel_l}, + lname.tunnel_exit_quag_end: {"code": 0x184, "offset": 0x10CA3F, "normal item": iname.five_hundred_gold}, + lname.tunnel_shovel: {"code": 0x18F, "offset": 0x86D8FC, "normal item": iname.roast_beef, + "type": "inv"}, + lname.tunnel_shovel_save: {"code": 0x18A, "offset": 0x10CA17, "normal item": iname.red_jewel_l}, + lname.tunnel_shovel_mdoor_l: {"code": 0x183, "offset": 0x10CA47, "normal item": iname.sun_card, + "hard item": iname.one_hundred_gold}, + lname.tunnel_shovel_mdoor_r: {"code": 0x51, "offset": 0x10CA77, "normal item": iname.axe, + "add conds": ["sub"]}, + lname.tunnel_shovel_sdoor_l: {"code": 0x182, "offset": 0x10CA4F, "normal item": iname.moon_card}, + lname.tunnel_shovel_sdoor_m: {"code": 0x19D, "offset": 0x10CAAF, "normal item": iname.roast_chicken}, + lname.tunnel_shovel_sdoor_r: {"code": 0x50, "offset": 0x10CA7F, "normal item": iname.cross, + "add conds": ["sub"]}, + # Underground Waterway + lname.uw_near_ent: {"code": 0x4C, "offset": 0x10CB03, "normal item": iname.three_hundred_gold}, + lname.uw_across_ent: {"code": 0x4E, "offset": 0x10CAF3, "normal item": iname.five_hundred_gold}, + lname.uw_first_ledge1: {"code": 0x242, "offset": 0x10CB39, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.uw_first_ledge2: {"code": 0x243, "offset": 0x10CB3B, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.uw_first_ledge3: {"code": 0x244, "offset": 0x10CB3D, "normal item": iname.purifying, + "hard item": iname.five_hundred_gold, "add conds": ["3hb"]}, + lname.uw_first_ledge4: {"code": 0x245, "offset": 0x10CB3F, "normal item": iname.cure_ampoule, + "hard item": iname.five_hundred_gold, "add conds": ["3hb"]}, + lname.uw_first_ledge5: {"code": 0x246, "offset": 0x10CB41, "normal item": iname.purifying, + "hard item": iname.three_hundred_gold, "add conds": ["3hb"]}, + lname.uw_first_ledge6: {"code": 0x247, "offset": 0x10CB43, "normal item": iname.cure_ampoule, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.uw_poison_parkour: {"code": 0x4D, "offset": 0x10CAFB, "normal item": iname.cure_ampoule}, + lname.uw_boss: {"event": iname.trophy, "add conds": ["boss"]}, + lname.uw_waterfall_alcove: {"code": 0x57, "offset": 0x10CB23, "normal item": iname.five_hundred_gold}, + lname.uw_carrie1: {"code": 0x4B, "offset": 0x10CB0B, "normal item": iname.moon_card, + "hard item": iname.five_hundred_gold, "add conds": ["carrie"]}, + lname.uw_carrie2: {"code": 0x4A, "offset": 0x10CB13, "normal item": iname.roast_beef, + "hard item": iname.five_hundred_gold, "add conds": ["carrie"]}, + lname.uw_bricks_save: {"code": 0x5A, "offset": 0x10CB33, "normal item": iname.powerup, + "hard item": iname.one_hundred_gold}, + lname.uw_above_skel_ledge: {"code": 0x56, "offset": 0x10CB2B, "normal item": iname.roast_chicken}, + lname.uw_in_skel_ledge1: {"code": 0x249, "offset": 0x10CB45, "normal item": iname.roast_chicken, + "add conds": ["3hb"]}, + lname.uw_in_skel_ledge2: {"code": 0x24A, "offset": 0x10CB47, "normal item": iname.roast_chicken, + "add conds": ["3hb"]}, + lname.uw_in_skel_ledge3: {"code": 0x24B, "offset": 0x10CB49, "normal item": iname.roast_chicken, + "add conds": ["3hb"]}, + # Castle Center + lname.ccb_skel_hallway_ent: {"code": 0x1AF, "offset": 0x10CB67, "normal item": iname.red_jewel_s}, + lname.ccb_skel_hallway_jun: {"code": 0x1A8, "offset": 0x10CBD7, "normal item": iname.powerup}, + lname.ccb_skel_hallway_tc: {"code": 0x1AE, "offset": 0x10CB6F, "normal item": iname.red_jewel_l}, + lname.ccb_skel_hallway_ba: {"code": 0x1B6, "offset": 0x10CBC7, "normal item": iname.cross, + "add conds": ["sub"]}, + lname.ccb_behemoth_l_ff: {"code": 0x1AD, "offset": 0x10CB77, "normal item": iname.red_jewel_s}, + lname.ccb_behemoth_l_mf: {"code": 0x1B3, "offset": 0x10CBA7, "normal item": iname.three_hundred_gold, + "hard item": iname.one_hundred_gold}, + lname.ccb_behemoth_l_mr: {"code": 0x1AC, "offset": 0x10CB7F, "normal item": iname.red_jewel_l}, + lname.ccb_behemoth_l_fr: {"code": 0x1B2, "offset": 0x10CBAF, "normal item": iname.three_hundred_gold, + "hard item": iname.one_hundred_gold}, + lname.ccb_behemoth_r_ff: {"code": 0x1B1, "offset": 0x10CBB7, "normal item": iname.three_hundred_gold, + "hard item": iname.one_hundred_gold}, + lname.ccb_behemoth_r_mf: {"code": 0x1AB, "offset": 0x10CB87, "normal item": iname.red_jewel_s}, + lname.ccb_behemoth_r_mr: {"code": 0x1B0, "offset": 0x10CBBF, "normal item": iname.three_hundred_gold, + "hard item": iname.one_hundred_gold}, + lname.ccb_behemoth_r_fr: {"code": 0x1AA, "offset": 0x10CB8F, "normal item": iname.red_jewel_l}, + lname.ccb_behemoth_crate1: {"code": 0x24D, "offset": 0x10CBDD, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ccb_behemoth_crate2: {"code": 0x24E, "offset": 0x10CBDF, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ccb_behemoth_crate3: {"code": 0x24F, "offset": 0x10CBE1, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ccb_behemoth_crate4: {"code": 0x250, "offset": 0x10CBE3, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ccb_behemoth_crate5: {"code": 0x251, "offset": 0x10CBE5, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ccelv_near_machine: {"code": 0x11A, "offset": 0x10CBF7, "normal item": iname.red_jewel_s}, + lname.ccelv_atop_machine: {"code": 0x118, "offset": 0x10CC17, "normal item": iname.powerup, + "hard item": iname.three_hundred_gold}, + lname.ccelv_stand1: {"code": 0x253, "offset": 0x10CC1D, "normal item": iname.roast_beef, + "add conds": ["3hb"]}, + lname.ccelv_stand2: {"code": 0x254, "offset": 0x10CC1F, "normal item": iname.roast_beef, + "hard item": iname.three_hundred_gold, "add conds": ["3hb"]}, + lname.ccelv_stand3: {"code": 0x255, "offset": 0x10CC21, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.ccelv_pipes: {"code": 0x11B, "offset": 0x10CC07, "normal item": iname.one_hundred_gold}, + lname.ccelv_switch: {"code": 0x100, "offset": 0x10CC0F, "normal item": iname.holy_water, + "add conds": ["sub"]}, + lname.ccelv_staircase: {"code": 0x119, "offset": 0x10CBFF, "normal item": iname.red_jewel_l, + "hard item": iname.five_hundred_gold}, + lname.ccff_redcarpet_knight: {"code": 0x10A, "offset": 0x8C44D9, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s, "type": "inv"}, + lname.ccff_gears_side: {"code": 0x10F, "offset": 0x10CC33, "normal item": iname.red_jewel_s}, + lname.ccff_gears_mid: {"code": 0x10E, "offset": 0x10CC3B, "normal item": iname.purifying, + "hard item": iname.one_hundred_gold}, + lname.ccff_gears_corner: {"code": 0x10D, "offset": 0x10CC43, "normal item": iname.roast_chicken, + "hard item": iname.one_hundred_gold}, + lname.ccff_lizard_knight: {"code": 0x109, "offset": 0x8C44E7, "normal item": iname.roast_chicken, + "hard item": iname.three_hundred_gold, "type": "inv"}, + lname.ccff_lizard_near_knight: {"code": 0x101, "offset": 0x10CC5B, "normal item": iname.axe, + "add conds": ["sub"]}, + lname.ccff_lizard_pit: {"code": 0x10C, "offset": 0x10CC4B, "normal item": iname.sun_card, + "hard item": iname.five_hundred_gold}, + lname.ccff_lizard_corner: {"code": 0x10B, "offset": 0x10CC53, "normal item": iname.moon_card, + "hard item": iname.five_hundred_gold}, + lname.ccff_lizard_locker_nfr: {"code": 0x104, "offset": 0x8C450A, "normal item": iname.red_jewel_l, + "add conds": ["liz"]}, + lname.ccff_lizard_locker_nmr: {"code": 0x105, "offset": 0xBFC9C3, "normal item": iname.five_hundred_gold, + "add conds": ["liz"]}, + lname.ccff_lizard_locker_nml: {"code": 0x106, "offset": 0xBFC9C7, "normal item": iname.red_jewel_l, + "hard item": iname.cure_ampoule, "add conds": ["liz"]}, + lname.ccff_lizard_locker_nfl: {"code": 0x107, "offset": 0xBFCA07, "normal item": iname.powerup, + "add conds": ["liz"]}, + lname.ccff_lizard_locker_fl: {"code": 0x102, "offset": 0xBFCA03, "normal item": iname.five_hundred_gold, + "add conds": ["liz"]}, + lname.ccff_lizard_locker_fr: {"code": 0x103, "offset": 0x8C44F5, "normal item": iname.sun_card, + "hard item": iname.three_hundred_gold, "add conds": ["liz"]}, + lname.ccff_lizard_slab1: {"code": 0x257, "offset": 0x10CC61, "normal item": iname.purifying, + "hard item": iname.roast_chicken, "add conds": ["3hb"]}, + lname.ccff_lizard_slab2: {"code": 0x258, "offset": 0x10CC63, "normal item": iname.purifying, + "hard item": iname.powerup, "add conds": ["3hb"]}, + lname.ccff_lizard_slab3: {"code": 0x259, "offset": 0x10CC65, "normal item": iname.cure_ampoule, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.ccff_lizard_slab4: {"code": 0x25A, "offset": 0x10CC67, "normal item": iname.cure_ampoule, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.ccb_mandrag_shelf_l: {"code": 0x1A0, "offset": 0xBFCBB3, "normal item": iname.mandragora}, + lname.ccb_mandrag_shelf_r: {"code": 0x1A1, "offset": 0xBFCBAF, "normal item": iname.mandragora}, + lname.ccb_torture_rack: {"code": 0x1A9, "offset": 0x8985E5, "normal item": iname.purifying, + "type": "inv"}, + lname.ccb_torture_rafters: {"code": 0x1A2, "offset": 0x8985D6, "normal item": iname.roast_beef}, + lname.cc_behind_the_seal: {"event": iname.crystal, "add conds": ["crystal"]}, + lname.cc_boss_one: {"event": iname.trophy, "add conds": ["boss"]}, + lname.cc_boss_two: {"event": iname.trophy, "add conds": ["boss"]}, + lname.ccll_brokenstairs_floor: {"code": 0x7B, "offset": 0x10CC8F, "normal item": iname.red_jewel_l, + "countdown": 14}, + lname.ccll_brokenstairs_knight: {"code": 0x74, "offset": 0x8DF782, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold, "type": "inv", "countdown": 14}, + lname.ccll_brokenstairs_save: {"code": 0x7C, "offset": 0x10CC87, "normal item": iname.red_jewel_l, + "countdown": 14}, + lname.ccll_glassknight_l: {"code": 0x7A, "offset": 0x10CC97, "normal item": iname.red_jewel_s, + "hard item": iname.five_hundred_gold, "countdown": 14}, + lname.ccll_glassknight_r: {"code": 0x7E, "offset": 0x10CC77, "normal item": iname.red_jewel_s, + "hard item": iname.five_hundred_gold, "countdown": 14}, + lname.ccll_butlers_door: {"code": 0x7D, "offset": 0x10CC7F, "normal item": iname.red_jewel_s, + "countdown": 14}, + lname.ccll_butlers_side: {"code": 0x79, "offset": 0x10CC9F, "normal item": iname.purifying, + "hard item": iname.one_hundred_gold, "countdown": 14}, + lname.ccll_cwhall_butlerflames_past: {"code": 0x78, "offset": 0x10CCA7, "normal item": iname.cure_ampoule, + "hard item": iname.red_jewel_l, "countdown": 14}, + lname.ccll_cwhall_flamethrower: {"code": 0x73, "offset": 0x8DF580, "normal item": iname.five_hundred_gold, + "type": "inv", "countdown": 14}, + lname.ccll_cwhall_cwflames: {"code": 0x77, "offset": 0x10CCAF, "normal item": iname.roast_chicken, + "hard item": iname.red_jewel_l, "countdown": 14}, + lname.ccll_heinrich: {"code": 0x69, "offset": 0xBFE443, "normal item": iname.chamber_key, + "type": "npc", "countdown": 14}, + lname.ccia_nitro_crates: {"code": 0x66, "offset": 0x90FCE9, "normal item": iname.healing_kit, + "hard item": iname.one_hundred_gold, "type": "inv", "countdown": 14}, + lname.ccia_nitro_shelf_h: {"code": 0x55, "offset": 0xBFCC03, "normal item": iname.magical_nitro, + "countdown": 14}, + lname.ccia_stairs_knight: {"code": 0x61, "offset": 0x90FE5C, "normal item": iname.five_hundred_gold, + "type": "inv", "countdown": 14}, + lname.ccia_maids_vase: {"code": 0x63, "offset": 0x90FF1D, "normal item": iname.red_jewel_l, + "type": "inv", "countdown": 14}, + lname.ccia_maids_outer: {"code": 0x6B, "offset": 0x10CCFF, "normal item": iname.purifying, + "hard item": iname.three_hundred_gold, "countdown": 14}, + lname.ccia_maids_inner: {"code": 0x6A, "offset": 0x10CD07, "normal item": iname.cure_ampoule, + "hard item": iname.three_hundred_gold, "countdown": 14}, + lname.ccia_inventions_maids: {"code": 0x6C, "offset": 0x10CCE7, "normal item": iname.moon_card, + "hard item": iname.one_hundred_gold, "countdown": 14}, + lname.ccia_inventions_crusher: {"code": 0x6E, "offset": 0x10CCDF, "normal item": iname.sun_card, + "hard item": iname.one_hundred_gold, "countdown": 14}, + lname.ccia_inventions_famicart: {"code": 0x64, "offset": 0x90FBB3, "normal item": iname.five_hundred_gold, + "type": "inv", "countdown": 14}, + lname.ccia_inventions_zeppelin: {"code": 0x6D, "offset": 0x90FBC0, "normal item": iname.roast_beef, + "countdown": 14}, + lname.ccia_inventions_round: {"code": 0x65, "offset": 0x90FBA7, "normal item": iname.roast_beef, + "hard item": iname.five_hundred_gold, "type": "inv", "countdown": 14}, + lname.ccia_nitrohall_flamethrower: {"code": 0x62, "offset": 0x90FCDA, "normal item": iname.red_jewel_l, + "type": "inv", "countdown": 14}, + lname.ccia_nitrohall_torch: {"code": 0x6F, "offset": 0x10CCD7, "normal item": iname.roast_chicken, + "hard item": iname.red_jewel_s, "countdown": 14}, + lname.ccia_nitro_shelf_i: {"code": 0x60, "offset": 0xBFCBFF, "normal item": iname.magical_nitro, + "countdown": 14}, + lname.ccll_cwhall_wall: {"code": 0x76, "offset": 0x10CCB7, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold, "countdown": 14}, + lname.ccl_bookcase: {"code": 0x166, "offset": 0x8F1197, "normal item": iname.sun_card, + "countdown": 14}, + # Duel Tower + lname.dt_boss_one: {"event": iname.trophy, "add conds": ["boss"]}, + lname.dt_boss_two: {"event": iname.trophy, "add conds": ["boss"]}, + lname.dt_ibridge_l: {"code": 0x81, "offset": 0x10CE8B, "normal item": iname.roast_beef, + "hard item": iname.five_hundred_gold}, + lname.dt_ibridge_r: {"code": 0x80, "offset": 0x10CE93, "normal item": iname.powerup}, + lname.dt_stones_start: {"code": 0x83, "offset": 0x10CE73, "normal item": iname.roast_chicken, + "hard item": iname.five_hundred_gold}, + lname.dt_stones_end: {"code": 0x97, "offset": 0x10CE83, "normal item": iname.knife, "add conds": ["sub"]}, + lname.dt_werebull_arena: {"code": 0x82, "offset": 0x10CE7B, "normal item": iname.roast_beef}, + lname.dt_boss_three: {"event": iname.trophy, "add conds": ["boss"]}, + lname.dt_boss_four: {"event": iname.trophy, "add conds": ["boss"]}, + # Tower of Execution + lname.toe_ledge1: {"code": 0x25C, "offset": 0x10CD5D, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + lname.toe_ledge2: {"code": 0x25D, "offset": 0x10CD5F, "normal item": iname.purifying, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.toe_ledge3: {"code": 0x25E, "offset": 0x10CD61, "normal item": iname.five_hundred_gold, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.toe_ledge4: {"code": 0x25F, "offset": 0x10CD63, "normal item": iname.cure_ampoule, + "hard item": iname.five_hundred_gold, "add conds": ["3hb"]}, + lname.toe_ledge5: {"code": 0x260, "offset": 0x10CD65, "normal item": iname.holy_water, + "add conds": ["3hb", "sub"]}, + lname.toe_midsavespikes_r: {"code": 0x9C, "offset": 0x10CD1F, "normal item": iname.five_hundred_gold}, + lname.toe_midsavespikes_l: {"code": 0x9B, "offset": 0x10CD27, "normal item": iname.roast_chicken, + "hard item": iname.five_hundred_gold}, + lname.toe_elec_grate: {"code": 0x99, "offset": 0x10CD17, "normal item": iname.execution_key}, + lname.toe_ibridge: {"code": 0x98, "offset": 0x10CD47, "normal item": iname.one_hundred_gold}, + lname.toe_top: {"code": 0x9D, "offset": 0x10CD4F, "normal item": iname.red_jewel_l}, + lname.toe_keygate_l: {"code": 0x9A, "offset": 0x10CD37, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold}, + lname.toe_keygate_r: {"code": 0x9E, "offset": 0x10CD3F, "normal item": iname.cross, "add conds": ["sub"]}, + # Tower of Science + lname.tosci_elevator: {"code": 0x1FC, "offset": 0x10CE0B, "normal item": iname.three_hundred_gold}, + lname.tosci_plain_sr: {"code": 0x1FF, "offset": 0x10CDF3, "normal item": iname.science_key1}, + lname.tosci_stairs_sr: {"code": 0x1FB, "offset": 0x10CE13, "normal item": iname.three_hundred_gold}, + lname.tosci_three_door_hall: {"code": 0x1FE, "offset": 0x10CDFB, "normal item": iname.science_key2}, + lname.tosci_ibridge_t: {"code": 0x1F3, "offset": 0x10CE3B, "normal item": iname.roast_beef, + "hard item": iname.red_jewel_l}, + lname.tosci_ibridge_b1: {"code": 0x262, "offset": 0x10CE59, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + lname.tosci_ibridge_b2: {"code": 0x263, "offset": 0x10CE5B, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + lname.tosci_ibridge_b3: {"code": 0x264, "offset": 0x10CE5D, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.tosci_ibridge_b4: {"code": 0x265, "offset": 0x10CE5F, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.tosci_ibridge_b5: {"code": 0x266, "offset": 0x10CE61, "normal item": iname.roast_chicken, + "add conds": ["3hb"]}, + lname.tosci_ibridge_b6: {"code": 0x267, "offset": 0x10CE63, "normal item": iname.roast_chicken, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.tosci_conveyor_sr: {"code": 0x1F7, "offset": 0x10CE33, "normal item": iname.red_jewel_l, + "hard item": iname.red_jewel_s}, + lname.tosci_exit: {"code": 0x1FD, "offset": 0x10CE03, "normal item": iname.science_key3}, + lname.tosci_key3_r: {"code": 0x1FA, "offset": 0x10CE1B, "normal item": iname.five_hundred_gold}, + lname.tosci_key3_m: {"code": 0x1F2, "offset": 0x10CE2B, "normal item": iname.cross, "add conds": ["sub"]}, + lname.tosci_key3_l: {"code": 0x1F9, "offset": 0x10CE23, "normal item": iname.five_hundred_gold}, + # Tower of Sorcery + lname.tosor_stained_tower: {"code": 0x96, "offset": 0x10CDB3, "normal item": iname.red_jewel_l}, + lname.tosor_savepoint: {"code": 0x95, "offset": 0x10CDBB, "normal item": iname.red_jewel_l}, + lname.tosor_trickshot: {"code": 0x92, "offset": 0x10CDD3, "normal item": iname.roast_beef}, + lname.tosor_yellow_bubble: {"code": 0x91, "offset": 0x10CDDB, "normal item": iname.five_hundred_gold}, + lname.tosor_blue_platforms: {"code": 0x94, "offset": 0x10CDC3, "normal item": iname.red_jewel_s}, + lname.tosor_side_isle: {"code": 0x93, "offset": 0x10CDCB, "normal item": iname.red_jewel_s}, + lname.tosor_ibridge: {"code": 0x90, "offset": 0x10CDE3, "normal item": iname.three_hundred_gold}, + # Room of Clocks + lname.roc_ent_l: {"code": 0xC6, "offset": 0x10CF7B, "normal item": iname.roast_beef, + "hard item": iname.red_jewel_l}, + lname.roc_ent_r: {"code": 0xC3, "offset": 0x10CFBB, "normal item": iname.powerup, + "hard item": iname.five_hundred_gold}, + lname.roc_elev_r: {"code": 0xD4, "offset": 0x10CF93, "normal item": iname.holy_water, "add conds": ["sub"]}, + lname.roc_elev_l: {"code": 0xD5, "offset": 0x10CF8B, "normal item": iname.axe, "add conds": ["sub"]}, + lname.roc_cont_r: {"code": 0xC5, "offset": 0x10CFB3, "normal item": iname.powerup, + "hard item": iname.one_hundred_gold}, + lname.roc_cont_l: {"code": 0xDF, "offset": 0x10CFA3, "normal item": iname.three_hundred_gold, + "add conds": ["empty"]}, + lname.roc_exit: {"code": 0xDC, "offset": 0x10CF9B, "normal item": iname.three_hundred_gold, + "add conds": ["empty"]}, + lname.roc_boss: {"event": iname.trophy, "add conds": ["boss"]}, + # Clock Tower + lname.ct_gearclimb_battery_slab1: {"code": 0x269, "offset": 0x10CEF9, "normal item": iname.roast_chicken, + "add conds": ["3hb"]}, + lname.ct_gearclimb_battery_slab2: {"code": 0x26A, "offset": 0x10CEFB, "normal item": iname.roast_chicken, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.ct_gearclimb_battery_slab3: {"code": 0x26B, "offset": 0x10CEFD, "normal item": iname.roast_chicken, + "hard item": iname.red_jewel_s, "add conds": ["3hb"]}, + lname.ct_gearclimb_corner: {"code": 0xA7, "offset": 0x10CEB3, "normal item": iname.red_jewel_s}, + lname.ct_gearclimb_side: {"code": 0xAD, "offset": 0x10CEC3, "normal item": iname.clocktower_key1}, + lname.ct_gearclimb_door_slab1: {"code": 0x26D, "offset": 0x10CF01, "normal item": iname.roast_beef, + "add conds": ["3hb"]}, + lname.ct_gearclimb_door_slab2: {"code": 0x26E, "offset": 0x10CF03, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.ct_gearclimb_door_slab3: {"code": 0x26F, "offset": 0x10CF05, "normal item": iname.roast_beef, + "hard item": iname.one_hundred_gold, "add conds": ["3hb"]}, + lname.ct_bp_chasm_fl: {"code": 0xA5, "offset": 0x99BC4D, "normal item": iname.five_hundred_gold}, + lname.ct_bp_chasm_fr: {"code": 0xA6, "offset": 0x99BC3E, "normal item": iname.red_jewel_l}, + lname.ct_bp_chasm_rl: {"code": 0xA4, "offset": 0x99BC5A, "normal item": iname.holy_water, + "add conds": ["sub"]}, + lname.ct_bp_chasm_k: {"code": 0xAC, "offset": 0x99BC30, "normal item": iname.clocktower_key2}, + lname.ct_finalroom_door_slab1: {"code": 0x271, "offset": 0x10CEF5, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_door_slab2: {"code": 0x272, "offset": 0x10CEF7, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_fl: {"code": 0xB3, "offset": 0x10CED3, "normal item": iname.axe, + "add conds": ["sub"]}, + lname.ct_finalroom_fr: {"code": 0xB4, "offset": 0x10CECB, "normal item": iname.knife, + "add conds": ["sub"]}, + lname.ct_finalroom_rl: {"code": 0xB2, "offset": 0x10CEE3, "normal item": iname.holy_water, + "add conds": ["sub"]}, + lname.ct_finalroom_rr: {"code": 0xB0, "offset": 0x10CEDB, "normal item": iname.cross, + "add conds": ["sub"]}, + lname.ct_finalroom_platform: {"code": 0xAB, "offset": 0x10CEBB, "normal item": iname.clocktower_key3}, + lname.ct_finalroom_renon_slab1: {"code": 0x274, "offset": 0x10CF09, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_renon_slab2: {"code": 0x275, "offset": 0x10CF0B, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_renon_slab3: {"code": 0x276, "offset": 0x10CF0D, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_renon_slab4: {"code": 0x277, "offset": 0x10CF0F, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_renon_slab5: {"code": 0x278, "offset": 0x10CF11, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_renon_slab6: {"code": 0x279, "offset": 0x10CF13, "normal item": iname.five_hundred_gold, + "add conds": ["3hb"]}, + lname.ct_finalroom_renon_slab7: {"code": 0x27A, "offset": 0x10CF15, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + lname.ct_finalroom_renon_slab8: {"code": 0x27B, "offset": 0x10CF17, "normal item": iname.red_jewel_l, + "add conds": ["3hb"]}, + # Castle Keep + lname.ck_boss_one: {"event": iname.trophy, "add conds": ["boss", "renon"]}, + lname.ck_boss_two: {"event": iname.trophy, "add conds": ["boss", "vincent"]}, + lname.ck_flame_l: {"code": 0xAF, "offset": 0x9778C8, "normal item": iname.healing_kit, "type": "inv"}, + lname.ck_flame_r: {"code": 0xAE, "offset": 0xBFCA67, "normal item": iname.healing_kit, "type": "inv"}, + lname.ck_behind_drac: {"code": 0xBF, "offset": 0x10CE9B, "normal item": iname.red_jewel_l}, + lname.ck_cube: {"code": 0xB5, "offset": 0x10CEA3, "normal item": iname.healing_kit}, + lname.renon1: {"code": 0x1C8, "offset": 0xBFD8E5, "normal item": iname.roast_chicken, "type": "shop"}, + lname.renon2: {"code": 0x1C9, "offset": 0xBFD8E7, "normal item": iname.roast_beef, "type": "shop"}, + lname.renon3: {"code": 0x1CA, "offset": 0xBFD8E9, "normal item": iname.healing_kit, "type": "shop"}, + lname.renon4: {"code": 0x1CB, "offset": 0xBFD8EB, "normal item": iname.purifying, "type": "shop"}, + lname.renon5: {"code": 0x1CC, "offset": 0xBFD8ED, "normal item": iname.cure_ampoule, "type": "shop"}, + lname.renon6: {"code": 0x1CD, "offset": 0xBFD907, "normal item": iname.sun_card, "type": "shop"}, + lname.renon7: {"code": 0x1CE, "offset": 0xBFD909, "normal item": iname.moon_card, "type": "shop"}, + lname.the_end: {"event": iname.victory}, +} + + +add_conds = {"carrie": ("carrie_logic", True, True), + "liz": ("lizard_locker_items", True, True), + "sub": ("sub_weapon_shuffle", SubWeaponShuffle.option_anywhere, True), + "3hb": ("multi_hit_breakables", True, True), + "empty": ("empty_breakables", True, True), + "shop": ("shopsanity", True, True), + "crystal": ("draculas_condition", DraculasCondition.option_crystal, True), + "boss": ("draculas_condition", DraculasCondition.option_bosses, True), + "renon": ("renon_fight_condition", RenonFightCondition.option_never, False), + "vincent": ("vincent_fight_condition", VincentFightCondition.option_never, False)} + + +def get_location_info(location: str, info: str) -> Union[int, str, List[str], None]: + return location_info[location].get(info, None) + + +def get_location_names_to_ids() -> Dict[str, int]: + return {name: get_location_info(name, "code")+base_id for name in location_info if get_location_info(name, "code") + is not None} + + +def verify_locations(options: CV64Options, locations: List[str]) -> Tuple[Dict[str, Optional[int]], Dict[str, str]]: + + verified_locations = {} + events = {} + + for loc in locations: + loc_add_conds = get_location_info(loc, "add conds") + loc_code = get_location_info(loc, "code") + + # Check any options that might be associated with the Location before adding it. + add_it = True + if isinstance(loc_add_conds, list): + for cond in loc_add_conds: + if not ((getattr(options, add_conds[cond][0]).value == add_conds[cond][1]) == add_conds[cond][2]): + add_it = False + + if not add_it: + continue + + # Add the location to the verified Locations if the above check passes. + # If we are looking at an event Location, add its associated event Item to the events' dict. + # Otherwise, add the base_id to the Location's code. + if loc_code is None: + events[loc] = get_location_info(loc, "event") + else: + loc_code += base_id + verified_locations.update({loc: loc_code}) + + return verified_locations, events diff --git a/worlds/cv64/lzkn64.py b/worlds/cv64/lzkn64.py new file mode 100644 index 0000000000..9a94cebbb4 --- /dev/null +++ b/worlds/cv64/lzkn64.py @@ -0,0 +1,266 @@ +# ************************************************************** +# * LZKN64 Compression and Decompression Utility * +# * Original repo at https://github.com/Fluvian/lzkn64, * +# * converted from C to Python with permission from Fluvian. * +# ************************************************************** + +TYPE_COMPRESS = 1 +TYPE_DECOMPRESS = 2 + +MODE_NONE = 0x7F +MODE_WINDOW_COPY = 0x00 +MODE_RAW_COPY = 0x80 +MODE_RLE_WRITE_A = 0xC0 +MODE_RLE_WRITE_B = 0xE0 +MODE_RLE_WRITE_C = 0xFF + +WINDOW_SIZE = 0x3FF +COPY_SIZE = 0x21 +RLE_SIZE = 0x101 + + +# Compresses the data in the buffer specified in the arguments. +def compress_buffer(file_buffer: bytearray) -> bytearray: + # Size of the buffer to compress + buffer_size = len(file_buffer) - 1 + + # Position of the current read location in the buffer. + buffer_position = 0 + + # Position of the current write location in the written buffer. + write_position = 4 + + # Allocate write_buffer with size of 0xFFFFFF (24-bit). + write_buffer = bytearray(0xFFFFFF) + + # Position in the input buffer of the last time one of the copy modes was used. + buffer_last_copy_position = 0 + + while buffer_position < buffer_size: + # Calculate maximum length we are able to copy without going out of bounds. + if COPY_SIZE < (buffer_size - 1) - buffer_position: + sliding_window_maximum_length = COPY_SIZE + else: + sliding_window_maximum_length = (buffer_size - 1) - buffer_position + + # Calculate how far we are able to look back without going behind the start of the uncompressed buffer. + if buffer_position - WINDOW_SIZE > 0: + sliding_window_maximum_offset = buffer_position - WINDOW_SIZE + else: + sliding_window_maximum_offset = 0 + + # Calculate maximum length the forwarding looking window is able to search. + if RLE_SIZE < (buffer_size - 1) - buffer_position: + forward_window_maximum_length = RLE_SIZE + else: + forward_window_maximum_length = (buffer_size - 1) - buffer_position + + sliding_window_match_position = -1 + sliding_window_match_size = 0 + + forward_window_match_value = 0 + forward_window_match_size = 0 + + # The current mode the compression algorithm prefers. (0x7F == None) + current_mode = MODE_NONE + + # The current submode the compression algorithm prefers. + current_submode = MODE_NONE + + # How many bytes will have to be copied in the raw copy command. + raw_copy_size = buffer_position - buffer_last_copy_position + + # How many bytes we still have to copy in RLE matches with more than 0x21 bytes. + rle_bytes_left = 0 + + """Go backwards in the buffer, is there a matching value? + If yes, search forward and check for more matching values in a loop. + If no, go further back and repeat.""" + for search_position in range(buffer_position - 1, sliding_window_maximum_offset - 1, -1): + matching_sequence_size = 0 + + while file_buffer[search_position + matching_sequence_size] == file_buffer[buffer_position + + matching_sequence_size]: + matching_sequence_size += 1 + + if matching_sequence_size >= sliding_window_maximum_length: + break + + # Once we find a match or a match that is bigger than the match before it, we save its position and length. + if matching_sequence_size > sliding_window_match_size: + sliding_window_match_position = search_position + sliding_window_match_size = matching_sequence_size + + """Look one step forward in the buffer, is there a matching value? + If yes, search further and check for a repeating value in a loop. + If no, continue to the rest of the function.""" + matching_sequence_value = file_buffer[buffer_position] + matching_sequence_size = 0 + + while file_buffer[buffer_position + matching_sequence_size] == matching_sequence_value: + matching_sequence_size += 1 + + if matching_sequence_size >= forward_window_maximum_length: + break + + # If we find a sequence of matching values, save them. + if matching_sequence_size >= 1: + forward_window_match_value = matching_sequence_value + forward_window_match_size = matching_sequence_size + + # Try to pick which mode works best with the current values. + if sliding_window_match_size >= 3: + current_mode = MODE_WINDOW_COPY + elif forward_window_match_size >= 3: + current_mode = MODE_RLE_WRITE_A + + if forward_window_match_value != 0x00 and forward_window_match_size <= COPY_SIZE: + current_submode = MODE_RLE_WRITE_A + elif forward_window_match_value != 0x00 and forward_window_match_size > COPY_SIZE: + current_submode = MODE_RLE_WRITE_A + rle_bytes_left = forward_window_match_size + elif forward_window_match_value == 0x00 and forward_window_match_size <= COPY_SIZE: + current_submode = MODE_RLE_WRITE_B + elif forward_window_match_value == 0x00 and forward_window_match_size > COPY_SIZE: + current_submode = MODE_RLE_WRITE_C + elif forward_window_match_size >= 2 and forward_window_match_value == 0x00: + current_mode = MODE_RLE_WRITE_A + current_submode = MODE_RLE_WRITE_B + + """Write a raw copy command when these following conditions are met: + The current mode is set and there are raw bytes available to be copied. + The raw byte length exceeds the maximum length that can be stored. + Raw bytes need to be written due to the proximity to the end of the buffer.""" + if (current_mode != MODE_NONE and raw_copy_size >= 1) or raw_copy_size >= 0x1F or \ + (buffer_position + 1) == buffer_size: + if buffer_position + 1 == buffer_size: + raw_copy_size = buffer_size - buffer_last_copy_position + + write_buffer[write_position] = MODE_RAW_COPY | raw_copy_size & 0x1F + write_position += 1 + + for written_bytes in range(raw_copy_size): + write_buffer[write_position] = file_buffer[buffer_last_copy_position] + write_position += 1 + buffer_last_copy_position += 1 + + if current_mode == MODE_WINDOW_COPY: + write_buffer[write_position] = MODE_WINDOW_COPY | ((sliding_window_match_size - 2) & 0x1F) << 2 | \ + (((buffer_position - sliding_window_match_position) & 0x300) >> 8) + write_position += 1 + write_buffer[write_position] = (buffer_position - sliding_window_match_position) & 0xFF + write_position += 1 + + buffer_position += sliding_window_match_size + buffer_last_copy_position = buffer_position + elif current_mode == MODE_RLE_WRITE_A: + if current_submode == MODE_RLE_WRITE_A: + if rle_bytes_left > 0: + while rle_bytes_left > 0: + # Dump raw bytes if we have less than two bytes left, not doing so would cause an underflow + # error. + if rle_bytes_left < 2: + write_buffer[write_position] = MODE_RAW_COPY | rle_bytes_left & 0x1F + write_position += 1 + + for writtenBytes in range(rle_bytes_left): + write_buffer[write_position] = forward_window_match_value & 0xFF + write_position += 1 + + rle_bytes_left = 0 + break + + if rle_bytes_left < COPY_SIZE: + write_buffer[write_position] = MODE_RLE_WRITE_A | (rle_bytes_left - 2) & 0x1F + write_position += 1 + else: + write_buffer[write_position] = MODE_RLE_WRITE_A | (COPY_SIZE - 2) & 0x1F + write_position += 1 + write_buffer[write_position] = forward_window_match_value & 0xFF + write_position += 1 + rle_bytes_left -= COPY_SIZE + else: + write_buffer[write_position] = MODE_RLE_WRITE_A | (forward_window_match_size - 2) & 0x1F + write_position += 1 + write_buffer[write_position] = forward_window_match_value & 0xFF + write_position += 1 + + elif current_submode == MODE_RLE_WRITE_B: + write_buffer[write_position] = MODE_RLE_WRITE_B | (forward_window_match_size - 2) & 0x1F + write_position += 1 + elif current_submode == MODE_RLE_WRITE_C: + write_buffer[write_position] = MODE_RLE_WRITE_C + write_position += 1 + write_buffer[write_position] = (forward_window_match_size - 2) & 0xFF + write_position += 1 + + buffer_position += forward_window_match_size + buffer_last_copy_position = buffer_position + else: + buffer_position += 1 + + # Write the compressed size. + write_buffer[1] = 0x00 + write_buffer[1] = write_position >> 16 & 0xFF + write_buffer[2] = write_position >> 8 & 0xFF + write_buffer[3] = write_position & 0xFF + + # Return the compressed write buffer. + return write_buffer[0:write_position] + + +# Decompresses the data in the buffer specified in the arguments. +def decompress_buffer(file_buffer: bytearray) -> bytearray: + # Position of the current read location in the buffer. + buffer_position = 4 + + # Position of the current write location in the written buffer. + write_position = 0 + + # Get compressed size. + compressed_size = (file_buffer[1] << 16) + (file_buffer[2] << 8) + file_buffer[3] - 1 + + # Allocate writeBuffer with size of 0xFFFFFF (24-bit). + write_buffer = bytearray(0xFFFFFF) + + while buffer_position < compressed_size: + mode_command = file_buffer[buffer_position] + buffer_position += 1 + + if MODE_WINDOW_COPY <= mode_command < MODE_RAW_COPY: + copy_length = (mode_command >> 2) + 2 + copy_offset = file_buffer[buffer_position] + (mode_command << 8) & 0x3FF + buffer_position += 1 + + for current_length in range(copy_length, 0, -1): + write_buffer[write_position] = write_buffer[write_position - copy_offset] + write_position += 1 + elif MODE_RAW_COPY <= mode_command < MODE_RLE_WRITE_A: + copy_length = mode_command & 0x1F + + for current_length in range(copy_length, 0, -1): + write_buffer[write_position] = file_buffer[buffer_position] + write_position += 1 + buffer_position += 1 + elif MODE_RLE_WRITE_A <= mode_command <= MODE_RLE_WRITE_C: + write_length = 0 + write_value = 0x00 + + if MODE_RLE_WRITE_A <= mode_command < MODE_RLE_WRITE_B: + write_length = (mode_command & 0x1F) + 2 + write_value = file_buffer[buffer_position] + buffer_position += 1 + elif MODE_RLE_WRITE_B <= mode_command < MODE_RLE_WRITE_C: + write_length = (mode_command & 0x1F) + 2 + elif mode_command == MODE_RLE_WRITE_C: + write_length = file_buffer[buffer_position] + 2 + buffer_position += 1 + + for current_length in range(write_length, 0, -1): + write_buffer[write_position] = write_value + write_position += 1 + + # Return the current position of the write buffer, essentially giving us the size of the write buffer. + while write_position % 16 != 0: + write_position += 1 + return write_buffer[0:write_position] diff --git a/worlds/cv64/options.py b/worlds/cv64/options.py new file mode 100644 index 0000000000..4545cd0b5c --- /dev/null +++ b/worlds/cv64/options.py @@ -0,0 +1,490 @@ +from dataclasses import dataclass +from Options import Choice, DefaultOnToggle, Range, Toggle, PerGameCommonOptions, StartInventoryPool + + +class CharacterStages(Choice): + """Whether to include Reinhardt-only stages, Carrie-only stages, or both with or without branching paths at the end + of Villa and Castle Center.""" + display_name = "Character Stages" + option_both = 0 + option_branchless_both = 1 + option_reinhardt_only = 2 + option_carrie_only = 3 + default = 0 + + +class StageShuffle(Toggle): + """Shuffles which stages appear in which stage slots. Villa and Castle Center will never appear in any character + stage slots if Character Stages is set to Both; they can only be somewhere on the main path. + Castle Keep will always be at the end of the line.""" + display_name = "Stage Shuffle" + + +class StartingStage(Choice): + """Which stage to start at if Stage Shuffle is turned on.""" + display_name = "Starting Stage" + option_forest_of_silence = 0 + option_castle_wall = 1 + option_villa = 2 + option_tunnel = 3 + option_underground_waterway = 4 + option_castle_center = 5 + option_duel_tower = 6 + option_tower_of_execution = 7 + option_tower_of_science = 8 + option_tower_of_sorcery = 9 + option_room_of_clocks = 10 + option_clock_tower = 11 + default = "random" + + +class WarpOrder(Choice): + """Arranges the warps in the warp menu in whichever stage order chosen, + thereby changing the order they are unlocked in.""" + display_name = "Warp Order" + option_seed_stage_order = 0 + option_vanilla_stage_order = 1 + option_randomized_order = 2 + default = 0 + + +class SubWeaponShuffle(Choice): + """Shuffles all sub-weapons in the game within each other in their own pool or in the main item pool.""" + display_name = "Sub-weapon Shuffle" + option_off = 0 + option_own_pool = 1 + option_anywhere = 2 + default = 0 + + +class SpareKeys(Choice): + """Puts an additional copy of every non-Special key item in the pool for every key item that there is. + Chance gives each key item a 50% chance of having a duplicate instead of guaranteeing one for all of them.""" + display_name = "Spare Keys" + option_off = 0 + option_on = 1 + option_chance = 2 + default = 0 + + +class HardItemPool(Toggle): + """Replaces some items in the item pool with less valuable ones, to make the item pool sort of resemble Hard Mode + in the PAL version.""" + display_name = "Hard Item Pool" + + +class Special1sPerWarp(Range): + """Sets how many Special1 jewels are needed per warp menu option unlock.""" + range_start = 1 + range_end = 10 + default = 1 + display_name = "Special1s Per Warp" + + +class TotalSpecial1s(Range): + """Sets how many Speical1 jewels are in the pool in total. + If this is set to be less than Special1s Per Warp x 7, it will decrease by 1 until it isn't.""" + range_start = 7 + range_end = 70 + default = 7 + display_name = "Total Special1s" + + +class DraculasCondition(Choice): + """Sets the requirement for unlocking and opening the door to Dracula's chamber. + None: No requirement. Door is unlocked from the start. + Crystal: Activate the big crystal in Castle Center's basement. Neither boss afterwards has to be defeated. + Bosses: Kill a specified number of bosses with health bars and claim their Trophies. + Specials: Find a specified number of Special2 jewels shuffled in the main item pool.""" + display_name = "Dracula's Condition" + option_none = 0 + option_crystal = 1 + option_bosses = 2 + option_specials = 3 + default = 1 + + +class PercentSpecial2sRequired(Range): + """Percentage of Special2s required to enter Dracula's chamber when Dracula's Condition is Special2s.""" + range_start = 1 + range_end = 100 + default = 80 + display_name = "Percent Special2s Required" + + +class TotalSpecial2s(Range): + """How many Speical2 jewels are in the pool in total when Dracula's Condition is Special2s.""" + range_start = 1 + range_end = 70 + default = 25 + display_name = "Total Special2s" + + +class BossesRequired(Range): + """How many bosses need to be defeated to enter Dracula's chamber when Dracula's Condition is set to Bosses. + This will automatically adjust if there are fewer available bosses than the chosen number.""" + range_start = 1 + range_end = 16 + default = 14 + display_name = "Bosses Required" + + +class CarrieLogic(Toggle): + """Adds the 2 checks inside Underground Waterway's crawlspace to the pool. + If you (and everyone else if racing the same seed) are planning to only ever play Reinhardt, don't enable this. + Can be combined with Hard Logic to include Carrie-only tricks.""" + display_name = "Carrie Logic" + + +class HardLogic(Toggle): + """Properly considers sequence break tricks in logic (i.e. maze skip). Can be combined with Carrie Logic to include + Carrie-only tricks. + See the Game Page for a full list of tricks and glitches that may be logically required.""" + display_name = "Hard Logic" + + +class MultiHitBreakables(Toggle): + """Adds the items that drop from the objects that break in three hits to the pool. There are 17 of these throughout + the game, adding up to 74 checks in total with all stages. + The game will be modified to + remember exactly which of their items you've picked up instead of simply whether they were broken or not.""" + display_name = "Multi-hit Breakables" + + +class EmptyBreakables(Toggle): + """Adds 9 check locations in the form of breakables that normally have nothing (all empty Forest coffins, etc.) + and some additional Red Jewels and/or moneybags into the item pool to compensate.""" + display_name = "Empty Breakables" + + +class LizardLockerItems(Toggle): + """Adds the 6 items inside Castle Center 2F's Lizard-man generators to the pool. + Picking up all of these can be a very tedious luck-based process, so they are off by default.""" + display_name = "Lizard Locker Items" + + +class Shopsanity(Toggle): + """Adds 7 one-time purchases from Renon's shop into the location pool. After buying an item from a slot, it will + revert to whatever it is in the vanilla game.""" + display_name = "Shopsanity" + + +class ShopPrices(Choice): + """Randomizes the amount of gold each item costs in Renon's shop. + Use the below options to control how much or little an item can cost.""" + display_name = "Shop Prices" + option_vanilla = 0 + option_randomized = 1 + default = 0 + + +class MinimumGoldPrice(Range): + """The lowest amount of gold an item can cost in Renon's shop, divided by 100.""" + display_name = "Minimum Gold Price" + range_start = 1 + range_end = 50 + default = 2 + + +class MaximumGoldPrice(Range): + """The highest amount of gold an item can cost in Renon's shop, divided by 100.""" + display_name = "Maximum Gold Price" + range_start = 1 + range_end = 50 + default = 30 + + +class PostBehemothBoss(Choice): + """Sets which boss is fought in the vampire triplets' room in Castle Center by which characters after defeating + Behemoth.""" + display_name = "Post-Behemoth Boss" + option_vanilla = 0 + option_inverted = 1 + option_always_rosa = 2 + option_always_camilla = 3 + default = 0 + + +class RoomOfClocksBoss(Choice): + """Sets which boss is fought at Room of Clocks by which characters.""" + display_name = "Room of Clocks Boss" + option_vanilla = 0 + option_inverted = 1 + option_always_death = 2 + option_always_actrise = 3 + default = 0 + + +class RenonFightCondition(Choice): + """Sets the condition on which the Renon fight will trigger.""" + display_name = "Renon Fight Condition" + option_never = 0 + option_spend_30k = 1 + option_always = 2 + default = 1 + + +class VincentFightCondition(Choice): + """Sets the condition on which the vampire Vincent fight will trigger.""" + display_name = "Vincent Fight Condition" + option_never = 0 + option_wait_16_days = 1 + option_always = 2 + default = 1 + + +class BadEndingCondition(Choice): + """Sets the condition on which the currently-controlled character's Bad Ending will trigger.""" + display_name = "Bad Ending Condition" + option_never = 0 + option_kill_vincent = 1 + option_always = 2 + default = 1 + + +class IncreaseItemLimit(DefaultOnToggle): + """Increases the holding limit of usable items from 10 to 99 of each item.""" + display_name = "Increase Item Limit" + + +class NerfHealingItems(Toggle): + """Decreases the amount of health healed by Roast Chickens to 25%, Roast Beefs to 50%, and Healing Kits to 80%.""" + display_name = "Nerf Healing Items" + + +class LoadingZoneHeals(DefaultOnToggle): + """Whether end-of-level loading zones restore health and cure status aliments or not. + Recommended off for those looking for more of a survival horror experience!""" + display_name = "Loading Zone Heals" + + +class InvisibleItems(Choice): + """Sets which items are visible in their locations and which are invisible until picked up. + 'Chance' gives each item a 50/50 chance of being visible or invisible.""" + display_name = "Invisible Items" + option_vanilla = 0 + option_reveal_all = 1 + option_hide_all = 2 + option_chance = 3 + default = 0 + + +class DropPreviousSubWeapon(Toggle): + """When receiving a sub-weapon, the one you had before will drop behind you, so it can be taken back if desired.""" + display_name = "Drop Previous Sub-weapon" + + +class PermanentPowerUps(Toggle): + """Replaces PowerUps with PermaUps, which upgrade your B weapon level permanently and will stay even after + dying and/or continuing. + To compensate, only two will be in the pool overall, and they will not drop from any enemy or projectile.""" + display_name = "Permanent PowerUps" + + +class IceTrapPercentage(Range): + """Replaces a percentage of junk items with Ice Traps. + These will be visibly disguised as other items, and receiving one will freeze you + as if you were hit by Camilla's ice cloud attack.""" + display_name = "Ice Trap Percentage" + range_start = 0 + range_end = 100 + default = 0 + + +class IceTrapAppearance(Choice): + """What items Ice Traps can possibly be disguised as.""" + display_name = "Ice Trap Appearance" + option_major_only = 0 + option_junk_only = 1 + option_anything = 2 + default = 0 + + +class DisableTimeRestrictions(Toggle): + """Disables the restriction on every event and door that requires the current time + to be within a specific range, so they can be triggered at any time. + This includes all sun/moon doors and, in the Villa, the meeting with Rosa and the fountain pillar. + The Villa coffin is not affected by this.""" + display_name = "Disable Time Requirements" + + +class SkipGondolas(Toggle): + """Makes jumping on and activating a gondola in Tunnel instantly teleport you + to the other station, thereby skipping the entire three-minute ride. + The item normally at the gondola transfer point is moved to instead be + near the red gondola at its station.""" + display_name = "Skip Gondolas" + + +class SkipWaterwayBlocks(Toggle): + """Opens the door to the third switch in Underground Waterway from the start so that the jumping across floating + brick platforms won't have to be done. Shopping at the Contract on the other side of them may still be logically + required if Shopsanity is on.""" + display_name = "Skip Waterway Blocks" + + +class Countdown(Choice): + """Displays, near the HUD clock and below the health bar, the number of unobtained progression-marked items + or the total check locations remaining in the stage you are currently in.""" + display_name = "Countdown" + option_none = 0 + option_majors = 1 + option_all_locations = 2 + default = 0 + + +class BigToss(Toggle): + """Makes every non-immobilizing damage source launch you as if you got hit by Behemoth's charge. + Press A while tossed to cancel the launch momentum and avoid being thrown off ledges. + Hold Z to have all incoming damage be treated as it normally would. + Any tricks that might be possible with it are NOT considered in logic on any setting.""" + display_name = "Big Toss" + + +class PantherDash(Choice): + """Hold C-right at any time to sprint way faster. Any tricks that might be + possible with it are NOT considered in logic on any setting and any boss + fights with boss health meters, if started, are expected to be finished + before leaving their arenas if Dracula's Condition is bosses. Jumpless will + prevent jumping while moving at the increased speed to ensure logic cannot be broken with it.""" + display_name = "Panther Dash" + option_off = 0 + option_on = 1 + option_jumpless = 2 + default = 0 + + +class IncreaseShimmySpeed(Toggle): + """Increases the speed at which characters shimmy left and right while hanging on ledges.""" + display_name = "Increase Shimmy Speed" + + +class FallGuard(Toggle): + """Removes fall damage from landing too hard. Note that falling for too long will still result in instant death.""" + display_name = "Fall Guard" + + +class BackgroundMusic(Choice): + """Randomizes or disables the music heard throughout the game. + Randomized music is split into two pools: songs that loop and songs that don't. + The "lead-in" versions of some songs will be paired accordingly.""" + display_name = "Background Music" + option_normal = 0 + option_disabled = 1 + option_randomized = 2 + default = 0 + + +class MapLighting(Choice): + """Randomizes the lighting color RGB values on every map during every time of day to be literally anything. + The colors and/or shading of the following things are affected: fog, maps, player, enemies, and some objects.""" + display_name = "Map Lighting" + option_normal = 0 + option_randomized = 1 + default = 0 + + +class CinematicExperience(Toggle): + """Enables an unused film reel effect on every cutscene in the game. Purely cosmetic.""" + display_name = "Cinematic Experience" + + +class WindowColorR(Range): + """The red value for the background color of the text windows during gameplay.""" + display_name = "Window Color R" + range_start = 0 + range_end = 15 + default = 1 + + +class WindowColorG(Range): + """The green value for the background color of the text windows during gameplay.""" + display_name = "Window Color G" + range_start = 0 + range_end = 15 + default = 5 + + +class WindowColorB(Range): + """The blue value for the background color of the text windows during gameplay.""" + display_name = "Window Color B" + range_start = 0 + range_end = 15 + default = 15 + + +class WindowColorA(Range): + """The alpha value for the background color of the text windows during gameplay.""" + display_name = "Window Color A" + range_start = 0 + range_end = 15 + default = 8 + + +class DeathLink(Choice): + """When you die, everyone dies. Of course the reverse is true too. + Explosive: Makes received DeathLinks kill you via the Magical Nitro explosion + instead of the normal death animation.""" + display_name = "DeathLink" + option_off = 0 + alias_no = 0 + alias_true = 1 + alias_yes = 1 + option_on = 1 + option_explosive = 2 + + +@dataclass +class CV64Options(PerGameCommonOptions): + character_stages: CharacterStages + stage_shuffle: StageShuffle + starting_stage: StartingStage + warp_order: WarpOrder + sub_weapon_shuffle: SubWeaponShuffle + spare_keys: SpareKeys + hard_item_pool: HardItemPool + special1s_per_warp: Special1sPerWarp + total_special1s: TotalSpecial1s + draculas_condition: DraculasCondition + percent_special2s_required: PercentSpecial2sRequired + total_special2s: TotalSpecial2s + bosses_required: BossesRequired + carrie_logic: CarrieLogic + hard_logic: HardLogic + multi_hit_breakables: MultiHitBreakables + empty_breakables: EmptyBreakables + lizard_locker_items: LizardLockerItems + shopsanity: Shopsanity + shop_prices: ShopPrices + minimum_gold_price: MinimumGoldPrice + maximum_gold_price: MaximumGoldPrice + post_behemoth_boss: PostBehemothBoss + room_of_clocks_boss: RoomOfClocksBoss + renon_fight_condition: RenonFightCondition + vincent_fight_condition: VincentFightCondition + bad_ending_condition: BadEndingCondition + increase_item_limit: IncreaseItemLimit + nerf_healing_items: NerfHealingItems + loading_zone_heals: LoadingZoneHeals + invisible_items: InvisibleItems + drop_previous_sub_weapon: DropPreviousSubWeapon + permanent_powerups: PermanentPowerUps + ice_trap_percentage: IceTrapPercentage + ice_trap_appearance: IceTrapAppearance + disable_time_restrictions: DisableTimeRestrictions + skip_gondolas: SkipGondolas + skip_waterway_blocks: SkipWaterwayBlocks + countdown: Countdown + big_toss: BigToss + panther_dash: PantherDash + increase_shimmy_speed: IncreaseShimmySpeed + background_music: BackgroundMusic + map_lighting: MapLighting + fall_guard: FallGuard + cinematic_experience: CinematicExperience + window_color_r: WindowColorR + window_color_g: WindowColorG + window_color_b: WindowColorB + window_color_a: WindowColorA + death_link: DeathLink + start_inventory_from_pool: StartInventoryPool diff --git a/worlds/cv64/regions.py b/worlds/cv64/regions.py new file mode 100644 index 0000000000..2194828a19 --- /dev/null +++ b/worlds/cv64/regions.py @@ -0,0 +1,517 @@ +from .data import lname, rname, ename +from typing import List, Union + + +# # # KEY # # # +# "stage" = What stage the Region is a part of. The Region and its corresponding Locations and Entrances will only be +# put in if its stage is active. +# "locations" = The Locations to add to that Region when putting in said Region (provided their add conditions pass). +# "entrances" = The Entrances to add to that Region when putting in said Region (provided their add conditions pass). +region_info = { + "Menu": {}, + + rname.forest_start: {"stage": rname.forest_of_silence, + "locations": [lname.forest_pillars_right, + lname.forest_pillars_left, + lname.forest_pillars_top, + lname.forest_king_skeleton, + lname.forest_boss_one, + lname.forest_lgaz_in, + lname.forest_lgaz_top, + lname.forest_hgaz_in, + lname.forest_hgaz_top, + lname.forest_weretiger_sw, + lname.forest_boss_two, + lname.forest_weretiger_gate, + lname.forest_dirge_tomb_l, + lname.forest_dirge_tomb_u, + lname.forest_dirge_plaque, + lname.forest_dirge_ped, + lname.forest_dirge_rock1, + lname.forest_dirge_rock2, + lname.forest_dirge_rock3, + lname.forest_dirge_rock4, + lname.forest_dirge_rock5, + lname.forest_corpse_save, + lname.forest_dbridge_wall, + lname.forest_dbridge_sw], + "entrances": [ename.forest_dbridge_gate]}, + + rname.forest_mid: {"stage": rname.forest_of_silence, + "locations": [lname.forest_dbridge_gate_l, + lname.forest_dbridge_gate_r, + lname.forest_dbridge_tomb_l, + lname.forest_dbridge_tomb_ur, + lname.forest_dbridge_tomb_uf, + lname.forest_bface_tomb_lf, + lname.forest_bface_tomb_lr, + lname.forest_bface_tomb_u, + lname.forest_ibridge, + lname.forest_bridge_rock1, + lname.forest_bridge_rock2, + lname.forest_bridge_rock3, + lname.forest_bridge_rock4, + lname.forest_werewolf_tomb_lf, + lname.forest_werewolf_tomb_lr, + lname.forest_werewolf_tomb_r, + lname.forest_werewolf_plaque, + lname.forest_werewolf_tree, + lname.forest_werewolf_island, + lname.forest_final_sw], + "entrances": [ename.forest_werewolf_gate]}, + + rname.forest_end: {"stage": rname.forest_of_silence, + "locations": [lname.forest_boss_three], + "entrances": [ename.forest_end]}, + + rname.cw_start: {"stage": rname.castle_wall, + "locations": [lname.cwr_bottom, + lname.cw_dragon_sw, + lname.cw_boss, + lname.cw_save_slab1, + lname.cw_save_slab2, + lname.cw_save_slab3, + lname.cw_save_slab4, + lname.cw_save_slab5, + lname.cw_rrampart, + lname.cw_lrampart, + lname.cw_pillar, + lname.cw_shelf_visible, + lname.cw_shelf_sandbags, + lname.cw_shelf_torch], + "entrances": [ename.cw_portcullis_c, + ename.cw_lt_skip, + ename.cw_lt_door]}, + + rname.cw_exit: {"stage": rname.castle_wall, + "locations": [lname.cw_ground_left, + lname.cw_ground_middle, + lname.cw_ground_right]}, + + rname.cw_ltower: {"stage": rname.castle_wall, + "locations": [lname.cwl_bottom, + lname.cwl_bridge, + lname.cw_drac_sw, + lname.cw_drac_slab1, + lname.cw_drac_slab2, + lname.cw_drac_slab3, + lname.cw_drac_slab4, + lname.cw_drac_slab5], + "entrances": [ename.cw_end]}, + + rname.villa_start: {"stage": rname.villa, + "locations": [lname.villafy_outer_gate_l, + lname.villafy_outer_gate_r, + lname.villafy_dog_platform, + lname.villafy_inner_gate], + "entrances": [ename.villa_dog_gates]}, + + rname.villa_main: {"stage": rname.villa, + "locations": [lname.villafy_gate_marker, + lname.villafy_villa_marker, + lname.villafy_tombstone, + lname.villafy_fountain_fl, + lname.villafy_fountain_fr, + lname.villafy_fountain_ml, + lname.villafy_fountain_mr, + lname.villafy_fountain_rl, + lname.villafy_fountain_rr, + lname.villafo_front_r, + lname.villafo_front_l, + lname.villafo_mid_l, + lname.villafo_mid_r, + lname.villafo_rear_r, + lname.villafo_rear_l, + lname.villafo_pot_r, + lname.villafo_pot_l, + lname.villafo_sofa, + lname.villafo_chandelier1, + lname.villafo_chandelier2, + lname.villafo_chandelier3, + lname.villafo_chandelier4, + lname.villafo_chandelier5, + lname.villala_hallway_stairs, + lname.villala_hallway_l, + lname.villala_hallway_r, + lname.villala_bedroom_chairs, + lname.villala_bedroom_bed, + lname.villala_vincent, + lname.villala_slivingroom_table, + lname.villala_slivingroom_mirror, + lname.villala_diningroom_roses, + lname.villala_llivingroom_pot_r, + lname.villala_llivingroom_pot_l, + lname.villala_llivingroom_painting, + lname.villala_llivingroom_light, + lname.villala_llivingroom_lion, + lname.villala_exit_knight], + "entrances": [ename.villa_snipe_dogs, + ename.villa_renon, + ename.villa_to_storeroom, + ename.villa_to_archives, + ename.villa_to_maze]}, + + rname.villa_storeroom: {"stage": rname.villa, + "locations": [lname.villala_storeroom_l, + lname.villala_storeroom_r, + lname.villala_storeroom_s], + "entrances": [ename.villa_from_storeroom]}, + + rname.villa_archives: {"stage": rname.villa, + "locations": [lname.villala_archives_entrance, + lname.villala_archives_table, + lname.villala_archives_rear]}, + + rname.villa_maze: {"stage": rname.villa, + "locations": [lname.villam_malus_torch, + lname.villam_malus_bush, + lname.villam_fplatform, + lname.villam_frankieturf_l, + lname.villam_frankieturf_r, + lname.villam_frankieturf_ru, + lname.villam_fgarden_f, + lname.villam_fgarden_mf, + lname.villam_fgarden_mr, + lname.villam_fgarden_r, + lname.villam_rplatform, + lname.villam_rplatform_de, + lname.villam_exit_de, + lname.villam_serv_path], + "entrances": [ename.villa_from_maze, + ename.villa_copper_door, + ename.villa_copper_skip]}, + + rname.villa_servants: {"stage": rname.villa, + "locations": [lname.villafo_serv_ent], + "entrances": [ename.villa_servant_door]}, + + rname.villa_crypt: {"stage": rname.villa, + "locations": [lname.villam_crypt_ent, + lname.villam_crypt_upstream, + lname.villac_ent_l, + lname.villac_ent_r, + lname.villac_wall_l, + lname.villac_wall_r, + lname.villac_coffin_l, + lname.villac_coffin_r, + lname.villa_boss_one, + lname.villa_boss_two], + "entrances": [ename.villa_bridge_door, + ename.villa_end_r, + ename.villa_end_c]}, + + rname.tunnel_start: {"stage": rname.tunnel, + "locations": [lname.tunnel_landing, + lname.tunnel_landing_rc, + lname.tunnel_stone_alcove_r, + lname.tunnel_stone_alcove_l, + lname.tunnel_twin_arrows, + lname.tunnel_arrows_rock1, + lname.tunnel_arrows_rock2, + lname.tunnel_arrows_rock3, + lname.tunnel_arrows_rock4, + lname.tunnel_arrows_rock5, + lname.tunnel_lonesome_bucket, + lname.tunnel_lbucket_mdoor_l, + lname.tunnel_lbucket_quag, + lname.tunnel_bucket_quag_rock1, + lname.tunnel_bucket_quag_rock2, + lname.tunnel_bucket_quag_rock3, + lname.tunnel_lbucket_albert, + lname.tunnel_albert_camp, + lname.tunnel_albert_quag, + lname.tunnel_gondola_rc_sdoor_l, + lname.tunnel_gondola_rc_sdoor_m, + lname.tunnel_gondola_rc_sdoor_r, + lname.tunnel_gondola_rc, + lname.tunnel_rgondola_station, + lname.tunnel_gondola_transfer], + "entrances": [ename.tunnel_start_renon, + ename.tunnel_gondolas]}, + + rname.tunnel_end: {"stage": rname.tunnel, + "locations": [lname.tunnel_corpse_bucket_quag, + lname.tunnel_corpse_bucket_mdoor_l, + lname.tunnel_corpse_bucket_mdoor_r, + lname.tunnel_shovel_quag_start, + lname.tunnel_exit_quag_start, + lname.tunnel_shovel_quag_end, + lname.tunnel_exit_quag_end, + lname.tunnel_shovel, + lname.tunnel_shovel_save, + lname.tunnel_shovel_mdoor_l, + lname.tunnel_shovel_mdoor_r, + lname.tunnel_shovel_sdoor_l, + lname.tunnel_shovel_sdoor_m, + lname.tunnel_shovel_sdoor_r], + "entrances": [ename.tunnel_end_renon, + ename.tunnel_end]}, + + rname.uw_main: {"stage": rname.underground_waterway, + "locations": [lname.uw_near_ent, + lname.uw_across_ent, + lname.uw_first_ledge1, + lname.uw_first_ledge2, + lname.uw_first_ledge3, + lname.uw_first_ledge4, + lname.uw_first_ledge5, + lname.uw_first_ledge6, + lname.uw_poison_parkour, + lname.uw_boss, + lname.uw_waterfall_alcove, + lname.uw_carrie1, + lname.uw_carrie2, + lname.uw_bricks_save, + lname.uw_above_skel_ledge, + lname.uw_in_skel_ledge1, + lname.uw_in_skel_ledge2, + lname.uw_in_skel_ledge3], + "entrances": [ename.uw_final_waterfall, + ename.uw_renon]}, + + rname.uw_end: {"stage": rname.underground_waterway, + "entrances": [ename.uw_waterfall_skip, + ename.uw_end]}, + + rname.cc_main: {"stage": rname.castle_center, + "locations": [lname.ccb_skel_hallway_ent, + lname.ccb_skel_hallway_jun, + lname.ccb_skel_hallway_tc, + lname.ccb_skel_hallway_ba, + lname.ccb_behemoth_l_ff, + lname.ccb_behemoth_l_mf, + lname.ccb_behemoth_l_mr, + lname.ccb_behemoth_l_fr, + lname.ccb_behemoth_r_ff, + lname.ccb_behemoth_r_mf, + lname.ccb_behemoth_r_mr, + lname.ccb_behemoth_r_fr, + lname.ccb_behemoth_crate1, + lname.ccb_behemoth_crate2, + lname.ccb_behemoth_crate3, + lname.ccb_behemoth_crate4, + lname.ccb_behemoth_crate5, + lname.ccelv_near_machine, + lname.ccelv_atop_machine, + lname.ccelv_stand1, + lname.ccelv_stand2, + lname.ccelv_stand3, + lname.ccelv_pipes, + lname.ccelv_switch, + lname.ccelv_staircase, + lname.ccff_redcarpet_knight, + lname.ccff_gears_side, + lname.ccff_gears_mid, + lname.ccff_gears_corner, + lname.ccff_lizard_knight, + lname.ccff_lizard_near_knight, + lname.ccff_lizard_pit, + lname.ccff_lizard_corner, + lname.ccff_lizard_locker_nfr, + lname.ccff_lizard_locker_nmr, + lname.ccff_lizard_locker_nml, + lname.ccff_lizard_locker_nfl, + lname.ccff_lizard_locker_fl, + lname.ccff_lizard_locker_fr, + lname.ccff_lizard_slab1, + lname.ccff_lizard_slab2, + lname.ccff_lizard_slab3, + lname.ccff_lizard_slab4, + lname.ccll_brokenstairs_floor, + lname.ccll_brokenstairs_knight, + lname.ccll_brokenstairs_save, + lname.ccll_glassknight_l, + lname.ccll_glassknight_r, + lname.ccll_butlers_door, + lname.ccll_butlers_side, + lname.ccll_cwhall_butlerflames_past, + lname.ccll_cwhall_flamethrower, + lname.ccll_cwhall_cwflames, + lname.ccll_heinrich, + lname.ccia_nitro_crates, + lname.ccia_nitro_shelf_h, + lname.ccia_stairs_knight, + lname.ccia_maids_vase, + lname.ccia_maids_outer, + lname.ccia_maids_inner, + lname.ccia_inventions_maids, + lname.ccia_inventions_crusher, + lname.ccia_inventions_famicart, + lname.ccia_inventions_zeppelin, + lname.ccia_inventions_round, + lname.ccia_nitrohall_flamethrower, + lname.ccia_nitrohall_torch, + lname.ccia_nitro_shelf_i], + "entrances": [ename.cc_tc_door, + ename.cc_lower_wall, + ename.cc_renon, + ename.cc_upper_wall]}, + + rname.cc_torture_chamber: {"stage": rname.castle_center, + "locations": [lname.ccb_mandrag_shelf_l, + lname.ccb_mandrag_shelf_r, + lname.ccb_torture_rack, + lname.ccb_torture_rafters]}, + + rname.cc_library: {"stage": rname.castle_center, + "locations": [lname.ccll_cwhall_wall, + lname.ccl_bookcase]}, + + rname.cc_crystal: {"stage": rname.castle_center, + "locations": [lname.cc_behind_the_seal, + lname.cc_boss_one, + lname.cc_boss_two], + "entrances": [ename.cc_elevator]}, + + rname.cc_elev_top: {"stage": rname.castle_center, + "entrances": [ename.cc_exit_r, + ename.cc_exit_c]}, + + rname.dt_main: {"stage": rname.duel_tower, + "locations": [lname.dt_boss_one, + lname.dt_boss_two, + lname.dt_ibridge_l, + lname.dt_ibridge_r, + lname.dt_stones_start, + lname.dt_stones_end, + lname.dt_werebull_arena, + lname.dt_boss_three, + lname.dt_boss_four], + "entrances": [ename.dt_start, + ename.dt_end]}, + + rname.toe_main: {"stage": rname.tower_of_execution, + "locations": [lname.toe_ledge1, + lname.toe_ledge2, + lname.toe_ledge3, + lname.toe_ledge4, + lname.toe_ledge5, + lname.toe_midsavespikes_r, + lname.toe_midsavespikes_l, + lname.toe_elec_grate, + lname.toe_ibridge, + lname.toe_top], + "entrances": [ename.toe_start, + ename.toe_gate, + ename.toe_gate_skip, + ename.toe_end]}, + + rname.toe_ledge: {"stage": rname.tower_of_execution, + "locations": [lname.toe_keygate_l, + lname.toe_keygate_r]}, + + rname.tosci_start: {"stage": rname.tower_of_science, + "locations": [lname.tosci_elevator, + lname.tosci_plain_sr, + lname.tosci_stairs_sr], + "entrances": [ename.tosci_start, + ename.tosci_key1_door, + ename.tosci_to_key2_door]}, + + rname.tosci_three_doors: {"stage": rname.tower_of_science, + "locations": [lname.tosci_three_door_hall]}, + + rname.tosci_conveyors: {"stage": rname.tower_of_science, + "locations": [lname.tosci_ibridge_t, + lname.tosci_ibridge_b1, + lname.tosci_ibridge_b2, + lname.tosci_ibridge_b3, + lname.tosci_ibridge_b4, + lname.tosci_ibridge_b5, + lname.tosci_ibridge_b6, + lname.tosci_conveyor_sr, + lname.tosci_exit], + "entrances": [ename.tosci_from_key2_door, + ename.tosci_key3_door, + ename.tosci_end]}, + + rname.tosci_key3: {"stage": rname.tower_of_science, + "locations": [lname.tosci_key3_r, + lname.tosci_key3_m, + lname.tosci_key3_l]}, + + rname.tosor_main: {"stage": rname.tower_of_sorcery, + "locations": [lname.tosor_stained_tower, + lname.tosor_savepoint, + lname.tosor_trickshot, + lname.tosor_yellow_bubble, + lname.tosor_blue_platforms, + lname.tosor_side_isle, + lname.tosor_ibridge], + "entrances": [ename.tosor_start, + ename.tosor_end]}, + + rname.roc_main: {"stage": rname.room_of_clocks, + "locations": [lname.roc_ent_l, + lname.roc_ent_r, + lname.roc_elev_r, + lname.roc_elev_l, + lname.roc_cont_r, + lname.roc_cont_l, + lname.roc_exit, + lname.roc_boss], + "entrances": [ename.roc_gate]}, + + rname.ct_start: {"stage": rname.clock_tower, + "locations": [lname.ct_gearclimb_battery_slab1, + lname.ct_gearclimb_battery_slab2, + lname.ct_gearclimb_battery_slab3, + lname.ct_gearclimb_side, + lname.ct_gearclimb_corner, + lname.ct_gearclimb_door_slab1, + lname.ct_gearclimb_door_slab2, + lname.ct_gearclimb_door_slab3], + "entrances": [ename.ct_to_door1]}, + + rname.ct_middle: {"stage": rname.clock_tower, + "locations": [lname.ct_bp_chasm_fl, + lname.ct_bp_chasm_fr, + lname.ct_bp_chasm_rl, + lname.ct_bp_chasm_k], + "entrances": [ename.ct_from_door1, + ename.ct_to_door2]}, + + rname.ct_end: {"stage": rname.clock_tower, + "locations": [lname.ct_finalroom_door_slab1, + lname.ct_finalroom_door_slab2, + lname.ct_finalroom_fl, + lname.ct_finalroom_fr, + lname.ct_finalroom_rl, + lname.ct_finalroom_rr, + lname.ct_finalroom_platform, + lname.ct_finalroom_renon_slab1, + lname.ct_finalroom_renon_slab2, + lname.ct_finalroom_renon_slab3, + lname.ct_finalroom_renon_slab4, + lname.ct_finalroom_renon_slab5, + lname.ct_finalroom_renon_slab6, + lname.ct_finalroom_renon_slab7, + lname.ct_finalroom_renon_slab8], + "entrances": [ename.ct_from_door2, + ename.ct_renon, + ename.ct_door_3]}, + + rname.ck_main: {"stage": rname.castle_keep, + "locations": [lname.ck_boss_one, + lname.ck_boss_two, + lname.ck_flame_l, + lname.ck_flame_r, + lname.ck_behind_drac, + lname.ck_cube], + "entrances": [ename.ck_slope_jump, + ename.ck_drac_door]}, + + rname.renon: {"locations": [lname.renon1, + lname.renon2, + lname.renon3, + lname.renon4, + lname.renon5, + lname.renon6, + lname.renon7]}, + + rname.ck_drac_chamber: {"locations": [lname.the_end]} +} + + +def get_region_info(region: str, info: str) -> Union[str, List[str], None]: + return region_info[region].get(info, None) diff --git a/worlds/cv64/rom.py b/worlds/cv64/rom.py new file mode 100644 index 0000000000..ab8c7030aa --- /dev/null +++ b/worlds/cv64/rom.py @@ -0,0 +1,959 @@ + +import Utils + +from BaseClasses import Location +from worlds.Files import APDeltaPatch +from typing import List, Dict, Union, Iterable, Collection, TYPE_CHECKING + +import hashlib +import os +import pkgutil + +from . import lzkn64 +from .data import patches +from .stages import get_stage_info +from .text import cv64_string_to_bytearray, cv64_text_truncate, cv64_text_wrap +from .aesthetics import renon_item_dialogue, get_item_text_color +from .locations import get_location_info +from .options import CharacterStages, VincentFightCondition, RenonFightCondition, PostBehemothBoss, RoomOfClocksBoss, \ + BadEndingCondition, DeathLink, DraculasCondition, InvisibleItems, Countdown, PantherDash +from settings import get_settings + +if TYPE_CHECKING: + from . import CV64World + +CV64US10HASH = "1cc5cf3b4d29d8c3ade957648b529dc1" +ROM_PLAYER_LIMIT = 65535 + +warp_map_offsets = [0xADF67, 0xADF77, 0xADF87, 0xADF97, 0xADFA7, 0xADFBB, 0xADFCB, 0xADFDF] + + +class LocalRom: + orig_buffer: None + buffer: bytearray + + def __init__(self, file: str) -> None: + self.orig_buffer = None + + with open(file, "rb") as stream: + self.buffer = bytearray(stream.read()) + + def read_bit(self, address: int, bit_number: int) -> bool: + bitflag = (1 << bit_number) + return (self.buffer[address] & bitflag) != 0 + + def read_byte(self, address: int) -> int: + return self.buffer[address] + + def read_bytes(self, start_address: int, length: int) -> bytearray: + return self.buffer[start_address:start_address + length] + + def write_byte(self, address: int, value: int) -> None: + self.buffer[address] = value + + def write_bytes(self, start_address: int, values: Collection[int]) -> None: + self.buffer[start_address:start_address + len(values)] = values + + def write_int16(self, address: int, value: int) -> None: + value = value & 0xFFFF + self.write_bytes(address, [(value >> 8) & 0xFF, value & 0xFF]) + + def write_int16s(self, start_address: int, values: List[int]) -> None: + for i, value in enumerate(values): + self.write_int16(start_address + (i * 2), value) + + def write_int24(self, address: int, value: int) -> None: + value = value & 0xFFFFFF + self.write_bytes(address, [(value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF]) + + def write_int24s(self, start_address: int, values: List[int]) -> None: + for i, value in enumerate(values): + self.write_int24(start_address + (i * 3), value) + + def write_int32(self, address, value: int) -> None: + value = value & 0xFFFFFFFF + self.write_bytes(address, [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF]) + + def write_int32s(self, start_address: int, values: list) -> None: + for i, value in enumerate(values): + self.write_int32(start_address + (i * 4), value) + + def write_to_file(self, filepath: str) -> None: + with open(filepath, "wb") as outfile: + outfile.write(self.buffer) + + +def patch_rom(world: "CV64World", rom: LocalRom, offset_data: Dict[int, int], shop_name_list: List[str], + shop_desc_list: List[List[Union[int, str, None]]], shop_colors_list: List[bytearray], + active_locations: Iterable[Location]) -> None: + + multiworld = world.multiworld + options = world.options + player = world.player + active_stage_exits = world.active_stage_exits + s1s_per_warp = world.s1s_per_warp + active_warp_list = world.active_warp_list + required_s2s = world.required_s2s + total_s2s = world.total_s2s + + # NOP out the CRC BNEs + rom.write_int32(0x66C, 0x00000000) + rom.write_int32(0x678, 0x00000000) + + # Always offer Hard Mode on file creation + rom.write_int32(0xC8810, 0x240A0100) # ADDIU T2, R0, 0x0100 + + # Disable Easy Mode cutoff point at Castle Center elevator + rom.write_int32(0xD9E18, 0x240D0000) # ADDIU T5, R0, 0x0000 + + # Disable the Forest, Castle Wall, and Villa intro cutscenes and make it possible to change the starting level + rom.write_byte(0xB73308, 0x00) + rom.write_byte(0xB7331A, 0x40) + rom.write_byte(0xB7332B, 0x4C) + rom.write_byte(0xB6302B, 0x00) + rom.write_byte(0x109F8F, 0x00) + + # Prevent Forest end cutscene flag from setting so it can be triggered infinitely + rom.write_byte(0xEEA51, 0x01) + + # Hack to make the Forest, CW and Villa intro cutscenes play at the start of their levels no matter what map came + # before them + rom.write_int32(0x97244, 0x803FDD60) + rom.write_int32s(0xBFDD60, patches.forest_cw_villa_intro_cs_player) + + # Make changing the map ID to 0xFF reset the map. Helpful to work around a bug wherein the camera gets stuck when + # entering a loading zone that doesn't change the map. + rom.write_int32s(0x197B0, [0x0C0FF7E6, # JAL 0x803FDF98 + 0x24840008]) # ADDIU A0, A0, 0x0008 + rom.write_int32s(0xBFDF98, patches.map_id_refresher) + + # Enable swapping characters when loading into a map by holding L. + rom.write_int32(0x97294, 0x803FDFC4) + rom.write_int32(0x19710, 0x080FF80E) # J 0x803FE038 + rom.write_int32s(0xBFDFC4, patches.character_changer) + + # Villa coffin time-of-day hack + rom.write_byte(0xD9D83, 0x74) + rom.write_int32(0xD9D84, 0x080FF14D) # J 0x803FC534 + rom.write_int32s(0xBFC534, patches.coffin_time_checker) + + # Fix both Castle Center elevator bridges for both characters unless enabling only one character's stages. At which + # point one bridge will be always broken and one always repaired instead. + if options.character_stages == CharacterStages.option_reinhardt_only: + rom.write_int32(0x6CEAA0, 0x240B0000) # ADDIU T3, R0, 0x0000 + elif options.character_stages == CharacterStages.option_carrie_only: + rom.write_int32(0x6CEAA0, 0x240B0001) # ADDIU T3, R0, 0x0001 + else: + rom.write_int32(0x6CEAA0, 0x240B0001) # ADDIU T3, R0, 0x0001 + rom.write_int32(0x6CEAA4, 0x240D0001) # ADDIU T5, R0, 0x0001 + + # Were-bull arena flag hack + rom.write_int32(0x6E38F0, 0x0C0FF157) # JAL 0x803FC55C + rom.write_int32s(0xBFC55C, patches.werebull_flag_unsetter) + rom.write_int32(0xA949C, 0x0C0FF380) # JAL 0x803FCE00 + rom.write_int32s(0xBFCE00, patches.werebull_flag_pickup_setter) + + # Enable being able to carry multiple Special jewels, Nitros, and Mandragoras simultaneously + rom.write_int32(0xBF1F4, 0x3C038039) # LUI V1, 0x8039 + # Special1 + rom.write_int32(0xBF210, 0x80659C4B) # LB A1, 0x9C4B (V1) + rom.write_int32(0xBF214, 0x24A50001) # ADDIU A1, A1, 0x0001 + rom.write_int32(0xBF21C, 0xA0659C4B) # SB A1, 0x9C4B (V1) + # Special2 + rom.write_int32(0xBF230, 0x80659C4C) # LB A1, 0x9C4C (V1) + rom.write_int32(0xBF234, 0x24A50001) # ADDIU A1, A1, 0x0001 + rom.write_int32(0xbf23C, 0xA0659C4C) # SB A1, 0x9C4C (V1) + # Magical Nitro + rom.write_int32(0xBF360, 0x10000004) # B 0x8013C184 + rom.write_int32(0xBF378, 0x25E50001) # ADDIU A1, T7, 0x0001 + rom.write_int32(0xBF37C, 0x10000003) # B 0x8013C19C + # Mandragora + rom.write_int32(0xBF3A8, 0x10000004) # B 0x8013C1CC + rom.write_int32(0xBF3C0, 0x25050001) # ADDIU A1, T0, 0x0001 + rom.write_int32(0xBF3C4, 0x10000003) # B 0x8013C1E4 + + # Give PowerUps their Legacy of Darkness behavior when attempting to pick up more than two + rom.write_int16(0xA9624, 0x1000) + rom.write_int32(0xA9730, 0x24090000) # ADDIU T1, R0, 0x0000 + rom.write_int32(0xBF2FC, 0x080FF16D) # J 0x803FC5B4 + rom.write_int32(0xBF300, 0x00000000) # NOP + rom.write_int32s(0xBFC5B4, patches.give_powerup_stopper) + + # Rename the Wooden Stake and Rose to "You are a FOOL!" + rom.write_bytes(0xEFE34, + bytearray([0xFF, 0xFF, 0xA2, 0x0B]) + cv64_string_to_bytearray("You are a FOOL!", append_end=False)) + # Capitalize the "k" in "Archives key" to be consistent with...literally every other key name! + rom.write_byte(0xEFF21, 0x2D) + + # Skip the "There is a white jewel" text so checking one saves the game instantly. + rom.write_int32s(0xEFC72, [0x00020002 for _ in range(37)]) + rom.write_int32(0xA8FC0, 0x24020001) # ADDIU V0, R0, 0x0001 + # Skip the yes/no prompts when activating things. + rom.write_int32s(0xBFDACC, patches.map_text_redirector) + rom.write_int32(0xA9084, 0x24020001) # ADDIU V0, R0, 0x0001 + rom.write_int32(0xBEBE8, 0x0C0FF6B4) # JAL 0x803FDAD0 + # Skip Vincent and Heinrich's mandatory-for-a-check dialogue + rom.write_int32(0xBED9C, 0x0C0FF6DA) # JAL 0x803FDB68 + # Skip the long yes/no prompt in the CC planetarium to set the pieces. + rom.write_int32(0xB5C5DF, 0x24030001) # ADDIU V1, R0, 0x0001 + # Skip the yes/no prompt to activate the CC elevator. + rom.write_int32(0xB5E3FB, 0x24020001) # ADDIU V0, R0, 0x0001 + # Skip the yes/no prompts to set Nitro/Mandragora at both walls. + rom.write_int32(0xB5DF3E, 0x24030001) # ADDIU V1, R0, 0x0001 + + # Custom message if you try checking the downstairs CC crack before removing the seal. + rom.write_bytes(0xBFDBAC, cv64_string_to_bytearray("The Furious Nerd Curse\n" + "prevents you from setting\n" + "anything until the seal\n" + "is removed!", True)) + + rom.write_int32s(0xBFDD20, patches.special_descriptions_redirector) + + # Change the Stage Select menu options + rom.write_int32s(0xADF64, patches.warp_menu_rewrite) + rom.write_int32s(0x10E0C8, patches.warp_pointer_table) + for i in range(len(active_warp_list)): + if i == 0: + rom.write_byte(warp_map_offsets[i], get_stage_info(active_warp_list[i], "start map id")) + rom.write_byte(warp_map_offsets[i] + 4, get_stage_info(active_warp_list[i], "start spawn id")) + else: + rom.write_byte(warp_map_offsets[i], get_stage_info(active_warp_list[i], "mid map id")) + rom.write_byte(warp_map_offsets[i] + 4, get_stage_info(active_warp_list[i], "mid spawn id")) + + # Play the "teleportation" sound effect when teleporting + rom.write_int32s(0xAE088, [0x08004FAB, # J 0x80013EAC + 0x2404019E]) # ADDIU A0, R0, 0x019E + + # Change the Stage Select menu's text to reflect its new purpose + rom.write_bytes(0xEFAD0, cv64_string_to_bytearray(f"Where to...?\t{active_warp_list[0]}\t" + f"`{str(s1s_per_warp).zfill(2)} {active_warp_list[1]}\t" + f"`{str(s1s_per_warp * 2).zfill(2)} {active_warp_list[2]}\t" + f"`{str(s1s_per_warp * 3).zfill(2)} {active_warp_list[3]}\t" + f"`{str(s1s_per_warp * 4).zfill(2)} {active_warp_list[4]}\t" + f"`{str(s1s_per_warp * 5).zfill(2)} {active_warp_list[5]}\t" + f"`{str(s1s_per_warp * 6).zfill(2)} {active_warp_list[6]}\t" + f"`{str(s1s_per_warp * 7).zfill(2)} {active_warp_list[7]}")) + + # Lizard-man save proofing + rom.write_int32(0xA99AC, 0x080FF0B8) # J 0x803FC2E0 + rom.write_int32s(0xBFC2E0, patches.boss_save_stopper) + + # Disable or guarantee vampire Vincent's fight + if options.vincent_fight_condition == VincentFightCondition.option_never: + rom.write_int32(0xAACC0, 0x24010001) # ADDIU AT, R0, 0x0001 + rom.write_int32(0xAACE0, 0x24180000) # ADDIU T8, R0, 0x0000 + elif options.vincent_fight_condition == VincentFightCondition.option_always: + rom.write_int32(0xAACE0, 0x24180010) # ADDIU T8, R0, 0x0010 + else: + rom.write_int32(0xAACE0, 0x24180000) # ADDIU T8, R0, 0x0000 + + # Disable or guarantee Renon's fight + rom.write_int32(0xAACB4, 0x080FF1A4) # J 0x803FC690 + if options.renon_fight_condition == RenonFightCondition.option_never: + rom.write_byte(0xB804F0, 0x00) + rom.write_byte(0xB80632, 0x00) + rom.write_byte(0xB807E3, 0x00) + rom.write_byte(0xB80988, 0xB8) + rom.write_byte(0xB816BD, 0xB8) + rom.write_byte(0xB817CF, 0x00) + rom.write_int32s(0xBFC690, patches.renon_cutscene_checker_jr) + elif options.renon_fight_condition == RenonFightCondition.option_always: + rom.write_byte(0xB804F0, 0x0C) + rom.write_byte(0xB80632, 0x0C) + rom.write_byte(0xB807E3, 0x0C) + rom.write_byte(0xB80988, 0xC4) + rom.write_byte(0xB816BD, 0xC4) + rom.write_byte(0xB817CF, 0x0C) + rom.write_int32s(0xBFC690, patches.renon_cutscene_checker_jr) + else: + rom.write_int32s(0xBFC690, patches.renon_cutscene_checker) + + # NOP the Easy Mode check when buying a thing from Renon, so he can be triggered even on this mode. + rom.write_int32(0xBD8B4, 0x00000000) + + # Disable or guarantee the Bad Ending + if options.bad_ending_condition == BadEndingCondition.option_never: + rom.write_int32(0xAEE5C6, 0x3C0A0000) # LUI T2, 0x0000 + elif options.bad_ending_condition == BadEndingCondition.option_always: + rom.write_int32(0xAEE5C6, 0x3C0A0040) # LUI T2, 0x0040 + + # Play Castle Keep's song if teleporting in front of Dracula's door outside the escape sequence + rom.write_int32(0x6E937C, 0x080FF12E) # J 0x803FC4B8 + rom.write_int32s(0xBFC4B8, patches.ck_door_music_player) + + # Increase item capacity to 100 if "Increase Item Limit" is turned on + if options.increase_item_limit: + rom.write_byte(0xBF30B, 0x63) # Most items + rom.write_byte(0xBF3F7, 0x63) # Sun/Moon cards + rom.write_byte(0xBF353, 0x64) # Keys (increase regardless) + + # Change the item healing values if "Nerf Healing" is turned on + if options.nerf_healing_items: + rom.write_byte(0xB56371, 0x50) # Healing kit (100 -> 80) + rom.write_byte(0xB56374, 0x32) # Roast beef ( 80 -> 50) + rom.write_byte(0xB56377, 0x19) # Roast chicken ( 50 -> 25) + + # Disable loading zone healing if turned off + if not options.loading_zone_heals: + rom.write_byte(0xD99A5, 0x00) # Skip all loading zone checks + rom.write_byte(0xA9DFFB, 0x40) # Disable free heal from King Skeleton by reading the unused magic meter value + + # Disable spinning on the Special1 and 2 pickup models so colorblind people can more easily identify them + rom.write_byte(0xEE4F5, 0x00) # Special1 + rom.write_byte(0xEE505, 0x00) # Special2 + # Make the Special2 the same size as a Red jewel(L) to further distinguish them + rom.write_int32(0xEE4FC, 0x3FA66666) + + # Prevent the vanilla Magical Nitro transport's "can explode" flag from setting + rom.write_int32(0xB5D7AA, 0x00000000) # NOP + + # Ensure the vampire Nitro check will always pass, so they'll never not spawn and crash the Villa cutscenes + rom.write_byte(0xA6253D, 0x03) + + # Enable the Game Over's "Continue" menu starting the cursor on whichever checkpoint is most recent + rom.write_int32(0xB4DDC, 0x0C060D58) # JAL 0x80183560 + rom.write_int32s(0x106750, patches.continue_cursor_start_checker) + rom.write_int32(0x1C444, 0x080FF08A) # J 0x803FC228 + rom.write_int32(0x1C2A0, 0x080FF08A) # J 0x803FC228 + rom.write_int32s(0xBFC228, patches.savepoint_cursor_updater) + rom.write_int32(0x1C2D0, 0x080FF094) # J 0x803FC250 + rom.write_int32s(0xBFC250, patches.stage_start_cursor_updater) + rom.write_byte(0xB585C8, 0xFF) + + # Make the Special1 and 2 play sounds when you reach milestones with them. + rom.write_int32s(0xBFDA50, patches.special_sound_notifs) + rom.write_int32(0xBF240, 0x080FF694) # J 0x803FDA50 + rom.write_int32(0xBF220, 0x080FF69E) # J 0x803FDA78 + + # Add data for White Jewel #22 (the new Duel Tower savepoint) at the end of the White Jewel ID data list + rom.write_int16s(0x104AC8, [0x0000, 0x0006, + 0x0013, 0x0015]) + + # Take the contract in Waterway off of its 00400000 bitflag. + rom.write_byte(0x87E3DA, 0x00) + + # Spawn coordinates list extension + rom.write_int32(0xD5BF4, 0x080FF103) # J 0x803FC40C + rom.write_int32s(0xBFC40C, patches.spawn_coordinates_extension) + rom.write_int32s(0x108A5E, patches.waterway_end_coordinates) + + # Change the File Select stage numbers to match the new stage order. Also fix a vanilla issue wherein saving in a + # character-exclusive stage as the other character would incorrectly display the name of that character's equivalent + # stage on the save file instead of the one they're actually in. + rom.write_byte(0xC9FE3, 0xD4) + rom.write_byte(0xCA055, 0x08) + rom.write_byte(0xCA066, 0x40) + rom.write_int32(0xCA068, 0x860C17D0) # LH T4, 0x17D0 (S0) + rom.write_byte(0xCA06D, 0x08) + rom.write_byte(0x104A31, 0x01) + rom.write_byte(0x104A39, 0x01) + rom.write_byte(0x104A89, 0x01) + rom.write_byte(0x104A91, 0x01) + rom.write_byte(0x104A99, 0x01) + rom.write_byte(0x104AA1, 0x01) + + for stage in active_stage_exits: + for offset in get_stage_info(stage, "save number offsets"): + rom.write_byte(offset, active_stage_exits[stage]["position"]) + + # CC top elevator switch check + rom.write_int32(0x6CF0A0, 0x0C0FF0B0) # JAL 0x803FC2C0 + rom.write_int32s(0xBFC2C0, patches.elevator_flag_checker) + + # Disable time restrictions + if options.disable_time_restrictions: + # Fountain + rom.write_int32(0x6C2340, 0x00000000) # NOP + rom.write_int32(0x6C257C, 0x10000023) # B [forward 0x23] + # Rosa + rom.write_byte(0xEEAAB, 0x00) + rom.write_byte(0xEEAAD, 0x18) + # Moon doors + rom.write_int32(0xDC3E0, 0x00000000) # NOP + rom.write_int32(0xDC3E8, 0x00000000) # NOP + # Sun doors + rom.write_int32(0xDC410, 0x00000000) # NOP + rom.write_int32(0xDC418, 0x00000000) # NOP + + # Custom data-loading code + rom.write_int32(0x6B5028, 0x08060D70) # J 0x801835D0 + rom.write_int32s(0x1067B0, patches.custom_code_loader) + + # Custom remote item rewarding and DeathLink receiving code + rom.write_int32(0x19B98, 0x080FF000) # J 0x803FC000 + rom.write_int32s(0xBFC000, patches.remote_item_giver) + rom.write_int32s(0xBFE190, patches.subweapon_surface_checker) + + # Make received DeathLinks blow you to smithereens instead of kill you normally. + if options.death_link == DeathLink.option_explosive: + rom.write_int32(0x27A70, 0x10000008) # B [forward 0x08] + rom.write_int32s(0xBFC0D0, patches.deathlink_nitro_edition) + + # Set the DeathLink ROM flag if it's on at all. + if options.death_link != DeathLink.option_off: + rom.write_byte(0xBFBFDE, 0x01) + + # DeathLink counter decrementer code + rom.write_int32(0x1C340, 0x080FF8F0) # J 0x803FE3C0 + rom.write_int32s(0xBFE3C0, patches.deathlink_counter_decrementer) + rom.write_int32(0x25B6C, 0x0080FF052) # J 0x803FC148 + rom.write_int32s(0xBFC148, patches.nitro_fall_killer) + + # Death flag un-setter on "Beginning of stage" state overwrite code + rom.write_int32(0x1C2B0, 0x080FF047) # J 0x803FC11C + rom.write_int32s(0xBFC11C, patches.death_flag_unsetter) + + # Warp menu-opening code + rom.write_int32(0xB9BA8, 0x080FF099) # J 0x803FC264 + rom.write_int32s(0xBFC264, patches.warp_menu_opener) + + # NPC item textbox hack + rom.write_int32(0xBF1DC, 0x080FF904) # J 0x803FE410 + rom.write_int32(0xBF1E0, 0x27BDFFE0) # ADDIU SP, SP, -0x20 + rom.write_int32s(0xBFE410, patches.npc_item_hack) + + # Sub-weapon check function hook + rom.write_int32(0xBF32C, 0x00000000) # NOP + rom.write_int32(0xBF330, 0x080FF05E) # J 0x803FC178 + rom.write_int32s(0xBFC178, patches.give_subweapon_stopper) + + # Warp menu Special1 restriction + rom.write_int32(0xADD68, 0x0C04AB12) # JAL 0x8012AC48 + rom.write_int32s(0xADE28, patches.stage_select_overwrite) + rom.write_byte(0xADE47, s1s_per_warp) + + # Dracula's door text pointer hijack + rom.write_int32(0xD69F0, 0x080FF141) # J 0x803FC504 + rom.write_int32s(0xBFC504, patches.dracula_door_text_redirector) + + # Dracula's chamber condition + rom.write_int32(0xE2FDC, 0x0804AB25) # J 0x8012AC78 + rom.write_int32s(0xADE84, patches.special_goal_checker) + rom.write_bytes(0xBFCC48, [0xA0, 0x00, 0xFF, 0xFF, 0xA0, 0x01, 0xFF, 0xFF, 0xA0, 0x02, 0xFF, 0xFF, 0xA0, 0x03, 0xFF, + 0xFF, 0xA0, 0x04, 0xFF, 0xFF, 0xA0, 0x05, 0xFF, 0xFF, 0xA0, 0x06, 0xFF, 0xFF, 0xA0, 0x07, + 0xFF, 0xFF, 0xA0, 0x08, 0xFF, 0xFF, 0xA0, 0x09]) + if options.draculas_condition == DraculasCondition.option_crystal: + rom.write_int32(0x6C8A54, 0x0C0FF0C1) # JAL 0x803FC304 + rom.write_int32s(0xBFC304, patches.crystal_special2_giver) + rom.write_bytes(0xBFCC6E, cv64_string_to_bytearray(f"It won't budge!\n" + f"You'll need the power\n" + f"of the basement crystal\n" + f"to undo the seal.", True)) + special2_name = "Crystal " + special2_text = "The crystal is on!\n" \ + "Time to teach the old man\n" \ + "a lesson!" + elif options.draculas_condition == DraculasCondition.option_bosses: + rom.write_int32(0xBBD50, 0x080FF18C) # J 0x803FC630 + rom.write_int32s(0xBFC630, patches.boss_special2_giver) + rom.write_int32s(0xBFC55C, patches.werebull_flag_unsetter_special2_electric_boogaloo) + rom.write_bytes(0xBFCC6E, cv64_string_to_bytearray(f"It won't budge!\n" + f"You'll need to defeat\n" + f"{required_s2s} powerful monsters\n" + f"to undo the seal.", True)) + special2_name = "Trophy " + special2_text = f"Proof you killed a powerful\n" \ + f"Night Creature. Earn {required_s2s}/{total_s2s}\n" \ + f"to battle Dracula." + elif options.draculas_condition == DraculasCondition.option_specials: + special2_name = "Special2" + rom.write_bytes(0xBFCC6E, cv64_string_to_bytearray(f"It won't budge!\n" + f"You'll need to find\n" + f"{required_s2s} Special2 jewels\n" + f"to undo the seal.", True)) + special2_text = f"Need {required_s2s}/{total_s2s} to kill Dracula.\n" \ + f"Looking closely, you see...\n" \ + f"a piece of him within?" + else: + rom.write_byte(0xADE8F, 0x00) + special2_name = "Special2" + special2_text = "If you're reading this,\n" \ + "how did you get a Special2!?" + rom.write_byte(0xADE8F, required_s2s) + # Change the Special2 name depending on the setting. + rom.write_bytes(0xEFD4E, cv64_string_to_bytearray(special2_name)) + # Change the Special1 and 2 menu descriptions to tell you how many you need to unlock a warp and fight Dracula + # respectively. + special_text_bytes = cv64_string_to_bytearray(f"{s1s_per_warp} per warp unlock.\n" + f"{options.total_special1s.value} exist in total.\n" + f"Z + R + START to warp.") + cv64_string_to_bytearray(special2_text) + rom.write_bytes(0xBFE53C, special_text_bytes) + + # On-the-fly TLB script modifier + rom.write_int32s(0xBFC338, patches.double_component_checker) + rom.write_int32s(0xBFC3D4, patches.downstairs_seal_checker) + rom.write_int32s(0xBFE074, patches.mandragora_with_nitro_setter) + rom.write_int32s(0xBFC700, patches.overlay_modifiers) + + # On-the-fly actor data modifier hook + rom.write_int32(0xEAB04, 0x080FF21E) # J 0x803FC878 + rom.write_int32s(0xBFC870, patches.map_data_modifiers) + + # Fix to make flags apply to freestanding invisible items properly + rom.write_int32(0xA84F8, 0x90CC0039) # LBU T4, 0x0039 (A2) + + # Fix locked doors to check the key counters instead of their vanilla key locations' bitflags + # Pickup flag check modifications: + rom.write_int32(0x10B2D8, 0x00000002) # Left Tower Door + rom.write_int32(0x10B2F0, 0x00000003) # Storeroom Door + rom.write_int32(0x10B2FC, 0x00000001) # Archives Door + rom.write_int32(0x10B314, 0x00000004) # Maze Gate + rom.write_int32(0x10B350, 0x00000005) # Copper Door + rom.write_int32(0x10B3A4, 0x00000006) # Torture Chamber Door + rom.write_int32(0x10B3B0, 0x00000007) # ToE Gate + rom.write_int32(0x10B3BC, 0x00000008) # Science Door1 + rom.write_int32(0x10B3C8, 0x00000009) # Science Door2 + rom.write_int32(0x10B3D4, 0x0000000A) # Science Door3 + rom.write_int32(0x6F0094, 0x0000000B) # CT Door 1 + rom.write_int32(0x6F00A4, 0x0000000C) # CT Door 2 + rom.write_int32(0x6F00B4, 0x0000000D) # CT Door 3 + # Item counter decrement check modifications: + rom.write_int32(0xEDA84, 0x00000001) # Archives Door + rom.write_int32(0xEDA8C, 0x00000002) # Left Tower Door + rom.write_int32(0xEDA94, 0x00000003) # Storeroom Door + rom.write_int32(0xEDA9C, 0x00000004) # Maze Gate + rom.write_int32(0xEDAA4, 0x00000005) # Copper Door + rom.write_int32(0xEDAAC, 0x00000006) # Torture Chamber Door + rom.write_int32(0xEDAB4, 0x00000007) # ToE Gate + rom.write_int32(0xEDABC, 0x00000008) # Science Door1 + rom.write_int32(0xEDAC4, 0x00000009) # Science Door2 + rom.write_int32(0xEDACC, 0x0000000A) # Science Door3 + rom.write_int32(0xEDAD4, 0x0000000B) # CT Door 1 + rom.write_int32(0xEDADC, 0x0000000C) # CT Door 2 + rom.write_int32(0xEDAE4, 0x0000000D) # CT Door 3 + + # Fix ToE gate's "unlocked" flag in the locked door flags table + rom.write_int16(0x10B3B6, 0x0001) + + rom.write_int32(0x10AB2C, 0x8015FBD4) # Maze Gates' check code pointer adjustments + rom.write_int32(0x10AB40, 0x8015FBD4) + rom.write_int32s(0x10AB50, [0x0D0C0000, + 0x8015FBD4]) + rom.write_int32s(0x10AB64, [0x0D0C0000, + 0x8015FBD4]) + rom.write_int32s(0xE2E14, patches.normal_door_hook) + rom.write_int32s(0xBFC5D0, patches.normal_door_code) + rom.write_int32s(0x6EF298, patches.ct_door_hook) + rom.write_int32s(0xBFC608, patches.ct_door_code) + # Fix key counter not decrementing if 2 or above + rom.write_int32(0xAA0E0, 0x24020000) # ADDIU V0, R0, 0x0000 + + # Make the Easy-only candle drops in Room of Clocks appear on any difficulty + rom.write_byte(0x9B518F, 0x01) + + # Slightly move some once-invisible freestanding items to be more visible + if options.invisible_items == InvisibleItems.option_reveal_all: + rom.write_byte(0x7C7F95, 0xEF) # Forest dirge maiden statue + rom.write_byte(0x7C7FA8, 0xAB) # Forest werewolf statue + rom.write_byte(0x8099C4, 0x8C) # Villa courtyard tombstone + rom.write_byte(0x83A626, 0xC2) # Villa living room painting + # rom.write_byte(0x83A62F, 0x64) # Villa Mary's room table + rom.write_byte(0xBFCB97, 0xF5) # CC torture instrument rack + rom.write_byte(0x8C44D5, 0x22) # CC red carpet hallway knight + rom.write_byte(0x8DF57C, 0xF1) # CC cracked wall hallway flamethrower + rom.write_byte(0x90FCD6, 0xA5) # CC nitro hallway flamethrower + rom.write_byte(0x90FB9F, 0x9A) # CC invention room round machine + rom.write_byte(0x90FBAF, 0x03) # CC invention room giant famicart + rom.write_byte(0x90FE54, 0x97) # CC staircase knight (x) + rom.write_byte(0x90FE58, 0xFB) # CC staircase knight (z) + + # Change bitflag on item in upper coffin in Forest final switch gate tomb to one that's not used by something else + rom.write_int32(0x10C77C, 0x00000002) + + # Make the torch directly behind Dracula's chamber that normally doesn't set a flag set bitflag 0x08 in 0x80389BFA + rom.write_byte(0x10CE9F, 0x01) + + # Change the CC post-Behemoth boss depending on the option for Post-Behemoth Boss + if options.post_behemoth_boss == PostBehemothBoss.option_inverted: + rom.write_byte(0xEEDAD, 0x02) + rom.write_byte(0xEEDD9, 0x01) + elif options.post_behemoth_boss == PostBehemothBoss.option_always_rosa: + rom.write_byte(0xEEDAD, 0x00) + rom.write_byte(0xEEDD9, 0x03) + # Put both on the same flag so changing character won't trigger a rematch with the same boss. + rom.write_byte(0xEED8B, 0x40) + elif options.post_behemoth_boss == PostBehemothBoss.option_always_camilla: + rom.write_byte(0xEEDAD, 0x03) + rom.write_byte(0xEEDD9, 0x00) + rom.write_byte(0xEED8B, 0x40) + + # Change the RoC boss depending on the option for Room of Clocks Boss + if options.room_of_clocks_boss == RoomOfClocksBoss.option_inverted: + rom.write_byte(0x109FB3, 0x56) + rom.write_byte(0x109FBF, 0x44) + rom.write_byte(0xD9D44, 0x14) + rom.write_byte(0xD9D4C, 0x14) + elif options.room_of_clocks_boss == RoomOfClocksBoss.option_always_death: + rom.write_byte(0x109FBF, 0x44) + rom.write_byte(0xD9D45, 0x00) + # Put both on the same flag so changing character won't trigger a rematch with the same boss. + rom.write_byte(0x109FB7, 0x90) + rom.write_byte(0x109FC3, 0x90) + elif options.room_of_clocks_boss == RoomOfClocksBoss.option_always_actrise: + rom.write_byte(0x109FB3, 0x56) + rom.write_int32(0xD9D44, 0x00000000) + rom.write_byte(0xD9D4D, 0x00) + rom.write_byte(0x109FB7, 0x90) + rom.write_byte(0x109FC3, 0x90) + + # Un-nerf Actrise when playing as Reinhardt. + # This is likely a leftover TGS demo feature in which players could battle Actrise as Reinhardt. + rom.write_int32(0xB318B4, 0x240E0001) # ADDIU T6, R0, 0x0001 + + # Tunnel gondola skip + if options.skip_gondolas: + rom.write_int32(0x6C5F58, 0x080FF7D0) # J 0x803FDF40 + rom.write_int32s(0xBFDF40, patches.gondola_skipper) + # New gondola transfer point candle coordinates + rom.write_byte(0xBFC9A3, 0x04) + rom.write_bytes(0x86D824, [0x27, 0x01, 0x10, 0xF7, 0xA0]) + + # Waterway brick platforms skip + if options.skip_waterway_blocks: + rom.write_int32(0x6C7E2C, 0x00000000) # NOP + + # Ambience silencing fix + rom.write_int32(0xD9270, 0x080FF840) # J 0x803FE100 + rom.write_int32s(0xBFE100, patches.ambience_silencer) + # Fix for the door sliding sound playing infinitely if leaving the fan meeting room before the door closes entirely. + # Hooking this in the ambience silencer code does nothing for some reason. + rom.write_int32s(0xAE10C, [0x08004FAB, # J 0x80013EAC + 0x3404829B]) # ORI A0, R0, 0x829B + rom.write_int32s(0xD9E8C, [0x08004FAB, # J 0x80013EAC + 0x3404829B]) # ORI A0, R0, 0x829B + # Fan meeting room ambience fix + rom.write_int32(0x109964, 0x803FE13C) + + # Make the Villa coffin cutscene skippable + rom.write_int32(0xAA530, 0x080FF880) # J 0x803FE200 + rom.write_int32s(0xBFE200, patches.coffin_cutscene_skipper) + + # Increase shimmy speed + if options.increase_shimmy_speed: + rom.write_byte(0xA4241, 0x5A) + + # Disable landing fall damage + if options.fall_guard: + rom.write_byte(0x27B23, 0x00) + + # Enable the unused film reel effect on all cutscenes + if options.cinematic_experience: + rom.write_int32(0xAA33C, 0x240A0001) # ADDIU T2, R0, 0x0001 + rom.write_byte(0xAA34B, 0x0C) + rom.write_int32(0xAA4C4, 0x24090001) # ADDIU T1, R0, 0x0001 + + # Permanent PowerUp stuff + if options.permanent_powerups: + # Make receiving PowerUps increase the unused menu PowerUp counter instead of the one outside the save struct + rom.write_int32(0xBF2EC, 0x806B619B) # LB T3, 0x619B (V1) + rom.write_int32(0xBFC5BC, 0xA06C619B) # SB T4, 0x619B (V1) + # Make Reinhardt's whip check the menu PowerUp counter + rom.write_int32(0x69FA08, 0x80CC619B) # LB T4, 0x619B (A2) + rom.write_int32(0x69FBFC, 0x80C3619B) # LB V1, 0x619B (A2) + rom.write_int32(0x69FFE0, 0x818C9C53) # LB T4, 0x9C53 (T4) + # Make Carrie's orb check the menu PowerUp counter + rom.write_int32(0x6AC86C, 0x8105619B) # LB A1, 0x619B (T0) + rom.write_int32(0x6AC950, 0x8105619B) # LB A1, 0x619B (T0) + rom.write_int32(0x6AC99C, 0x810E619B) # LB T6, 0x619B (T0) + rom.write_int32(0x5AFA0, 0x80639C53) # LB V1, 0x9C53 (V1) + rom.write_int32(0x5B0A0, 0x81089C53) # LB T0, 0x9C53 (T0) + rom.write_byte(0x391C7, 0x00) # Prevent PowerUps from dropping from regular enemies + rom.write_byte(0xEDEDF, 0x03) # Make any vanishing PowerUps that do show up L jewels instead + # Rename the PowerUp to "PermaUp" + rom.write_bytes(0xEFDEE, cv64_string_to_bytearray("PermaUp")) + # Replace the PowerUp in the Forest Special1 Bridge 3HB rock with an L jewel if 3HBs aren't randomized + if not options.multi_hit_breakables: + rom.write_byte(0x10C7A1, 0x03) + # Change the appearance of the Pot-Pourri to that of a larger PowerUp regardless of the above setting, so other + # game PermaUps are distinguishable. + rom.write_int32s(0xEE558, [0x06005F08, 0x3FB00000, 0xFFFFFF00]) + + # Write the randomized (or disabled) music ID list and its associated code + if options.background_music: + rom.write_int32(0x14588, 0x08060D60) # J 0x80183580 + rom.write_int32(0x14590, 0x00000000) # NOP + rom.write_int32s(0x106770, patches.music_modifier) + rom.write_int32(0x15780, 0x0C0FF36E) # JAL 0x803FCDB8 + rom.write_int32s(0xBFCDB8, patches.music_comparer_modifier) + + # Enable storing item flags anywhere and changing the item model/visibility on any item instance. + rom.write_int32s(0xA857C, [0x080FF38F, # J 0x803FCE3C + 0x94D90038]) # LHU T9, 0x0038 (A2) + rom.write_int32s(0xBFCE3C, patches.item_customizer) + rom.write_int32s(0xA86A0, [0x0C0FF3AF, # JAL 0x803FCEBC + 0x95C40002]) # LHU A0, 0x0002 (T6) + rom.write_int32s(0xBFCEBC, patches.item_appearance_switcher) + rom.write_int32s(0xA8728, [0x0C0FF3B8, # JAL 0x803FCEE4 + 0x01396021]) # ADDU T4, T1, T9 + rom.write_int32s(0xBFCEE4, patches.item_model_visibility_switcher) + rom.write_int32s(0xA8A04, [0x0C0FF3C2, # JAL 0x803FCF08 + 0x018B6021]) # ADDU T4, T4, T3 + rom.write_int32s(0xBFCF08, patches.item_shine_visibility_switcher) + + # Make Axes and Crosses in AP Locations drop to their correct height, and make items with changed appearances spin + # their correct speed. + rom.write_int32s(0xE649C, [0x0C0FFA03, # JAL 0x803FE80C + 0x956C0002]) # LHU T4, 0x0002 (T3) + rom.write_int32s(0xA8B08, [0x080FFA0C, # J 0x803FE830 + 0x960A0038]) # LHU T2, 0x0038 (S0) + rom.write_int32s(0xE8584, [0x0C0FFA21, # JAL 0x803FE884 + 0x95D80000]) # LHU T8, 0x0000 (T6) + rom.write_int32s(0xE7AF0, [0x0C0FFA2A, # JAL 0x803FE8A8 + 0x958D0000]) # LHU T5, 0x0000 (T4) + rom.write_int32s(0xBFE7DC, patches.item_drop_spin_corrector) + + # Disable the 3HBs checking and setting flags when breaking them and enable their individual items checking and + # setting flags instead. + if options.multi_hit_breakables: + rom.write_int32(0xE87F8, 0x00000000) # NOP + rom.write_int16(0xE836C, 0x1000) + rom.write_int32(0xE8B40, 0x0C0FF3CD) # JAL 0x803FCF34 + rom.write_int32s(0xBFCF34, patches.three_hit_item_flags_setter) + # Villa foyer chandelier-specific functions (yeah, IDK why KCEK made different functions for this one) + rom.write_int32(0xE7D54, 0x00000000) # NOP + rom.write_int16(0xE7908, 0x1000) + rom.write_byte(0xE7A5C, 0x10) + rom.write_int32(0xE7F08, 0x0C0FF3DF) # JAL 0x803FCF7C + rom.write_int32s(0xBFCF7C, patches.chandelier_item_flags_setter) + + # New flag values to put in each 3HB vanilla flag's spot + rom.write_int32(0x10C7C8, 0x8000FF48) # FoS dirge maiden rock + rom.write_int32(0x10C7B0, 0x0200FF48) # FoS S1 bridge rock + rom.write_int32(0x10C86C, 0x0010FF48) # CW upper rampart save nub + rom.write_int32(0x10C878, 0x4000FF49) # CW Dracula switch slab + rom.write_int32(0x10CAD8, 0x0100FF49) # Tunnel twin arrows slab + rom.write_int32(0x10CAE4, 0x0004FF49) # Tunnel lonesome bucket pit rock + rom.write_int32(0x10CB54, 0x4000FF4A) # UW poison parkour ledge + rom.write_int32(0x10CB60, 0x0080FF4A) # UW skeleton crusher ledge + rom.write_int32(0x10CBF0, 0x0008FF4A) # CC Behemoth crate + rom.write_int32(0x10CC2C, 0x2000FF4B) # CC elevator pedestal + rom.write_int32(0x10CC70, 0x0200FF4B) # CC lizard locker slab + rom.write_int32(0x10CD88, 0x0010FF4B) # ToE pre-midsavepoint platforms ledge + rom.write_int32(0x10CE6C, 0x4000FF4C) # ToSci invisible bridge crate + rom.write_int32(0x10CF20, 0x0080FF4C) # CT inverted battery slab + rom.write_int32(0x10CF2C, 0x0008FF4C) # CT inverted door slab + rom.write_int32(0x10CF38, 0x8000FF4D) # CT final room door slab + rom.write_int32(0x10CF44, 0x1000FF4D) # CT Renon slab + rom.write_int32(0x10C908, 0x0008FF4D) # Villa foyer chandelier + rom.write_byte(0x10CF37, 0x04) # pointer for CT final room door slab item data + + # Once-per-frame gameplay checks + rom.write_int32(0x6C848, 0x080FF40D) # J 0x803FD034 + rom.write_int32(0xBFD058, 0x0801AEB5) # J 0x8006BAD4 + + # Everything related to dropping the previous sub-weapon + if options.drop_previous_sub_weapon: + rom.write_int32(0xBFD034, 0x080FF3FF) # J 0x803FCFFC + rom.write_int32(0xBFC190, 0x080FF3F2) # J 0x803FCFC8 + rom.write_int32s(0xBFCFC4, patches.prev_subweapon_spawn_checker) + rom.write_int32s(0xBFCFFC, patches.prev_subweapon_fall_checker) + rom.write_int32s(0xBFD060, patches.prev_subweapon_dropper) + + # Everything related to the Countdown counter + if options.countdown: + rom.write_int32(0xBFD03C, 0x080FF9DC) # J 0x803FE770 + rom.write_int32(0xD5D48, 0x080FF4EC) # J 0x803FD3B0 + rom.write_int32s(0xBFD3B0, patches.countdown_number_displayer) + rom.write_int32s(0xBFD6DC, patches.countdown_number_manager) + rom.write_int32s(0xBFE770, patches.countdown_demo_hider) + rom.write_int32(0xBFCE2C, 0x080FF5D2) # J 0x803FD748 + rom.write_int32s(0xBB168, [0x080FF5F4, # J 0x803FD7D0 + 0x8E020028]) # LW V0, 0x0028 (S0) + rom.write_int32s(0xBB1D0, [0x080FF5FB, # J 0x803FD7EC + 0x8E020028]) # LW V0, 0x0028 (S0) + rom.write_int32(0xBC4A0, 0x080FF5E6) # J 0x803FD798 + rom.write_int32(0xBC4C4, 0x080FF5E6) # J 0x803FD798 + rom.write_int32(0x19844, 0x080FF602) # J 0x803FD808 + # If the option is set to "all locations", count it down no matter what the item is. + if options.countdown == Countdown.option_all_locations: + rom.write_int32s(0xBFD71C, [0x01010101, 0x01010101, 0x01010101, 0x01010101, 0x01010101, 0x01010101, + 0x01010101, 0x01010101, 0x01010101, 0x01010101, 0x01010101]) + else: + # If it's majors, then insert this last minute check I threw together for the weird edge case of a CV64 ice + # trap for another CV64 player taking the form of a major. + rom.write_int32s(0xBFD788, [0x080FF717, # J 0x803FDC5C + 0x2529FFFF]) # ADDIU T1, T1, 0xFFFF + rom.write_int32s(0xBFDC5C, patches.countdown_extra_safety_check) + rom.write_int32(0xA9ECC, 0x00000000) # NOP the pointless overwrite of the item actor appearance custom value. + + # Ice Trap stuff + rom.write_int32(0x697C60, 0x080FF06B) # J 0x803FC18C + rom.write_int32(0x6A5160, 0x080FF06B) # J 0x803FC18C + rom.write_int32s(0xBFC1AC, patches.ice_trap_initializer) + rom.write_int32s(0xBFE700, patches.the_deep_freezer) + rom.write_int32s(0xB2F354, [0x3739E4C0, # ORI T9, T9, 0xE4C0 + 0x03200008, # JR T9 + 0x00000000]) # NOP + rom.write_int32s(0xBFE4C0, patches.freeze_verifier) + + # Initial Countdown numbers + rom.write_int32(0xAD6A8, 0x080FF60A) # J 0x803FD828 + rom.write_int32s(0xBFD828, patches.new_game_extras) + + # Everything related to shopsanity + if options.shopsanity: + rom.write_byte(0xBFBFDF, 0x01) + rom.write_bytes(0x103868, cv64_string_to_bytearray("Not obtained. ")) + rom.write_int32s(0xBFD8D0, patches.shopsanity_stuff) + rom.write_int32(0xBD828, 0x0C0FF643) # JAL 0x803FD90C + rom.write_int32(0xBD5B8, 0x0C0FF651) # JAL 0x803FD944 + rom.write_int32(0xB0610, 0x0C0FF665) # JAL 0x803FD994 + rom.write_int32s(0xBD24C, [0x0C0FF677, # J 0x803FD9DC + 0x00000000]) # NOP + rom.write_int32(0xBD618, 0x0C0FF684) # JAL 0x803FDA10 + + shopsanity_name_text = [] + shopsanity_desc_text = [] + for i in range(len(shop_name_list)): + shopsanity_name_text += bytearray([0xA0, i]) + shop_colors_list[i] + \ + cv64_string_to_bytearray(cv64_text_truncate(shop_name_list[i], 74)) + + shopsanity_desc_text += [0xA0, i] + if shop_desc_list[i][1] is not None: + shopsanity_desc_text += cv64_string_to_bytearray("For " + shop_desc_list[i][1] + ".\n", + append_end=False) + shopsanity_desc_text += cv64_string_to_bytearray(renon_item_dialogue[shop_desc_list[i][0]]) + rom.write_bytes(0x1AD00, shopsanity_name_text) + rom.write_bytes(0x1A800, shopsanity_desc_text) + + # Panther Dash running + if options.panther_dash: + rom.write_int32(0x69C8C4, 0x0C0FF77E) # JAL 0x803FDDF8 + rom.write_int32(0x6AA228, 0x0C0FF77E) # JAL 0x803FDDF8 + rom.write_int32s(0x69C86C, [0x0C0FF78E, # JAL 0x803FDE38 + 0x3C01803E]) # LUI AT, 0x803E + rom.write_int32s(0x6AA1D0, [0x0C0FF78E, # JAL 0x803FDE38 + 0x3C01803E]) # LUI AT, 0x803E + rom.write_int32(0x69D37C, 0x0C0FF79E) # JAL 0x803FDE78 + rom.write_int32(0x6AACE0, 0x0C0FF79E) # JAL 0x803FDE78 + rom.write_int32s(0xBFDDF8, patches.panther_dash) + # Jump prevention + if options.panther_dash == PantherDash.option_jumpless: + rom.write_int32(0xBFDE2C, 0x080FF7BB) # J 0x803FDEEC + rom.write_int32(0xBFD044, 0x080FF7B1) # J 0x803FDEC4 + rom.write_int32s(0x69B630, [0x0C0FF7C6, # JAL 0x803FDF18 + 0x8CCD0000]) # LW T5, 0x0000 (A2) + rom.write_int32s(0x6A8EC0, [0x0C0FF7C6, # JAL 0x803FDF18 + 0x8CCC0000]) # LW T4, 0x0000 (A2) + # Fun fact: KCEK put separate code to handle coyote time jumping + rom.write_int32s(0x69910C, [0x0C0FF7C6, # JAL 0x803FDF18 + 0x8C4E0000]) # LW T6, 0x0000 (V0) + rom.write_int32s(0x6A6718, [0x0C0FF7C6, # JAL 0x803FDF18 + 0x8C4E0000]) # LW T6, 0x0000 (V0) + rom.write_int32s(0xBFDEC4, patches.panther_jump_preventer) + + # Everything related to Big Toss. + if options.big_toss: + rom.write_int32s(0x27E90, [0x0C0FFA38, # JAL 0x803FE8E0 + 0xAFB80074]) # SW T8, 0x0074 (SP) + rom.write_int32(0x26F54, 0x0C0FFA4D) # JAL 0x803FE934 + rom.write_int32s(0xBFE8E0, patches.big_tosser) + + # Write all the new randomized bytes. + for offset, item_id in offset_data.items(): + if item_id <= 0xFF: + rom.write_byte(offset, item_id) + elif item_id <= 0xFFFF: + rom.write_int16(offset, item_id) + elif item_id <= 0xFFFFFF: + rom.write_int24(offset, item_id) + else: + rom.write_int32(offset, item_id) + + # Write the secondary name the client will use to distinguish a vanilla ROM from an AP one. + rom.write_bytes(0xBFBFD0, "ARCHIPELAGO1".encode("utf-8")) + # Write the slot authentication + rom.write_bytes(0xBFBFE0, world.auth) + + # Write the specified window colors + rom.write_byte(0xAEC23, options.window_color_r.value << 4) + rom.write_byte(0xAEC33, options.window_color_g.value << 4) + rom.write_byte(0xAEC47, options.window_color_b.value << 4) + rom.write_byte(0xAEC43, options.window_color_a.value << 4) + + # Write the item/player names for other game items + for loc in active_locations: + if loc.address is None or get_location_info(loc.name, "type") == "shop" or loc.item.player == player: + continue + if len(loc.item.name) > 67: + item_name = loc.item.name[0x00:0x68] + else: + item_name = loc.item.name + inject_address = 0xBB7164 + (256 * (loc.address & 0xFFF)) + wrapped_name, num_lines = cv64_text_wrap(item_name + "\nfor " + multiworld.get_player_name(loc.item.player), 96) + rom.write_bytes(inject_address, get_item_text_color(loc) + cv64_string_to_bytearray(wrapped_name)) + rom.write_byte(inject_address + 255, num_lines) + + # Everything relating to loading the other game items text + rom.write_int32(0xA8D8C, 0x080FF88F) # J 0x803FE23C + rom.write_int32(0xBEA98, 0x0C0FF8B4) # JAL 0x803FE2D0 + rom.write_int32(0xBEAB0, 0x0C0FF8BD) # JAL 0x803FE2F8 + rom.write_int32(0xBEACC, 0x0C0FF8C5) # JAL 0x803FE314 + rom.write_int32s(0xBFE23C, patches.multiworld_item_name_loader) + rom.write_bytes(0x10F188, [0x00 for _ in range(264)]) + rom.write_bytes(0x10F298, [0x00 for _ in range(264)]) + + # When the game normally JALs to the item prepare textbox function after the player picks up an item, set the + # "no receiving" timer to ensure the item textbox doesn't freak out if you pick something up while there's a queue + # of unreceived items. + rom.write_int32(0xA8D94, 0x0C0FF9F0) # JAL 0x803FE7C0 + rom.write_int32s(0xBFE7C0, [0x3C088039, # LUI T0, 0x8039 + 0x24090020, # ADDIU T1, R0, 0x0020 + 0x0804EDCE, # J 0x8013B738 + 0xA1099BE0]) # SB T1, 0x9BE0 (T0) + + +class CV64DeltaPatch(APDeltaPatch): + hash = CV64US10HASH + patch_file_ending: str = ".apcv64" + result_file_ending: str = ".z64" + + game = "Castlevania 64" + + @classmethod + def get_source_data(cls) -> bytes: + return get_base_rom_bytes() + + def patch(self, target: str): + super().patch(target) + rom = LocalRom(target) + + # Extract the item models file, decompress it, append the AP icons, compress it back, re-insert it. + items_file = lzkn64.decompress_buffer(rom.read_bytes(0x9C5310, 0x3D28)) + compressed_file = lzkn64.compress_buffer(items_file[0:0x69B6] + pkgutil.get_data(__name__, "data/ap_icons.bin")) + rom.write_bytes(0xBB2D88, compressed_file) + # Update the items' Nisitenma-Ichigo table entry to point to the new file's start and end addresses in the ROM. + rom.write_int32s(0x95F04, [0x80BB2D88, 0x00BB2D88 + len(compressed_file)]) + # Update the items' decompressed file size tables with the new file's decompressed file size. + rom.write_int16(0x95706, 0x7BF0) + rom.write_int16(0x104CCE, 0x7BF0) + # Update the Wooden Stake and Roses' item appearance settings table to point to the Archipelago item graphics. + rom.write_int16(0xEE5BA, 0x7B38) + rom.write_int16(0xEE5CA, 0x7280) + # Change the items' sizes. The progression one will be larger than the non-progression one. + rom.write_int32(0xEE5BC, 0x3FF00000) + rom.write_int32(0xEE5CC, 0x3FA00000) + rom.write_to_file(target) + + +def get_base_rom_bytes(file_name: str = "") -> bytes: + base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) + if not base_rom_bytes: + file_name = get_base_rom_path(file_name) + base_rom_bytes = bytes(open(file_name, "rb").read()) + + basemd5 = hashlib.md5() + basemd5.update(base_rom_bytes) + if CV64US10HASH != basemd5.hexdigest(): + raise Exception("Supplied Base Rom does not match known MD5 for Castlevania 64 US 1.0." + "Get the correct game and version, then dump it.") + setattr(get_base_rom_bytes, "base_rom_bytes", base_rom_bytes) + return base_rom_bytes + + +def get_base_rom_path(file_name: str = "") -> str: + if not file_name: + file_name = get_settings()["cv64_options"]["rom_file"] + if not os.path.exists(file_name): + file_name = Utils.user_path(file_name) + return file_name diff --git a/worlds/cv64/rules.py b/worlds/cv64/rules.py new file mode 100644 index 0000000000..9642073341 --- /dev/null +++ b/worlds/cv64/rules.py @@ -0,0 +1,103 @@ +from typing import Dict, TYPE_CHECKING + +from BaseClasses import CollectionState +from worlds.generic.Rules import allow_self_locking_items, CollectionRule +from .options import DraculasCondition +from .entrances import get_entrance_info +from .data import iname, rname + +if TYPE_CHECKING: + from . import CV64World + + +class CV64Rules: + player: int + world: "CV64World" + rules: Dict[str, CollectionRule] + s1s_per_warp: int + required_s2s: int + drac_condition: int + + def __init__(self, world: "CV64World") -> None: + self.player = world.player + self.world = world + self.s1s_per_warp = world.s1s_per_warp + self.required_s2s = world.required_s2s + self.drac_condition = world.drac_condition + + self.rules = { + iname.left_tower_key: lambda state: state.has(iname.left_tower_key, self.player), + iname.storeroom_key: lambda state: state.has(iname.storeroom_key, self.player), + iname.archives_key: lambda state: state.has(iname.archives_key, self.player), + iname.garden_key: lambda state: state.has(iname.garden_key, self.player), + iname.copper_key: lambda state: state.has(iname.copper_key, self.player), + iname.chamber_key: lambda state: state.has(iname.chamber_key, self.player), + "Bomb 1": lambda state: state.has_all({iname.magical_nitro, iname.mandragora}, self.player), + "Bomb 2": lambda state: state.has(iname.magical_nitro, self.player, 2) + and state.has(iname.mandragora, self.player, 2), + iname.execution_key: lambda state: state.has(iname.execution_key, self.player), + iname.science_key1: lambda state: state.has(iname.science_key1, self.player), + iname.science_key2: lambda state: state.has(iname.science_key2, self.player), + iname.science_key3: lambda state: state.has(iname.science_key3, self.player), + iname.clocktower_key1: lambda state: state.has(iname.clocktower_key1, self.player), + iname.clocktower_key2: lambda state: state.has(iname.clocktower_key2, self.player), + iname.clocktower_key3: lambda state: state.has(iname.clocktower_key3, self.player), + "Dracula": self.can_enter_dracs_chamber + } + + def can_enter_dracs_chamber(self, state: CollectionState) -> bool: + drac_object_name = None + if self.drac_condition == DraculasCondition.option_crystal: + drac_object_name = "Crystal" + elif self.drac_condition == DraculasCondition.option_bosses: + drac_object_name = "Trophy" + elif self.drac_condition == DraculasCondition.option_specials: + drac_object_name = "Special2" + + if drac_object_name is not None: + return state.has(drac_object_name, self.player, self.required_s2s) + return True + + def set_cv64_rules(self) -> None: + multiworld = self.world.multiworld + + for region in multiworld.get_regions(self.player): + # Set each entrance's rule if it should have one. + # Warp entrances have their own special handling. + for entrance in region.entrances: + if entrance.parent_region.name == "Menu": + if entrance.name.startswith("Warp "): + entrance.access_rule = lambda state, warp_num=int(entrance.name[5]): \ + state.has(iname.special_one, self.player, self.s1s_per_warp * warp_num) + else: + ent_rule = get_entrance_info(entrance.name, "rule") + if ent_rule in self.rules: + entrance.access_rule = self.rules[ent_rule] + + multiworld.completion_condition[self.player] = lambda state: state.has(iname.victory, self.player) + if self.world.options.accessibility: # not locations accessibility + self.set_self_locking_items() + + def set_self_locking_items(self) -> None: + multiworld = self.world.multiworld + + # Do the regions that we know for a fact always exist, and we always do no matter what. + allow_self_locking_items(multiworld.get_region(rname.villa_archives, self.player), iname.archives_key) + allow_self_locking_items(multiworld.get_region(rname.cc_torture_chamber, self.player), iname.chamber_key) + + # Add this region if the world doesn't have the Villa Storeroom warp entrance. + if "Villa" not in self.world.active_warp_list[1:]: + allow_self_locking_items(multiworld.get_region(rname.villa_storeroom, self.player), iname.storeroom_key) + + # Add this region if Hard Logic is on and Multi Hit Breakables are off. + if self.world.options.hard_logic and not self.world.options.multi_hit_breakables: + allow_self_locking_items(multiworld.get_region(rname.cw_ltower, self.player), iname.left_tower_key) + + # Add these regions if Tower of Science is in the world. + if "Tower of Science" in self.world.active_stage_exits: + allow_self_locking_items(multiworld.get_region(rname.tosci_three_doors, self.player), iname.science_key1) + allow_self_locking_items(multiworld.get_region(rname.tosci_key3, self.player), iname.science_key3) + + # Add this region if Tower of Execution is in the world and Hard Logic is not on. + if "Tower of Execution" in self.world.active_stage_exits and self.world.options.hard_logic: + allow_self_locking_items(multiworld.get_region(rname.toe_ledge, self.player), iname.execution_key) diff --git a/worlds/cv64/src/drop_sub_weapon.c b/worlds/cv64/src/drop_sub_weapon.c new file mode 100644 index 0000000000..d1a4b52269 --- /dev/null +++ b/worlds/cv64/src/drop_sub_weapon.c @@ -0,0 +1,69 @@ +// Written by Moisés +#include "include/game/module.h" +#include "include/game/math.h" +#include "cv64.h" + +extern vec3f player_pos; +extern vec3s player_angle; // player_angle.y = Player's facing angle (yaw) +extern f32 player_height_with_respect_of_floor; // Stored negative in-game + +#define SHT_MAX 32767.0f +#define SHT_MINV (1.0f / SHT_MAX) + +void spawn_item_behind_player(s32 item) { + interactuablesModule* pickable_item = NULL; + const f32 spawnDistance = 8.0f; + vec3f player_backwards_dir; + + pickable_item = (interactuablesModule*)module_createAndSetChild(moduleList_findFirstModuleByID(ACTOR_CREATOR), ACTOR_ITEM); + if (pickable_item != NULL) { + // Convert facing angle to a vec3f + // SHT_MINV needs to be negative here for the item to be spawned properly on the character's back + player_backwards_dir.x = coss(-player_angle.y) * -SHT_MINV; + player_backwards_dir.z = sins(-player_angle.y) * -SHT_MINV; + // Multiply facing vector with distance away from the player + vec3f_multiplyScalar(&player_backwards_dir, &player_backwards_dir, spawnDistance); + // Assign the position of the item relative to the player's current position. + vec3f_add(&pickable_item->position, &player_pos, &player_backwards_dir); + // The Y position of the item will be the same as the floor right under the player + // The player's height with respect of the flower under them is already stored negative in-game, + // so no need to substract + pickable_item->position.y = player_pos.y + 5.0f; + pickable_item->height = pickable_item->position.y; + + // Assign item ID + pickable_item->item_ID = item; + } +} + + +const f32 droppingAccel = 0.05f; +const f32 maxDroppingSpeed = 1.5f; +f32 droppingSpeed = 0.0f; +f32 droppingTargetYPos = 0.0f; +u8 dropItemCalcFuncCalled = FALSE; + +s32 drop_item_calc(interactuablesModule* pickable_item) { + if (dropItemCalcFuncCalled == FALSE) { + droppingTargetYPos = player_pos.y + player_height_with_respect_of_floor + 1.0f; + if (pickable_item->item_ID == CROSS || pickable_item->item_ID == AXE || + pickable_item->item_ID == CROSS__VANISH || pickable_item->item_ID == AXE__VANISH) { + droppingTargetYPos += 3.0f; + } + dropItemCalcFuncCalled = TRUE; + return TRUE; + } + if (pickable_item->position.y <= droppingTargetYPos) { + droppingSpeed = 0.0f; + dropItemCalcFuncCalled = FALSE; + return FALSE; + } + else { + if (droppingSpeed < maxDroppingSpeed) { + droppingSpeed += droppingAccel; + } + pickable_item->position.y -= droppingSpeed; + pickable_item->height = pickable_item->position.y; + return TRUE; + } +} \ No newline at end of file diff --git a/worlds/cv64/src/print.c b/worlds/cv64/src/print.c new file mode 100644 index 0000000000..7f77afb00f --- /dev/null +++ b/worlds/cv64/src/print.c @@ -0,0 +1,116 @@ +// Written by Moisés. +// NOTE: This is an earlier version to-be-replaced. +#include +#include + +// Helper function +// https://decomp.me/scratch/9H1Uy +u32 convertUTF8StringToUTF16(char* src, u16* buffer) { + u32 string_length = 0; + + // If the source string starts with a null char (0), we assume the string empty. + if (*src != 0) { + // Copy the char from the source string into the bufferination. + // Then advance to the next char until we find the null char (0). + do { + *buffer = *src; + src++; + buffer++; + string_length++; + } while (*src != 0); + } + // Make sure to add the null char at the end of the bufferination string, + // and then return the length of the string. + *buffer = 0; + return string_length; +} + +// Begin printing ASCII text stored in a char* +textbox* print_text(const char* message, const s16 X_pos, const s16 Y_pos, const u8 number_of_lines, const s16 textbox_width, const u32 txtbox_flags, const void* module) { + textbox* (*ptr_textbox_create)(void*, void*, u32) = textbox_create; + void (*ptr_textbox_setPos)(textbox*, u16, u16, s32) = textbox_setPos; + void (*ptr_textbox_setDimensions)(textbox*, s8, s16, u8, u8) = textbox_setDimensions; + void (*ptr_textbox_setMessagePtr)(textbox*, u16*, s32, s16) = textbox_setMessagePtr; + u16* (*ptr_convertUTF16ToCustomTextFormat)(u16*) = convertUTF16ToCustomTextFormat; + void* (*ptr_malloc)(s32, u32) = malloc; + + textbox* txtbox = NULL; + + // Allocate memory for the text buffer + u16* text_buffer = (u16*) ptr_malloc(0, 100); + + // Create the textbox data structure + if (module != NULL) { + txtbox = ptr_textbox_create(module, HUD_camera, txtbox_flags); + } + + if (txtbox != NULL && text_buffer != NULL && message != NULL) { + // Set text position and dimensions + ptr_textbox_setPos(txtbox, X_pos, Y_pos, 1); + ptr_textbox_setDimensions(txtbox, number_of_lines, textbox_width, 0, 0); + + // Convert the ASCII message to the CV64 custom format + convertUTF8StringToUTF16(message, text_buffer); + ptr_convertUTF16ToCustomTextFormat(text_buffer); + + // Set the text buffer pointer to the textbox data structure + ptr_textbox_setMessagePtr(txtbox, text_buffer, 0, 0); + } + // We return the textbox so that we can modify its properties once it begins printing + // (say to show, hide the text) + return txtbox; +} + +// Begin printing signed integer +textbox* print_number(const s32 number, u16* text_buffer, const s16 X_pos, const s16 Y_pos, const u8 number_of_digits, const u32 txtbox_flags, const u32 additional_text_flag, const void* module) { + textbox* (*ptr_textbox_create)(void*, void*, u32) = textbox_create; + void (*ptr_textbox_setPos)(textbox*, u16, u16, s32) = textbox_setPos; + void (*ptr_textbox_setDimensions)(textbox*, s8, s16, u8, u8) = textbox_setDimensions; + void (*ptr_textbox_setMessagePtr)(textbox*, u16*, s32, s16) = textbox_setMessagePtr; + void (*ptr_text_convertIntNumberToText)(u32, u16*, u8, u32) = text_convertIntNumberToText; + + textbox* txtbox = NULL; + + // Create the textbox data structure + if (module != NULL) { + txtbox = ptr_textbox_create(module, HUD_camera, txtbox_flags); + } + + if (txtbox != NULL && text_buffer != NULL) { + // Set text position and dimensions + ptr_textbox_setPos(txtbox, X_pos, Y_pos, 1); + ptr_textbox_setDimensions(txtbox, 1, 100, 0, 0); + + // Convert the number to the CV64 custom format + ptr_text_convertIntNumberToText(number, text_buffer, number_of_digits, additional_text_flag); + + // Set the text buffer pointer to the textbox data structure + ptr_textbox_setMessagePtr(txtbox, text_buffer, 0, 0); + } + // We return the textbox so that we can modify its properties once it begins printing + // (say to show, hide the text) + return txtbox; +} + +// Update the value of a number that began printing after calling "print_number()" +void update_printed_number(textbox* txtbox, const s32 number, u16* text_buffer, const u8 number_of_digits, const u32 additional_text_flag) { + void (*ptr_text_convertIntNumberToText)(u32, u16*, u8, u32) = text_convertIntNumberToText; + + if (text_buffer != NULL) { + ptr_text_convertIntNumberToText(number, text_buffer, number_of_digits, additional_text_flag); + txtbox->flags |= 0x1000000; // Needed to make sure the number updates properly + } +} + +void display_text(textbox* txtbox, const u8 display_textbox) { + if (txtbox != NULL) { + if (display_textbox == TRUE) { + // Show text + txtbox->flags &= ~HIDE_TEXTBOX; + } + else { + // Hide text + txtbox->flags |= HIDE_TEXTBOX; + } + } +} diff --git a/worlds/cv64/src/print_text_ovl.c b/worlds/cv64/src/print_text_ovl.c new file mode 100644 index 0000000000..7ca8e6f35e --- /dev/null +++ b/worlds/cv64/src/print_text_ovl.c @@ -0,0 +1,26 @@ +// Written by Moisés +#include "print.h" +#include +#include + +#define counter_X_pos 30 +#define counter_Y_pos 40 +#define counter_number_of_digits 2 +#define GOLD_JEWEL_FONT 0x14 + +extern u8 bytes[13]; + +u16* number_text_buffer = NULL; +textbox* txtbox = NULL; + +void begin_print() { + // Allocate memory for the number text + number_text_buffer = (u16*) malloc(0, 12); + + // Assuming that 0x80342814 = HUD Module + txtbox = print_number(0, number_text_buffer, counter_X_pos, counter_Y_pos, counter_number_of_digits, 0x08600000, GOLD_JEWEL_FONT, (void*) 0x80342814); +} + +void update_print(u8 i) { + update_printed_number(txtbox, (s32) bytes[i], number_text_buffer, counter_number_of_digits, GOLD_JEWEL_FONT); +} diff --git a/worlds/cv64/stages.py b/worlds/cv64/stages.py new file mode 100644 index 0000000000..a6fa667921 --- /dev/null +++ b/worlds/cv64/stages.py @@ -0,0 +1,490 @@ +import logging + +from .data import rname +from .regions import get_region_info +from .locations import get_location_info +from .options import WarpOrder + +from typing import TYPE_CHECKING, Dict, List, Tuple, Union + +if TYPE_CHECKING: + from . import CV64World + + +# # # KEY # # # +# "start region" = The Region that the start of the stage is in. Used for connecting the previous stage's end and +# alternate end (if it exists) Entrances to the start of the next one. +# "start map id" = The map ID that the start of the stage is in. +# "start spawn id" = The player spawn location ID for the start of the stage. This and "start map id" are both written +# to the previous stage's end loading zone to make it send the player to the next stage in the +# world's determined stage order. +# "mid region" = The Region that the stage's middle warp point is in. Used for connecting the warp Entrances after the +# starting stage to where they should be connecting to. +# "mid map id" = The map ID that the stage's middle warp point is in. +# "mid spawn id" = The player spawn location ID for the stage's middle warp point. This and "mid map id" are both +# written to the warp menu code to make it send the player to where it should be sending them. +# "end region" = The Region that the end of the stage is in. Used for connecting the next stage's beginning Entrance +# (if it exists) to the end of the previous one. +# "end map id" = The map ID that the end of the stage is in. +# "end spawn id" = The player spawn location ID for the end of the stage. This and "end map id" are both written to the +# next stage's beginning loading zone (if it exists) to make it send the player to the previous stage +# in the world's determined stage order. +# startzone map offset = The offset in the ROM to overwrite to change where the start of the stage leads. +# startzone spawn offset = The offset in the ROM to overwrite to change what spawn location in the previous map the +# start of the stage puts the player at. +# endzone map offset = The offset in the ROM to overwrite to change where the end of the stage leads. +# endzone spawn offset = The offset in the ROM to overwrite to change what spawn location in the next map the end of +# the stage puts the player at. +# altzone map offset = The offset in the ROM to overwrite to change where the alternate end of the stage leads +# (if it exists). +# altzone spawn offset = The offset in the ROM to overwrite to change what spawn location in the next map the alternate +# end of the stage puts the player at. +# character = What character that stage is exclusively meant for normally. Used in determining what stages to leave out +# depending on what character stage setting was chosen in the player options. +# save number offsets = The offsets to overwrite to change what stage number is displayed on the save file when saving +# at the stage's White Jewels. +# regions = All Regions that make up the stage. If the stage is in the world's active stages, its Regions and their +# corresponding Locations and Entrances will all be created. +stage_info = { + "Forest of Silence": { + "start region": rname.forest_start, "start map id": 0x00, "start spawn id": 0x00, + "mid region": rname.forest_mid, "mid map id": 0x00, "mid spawn id": 0x04, + "end region": rname.forest_end, "end map id": 0x00, "end spawn id": 0x01, + "endzone map offset": 0xB6302F, "endzone spawn offset": 0xB6302B, + "save number offsets": [0x1049C5, 0x1049CD, 0x1049D5], + "regions": [rname.forest_start, + rname.forest_mid, + rname.forest_end] + }, + + "Castle Wall": { + "start region": rname.cw_start, "start map id": 0x02, "start spawn id": 0x00, + "mid region": rname.cw_start, "mid map id": 0x02, "mid spawn id": 0x07, + "end region": rname.cw_exit, "end map id": 0x02, "end spawn id": 0x10, + "endzone map offset": 0x109A5F, "endzone spawn offset": 0x109A61, + "save number offsets": [0x1049DD, 0x1049E5, 0x1049ED], + "regions": [rname.cw_start, + rname.cw_exit, + rname.cw_ltower] + }, + + "Villa": { + "start region": rname.villa_start, "start map id": 0x03, "start spawn id": 0x00, + "mid region": rname.villa_storeroom, "mid map id": 0x05, "mid spawn id": 0x04, + "end region": rname.villa_crypt, "end map id": 0x1A, "end spawn id": 0x03, + "endzone map offset": 0xD9DA3, "endzone spawn offset": 0x109E81, + "altzone map offset": 0xD9DAB, "altzone spawn offset": 0x109E81, + "save number offsets": [0x1049F5, 0x1049FD, 0x104A05, 0x104A0D], + "regions": [rname.villa_start, + rname.villa_main, + rname.villa_storeroom, + rname.villa_archives, + rname.villa_maze, + rname.villa_servants, + rname.villa_crypt] + }, + + "Tunnel": { + "start region": rname.tunnel_start, "start map id": 0x07, "start spawn id": 0x00, + "mid region": rname.tunnel_end, "mid map id": 0x07, "mid spawn id": 0x03, + "end region": rname.tunnel_end, "end map id": 0x07, "end spawn id": 0x11, + "endzone map offset": 0x109B4F, "endzone spawn offset": 0x109B51, "character": "Reinhardt", + "save number offsets": [0x104A15, 0x104A1D, 0x104A25, 0x104A2D], + "regions": [rname.tunnel_start, + rname.tunnel_end] + }, + + "Underground Waterway": { + "start region": rname.uw_main, "start map id": 0x08, "start spawn id": 0x00, + "mid region": rname.uw_main, "mid map id": 0x08, "mid spawn id": 0x03, + "end region": rname.uw_end, "end map id": 0x08, "end spawn id": 0x01, + "endzone map offset": 0x109B67, "endzone spawn offset": 0x109B69, "character": "Carrie", + "save number offsets": [0x104A35, 0x104A3D], + "regions": [rname.uw_main, + rname.uw_end] + }, + + "Castle Center": { + "start region": rname.cc_main, "start map id": 0x19, "start spawn id": 0x00, + "mid region": rname.cc_main, "mid map id": 0x0E, "mid spawn id": 0x03, + "end region": rname.cc_elev_top, "end map id": 0x0F, "end spawn id": 0x02, + "endzone map offset": 0x109CB7, "endzone spawn offset": 0x109CB9, + "altzone map offset": 0x109CCF, "altzone spawn offset": 0x109CD1, + "save number offsets": [0x104A45, 0x104A4D, 0x104A55, 0x104A5D, 0x104A65, 0x104A6D, 0x104A75], + "regions": [rname.cc_main, + rname.cc_torture_chamber, + rname.cc_library, + rname.cc_crystal, + rname.cc_elev_top] + }, + + "Duel Tower": { + "start region": rname.dt_main, "start map id": 0x13, "start spawn id": 0x00, + "startzone map offset": 0x109DA7, "startzone spawn offset": 0x109DA9, + "mid region": rname.dt_main, "mid map id": 0x13, "mid spawn id": 0x15, + "end region": rname.dt_main, "end map id": 0x13, "end spawn id": 0x01, + "endzone map offset": 0x109D8F, "endzone spawn offset": 0x109D91, "character": "Reinhardt", + "save number offsets": [0x104ACD], + "regions": [rname.dt_main] + }, + + "Tower of Execution": { + "start region": rname.toe_main, "start map id": 0x10, "start spawn id": 0x00, + "startzone map offset": 0x109D17, "startzone spawn offset": 0x109D19, + "mid region": rname.toe_main, "mid map id": 0x10, "mid spawn id": 0x02, + "end region": rname.toe_main, "end map id": 0x10, "end spawn id": 0x12, + "endzone map offset": 0x109CFF, "endzone spawn offset": 0x109D01, "character": "Reinhardt", + "save number offsets": [0x104A7D, 0x104A85], + "regions": [rname.toe_main, + rname.toe_ledge] + }, + + "Tower of Science": { + "start region": rname.tosci_start, "start map id": 0x12, "start spawn id": 0x00, + "startzone map offset": 0x109D77, "startzone spawn offset": 0x109D79, + "mid region": rname.tosci_conveyors, "mid map id": 0x12, "mid spawn id": 0x03, + "end region": rname.tosci_conveyors, "end map id": 0x12, "end spawn id": 0x04, + "endzone map offset": 0x109D5F, "endzone spawn offset": 0x109D61, "character": "Carrie", + "save number offsets": [0x104A95, 0x104A9D, 0x104AA5], + "regions": [rname.tosci_start, + rname.tosci_three_doors, + rname.tosci_conveyors, + rname.tosci_key3] + }, + + "Tower of Sorcery": { + "start region": rname.tosor_main, "start map id": 0x11, "start spawn id": 0x00, + "startzone map offset": 0x109D47, "startzone spawn offset": 0x109D49, + "mid region": rname.tosor_main, "mid map id": 0x11, "mid spawn id": 0x01, + "end region": rname.tosor_main, "end map id": 0x11, "end spawn id": 0x13, + "endzone map offset": 0x109D2F, "endzone spawn offset": 0x109D31, "character": "Carrie", + "save number offsets": [0x104A8D], + "regions": [rname.tosor_main] + }, + + "Room of Clocks": { + "start region": rname.roc_main, "start map id": 0x1B, "start spawn id": 0x00, + "mid region": rname.roc_main, "mid map id": 0x1B, "mid spawn id": 0x02, + "end region": rname.roc_main, "end map id": 0x1B, "end spawn id": 0x14, + "endzone map offset": 0x109EAF, "endzone spawn offset": 0x109EB1, + "save number offsets": [0x104AC5], + "regions": [rname.roc_main] + }, + + "Clock Tower": { + "start region": rname.ct_start, "start map id": 0x17, "start spawn id": 0x00, + "mid region": rname.ct_middle, "mid map id": 0x17, "mid spawn id": 0x02, + "end region": rname.ct_end, "end map id": 0x17, "end spawn id": 0x03, + "endzone map offset": 0x109E37, "endzone spawn offset": 0x109E39, + "save number offsets": [0x104AB5, 0x104ABD], + "regions": [rname.ct_start, + rname.ct_middle, + rname.ct_end] + }, + + "Castle Keep": { + "start region": rname.ck_main, "start map id": 0x14, "start spawn id": 0x02, + "mid region": rname.ck_main, "mid map id": 0x14, "mid spawn id": 0x03, + "end region": rname.ck_drac_chamber, + "save number offsets": [0x104AAD], + "regions": [rname.ck_main] + }, +} + +vanilla_stage_order = ("Forest of Silence", "Castle Wall", "Villa", "Tunnel", "Underground Waterway", "Castle Center", + "Duel Tower", "Tower of Execution", "Tower of Science", "Tower of Sorcery", "Room of Clocks", + "Clock Tower", "Castle Keep") + +# # # KEY # # # +# "prev" = The previous stage in the line. +# "next" = The next stage in the line. +# "alt" = The alternate next stage in the line (if one exists). +# "position" = The stage's number in the order of stages. +# "path" = Character indicating whether the stage is on the main path or an alternate path, similar to Rondo of Blood. +# Used in writing the randomized stage order to the spoiler. +vanilla_stage_exits = {rname.forest_of_silence: {"prev": None, "next": rname.castle_wall, + "alt": None, "position": 1, "path": " "}, + rname.castle_wall: {"prev": None, "next": rname.villa, + "alt": None, "position": 2, "path": " "}, + rname.villa: {"prev": None, "next": rname.tunnel, + "alt": rname.underground_waterway, "position": 3, "path": " "}, + rname.tunnel: {"prev": None, "next": rname.castle_center, + "alt": None, "position": 4, "path": " "}, + rname.underground_waterway: {"prev": None, "next": rname.castle_center, + "alt": None, "position": 4, "path": "'"}, + rname.castle_center: {"prev": None, "next": rname.duel_tower, + "alt": rname.tower_of_science, "position": 5, "path": " "}, + rname.duel_tower: {"prev": rname.castle_center, "next": rname.tower_of_execution, + "alt": None, "position": 6, "path": " "}, + rname.tower_of_execution: {"prev": rname.duel_tower, "next": rname.room_of_clocks, + "alt": None, "position": 7, "path": " "}, + rname.tower_of_science: {"prev": rname.castle_center, "next": rname.tower_of_sorcery, + "alt": None, "position": 6, "path": "'"}, + rname.tower_of_sorcery: {"prev": rname.tower_of_science, "next": rname.room_of_clocks, + "alt": None, "position": 7, "path": "'"}, + rname.room_of_clocks: {"prev": None, "next": rname.clock_tower, + "alt": None, "position": 8, "path": " "}, + rname.clock_tower: {"prev": None, "next": rname.castle_keep, + "alt": None, "position": 9, "path": " "}, + rname.castle_keep: {"prev": None, "next": None, + "alt": None, "position": 10, "path": " "}} + + +def get_stage_info(stage: str, info: str) -> Union[str, int, Union[List[int], List[str]], None]: + return stage_info[stage].get(info, None) + + +def get_locations_from_stage(stage: str) -> List[str]: + overall_locations = [] + for region in get_stage_info(stage, "regions"): + stage_locations = get_region_info(region, "locations") + if stage_locations is not None: + overall_locations += stage_locations + + final_locations = [] + for loc in overall_locations: + if get_location_info(loc, "code") is not None: + final_locations.append(loc) + return final_locations + + +def verify_character_stage(world: "CV64World", stage: str) -> bool: + # Verify a character stage is in the world if the given stage is a character stage. + stage_char = get_stage_info(stage, "character") + return stage_char is None or (world.reinhardt_stages and stage_char == "Reinhardt") or \ + (world.carrie_stages and stage_char == "Carrie") + + +def get_normal_stage_exits(world: "CV64World") -> Dict[str, dict]: + exits = {name: vanilla_stage_exits[name].copy() for name in vanilla_stage_exits} + non_branching_pos = 1 + + for stage in stage_info: + # Remove character stages that are not enabled. + if not verify_character_stage(world, stage): + del exits[stage] + continue + + # If branching pathways are not enabled, update the exit info to converge said stages on a single path. + if world.branching_stages: + continue + if world.carrie_stages and not world.reinhardt_stages and exits[stage]["alt"] is not None: + exits[stage]["next"] = exits[stage]["alt"] + elif world.carrie_stages and world.reinhardt_stages and stage != rname.castle_keep: + exits[stage]["next"] = vanilla_stage_order[vanilla_stage_order.index(stage) + 1] + exits[stage]["alt"] = None + exits[stage]["position"] = non_branching_pos + exits[stage]["path"] = " " + non_branching_pos += 1 + + return exits + + +def shuffle_stages(world: "CV64World", stage_1_blacklist: List[str]) \ + -> Tuple[Dict[str, Dict[str, Union[str, int, None]]], str, List[str]]: + """Woah, this is a lot! I should probably summarize what's happening in here, huh? + + So, in the vanilla game, all the stages are basically laid out on a linear "timeline" with some stages being + different depending on who you are playing as. The different character stages, in question, are the one following + Villa and the two following Castle Center. The ends of these two stages are considered the route divergences and, in + this rando, the game's behavior has been changed in such that both characters can access each other's exclusive + stages (thereby making the entire game playable in just one character run). With this in mind, when shuffling the + stages around, there is one particularly big rule that must be kept in mind to ensure things don't get too wacky. + That being: + + Villa and Castle Center cannot appear in branching path stage slots; they can only be on "main" path slots. + + So for this reason, generating a new stage layout is not as simple as just scrambling a list of stages around. It + must be done in such a way that whatever stages directly follow Villa or CC is not the other stage. The exception is + if branching stages are not a thing at all due to the player settings, in which case everything I said above does + not matter. Consider the following representation of a stage "timeline", wherein each "-" represents a main stage + and a "=" represents a pair of branching stages: + + -==---=--- + + In the above example, CC is the first "-" and Villa is the fourth. CC and Villa can only be "-"s whereas every other + stage can be literally anywhere, including on one of the "=" dashes. Villa will always be followed by one pair of + branching stages and CC will be followed by two pairs. + + This code starts by first generating a singular list of stages that fit the criteria of Castle Center not being in + the next two entries following Villa and Villa not being in the next four entries after Castle Center. Once that has + been figured out, it will then generate a dictionary of stages with the appropriate information regarding what + stages come before and after them to then be used for Entrance creation as well as what position in the list they + are in for the purposes of the spoiler log and extended hint information. + + I opted to use the Rondo of Blood "'" stage notation to represent Carrie stage slots specifically. If a main stage + with a backwards connection connects backwards into a pair of branching stages, it will be the non-"'" stage + (Reinhardt's) that it connects to. The Carrie stage slot cannot be accessed this way. + + If anyone has any ideas or suggestions on how to improve this, I'd love to hear them! Because it's only going to get + uglier come Legacy of Darkness and Cornell's funny side route later on. + """ + + starting_stage_value = world.options.starting_stage.value + + # Verify the starting stage is valid. If it isn't, pick a stage at random. + if vanilla_stage_order[starting_stage_value] not in stage_1_blacklist and \ + verify_character_stage(world, vanilla_stage_order[starting_stage_value]): + starting_stage = vanilla_stage_order[starting_stage_value] + else: + logging.warning(f"[{world.multiworld.player_name[world.player]}] {vanilla_stage_order[starting_stage_value]} " + f"cannot be the starting stage with the chosen settings. Picking a different stage instead...") + possible_stages = [] + for stage in vanilla_stage_order: + if stage in world.active_stage_exits and stage != rname.castle_keep: + possible_stages.append(stage) + starting_stage = world.random.choice(possible_stages) + world.options.starting_stage.value = vanilla_stage_order.index(starting_stage) + + remaining_stage_pool = [stage for stage in world.active_stage_exits] + remaining_stage_pool.remove(rname.castle_keep) + + total_stages = len(remaining_stage_pool) + + new_stage_order = [] + villa_cc_ids = [2, 3] + alt_villa_stage = [] + alt_cc_stages = [] + + # If there are branching stages, remove Villa and CC from the list and determine their placements first. + if world.branching_stages: + villa_cc_ids = world.random.sample(range(1, 5), 2) + remaining_stage_pool.remove(rname.villa) + remaining_stage_pool.remove(rname.castle_center) + + # Remove the starting stage from the remaining pool if it's in there at this point. + if starting_stage in remaining_stage_pool: + remaining_stage_pool.remove(starting_stage) + + # If Villa or CC is our starting stage, force its respective ID to be 0 and re-randomize the other. + if starting_stage == rname.villa: + villa_cc_ids[0] = 0 + villa_cc_ids[1] = world.random.randint(1, 5) + elif starting_stage == rname.castle_center: + villa_cc_ids[1] = 0 + villa_cc_ids[0] = world.random.randint(1, 5) + + for i in range(total_stages): + # If we're on Villa or CC's ID while in branching stage mode, put the respective stage in the slot. + if world.branching_stages and i == villa_cc_ids[0] and rname.villa not in new_stage_order: + new_stage_order.append(rname.villa) + villa_cc_ids[1] += 2 + elif world.branching_stages and i == villa_cc_ids[1] and rname.castle_center not in new_stage_order: + new_stage_order.append(rname.castle_center) + villa_cc_ids[0] += 4 + else: + # If neither of the above are true, if we're looking at Stage 1, append the starting stage. + # Otherwise, draw a random stage from the active list and delete it from there. + if i == 0: + new_stage_order.append(starting_stage) + else: + new_stage_order.append(world.random.choice(remaining_stage_pool)) + remaining_stage_pool.remove(new_stage_order[i]) + + # If we're looking at an alternate stage slot, put the stage in one of these lists to indicate it as such + if not world.branching_stages: + continue + if i - 2 >= 0: + if new_stage_order[i - 2] == rname.villa: + alt_villa_stage.append(new_stage_order[i]) + if i - 3 >= 0: + if new_stage_order[i - 3] == rname.castle_center: + alt_cc_stages.append(new_stage_order[i]) + if i - 4 >= 0: + if new_stage_order[i - 4] == rname.castle_center: + alt_cc_stages.append(new_stage_order[i]) + + new_stage_order.append(rname.castle_keep) + + # Update the dictionary of stage exits + current_stage_number = 1 + for i in range(len(new_stage_order)): + # Stage position number and alternate path indicator + world.active_stage_exits[new_stage_order[i]]["position"] = current_stage_number + if new_stage_order[i] in alt_villa_stage + alt_cc_stages: + world.active_stage_exits[new_stage_order[i]]["path"] = "'" + else: + world.active_stage_exits[new_stage_order[i]]["path"] = " " + + # Previous stage + if world.active_stage_exits[new_stage_order[i]]["prev"]: + if i - 1 < 0: + world.active_stage_exits[new_stage_order[i]]["prev"] = "Menu" + elif world.branching_stages: + if new_stage_order[i - 1] == alt_villa_stage[0] or new_stage_order[i] == alt_villa_stage[0]: + world.active_stage_exits[new_stage_order[i]]["prev"] = new_stage_order[i - 2] + elif new_stage_order[i - 1] == alt_cc_stages[1] or new_stage_order[i] == alt_cc_stages[0]: + world.active_stage_exits[new_stage_order[i]]["prev"] = new_stage_order[i - 3] + else: + world.active_stage_exits[new_stage_order[i]]["prev"] = new_stage_order[i - 1] + else: + world.active_stage_exits[new_stage_order[i]]["prev"] = new_stage_order[i - 1] + + # Next stage + if world.active_stage_exits[new_stage_order[i]]["next"]: + if world.branching_stages: + if new_stage_order[i + 1] == alt_villa_stage[0]: + world.active_stage_exits[new_stage_order[i]]["next"] = new_stage_order[i + 2] + current_stage_number -= 1 + elif new_stage_order[i + 1] == alt_cc_stages[0]: + world.active_stage_exits[new_stage_order[i]]["next"] = new_stage_order[i + 3] + current_stage_number -= 2 + else: + world.active_stage_exits[new_stage_order[i]]["next"] = new_stage_order[i + 1] + else: + world.active_stage_exits[new_stage_order[i]]["next"] = new_stage_order[i + 1] + + # Alternate next stage + if world.active_stage_exits[new_stage_order[i]]["alt"]: + if world.branching_stages: + if new_stage_order[i] == rname.villa: + world.active_stage_exits[new_stage_order[i]]["alt"] = alt_villa_stage[0] + else: + world.active_stage_exits[new_stage_order[i]]["alt"] = alt_cc_stages[0] + else: + world.active_stage_exits[new_stage_order[i]]["alt"] = None + + current_stage_number += 1 + + return world.active_stage_exits, starting_stage, new_stage_order + + +def generate_warps(world: "CV64World") -> List[str]: + # Create a list of warps from the active stage list. They are in a random order by default and will never + # include the starting stage. + possible_warps = [stage for stage in world.active_stage_list] + + # Remove the starting stage from the possible warps. + del (possible_warps[0]) + + active_warp_list = world.random.sample(possible_warps, 7) + + if world.options.warp_order == WarpOrder.option_seed_stage_order: + # Arrange the warps to be in the seed's stage order + new_list = world.active_stage_list.copy() + for warp in world.active_stage_list: + if warp not in active_warp_list: + new_list.remove(warp) + active_warp_list = new_list + elif world.options.warp_order == WarpOrder.option_vanilla_stage_order: + # Arrange the warps to be in the vanilla game's stage order + new_list = list(vanilla_stage_order) + for warp in vanilla_stage_order: + if warp not in active_warp_list: + new_list.remove(warp) + active_warp_list = new_list + + # Insert the starting stage at the start of the warp list + active_warp_list.insert(0, world.active_stage_list[0]) + + return active_warp_list + + +def get_region_names(active_stage_exits: Dict[str, Dict[str, Union[str, int, None]]]) -> List[str]: + region_names = [] + for stage in active_stage_exits: + stage_regions = get_stage_info(stage, "regions") + for region in stage_regions: + region_names.append(region) + + return region_names diff --git a/worlds/cv64/test/__init__.py b/worlds/cv64/test/__init__.py new file mode 100644 index 0000000000..2d09e27cb3 --- /dev/null +++ b/worlds/cv64/test/__init__.py @@ -0,0 +1,6 @@ +from test.bases import WorldTestBase + + +class CV64TestBase(WorldTestBase): + game = "Castlevania 64" + player: int = 1 diff --git a/worlds/cv64/test/test_access.py b/worlds/cv64/test/test_access.py new file mode 100644 index 0000000000..79b1e14e11 --- /dev/null +++ b/worlds/cv64/test/test_access.py @@ -0,0 +1,250 @@ +from . import CV64TestBase + + +class WarpTest(CV64TestBase): + options = { + "special1s_per_warp": 3, + "total_special1s": 21 + } + + def test_warps(self) -> None: + for i in range(1, 8): + self.assertFalse(self.can_reach_entrance(f"Warp {i}")) + self.collect([self.get_item_by_name("Special1")] * 2) + self.assertFalse(self.can_reach_entrance(f"Warp {i}")) + self.collect([self.get_item_by_name("Special1")] * 1) + self.assertTrue(self.can_reach_entrance(f"Warp {i}")) + + +class CastleWallTest(CV64TestBase): + options = { + "stage_shuffle": True, + "starting_stage": 1 + } + + def test_doors(self) -> None: + self.assertFalse(self.can_reach_entrance(f"Left Tower door")) + self.collect([self.get_item_by_name("Left Tower Key")] * 1) + self.assertTrue(self.can_reach_entrance(f"Left Tower door")) + + +class VillaTest(CV64TestBase): + options = { + "stage_shuffle": True, + "starting_stage": 2 + } + + def test_doors(self) -> None: + self.assertFalse(self.can_reach_entrance("To Storeroom door")) + self.collect([self.get_item_by_name("Storeroom Key")] * 1) + self.assertTrue(self.can_reach_entrance("To Storeroom door")) + self.assertFalse(self.can_reach_entrance("To Archives door")) + self.collect([self.get_item_by_name("Archives Key")] * 1) + self.assertTrue(self.can_reach_entrance("To Archives door")) + self.assertFalse(self.can_reach_entrance("To maze gate")) + self.assertFalse(self.can_reach_entrance("Copper door")) + self.collect([self.get_item_by_name("Garden Key")] * 1) + self.assertTrue(self.can_reach_entrance("To maze gate")) + self.assertFalse(self.can_reach_entrance("Copper door")) + self.collect([self.get_item_by_name("Copper Key")] * 1) + self.assertTrue(self.can_reach_entrance("Copper door")) + + +class CastleCenterTest(CV64TestBase): + options = { + "stage_shuffle": True, + "starting_stage": 5 + } + + def test_doors(self) -> None: + self.assertFalse(self.can_reach_entrance("Torture Chamber door")) + self.collect([self.get_item_by_name("Chamber Key")] * 1) + self.assertTrue(self.can_reach_entrance("Torture Chamber door")) + self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall")) + self.assertFalse(self.can_reach_entrance("Upper cracked wall")) + self.collect([self.get_item_by_name("Magical Nitro")] * 1) + self.assertFalse(self.can_reach_entrance("Upper cracked wall")) + self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall")) + self.collect([self.get_item_by_name("Mandragora")] * 1) + self.assertTrue(self.can_reach_entrance("Upper cracked wall")) + self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall")) + self.collect([self.get_item_by_name("Magical Nitro")] * 1) + self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall")) + self.collect([self.get_item_by_name("Mandragora")] * 1) + self.assertTrue(self.can_reach_entrance("Upper cracked wall")) + + +class ExecutionTest(CV64TestBase): + options = { + "stage_shuffle": True, + "starting_stage": 7 + } + + def test_doors(self) -> None: + self.assertFalse(self.can_reach_entrance("Execution gate")) + self.collect([self.get_item_by_name("Execution Key")] * 1) + self.assertTrue(self.can_reach_entrance("Execution gate")) + + +class ScienceTest(CV64TestBase): + options = { + "stage_shuffle": True, + "starting_stage": 8 + } + + def test_doors(self) -> None: + self.assertFalse(self.can_reach_entrance("Science Door 1")) + self.collect([self.get_item_by_name("Science Key1")] * 1) + self.assertTrue(self.can_reach_entrance("Science Door 1")) + self.assertFalse(self.can_reach_entrance("To Science Door 2")) + self.assertFalse(self.can_reach_entrance("Science Door 3")) + self.collect([self.get_item_by_name("Science Key2")] * 1) + self.assertTrue(self.can_reach_entrance("To Science Door 2")) + self.assertFalse(self.can_reach_entrance("Science Door 3")) + self.collect([self.get_item_by_name("Science Key3")] * 1) + self.assertTrue(self.can_reach_entrance("Science Door 3")) + + +class ClocktowerTest(CV64TestBase): + options = { + "stage_shuffle": True, + "starting_stage": 11 + } + + def test_doors(self) -> None: + self.assertFalse(self.can_reach_entrance("To Clocktower Door 1")) + self.assertFalse(self.can_reach_entrance("To Clocktower Door 2")) + self.assertFalse(self.can_reach_entrance("Clocktower Door 3")) + self.collect([self.get_item_by_name("Clocktower Key1")] * 1) + self.assertTrue(self.can_reach_entrance("To Clocktower Door 1")) + self.assertFalse(self.can_reach_entrance("To Clocktower Door 2")) + self.assertFalse(self.can_reach_entrance("Clocktower Door 3")) + self.collect([self.get_item_by_name("Clocktower Key2")] * 1) + self.assertTrue(self.can_reach_entrance("To Clocktower Door 2")) + self.assertFalse(self.can_reach_entrance("Clocktower Door 3")) + self.collect([self.get_item_by_name("Clocktower Key3")] * 1) + self.assertTrue(self.can_reach_entrance("Clocktower Door 3")) + + +class DraculaNoneTest(CV64TestBase): + options = { + "draculas_condition": 0, + "stage_shuffle": True, + "starting_stage": 5, + } + + def test_dracula_none_condition(self) -> None: + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.collect([self.get_item_by_name("Left Tower Key"), + self.get_item_by_name("Garden Key"), + self.get_item_by_name("Copper Key"), + self.get_item_by_name("Science Key1"), + self.get_item_by_name("Science Key2"), + self.get_item_by_name("Science Key3"), + self.get_item_by_name("Clocktower Key1"), + self.get_item_by_name("Clocktower Key2"), + self.get_item_by_name("Clocktower Key3")] * 1) + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.collect([self.get_item_by_name("Special1")] * 7) + self.assertTrue(self.can_reach_entrance("Dracula's door")) + + +class DraculaSpecialTest(CV64TestBase): + options = { + "draculas_condition": 3 + } + + def test_dracula_special_condition(self) -> None: + self.assertFalse(self.can_reach_entrance("Clocktower Door 3")) + self.collect([self.get_item_by_name("Left Tower Key"), + self.get_item_by_name("Garden Key"), + self.get_item_by_name("Copper Key"), + self.get_item_by_name("Magical Nitro"), + self.get_item_by_name("Mandragora"), + self.get_item_by_name("Clocktower Key1"), + self.get_item_by_name("Clocktower Key2"), + self.get_item_by_name("Clocktower Key3")] * 2) + self.assertTrue(self.can_reach_entrance("Clocktower Door 3")) + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.collect([self.get_item_by_name("Special2")] * 19) + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.collect([self.get_item_by_name("Special2")] * 1) + self.assertTrue(self.can_reach_entrance("Dracula's door")) + + +class DraculaCrystalTest(CV64TestBase): + options = { + "draculas_condition": 1, + "stage_shuffle": True, + "starting_stage": 5, + "hard_logic": True + } + + def test_dracula_crystal_condition(self) -> None: + self.assertFalse(self.can_reach_entrance("Slope Jump to boss tower")) + self.collect([self.get_item_by_name("Left Tower Key"), + self.get_item_by_name("Garden Key"), + self.get_item_by_name("Copper Key"), + self.get_item_by_name("Science Key1"), + self.get_item_by_name("Science Key2"), + self.get_item_by_name("Science Key3"), + self.get_item_by_name("Clocktower Key1"), + self.get_item_by_name("Clocktower Key2"), + self.get_item_by_name("Clocktower Key3")] * 1) + self.assertFalse(self.can_reach_entrance("Slope Jump to boss tower")) + self.collect([self.get_item_by_name("Special1")] * 7) + self.assertTrue(self.can_reach_entrance("Slope Jump to boss tower")) + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.collect([self.get_item_by_name("Magical Nitro"), + self.get_item_by_name("Mandragora")] * 1) + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall")) + self.collect([self.get_item_by_name("Magical Nitro"), + self.get_item_by_name("Mandragora")] * 1) + self.assertTrue(self.can_reach_entrance("Lower sealed cracked wall")) + self.assertTrue(self.can_reach_entrance("Dracula's door")) + + +class DraculaBossTest(CV64TestBase): + options = { + "draculas_condition": 2, + "stage_shuffle": True, + "starting_stage": 5, + "hard_logic": True, + "bosses_required": 16 + } + + def test_dracula_boss_condition(self) -> None: + self.assertFalse(self.can_reach_entrance("Slope Jump to boss tower")) + self.collect([self.get_item_by_name("Left Tower Key"), + self.get_item_by_name("Garden Key"), + self.get_item_by_name("Copper Key"), + self.get_item_by_name("Science Key1"), + self.get_item_by_name("Science Key2"), + self.get_item_by_name("Science Key3"), + self.get_item_by_name("Clocktower Key1"), + self.get_item_by_name("Clocktower Key2"), + self.get_item_by_name("Clocktower Key3")] * 1) + self.assertFalse(self.can_reach_entrance("Slope Jump to boss tower")) + self.collect([self.get_item_by_name("Special1")] * 7) + self.assertTrue(self.can_reach_entrance("Slope Jump to boss tower")) + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.collect([self.get_item_by_name("Magical Nitro"), + self.get_item_by_name("Mandragora")] * 1) + self.assertFalse(self.can_reach_entrance("Dracula's door")) + self.assertFalse(self.can_reach_entrance("Lower sealed cracked wall")) + self.collect([self.get_item_by_name("Magical Nitro"), + self.get_item_by_name("Mandragora")] * 1) + self.assertTrue(self.can_reach_entrance("Lower sealed cracked wall")) + self.assertTrue(self.can_reach_entrance("Dracula's door")) + + +class LizardTest(CV64TestBase): + options = { + "stage_shuffle": True, + "draculas_condition": 2, + "starting_stage": 4 + } + + def test_lizard_man_trio(self) -> None: + self.assertTrue(self.can_reach_location("Underground Waterway: Lizard-man trio")) diff --git a/worlds/cv64/text.py b/worlds/cv64/text.py new file mode 100644 index 0000000000..76ffaf1f7d --- /dev/null +++ b/worlds/cv64/text.py @@ -0,0 +1,95 @@ +from typing import Tuple + +cv64_char_dict = {"\n": (0x01, 0), " ": (0x02, 4), "!": (0x03, 2), '"': (0x04, 5), "#": (0x05, 6), "$": (0x06, 5), + "%": (0x07, 8), "&": (0x08, 7), "'": (0x09, 4), "(": (0x0A, 3), ")": (0x0B, 3), "*": (0x0C, 4), + "+": (0x0D, 5), ",": (0x0E, 3), "-": (0x0F, 4), ".": (0x10, 3), "/": (0x11, 6), "0": (0x12, 5), + "1": (0x13, 3), "2": (0x14, 5), "3": (0x15, 4), "4": (0x16, 5), "5": (0x17, 5), "6": (0x18, 5), + "7": (0x19, 5), "8": (0x1A, 5), "9": (0x1B, 5), ":": (0x1C, 3), ";": (0x1D, 3), "<": (0x1E, 3), + "=": (0x1F, 4), ">": (0x20, 3), "?": (0x21, 5), "@": (0x22, 8), "A": (0x23, 7), "B": (0x24, 6), + "C": (0x25, 5), "D": (0x26, 7), "E": (0x27, 5), "F": (0x28, 6), "G": (0x29, 6), "H": (0x2A, 7), + "I": (0x2B, 3), "J": (0x2C, 3), "K": (0x2D, 6), "L": (0x2E, 6), "M": (0x2F, 8), "N": (0x30, 7), + "O": (0x31, 6), "P": (0x32, 6), "Q": (0x33, 8), "R": (0x34, 6), "S": (0x35, 5), "T": (0x36, 6), + "U": (0x37, 6), "V": (0x38, 7), "W": (0x39, 8), "X": (0x3A, 6), "Y": (0x3B, 7), "Z": (0x3C, 6), + "[": (0x3D, 3), "\\": (0x3E, 6), "]": (0x3F, 3), "^": (0x40, 6), "_": (0x41, 5), "a": (0x43, 5), + "b": (0x44, 6), "c": (0x45, 4), "d": (0x46, 6), "e": (0x47, 5), "f": (0x48, 5), "g": (0x49, 5), + "h": (0x4A, 6), "i": (0x4B, 3), "j": (0x4C, 3), "k": (0x4D, 6), "l": (0x4E, 3), "m": (0x4F, 8), + "n": (0x50, 6), "o": (0x51, 5), "p": (0x52, 5), "q": (0x53, 5), "r": (0x54, 4), "s": (0x55, 4), + "t": (0x56, 4), "u": (0x57, 5), "v": (0x58, 6), "w": (0x59, 8), "x": (0x5A, 5), "y": (0x5B, 5), + "z": (0x5C, 4), "{": (0x5D, 4), "|": (0x5E, 2), "}": (0x5F, 3), "`": (0x61, 4), "「": (0x62, 3), + "」": (0x63, 3), "~": (0x65, 3), "″": (0x72, 3), "°": (0x73, 3), "∞": (0x74, 8)} +# [0] = CV64's in-game ID for that text character. +# [1] = How much space towards the in-game line length limit it contributes. + + +def cv64_string_to_bytearray(cv64text: str, a_advance: bool = False, append_end: bool = True) -> bytearray: + """Converts a string into a bytearray following CV64's string format.""" + text_bytes = bytearray(0) + for i, char in enumerate(cv64text): + if char == "\t": + text_bytes.extend([0xFF, 0xFF]) + else: + if char in cv64_char_dict: + text_bytes.extend([0x00, cv64_char_dict[char][0]]) + else: + text_bytes.extend([0x00, 0x41]) + + if a_advance: + text_bytes.extend([0xA3, 0x00]) + if append_end: + text_bytes.extend([0xFF, 0xFF]) + return text_bytes + + +def cv64_text_truncate(cv64text: str, textbox_len_limit: int) -> str: + """Truncates a string at a given in-game text line length.""" + line_len = 0 + + for i in range(len(cv64text)): + line_len += cv64_char_dict[cv64text[i]][1] + + if line_len > textbox_len_limit: + return cv64text[0x00:i] + + return cv64text + + +def cv64_text_wrap(cv64text: str, textbox_len_limit: int) -> Tuple[str, int]: + """Rebuilds a string with some of its spaces replaced with newlines to ensure the text wraps properly in an in-game + textbox of a given length.""" + words = cv64text.split(" ") + new_text = "" + line_len = 0 + num_lines = 1 + + for i in range(len(words)): + word_len = 0 + word_divider = " " + + if line_len != 0: + line_len += 4 + else: + word_divider = "" + + for char in words[i]: + if char in cv64_char_dict: + line_len += cv64_char_dict[char][1] + word_len += cv64_char_dict[char][1] + else: + line_len += 5 + word_len += 5 + + if word_len > textbox_len_limit or char in ["\n", "\t"]: + word_len = 0 + line_len = 0 + if num_lines < 4: + num_lines += 1 + + if line_len > textbox_len_limit: + word_divider = "\n" + line_len = word_len + if num_lines < 4: + num_lines += 1 + + new_text += word_divider + words[i] + + return new_text, num_lines From 12864f7b24028fa56135e599f0fe1642c9d2d377 Mon Sep 17 00:00:00 2001 From: chandler05 <66492208+chandler05@users.noreply.github.com> Date: Wed, 20 Mar 2024 22:44:09 +0100 Subject: [PATCH 159/166] A Short Hike: Implement New Game (#2577) --- README.md | 1 + docs/CODEOWNERS | 3 + worlds/shorthike/Items.py | 62 ++ worlds/shorthike/Locations.py | 709 +++++++++++++++++++++++ worlds/shorthike/Options.py | 73 +++ worlds/shorthike/Rules.py | 73 +++ worlds/shorthike/__init__.py | 136 +++++ worlds/shorthike/docs/en_A Short Hike.md | 30 + worlds/shorthike/docs/setup_en.md | 32 + 9 files changed, 1119 insertions(+) create mode 100644 worlds/shorthike/Items.py create mode 100644 worlds/shorthike/Locations.py create mode 100644 worlds/shorthike/Options.py create mode 100644 worlds/shorthike/Rules.py create mode 100644 worlds/shorthike/__init__.py create mode 100644 worlds/shorthike/docs/en_A Short Hike.md create mode 100644 worlds/shorthike/docs/setup_en.md diff --git a/README.md b/README.md index 2c0c164b53..18b1651bb0 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ Currently, the following games are supported: * Celeste 64 * Zork Grand Inquisitor * Castlevania 64 +* A Short Hike 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 9c801f04af..6440735662 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -134,6 +134,9 @@ # Shivers /worlds/shivers/ @GodlFire +# A Short Hike +/worlds/shorthike/ @chandler05 + # Sonic Adventure 2 Battle /worlds/sa2b/ @PoryGone @RaspberrySpace diff --git a/worlds/shorthike/Items.py b/worlds/shorthike/Items.py new file mode 100644 index 0000000000..a240dcbc6a --- /dev/null +++ b/worlds/shorthike/Items.py @@ -0,0 +1,62 @@ +from BaseClasses import ItemClassification +from typing import TypedDict, Dict, List, Set + +class ItemDict(TypedDict): + name: str + id: int + count: int + classification: ItemClassification + +base_id = 82000 + +item_table: List[ItemDict] = [ + {"name": "Stick", "id": base_id + 1, "count": 8, "classification": ItemClassification.progression_skip_balancing}, + {"name": "Seashell", "id": base_id + 2, "count": 23, "classification": ItemClassification.progression_skip_balancing}, + {"name": "Golden Feather", "id": base_id + 3, "count": 0, "classification": ItemClassification.progression}, + {"name": "Silver Feather", "id": base_id + 4, "count": 0, "classification": ItemClassification.useful}, + {"name": "Bucket", "id": base_id + 5, "count": 0, "classification": ItemClassification.progression}, + {"name": "Bait", "id": base_id + 6, "count": 2, "classification": ItemClassification.filler}, + {"name": "Fishing Rod", "id": base_id + 7, "count": 2, "classification": ItemClassification.progression}, + {"name": "Shovel", "id": base_id + 8, "count": 1, "classification": ItemClassification.progression}, + {"name": "Toy Shovel", "id": base_id + 9, "count": 5, "classification": ItemClassification.progression_skip_balancing}, + {"name": "Compass", "id": base_id + 10, "count": 1, "classification": ItemClassification.useful}, + {"name": "Medal", "id": base_id + 11, "count": 3, "classification": ItemClassification.filler}, + {"name": "Shell Necklace", "id": base_id + 12, "count": 1, "classification": ItemClassification.progression}, + {"name": "Wristwatch", "id": base_id + 13, "count": 1, "classification": ItemClassification.progression}, + {"name": "Motorboat Key", "id": base_id + 14, "count": 1, "classification": ItemClassification.progression}, + {"name": "Pickaxe", "id": base_id + 15, "count": 3, "classification": ItemClassification.useful}, + {"name": "Fishing Journal", "id": base_id + 16, "count": 1, "classification": ItemClassification.useful}, + {"name": "A Stormy View Map", "id": base_id + 17, "count": 1, "classification": ItemClassification.filler}, + {"name": "The King Map", "id": base_id + 18, "count": 1, "classification": ItemClassification.filler}, + {"name": "The Treasure of Sid Beach Map", "id": base_id + 19, "count": 1, "classification": ItemClassification.filler}, + {"name": "In Her Shadow Map", "id": base_id + 20, "count": 1, "classification": ItemClassification.filler}, + {"name": "Sunhat", "id": base_id + 21, "count": 1, "classification": ItemClassification.filler}, + {"name": "Baseball Cap", "id": base_id + 22, "count": 1, "classification": ItemClassification.filler}, + {"name": "Provincial Park Hat", "id": base_id + 23, "count": 1, "classification": ItemClassification.filler}, + {"name": "Headband", "id": base_id + 24, "count": 1, "classification": ItemClassification.progression}, + {"name": "Running Shoes", "id": base_id + 25, "count": 1, "classification": ItemClassification.useful}, + {"name": "Camping Permit", "id": base_id + 26, "count": 1, "classification": ItemClassification.progression}, + {"name": "Walkie Talkie", "id": base_id + 27, "count": 1, "classification": ItemClassification.useful}, + + # Not in the item pool for now + #{"name": "Boating Manual", "id": base_id + ~, "count": 1, "classification": ItemClassification.filler}, + + # Different Coin Amounts (Fillers) + {"name": "7 Coins", "id": base_id + 28, "count": 3, "classification": ItemClassification.filler}, + {"name": "15 Coins", "id": base_id + 29, "count": 1, "classification": ItemClassification.filler}, + {"name": "18 Coins", "id": base_id + 30, "count": 1, "classification": ItemClassification.filler}, + {"name": "21 Coins", "id": base_id + 31, "count": 2, "classification": ItemClassification.filler}, + {"name": "25 Coins", "id": base_id + 32, "count": 7, "classification": ItemClassification.filler}, + {"name": "27 Coins", "id": base_id + 33, "count": 1, "classification": ItemClassification.filler}, + {"name": "32 Coins", "id": base_id + 34, "count": 1, "classification": ItemClassification.filler}, + {"name": "33 Coins", "id": base_id + 35, "count": 6, "classification": ItemClassification.filler}, + {"name": "50 Coins", "id": base_id + 36, "count": 1, "classification": ItemClassification.filler}, + + # Filler item determined by settings + {"name": "13 Coins", "id": base_id + 37, "count": 0, "classification": ItemClassification.filler}, +] + +group_table: Dict[str, Set[str]] = { + "Coins": {"7 Coins", "13 Coins", "15 Coins", "18 Coins", "21 Coins", "25 Coins", "27 Coins", "32 Coins", "33 Coins", "50 Coins"}, + "Maps": {"A Stormy View Map", "The King Map", "The Treasure of Sid Beach Map", "In Her Shadow Map"}, +} diff --git a/worlds/shorthike/Locations.py b/worlds/shorthike/Locations.py new file mode 100644 index 0000000000..c2d316c686 --- /dev/null +++ b/worlds/shorthike/Locations.py @@ -0,0 +1,709 @@ +from typing import List, TypedDict + +class LocationInfo(TypedDict): + name: str + id: int + inGameId: str + needsShovel: bool + purchase: bool + minGoldenFeathers: int + minGoldenFeathersEasy: int + minGoldenFeathersBucket: int + +base_id = 83000 + +location_table: List[LocationInfo] = [ + # Original Seashell Locations + {"name": "Start Beach Seashell", + "id": base_id + 1, + "inGameId": "PickUps.3", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Beach Hut Seashell", + "id": base_id + 2, + "inGameId": "PickUps.2", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Beach Umbrella Seashell", + "id": base_id + 3, + "inGameId": "PickUps.8", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Sid Beach Mound Seashell", + "id": base_id + 4, + "inGameId": "PickUps.12", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Sid Beach Seashell", + "id": base_id + 5, + "inGameId": "PickUps.11", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Shirley's Point Beach Seashell", + "id": base_id + 6, + "inGameId": "PickUps.18", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Shirley's Point Rock Seashell", + "id": base_id + 7, + "inGameId": "PickUps.17", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Visitor's Center Beach Seashell", + "id": base_id + 8, + "inGameId": "PickUps.19", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "West River Seashell", + "id": base_id + 9, + "inGameId": "PickUps.10", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "West Riverbank Seashell", + "id": base_id + 10, + "inGameId": "PickUps.4", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Stone Tower Riverbank Seashell", + "id": base_id + 11, + "inGameId": "PickUps.23", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "North Beach Seashell", + "id": base_id + 12, + "inGameId": "PickUps.6", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "North Coast Seashell", + "id": base_id + 13, + "inGameId": "PickUps.7", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Boat Cliff Seashell", + "id": base_id + 14, + "inGameId": "PickUps.14", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Boat Isle Mound Seashell", + "id": base_id + 15, + "inGameId": "PickUps.22", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "East Coast Seashell", + "id": base_id + 16, + "inGameId": "PickUps.21", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "House North Beach Seashell", + "id": base_id + 17, + "inGameId": "PickUps.16", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Airstream Island North Seashell", + "id": base_id + 18, + "inGameId": "PickUps.13", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Airstream Island South Seashell", + "id": base_id + 19, + "inGameId": "PickUps.15", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Secret Island Beach Seashell", + "id": base_id + 20, + "inGameId": "PickUps.1", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Meteor Lake Seashell", + "id": base_id + 126, + "inGameId": "PickUps.20", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Good Creek Path Seashell", + "id": base_id + 127, + "inGameId": "PickUps.9", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + + # Visitor's Center Shop + {"name": "Visitor's Center Shop Golden Feather 1", + "id": base_id + 21, + "inGameId": "CampRangerNPC[0]", + "needsShovel": False, "purchase": True, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Visitor's Center Shop Golden Feather 2", + "id": base_id + 22, + "inGameId": "CampRangerNPC[1]", + "needsShovel": False, "purchase": True, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Visitor's Center Shop Hat", + "id": base_id + 23, + "inGameId": "CampRangerNPC[9]", + "needsShovel": False, "purchase": True, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Tough Bird Salesman + {"name": "Tough Bird Salesman Golden Feather 1", + "id": base_id + 24, + "inGameId": "ToughBirdNPC (1)[0]", + "needsShovel": False, "purchase": True, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Tough Bird Salesman Golden Feather 2", + "id": base_id + 25, + "inGameId": "ToughBirdNPC (1)[1]", + "needsShovel": False, "purchase": True, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Tough Bird Salesman Golden Feather 3", + "id": base_id + 26, + "inGameId": "ToughBirdNPC (1)[2]", + "needsShovel": False, "purchase": True, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Tough Bird Salesman Golden Feather 4", + "id": base_id + 27, + "inGameId": "ToughBirdNPC (1)[3]", + "needsShovel": False, "purchase": True, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Tough Bird Salesman (400 Coins)", + "id": base_id + 28, + "inGameId": "ToughBirdNPC (1)[9]", + "needsShovel": False, "purchase": True, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + + # Beachstickball + {"name": "Beachstickball (10 Hits)", + "id": base_id + 29, + "inGameId": "VolleyballOpponent[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Beachstickball (20 Hits)", + "id": base_id + 30, + "inGameId": "VolleyballOpponent[1]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Beachstickball (30 Hits)", + "id": base_id + 31, + "inGameId": "VolleyballOpponent[2]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Misc Item Locations + {"name": "Shovel Kid Trade", + "id": base_id + 32, + "inGameId": "Frog_StandingNPC[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Compass Guy", + "id": base_id + 33, + "inGameId": "Fox_WalkingNPC[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Hawk Peak Bucket Rock", + "id": base_id + 34, + "inGameId": "Tools.23", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Orange Islands Bucket Rock", + "id": base_id + 35, + "inGameId": "Tools.42", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Bill the Walrus Fisherman", + "id": base_id + 36, + "inGameId": "SittingNPC (1)[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Catch 3 Fish Reward", + "id": base_id + 37, + "inGameId": "FishBuyer[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Catch All Fish Reward", + "id": base_id + 38, + "inGameId": "FishBuyer[1]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 7, "minGoldenFeathersEasy": 9, "minGoldenFeathersBucket": 7}, + {"name": "Permit Guy Bribe", + "id": base_id + 39, + "inGameId": "CamperNPC[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Catch Fish with Permit", + "id": base_id + 129, + "inGameId": "Player[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Return Camping Permit", + "id": base_id + 130, + "inGameId": "CamperNPC[1]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + + # Original Pickaxe Locations + {"name": "Blocked Mine Pickaxe 1", + "id": base_id + 40, + "inGameId": "Tools.31", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Blocked Mine Pickaxe 2", + "id": base_id + 41, + "inGameId": "Tools.32", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Blocked Mine Pickaxe 3", + "id": base_id + 42, + "inGameId": "Tools.33", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Original Toy Shovel Locations + {"name": "Blackwood Trail Lookout Toy Shovel", + "id": base_id + 43, + "inGameId": "PickUps.27", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Shirley's Point Beach Toy Shovel", + "id": base_id + 44, + "inGameId": "PickUps.30", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Visitor's Center Beach Toy Shovel", + "id": base_id + 45, + "inGameId": "PickUps.29", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Blackwood Trail Rock Toy Shovel", + "id": base_id + 46, + "inGameId": "PickUps.26", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Beach Hut Cliff Toy Shovel", + "id": base_id + 128, + "inGameId": "PickUps.28", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Original Stick Locations + {"name": "Secret Island Beach Trail Stick", + "id": base_id + 47, + "inGameId": "PickUps.25", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Below Lighthouse Walkway Stick", + "id": base_id + 48, + "inGameId": "Tools.3", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Beach Hut Rocky Pool Sand Stick", + "id": base_id + 49, + "inGameId": "Tools.0", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Cliff Overlooking West River Waterfall Stick", + "id": base_id + 50, + "inGameId": "Tools.2", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 2, "minGoldenFeathersBucket": 0}, + {"name": "Trail to Tough Bird Salesman Stick", + "id": base_id + 51, + "inGameId": "Tools.8", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "North Beach Stick", + "id": base_id + 52, + "inGameId": "Tools.4", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Beachstickball Court Stick", + "id": base_id + 53, + "inGameId": "VolleyballMinigame.4", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Stick Under Sid Beach Umbrella", + "id": base_id + 54, + "inGameId": "Tools.1", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Boating + {"name": "Boat Rental", + "id": base_id + 55, + "inGameId": "DadDeer[0]", + "needsShovel": False, "purchase": True, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Boat Challenge Reward", + "id": base_id + 56, + "inGameId": "DeerKidBoat[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Not a location for now, corresponding with the Boating Manual + # {"name": "Receive Boating Manual", + # "id": base_id + 133, + # "inGameId": "DadDeer[1]", + # "needsShovel": False, "purchase": False, + # "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Original Map Locations + {"name": "Outlook Point Dog Gift", + "id": base_id + 57, + "inGameId": "Dog_WalkingNPC_BlueEyed[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + + # Original Clothes Locations + {"name": "Collect 15 Seashells", + "id": base_id + 58, + "inGameId": "LittleKidNPCVariant (1)[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Return to Shell Kid", + "id": base_id + 132, + "inGameId": "LittleKidNPCVariant (1)[1]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Taylor the Turtle Headband Gift", + "id": base_id + 59, + "inGameId": "Turtle_WalkingNPC[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Sue the Rabbit Shoes Reward", + "id": base_id + 60, + "inGameId": "Bunny_WalkingNPC (1)[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Purchase Sunhat", + "id": base_id + 61, + "inGameId": "SittingNPC[0]", + "needsShovel": False, "purchase": True, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Original Golden Feather Locations + {"name": "Blackwood Forest Golden Feather", + "id": base_id + 62, + "inGameId": "Feathers.3", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Ranger May Shell Necklace Golden Feather", + "id": base_id + 63, + "inGameId": "AuntMayNPC[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Sand Castle Golden Feather", + "id": base_id + 64, + "inGameId": "SandProvince.3", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Artist Golden Feather", + "id": base_id + 65, + "inGameId": "StandingNPC[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Visitor Camp Rock Golden Feather", + "id": base_id + 66, + "inGameId": "Feathers.8", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Outlook Cliff Golden Feather", + "id": base_id + 67, + "inGameId": "Feathers.2", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Meteor Lake Cliff Golden Feather", + "id": base_id + 68, + "inGameId": "Feathers.7", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 0}, + + # Original Silver Feather Locations + {"name": "Secret Island Peak", + "id": base_id + 69, + "inGameId": "PickUps.24", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 5, "minGoldenFeathersEasy": 7, "minGoldenFeathersBucket": 7}, + {"name": "Wristwatch Trade", + "id": base_id + 70, + "inGameId": "Goat_StandingNPC[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + + # Golden Chests + {"name": "Lighthouse Golden Chest", + "id": base_id + 71, + "inGameId": "Feathers.0", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 2, "minGoldenFeathersEasy": 3, "minGoldenFeathersBucket": 0}, + {"name": "Outlook Golden Chest", + "id": base_id + 72, + "inGameId": "Feathers.6", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Stone Tower Golden Chest", + "id": base_id + 73, + "inGameId": "Feathers.5", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "North Cliff Golden Chest", + "id": base_id + 74, + "inGameId": "Feathers.4", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 3, "minGoldenFeathersEasy": 10, "minGoldenFeathersBucket": 10}, + + # Chests + {"name": "Blackwood Cliff Chest", + "id": base_id + 75, + "inGameId": "Coins.22", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "White Coast Trail Chest", + "id": base_id + 76, + "inGameId": "Coins.6", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Sid Beach Chest", + "id": base_id + 77, + "inGameId": "Coins.7", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Sid Beach Buried Treasure Chest", + "id": base_id + 78, + "inGameId": "Coins.46", + "needsShovel": True, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Sid Beach Cliff Chest", + "id": base_id + 79, + "inGameId": "Coins.9", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Visitor's Center Buried Chest", + "id": base_id + 80, + "inGameId": "Coins.94", + "needsShovel": True, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Visitor's Center Hidden Chest", + "id": base_id + 81, + "inGameId": "Coins.42", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Shirley's Point Chest", + "id": base_id + 82, + "inGameId": "Coins.10", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 1, "minGoldenFeathersEasy": 2, "minGoldenFeathersBucket": 2}, + {"name": "Caravan Cliff Chest", + "id": base_id + 83, + "inGameId": "Coins.12", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Caravan Arch Chest", + "id": base_id + 84, + "inGameId": "Coins.11", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "King Buried Treasure Chest", + "id": base_id + 85, + "inGameId": "Coins.41", + "needsShovel": True, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Good Creek Path Buried Chest", + "id": base_id + 86, + "inGameId": "Coins.48", + "needsShovel": True, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Good Creek Path West Chest", + "id": base_id + 87, + "inGameId": "Coins.33", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Good Creek Path East Chest", + "id": base_id + 88, + "inGameId": "Coins.62", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "West Waterfall Chest", + "id": base_id + 89, + "inGameId": "Coins.20", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Stone Tower West Cliff Chest", + "id": base_id + 90, + "inGameId": "PickUps.0", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Bucket Path Chest", + "id": base_id + 91, + "inGameId": "Coins.50", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Bucket Cliff Chest", + "id": base_id + 92, + "inGameId": "Coins.49", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 3, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 5}, + {"name": "In Her Shadow Buried Treasure Chest", + "id": base_id + 93, + "inGameId": "Feathers.9", + "needsShovel": True, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Meteor Lake Buried Chest", + "id": base_id + 94, + "inGameId": "Coins.86", + "needsShovel": True, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Meteor Lake Chest", + "id": base_id + 95, + "inGameId": "Coins.64", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "House North Beach Chest", + "id": base_id + 96, + "inGameId": "Coins.65", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "East Coast Chest", + "id": base_id + 97, + "inGameId": "Coins.98", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Fisherman's Boat Chest 1", + "id": base_id + 99, + "inGameId": "Boat.0", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Fisherman's Boat Chest 2", + "id": base_id + 100, + "inGameId": "Boat.7", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Airstream Island Chest", + "id": base_id + 101, + "inGameId": "Coins.31", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "West River Waterfall Head Chest", + "id": base_id + 102, + "inGameId": "Coins.34", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Old Building Chest", + "id": base_id + 103, + "inGameId": "Coins.104", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Old Building West Chest", + "id": base_id + 104, + "inGameId": "Coins.109", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Old Building East Chest", + "id": base_id + 105, + "inGameId": "Coins.8", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Hawk Peak West Chest", + "id": base_id + 106, + "inGameId": "Coins.21", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 3, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 5}, + {"name": "Hawk Peak East Buried Chest", + "id": base_id + 107, + "inGameId": "Coins.76", + "needsShovel": True, "purchase": False, + "minGoldenFeathers": 3, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 5}, + {"name": "Hawk Peak Northeast Chest", + "id": base_id + 108, + "inGameId": "Coins.79", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 3, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 5}, + {"name": "Northern East Coast Chest", + "id": base_id + 109, + "inGameId": "Coins.45", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 2, "minGoldenFeathersBucket": 0}, + {"name": "North Coast Chest", + "id": base_id + 110, + "inGameId": "Coins.28", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "North Coast Buried Chest", + "id": base_id + 111, + "inGameId": "Coins.47", + "needsShovel": True, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Small South Island Buried Chest", + "id": base_id + 112, + "inGameId": "Coins.87", + "needsShovel": True, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Secret Island Bottom Chest", + "id": base_id + 113, + "inGameId": "Coins.88", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Secret Island Treehouse Chest", + "id": base_id + 114, + "inGameId": "Coins.89", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 1, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 1}, + {"name": "Sunhat Island Buried Chest", + "id": base_id + 115, + "inGameId": "Coins.112", + "needsShovel": True, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Orange Islands South Buried Chest", + "id": base_id + 116, + "inGameId": "Coins.119", + "needsShovel": True, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Orange Islands West Chest", + "id": base_id + 117, + "inGameId": "Coins.121", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Orange Islands North Buried Chest", + "id": base_id + 118, + "inGameId": "Coins.117", + "needsShovel": True, "purchase": False, + "minGoldenFeathers": 1, "minGoldenFeathersEasy": 1, "minGoldenFeathersBucket": 0}, + {"name": "Orange Islands East Chest", + "id": base_id + 119, + "inGameId": "Coins.120", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Orange Islands South Hidden Chest", + "id": base_id + 120, + "inGameId": "Coins.124", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "A Stormy View Buried Treasure Chest", + "id": base_id + 121, + "inGameId": "Coins.113", + "needsShovel": True, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, + {"name": "Orange Islands Ruins Buried Chest", + "id": base_id + 122, + "inGameId": "Coins.118", + "needsShovel": True, "purchase": False, + "minGoldenFeathers": 2, "minGoldenFeathersEasy": 4, "minGoldenFeathersBucket": 0}, + + # Race Rewards + {"name": "Lighthouse Race Reward", + "id": base_id + 123, + "inGameId": "RaceOpponent[0]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 2, "minGoldenFeathersEasy": 3, "minGoldenFeathersBucket": 1}, + {"name": "Old Building Race Reward", + "id": base_id + 124, + "inGameId": "RaceOpponent[1]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 1, "minGoldenFeathersEasy": 5, "minGoldenFeathersBucket": 0}, + {"name": "Hawk Peak Race Reward", + "id": base_id + 125, + "inGameId": "RaceOpponent[2]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 7, "minGoldenFeathersEasy": 9, "minGoldenFeathersBucket": 7}, + {"name": "Lose Race Gift", + "id": base_id + 131, + "inGameId": "RaceOpponent[9]", + "needsShovel": False, "purchase": False, + "minGoldenFeathers": 0, "minGoldenFeathersEasy": 0, "minGoldenFeathersBucket": 0}, +] diff --git a/worlds/shorthike/Options.py b/worlds/shorthike/Options.py new file mode 100644 index 0000000000..2f378f18ff --- /dev/null +++ b/worlds/shorthike/Options.py @@ -0,0 +1,73 @@ +from dataclasses import dataclass +from Options import Choice, PerGameCommonOptions, Range, StartInventoryPool, Toggle + +class Goal(Choice): + """Choose the end goal. + Nap: Complete the climb to the top of Hawk Peak and take a nap + Photo: Get your picture taken at the top of Hawk Peak + Races: Complete all three races with Avery + Help Everyone: Travel around Hawk Peak and help every character with their troubles + Fishmonger: Catch one of every fish from around Hawk Peak""" + display_name = "Goal" + option_nap = 0 + option_photo = 1 + option_races = 2 + option_help_everyone = 3 + option_fishmonger = 4 + default = 3 + +class CoinsInShops(Toggle): + """When enabled, the randomizer can place coins into locations that are purchased, such as shops.""" + display_name = "Coins in Purchaseable Locations" + default = False + +class GoldenFeathers(Range): + """Number of Golden Feathers in the item pool. + (Note that for the Photo and Help Everyone goals, a minimum of 12 Golden Feathers is enforced)""" + display_name = "Golden Feathers" + range_start = 0 + range_end = 20 + default = 20 + +class SilverFeathers(Range): + """Number of Silver Feathers in the item pool.""" + display_name = "Silver Feathers" + range_start = 0 + range_end = 20 + default = 2 + +class Buckets(Range): + """Number of Buckets in the item pool.""" + display_name = "Buckets" + range_start = 0 + range_end = 2 + default = 2 + +class GoldenFeatherProgression(Choice): + """Determines which locations are considered in logic based on the required amount of golden feathers to reach them. + Easy: Locations will be considered inaccessible until the player has enough golden feathers to easily reach them. A minimum of 10 golden feathers is recommended for this setting. + Normal: Locations will be considered inaccessible until the player has the minimum possible number of golden feathers to reach them. A minimum of 7 golden feathers is recommended for this setting. + Hard: Removes the requirement of golden feathers for progression entirely and glitches may need to be used to progress""" + display_name = "Golden Feather Progression" + option_easy = 0 + option_normal = 1 + option_hard = 2 + default = 1 + +class CostMultiplier(Range): + """The percentage that all item shop costs will be of the vanilla values.""" + display_name = "Shop Cost Multiplier" + range_start = 25 + range_end = 200 + default = 100 + +@dataclass +class ShortHikeOptions(PerGameCommonOptions): + start_inventory_from_pool: StartInventoryPool + goal: Goal + coins_in_shops: CoinsInShops + golden_feathers: GoldenFeathers + silver_feathers: SilverFeathers + buckets: Buckets + golden_feather_progression: GoldenFeatherProgression + cost_multiplier: CostMultiplier diff --git a/worlds/shorthike/Rules.py b/worlds/shorthike/Rules.py new file mode 100644 index 0000000000..73a1643421 --- /dev/null +++ b/worlds/shorthike/Rules.py @@ -0,0 +1,73 @@ +from worlds.generic.Rules import forbid_items_for_player, add_rule + +def create_rules(self, location_table): + multiworld = self.multiworld + player = self.player + options = self.options + + # Shovel Rules + for loc in location_table: + if loc["needsShovel"]: + forbid_items_for_player(multiworld.get_location(loc["name"], player), self.item_name_groups['Maps'], player) + add_rule(multiworld.get_location(loc["name"], player), + lambda state: state.has("Shovel", player)) + if loc["purchase"] and not options.coins_in_shops: + forbid_items_for_player(multiworld.get_location(loc["name"], player), self.item_name_groups['Coins'], player) + + # Minimum Feather Rules + if options.golden_feather_progression != 2: + min_feathers = get_min_feathers(self, loc["minGoldenFeathers"], loc["minGoldenFeathersEasy"]) + + if options.buckets > 0 and loc["minGoldenFeathersBucket"] < min_feathers: + add_rule(multiworld.get_location(loc["name"], player), + lambda state, loc=loc, min_feathers=min_feathers: state.has("Golden Feather", player, min_feathers) + or (state.has("Bucket", player) and state.has("Golden Feather", player, loc["minGoldenFeathersBucket"]))) + elif min_feathers > 0: + add_rule(multiworld.get_location(loc["name"], player), + lambda state, min_feathers=min_feathers: state.has("Golden Feather", player, min_feathers)) + add_rule(multiworld.get_location("Shovel Kid Trade", player), + lambda state: state.has("Toy Shovel", player)) + add_rule(multiworld.get_location("Sand Castle Golden Feather", player), + lambda state: state.has("Toy Shovel", player)) + + # Fishing Rules + add_rule(multiworld.get_location("Catch 3 Fish Reward", player), + lambda state: state.has("Fishing Rod", player)) + add_rule(multiworld.get_location("Catch Fish with Permit", player), + lambda state: state.has("Fishing Rod", player)) + add_rule(multiworld.get_location("Catch All Fish Reward", player), + lambda state: state.has("Fishing Rod", player)) + + # Misc Rules + add_rule(multiworld.get_location("Return Camping Permit", player), + lambda state: state.has("Camping Permit", player)) + add_rule(multiworld.get_location("Boat Challenge Reward", player), + lambda state: state.has("Motorboat Key", player)) + add_rule(multiworld.get_location("Collect 15 Seashells", player), + lambda state: state.has("Seashell", player, 15)) + add_rule(multiworld.get_location("Wristwatch Trade", player), + lambda state: state.has("Wristwatch", player)) + add_rule(multiworld.get_location("Sue the Rabbit Shoes Reward", player), + lambda state: state.has("Headband", player)) + add_rule(multiworld.get_location("Return to Shell Kid", player), + lambda state: state.has("Shell Necklace", player) and state.has("Seashell", player, 15)) + add_rule(multiworld.get_location("Ranger May Shell Necklace Golden Feather", player), + lambda state: state.has("Shell Necklace", player)) + add_rule(multiworld.get_location("Beachstickball (10 Hits)", player), + lambda state: state.has("Stick", player)) + add_rule(multiworld.get_location("Beachstickball (20 Hits)", player), + lambda state: state.has("Stick", player)) + add_rule(multiworld.get_location("Beachstickball (30 Hits)", player), + lambda state: state.has("Stick", player)) + +def get_min_feathers(self, min_golden_feathers, min_golden_feathers_easy): + options = self.options + + min_feathers = min_golden_feathers + if options.golden_feather_progression == 0: + min_feathers = min_golden_feathers_easy + if min_feathers > options.golden_feathers: + if options.goal != 1 and options.goal != 3: + min_feathers = options.golden_feathers + + return min_feathers diff --git a/worlds/shorthike/__init__.py b/worlds/shorthike/__init__.py new file mode 100644 index 0000000000..8a4ef93233 --- /dev/null +++ b/worlds/shorthike/__init__.py @@ -0,0 +1,136 @@ +from collections import Counter +from typing import ClassVar, Dict, Any, Type +from BaseClasses import Region, Location, Item, Tutorial +from Options import PerGameCommonOptions +from worlds.AutoWorld import World, WebWorld +from .Items import item_table, group_table, base_id +from .Locations import location_table +from .Rules import create_rules, get_min_feathers +from .Options import ShortHikeOptions + +class ShortHikeWeb(WebWorld): + theme = "ocean" + tutorials = [Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the A Short Hike randomizer connected to an Archipelago Multiworld", + "English", + "setup_en.md", + "setup/en", + ["Chandler"] + )] + +class ShortHikeWorld(World): + """ + A Short Hike is a relaxing adventure set on the islands of Hawk Peak. Fly and climb using Claire's wings and Golden Feathers + to make your way up to the summit. Along the way you'll meet other hikers, discover hidden treasures, + and take in the beautiful world around you. + """ + + game = "A Short Hike" + web = ShortHikeWeb() + data_version = 2 + + item_name_to_id = {item["name"]: item["id"] for item in item_table} + location_name_to_id = {loc["name"]: loc["id"] for loc in location_table} + location_name_to_game_id = {loc["name"]: loc["inGameId"] for loc in location_table} + + item_name_groups = group_table + + options_dataclass: ClassVar[Type[PerGameCommonOptions]] = ShortHikeOptions + options: ShortHikeOptions + + required_client_version = (0, 4, 4) + + def __init__(self, multiworld, player): + super(ShortHikeWorld, self).__init__(multiworld, player) + + def get_filler_item_name(self) -> str: + return "13 Coins" + + def create_item(self, name: str) -> "ShortHikeItem": + item_id: int = self.item_name_to_id[name] + id = item_id - base_id - 1 + + return ShortHikeItem(name, item_table[id]["classification"], item_id, player=self.player) + + def create_items(self) -> None: + for item in item_table: + count = item["count"] + + if count <= 0: + continue + else: + for i in range(count): + self.multiworld.itempool.append(self.create_item(item["name"])) + + feather_count = self.options.golden_feathers + if self.options.goal == 1 or self.options.goal == 3: + if feather_count < 12: + feather_count = 12 + + junk = 45 - self.options.silver_feathers - feather_count - self.options.buckets + self.multiworld.itempool += [self.create_item(self.get_filler_item_name()) for _ in range(junk)] + self.multiworld.itempool += [self.create_item("Golden Feather") for _ in range(feather_count)] + self.multiworld.itempool += [self.create_item("Silver Feather") for _ in range(self.options.silver_feathers)] + self.multiworld.itempool += [self.create_item("Bucket") for _ in range(self.options.buckets)] + + def create_regions(self) -> None: + menu_region = Region("Menu", self.player, self.multiworld) + self.multiworld.regions.append(menu_region) + + main_region = Region("Hawk Peak", self.player, self.multiworld) + + for loc in self.location_name_to_id.keys(): + main_region.locations.append(ShortHikeLocation(self.player, loc, self.location_name_to_id[loc], main_region)) + + self.multiworld.regions.append(main_region) + + menu_region.connect(main_region) + + if self.options.goal == "nap": + # Nap + self.multiworld.completion_condition[self.player] = lambda state: (state.has("Golden Feather", self.player, get_min_feathers(self, 7, 9)) + or (state.has("Bucket", self.player) and state.has("Golden Feather", self.player, 7))) + elif self.options.goal == "photo": + # Photo + self.multiworld.completion_condition[self.player] = lambda state: state.has("Golden Feather", self.player, 12) + elif self.options.goal == "races": + # Races + self.multiworld.completion_condition[self.player] = lambda state: (state.has("Golden Feather", self.player, get_min_feathers(self, 7, 9)) + or (state.has("Bucket", self.player) and state.has("Golden Feather", self.player, 7))) + elif self.options.goal == "help_everyone": + # Help Everyone + self.multiworld.completion_condition[self.player] = lambda state: (state.has("Golden Feather", self.player, 12) + and state.has("Toy Shovel", self.player) and state.has("Camping Permit", self.player) + and state.has("Motorboat Key", self.player) and state.has("Headband", self.player) + and state.has("Wristwatch", self.player) and state.has("Seashell", self.player, 15) + and state.has("Shell Necklace", self.player)) + elif self.options.goal == "fishmonger": + # Fishmonger + self.multiworld.completion_condition[self.player] = lambda state: (state.has("Golden Feather", self.player, get_min_feathers(self, 7, 9)) + or (state.has("Bucket", self.player) and state.has("Golden Feather", self.player, 7)) + and state.has("Fishing Rod", self.player)) + + def set_rules(self): + create_rules(self, location_table) + + def fill_slot_data(self) -> Dict[str, Any]: + options = self.options + + settings = { + "goal": int(options.goal), + "logicLevel": int(options.golden_feather_progression), + "costMultiplier": int(options.cost_multiplier), + } + + slot_data = { + "settings": settings, + } + + return slot_data + +class ShortHikeItem(Item): + game: str = "A Short Hike" + +class ShortHikeLocation(Location): + game: str = "A Short Hike" diff --git a/worlds/shorthike/docs/en_A Short Hike.md b/worlds/shorthike/docs/en_A Short Hike.md new file mode 100644 index 0000000000..516bf28e47 --- /dev/null +++ b/worlds/shorthike/docs/en_A Short Hike.md @@ -0,0 +1,30 @@ +# A Short Hike + +## What does randomization do to this game? + +All items that can be obtained from chests, the ground, and NPCs are randomized. + +## What does another world's item look like in A Short Hike? + +All items are replaced with chests that can contain items from other worlds. +Items will appear with the Archipelago logo next to them when obtained. + +## Which characters need to be helped for the Help Everyone goal? + +To achieve the Help Everyone goal, the following characters will need to be helped: +- Pay Tough Bird Salesman's Tuition Fee +- Give Frog a Toy Shovel +- Return the Camper's Camping Permit +- Complete the Deer Kid's Boating Challenge +- Find Sue's Headband +- Clean Up and Purchase the Sunhat from the Deer +- Return the Camper's Wristwatch +- Cheer Up the Artist +- Collect 15 Shells for the Kid +- Give the Shell Necklace to Aunt May +- Help the Fox Climb the Mountain + +## Can I have more than one save at a time? + +No, unfortunately only one save slot is available for use in A Short Hike. +Starting a new save will erase the old one _permanently_. \ No newline at end of file diff --git a/worlds/shorthike/docs/setup_en.md b/worlds/shorthike/docs/setup_en.md new file mode 100644 index 0000000000..e327d8bed9 --- /dev/null +++ b/worlds/shorthike/docs/setup_en.md @@ -0,0 +1,32 @@ +# A Short Hike Multiworld Setup Guide + +## Required Software + +- A Short Hike: [Steam](https://store.steampowered.com/app/1055540/A_Short_Hike/) + - The Epic Games Store or itch.io version of A Short Hike will also work. +- A Short Hike Modding Tools: [GitHub](https://github.com/BrandenEK/AShortHike.ModdingTools) +- A Short Hike Randomizer: [GitHub](https://github.com/BrandenEK/AShortHike.Randomizer) + +## Optional Software + +- [PopTracker](https://github.com/black-sliver/PopTracker/) + - [Chandler's A Short Hike PopTracker Pack](https://github.com/chandler05/shorthike-archipelago-poptracker/releases) + +## Installation + +1. Download the [Modding Tools](https://github.com/BrandenEK/AShortHike.ModdingTools/releases), and follow +the [installation instructions](https://github.com/BrandenEK/AShortHike.ModdingTools#a-short-hike-modding-tools) on the GitHub page. + +2. After the Modding Tools have been installed, download the +[Randomizer](https://github.com/BrandenEK/AShortHike.Randomizer/releases) and extract the contents of it +into the `Modding` folder. + +## Connecting + +A Short Hike will prompt you with the server details when a new game is started or a previous one is continued. +Enter in the Server Port, Name, and Password (optional) in the popup menu that appears and hit connect. + +## Tracking + +Install PopTracker from the link above and place the PopTracker pack into the packs folder. +Connect to Archipelago via the AP button in the top left. \ No newline at end of file From f4b7c28a33bb163768871616023a8cf3879840b4 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:45:32 -0500 Subject: [PATCH 160/166] APProcedurePatch: hotfix changing class variables to instance variables (#2996) * change class variables to instance variables * Update worlds/Files.py Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * Update worlds/Files.py Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * move required_extensions to tuple * fix missing tuple ellipsis * fix classvar mixup * rename tokens to _tokens. use hasattr * type hint cleanup * Update Files.py * check using isinstance instead --------- Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --- worlds/Files.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/worlds/Files.py b/worlds/Files.py index 6fee582c87..6e9bf6b31b 100644 --- a/worlds/Files.py +++ b/worlds/Files.py @@ -7,7 +7,7 @@ from enum import IntEnum import os import threading -from typing import ClassVar, Dict, List, Literal, Tuple, Any, Optional, Union, BinaryIO, overload +from typing import ClassVar, Dict, List, Literal, Tuple, Any, Optional, Union, BinaryIO, overload, Sequence import bsdiff4 @@ -41,7 +41,7 @@ class AutoPatchRegister(abc.ABCMeta): class AutoPatchExtensionRegister(abc.ABCMeta): extension_types: ClassVar[Dict[str, AutoPatchExtensionRegister]] = {} - required_extensions: List[str] = [] + required_extensions: Tuple[str, ...] = () def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoPatchExtensionRegister: # construct class @@ -51,7 +51,9 @@ class AutoPatchExtensionRegister(abc.ABCMeta): return new_class @staticmethod - def get_handler(game: str) -> Union[AutoPatchExtensionRegister, List[AutoPatchExtensionRegister]]: + def get_handler(game: Optional[str]) -> Union[AutoPatchExtensionRegister, List[AutoPatchExtensionRegister]]: + if not game: + return APPatchExtension handler = AutoPatchExtensionRegister.extension_types.get(game, APPatchExtension) if handler.required_extensions: handlers = [handler] @@ -191,7 +193,7 @@ class APProcedurePatch(APAutoPatchInterface): hash: Optional[str] # base checksum of source file source_data: bytes patch_file_ending: str = "" - files: Dict[str, bytes] = {} + files: Dict[str, bytes] @classmethod def get_source_data(cls) -> bytes: @@ -206,6 +208,7 @@ class APProcedurePatch(APAutoPatchInterface): def __init__(self, *args: Any, **kwargs: Any): super(APProcedurePatch, self).__init__(*args, **kwargs) + self.files = {} def get_manifest(self) -> Dict[str, Any]: manifest = super(APProcedurePatch, self).get_manifest() @@ -277,7 +280,7 @@ class APDeltaPatch(APProcedurePatch): super(APDeltaPatch, self).__init__(*args, **kwargs) self.patched_path = patched_path - def write_contents(self, opened_zipfile: zipfile.ZipFile): + def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None: self.write_file("delta.bsdiff4", bsdiff4.diff(self.get_source_data_with_cache(), open(self.patched_path, "rb").read())) super(APDeltaPatch, self).write_contents(opened_zipfile) @@ -296,12 +299,12 @@ class APTokenMixin: """ A class that defines functions for generating a token binary, for use in patches. """ - tokens: List[ + _tokens: Sequence[ Tuple[APTokenTypes, int, Union[ bytes, # WRITE Tuple[int, int], # COPY, RLE int # AND_8, OR_8, XOR_8 - ]]] = [] + ]]] = () def get_token_binary(self) -> bytes: """ @@ -309,8 +312,8 @@ class APTokenMixin: :return: A bytes object representing the token data. """ data = bytearray() - data.extend(len(self.tokens).to_bytes(4, "little")) - for token_type, offset, args in self.tokens: + data.extend(len(self._tokens).to_bytes(4, "little")) + for token_type, offset, args in self._tokens: data.append(token_type) data.extend(offset.to_bytes(4, "little")) if token_type in [APTokenTypes.AND_8, APTokenTypes.OR_8, APTokenTypes.XOR_8]: @@ -351,11 +354,14 @@ class APTokenMixin: data: bytes) -> None: ... - def write_token(self, token_type: APTokenTypes, offset: int, data: Union[bytes, Tuple[int, int], int]): + def write_token(self, token_type: APTokenTypes, offset: int, data: Union[bytes, Tuple[int, int], int]) -> None: """ Stores a token to be used by patching. """ - self.tokens.append((token_type, offset, data)) + if not isinstance(self._tokens, list): + assert len(self._tokens) == 0, f"{type(self)}._tokens was tampered with." + self._tokens = [] + self._tokens.append((token_type, offset, data)) class APPatchExtension(metaclass=AutoPatchExtensionRegister): @@ -371,10 +377,10 @@ class APPatchExtension(metaclass=AutoPatchExtensionRegister): Patch extension functions must return the changed bytes. """ game: str - required_extensions: List[str] = [] + required_extensions: ClassVar[Tuple[str, ...]] = () @staticmethod - def apply_bsdiff4(caller: APProcedurePatch, rom: bytes, patch: str): + def apply_bsdiff4(caller: APProcedurePatch, rom: bytes, patch: str) -> bytes: """Applies the given bsdiff4 from the patch onto the current file.""" return bsdiff4.patch(rom, caller.get_file(patch)) @@ -411,7 +417,7 @@ class APPatchExtension(metaclass=AutoPatchExtensionRegister): return bytes(rom_data) @staticmethod - def calc_snes_crc(caller: APProcedurePatch, rom: bytes): + def calc_snes_crc(caller: APProcedurePatch, rom: bytes) -> bytes: """Calculates and applies a valid CRC for the SNES rom header.""" rom_data = bytearray(rom) if len(rom) < 0x8000: From 30a0aa2c85a7015e2072b5781ed1078965f62f4b Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 21 Mar 2024 10:46:53 -0500 Subject: [PATCH 161/166] Lingo: Add item/location groups (#2789) --- worlds/lingo/__init__.py | 6 +- worlds/lingo/data/LL1.yaml | 194 ++++++++++++++--------- worlds/lingo/data/generated.dat | Bin 129731 -> 130791 bytes worlds/lingo/datatypes.py | 3 +- worlds/lingo/items.py | 18 ++- worlds/lingo/locations.py | 6 +- worlds/lingo/player_logic.py | 4 +- worlds/lingo/utils/pickle_static_data.py | 13 +- worlds/lingo/utils/validate_config.rb | 2 +- 9 files changed, 157 insertions(+), 89 deletions(-) diff --git a/worlds/lingo/__init__.py b/worlds/lingo/__init__.py index e35a1026b7..c92e53069e 100644 --- a/worlds/lingo/__init__.py +++ b/worlds/lingo/__init__.py @@ -6,8 +6,8 @@ from logging import warning from BaseClasses import Item, ItemClassification, Tutorial from worlds.AutoWorld import WebWorld, World from .datatypes import Room, RoomEntrance -from .items import ALL_ITEM_TABLE, LingoItem -from .locations import ALL_LOCATION_TABLE +from .items import ALL_ITEM_TABLE, ITEMS_BY_GROUP, LingoItem +from .locations import ALL_LOCATION_TABLE, LOCATIONS_BY_GROUP from .options import LingoOptions from .player_logic import LingoPlayerLogic from .regions import create_regions @@ -46,6 +46,8 @@ class LingoWorld(World): location_name_to_id = { name: data.code for name, data in ALL_LOCATION_TABLE.items() } + item_name_groups = ITEMS_BY_GROUP + location_name_groups = LOCATIONS_BY_GROUP player_logic: LingoPlayerLogic diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index f72e63c142..75f688268f 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -63,12 +63,13 @@ # - item_name: Overrides the name of the item generated for this door. # If not specified, the item name will be generated from # the room name and the door name. + # - item_group: If set, this item will be in the specified item group. # - location_name: Overrides the name of the location generated for this # door. If not specified, the location name will be # generated using the names of the panels. # - skip_location: If true, no location is generated for this door. # - skip_item: If true, no item is generated for this door. - # - group: When simple doors is used, all doors with the same group + # - door_group: When simple doors is used, all doors with the same group # will be covered by a single item. # - include_reduce: Door checks are assumed to be EXCLUDED when reduce checks # is on. This option includes the check anyway. @@ -144,7 +145,7 @@ - Palindrome Room Area Doors/Door_racecar_racecar_2 - Palindrome Room Area Doors/Door_solos_solos_2 skip_location: True - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - room: The Tenacious panel: LEVEL (Black) @@ -231,7 +232,7 @@ Dead End Door: id: Appendix Room Area Doors/Door_rat_tar_2 skip_location: true - group: Dead End Area Access + door_group: Dead End Area Access panels: - room: Hub Room panel: RAT @@ -244,6 +245,7 @@ Seeker Entrance: id: Entry Room Area Doors/Door_entrance_entrance item_name: The Seeker - Entrance + item_group: Achievement Room Entrances panels: - OPEN Rhyme Room Entrance: @@ -251,7 +253,7 @@ - Appendix Room Area Doors/Door_rat_tar_3 - Double Room Area Doors/Door_room_entry_stairs skip_location: True - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - room: The Tenacious panel: LEVEL (Black) @@ -466,25 +468,27 @@ - ORDER Tenacious Entrance: id: Palindrome Room Area Doors/Door_slaughter_laughter - group: Entrances to The Tenacious + door_group: Entrances to The Tenacious + item_group: Achievement Room Entrances panels: - SLAUGHTER Shortcut to Hedge Maze: id: Maze Area Doors/Door_trace_trace - group: Hedge Maze Doors + door_group: Hedge Maze Doors panels: - TRACE Near RAT Door: id: Appendix Room Area Doors/Door_deadend_deadened skip_location: True - group: Dead End Area Access + door_group: Dead End Area Access panels: - room: Hidden Room panel: DEAD END Traveled Entrance: id: Appendix Room Area Doors/Door_open_open item_name: The Traveled - Entrance - group: Entrance to The Traveled + door_group: Entrance to The Traveled + item_group: Achievement Room Entrances panels: - OPEN Lost Door: @@ -546,6 +550,7 @@ doors: Sun Painting: item_name: Pilgrim Room - Sun Painting + item_group: Paintings location_name: Pilgrim Room - HOT CRUST painting_id: pilgrim_painting2 panels: @@ -715,12 +720,14 @@ doors: Tenacious Entrance: id: Palindrome Room Area Doors/Door_decay_day - group: Entrances to The Tenacious + door_group: Entrances to The Tenacious + item_group: Achievement Room Entrances panels: - DECAY Discerning Entrance: id: Shuffle Room Area Doors/Door_nope_open item_name: The Discerning - Entrance + item_group: Achievement Room Entrances panels: - NOPE Tower Entrance: @@ -729,13 +736,13 @@ - Shuffle Room Area Doors/Door_tower2 - Shuffle Room Area Doors/Door_tower3 - Shuffle Room Area Doors/Door_tower4 - group: Crossroads - Tower Entrances + door_group: Crossroads - Tower Entrances panels: - WE ROT Tower Back Entrance: id: Shuffle Room Area Doors/Door_runt location_name: Crossroads - TURN/RUNT - group: Crossroads - Tower Entrances + door_group: Crossroads - Tower Entrances panels: - TURN - room: Orange Tower Fourth Floor @@ -744,20 +751,20 @@ id: - Shuffle Room Area Doors/Door_words_shuffle_3 - Shuffle Room Area Doors/Door_words_shuffle_4 - group: Crossroads Doors + door_group: Crossroads Doors panels: - WORDS - SWORD Eye Wall: id: Shuffle Room Area Doors/Door_behind junk_item: True - group: Crossroads Doors + door_group: Crossroads Doors panels: - BEND HI Hollow Hallway: id: Shuffle Room Area Doors/Door_crossroads6 skip_location: True - group: Crossroads Doors + door_group: Crossroads Doors panels: - BEND HI Roof Access: @@ -934,7 +941,7 @@ - Palindrome Room Area Doors/Door_racecar_racecar_1 - Palindrome Room Area Doors/Door_solos_solos_1 location_name: The Tenacious - Palindromes - group: Entrances to The Tenacious + door_group: Entrances to The Tenacious panels: - LEVEL (Black) - RACECAR (Black) @@ -965,7 +972,7 @@ id: - Symmetry Room Area Doors/Door_near_far - Symmetry Room Area Doors/Door_far_near - group: Symmetry Doors + door_group: Symmetry Doors item_name: Symmetry Room - Near Far Door location_name: Symmetry Room - NEAR, FAR panels: @@ -992,7 +999,7 @@ id: - Symmetry Room Area Doors/Door_warts_straw - Symmetry Room Area Doors/Door_straw_warts - group: Symmetry Doors + door_group: Symmetry Doors item_name: Symmetry Room - Warts Straw Door location_name: Symmetry Room - WARTS, STRAW panels: @@ -1019,7 +1026,7 @@ id: - Symmetry Room Area Doors/Door_leaf_feel - Symmetry Room Area Doors/Door_feel_leaf - group: Symmetry Doors + door_group: Symmetry Doors item_name: Symmetry Room - Leaf Feel Door location_name: Symmetry Room - LEAF, FEEL panels: @@ -1156,34 +1163,37 @@ doors: Tenacious Entrance: id: Palindrome Room Area Doors/Door_massacred_sacred - group: Entrances to The Tenacious + door_group: Entrances to The Tenacious + item_group: Achievement Room Entrances panels: - MASSACRED Black Door: id: Symmetry Room Area Doors/Door_black_white - group: Entrances to The Tenacious + door_group: Entrances to The Tenacious panels: - BLACK Agreeable Entrance: id: Symmetry Room Area Doors/Door_close_open item_name: The Agreeable - Entrance + item_group: Achievement Room Entrances panels: - CLOSE Painting Shortcut: item_name: Starting Room - Street Painting + item_group: Paintings painting_id: eyes_yellow_painting2 panels: - RIGHT Purple Barrier: id: Color Arrow Room Doors/Door_purple_3 - group: Color Hunt Barriers + door_group: Color Hunt Barriers skip_location: True panels: - room: Color Hunt panel: PURPLE Hallway Door: id: Red Blue Purple Room Area Doors/Door_room_2 - group: Hallway Room Doors + door_group: Hallway Room Doors location_name: Hallway Room - First Room panels: - WALL @@ -1229,7 +1239,8 @@ doors: Tenacious Entrance: id: Palindrome Room Area Doors/Door_dread_dead - group: Entrances to The Tenacious + door_group: Entrances to The Tenacious + item_group: Achievement Room Entrances panels: - DREAD The Agreeable: @@ -1298,7 +1309,8 @@ doors: Shortcut to Hedge Maze: id: Symmetry Room Area Doors/Door_bye_hi - group: Hedge Maze Doors + item_group: Achievement Room Entrances + door_group: Hedge Maze Doors panels: - BYE Hedge Maze: @@ -1391,12 +1403,14 @@ Perceptive Entrance: id: Maze Area Doors/Door_maze_maze item_name: The Perceptive - Entrance - group: Hedge Maze Doors + door_group: Hedge Maze Doors + item_group: Achievement Room Entrances panels: - DOWN Painting Shortcut: painting_id: garden_painting_tower2 item_name: Starting Room - Hedge Maze Painting + item_group: Paintings skip_location: True panels: - DOWN @@ -1407,7 +1421,8 @@ - Maze Area Doors/Door_look_room_3 skip_location: True item_name: The Observant - Entrance - group: Observant Doors + door_group: Observant Doors + item_group: Achievement Room Entrances panels: - room: The Perceptive panel: GAZE @@ -1473,7 +1488,7 @@ Second Floor: id: Naps Room Doors/Door_hider_5 location_name: The Fearless - First Floor Puzzles - group: Fearless Doors + door_group: Fearless Doors panels: - SPAN - TEAM @@ -1525,7 +1540,7 @@ - Naps Room Doors/Door_hider_1b2 - Naps Room Doors/Door_hider_new1 location_name: The Fearless - Second Floor Puzzles - group: Fearless Doors + door_group: Fearless Doors panels: - NONE - SUM @@ -1680,13 +1695,13 @@ doors: Backside Door: id: Maze Area Doors/Door_backside - group: Backside Doors + door_group: Backside Doors panels: - FOUR (1) - FOUR (2) Stairs: id: Maze Area Doors/Door_stairs - group: Observant Doors + door_group: Observant Doors panels: - SIX The Incomparable: @@ -1764,7 +1779,7 @@ Eight Door: id: Red Blue Purple Room Area Doors/Door_a_strands location_name: Giant Sevens - group: Observant Doors + door_group: Observant Doors panels: - I (Seven) - room: Courtyard @@ -1915,13 +1930,13 @@ doors: Shortcut to Hub Room: id: Shuffle Room Area Doors/Door_secret_secret - group: Orange Tower First Floor - Shortcuts + door_group: Orange Tower First Floor - Shortcuts panels: - SECRET Salt Pepper Door: id: Count Up Room Area Doors/Door_salt_pepper location_name: Orange Tower First Floor - Salt Pepper Door - group: Orange Tower First Floor - Shortcuts + door_group: Orange Tower First Floor - Shortcuts panels: - SALT - room: Directional Gallery @@ -1967,7 +1982,7 @@ doors: Red Barrier: id: Color Arrow Room Doors/Door_red_6 - group: Color Hunt Barriers + door_group: Color Hunt Barriers skip_location: True panels: - room: Color Hunt @@ -1975,7 +1990,7 @@ Rhyme Room Entrance: id: Double Room Area Doors/Door_room_entry_stairs2 skip_location: True - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - room: The Tenacious panel: LEVEL (Black) @@ -1989,7 +2004,7 @@ - Color Arrow Room Doors/Door_orange_hider_2 - Color Arrow Room Doors/Door_orange_hider_3 location_name: Color Barriers - RED and YELLOW - group: Color Hunt Barriers + door_group: Color Hunt Barriers item_name: Color Hunt - Orange Barrier panels: - RED @@ -2150,7 +2165,7 @@ doors: Welcome Back: id: Entry Room Area Doors/Door_sizes - group: Welcome Back Doors + door_group: Welcome Back Doors panels: - SIZE (Small) - SIZE (Big) @@ -2404,6 +2419,7 @@ Painting Shortcut: painting_id: flower_painting_8 item_name: Starting Room - Flower Painting + item_group: Paintings skip_location: True panels: - room: First Second Third Fourth @@ -2416,7 +2432,7 @@ panel: FOURTH Green Barrier: id: Color Arrow Room Doors/Door_green_5 - group: Color Hunt Barriers + door_group: Color Hunt Barriers skip_location: True panels: - room: Color Hunt @@ -2470,7 +2486,7 @@ doors: Backside Door: id: Count Up Room Area Doors/Door_yellow_backside - group: Backside Doors + door_group: Backside Doors location_name: Courtyard - FIRST, SECOND, THIRD, FOURTH item_name: Courtyard - Backside Door panels: @@ -2491,7 +2507,7 @@ Progress Door: id: Doorway Room Doors/Door_white item_name: The Colorful - White Door - group: Colorful Doors + door_group: Colorful Doors location_name: The Colorful - White panels: - BEGIN @@ -2512,7 +2528,7 @@ id: Doorway Room Doors/Door_black item_name: The Colorful - Black Door location_name: The Colorful - Black - group: Colorful Doors + door_group: Colorful Doors panels: - FOUND The Colorful (Red): @@ -2532,7 +2548,7 @@ id: Doorway Room Doors/Door_red item_name: The Colorful - Red Door location_name: The Colorful - Red - group: Colorful Doors + door_group: Colorful Doors panels: - LOAF The Colorful (Yellow): @@ -2552,7 +2568,7 @@ id: Doorway Room Doors/Door_yellow item_name: The Colorful - Yellow Door location_name: The Colorful - Yellow - group: Colorful Doors + door_group: Colorful Doors panels: - CREAM The Colorful (Blue): @@ -2572,7 +2588,7 @@ id: Doorway Room Doors/Door_blue item_name: The Colorful - Blue Door location_name: The Colorful - Blue - group: Colorful Doors + door_group: Colorful Doors panels: - SUN The Colorful (Purple): @@ -2592,7 +2608,7 @@ id: Doorway Room Doors/Door_purple item_name: The Colorful - Purple Door location_name: The Colorful - Purple - group: Colorful Doors + door_group: Colorful Doors panels: - SPOON The Colorful (Orange): @@ -2612,7 +2628,7 @@ id: Doorway Room Doors/Door_orange item_name: The Colorful - Orange Door location_name: The Colorful - Orange - group: Colorful Doors + door_group: Colorful Doors panels: - LETTERS The Colorful (Green): @@ -2632,7 +2648,7 @@ id: Doorway Room Doors/Door_green item_name: The Colorful - Green Door location_name: The Colorful - Green - group: Colorful Doors + door_group: Colorful Doors panels: - WALLS The Colorful (Brown): @@ -2652,7 +2668,7 @@ id: Doorway Room Doors/Door_brown item_name: The Colorful - Brown Door location_name: The Colorful - Brown - group: Colorful Doors + door_group: Colorful Doors panels: - IRON The Colorful (Gray): @@ -2672,7 +2688,7 @@ id: Doorway Room Doors/Door_gray item_name: The Colorful - Gray Door location_name: The Colorful - Gray - group: Colorful Doors + door_group: Colorful Doors panels: - OBSTACLE The Colorful: @@ -2768,7 +2784,7 @@ doors: Shortcut to Starting Room: id: Entry Room Area Doors/Door_return_return - group: Welcome Back Doors + door_group: Welcome Back Doors include_reduce: True panels: - WELCOME BACK @@ -2793,7 +2809,7 @@ doors: Shortcut to Hedge Maze: id: Maze Area Doors/Door_strays_maze - group: Hedge Maze Doors + door_group: Hedge Maze Doors panels: - STRAYS paintings: @@ -2916,14 +2932,14 @@ - UNCOVER Blue Barrier: id: Color Arrow Room Doors/Door_blue_3 - group: Color Hunt Barriers + door_group: Color Hunt Barriers skip_location: True panels: - room: Color Hunt panel: BLUE Orange Barrier: id: Color Arrow Room Doors/Door_orange_3 - group: Color Hunt Barriers + door_group: Color Hunt Barriers skip_location: True panels: - room: Color Hunt @@ -2931,6 +2947,7 @@ Initiated Entrance: id: Red Blue Purple Room Area Doors/Door_locked_knocked item_name: The Initiated - Entrance + item_group: Achievement Room Entrances panels: - OXEN # These would be more appropriate in Champion's Rest, but as currently @@ -2940,7 +2957,7 @@ id: Color Arrow Room Doors/Door_green_hider_1 location_name: Color Barriers - BLUE and YELLOW item_name: Color Hunt - Green Barrier - group: Color Hunt Barriers + door_group: Color Hunt Barriers panels: - BLUE - room: Directional Gallery @@ -2952,7 +2969,7 @@ - Color Arrow Room Doors/Door_purple_hider_3 location_name: Color Barriers - RED and BLUE item_name: Color Hunt - Purple Barrier - group: Color Hunt Barriers + door_group: Color Hunt Barriers panels: - BLUE - room: Orange Tower Third Floor @@ -2972,6 +2989,7 @@ panel: PURPLE Eight Door: id: Red Blue Purple Room Area Doors/Door_a_strands2 + item_group: Achievement Room Entrances skip_location: True panels: - room: The Incomparable @@ -3189,7 +3207,8 @@ doors: Color Hallways Entrance: id: Appendix Room Area Doors/Door_hello_hi - group: Entrance to The Traveled + door_group: Entrance to The Traveled + item_group: Achievement Room Entrances panels: - HELLO Color Hallways: @@ -3305,17 +3324,20 @@ Bold Entrance: id: Red Blue Purple Room Area Doors/Door_unopened_open item_name: The Bold - Entrance + item_group: Achievement Room Entrances panels: - UNOPEN Painting Shortcut: painting_id: pencil_painting6 skip_location: True item_name: Starting Room - Pencil Painting + item_group: Paintings panels: - UNOPEN Steady Entrance: id: Rock Room Doors/Door_2 item_name: The Steady - Entrance + item_group: Achievement Room Entrances panels: - BEGIN Lilac Entrance: @@ -3536,6 +3558,7 @@ Undeterred Entrance: id: Red Blue Purple Room Area Doors/Door_pen_open item_name: The Undeterred - Entrance + item_group: Achievement Room Entrances panels: - PEN Painting Shortcut: @@ -3544,11 +3567,13 @@ - arrows_painting3 skip_location: True item_name: Starting Room - Blue Painting + item_group: Paintings panels: - PEN Green Painting: painting_id: maze_painting_3 skip_location: True + item_group: Paintings panels: - FOUR Twos: @@ -3556,6 +3581,7 @@ - Count Up Room Area Doors/Door_two_hider - Count Up Room Area Doors/Door_two_hider_2 include_reduce: True + item_group: Numbers panels: - ONE Threes: @@ -3565,6 +3591,7 @@ - Count Up Room Area Doors/Door_three_hider_3 location_name: Twos include_reduce: True + item_group: Numbers panels: - TWO (1) - TWO (2) @@ -3583,6 +3610,7 @@ - Count Up Room Area Doors/Door_four_hider_3 - Count Up Room Area Doors/Door_four_hider_4 skip_location: True + item_group: Numbers panels: - THREE (1) - THREE (2) @@ -3594,6 +3622,7 @@ - Count Up Room Area Doors/Door_five_hider_5 location_name: Fours item_name: Number Hunt - Fives + item_group: Numbers include_reduce: True panels: - FOUR @@ -3606,6 +3635,7 @@ Challenge Entrance: id: Count Up Room Area Doors/Door_zero_unlocked item_name: Number Hunt - Challenge Entrance + item_group: Achievement Room Entrances panels: - ZERO paintings: @@ -3752,7 +3782,7 @@ doors: Door to Directional Gallery: id: Count Up Room Area Doors/Door_five_unlocked - group: Directional Gallery Doors + door_group: Directional Gallery Doors skip_location: True panels: - FIVE @@ -3766,6 +3796,7 @@ - Count Up Room Area Doors/Door_six_hider_6 painting_id: pencil_painting3 # See note in Outside The Bold location_name: Fives + item_group: Numbers include_reduce: True panels: - FIVE @@ -3788,6 +3819,7 @@ - Count Up Room Area Doors/Door_seven_hider_6 - Count Up Room Area Doors/Door_seven_hider_7 location_name: Sixes + item_group: Numbers include_reduce: True panels: - SIX @@ -3813,6 +3845,7 @@ - Count Up Room Area Doors/Door_eight_hider_7 - Count Up Room Area Doors/Door_eight_hider_8 location_name: Sevens + item_group: Numbers include_reduce: True panels: - SEVEN @@ -3840,6 +3873,7 @@ - Count Up Room Area Doors/Door_nine_hider_8 - Count Up Room Area Doors/Door_nine_hider_9 location_name: Eights + item_group: Numbers include_reduce: True panels: - EIGHT @@ -3862,6 +3896,7 @@ id: Count Up Room Area Doors/Door_zero_hider_2 location_name: Nines item_name: Outside The Undeterred - Zero Door + item_group: Numbers include_reduce: True panels: - NINE @@ -4030,13 +4065,13 @@ doors: Shortcut to The Undeterred: id: Count Up Room Area Doors/Door_return_double - group: Directional Gallery Doors + door_group: Directional Gallery Doors panels: - TURN - LEARN Yellow Barrier: id: Color Arrow Room Doors/Door_yellow_4 - group: Color Hunt Barriers + door_group: Color Hunt Barriers skip_location: True panels: - room: Color Hunt @@ -4231,11 +4266,12 @@ doors: Entrance: id: Red Blue Purple Room Area Doors/Door_middle_middle + item_group: Achievement Room Entrances panels: - MIDDLE Backside Door: id: Red Blue Purple Room Area Doors/Door_locked_knocked2 # yeah... - group: Backside Doors + door_group: Backside Doors panels: - FARTHER East Entrance: @@ -5223,7 +5259,7 @@ - Ceiling Room Doors/Door_blue - Ceiling Room Doors/Door_blue2 location_name: The Artistic - Smiley and Panda - group: Artistic Doors + door_group: Artistic Doors panels: - FINE - BLADE @@ -5333,7 +5369,7 @@ - Ceiling Room Doors/Door_red - Ceiling Room Doors/Door_red2 location_name: The Artistic - Panda and Lattice - group: Artistic Doors + door_group: Artistic Doors panels: - EYE (Top) - EYE (Bottom) @@ -5444,7 +5480,7 @@ - Ceiling Room Doors/Door_black - Ceiling Room Doors/Door_black2 location_name: The Artistic - Lattice and Apple - group: Artistic Doors + door_group: Artistic Doors panels: - POSH - MALL @@ -5557,7 +5593,7 @@ - Ceiling Room Doors/Door_yellow - Ceiling Room Doors/Door_yellow2 location_name: The Artistic - Apple and Smiley - group: Artistic Doors + door_group: Artistic Doors panels: - SPRIG - RELEASES @@ -5721,7 +5757,7 @@ doors: Exit: id: Count Up Room Area Doors/Door_near_near - group: Crossroads Doors + door_group: Crossroads Doors panels: - NEAR paintings: @@ -5762,6 +5798,7 @@ Wondrous Entrance: id: Red Blue Purple Room Area Doors/Door_wonderland item_name: The Wondrous - Entrance + item_group: Achievement Room Entrances panels: - SHRINK The Wondrous (Doorknob): @@ -5782,6 +5819,7 @@ - arrows_painting2 skip_location: True item_name: Starting Room - Symmetry Painting + item_group: Paintings panels: - room: Outside The Wondrous panel: SHRINK @@ -5886,6 +5924,7 @@ doors: Exit: id: Red Blue Purple Room Area Doors/Door_wonderland_exit + item_group: Paintings painting_id: arrows_painting_9 include_reduce: True panels: @@ -5955,7 +5994,7 @@ Exit: id: Red Blue Purple Room Area Doors/Door_room_3 location_name: Hallway Room - Second Room - group: Hallway Room Doors + door_group: Hallway Room Doors panels: - WISE - CLOCK @@ -5992,7 +6031,7 @@ Exit: id: Red Blue Purple Room Area Doors/Door_room_4 location_name: Hallway Room - Third Room - group: Hallway Room Doors + door_group: Hallway Room Doors panels: - TRANCE - FORM @@ -6014,7 +6053,7 @@ id: - Red Blue Purple Room Area Doors/Door_room_5 - Red Blue Purple Room Area Doors/Door_room_6 # this is the connection to The Artistic - group: Hallway Room Doors + door_group: Hallway Room Doors location_name: Hallway Room - Fourth Room panels: - WHEEL @@ -6082,6 +6121,7 @@ Wanderer Entrance: id: Tower Room Area Doors/Door_wanderer_entrance item_name: The Wanderer - Entrance + item_group: Achievement Room Entrances panels: - WANDERLUST Tower Entrance: @@ -6222,6 +6262,7 @@ id: Tower Room Area Doors/Door_painting_exit include_reduce: True item_name: Orange Tower Fifth Floor - Quadruple Intersection + item_group: Achievement Room Entrances panels: - ORDER paintings: @@ -6417,7 +6458,7 @@ - Double Room Area Doors/Door_room_3a - Double Room Area Doors/Door_room_3bc skip_location: True - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - SCHEME - FANTASY @@ -6518,7 +6559,7 @@ Exit: id: Double Room Area Doors/Door_room_exit location_name: Rhyme Room (Cross) - Exit Puzzles - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - PLUMP - BOUNCE @@ -6581,7 +6622,7 @@ - Double Room Area Doors/Door_room_2b - Double Room Area Doors/Door_room_3b location_name: Rhyme Room - Circle/Smiley Wall - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - BIRD - LETTER @@ -6664,7 +6705,7 @@ - Double Room Area Doors/Door_room_2a - Double Room Area Doors/Door_room_1c location_name: Rhyme Room - Circle/Looped Square Wall - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - WALKED - OBSTRUCTED @@ -6683,7 +6724,7 @@ - Double Room Area Doors/Door_room_1a - Double Room Area Doors/Door_room_5a location_name: Rhyme Room - Cross/Looped Square Wall - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - SKIES - SWELL @@ -6702,7 +6743,7 @@ - Double Room Area Doors/Door_room_1b - Double Room Area Doors/Door_room_4b location_name: Rhyme Room - Target/Looped Square Wall - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - PENNED - CLIMB @@ -6765,7 +6806,7 @@ Door to Cross: id: Double Room Area Doors/Door_room_4a location_name: Rhyme Room (Target) - Puzzles Toward Cross - group: Rhyme Room Doors + door_group: Rhyme Room Doors panels: - PISTOL - GEM @@ -7016,6 +7057,7 @@ Wise Entrance: id: Clock Room Area Doors/Door_time_start item_name: The Wise - Entrance + item_group: Achievement Room Entrances panels: - KITTEN - CAT @@ -7269,6 +7311,7 @@ Scientific Entrance: id: Red Blue Purple Room Area Doors/Door_chemistry_lab item_name: The Scientific - Entrance + item_group: Achievement Room Entrances panels: - OPEN The Scientific: @@ -7704,5 +7747,6 @@ doors: Welcome Door: id: Entry Room Area Doors/Door_challenge_challenge + item_group: Achievement Room Entrances panels: - WELCOME diff --git a/worlds/lingo/data/generated.dat b/worlds/lingo/data/generated.dat index 49ea60df4183b9c7a59c4be54f03a95591668441..3bd8ff5a8b5fcd7ccfd60d32f34da3728e62c2d2 100644 GIT binary patch delta 17352 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 Date: Thu, 21 Mar 2024 11:50:07 -0400 Subject: [PATCH 162/166] TUNIC: Shuffle Ladders option (#2919) --- docs/CODEOWNERS | 2 +- worlds/tunic/__init__.py | 96 ++- worlds/tunic/docs/en_TUNIC.md | 2 +- worlds/tunic/er_data.py | 1094 +++++++++++++++++---------------- worlds/tunic/er_rules.py | 1009 +++++++++++++++++++++++------- worlds/tunic/er_scripts.py | 178 ++---- worlds/tunic/items.py | 26 +- worlds/tunic/locations.py | 240 ++++---- worlds/tunic/options.py | 43 +- worlds/tunic/regions.py | 6 +- worlds/tunic/rules.py | 19 +- 11 files changed, 1659 insertions(+), 1056 deletions(-) diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 6440735662..a67d588300 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -177,7 +177,7 @@ /worlds/tloz/ @Rosalie-A @t3hf1gm3nt # TUNIC -/worlds/tunic/ @silent-destroyer +/worlds/tunic/ @silent-destroyer @ScipioWright # Undertale /worlds/undertale/ @jonloveslegos diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index c4b1bbec8e..3220c6c934 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -7,6 +7,7 @@ from .rules import set_location_rules, set_region_rules, randomize_ability_unloc from .er_rules import set_er_location_rules from .regions import tunic_regions from .er_scripts import create_er_regions +from .er_data import portal_mapping from .options import TunicOptions from worlds.AutoWorld import WebWorld, World from decimal import Decimal, ROUND_HALF_UP @@ -44,7 +45,6 @@ class TunicWorld(World): game = "TUNIC" web = TunicWeb() - data_version = 2 options: TunicOptions options_dataclass = TunicOptions item_name_groups = item_name_groups @@ -72,6 +72,7 @@ class TunicWorld(World): self.options.maskless.value = passthrough["maskless"] self.options.hexagon_quest.value = passthrough["hexagon_quest"] self.options.entrance_rando.value = passthrough["entrance_rando"] + self.options.shuffle_ladders.value = passthrough["shuffle_ladders"] def create_item(self, name: str) -> TunicItem: item_data = item_table[name] @@ -119,27 +120,46 @@ class TunicWorld(World): items_to_create[rgb_hexagon] = 0 items_to_create[gold_hexagon] -= 3 + # Filler items in the item pool + available_filler: List[str] = [filler for filler in items_to_create if items_to_create[filler] > 0 and + item_table[filler].classification == ItemClassification.filler] + + # Remove filler to make room for other items + def remove_filler(amount: int): + for _ in range(0, amount): + if not available_filler: + fill = "Fool Trap" + else: + fill = self.random.choice(available_filler) + if items_to_create[fill] == 0: + raise Exception("No filler items left to accommodate options selected. Turn down fool trap amount.") + items_to_create[fill] -= 1 + if items_to_create[fill] == 0: + available_filler.remove(fill) + + if self.options.shuffle_ladders: + ladder_count = 0 + for item_name, item_data in item_table.items(): + if item_data.item_group == "ladders": + items_to_create[item_name] = 1 + ladder_count += 1 + remove_filler(ladder_count) + if hexagon_quest: # Calculate number of hexagons in item pool hexagon_goal = self.options.hexagon_goal extra_hexagons = self.options.extra_hexagon_percentage items_to_create[gold_hexagon] += int((Decimal(100 + extra_hexagons) / 100 * hexagon_goal).to_integral_value(rounding=ROUND_HALF_UP)) - + # Replace pages and normal hexagons with filler for replaced_item in list(filter(lambda item: "Pages" in item or item in hexagon_locations, items_to_create)): - items_to_create[self.get_filler_item_name()] += items_to_create[replaced_item] + filler_name = self.get_filler_item_name() + items_to_create[filler_name] += items_to_create[replaced_item] + if items_to_create[filler_name] >= 1 and filler_name not in available_filler: + available_filler.append(filler_name) items_to_create[replaced_item] = 0 - # Filler items that are still in the item pool to swap out - available_filler: List[str] = [filler for filler in items_to_create if items_to_create[filler] > 0 and - item_table[filler].classification == ItemClassification.filler] - - # Remove filler to make room for extra hexagons - for i in range(0, items_to_create[gold_hexagon]): - fill = self.random.choice(available_filler) - items_to_create[fill] -= 1 - if items_to_create[fill] == 0: - available_filler.remove(fill) + remove_filler(items_to_create[gold_hexagon]) if self.options.maskless: mask_item = TunicItem("Scavenger Mask", ItemClassification.useful, self.item_name_to_id["Scavenger Mask"], self.player) @@ -147,8 +167,8 @@ class TunicWorld(World): items_to_create["Scavenger Mask"] = 0 if self.options.lanternless: - mask_item = TunicItem("Lantern", ItemClassification.useful, self.item_name_to_id["Lantern"], self.player) - tunic_items.append(mask_item) + lantern_item = TunicItem("Lantern", ItemClassification.useful, self.item_name_to_id["Lantern"], self.player) + tunic_items.append(lantern_item) items_to_create["Lantern"] = 0 for item, quantity in items_to_create.items(): @@ -172,15 +192,16 @@ class TunicWorld(World): self.ability_unlocks["Pages 24-25 (Prayer)"] = passthrough["Hexagon Quest Prayer"] self.ability_unlocks["Pages 42-43 (Holy Cross)"] = passthrough["Hexagon Quest Holy Cross"] self.ability_unlocks["Pages 52-53 (Icebolt)"] = passthrough["Hexagon Quest Icebolt"] - - if self.options.entrance_rando: - portal_pairs, portal_hints = create_er_regions(self) - for portal1, portal2 in portal_pairs.items(): - self.tunic_portal_pairs[portal1.scene_destination()] = portal2.scene_destination() - - self.er_portal_hints = portal_hints + # ladder rando uses ER with vanilla connections, so that we're not managing more rules files + if self.options.entrance_rando or self.options.shuffle_ladders: + portal_pairs = create_er_regions(self) + if self.options.entrance_rando: + # these get interpreted by the game to tell it which entrances to connect + for portal1, portal2 in portal_pairs.items(): + self.tunic_portal_pairs[portal1.scene_destination()] = portal2.scene_destination() else: + # for non-ER, non-ladders for region_name in tunic_regions: region = Region(region_name, self.player, self.multiworld) self.multiworld.regions.append(region) @@ -201,7 +222,7 @@ class TunicWorld(World): victory_region.locations.append(victory_location) def set_rules(self) -> None: - if self.options.entrance_rando: + if self.options.entrance_rando or self.options.shuffle_ladders: set_er_location_rules(self, self.ability_unlocks) else: set_region_rules(self, self.ability_unlocks) @@ -212,7 +233,31 @@ class TunicWorld(World): def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): if self.options.entrance_rando: - hint_data[self.player] = self.er_portal_hints + hint_data.update({self.player: {}}) + # all state seems to have efficient paths + all_state = self.multiworld.get_all_state(True) + all_state.update_reachable_regions(self.player) + paths = all_state.path + portal_names = [portal.name for portal in portal_mapping] + for location in self.multiworld.get_locations(self.player): + # skipping event locations + if not location.address: + continue + path_to_loc = [] + previous_name = "placeholder" + name, connection = paths[location.parent_region] + while connection != ("Menu", None): + name, connection = connection + # for LS entrances, we just want to give the portal name + if "(LS)" in name: + name, _ = name.split(" (LS) ") + # was getting some cases like Library Grave -> Library Grave -> other place + if name in portal_names and name != previous_name: + previous_name = name + path_to_loc.append(name) + hint_text = " -> ".join(reversed(path_to_loc)) + if hint_text: + hint_data[self.player][location.address] = hint_text def fill_slot_data(self) -> Dict[str, Any]: slot_data: Dict[str, Any] = { @@ -226,7 +271,8 @@ class TunicWorld(World): "logic_rules": self.options.logic_rules.value, "lanternless": self.options.lanternless.value, "maskless": self.options.maskless.value, - "entrance_rando": bool(self.options.entrance_rando.value), + "entrance_rando": int(bool(self.options.entrance_rando.value)), + "shuffle_ladders": self.options.shuffle_ladders.value, "Hexagon Quest Prayer": self.ability_unlocks["Pages 24-25 (Prayer)"], "Hexagon Quest Holy Cross": self.ability_unlocks["Pages 42-43 (Holy Cross)"], "Hexagon Quest Icebolt": self.ability_unlocks["Pages 52-53 (Icebolt)"], diff --git a/worlds/tunic/docs/en_TUNIC.md b/worlds/tunic/docs/en_TUNIC.md index 5921d0ed09..ad328999ac 100644 --- a/worlds/tunic/docs/en_TUNIC.md +++ b/worlds/tunic/docs/en_TUNIC.md @@ -67,7 +67,7 @@ For the Entrance Randomizer: Bombs, consumables (non-bomb ones), weapons, melee weapons (stick and sword), keys, hexagons, offerings, hero relics, cards, golden treasures, money, pages, and abilities (the three ability pages). There are also a few groups being used for singular items: laurels, orb, dagger, magic rod, holy cross, prayer, icebolt, and progressive sword. ## What location groups are there? -Holy cross (for all holy cross checks), fairies (for the two fairy checks), well (for the coin well checks), and shop. Additionally, for checks that do not fall into the above categories, the name of the region is the name of the location group. +Holy cross (for all holy cross checks), fairies (for the two fairy checks), well (for the coin well checks), shop, bosses (for the bosses with checks associated with them), hero relic (for the 6 hero grave checks), and ladders (for the ladder items when you have shuffle ladders enabled). ## Is Connection Plando supported? Yes. The host needs to enable it in their `host.yaml`, and the player's yaml needs to contain a plando_connections block. diff --git a/worlds/tunic/er_data.py b/worlds/tunic/er_data.py index 8d8db426f6..d850a06dfa 100644 --- a/worlds/tunic/er_data.py +++ b/worlds/tunic/er_data.py @@ -5,505 +5,509 @@ from enum import IntEnum class Portal(NamedTuple): name: str # human-readable name region: str # AP region - destination: str # vanilla destination scene and tag + destination: str # vanilla destination scene + tag: str # vanilla tag def scene(self) -> str: # the actual scene name in Tunic return tunic_er_regions[self.region].game_scene def scene_destination(self) -> str: # full, nonchanging name to interpret by the mod - return self.scene() + ", " + self.destination + return self.scene() + ", " + self.destination + self.tag + + def destination_scene(self) -> str: # the vanilla connection + return self.destination + ", " + self.scene() + self.tag portal_mapping: List[Portal] = [ Portal(name="Stick House Entrance", region="Overworld", - destination="Sword Cave_"), + destination="Sword Cave", tag="_"), Portal(name="Windmill Entrance", region="Overworld", - destination="Windmill_"), - Portal(name="Well Ladder Entrance", region="Overworld", - destination="Sewer_entrance"), + destination="Windmill", tag="_"), + Portal(name="Well Ladder Entrance", region="Overworld Well Ladder", + destination="Sewer", tag="_entrance"), Portal(name="Entrance to Well from Well Rail", region="Overworld Well to Furnace Rail", - destination="Sewer_west_aqueduct"), + destination="Sewer", tag="_west_aqueduct"), Portal(name="Old House Door Entrance", region="Overworld Old House Door", - destination="Overworld Interiors_house"), + destination="Overworld Interiors", tag="_house"), Portal(name="Old House Waterfall Entrance", region="Overworld", - destination="Overworld Interiors_under_checkpoint"), + destination="Overworld Interiors", tag="_under_checkpoint"), Portal(name="Entrance to Furnace from Well Rail", region="Overworld Well to Furnace Rail", - destination="Furnace_gyro_upper_north"), + destination="Furnace", tag="_gyro_upper_north"), Portal(name="Entrance to Furnace under Windmill", region="Overworld", - destination="Furnace_gyro_upper_east"), + destination="Furnace", tag="_gyro_upper_east"), Portal(name="Entrance to Furnace near West Garden", region="Overworld to West Garden from Furnace", - destination="Furnace_gyro_west"), - Portal(name="Entrance to Furnace from Beach", region="Overworld", - destination="Furnace_gyro_lower"), - Portal(name="Caustic Light Cave Entrance", region="Overworld", - destination="Overworld Cave_"), + destination="Furnace", tag="_gyro_west"), + Portal(name="Entrance to Furnace from Beach", region="Overworld Tunnel Turret", + destination="Furnace", tag="_gyro_lower"), + Portal(name="Caustic Light Cave Entrance", region="Overworld Swamp Lower Entry", + destination="Overworld Cave", tag="_"), Portal(name="Swamp Upper Entrance", region="Overworld Swamp Upper Entry", - destination="Swamp Redux 2_wall"), - Portal(name="Swamp Lower Entrance", region="Overworld", - destination="Swamp Redux 2_conduit"), - Portal(name="Ruined Passage Not-Door Entrance", region="Overworld", - destination="Ruins Passage_east"), + destination="Swamp Redux 2", tag="_wall"), + Portal(name="Swamp Lower Entrance", region="Overworld Swamp Lower Entry", + destination="Swamp Redux 2", tag="_conduit"), + Portal(name="Ruined Passage Not-Door Entrance", region="After Ruined Passage", + destination="Ruins Passage", tag="_east"), Portal(name="Ruined Passage Door Entrance", region="Overworld Ruined Passage Door", - destination="Ruins Passage_west"), - Portal(name="Atoll Upper Entrance", region="Overworld", - destination="Atoll Redux_upper"), - Portal(name="Atoll Lower Entrance", region="Overworld", - destination="Atoll Redux_lower"), + destination="Ruins Passage", tag="_west"), + Portal(name="Atoll Upper Entrance", region="Overworld to Atoll Upper", + destination="Atoll Redux", tag="_upper"), + Portal(name="Atoll Lower Entrance", region="Overworld Beach", + destination="Atoll Redux", tag="_lower"), Portal(name="Special Shop Entrance", region="Overworld Special Shop Entry", - destination="ShopSpecial_"), - Portal(name="Maze Cave Entrance", region="Overworld", - destination="Maze Room_"), - Portal(name="West Garden Entrance near Belltower", region="Overworld Belltower", - destination="Archipelagos Redux_upper"), + destination="ShopSpecial", tag="_"), + Portal(name="Maze Cave Entrance", region="Overworld Beach", + destination="Maze Room", tag="_"), + Portal(name="West Garden Entrance near Belltower", region="Overworld to West Garden Upper", + destination="Archipelagos Redux", tag="_upper"), Portal(name="West Garden Entrance from Furnace", region="Overworld to West Garden from Furnace", - destination="Archipelagos Redux_lower"), + destination="Archipelagos Redux", tag="_lower"), Portal(name="West Garden Laurels Entrance", region="Overworld West Garden Laurels Entry", - destination="Archipelagos Redux_lowest"), + destination="Archipelagos Redux", tag="_lowest"), Portal(name="Temple Door Entrance", region="Overworld Temple Door", - destination="Temple_main"), - Portal(name="Temple Rafters Entrance", region="Overworld", - destination="Temple_rafters"), + destination="Temple", tag="_main"), + Portal(name="Temple Rafters Entrance", region="Overworld after Temple Rafters", + destination="Temple", tag="_rafters"), Portal(name="Ruined Shop Entrance", region="Overworld", - destination="Ruined Shop_"), - Portal(name="Patrol Cave Entrance", region="Overworld", - destination="PatrolCave_"), - Portal(name="Hourglass Cave Entrance", region="Overworld", - destination="Town Basement_beach"), + destination="Ruined Shop", tag="_"), + Portal(name="Patrol Cave Entrance", region="Overworld at Patrol Cave", + destination="PatrolCave", tag="_"), + Portal(name="Hourglass Cave Entrance", region="Overworld Beach", + destination="Town Basement", tag="_beach"), Portal(name="Changing Room Entrance", region="Overworld", - destination="Changing Room_"), + destination="Changing Room", tag="_"), Portal(name="Cube Cave Entrance", region="Overworld", - destination="CubeRoom_"), - Portal(name="Stairs from Overworld to Mountain", region="Overworld", - destination="Mountain_"), - Portal(name="Overworld to Fortress", region="Overworld", - destination="Fortress Courtyard_"), + destination="CubeRoom", tag="_"), + Portal(name="Stairs from Overworld to Mountain", region="Upper Overworld", + destination="Mountain", tag="_"), + Portal(name="Overworld to Fortress", region="East Overworld", + destination="Fortress Courtyard", tag="_"), Portal(name="Fountain HC Door Entrance", region="Overworld Fountain Cross Door", - destination="Town_FiligreeRoom_"), + destination="Town_FiligreeRoom", tag="_"), Portal(name="Southeast HC Door Entrance", region="Overworld Southeast Cross Door", - destination="EastFiligreeCache_"), - Portal(name="Overworld to Quarry Connector", region="Overworld", - destination="Darkwoods Tunnel_"), + destination="EastFiligreeCache", tag="_"), + Portal(name="Overworld to Quarry Connector", region="Overworld Quarry Entry", + destination="Darkwoods Tunnel", tag="_"), Portal(name="Dark Tomb Main Entrance", region="Overworld", - destination="Crypt Redux_"), - Portal(name="Overworld to Forest Belltower", region="Overworld", - destination="Forest Belltower_"), + destination="Crypt Redux", tag="_"), + Portal(name="Overworld to Forest Belltower", region="East Overworld", + destination="Forest Belltower", tag="_"), Portal(name="Town to Far Shore", region="Overworld Town Portal", - destination="Transit_teleporter_town"), + destination="Transit", tag="_teleporter_town"), Portal(name="Spawn to Far Shore", region="Overworld Spawn Portal", - destination="Transit_teleporter_starting island"), + destination="Transit", tag="_teleporter_starting island"), Portal(name="Secret Gathering Place Entrance", region="Overworld", - destination="Waterfall_"), + destination="Waterfall", tag="_"), Portal(name="Secret Gathering Place Exit", region="Secret Gathering Place", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Windmill Exit", region="Windmill", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Windmill Shop", region="Windmill", - destination="Shop_"), + destination="Shop", tag="_"), Portal(name="Old House Door Exit", region="Old House Front", - destination="Overworld Redux_house"), + destination="Overworld Redux", tag="_house"), Portal(name="Old House to Glyph Tower", region="Old House Front", - destination="g_elements_"), + destination="g_elements", tag="_"), Portal(name="Old House Waterfall Exit", region="Old House Back", - destination="Overworld Redux_under_checkpoint"), + destination="Overworld Redux", tag="_under_checkpoint"), Portal(name="Glyph Tower Exit", region="Relic Tower", - destination="Overworld Interiors_"), + destination="Overworld Interiors", tag="_"), Portal(name="Changing Room Exit", region="Changing Room", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Fountain HC Room Exit", region="Fountain Cross Room", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Cube Cave Exit", region="Cube Cave", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Guard Patrol Cave Exit", region="Patrol Cave", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Ruined Shop Exit", region="Ruined Shop", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Furnace Exit towards Well", region="Furnace Fuse", - destination="Overworld Redux_gyro_upper_north"), + destination="Overworld Redux", tag="_gyro_upper_north"), Portal(name="Furnace Exit to Dark Tomb", region="Furnace Walking Path", - destination="Crypt Redux_"), + destination="Crypt Redux", tag="_"), Portal(name="Furnace Exit towards West Garden", region="Furnace Walking Path", - destination="Overworld Redux_gyro_west"), + destination="Overworld Redux", tag="_gyro_west"), Portal(name="Furnace Exit to Beach", region="Furnace Ladder Area", - destination="Overworld Redux_gyro_lower"), + destination="Overworld Redux", tag="_gyro_lower"), Portal(name="Furnace Exit under Windmill", region="Furnace Ladder Area", - destination="Overworld Redux_gyro_upper_east"), + destination="Overworld Redux", tag="_gyro_upper_east"), Portal(name="Stick House Exit", region="Stick House", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Ruined Passage Not-Door Exit", region="Ruined Passage", - destination="Overworld Redux_east"), + destination="Overworld Redux", tag="_east"), Portal(name="Ruined Passage Door Exit", region="Ruined Passage", - destination="Overworld Redux_west"), + destination="Overworld Redux", tag="_west"), Portal(name="Southeast HC Room Exit", region="Southeast Cross Room", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Caustic Light Cave Exit", region="Caustic Light Cave", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Maze Cave Exit", region="Maze Cave", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Hourglass Cave Exit", region="Hourglass Cave", - destination="Overworld Redux_beach"), + destination="Overworld Redux", tag="_beach"), Portal(name="Special Shop Exit", region="Special Shop", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Temple Rafters Exit", region="Sealed Temple Rafters", - destination="Overworld Redux_rafters"), + destination="Overworld Redux", tag="_rafters"), Portal(name="Temple Door Exit", region="Sealed Temple", - destination="Overworld Redux_main"), + destination="Overworld Redux", tag="_main"), - Portal(name="Well Ladder Exit", region="Beneath the Well Front", - destination="Overworld Redux_entrance"), + Portal(name="Well Ladder Exit", region="Beneath the Well Ladder Exit", + destination="Overworld Redux", tag="_entrance"), Portal(name="Well to Well Boss", region="Beneath the Well Back", - destination="Sewer_Boss_"), + destination="Sewer_Boss", tag="_"), Portal(name="Well Exit towards Furnace", region="Beneath the Well Back", - destination="Overworld Redux_west_aqueduct"), + destination="Overworld Redux", tag="_west_aqueduct"), Portal(name="Well Boss to Well", region="Well Boss", - destination="Sewer_"), + destination="Sewer", tag="_"), Portal(name="Checkpoint to Dark Tomb", region="Dark Tomb Checkpoint", - destination="Crypt Redux_"), + destination="Crypt Redux", tag="_"), Portal(name="Dark Tomb to Overworld", region="Dark Tomb Entry Point", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Dark Tomb to Furnace", region="Dark Tomb Dark Exit", - destination="Furnace_"), + destination="Furnace", tag="_"), Portal(name="Dark Tomb to Checkpoint", region="Dark Tomb Entry Point", - destination="Sewer_Boss_"), + destination="Sewer_Boss", tag="_"), Portal(name="West Garden Exit near Hero's Grave", region="West Garden", - destination="Overworld Redux_lower"), + destination="Overworld Redux", tag="_lower"), Portal(name="West Garden to Magic Dagger House", region="West Garden", - destination="archipelagos_house_"), + destination="archipelagos_house", tag="_"), Portal(name="West Garden Exit after Boss", region="West Garden after Boss", - destination="Overworld Redux_upper"), + destination="Overworld Redux", tag="_upper"), Portal(name="West Garden Shop", region="West Garden", - destination="Shop_"), - Portal(name="West Garden Laurels Exit", region="West Garden Laurels Exit", - destination="Overworld Redux_lowest"), - Portal(name="West Garden Hero's Grave", region="West Garden Hero's Grave", - destination="RelicVoid_teleporter_relic plinth"), + destination="Shop", tag="_"), + Portal(name="West Garden Laurels Exit", region="West Garden Laurels Exit Region", + destination="Overworld Redux", tag="_lowest"), + Portal(name="West Garden Hero's Grave", region="West Garden Hero's Grave Region", + destination="RelicVoid", tag="_teleporter_relic plinth"), Portal(name="West Garden to Far Shore", region="West Garden Portal", - destination="Transit_teleporter_archipelagos_teleporter"), + destination="Transit", tag="_teleporter_archipelagos_teleporter"), Portal(name="Magic Dagger House Exit", region="Magic Dagger House", - destination="Archipelagos Redux_"), + destination="Archipelagos Redux", tag="_"), Portal(name="Atoll Upper Exit", region="Ruined Atoll", - destination="Overworld Redux_upper"), + destination="Overworld Redux", tag="_upper"), Portal(name="Atoll Lower Exit", region="Ruined Atoll Lower Entry Area", - destination="Overworld Redux_lower"), + destination="Overworld Redux", tag="_lower"), Portal(name="Atoll Shop", region="Ruined Atoll", - destination="Shop_"), + destination="Shop", tag="_"), Portal(name="Atoll to Far Shore", region="Ruined Atoll Portal", - destination="Transit_teleporter_atoll"), + destination="Transit", tag="_teleporter_atoll"), Portal(name="Atoll Statue Teleporter", region="Ruined Atoll Statue", - destination="Library Exterior_"), - Portal(name="Frog Stairs Eye Entrance", region="Ruined Atoll", - destination="Frog Stairs_eye"), + destination="Library Exterior", tag="_"), + Portal(name="Frog Stairs Eye Entrance", region="Ruined Atoll Frog Eye", + destination="Frog Stairs", tag="_eye"), Portal(name="Frog Stairs Mouth Entrance", region="Ruined Atoll Frog Mouth", - destination="Frog Stairs_mouth"), + destination="Frog Stairs", tag="_mouth"), - Portal(name="Frog Stairs Eye Exit", region="Frog's Domain Entry", - destination="Atoll Redux_eye"), - Portal(name="Frog Stairs Mouth Exit", region="Frog's Domain Entry", - destination="Atoll Redux_mouth"), - Portal(name="Frog Stairs to Frog's Domain's Entrance", region="Frog's Domain Entry", - destination="frog cave main_Entrance"), - Portal(name="Frog Stairs to Frog's Domain's Exit", region="Frog's Domain Entry", - destination="frog cave main_Exit"), + Portal(name="Frog Stairs Eye Exit", region="Frog Stairs Eye Exit", + destination="Atoll Redux", tag="_eye"), + Portal(name="Frog Stairs Mouth Exit", region="Frog Stairs Upper", + destination="Atoll Redux", tag="_mouth"), + Portal(name="Frog Stairs to Frog's Domain's Entrance", region="Frog Stairs to Frog's Domain", + destination="frog cave main", tag="_Entrance"), + Portal(name="Frog Stairs to Frog's Domain's Exit", region="Frog Stairs Lower", + destination="frog cave main", tag="_Exit"), - Portal(name="Frog's Domain Ladder Exit", region="Frog's Domain", - destination="Frog Stairs_Entrance"), + Portal(name="Frog's Domain Ladder Exit", region="Frog's Domain Entry", + destination="Frog Stairs", tag="_Entrance"), Portal(name="Frog's Domain Orb Exit", region="Frog's Domain Back", - destination="Frog Stairs_Exit"), + destination="Frog Stairs", tag="_Exit"), - Portal(name="Library Exterior Tree", region="Library Exterior Tree", - destination="Atoll Redux_"), - Portal(name="Library Exterior Ladder", region="Library Exterior Ladder", - destination="Library Hall_"), + Portal(name="Library Exterior Tree", region="Library Exterior Tree Region", + destination="Atoll Redux", tag="_"), + Portal(name="Library Exterior Ladder", region="Library Exterior Ladder Region", + destination="Library Hall", tag="_"), - Portal(name="Library Hall Bookshelf Exit", region="Library Hall", - destination="Library Exterior_"), - Portal(name="Library Hero's Grave", region="Library Hero's Grave", - destination="RelicVoid_teleporter_relic plinth"), - Portal(name="Library Hall to Rotunda", region="Library Hall", - destination="Library Rotunda_"), + Portal(name="Library Hall Bookshelf Exit", region="Library Hall Bookshelf", + destination="Library Exterior", tag="_"), + Portal(name="Library Hero's Grave", region="Library Hero's Grave Region", + destination="RelicVoid", tag="_teleporter_relic plinth"), + Portal(name="Library Hall to Rotunda", region="Library Hall to Rotunda", + destination="Library Rotunda", tag="_"), - Portal(name="Library Rotunda Lower Exit", region="Library Rotunda", - destination="Library Hall_"), - Portal(name="Library Rotunda Upper Exit", region="Library Rotunda", - destination="Library Lab_"), + Portal(name="Library Rotunda Lower Exit", region="Library Rotunda to Hall", + destination="Library Hall", tag="_"), + Portal(name="Library Rotunda Upper Exit", region="Library Rotunda to Lab", + destination="Library Lab", tag="_"), Portal(name="Library Lab to Rotunda", region="Library Lab Lower", - destination="Library Rotunda_"), + destination="Library Rotunda", tag="_"), Portal(name="Library to Far Shore", region="Library Portal", - destination="Transit_teleporter_library teleporter"), - Portal(name="Library Lab to Librarian Arena", region="Library Lab", - destination="Library Arena_"), + destination="Transit", tag="_teleporter_library teleporter"), + Portal(name="Library Lab to Librarian Arena", region="Library Lab to Librarian", + destination="Library Arena", tag="_"), Portal(name="Librarian Arena Exit", region="Library Arena", - destination="Library Lab_"), + destination="Library Lab", tag="_"), Portal(name="Forest to Belltower", region="East Forest", - destination="Forest Belltower_"), + destination="Forest Belltower", tag="_"), Portal(name="Forest Guard House 1 Lower Entrance", region="East Forest", - destination="East Forest Redux Laddercave_lower"), + destination="East Forest Redux Laddercave", tag="_lower"), Portal(name="Forest Guard House 1 Gate Entrance", region="East Forest", - destination="East Forest Redux Laddercave_gate"), + destination="East Forest Redux Laddercave", tag="_gate"), Portal(name="Forest Dance Fox Outside Doorway", region="East Forest Dance Fox Spot", - destination="East Forest Redux Laddercave_upper"), + destination="East Forest Redux Laddercave", tag="_upper"), Portal(name="Forest to Far Shore", region="East Forest Portal", - destination="Transit_teleporter_forest teleporter"), - Portal(name="Forest Guard House 2 Lower Entrance", region="East Forest", - destination="East Forest Redux Interior_lower"), + destination="Transit", tag="_teleporter_forest teleporter"), + Portal(name="Forest Guard House 2 Lower Entrance", region="Lower Forest", + destination="East Forest Redux Interior", tag="_lower"), Portal(name="Forest Guard House 2 Upper Entrance", region="East Forest", - destination="East Forest Redux Interior_upper"), + destination="East Forest Redux Interior", tag="_upper"), Portal(name="Forest Grave Path Lower Entrance", region="East Forest", - destination="Sword Access_lower"), + destination="Sword Access", tag="_lower"), Portal(name="Forest Grave Path Upper Entrance", region="East Forest", - destination="Sword Access_upper"), + destination="Sword Access", tag="_upper"), Portal(name="Guard House 1 Dance Fox Exit", region="Guard House 1 West", - destination="East Forest Redux_upper"), + destination="East Forest Redux", tag="_upper"), Portal(name="Guard House 1 Lower Exit", region="Guard House 1 West", - destination="East Forest Redux_lower"), + destination="East Forest Redux", tag="_lower"), Portal(name="Guard House 1 Upper Forest Exit", region="Guard House 1 East", - destination="East Forest Redux_gate"), + destination="East Forest Redux", tag="_gate"), Portal(name="Guard House 1 to Guard Captain Room", region="Guard House 1 East", - destination="Forest Boss Room_"), + destination="Forest Boss Room", tag="_"), Portal(name="Forest Grave Path Upper Exit", region="Forest Grave Path Upper", - destination="East Forest Redux_upper"), + destination="East Forest Redux", tag="_upper"), Portal(name="Forest Grave Path Lower Exit", region="Forest Grave Path Main", - destination="East Forest Redux_lower"), + destination="East Forest Redux", tag="_lower"), Portal(name="East Forest Hero's Grave", region="Forest Hero's Grave", - destination="RelicVoid_teleporter_relic plinth"), + destination="RelicVoid", tag="_teleporter_relic plinth"), - Portal(name="Guard House 2 Lower Exit", region="Guard House 2", - destination="East Forest Redux_lower"), - Portal(name="Guard House 2 Upper Exit", region="Guard House 2", - destination="East Forest Redux_upper"), + Portal(name="Guard House 2 Lower Exit", region="Guard House 2 Lower", + destination="East Forest Redux", tag="_lower"), + Portal(name="Guard House 2 Upper Exit", region="Guard House 2 Upper", + destination="East Forest Redux", tag="_upper"), Portal(name="Guard Captain Room Non-Gate Exit", region="Forest Boss Room", - destination="East Forest Redux Laddercave_"), + destination="East Forest Redux Laddercave", tag="_"), Portal(name="Guard Captain Room Gate Exit", region="Forest Boss Room", - destination="Forest Belltower_"), + destination="Forest Belltower", tag="_"), Portal(name="Forest Belltower to Fortress", region="Forest Belltower Main", - destination="Fortress Courtyard_"), + destination="Fortress Courtyard", tag="_"), Portal(name="Forest Belltower to Forest", region="Forest Belltower Lower", - destination="East Forest Redux_"), + destination="East Forest Redux", tag="_"), Portal(name="Forest Belltower to Overworld", region="Forest Belltower Main", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Forest Belltower to Guard Captain Room", region="Forest Belltower Upper", - destination="Forest Boss Room_"), + destination="Forest Boss Room", tag="_"), Portal(name="Fortress Courtyard to Fortress Grave Path Lower", region="Fortress Courtyard", - destination="Fortress Reliquary_Lower"), + destination="Fortress Reliquary", tag="_Lower"), Portal(name="Fortress Courtyard to Fortress Grave Path Upper", region="Fortress Courtyard Upper", - destination="Fortress Reliquary_Upper"), + destination="Fortress Reliquary", tag="_Upper"), Portal(name="Fortress Courtyard to Fortress Interior", region="Fortress Courtyard", - destination="Fortress Main_Big Door"), + destination="Fortress Main", tag="_Big Door"), Portal(name="Fortress Courtyard to East Fortress", region="Fortress Courtyard Upper", - destination="Fortress East_"), - Portal(name="Fortress Courtyard to Beneath the Earth", region="Fortress Exterior near cave", - destination="Fortress Basement_"), + destination="Fortress East", tag="_"), + Portal(name="Fortress Courtyard to Beneath the Vault", region="Beneath the Vault Entry", + destination="Fortress Basement", tag="_"), Portal(name="Fortress Courtyard to Forest Belltower", region="Fortress Exterior from East Forest", - destination="Forest Belltower_"), + destination="Forest Belltower", tag="_"), Portal(name="Fortress Courtyard to Overworld", region="Fortress Exterior from Overworld", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Fortress Courtyard Shop", region="Fortress Exterior near cave", - destination="Shop_"), + destination="Shop", tag="_"), - Portal(name="Beneath the Earth to Fortress Interior", region="Beneath the Vault Back", - destination="Fortress Main_"), - Portal(name="Beneath the Earth to Fortress Courtyard", region="Beneath the Vault Front", - destination="Fortress Courtyard_"), + Portal(name="Beneath the Vault to Fortress Interior", region="Beneath the Vault Back", + destination="Fortress Main", tag="_"), + Portal(name="Beneath the Vault to Fortress Courtyard", region="Beneath the Vault Ladder Exit", + destination="Fortress Courtyard", tag="_"), Portal(name="Fortress Interior Main Exit", region="Eastern Vault Fortress", - destination="Fortress Courtyard_Big Door"), + destination="Fortress Courtyard", tag="_Big Door"), Portal(name="Fortress Interior to Beneath the Earth", region="Eastern Vault Fortress", - destination="Fortress Basement_"), + destination="Fortress Basement", tag="_"), Portal(name="Fortress Interior to Siege Engine Arena", region="Eastern Vault Fortress Gold Door", - destination="Fortress Arena_"), + destination="Fortress Arena", tag="_"), Portal(name="Fortress Interior Shop", region="Eastern Vault Fortress", - destination="Shop_"), + destination="Shop", tag="_"), Portal(name="Fortress Interior to East Fortress Upper", region="Eastern Vault Fortress", - destination="Fortress East_upper"), + destination="Fortress East", tag="_upper"), Portal(name="Fortress Interior to East Fortress Lower", region="Eastern Vault Fortress", - destination="Fortress East_lower"), + destination="Fortress East", tag="_lower"), Portal(name="East Fortress to Interior Lower", region="Fortress East Shortcut Lower", - destination="Fortress Main_lower"), + destination="Fortress Main", tag="_lower"), Portal(name="East Fortress to Courtyard", region="Fortress East Shortcut Upper", - destination="Fortress Courtyard_"), + destination="Fortress Courtyard", tag="_"), Portal(name="East Fortress to Interior Upper", region="Fortress East Shortcut Upper", - destination="Fortress Main_upper"), + destination="Fortress Main", tag="_upper"), Portal(name="Fortress Grave Path Lower Exit", region="Fortress Grave Path", - destination="Fortress Courtyard_Lower"), - Portal(name="Fortress Hero's Grave", region="Fortress Grave Path", - destination="RelicVoid_teleporter_relic plinth"), + destination="Fortress Courtyard", tag="_Lower"), + Portal(name="Fortress Hero's Grave", region="Fortress Hero's Grave Region", + destination="RelicVoid", tag="_teleporter_relic plinth"), Portal(name="Fortress Grave Path Upper Exit", region="Fortress Grave Path Upper", - destination="Fortress Courtyard_Upper"), - Portal(name="Fortress Grave Path Dusty Entrance", region="Fortress Grave Path Dusty Entrance", - destination="Dusty_"), + destination="Fortress Courtyard", tag="_Upper"), + Portal(name="Fortress Grave Path Dusty Entrance", region="Fortress Grave Path Dusty Entrance Region", + destination="Dusty", tag="_"), Portal(name="Dusty Exit", region="Fortress Leaf Piles", - destination="Fortress Reliquary_"), + destination="Fortress Reliquary", tag="_"), Portal(name="Siege Engine Arena to Fortress", region="Fortress Arena", - destination="Fortress Main_"), + destination="Fortress Main", tag="_"), Portal(name="Fortress to Far Shore", region="Fortress Arena Portal", - destination="Transit_teleporter_spidertank"), + destination="Transit", tag="_teleporter_spidertank"), Portal(name="Stairs to Top of the Mountain", region="Lower Mountain Stairs", - destination="Mountaintop_"), + destination="Mountaintop", tag="_"), Portal(name="Mountain to Quarry", region="Lower Mountain", - destination="Quarry Redux_"), + destination="Quarry Redux", tag="_"), Portal(name="Mountain to Overworld", region="Lower Mountain", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Top of the Mountain Exit", region="Top of the Mountain", - destination="Mountain_"), + destination="Mountain", tag="_"), Portal(name="Quarry Connector to Overworld", region="Quarry Connector", - destination="Overworld Redux_"), + destination="Overworld Redux", tag="_"), Portal(name="Quarry Connector to Quarry", region="Quarry Connector", - destination="Quarry Redux_"), + destination="Quarry Redux", tag="_"), Portal(name="Quarry to Overworld Exit", region="Quarry Entry", - destination="Darkwoods Tunnel_"), + destination="Darkwoods Tunnel", tag="_"), Portal(name="Quarry Shop", region="Quarry Entry", - destination="Shop_"), + destination="Shop", tag="_"), Portal(name="Quarry to Monastery Front", region="Quarry Monastery Entry", - destination="Monastery_front"), + destination="Monastery", tag="_front"), Portal(name="Quarry to Monastery Back", region="Monastery Rope", - destination="Monastery_back"), + destination="Monastery", tag="_back"), Portal(name="Quarry to Mountain", region="Quarry Back", - destination="Mountain_"), + destination="Mountain", tag="_"), Portal(name="Quarry to Ziggurat", region="Lower Quarry Zig Door", - destination="ziggurat2020_0_"), + destination="ziggurat2020_0", tag="_"), Portal(name="Quarry to Far Shore", region="Quarry Portal", - destination="Transit_teleporter_quarry teleporter"), + destination="Transit", tag="_teleporter_quarry teleporter"), Portal(name="Monastery Rear Exit", region="Monastery Back", - destination="Quarry Redux_back"), + destination="Quarry Redux", tag="_back"), Portal(name="Monastery Front Exit", region="Monastery Front", - destination="Quarry Redux_front"), - Portal(name="Monastery Hero's Grave", region="Monastery Hero's Grave", - destination="RelicVoid_teleporter_relic plinth"), + destination="Quarry Redux", tag="_front"), + Portal(name="Monastery Hero's Grave", region="Monastery Hero's Grave Region", + destination="RelicVoid", tag="_teleporter_relic plinth"), Portal(name="Ziggurat Entry Hallway to Ziggurat Upper", region="Rooted Ziggurat Entry", - destination="ziggurat2020_1_"), + destination="ziggurat2020_1", tag="_"), Portal(name="Ziggurat Entry Hallway to Quarry", region="Rooted Ziggurat Entry", - destination="Quarry Redux_"), + destination="Quarry Redux", tag="_"), Portal(name="Ziggurat Upper to Ziggurat Entry Hallway", region="Rooted Ziggurat Upper Entry", - destination="ziggurat2020_0_"), + destination="ziggurat2020_0", tag="_"), Portal(name="Ziggurat Upper to Ziggurat Tower", region="Rooted Ziggurat Upper Back", - destination="ziggurat2020_2_"), + destination="ziggurat2020_2", tag="_"), Portal(name="Ziggurat Tower to Ziggurat Upper", region="Rooted Ziggurat Middle Top", - destination="ziggurat2020_1_"), + destination="ziggurat2020_1", tag="_"), Portal(name="Ziggurat Tower to Ziggurat Lower", region="Rooted Ziggurat Middle Bottom", - destination="ziggurat2020_3_"), + destination="ziggurat2020_3", tag="_"), Portal(name="Ziggurat Lower to Ziggurat Tower", region="Rooted Ziggurat Lower Front", - destination="ziggurat2020_2_"), + destination="ziggurat2020_2", tag="_"), Portal(name="Ziggurat Portal Room Entrance", region="Rooted Ziggurat Portal Room Entrance", - destination="ziggurat2020_FTRoom_"), + destination="ziggurat2020_FTRoom", tag="_"), Portal(name="Ziggurat Portal Room Exit", region="Rooted Ziggurat Portal Room Exit", - destination="ziggurat2020_3_"), + destination="ziggurat2020_3", tag="_"), Portal(name="Ziggurat to Far Shore", region="Rooted Ziggurat Portal", - destination="Transit_teleporter_ziggurat teleporter"), + destination="Transit", tag="_teleporter_ziggurat teleporter"), - Portal(name="Swamp Lower Exit", region="Swamp", - destination="Overworld Redux_conduit"), - Portal(name="Swamp to Cathedral Main Entrance", region="Swamp to Cathedral Main Entrance", - destination="Cathedral Redux_main"), + Portal(name="Swamp Lower Exit", region="Swamp Front", + destination="Overworld Redux", tag="_conduit"), + Portal(name="Swamp to Cathedral Main Entrance", region="Swamp to Cathedral Main Entrance Region", + destination="Cathedral Redux", tag="_main"), Portal(name="Swamp to Cathedral Secret Legend Room Entrance", region="Swamp to Cathedral Treasure Room", - destination="Cathedral Redux_secret"), + destination="Cathedral Redux", tag="_secret"), Portal(name="Swamp to Gauntlet", region="Back of Swamp", - destination="Cathedral Arena_"), - Portal(name="Swamp Shop", region="Swamp", - destination="Shop_"), + destination="Cathedral Arena", tag="_"), + Portal(name="Swamp Shop", region="Swamp Front", + destination="Shop", tag="_"), Portal(name="Swamp Upper Exit", region="Back of Swamp Laurels Area", - destination="Overworld Redux_wall"), - Portal(name="Swamp Hero's Grave", region="Swamp Hero's Grave", - destination="RelicVoid_teleporter_relic plinth"), + destination="Overworld Redux", tag="_wall"), + Portal(name="Swamp Hero's Grave", region="Swamp Hero's Grave Region", + destination="RelicVoid", tag="_teleporter_relic plinth"), Portal(name="Cathedral Main Exit", region="Cathedral", - destination="Swamp Redux 2_main"), + destination="Swamp Redux 2", tag="_main"), Portal(name="Cathedral Elevator", region="Cathedral", - destination="Cathedral Arena_"), + destination="Cathedral Arena", tag="_"), Portal(name="Cathedral Secret Legend Room Exit", region="Cathedral Secret Legend Room", - destination="Swamp Redux 2_secret"), + destination="Swamp Redux 2", tag="_secret"), Portal(name="Gauntlet to Swamp", region="Cathedral Gauntlet Exit", - destination="Swamp Redux 2_"), + destination="Swamp Redux 2", tag="_"), Portal(name="Gauntlet Elevator", region="Cathedral Gauntlet Checkpoint", - destination="Cathedral Redux_"), + destination="Cathedral Redux", tag="_"), Portal(name="Gauntlet Shop", region="Cathedral Gauntlet Checkpoint", - destination="Shop_"), + destination="Shop", tag="_"), Portal(name="Hero's Grave to Fortress", region="Hero Relic - Fortress", - destination="Fortress Reliquary_teleporter_relic plinth"), + destination="Fortress Reliquary", tag="_teleporter_relic plinth"), Portal(name="Hero's Grave to Monastery", region="Hero Relic - Quarry", - destination="Monastery_teleporter_relic plinth"), + destination="Monastery", tag="_teleporter_relic plinth"), Portal(name="Hero's Grave to West Garden", region="Hero Relic - West Garden", - destination="Archipelagos Redux_teleporter_relic plinth"), + destination="Archipelagos Redux", tag="_teleporter_relic plinth"), Portal(name="Hero's Grave to East Forest", region="Hero Relic - East Forest", - destination="Sword Access_teleporter_relic plinth"), + destination="Sword Access", tag="_teleporter_relic plinth"), Portal(name="Hero's Grave to Library", region="Hero Relic - Library", - destination="Library Hall_teleporter_relic plinth"), + destination="Library Hall", tag="_teleporter_relic plinth"), Portal(name="Hero's Grave to Swamp", region="Hero Relic - Swamp", - destination="Swamp Redux 2_teleporter_relic plinth"), + destination="Swamp Redux 2", tag="_teleporter_relic plinth"), - Portal(name="Far Shore to West Garden", region="Far Shore to West Garden", - destination="Archipelagos Redux_teleporter_archipelagos_teleporter"), - Portal(name="Far Shore to Library", region="Far Shore to Library", - destination="Library Lab_teleporter_library teleporter"), - Portal(name="Far Shore to Quarry", region="Far Shore to Quarry", - destination="Quarry Redux_teleporter_quarry teleporter"), - Portal(name="Far Shore to East Forest", region="Far Shore to East Forest", - destination="East Forest Redux_teleporter_forest teleporter"), - Portal(name="Far Shore to Fortress", region="Far Shore to Fortress", - destination="Fortress Arena_teleporter_spidertank"), + Portal(name="Far Shore to West Garden", region="Far Shore to West Garden Region", + destination="Archipelagos Redux", tag="_teleporter_archipelagos_teleporter"), + Portal(name="Far Shore to Library", region="Far Shore to Library Region", + destination="Library Lab", tag="_teleporter_library teleporter"), + Portal(name="Far Shore to Quarry", region="Far Shore to Quarry Region", + destination="Quarry Redux", tag="_teleporter_quarry teleporter"), + Portal(name="Far Shore to East Forest", region="Far Shore to East Forest Region", + destination="East Forest Redux", tag="_teleporter_forest teleporter"), + Portal(name="Far Shore to Fortress", region="Far Shore to Fortress Region", + destination="Fortress Arena", tag="_teleporter_spidertank"), Portal(name="Far Shore to Atoll", region="Far Shore", - destination="Atoll Redux_teleporter_atoll"), + destination="Atoll Redux", tag="_teleporter_atoll"), Portal(name="Far Shore to Ziggurat", region="Far Shore", - destination="ziggurat2020_FTRoom_teleporter_ziggurat teleporter"), + destination="ziggurat2020_FTRoom", tag="_teleporter_ziggurat teleporter"), Portal(name="Far Shore to Heir", region="Far Shore", - destination="Spirit Arena_teleporter_spirit arena"), + destination="Spirit Arena", tag="_teleporter_spirit arena"), Portal(name="Far Shore to Town", region="Far Shore", - destination="Overworld Redux_teleporter_town"), - Portal(name="Far Shore to Spawn", region="Far Shore to Spawn", - destination="Overworld Redux_teleporter_starting island"), + destination="Overworld Redux", tag="_teleporter_town"), + Portal(name="Far Shore to Spawn", region="Far Shore to Spawn Region", + destination="Overworld Redux", tag="_teleporter_starting island"), Portal(name="Heir Arena Exit", region="Spirit Arena", - destination="Transit_teleporter_spirit arena"), + destination="Transit", tag="_teleporter_spirit arena"), Portal(name="Purgatory Bottom Exit", region="Purgatory", - destination="Purgatory_bottom"), + destination="Purgatory", tag="_bottom"), Portal(name="Purgatory Top Exit", region="Purgatory", - destination="Purgatory_top"), + destination="Purgatory", tag="_top"), ] @@ -520,32 +524,42 @@ class DeadEnd(IntEnum): # there's no dead ends that are only in unrestricted -class Hint(IntEnum): - none = 0 # big areas, empty hallways, etc. - region = 1 # at least one of the portals must not be a dead end - scene = 2 # multiple regions in the scene, so using region could mean no valid hints - special = 3 # for if there's a weird case of specific regions being viable - - # key is the AP region name. "Fake" in region info just means the mod won't receive that info at all tunic_er_regions: Dict[str, RegionInfo] = { "Menu": RegionInfo("Fake", dead_end=DeadEnd.all_cats), - "Overworld": RegionInfo("Overworld Redux"), - "Overworld Holy Cross": RegionInfo("Fake", dead_end=DeadEnd.all_cats), + "Overworld": RegionInfo("Overworld Redux"), # main overworld, the central area + "Overworld Holy Cross": RegionInfo("Fake", dead_end=DeadEnd.all_cats), # main overworld holy cross checks "Overworld Belltower": RegionInfo("Overworld Redux"), # the area with the belltower and chest + "Overworld Belltower at Bell": RegionInfo("Overworld Redux"), # being able to ring the belltower, basically "Overworld Swamp Upper Entry": RegionInfo("Overworld Redux"), # upper swamp entry spot + "Overworld Swamp Lower Entry": RegionInfo("Overworld Redux"), # lower swamp entrance, rotating lights entrance + "After Ruined Passage": RegionInfo("Overworld Redux"), # just the door and chest + "Above Ruined Passage": RegionInfo("Overworld Redux"), # one ladder up from ruined passage + "East Overworld": RegionInfo("Overworld Redux"), # where the east forest and fortress entrances are "Overworld Special Shop Entry": RegionInfo("Overworld Redux"), # special shop entry spot + "Upper Overworld": RegionInfo("Overworld Redux"), # where the mountain stairs are + "Overworld above Quarry Entrance": RegionInfo("Overworld Redux"), # top of the ladder where the chest is + "Overworld after Temple Rafters": RegionInfo("Overworld Redux"), # the ledge after the rafters exit, before ladder + "Overworld Quarry Entry": RegionInfo("Overworld Redux"), # at the top of the ladder, to darkwoods + "Overworld after Envoy": RegionInfo("Overworld Redux"), # after the envoy on the thin bridge to quarry + "Overworld at Patrol Cave": RegionInfo("Overworld Redux"), # right at the patrol cave entrance + "Overworld above Patrol Cave": RegionInfo("Overworld Redux"), # where the hook is, and one ladder up from patrol "Overworld West Garden Laurels Entry": RegionInfo("Overworld Redux"), # west garden laurels entry - "Overworld to West Garden from Furnace": RegionInfo("Overworld Redux", hint=Hint.region), - "Overworld Well to Furnace Rail": RegionInfo("Overworld Redux"), # the tiny rail passageway + "Overworld to West Garden Upper": RegionInfo("Overworld Redux"), # usually leads to garden knight + "Overworld to West Garden from Furnace": RegionInfo("Overworld Redux"), # isolated stairway with one chest + "Overworld Well Ladder": RegionInfo("Overworld Redux"), # just the ladder entrance itself as a region + "Overworld Beach": RegionInfo("Overworld Redux"), # from the two turrets to invisble maze, and lower atoll entry + "Overworld Tunnel Turret": RegionInfo("Overworld Redux"), # the tunnel turret by the southwest beach ladder + "Overworld to Atoll Upper": RegionInfo("Overworld Redux"), # the little ledge before the ladder + "Overworld Well to Furnace Rail": RegionInfo("Overworld Redux"), # the rail hallway, bane of unrestricted logic "Overworld Ruined Passage Door": RegionInfo("Overworld Redux"), # the small space betweeen the door and the portal "Overworld Old House Door": RegionInfo("Overworld Redux"), # the too-small space between the door and the portal "Overworld Southeast Cross Door": RegionInfo("Overworld Redux"), # the small space betweeen the door and the portal - "Overworld Fountain Cross Door": RegionInfo("Overworld Redux"), + "Overworld Fountain Cross Door": RegionInfo("Overworld Redux"), # the small space between the door and the portal "Overworld Temple Door": RegionInfo("Overworld Redux"), # the small space betweeen the door and the portal - "Overworld Town Portal": RegionInfo("Overworld Redux"), - "Overworld Spawn Portal": RegionInfo("Overworld Redux"), - "Stick House": RegionInfo("Sword Cave", dead_end=DeadEnd.all_cats, hint=Hint.region), + "Overworld Town Portal": RegionInfo("Overworld Redux"), # being able to go to or come from the portal + "Overworld Spawn Portal": RegionInfo("Overworld Redux"), # being able to go to or come from the portal + "Stick House": RegionInfo("Sword Cave", dead_end=DeadEnd.all_cats), "Windmill": RegionInfo("Windmill"), "Old House Back": RegionInfo("Overworld Interiors"), # part with the hc door "Old House Front": RegionInfo("Overworld Interiors"), # part with the bedroom @@ -553,87 +567,105 @@ tunic_er_regions: Dict[str, RegionInfo] = { "Furnace Fuse": RegionInfo("Furnace"), # top of the furnace "Furnace Ladder Area": RegionInfo("Furnace"), # the two portals accessible by the ladder "Furnace Walking Path": RegionInfo("Furnace"), # dark tomb to west garden - "Secret Gathering Place": RegionInfo("Waterfall", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Changing Room": RegionInfo("Changing Room", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Patrol Cave": RegionInfo("PatrolCave", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Ruined Shop": RegionInfo("Ruined Shop", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Ruined Passage": RegionInfo("Ruins Passage", hint=Hint.region), - "Special Shop": RegionInfo("ShopSpecial", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Caustic Light Cave": RegionInfo("Overworld Cave", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Maze Cave": RegionInfo("Maze Room", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Cube Cave": RegionInfo("CubeRoom", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Southeast Cross Room": RegionInfo("EastFiligreeCache", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Fountain Cross Room": RegionInfo("Town_FiligreeRoom", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Hourglass Cave": RegionInfo("Town Basement", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Sealed Temple": RegionInfo("Temple", hint=Hint.scene), - "Sealed Temple Rafters": RegionInfo("Temple", hint=Hint.scene), - "Forest Belltower Upper": RegionInfo("Forest Belltower", hint=Hint.region), + "Secret Gathering Place": RegionInfo("Waterfall", dead_end=DeadEnd.all_cats), + "Changing Room": RegionInfo("Changing Room", dead_end=DeadEnd.all_cats), + "Patrol Cave": RegionInfo("PatrolCave", dead_end=DeadEnd.all_cats), + "Ruined Shop": RegionInfo("Ruined Shop", dead_end=DeadEnd.all_cats), + "Ruined Passage": RegionInfo("Ruins Passage"), + "Special Shop": RegionInfo("ShopSpecial", dead_end=DeadEnd.all_cats), + "Caustic Light Cave": RegionInfo("Overworld Cave", dead_end=DeadEnd.all_cats), + "Maze Cave": RegionInfo("Maze Room", dead_end=DeadEnd.all_cats), + "Cube Cave": RegionInfo("CubeRoom", dead_end=DeadEnd.all_cats), + "Southeast Cross Room": RegionInfo("EastFiligreeCache", dead_end=DeadEnd.all_cats), + "Fountain Cross Room": RegionInfo("Town_FiligreeRoom", dead_end=DeadEnd.all_cats), + "Hourglass Cave": RegionInfo("Town Basement", dead_end=DeadEnd.all_cats), + "Hourglass Cave Tower": RegionInfo("Town Basement", dead_end=DeadEnd.all_cats), # top of the tower + "Sealed Temple": RegionInfo("Temple"), + "Sealed Temple Rafters": RegionInfo("Temple"), + "Forest Belltower Upper": RegionInfo("Forest Belltower"), "Forest Belltower Main": RegionInfo("Forest Belltower"), "Forest Belltower Lower": RegionInfo("Forest Belltower"), "East Forest": RegionInfo("East Forest Redux"), "East Forest Dance Fox Spot": RegionInfo("East Forest Redux"), "East Forest Portal": RegionInfo("East Forest Redux"), + "Lower Forest": RegionInfo("East Forest Redux"), # bottom of the forest "Guard House 1 East": RegionInfo("East Forest Redux Laddercave"), "Guard House 1 West": RegionInfo("East Forest Redux Laddercave"), - "Guard House 2": RegionInfo("East Forest Redux Interior"), + "Guard House 2 Upper": RegionInfo("East Forest Redux Interior"), + "Guard House 2 Lower": RegionInfo("East Forest Redux Interior"), "Forest Boss Room": RegionInfo("Forest Boss Room"), "Forest Grave Path Main": RegionInfo("Sword Access"), "Forest Grave Path Upper": RegionInfo("Sword Access"), "Forest Grave Path by Grave": RegionInfo("Sword Access"), "Forest Hero's Grave": RegionInfo("Sword Access"), "Dark Tomb Entry Point": RegionInfo("Crypt Redux"), # both upper exits + "Dark Tomb Upper": RegionInfo("Crypt Redux"), # the part with the casket and the top of the ladder "Dark Tomb Main": RegionInfo("Crypt Redux"), "Dark Tomb Dark Exit": RegionInfo("Crypt Redux"), - "Dark Tomb Checkpoint": RegionInfo("Sewer_Boss"), # can laurels backwards - "Well Boss": RegionInfo("Sewer_Boss"), # can walk through (with bombs at least) - "Beneath the Well Front": RegionInfo("Sewer"), - "Beneath the Well Main": RegionInfo("Sewer"), - "Beneath the Well Back": RegionInfo("Sewer"), + "Dark Tomb Checkpoint": RegionInfo("Sewer_Boss"), + "Well Boss": RegionInfo("Sewer_Boss"), + "Beneath the Well Ladder Exit": RegionInfo("Sewer"), # just the ladder + "Beneath the Well Front": RegionInfo("Sewer"), # the front, to separate it from the weapon requirement in the mid + "Beneath the Well Main": RegionInfo("Sewer"), # the main section of it, requires a weapon + "Beneath the Well Back": RegionInfo("Sewer"), # the back two portals, and all 4 upper chests "West Garden": RegionInfo("Archipelagos Redux"), - "Magic Dagger House": RegionInfo("archipelagos_house", dead_end=DeadEnd.all_cats, hint=Hint.region), + "Magic Dagger House": RegionInfo("archipelagos_house", dead_end=DeadEnd.all_cats), "West Garden Portal": RegionInfo("Archipelagos Redux", dead_end=DeadEnd.restricted), - "West Garden Portal Item": RegionInfo("Archipelagos Redux", dead_end=DeadEnd.restricted, hint=Hint.special), - "West Garden Laurels Exit": RegionInfo("Archipelagos Redux"), + "West Garden Portal Item": RegionInfo("Archipelagos Redux", dead_end=DeadEnd.restricted), + "West Garden Laurels Exit Region": RegionInfo("Archipelagos Redux"), "West Garden after Boss": RegionInfo("Archipelagos Redux"), - "West Garden Hero's Grave": RegionInfo("Archipelagos Redux"), + "West Garden Hero's Grave Region": RegionInfo("Archipelagos Redux"), "Ruined Atoll": RegionInfo("Atoll Redux"), "Ruined Atoll Lower Entry Area": RegionInfo("Atoll Redux"), + "Ruined Atoll Ladder Tops": RegionInfo("Atoll Redux"), # at the top of the 5 ladders in south Atoll "Ruined Atoll Frog Mouth": RegionInfo("Atoll Redux"), + "Ruined Atoll Frog Eye": RegionInfo("Atoll Redux"), "Ruined Atoll Portal": RegionInfo("Atoll Redux"), "Ruined Atoll Statue": RegionInfo("Atoll Redux"), - "Frog's Domain Entry": RegionInfo("Frog Stairs"), - "Frog's Domain": RegionInfo("frog cave main", hint=Hint.region), - "Frog's Domain Back": RegionInfo("frog cave main", hint=Hint.scene), - "Library Exterior Tree": RegionInfo("Library Exterior"), - "Library Exterior Ladder": RegionInfo("Library Exterior"), + "Frog Stairs Eye Exit": RegionInfo("Frog Stairs"), + "Frog Stairs Upper": RegionInfo("Frog Stairs"), + "Frog Stairs Lower": RegionInfo("Frog Stairs"), + "Frog Stairs to Frog's Domain": RegionInfo("Frog Stairs"), + "Frog's Domain Entry": RegionInfo("frog cave main"), + "Frog's Domain": RegionInfo("frog cave main"), + "Frog's Domain Back": RegionInfo("frog cave main"), + "Library Exterior Tree Region": RegionInfo("Library Exterior"), + "Library Exterior Ladder Region": RegionInfo("Library Exterior"), + "Library Hall Bookshelf": RegionInfo("Library Hall"), "Library Hall": RegionInfo("Library Hall"), - "Library Hero's Grave": RegionInfo("Library Hall"), + "Library Hero's Grave Region": RegionInfo("Library Hall"), + "Library Hall to Rotunda": RegionInfo("Library Hall"), + "Library Rotunda to Hall": RegionInfo("Library Rotunda"), "Library Rotunda": RegionInfo("Library Rotunda"), + "Library Rotunda to Lab": RegionInfo("Library Rotunda"), "Library Lab": RegionInfo("Library Lab"), "Library Lab Lower": RegionInfo("Library Lab"), "Library Portal": RegionInfo("Library Lab"), - "Library Arena": RegionInfo("Library Arena", dead_end=DeadEnd.all_cats, hint=Hint.region), + "Library Lab to Librarian": RegionInfo("Library Lab"), + "Library Arena": RegionInfo("Library Arena", dead_end=DeadEnd.all_cats), "Fortress Exterior from East Forest": RegionInfo("Fortress Courtyard"), "Fortress Exterior from Overworld": RegionInfo("Fortress Courtyard"), "Fortress Exterior near cave": RegionInfo("Fortress Courtyard"), # where the shop and beneath the earth entry are + "Beneath the Vault Entry": RegionInfo("Fortress Courtyard"), "Fortress Courtyard": RegionInfo("Fortress Courtyard"), "Fortress Courtyard Upper": RegionInfo("Fortress Courtyard"), - "Beneath the Vault Front": RegionInfo("Fortress Basement", hint=Hint.scene), # the vanilla entry point - "Beneath the Vault Back": RegionInfo("Fortress Basement", hint=Hint.scene), # the vanilla exit point + "Beneath the Vault Ladder Exit": RegionInfo("Fortress Basement"), + "Beneath the Vault Front": RegionInfo("Fortress Basement"), # the vanilla entry point + "Beneath the Vault Back": RegionInfo("Fortress Basement"), # the vanilla exit point "Eastern Vault Fortress": RegionInfo("Fortress Main"), "Eastern Vault Fortress Gold Door": RegionInfo("Fortress Main"), "Fortress East Shortcut Upper": RegionInfo("Fortress East"), "Fortress East Shortcut Lower": RegionInfo("Fortress East"), "Fortress Grave Path": RegionInfo("Fortress Reliquary"), - "Fortress Grave Path Upper": RegionInfo("Fortress Reliquary", dead_end=DeadEnd.restricted, hint=Hint.region), - "Fortress Grave Path Dusty Entrance": RegionInfo("Fortress Reliquary"), - "Fortress Hero's Grave": RegionInfo("Fortress Reliquary"), - "Fortress Leaf Piles": RegionInfo("Dusty", dead_end=DeadEnd.all_cats, hint=Hint.region), + "Fortress Grave Path Upper": RegionInfo("Fortress Reliquary", dead_end=DeadEnd.restricted), + "Fortress Grave Path Dusty Entrance Region": RegionInfo("Fortress Reliquary"), + "Fortress Hero's Grave Region": RegionInfo("Fortress Reliquary"), + "Fortress Leaf Piles": RegionInfo("Dusty", dead_end=DeadEnd.all_cats), "Fortress Arena": RegionInfo("Fortress Arena"), "Fortress Arena Portal": RegionInfo("Fortress Arena"), "Lower Mountain": RegionInfo("Mountain"), "Lower Mountain Stairs": RegionInfo("Mountain"), - "Top of the Mountain": RegionInfo("Mountaintop", dead_end=DeadEnd.all_cats, hint=Hint.region), + "Top of the Mountain": RegionInfo("Mountaintop", dead_end=DeadEnd.all_cats), "Quarry Connector": RegionInfo("Darkwoods Tunnel"), "Quarry Entry": RegionInfo("Quarry Redux"), "Quarry": RegionInfo("Quarry Redux"), @@ -642,9 +674,10 @@ tunic_er_regions: Dict[str, RegionInfo] = { "Quarry Monastery Entry": RegionInfo("Quarry Redux"), "Monastery Front": RegionInfo("Monastery"), "Monastery Back": RegionInfo("Monastery"), - "Monastery Hero's Grave": RegionInfo("Monastery"), + "Monastery Hero's Grave Region": RegionInfo("Monastery"), "Monastery Rope": RegionInfo("Quarry Redux"), "Lower Quarry": RegionInfo("Quarry Redux"), + "Even Lower Quarry": RegionInfo("Quarry Redux"), "Lower Quarry Zig Door": RegionInfo("Quarry Redux"), "Rooted Ziggurat Entry": RegionInfo("ziggurat2020_0"), "Rooted Ziggurat Upper Entry": RegionInfo("ziggurat2020_1"), @@ -657,82 +690,61 @@ tunic_er_regions: Dict[str, RegionInfo] = { "Rooted Ziggurat Portal Room Entrance": RegionInfo("ziggurat2020_3"), # the door itself on the zig 3 side "Rooted Ziggurat Portal": RegionInfo("ziggurat2020_FTRoom"), "Rooted Ziggurat Portal Room Exit": RegionInfo("ziggurat2020_FTRoom"), - "Swamp": RegionInfo("Swamp Redux 2"), - "Swamp to Cathedral Treasure Room": RegionInfo("Swamp Redux 2"), - "Swamp to Cathedral Main Entrance": RegionInfo("Swamp Redux 2"), + "Swamp Front": RegionInfo("Swamp Redux 2"), # from the main entry to the top of the ladder after south + "Swamp Mid": RegionInfo("Swamp Redux 2"), # from the bottom of the ladder to the cathedral door + "Swamp Ledge under Cathedral Door": RegionInfo("Swamp Redux 2"), # the ledge with the chest and secret door + "Swamp to Cathedral Treasure Room": RegionInfo("Swamp Redux 2"), # just the door + "Swamp to Cathedral Main Entrance Region": RegionInfo("Swamp Redux 2"), # just the door "Back of Swamp": RegionInfo("Swamp Redux 2"), # the area with hero grave and gauntlet entrance - "Swamp Hero's Grave": RegionInfo("Swamp Redux 2"), + "Swamp Hero's Grave Region": RegionInfo("Swamp Redux 2"), "Back of Swamp Laurels Area": RegionInfo("Swamp Redux 2"), # the spots you need laurels to traverse "Cathedral": RegionInfo("Cathedral Redux"), - "Cathedral Secret Legend Room": RegionInfo("Cathedral Redux", dead_end=DeadEnd.all_cats, hint=Hint.region), + "Cathedral Secret Legend Room": RegionInfo("Cathedral Redux", dead_end=DeadEnd.all_cats), "Cathedral Gauntlet Checkpoint": RegionInfo("Cathedral Arena"), "Cathedral Gauntlet": RegionInfo("Cathedral Arena"), "Cathedral Gauntlet Exit": RegionInfo("Cathedral Arena"), "Far Shore": RegionInfo("Transit"), - "Far Shore to Spawn": RegionInfo("Transit"), - "Far Shore to East Forest": RegionInfo("Transit"), - "Far Shore to Quarry": RegionInfo("Transit"), - "Far Shore to Fortress": RegionInfo("Transit"), - "Far Shore to Library": RegionInfo("Transit"), - "Far Shore to West Garden": RegionInfo("Transit"), - "Hero Relic - Fortress": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Hero Relic - Quarry": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Hero Relic - West Garden": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Hero Relic - East Forest": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Hero Relic - Library": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats, hint=Hint.region), - "Hero Relic - Swamp": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats, hint=Hint.region), + "Far Shore to Spawn Region": RegionInfo("Transit"), + "Far Shore to East Forest Region": RegionInfo("Transit"), + "Far Shore to Quarry Region": RegionInfo("Transit"), + "Far Shore to Fortress Region": RegionInfo("Transit"), + "Far Shore to Library Region": RegionInfo("Transit"), + "Far Shore to West Garden Region": RegionInfo("Transit"), + "Hero Relic - Fortress": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats), + "Hero Relic - Quarry": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats), + "Hero Relic - West Garden": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats), + "Hero Relic - East Forest": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats), + "Hero Relic - Library": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats), + "Hero Relic - Swamp": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats), "Purgatory": RegionInfo("Purgatory"), "Shop": RegionInfo("Shop", dead_end=DeadEnd.all_cats), - "Spirit Arena": RegionInfo("Spirit Arena", dead_end=DeadEnd.all_cats, hint=Hint.region), + "Spirit Arena": RegionInfo("Spirit Arena", dead_end=DeadEnd.all_cats), "Spirit Arena Victory": RegionInfo("Spirit Arena", dead_end=DeadEnd.all_cats) } -# so we can just loop over this instead of doing some complicated thing to deal with hallways in the hints -hallways: Dict[str, str] = { - "Overworld Redux, Furnace_gyro_west": "Overworld Redux, Archipelagos Redux_lower", - "Overworld Redux, Furnace_gyro_upper_north": "Overworld Redux, Sewer_west_aqueduct", - "Ruins Passage, Overworld Redux_east": "Ruins Passage, Overworld Redux_west", - "East Forest Redux Interior, East Forest Redux_upper": "East Forest Redux Interior, East Forest Redux_lower", - "Forest Boss Room, East Forest Redux Laddercave_": "Forest Boss Room, Forest Belltower_", - "Library Exterior, Atoll Redux_": "Library Exterior, Library Hall_", - "Library Rotunda, Library Lab_": "Library Rotunda, Library Hall_", - "Darkwoods Tunnel, Quarry Redux_": "Darkwoods Tunnel, Overworld Redux_", - "ziggurat2020_0, Quarry Redux_": "ziggurat2020_0, ziggurat2020_1_", - "Purgatory, Purgatory_bottom": "Purgatory, Purgatory_top", -} -hallway_helper: Dict[str, str] = {} -for p1, p2 in hallways.items(): - hallway_helper[p1] = p2 - hallway_helper[p2] = p1 - -# so we can just loop over this instead of doing some complicated thing to deal with hallways in the hints -hallways_ur: Dict[str, str] = { - "Ruins Passage, Overworld Redux_east": "Ruins Passage, Overworld Redux_west", - "East Forest Redux Interior, East Forest Redux_upper": "East Forest Redux Interior, East Forest Redux_lower", - "Forest Boss Room, East Forest Redux Laddercave_": "Forest Boss Room, Forest Belltower_", - "Library Exterior, Atoll Redux_": "Library Exterior, Library Hall_", - "Library Rotunda, Library Lab_": "Library Rotunda, Library Hall_", - "Darkwoods Tunnel, Quarry Redux_": "Darkwoods Tunnel, Overworld Redux_", - "ziggurat2020_0, Quarry Redux_": "ziggurat2020_0, ziggurat2020_1_", - "Purgatory, Purgatory_bottom": "Purgatory, Purgatory_top", -} -hallway_helper_ur: Dict[str, str] = {} -for p1, p2 in hallways_ur.items(): - hallway_helper_ur[p1] = p2 - hallway_helper_ur[p2] = p1 - - # the key is the region you have, the value is the regions you get for having that region # this is mostly so we don't have to do something overly complex to get this information +# really want to get rid of this, but waiting on item plando being workable with ER dependent_regions_restricted: Dict[Tuple[str, ...], List[str]] = { - ("Overworld", "Overworld Belltower", "Overworld Swamp Upper Entry", "Overworld Special Shop Entry", - "Overworld West Garden Laurels Entry", "Overworld Southeast Cross Door", "Overworld Temple Door", - "Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal"): - ["Overworld", "Overworld Belltower", "Overworld Swamp Upper Entry", "Overworld Special Shop Entry", - "Overworld West Garden Laurels Entry", "Overworld Ruined Passage Door", "Overworld Southeast Cross Door", - "Overworld Old House Door", "Overworld Temple Door", "Overworld Fountain Cross Door", "Overworld Town Portal", - "Overworld Spawn Portal"], + ("Overworld", "Overworld Belltower", "Overworld Belltower at Bell", "Overworld Swamp Upper Entry", + "Overworld Special Shop Entry", "Overworld West Garden Laurels Entry", "Overworld Southeast Cross Door", + "Overworld Temple Door", "Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal", + "Overworld Swamp Lower Entry", "After Ruined Passage", "Above Ruined Passage", "East Overworld", "Upper Overworld", + "Overworld after Temple Rafters", "Overworld Quarry Entry", "Overworld above Patrol Cave", + "Overworld at Patrol Cave", "Overworld to West Garden Upper", "Overworld Well Ladder", "Overworld Beach", + "Overworld to Atoll Upper", "Overworld above Quarry Entrance", "Overworld after Envoy", "Overworld Tunnel Turret"): + ["Overworld", "Overworld Belltower", "Overworld Belltower at Bell", "Overworld Swamp Upper Entry", + "Overworld Special Shop Entry", "Overworld West Garden Laurels Entry", "Overworld Ruined Passage Door", + "Overworld Southeast Cross Door", "Overworld Old House Door", "Overworld Temple Door", + "Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal", + "Overworld Swamp Lower Entry", "After Ruined Passage", "Above Ruined Passage", "East Overworld", + "Upper Overworld", "Overworld after Temple Rafters", "Overworld Quarry Entry", "Overworld above Patrol Cave", + "Overworld at Patrol Cave", "Overworld to West Garden Upper", "Overworld Well Ladder", "Overworld Beach", + "Overworld to Atoll Upper", "Overworld Temple Door", "Overworld above Quarry Entrance", + "Overworld after Envoy", "Overworld Tunnel Turret"], + ("Hourglass Cave",): + ["Hourglass Cave", "Hourglass Cave Tower"], ("Old House Front",): ["Old House Front", "Old House Back"], ("Furnace Fuse", "Furnace Ladder Area", "Furnace Walking Path"): @@ -742,63 +754,70 @@ dependent_regions_restricted: Dict[Tuple[str, ...], List[str]] = { ["Forest Belltower Upper", "Forest Belltower Main", "Forest Belltower Lower"], ("Forest Belltower Main",): ["Forest Belltower Main", "Forest Belltower Lower"], - ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"): - ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"], + ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal", "Lower Forest"): + ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal", "Lower Forest"], ("Guard House 1 East", "Guard House 1 West"): ["Guard House 1 East", "Guard House 1 West"], + ("Guard House 2 Upper", "Guard House 2 Lower"): + ["Guard House 2 Upper", "Guard House 2 Lower"], ("Forest Grave Path Main", "Forest Grave Path Upper"): ["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"], ("Forest Grave Path by Grave", "Forest Hero's Grave"): ["Forest Grave Path by Grave", "Forest Hero's Grave"], - ("Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back"): - ["Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back"], - ("Dark Tomb Entry Point", "Dark Tomb Main", "Dark Tomb Dark Exit"): - ["Dark Tomb Entry Point", "Dark Tomb Main", "Dark Tomb Dark Exit"], + ("Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back", "Beneath the Well Ladder Exit"): + ["Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back", "Beneath the Well Ladder Exit"], + ("Dark Tomb Entry Point", "Dark Tomb Main", "Dark Tomb Dark Exit", "Dark Tomb Upper"): + ["Dark Tomb Entry Point", "Dark Tomb Main", "Dark Tomb Dark Exit", "Dark Tomb Upper"], ("Well Boss",): ["Dark Tomb Checkpoint", "Well Boss"], - ("West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave"): - ["West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave"], + ("West Garden", "West Garden Laurels Exit Region", "West Garden after Boss", "West Garden Hero's Grave Region"): + ["West Garden", "West Garden Laurels Exit Region", "West Garden after Boss", "West Garden Hero's Grave Region"], ("West Garden Portal", "West Garden Portal Item"): ["West Garden Portal", "West Garden Portal Item"], ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", - "Ruined Atoll Statue"): + "Ruined Atoll Statue", "Ruined Atoll Ladder Tops", "Ruined Atoll Frog Eye", "Ruined Atoll Ladder Tops"): ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", - "Ruined Atoll Statue"], - ("Frog's Domain",): - ["Frog's Domain", "Frog's Domain Back"], - ("Library Exterior Ladder", "Library Exterior Tree"): - ["Library Exterior Ladder", "Library Exterior Tree"], - ("Library Hall", "Library Hero's Grave"): - ["Library Hall", "Library Hero's Grave"], - ("Library Lab", "Library Lab Lower", "Library Portal"): - ["Library Lab", "Library Lab Lower", "Library Portal"], + "Ruined Atoll Statue", "Ruined Atoll Ladder Tops", "Ruined Atoll Frog Eye", "Ruined Atoll Ladder Tops"], + ("Frog Stairs Upper", "Frog Stairs Lower", "Frog Stairs to Frog's Domain"): + ["Frog Stairs Upper", "Frog Stairs Lower", "Frog Stairs to Frog's Domain"], + ("Frog's Domain", "Frog's Domain Entry"): + ["Frog's Domain", "Frog's Domain Back", "Frog's Domain Entry"], + ("Library Exterior Ladder Region", "Library Exterior Tree Region"): + ["Library Exterior Ladder Region", "Library Exterior Tree Region"], + ("Library Hall", "Library Hero's Grave Region", "Library Hall Bookshelf", "Library Hall to Rotunda"): + ["Library Hall", "Library Hero's Grave Region", "Library Hall Bookshelf", "Library Hall to Rotunda"], + ("Library Rotunda to Hall", "Library Rotunda", "Library Rotunda to Lab"): + ["Library Rotunda to Hall", "Library Rotunda", "Library Rotunda to Lab"], + ("Library Lab", "Library Lab Lower", "Library Portal", "Library Lab to Librarian"): + ["Library Lab", "Library Lab Lower", "Library Portal", "Library Lab to Librarian"], ("Fortress Courtyard Upper",): ["Fortress Courtyard Upper", "Fortress Exterior from East Forest", "Fortress Exterior from Overworld", "Fortress Exterior near cave", "Fortress Courtyard"], ("Fortress Exterior from East Forest", "Fortress Exterior from Overworld", - "Fortress Exterior near cave", "Fortress Courtyard"): + "Fortress Exterior near cave", "Fortress Courtyard", "Beneath the Vault Entry"): ["Fortress Exterior from East Forest", "Fortress Exterior from Overworld", - "Fortress Exterior near cave", "Fortress Courtyard"], - ("Beneath the Vault Front", "Beneath the Vault Back"): - ["Beneath the Vault Front", "Beneath the Vault Back"], + "Fortress Exterior near cave", "Fortress Courtyard", "Beneath the Vault Entry"], + ("Beneath the Vault Front", "Beneath the Vault Back", "Beneath the Vault Ladder Exit"): + ["Beneath the Vault Front", "Beneath the Vault Back", "Beneath the Vault Ladder Exit"], ("Fortress East Shortcut Upper",): ["Fortress East Shortcut Upper", "Fortress East Shortcut Lower"], ("Eastern Vault Fortress",): ["Eastern Vault Fortress", "Eastern Vault Fortress Gold Door"], - ("Fortress Grave Path", "Fortress Grave Path Dusty Entrance", "Fortress Hero's Grave"): - ["Fortress Grave Path", "Fortress Grave Path Dusty Entrance", "Fortress Hero's Grave"], + ("Fortress Grave Path", "Fortress Grave Path Dusty Entrance Region", "Fortress Hero's Grave Region"): + ["Fortress Grave Path", "Fortress Grave Path Dusty Entrance Region", "Fortress Hero's Grave Region"], ("Fortress Arena", "Fortress Arena Portal"): ["Fortress Arena", "Fortress Arena Portal"], ("Lower Mountain", "Lower Mountain Stairs"): ["Lower Mountain", "Lower Mountain Stairs"], ("Monastery Front",): - ["Monastery Front", "Monastery Back", "Monastery Hero's Grave"], - ("Monastery Back", "Monastery Hero's Grave"): - ["Monastery Back", "Monastery Hero's Grave"], - ("Quarry", "Quarry Portal", "Lower Quarry", "Quarry Entry", "Quarry Back", "Quarry Monastery Entry"): + ["Monastery Front", "Monastery Back", "Monastery Hero's Grave Region"], + ("Monastery Back", "Monastery Hero's Grave Region"): + ["Monastery Back", "Monastery Hero's Grave Region"], + ("Quarry", "Quarry Portal", "Lower Quarry", "Quarry Entry", "Quarry Back", "Quarry Monastery Entry", + "Even Lower Quarry"): ["Quarry", "Quarry Portal", "Lower Quarry", "Quarry Entry", "Quarry Back", "Quarry Monastery Entry", - "Lower Quarry Zig Door"], + "Lower Quarry Zig Door", "Even Lower Quarry"], ("Monastery Rope",): ["Monastery Rope", "Quarry", "Quarry Entry", "Quarry Back", "Quarry Portal", "Lower Quarry", - "Lower Quarry Zig Door"], + "Lower Quarry Zig Door", "Even Lower Quarry"], ("Rooted Ziggurat Upper Entry", "Rooted Ziggurat Upper Front"): ["Rooted Ziggurat Upper Entry", "Rooted Ziggurat Upper Front", "Rooted Ziggurat Upper Back"], ("Rooted Ziggurat Middle Top",): @@ -807,31 +826,45 @@ dependent_regions_restricted: Dict[Tuple[str, ...], List[str]] = { ["Rooted Ziggurat Lower Front", "Rooted Ziggurat Lower Back", "Rooted Ziggurat Portal Room Entrance"], ("Rooted Ziggurat Portal", "Rooted Ziggurat Portal Room Exit"): ["Rooted Ziggurat Portal", "Rooted Ziggurat Portal Room Exit"], - ("Swamp", "Swamp to Cathedral Treasure Room"): - ["Swamp", "Swamp to Cathedral Treasure Room", "Swamp to Cathedral Main Entrance"], - ("Back of Swamp", "Back of Swamp Laurels Area", "Swamp Hero's Grave"): - ["Back of Swamp", "Back of Swamp Laurels Area", "Swamp Hero's Grave"], + ("Swamp Front", "Swamp Mid", "Swamp to Cathedral Treasure Room", "Swamp Ledge under Cathedral Door"): + ["Swamp Front", "Swamp Mid", "Swamp to Cathedral Treasure Room", "Swamp to Cathedral Main Entrance Region", + "Swamp Ledge under Cathedral Door"], + ("Back of Swamp", "Back of Swamp Laurels Area", "Swamp Hero's Grave Region"): + ["Back of Swamp", "Back of Swamp Laurels Area", "Swamp Hero's Grave Region"], ("Cathedral Gauntlet Checkpoint",): ["Cathedral Gauntlet Checkpoint", "Cathedral Gauntlet Exit", "Cathedral Gauntlet"], - ("Far Shore", "Far Shore to Spawn", "Far Shore to East Forest", "Far Shore to Quarry", - "Far Shore to Fortress", "Far Shore to Library", "Far Shore to West Garden"): - ["Far Shore", "Far Shore to Spawn", "Far Shore to East Forest", "Far Shore to Quarry", - "Far Shore to Fortress", "Far Shore to Library", "Far Shore to West Garden"] + ("Cathedral Gauntlet Exit",): + ["Cathedral Gauntlet Exit", "Cathedral Gauntlet"], + ("Far Shore", "Far Shore to Spawn Region", "Far Shore to East Forest Region", "Far Shore to Quarry Region", + "Far Shore to Fortress Region", "Far Shore to Library Region", "Far Shore to West Garden Region"): + ["Far Shore", "Far Shore to Spawn Region", "Far Shore to East Forest Region", "Far Shore to Quarry Region", + "Far Shore to Fortress Region", "Far Shore to Library Region", "Far Shore to West Garden Region"] } dependent_regions_nmg: Dict[Tuple[str, ...], List[str]] = { - ("Overworld", "Overworld Belltower", "Overworld Swamp Upper Entry", "Overworld Special Shop Entry", - "Overworld West Garden Laurels Entry", "Overworld Southeast Cross Door", "Overworld Temple Door", - "Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal", - "Overworld Ruined Passage Door"): - ["Overworld", "Overworld Belltower", "Overworld Swamp Upper Entry", "Overworld Special Shop Entry", - "Overworld West Garden Laurels Entry", "Overworld Ruined Passage Door", "Overworld Southeast Cross Door", - "Overworld Old House Door", "Overworld Temple Door", "Overworld Fountain Cross Door", "Overworld Town Portal", - "Overworld Spawn Portal"], + ("Overworld", "Overworld Belltower", "Overworld Belltower at Bell", "Overworld Swamp Upper Entry", + "Overworld Special Shop Entry", "Overworld West Garden Laurels Entry", "Overworld Southeast Cross Door", + "Overworld Temple Door", "Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal", + "Overworld Ruined Passage Door", "Overworld Swamp Lower Entry", "After Ruined Passage", "Above Ruined Passage", + "East Overworld", "Upper Overworld", "Overworld after Temple Rafters", "Overworld Quarry Entry", + "Overworld above Patrol Cave", "Overworld at Patrol Cave", "Overworld to West Garden Upper", + "Overworld Well Ladder", "Overworld Beach", "Overworld to Atoll Upper", "Overworld above Quarry Entrance", + "Overworld after Envoy", "Overworld Tunnel Turret"): + ["Overworld", "Overworld Belltower", "Overworld Belltower at Bell", "Overworld Swamp Upper Entry", + "Overworld Special Shop Entry", "Overworld West Garden Laurels Entry", "Overworld Ruined Passage Door", + "Overworld Southeast Cross Door", "Overworld Old House Door", "Overworld Temple Door", + "Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal", + "Overworld Swamp Lower Entry", "After Ruined Passage", "Above Ruined Passage", "East Overworld", + "Upper Overworld", "Overworld after Temple Rafters", "Overworld Quarry Entry", "Overworld above Patrol Cave", + "Overworld at Patrol Cave", "Overworld to West Garden Upper", "Overworld Well Ladder", "Overworld Beach", + "Overworld to Atoll Upper", "Overworld above Quarry Entrance", "Overworld after Envoy", + "Overworld Tunnel Turret"], # can laurels through the gate ("Old House Front", "Old House Back"): ["Old House Front", "Old House Back"], + ("Hourglass Cave",): + ["Hourglass Cave", "Hourglass Cave Tower"], ("Furnace Fuse", "Furnace Ladder Area", "Furnace Walking Path"): ["Furnace Fuse", "Furnace Ladder Area", "Furnace Walking Path"], ("Sealed Temple", "Sealed Temple Rafters"): ["Sealed Temple", "Sealed Temple Rafters"], @@ -839,60 +872,67 @@ dependent_regions_nmg: Dict[Tuple[str, ...], List[str]] = { ["Forest Belltower Upper", "Forest Belltower Main", "Forest Belltower Lower"], ("Forest Belltower Main",): ["Forest Belltower Main", "Forest Belltower Lower"], - ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"): - ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"], + ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal", "Lower Forest"): + ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal", "Lower Forest"], ("Guard House 1 East", "Guard House 1 West"): ["Guard House 1 East", "Guard House 1 West"], + ("Guard House 2 Upper", "Guard House 2 Lower"): + ["Guard House 2 Upper", "Guard House 2 Lower"], ("Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"): ["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"], - ("Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back"): - ["Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back"], - ("Dark Tomb Entry Point", "Dark Tomb Main", "Dark Tomb Dark Exit"): - ["Dark Tomb Entry Point", "Dark Tomb Main", "Dark Tomb Dark Exit"], + ("Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back", "Beneath the Well Ladder Exit"): + ["Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back", "Beneath the Well Ladder Exit"], + ("Dark Tomb Entry Point", "Dark Tomb Main", "Dark Tomb Dark Exit", "Dark Tomb Upper"): + ["Dark Tomb Entry Point", "Dark Tomb Main", "Dark Tomb Dark Exit", "Dark Tomb Upper"], ("Dark Tomb Checkpoint", "Well Boss"): ["Dark Tomb Checkpoint", "Well Boss"], - ("West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave", + ("West Garden", "West Garden Laurels Exit Region", "West Garden after Boss", "West Garden Hero's Grave Region", "West Garden Portal", "West Garden Portal Item"): - ["West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave", + ["West Garden", "West Garden Laurels Exit Region", "West Garden after Boss", "West Garden Hero's Grave Region", "West Garden Portal", "West Garden Portal Item"], ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", - "Ruined Atoll Statue"): + "Ruined Atoll Statue", "Ruined Atoll Ladder Tops", "Ruined Atoll Frog Eye", "Ruined Atoll Ladder Tops"): ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", - "Ruined Atoll Statue"], - ("Frog's Domain",): - ["Frog's Domain", "Frog's Domain Back"], - ("Library Exterior Ladder", "Library Exterior Tree"): - ["Library Exterior Ladder", "Library Exterior Tree"], - ("Library Hall", "Library Hero's Grave"): - ["Library Hall", "Library Hero's Grave"], - ("Library Lab", "Library Lab Lower", "Library Portal"): - ["Library Lab", "Library Lab Lower", "Library Portal"], + "Ruined Atoll Statue", "Ruined Atoll Ladder Tops", "Ruined Atoll Frog Eye", "Ruined Atoll Ladder Tops"], + ("Frog Stairs Upper", "Frog Stairs Lower", "Frog Stairs to Frog's Domain"): + ["Frog Stairs Upper", "Frog Stairs Lower", "Frog Stairs to Frog's Domain"], + ("Frog's Domain", "Frog's Domain Entry"): + ["Frog's Domain", "Frog's Domain Back", "Frog's Domain Entry"], + ("Library Exterior Ladder Region", "Library Exterior Tree Region"): + ["Library Exterior Ladder Region", "Library Exterior Tree Region"], + ("Library Hall", "Library Hero's Grave Region", "Library Hall Bookshelf", "Library Hall to Rotunda"): + ["Library Hall", "Library Hero's Grave Region", "Library Hall Bookshelf", "Library Hall to Rotunda"], + ("Library Rotunda to Hall", "Library Rotunda", "Library Rotunda to Lab"): + ["Library Rotunda to Hall", "Library Rotunda", "Library Rotunda to Lab"], + ("Library Lab", "Library Lab Lower", "Library Portal", "Library Lab to Librarian"): + ["Library Lab", "Library Lab Lower", "Library Portal", "Library Lab to Librarian"], ("Fortress Exterior from East Forest", "Fortress Exterior from Overworld", - "Fortress Exterior near cave", "Fortress Courtyard", "Fortress Courtyard Upper"): + "Fortress Exterior near cave", "Fortress Courtyard", "Fortress Courtyard Upper", "Beneath the Vault Entry"): ["Fortress Exterior from East Forest", "Fortress Exterior from Overworld", - "Fortress Exterior near cave", "Fortress Courtyard", "Fortress Courtyard Upper"], - ("Beneath the Vault Front", "Beneath the Vault Back"): - ["Beneath the Vault Front", "Beneath the Vault Back"], + "Fortress Exterior near cave", "Fortress Courtyard", "Fortress Courtyard Upper", "Beneath the Vault Entry"], + ("Beneath the Vault Front", "Beneath the Vault Back", "Beneath the Vault Ladder Exit"): + ["Beneath the Vault Front", "Beneath the Vault Back", "Beneath the Vault Ladder Exit"], ("Fortress East Shortcut Upper", "Fortress East Shortcut Lower"): ["Fortress East Shortcut Upper", "Fortress East Shortcut Lower"], ("Eastern Vault Fortress", "Eastern Vault Fortress Gold Door"): ["Eastern Vault Fortress", "Eastern Vault Fortress Gold Door"], - ("Fortress Grave Path", "Fortress Grave Path Dusty Entrance", "Fortress Hero's Grave"): - ["Fortress Grave Path", "Fortress Grave Path Dusty Entrance", "Fortress Hero's Grave"], + ("Fortress Grave Path", "Fortress Grave Path Dusty Entrance Region", "Fortress Hero's Grave Region"): + ["Fortress Grave Path", "Fortress Grave Path Dusty Entrance Region", "Fortress Hero's Grave Region"], ("Fortress Grave Path Upper",): - ["Fortress Grave Path Upper", "Fortress Grave Path", "Fortress Grave Path Dusty Entrance", - "Fortress Hero's Grave"], + ["Fortress Grave Path Upper", "Fortress Grave Path", "Fortress Grave Path Dusty Entrance Region", + "Fortress Hero's Grave Region"], ("Fortress Arena", "Fortress Arena Portal"): ["Fortress Arena", "Fortress Arena Portal"], ("Lower Mountain", "Lower Mountain Stairs"): ["Lower Mountain", "Lower Mountain Stairs"], - ("Monastery Front", "Monastery Back", "Monastery Hero's Grave"): - ["Monastery Front", "Monastery Back", "Monastery Hero's Grave"], - ("Quarry", "Quarry Portal", "Lower Quarry", "Quarry Entry", "Quarry Back", "Quarry Monastery Entry"): + ("Monastery Front", "Monastery Back", "Monastery Hero's Grave Region"): + ["Monastery Front", "Monastery Back", "Monastery Hero's Grave Region"], + ("Quarry", "Quarry Portal", "Lower Quarry", "Quarry Entry", "Quarry Back", "Quarry Monastery Entry", + "Even Lower Quarry"): ["Quarry", "Quarry Portal", "Lower Quarry", "Quarry Entry", "Quarry Back", "Quarry Monastery Entry", - "Lower Quarry Zig Door"], + "Lower Quarry Zig Door", "Even Lower Quarry"], ("Monastery Rope",): ["Monastery Rope", "Quarry", "Quarry Entry", "Quarry Back", "Quarry Portal", "Lower Quarry", - "Lower Quarry Zig Door"], + "Lower Quarry Zig Door", "Even Lower Quarry"], ("Rooted Ziggurat Upper Entry", "Rooted Ziggurat Upper Front"): ["Rooted Ziggurat Upper Entry", "Rooted Ziggurat Upper Front", "Rooted Ziggurat Upper Back"], ("Rooted Ziggurat Middle Top",): @@ -901,33 +941,48 @@ dependent_regions_nmg: Dict[Tuple[str, ...], List[str]] = { ["Rooted Ziggurat Lower Front", "Rooted Ziggurat Lower Back", "Rooted Ziggurat Portal Room Entrance"], ("Rooted Ziggurat Portal", "Rooted Ziggurat Portal Room Exit"): ["Rooted Ziggurat Portal", "Rooted Ziggurat Portal Room Exit"], - ("Swamp", "Swamp to Cathedral Treasure Room", "Swamp to Cathedral Main Entrance"): - ["Swamp", "Swamp to Cathedral Treasure Room", "Swamp to Cathedral Main Entrance"], - ("Back of Swamp", "Back of Swamp Laurels Area", "Swamp Hero's Grave"): - ["Back of Swamp", "Back of Swamp Laurels Area", "Swamp Hero's Grave", "Swamp", - "Swamp to Cathedral Treasure Room", "Swamp to Cathedral Main Entrance"], + ("Swamp Front", "Swamp Mid", "Swamp to Cathedral Treasure Room", "Swamp to Cathedral Main Entrance Region", + "Swamp Ledge under Cathedral Door"): + ["Swamp Front", "Swamp Mid", "Swamp to Cathedral Treasure Room", "Swamp to Cathedral Main Entrance Region", + "Swamp Ledge under Cathedral Door"], + ("Back of Swamp", "Back of Swamp Laurels Area", "Swamp Hero's Grave Region"): + ["Back of Swamp", "Back of Swamp Laurels Area", "Swamp Hero's Grave Region", "Swamp Front", "Swamp Mid", + "Swamp to Cathedral Treasure Room", "Swamp to Cathedral Main Entrance Region", + "Swamp Ledge under Cathedral Door"], ("Cathedral Gauntlet Checkpoint",): ["Cathedral Gauntlet Checkpoint", "Cathedral Gauntlet Exit", "Cathedral Gauntlet"], - ("Far Shore", "Far Shore to Spawn", "Far Shore to East Forest", "Far Shore to Quarry", - "Far Shore to Fortress", "Far Shore to Library", "Far Shore to West Garden"): - ["Far Shore", "Far Shore to Spawn", "Far Shore to East Forest", "Far Shore to Quarry", - "Far Shore to Fortress", "Far Shore to Library", "Far Shore to West Garden"] + ("Cathedral Gauntlet Exit",): + ["Cathedral Gauntlet Exit", "Cathedral Gauntlet"], + ("Far Shore", "Far Shore to Spawn Region", "Far Shore to East Forest Region", "Far Shore to Quarry Region", + "Far Shore to Fortress Region", "Far Shore to Library Region", "Far Shore to West Garden Region"): + ["Far Shore", "Far Shore to Spawn Region", "Far Shore to East Forest Region", "Far Shore to Quarry Region", + "Far Shore to Fortress Region", "Far Shore to Library Region", "Far Shore to West Garden Region"] } dependent_regions_ur: Dict[Tuple[str, ...], List[str]] = { # can use ladder storage to get to the well rail - ("Overworld", "Overworld Belltower", "Overworld Swamp Upper Entry", "Overworld Special Shop Entry", - "Overworld West Garden Laurels Entry", "Overworld Southeast Cross Door", "Overworld Temple Door", - "Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal", - "Overworld Ruined Passage Door"): - ["Overworld", "Overworld Belltower", "Overworld Swamp Upper Entry", "Overworld Special Shop Entry", - "Overworld West Garden Laurels Entry", "Overworld Ruined Passage Door", "Overworld Southeast Cross Door", - "Overworld Old House Door", "Overworld Temple Door", "Overworld Fountain Cross Door", "Overworld Town Portal", - "Overworld Spawn Portal", "Overworld Well to Furnace Rail"], + ("Overworld", "Overworld Belltower", "Overworld Belltower at Bell", "Overworld Swamp Upper Entry", + "Overworld Special Shop Entry", "Overworld West Garden Laurels Entry", "Overworld Southeast Cross Door", + "Overworld Temple Door", "Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal", + "Overworld Ruined Passage Door", "Overworld Swamp Lower Entry", "After Ruined Passage", "Above Ruined Passage", + "East Overworld", "Upper Overworld", "Overworld after Temple Rafters", "Overworld Quarry Entry", + "Overworld above Patrol Cave", "Overworld at Patrol Cave", "Overworld to West Garden Upper", + "Overworld Well Ladder", "Overworld Beach", "Overworld to Atoll Upper", "Overworld above Quarry Entrance", + "Overworld after Envoy", "Overworld Tunnel Turret"): + ["Overworld", "Overworld Belltower", "Overworld Belltower at Bell", "Overworld Swamp Upper Entry", + "Overworld Special Shop Entry", "Overworld West Garden Laurels Entry", "Overworld Southeast Cross Door", + "Overworld Temple Door", "Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal", + "Overworld Ruined Passage Door", "Overworld Swamp Lower Entry", "After Ruined Passage", + "Above Ruined Passage", "East Overworld", "Upper Overworld", "Overworld after Temple Rafters", + "Overworld Quarry Entry", "Overworld above Patrol Cave", "Overworld at Patrol Cave", + "Overworld to West Garden Upper", "Overworld Well Ladder", "Overworld Beach", "Overworld to Atoll Upper", + "Overworld above Quarry Entrance", "Overworld after Envoy", "Overworld Tunnel Turret"], # can laurels through the gate ("Old House Front", "Old House Back"): ["Old House Front", "Old House Back"], + ("Hourglass Cave",): + ["Hourglass Cave", "Hourglass Cave Tower"], ("Furnace Fuse", "Furnace Ladder Area", "Furnace Walking Path"): ["Furnace Fuse", "Furnace Ladder Area", "Furnace Walking Path"], ("Sealed Temple", "Sealed Temple Rafters"): ["Sealed Temple", "Sealed Temple Rafters"], @@ -935,65 +990,71 @@ dependent_regions_ur: Dict[Tuple[str, ...], List[str]] = { ["Forest Belltower Upper", "Forest Belltower Main", "Forest Belltower Lower"], ("Forest Belltower Main",): ["Forest Belltower Main", "Forest Belltower Lower"], - ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"): - ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"], + ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal", "Lower Forest"): + ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal", "Lower Forest"], ("Guard House 1 East", "Guard House 1 West"): ["Guard House 1 East", "Guard House 1 West"], + ("Guard House 2 Upper", "Guard House 2 Lower"): + ["Guard House 2 Upper", "Guard House 2 Lower"], # can use laurels, ice grapple, or ladder storage to traverse ("Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"): ["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"], - ("Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back"): - ["Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back"], - ("Dark Tomb Entry Point", "Dark Tomb Main", "Dark Tomb Dark Exit"): - ["Dark Tomb Entry Point", "Dark Tomb Main", "Dark Tomb Dark Exit"], + ("Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back", "Beneath the Well Ladder Exit"): + ["Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back", "Beneath the Well Ladder Exit"], + ("Dark Tomb Entry Point", "Dark Tomb Main", "Dark Tomb Dark Exit", "Dark Tomb Upper"): + ["Dark Tomb Entry Point", "Dark Tomb Main", "Dark Tomb Dark Exit", "Dark Tomb Upper"], ("Dark Tomb Checkpoint", "Well Boss"): ["Dark Tomb Checkpoint", "Well Boss"], # can ice grapple from portal area to the rest, and vice versa - ("West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave", + ("West Garden", "West Garden Laurels Exit Region", "West Garden after Boss", "West Garden Hero's Grave Region", "West Garden Portal", "West Garden Portal Item"): - ["West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave", + ["West Garden", "West Garden Laurels Exit Region", "West Garden after Boss", "West Garden Hero's Grave Region", "West Garden Portal", "West Garden Portal Item"], ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", - "Ruined Atoll Statue"): + "Ruined Atoll Statue", "Ruined Atoll Ladder Tops", "Ruined Atoll Frog Eye", "Ruined Atoll Ladder Tops"): ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal", - "Ruined Atoll Statue"], - ("Frog's Domain",): - ["Frog's Domain", "Frog's Domain Back"], - ("Library Exterior Ladder", "Library Exterior Tree"): - ["Library Exterior Ladder", "Library Exterior Tree"], - ("Library Hall", "Library Hero's Grave"): - ["Library Hall", "Library Hero's Grave"], - ("Library Lab", "Library Lab Lower", "Library Portal"): - ["Library Lab", "Library Lab Lower", "Library Portal"], + "Ruined Atoll Statue", "Ruined Atoll Ladder Tops", "Ruined Atoll Frog Eye", "Ruined Atoll Ladder Tops"], + ("Frog Stairs Upper", "Frog Stairs Lower", "Frog Stairs to Frog's Domain"): + ["Frog Stairs Upper", "Frog Stairs Lower", "Frog Stairs to Frog's Domain"], + ("Frog's Domain", "Frog's Domain Entry"): + ["Frog's Domain", "Frog's Domain Back", "Frog's Domain Entry"], + ("Library Exterior Ladder Region", "Library Exterior Tree Region"): + ["Library Exterior Ladder Region", "Library Exterior Tree Region"], + ("Library Hall", "Library Hero's Grave Region", "Library Hall Bookshelf", "Library Hall to Rotunda"): + ["Library Hall", "Library Hero's Grave Region", "Library Hall Bookshelf", "Library Hall to Rotunda"], + ("Library Rotunda to Hall", "Library Rotunda", "Library Rotunda to Lab"): + ["Library Rotunda to Hall", "Library Rotunda", "Library Rotunda to Lab"], + ("Library Lab", "Library Lab Lower", "Library Portal", "Library Lab to Librarian"): + ["Library Lab", "Library Lab Lower", "Library Portal", "Library Lab to Librarian"], # can use ice grapple or ladder storage to get from any ladder to upper ("Fortress Exterior from East Forest", "Fortress Exterior from Overworld", - "Fortress Exterior near cave", "Fortress Courtyard", "Fortress Courtyard Upper"): + "Fortress Exterior near cave", "Fortress Courtyard", "Fortress Courtyard Upper", "Beneath the Vault Entry"): ["Fortress Exterior from East Forest", "Fortress Exterior from Overworld", - "Fortress Exterior near cave", "Fortress Courtyard", "Fortress Courtyard Upper"], - ("Beneath the Vault Front", "Beneath the Vault Back"): - ["Beneath the Vault Front", "Beneath the Vault Back"], + "Fortress Exterior near cave", "Fortress Courtyard", "Fortress Courtyard Upper", "Beneath the Vault Entry"], + ("Beneath the Vault Front", "Beneath the Vault Back", "Beneath the Vault Ladder Exit"): + ["Beneath the Vault Front", "Beneath the Vault Back", "Beneath the Vault Ladder Exit"], # can ice grapple up ("Fortress East Shortcut Upper", "Fortress East Shortcut Lower"): ["Fortress East Shortcut Upper", "Fortress East Shortcut Lower"], ("Eastern Vault Fortress", "Eastern Vault Fortress Gold Door"): ["Eastern Vault Fortress", "Eastern Vault Fortress Gold Door"], - ("Fortress Grave Path", "Fortress Grave Path Dusty Entrance", "Fortress Hero's Grave"): - ["Fortress Grave Path", "Fortress Grave Path Dusty Entrance", "Fortress Hero's Grave"], + ("Fortress Grave Path", "Fortress Grave Path Dusty Entrance Region", "Fortress Hero's Grave Region"): + ["Fortress Grave Path", "Fortress Grave Path Dusty Entrance Region", "Fortress Hero's Grave Region"], # can ice grapple down ("Fortress Grave Path Upper",): - ["Fortress Grave Path Upper", "Fortress Grave Path", "Fortress Grave Path Dusty Entrance", - "Fortress Hero's Grave"], + ["Fortress Grave Path Upper", "Fortress Grave Path", "Fortress Grave Path Dusty Entrance Region", + "Fortress Hero's Grave Region"], ("Fortress Arena", "Fortress Arena Portal"): ["Fortress Arena", "Fortress Arena Portal"], ("Lower Mountain", "Lower Mountain Stairs"): ["Lower Mountain", "Lower Mountain Stairs"], - ("Monastery Front", "Monastery Back", "Monastery Hero's Grave"): - ["Monastery Front", "Monastery Back", "Monastery Hero's Grave"], + ("Monastery Front", "Monastery Back", "Monastery Hero's Grave Region"): + ["Monastery Front", "Monastery Back", "Monastery Hero's Grave Region"], # can use ladder storage at any of the Quarry ladders to get to Monastery Rope ("Quarry", "Quarry Portal", "Lower Quarry", "Quarry Entry", "Quarry Back", "Quarry Monastery Entry", - "Monastery Rope"): + "Monastery Rope", "Even Lower Quarry"): ["Quarry", "Quarry Portal", "Lower Quarry", "Quarry Entry", "Quarry Back", "Quarry Monastery Entry", - "Monastery Rope", "Lower Quarry Zig Door"], + "Monastery Rope", "Lower Quarry Zig Door", "Even Lower Quarry"], ("Rooted Ziggurat Upper Entry", "Rooted Ziggurat Upper Front"): ["Rooted Ziggurat Upper Entry", "Rooted Ziggurat Upper Front", "Rooted Ziggurat Upper Back"], ("Rooted Ziggurat Middle Top",): @@ -1002,14 +1063,17 @@ dependent_regions_ur: Dict[Tuple[str, ...], List[str]] = { ["Rooted Ziggurat Lower Front", "Rooted Ziggurat Lower Back", "Rooted Ziggurat Portal Room Entrance"], ("Rooted Ziggurat Portal", "Rooted Ziggurat Portal Room Exit"): ["Rooted Ziggurat Portal", "Rooted Ziggurat Portal Room Exit"], - ("Swamp", "Swamp to Cathedral Treasure Room", "Swamp to Cathedral Main Entrance", "Back of Swamp", - "Back of Swamp Laurels Area", "Swamp Hero's Grave"): - ["Swamp", "Swamp to Cathedral Treasure Room", "Swamp to Cathedral Main Entrance", "Back of Swamp", - "Back of Swamp Laurels Area", "Swamp Hero's Grave"], + ("Swamp Front", "Swamp Mid", "Swamp to Cathedral Treasure Room", "Swamp to Cathedral Main Entrance Region", + "Back of Swamp", "Back of Swamp Laurels Area", "Swamp Hero's Grave Region", "Swamp Ledge under Cathedral Door"): + ["Swamp Front", "Swamp Mid", "Swamp to Cathedral Treasure Room", "Swamp to Cathedral Main Entrance Region", + "Back of Swamp", "Back of Swamp Laurels Area", "Swamp Hero's Grave Region", + "Swamp Ledge under Cathedral Door"], ("Cathedral Gauntlet Checkpoint",): ["Cathedral Gauntlet Checkpoint", "Cathedral Gauntlet Exit", "Cathedral Gauntlet"], - ("Far Shore", "Far Shore to Spawn", "Far Shore to East Forest", "Far Shore to Quarry", - "Far Shore to Fortress", "Far Shore to Library", "Far Shore to West Garden"): - ["Far Shore", "Far Shore to Spawn", "Far Shore to East Forest", "Far Shore to Quarry", - "Far Shore to Fortress", "Far Shore to Library", "Far Shore to West Garden"] + ("Cathedral Gauntlet Exit",): + ["Cathedral Gauntlet Exit", "Cathedral Gauntlet"], + ("Far Shore", "Far Shore to Spawn Region", "Far Shore to East Forest Region", "Far Shore to Quarry Region", + "Far Shore to Fortress Region", "Far Shore to Library Region", "Far Shore to West Garden Region"): + ["Far Shore", "Far Shore to Spawn Region", "Far Shore to East Forest Region", "Far Shore to Quarry Region", + "Far Shore to Fortress Region", "Far Shore to Library Region", "Far Shore to West Garden Region"] } diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index fec6635422..fdfd064561 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -1,8 +1,9 @@ -from typing import Dict, TYPE_CHECKING +from typing import Dict, Set, List, Tuple, TYPE_CHECKING from worlds.generic.Rules import set_rule, forbid_item from .rules import has_ability, has_sword, has_stick, has_ice_grapple_logic, has_lantern, has_mask, can_ladder_storage from .er_data import Portal -from BaseClasses import Region +from .options import TunicOptions +from BaseClasses import Region, CollectionState if TYPE_CHECKING: from . import TunicWorld @@ -27,6 +28,10 @@ blue_hexagon = "Blue Questagon" gold_hexagon = "Gold Questagon" +def has_ladder(ladder: str, state: CollectionState, player: int, options: TunicOptions): + return not options.shuffle_ladders or state.has(ladder, player) + + def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], regions: Dict[str, Region], portal_pairs: Dict[Portal, Portal]) -> None: player = world.player @@ -40,17 +45,203 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re connecting_region=regions["Overworld Holy Cross"], rule=lambda state: has_ability(state, player, holy_cross, options, ability_unlocks)) + # grapple on the west side, down the stairs from moss wall, across from ruined shop + regions["Overworld"].connect( + connecting_region=regions["Overworld Beach"], + rule=lambda state: has_ladder("Ladders in Overworld Town", state, player, options) + or state.has_any({laurels, grapple}, player)) + regions["Overworld Beach"].connect( + connecting_region=regions["Overworld"], + rule=lambda state: has_ladder("Ladders in Overworld Town", state, player, options) + or state.has_any({laurels, grapple}, player)) + + regions["Overworld Beach"].connect( + connecting_region=regions["Overworld West Garden Laurels Entry"], + rule=lambda state: state.has(laurels, player)) + regions["Overworld West Garden Laurels Entry"].connect( + connecting_region=regions["Overworld Beach"], + rule=lambda state: state.has(laurels, player)) + + regions["Overworld Beach"].connect( + connecting_region=regions["Overworld to Atoll Upper"], + rule=lambda state: has_ladder("Ladder to Ruined Atoll", state, player, options)) + regions["Overworld to Atoll Upper"].connect( + connecting_region=regions["Overworld Beach"], + rule=lambda state: has_ladder("Ladder to Ruined Atoll", state, player, options)) + + regions["Overworld"].connect( + connecting_region=regions["Overworld to Atoll Upper"], + rule=lambda state: state.has(laurels, player)) + regions["Overworld to Atoll Upper"].connect( + connecting_region=regions["Overworld"], + rule=lambda state: state.has_any({laurels, grapple}, player)) + regions["Overworld"].connect( connecting_region=regions["Overworld Belltower"], rule=lambda state: state.has(laurels, player)) regions["Overworld Belltower"].connect( connecting_region=regions["Overworld"]) + regions["Overworld Belltower"].connect( + connecting_region=regions["Overworld to West Garden Upper"], + rule=lambda state: has_ladder("Ladders to West Bell", state, player, options)) + regions["Overworld to West Garden Upper"].connect( + connecting_region=regions["Overworld Belltower"], + rule=lambda state: has_ladder("Ladders to West Bell", state, player, options)) + + regions["Overworld Belltower"].connect( + connecting_region=regions["Overworld Belltower at Bell"], + rule=lambda state: has_ladder("Ladders to West Bell", state, player, options)) + + # long dong, do not make a reverse connection here or to belltower + regions["Overworld above Patrol Cave"].connect( + connecting_region=regions["Overworld Belltower at Bell"], + rule=lambda state: options.logic_rules and state.has(fire_wand, player)) + # nmg: can laurels through the ruined passage door regions["Overworld"].connect( connecting_region=regions["Overworld Ruined Passage Door"], rule=lambda state: state.has(key, player, 2) or (state.has(laurels, player) and options.logic_rules)) + regions["Overworld Ruined Passage Door"].connect( + connecting_region=regions["Overworld"], + rule=lambda state: state.has(laurels, player) and options.logic_rules) + + regions["Overworld"].connect( + connecting_region=regions["After Ruined Passage"], + rule=lambda state: has_ladder("Ladders near Weathervane", state, player, options) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks)) + regions["After Ruined Passage"].connect( + connecting_region=regions["Overworld"], + rule=lambda state: has_ladder("Ladders near Weathervane", state, player, options)) + + regions["Overworld"].connect( + connecting_region=regions["Above Ruined Passage"], + rule=lambda state: has_ladder("Ladders near Weathervane", state, player, options) + or state.has(laurels, player)) + regions["Above Ruined Passage"].connect( + connecting_region=regions["Overworld"], + rule=lambda state: has_ladder("Ladders near Weathervane", state, player, options) + or state.has(laurels, player)) + + regions["After Ruined Passage"].connect( + connecting_region=regions["Above Ruined Passage"], + rule=lambda state: has_ladder("Ladders near Weathervane", state, player, options)) + regions["Above Ruined Passage"].connect( + connecting_region=regions["After Ruined Passage"], + rule=lambda state: has_ladder("Ladders near Weathervane", state, player, options)) + + regions["Above Ruined Passage"].connect( + connecting_region=regions["East Overworld"], + rule=lambda state: has_ladder("Ladders near Weathervane", state, player, options) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks)) + regions["East Overworld"].connect( + connecting_region=regions["Above Ruined Passage"], + rule=lambda state: has_ladder("Ladders near Weathervane", state, player, options) + or state.has(laurels, player)) + + # nmg: ice grapple the slimes, works both ways consistently + regions["East Overworld"].connect( + connecting_region=regions["After Ruined Passage"], + rule=lambda state: has_ice_grapple_logic(True, state, player, options, ability_unlocks)) + regions["After Ruined Passage"].connect( + connecting_region=regions["East Overworld"], + rule=lambda state: has_ice_grapple_logic(True, state, player, options, ability_unlocks)) + + regions["Overworld"].connect( + connecting_region=regions["East Overworld"], + rule=lambda state: has_ladder("Ladders near Overworld Checkpoint", state, player, options) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks)) + regions["East Overworld"].connect( + connecting_region=regions["Overworld"], + rule=lambda state: has_ladder("Ladders near Overworld Checkpoint", state, player, options)) + + regions["East Overworld"].connect( + connecting_region=regions["Overworld at Patrol Cave"]) + regions["Overworld at Patrol Cave"].connect( + connecting_region=regions["East Overworld"], + rule=lambda state: state.has(laurels, player)) + + regions["Overworld at Patrol Cave"].connect( + connecting_region=regions["Overworld above Patrol Cave"], + rule=lambda state: has_ladder("Ladders near Patrol Cave", state, player, options) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks)) + regions["Overworld above Patrol Cave"].connect( + connecting_region=regions["Overworld at Patrol Cave"], + rule=lambda state: has_ladder("Ladders near Patrol Cave", state, player, options)) + + regions["Overworld"].connect( + connecting_region=regions["Overworld above Patrol Cave"], + rule=lambda state: has_ladder("Ladders near Overworld Checkpoint", state, player, options) + or state.has(grapple, player)) + regions["Overworld above Patrol Cave"].connect( + connecting_region=regions["Overworld"], + rule=lambda state: has_ladder("Ladders near Overworld Checkpoint", state, player, options)) + + regions["East Overworld"].connect( + connecting_region=regions["Overworld above Patrol Cave"], + rule=lambda state: has_ladder("Ladders near Overworld Checkpoint", state, player, options) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks)) + regions["Overworld above Patrol Cave"].connect( + connecting_region=regions["East Overworld"], + rule=lambda state: has_ladder("Ladders near Overworld Checkpoint", state, player, options)) + + regions["Overworld above Patrol Cave"].connect( + connecting_region=regions["Upper Overworld"], + rule=lambda state: has_ladder("Ladders near Patrol Cave", state, player, options) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks)) + regions["Upper Overworld"].connect( + connecting_region=regions["Overworld above Patrol Cave"], + rule=lambda state: has_ladder("Ladders near Patrol Cave", state, player, options) + or state.has(grapple, player)) + + regions["Upper Overworld"].connect( + connecting_region=regions["Overworld above Quarry Entrance"], + rule=lambda state: state.has_any({grapple, laurels}, player)) + regions["Overworld above Quarry Entrance"].connect( + connecting_region=regions["Upper Overworld"], + rule=lambda state: state.has_any({grapple, laurels}, player)) + + regions["Upper Overworld"].connect( + connecting_region=regions["Overworld after Temple Rafters"], + rule=lambda state: has_ladder("Ladder near Temple Rafters", state, player, options)) + regions["Overworld after Temple Rafters"].connect( + connecting_region=regions["Upper Overworld"], + rule=lambda state: has_ladder("Ladder near Temple Rafters", state, player, options) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks)) + + regions["Overworld above Quarry Entrance"].connect( + connecting_region=regions["Overworld"], + rule=lambda state: has_ladder("Ladders near Dark Tomb", state, player, options)) + regions["Overworld"].connect( + connecting_region=regions["Overworld above Quarry Entrance"], + rule=lambda state: has_ladder("Ladders near Dark Tomb", state, player, options)) + + regions["Overworld"].connect( + connecting_region=regions["Overworld after Envoy"], + rule=lambda state: state.has_any({laurels, grapple}, player) + or state.has("Sword Upgrade", player, 4) + or options.logic_rules) + regions["Overworld after Envoy"].connect( + connecting_region=regions["Overworld"], + rule=lambda state: state.has_any({laurels, grapple}, player) + or state.has("Sword Upgrade", player, 4) + or options.logic_rules) + + regions["Overworld after Envoy"].connect( + connecting_region=regions["Overworld Quarry Entry"], + rule=lambda state: has_ladder("Ladder to Quarry", state, player, options)) + regions["Overworld Quarry Entry"].connect( + connecting_region=regions["Overworld after Envoy"], + rule=lambda state: has_ladder("Ladder to Quarry", state, player, options)) + + # ice grapple through the gate + regions["Overworld"].connect( + connecting_region=regions["Overworld Quarry Entry"], + rule=lambda state: has_ice_grapple_logic(False, state, player, options, ability_unlocks)) + regions["Overworld Quarry Entry"].connect( + connecting_region=regions["Overworld"], + rule=lambda state: has_ice_grapple_logic(False, state, player, options, ability_unlocks)) regions["Overworld"].connect( connecting_region=regions["Overworld Swamp Upper Entry"], @@ -60,18 +251,24 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re rule=lambda state: state.has(laurels, player)) regions["Overworld"].connect( + connecting_region=regions["Overworld Swamp Lower Entry"], + rule=lambda state: has_ladder("Ladder to Swamp", state, player, options)) + regions["Overworld Swamp Lower Entry"].connect( + connecting_region=regions["Overworld"], + rule=lambda state: has_ladder("Ladder to Swamp", state, player, options)) + + regions["East Overworld"].connect( connecting_region=regions["Overworld Special Shop Entry"], rule=lambda state: state.has(laurels, player)) regions["Overworld Special Shop Entry"].connect( - connecting_region=regions["Overworld"], + connecting_region=regions["East Overworld"], rule=lambda state: state.has(laurels, player)) regions["Overworld"].connect( - connecting_region=regions["Overworld West Garden Laurels Entry"], - rule=lambda state: state.has(laurels, player)) - regions["Overworld West Garden Laurels Entry"].connect( - connecting_region=regions["Overworld"], - rule=lambda state: state.has(laurels, player)) + connecting_region=regions["Overworld Well Ladder"], + rule=lambda state: has_ladder("Ladders in Well", state, player, options)) + regions["Overworld Well Ladder"].connect( + connecting_region=regions["Overworld"]) # nmg: can ice grapple through the door regions["Overworld"].connect( @@ -109,10 +306,30 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re # nmg: ice grapple through temple door regions["Overworld"].connect( connecting_region=regions["Overworld Temple Door"], - name="Overworld Temple Door", rule=lambda state: state.has_all({"Ring Eastern Bell", "Ring Western Bell"}, player) or has_ice_grapple_logic(False, state, player, options, ability_unlocks)) + regions["Overworld Temple Door"].connect( + connecting_region=regions["Overworld above Patrol Cave"], + rule=lambda state: state.has(grapple, player)) + + regions["Overworld Tunnel Turret"].connect( + connecting_region=regions["Overworld Beach"], + rule=lambda state: has_ladder("Ladders in Overworld Town", state, player, options) + or state.has(grapple, player)) + regions["Overworld Beach"].connect( + connecting_region=regions["Overworld Tunnel Turret"], + rule=lambda state: has_ladder("Ladders in Overworld Town", state, player, options) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks)) + + regions["Overworld"].connect( + connecting_region=regions["Overworld Tunnel Turret"], + rule=lambda state: state.has(laurels, player) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks)) + regions["Overworld Tunnel Turret"].connect( + connecting_region=regions["Overworld"], + rule=lambda state: state.has_any({grapple, laurels}, player)) + # Overworld side areas regions["Old House Front"].connect( connecting_region=regions["Old House Back"]) @@ -148,12 +365,17 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re connecting_region=regions["Furnace Fuse"], rule=lambda state: state.has(laurels, player)) + regions["Hourglass Cave"].connect( + connecting_region=regions["Hourglass Cave Tower"], + rule=lambda state: has_ladder("Ladders in Hourglass Cave", state, player, options)) + # East Forest regions["Forest Belltower Upper"].connect( connecting_region=regions["Forest Belltower Main"]) regions["Forest Belltower Main"].connect( - connecting_region=regions["Forest Belltower Lower"]) + connecting_region=regions["Forest Belltower Lower"], + rule=lambda state: has_ladder("Ladder to East Forest", state, player, options)) # nmg: ice grapple up to dance fox spot, and vice versa regions["East Forest"].connect( @@ -171,12 +393,28 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re regions["East Forest Portal"].connect( connecting_region=regions["East Forest"]) + regions["East Forest"].connect( + connecting_region=regions["Lower Forest"], + rule=lambda state: has_ladder("Ladders to Lower Forest", state, player, options) + or (state.has_all({grapple, fire_wand, ice_dagger}, player) # do ice slime, then go to the lower hook + and has_ability(state, player, icebolt, options, ability_unlocks))) + regions["Lower Forest"].connect( + connecting_region=regions["East Forest"], + rule=lambda state: has_ladder("Ladders to Lower Forest", state, player, options)) + regions["Guard House 1 East"].connect( connecting_region=regions["Guard House 1 West"]) regions["Guard House 1 West"].connect( connecting_region=regions["Guard House 1 East"], rule=lambda state: state.has(laurels, player)) + regions["Guard House 2 Upper"].connect( + connecting_region=regions["Guard House 2 Lower"], + rule=lambda state: has_ladder("Ladders to Lower Forest", state, player, options)) + regions["Guard House 2 Lower"].connect( + connecting_region=regions["Guard House 2 Upper"], + rule=lambda state: has_ladder("Ladders to Lower Forest", state, player, options)) + # nmg: ice grapple from upper grave path exit to the rest of it regions["Forest Grave Path Upper"].connect( connecting_region=regions["Forest Grave Path Main"], @@ -201,6 +439,14 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re connecting_region=regions["Forest Grave Path by Grave"]) # Beneath the Well and Dark Tomb + # don't need the ladder when entering at the ladder spot + regions["Beneath the Well Ladder Exit"].connect( + connecting_region=regions["Beneath the Well Front"], + rule=lambda state: has_ladder("Ladders in Well", state, player, options)) + regions["Beneath the Well Front"].connect( + connecting_region=regions["Beneath the Well Ladder Exit"], + rule=lambda state: has_ladder("Ladders in Well", state, player, options)) + regions["Beneath the Well Front"].connect( connecting_region=regions["Beneath the Well Main"], rule=lambda state: has_stick(state, player) or state.has(fire_wand, player)) @@ -208,12 +454,13 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re connecting_region=regions["Beneath the Well Front"], rule=lambda state: has_stick(state, player) or state.has(fire_wand, player)) - regions["Beneath the Well Back"].connect( - connecting_region=regions["Beneath the Well Main"], - rule=lambda state: has_stick(state, player) or state.has(fire_wand, player)) regions["Beneath the Well Main"].connect( connecting_region=regions["Beneath the Well Back"], - rule=lambda state: has_stick(state, player) or state.has(fire_wand, player)) + rule=lambda state: has_ladder("Ladders in Well", state, player, options)) + regions["Beneath the Well Back"].connect( + connecting_region=regions["Beneath the Well Main"], + rule=lambda state: has_ladder("Ladders in Well", state, player, options) + and (has_stick(state, player) or state.has(fire_wand, player))) regions["Well Boss"].connect( connecting_region=regions["Dark Tomb Checkpoint"]) @@ -223,25 +470,30 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re rule=lambda state: state.has(laurels, player) and options.logic_rules) regions["Dark Tomb Entry Point"].connect( + connecting_region=regions["Dark Tomb Upper"], + rule=lambda state: has_lantern(state, player, options)) + regions["Dark Tomb Upper"].connect( + connecting_region=regions["Dark Tomb Entry Point"]) + + regions["Dark Tomb Upper"].connect( connecting_region=regions["Dark Tomb Main"], - rule=lambda state: has_lantern(state, player, options)) + rule=lambda state: has_ladder("Ladder in Dark Tomb", state, player, options)) regions["Dark Tomb Main"].connect( - connecting_region=regions["Dark Tomb Entry Point"], - rule=lambda state: has_lantern(state, player, options)) + connecting_region=regions["Dark Tomb Upper"], + rule=lambda state: has_ladder("Ladder in Dark Tomb", state, player, options)) regions["Dark Tomb Main"].connect( - connecting_region=regions["Dark Tomb Dark Exit"], - rule=lambda state: has_lantern(state, player, options)) + connecting_region=regions["Dark Tomb Dark Exit"]) regions["Dark Tomb Dark Exit"].connect( connecting_region=regions["Dark Tomb Main"], rule=lambda state: has_lantern(state, player, options)) # West Garden - regions["West Garden Laurels Exit"].connect( + regions["West Garden Laurels Exit Region"].connect( connecting_region=regions["West Garden"], rule=lambda state: state.has(laurels, player)) regions["West Garden"].connect( - connecting_region=regions["West Garden Laurels Exit"], + connecting_region=regions["West Garden Laurels Exit Region"], rule=lambda state: state.has(laurels, player)) regions["West Garden after Boss"].connect( @@ -252,9 +504,9 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re rule=lambda state: state.has(laurels, player) or has_sword(state, player)) regions["West Garden"].connect( - connecting_region=regions["West Garden Hero's Grave"], + connecting_region=regions["West Garden Hero's Grave Region"], rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks)) - regions["West Garden Hero's Grave"].connect( + regions["West Garden Hero's Grave Region"].connect( connecting_region=regions["West Garden"]) regions["West Garden Portal"].connect( @@ -282,6 +534,10 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re connecting_region=regions["Ruined Atoll"], rule=lambda state: state.has(laurels, player) or state.has(grapple, player)) + regions["Ruined Atoll"].connect( + connecting_region=regions["Ruined Atoll Ladder Tops"], + rule=lambda state: has_ladder("Ladders in South Atoll", state, player, options)) + regions["Ruined Atoll"].connect( connecting_region=regions["Ruined Atoll Frog Mouth"], rule=lambda state: state.has(laurels, player) or state.has(grapple, player)) @@ -289,6 +545,13 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re connecting_region=regions["Ruined Atoll"], rule=lambda state: state.has(laurels, player) or state.has(grapple, player)) + regions["Ruined Atoll"].connect( + connecting_region=regions["Ruined Atoll Frog Eye"], + rule=lambda state: has_ladder("Ladders to Frog's Domain", state, player, options)) + regions["Ruined Atoll Frog Eye"].connect( + connecting_region=regions["Ruined Atoll"], + rule=lambda state: has_ladder("Ladders to Frog's Domain", state, player, options)) + regions["Ruined Atoll"].connect( connecting_region=regions["Ruined Atoll Portal"], rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks)) @@ -297,41 +560,109 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re regions["Ruined Atoll"].connect( connecting_region=regions["Ruined Atoll Statue"], - rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks)) + rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks) + and has_ladder("Ladders in South Atoll", state, player, options)) regions["Ruined Atoll Statue"].connect( connecting_region=regions["Ruined Atoll"]) + regions["Frog Stairs Eye Exit"].connect( + connecting_region=regions["Frog Stairs Upper"], + rule=lambda state: has_ladder("Ladders to Frog's Domain", state, player, options)) + regions["Frog Stairs Upper"].connect( + connecting_region=regions["Frog Stairs Eye Exit"], + rule=lambda state: has_ladder("Ladders to Frog's Domain", state, player, options)) + + regions["Frog Stairs Upper"].connect( + connecting_region=regions["Frog Stairs Lower"], + rule=lambda state: has_ladder("Ladders to Frog's Domain", state, player, options)) + regions["Frog Stairs Lower"].connect( + connecting_region=regions["Frog Stairs Upper"], + rule=lambda state: has_ladder("Ladders to Frog's Domain", state, player, options)) + + regions["Frog Stairs Lower"].connect( + connecting_region=regions["Frog Stairs to Frog's Domain"], + rule=lambda state: has_ladder("Ladders to Frog's Domain", state, player, options)) + regions["Frog Stairs to Frog's Domain"].connect( + connecting_region=regions["Frog Stairs Lower"], + rule=lambda state: has_ladder("Ladders to Frog's Domain", state, player, options)) + + regions["Frog's Domain Entry"].connect( + connecting_region=regions["Frog's Domain"], + rule=lambda state: has_ladder("Ladders to Frog's Domain", state, player, options)) + regions["Frog's Domain"].connect( connecting_region=regions["Frog's Domain Back"], rule=lambda state: state.has(grapple, player)) # Library - regions["Library Exterior Tree"].connect( - connecting_region=regions["Library Exterior Ladder"], - rule=lambda state: state.has(grapple, player) or state.has(laurels, player)) - regions["Library Exterior Ladder"].connect( - connecting_region=regions["Library Exterior Tree"], + regions["Library Exterior Tree Region"].connect( + connecting_region=regions["Library Exterior Ladder Region"], + rule=lambda state: state.has_any({grapple, laurels}, player) + and has_ladder("Ladders in Library", state, player, options)) + regions["Library Exterior Ladder Region"].connect( + connecting_region=regions["Library Exterior Tree Region"], rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks) - and (state.has(grapple, player) or state.has(laurels, player))) + and state.has_any({grapple, laurels}, player) + and has_ladder("Ladders in Library", state, player, options)) + + regions["Library Hall Bookshelf"].connect( + connecting_region=regions["Library Hall"], + rule=lambda state: has_ladder("Ladders in Library", state, player, options)) + regions["Library Hall"].connect( + connecting_region=regions["Library Hall Bookshelf"], + rule=lambda state: has_ladder("Ladders in Library", state, player, options)) regions["Library Hall"].connect( - connecting_region=regions["Library Hero's Grave"], + connecting_region=regions["Library Hero's Grave Region"], rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks)) - regions["Library Hero's Grave"].connect( + regions["Library Hero's Grave Region"].connect( connecting_region=regions["Library Hall"]) + regions["Library Hall to Rotunda"].connect( + connecting_region=regions["Library Hall"], + rule=lambda state: has_ladder("Ladders in Library", state, player, options)) + regions["Library Hall"].connect( + connecting_region=regions["Library Hall to Rotunda"], + rule=lambda state: has_ladder("Ladders in Library", state, player, options)) + + regions["Library Rotunda to Hall"].connect( + connecting_region=regions["Library Rotunda"], + rule=lambda state: has_ladder("Ladders in Library", state, player, options)) + regions["Library Rotunda"].connect( + connecting_region=regions["Library Rotunda to Hall"], + rule=lambda state: has_ladder("Ladders in Library", state, player, options)) + + regions["Library Rotunda"].connect( + connecting_region=regions["Library Rotunda to Lab"], + rule=lambda state: has_ladder("Ladders in Library", state, player, options)) + regions["Library Rotunda to Lab"].connect( + connecting_region=regions["Library Rotunda"], + rule=lambda state: has_ladder("Ladders in Library", state, player, options)) + regions["Library Lab Lower"].connect( connecting_region=regions["Library Lab"], - rule=lambda state: state.has(laurels, player) or state.has(grapple, player)) + rule=lambda state: state.has_any({grapple, laurels}, player) + and has_ladder("Ladders in Library", state, player, options)) regions["Library Lab"].connect( connecting_region=regions["Library Lab Lower"], - rule=lambda state: state.has(laurels, player)) + rule=lambda state: state.has(laurels, player) + and has_ladder("Ladders in Library", state, player, options)) regions["Library Lab"].connect( connecting_region=regions["Library Portal"], - rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks)) + rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks) + and has_ladder("Ladders in Library", state, player, options)) regions["Library Portal"].connect( - connecting_region=regions["Library Lab"]) + connecting_region=regions["Library Lab"], + rule=lambda state: has_ladder("Ladders in Library", state, player, options) + or state.has(laurels, player)) + + regions["Library Lab"].connect( + connecting_region=regions["Library Lab to Librarian"], + rule=lambda state: has_ladder("Ladders in Library", state, player, options)) + regions["Library Lab to Librarian"].connect( + connecting_region=regions["Library Lab"], + rule=lambda state: has_ladder("Ladders in Library", state, player, options)) # Eastern Vault Fortress regions["Fortress Exterior from East Forest"].connect( @@ -348,6 +679,13 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re connecting_region=regions["Fortress Exterior near cave"], rule=lambda state: state.has(laurels, player) or has_ability(state, player, prayer, options, ability_unlocks)) + regions["Fortress Exterior near cave"].connect( + connecting_region=regions["Beneath the Vault Entry"], + rule=lambda state: has_ladder("Ladder to Beneath the Vault", state, player, options)) + regions["Beneath the Vault Entry"].connect( + connecting_region=regions["Fortress Exterior near cave"], + rule=lambda state: has_ladder("Ladder to Beneath the Vault", state, player, options)) + regions["Fortress Courtyard"].connect( connecting_region=regions["Fortress Exterior from Overworld"], rule=lambda state: state.has(laurels, player)) @@ -367,6 +705,13 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re regions["Fortress Courtyard Upper"].connect( connecting_region=regions["Fortress Exterior from Overworld"]) + regions["Beneath the Vault Ladder Exit"].connect( + connecting_region=regions["Beneath the Vault Front"], + rule=lambda state: has_ladder("Ladder to Beneath the Vault", state, player, options)) + regions["Beneath the Vault Front"].connect( + connecting_region=regions["Beneath the Vault Ladder Exit"], + rule=lambda state: has_ladder("Ladder to Beneath the Vault", state, player, options)) + regions["Beneath the Vault Front"].connect( connecting_region=regions["Beneath the Vault Back"], rule=lambda state: has_lantern(state, player, options)) @@ -383,26 +728,24 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re # nmg: ice grapple through the big gold door, can do it both ways regions["Eastern Vault Fortress"].connect( connecting_region=regions["Eastern Vault Fortress Gold Door"], - name="Fortress to Gold Door", rule=lambda state: state.has_all({"Activate Eastern Vault West Fuses", "Activate Eastern Vault East Fuse"}, player) or has_ice_grapple_logic(False, state, player, options, ability_unlocks)) regions["Eastern Vault Fortress Gold Door"].connect( connecting_region=regions["Eastern Vault Fortress"], - name="Gold Door to Fortress", rule=lambda state: has_ice_grapple_logic(True, state, player, options, ability_unlocks)) regions["Fortress Grave Path"].connect( - connecting_region=regions["Fortress Grave Path Dusty Entrance"], + connecting_region=regions["Fortress Grave Path Dusty Entrance Region"], rule=lambda state: state.has(laurels, player)) - regions["Fortress Grave Path Dusty Entrance"].connect( + regions["Fortress Grave Path Dusty Entrance Region"].connect( connecting_region=regions["Fortress Grave Path"], rule=lambda state: state.has(laurels, player)) regions["Fortress Grave Path"].connect( - connecting_region=regions["Fortress Hero's Grave"], + connecting_region=regions["Fortress Hero's Grave Region"], rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks)) - regions["Fortress Hero's Grave"].connect( + regions["Fortress Hero's Grave Region"].connect( connecting_region=regions["Fortress Grave Path"]) # nmg: ice grapple from upper grave path to lower @@ -412,7 +755,6 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re regions["Fortress Arena"].connect( connecting_region=regions["Fortress Arena Portal"], - name="Fortress Arena to Fortress Portal", rule=lambda state: state.has("Activate Eastern Vault West Fuses", player)) regions["Fortress Arena Portal"].connect( connecting_region=regions["Fortress Arena"]) @@ -427,7 +769,6 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re regions["Quarry Entry"].connect( connecting_region=regions["Quarry Portal"], - name="Quarry to Quarry Portal", rule=lambda state: state.has("Activate Quarry Fuse", player)) regions["Quarry Portal"].connect( connecting_region=regions["Quarry Entry"]) @@ -464,17 +805,23 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re connecting_region=regions["Lower Quarry"], rule=lambda state: has_mask(state, player, options)) - # nmg: bring a scav over, then ice grapple through the door + # need the ladder, or you can ice grapple down in nmg regions["Lower Quarry"].connect( - connecting_region=regions["Lower Quarry Zig Door"], - name="Quarry to Zig Door", - rule=lambda state: state.has("Activate Quarry Fuse", player) - or has_ice_grapple_logic(False, state, player, options, ability_unlocks)) + connecting_region=regions["Even Lower Quarry"], + rule=lambda state: has_ladder("Ladders in Lower Quarry", state, player, options) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks)) - # nmg: use ice grapple to get from the beginning of Quarry to the door without really needing mask + # nmg: bring a scav over, then ice grapple through the door, only with ER on to avoid soft lock + regions["Even Lower Quarry"].connect( + connecting_region=regions["Lower Quarry Zig Door"], + rule=lambda state: state.has("Activate Quarry Fuse", player) + or (has_ice_grapple_logic(False, state, player, options, ability_unlocks) and options.entrance_rando)) + + # nmg: use ice grapple to get from the beginning of Quarry to the door without really needing mask only with ER on regions["Quarry"].connect( connecting_region=regions["Lower Quarry Zig Door"], - rule=lambda state: has_ice_grapple_logic(True, state, player, options, ability_unlocks)) + rule=lambda state: has_ice_grapple_logic(True, state, player, options, ability_unlocks) + and options.entrance_rando) regions["Monastery Front"].connect( connecting_region=regions["Monastery Back"]) @@ -484,9 +831,9 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re rule=lambda state: state.has(laurels, player) and options.logic_rules) regions["Monastery Back"].connect( - connecting_region=regions["Monastery Hero's Grave"], + connecting_region=regions["Monastery Hero's Grave Region"], rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks)) - regions["Monastery Hero's Grave"].connect( + regions["Monastery Hero's Grave Region"].connect( connecting_region=regions["Monastery Back"]) # Ziggurat @@ -511,10 +858,11 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re # nmg: can ice grapple on the voidlings to the double admin fight, still need to pray at the fuse regions["Rooted Ziggurat Lower Back"].connect( connecting_region=regions["Rooted Ziggurat Lower Front"], - rule=lambda state: ((state.has(laurels, player) or - has_ice_grapple_logic(True, state, player, options, ability_unlocks)) and - has_ability(state, player, prayer, options, ability_unlocks) - and has_sword(state, player)) or can_ladder_storage(state, player, options)) + rule=lambda state: ((state.has(laurels, player) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks)) + and has_ability(state, player, prayer, options, ability_unlocks) + and has_sword(state, player)) + or can_ladder_storage(state, player, options)) regions["Rooted Ziggurat Lower Back"].connect( connecting_region=regions["Rooted Ziggurat Portal Room Entrance"], @@ -524,27 +872,46 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re regions["Rooted Ziggurat Portal"].connect( connecting_region=regions["Rooted Ziggurat Portal Room Exit"], - name="Zig Portal Room Exit", rule=lambda state: state.has("Activate Ziggurat Fuse", player)) regions["Rooted Ziggurat Portal Room Exit"].connect( connecting_region=regions["Rooted Ziggurat Portal"], rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks)) # Swamp and Cathedral + regions["Swamp Front"].connect( + connecting_region=regions["Swamp Mid"], + rule=lambda state: has_ladder("Ladders in Swamp", state, player, options) + or state.has(laurels, player) + or has_ice_grapple_logic(False, state, player, options, ability_unlocks)) # nmg: ice grapple through gate + regions["Swamp Mid"].connect( + connecting_region=regions["Swamp Front"], + rule=lambda state: has_ladder("Ladders in Swamp", state, player, options) + or state.has(laurels, player) + or has_ice_grapple_logic(False, state, player, options, ability_unlocks)) # nmg: ice grapple through gate + # nmg: ice grapple through cathedral door, can do it both ways - regions["Swamp"].connect( - connecting_region=regions["Swamp to Cathedral Main Entrance"], - rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks) + regions["Swamp Mid"].connect( + connecting_region=regions["Swamp to Cathedral Main Entrance Region"], + rule=lambda state: (has_ability(state, player, prayer, options, ability_unlocks) + and state.has(laurels, player)) or has_ice_grapple_logic(False, state, player, options, ability_unlocks)) - regions["Swamp to Cathedral Main Entrance"].connect( - connecting_region=regions["Swamp"], + regions["Swamp to Cathedral Main Entrance Region"].connect( + connecting_region=regions["Swamp Mid"], rule=lambda state: has_ice_grapple_logic(False, state, player, options, ability_unlocks)) - regions["Swamp"].connect( + regions["Swamp Mid"].connect( + connecting_region=regions["Swamp Ledge under Cathedral Door"], + rule=lambda state: has_ladder("Ladders in Swamp", state, player, options)) + regions["Swamp Ledge under Cathedral Door"].connect( + connecting_region=regions["Swamp Mid"], + rule=lambda state: has_ladder("Ladders in Swamp", state, player, options) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks)) # nmg: ice grapple the enemy at door + + regions["Swamp Ledge under Cathedral Door"].connect( connecting_region=regions["Swamp to Cathedral Treasure Room"], rule=lambda state: has_ability(state, player, holy_cross, options, ability_unlocks)) regions["Swamp to Cathedral Treasure Room"].connect( - connecting_region=regions["Swamp"]) + connecting_region=regions["Swamp Ledge under Cathedral Door"]) regions["Back of Swamp"].connect( connecting_region=regions["Back of Swamp Laurels Area"], @@ -555,14 +922,14 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re # nmg: can ice grapple down while you're on the pillars regions["Back of Swamp Laurels Area"].connect( - connecting_region=regions["Swamp"], + connecting_region=regions["Swamp Mid"], rule=lambda state: state.has(laurels, player) and has_ice_grapple_logic(True, state, player, options, ability_unlocks)) regions["Back of Swamp"].connect( - connecting_region=regions["Swamp Hero's Grave"], + connecting_region=regions["Swamp Hero's Grave Region"], rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks)) - regions["Swamp Hero's Grave"].connect( + regions["Swamp Hero's Grave Region"].connect( connecting_region=regions["Back of Swamp"]) regions["Cathedral Gauntlet Checkpoint"].connect( @@ -577,45 +944,41 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re # Far Shore regions["Far Shore"].connect( - connecting_region=regions["Far Shore to Spawn"], + connecting_region=regions["Far Shore to Spawn Region"], rule=lambda state: state.has(laurels, player)) - regions["Far Shore to Spawn"].connect( + regions["Far Shore to Spawn Region"].connect( connecting_region=regions["Far Shore"], rule=lambda state: state.has(laurels, player)) regions["Far Shore"].connect( - connecting_region=regions["Far Shore to East Forest"], + connecting_region=regions["Far Shore to East Forest Region"], rule=lambda state: state.has(laurels, player)) - regions["Far Shore to East Forest"].connect( + regions["Far Shore to East Forest Region"].connect( connecting_region=regions["Far Shore"], rule=lambda state: state.has(laurels, player)) regions["Far Shore"].connect( - connecting_region=regions["Far Shore to West Garden"], - name="Far Shore to West Garden", + connecting_region=regions["Far Shore to West Garden Region"], rule=lambda state: state.has("Activate West Garden Fuse", player)) - regions["Far Shore to West Garden"].connect( + regions["Far Shore to West Garden Region"].connect( connecting_region=regions["Far Shore"]) regions["Far Shore"].connect( - connecting_region=regions["Far Shore to Quarry"], - name="Far Shore to Quarry", + connecting_region=regions["Far Shore to Quarry Region"], rule=lambda state: state.has("Activate Quarry Fuse", player)) - regions["Far Shore to Quarry"].connect( + regions["Far Shore to Quarry Region"].connect( connecting_region=regions["Far Shore"]) regions["Far Shore"].connect( - connecting_region=regions["Far Shore to Fortress"], - name="Far Shore to Fortress", + connecting_region=regions["Far Shore to Fortress Region"], rule=lambda state: state.has("Activate Eastern Vault West Fuses", player)) - regions["Far Shore to Fortress"].connect( + regions["Far Shore to Fortress Region"].connect( connecting_region=regions["Far Shore"]) regions["Far Shore"].connect( - connecting_region=regions["Far Shore to Library"], - name="Far Shore to Library", + connecting_region=regions["Far Shore to Library Region"], rule=lambda state: state.has("Activate Library Fuse", player)) - regions["Far Shore to Library"].connect( + regions["Far Shore to Library Region"].connect( connecting_region=regions["Far Shore"]) # Misc @@ -628,174 +991,335 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re # connecting the regions portals are in to other portals you can access via ladder storage # using has_stick instead of can_ladder_storage since it's already checking the logic rules if options.logic_rules == "unrestricted": - def get_paired_region(portal_sd: str) -> str: + def get_portal_info(portal_sd: str) -> (str, str): for portal1, portal2 in portal_pairs.items(): if portal1.scene_destination() == portal_sd: - return portal2.region + return portal1.name, portal2.region if portal2.scene_destination() == portal_sd: - return portal1.region + return portal2.name, portal1.region raise Exception("no matches found in get_paired_region") - # The upper Swamp entrance - regions["Overworld"].connect( - regions[get_paired_region("Overworld Redux, Swamp Redux 2_wall")], - rule=lambda state: has_stick(state, player)) - # Western Furnace entrance, next to the sign that leads to West Garden - regions["Overworld"].connect( - regions[get_paired_region("Overworld Redux, Furnace_gyro_west")], - rule=lambda state: has_stick(state, player)) - # Upper West Garden entry, by the belltower - regions["Overworld"].connect( - regions[get_paired_region("Overworld Redux, Archipelagos Redux_upper")], - rule=lambda state: has_stick(state, player)) - # West Garden entry by the Furnace - regions["Overworld"].connect( - regions[get_paired_region("Overworld Redux, Archipelagos Redux_lower")], - rule=lambda state: has_stick(state, player)) - # West Garden laurels entrance, by the beach - regions["Overworld"].connect( - regions[get_paired_region("Overworld Redux, Archipelagos Redux_lowest")], - rule=lambda state: has_stick(state, player)) - # Well rail, west side. Can ls in town, get extra height by going over the portal pad - regions["Overworld"].connect( - regions[get_paired_region("Overworld Redux, Sewer_west_aqueduct")], - rule=lambda state: has_stick(state, player)) - # Well rail, east side. Need some height from the temple stairs - regions["Overworld"].connect( - regions[get_paired_region("Overworld Redux, Furnace_gyro_upper_north")], - rule=lambda state: has_stick(state, player)) + ladder_storages: List[Tuple[str, str, Set[str]]] = [ + # LS from Overworld main + # The upper Swamp entrance + ("Overworld", "Overworld Redux, Swamp Redux 2_wall", + {"Ladders near Weathervane", "Ladder to Swamp", "Ladders in Overworld Town"}), + # Upper atoll entrance + ("Overworld", "Overworld Redux, Atoll Redux_upper", + {"Ladders near Weathervane", "Ladder to Swamp", "Ladders in Overworld Town"}), + # Furnace entrance, next to the sign that leads to West Garden + ("Overworld", "Overworld Redux, Furnace_gyro_west", + {"Ladders near Weathervane", "Ladder to Swamp", "Ladders in Overworld Town"}), + # Upper West Garden entry, by the belltower + ("Overworld", "Overworld Redux, Archipelagos Redux_upper", + {"Ladders near Weathervane", "Ladder to Swamp", "Ladders in Overworld Town"}), + # Ruined Passage + ("Overworld", "Overworld Redux, Ruins Passage_east", + {"Ladders near Weathervane", "Ladder to Swamp", "Ladders in Overworld Town"}), + # Well rail, west side. Can ls in town, get extra height by going over the portal pad + ("Overworld", "Overworld Redux, Sewer_west_aqueduct", + {"Ladders near Weathervane", "Ladder to Swamp", "Ladders in Overworld Town", "Ladder to Quarry"}), + # Well rail, east side. Need some height from the temple stairs + ("Overworld", "Overworld Redux, Furnace_gyro_upper_north", + {"Ladders near Weathervane", "Ladder to Swamp", "Ladders in Overworld Town", "Ladder to Quarry"}), + # Quarry entry + ("Overworld", "Overworld Redux, Darkwoods Tunnel_", + {"Ladders near Weathervane", "Ladder to Swamp", "Ladders in Overworld Town", "Ladders in Well"}), + # East Forest entry + ("Overworld", "Overworld Redux, Forest Belltower_", + {"Ladders near Weathervane", "Ladder to Swamp", "Ladders in Overworld Town", "Ladders in Well", + "Ladders near Patrol Cave", "Ladder to Quarry", "Ladders near Dark Tomb"}), + # Fortress entry + ("Overworld", "Overworld Redux, Fortress Courtyard_", + {"Ladders near Weathervane", "Ladder to Swamp", "Ladders in Overworld Town", "Ladders in Well", + "Ladders near Patrol Cave", "Ladder to Quarry", "Ladders near Dark Tomb"}), + # Patrol Cave entry + ("Overworld", "Overworld Redux, PatrolCave_", + {"Ladders near Weathervane", "Ladder to Swamp", "Ladders in Overworld Town", "Ladders in Well", + "Ladders near Overworld Checkpoint", "Ladder to Quarry", "Ladders near Dark Tomb"}), + # Special Shop entry, excluded in non-ER due to soft lock potential + ("Overworld", "Overworld Redux, ShopSpecial_", + {"Ladders near Weathervane", "Ladder to Swamp", "Ladders in Overworld Town", "Ladders in Well", + "Ladders near Overworld Checkpoint", "Ladders near Patrol Cave", "Ladder to Quarry", + "Ladders near Dark Tomb"}), + # Temple Rafters, excluded in non-ER + ladder rando due to soft lock potential + ("Overworld", "Overworld Redux, Temple_rafters", + {"Ladders near Weathervane", "Ladder to Swamp", "Ladders in Overworld Town", "Ladders in Well", + "Ladders near Overworld Checkpoint", "Ladders near Patrol Cave", "Ladder to Quarry", + "Ladders near Dark Tomb"}), + # Spot above the Quarry entrance, + # only gets you to the mountain stairs + ("Overworld above Quarry Entrance", "Overworld Redux, Mountain_", + {"Ladders near Dark Tomb"}), - # Furnace ladder to the fuse entrance - regions["Furnace Ladder Area"].connect( - regions[get_paired_region("Furnace, Overworld Redux_gyro_upper_north")], - rule=lambda state: has_stick(state, player)) - # Furnace ladder to Dark Tomb - regions["Furnace Ladder Area"].connect( - regions[get_paired_region("Furnace, Crypt Redux_")], - rule=lambda state: has_stick(state, player)) - # Furnace ladder to the West Garden connector - regions["Furnace Ladder Area"].connect( - regions[get_paired_region("Furnace, Overworld Redux_gyro_west")], - rule=lambda state: has_stick(state, player)) + # LS from the Overworld Beach + # West Garden entry by the Furnace + ("Overworld Beach", "Overworld Redux, Archipelagos Redux_lower", + {"Ladders in Overworld Town", "Ladder to Ruined Atoll"}), + # West Garden laurels entry + ("Overworld Beach", "Overworld Redux, Archipelagos Redux_lowest", + {"Ladders in Overworld Town", "Ladder to Ruined Atoll"}), + # Swamp lower entrance + ("Overworld Beach", "Overworld Redux, Swamp Redux 2_conduit", + {"Ladders in Overworld Town", "Ladder to Ruined Atoll"}), + # Rotating Lights entrance + ("Overworld Beach", "Overworld Redux, Overworld Cave_", + {"Ladders in Overworld Town", "Ladder to Ruined Atoll"}), + # Swamp upper entrance + ("Overworld Beach", "Overworld Redux, Swamp Redux 2_wall", + {"Ladder to Ruined Atoll"}), + # Furnace entrance, next to the sign that leads to West Garden + ("Overworld Beach", "Overworld Redux, Furnace_gyro_west", + {"Ladder to Ruined Atoll"}), + # Upper West Garden entry, by the belltower + ("Overworld Beach", "Overworld Redux, Archipelagos Redux_upper", + {"Ladder to Ruined Atoll"}), + # Ruined Passage + ("Overworld Beach", "Overworld Redux, Ruins Passage_east", + {"Ladder to Ruined Atoll"}), + # Well rail, west side. Can ls in town, get extra height by going over the portal pad + ("Overworld Beach", "Overworld Redux, Sewer_west_aqueduct", + {"Ladder to Ruined Atoll"}), + # Well rail, east side. Need some height from the temple stairs + ("Overworld Beach", "Overworld Redux, Furnace_gyro_upper_north", + {"Ladder to Ruined Atoll"}), + # Quarry entry + ("Overworld Beach", "Overworld Redux, Darkwoods Tunnel_", + {"Ladder to Ruined Atoll"}), - # West Garden exit after Garden Knight - regions["West Garden"].connect( - regions[get_paired_region("Archipelagos Redux, Overworld Redux_upper")], - rule=lambda state: has_stick(state, player)) - # West Garden laurels exit - regions["West Garden"].connect( - regions[get_paired_region("Archipelagos Redux, Overworld Redux_lowest")], - rule=lambda state: has_stick(state, player)) + # LS from that low spot where you normally walk to swamp + # Only has low ones you can't get to from main Overworld + # West Garden main entry from swamp ladder + ("Overworld Swamp Lower Entry", "Overworld Redux, Archipelagos Redux_lower", + {"Ladder to Swamp"}), + # Maze Cave entry from swamp ladder + ("Overworld Swamp Lower Entry", "Overworld Redux, Maze Room_", + {"Ladder to Swamp"}), + # Hourglass Cave entry from swamp ladder + ("Overworld Swamp Lower Entry", "Overworld Redux, Town Basement_beach", + {"Ladder to Swamp"}), + # Lower Atoll entry from swamp ladder + ("Overworld Swamp Lower Entry", "Overworld Redux, Atoll Redux_lower", + {"Ladder to Swamp"}), + # Lowest West Garden entry from swamp ladder + ("Overworld Swamp Lower Entry", "Overworld Redux, Archipelagos Redux_lowest", + {"Ladder to Swamp"}), - # Frog mouth entrance - regions["Ruined Atoll"].connect( - regions[get_paired_region("Atoll Redux, Frog Stairs_mouth")], - rule=lambda state: has_stick(state, player)) + # from the ladders by the belltower + # Ruined Passage + ("Overworld to West Garden Upper", "Overworld Redux, Ruins Passage_east", + {"Ladders to West Bell"}), + # Well rail, west side. Can ls in town, get extra height by going over the portal pad + ("Overworld to West Garden Upper", "Overworld Redux, Sewer_west_aqueduct", + {"Ladders to West Bell"}), + # Well rail, east side. Need some height from the temple stairs + ("Overworld to West Garden Upper", "Overworld Redux, Furnace_gyro_upper_north", + {"Ladders to West Bell"}), + # Quarry entry + ("Overworld to West Garden Upper", "Overworld Redux, Darkwoods Tunnel_", + {"Ladders to West Bell"}), + # East Forest entry + ("Overworld to West Garden Upper", "Overworld Redux, Forest Belltower_", + {"Ladders to West Bell"}), + # Fortress entry + ("Overworld to West Garden Upper", "Overworld Redux, Fortress Courtyard_", + {"Ladders to West Bell"}), + # Patrol Cave entry + ("Overworld to West Garden Upper", "Overworld Redux, PatrolCave_", + {"Ladders to West Bell"}), + # Special Shop entry, excluded in non-ER due to soft lock potential + ("Overworld to West Garden Upper", "Overworld Redux, ShopSpecial_", + {"Ladders to West Bell"}), + # Temple Rafters, excluded in non-ER and ladder rando due to soft lock potential + ("Overworld to West Garden Upper", "Overworld Redux, Temple_rafters", + {"Ladders to West Bell"}), - # Entrance by the dancing fox holy cross spot - regions["East Forest"].connect( - regions[get_paired_region("East Forest Redux, East Forest Redux Laddercave_upper")], - rule=lambda state: has_stick(state, player)) + # In the furnace + # Furnace ladder to the fuse entrance + ("Furnace Ladder Area", "Furnace, Overworld Redux_gyro_upper_north", set()), + # Furnace ladder to Dark Tomb + ("Furnace Ladder Area", "Furnace, Crypt Redux_", set()), + # Furnace ladder to the West Garden connector + ("Furnace Ladder Area", "Furnace, Overworld Redux_gyro_west", set()), - # From the west side of guard house 1 to the east side - regions["Guard House 1 West"].connect( - regions[get_paired_region("East Forest Redux Laddercave, East Forest Redux_gate")], - rule=lambda state: has_stick(state, player)) - regions["Guard House 1 West"].connect( - regions[get_paired_region("East Forest Redux Laddercave, Forest Boss Room_")], - rule=lambda state: has_stick(state, player)) + # West Garden + # exit after Garden Knight + ("West Garden", "Archipelagos Redux, Overworld Redux_upper", set()), + # West Garden laurels exit + ("West Garden", "Archipelagos Redux, Overworld Redux_lowest", set()), - # Upper exit from the Forest Grave Path, use ls at the ladder by the gate switch - regions["Forest Grave Path Main"].connect( - regions[get_paired_region("Sword Access, East Forest Redux_upper")], - rule=lambda state: has_stick(state, player)) + # Atoll, use the little ladder you fix at the beginning + ("Ruined Atoll", "Atoll Redux, Overworld Redux_lower", set()), + ("Ruined Atoll", "Atoll Redux, Frog Stairs_mouth", set()), + ("Ruined Atoll", "Atoll Redux, Frog Stairs_eye", set()), - # Fortress exterior shop, ls at the ladder by the telescope - regions["Fortress Exterior from Overworld"].connect( - regions[get_paired_region("Fortress Courtyard, Shop_")], - rule=lambda state: has_stick(state, player)) - # Fortress main entry and grave path lower entry, ls at the ladder by the telescope - regions["Fortress Exterior from Overworld"].connect( - regions[get_paired_region("Fortress Courtyard, Fortress Main_Big Door")], - rule=lambda state: has_stick(state, player)) - regions["Fortress Exterior from Overworld"].connect( - regions[get_paired_region("Fortress Courtyard, Fortress Reliquary_Lower")], - rule=lambda state: has_stick(state, player)) - # Upper exits from the courtyard. Use the ramp in the courtyard, then the blocks north of the first fuse - regions["Fortress Exterior from Overworld"].connect( - regions[get_paired_region("Fortress Courtyard, Fortress Reliquary_Upper")], - rule=lambda state: has_stick(state, player)) - regions["Fortress Exterior from Overworld"].connect( - regions[get_paired_region("Fortress Courtyard, Fortress East_")], - rule=lambda state: has_stick(state, player)) + # East Forest + # Entrance by the dancing fox holy cross spot + ("East Forest", "East Forest Redux, East Forest Redux Laddercave_upper", set()), - # same as above, except from the east side of the area - regions["Fortress Exterior from East Forest"].connect( - regions[get_paired_region("Fortress Courtyard, Overworld Redux_")], - rule=lambda state: has_stick(state, player)) - regions["Fortress Exterior from East Forest"].connect( - regions[get_paired_region("Fortress Courtyard, Shop_")], - rule=lambda state: has_stick(state, player)) - regions["Fortress Exterior from East Forest"].connect( - regions[get_paired_region("Fortress Courtyard, Fortress Main_Big Door")], - rule=lambda state: has_stick(state, player)) - regions["Fortress Exterior from East Forest"].connect( - regions[get_paired_region("Fortress Courtyard, Fortress Reliquary_Lower")], - rule=lambda state: has_stick(state, player)) - regions["Fortress Exterior from East Forest"].connect( - regions[get_paired_region("Fortress Courtyard, Fortress Reliquary_Upper")], - rule=lambda state: has_stick(state, player)) - regions["Fortress Exterior from East Forest"].connect( - regions[get_paired_region("Fortress Courtyard, Fortress East_")], - rule=lambda state: has_stick(state, player)) + # From the west side of Guard House 1 to the east side + ("Guard House 1 West", "East Forest Redux Laddercave, East Forest Redux_gate", set()), + ("Guard House 1 West", "East Forest Redux Laddercave, Forest Boss Room_", set()), - # same as above, except from the Beneath the Vault entrance ladder - regions["Fortress Exterior near cave"].connect( - regions[get_paired_region("Fortress Courtyard, Overworld Redux_")], - rule=lambda state: has_stick(state, player)) - regions["Fortress Exterior near cave"].connect( - regions[get_paired_region("Fortress Courtyard, Fortress Main_Big Door")], - rule=lambda state: has_stick(state, player)) - regions["Fortress Exterior near cave"].connect( - regions[get_paired_region("Fortress Courtyard, Fortress Reliquary_Lower")], - rule=lambda state: has_stick(state, player)) - regions["Fortress Exterior near cave"].connect( - regions[get_paired_region("Fortress Courtyard, Fortress Reliquary_Upper")], - rule=lambda state: has_stick(state, player)) - regions["Fortress Exterior near cave"].connect( - regions[get_paired_region("Fortress Courtyard, Fortress East_")], - rule=lambda state: has_stick(state, player)) + # Upper exit from the Forest Grave Path, use LS at the ladder by the gate switch + ("Forest Grave Path Main", "Sword Access, East Forest Redux_upper", set()), - # ls at the ladder, need to gain a little height to get up the stairs - regions["Lower Mountain"].connect( - regions[get_paired_region("Mountain, Mountaintop_")], - rule=lambda state: has_stick(state, player)) + # Fortress Exterior + # shop, ls at the ladder by the telescope + ("Fortress Exterior from Overworld", "Fortress Courtyard, Shop_", set()), + # Fortress main entry and grave path lower entry, ls at the ladder by the telescope + ("Fortress Exterior from Overworld", "Fortress Courtyard, Fortress Main_Big Door", set()), + ("Fortress Exterior from Overworld", "Fortress Courtyard, Fortress Reliquary_Lower", set()), + # Upper exits from the courtyard. Use the ramp in the courtyard, then the blocks north of the first fuse + ("Fortress Exterior from Overworld", "Fortress Courtyard, Fortress Reliquary_Upper", set()), + ("Fortress Exterior from Overworld", "Fortress Courtyard, Fortress East_", set()), - # Where the rope is behind Monastery. Connecting here since, if you have this region, you don't need a sword - regions["Quarry Monastery Entry"].connect( - regions[get_paired_region("Quarry Redux, Monastery_back")], - rule=lambda state: has_stick(state, player)) + # same as above, except from the east side of the area + ("Fortress Exterior from East Forest", "Fortress Courtyard, Overworld Redux_", set()), + ("Fortress Exterior from East Forest", "Fortress Courtyard, Shop_", set()), + ("Fortress Exterior from East Forest", "Fortress Courtyard, Fortress Main_Big Door", set()), + ("Fortress Exterior from East Forest", "Fortress Courtyard, Fortress Reliquary_Lower", set()), + ("Fortress Exterior from East Forest", "Fortress Courtyard, Fortress Reliquary_Upper", set()), + ("Fortress Exterior from East Forest", "Fortress Courtyard, Fortress East_", set()), - # Swamp to Gauntlet - regions["Swamp"].connect( - regions[get_paired_region("Swamp Redux 2, Cathedral Arena_")], - rule=lambda state: has_stick(state, player)) - # Swamp to Overworld upper - regions["Swamp"].connect( - regions[get_paired_region("Swamp Redux 2, Overworld Redux_wall")], - rule=lambda state: has_stick(state, player)) - # Ladder by the hero grave - regions["Back of Swamp"].connect( - regions[get_paired_region("Swamp Redux 2, Overworld Redux_conduit")], - rule=lambda state: has_stick(state, player)) - regions["Back of Swamp"].connect( - regions[get_paired_region("Swamp Redux 2, Shop_")], - rule=lambda state: has_stick(state, player)) - # Need to put the cathedral HC code mid-flight - regions["Back of Swamp"].connect( - regions[get_paired_region("Swamp Redux 2, Cathedral Redux_secret")], - rule=lambda state: has_stick(state, player) - and has_ability(state, player, holy_cross, options, ability_unlocks)) + # same as above, except from the Beneath the Vault entrance ladder + ("Fortress Exterior near cave", "Fortress Courtyard, Overworld Redux_", + {"Ladder to Beneath the Vault"}), + ("Fortress Exterior near cave", "Fortress Courtyard, Fortress Main_Big Door", + {"Ladder to Beneath the Vault"}), + ("Fortress Exterior near cave", "Fortress Courtyard, Fortress Reliquary_Lower", + {"Ladder to Beneath the Vault"}), + ("Fortress Exterior near cave", "Fortress Courtyard, Fortress Reliquary_Upper", + {"Ladder to Beneath the Vault"}), + ("Fortress Exterior near cave", "Fortress Courtyard, Fortress East_", + {"Ladder to Beneath the Vault"}), + + # ls at the ladder, need to gain a little height to get up the stairs + # excluded in non-ER due to soft lock potential + ("Lower Mountain", "Mountain, Mountaintop_", set()), + + # Where the rope is behind Monastery. Connecting here since, if you have this region, you don't need a sword + ("Quarry Monastery Entry", "Quarry Redux, Monastery_back", set()), + + # Swamp to Gauntlet + ("Swamp Mid", "Swamp Redux 2, Cathedral Arena_", + {"Ladders in Swamp"}), + # Swamp to Overworld upper + ("Swamp Mid", "Swamp Redux 2, Overworld Redux_wall", + {"Ladders in Swamp"}), + # Ladder by the hero grave + ("Back of Swamp", "Swamp Redux 2, Overworld Redux_conduit", set()), + ("Back of Swamp", "Swamp Redux 2, Shop_", set()), + # Need to put the cathedral HC code mid-flight + ("Back of Swamp", "Swamp Redux 2, Cathedral Redux_secret", set()), + ] + + for region_name, scene_dest, ladders in ladder_storages: + portal_name, paired_region = get_portal_info(scene_dest) + # this is the only exception, requiring holy cross as well + if portal_name == "Swamp to Cathedral Secret Legend Room Entrance" and region_name == "Back of Swamp": + regions[region_name].connect( + regions[paired_region], + name=portal_name + " (LS) " + region_name, + rule=lambda state: has_stick(state, player) + and has_ability(state, player, holy_cross, options, ability_unlocks) + and (has_ladder("Ladders in Swamp", state, player, options) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks) + or not options.entrance_rando)) + elif portal_name == "West Garden Exit after Boss" and not options.entrance_rando: + regions[region_name].connect( + regions[paired_region], + name=portal_name + " (LS) " + region_name, + rule=lambda state: has_stick(state, player) + and state.has_any(ladders, player) + and (state.has("Ladders to West Bell", player))) + # soft locked unless you have either ladder. if you have laurels, you use the other Entrance + elif portal_name in {"Furnace Exit towards West Garden", "Furnace Exit to Dark Tomb"} \ + and not options.entrance_rando: + regions[region_name].connect( + regions[paired_region], + name=portal_name + " (LS) " + region_name, + rule=lambda state: has_stick(state, player) + and state.has_any({"Ladder in Dark Tomb", "Ladders to West Bell"}, player)) + # soft locked for the same reasons as above + elif portal_name in {"Entrance to Furnace near West Garden", "West Garden Entrance from Furnace"} \ + and not options.entrance_rando: + regions[region_name].connect( + regions[paired_region], + name=portal_name + " (LS) " + region_name, + rule=lambda state: has_stick(state, player) + and state.has_any(ladders, player) + and state.has_any({"Ladder in Dark Tomb", "Ladders to West Bell"}, player)) + # soft locked if you can't get past garden knight backwards or up the belltower ladders + elif portal_name == "West Garden Entrance near Belltower" and not options.entrance_rando: + regions[region_name].connect( + regions[paired_region], + name=portal_name + " (LS) " + region_name, + rule=lambda state: has_stick(state, player) and state.has_any(ladders, player) + and state.has_any({"Ladders to West Bell", laurels}, player)) + # soft locked if you can't get back out + elif portal_name == "Fortress Courtyard to Beneath the Vault" and not options.entrance_rando: + regions[region_name].connect( + regions[paired_region], + name=portal_name + " (LS) " + region_name, + rule=lambda state: has_stick(state, player) + and state.has("Ladder to Beneath the Vault", player) + and has_lantern(state, player, options)) + elif portal_name == "Atoll Lower Entrance" and not options.entrance_rando: + regions[region_name].connect( + regions[paired_region], + name=portal_name + " (LS) " + region_name, + rule=lambda state: has_stick(state, player) + and state.has_any(ladders, player) + and (state.has_any({"Ladders in Overworld Town", grapple}, player) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks))) + elif portal_name == "Atoll Upper Entrance" and not options.entrance_rando: + regions[region_name].connect( + regions[paired_region], + name=portal_name + " (LS) " + region_name, + rule=lambda state: has_stick(state, player) + and state.has_any(ladders, player) + and state.has(grapple, player) or has_ability(state, player, prayer, options, ability_unlocks)) + # soft lock potential + elif portal_name in {"Special Shop Entrance", "Stairs to Top of the Mountain", "Swamp Upper Entrance", + "Swamp Lower Entrance", "Caustic Light Cave Entrance"} and not options.entrance_rando: + continue + # soft lock if you don't have the ladder, I regret writing unrestricted logic + elif portal_name == "Temple Rafters Entrance" and not options.entrance_rando: + regions[region_name].connect( + regions[paired_region], + name=portal_name + " (LS) " + region_name, + rule=lambda state: has_stick(state, player) + and state.has_any(ladders, player) + and (state.has("Ladder near Temple Rafters", player) + or (state.has_all({laurels, grapple}, player) + and ((state.has("Ladders near Patrol Cave", player) + and (state.has("Ladders near Dark Tomb", player) + or state.has("Ladder to Quarry", player) + and (state.has(fire_wand, player) or has_sword(state, player)))) + or state.has("Ladders near Overworld Checkpoint", player) + or has_ice_grapple_logic(True, state, player, options, ability_unlocks))))) + # if no ladder items are required, just do the basic stick only lambda + elif not ladders or not options.shuffle_ladders: + regions[region_name].connect( + regions[paired_region], + name=portal_name + " (LS) " + region_name, + rule=lambda state: has_stick(state, player)) + # one ladder required + elif len(ladders) == 1: + ladder = ladders.pop() + regions[region_name].connect( + regions[paired_region], + name=portal_name + " (LS) " + region_name, + rule=lambda state: has_stick(state, player) + and state.has(ladder, player)) + # if multiple ladders can be used + else: + regions[region_name].connect( + regions[paired_region], + name=portal_name + " (LS) " + region_name, + rule=lambda state: has_stick(state, player) + and state.has_any(ladders, player)) def set_er_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) -> None: @@ -825,6 +1349,16 @@ def set_er_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) lambda state: has_ability(state, player, holy_cross, options, ability_unlocks)) set_rule(multiworld.get_location("Cathedral - Secret Legend Trophy Chest", player), lambda state: has_ability(state, player, holy_cross, options, ability_unlocks)) + set_rule(multiworld.get_location("Overworld - [Southwest] Flowers Holy Cross", player), + lambda state: has_ability(state, player, holy_cross, options, ability_unlocks)) + set_rule(multiworld.get_location("Overworld - [East] Weathervane Holy Cross", player), + lambda state: has_ability(state, player, holy_cross, options, ability_unlocks)) + set_rule(multiworld.get_location("Overworld - [Northeast] Flowers Holy Cross", player), + lambda state: has_ability(state, player, holy_cross, options, ability_unlocks)) + set_rule(multiworld.get_location("Overworld - [Southwest] Haiku Holy Cross", player), + lambda state: has_ability(state, player, holy_cross, options, ability_unlocks)) + set_rule(multiworld.get_location("Overworld - [Northwest] Golden Obelisk Page", player), + lambda state: has_ability(state, player, holy_cross, options, ability_unlocks)) # Overworld set_rule(multiworld.get_location("Overworld - [Southwest] Grapple Chest Over Walkway", player), @@ -939,7 +1473,8 @@ def set_er_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) lambda state: has_sword(state, player)) # nmg - kill Librarian with a lure, or gun I guess set_rule(multiworld.get_location("Librarian - Hexagon Green", player), - lambda state: has_sword(state, player) or options.logic_rules) + lambda state: (has_sword(state, player) or options.logic_rules) + and has_ladder("Ladders in Library", state, player, options)) # nmg - kill boss scav with orb + firecracker, or similar set_rule(multiworld.get_location("Rooted Ziggurat Lower - Hexagon Blue", player), lambda state: has_sword(state, player) or (state.has(grapple, player) and options.logic_rules)) @@ -954,8 +1489,6 @@ def set_er_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) # these two swamp checks really want you to kill the big skeleton first set_rule(multiworld.get_location("Swamp - [South Graveyard] 4 Orange Skulls", player), lambda state: has_sword(state, player)) - set_rule(multiworld.get_location("Swamp - [South Graveyard] Guarded By Tentacles", player), - lambda state: has_sword(state, player)) # Hero's Grave and Far Shore set_rule(multiworld.get_location("Hero's Grave - Tooth Relic", player), diff --git a/worlds/tunic/er_scripts.py b/worlds/tunic/er_scripts.py index 291cd7b331..5756ec90be 100644 --- a/worlds/tunic/er_scripts.py +++ b/worlds/tunic/er_scripts.py @@ -1,7 +1,7 @@ from typing import Dict, List, Set, Tuple, TYPE_CHECKING from BaseClasses import Region, ItemClassification, Item, Location from .locations import location_table -from .er_data import Portal, tunic_er_regions, portal_mapping, hallway_helper, hallway_helper_ur, \ +from .er_data import Portal, tunic_er_regions, portal_mapping, \ dependent_regions_restricted, dependent_regions_nmg, dependent_regions_ur from .er_rules import set_er_region_rules from worlds.generic import PlandoConnection @@ -19,118 +19,26 @@ class TunicERLocation(Location): game: str = "TUNIC" -def create_er_regions(world: "TunicWorld") -> Tuple[Dict[Portal, Portal], Dict[int, str]]: +def create_er_regions(world: "TunicWorld") -> Dict[Portal, Portal]: regions: Dict[str, Region] = {} - portal_pairs: Dict[Portal, Portal] = pair_portals(world) - logic_rules = world.options.logic_rules + if world.options.entrance_rando: + portal_pairs: Dict[Portal, Portal] = pair_portals(world) - # output the entrances to the spoiler log here for convenience - for portal1, portal2 in portal_pairs.items(): - world.multiworld.spoiler.set_entrance(portal1.name, portal2.name, "both", world.player) + # output the entrances to the spoiler log here for convenience + for portal1, portal2 in portal_pairs.items(): + world.multiworld.spoiler.set_entrance(portal1.name, portal2.name, "both", world.player) + else: + portal_pairs: Dict[Portal, Portal] = vanilla_portals() - # check if a portal leads to a hallway. if it does, update the hint text accordingly - def hint_helper(portal: Portal, hint_string: str = "") -> str: - # start by setting it as the name of the portal, for the case we're not using the hallway helper - if hint_string == "": - hint_string = portal.name - - # unrestricted has fewer hallways, like the well rail - if logic_rules == "unrestricted": - hallways = hallway_helper_ur - else: - hallways = hallway_helper - - if portal.scene_destination() in hallways: - # if we have a hallway, we want the region rather than the portal name - if hint_string == portal.name: - hint_string = portal.region - # library exterior is two regions, we just want to fix up the name - if hint_string in {"Library Exterior Tree", "Library Exterior Ladder"}: - hint_string = "Library Exterior" - - # search through the list for the other end of the hallway - for portala, portalb in portal_pairs.items(): - if portala.scene_destination() == hallways[portal.scene_destination()]: - # if we find that we have a chain of hallways, do recursion - if portalb.scene_destination() in hallways: - hint_region = portalb.region - if hint_region in {"Library Exterior Tree", "Library Exterior Ladder"}: - hint_region = "Library Exterior" - hint_string = hint_region + " then " + hint_string - hint_string = hint_helper(portalb, hint_string) - else: - # if we didn't find a chain, get the portal name for the end of the chain - hint_string = portalb.name + " then " + hint_string - return hint_string - # and then the same thing for the other portal, since we have to check each separately - if portalb.scene_destination() == hallways[portal.scene_destination()]: - if portala.scene_destination() in hallways: - hint_region = portala.region - if hint_region in {"Library Exterior Tree", "Library Exterior Ladder"}: - hint_region = "Library Exterior" - hint_string = hint_region + " then " + hint_string - hint_string = hint_helper(portala, hint_string) - else: - hint_string = portala.name + " then " + hint_string - return hint_string - return hint_string - - # create our regions, give them hint text if they're in a spot where it makes sense to - # we're limiting which ones get hints so that it still gets that ER feel with a little less BS for region_name, region_data in tunic_er_regions.items(): - hint_text = "error" - if region_data.hint == 1: - for portal1, portal2 in portal_pairs.items(): - if portal1.region == region_name: - hint_text = hint_helper(portal2) - break - if portal2.region == region_name: - hint_text = hint_helper(portal1) - break - regions[region_name] = Region(region_name, world.player, world.multiworld, hint_text) - elif region_data.hint == 2: - for portal1, portal2 in portal_pairs.items(): - if portal1.scene() == tunic_er_regions[region_name].game_scene: - hint_text = hint_helper(portal2) - break - if portal2.scene() == tunic_er_regions[region_name].game_scene: - hint_text = hint_helper(portal1) - break - regions[region_name] = Region(region_name, world.player, world.multiworld, hint_text) - elif region_data.hint == 3: - # west garden portal item is at a dead end in restricted, otherwise just in west garden - if region_name == "West Garden Portal Item": - if world.options.logic_rules: - for portal1, portal2 in portal_pairs.items(): - if portal1.scene() == "Archipelagos Redux": - hint_text = hint_helper(portal2) - break - if portal2.scene() == "Archipelagos Redux": - hint_text = hint_helper(portal1) - break - regions[region_name] = Region(region_name, world.player, world.multiworld, hint_text) - else: - for portal1, portal2 in portal_pairs.items(): - if portal1.region == "West Garden Portal": - hint_text = hint_helper(portal2) - break - if portal2.region == "West Garden Portal": - hint_text = hint_helper(portal1) - break - regions[region_name] = Region(region_name, world.player, world.multiworld, hint_text) - else: - regions[region_name] = Region(region_name, world.player, world.multiworld) + regions[region_name] = Region(region_name, world.player, world.multiworld) set_er_region_rules(world, world.ability_unlocks, regions, portal_pairs) - er_hint_data: Dict[int, str] = {} for location_name, location_id in world.location_name_to_id.items(): region = regions[location_table[location_name].er_region] location = TunicERLocation(world.player, location_name, location_id, region) region.locations.append(location) - if region.name == region.hint_text: - continue - er_hint_data[location.address] = region.hint_text create_randomized_entrances(portal_pairs, regions) @@ -145,14 +53,12 @@ def create_er_regions(world: "TunicWorld") -> Tuple[Dict[Portal, Portal], Dict[i world.multiworld.completion_condition[world.player] = lambda state: state.has("Victory", world.player) victory_region.locations.append(victory_location) - portals_and_hints = (portal_pairs, er_hint_data) - - return portals_and_hints + return portal_pairs tunic_events: Dict[str, str] = { "Eastern Bell": "Forest Belltower Upper", - "Western Bell": "Overworld Belltower", + "Western Bell": "Overworld Belltower at Bell", "Furnace Fuse": "Furnace Fuse", "South and West Fortress Exterior Fuses": "Fortress Exterior from Overworld", "Upper and Central Fortress Exterior Fuses": "Fortress Courtyard Upper", @@ -163,7 +69,7 @@ tunic_events: Dict[str, str] = { "Quarry Fuse": "Quarry", "Ziggurat Fuse": "Rooted Ziggurat Lower Back", "West Garden Fuse": "West Garden", - "Library Fuse": "Library Lab", + "Library Fuse": "Library Lab" } @@ -180,6 +86,38 @@ def place_event_items(world: "TunicWorld", regions: Dict[str, Region]) -> None: region.locations.append(location) +def vanilla_portals() -> Dict[Portal, Portal]: + portal_pairs: Dict[Portal, Portal] = {} + portal_map = portal_mapping.copy() + shop_num = 1 + + while portal_map: + portal1 = portal_map[0] + portal2 = None + # portal2 scene destination tag is portal1's destination scene tag + portal2_sdt = portal1.destination_scene() + + if portal2_sdt.startswith("Shop,"): + portal2 = Portal(name=f"Shop", region="Shop", + destination="Previous Region", tag="_") + shop_num += 1 + + if portal2_sdt == "Purgatory, Purgatory_bottom": + portal2_sdt = "Purgatory, Purgatory_top" + + for portal in portal_map: + if portal.scene_destination() == portal2_sdt: + portal2 = portal + break + + portal_pairs[portal1] = portal2 + portal_map.remove(portal1) + if not portal2_sdt.startswith("Shop,"): + portal_map.remove(portal2) + + return portal_pairs + + # pairing off portals, starting with dead ends def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: # separate the portals into dead ends and non-dead ends @@ -290,7 +228,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: break if p_exit in ["Shop Portal", "Shop"]: portal2 = Portal(name="Shop Portal", region=f"Shop", - destination="Previous Region_") + destination="Previous Region", tag="_") shop_count -= 1 if shop_count < 0: shop_count += 2 @@ -355,10 +293,12 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: if portal.scene_destination() == "Overworld Redux, Windmill_": portal1 = portal break - portal2 = Portal(name="Shop Portal", region="Shop", destination="Previous Region_") if not portal1: raise Exception(f"Failed to do Fixed Shop option. " f"Did {player_name} plando connection the Windmill Shop entrance?") + + portal2 = Portal(name="Shop Portal", region="Shop", destination="Previous Region", tag="_") + portal_pairs[portal1] = portal2 two_plus.remove(portal1) @@ -433,7 +373,8 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: break if portal1 is None: raise Exception("Too many shops in the pool, or something else went wrong.") - portal2 = Portal(name="Shop Portal", region="Shop", destination="Previous Region_") + portal2 = Portal(name="Shop Portal", region="Shop", destination="Previous Region", tag="_") + portal_pairs[portal1] = portal2 # connect dead ends to random non-dead ends @@ -465,10 +406,10 @@ def create_randomized_entrances(portal_pairs: Dict[Portal, Portal], regions: Dic for portal1, portal2 in portal_pairs.items(): region1 = regions[portal1.region] region2 = regions[portal2.region] - region1.connect(region2, f"{portal1.name} -> {portal2.name}") + region1.connect(connecting_region=region2, name=portal1.name) # prevent the logic from thinking you can get to any shop-connected region from the shop - if not portal2.name.startswith("Shop"): - region2.connect(region1, f"{portal2.name} -> {portal1.name}") + if portal2.name not in {"Shop", "Shop Portal"}: + region2.connect(connecting_region=region1, name=portal2.name) # loop through the static connections, return regions you can reach from this region @@ -519,8 +460,8 @@ def gate_before_switch(check_portal: Portal, two_plus: List[Portal]) -> bool: return True # fortress teleporter needs only the left fuses - elif check_portal.scene_destination() in ["Fortress Arena, Transit_teleporter_spidertank", - "Transit, Fortress Arena_teleporter_spidertank"]: + elif check_portal.scene_destination() in {"Fortress Arena, Transit_teleporter_spidertank", + "Transit, Fortress Arena_teleporter_spidertank"}: i = j = k = 0 for portal in two_plus: if portal.scene() == "Fortress Courtyard": @@ -537,7 +478,8 @@ def gate_before_switch(check_portal: Portal, two_plus: List[Portal]) -> bool: elif check_portal.scene_destination() == "Swamp Redux 2, Cathedral Redux_main": i = 0 for portal in two_plus: - if portal.region == "Swamp": + if portal.region in {"Swamp Front", "Swamp to Cathedral Treasure Room", + "Swamp to Cathedral Main Entrance Region"}: i += 1 if i == 4: return True @@ -553,8 +495,8 @@ def gate_before_switch(check_portal: Portal, two_plus: List[Portal]) -> bool: # Quarry teleporter needs you to hit the Darkwoods fuse # Since it's physically in Quarry, we don't need to check for it - elif check_portal.scene_destination() in ["Quarry Redux, Transit_teleporter_quarry teleporter", - "Quarry Redux, ziggurat2020_0_"]: + elif check_portal.scene_destination() in {"Quarry Redux, Transit_teleporter_quarry teleporter", + "Quarry Redux, ziggurat2020_0_"}: i = 0 for portal in two_plus: if portal.scene() == "Darkwoods Tunnel": diff --git a/worlds/tunic/items.py b/worlds/tunic/items.py index 547a0ffb81..7483d55bf1 100644 --- a/worlds/tunic/items.py +++ b/worlds/tunic/items.py @@ -143,6 +143,28 @@ item_table: Dict[str, TunicItemData] = { "Pages 50-51": TunicItemData(ItemClassification.useful, 1, 127, "pages"), "Pages 52-53 (Icebolt)": TunicItemData(ItemClassification.progression, 1, 128, "pages"), "Pages 54-55": TunicItemData(ItemClassification.useful, 1, 129, "pages"), + + "Ladders near Weathervane": TunicItemData(ItemClassification.progression, 0, 130, "ladders"), + "Ladders near Overworld Checkpoint": TunicItemData(ItemClassification.progression, 0, 131, "ladders"), + "Ladders near Patrol Cave": TunicItemData(ItemClassification.progression, 0, 132, "ladders"), + "Ladder near Temple Rafters": TunicItemData(ItemClassification.progression, 0, 133, "ladders"), + "Ladders near Dark Tomb": TunicItemData(ItemClassification.progression, 0, 134, "ladders"), + "Ladder to Quarry": TunicItemData(ItemClassification.progression, 0, 135, "ladders"), + "Ladders to West Bell": TunicItemData(ItemClassification.progression, 0, 136, "ladders"), + "Ladders in Overworld Town": TunicItemData(ItemClassification.progression, 0, 137, "ladders"), + "Ladder to Ruined Atoll": TunicItemData(ItemClassification.progression, 0, 138, "ladders"), + "Ladder to Swamp": TunicItemData(ItemClassification.progression, 0, 139, "ladders"), + "Ladders in Well": TunicItemData(ItemClassification.progression, 0, 140, "ladders"), + "Ladder in Dark Tomb": TunicItemData(ItemClassification.progression, 0, 141, "ladders"), + "Ladder to East Forest": TunicItemData(ItemClassification.progression, 0, 142, "ladders"), + "Ladders to Lower Forest": TunicItemData(ItemClassification.progression, 0, 143, "ladders"), + "Ladder to Beneath the Vault": TunicItemData(ItemClassification.progression, 0, 144, "ladders"), + "Ladders in Hourglass Cave": TunicItemData(ItemClassification.progression, 0, 145, "ladders"), + "Ladders in South Atoll": TunicItemData(ItemClassification.progression, 0, 146, "ladders"), + "Ladders to Frog's Domain": TunicItemData(ItemClassification.progression, 0, 147, "ladders"), + "Ladders in Library": TunicItemData(ItemClassification.progression, 0, 148, "ladders"), + "Ladders in Lower Quarry": TunicItemData(ItemClassification.progression, 0, 149, "ladders"), + "Ladders in Swamp": TunicItemData(ItemClassification.progression, 0, 150, "ladders"), } fool_tiers: List[List[str]] = [ @@ -209,7 +231,9 @@ extra_groups: Dict[str, Set[str]] = { "melee weapons": {"Stick", "Sword", "Sword Upgrade"}, "progressive sword": {"Sword Upgrade"}, "abilities": {"Pages 24-25 (Prayer)", "Pages 42-43 (Holy Cross)", "Pages 52-53 (Icebolt)"}, - "questagons": {"Red Questagon", "Green Questagon", "Blue Questagon", "Gold Questagon"} + "questagons": {"Red Questagon", "Green Questagon", "Blue Questagon", "Gold Questagon"}, + "ladder to atoll": {"Ladder to Ruined Atoll"}, # fuzzy matching made it hint Ladders in Well, now it won't + "ladders to bell": {"Ladders to West Bell"}, } item_name_groups.update(extra_groups) diff --git a/worlds/tunic/locations.py b/worlds/tunic/locations.py index 1501fb7da2..4d95e91cb3 100644 --- a/worlds/tunic/locations.py +++ b/worlds/tunic/locations.py @@ -1,11 +1,11 @@ -from typing import Dict, NamedTuple, Set -from itertools import groupby +from typing import Dict, NamedTuple, Set, Optional, List class TunicLocationData(NamedTuple): region: str er_region: str # entrance rando region - location_group: str = "region" + location_group: Optional[str] = None + location_groups: Optional[List[str]] = None location_base_id = 509342400 @@ -22,10 +22,10 @@ location_table: Dict[str, TunicLocationData] = { "Beneath the Well - [Back Corridor] Right Secret": TunicLocationData("Beneath the Well", "Beneath the Well Main"), "Beneath the Well - [Back Corridor] Left Secret": TunicLocationData("Beneath the Well", "Beneath the Well Main"), "Beneath the Well - [Second Room] Obscured Behind Waterfall": TunicLocationData("Beneath the Well", "Beneath the Well Main"), - "Beneath the Well - [Side Room] Chest By Pots": TunicLocationData("Beneath the Well", "Beneath the Well Main"), + "Beneath the Well - [Side Room] Chest By Pots": TunicLocationData("Beneath the Well", "Beneath the Well Back"), "Beneath the Well - [Side Room] Chest By Phrends": TunicLocationData("Beneath the Well", "Beneath the Well Back"), "Beneath the Well - [Second Room] Page": TunicLocationData("Beneath the Well", "Beneath the Well Main"), - "Dark Tomb Checkpoint - [Passage To Dark Tomb] Page Pickup": TunicLocationData("Beneath the Well", "Dark Tomb Checkpoint"), + "Dark Tomb Checkpoint - [Passage To Dark Tomb] Page Pickup": TunicLocationData("Overworld", "Dark Tomb Checkpoint"), "Cathedral - [1F] Guarded By Lasers": TunicLocationData("Cathedral", "Cathedral"), "Cathedral - [1F] Near Spikes": TunicLocationData("Cathedral", "Cathedral"), "Cathedral - [2F] Bird Room": TunicLocationData("Cathedral", "Cathedral"), @@ -39,25 +39,25 @@ location_table: Dict[str, TunicLocationData] = { "Dark Tomb - 2nd Laser Room": TunicLocationData("Dark Tomb", "Dark Tomb Main"), "Dark Tomb - 1st Laser Room": TunicLocationData("Dark Tomb", "Dark Tomb Main"), "Dark Tomb - Spike Maze Upper Walkway": TunicLocationData("Dark Tomb", "Dark Tomb Main"), - "Dark Tomb - Skulls Chest": TunicLocationData("Dark Tomb", "Dark Tomb Main"), + "Dark Tomb - Skulls Chest": TunicLocationData("Dark Tomb", "Dark Tomb Upper"), "Dark Tomb - Spike Maze Near Stairs": TunicLocationData("Dark Tomb", "Dark Tomb Main"), "Dark Tomb - 1st Laser Room Obscured": TunicLocationData("Dark Tomb", "Dark Tomb Main"), - "Guardhouse 2 - Upper Floor": TunicLocationData("East Forest", "Guard House 2"), - "Guardhouse 2 - Bottom Floor Secret": TunicLocationData("East Forest", "Guard House 2"), + "Guardhouse 2 - Upper Floor": TunicLocationData("East Forest", "Guard House 2 Upper"), + "Guardhouse 2 - Bottom Floor Secret": TunicLocationData("East Forest", "Guard House 2 Lower"), "Guardhouse 1 - Upper Floor Obscured": TunicLocationData("East Forest", "Guard House 1 East"), "Guardhouse 1 - Upper Floor": TunicLocationData("East Forest", "Guard House 1 East"), - "East Forest - Dancing Fox Spirit Holy Cross": TunicLocationData("East Forest", "East Forest Dance Fox Spot", "holy cross"), - "East Forest - Golden Obelisk Holy Cross": TunicLocationData("East Forest", "East Forest", "holy cross"), + "East Forest - Dancing Fox Spirit Holy Cross": TunicLocationData("East Forest", "East Forest Dance Fox Spot", location_group="holy cross"), + "East Forest - Golden Obelisk Holy Cross": TunicLocationData("East Forest", "Lower Forest", location_group="holy cross"), "East Forest - Ice Rod Grapple Chest": TunicLocationData("East Forest", "East Forest"), "East Forest - Above Save Point": TunicLocationData("East Forest", "East Forest"), "East Forest - Above Save Point Obscured": TunicLocationData("East Forest", "East Forest"), "East Forest - From Guardhouse 1 Chest": TunicLocationData("East Forest", "East Forest Dance Fox Spot"), "East Forest - Near Save Point": TunicLocationData("East Forest", "East Forest"), - "East Forest - Beneath Spider Chest": TunicLocationData("East Forest", "East Forest"), + "East Forest - Beneath Spider Chest": TunicLocationData("East Forest", "Lower Forest"), "East Forest - Near Telescope": TunicLocationData("East Forest", "East Forest"), - "East Forest - Spider Chest": TunicLocationData("East Forest", "East Forest"), - "East Forest - Lower Dash Chest": TunicLocationData("East Forest", "East Forest"), - "East Forest - Lower Grapple Chest": TunicLocationData("East Forest", "East Forest"), + "East Forest - Spider Chest": TunicLocationData("East Forest", "Lower Forest"), + "East Forest - Lower Dash Chest": TunicLocationData("East Forest", "Lower Forest"), + "East Forest - Lower Grapple Chest": TunicLocationData("East Forest", "Lower Forest"), "East Forest - Bombable Wall": TunicLocationData("East Forest", "East Forest"), "East Forest - Page On Teleporter": TunicLocationData("East Forest", "East Forest"), "Forest Belltower - Near Save Point": TunicLocationData("East Forest", "Forest Belltower Lower"), @@ -65,18 +65,18 @@ location_table: Dict[str, TunicLocationData] = { "Forest Belltower - Obscured Near Bell Top Floor": TunicLocationData("East Forest", "Forest Belltower Upper"), "Forest Belltower - Obscured Beneath Bell Bottom Floor": TunicLocationData("East Forest", "Forest Belltower Main"), "Forest Belltower - Page Pickup": TunicLocationData("East Forest", "Forest Belltower Main"), - "Forest Grave Path - Holy Cross Code by Grave": TunicLocationData("East Forest", "Forest Grave Path by Grave", "holy cross"), + "Forest Grave Path - Holy Cross Code by Grave": TunicLocationData("East Forest", "Forest Grave Path by Grave", location_group="holy cross"), "Forest Grave Path - Above Gate": TunicLocationData("East Forest", "Forest Grave Path Main"), "Forest Grave Path - Obscured Chest": TunicLocationData("East Forest", "Forest Grave Path Main"), "Forest Grave Path - Upper Walkway": TunicLocationData("East Forest", "Forest Grave Path Upper"), "Forest Grave Path - Sword Pickup": TunicLocationData("East Forest", "Forest Grave Path by Grave"), - "Hero's Grave - Tooth Relic": TunicLocationData("East Forest", "Hero Relic - East Forest"), + "Hero's Grave - Tooth Relic": TunicLocationData("East Forest", "Hero Relic - East Forest", location_group="hero relic"), "Fortress Courtyard - From East Belltower": TunicLocationData("East Forest", "Fortress Exterior from East Forest"), "Fortress Leaf Piles - Secret Chest": TunicLocationData("Eastern Vault Fortress", "Fortress Leaf Piles"), "Fortress Arena - Hexagon Red": TunicLocationData("Eastern Vault Fortress", "Fortress Arena"), - "Fortress Arena - Siege Engine/Vault Key Pickup": TunicLocationData("Eastern Vault Fortress", "Fortress Arena"), + "Fortress Arena - Siege Engine/Vault Key Pickup": TunicLocationData("Eastern Vault Fortress", "Fortress Arena", location_group="bosses"), "Fortress East Shortcut - Chest Near Slimes": TunicLocationData("Eastern Vault Fortress", "Fortress East Shortcut Lower"), - "Eastern Vault Fortress - [West Wing] Candles Holy Cross": TunicLocationData("Eastern Vault Fortress", "Eastern Vault Fortress", "holy cross"), + "Eastern Vault Fortress - [West Wing] Candles Holy Cross": TunicLocationData("Eastern Vault Fortress", "Eastern Vault Fortress", location_group="holy cross"), "Eastern Vault Fortress - [West Wing] Dark Room Chest 1": TunicLocationData("Eastern Vault Fortress", "Eastern Vault Fortress"), "Eastern Vault Fortress - [West Wing] Dark Room Chest 2": TunicLocationData("Eastern Vault Fortress", "Eastern Vault Fortress"), "Eastern Vault Fortress - [East Wing] Bombable Wall": TunicLocationData("Eastern Vault Fortress", "Eastern Vault Fortress"), @@ -84,7 +84,7 @@ location_table: Dict[str, TunicLocationData] = { "Fortress Grave Path - Upper Walkway": TunicLocationData("Eastern Vault Fortress", "Fortress Grave Path Upper"), "Fortress Grave Path - Chest Right of Grave": TunicLocationData("Eastern Vault Fortress", "Fortress Grave Path"), "Fortress Grave Path - Obscured Chest Left of Grave": TunicLocationData("Eastern Vault Fortress", "Fortress Grave Path"), - "Hero's Grave - Flowers Relic": TunicLocationData("Eastern Vault Fortress", "Hero Relic - Fortress"), + "Hero's Grave - Flowers Relic": TunicLocationData("Eastern Vault Fortress", "Hero Relic - Fortress", location_group="hero relic"), "Beneath the Fortress - Bridge": TunicLocationData("Beneath the Vault", "Beneath the Vault Back"), "Beneath the Fortress - Cell Chest 1": TunicLocationData("Beneath the Vault", "Beneath the Vault Back"), "Beneath the Fortress - Obscured Behind Waterfall": TunicLocationData("Beneath the Vault", "Beneath the Vault Front"), @@ -101,8 +101,8 @@ location_table: Dict[str, TunicLocationData] = { "Frog's Domain - Side Room Chest": TunicLocationData("Frog's Domain", "Frog's Domain"), "Frog's Domain - Side Room Grapple Secret": TunicLocationData("Frog's Domain", "Frog's Domain"), "Frog's Domain - Magic Orb Pickup": TunicLocationData("Frog's Domain", "Frog's Domain"), - "Librarian - Hexagon Green": TunicLocationData("Library", "Library Arena"), - "Library Hall - Holy Cross Chest": TunicLocationData("Library", "Library Hall", "holy cross"), + "Librarian - Hexagon Green": TunicLocationData("Library", "Library Arena", location_group="bosses"), + "Library Hall - Holy Cross Chest": TunicLocationData("Library", "Library Hall", location_group="holy cross"), "Library Lab - Chest By Shrine 2": TunicLocationData("Library", "Library Lab"), "Library Lab - Chest By Shrine 1": TunicLocationData("Library", "Library Lab"), "Library Lab - Chest By Shrine 3": TunicLocationData("Library", "Library Lab"), @@ -110,7 +110,7 @@ location_table: Dict[str, TunicLocationData] = { "Library Lab - Page 3": TunicLocationData("Library", "Library Lab"), "Library Lab - Page 1": TunicLocationData("Library", "Library Lab"), "Library Lab - Page 2": TunicLocationData("Library", "Library Lab"), - "Hero's Grave - Mushroom Relic": TunicLocationData("Library", "Hero Relic - Library"), + "Hero's Grave - Mushroom Relic": TunicLocationData("Library", "Hero Relic - Library", location_group="hero relic"), "Lower Mountain - Page Before Door": TunicLocationData("Overworld", "Lower Mountain"), "Changing Room - Normal Chest": TunicLocationData("Overworld", "Changing Room"), "Fortress Courtyard - Chest Near Cave": TunicLocationData("Overworld", "Fortress Exterior near cave"), @@ -122,42 +122,42 @@ location_table: Dict[str, TunicLocationData] = { "Old House - Normal Chest": TunicLocationData("Overworld", "Old House Front"), "Old House - Shield Pickup": TunicLocationData("Overworld", "Old House Front"), "Overworld - [West] Obscured Behind Windmill": TunicLocationData("Overworld", "Overworld"), - "Overworld - [South] Beach Chest": TunicLocationData("Overworld", "Overworld"), + "Overworld - [South] Beach Chest": TunicLocationData("Overworld", "Overworld Beach"), "Overworld - [West] Obscured Near Well": TunicLocationData("Overworld", "Overworld"), "Overworld - [Central] Bombable Wall": TunicLocationData("Overworld", "Overworld"), "Overworld - [Northwest] Chest Near Turret": TunicLocationData("Overworld", "Overworld"), - "Overworld - [East] Chest Near Pots": TunicLocationData("Overworld", "Overworld"), - "Overworld - [Northwest] Chest Near Golden Obelisk": TunicLocationData("Overworld", "Overworld"), + "Overworld - [East] Chest Near Pots": TunicLocationData("Overworld", "East Overworld"), + "Overworld - [Northwest] Chest Near Golden Obelisk": TunicLocationData("Overworld", "Overworld above Quarry Entrance"), "Overworld - [Southwest] South Chest Near Guard": TunicLocationData("Overworld", "Overworld"), - "Overworld - [Southwest] West Beach Guarded By Turret": TunicLocationData("Overworld", "Overworld"), + "Overworld - [Southwest] West Beach Guarded By Turret": TunicLocationData("Overworld", "Overworld Beach"), "Overworld - [Southwest] Chest Guarded By Turret": TunicLocationData("Overworld", "Overworld"), "Overworld - [Northwest] Shadowy Corner Chest": TunicLocationData("Overworld", "Overworld"), "Overworld - [Southwest] Obscured In Tunnel To Beach": TunicLocationData("Overworld", "Overworld"), "Overworld - [Southwest] Grapple Chest Over Walkway": TunicLocationData("Overworld", "Overworld"), - "Overworld - [Northwest] Chest Beneath Quarry Gate": TunicLocationData("Overworld", "Overworld"), - "Overworld - [Southeast] Chest Near Swamp": TunicLocationData("Overworld", "Overworld"), - "Overworld - [Southwest] From West Garden": TunicLocationData("Overworld", "Overworld"), - "Overworld - [East] Grapple Chest": TunicLocationData("Overworld", "Overworld"), - "Overworld - [Southwest] West Beach Guarded By Turret 2": TunicLocationData("Overworld", "Overworld"), - "Overworld - [Southwest] Beach Chest Near Flowers": TunicLocationData("Overworld", "Overworld"), + "Overworld - [Northwest] Chest Beneath Quarry Gate": TunicLocationData("Overworld", "Overworld after Envoy"), + "Overworld - [Southeast] Chest Near Swamp": TunicLocationData("Overworld", "Overworld Swamp Lower Entry"), + "Overworld - [Southwest] From West Garden": TunicLocationData("Overworld", "Overworld Beach"), + "Overworld - [East] Grapple Chest": TunicLocationData("Overworld", "Overworld above Patrol Cave"), + "Overworld - [Southwest] West Beach Guarded By Turret 2": TunicLocationData("Overworld", "Overworld Beach"), + "Overworld - [Southwest] Beach Chest Near Flowers": TunicLocationData("Overworld", "Overworld Beach"), "Overworld - [Southwest] Bombable Wall Near Fountain": TunicLocationData("Overworld", "Overworld"), "Overworld - [West] Chest After Bell": TunicLocationData("Overworld", "Overworld Belltower"), - "Overworld - [Southwest] Tunnel Guarded By Turret": TunicLocationData("Overworld", "Overworld"), - "Overworld - [East] Between Ladders Near Ruined Passage": TunicLocationData("Overworld", "Overworld"), - "Overworld - [Northeast] Chest Above Patrol Cave": TunicLocationData("Overworld", "Overworld"), - "Overworld - [Southwest] Beach Chest Beneath Guard": TunicLocationData("Overworld", "Overworld"), + "Overworld - [Southwest] Tunnel Guarded By Turret": TunicLocationData("Overworld", "Overworld Tunnel Turret"), + "Overworld - [East] Between Ladders Near Ruined Passage": TunicLocationData("Overworld", "Above Ruined Passage"), + "Overworld - [Northeast] Chest Above Patrol Cave": TunicLocationData("Overworld", "Upper Overworld"), + "Overworld - [Southwest] Beach Chest Beneath Guard": TunicLocationData("Overworld", "Overworld Beach"), "Overworld - [Central] Chest Across From Well": TunicLocationData("Overworld", "Overworld"), "Overworld - [Northwest] Chest Near Quarry Gate": TunicLocationData("Overworld", "Overworld"), - "Overworld - [East] Chest In Trees": TunicLocationData("Overworld", "Overworld"), + "Overworld - [East] Chest In Trees": TunicLocationData("Overworld", "Above Ruined Passage"), "Overworld - [West] Chest Behind Moss Wall": TunicLocationData("Overworld", "Overworld"), - "Overworld - [South] Beach Page": TunicLocationData("Overworld", "Overworld"), + "Overworld - [South] Beach Page": TunicLocationData("Overworld", "Overworld Beach"), "Overworld - [Southeast] Page on Pillar by Swamp": TunicLocationData("Overworld", "Overworld"), "Overworld - [Southwest] Key Pickup": TunicLocationData("Overworld", "Overworld"), "Overworld - [West] Key Pickup": TunicLocationData("Overworld", "Overworld"), - "Overworld - [East] Page Near Secret Shop": TunicLocationData("Overworld", "Overworld"), + "Overworld - [East] Page Near Secret Shop": TunicLocationData("Overworld", "East Overworld"), "Overworld - [Southwest] Fountain Page": TunicLocationData("Overworld", "Overworld"), "Overworld - [Northwest] Page on Pillar by Dark Tomb": TunicLocationData("Overworld", "Overworld"), - "Overworld - [Northwest] Fire Wand Pickup": TunicLocationData("Overworld", "Overworld"), + "Overworld - [Northwest] Fire Wand Pickup": TunicLocationData("Overworld", "Upper Overworld"), "Overworld - [West] Page On Teleporter": TunicLocationData("Overworld", "Overworld"), "Overworld - [Northwest] Page By Well": TunicLocationData("Overworld", "Overworld"), "Patrol Cave - Normal Chest": TunicLocationData("Overworld", "Patrol Cave"), @@ -165,49 +165,49 @@ location_table: Dict[str, TunicLocationData] = { "Ruined Shop - Chest 2": TunicLocationData("Overworld", "Ruined Shop"), "Ruined Shop - Chest 3": TunicLocationData("Overworld", "Ruined Shop"), "Ruined Passage - Page Pickup": TunicLocationData("Overworld", "Ruined Passage"), - "Shop - Potion 1": TunicLocationData("Overworld", "Shop", "shop"), - "Shop - Potion 2": TunicLocationData("Overworld", "Shop", "shop"), - "Shop - Coin 1": TunicLocationData("Overworld", "Shop", "shop"), - "Shop - Coin 2": TunicLocationData("Overworld", "Shop", "shop"), + "Shop - Potion 1": TunicLocationData("Overworld", "Shop", location_group="shop"), + "Shop - Potion 2": TunicLocationData("Overworld", "Shop", location_group="shop"), + "Shop - Coin 1": TunicLocationData("Overworld", "Shop", location_group="shop"), + "Shop - Coin 2": TunicLocationData("Overworld", "Shop", location_group="shop"), "Special Shop - Secret Page Pickup": TunicLocationData("Overworld", "Special Shop"), "Stick House - Stick Chest": TunicLocationData("Overworld", "Stick House"), "Sealed Temple - Page Pickup": TunicLocationData("Overworld", "Sealed Temple"), "Hourglass Cave - Hourglass Chest": TunicLocationData("Overworld", "Hourglass Cave"), "Far Shore - Secret Chest": TunicLocationData("Overworld", "Far Shore"), - "Far Shore - Page Pickup": TunicLocationData("Overworld", "Far Shore to Spawn"), - "Coins in the Well - 10 Coins": TunicLocationData("Overworld", "Overworld", "well"), - "Coins in the Well - 15 Coins": TunicLocationData("Overworld", "Overworld", "well"), - "Coins in the Well - 3 Coins": TunicLocationData("Overworld", "Overworld", "well"), - "Coins in the Well - 6 Coins": TunicLocationData("Overworld", "Overworld", "well"), - "Secret Gathering Place - 20 Fairy Reward": TunicLocationData("Overworld", "Secret Gathering Place", "fairies"), - "Secret Gathering Place - 10 Fairy Reward": TunicLocationData("Overworld", "Secret Gathering Place", "fairies"), - "Overworld - [West] Moss Wall Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", "holy cross"), - "Overworld - [Southwest] Flowers Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", "holy cross"), - "Overworld - [Southwest] Fountain Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", "holy cross"), - "Overworld - [Northeast] Flowers Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", "holy cross"), - "Overworld - [East] Weathervane Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", "holy cross"), - "Overworld - [West] Windmill Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", "holy cross"), - "Overworld - [Southwest] Haiku Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", "holy cross"), - "Overworld - [West] Windchimes Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", "holy cross"), - "Overworld - [South] Starting Platform Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", "holy cross"), - "Overworld - [Northwest] Golden Obelisk Page": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", "holy cross"), - "Old House - Holy Cross Door Page": TunicLocationData("Overworld Holy Cross", "Old House Back", "holy cross"), - "Cube Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Cube Cave", "holy cross"), - "Southeast Cross Door - Chest 3": TunicLocationData("Overworld Holy Cross", "Southeast Cross Room", "holy cross"), - "Southeast Cross Door - Chest 2": TunicLocationData("Overworld Holy Cross", "Southeast Cross Room", "holy cross"), - "Southeast Cross Door - Chest 1": TunicLocationData("Overworld Holy Cross", "Southeast Cross Room", "holy cross"), - "Maze Cave - Maze Room Holy Cross": TunicLocationData("Overworld Holy Cross", "Maze Cave", "holy cross"), - "Caustic Light Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Caustic Light Cave", "holy cross"), - "Old House - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Old House Front", "holy cross"), - "Patrol Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Patrol Cave", "holy cross"), - "Ruined Passage - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Ruined Passage", "holy cross"), - "Hourglass Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Hourglass Cave", "holy cross"), - "Sealed Temple - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Sealed Temple", "holy cross"), - "Fountain Cross Door - Page Pickup": TunicLocationData("Overworld Holy Cross", "Fountain Cross Room", "holy cross"), - "Secret Gathering Place - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Secret Gathering Place", "holy cross"), - "Top of the Mountain - Page At The Peak": TunicLocationData("Overworld Holy Cross", "Top of the Mountain", "holy cross"), + "Far Shore - Page Pickup": TunicLocationData("Overworld", "Far Shore to Spawn Region"), + "Coins in the Well - 10 Coins": TunicLocationData("Overworld", "Overworld", location_group="well"), + "Coins in the Well - 15 Coins": TunicLocationData("Overworld", "Overworld", location_group="well"), + "Coins in the Well - 3 Coins": TunicLocationData("Overworld", "Overworld", location_group="well"), + "Coins in the Well - 6 Coins": TunicLocationData("Overworld", "Overworld", location_group="well"), + "Secret Gathering Place - 20 Fairy Reward": TunicLocationData("Overworld", "Secret Gathering Place", location_group="fairies"), + "Secret Gathering Place - 10 Fairy Reward": TunicLocationData("Overworld", "Secret Gathering Place", location_group="fairies"), + "Overworld - [West] Moss Wall Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="holy cross"), + "Overworld - [Southwest] Flowers Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Beach", location_group="holy cross"), + "Overworld - [Southwest] Fountain Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="holy cross"), + "Overworld - [Northeast] Flowers Holy Cross": TunicLocationData("Overworld Holy Cross", "East Overworld", location_group="holy cross"), + "Overworld - [East] Weathervane Holy Cross": TunicLocationData("Overworld Holy Cross", "East Overworld", location_group="holy cross"), + "Overworld - [West] Windmill Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="holy cross"), + "Overworld - [Southwest] Haiku Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Beach", location_group="holy cross"), + "Overworld - [West] Windchimes Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="holy cross"), + "Overworld - [South] Starting Platform Holy Cross": TunicLocationData("Overworld Holy Cross", "Overworld Holy Cross", location_group="holy cross"), + "Overworld - [Northwest] Golden Obelisk Page": TunicLocationData("Overworld Holy Cross", "Upper Overworld", location_group="holy cross"), + "Old House - Holy Cross Door Page": TunicLocationData("Overworld Holy Cross", "Old House Back", location_group="holy cross"), + "Cube Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Cube Cave", location_group="holy cross"), + "Southeast Cross Door - Chest 3": TunicLocationData("Overworld Holy Cross", "Southeast Cross Room", location_group="holy cross"), + "Southeast Cross Door - Chest 2": TunicLocationData("Overworld Holy Cross", "Southeast Cross Room", location_group="holy cross"), + "Southeast Cross Door - Chest 1": TunicLocationData("Overworld Holy Cross", "Southeast Cross Room", location_group="holy cross"), + "Maze Cave - Maze Room Holy Cross": TunicLocationData("Overworld Holy Cross", "Maze Cave", location_group="holy cross"), + "Caustic Light Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Caustic Light Cave", location_group="holy cross"), + "Old House - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Old House Front", location_group="holy cross"), + "Patrol Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Patrol Cave", location_group="holy cross"), + "Ruined Passage - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Ruined Passage", location_group="holy cross"), + "Hourglass Cave - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Hourglass Cave Tower", location_group="holy cross"), + "Sealed Temple - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Sealed Temple", location_group="holy cross"), + "Fountain Cross Door - Page Pickup": TunicLocationData("Overworld Holy Cross", "Fountain Cross Room", location_group="holy cross"), + "Secret Gathering Place - Holy Cross Chest": TunicLocationData("Overworld Holy Cross", "Secret Gathering Place", location_group="holy cross"), + "Top of the Mountain - Page At The Peak": TunicLocationData("Overworld Holy Cross", "Top of the Mountain", location_group="holy cross"), "Monastery - Monastery Chest": TunicLocationData("Quarry", "Monastery Back"), - "Quarry - [Back Entrance] Bushes Holy Cross": TunicLocationData("Quarry Back", "Quarry Back", "holy cross"), + "Quarry - [Back Entrance] Bushes Holy Cross": TunicLocationData("Quarry Back", "Quarry Back", location_group="holy cross"), "Quarry - [Back Entrance] Chest": TunicLocationData("Quarry Back", "Quarry Back"), "Quarry - [Central] Near Shortcut Ladder": TunicLocationData("Quarry", "Quarry"), "Quarry - [East] Near Telescope": TunicLocationData("Quarry", "Quarry"), @@ -225,16 +225,16 @@ location_table: Dict[str, TunicLocationData] = { "Quarry - [Central] Above Ladder Dash Chest": TunicLocationData("Quarry", "Quarry Monastery Entry"), "Quarry - [West] Upper Area Bombable Wall": TunicLocationData("Quarry Back", "Quarry Back"), "Quarry - [East] Bombable Wall": TunicLocationData("Quarry", "Quarry"), - "Hero's Grave - Ash Relic": TunicLocationData("Quarry", "Hero Relic - Quarry"), + "Hero's Grave - Ash Relic": TunicLocationData("Quarry", "Hero Relic - Quarry", location_group="hero relics"), "Quarry - [West] Shooting Range Secret Path": TunicLocationData("Lower Quarry", "Lower Quarry"), "Quarry - [West] Near Shooting Range": TunicLocationData("Lower Quarry", "Lower Quarry"), "Quarry - [West] Below Shooting Range": TunicLocationData("Lower Quarry", "Lower Quarry"), - "Quarry - [Lowlands] Below Broken Ladder": TunicLocationData("Lower Quarry", "Lower Quarry"), + "Quarry - [Lowlands] Below Broken Ladder": TunicLocationData("Lower Quarry", "Even Lower Quarry"), "Quarry - [West] Upper Area Near Waterfall": TunicLocationData("Lower Quarry", "Lower Quarry"), - "Quarry - [Lowlands] Upper Walkway": TunicLocationData("Lower Quarry", "Lower Quarry"), + "Quarry - [Lowlands] Upper Walkway": TunicLocationData("Lower Quarry", "Even Lower Quarry"), "Quarry - [West] Lower Area Below Bridge": TunicLocationData("Lower Quarry", "Lower Quarry"), "Quarry - [West] Lower Area Isolated Chest": TunicLocationData("Lower Quarry", "Lower Quarry"), - "Quarry - [Lowlands] Near Elevator": TunicLocationData("Lower Quarry", "Lower Quarry"), + "Quarry - [Lowlands] Near Elevator": TunicLocationData("Lower Quarry", "Even Lower Quarry"), "Quarry - [West] Lower Area After Bridge": TunicLocationData("Lower Quarry", "Lower Quarry"), "Rooted Ziggurat Upper - Near Bridge Switch": TunicLocationData("Rooted Ziggurat", "Rooted Ziggurat Upper Front"), "Rooted Ziggurat Upper - Beneath Bridge To Administrator": TunicLocationData("Rooted Ziggurat", "Rooted Ziggurat Upper Back"), @@ -246,15 +246,15 @@ location_table: Dict[str, TunicLocationData] = { "Rooted Ziggurat Lower - Guarded By Double Turrets": TunicLocationData("Rooted Ziggurat", "Rooted Ziggurat Lower Front"), "Rooted Ziggurat Lower - After 2nd Double Turret Chest": TunicLocationData("Rooted Ziggurat", "Rooted Ziggurat Lower Front"), "Rooted Ziggurat Lower - Guarded By Double Turrets 2": TunicLocationData("Rooted Ziggurat", "Rooted Ziggurat Lower Front"), - "Rooted Ziggurat Lower - Hexagon Blue": TunicLocationData("Rooted Ziggurat", "Rooted Ziggurat Lower Back"), + "Rooted Ziggurat Lower - Hexagon Blue": TunicLocationData("Rooted Ziggurat", "Rooted Ziggurat Lower Back", location_group="bosses"), "Ruined Atoll - [West] Near Kevin Block": TunicLocationData("Ruined Atoll", "Ruined Atoll"), - "Ruined Atoll - [South] Upper Floor On Power Line": TunicLocationData("Ruined Atoll", "Ruined Atoll"), + "Ruined Atoll - [South] Upper Floor On Power Line": TunicLocationData("Ruined Atoll", "Ruined Atoll Ladder Tops"), "Ruined Atoll - [South] Chest Near Big Crabs": TunicLocationData("Ruined Atoll", "Ruined Atoll"), "Ruined Atoll - [North] Guarded By Bird": TunicLocationData("Ruined Atoll", "Ruined Atoll"), "Ruined Atoll - [Northeast] Chest Beneath Brick Walkway": TunicLocationData("Ruined Atoll", "Ruined Atoll"), "Ruined Atoll - [Northwest] Bombable Wall": TunicLocationData("Ruined Atoll", "Ruined Atoll"), "Ruined Atoll - [North] Obscured Beneath Bridge": TunicLocationData("Ruined Atoll", "Ruined Atoll"), - "Ruined Atoll - [South] Upper Floor On Bricks": TunicLocationData("Ruined Atoll", "Ruined Atoll"), + "Ruined Atoll - [South] Upper Floor On Bricks": TunicLocationData("Ruined Atoll", "Ruined Atoll Ladder Tops"), "Ruined Atoll - [South] Near Birds": TunicLocationData("Ruined Atoll", "Ruined Atoll"), "Ruined Atoll - [Northwest] Behind Envoy": TunicLocationData("Ruined Atoll", "Ruined Atoll"), "Ruined Atoll - [Southwest] Obscured Behind Fuse": TunicLocationData("Ruined Atoll", "Ruined Atoll"), @@ -262,40 +262,40 @@ location_table: Dict[str, TunicLocationData] = { "Ruined Atoll - [North] From Lower Overworld Entrance": TunicLocationData("Ruined Atoll", "Ruined Atoll Lower Entry Area"), "Ruined Atoll - [East] Locked Room Lower Chest": TunicLocationData("Ruined Atoll", "Ruined Atoll"), "Ruined Atoll - [Northeast] Chest On Brick Walkway": TunicLocationData("Ruined Atoll", "Ruined Atoll"), - "Ruined Atoll - [Southeast] Chest Near Fuse": TunicLocationData("Ruined Atoll", "Ruined Atoll"), + "Ruined Atoll - [Southeast] Chest Near Fuse": TunicLocationData("Ruined Atoll", "Ruined Atoll Ladder Tops"), "Ruined Atoll - [Northeast] Key Pickup": TunicLocationData("Ruined Atoll", "Ruined Atoll"), "Cathedral Gauntlet - Gauntlet Reward": TunicLocationData("Swamp", "Cathedral Gauntlet"), "Cathedral - Secret Legend Trophy Chest": TunicLocationData("Swamp", "Cathedral Secret Legend Room"), - "Swamp - [Upper Graveyard] Obscured Behind Hill": TunicLocationData("Swamp", "Swamp"), - "Swamp - [South Graveyard] 4 Orange Skulls": TunicLocationData("Swamp", "Swamp"), - "Swamp - [Central] Near Ramps Up": TunicLocationData("Swamp", "Swamp"), - "Swamp - [Upper Graveyard] Near Shield Fleemers": TunicLocationData("Swamp", "Swamp"), - "Swamp - [South Graveyard] Obscured Behind Ridge": TunicLocationData("Swamp", "Swamp"), - "Swamp - [South Graveyard] Obscured Beneath Telescope": TunicLocationData("Swamp", "Swamp"), + "Swamp - [Upper Graveyard] Obscured Behind Hill": TunicLocationData("Swamp", "Swamp Mid"), + "Swamp - [South Graveyard] 4 Orange Skulls": TunicLocationData("Swamp", "Swamp Front"), + "Swamp - [Central] Near Ramps Up": TunicLocationData("Swamp", "Swamp Mid"), + "Swamp - [Upper Graveyard] Near Shield Fleemers": TunicLocationData("Swamp", "Swamp Mid"), + "Swamp - [South Graveyard] Obscured Behind Ridge": TunicLocationData("Swamp", "Swamp Mid"), + "Swamp - [South Graveyard] Obscured Beneath Telescope": TunicLocationData("Swamp", "Swamp Front"), "Swamp - [Entrance] Above Entryway": TunicLocationData("Swamp", "Back of Swamp Laurels Area"), - "Swamp - [Central] South Secret Passage": TunicLocationData("Swamp", "Swamp"), - "Swamp - [South Graveyard] Upper Walkway On Pedestal": TunicLocationData("Swamp", "Swamp"), - "Swamp - [South Graveyard] Guarded By Tentacles": TunicLocationData("Swamp", "Swamp"), - "Swamp - [Upper Graveyard] Near Telescope": TunicLocationData("Swamp", "Swamp"), - "Swamp - [Outside Cathedral] Near Moonlight Bridge Door": TunicLocationData("Swamp", "Swamp"), - "Swamp - [Entrance] Obscured Inside Watchtower": TunicLocationData("Swamp", "Swamp"), - "Swamp - [Entrance] South Near Fence": TunicLocationData("Swamp", "Swamp"), - "Swamp - [South Graveyard] Guarded By Big Skeleton": TunicLocationData("Swamp", "Swamp"), - "Swamp - [South Graveyard] Chest Near Graves": TunicLocationData("Swamp", "Swamp"), - "Swamp - [Entrance] North Small Island": TunicLocationData("Swamp", "Swamp"), + "Swamp - [Central] South Secret Passage": TunicLocationData("Swamp", "Swamp Mid"), + "Swamp - [South Graveyard] Upper Walkway On Pedestal": TunicLocationData("Swamp", "Swamp Front"), + "Swamp - [South Graveyard] Guarded By Tentacles": TunicLocationData("Swamp", "Swamp Front"), + "Swamp - [Upper Graveyard] Near Telescope": TunicLocationData("Swamp", "Swamp Mid"), + "Swamp - [Outside Cathedral] Near Moonlight Bridge Door": TunicLocationData("Swamp", "Swamp Ledge under Cathedral Door"), + "Swamp - [Entrance] Obscured Inside Watchtower": TunicLocationData("Swamp", "Swamp Front"), + "Swamp - [Entrance] South Near Fence": TunicLocationData("Swamp", "Swamp Front"), + "Swamp - [South Graveyard] Guarded By Big Skeleton": TunicLocationData("Swamp", "Swamp Front"), + "Swamp - [South Graveyard] Chest Near Graves": TunicLocationData("Swamp", "Swamp Front"), + "Swamp - [Entrance] North Small Island": TunicLocationData("Swamp", "Swamp Front"), "Swamp - [Outside Cathedral] Obscured Behind Memorial": TunicLocationData("Swamp", "Back of Swamp"), - "Swamp - [Central] Obscured Behind Northern Mountain": TunicLocationData("Swamp", "Swamp"), - "Swamp - [South Graveyard] Upper Walkway Dash Chest": TunicLocationData("Swamp", "Swamp"), - "Swamp - [South Graveyard] Above Big Skeleton": TunicLocationData("Swamp", "Swamp"), - "Swamp - [Central] Beneath Memorial": TunicLocationData("Swamp", "Swamp"), - "Hero's Grave - Feathers Relic": TunicLocationData("Swamp", "Hero Relic - Swamp"), + "Swamp - [Central] Obscured Behind Northern Mountain": TunicLocationData("Swamp", "Swamp Mid"), + "Swamp - [South Graveyard] Upper Walkway Dash Chest": TunicLocationData("Swamp", "Swamp Mid"), + "Swamp - [South Graveyard] Above Big Skeleton": TunicLocationData("Swamp", "Swamp Front"), + "Swamp - [Central] Beneath Memorial": TunicLocationData("Swamp", "Swamp Mid"), + "Hero's Grave - Feathers Relic": TunicLocationData("Swamp", "Hero Relic - Swamp", location_group="hero relic"), "West Furnace - Chest": TunicLocationData("West Garden", "Furnace Walking Path"), "Overworld - [West] Near West Garden Entrance": TunicLocationData("West Garden", "Overworld to West Garden from Furnace"), - "West Garden - [Central Highlands] Holy Cross (Blue Lines)": TunicLocationData("West Garden", "West Garden", "holy cross"), - "West Garden - [West Lowlands] Tree Holy Cross Chest": TunicLocationData("West Garden", "West Garden", "holy cross"), + "West Garden - [Central Highlands] Holy Cross (Blue Lines)": TunicLocationData("West Garden", "West Garden", location_group="holy cross"), + "West Garden - [West Lowlands] Tree Holy Cross Chest": TunicLocationData("West Garden", "West Garden", location_group="holy cross"), "West Garden - [Southeast Lowlands] Outside Cave": TunicLocationData("West Garden", "West Garden"), "West Garden - [Central Lowlands] Chest Beneath Faeries": TunicLocationData("West Garden", "West Garden"), - "West Garden - [North] Behind Holy Cross Door": TunicLocationData("West Garden", "West Garden", "holy cross"), + "West Garden - [North] Behind Holy Cross Door": TunicLocationData("West Garden", "West Garden", location_group="holy cross"), "West Garden - [Central Highlands] Top of Ladder Before Boss": TunicLocationData("West Garden", "West Garden"), "West Garden - [Central Lowlands] Passage Beneath Bridge": TunicLocationData("West Garden", "West Garden"), "West Garden - [North] Across From Page Pickup": TunicLocationData("West Garden", "West Garden"), @@ -307,12 +307,12 @@ location_table: Dict[str, TunicLocationData] = { "West Garden - [West Highlands] Upper Left Walkway": TunicLocationData("West Garden", "West Garden"), "West Garden - [Central Lowlands] Chest Beneath Save Point": TunicLocationData("West Garden", "West Garden"), "West Garden - [Central Highlands] Behind Guard Captain": TunicLocationData("West Garden", "West Garden"), - "West Garden - [Central Highlands] After Garden Knight": TunicLocationData("West Garden", "West Garden after Boss"), + "West Garden - [Central Highlands] After Garden Knight": TunicLocationData("Overworld", "West Garden after Boss", location_group="bosses"), "West Garden - [South Highlands] Secret Chest Beneath Fuse": TunicLocationData("West Garden", "West Garden"), "West Garden - [East Lowlands] Page Behind Ice Dagger House": TunicLocationData("West Garden", "West Garden Portal Item"), "West Garden - [North] Page Pickup": TunicLocationData("West Garden", "West Garden"), "West Garden House - [Southeast Lowlands] Ice Dagger Pickup": TunicLocationData("West Garden", "Magic Dagger House"), - "Hero's Grave - Effigy Relic": TunicLocationData("West Garden", "Hero Relic - West Garden"), + "Hero's Grave - Effigy Relic": TunicLocationData("West Garden", "Hero Relic - West Garden", location_group="hero relic"), } hexagon_locations: Dict[str, str] = { @@ -323,15 +323,9 @@ hexagon_locations: Dict[str, str] = { location_name_to_id: Dict[str, int] = {name: location_base_id + index for index, name in enumerate(location_table)} - -def get_loc_group(location_name: str) -> str: - loc_group = location_table[location_name].location_group - if loc_group == "region": - # set loc_group as the region name. Typically, location groups are lowercase - loc_group = location_table[location_name].region.lower() - return loc_group - - -location_name_groups: Dict[str, Set[str]] = { - group: set(item_names) for group, item_names in groupby(sorted(location_table, key=get_loc_group), get_loc_group) -} +location_name_groups: Dict[str, Set[str]] = {} +for loc_name, loc_data in location_table.items(): + if loc_data.location_group: + if loc_data.location_group not in location_name_groups.keys(): + location_name_groups[loc_data.location_group] = set() + location_name_groups[loc_data.location_group].add(loc_name) diff --git a/worlds/tunic/options.py b/worlds/tunic/options.py index 779e632326..38ddcbe8e4 100644 --- a/worlds/tunic/options.py +++ b/worlds/tunic/options.py @@ -4,8 +4,7 @@ from Options import DefaultOnToggle, Toggle, StartInventoryPool, Choice, Range, class SwordProgression(DefaultOnToggle): - """Adds four sword upgrades to the item pool that will progressively grant stronger melee weapons, including two new - swords with increased range and attack power.""" + """Adds four sword upgrades to the item pool that will progressively grant stronger melee weapons, including two new swords with increased range and attack power.""" internal_name = "sword_progression" display_name = "Sword Progression" @@ -24,25 +23,24 @@ class KeysBehindBosses(Toggle): class AbilityShuffling(Toggle): """Locks the usage of Prayer, Holy Cross*, and the Icebolt combo until the relevant pages of the manual have been found. - If playing Hexagon Quest, abilities are instead randomly unlocked after obtaining 25%, 50%, and 75% of the required - Hexagon goal amount. - *Certain Holy Cross usages are still allowed, such as the free bomb codes, the seeking spell, and other - player-facing codes. + If playing Hexagon Quest, abilities are instead randomly unlocked after obtaining 25%, 50%, and 75% of the required Hexagon goal amount. + *Certain Holy Cross usages are still allowed, such as the free bomb codes, the seeking spell, and other player-facing codes. """ internal_name = "ability_shuffling" display_name = "Shuffle Abilities" class LogicRules(Choice): - """Set which logic rules to use for your world. + """ + Set which logic rules to use for your world. Restricted: Standard logic, no glitches. No Major Glitches: Sneaky Laurels zips, ice grapples through doors, shooting the west bell, and boss quick kills are included in logic. * Ice grappling through the Ziggurat door is not in logic since you will get stuck in there without Prayer. Unrestricted: Logic in No Major Glitches, as well as ladder storage to get to certain places early. - *Special Shop is not in logic without the Hero's Laurels due to soft lock potential. + *Torch is given to the player at the start of the game due to the high softlock potential with various tricks. Using the torch is not required in logic. *Using Ladder Storage to get to individual chests is not in logic to avoid tedium. - *Getting knocked out of the air by enemies during Ladder Storage to reach places is not in logic, except for in - Rooted Ziggurat Lower. This is so you're not punished for playing with enemy rando on.""" + *Getting knocked out of the air by enemies during Ladder Storage to reach places is not in logic, except for in Rooted Ziggurat Lower. This is so you're not punished for playing with enemy rando on. + """ internal_name = "logic_rules" display_name = "Logic Rules" option_restricted = 0 @@ -68,8 +66,7 @@ class Maskless(Toggle): class FoolTraps(Choice): - """Replaces low-to-medium value money rewards in the item pool with fool traps, which cause random negative - effects to the player.""" + """Replaces low-to-medium value money rewards in the item pool with fool traps, which cause random negative effects to the player.""" internal_name = "fool_traps" display_name = "Fool Traps" option_off = 0 @@ -80,8 +77,7 @@ class FoolTraps(Choice): class HexagonQuest(Toggle): - """An alternate goal that shuffles Gold "Questagon" items into the item pool and allows the game to be completed - after collecting the required number of them.""" + """An alternate goal that shuffles Gold "Questagon" items into the item pool and allows the game to be completed after collecting the required number of them.""" internal_name = "hexagon_quest" display_name = "Hexagon Quest" @@ -105,9 +101,11 @@ class ExtraHexagonPercentage(Range): class EntranceRando(TextChoice): - """Randomize the connections between scenes. - You can choose a custom seed by editing this option. - A small, very lost fox on a big adventure.""" + """ + Randomize the connections between scenes. + If you set this to a value besides true or false, that value will be used as a custom seed. + A small, very lost fox on a big adventure. + """ internal_name = "entrance_rando" display_name = "Entrance Rando" alias_false = 0 @@ -137,15 +135,24 @@ class LaurelsLocation(Choice): default = 0 +class ShuffleLadders(Toggle): + """Turns several ladders in the game into items that must be found before they can be climbed on. + Adds more layers of progression to the game by blocking access to many areas early on. + "Ladders were a mistake." —Andrew Shouldice""" + internal_name = "shuffle_ladders" + display_name = "Shuffle Ladders" + + @dataclass class TunicOptions(PerGameCommonOptions): sword_progression: SwordProgression start_with_sword: StartWithSword keys_behind_bosses: KeysBehindBosses ability_shuffling: AbilityShuffling - logic_rules: LogicRules + shuffle_ladders: ShuffleLadders entrance_rando: EntranceRando fixed_shop: FixedShop + logic_rules: LogicRules fool_traps: FoolTraps hexagon_quest: HexagonQuest hexagon_goal: HexagonGoal diff --git a/worlds/tunic/regions.py b/worlds/tunic/regions.py index 70204c6397..c30a44bb8f 100644 --- a/worlds/tunic/regions.py +++ b/worlds/tunic/regions.py @@ -6,10 +6,10 @@ tunic_regions: Dict[str, Set[str]] = { "Ruined Atoll", "Eastern Vault Fortress", "Beneath the Vault", "Quarry Back", "Quarry", "Swamp", "Spirit Arena"}, "Overworld Holy Cross": set(), - "East Forest": {"Eastern Vault Fortress"}, + "East Forest": set(), "Dark Tomb": {"West Garden"}, - "Beneath the Well": {"Dark Tomb"}, - "West Garden": {"Overworld", "Dark Tomb"}, + "Beneath the Well": set(), + "West Garden": set(), "Ruined Atoll": {"Frog's Domain", "Library"}, "Frog's Domain": set(), "Library": set(), diff --git a/worlds/tunic/rules.py b/worlds/tunic/rules.py index b3dd0b6832..c82c5ca133 100644 --- a/worlds/tunic/rules.py +++ b/worlds/tunic/rules.py @@ -103,18 +103,10 @@ def set_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) -> No multiworld.get_entrance("Overworld -> West Garden", player).access_rule = \ lambda state: state.has(laurels, player) \ or can_ladder_storage(state, player, options) - multiworld.get_entrance("Beneath the Well -> Dark Tomb", player).access_rule = \ - lambda state: has_lantern(state, player, options) - multiworld.get_entrance("West Garden -> Dark Tomb", player).access_rule = \ - lambda state: has_lantern(state, player, options) multiworld.get_entrance("Overworld -> Eastern Vault Fortress", player).access_rule = \ lambda state: state.has(laurels, player) \ or has_ice_grapple_logic(True, state, player, options, ability_unlocks) \ or can_ladder_storage(state, player, options) - multiworld.get_entrance("East Forest -> Eastern Vault Fortress", player).access_rule = \ - lambda state: state.has(laurels, player) \ - or has_ice_grapple_logic(True, state, player, options, ability_unlocks) \ - or can_ladder_storage(state, player, options) # using laurels or ls to get in is covered by the -> Eastern Vault Fortress rules multiworld.get_entrance("Overworld -> Beneath the Vault", player).access_rule = \ lambda state: has_lantern(state, player, options) and \ @@ -211,7 +203,8 @@ def set_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) -> lambda state: state.has(laurels, player)) set_rule(multiworld.get_location("Overworld - [West] Chest After Bell", player), lambda state: state.has(laurels, player) - or (has_lantern(state, player, options) and has_sword(state, player))) + or (has_lantern(state, player, options) and has_sword(state, player)) + or can_ladder_storage(state, player, options)) set_rule(multiworld.get_location("Overworld - [Northwest] Chest Beneath Quarry Gate", player), lambda state: state.has_any({grapple, laurels}, player) or options.logic_rules) set_rule(multiworld.get_location("Overworld - [East] Grapple Chest", player), @@ -228,6 +221,8 @@ def set_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) -> lambda state: state.has(laurels, player) or (has_lantern(state, player, options) and (has_sword(state, player) or state.has(fire_wand, player))) or has_ice_grapple_logic(False, state, player, options, ability_unlocks)) + set_rule(multiworld.get_location("West Furnace - Lantern Pickup", player), + lambda state: has_stick(state, player) or state.has_any({fire_wand, laurels}, player)) set_rule(multiworld.get_location("Secret Gathering Place - 10 Fairy Reward", player), lambda state: state.has(fairies, player, 10)) @@ -265,8 +260,8 @@ def set_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) -> set_rule(multiworld.get_location("West Garden - [Central Lowlands] Below Left Walkway", player), lambda state: state.has(laurels, player)) set_rule(multiworld.get_location("West Garden - [Central Highlands] After Garden Knight", player), - lambda state: has_sword(state, player) or state.has(laurels, player) - or has_ice_grapple_logic(False, state, player, options, ability_unlocks) + lambda state: state.has(laurels, player) + or (has_lantern(state, player, options) and has_sword(state, player)) or can_ladder_storage(state, player, options)) # Ruined Atoll @@ -325,8 +320,6 @@ def set_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) -> lambda state: state.has(laurels, player)) set_rule(multiworld.get_location("Swamp - [South Graveyard] 4 Orange Skulls", player), lambda state: has_sword(state, player)) - set_rule(multiworld.get_location("Swamp - [South Graveyard] Guarded By Tentacles", player), - lambda state: has_sword(state, player)) # Hero's Grave set_rule(multiworld.get_location("Hero's Grave - Tooth Relic", player), From 43084da23c719133fcae672e20c9b046e6ef8067 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:51:29 +0100 Subject: [PATCH 163/166] The Witness: Fix newlines in Witness option tooltips (#2971) --- worlds/witness/options.py | 169 ++++++++++++++++++++++++-------------- 1 file changed, 106 insertions(+), 63 deletions(-) diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 5bce3e3a22..b66308df43 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -8,19 +8,23 @@ from .static_logic import WeightedItemDefinition, ItemCategory, StaticWitnessLog class DisableNonRandomizedPuzzles(Toggle): - """Disables puzzles that cannot be randomized. + """ + Disables puzzles that cannot be randomized. This includes many puzzles that heavily involve the environment, such as Shadows, Monastery or Orchard. + The lasers for those areas will activate as you solve optional puzzles, such as Discarded Panels. - Additionally, the panels activating Monastery Laser and Jungle Popup Wall will be on from the start.""" + Additionally, the panel activating the Jungle Popup Wall will be on from the start. + """ display_name = "Disable non randomized puzzles" class EarlyCaves(Choice): - """Adds an item that opens the Caves Shortcuts to Swamp and Mountain, - allowing early access to the Caves even if you are not playing a remote Door Shuffle mode. - You can either add this item to the pool to be found on one of your randomized checks, - or you can outright start with it and have immediate access to the Caves. - If you choose "add_to_pool" and you are already playing a remote Door Shuffle mode, this setting will do nothing.""" + """ + Adds an item that opens the Caves Shortcuts to Swamp and Mountain, allowing early access to the Caves even if you are not playing a remote Door Shuffle mode. + You can either add this item to the pool to be found in the multiworld, or you can outright start with it and have immediate access to the Caves. + + If you choose "Add To Pool" and you are already playing a remote Door Shuffle mode, this option will do nothing. + """ display_name = "Early Caves" option_off = 0 alias_false = 0 @@ -31,15 +35,19 @@ class EarlyCaves(Choice): class ShuffleSymbols(DefaultOnToggle): - """You will need to unlock puzzle symbols as items to be able to solve the panels that contain those symbols. - If you turn this off, there will be no progression items in the game unless you turn on door shuffle.""" + """ + If on, you will need to unlock puzzle symbols as items to be able to solve the panels that contain those symbols. + + Please note that there is no minimum set of progression items in this randomizer. + If you turn this option off and don't turn on door shuffle or obelisk keys, there will be no progression items, which will disallow you from adding your yaml to a multiworld generation. + """ display_name = "Shuffle Symbols" class ShuffleLasers(Choice): - """If on, the 11 lasers are turned into items and will activate on their own upon receiving them. - Note: There is a visual bug that can occur with the Desert Laser. It does not affect gameplay - The Laser can still - be redirected as normal, for both applications of redirection.""" + """ + If on, the 11 lasers are turned into items and will activate on their own upon receiving them. + """ display_name = "Shuffle Lasers" option_off = 0 alias_false = 0 @@ -50,10 +58,12 @@ class ShuffleLasers(Choice): class ShuffleDoors(Choice): - """If on, opening doors, moving bridges etc. will require a "key". + """ + If on, opening doors, moving bridges etc. will require a "key". If set to "panels", the panel on the door will be locked until receiving its corresponding key. If set to "doors", the door will open immediately upon receiving its key. Door panels are added as location checks. - "Mixed" includes all doors from "doors", and all control panels (bridges, elevators etc.) from "panels".""" + "Mixed" includes all doors from "doors", and all control panels (bridges, elevators etc.) from "panels". + """ display_name = "Shuffle Doors" option_off = 0 option_panels = 1 @@ -62,38 +72,45 @@ class ShuffleDoors(Choice): class DoorGroupings(Choice): - """If set to "none", there will be one key for every door, resulting in up to 120 keys being added to the item pool. - If set to "regional", all doors in the same general region will open at once with a single key, - reducing the amount of door items and complexity.""" + """ + If set to "none", there will be one key for each door, potentially resulting in upwards of 120 keys being added to the item pool. + If set to "regional", all doors in the same general region will open at once with a single key, reducing the amount of door items and complexity. + """ display_name = "Door Groupings" option_off = 0 option_regional = 1 class ShuffleBoat(DefaultOnToggle): - """If set, adds a "Boat" item to the item pool. Before receiving this item, you will not be able to use the boat.""" + """ + If on, adds a "Boat" item to the item pool. Before receiving this item, you will not be able to use the boat. + """ display_name = "Shuffle Boat" class ShuffleDiscardedPanels(Toggle): - """Add Discarded Panels into the location pool. - Solving certain Discarded Panels may still be necessary to beat the game, even if this is off - The main example - of this being the alternate activation triggers in disable_non_randomized.""" + """ + Adds Discarded Panels into the location pool. + Even if this is off, solving certain Discarded Panels may still be necessary to beat the game - The main example of this being the alternate activation triggers in "Disable non randomized puzzles". + """ display_name = "Shuffle Discarded Panels" class ShuffleVaultBoxes(Toggle): - """Add Vault Boxes to the location pool.""" + """ + Adds Vault Boxes to the location pool. + """ display_name = "Shuffle Vault Boxes" class ShuffleEnvironmentalPuzzles(Choice): """ - Add Environmental/Obelisk Puzzles into the location pool. - In "individual", every Environmental Puzzle sends an item. - In "obelisk_sides", completing every puzzle on one side of an Obelisk sends an item. - Note: In Obelisk Sides, any EPs excluded through another setting will be counted as pre-completed on their Obelisk. + Adds Environmental/Obelisk Puzzles into the location pool. + If set to "individual", every Environmental Puzzle sends an item. + If set to "Obelisk Sides", completing every puzzle on one side of an Obelisk sends an item. + + Note: In Obelisk Sides, any EPs excluded through another option will be pre-completed on their Obelisk. """ display_name = "Shuffle Environmental Puzzles" option_off = 0 @@ -102,17 +119,18 @@ class ShuffleEnvironmentalPuzzles(Choice): class ShuffleDog(Toggle): - """Add petting the Town dog into the location pool.""" - + """ + Adds petting the Town dog into the location pool. + """ display_name = "Pet the Dog" class EnvironmentalPuzzlesDifficulty(Choice): """ When "Shuffle Environmental Puzzles" is on, this setting governs which EPs are eligible for the location pool. - On "eclipse", every EP in the game is eligible, including the 1-hour-long "Theater Eclipse EP". - On "tedious", Theater Eclipse EP is excluded from the location pool. - On "normal", several other difficult or long EPs are excluded as well. + If set to "eclipse", every EP in the game is eligible, including the 1-hour-long "Theater Eclipse EP". + If set to "tedious", Theater Eclipse EP is excluded from the location pool. + If set to "normal", several other difficult or long EPs are excluded as well. """ display_name = "Environmental Puzzles Difficulty" option_normal = 0 @@ -123,26 +141,31 @@ class EnvironmentalPuzzlesDifficulty(Choice): class ObeliskKeys(DefaultOnToggle): """ Add one Obelisk Key item per Obelisk, locking you out of solving any of the associated Environmental Puzzles. + Does nothing if "Shuffle Environmental Puzzles" is set to "off". """ display_name = "Obelisk Keys" class ShufflePostgame(Toggle): - """Adds locations into the pool that are guaranteed to become accessible after or at the same time as your goal. - Use this if you don't play with release on victory. IMPORTANT NOTE: The possibility of your second - "Progressive Dots" showing up in the Caves is ignored, they will still be considered "postgame" in base settings.""" + """ + Adds locations into the pool that are guaranteed to become accessible after or at the same time as your goal. + Use this if you don't play with release on victory. + """ display_name = "Shuffle Postgame" class VictoryCondition(Choice): - """Set the victory condition for this world. + """ + Set the victory condition for this world. Elevator: Start the elevator at the bottom of the mountain (requires Mountain Lasers). Challenge: Beat the secret Challenge (requires Challenge Lasers). Mountain Box Short: Input the short solution to the Mountaintop Box (requires Mountain Lasers). Mountain Box Long: Input the long solution to the Mountaintop Box (requires Challenge Lasers). + It is important to note that while the Mountain Box requires Desert Laser to be redirected in Town for that laser - to count, the laser locks on the Elevator and Challenge Timer panels do not.""" + to count, the laser locks on the Elevator and Challenge Timer panels do not. + """ display_name = "Victory Condition" option_elevator = 0 option_challenge = 1 @@ -151,7 +174,9 @@ class VictoryCondition(Choice): class PuzzleRandomization(Choice): - """Puzzles in this randomizer are randomly generated. This setting changes the difficulty/types of puzzles.""" + """ + Puzzles in this randomizer are randomly generated. This option changes the difficulty/types of puzzles. + """ display_name = "Puzzle Randomization" option_sigma_normal = 0 option_sigma_expert = 1 @@ -159,10 +184,11 @@ class PuzzleRandomization(Choice): class MountainLasers(Range): - """Sets the amount of lasers required to enter the Mountain. - If set to a higher amount than 7, the mountaintop box will be slightly rotated to make it possible to solve without - the hatch being opened. - This change will also be applied logically to the long solution ("Challenge Lasers" setting).""" + """ + Sets the number of lasers required to enter the Mountain. + If set to a higher number than 7, the mountaintop box will be slightly rotated to make it possible to solve without the hatch being opened. + This change will also be applied logically to the long solution ("Challenge Lasers" option). + """ display_name = "Required Lasers for Mountain Entry" range_start = 1 range_end = 11 @@ -170,7 +196,9 @@ class MountainLasers(Range): class ChallengeLasers(Range): - """Sets the amount of beams required to enter the Caves through the Mountain Bottom Floor Discard.""" + """ + Sets the number of lasers required to enter the Caves through the Mountain Bottom Floor Discard and to unlock the Challenge Timer Panel. + """ display_name = "Required Lasers for Challenge" range_start = 1 range_end = 11 @@ -178,13 +206,17 @@ class ChallengeLasers(Range): class ElevatorsComeToYou(Toggle): - """If true, the Quarry Elevator, Bunker Elevator and Swamp Long Bridge will "come to you" if you approach them. - This does actually affect logic as it allows unintended backwards / early access into these areas.""" + """ + If on, the Quarry Elevator, Bunker Elevator and Swamp Long Bridge will "come to you" if you approach them. + This does actually affect logic as it allows unintended backwards / early access into these areas. + """ display_name = "All Bridges & Elevators come to you" class TrapPercentage(Range): - """Replaces junk items with traps, at the specified rate.""" + """ + Replaces junk items with traps, at the specified rate. + """ display_name = "Trap Percentage" range_start = 0 range_end = 100 @@ -192,10 +224,11 @@ class TrapPercentage(Range): class TrapWeights(OptionDict): - """Specify the weights determining how many copies of each trap item will be in your itempool. + """ + Specify the weights determining how many copies of each trap item will be in your itempool. If you don't want a specific type of trap, you can set the weight for it to 0 (Do not delete the entry outright!). - If you set all trap weights to 0, you will get no traps, bypassing the "Trap Percentage" option.""" - + If you set all trap weights to 0, you will get no traps, bypassing the "Trap Percentage" option. + """ display_name = "Trap Weights" schema = Schema({ trap_name: And(int, lambda n: n >= 0) @@ -210,8 +243,9 @@ class TrapWeights(OptionDict): class PuzzleSkipAmount(Range): - """Adds this number of Puzzle Skips into the pool, if there is room. Puzzle Skips let you skip one panel. - Works on most panels in the game - The only big exception is The Challenge.""" + """ + Adds this many Puzzle Skips into the pool, if there is room. Puzzle Skips let you skip one panel. + """ display_name = "Puzzle Skips" range_start = 0 range_end = 30 @@ -219,8 +253,10 @@ class PuzzleSkipAmount(Range): class HintAmount(Range): - """Adds hints to Audio Logs. If set to a low amount, up to 2 additional duplicates of each hint will be added. - Remaining Audio Logs will have junk hints.""" + """ + Adds hints to Audio Logs. If set to a low amount, up to 2 additional duplicates of each hint will be added. + Remaining Audio Logs will have junk hints. + """ display_name = "Hints on Audio Logs" range_start = 0 range_end = 49 @@ -228,11 +264,12 @@ class HintAmount(Range): class AreaHintPercentage(Range): - """There are two types of hints for The Witness. - "Location hints" hint one location in your world / containing an item for your world. - "Area hints" will tell you some general info about the items you can find in one of the - main geographic areas on the island. - Use this option to specify how many of your hints you want to be area hints. The rest will be location hints.""" + """ + There are two types of hints for The Witness. + "Location hints" hint one location in your world or one location containing an item for your world. + "Area hints" tell you some general info about the items you can find in one of the main geographic areas on the island. + Use this option to specify how many of your hints you want to be area hints. The rest will be location hints. + """ display_name = "Area Hint Percentage" range_start = 0 range_end = 100 @@ -240,20 +277,26 @@ class AreaHintPercentage(Range): class LaserHints(Toggle): - """If on, lasers will tell you where their items are if you walk close to them in-game. - Only applies if laser shuffle is enabled.""" + """ + If on, lasers will tell you where their items are if you walk close to them in-game. + Only applies if Laser Shuffle is enabled. + """ display_name = "Laser Hints" class DeathLink(Toggle): - """If on: Whenever you fail a puzzle (with some exceptions), everyone who is also on Death Link dies. - The effect of a "death" in The Witness is a Bonk Trap.""" + """ + If on, whenever you fail a puzzle (with some exceptions), you and everyone who is also on Death Link dies. + The effect of a "death" in The Witness is a Bonk Trap. + """ display_name = "Death Link" class DeathLinkAmnesty(Range): - """Number of panel fails to allow before sending a death through Death Link. - 0 means every panel fail will send a death, 1 means every other panel fail will send a death, etc.""" + """ + The number of panel fails to allow before sending a death through Death Link. + 0 means every panel fail will send a death, 1 means every other panel fail will send a death, etc. + """ display_name = "Death Link Amnesty" range_start = 0 range_end = 5 From da333fbb0c88feedd4821a7bade3f56028a02111 Mon Sep 17 00:00:00 2001 From: GodlFire <46984098+GodlFire@users.noreply.github.com> Date: Thu, 21 Mar 2024 09:52:16 -0600 Subject: [PATCH 164/166] Shivers: Adds missing logic rule for skull dial door location (#2997) --- worlds/shivers/Rules.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/worlds/shivers/Rules.py b/worlds/shivers/Rules.py index 8aa8aa2c28..b1abb718c2 100644 --- a/worlds/shivers/Rules.py +++ b/worlds/shivers/Rules.py @@ -71,6 +71,12 @@ def first_nine_ixupi_capturable(state: CollectionState, player: int) -> bool: and metal_capturable(state, player) +def all_skull_dials_available(state: CollectionState, player: int) -> bool: + return state.can_reach("Prehistoric", "Region", player) and state.can_reach("Tar River", "Region", player) \ + and state.can_reach("Egypt", "Region", player) and state.can_reach("Burial", "Region", player) \ + and state.can_reach("Gods Room", "Region", player) and state.can_reach("Werewolf", "Region", player) + + def get_rules_lookup(player: int): rules_lookup: Dict[str, List[Callable[[CollectionState], bool]]] = { "entrances": { @@ -116,10 +122,7 @@ def get_rules_lookup(player: int): "To Tar River From Lobby": lambda state: (state.has("Crawling", player) and oil_capturable(state, player) and state.can_reach("Tar River", "Region", player)), "To Burial From Egypt": lambda state: state.can_reach("Egypt", "Region", player), "To Gods Room From Anansi": lambda state: state.can_reach("Gods Room", "Region", player), - "To Slide Room": lambda state: ( - state.can_reach("Prehistoric", "Region", player) and state.can_reach("Tar River", "Region",player) and - state.can_reach("Egypt", "Region", player) and state.can_reach("Burial", "Region", player) and - state.can_reach("Gods Room", "Region", player) and state.can_reach("Werewolf", "Region", player)), + "To Slide Room": lambda state: all_skull_dials_available(state, player), "To Lobby From Slide Room": lambda state: (beths_body_available(state, player)) }, "locations_required": { @@ -141,6 +144,7 @@ def get_rules_lookup(player: int): "Final Riddle: Norse God Stone Message": lambda state: (state.can_reach("Fortune Teller", "Region", player) and state.can_reach("UFO", "Region", player)), "Final Riddle: Beth's Body Page 17": lambda state: beths_body_available(state, player), "Final Riddle: Guillotine Dropped": lambda state: beths_body_available(state, player), + "Puzzle Solved Skull Dial Door": lambda state: all_skull_dials_available(state, player), }, "locations_puzzle_hints": { "Puzzle Solved Clock Tower Door": lambda state: state.can_reach("Three Floor Elevator", "Region", player), From 40f843f54d5970302caeb2a21b76a4845cf5c0ed Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 21 Mar 2024 11:00:53 -0500 Subject: [PATCH 165/166] Lingo: Minor game data fixes (#3003) --- worlds/lingo/data/LL1.yaml | 20 ++++++++++++-------- worlds/lingo/data/generated.dat | Bin 130791 -> 130691 bytes worlds/lingo/data/ids.yaml | 5 +---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index 75f688268f..f2d2a9ff54 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -452,6 +452,7 @@ id: Shuffle Room/Panel_lost_found colors: black tag: botblack + check: True FORWARD: id: Entry Room/Panel_forward_forward tag: midwhite @@ -463,7 +464,12 @@ tag: midwhite doors: Crossroads Entrance: - id: Shuffle Room Area Doors/Door_chaos + id: + - Shuffle Room Area Doors/Door_chaos + - Shuffle Room Area Doors/Door_swap + - Shuffle Room Area Doors/Door_swap2 + - Shuffle Room Area Doors/Door_swap3 + - Shuffle Room Area Doors/Door_swap4 panels: - ORDER Tenacious Entrance: @@ -491,11 +497,6 @@ item_group: Achievement Room Entrances panels: - OPEN - Lost Door: - id: Shuffle Room Area Doors/Door_lost_found - junk_item: True - panels: - - LOST paintings: - id: maze_painting orientation: west @@ -699,6 +700,8 @@ door: Hollow Hallway tag: midwhite SWAP: + # In vanilla doors, solving this panel will open the way to Hub Room. This does not impact logic at all because + # Hub Room is always sphere 1 in vanilla doors. id: Shuffle Room/Panel_swap_wasp colors: yellow tag: midyellow @@ -1298,7 +1301,7 @@ id: Antonym Room/Panel_star_rats colors: black tag: midblack - TAME: + TUBE: id: Antonym Room/Panel_tame_mate colors: black tag: topblack @@ -1967,6 +1970,7 @@ door: Shortcut to Tower Rhyme Room (Smiley): door: Rhyme Room Entrance + Art Gallery: True # mark this as a warp in the sunwarps branch panels: RED: id: Color Arrow Room/Panel_red_afar @@ -4988,7 +4992,7 @@ colors: - red - blue - tag: mid red blue + tag: chain mid red blue required_panel: - room: Knight Night (Right Lower Segment) panel: ADJUST diff --git a/worlds/lingo/data/generated.dat b/worlds/lingo/data/generated.dat index 3bd8ff5a8b5fcd7ccfd60d32f34da3728e62c2d2..c957e5d51c895e444f1d5245bb9708a0a799cd14 100644 GIT binary patch delta 24324 zcmbt+33!x65-3fMnH=Q4ubBiA!kvp7gh?_<1|~Df%!B|68X!PMNF*VyAg3q`63&ja zC@6T}fUbfr2zaflE63`3;Oc_sdV{O1is-AV?w-s9bl>~l?kCe-U0q$>U0q#OU4K5k zU|4m*5WFJzmiW!Vy&WSOM-DG;C@dN|yRo=@d$Ro>4rzV8*b**~JaTBZf5=7R)Xvm{CyNFsxui$*jf^d)uEFz;`Bw z^3g+j@dN!#;`o!S*AtF`;fz&3F=fz5J6p{8qITnw$R0iNy=x+Q!~O)`XYoL`lGiRC zZjj(DeBvI9Rb7=~H>iWwRbT2=D{U8Ok zEXh&OKXdPrVt}4mk;KjWbNM?;@r*|bEzigC3_>!+&HVV2NSTQS z6^@&mZX5=XNjKURWIBKFMx5P8H`Xag6CZz5S-f13U{9UPp_U|)?@iC*f4Zqg;a$fg zR%^VFdZ(-0t=Fz$^;m_on%}#6C{TZQwN*hj^S*z?jjjHpUYOhY)<5hdh@00w7~zs|GchD^Wd;rT$;_84!u+c?&+|p zWe=&hxIDh)+|!}?OycJ{teTA7YN^m!fo$e*7L>?W zY$$}L$2Zg}jtE|!kNXFcjiU1

!S*>u^x%^g*e{rkc$P7Ms8|sT$+w@N5^GCJ~E07$A+06Ow z#%mj!{Z_FftgNNIb>X6h)>)k$%%(>^^HKuOyKTH0eGK1pdqSMMab`>NEOW)YmX_8| z$28RPo3{_)&)t@#&?@++x1nzG*F-Vgd%H=sdw@({w|1$i3V6kKO=UK}dV6;&7wNM^ zK52*Uu!LW~<9~LL3b2rGyTh!w*~$;!(f5+v{D$5f%9D5MsXWQY?z{pz-?nqA5|YEw zAS46Ct_v)KXY4A~TE=a$>ka2CUr6IO@0z4K9LElMJZnt~556lNXr#)tG_^ zhU$j4g^jI?lt?^q?=aOp8(Rm66GoQCgYUzo9(Z3CUP;WI91qvC(7w|qX5|HVLq+FoA1ZITj<}X?nj#i zWIC=}AE3%()026|15to5-~p#v)mePy1O1UzQ%M$o^?_cx_`?HHsu@wHSV4cRU~VD6ZMmM~7rB z|2^z?`2COiMsU|Soemr!Yt!%b`Ir|yHX4XGKUSQz>-P}P(xQCxSO|anF*9`d@v*|J z$A7QG1?q6c?%W7D=U|&lG4%ZJbqnLicMk{p#yutMG#|T1TjPm*_MRyKKfFiZq1oKL z7Y)24b^1zU;q>M4BYRW$na5GR1V2%)gtMB@ zcw)Tf);Zbg;8)#P$lre=Mzv4o-#<~Q6a&1DLE{mhmNAJ}qGw5W~RUZ6QKgIeQWIYF2kFz$-nY&QxE4Xp;y>KLV z_D$s1JrzrLn*Z(PEdWg>y$?7{9lfmc8s|4QgSNA@Ha2v6OZd}Im9WYD>!Oxi#>|0tKu4F(UmD6LIxK697Oe$dn|%X;rK+6DQPfBTHZAUg-~ z*!|ib_?EBzGiubmf1afbuwP<%=79nLISy#MB`glsA(LkxNagzuAojlw6sRRmz$Ozl zl^l%I3e{8Z^tUT6Kd5hl97OL!=uaO^9VGb)^HjO47nyGw+NFtqJvdN}T7+b>h)m71 z5&zF*68Vm2_2!lG7oN@4GKvO~zWB~Rn+7vYI%I->haJ-H-W0y(kWGvHRIAGgB45BG z59g|_YUCphqscz+u$Jx?{^a3YwJ^aIwnC%Z95L@?)f%^6S1^doqa z$&!I@Jfc07b^P@sdHTwB+a@c{MNJY*3Gb>?&z9#xx&1k#qSNvmTvMW^mZd}l`62Fl zu3SyyA^zoaeYF$h2G2@c!9XlMG5~Pr^JYH(d87;_zkBn}=kxK3JDrloV_pd3Up{Z_ zs%mZX8n1dGPY+}DvuR%|hiqYj(Tw#Idc4E6`~dI<>*eko6LcFjwXxGqU7IS_H@OML)wW> zgka{if0?ew^z+sEk-ylL%nptx(LX&So)3H(t;n*MuTrZ$ia+%7cz}HIvN_Fdkl_cT zwRvG{L-Wi=ZC_b=?q5p)Y3g6KzNq1k{S`w7pZ!(K+U30H71TRdzmm<``1)5;)Bv;j zBd?%I_T?)&g0p}ZzKWLEp}(by2p5asJ70}8t)j6zqTDU*3+I}&va~20=N_*_UXD1E#kwn5H2-8#bWpoD$B)cA{Q2WH#2Gg^Ntmaz z7;bnq=2FBy;*(D}5V4>pUEDN{#UZiGcJFv2*;khs{tRb-cLMjde;;kSQeuUbHq5-* z?9b!RC@(qu_}^3c3xCJp+1bDA*&FzP*C*hJr|XjWhS#;79G^g1@$Bn~{NvX#lo#_x zwrS6A(a)0D3*JcNjc=e?yWtI!>9oSOH_l!dz)e5I&gMfdPvocHn22Lvb$OEbc^ZQQ z!)yOBG$$^MW*b@2()tJR0RuXZl$~pVv4j7>;1b{IN$25jW~7zLUSSuFXXWGHq}AW- zO6T{znVvc0cj(vh_um9*5J#r59A5EOY}(@AB0G&Qc&h>#7;t%-I5?e!^Y`A0P2M8W zLtWt0Hq6sP(!@jFM#IYSX)3q96_vh&u<|>?V2X_auFpcg{Owx6Jn?pab=a5TjNzWU zDwUhxDb(=~hqa!cUX;kMc_&Os_8Pw8on=}9y8IPj?8)++ht-_jjdRcf2^dr02!-Or zESU1PlMA&=@2bP_Ti5*&d$J$5znhu(h9nj2FJbrbW$)s#diGsyCG6$DzFRv--pX)S z`BWzv79pi}OS#|e<>>J96BPzT)B?PYAX`~Lk6@S6sXY~b(#*mY0zN8ixZE-S=>-B!&U0fVdS%6vOUt5sp^+CsH_K7M4EDqFYctsh&FJz|tM@9@p} z*hiTWey5cW`3Uv-kdHDV{7xue-UaPCmHgO8`n}0!pGW?JjADcG5|&G@aR;2_a|P3_!>|66g?WprR@$W= z9GC$4D6>1u^tRA4^z_LuPyhGOMDryme#0C8J+?$r4E2CZ*iGA1I9>jcRBwsTv*U06 zyCiYIB}fKw(`WcrGwrjXy{oA`zazH3p?OwgYh&w0Hc>Qx@-uX)fBMX&79owhKOX{+ zRiEpZom~Fr=V(5j|9pmez9>in+kf2`ec-?RQIh*0t5ciUH2DY*YI1nZ@l*vKhzf9( zwD5lQ#XvP`88?5~8+T-Ri;2(ranNl^UC!6&#vE9d!kfR&O<5^d%23n3c=5c(Hho35@rS>550x_rsdCl@ZfP(C z!UILW#^Taj%!|M2lhSc%vVY_)-_)vDbI-s4d{DI|U_F8?zQ{N~^NlG}T$ z5T_DMcUhG+P|X`EEDn#w9WWC^mCfyO`o}Rr*+Cr7W%XT{vEQci`RBr6Ut+&a<0sFe zQv{`G!z7#2?)Tyl`|S*+x!%Tk58>Ho`|!KJy==_?4?{fjKRJo}o2+IKb7T5 z2x{zg)!iPNoORufWvIK%WvLH%p9+P3j!NF}Lth^HomM=I_x-L$zr>XK#YpKMuvIf2o7;iv<2P~A#S+R^m@RK zUUHZDGw0BT!JYs3oPL#=zz6;J3bjQ4gD$ZYT4QxMy0KGcw^f&FLEiaN8vp9QC}L2G zaC>~G4eF^;p%pH8+0s`wqVVZI=r^NgSdr<8s!O+V%DjEUdh)#%?PE zdrDspyrDIP&;3yvEKuC=V+2__ttl}bp*47B^vZ#m1MTnrsEs?G48q@=*Z!2mEkBL3 zXwCw&PI=3wl6kH8>dNzlD(WxMj{O4b)HTR`9XQj(h6L@ommRZU?b-F8&4+j(3 zV=Z<0NgEktE@$9HX7o^e(&trC=a33lolUD|$!&hMr5bEuExAOiyWRq~%3Z}R-z3}; z%;F+c;AC)0j~=&Fv)=gM)LX9jr3d{_#;K8t0j2}l4T1MXsmnUa<_Unojq3_zsoLVF zC2u*x60{#<@i?oy%ZkBatph76U;;*o7&!OP8ke)idMUg>i3x#h6rU9**GPC6%V4v` z0>%=RpidR+8H4l(ag4E>m{)A>$&!r0o+&lfPOq;9x1*|M`B3PITcRY24|}pWlNS~; ze_E&f|C+UH)~;O$X+#~tz^$W;m>u=6?pGPaQh?%BL974?2eEAVlPQh|v0ycpAqd=# zE&}CFAAzK(@M>o%6?_fqNLm?_BH^LAEhXr9rl3Aj7Clu0i^bS$%99m8p z*kwlW+du&*yhPXrwGjqT4Ukh)j!m>6n+#nn$Jkl0LASyZB}TW;ort9ZP)@M>kpu_eV9r7*(g_f5GxbLFybxyMu3kDV-bMUFoKyyW0-F(#JVsRr}&SK zfcGTV6mt!Dmo#Zaj)|KbEC}BKu=3gJ&age=N)Nz3s$?LEOP@0efEGgW}s*Hb!&ph$C%a z=?g-$FpiceHj9~!U_n|)HBifIab0t}5Jx);aI?sbryg)pvy2d?14kN`3j2$t@q~q| z0jJVhp--hryc$p9NTbNmBlH10voIylYUU@99G^0GSU6x3bhQpf`t~1i04eMbc9(@$Cnxt~k{6~b1qPH4h zO2RO-EFYe%%NF2wGyrVow4 zcE0>Ehn2(nOO=3=F$O~-0;w<5lVWNLts%A;g49W&1-01mrBAH@TxL~1wP_MRb#*cZ zgNaT0MiTebK`c)7G6aDcj1}7XZK+*5Z%ShZaXgi7C+sH>5F*c*uA{=E@`N-}@`b1r zx2KWvqw+UtuuxvH+YJ3PGkpEK>Ts3A;9d2SZuOzv>chI#hX>S=AEXryVL#A!B+{gm zz~!#Nf#_gw%plDHh>7Bv43g=;Wx&&roC~ZYrr)Xc!5SFuxImd~fI@-Gv1`#{y*C6& zYdx59>{LesCD=iQc1kAK$r@@Txe=63r|`w{EWa~UhIw_+Lc8)>7E6|W62+cm7H$lN z0N@nGlUWQj`NYEracKyCXp+lJM3RWyYa$|hdc!$`Nr#KCWH(%{NTW>BlbcJManMaf zon}@7L;EB7cC4Rl$W=0dI7a#Q4C51#XS10x1hRCY4&x39>j}c9br~dVfEm2b#=AY*-Ld*Je)%^MqQyCp?IKrzux|&D#5Cj{)!?6o2eFUVs9@duQg z!O_&*h@3#XNqm?`LQNM^L|=DqR@!r5;D(1{gdC#OFn4O}-=8GI zIRMO?pZl{+B^h0HDZjqDIMtt2oCcs_uFG+th@DP4{Qw#PEnvPTiX8(;**!9V)c9)y zSiktcP}*XCSDqlSLkiG7&}XqEw0n=r>_%T+pTx4p{o^;_|NIU3hra>;{F3nU*txsW z!u21P)@s6Fng~oPSzI%ig@j^K67Ym_9~MHV1lRSh!L;dnTbU^=#8T#jj|2d^vK1sT zXNb?-G0Ln5Xn{>Dj`Q}Re6suwl{g4+8uPIsdhe3Ou_2_N2>z81o*38eRcV7ErC=0m zpDHn~&8sp5LqK1Frw=qik!HoVc~vqXcm$9pkIccv3+O8KF9hr%snS6(O)+g=mEH#` znuwn$0H{jhgBKS_(L)*yvsreWO4BpQKp@FD2&oBZtGqmTa6lVb>))%ovY5JWQo-7U)!4Xw0n?7U4s17w3nP!GvC`)wjh*WyIL$*~08gCet zDyA1Rihq4QDn;B_%ra9>s~wDlz+suQ+G;LUHXm)+v0~cD>^nht&J^n_C{e_gfOV?; zpot}P81=CZ6N^hobAg1UiaoO#UWK}xF0zbd5fT6#uQV}ks0I}I-5sljl9TYvP?Ab% z@L^sc?HPJsV}=noD6eA9Fsc65vA$yaFcNnV8IyQL;$S(#Py>-9jBKOe0Ae^STs1h` zSO@Z-O9|ST}MAADo|d<-chg$E5%Er zzo?Apq*dXXaykwXiaqBxQmZ=vltVqsYk<>1`dl{*fNSw?rA z8@|OuQUZK5$W(WG+!q70^RLy+1)vH<6mksX9KQ8L^fcFLyl_ZPuk z++D_o^zu6+0Q#bg6?X%zpTIH`Lx2XnEp{U?t^6Va+ses6BHehZoF+kHo?)eR&9ky0 zFt15gRtP_5tf1|7TWRNY*%%@XAE*xo6jKH3+w;G^9S2!Sh=T~)8WqYc#jk~6@c?cL zr%V-T#-mJyh4|K%Oivr62?ydz!K56rJB01PoSF& zRc(q&zoi9D@kNqBNI)X;y9vp26UZ-xZ6_U(Dl#UL{P_T3K3@MXFH#X@r^O-_I-=0S<2kf!awf%yfjRiPe=PA{6-Jw$I3i({Du;BKwl`E|wadqsK zwx`M_%{Y<)Ot1ky$h(5f&2o(itClUsIcSOHjpnjqJIRQH&V?jhRo4|Uhaj@N(M~!j z)cAvg=9J>33uL&HIU@Wvp4YyZDjsx_KlP@QO@>*5d6p2<6;$r29CC18q9f#sxmkO& zIJ($cL$MPoAFT0N{a`4#A&vruLXmI0Y4mLXz1(zvO9>&}>9fl&mBtDg)sX3-vRNUw zwqH;9w(Zd0a{SVwRl+T%O=2;#o<#tX&U}S@R3mT^4D$i?hgOw1M2-E>{i^<4p`jyjX5mBq7el2E6a4Nj0xMP26zKf}X}@ZMsu zhXmVE&+?<&C?hz(gR;c^1Tj?{^3btCEVfFV^{|9)=L2}_EjHD$tTb>aF*||M61xqk zkubuuY!ax(_v@JPVnQ_GaFIBPnW8D{81C=xETtLXEtQlL5E|T##?t`Gq?Ban=`$hy zkGS3qd<4QtB|NI8Zgf{c7ET#OJEfnrv!3J~v0{%AmM@(dmgsan*v?&v)nd?OmX!IP zMEi{(rrJi#JO7PXW*>3yEEY4k&HIHU1i>5^YHvA%?%$|1;QJSB=a4CET>LrN!&q*! zRKqu6(odR#VQM|)D6wxQiv|BESZ4nQyPdw6B)UyG3S3$zEj{$qQrdKWbA-)N21zsU zTrO=JyuiVaQ+dt)eq8hdMTl}lV5yLhcz!BrE9@q|pGsyE!eLyQx(qw*aZ-d3E|GvY zp4fKp%bGR(xCj*#_?$4fN@E7of;yrT8U157R0Og(QE&zM2{?Rwo3~J=$-;FA&!D8m z(AA7rY0AbLOPLMcZ%N9iL1iTy;YHD`!|tGF&>vPg$RSl=3z3P71Lmnz;|R;lxlNF5 zjA^~TZNy!Zj}Kga{}UbrPZ2t;oZL*vhK>Zu2j5tEj*#+9d0aXh8+}ogupW!$y_1r~ zgz3^>h0i6$HPcDC)c&In(N<~Dlu;pbvLgsC6U*aJ-(+10CmeIt`%{a`n1@spvHeO` z9ILaULuBSE>;c{T%Kw371q-hfQr4hBRv-x)D{#l;1pC10GT+Twt|D6jCsXDyM~cb@ zmLUFn6^jZy+}y9-oOcOL9za{I+xiAJJdlhy(ZJl@K!r1yBM^jxiC!~Vq&PBzC3bVS zbR*?uoh%W~nJmLBlej5xr~Wd6KDO$Vo}S6ZikE?8;79`C<4lK0Ai-O)lq=h-YiaQP zW5F!eFAVGzH7%%K%y6n`d6h+r?`CzkVSwMRws>*H6c&@{b8))Hfnk6bvpC-fRQ5Ms zG;Loi3I$;foXvX2sg!fb@&Th!Uif%s5Kd3Li5E{bLoO=n#<6rhM#iqfDL#YFHD~=F{{MO>CUcLS>hJ z;?R6rdyK}!w|f_c%a8?pf{b~P&>1y>FgMHn17&ZZZ!Vz$jZM+g%tC;Y1)f5KyvxIN zCUySy=0GjwAhl9&N5RuMSW>jN6m9aj<^4!_5KvOl9UKsRqB6Q32ocPQE-g zi11djbn&VYqgv@=#miK*w9*I=7)Doghj;IQj)ND{_!NcTv5C$EL)#_!(-lbB5 zL*PvV63C%H`o57}NyQ2xew7pe`$p6t+2|N1|<&}mNR%DgDs|I@qr*nBlCBGnhyvMY4|CI9|%)xq%&VC zbGJ2<^OozS1FcIL+tiPkKD?at7HOgPmXpBBiK;wpNgwYPLLV@eu173K`0}>(I7e3y zEu3(?SLb1anx_P7jnr=??G{jgFg|5e%ND3nHnqY7MrFz~)FH;6W1wtjP2}g5zGd&_ zmwF7%WL9`?ptFEn8iFRmw@SL-BRBX~0<)p$9g^mC;RXuF#IBOrQWUb+DZ|Ecr&A`} z0-3$5LCUtTqT%t`6|OOeo5;Kftc$N!(GZ9i0pCeE;2R){!9YeECcMET*Pw zK&~PSMSM#~$ksp7(=IhieyJ!)P|>fqk5Un-6y*4Y|b4*1Daqfk-uO-0Ev73D}& zlp|45jzmQ{5*6i0RP>FM8(LJLN6i(#u9bEQ>{f)K6rZD|l{@%mHl~{`|A(7dL9*Y? zDpSnBHUpDU?4AiB#Al2o3UDWmK(8xv9mVrdDf;q&-~ntLEZ>oJWSe0Vy!n>b!QTY} zc~Y-q7}_T>#b)8{AO$L^I91Fap3y+1!8=ZkJobe7Rfp7f_!yV9o|HCH6pr;i3iyg# zDdYC*>Hfx!nA+s5BL?8n89t%5h+UhsLr*Ui_~IxZ55Q8vd}U-7p9Gd|klGzz5GwEn zv??nOZIPHr_{&@6R>;3pVIy&R15KL(Sp^$uvB+qdyphf*l~?=9Sw_s$p(GW01hIET zCrk!gr3zGUVttg*6mFuJH85%lv$-r4@W-@}Pq=wtPE~qNa7~a2irI~1)9F|zRjiej zbQ$1f5(T;Cl~#`hK5vj0#Ac$8O{j$-bKHj-j$a*3@}XsmUpGtj2e<}GlLt=7aCL6+ zYXF-JS;UdWom&{%-o%B=sYqjD=Qmqupy`=p-)*%f0kB1M)Qjb{LO3{N#$`oibAmADDcf5CH2$ zBI6bxIanC^;Q+Krze#>IpsK#LK)H*M-wQ~?1fo9XF!iYbAVU@8lD-qb8fFB^1b)dH z4eQhi<8SWX|p*@6@pdU+iYp1Do%haBXe>GR4~adsC=mA$rzn7det zE)VuA+wWp&8b0qXRw8-hL|Itwf&G8$-#>EY;U@%ss3`a>>QD_poAJ z-hGc=i_YI&i%!3_==58QPEm9(8#8*&+(UCudM7g2p(OZc?lf;9{#@ZQex^ay>$_39>{&mLVjEAS)!~282WsWR-;6gpl3@SuG)J5YmSrYYCF?U5B7t z0C_tSu$YI%^;qhQr48bd@0h7{BLW5>U=x<^hEltCGZcYcj*!6sY4dhU$X0|5A&4MI zzIU7G^*tL~ce_N`fe0mpaEFBKM96T0>>^0M_ihA@0Fd_{ERBLvyZ2sd^xlt<(S-iM zNM3tef_UP4)|B-SwdQ*t!R9jT`Y4vH)b%kj>Kq$dum>Sk1lddI-X{<;fr$M{LY_j% zM1t%Scb;QOhG(cDpEvYO5XaB4jO7O;=Cg=dLzssoj;`mpf@DsO@vG#$XgQf4nn395t63Jcz2^5crvJJs`As zLluG%K})cA7?ze&?{I4LMj~Vxq4$!IXoM^$NQ{KUA!G$X;w2;zAvX{tNkURA2wFv; zR0&E)$c+TakdQ2d+(ZzQgk&RRH9>O3w4a!{wvTMc!-h4~&{snGA!IE<`b)?_gxpM! zK@u_qA?pZIK#+WIkvRGjGxsbWMTau@JR84!7-DS!EJ*DpEbl1!GgkhLr=Jer*XN#w z);Wy}J12A!;WCM2g`Wwx5Trsvst~f3AU1;JdnY1D0LW{{(ydskZouL;D7JeYgy*e6 s$ZgcKRzlnext$;$37LeD?F6ZpkSPe+L6E5ua;5xf*w^Q0_I%I(14?@IcK`qY delta 24677 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 diff --git a/worlds/lingo/data/ids.yaml b/worlds/lingo/data/ids.yaml index 4cad948555..d3307deaa3 100644 --- a/worlds/lingo/data/ids.yaml +++ b/worlds/lingo/data/ids.yaml @@ -165,7 +165,7 @@ panels: THAT: 444525 STRESSED: 444526 STAR: 444527 - TAME: 444528 + TUBE: 444528 CAT: 444529 Hedge Maze: DOWN: 444530 @@ -995,9 +995,6 @@ doors: Traveled Entrance: item: 444433 location: 444438 - Lost Door: - item: 444435 - location: 444440 Pilgrim Antechamber: Sun Painting: item: 444436 From 4196bde597cdbb6186ff614294fd54ff043a0c99 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:38:36 -0400 Subject: [PATCH 166/166] Docs: Fixing special_range_names example (#3005) --- docs/options api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/options api.md b/docs/options api.md index 1141528991..6205089f3d 100644 --- a/docs/options api.md +++ b/docs/options api.md @@ -204,7 +204,7 @@ For example: ```python range_start = 1 range_end = 99 -special_range_names: { +special_range_names = { "normal": 20, "extreme": 99, "unlimited": -1,

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