The walrus is moved a bit, so that you can access the desert without taking Marin on a date.
Depending on your settings, you can only steal after you find the sword, always, or never.
+Depending on your options, you can only steal after you find the sword, always, or never.
Do not forget that there are two items in the rafting ride. You can access this with just Hookshot or Flippers.
Killing enemies with bombs is in normal logic. You can switch to casual logic if you do not want this.
D7 confuses some people, but by dropping down pits on the 2nd floor you can access almost all of this dungeon, even without feather and power bracelet.
diff --git a/worlds/ladx/docs/setup_en.md b/worlds/ladx/docs/setup_en.md index aad077d73037..d12f9b8b3b84 100644 --- a/worlds/ladx/docs/setup_en.md +++ b/worlds/ladx/docs/setup_en.md @@ -35,8 +35,8 @@ options. ### Where do I get a config file? -The [Player Settings](/games/Links%20Awakening%20DX/player-settings) page on the website allows you to configure -your personal settings and export a config file from them. +The [Player Options](/games/Links%20Awakening%20DX/player-options) page on the website allows you to configure +your personal options and export a config file from them. ### Verifying your config file @@ -45,7 +45,7 @@ If you would like to validate your config file to make sure it works, you may do ## Generating a Single-Player Game -1. Navigate to the [Player Settings](/games/Links%20Awakening%20DX/player-settings) page, configure your options, +1. Navigate to the [Player Options](/games/Links%20Awakening%20DX/player-options) 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. diff --git a/worlds/landstalker/docs/en_Landstalker - The Treasures of King Nole.md b/worlds/landstalker/docs/en_Landstalker - The Treasures of King Nole.md index 90a79f8bd986..9239f741b436 100644 --- a/worlds/landstalker/docs/en_Landstalker - The Treasures of King Nole.md +++ b/worlds/landstalker/docs/en_Landstalker - The Treasures of King Nole.md @@ -1,8 +1,8 @@ # Landstalker: The Treasures of King Nole -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains most of the options you need to +The [player options page for this game](../player-options) contains most of the options you need to configure and export a config file. ## What does randomization do to this game? @@ -35,7 +35,7 @@ All key doors are gone, except three of them : The secondary shop of Mercator requiring to do the traders sidequest in the original game is now unlocked by having **Buyer Card** in your inventory. -You will need as many **jewels** as specified in the settings to use the teleporter to go to Kazalt and the final dungeon. +You will need as many **jewels** as specified in the options to use the teleporter to go to Kazalt and the final dungeon. If you find and use the **Lithograph**, it will tell you in which world are each one of your jewels. Each seed, there is a random dungeon which is chosen to be the "dark dungeon" where you won't see anything unless you @@ -54,7 +54,7 @@ be significantly harder, both combat-wise and logic-wise. Having fully open & shuffled teleportation trees is an interesting way to play, but is discouraged for beginners as well since it can force you to go in late-game zones with few Life Stocks. -Overall, the default settings are good for a beginner-friendly seed, and if you don't feel too confident, you can also +Overall, the default options are good for a beginner-friendly seed, and if you don't feel too confident, you can also lower the combat difficulty to make it more forgiving. *Have fun on your adventure!* diff --git a/worlds/landstalker/docs/landstalker_setup_en.md b/worlds/landstalker/docs/landstalker_setup_en.md index 32e46a4b3354..30f85dd8f19b 100644 --- a/worlds/landstalker/docs/landstalker_setup_en.md +++ b/worlds/landstalker/docs/landstalker_setup_en.md @@ -30,8 +30,10 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The [Player Settings Page](/games/Landstalker%20-%20The%20Treasures%20of%20King%20Nole/player-settings) on the website allows -you to easily configure your personal settings + +The [Player Options Page](/games/Landstalker%20-%20The%20Treasures%20of%20King%20Nole/player-options) on the website allows +you to easily configure your personal options. + ## How-to-play diff --git a/worlds/lingo/__init__.py b/worlds/lingo/__init__.py index e35a1026b731..b749418368d1 100644 --- a/worlds/lingo/__init__.py +++ b/worlds/lingo/__init__.py @@ -6,8 +6,8 @@ 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, TRAP_ITEMS, 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 @@ -89,10 +91,23 @@ def create_items(self): pool.append(self.create_item("Puzzle Skip")) if traps: - traps_list = ["Slowness Trap", "Iceland Trap", "Atbash Trap"] - - for i in range(0, traps): - pool.append(self.create_item(traps_list[i % len(traps_list)])) + total_weight = sum(self.options.trap_weights.values()) + + if total_weight == 0: + raise Exception("Sum of trap weights must be at least one.") + + trap_counts = {name: int(weight * traps / total_weight) + for name, weight in self.options.trap_weights.items()} + + trap_difference = traps - sum(trap_counts.values()) + if trap_difference > 0: + allowed_traps = [name for name in TRAP_ITEMS if self.options.trap_weights[name] > 0] + for i in range(0, trap_difference): + trap_counts[allowed_traps[i % len(allowed_traps)]] += 1 + + for name, count in trap_counts.items(): + for i in range(0, count): + pool.append(self.create_item(name)) self.multiworld.itempool += pool diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index f72e63c1427e..f2d2a9ff5448 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) @@ -450,6 +452,7 @@ id: Shuffle Room/Panel_lost_found colors: black tag: botblack + check: True FORWARD: id: Entry Room/Panel_forward_forward tag: midwhite @@ -461,37 +464,39 @@ 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: 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: - id: Shuffle Room Area Doors/Door_lost_found - junk_item: True - panels: - - LOST paintings: - id: maze_painting orientation: west @@ -546,6 +551,7 @@ doors: Sun Painting: item_name: Pilgrim Room - Sun Painting + item_group: Paintings location_name: Pilgrim Room - HOT CRUST painting_id: pilgrim_painting2 panels: @@ -694,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 @@ -715,12 +723,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 +739,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 +754,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 +944,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 +975,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 +1002,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 +1029,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 +1166,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 +1242,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: @@ -1287,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 @@ -1298,7 +1312,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 +1406,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 +1424,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 +1491,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 +1543,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 +1698,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 +1782,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 +1933,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 @@ -1952,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 @@ -1967,7 +1986,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 +1994,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 +2008,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 +2169,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 +2423,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 +2436,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 +2490,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 +2511,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 +2532,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 +2552,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 +2572,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 +2592,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 +2612,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 +2632,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 +2652,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 +2672,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 +2692,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 +2788,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 +2813,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 +2936,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 +2951,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 +2961,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 +2973,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 +2993,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 +3211,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 +3328,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 +3562,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 +3571,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 +3585,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 +3595,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 +3614,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 +3626,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 +3639,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 +3786,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 +3800,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 +3823,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 +3849,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 +3877,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 +3900,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 +4069,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 +4270,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: @@ -4952,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 @@ -5223,7 +5263,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 +5373,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 +5484,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 +5597,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 +5761,7 @@ doors: Exit: id: Count Up Room Area Doors/Door_near_near - group: Crossroads Doors + door_group: Crossroads Doors panels: - NEAR paintings: @@ -5762,6 +5802,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 +5823,7 @@ - arrows_painting2 skip_location: True item_name: Starting Room - Symmetry Painting + item_group: Paintings panels: - room: Outside The Wondrous panel: SHRINK @@ -5886,6 +5928,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 +5998,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 +6035,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 +6057,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 +6125,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 +6266,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 +6462,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 +6563,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 +6626,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 +6709,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 +6728,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 +6747,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 +6810,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 +7061,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 +7315,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 +7751,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 49ea60df4183..c957e5d51c89 100644 Binary files a/worlds/lingo/data/generated.dat and b/worlds/lingo/data/generated.dat differ diff --git a/worlds/lingo/data/ids.yaml b/worlds/lingo/data/ids.yaml index 4cad94855512..d3307deaa300 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 diff --git a/worlds/lingo/datatypes.py b/worlds/lingo/datatypes.py index eb5c8797d40b..e9bf0a378039 100644 --- a/worlds/lingo/datatypes.py +++ b/worlds/lingo/datatypes.py @@ -32,9 +32,10 @@ class Door(NamedTuple): has_doors: bool painting_ids: List[str] event: bool - group: Optional[str] + door_group: Optional[str] include_reduce: bool junk_item: bool + item_group: Optional[str] class Panel(NamedTuple): diff --git a/worlds/lingo/docs/en_Lingo.md b/worlds/lingo/docs/en_Lingo.md index cff0581d9b2f..c7e1bfc8192e 100644 --- a/worlds/lingo/docs/en_Lingo.md +++ b/worlds/lingo/docs/en_Lingo.md @@ -1,8 +1,8 @@ # Lingo -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/lingo/items.py b/worlds/lingo/items.py index 623cd79e96c3..7c7928cbab68 100644 --- a/worlds/lingo/items.py +++ b/worlds/lingo/items.py @@ -1,13 +1,9 @@ from typing import Dict, List, NamedTuple, Optional, TYPE_CHECKING from BaseClasses import Item, ItemClassification -from .options import ShuffleDoors from .static_logic import DOORS_BY_ROOM, PROGRESSION_BY_ROOM, PROGRESSIVE_ITEMS, get_door_group_item_id, \ get_door_item_id, get_progressive_item_id, get_special_item_id -if TYPE_CHECKING: - from . import LingoWorld - class ItemData(NamedTuple): """ @@ -19,20 +15,6 @@ class ItemData(NamedTuple): has_doors: bool painting_ids: List[str] - def should_include(self, world: "LingoWorld") -> bool: - if self.mode == "colors": - return world.options.shuffle_colors > 0 - elif self.mode == "doors": - return world.options.shuffle_doors != ShuffleDoors.option_none - elif self.mode == "complex door": - return world.options.shuffle_doors == ShuffleDoors.option_complex - elif self.mode == "door group": - return world.options.shuffle_doors == ShuffleDoors.option_simple - elif self.mode == "special": - return False - else: - return True - class LingoItem(Item): """ @@ -42,14 +24,18 @@ class LingoItem(Item): ALL_ITEM_TABLE: Dict[str, ItemData] = {} +ITEMS_BY_GROUP: Dict[str, List[str]] = {} + +TRAP_ITEMS: List[str] = ["Slowness Trap", "Iceland Trap", "Atbash Trap"] def load_item_data(): - global ALL_ITEM_TABLE + global ALL_ITEM_TABLE, ITEMS_BY_GROUP for color in ["Black", "Red", "Blue", "Yellow", "Green", "Orange", "Gray", "Brown", "Purple"]: ALL_ITEM_TABLE[color] = ItemData(get_special_item_id(color), ItemClassification.progression, "colors", [], []) + ITEMS_BY_GROUP.setdefault("Colors", []).append(color) door_groups: Dict[str, List[str]] = {} for room_name, doors in DOORS_BY_ROOM.items(): @@ -57,11 +43,11 @@ def load_item_data(): if door.skip_item is True or door.event is True: continue - if door.group is None: + if door.door_group is None: door_mode = "doors" else: door_mode = "complex door" - door_groups.setdefault(door.group, []) + door_groups.setdefault(door.door_group, []) if room_name in PROGRESSION_BY_ROOM and door_name in PROGRESSION_BY_ROOM[room_name]: door_mode = "special" @@ -70,19 +56,22 @@ def load_item_data(): ItemData(get_door_item_id(room_name, door_name), ItemClassification.filler if door.junk_item else ItemClassification.progression, door_mode, door.has_doors, door.painting_ids) + ITEMS_BY_GROUP.setdefault("Doors", []).append(door.item_name) + + if door.item_group is not None: + ITEMS_BY_GROUP.setdefault(door.item_group, []).append(door.item_name) for group, group_door_ids in door_groups.items(): ALL_ITEM_TABLE[group] = ItemData(get_door_group_item_id(group), ItemClassification.progression, "door group", True, []) + ITEMS_BY_GROUP.setdefault("Doors", []).append(group) special_items: Dict[str, ItemClassification] = { ":)": ItemClassification.filler, "The Feeling of Being Lost": ItemClassification.filler, "Wanderlust": ItemClassification.filler, "Empty White Hallways": ItemClassification.filler, - "Slowness Trap": ItemClassification.trap, - "Iceland Trap": ItemClassification.trap, - "Atbash Trap": ItemClassification.trap, + **{trap_name: ItemClassification.trap for trap_name in TRAP_ITEMS}, "Puzzle Skip": ItemClassification.useful, } @@ -90,6 +79,11 @@ def load_item_data(): ALL_ITEM_TABLE[item_name] = ItemData(get_special_item_id(item_name), classification, "special", False, []) + if classification == ItemClassification.filler: + ITEMS_BY_GROUP.setdefault("Junk", []).append(item_name) + elif classification == ItemClassification.trap: + ITEMS_BY_GROUP.setdefault("Traps", []).append(item_name) + for item_name in PROGRESSIVE_ITEMS: ALL_ITEM_TABLE[item_name] = ItemData(get_progressive_item_id(item_name), ItemClassification.progression, "special", False, []) diff --git a/worlds/lingo/locations.py b/worlds/lingo/locations.py index e66ebac68198..92ee309487a5 100644 --- a/worlds/lingo/locations.py +++ b/worlds/lingo/locations.py @@ -30,10 +30,11 @@ class LingoLocation(Location): ALL_LOCATION_TABLE: Dict[str, LocationData] = {} +LOCATIONS_BY_GROUP: Dict[str, List[str]] = {} def load_location_data(): - global ALL_LOCATION_TABLE + global ALL_LOCATION_TABLE, LOCATIONS_BY_GROUP for room_name, panels in PANELS_BY_ROOM.items(): for panel_name, panel in panels.items(): @@ -50,6 +51,9 @@ def load_location_data(): LocationData(get_panel_location_id(room_name, panel_name), room_name, [RoomAndPanel(None, panel_name)], classification) + if panel.achievement: + LOCATIONS_BY_GROUP.setdefault("Achievements", []).append(location_name) + for room_name, doors in DOORS_BY_ROOM.items(): for door_name, door in doors.items(): if door.skip_location or door.event or door.panels is None: diff --git a/worlds/lingo/options.py b/worlds/lingo/options.py index ed1426450eb7..293992ab91d6 100644 --- a/worlds/lingo/options.py +++ b/worlds/lingo/options.py @@ -1,6 +1,9 @@ from dataclasses import dataclass -from Options import Toggle, Choice, DefaultOnToggle, Range, PerGameCommonOptions, StartInventoryPool +from schema import And, Schema + +from Options import Toggle, Choice, DefaultOnToggle, Range, PerGameCommonOptions, StartInventoryPool, OptionDict +from worlds.lingo.items import TRAP_ITEMS class ShuffleDoors(Choice): @@ -107,6 +110,14 @@ class TrapPercentage(Range): default = 20 +class TrapWeights(OptionDict): + """Specify the distribution of traps that should be placed into the pool. + If you don't want a specific type of trap, set the weight to zero.""" + display_name = "Trap Weights" + schema = Schema({trap_name: And(int, lambda n: n >= 0) for trap_name in TRAP_ITEMS}) + default = {trap_name: 1 for trap_name in TRAP_ITEMS} + + class PuzzleSkipPercentage(Range): """Replaces junk items with puzzle skips, at the specified rate.""" display_name = "Puzzle Skip Percentage" @@ -134,6 +145,7 @@ class LingoOptions(PerGameCommonOptions): level_2_requirement: Level2Requirement early_color_hallways: EarlyColorHallways trap_percentage: TrapPercentage + trap_weights: TrapWeights puzzle_skip_percentage: PuzzleSkipPercentage death_link: DeathLink start_inventory_from_pool: StartInventoryPool diff --git a/worlds/lingo/player_logic.py b/worlds/lingo/player_logic.py index f5eb986a5c4d..966f5a163762 100644 --- a/worlds/lingo/player_logic.py +++ b/worlds/lingo/player_logic.py @@ -2,7 +2,7 @@ from typing import Dict, List, NamedTuple, Optional, Set, Tuple, TYPE_CHECKING from .datatypes import Door, RoomAndDoor, RoomAndPanel -from .items import ALL_ITEM_TABLE +from .items import ALL_ITEM_TABLE, ItemData from .locations import ALL_LOCATION_TABLE, LocationClassification from .options import LocationChecks, ShuffleDoors, VictoryCondition from .static_logic import DOORS_BY_ROOM, PAINTINGS, PAINTING_ENTRANCES, PAINTING_EXITS, \ @@ -58,6 +58,21 @@ def should_split_progression(progression_name: str, world: "LingoWorld") -> Prog return ProgressiveItemBehavior.PROGRESSIVE +def should_include_item(item: ItemData, world: "LingoWorld") -> bool: + if item.mode == "colors": + return world.options.shuffle_colors > 0 + elif item.mode == "doors": + return world.options.shuffle_doors != ShuffleDoors.option_none + elif item.mode == "complex door": + return world.options.shuffle_doors == ShuffleDoors.option_complex + elif item.mode == "door group": + return world.options.shuffle_doors == ShuffleDoors.option_simple + elif item.mode == "special": + return False + else: + return True + + class LingoPlayerLogic: """ Defines logic after a player's options have been applied @@ -150,9 +165,9 @@ def __init__(self, world: "LingoWorld"): for room_name, room_data in DOORS_BY_ROOM.items(): for door_name, door_data in room_data.items(): if door_data.skip_item is False and door_data.event is False: - if door_data.group is not None and door_shuffle == ShuffleDoors.option_simple: + if door_data.door_group is not None and door_shuffle == ShuffleDoors.option_simple: # Grouped doors are handled differently if shuffle doors is on simple. - self.set_door_item(room_name, door_name, door_data.group) + self.set_door_item(room_name, door_name, door_data.door_group) else: self.handle_non_grouped_door(room_name, door_data, world) @@ -212,7 +227,7 @@ def __init__(self, world: "LingoWorld"): # Instantiate all real items. for name, item in ALL_ITEM_TABLE.items(): - if item.should_include(world): + if should_include_item(item, world): self.real_items.append(name) # Calculate the requirements for the fake pilgrimage. diff --git a/worlds/lingo/utils/pickle_static_data.py b/worlds/lingo/utils/pickle_static_data.py index c7a2711d06c1..5d6fa1e68328 100644 --- a/worlds/lingo/utils/pickle_static_data.py +++ b/worlds/lingo/utils/pickle_static_data.py @@ -255,10 +255,15 @@ def process_door(room_name, door_name, door_data): else: junk_item = False - if "group" in door_data: - group = door_data["group"] + if "door_group" in door_data: + door_group = door_data["door_group"] else: - group = None + door_group = None + + if "item_group" in door_data: + item_group = door_data["item_group"] + else: + item_group = None # panels is a list of panels. Each panel can either be a simple string (the name of a panel in the current room) or # a dictionary specifying a panel in a different room. @@ -308,7 +313,7 @@ def process_door(room_name, door_name, door_data): painting_ids = [] door_obj = Door(door_name, item_name, location_name, panels, skip_location, skip_item, has_doors, - painting_ids, event, group, include_reduce, junk_item) + painting_ids, event, door_group, include_reduce, junk_item, item_group) DOORS_BY_ROOM[room_name][door_name] = door_obj diff --git a/worlds/lingo/utils/validate_config.rb b/worlds/lingo/utils/validate_config.rb index 96ed9fcd66b9..ae0ac61cdb1b 100644 --- a/worlds/lingo/utils/validate_config.rb +++ b/worlds/lingo/utils/validate_config.rb @@ -42,7 +42,7 @@ directives = Set["entrances", "panels", "doors", "paintings", "progression"] panel_directives = Set["id", "required_room", "required_door", "required_panel", "colors", "check", "exclude_reduce", "tag", "link", "subtag", "achievement", "copy_to_sign", "non_counting", "hunt"] -door_directives = Set["id", "painting_id", "panels", "item_name", "location_name", "skip_location", "skip_item", "group", "include_reduce", "junk_item", "event"] +door_directives = Set["id", "painting_id", "panels", "item_name", "item_group", "location_name", "skip_location", "skip_item", "door_group", "include_reduce", "junk_item", "event"] painting_directives = Set["id", "enter_only", "exit_only", "orientation", "required_door", "required", "required_when_no_doors", "move", "req_blocked", "req_blocked_when_no_doors"] non_counting = 0 diff --git a/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md b/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md index d24c4ef9f9af..1080a77d54f4 100644 --- a/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md +++ b/worlds/lufia2ac/docs/en_Lufia II Ancient Cave.md @@ -1,8 +1,8 @@ # Lufia II - Rise of the Sinistrals (Ancient Cave) -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/lufia2ac/docs/setup_en.md b/worlds/lufia2ac/docs/setup_en.md index 4d4ea811abcf..d82853d4fddf 100644 --- a/worlds/lufia2ac/docs/setup_en.md +++ b/worlds/lufia2ac/docs/setup_en.md @@ -39,8 +39,8 @@ options. ### Where do I get a config file? -The [Player Settings](/games/Lufia%20II%20Ancient%20Cave/player-settings) page on the website allows you to configure -your personal settings and export a config file from them. +The [Player Options](/games/Lufia%20II%20Ancient%20Cave/player-options) page on the website allows you to configure +your personal options and export a config file from them. ### Verifying your config file @@ -49,7 +49,7 @@ If you would like to validate your config file to make sure it works, you may do ## Generating a Single-Player Game -1. Navigate to the [Player Settings](/games/Lufia%20II%20Ancient%20Cave/player-settings) page, configure your options, +1. Navigate to the [Player Options](/games/Lufia%20II%20Ancient%20Cave/player-options) 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. diff --git a/worlds/meritous/docs/en_Meritous.md b/worlds/meritous/docs/en_Meritous.md index bceae7d9ae2b..d119c3634c58 100644 --- a/worlds/meritous/docs/en_Meritous.md +++ b/worlds/meritous/docs/en_Meritous.md @@ -1,7 +1,7 @@ # Meritous -## Where is the settings page? -The [player settings page for Meritous](../player-settings) contains all the options you need to configure and export a config file. +## Where is the options page? +The [player options page for Meritous](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? The PSI Enhancement Tiles have become general-purpose Item Caches, and all upgrades and artifacts are added to the multiworld item pool. Optionally, the progression-critical PSI Keys can also be added to the pool, as well as monster evolution traps which (in vanilla) trigger when bosses are defeated. diff --git a/worlds/meritous/docs/setup_en.md b/worlds/meritous/docs/setup_en.md index 63f8657b63bb..9b91f12106de 100644 --- a/worlds/meritous/docs/setup_en.md +++ b/worlds/meritous/docs/setup_en.md @@ -40,9 +40,9 @@ Eventually, this process will be moved to in-game menus for better ease of use. ## Finishing the Game -Your initial goal is to find all three PSI Keys. Depending on your YAML settings, these may be located on pedestals in special rooms in the Atlas Dome, or they may be scattered across other players' worlds. These PSI Keys are then brought to their respective locations in the Dome, where you will be subjected to a boss battle. Once all three bosses are defeated, this unlocks the Cursed Seal, hidden in the farthest-away location from the Entrance. The Compass tiles can help you find your way to these locations. +Your initial goal is to find all three PSI Keys. Depending on your YAML options, these may be located on pedestals in special rooms in the Atlas Dome, or they may be scattered across other players' worlds. These PSI Keys are then brought to their respective locations in the Dome, where you will be subjected to a boss battle. Once all three bosses are defeated, this unlocks the Cursed Seal, hidden in the farthest-away location from the Entrance. The Compass tiles can help you find your way to these locations. -At minimum, every seed will require you to find the Cursed Seal and bring it back to the Entrance. The goal can then vary based on your `goal` YAML setting: +At minimum, every seed will require you to find the Cursed Seal and bring it back to the Entrance. The goal can then vary based on your `goal` YAML option: - `return_the_cursed_seal`: You will fight the final boss, but win or lose, a victory will be posted. - `any_ending`: You must defeat the final boss. diff --git a/worlds/minecraft/docs/en_Minecraft.md b/worlds/minecraft/docs/en_Minecraft.md index c700f59a51c7..3a69a7f59a22 100644 --- a/worlds/minecraft/docs/en_Minecraft.md +++ b/worlds/minecraft/docs/en_Minecraft.md @@ -1,8 +1,8 @@ # Minecraft -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/minecraft/docs/minecraft_en.md b/worlds/minecraft/docs/minecraft_en.md index b71ed930a5d8..e0b5ae3b98b5 100644 --- a/worlds/minecraft/docs/minecraft_en.md +++ b/worlds/minecraft/docs/minecraft_en.md @@ -15,7 +15,7 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a YAML file? -You can customize your settings by visiting the [Minecraft Player Settings Page](/games/Minecraft/player-settings) +You can customize your options by visiting the [Minecraft Player Options Page](/games/Minecraft/player-options) ## Joining a MultiWorld Game diff --git a/worlds/mmbn3/docs/en_MegaMan Battle Network 3.md b/worlds/mmbn3/docs/en_MegaMan Battle Network 3.md index 7ffa4665fd2a..bb9d2c15af2c 100644 --- a/worlds/mmbn3/docs/en_MegaMan Battle Network 3.md +++ b/worlds/mmbn3/docs/en_MegaMan Battle Network 3.md @@ -1,8 +1,8 @@ # MegaMan Battle Network 3 -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/mmbn3/docs/setup_en.md b/worlds/mmbn3/docs/setup_en.md index e9181ea54881..44a6b9c14448 100644 --- a/worlds/mmbn3/docs/setup_en.md +++ b/worlds/mmbn3/docs/setup_en.md @@ -53,8 +53,8 @@ an experience customized for their taste, and different players in the same mult ### Where do I get a YAML file? -You can customize your settings by visiting the -[MegaMan Battle Network 3 Player Settings Page](/games/MegaMan%20Battle%20Network%203/player-settings) +You can customize your options by visiting the +[MegaMan Battle Network 3 Player Options Page](/games/MegaMan%20Battle%20Network%203/player-options) ## Joining a MultiWorld Game diff --git a/worlds/musedash/docs/en_Muse Dash.md b/worlds/musedash/docs/en_Muse Dash.md index 008fd4d2df0c..29d1465ed098 100644 --- a/worlds/musedash/docs/en_Muse Dash.md +++ b/worlds/musedash/docs/en_Muse Dash.md @@ -2,10 +2,10 @@ ## Quick Links - [Setup Guide](../../../tutorial/Muse%20Dash/setup/en) -- [Settings Page](../player-settings) +- [Options Page](../player-options) ## What Does Randomization do to this Game? -- You will be given a number of starting songs. The number of which depends on your settings. +- You will be given a number of starting songs. The number of which depends on your options. - Completing any song will give you 1 or 2 rewards. - The rewards for completing songs will range from songs to traps and **Music Sheets**. diff --git a/worlds/musedash/docs/setup_en.md b/worlds/musedash/docs/setup_en.md index ebf165c7dd78..312cdbd1958f 100644 --- a/worlds/musedash/docs/setup_en.md +++ b/worlds/musedash/docs/setup_en.md @@ -2,7 +2,7 @@ ## Quick Links - [Main Page](../../../../games/Muse%20Dash/info/en) -- [Settings Page](../../../../games/Muse%20Dash/player-settings) +- [Options Page](../../../../games/Muse%20Dash/player-options) ## Required Software @@ -27,7 +27,7 @@ If you've successfully installed everything, a button will appear in the bottom right which will allow you to log into an Archipelago server. ## Generating a MultiWorld Game -1. Visit the [Player Settings](/games/Muse%20Dash/player-settings) page and configure the game-specific settings to your taste. +1. Visit the [Player Options](/games/Muse%20Dash/player-options) page and configure the game-specific options to your taste. 2. Export your yaml file and use it to generate a new randomized game - (For instructions on how to generate an Archipelago game, refer to the [Archipelago Web Guide](/tutorial/Archipelago/setup/en)) diff --git a/worlds/noita/docs/en_Noita.md b/worlds/noita/docs/en_Noita.md index b1480068e96c..1e560cfcb748 100644 --- a/worlds/noita/docs/en_Noita.md +++ b/worlds/noita/docs/en_Noita.md @@ -1,15 +1,15 @@ # Noita -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? Noita is a procedurally generated action roguelike. During runs in Noita you will find potions, wands, spells, perks, chests, etc. Shop items, chests/hearts hidden in the environment, and pedestal items will be replaced with location -checks. Orbs and boss drops can give location checks as well, if they are enabled in the settings. +checks. Orbs and boss drops can give location checks as well, if their respective options are enabled. Noita items that can be found in other players' games include specific perks, orbs (optional), wands, hearts, gold, potions, and other items. If traps are enabled, some randomized negative effects can affect your game when found. @@ -50,9 +50,9 @@ Traps consist of all "Bad" and "Awful" events from Noita's native stream integra ## How many location checks are there? -When using the default settings, there are 109 location checks. The number of checks in the game is dependent on the settings that you choose. -Please check the information boxes next to the settings when setting up your YAML to see how many checks the individual options add. -There are always 42 Holy Mountain checks and 4 Secret Shop checks in the pool which are not affected by your YAML settings. +When using the default options, there are 109 location checks. The number of checks in the game is dependent on the options that you choose. +Please check the information boxes next to the options when setting up your YAML to see how many checks the individual options add. +There are always 42 Holy Mountain checks and 4 Secret Shop checks in the pool which are not affected by your YAML options. ## What does another world's item look like in Noita? diff --git a/worlds/noita/docs/setup_en.md b/worlds/noita/docs/setup_en.md index b67e840bb94c..25c6cbc948bc 100644 --- a/worlds/noita/docs/setup_en.md +++ b/worlds/noita/docs/setup_en.md @@ -44,7 +44,7 @@ Please note that Noita only allows you to type certain characters for your slot These characters are: `` !#$%&'()+,-.0123456789;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{}~<>|\/`` ### Where do I get a YAML? -You can use the [game settings page for Noita](/games/Noita/player-settings) here on the Archipelago website to +You can use the [game options page for Noita](/games/Noita/player-options) here on the Archipelago website to generate a YAML using a graphical interface. ## Poptracker Pack diff --git a/worlds/oot/docs/en_Ocarina of Time.md b/worlds/oot/docs/en_Ocarina of Time.md index fa8e148957c7..5a480d864124 100644 --- a/worlds/oot/docs/en_Ocarina of Time.md +++ b/worlds/oot/docs/en_Ocarina of Time.md @@ -1,8 +1,8 @@ # Ocarina of Time -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? @@ -37,4 +37,4 @@ business! The following commands are only available when using the OoTClient to play with Archipelago. - `/n64` Check N64 Connection State -- `/deathlink` Toggle deathlink from client. Overrides default setting. +- `/deathlink` Toggle deathlink from client. Overrides default option. diff --git a/worlds/oot/docs/setup_en.md b/worlds/oot/docs/setup_en.md index 4d27019fa771..553f1820c3ea 100644 --- a/worlds/oot/docs/setup_en.md +++ b/worlds/oot/docs/setup_en.md @@ -50,8 +50,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The Player Settings page on the website allows you to configure your personal settings and export a config file from -them. Player settings page: [Ocarina of Time Player Settings Page](/games/Ocarina%20of%20Time/player-settings) +The Player Options page on the website allows you to configure your personal options and export a config file from +them. Player options page: [Ocarina of Time Player Options Page](/games/Ocarina%20of%20Time/player-options) ### Verifying your config file diff --git a/worlds/overcooked2/docs/en_Overcooked! 2.md b/worlds/overcooked2/docs/en_Overcooked! 2.md index 298c33683ce7..d4cb6fba1f9a 100644 --- a/worlds/overcooked2/docs/en_Overcooked! 2.md +++ b/worlds/overcooked2/docs/en_Overcooked! 2.md @@ -2,7 +2,7 @@ ## Quick Links - [Setup Guide](../../../../tutorial/Overcooked!%202/setup/en) -- [Settings Page](../../../../games/Overcooked!%202/player-settings) +- [Options Page](../../../../games/Overcooked!%202/player-options) - [OC2-Modding GitHub](https://github.com/toasterparty/oc2-modding) ## How Does Randomizer Work in the Kitchen? @@ -55,7 +55,7 @@ The following items were invented for Randomizer: - Ramp Buttons (x7) - Bonus Star (Filler Item*) -**Note: Bonus star count varies with settings* +**Note: Bonus star count varies with options* ## Other Game Modifications diff --git a/worlds/overcooked2/docs/setup_en.md b/worlds/overcooked2/docs/setup_en.md index 1b21642cfe03..9f9eae5fc1ae 100644 --- a/worlds/overcooked2/docs/setup_en.md +++ b/worlds/overcooked2/docs/setup_en.md @@ -2,7 +2,7 @@ ## Quick Links - [Main Page](../../../../games/Overcooked!%202/info/en) -- [Settings Page](../../../../games/Overcooked!%202/player-settings) +- [Options Page](../../../../games/Overcooked!%202/player-options) - [OC2-Modding GitHub](https://github.com/toasterparty/oc2-modding) ## Required Software @@ -49,9 +49,9 @@ To completely remove *OC2-Modding*, navigate to your game's installation folder ## Generate a MultiWorld Game -1. Visit the [Player Settings](../../../../games/Overcooked!%202/player-settings) page and configure the game-specific settings to taste +1. Visit the [Player Options](../../../../games/Overcooked!%202/player-options) page and configure the game-specific options to taste -*By default, these settings will only use levels from the base game and the "Seasonal" free DLC updates. If you own any of the paid DLC, you may select individual DLC packs to include/exclude on the [Weighted Settings](../../../../weighted-settings) page* +*By default, these options will only use levels from the base game and the "Seasonal" free DLC updates. If you own any of the paid DLC, you may select individual DLC packs to include/exclude on the [Weighted Options](../../../../weighted-options) page* 2. Export your yaml file and use it to generate a new randomized game @@ -84,11 +84,11 @@ To completely remove *OC2-Modding*, navigate to your game's installation folder Since the goal of randomizer isn't necessarily to achieve new personal high scores, players may find themselves waiting for a level timer to expire once they've met their objective. A new feature called *Auto-Complete* has been added to automatically complete levels once a target star count has been achieved. -To enable *Auto-Complete*, press the **Show** button near the top of your screen to expand the modding controls. Then, repeatedly press the **Auto-Complete** button until it shows the desired setting. +To enable *Auto-Complete*, press the **Show** button near the top of your screen to expand the modding controls. Then, repeatedly press the **Auto-Complete** button until it shows the desired option. ## Overworld Sequence Breaking -In the world's settings, there is an option called "Overworld Tricks" which allows the generator to make games which require doing tricks with the food truck to complete. This includes: +In the world's options, there is an option called "Overworld Tricks" which allows the generator to make games which require doing tricks with the food truck to complete. This includes: - Dashing across gaps 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 dc55aca0f09f..1e5c14eb99f5 100644 --- a/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md +++ b/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md @@ -1,8 +1,8 @@ # Pokémon Red and Blue -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? @@ -12,7 +12,7 @@ always able to be completed, but because of the item shuffle the player may need would in the vanilla game. A great many things besides item placement can be randomized, such as the location of Pokémon, their stats, types, etc., -depending on your yaml settings. +depending on your yaml options. Many baseline changes are made to the game, including: @@ -21,13 +21,13 @@ Many baseline changes are made to the game, including: * You can hold B to run (or bike extra fast!). * You can hold select while talking to a trainer to re-battle them. * You can select "Pallet Warp" below the "Continue" option to warp to Pallet Town as you load your save. -* Mew can be encountered at the S.S. Anne dock truck. This can be randomized depending on your settings. +* Mew can be encountered at the S.S. Anne dock truck. This can be randomized depending on your options. * The S.S. Anne will never depart. * Seafoam Islands entrances are swapped. This means you need Strength to travel through from Cinnabar Island to Fuchsia City. You also cannot Surf onto the water from the end of Seafoam Islands going backwards if you have not yet dropped the boulders. * After obtaining one of the fossil item checks in Mt Moon, the remaining item can be received from the Cinnabar Lab -fossil scientist. This may require reviving a number of fossils, depending on your settings. +fossil scientist. This may require reviving a number of fossils, depending on your options. * Obedience depends on the total number of badges you have obtained instead of depending on specific badges. * Pokémon that evolve by trading can also evolve by reaching level 35. * Evolution stones are reusable key items. diff --git a/worlds/pokemon_rb/docs/setup_en.md b/worlds/pokemon_rb/docs/setup_en.md index c9344959f6b9..45b0175eac9d 100644 --- a/worlds/pokemon_rb/docs/setup_en.md +++ b/worlds/pokemon_rb/docs/setup_en.md @@ -47,7 +47,7 @@ an experience customized for their taste, and different players in the same mult ### Where do I get a YAML file? -You can generate a yaml or download a template by visiting the [Pokemon Red and Blue Player Settings Page](/games/Pokemon%20Red%20and%20Blue/player-settings) +You can generate a yaml or download a template by visiting the [Pokemon Red and Blue Player Options Page](/games/Pokemon%20Red%20and%20Blue/player-options) It is important to note that the `game_version` option determines the ROM file that will be patched. Both the player and the person generating (if they are generating locally) will need the corresponding ROM file. @@ -72,7 +72,7 @@ And the following special characters (these each count as one character): ### Generating and Patching a Game -1. Create your settings file (YAML). +1. Create your options file (YAML). 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 a `.apred` or `.apblue` file extension. 3. Open `ArchipelagoLauncher.exe` @@ -114,5 +114,5 @@ Pokémon Red and Blue has a fully functional map tracker that supports auto-trac 3. Click on the "AP" symbol at the top. 4. Enter the AP address, slot name and password. -The rest should take care of itself! Items and checks will be marked automatically, and it even knows your settings - It +The rest should take care of itself! Items and checks will be marked automatically, and it even knows your options - It will hide checks & adjust logic accordingly. diff --git a/worlds/raft/docs/en_Raft.md b/worlds/raft/docs/en_Raft.md index 385377d45608..0c68e23d0019 100644 --- a/worlds/raft/docs/en_Raft.md +++ b/worlds/raft/docs/en_Raft.md @@ -1,7 +1,7 @@ # Raft -## Where is the settings page? -The player settings page for this game is located here. It contains all the options +## Where is the options page? +The player options page for this game is located here. It contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/rogue_legacy/docs/en_Rogue Legacy.md b/worlds/rogue_legacy/docs/en_Rogue Legacy.md index c91dc0de6f7a..dd203c73ac26 100644 --- a/worlds/rogue_legacy/docs/en_Rogue Legacy.md +++ b/worlds/rogue_legacy/docs/en_Rogue Legacy.md @@ -1,9 +1,9 @@ # Rogue Legacy (PC) -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains most of the options you need to -configure and export a config file. Some settings can only be made in YAML, but an explanation can be found in the +The [player options page for this game](../player-options) contains most of the options you need to +configure and export a config file. Some options can only be made in YAML, but an explanation can be found in the [template yaml here](../../../static/generated/configs/Rogue%20Legacy.yaml). ## What does randomization do to this game? diff --git a/worlds/rogue_legacy/docs/rogue-legacy_en.md b/worlds/rogue_legacy/docs/rogue-legacy_en.md index e513d0f0ca18..fc9f6920178d 100644 --- a/worlds/rogue_legacy/docs/rogue-legacy_en.md +++ b/worlds/rogue_legacy/docs/rogue-legacy_en.md @@ -21,7 +21,7 @@ an experience customized for their taste, and different players in the same mult ### Where do I get a YAML file? -you can customize your settings by visiting the [Rogue Legacy Settings Page](/games/Rogue%20Legacy/player-settings). +you can customize your options by visiting the [Rogue Legacy Options Page](/games/Rogue%20Legacy/player-options). ### Connect to the MultiServer diff --git a/worlds/ror2/docs/en_Risk of Rain 2.md b/worlds/ror2/docs/en_Risk of Rain 2.md index d30edf888944..b2210e348d50 100644 --- a/worlds/ror2/docs/en_Risk of Rain 2.md +++ b/worlds/ror2/docs/en_Risk of Rain 2.md @@ -1,8 +1,8 @@ # Risk of Rain 2 -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? @@ -23,7 +23,7 @@ Explore Mode: - Chests will continue to work as they did in Classic Mode, the difference being that each environment will have a set amount of items that can be sent out. In addition, shrines, radio scanners, newt altars, - and scavenger bags will need to be checked, depending on your settings. + and scavenger bags will need to be checked, depending on your options. This mode also makes each environment an item. In order to access a particular stage, you'll need it to be sent in the multiworld. @@ -32,7 +32,7 @@ Explore Mode: Just like in the original game, any way to "beat the game" counts as a win. This means beating one of the bosses on Commencement, The Planetarium, or A Moment, Whole. Alternatively, if you are new to the game and aren't very confident in being able to "beat the game", you can set **Final Stage Death is Win** to true -(You can turn this on in your player settings.) This will make it so dying on either Commencement or The Planetarium, +(You can turn this on in your player options.) This will make it so dying on either Commencement or The Planetarium, or **obliterating yourself in A Moment, Fractured** will count as your goal. **You do not need to complete all the location checks** to win; any item you don't collect may be released if the server options allow. @@ -48,12 +48,12 @@ then finish a normal mode run while keeping the items you received via the multi ## Can you play multiplayer? Yes! You can have a single multiplayer instance as one world in the multiworld. All the players involved need to have -the Archipelago mod, but only the host needs to configure the Archipelago settings. When someone finds an item for your +the Archipelago mod, but only the host needs to configure the Archipelago options. When someone finds an item for your world, all the connected players will receive a copy of the item, and the location check bar will increase whenever any player finds an item in Risk of Rain. You cannot have players with different player slots in the same co-op game instance. Only the host's Archipelago -settings apply, so each Risk of Rain 2 player slot in the multiworld needs to be a separate game instance. You could, +options apply, so each Risk of Rain 2 player slot in the multiworld needs to be a separate game instance. You could, for example, have two players trade off hosting and making progress on each other's player slot, but a single co-op instance can't make progress towards multiple player slots in the multiworld. @@ -69,7 +69,7 @@ The Risk of Rain items are: * `Legendary Item` (Red items) * `Lunar Item` (Blue items) * `Equipment` (Orange items) -* `Dio's Best Friend` (Used if you set the YAML setting `total_revives_available` above `0`) +* `Dio's Best Friend` (Used if you set the YAML option `total_revives_available` above `0`) * `Void Item` (Purple items) (needs dlc_sotv: enabled) Each item grants you a random in-game item from the category it belongs to. @@ -127,7 +127,7 @@ what item you sent out. If the message does not appear, this likely means that a ## What is the item pickup step? -The item pickup step is a setting in the YAML which allows you to set how many items you need to spawn before the _next_ item +The item pickup step is an option in the YAML which allows you to set how many items you need to spawn before the _next_ item that is spawned disappears (in a poof of smoke) and goes out to the multiworld. For instance, an item step of **1** means that every other chest will send an item to the multiworld. An item step of **2** means that every third chest sends out an item just as an item step of **0** would send an item on **each chest.** diff --git a/worlds/ror2/docs/setup_en.md b/worlds/ror2/docs/setup_en.md index 0fa99c071b9c..6acf2654a8b2 100644 --- a/worlds/ror2/docs/setup_en.md +++ b/worlds/ror2/docs/setup_en.md @@ -29,7 +29,7 @@ You can see the [basic multiworld setup guide](/tutorial/Archipelago/setup/en) h about why Archipelago uses YAML files and what they're for. ### Where do I get a YAML? -You can use the [game settings page](/games/Risk%20of%20Rain%202/player-settings) here on the Archipelago +You can use the [game options page](/games/Risk%20of%20Rain%202/player-options) here on the Archipelago website to generate a YAML using a graphical interface. diff --git a/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md b/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md index 12ccc50ccd2c..e2f732ffe585 100644 --- a/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md +++ b/worlds/sa2b/docs/en_Sonic Adventure 2 Battle.md @@ -1,8 +1,8 @@ # Sonic Adventure 2: Battle -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a config file. +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/shivers/Rules.py b/worlds/shivers/Rules.py index 8aa8aa2c28dd..b1abb718c275 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), diff --git a/worlds/shivers/docs/en_Shivers.md b/worlds/shivers/docs/en_Shivers.md index 51730057b034..a92f8a6b7911 100644 --- a/worlds/shivers/docs/en_Shivers.md +++ b/worlds/shivers/docs/en_Shivers.md @@ -1,8 +1,8 @@ # Shivers -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a configuration file. ## What does randomization do to this game? diff --git a/worlds/shivers/docs/setup_en.md b/worlds/shivers/docs/setup_en.md index ee33bb70408e..187382ef643c 100644 --- a/worlds/shivers/docs/setup_en.md +++ b/worlds/shivers/docs/setup_en.md @@ -33,8 +33,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The Player Settings page on the website allows you to configure your personal settings and export a config file from -them. Player settings page: [Shivers Player Settings Page](/games/Shivers/player-settings) +The Player Options page on the website allows you to configure your personal options and export a config file from +them. Player options page: [Shivers Player Options Page](/games/Shivers/player-options) ### Verifying your config file diff --git a/worlds/shorthike/Items.py b/worlds/shorthike/Items.py new file mode 100644 index 000000000000..a240dcbc6a1f --- /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 000000000000..c2d316c68675 --- /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 000000000000..1ac0ff52f974 --- /dev/null +++ b/worlds/shorthike/Options.py @@ -0,0 +1,89 @@ +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 + +class FillerCoinAmount(Choice): + """The number of coins that will be in each filler coin item.""" + display_name = "Coins per Filler Item" + option_7_coins = 0 + option_13_coins = 1 + option_15_coins = 2 + option_18_coins = 3 + option_21_coins = 4 + option_25_coins = 5 + option_27_coins = 6 + option_32_coins = 7 + option_33_coins = 8 + option_50_coins = 9 + default = 1 + +@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 + filler_coin_amount: FillerCoinAmount diff --git a/worlds/shorthike/Rules.py b/worlds/shorthike/Rules.py new file mode 100644 index 000000000000..73a16434219e --- /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 000000000000..3e0430f024ca --- /dev/null +++ b/worlds/shorthike/__init__.py @@ -0,0 +1,133 @@ +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 get_filler_item_name(self) -> str: + return self.options.filler_coin_amount.current_option_name + + 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 000000000000..516bf28e47fc --- /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 000000000000..e327d8bed93c --- /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 diff --git a/worlds/sm/docs/en_Super Metroid.md b/worlds/sm/docs/en_Super Metroid.md index 5c87e026f634..c8c1d0faabee 100644 --- a/worlds/sm/docs/en_Super Metroid.md +++ b/worlds/sm/docs/en_Super Metroid.md @@ -1,8 +1,8 @@ # Super Metroid -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/sm/docs/multiworld_en.md b/worlds/sm/docs/multiworld_en.md index abd9f42f887c..8f30630bc96d 100644 --- a/worlds/sm/docs/multiworld_en.md +++ b/worlds/sm/docs/multiworld_en.md @@ -46,8 +46,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The Player Settings page on the website allows you to configure your personal settings and export a config file from -them. Player settings page: [Super Metroid Player Settings Page](/games/Super%20Metroid/player-settings) +The Player Options page on the website allows you to configure your personal options and export a config file from +them. Player options page: [Super Metroid Player Options Page](/games/Super%20Metroid/player-options) ### Verifying your config file @@ -56,8 +56,8 @@ validator page: [YAML Validation page](/check) ## Generating a Single-Player Game -1. Navigate to the Player Settings page, configure your options, and click the "Generate Game" button. - - Player Settings page: [Super Metroid Player Settings Page](/games/Super%20Metroid/player-settings) +1. Navigate to the Player Options page, configure your options, and click the "Generate Game" button. + - Player Options page: [Super Metroid Player Options Page](/games/Super%20Metroid/player-options) 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. diff --git a/worlds/sm64ex/Regions.py b/worlds/sm64ex/Regions.py index 8c2d32e401bf..a493281ec3f6 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 f2b8e0bcdf2d..cc2b52f0f12f 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 e54a4b7a9103..e6a6e42c76a0 100644 --- a/worlds/sm64ex/__init__.py +++ b/worlds/sm64ex/__init__.py @@ -5,8 +5,8 @@ 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 @@ def generate_output(self, output_directory: str): 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 diff --git a/worlds/sm64ex/docs/en_Super Mario 64.md b/worlds/sm64ex/docs/en_Super Mario 64.md index def6e2a37536..3d182a422081 100644 --- a/worlds/sm64ex/docs/en_Super Mario 64.md +++ b/worlds/sm64ex/docs/en_Super Mario 64.md @@ -1,9 +1,9 @@ # Super Mario 64 EX -## Where is the settings page? +## Where is the options page? -The player settings page for this game contains all the options you need to configure and export a config file. Player -settings page link: [SM64EX Player Settings Page](../player-settings). +The player options page for this game contains all the options you need to configure and export a config file. Player +options page link: [SM64EX Player Options Page](../player-options). ## What does randomization do to this game? All 120 Stars, the 3 Cap Switches, the Basement and Secound Floor Key are now Location Checks and may contain Items for different games as well diff --git a/worlds/sm64ex/docs/setup_en.md b/worlds/sm64ex/docs/setup_en.md index 2817d3c324c0..5983057f7d7a 100644 --- a/worlds/sm64ex/docs/setup_en.md +++ b/worlds/sm64ex/docs/setup_en.md @@ -70,7 +70,7 @@ After the compliation was successful, there will be a binary in your `sm64ex/bui ### Joining a MultiWorld Game To join, set the following launch options: `--sm64ap_name YourName --sm64ap_ip ServerIP:Port`. -For example, if you are hosting a game using the website, `YourName` will be the name from the Settings Page, `ServerIP` is `archipelago.gg` and `Port` the port given on the Archipelago room page. +For example, if you are hosting a game using the website, `YourName` will be the name from the Options Page, `ServerIP` is `archipelago.gg` and `Port` the port given on the Archipelago room page. Optionally, add `--sm64ap_passwd "YourPassword"` if the room you are using requires a password. Should your name or password have spaces, enclose it in quotes: `"YourPassword"` and `"YourName"`. @@ -82,7 +82,7 @@ Failing to use a new file may make some locations unavailable. However, this can ### Playing offline -To play offline, first generate a seed on the game's settings page. +To play offline, first generate a seed on the game's options page. Create a room and download the `.apsm64ex` file, and start the game with the `--sm64ap_file "path/to/FileName"` launch argument. ### Optional: Using Batch Files to play offline and MultiWorld games diff --git a/worlds/smw/docs/en_Super Mario World.md b/worlds/smw/docs/en_Super Mario World.md index 87a96e558b65..26623bac83d3 100644 --- a/worlds/smw/docs/en_Super Mario World.md +++ b/worlds/smw/docs/en_Super Mario World.md @@ -1,8 +1,8 @@ # Super Mario World -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a config file. +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/smw/docs/setup_en.md b/worlds/smw/docs/setup_en.md index c8f408d6e256..825f0954c8f1 100644 --- a/worlds/smw/docs/setup_en.md +++ b/worlds/smw/docs/setup_en.md @@ -44,8 +44,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The Player Settings page on the website allows you to configure your personal settings and export a config file from -them. Player settings page: [Super Mario World Player Settings Page](/games/Super%20Mario%20World/player-settings) +The Player Options page on the website allows you to configure your personal options and export a config file from +them. Player options page: [Super Mario World Player Options Page](/games/Super%20Mario%20World/player-options) ### Verifying your config file diff --git a/worlds/smz3/docs/en_SMZ3.md b/worlds/smz3/docs/en_SMZ3.md index f0302d12f3a1..2116432ea7a1 100644 --- a/worlds/smz3/docs/en_SMZ3.md +++ b/worlds/smz3/docs/en_SMZ3.md @@ -1,8 +1,8 @@ # SMZ3 -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/smz3/docs/multiworld_en.md b/worlds/smz3/docs/multiworld_en.md index 5e226798a30b..38c410faee02 100644 --- a/worlds/smz3/docs/multiworld_en.md +++ b/worlds/smz3/docs/multiworld_en.md @@ -43,8 +43,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The Player Settings page on the website allows you to configure your personal settings and export a config file from -them. Player settings page: [SMZ3 Player Settings Page](/games/SMZ3/player-settings) +The Player Options page on the website allows you to configure your personal options and export a config file from +them. Player options page: [SMZ3 Player Options Page](/games/SMZ3/player-options) ### Verifying your config file @@ -53,8 +53,8 @@ validator page: [YAML Validation page](/check) ## Generating a Single-Player Game -1. Navigate to the Player Settings page, configure your options, and click the "Generate Game" button. - - Player Settings page: [SMZ3 Player Settings Page](/games/SMZ3/player-settings) +1. Navigate to the Player Options page, configure your options, and click the "Generate Game" button. + - Player Options page: [SMZ3 Player Options Page](/games/SMZ3/player-options) 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. diff --git a/worlds/soe/docs/en_Secret of Evermore.md b/worlds/soe/docs/en_Secret of Evermore.md index 215a5387bb9f..98882b6ff7e3 100644 --- a/worlds/soe/docs/en_Secret of Evermore.md +++ b/worlds/soe/docs/en_Secret of Evermore.md @@ -1,8 +1,8 @@ # Secret of Evermore -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/soe/docs/multiworld_en.md b/worlds/soe/docs/multiworld_en.md index 89b1ff9fd931..065d43fc3a1b 100644 --- a/worlds/soe/docs/multiworld_en.md +++ b/worlds/soe/docs/multiworld_en.md @@ -25,8 +25,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The Player Settings page on the website allows you to configure your personal settings and export a config file from -them. Player settings page: [Secret of Evermore Player Settings PAge](/games/Secret%20of%20Evermore/player-settings) +The Player Options page on the website allows you to configure your personal options and export a config file from +them. Player options page: [Secret of Evermore Player Options Page](/games/Secret%20of%20Evermore/player-options) ### Verifying your config file @@ -38,8 +38,8 @@ page: [YAML Validation page](/check) Stand-alone "Evermizer" has a way of balancing single-player games, but may not always be on par feature-wise. Head over to the [Evermizer Website](https://evermizer.com) if you want to try the official stand-alone, otherwise read below. -1. Navigate to the Player Settings page, configure your options, and click the "Generate Game" button. - - Player Settings page: [Secret of Evermore Player Settings Page](/games/Secret%20of%20Evermore/player-settings) +1. Navigate to the Player Options page, configure your options, and click the "Generate Game" button. + - Player Options page: [Secret of Evermore Player Options Page](/games/Secret%20of%20Evermore/player-options) 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. diff --git a/worlds/spire/docs/en_Slay the Spire.md b/worlds/spire/docs/en_Slay the Spire.md index f4519455fa83..4591db58dc51 100644 --- a/worlds/spire/docs/en_Slay the Spire.md +++ b/worlds/spire/docs/en_Slay the Spire.md @@ -1,8 +1,8 @@ # Slay the Spire (PC) -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/spire/docs/slay-the-spire_en.md b/worlds/spire/docs/slay-the-spire_en.md index d85a17d987b2..daeb65415196 100644 --- a/worlds/spire/docs/slay-the-spire_en.md +++ b/worlds/spire/docs/slay-the-spire_en.md @@ -43,8 +43,8 @@ an experience customized for their taste, and different players in the same mult ### Where do I get a YAML file? -you can customize your settings by visiting -the [Slay the Spire Settings Page](/games/Slay%20the%20Spire/player-settings). +you can customize your options by visiting +the [Slay the Spire Options Page](/games/Slay%20the%20Spire/player-options). ### Connect to the MultiServer diff --git a/worlds/stardew_valley/docs/en_Stardew Valley.md b/worlds/stardew_valley/docs/en_Stardew Valley.md index 06c41a2f0563..789c12020c18 100644 --- a/worlds/stardew_valley/docs/en_Stardew Valley.md +++ b/worlds/stardew_valley/docs/en_Stardew Valley.md @@ -1,28 +1,35 @@ # Stardew Valley -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? -A vast number of objectives in Stardew Valley can be shuffled around the multiworld. Most of these are optional, and the player can customize their experience in their YAML file. +A vast number of objectives in Stardew Valley can be shuffled around the multiworld. Most of these are optional, and the +player can customize their experience in their YAML file. -For these objectives, if they have a vanilla reward, this reward will instead be an item in the multiworld. For the remaining number of such objectives, there are a number of "Resource Pack" items, which are simply an item or a stack of items that may be useful to the player. +For these objectives, if they have a vanilla reward, this reward will instead be an item in the multiworld. For the remaining +number of such objectives, there are a number of "Resource Pack" items, which are simply an item or a stack of items that +may be useful to the player. ## What is the goal of Stardew Valley? -The player can choose from a number of goals, using their YAML settings. +The player can choose from a number of goals, using their YAML options. - Complete the [Community Center](https://stardewvalleywiki.com/Bundles) - Succeed [Grandpa's Evaluation](https://stardewvalleywiki.com/Grandpa) with 4 lit candles - Reach the bottom of the [Pelican Town Mineshaft](https://stardewvalleywiki.com/The_Mines) -- Complete the [Cryptic Note](https://stardewvalleywiki.com/Secret_Notes#Secret_Note_.2310) quest, by meeting Mr Qi on floor 100 of the Skull Cavern +- Complete the [Cryptic Note](https://stardewvalleywiki.com/Secret_Notes#Secret_Note_.2310) quest, by meeting Mr Qi on +floor 100 of the Skull Cavern - Become a [Master Angler](https://stardewvalleywiki.com/Fish), which requires catching every fish in your slot -- Restore [A Complete Collection](https://stardewvalleywiki.com/Museum), which requires donating all the artifacts and minerals to the museum +- Restore [A Complete Collection](https://stardewvalleywiki.com/Museum), which requires donating all the artifacts and +minerals to the museum - Get the achievement [Full House](https://stardewvalleywiki.com/Children), which requires getting married and having two kids -- Get recognized as the [Greatest Walnut Hunter](https://stardewvalleywiki.com/Golden_Walnut) by Mr Qi, which requires finding all 130 golden walnuts on ginger island -- Become the [Protector of the Valley](https://stardewvalleywiki.com/Adventurer%27s_Guild#Monster_Eradication_Goals) by completing all the monster slayer goals at the Adventure Guild +- Get recognized as the [Greatest Walnut Hunter](https://stardewvalleywiki.com/Golden_Walnut) by Mr Qi, which requires +finding all 130 golden walnuts on ginger island +- Become the [Protector of the Valley](https://stardewvalleywiki.com/Adventurer%27s_Guild#Monster_Eradication_Goals) by +completing all the monster slayer goals at the Adventure Guild - Complete a [Full Shipment](https://stardewvalleywiki.com/Shipping#Collection) by shipping every item in your slot - Become a [Gourmet Chef](https://stardewvalleywiki.com/Cooking) by cooking every recipe in your slot - Become a [Craft Master](https://stardewvalleywiki.com/Crafting) by crafting every item @@ -31,7 +38,9 @@ The player can choose from a number of goals, using their YAML settings. - Finish 100% of your randomizer slot with Allsanity: Complete every check in your slot - Achieve [Perfection](https://stardewvalleywiki.com/Perfection) in your save file -The following goals [Community Center, Master Angler, Protector of the Valley, Full Shipment and Gourmet Chef] will adapt to other settings in your slots, and are therefore customizable in duration and difficulty. For example, if you set "Fishsanity" to "Exclude Legendaries", and pick the Master Angler goal, you will not need to catch the legendaries to complete the goal. +The following goals [Community Center, Master Angler, Protector of the Valley, Full Shipment and Gourmet Chef] will adapt +to other options in your slots, and are therefore customizable in duration and difficulty. For example, if you set "Fishsanity" +to "Exclude Legendaries", and pick the Master Angler goal, you will not need to catch the legendaries to complete the goal. ## What are location checks in Stardew Valley? @@ -39,7 +48,9 @@ Location checks in Stardew Valley always include: - [Community Center Bundles](https://stardewvalleywiki.com/Bundles) - [Mineshaft Chest Rewards](https://stardewvalleywiki.com/The_Mines#Remixed_Rewards) - [Traveling Merchant Items](https://stardewvalleywiki.com/Traveling_Cart) -- Isolated objectives such as the [beach bridge](https://stardewvalleywiki.com/The_Beach#Tide_Pools), [Old Master Cannoli](https://stardewvalleywiki.com/Secret_Woods#Old_Master_Cannoli), [Grim Reaper Statue](https://stardewvalleywiki.com/Golden_Scythe), etc +- Isolated objectives such as the [beach bridge](https://stardewvalleywiki.com/The_Beach#Tide_Pools), +[Old Master Cannoli](https://stardewvalleywiki.com/Secret_Woods#Old_Master_Cannoli), +[Grim Reaper Statue](https://stardewvalleywiki.com/Golden_Scythe), etc There also are a number of location checks that are optional, and individual players choose to include them or not in their shuffling: - [Tools and Fishing Rod Upgrades](https://stardewvalleywiki.com/Tools) @@ -51,7 +62,8 @@ There also are a number of location checks that are optional, and individual pla - [Story Quests](https://stardewvalleywiki.com/Quests#List_of_Story_Quests) - [Help Wanted Quests](https://stardewvalleywiki.com/Quests#Help_Wanted_Quests) - Participating in [Festivals](https://stardewvalleywiki.com/Festivals) -- [Special Orders](https://stardewvalleywiki.com/Quests#List_of_Special_Orders) from the town board, or from [Mr Qi](https://stardewvalleywiki.com/Quests#List_of_Mr._Qi.27s_Special_Orders) +- [Special Orders](https://stardewvalleywiki.com/Quests#List_of_Special_Orders) from the town board, or from +[Mr Qi](https://stardewvalleywiki.com/Quests#List_of_Mr._Qi.27s_Special_Orders) - [Cropsanity](https://stardewvalleywiki.com/Crops): Growing and Harvesting individual crop types - [Fishsanity](https://stardewvalleywiki.com/Fish): Catching individual fish - [Museumsanity](https://stardewvalleywiki.com/Museum): Donating individual items, or reaching milestones for museum donations @@ -67,16 +79,21 @@ There also are a number of location checks that are optional, and individual pla Every normal reward from the above locations can be in another player's world. For the locations which do not include a normal reward, Resource Packs and traps are instead added to the pool. Traps are optional. -A player can enable some settings that will add some items to the pool that are relevant to progression +A player can enable some options that will add some items to the pool that are relevant to progression - Seasons Randomizer: * All 4 seasons will be items, and one of them will be selected randomly and be added to the player's start inventory. - * At the end of each month, the player can choose the next season, instead of following the vanilla season order. On Seasons Randomizer, they can only choose from the seasons they have received. + * At the end of each month, the player can choose the next season, instead of following the vanilla season order. On +Seasons Randomizer, they can only choose from the seasons they have received. - Cropsanity: - * Every single seed in the game starts off locked and cannot be purchased from any merchant. Their unlocks are received as multiworld items. Growing each seed and harvesting the resulting crop sends a location check - * The way merchants sell seeds is considerably changed. Pierre sells fewer seeds at a high price, while Joja sells unlimited seeds but in huge discount packs, not individually. + * Every single seed in the game starts off locked and cannot be purchased from any merchant. Their unlocks are received + as multiworld items. Growing each seed and harvesting the resulting crop sends a location check + * The way merchants sell seeds is considerably changed. Pierre sells fewer seeds at a high price, while Joja sells + unlimited seeds but in huge discount packs, not individually. - Museumsanity: - * The items that are normally obtained from museum donation milestones are added to the item pool. Some items, like the magic rock candy, are duplicated for convenience. - * The Traveling Merchant now sells artifacts and minerals, with a bias towards undonated ones, to mitigate randomness. She will sell these items as the player receives "Traveling Merchant Metal Detector" items. + * The items that are normally obtained from museum donation milestones are added to the item pool. Some items, like the + magic rock candy, are duplicated for convenience. + * The Traveling Merchant now sells artifacts and minerals, with a bias towards undonated ones, to mitigate randomness. + She will sell these items as the player receives "Traveling Merchant Metal Detector" items. - TV Channels - Babies * Only if Friendsanity is enabled @@ -96,18 +113,22 @@ And lastly, some Archipelago-exclusive items exist in the pool, which are design ## When the player receives an item, what happens? -Since Pelican Town is a remote area, it takes one business day for every item to reach the player. If an item is received while online, it will appear in the player's mailbox the next morning, with a message from the sender telling them where it was found. -If an item is received while offline, it will be in the mailbox as soon as the player logs in. +Since Pelican Town is a remote area, it takes one business day for every item to reach the player. If an item is received +while online, it will appear in the player's mailbox the next morning, with a message from the sender telling them where +it was found. If an item is received while offline, it will be in the mailbox as soon as the player logs in. -Some items will be directly attached to the letter, while some others will instead be a world-wide unlock, and the letter only serves to tell the player about it. +Some items will be directly attached to the letter, while some others will instead be a world-wide unlock, and the letter +only serves to tell the player about it. -In some cases, like receiving Carpenter and Wizard buildings, the player will still need to go ask Robin to construct the building that they have received, so they can choose its position. This construction will be completely free. +In some cases, like receiving Carpenter and Wizard buildings, the player will still need to go ask Robin to construct the +building that they have received, so they can choose its position. This construction will be completely free. ## Mods Some Stardew Valley mods unrelated to Archipelago are officially "supported". -This means that, for these specific mods, if you decide to include them in your yaml settings, the multiworld will be generated with the assumption that you will install and play with these mods. -The multiworld will contain related items and locations for these mods, the specifics will vary from mod to mod +This means that, for these specific mods, if you decide to include them in your yaml options, the multiworld will be generated +with the assumption that you will install and play with these mods. The multiworld will contain related items and locations +for these mods, the specifics will vary from mod to mod [Supported Mods Documentation](https://github.com/agilbert1412/StardewArchipelago/blob/5.x.x/Documentation/Supported%20Mods.md) @@ -141,10 +162,12 @@ List of supported mods: * [Custom NPC - Riley](https://www.nexusmods.com/stardewvalley/mods/5811) * [Alecto the Witch](https://www.nexusmods.com/stardewvalley/mods/10671) -Some of these mods might need a patch mod to tie the randomizer with the mod. These can be found [here](https://github.com/Witchybun/SDV-Randomizer-Content-Patcher/releases) +Some of these mods might need a patch mod to tie the randomizer with the mod. These can be found +[here](https://github.com/Witchybun/SDV-Randomizer-Content-Patcher/releases) ## Multiplayer You cannot play an Archipelago Slot in multiplayer at the moment. There are no short-term plans to support that feature. -You can, however, send Stardew Valley objects as gifts from one Stardew Player to another Stardew player, using in-game Joja Prime delivery, for a fee. This exclusive feature can be turned off if you don't want to send and receive gifts. +You can, however, send Stardew Valley objects as gifts from one Stardew Player to another Stardew player, using in-game +Joja Prime delivery, for a fee. This exclusive feature can be turned off if you don't want to send and receive gifts. diff --git a/worlds/stardew_valley/docs/setup_en.md b/worlds/stardew_valley/docs/setup_en.md index 02d6979b7aee..3b51622d8d0a 100644 --- a/worlds/stardew_valley/docs/setup_en.md +++ b/worlds/stardew_valley/docs/setup_en.md @@ -5,15 +5,18 @@ - Stardew Valley on PC (Recommended: [Steam version](https://store.steampowered.com/app/413150/Stardew_Valley/)) - SMAPI ([Mod loader for Stardew Valley](https://smapi.io/)) - [StardewArchipelago Mod Release 5.x.x](https://github.com/agilbert1412/StardewArchipelago/releases) - - It is important to use a mod release of version 5.x.x to play seeds that have been generated here. Later releases can only be used with later releases of the world generator, that are not hosted on archipelago.gg yet. + - It is important to use a mod release of version 5.x.x to play seeds that have been generated here. Later releases + can only be used with later releases of the world generator, that are not hosted on archipelago.gg yet. ## Optional Software - Archipelago from the [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases) * (Only for the TextClient) - Other Stardew Valley Mods [Nexus Mods](https://www.nexusmods.com/stardewvalley) - * There are [supported mods](https://github.com/agilbert1412/StardewArchipelago/blob/5.x.x/Documentation/Supported%20Mods.md) that you can add to your yaml to include them with the Archipelago randomization + * There are [supported mods](https://github.com/agilbert1412/StardewArchipelago/blob/5.x.x/Documentation/Supported%20Mods.md) + that you can add to your yaml to include them with the Archipelago randomization - * It is **not** recommended to further mod Stardew Valley with unsupported mods, although it is possible to do so. Mod interactions can be unpredictable, and no support will be offered for related bugs. + * It is **not** recommended to further mod Stardew Valley with unsupported mods, although it is possible to do so. + Mod interactions can be unpredictable, and no support will be offered for related bugs. * The more unsupported mods you have, and the bigger they are, the more likely things are to break. ## Configuring your YAML file @@ -25,14 +28,15 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a YAML file? -You can customize your settings by visiting the [Stardew Valley Player Settings Page](/games/Stardew%20Valley/player-settings) +You can customize your options by visiting the [Stardew Valley Player Options Page](/games/Stardew%20Valley/player-options) ## Joining a MultiWorld Game ### Installing the mod - Install [SMAPI](https://smapi.io/) by following the instructions on their website -- Download and extract the [StardewArchipelago](https://github.com/agilbert1412/StardewArchipelago/releases) mod into your Stardew Valley "Mods" folder +- Download and extract the [StardewArchipelago](https://github.com/agilbert1412/StardewArchipelago/releases) mod into +your Stardew Valley "Mods" folder - *OPTIONAL*: If you want to launch your game through Steam, add the following to your Stardew Valley launch options: - "[PATH TO STARDEW VALLEY]\Stardew Valley\StardewModdingAPI.exe" %command% - Otherwise just launch "StardewModdingAPI.exe" in your installation folder directly @@ -69,14 +73,20 @@ If the room's ip or port **does** change, you can follow these instructions to m ### Interacting with the MultiWorld from in-game -When you connect, you should see a message in the chat informing you of the `!!help` command. This command will list other Stardew-exclusive chat commands you can use. +When you connect, you should see a message in the chat informing you of the `!!help` command. This command will list other +Stardew-exclusive chat commands you can use. -Furthermore, you can use the in-game chat box to talk to other players in the multiworld, assuming they are using a game that supports chatting. +Furthermore, you can use the in-game chat box to talk to other players in the multiworld, assuming they are using a game +that supports chatting. -Lastly, you can also run Archipelago commands `!help` from the in game chat box, allowing you to request hints on certain items, or check missing locations. +Lastly, you can also run Archipelago commands `!help` from the in game chat box, allowing you to request hints on certain +items, or check missing locations. -It is important to note that the Stardew Valley chat is fairly limited in its capabilities. For example, it doesn't allow scrolling up to see history that has been pushed off screen. The SMAPI console running alonside your game will have the full history as well and may be better suited to read older messages. -For a better chat experience, you can also use the official Archipelago Text Client, altough it will not allow you to run Stardew-exclusive commands. +It is important to note that the Stardew Valley chat is fairly limited in its capabilities. For example, it doesn't allow +scrolling up to see history that has been pushed off screen. The SMAPI console running alonside your game will have the +full history as well and may be better suited to read older messages. +For a better chat experience, you can also use the official Archipelago Text Client, altough it will not allow you to run +Stardew-exclusive commands. ### Playing with supported mods @@ -84,4 +94,4 @@ See the [Supported mods documentation](https://github.com/agilbert1412/StardewAr ### Multiplayer -You cannot play an Archipelago Slot in multiplayer at the moment. There are no short-term plans to support that feature. +You cannot play an Archipelago Slot in multiplayer at the moment. There are no short-term plans to support that feature. \ No newline at end of file diff --git a/worlds/stardew_valley/locations.py b/worlds/stardew_valley/locations.py index 3bd1cf21e3f6..103b3bd96081 100644 --- a/worlds/stardew_valley/locations.py +++ b/worlds/stardew_valley/locations.py @@ -6,7 +6,7 @@ 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 c2d239d2749e..634de45285f7 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 diff --git a/worlds/subnautica/docs/en_Subnautica.md b/worlds/subnautica/docs/en_Subnautica.md index 5e99208b5f44..50004de5a003 100644 --- a/worlds/subnautica/docs/en_Subnautica.md +++ b/worlds/subnautica/docs/en_Subnautica.md @@ -1,8 +1,8 @@ # Subnautica -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/subnautica/docs/setup_en.md b/worlds/subnautica/docs/setup_en.md index 83f4186bdfaf..7fc637df2639 100644 --- a/worlds/subnautica/docs/setup_en.md +++ b/worlds/subnautica/docs/setup_en.md @@ -19,7 +19,7 @@ Use the connect form in Subnautica's main menu to enter your connection information to connect to an Archipelago multiworld. Connection information consists of: - Host: the full url that you're trying to connect to, such as `archipelago.gg:38281`. - - PlayerName: your name in the multiworld. Can also be called "slot name" and is the name you entered when creating your settings. + - PlayerName: your name in the multiworld. Can also be called "slot name" and is the name you entered when creating your options. - Password: optional password, leave blank if no password was set. After the connection is made, start a new game. You should start to see Archipelago chat messages to appear, such as a message announcing that you joined the multiworld. diff --git a/worlds/terraria/docs/en_Terraria.md b/worlds/terraria/docs/en_Terraria.md index b0a8529ba76b..26428f75789d 100644 --- a/worlds/terraria/docs/en_Terraria.md +++ b/worlds/terraria/docs/en_Terraria.md @@ -1,14 +1,14 @@ # Terraria -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? Boss/event flags are randomized. So, defeating Empress of Light could give you Post-Skeletron, which allows you to enter -the Dungeon, for example. In your player settings, you may also add item rewards and achievements to the pool. +the Dungeon, for example. In your player options, you may also add item rewards and achievements to the pool. ## What Terraria items can appear in other players' worlds? diff --git a/worlds/terraria/docs/setup_en.md b/worlds/terraria/docs/setup_en.md index b69af591fa5c..55a4df1df30d 100644 --- a/worlds/terraria/docs/setup_en.md +++ b/worlds/terraria/docs/setup_en.md @@ -43,7 +43,7 @@ files are, and how they are used. ### Where do I get a YAML? -You can use the [game settings page for Terraria](/games/Terraria/player-settings) here +You can use the [game options page for Terraria](/games/Terraria/player-options) here on the Archipelago website to generate a YAML using a graphical interface. ## Joining an Archipelago Game in Terraria diff --git a/worlds/timespinner/docs/en_Timespinner.md b/worlds/timespinner/docs/en_Timespinner.md index 6a9e7fa4c039..a5b1419b94a7 100644 --- a/worlds/timespinner/docs/en_Timespinner.md +++ b/worlds/timespinner/docs/en_Timespinner.md @@ -1,8 +1,8 @@ # Timespinner -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/timespinner/docs/setup_en.md b/worlds/timespinner/docs/setup_en.md index c47c639cd20d..7ee51f91323a 100644 --- a/worlds/timespinner/docs/setup_en.md +++ b/worlds/timespinner/docs/setup_en.md @@ -33,8 +33,8 @@ randomized mode. For more info see the [ReadMe](https://github.com/Jarno458/TsRa ## Where do I get a config file? -The [Player Settings](/games/Timespinner/player-settings) page on the website allows you to -configure your personal settings and export them into a config file +The [Player Options](/games/Timespinner/player-options) page on the website allows you to +configure your personal options and export them into a config file * The Timespinner Randomizer option "StinkyMaw" is currently always enabled for Archipelago generated seeds * The Timespinner Randomizer options "ProgressiveVerticalMovement" & "ProgressiveKeycards" are currently not supported diff --git a/worlds/tloz/docs/en_The Legend of Zelda.md b/worlds/tloz/docs/en_The Legend of Zelda.md index 7c2e6deda5bd..938496a161ae 100644 --- a/worlds/tloz/docs/en_The Legend of Zelda.md +++ b/worlds/tloz/docs/en_The Legend of Zelda.md @@ -1,8 +1,8 @@ # The Legend of Zelda (NES) -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/tloz/docs/multiworld_en.md b/worlds/tloz/docs/multiworld_en.md index df857f16df5b..366531e2e43a 100644 --- a/worlds/tloz/docs/multiworld_en.md +++ b/worlds/tloz/docs/multiworld_en.md @@ -39,8 +39,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The Player Settings page on the website allows you to configure your personal settings and export a config file from -them. Player settings page: [The Legend of Zelda Player Settings Page](/games/The%20Legend%20of%20Zelda/player-settings) +The Player Options page on the website allows you to configure your personal options and export a config file from +them. Player options page: [The Legend of Zelda Player Sptions Page](/games/The%20Legend%20of%20Zelda/player-options) ### Verifying your config file @@ -49,8 +49,8 @@ validator page: [YAML Validation page](/check) ## Generating a Single-Player Game -1. Navigate to the Player Settings page, configure your options, and click the "Generate Game" button. - - Player Settings page: [The Legend of Zelda Player Settings Page](/games/The%20Legend%20of%20Zelda/player-settings) +1. Navigate to the Player Options page, configure your options, and click the "Generate Game" button. + - Player Options page: [The Legend of Zelda Player Options Page](/games/The%20Legend%20of%20Zelda/player-options) 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. diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index c4b1bbec8ea3..3220c6c9347d 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -7,6 +7,7 @@ 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 @@ def generate_early(self) -> None: 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 @@ def create_items(self) -> None: 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 @@ def create_items(self) -> None: 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 @@ def create_regions(self) -> None: 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 @@ def create_regions(self) -> None: 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 @@ def get_filler_item_name(self) -> str: 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 @@ def fill_slot_data(self) -> Dict[str, Any]: "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 5921d0ed092d..ad328999ac0c 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 8d8db426f67d..d850a06dfa78 100644 --- a/worlds/tunic/er_data.py +++ b/worlds/tunic/er_data.py @@ -5,505 +5,509 @@ 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"), - - 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's Domain Ladder Exit", region="Frog's Domain", - destination="Frog Stairs_Entrance"), + destination="Frog Stairs", tag="_mouth"), + + 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 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"), - - 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"), + destination="Swamp Redux 2", tag="_teleporter_relic plinth"), + + 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 @@ class Hint(IntEnum): "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 @@ class Hint(IntEnum): "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 @@ class Hint(IntEnum): "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 @@ class Hint(IntEnum): ["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 @@ class Hint(IntEnum): ["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 @@ class Hint(IntEnum): ["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 @@ class Hint(IntEnum): ["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 @@ class Hint(IntEnum): ["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 @@ class Hint(IntEnum): ["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 fec6635422ac..fdfd064561fe 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 @@ 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 Main"], + 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_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 Hero's Grave"], + 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 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["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: 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"], - 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)) + 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 + # 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)) - - # 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)) - - # 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)) - - # Frog mouth entrance - regions["Ruined Atoll"].connect( - regions[get_paired_region("Atoll Redux, Frog Stairs_mouth")], - rule=lambda state: has_stick(state, player)) - - # 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)) - - # 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)) - - # 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)) - - # 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)) - - # 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)) - - # 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)) - - # 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)) - - # 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)) - - # 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)) + 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"}), + + # 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"}), + + # 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"}), + + # 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"}), + + # 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()), + + # 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()), + + # 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()), + + # East Forest + # Entrance by the dancing fox holy cross spot + ("East Forest", "East Forest Redux, East Forest Redux Laddercave_upper", set()), + + # 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()), + + # 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()), + + # 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()), + + # 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()), + + # 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 291cd7b3310e..5756ec90be14 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) - - # 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 + # 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() - # 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 hint_helper(portal: Portal, hint_string: str = "") -> str: 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 @@ def hint_helper(portal: Portal, hint_string: 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 547a0ffb816f..7483d55bf1cc 100644 --- a/worlds/tunic/items.py +++ b/worlds/tunic/items.py @@ -143,6 +143,28 @@ class TunicItemData(NamedTuple): "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 @@ def get_item_group(item_name: str) -> 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 1501fb7da24d..4d95e91cb3cc 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 @@ class TunicLocationData(NamedTuple): "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 @@ class TunicLocationData(NamedTuple): "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 @@ class TunicLocationData(NamedTuple): "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 @@ class TunicLocationData(NamedTuple): "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 @@ class TunicLocationData(NamedTuple): "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 @@ class TunicLocationData(NamedTuple): "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 @@ class TunicLocationData(NamedTuple): "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 @@ class TunicLocationData(NamedTuple): "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 @@ class TunicLocationData(NamedTuple): "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 @@ class TunicLocationData(NamedTuple): "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 @@ class TunicLocationData(NamedTuple): "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 @@ class TunicLocationData(NamedTuple): "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 @@ class TunicLocationData(NamedTuple): 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 779e632326db..38ddcbe8e40f 100644 --- a/worlds/tunic/options.py +++ b/worlds/tunic/options.py @@ -4,8 +4,7 @@ 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 70204c639733..c30a44bb8ff6 100644 --- a/worlds/tunic/regions.py +++ b/worlds/tunic/regions.py @@ -6,10 +6,10 @@ "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 b3dd0b683220..c82c5ca13339 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), diff --git a/worlds/undertale/docs/en_Undertale.md b/worlds/undertale/docs/en_Undertale.md index 7ff5d55edad9..02fc32f0abc6 100644 --- a/worlds/undertale/docs/en_Undertale.md +++ b/worlds/undertale/docs/en_Undertale.md @@ -1,8 +1,8 @@ # Undertale -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What is considered a location check in Undertale? diff --git a/worlds/undertale/docs/setup_en.md b/worlds/undertale/docs/setup_en.md index 3c20b614d3fa..f1f740959127 100644 --- a/worlds/undertale/docs/setup_en.md +++ b/worlds/undertale/docs/setup_en.md @@ -61,4 +61,4 @@ gameplay differences at the bottom. ### Where do I get a YAML file? -You can customize your settings by visiting the [Undertale Player Settings Page](/games/Undertale/player-settings) +You can customize your options by visiting the [Undertale Player Options Page](/games/Undertale/player-options) diff --git a/worlds/v6/docs/en_VVVVVV.md b/worlds/v6/docs/en_VVVVVV.md index 5c2aa8fec957..c5790e01c5dd 100644 --- a/worlds/v6/docs/en_VVVVVV.md +++ b/worlds/v6/docs/en_VVVVVV.md @@ -1,9 +1,9 @@ # VVVVVV -## Where is the settings page? +## Where is the options page? -The player settings page for this game contains all the options you need to configure and export a config file. Player -settings page link: [VVVVVV Player Settings Page](../player-settings). +The player options page for this game contains all the options you need to configure and export a config file. Player +options page link: [VVVVVV Player Options Page](../player-options). ## What does randomization do to this game? All 20 Trinkets are now Location Checks and may not actually contain Trinkets, but Items for different games. diff --git a/worlds/v6/docs/setup_en.md b/worlds/v6/docs/setup_en.md index 7adf5948c7e4..a23b6c5b252a 100644 --- a/worlds/v6/docs/setup_en.md +++ b/worlds/v6/docs/setup_en.md @@ -30,7 +30,7 @@ If everything worked out, you will see a textbox informing you the connection ha # Playing offline -To play offline, first generate a seed on the game's settings page. +To play offline, first generate a seed on the game's options page. Create a room and download the `.apv6` file, include the offline single-player launch option described above. ## Installation Troubleshooting diff --git a/worlds/wargroove/docs/en_Wargroove.md b/worlds/wargroove/docs/en_Wargroove.md index f08902535d4b..31fd8c81301c 100644 --- a/worlds/wargroove/docs/en_Wargroove.md +++ b/worlds/wargroove/docs/en_Wargroove.md @@ -1,8 +1,8 @@ # Wargroove (Steam, Windows) -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? diff --git a/worlds/wargroove/docs/wargroove_en.md b/worlds/wargroove/docs/wargroove_en.md index 1954dc013924..9c2645178aa2 100644 --- a/worlds/wargroove/docs/wargroove_en.md +++ b/worlds/wargroove/docs/wargroove_en.md @@ -38,7 +38,7 @@ This should install the mod and campaign for you. ## Starting a Multiworld game 1. Start the Wargroove Client and connect to the server. Enter your username from your -[settings file.](/games/Wargroove/player-settings) +[options file.](/games/Wargroove/player-options) 2. Start Wargroove and play the Archipelago campaign by going to `Story->Campaign->Custom->Archipelago`. ## Ending a Multiworld game diff --git a/worlds/witness/docs/en_The Witness.md b/worlds/witness/docs/en_The Witness.md index 4d00ecaae451..6882ed3fdedf 100644 --- a/worlds/witness/docs/en_The Witness.md +++ b/worlds/witness/docs/en_The Witness.md @@ -1,8 +1,8 @@ # The Witness -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What does randomization do to this game? @@ -16,7 +16,7 @@ Panels with puzzle symbols on them are now locked initially. ## What is a "check" in The Witness? Solving the last panel in a row of panels or an important standalone panel will count as a check, and send out an item. -It is also possible to add Environmental Puzzles into the location pool via the "Shuffle Environmental Puzzles" setting. +It is also possible to add Environmental Puzzles into the location pool via the "Shuffle Environmental Puzzles" option. ## What "items" can you unlock in The Witness? @@ -25,7 +25,7 @@ This includes symbols such as "Dots", "Black/White Squares", "Colored Squares", Alternatively (or additionally), you can play "Door shuffle", where some doors won't open until you receive their "key". -Receiving lasers as items is also a possible setting. +You can also set lasers to be items you can receive. ## What else can I find in the world? diff --git a/worlds/witness/options.py b/worlds/witness/options.py index 5bce3e3a220c..b66308df432a 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -8,19 +8,23 @@ 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 diff --git a/worlds/witness/player_logic.py b/worlds/witness/player_logic.py index 099a3a64e611..6bc263b9cc68 100644 --- a/worlds/witness/player_logic.py +++ b/worlds/witness/player_logic.py @@ -652,8 +652,8 @@ def __init__(self, world: "WitnessWorld", disabled_locations: Set[str], start_in elif self.DIFFICULTY == "none": self.REFERENCE_LOGIC = StaticWitnessLogic.vanilla - self.CONNECTIONS_BY_REGION_NAME = copy.copy(self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME) - self.DEPENDENT_REQUIREMENTS_BY_HEX = copy.copy(self.REFERENCE_LOGIC.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX) + self.CONNECTIONS_BY_REGION_NAME = copy.deepcopy(self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME) + self.DEPENDENT_REQUIREMENTS_BY_HEX = copy.deepcopy(self.REFERENCE_LOGIC.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX) self.REQUIREMENTS_BY_HEX = dict() # Determining which panels need to be events is a difficult process. diff --git a/worlds/yoshisisland/Client.py b/worlds/yoshisisland/Client.py new file mode 100644 index 000000000000..c512a8316ab5 --- /dev/null +++ b/worlds/yoshisisland/Client.py @@ -0,0 +1,144 @@ +import logging +import struct +import typing +import time +from struct import pack + +from NetUtils import ClientStatus, color +from worlds.AutoSNIClient import SNIClient + +if typing.TYPE_CHECKING: + from SNIClient import SNIContext + +snes_logger = logging.getLogger("SNES") + +ROM_START = 0x000000 +WRAM_START = 0xF50000 +WRAM_SIZE = 0x20000 +SRAM_START = 0xE00000 + +YOSHISISLAND_ROMHASH_START = 0x007FC0 +ROMHASH_SIZE = 0x15 + +ITEMQUEUE_HIGH = WRAM_START + 0x1465 +ITEM_RECEIVED = WRAM_START + 0x1467 +DEATH_RECEIVED = WRAM_START + 0x7E23B0 +GAME_MODE = WRAM_START + 0x0118 +YOSHI_STATE = SRAM_START + 0x00AC +DEATHLINK_ADDR = ROM_START + 0x06FC8C +DEATHMUSIC_FLAG = WRAM_START + 0x004F +DEATHFLAG = WRAM_START + 0x00DB +DEATHLINKRECV = WRAM_START + 0x00E0 +GOALFLAG = WRAM_START + 0x14B6 + +VALID_GAME_STATES = [0x0F, 0x10, 0x2C] + + +class YoshisIslandSNIClient(SNIClient): + game = "Yoshi's Island" + + async def deathlink_kill_player(self, ctx: "SNIContext") -> None: + from SNIClient import DeathState, snes_buffered_write, snes_flush_writes, snes_read + game_state = await snes_read(ctx, GAME_MODE, 0x1) + if game_state[0] != 0x0F: + return + + yoshi_state = await snes_read(ctx, YOSHI_STATE, 0x1) + if yoshi_state[0] != 0x00: + return + + snes_buffered_write(ctx, WRAM_START + 0x026A, bytes([0x01])) + snes_buffered_write(ctx, WRAM_START + 0x00E0, bytes([0x01])) + await snes_flush_writes(ctx) + ctx.death_state = DeathState.dead + ctx.last_death_link = time.time() + + async def validate_rom(self, ctx: "SNIContext") -> bool: + from SNIClient import snes_read + + rom_name = await snes_read(ctx, YOSHISISLAND_ROMHASH_START, ROMHASH_SIZE) + if rom_name is None or rom_name[:7] != b"YOSHIAP": + return False + + ctx.game = self.game + ctx.items_handling = 0b111 # remote items + ctx.rom = rom_name + + death_link = await snes_read(ctx, DEATHLINK_ADDR, 1) + if death_link: + await ctx.update_death_link(bool(death_link[0] & 0b1)) + return True + + async def game_watcher(self, ctx: "SNIContext") -> None: + from SNIClient import snes_buffered_write, snes_flush_writes, snes_read + + game_mode = await snes_read(ctx, GAME_MODE, 0x1) + item_received = await snes_read(ctx, ITEM_RECEIVED, 0x1) + game_music = await snes_read(ctx, DEATHMUSIC_FLAG, 0x1) + goal_flag = await snes_read(ctx, GOALFLAG, 0x1) + + if "DeathLink" in ctx.tags and ctx.last_death_link + 1 < time.time(): + death_flag = await snes_read(ctx, DEATHFLAG, 0x1) + deathlink_death = await snes_read(ctx, DEATHLINKRECV, 0x1) + currently_dead = (game_music[0] == 0x07 or game_mode[0] == 0x12 or + (death_flag[0] == 0x00 and game_mode[0] == 0x11)) and deathlink_death[0] == 0x00 + await ctx.handle_deathlink_state(currently_dead) + + if game_mode is None: + return + elif goal_flag[0] != 0x00: + await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) + ctx.finished_game = True + elif game_mode[0] not in VALID_GAME_STATES: + return + elif item_received[0] > 0x00: + return + + from .Rom import item_values + rom = await snes_read(ctx, YOSHISISLAND_ROMHASH_START, ROMHASH_SIZE) + if rom != ctx.rom: + ctx.rom = None + return + + new_checks = [] + from .Rom import location_table + + location_ram_data = await snes_read(ctx, WRAM_START + 0x1440, 0x80) + for loc_id, loc_data in location_table.items(): + if loc_id not in ctx.locations_checked: + data = location_ram_data[loc_data[0] - 0x1440] + masked_data = data & (1 << loc_data[1]) + bit_set = masked_data != 0 + invert_bit = ((len(loc_data) >= 3) and loc_data[2]) + if bit_set != invert_bit: + new_checks.append(loc_id) + + for new_check_id in new_checks: + ctx.locations_checked.add(new_check_id) + location = ctx.location_names[new_check_id] + total_locations = len(ctx.missing_locations) + len(ctx.checked_locations) + snes_logger.info(f"New Check: {location} ({len(ctx.locations_checked)}/{total_locations})") + await ctx.send_msgs([{"cmd": "LocationChecks", "locations": [new_check_id]}]) + + recv_count = await snes_read(ctx, ITEMQUEUE_HIGH, 2) + recv_index = struct.unpack("H", recv_count)[0] + if recv_index < len(ctx.items_received): + item = ctx.items_received[recv_index] + recv_index += 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_index, len(ctx.items_received))) + + snes_buffered_write(ctx, ITEMQUEUE_HIGH, pack("H", recv_index)) + if item.item in item_values: + item_count = await snes_read(ctx, WRAM_START + item_values[item.item][0], 0x1) + increment = item_values[item.item][1] + new_item_count = item_count[0] + if increment > 1: + new_item_count = increment + else: + new_item_count += increment + + snes_buffered_write(ctx, WRAM_START + item_values[item.item][0], bytes([new_item_count])) + await snes_flush_writes(ctx) diff --git a/worlds/yoshisisland/Items.py b/worlds/yoshisisland/Items.py new file mode 100644 index 000000000000..c97678ed4ed4 --- /dev/null +++ b/worlds/yoshisisland/Items.py @@ -0,0 +1,122 @@ +from typing import Dict, Set, Tuple, NamedTuple, Optional +from BaseClasses import ItemClassification + +class ItemData(NamedTuple): + category: str + code: Optional[int] + classification: ItemClassification + amount: Optional[int] = 1 + +item_table: Dict[str, ItemData] = { + "! Switch": ItemData("Items", 0x302050, ItemClassification.progression), + "Dashed Platform": ItemData("Items", 0x302051, ItemClassification.progression), + "Dashed Stairs": ItemData("Items", 0x302052, ItemClassification.progression), + "Beanstalk": ItemData("Items", 0x302053, ItemClassification.progression), + "Helicopter Morph": ItemData("Morphs", 0x302054, ItemClassification.progression), + "Spring Ball": ItemData("Items", 0x302055, ItemClassification.progression), + "Large Spring Ball": ItemData("Items", 0x302056, ItemClassification.progression), + "Arrow Wheel": ItemData("Items", 0x302057, ItemClassification.progression), + "Vanishing Arrow Wheel": ItemData("Items", 0x302058, ItemClassification.progression), + "Mole Tank Morph": ItemData("Morphs", 0x302059, ItemClassification.progression), + "Watermelon": ItemData("Items", 0x30205A, ItemClassification.progression), + "Ice Melon": ItemData("Items", 0x30205B, ItemClassification.progression), + "Fire Melon": ItemData("Items", 0x30205C, ItemClassification.progression), + "Super Star": ItemData("Items", 0x30205D, ItemClassification.progression), + "Car Morph": ItemData("Morphs", 0x30205E, ItemClassification.progression), + "Flashing Eggs": ItemData("Items", 0x30205F, ItemClassification.progression), + "Giant Eggs": ItemData("Items", 0x302060, ItemClassification.progression), + "Egg Launcher": ItemData("Items", 0x302061, ItemClassification.progression), + "Egg Plant": ItemData("Items", 0x302062, ItemClassification.progression), + "Submarine Morph": ItemData("Morphs", 0x302063, ItemClassification.progression), + "Chomp Rock": ItemData("Items", 0x302064, ItemClassification.progression), + "Poochy": ItemData("Items", 0x302065, ItemClassification.progression), + "Platform Ghost": ItemData("Items", 0x302066, ItemClassification.progression), + "Skis": ItemData("Items", 0x302067, ItemClassification.progression), + "Train Morph": ItemData("Morphs", 0x302068, ItemClassification.progression), + "Key": ItemData("Items", 0x302069, ItemClassification.progression), + "Middle Ring": ItemData("Items", 0x30206A, ItemClassification.progression), + "Bucket": ItemData("Items", 0x30206B, ItemClassification.progression), + "Tulip": ItemData("Items", 0x30206C, ItemClassification.progression), + "Egg Capacity Upgrade": ItemData("Items", 0x30206D, ItemClassification.progression, 5), + "Secret Lens": ItemData("Items", 0x302081, ItemClassification.progression), + + "World 1 Gate": ItemData("Gates", 0x30206E, ItemClassification.progression), + "World 2 Gate": ItemData("Gates", 0x30206F, ItemClassification.progression), + "World 3 Gate": ItemData("Gates", 0x302070, ItemClassification.progression), + "World 4 Gate": ItemData("Gates", 0x302071, ItemClassification.progression), + "World 5 Gate": ItemData("Gates", 0x302072, ItemClassification.progression), + "World 6 Gate": ItemData("Gates", 0x302073, ItemClassification.progression), + + "Extra 1": ItemData("Panels", 0x302074, ItemClassification.progression), + "Extra 2": ItemData("Panels", 0x302075, ItemClassification.progression), + "Extra 3": ItemData("Panels", 0x302076, ItemClassification.progression), + "Extra 4": ItemData("Panels", 0x302077, ItemClassification.progression), + "Extra 5": ItemData("Panels", 0x302078, ItemClassification.progression), + "Extra 6": ItemData("Panels", 0x302079, ItemClassification.progression), + "Extra Panels": ItemData("Panels", 0x30207A, ItemClassification.progression), + + "Bonus 1": ItemData("Panels", 0x30207B, ItemClassification.progression), + "Bonus 2": ItemData("Panels", 0x30207C, ItemClassification.progression), + "Bonus 3": ItemData("Panels", 0x30207D, ItemClassification.progression), + "Bonus 4": ItemData("Panels", 0x30207E, ItemClassification.progression), + "Bonus 5": ItemData("Panels", 0x30207F, ItemClassification.progression), + "Bonus 6": ItemData("Panels", 0x302080, ItemClassification.progression), + "Bonus Panels": ItemData("Panels", 0x302082, ItemClassification.progression), + + "Anytime Egg": ItemData("Consumable", 0x302083, ItemClassification.useful, 0), + "Anywhere Pow": ItemData("Consumable", 0x302084, ItemClassification.filler, 0), + "Winged Cloud Maker": ItemData("Consumable", 0x302085, ItemClassification.filler, 0), + "Pocket Melon": ItemData("Consumable", 0x302086, ItemClassification.filler, 0), + "Pocket Fire Melon": ItemData("Consumable", 0x302087, ItemClassification.filler, 0), + "Pocket Ice Melon": ItemData("Consumable", 0x302088, ItemClassification.filler, 0), + "Magnifying Glass": ItemData("Consumable", 0x302089, ItemClassification.filler, 0), + "+10 Stars": ItemData("Consumable", 0x30208A, ItemClassification.useful, 0), + "+20 Stars": ItemData("Consumable", 0x30208B, ItemClassification.useful, 0), + "1-Up": ItemData("Lives", 0x30208C, ItemClassification.filler, 0), + "2-Up": ItemData("Lives", 0x30208D, ItemClassification.filler, 0), + "3-Up": ItemData("Lives", 0x30208E, ItemClassification.filler, 0), + "10-Up": ItemData("Lives", 0x30208F, ItemClassification.filler, 5), + "Bonus Consumables": ItemData("Events", None, ItemClassification.progression, 0), + "Bandit Consumables": ItemData("Events", None, ItemClassification.progression, 0), + "Bandit Watermelons": ItemData("Events", None, ItemClassification.progression, 0), + + "Fuzzy Trap": ItemData("Traps", 0x302090, ItemClassification.trap, 0), + "Reversal Trap": ItemData("Traps", 0x302091, ItemClassification.trap, 0), + "Darkness Trap": ItemData("Traps", 0x302092, ItemClassification.trap, 0), + "Freeze Trap": ItemData("Traps", 0x302093, ItemClassification.trap, 0), + + "Boss Clear": ItemData("Events", None, ItemClassification.progression, 0), + "Piece of Luigi": ItemData("Items", 0x302095, ItemClassification.progression, 0), + "Saved Baby Luigi": ItemData("Events", None, ItemClassification.progression, 0) +} + +filler_items: Tuple[str, ...] = ( + "Anytime Egg", + "Anywhere Pow", + "Winged Cloud Maker", + "Pocket Melon", + "Pocket Fire Melon", + "Pocket Ice Melon", + "Magnifying Glass", + "+10 Stars", + "+20 Stars", + "1-Up", + "2-Up", + "3-Up" +) + +trap_items: Tuple[str, ...] = ( + "Fuzzy Trap", + "Reversal Trap", + "Darkness Trap", + "Freeze Trap" +) + +def get_item_names_per_category() -> Dict[str, Set[str]]: + categories: Dict[str, Set[str]] = {} + + for name, data in item_table.items(): + if data.category != "Events": + categories.setdefault(data.category, set()).add(name) + + return categories diff --git a/worlds/yoshisisland/Locations.py b/worlds/yoshisisland/Locations.py new file mode 100644 index 000000000000..bc0855260eb4 --- /dev/null +++ b/worlds/yoshisisland/Locations.py @@ -0,0 +1,355 @@ +from typing import List, Optional, NamedTuple, TYPE_CHECKING + +from .Options import PlayerGoal, MinigameChecks +from worlds.generic.Rules import CollectionRule + +if TYPE_CHECKING: + from . import YoshisIslandWorld +from .level_logic import YoshiLogic + + +class LocationData(NamedTuple): + region: str + name: str + code: Optional[int] + LevelID: int + rule: CollectionRule = lambda state: True + + +def get_locations(world: Optional["YoshisIslandWorld"]) -> List[LocationData]: + if world: + logic = YoshiLogic(world) + + location_table: List[LocationData] = [ + LocationData("1-1", "Make Eggs, Throw Eggs: Red Coins", 0x305020, 0x00), + LocationData("1-1", "Make Eggs, Throw Eggs: Flowers", 0x305021, 0x00), + LocationData("1-1", "Make Eggs, Throw Eggs: Stars", 0x305022, 0x00), + LocationData("1-1", "Make Eggs, Throw Eggs: Level Clear", 0x305023, 0x00), + + LocationData("1-2", "Watch Out Below!: Red Coins", 0x305024, 0x01), + LocationData("1-2", "Watch Out Below!: Flowers", 0x305025, 0x01), + LocationData("1-2", "Watch Out Below!: Stars", 0x305026, 0x01), + LocationData("1-2", "Watch Out Below!: Level Clear", 0x305027, 0x01), + + LocationData("1-3", "The Cave Of Chomp Rock: Red Coins", 0x305028, 0x02), + LocationData("1-3", "The Cave Of Chomp Rock: Flowers", 0x305029, 0x02), + LocationData("1-3", "The Cave Of Chomp Rock: Stars", 0x30502A, 0x02), + LocationData("1-3", "The Cave Of Chomp Rock: Level Clear", 0x30502B, 0x02), + + LocationData("1-4", "Burt The Bashful's Fort: Red Coins", 0x30502C, 0x03), + LocationData("1-4", "Burt The Bashful's Fort: Flowers", 0x30502D, 0x03), + LocationData("1-4", "Burt The Bashful's Fort: Stars", 0x30502E, 0x03), + LocationData("1-4", "Burt The Bashful's Fort: Level Clear", 0x30502F, 0x03, lambda state: logic._14CanFightBoss(state)), + LocationData("Burt The Bashful's Boss Room", "Burt The Bashful's Boss Room", None, 0x03, lambda state: logic._14Boss(state)), + + LocationData("1-5", "Hop! Hop! Donut Lifts: Red Coins", 0x305031, 0x04), + LocationData("1-5", "Hop! Hop! Donut Lifts: Flowers", 0x305032, 0x04), + LocationData("1-5", "Hop! Hop! Donut Lifts: Stars", 0x305033, 0x04), + LocationData("1-5", "Hop! Hop! Donut Lifts: Level Clear", 0x305034, 0x04), + + LocationData("1-6", "Shy-Guys On Stilts: Red Coins", 0x305035, 0x05), + LocationData("1-6", "Shy-Guys On Stilts: Flowers", 0x305036, 0x05), + LocationData("1-6", "Shy-Guys On Stilts: Stars", 0x305037, 0x05), + LocationData("1-6", "Shy-Guys On Stilts: Level Clear", 0x305038, 0x05), + + LocationData("1-7", "Touch Fuzzy Get Dizzy: Red Coins", 0x305039, 0x06), + LocationData("1-7", "Touch Fuzzy Get Dizzy: Flowers", 0x30503A, 0x06), + LocationData("1-7", "Touch Fuzzy Get Dizzy: Stars", 0x30503B, 0x06), + LocationData("1-7", "Touch Fuzzy Get Dizzy: Level Clear", 0x30503C, 0x06), + LocationData("1-7", "Touch Fuzzy Get Dizzy: Gather Coins", None, 0x06, lambda state: logic._17Game(state)), + + LocationData("1-8", "Salvo The Slime's Castle: Red Coins", 0x30503D, 0x07), + LocationData("1-8", "Salvo The Slime's Castle: Flowers", 0x30503E, 0x07), + LocationData("1-8", "Salvo The Slime's Castle: Stars", 0x30503F, 0x07), + LocationData("1-8", "Salvo The Slime's Castle: Level Clear", 0x305040, 0x07, lambda state: logic._18CanFightBoss(state)), + LocationData("Salvo The Slime's Boss Room", "Salvo The Slime's Boss Room", None, 0x07, lambda state: logic._18Boss(state)), + + LocationData("1-Bonus", "Flip Cards", None, 0x09), + ############################################################################################ + LocationData("2-1", "Visit Koopa And Para-Koopa: Red Coins", 0x305041, 0x0C), + LocationData("2-1", "Visit Koopa And Para-Koopa: Flowers", 0x305042, 0x0C), + LocationData("2-1", "Visit Koopa And Para-Koopa: Stars", 0x305043, 0x0C), + LocationData("2-1", "Visit Koopa And Para-Koopa: Level Clear", 0x305044, 0x0C), + + LocationData("2-2", "The Baseball Boys: Red Coins", 0x305045, 0x0D), + LocationData("2-2", "The Baseball Boys: Flowers", 0x305046, 0x0D), + LocationData("2-2", "The Baseball Boys: Stars", 0x305047, 0x0D), + LocationData("2-2", "The Baseball Boys: Level Clear", 0x305048, 0x0D), + + LocationData("2-3", "What's Gusty Taste Like?: Red Coins", 0x305049, 0x0E), + LocationData("2-3", "What's Gusty Taste Like?: Flowers", 0x30504A, 0x0E), + LocationData("2-3", "What's Gusty Taste Like?: Stars", 0x30504B, 0x0E), + LocationData("2-3", "What's Gusty Taste Like?: Level Clear", 0x30504C, 0x0E), + + LocationData("2-4", "Bigger Boo's Fort: Red Coins", 0x30504D, 0x0F), + LocationData("2-4", "Bigger Boo's Fort: Flowers", 0x30504E, 0x0F), + LocationData("2-4", "Bigger Boo's Fort: Stars", 0x30504F, 0x0F), + LocationData("2-4", "Bigger Boo's Fort: Level Clear", 0x305050, 0x0F, lambda state: logic._24CanFightBoss(state)), + LocationData("Bigger Boo's Boss Room", "Bigger Boo's Boss Room", None, 0x0F, lambda state: logic._24Boss(state)), + + LocationData("2-5", "Watch Out For Lakitu: Red Coins", 0x305051, 0x10), + LocationData("2-5", "Watch Out For Lakitu: Flowers", 0x305052, 0x10), + LocationData("2-5", "Watch Out For Lakitu: Stars", 0x305053, 0x10), + LocationData("2-5", "Watch Out For Lakitu: Level Clear", 0x305054, 0x10), + + LocationData("2-6", "The Cave Of The Mystery Maze: Red Coins", 0x305055, 0x11), + LocationData("2-6", "The Cave Of The Mystery Maze: Flowers", 0x305056, 0x11), + LocationData("2-6", "The Cave Of The Mystery Maze: Stars", 0x305057, 0x11), + LocationData("2-6", "The Cave Of The Mystery Maze: Level Clear", 0x305058, 0x11), + LocationData("2-6", "The Cave Of the Mystery Maze: Seed Spitting Contest", None, 0x11, lambda state: logic._26Game(state)), + + LocationData("2-7", "Lakitu's Wall: Red Coins", 0x305059, 0x12), + LocationData("2-7", "Lakitu's Wall: Flowers", 0x30505A, 0x12), + LocationData("2-7", "Lakitu's Wall: Stars", 0x30505B, 0x12), + LocationData("2-7", "Lakitu's Wall: Level Clear", 0x30505C, 0x12), + LocationData("2-7", "Lakitu's Wall: Gather Coins", None, 0x12, lambda state: logic._27Game(state)), + + LocationData("2-8", "The Potted Ghost's Castle: Red Coins", 0x30505D, 0x13), + LocationData("2-8", "The Potted Ghost's Castle: Flowers", 0x30505E, 0x13), + LocationData("2-8", "The Potted Ghost's Castle: Stars", 0x30505F, 0x13), + LocationData("2-8", "The Potted Ghost's Castle: Level Clear", 0x305060, 0x13, lambda state: logic._28CanFightBoss(state)), + LocationData("Roger The Ghost's Boss Room", "Roger The Ghost's Boss Room", None, 0x13, lambda state: logic._28Boss(state)), + ############################################################################################### + LocationData("3-1", "Welcome To Monkey World!: Red Coins", 0x305061, 0x18), + LocationData("3-1", "Welcome To Monkey World!: Flowers", 0x305062, 0x18), + LocationData("3-1", "Welcome To Monkey World!: Stars", 0x305063, 0x18), + LocationData("3-1", "Welcome To Monkey World!: Level Clear", 0x305064, 0x18), + + LocationData("3-2", "Jungle Rhythm...: Red Coins", 0x305065, 0x19), + LocationData("3-2", "Jungle Rhythm...: Flowers", 0x305066, 0x19), + LocationData("3-2", "Jungle Rhythm...: Stars", 0x305067, 0x19), + LocationData("3-2", "Jungle Rhythm...: Level Clear", 0x305068, 0x19), + + LocationData("3-3", "Nep-Enuts' Domain: Red Coins", 0x305069, 0x1A), + LocationData("3-3", "Nep-Enuts' Domain: Flowers", 0x30506A, 0x1A), + LocationData("3-3", "Nep-Enuts' Domain: Stars", 0x30506B, 0x1A), + LocationData("3-3", "Nep-Enuts' Domain: Level Clear", 0x30506C, 0x1A), + + LocationData("3-4", "Prince Froggy's Fort: Red Coins", 0x30506D, 0x1B), + LocationData("3-4", "Prince Froggy's Fort: Flowers", 0x30506E, 0x1B), + LocationData("3-4", "Prince Froggy's Fort: Stars", 0x30506F, 0x1B), + LocationData("3-4", "Prince Froggy's Fort: Level Clear", 0x305070, 0x1B, lambda state: logic._34CanFightBoss(state)), + LocationData("Prince Froggy's Boss Room", "Prince Froggy's Boss Room", None, 0x1B, lambda state: logic._34Boss(state)), + + LocationData("3-5", "Jammin' Through The Trees: Red Coins", 0x305071, 0x1C), + LocationData("3-5", "Jammin' Through The Trees: Flowers", 0x305072, 0x1C), + LocationData("3-5", "Jammin' Through The Trees: Stars", 0x305073, 0x1C), + LocationData("3-5", "Jammin' Through The Trees: Level Clear", 0x305074, 0x1C), + + LocationData("3-6", "The Cave Of Harry Hedgehog: Red Coins", 0x305075, 0x1D), + LocationData("3-6", "The Cave Of Harry Hedgehog: Flowers", 0x305076, 0x1D), + LocationData("3-6", "The Cave Of Harry Hedgehog: Stars", 0x305077, 0x1D), + LocationData("3-6", "The Cave Of Harry Hedgehog: Level Clear", 0x305078, 0x1D), + + LocationData("3-7", "Monkeys' Favorite Lake: Red Coins", 0x305079, 0x1E), + LocationData("3-7", "Monkeys' Favorite Lake: Flowers", 0x30507A, 0x1E), + LocationData("3-7", "Monkeys' Favorite Lake: Stars", 0x30507B, 0x1E), + LocationData("3-7", "Monkeys' Favorite Lake: Level Clear", 0x30507C, 0x1E), + + LocationData("3-8", "Naval Piranha's Castle: Red Coins", 0x30507D, 0x1F), + LocationData("3-8", "Naval Piranha's Castle: Flowers", 0x30507E, 0x1F), + LocationData("3-8", "Naval Piranha's Castle: Stars", 0x30507F, 0x1F), + LocationData("3-8", "Naval Piranha's Castle: Level Clear", 0x305080, 0x1F, lambda state: logic._38CanFightBoss(state)), + LocationData("Naval Piranha's Boss Room", "Naval Piranha's Boss Room", None, 0x1F, lambda state: logic._38Boss(state)), + + LocationData("3-Bonus", "Drawing Lots", None, 0x21), + ############################################################################################## + LocationData("4-1", "GO! GO! MARIO!!: Red Coins", 0x305081, 0x24), + LocationData("4-1", "GO! GO! MARIO!!: Flowers", 0x305082, 0x24), + LocationData("4-1", "GO! GO! MARIO!!: Stars", 0x305083, 0x24), + LocationData("4-1", "GO! GO! MARIO!!: Level Clear", 0x305084, 0x24), + + LocationData("4-2", "The Cave Of The Lakitus: Red Coins", 0x305085, 0x25), + LocationData("4-2", "The Cave Of The Lakitus: Flowers", 0x305086, 0x25), + LocationData("4-2", "The Cave Of The Lakitus: Stars", 0x305087, 0x25), + LocationData("4-2", "The Cave Of The Lakitus: Level Clear", 0x305088, 0x25), + + LocationData("4-3", "Don't Look Back!: Red Coins", 0x305089, 0x26), + LocationData("4-3", "Don't Look Back!: Flowers", 0x30508A, 0x26), + LocationData("4-3", "Don't Look Back!: Stars", 0x30508B, 0x26), + LocationData("4-3", "Don't Look Back!: Level Clear", 0x30508C, 0x26), + + LocationData("4-4", "Marching Milde's Fort: Red Coins", 0x30508D, 0x27), + LocationData("4-4", "Marching Milde's Fort: Flowers", 0x30508E, 0x27), + LocationData("4-4", "Marching Milde's Fort: Stars", 0x30508F, 0x27), + LocationData("4-4", "Marching Milde's Fort: Level Clear", 0x305090, 0x27, lambda state: logic._44CanFightBoss(state)), + LocationData("Marching Milde's Boss Room", "Marching Milde's Boss Room", None, 0x27, lambda state: logic._44Boss(state)), + + LocationData("4-5", "Chomp Rock Zone: Red Coins", 0x305091, 0x28), + LocationData("4-5", "Chomp Rock Zone: Flowers", 0x305092, 0x28), + LocationData("4-5", "Chomp Rock Zone: Stars", 0x305093, 0x28), + LocationData("4-5", "Chomp Rock Zone: Level Clear", 0x305094, 0x28), + + LocationData("4-6", "Lake Shore Paradise: Red Coins", 0x305095, 0x29), + LocationData("4-6", "Lake Shore Paradise: Flowers", 0x305096, 0x29), + LocationData("4-6", "Lake Shore Paradise: Stars", 0x305097, 0x29), + LocationData("4-6", "Lake Shore Paradise: Level Clear", 0x305098, 0x29), + + LocationData("4-7", "Ride Like The Wind: Red Coins", 0x305099, 0x2A), + LocationData("4-7", "Ride Like The Wind: Flowers", 0x30509A, 0x2A), + LocationData("4-7", "Ride Like The Wind: Stars", 0x30509B, 0x2A), + LocationData("4-7", "Ride Like The Wind: Level Clear", 0x30509C, 0x2A), + LocationData("4-7", "Ride Like The Wind: Gather Coins", None, 0x2A, lambda state: logic._47Game(state)), + + LocationData("4-8", "Hookbill The Koopa's Castle: Red Coins", 0x30509D, 0x2B), + LocationData("4-8", "Hookbill The Koopa's Castle: Flowers", 0x30509E, 0x2B), + LocationData("4-8", "Hookbill The Koopa's Castle: Stars", 0x30509F, 0x2B), + LocationData("4-8", "Hookbill The Koopa's Castle: Level Clear", 0x3050A0, 0x2B, lambda state: logic._48CanFightBoss(state)), + LocationData("Hookbill The Koopa's Boss Room", "Hookbill The Koopa's Boss Room", None, 0x2B, lambda state: logic._48Boss(state)), + + LocationData("4-Bonus", "Match Cards", None, 0x2D), + ###################################################################################################### + LocationData("5-1", "BLIZZARD!!!: Red Coins", 0x3050A1, 0x30), + LocationData("5-1", "BLIZZARD!!!: Flowers", 0x3050A2, 0x30), + LocationData("5-1", "BLIZZARD!!!: Stars", 0x3050A3, 0x30), + LocationData("5-1", "BLIZZARD!!!: Level Clear", 0x3050A4, 0x30), + + LocationData("5-2", "Ride The Ski Lifts: Red Coins", 0x3050A5, 0x31), + LocationData("5-2", "Ride The Ski Lifts: Flowers", 0x3050A6, 0x31), + LocationData("5-2", "Ride The Ski Lifts: Stars", 0x3050A7, 0x31), + LocationData("5-2", "Ride The Ski Lifts: Level Clear", 0x3050A8, 0x31), + + LocationData("5-3", "Danger - Icy Conditions Ahead: Red Coins", 0x3050A9, 0x32), + LocationData("5-3", "Danger - Icy Conditions Ahead: Flowers", 0x3050AA, 0x32), + LocationData("5-3", "Danger - Icy Conditions Ahead: Stars", 0x3050AB, 0x32), + LocationData("5-3", "Danger - Icy Conditions Ahead: Level Clear", 0x3050AC, 0x32), + + LocationData("5-4", "Sluggy The Unshaven's Fort: Red Coins", 0x3050AD, 0x33), + LocationData("5-4", "Sluggy The Unshaven's Fort: Flowers", 0x3050AE, 0x33), + LocationData("5-4", "Sluggy The Unshaven's Fort: Stars", 0x3050AF, 0x33), + LocationData("5-4", "Sluggy The Unshaven's Fort: Level Clear", 0x3050B0, 0x33, lambda state: logic._54CanFightBoss(state)), + LocationData("Sluggy The Unshaven's Boss Room", "Sluggy The Unshaven's Boss Room", None, 0x33, lambda state: logic._54Boss(state)), + + LocationData("5-5", "Goonie Rides!: Red Coins", 0x3050B1, 0x34), + LocationData("5-5", "Goonie Rides!: Flowers", 0x3050B2, 0x34), + LocationData("5-5", "Goonie Rides!: Stars", 0x3050B3, 0x34), + LocationData("5-5", "Goonie Rides!: Level Clear", 0x3050B4, 0x34), + + LocationData("5-6", "Welcome To Cloud World: Red Coins", 0x3050B5, 0x35), + LocationData("5-6", "Welcome To Cloud World: Flowers", 0x3050B6, 0x35), + LocationData("5-6", "Welcome To Cloud World: Stars", 0x3050B7, 0x35), + LocationData("5-6", "Welcome To Cloud World: Level Clear", 0x3050B8, 0x35), + + LocationData("5-7", "Shifting Platforms Ahead: Red Coins", 0x3050B9, 0x36), + LocationData("5-7", "Shifting Platforms Ahead: Flowers", 0x3050BA, 0x36), + LocationData("5-7", "Shifting Platforms Ahead: Stars", 0x3050BB, 0x36), + LocationData("5-7", "Shifting Platforms Ahead: Level Clear", 0x3050BC, 0x36), + + LocationData("5-8", "Raphael The Raven's Castle: Red Coins", 0x3050BD, 0x37), + LocationData("5-8", "Raphael The Raven's Castle: Flowers", 0x3050BE, 0x37), + LocationData("5-8", "Raphael The Raven's Castle: Stars", 0x3050BF, 0x37), + LocationData("5-8", "Raphael The Raven's Castle: Level Clear", 0x3050C0, 0x37, lambda state: logic._58CanFightBoss(state)), + LocationData("Raphael The Raven's Boss Room", "Raphael The Raven's Boss Room", None, 0x37, lambda state: logic._58Boss(state)), + ###################################################################################################### + + LocationData("6-1", "Scary Skeleton Goonies!: Red Coins", 0x3050C1, 0x3C), + LocationData("6-1", "Scary Skeleton Goonies!: Flowers", 0x3050C2, 0x3C), + LocationData("6-1", "Scary Skeleton Goonies!: Stars", 0x3050C3, 0x3C), + LocationData("6-1", "Scary Skeleton Goonies!: Level Clear", 0x3050C4, 0x3C), + + LocationData("6-2", "The Cave Of The Bandits: Red Coins", 0x3050C5, 0x3D), + LocationData("6-2", "The Cave Of The Bandits: Flowers", 0x3050C6, 0x3D), + LocationData("6-2", "The Cave Of The Bandits: Stars", 0x3050C7, 0x3D), + LocationData("6-2", "The Cave Of The Bandits: Level Clear", 0x3050C8, 0x3D), + + LocationData("6-3", "Beware The Spinning Logs: Red Coins", 0x3050C9, 0x3E), + LocationData("6-3", "Beware The Spinning Logs: Flowers", 0x3050CA, 0x3E), + LocationData("6-3", "Beware The Spinning Logs: Stars", 0x3050CB, 0x3E), + LocationData("6-3", "Beware The Spinning Logs: Level Clear", 0x3050CC, 0x3E), + + LocationData("6-4", "Tap-Tap The Red Nose's Fort: Red Coins", 0x3050CD, 0x3F), + LocationData("6-4", "Tap-Tap The Red Nose's Fort: Flowers", 0x3050CE, 0x3F), + LocationData("6-4", "Tap-Tap The Red Nose's Fort: Stars", 0x3050CF, 0x3F), + LocationData("6-4", "Tap-Tap The Red Nose's Fort: Level Clear", 0x3050D0, 0x3F, lambda state: logic._64CanFightBoss(state)), + LocationData("Tap-Tap The Red Nose's Boss Room", "Tap-Tap The Red Nose's Boss Room", None, 0x3F, lambda state: logic._64Boss(state)), + + LocationData("6-5", "The Very Loooooong Cave: Red Coins", 0x3050D1, 0x40), + LocationData("6-5", "The Very Loooooong Cave: Flowers", 0x3050D2, 0x40), + LocationData("6-5", "The Very Loooooong Cave: Stars", 0x3050D3, 0x40), + LocationData("6-5", "The Very Loooooong Cave: Level Clear", 0x3050D4, 0x40), + + LocationData("6-6", "The Deep, Underground Maze: Red Coins", 0x3050D5, 0x41), + LocationData("6-6", "The Deep, Underground Maze: Flowers", 0x3050D6, 0x41), + LocationData("6-6", "The Deep, Underground Maze: Stars", 0x3050D7, 0x41), + LocationData("6-6", "The Deep, Underground Maze: Level Clear", 0x3050D8, 0x41), + + LocationData("6-7", "KEEP MOVING!!!!: Red Coins", 0x3050D9, 0x42), + LocationData("6-7", "KEEP MOVING!!!!: Flowers", 0x3050DA, 0x42), + LocationData("6-7", "KEEP MOVING!!!!: Stars", 0x3050DB, 0x42), + LocationData("6-7", "KEEP MOVING!!!!: Level Clear", 0x3050DC, 0x42), + + LocationData("6-8", "King Bowser's Castle: Red Coins", 0x3050DD, 0x43), + LocationData("6-8", "King Bowser's Castle: Flowers", 0x3050DE, 0x43), + LocationData("6-8", "King Bowser's Castle: Stars", 0x3050DF, 0x43) + ] + + if not world or world.options.extras_enabled: + location_table += [ + LocationData("1-Extra", "Poochy Ain't Stupid: Red Coins", 0x3050E0, 0x08), + LocationData("1-Extra", "Poochy Ain't Stupid: Flowers", 0x3050E1, 0x08), + LocationData("1-Extra", "Poochy Ain't Stupid: Stars", 0x3050E2, 0x08), + LocationData("1-Extra", "Poochy Ain't Stupid: Level Clear", 0x3050E3, 0x08), + + LocationData("2-Extra", "Hit That Switch!!: Red Coins", 0x3050E4, 0x14), + LocationData("2-Extra", "Hit That Switch!!: Flowers", 0x3050E5, 0x14), + LocationData("2-Extra", "Hit That Switch!!: Stars", 0x3050E6, 0x14), + LocationData("2-Extra", "Hit That Switch!!: Level Clear", 0x3050E7, 0x14), + + LocationData("3-Extra", "More Monkey Madness: Red Coins", 0x3050E8, 0x20), + LocationData("3-Extra", "More Monkey Madness: Flowers", 0x3050E9, 0x20), + LocationData("3-Extra", "More Monkey Madness: Stars", 0x3050EA, 0x20), + LocationData("3-Extra", "More Monkey Madness: Level Clear", 0x3050EB, 0x20), + + LocationData("4-Extra", "The Impossible? Maze: Red Coins", 0x3050EC, 0x2C), + LocationData("4-Extra", "The Impossible? Maze: Flowers", 0x3050ED, 0x2C), + LocationData("4-Extra", "The Impossible? Maze: Stars", 0x3050EE, 0x2C), + LocationData("4-Extra", "The Impossible? Maze: Level Clear", 0x3050EF, 0x2C), + + LocationData("5-Extra", "Kamek's Revenge: Red Coins", 0x3050F0, 0x38), + LocationData("5-Extra", "Kamek's Revenge: Flowers", 0x3050F1, 0x38), + LocationData("5-Extra", "Kamek's Revenge: Stars", 0x3050F2, 0x38), + LocationData("5-Extra", "Kamek's Revenge: Level Clear", 0x3050F3, 0x38), + + LocationData("6-Extra", "Castles - Masterpiece Set: Red Coins", 0x3050F4, 0x44), + LocationData("6-Extra", "Castles - Masterpiece Set: Flowers", 0x3050F5, 0x44), + LocationData("6-Extra", "Castles - Masterpiece Set: Stars", 0x3050F6, 0x44), + LocationData("6-Extra", "Castles - Masterpiece Set: Level Clear", 0x3050F7, 0x44), + ] + + if not world or world.options.minigame_checks in {MinigameChecks.option_bandit_games, MinigameChecks.option_both}: + location_table += [ + LocationData("1-3", "The Cave Of Chomp Rock: Bandit Game", 0x3050F8, 0x02, lambda state: logic._13Game(state)), + LocationData("1-7", "Touch Fuzzy Get Dizzy: Bandit Game", 0x3050F9, 0x06, lambda state: logic._17Game(state)), + LocationData("2-1", "Visit Koopa And Para-Koopa: Bandit Game", 0x3050FA, 0x0C, lambda state: logic._21Game(state)), + LocationData("2-3", "What's Gusty Taste Like?: Bandit Game", 0x3050FB, 0x0E, lambda state: logic._23Game(state)), + LocationData("2-6", "The Cave Of The Mystery Maze: Bandit Game", 0x3050FC, 0x11, lambda state: logic._26Game(state)), + LocationData("2-7", "Lakitu's Wall: Bandit Game", 0x3050FD, 0x12, lambda state: logic._27Game(state)), + LocationData("3-2", "Jungle Rhythm...: Bandit Game", 0x3050FE, 0x19, lambda state: logic._32Game(state)), + LocationData("3-7", "Monkeys' Favorite Lake: Bandit Game", 0x3050FF, 0x1E, lambda state: logic._37Game(state)), + LocationData("4-2", "The Cave Of The Lakitus: Bandit Game", 0x305100, 0x25, lambda state: logic._42Game(state)), + LocationData("4-6", "Lake Shore Paradise: Bandit Game", 0x305101, 0x29, lambda state: logic._46Game(state)), + LocationData("4-7", "Ride Like The Wind: Bandit Game", 0x305102, 0x2A, lambda state: logic._47Game(state)), + LocationData("5-1", "BLIZZARD!!!: Bandit Game", 0x305103, 0x30, lambda state: logic._51Game(state)), + LocationData("6-1", "Scary Skeleton Goonies!: Bandit Game", 0x305104, 0x3C, lambda state: logic._61Game(state)), + LocationData("6-7", "KEEP MOVING!!!!: Bandit Game", 0x305105, 0x42, lambda state: logic._67Game(state)), + ] + + if not world or world.options.minigame_checks in {MinigameChecks.option_bonus_games, MinigameChecks.option_both}: + location_table += [ + LocationData("1-Bonus", "Flip Cards: Victory", 0x305106, 0x09), + LocationData("2-Bonus", "Scratch And Match: Victory", 0x305107, 0x15), + LocationData("3-Bonus", "Drawing Lots: Victory", 0x305108, 0x21), + LocationData("4-Bonus", "Match Cards: Victory", 0x305109, 0x2D), + LocationData("5-Bonus", "Roulette: Victory", 0x30510A, 0x39), + LocationData("6-Bonus", "Slot Machine: Victory", 0x30510B, 0x45), + ] + if not world or world.options.goal == PlayerGoal.option_luigi_hunt: + location_table += [ + LocationData("Overworld", "Reconstituted Luigi", None, 0x00, lambda state: logic.reconstitute_luigi(state)), + ] + if not world or world.options.goal == PlayerGoal.option_bowser: + location_table += [ + LocationData("Bowser's Room", "King Bowser's Castle: Level Clear", None, 0x43, lambda state: logic._68Clear(state)), + ] + + return location_table diff --git a/worlds/yoshisisland/Options.py b/worlds/yoshisisland/Options.py new file mode 100644 index 000000000000..d02999309f61 --- /dev/null +++ b/worlds/yoshisisland/Options.py @@ -0,0 +1,296 @@ +from dataclasses import dataclass +from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, PerGameCommonOptions + + +class ExtrasEnabled(Toggle): + """If enabled, the more difficult Extra stages will be added into logic. Otherwise, they will be inaccessible.""" + display_name = "Include Extra Stages" + + +class SplitExtras(Toggle): + """If enabled, Extra stages will be unlocked individually. Otherwise, there will be a single 'Extra Panels' item that unlocks all of them.""" + display_name = "Split Extra Stages" + + +class SplitBonus(Toggle): + """If enabled, Bonus Games will be unlocked individually. Otherwise, there will be a single 'Bonus Panels' item that unlocks all of them.""" + display_name = "Split Bonus Games" + + +class ObjectVis(Choice): + """This will determine the default visibility of objects revealed by the Magnifying Glass. + Strict Logic will expect the Secret Lens or a Magnifying Glass to interact with hidden clouds containing stars if they are not set to visible by default.""" + display_name = "Hidden Object Visibility" + option_none = 0 + option_coins_only = 1 + option_clouds_only = 2 + option_full = 3 + default = 1 + + +class SoftlockPrevention(DefaultOnToggle): + """If enabled, hold R + X to warp to the last used Middle Ring, or the start of the level if none have been activated.""" + display_name = "Softlock Prevention Code" + + +class StageLogic(Choice): + """This determines what logic mode the stages will use. + Strict: Best for casual players or those new to playing Yoshi's Island in AP. Level requirements won't expect anything too difficult of the player. + Loose: Recommended for veterans of the original game. Won't expect anything too difficult, but may expect unusual platforming or egg throws. + Expert: Logic may expect advanced knowledge or memorization of level layouts, as well as jumps the player may only have one chance to make without restarting.""" + display_name = "Stage Logic" + option_strict = 0 + option_loose = 1 + option_expert = 2 + # option_glitched = 3 + default = 0 + + +class ShuffleMiddleRings(Toggle): + """If enabled, Middle Rings will be added to the item pool.""" + display_name = "Shuffle Middle Rings" + + +class ShuffleSecretLens(Toggle): + """If enabled, the Secret Lens will be added to the item pool. + The Secret Lens will act as a permanent Magnifying Glass.""" + display_name = "Add Secret Lens" + + +class DisableAutoScrollers(Toggle): + """If enabled, will disable autoscrolling during levels, except during levels which cannot function otherwise.""" + display_name = "Disable Autoscrolling" + + +class ItemLogic(Toggle): + """This will enable logic to expect consumables to be used from the inventory in place of some major items. + Logic will expect you to have access to an Overworld bonus game, or a bandit game to get the necessary items. + Logic will NOT expect grinding end-of-level bonus games, or any inventory consumables received from checks. + Casual logic will only expect consumables from Overworld games; Loose and Expert may expect them from bandit games.""" + display_name = "Consumable Logic" + + +class MinigameChecks(Choice): + """This will set minigame victories to give Archipelago checks. + This will not randomize minigames amongst themselves, and is compatible with item logic. + Bonus games will be expected to be cleared from the Overworld, not the end of levels. + Additionally, 1-Up bonus games will accept any profit as a victory.""" + display_name = "Minigame Reward Checks" + option_none = 0 + option_bandit_games = 1 + option_bonus_games = 2 + option_both = 3 + default = 0 + + +class StartingWorld(Choice): + """This sets which world you start in. Other worlds can be accessed by receiving a Gate respective to that world.""" + display_name = "Starting World" + option_world_1 = 0 + option_world_2 = 1 + option_world_3 = 2 + option_world_4 = 3 + option_world_5 = 4 + option_world_6 = 5 + default = 0 + + +class StartingLives(Range): + """This sets the amount of lives Yoshi will have upon loading the game.""" + display_name = "Starting Life Count" + range_start = 1 + range_end = 999 + default = 3 + + +class PlayerGoal(Choice): + """This sets the goal. Bowser goal requires defeating Bowser at the end of 6-8, while Luigi Hunt requires collecting all required Luigi Pieces.""" + display_name = "Goal" + option_bowser = 0 + option_luigi_hunt = 1 + default = 0 + + +class LuigiPiecesReq(Range): + """This will set how many Luigi Pieces are required to trigger a victory.""" + display_name = "Luigi Pieces Required" + range_start = 1 + range_end = 100 + default = 25 + + +class LuigiPiecesAmt(Range): + """This will set how many Luigi Pieces are in the item pool. + If the number in the pool is lower than the number required, + the amount in the pool will be randomized, with the minimum being the amount required.""" + display_name = "Amount of Luigi Pieces" + range_start = 1 + range_end = 100 + default = 50 + + +class FinalLevelBosses(Range): + """This sets how many bosses need to be defeated to access 6-8. + You can check this in-game by pressing SELECT while in any level.""" + display_name = "Bosses Required for 6-8 Unlock" + range_start = 0 + range_end = 11 + default = 5 + + +class FinalBossBosses(Range): + """This sets how many bosses need to be defeated to access the boss of 6-8. + You can check this in-game by pressing SELECT while in any level.""" + display_name = "Bosses Required for 6-8 Clear" + range_start = 0 + range_end = 11 + default = 0 + + +class BowserDoor(Choice): + """This will set which route you take through 6-8. + Manual: You go through the door that you hit with an egg, as normal. + Doors: Route will be forced to be the door chosen here, regardless of which door you hit. + Gauntlet: You will be forced to go through all 4 routes in order before the final hallway.""" + display_name = "Bowser's Castle Doors" + option_manual = 0 + option_door_1 = 1 + option_door_2 = 2 + option_door_3 = 3 + option_door_4 = 4 + option_gauntlet = 5 + default = 0 + + +class BossShuffle(Toggle): + """This whill shuffle which boss each boss door will lead to. Each boss can only appear once, and Baby Bowser is left alone.""" + display_name = "Boss Shuffle" + + +class LevelShuffle(Choice): + """Disabled: All levels will appear in their normal location. + Bosses Guranteed: All worlds will have a boss on -4 and -8. + Full: Worlds may have more than 2 or no bosses in them. + Regardless of the setting, 6-8 and Extra stages are not shuffled.""" + display_name = "Level Shuffle" + option_disabled = 0 + option_bosses_guranteed = 1 + option_full = 2 + default = 0 + + +class YoshiColors(Choice): + """Sets the Yoshi color for each level. + Normal will use the vanilla colors. + Random order will generate a random order of colors that will be used in each level. The stage 1 color will be used for Extra stages, and 6-8. + Random color will generate a random color for each stage. + Singularity will use a single color defined under 'Singularity Yoshi Color' for use in all stages.""" + display_name = "Yoshi Colors" + option_normal = 0 + option_random_order = 1 + option_random_color = 2 + option_singularity = 3 + default = 0 + + +class SinguColor(Choice): + """Sets which color Yoshi will be if Yoshi Colors is set to singularity.""" + display_name = "Singularity Yoshi Color" + option_green = 0 + option_pink = 1 + option_cyan = 3 + option_yellow = 2 + option_purple = 4 + option_brown = 5 + option_red = 6 + option_blue = 7 + default = 0 + + +class BabySound(Choice): + """Change the sound that Baby Mario makes when not on Yoshi.""" + display_name = "Mario Sound Effect" + option_normal = 0 + option_disabled = 1 + option_random_sound_effect = 2 + default = 0 + + +class TrapsEnabled(Toggle): + """Will place traps into the item pool. + Traps have a variety of negative effects, and will only replace filler items.""" + display_name = "Traps Enabled" + + +class TrapPercent(Range): + """Percentage of the item pool that becomes replaced with traps.""" + display_name = "Trap Chance" + range_start = 0 + range_end = 100 + default = 10 + +# class EnableScrets(Range): + # """This sets the amount of lives Yoshi will have upon loading the game.""" + # display_name = "Starting Life Count" + # range_start = 1 + # range_end = 255 + # default = 3 + +# class BackgroundColors(Range): + # """This sets the amount of lives Yoshi will have upon loading the game.""" + # display_name = "Starting Life Count" + # range_start = 1 + # range_end = 255 + # default = 3 + +# class Foreground Colors(Range): + # """This sets the amount of lives Yoshi will have upon loading the game.""" + # display_name = "Starting Life Count" + # range_start = 1 + # range_end = 255 + # default = 3 + +# class Music Shuffle(Range): + # """This sets the amount of lives Yoshi will have upon loading the game.""" + # display_name = "Starting Life Count" + # range_start = 1 + # range_end = 255 + # default = 3 + +# class Star Loss Rate(Range): + # """This sets the amount of lives Yoshi will have upon loading the game.""" + # display_name = "Starting Life Count" + # range_start = 1 + # range_end = 255 + # default = 3 + + +@dataclass +class YoshisIslandOptions(PerGameCommonOptions): + starting_world: StartingWorld + starting_lives: StartingLives + goal: PlayerGoal + luigi_pieces_required: LuigiPiecesReq + luigi_pieces_in_pool: LuigiPiecesAmt + extras_enabled: ExtrasEnabled + minigame_checks: MinigameChecks + split_extras: SplitExtras + split_bonus: SplitBonus + hidden_object_visibility: ObjectVis + add_secretlens: ShuffleSecretLens + shuffle_midrings: ShuffleMiddleRings + stage_logic: StageLogic + item_logic: ItemLogic + disable_autoscroll: DisableAutoScrollers + softlock_prevention: SoftlockPrevention + castle_open_condition: FinalLevelBosses + castle_clear_condition: FinalBossBosses + bowser_door_mode: BowserDoor + level_shuffle: LevelShuffle + boss_shuffle: BossShuffle + yoshi_colors: YoshiColors + yoshi_singularity_color: SinguColor + baby_mario_sound: BabySound + traps_enabled: TrapsEnabled + trap_percent: TrapPercent + death_link: DeathLink diff --git a/worlds/yoshisisland/Regions.py b/worlds/yoshisisland/Regions.py new file mode 100644 index 000000000000..59e93cfe7979 --- /dev/null +++ b/worlds/yoshisisland/Regions.py @@ -0,0 +1,248 @@ +from typing import List, Dict, TYPE_CHECKING +from BaseClasses import Region, Location +from .Locations import LocationData +from .Options import MinigameChecks +from .level_logic import YoshiLogic +from .setup_bosses import BossReqs +if TYPE_CHECKING: + from . import YoshisIslandWorld + + +class YoshisIslandLocation(Location): + game: str = "Yoshi's Island" + level_id: int + + def __init__(self, player: int, name: str = " ", address: int = None, parent=None, level_id: int = None): + super().__init__(player, name, address, parent) + self.level_id = level_id + + +def init_areas(world: "YoshisIslandWorld", locations: List[LocationData]) -> None: + multiworld = world.multiworld + player = world.player + logic = YoshiLogic(world) + + locations_per_region = get_locations_per_region(locations) + + regions = [ + create_region(world, player, locations_per_region, "Menu"), + create_region(world, player, locations_per_region, "Overworld"), + create_region(world, player, locations_per_region, "World 1"), + create_region(world, player, locations_per_region, "World 2"), + create_region(world, player, locations_per_region, "World 3"), + create_region(world, player, locations_per_region, "World 4"), + create_region(world, player, locations_per_region, "World 5"), + create_region(world, player, locations_per_region, "World 6"), + + create_region(world, player, locations_per_region, "1-1"), + create_region(world, player, locations_per_region, "1-2"), + create_region(world, player, locations_per_region, "1-3"), + create_region(world, player, locations_per_region, "1-4"), + create_region(world, player, locations_per_region, "Burt The Bashful's Boss Room"), + create_region(world, player, locations_per_region, "1-5"), + create_region(world, player, locations_per_region, "1-6"), + create_region(world, player, locations_per_region, "1-7"), + create_region(world, player, locations_per_region, "1-8"), + create_region(world, player, locations_per_region, "Salvo The Slime's Boss Room"), + + create_region(world, player, locations_per_region, "2-1"), + create_region(world, player, locations_per_region, "2-2"), + create_region(world, player, locations_per_region, "2-3"), + create_region(world, player, locations_per_region, "2-4"), + create_region(world, player, locations_per_region, "Bigger Boo's Boss Room"), + create_region(world, player, locations_per_region, "2-5"), + create_region(world, player, locations_per_region, "2-6"), + create_region(world, player, locations_per_region, "2-7"), + create_region(world, player, locations_per_region, "2-8"), + create_region(world, player, locations_per_region, "Roger The Ghost's Boss Room"), + + create_region(world, player, locations_per_region, "3-1"), + create_region(world, player, locations_per_region, "3-2"), + create_region(world, player, locations_per_region, "3-3"), + create_region(world, player, locations_per_region, "3-4"), + create_region(world, player, locations_per_region, "Prince Froggy's Boss Room"), + create_region(world, player, locations_per_region, "3-5"), + create_region(world, player, locations_per_region, "3-6"), + create_region(world, player, locations_per_region, "3-7"), + create_region(world, player, locations_per_region, "3-8"), + create_region(world, player, locations_per_region, "Naval Piranha's Boss Room"), + + create_region(world, player, locations_per_region, "4-1"), + create_region(world, player, locations_per_region, "4-2"), + create_region(world, player, locations_per_region, "4-3"), + create_region(world, player, locations_per_region, "4-4"), + create_region(world, player, locations_per_region, "Marching Milde's Boss Room"), + create_region(world, player, locations_per_region, "4-5"), + create_region(world, player, locations_per_region, "4-6"), + create_region(world, player, locations_per_region, "4-7"), + create_region(world, player, locations_per_region, "4-8"), + create_region(world, player, locations_per_region, "Hookbill The Koopa's Boss Room"), + + create_region(world, player, locations_per_region, "5-1"), + create_region(world, player, locations_per_region, "5-2"), + create_region(world, player, locations_per_region, "5-3"), + create_region(world, player, locations_per_region, "5-4"), + create_region(world, player, locations_per_region, "Sluggy The Unshaven's Boss Room"), + create_region(world, player, locations_per_region, "5-5"), + create_region(world, player, locations_per_region, "5-6"), + create_region(world, player, locations_per_region, "5-7"), + create_region(world, player, locations_per_region, "5-8"), + create_region(world, player, locations_per_region, "Raphael The Raven's Boss Room"), + + create_region(world, player, locations_per_region, "6-1"), + create_region(world, player, locations_per_region, "6-2"), + create_region(world, player, locations_per_region, "6-3"), + create_region(world, player, locations_per_region, "6-4"), + create_region(world, player, locations_per_region, "Tap-Tap The Red Nose's Boss Room"), + create_region(world, player, locations_per_region, "6-5"), + create_region(world, player, locations_per_region, "6-6"), + create_region(world, player, locations_per_region, "6-7"), + create_region(world, player, locations_per_region, "6-8"), + create_region(world, player, locations_per_region, "Bowser's Room"), + ] + + if world.options.extras_enabled: + regions.insert(68, create_region(world, player, locations_per_region, "6-Extra")) + regions.insert(58, create_region(world, player, locations_per_region, "5-Extra")) + regions.insert(48, create_region(world, player, locations_per_region, "4-Extra")) + regions.insert(38, create_region(world, player, locations_per_region, "3-Extra")) + regions.insert(28, create_region(world, player, locations_per_region, "2-Extra")) + regions.insert(18, create_region(world, player, locations_per_region, "1-Extra")) + + if world.options.minigame_checks in {MinigameChecks.option_bonus_games, MinigameChecks.option_both}: + regions.insert(74, create_region(world, player, locations_per_region, "6-Bonus")) + regions.insert(63, create_region(world, player, locations_per_region, "5-Bonus")) + regions.insert(52, create_region(world, player, locations_per_region, "4-Bonus")) + regions.insert(41, create_region(world, player, locations_per_region, "3-Bonus")) + regions.insert(29, create_region(world, player, locations_per_region, "2-Bonus")) + regions.insert(19, create_region(world, player, locations_per_region, "1-Bonus")) + + multiworld.regions += regions + + connect_starting_region(world) + + bosses = BossReqs(world) + + multiworld.get_region("Overworld", player).add_exits( + ["World 1", "World 2", "World 3", "World 4", "World 5", "World 6"], + { + "World 1": lambda state: state.has("World 1 Gate", player), + "World 2": lambda state: state.has("World 2 Gate", player), + "World 3": lambda state: state.has("World 3 Gate", player), + "World 4": lambda state: state.has("World 4 Gate", player), + "World 5": lambda state: state.has("World 5 Gate", player), + "World 6": lambda state: state.has("World 6 Gate", player) + } + ) + + for cur_world in range(1, 7): + for cur_level in range(8): + if cur_world != 6 or cur_level != 7: + multiworld.get_region(f"World {cur_world}", player).add_exits( + [world.level_location_list[(cur_world - 1) * 8 + cur_level]] + ) + + multiworld.get_region("1-4", player).add_exits([world.boss_order[0]],{world.boss_order[0]: lambda state: logic._14Clear(state)}) + multiworld.get_region("1-8", player).add_exits([world.boss_order[1]],{world.boss_order[1]: lambda state: logic._18Clear(state)}) + multiworld.get_region("2-4", player).add_exits([world.boss_order[2]],{world.boss_order[2]: lambda state: logic._24Clear(state)}) + multiworld.get_region("2-8", player).add_exits([world.boss_order[3]],{world.boss_order[3]: lambda state: logic._28Clear(state)}) + multiworld.get_region("3-4", player).add_exits([world.boss_order[4]],{world.boss_order[4]: lambda state: logic._34Clear(state)}) + multiworld.get_region("3-8", player).add_exits([world.boss_order[5]],{world.boss_order[5]: lambda state: logic._38Clear(state)}) + multiworld.get_region("4-4", player).add_exits([world.boss_order[6]],{world.boss_order[6]: lambda state: logic._44Clear(state)}) + multiworld.get_region("4-8", player).add_exits([world.boss_order[7]],{world.boss_order[7]: lambda state: logic._48Clear(state)}) + multiworld.get_region("5-4", player).add_exits([world.boss_order[8]],{world.boss_order[8]: lambda state: logic._54Clear(state)}) + multiworld.get_region("5-8", player).add_exits([world.boss_order[9]],{world.boss_order[9]: lambda state: logic._58Clear(state)}) + multiworld.get_region("World 6", player).add_exits(["6-8"],{"6-8": lambda state: bosses.castle_access(state)}) + multiworld.get_region("6-4", player).add_exits([world.boss_order[10]],{world.boss_order[10]: lambda state: logic._64Clear(state)}) + multiworld.get_region("6-8", player).add_exits(["Bowser's Room"],{"Bowser's Room": lambda state: bosses.castle_clear(state)}) + + if world.options.extras_enabled: + multiworld.get_region("World 1", player).add_exits( + ["1-Extra"], + {"1-Extra": lambda state: state.has_any({"Extra Panels", "Extra 1"}, player)} + ) + multiworld.get_region("World 2", player).add_exits( + ["2-Extra"], + {"2-Extra": lambda state: state.has_any({"Extra Panels", "Extra 2"}, player)} + ) + multiworld.get_region( + "World 3", player).add_exits(["3-Extra"], + {"3-Extra": lambda state: state.has_any({"Extra Panels", "Extra 3"}, player)} + ) + multiworld.get_region("World 4", player).add_exits( + ["4-Extra"], + {"4-Extra": lambda state: state.has_any({"Extra Panels", "Extra 4"}, player)} + ) + multiworld.get_region("World 5", player).add_exits( + ["5-Extra"], + {"5-Extra": lambda state: state.has_any({"Extra Panels", "Extra 5"}, player)} + ) + multiworld.get_region("World 6", player).add_exits( + ["6-Extra"], + {"6-Extra": lambda state: state.has_any({"Extra Panels", "Extra 6"}, player)} + ) + + if world.options.minigame_checks in {MinigameChecks.option_bonus_games, MinigameChecks.option_both}: + multiworld.get_region("World 1", player).add_exits( + ["1-Bonus"], + {"1-Bonus": lambda state: state.has_any({"Bonus Panels", "Bonus 1"}, player)} + ) + multiworld.get_region("World 2", player).add_exits( + ["2-Bonus"], + {"2-Bonus": lambda state: state.has_any({"Bonus Panels", "Bonus 2"}, player)} + ) + multiworld.get_region("World 3", player).add_exits( + ["3-Bonus"], + {"3-Bonus": lambda state: state.has_any({"Bonus Panels", "Bonus 3"}, player)} + ) + multiworld.get_region("World 4", player).add_exits( + ["4-Bonus"], + {"4-Bonus": lambda state: state.has_any({"Bonus Panels", "Bonus 4"}, player)} + ) + multiworld.get_region("World 5", player).add_exits( + ["5-Bonus"], + {"5-Bonus": lambda state: state.has_any({"Bonus Panels", "Bonus 5"}, player)} + ) + multiworld.get_region("World 6", player).add_exits( + ["6-Bonus"], + {"6-Bonus": lambda state: state.has_any({"Bonus Panels", "Bonus 6"}, player)} + ) + + +def create_location(player: int, location_data: LocationData, region: Region) -> Location: + location = YoshisIslandLocation(player, location_data.name, location_data.code, region) + location.access_rule = location_data.rule + location.level_id = location_data.LevelID + + return location + + +def create_region(world: "YoshisIslandWorld", player: int, locations_per_region: Dict[str, List[LocationData]], name: str) -> Region: + region = Region(name, player, world.multiworld) + + if name in locations_per_region: + for location_data in locations_per_region[name]: + location = create_location(player, location_data, region) + region.locations.append(location) + + return region + +def connect_starting_region(world: "YoshisIslandWorld") -> None: + multiworld = world.multiworld + player = world.player + menu = multiworld.get_region("Menu", player) + world_main = multiworld.get_region("Overworld", player) + + starting_region = multiworld.get_region(f"World {world.options.starting_world + 1}", player) + + menu.connect(world_main, "Start Game") + world_main.connect(starting_region, "Overworld") + + +def get_locations_per_region(locations: List[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/yoshisisland/Rom.py b/worlds/yoshisisland/Rom.py new file mode 100644 index 000000000000..fa3006afcf9f --- /dev/null +++ b/worlds/yoshisisland/Rom.py @@ -0,0 +1,1230 @@ +import hashlib +import os +import Utils +from worlds.Files import APDeltaPatch +from settings import get_settings +from typing import TYPE_CHECKING + +from .Options import YoshiColors, BowserDoor, PlayerGoal, MinigameChecks + +if TYPE_CHECKING: + from . import YoshisIslandWorld +USHASH = "cb472164c5a71ccd3739963390ec6a50" + +item_values = { + 0x302050: [0x1467, 0x01], # ! Switch + 0x302051: [0x1467, 0x02], # Dashed Platform + 0x302052: [0x1467, 0x03], # Dashed Stairs + 0x302053: [0x1467, 0x04], # Beanstalk + 0x302054: [0x1467, 0x05], # Helicopter + 0x302059: [0x1467, 0x06], # Mole Tank + 0x302068: [0x1467, 0x07], # Train + 0x30205E: [0x1467, 0x08], # Car + 0x302063: [0x1467, 0x09], # Submarine + 0x302055: [0x1467, 0x0A], # Spring Ball + 0x302056: [0x1467, 0x0B], # Large Spring Ball + 0x302057: [0x1467, 0x0C], # Arrow Wheel + 0x302058: [0x1467, 0x0D], # Vanishing Arrow Wheel + 0x30205A: [0x1467, 0x0E], # Watermelon + 0x30205B: [0x1467, 0x0F], # Ice Melon + 0x30205C: [0x1467, 0x10], # Fire Melon + 0x30205D: [0x1467, 0x11], # Super Star + 0x30205F: [0x1467, 0x12], # Flashing Eggs + 0x302060: [0x1467, 0x13], # Giant Eggs + 0x302061: [0x1467, 0x14], # Egg Launcher + 0x302062: [0x1467, 0x15], # Egg Plant + 0x302064: [0x1467, 0x16], # Chomp Rock + 0x302065: [0x1467, 0x17], # Poochy + 0x302066: [0x1467, 0x18], # Platform Ghost + 0x302067: [0x1467, 0x19], # Skis + 0x302069: [0x1467, 0x1A], # Key + 0x30206A: [0x1467, 0x1B], # Middle Ring + 0x30206B: [0x1467, 0x1C], # Bucket + 0x30206C: [0x1467, 0x1D], # Tulip + 0x302081: [0x1467, 0x1E], # Secret Lens + + 0x30206D: [0x1467, 0x1F], # Egg Capacity Upgrade + + 0x30206E: [0x1467, 0x20], # World 1 Gate + 0x30206F: [0x1467, 0x21], # World 2 Gate + 0x302070: [0x1467, 0x22], # World 3 Gate + 0x302071: [0x1467, 0x23], # World 4 Gate + 0x302072: [0x1467, 0x24], # World 5 Gate + 0x302073: [0x1467, 0x25], # World 6 Gate + + 0x302074: [0x1467, 0x26], # Extra 1 + 0x302075: [0x1467, 0x27], # Extra 2 + 0x302076: [0x1467, 0x28], # Extra 3 + 0x302077: [0x1467, 0x29], # Extra 4 + 0x302078: [0x1467, 0x2A], # Extra 5 + 0x302079: [0x1467, 0x2B], # Extra 6 + 0x30207A: [0x1467, 0x2C], # Extra Panels + + 0x30207B: [0x1467, 0x2D], # Bonus 1 + 0x30207C: [0x1467, 0x2E], # Bonus 2 + 0x30207D: [0x1467, 0x2F], # Bonus 3 + 0x30207E: [0x1467, 0x30], # Bonus 4 + 0x30207F: [0x1467, 0x31], # Bonus 5 + 0x302080: [0x1467, 0x32], # Bonus 6 + 0x302082: [0x1467, 0x33], # Bonus Panels + + 0x302083: [0x1467, 0x34], # Anytime Egg + 0x302084: [0x1467, 0x35], # Anywhere Pow + 0x302085: [0x1467, 0x36], # Cloud + 0x302086: [0x1467, 0x37], # Pocket Melon + 0x302088: [0x1467, 0x38], # Ice Melon + 0x302087: [0x1467, 0x39], # Fire Melon + 0x302089: [0x1467, 0x3A], # Magnifying Glass + 0x30208A: [0x1467, 0x3B], # 10 Stars + 0x30208B: [0x1467, 0x3C], # 20 Stars + + 0x30208C: [0x1467, 0x3D], # 1up + 0x30208D: [0x1467, 0x3E], # 2up + 0x30208E: [0x1467, 0x3F], # 3up + 0x30208F: [0x1467, 0x40], # 10up + + 0x302090: [0x1467, 0x41], # Fuzzy Trap + 0x302093: [0x1467, 0x42], # Freeze Trap + 0x302091: [0x1467, 0x43], # Reverse Trap + 0x302092: [0x1467, 0x44], # Dark Trap + 0x302094: [0x1467, 0x00], # Boss clear, local handling + + 0x302095: [0x1467, 0x45] # Luigi Piece + +} + +location_table = { + # 1-1 + 0x305020: [0x146D, 0], # Red Coins + 0x305021: [0x146D, 1], # Flowers + 0x305022: [0x146D, 2], # Stars + 0x305023: [0x146D, 3], # Level Clear + # 1-2 + 0x305024: [0x146E, 0], + 0x305025: [0x146E, 1], + 0x305026: [0x146E, 2], + 0x305027: [0x146E, 3], + # 1-3 + 0x305028: [0x146F, 0], + 0x305029: [0x146F, 1], + 0x30502A: [0x146F, 2], + 0x30502B: [0x146F, 3], + 0x3050F8: [0x146F, 4], + # 1-4 + 0x30502C: [0x1470, 0], + 0x30502D: [0x1470, 1], + 0x30502E: [0x1470, 2], + 0x30502F: [0x1470, 3], + # 1-5 + 0x305031: [0x1471, 0], + 0x305032: [0x1471, 1], + 0x305033: [0x1471, 2], + 0x305034: [0x1471, 3], + # 1-6 + 0x305035: [0x1472, 0], + 0x305036: [0x1472, 1], + 0x305037: [0x1472, 2], + 0x305038: [0x1472, 3], + # 1-7 + 0x305039: [0x1473, 0], + 0x30503A: [0x1473, 1], + 0x30503B: [0x1473, 2], + 0x30503C: [0x1473, 3], + 0x3050F9: [0x1473, 4], + # 1-8 + 0x30503D: [0x1474, 0], + 0x30503E: [0x1474, 1], + 0x30503F: [0x1474, 2], + 0x305040: [0x1474, 3], + # 1-E + 0x3050E0: [0x1475, 0], + 0x3050E1: [0x1475, 1], + 0x3050E2: [0x1475, 2], + 0x3050E3: [0x1475, 3], + # 1-B + 0x305106: [0x1476, 4], + ###################### + # 2-1 + 0x305041: [0x1479, 0], + 0x305042: [0x1479, 1], + 0x305043: [0x1479, 2], + 0x305044: [0x1479, 3], + 0x3050FA: [0x1479, 4], + # 2-2 + 0x305045: [0x147A, 0], + 0x305046: [0x147A, 1], + 0x305047: [0x147A, 2], + 0x305048: [0x147A, 3], + # 2-3 + 0x305049: [0x147B, 0], + 0x30504A: [0x147B, 1], + 0x30504B: [0x147B, 2], + 0x30504C: [0x147B, 3], + 0x3050FB: [0x147B, 4], + # 2-4 + 0x30504D: [0x147C, 0], + 0x30504E: [0x147C, 1], + 0x30504F: [0x147C, 2], + 0x305050: [0x147C, 3], + # 2-5 + 0x305051: [0x147D, 0], + 0x305052: [0x147D, 1], + 0x305053: [0x147D, 2], + 0x305054: [0x147D, 3], + # 2-6 + 0x305055: [0x147E, 0], + 0x305056: [0x147E, 1], + 0x305057: [0x147E, 2], + 0x305058: [0x147E, 3], + 0x3050FC: [0x147E, 4], + # 2-7 + 0x305059: [0x147F, 0], + 0x30505A: [0x147F, 1], + 0x30505B: [0x147F, 2], + 0x30505C: [0x147F, 3], + 0x3050FD: [0x147F, 4], + # 2-8 + 0x30505D: [0x1480, 0], + 0x30505E: [0x1480, 1], + 0x30505F: [0x1480, 2], + 0x305060: [0x1480, 3], + # 2-E + 0x3050E4: [0x1481, 0], + 0x3050E5: [0x1481, 1], + 0x3050E6: [0x1481, 2], + 0x3050E7: [0x1481, 3], + # 2-B + 0x305107: [0x1482, 4], + ###################### + # 3-1 + 0x305061: [0x1485, 0], + 0x305062: [0x1485, 1], + 0x305063: [0x1485, 2], + 0x305064: [0x1485, 3], + # 3-2 + 0x305065: [0x1486, 0], + 0x305066: [0x1486, 1], + 0x305067: [0x1486, 2], + 0x305068: [0x1486, 3], + 0x3050FE: [0x1486, 4], + # 3-3 + 0x305069: [0x1487, 0], + 0x30506A: [0x1487, 1], + 0x30506B: [0x1487, 2], + 0x30506C: [0x1487, 3], + # 3-4 + 0x30506D: [0x1488, 0], + 0x30506E: [0x1488, 1], + 0x30506F: [0x1488, 2], + 0x305070: [0x1488, 3], + # 3-5 + 0x305071: [0x1489, 0], + 0x305072: [0x1489, 1], + 0x305073: [0x1489, 2], + 0x305074: [0x1489, 3], + # 3-6 + 0x305075: [0x148A, 0], + 0x305076: [0x148A, 1], + 0x305077: [0x148A, 2], + 0x305078: [0x148A, 3], + # 3-7 + 0x305079: [0x148B, 0], + 0x30507A: [0x148B, 1], + 0x30507B: [0x148B, 2], + 0x30507C: [0x148B, 3], + 0x3050FF: [0x148B, 4], + # 3-8 + 0x30507D: [0x148C, 0], + 0x30507E: [0x148C, 1], + 0x30507F: [0x148C, 2], + 0x305080: [0x148C, 3], + # 3-E + 0x3050E8: [0x148D, 0], + 0x3050E9: [0x148D, 1], + 0x3050EA: [0x148D, 2], + 0x3050EB: [0x148D, 3], + # 3-B + 0x305108: [0x148E, 4], + ###################### + # 4-1 + 0x305081: [0x1491, 0], + 0x305082: [0x1491, 1], + 0x305083: [0x1491, 2], + 0x305084: [0x1491, 3], + # 4-2 + 0x305085: [0x1492, 0], + 0x305086: [0x1492, 1], + 0x305087: [0x1492, 2], + 0x305088: [0x1492, 3], + 0x305100: [0x1492, 4], + # 4-3 + 0x305089: [0x1493, 0], + 0x30508A: [0x1493, 1], + 0x30508B: [0x1493, 2], + 0x30508C: [0x1493, 3], + # 4-4 + 0x30508D: [0x1494, 0], + 0x30508E: [0x1494, 1], + 0x30508F: [0x1494, 2], + 0x305090: [0x1494, 3], + # 4-5 + 0x305091: [0x1495, 0], + 0x305092: [0x1495, 1], + 0x305093: [0x1495, 2], + 0x305094: [0x1495, 3], + # 4-6 + 0x305095: [0x1496, 0], + 0x305096: [0x1496, 1], + 0x305097: [0x1496, 2], + 0x305098: [0x1496, 3], + 0x305101: [0x1496, 4], + # 4-7 + 0x305099: [0x1497, 0], + 0x30509A: [0x1497, 1], + 0x30509B: [0x1497, 2], + 0x30509C: [0x1497, 3], + 0x305102: [0x1497, 4], + # 4-8 + 0x30509D: [0x1498, 0], + 0x30509E: [0x1498, 1], + 0x30509F: [0x1498, 2], + 0x3050A0: [0x1498, 3], + # 4-E + 0x3050EC: [0x1499, 0], + 0x3050ED: [0x1499, 1], + 0x3050EE: [0x1499, 2], + 0x3050EF: [0x1499, 3], + # 4-B + 0x305109: [0x149A, 4], + ###################### + # 5-1 + 0x3050A1: [0x149D, 0], + 0x3050A2: [0x149D, 1], + 0x3050A3: [0x149D, 2], + 0x3050A4: [0x149D, 3], + 0x305103: [0x149D, 4], + # 5-2 + 0x3050A5: [0x149E, 0], + 0x3050A6: [0x149E, 1], + 0x3050A7: [0x149E, 2], + 0x3050A8: [0x149E, 3], + # 5-3 + 0x3050A9: [0x149F, 0], + 0x3050AA: [0x149F, 1], + 0x3050AB: [0x149F, 2], + 0x3050AC: [0x149F, 3], + # 5-4 + 0x3050AD: [0x14A0, 0], + 0x3050AE: [0x14A0, 1], + 0x3050AF: [0x14A0, 2], + 0x3050B0: [0x14A0, 3], + # 5-5 + 0x3050B1: [0x14A1, 0], + 0x3050B2: [0x14A1, 1], + 0x3050B3: [0x14A1, 2], + 0x3050B4: [0x14A1, 3], + # 5-6 + 0x3050B5: [0x14A2, 0], + 0x3050B6: [0x14A2, 1], + 0x3050B7: [0x14A2, 2], + 0x3050B8: [0x14A2, 3], + # 5-7 + 0x3050B9: [0x14A3, 0], + 0x3050BA: [0x14A3, 1], + 0x3050BB: [0x14A3, 2], + 0x3050BC: [0x14A3, 3], + # 5-8 + 0x3050BD: [0x14A4, 0], + 0x3050BE: [0x14A4, 1], + 0x3050BF: [0x14A4, 2], + 0x3050C0: [0x14A4, 3], + # 5-E + 0x3050F0: [0x14A5, 0], + 0x3050F1: [0x14A5, 1], + 0x3050F2: [0x14A5, 2], + 0x3050F3: [0x14A5, 3], + # 5-B + 0x30510A: [0x14A6, 4], + ####################### + # 6-1 + 0x3050C1: [0x14A9, 0], + 0x3050C2: [0x14A9, 1], + 0x3050C3: [0x14A9, 2], + 0x3050C4: [0x14A9, 3], + 0x305104: [0x14A9, 4], + # 6-2 + 0x3050C5: [0x14AA, 0], + 0x3050C6: [0x14AA, 1], + 0x3050C7: [0x14AA, 2], + 0x3050C8: [0x14AA, 3], + # 6-3 + 0x3050C9: [0x14AB, 0], + 0x3050CA: [0x14AB, 1], + 0x3050CB: [0x14AB, 2], + 0x3050CC: [0x14AB, 3], + # 6-4 + 0x3050CD: [0x14AC, 0], + 0x3050CE: [0x14AC, 1], + 0x3050CF: [0x14AC, 2], + 0x3050D0: [0x14AC, 3], + # 6-5 + 0x3050D1: [0x14AD, 0], + 0x3050D2: [0x14AD, 1], + 0x3050D3: [0x14AD, 2], + 0x3050D4: [0x14AD, 3], + # 6-6 + 0x3050D5: [0x14AE, 0], + 0x3050D6: [0x14AE, 1], + 0x3050D7: [0x14AE, 2], + 0x3050D8: [0x14AE, 3], + # 6-7 + 0x3050D9: [0x14AF, 0], + 0x3050DA: [0x14AF, 1], + 0x3050DB: [0x14AF, 2], + 0x3050DC: [0x14AF, 3], + 0x305105: [0x14AF, 4], + # 6-8 + 0x3050DD: [0x14B0, 0], + 0x3050DE: [0x14B0, 1], + 0x3050DF: [0x14B0, 2], + # 6-E + 0x3050F4: [0x14B1, 0], + 0x3050F5: [0x14B1, 1], + 0x3050F6: [0x14B1, 2], + 0x3050F7: [0x14B1, 3], + # 6-B + 0x30510B: [0x14B2, 4] +} + +class LocalRom(object): + + def __init__(self, file: str) -> None: + self.name = None + self.hash = hash + self.orig_buffer = None + + with open(file, "rb") as stream: + self.buffer = Utils.read_snes_rom(stream) + + 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, startaddress: int, length: int) -> bytes: + return self.buffer[startaddress:startaddress + length] + + def write_byte(self, address: int, value: int) -> None: + self.buffer[address] = value + + def write_bytes(self, startaddress: int, values: bytearray) -> None: + self.buffer[startaddress:startaddress + len(values)] = values + + def write_to_file(self, file: str) -> None: + with open(file, "wb") as outfile: + outfile.write(self.buffer) + + def read_from_file(self, file: str) -> None: + with open(file, "rb") as stream: + self.buffer = bytearray(stream.read()) + +def handle_items(rom: LocalRom) -> None: + rom.write_bytes(0x0077B0, bytearray([0xE2, 0x20, 0xAD, 0x40, 0x14, 0xC2, 0x20, 0xF0, 0x08, 0xBD, 0x82, 0x71, 0x18, 0x5C, 0x3B, 0xB6])) + rom.write_bytes(0x0077C0, bytearray([0x0E, 0x5C, 0x97, 0xB6, 0x0E, 0xA0, 0xFF, 0xAD, 0x74, 0x79, 0x29, 0x01, 0x00, 0xD0, 0x02, 0xA0])) + rom.write_bytes(0x0077D0, bytearray([0x05, 0x98, 0x9D, 0xA2, 0x74, 0x6B, 0xE2, 0x20, 0xBD, 0x60, 0x73, 0xDA, 0xC2, 0x20, 0xA2, 0x00])) + rom.write_bytes(0x0077E0, bytearray([0xDF, 0x70, 0xAF, 0x09, 0xF0, 0x08, 0xE8, 0xE8, 0xE0, 0x08, 0xF0, 0x23, 0x80, 0xF2, 0xE2, 0x20])) + rom.write_bytes(0x0077F0, bytearray([0x8A, 0x4A, 0xAA, 0xBF, 0x78, 0xAF, 0x09, 0xAA, 0xBD, 0x40, 0x14, 0xC2, 0x20, 0xF0, 0x08, 0xFA])) + rom.write_bytes(0x007800, bytearray([0xA0, 0x05, 0x98, 0x9D, 0xA2, 0x74, 0x60, 0xFA, 0x22, 0xC5, 0xF7, 0x00, 0xEA, 0xEA, 0x60, 0xFA])) + rom.write_bytes(0x007810, bytearray([0x60, 0x22, 0x23, 0xAF, 0x03, 0x20, 0xD6, 0xF7, 0x6B, 0x20, 0x2F, 0xF8, 0xE2, 0x20, 0xC9, 0x00])) + rom.write_bytes(0x007820, bytearray([0xD0, 0x03, 0xC2, 0x20, 0x6B, 0xC2, 0x20, 0xBD, 0x60, 0x73, 0x38, 0x5C, 0xB1, 0xC9, 0x03, 0xDA])) + rom.write_bytes(0x007830, bytearray([0xBD, 0x60, 0x73, 0xA2, 0x00, 0xDF, 0x7C, 0xAF, 0x09, 0xF0, 0x08, 0xE8, 0xE8, 0xE0, 0x0A, 0xF0])) + rom.write_bytes(0x007840, bytearray([0x13, 0x80, 0xF2, 0xE2, 0x20, 0x8A, 0x4A, 0xAA, 0xBF, 0x86, 0xAF, 0x09, 0xAA, 0xBD, 0x40, 0x14])) + rom.write_bytes(0x007850, bytearray([0xFA, 0xC2, 0x20, 0x60, 0xA9, 0x01, 0x00, 0xFA, 0x60, 0x20, 0x2F, 0xF8, 0xE2, 0x20, 0xC9, 0x00])) + rom.write_bytes(0x007860, bytearray([0xC2, 0x20, 0xD0, 0x06, 0x22, 0xC5, 0xF7, 0x00, 0x80, 0x04, 0x22, 0xCF, 0xF7, 0x00, 0xA5, 0x14])) + rom.write_bytes(0x007870, bytearray([0x29, 0x0F, 0x00, 0x5C, 0x9A, 0xC9, 0x03, 0x5A, 0xE2, 0x10, 0x20, 0x2F, 0xF8, 0xC2, 0x10, 0x7A])) + rom.write_bytes(0x007880, bytearray([0xE2, 0x20, 0xC9, 0x00, 0xC2, 0x20, 0xD0, 0x08, 0xAD, 0x74, 0x79, 0x29, 0x01, 0x00, 0xF0, 0x04])) + rom.write_bytes(0x007890, bytearray([0x22, 0x3C, 0xAA, 0x03, 0xE2, 0x10, 0x5C, 0x47, 0xC9, 0x03, 0x22, 0x23, 0xAF, 0x03, 0xBD, 0x60])) + rom.write_bytes(0x0078A0, bytearray([0x73, 0xC9, 0x6F, 0x00, 0xF0, 0x07, 0xE2, 0x20, 0xAD, 0x4A, 0x14, 0x80, 0x05, 0xE2, 0x20, 0xAD])) + rom.write_bytes(0x0078B0, bytearray([0x49, 0x14, 0xC2, 0x20, 0xD0, 0x06, 0x22, 0xC5, 0xF7, 0x00, 0x80, 0x04, 0x22, 0xCF, 0xF7, 0x00])) + rom.write_bytes(0x0078C0, bytearray([0x5C, 0x2D, 0x83, 0x05, 0xBD, 0x60, 0x73, 0xC9, 0x6F, 0x00, 0xF0, 0x07, 0xE2, 0x20, 0xAD, 0x4A])) + rom.write_bytes(0x0078D0, bytearray([0x14, 0x80, 0x05, 0xE2, 0x20, 0xAD, 0x49, 0x14, 0xC2, 0x20, 0xD0, 0x04, 0x5C, 0xA0, 0x83, 0x05])) + rom.write_bytes(0x0078E0, bytearray([0xAD, 0xAC, 0x60, 0x0D, 0xAE, 0x60, 0x5C, 0x84, 0x83, 0x05, 0x22, 0x52, 0xAA, 0x03, 0xBD, 0x60])) + rom.write_bytes(0x0078F0, bytearray([0x73, 0xC9, 0x1E, 0x01, 0xE2, 0x20, 0xF0, 0x05, 0xAD, 0x4C, 0x14, 0x80, 0x03, 0xAD, 0x4B, 0x14])) + rom.write_bytes(0x007900, bytearray([0xEA, 0xC2, 0x20, 0xF0, 0x08, 0x22, 0xCF, 0xF7, 0x00, 0x5C, 0xA6, 0xF0, 0x05, 0x22, 0xC5, 0xF7])) + rom.write_bytes(0x007910, bytearray([0x00, 0x5C, 0xA6, 0xF0, 0x05, 0xE2, 0x20, 0xAD, 0x1C, 0x01, 0xC9, 0x0E, 0xC2, 0x20, 0xF0, 0x18])) + rom.write_bytes(0x007920, bytearray([0x20, 0x59, 0xF9, 0xE2, 0x20, 0xC9, 0x00, 0xF0, 0x04, 0xA9, 0x10, 0x80, 0x2A, 0xA9, 0x02, 0x9D])) + rom.write_bytes(0x007930, bytearray([0x00, 0x6F, 0xC2, 0x20, 0x22, 0xC5, 0xF7, 0x00, 0xA2, 0x0A, 0xA9, 0x2F, 0xCE, 0x5C, 0x22, 0x80])) + rom.write_bytes(0x007940, bytearray([0x04, 0x20, 0x59, 0xF9, 0xE2, 0x20, 0xC9, 0x00, 0xC2, 0x20, 0xF0, 0x0A, 0xAD, 0x0E, 0x30, 0x29])) + rom.write_bytes(0x007950, bytearray([0x03, 0x00, 0x5C, 0x2E, 0x80, 0x04, 0x6B, 0x80, 0xD6, 0xDA, 0xBD, 0x60, 0x73, 0xA2, 0x00, 0xDF])) + rom.write_bytes(0x007960, bytearray([0x8B, 0xAF, 0x09, 0xF0, 0x04, 0xE8, 0xE8, 0x80, 0xF6, 0xE2, 0x20, 0x8A, 0x4A, 0xAA, 0xBF, 0x91])) + rom.write_bytes(0x007970, bytearray([0xAF, 0x09, 0xAA, 0xBD, 0x40, 0x14, 0xFA, 0xC2, 0x20, 0x60, 0x22, 0x2E, 0xAA, 0x03, 0xE2, 0x20])) + rom.write_bytes(0x007980, bytearray([0xAD, 0x50, 0x14, 0xC2, 0x20, 0xD0, 0x06, 0x22, 0xC5, 0xF7, 0x00, 0x80, 0x04, 0x22, 0xCF, 0xF7])) + rom.write_bytes(0x007990, bytearray([0x00, 0x5C, 0x05, 0x99, 0x02, 0x69, 0x20, 0x00, 0xC9, 0x20, 0x01, 0xB0, 0x0D, 0xE2, 0x20, 0xAD])) + rom.write_bytes(0x0079A0, bytearray([0x50, 0x14, 0xC2, 0x20, 0xF0, 0x04, 0x5C, 0x3E, 0x99, 0x02, 0x5C, 0x8C, 0x99, 0x02, 0x22, 0x23])) + rom.write_bytes(0x0079B0, bytearray([0xAF, 0x03, 0xE2, 0x20, 0xAD, 0x1C, 0x01, 0xC9, 0x02, 0xC2, 0x20, 0xD0, 0x18, 0x20, 0x59, 0xF9])) + rom.write_bytes(0x0079C0, bytearray([0xE2, 0x20, 0xC9, 0x00, 0xD0, 0x13, 0xC2, 0x20, 0x22, 0xC5, 0xF7, 0x00, 0xE2, 0x20, 0xA9, 0x02])) + rom.write_bytes(0x0079D0, bytearray([0x9D, 0x00, 0x6F, 0xC2, 0x20, 0x5C, 0x35, 0x80, 0x04, 0xC2, 0x20, 0x22, 0xCF, 0xF7, 0x00, 0x80])) + rom.write_bytes(0x0079E0, bytearray([0xF2, 0xE2, 0x20, 0xAD, 0x4E, 0x14, 0xC2, 0x20, 0xF0, 0x07, 0xA9, 0x14, 0x00, 0x5C, 0x9E, 0xF1])) + rom.write_bytes(0x0079F0, bytearray([0x07, 0xA9, 0x0E, 0x00, 0x80, 0xF7, 0xBD, 0x60, 0x73, 0xDA, 0xA2, 0x00, 0xDF, 0x94, 0xAF, 0x09])) + rom.write_bytes(0x007A00, bytearray([0xF0, 0x11, 0xE0, 0x08, 0xF0, 0x04, 0xE8, 0xE8, 0x80, 0xF2, 0xFA, 0x22, 0x57, 0xF9, 0x0C, 0x5C])) + rom.write_bytes(0x007A10, bytearray([0xBD, 0xBE, 0x03, 0x8A, 0x4A, 0xE2, 0x20, 0xAA, 0xBF, 0x9E, 0xAF, 0x09, 0xAA, 0xBD, 0x40, 0x14])) + rom.write_bytes(0x007A20, bytearray([0xC9, 0x00, 0xC2, 0x20, 0xF0, 0x02, 0x80, 0xE2, 0x4C, 0x61, 0xFF, 0x00, 0x9D, 0x00, 0x6F, 0x74])) + rom.write_bytes(0x007A30, bytearray([0x78, 0x74, 0x18, 0x74, 0x76, 0x9E, 0x36, 0x7A, 0x9E, 0x38, 0x7A, 0x9E, 0x38, 0x7D, 0xBC, 0xC2])) + rom.write_bytes(0x007A40, bytearray([0x77, 0xB9, 0xB5, 0xBE, 0x9D, 0x20, 0x72, 0xA9, 0x00, 0xFC, 0x9D, 0x22, 0x72, 0xA9, 0x40, 0x00])) + rom.write_bytes(0x007A50, bytearray([0x9D, 0x42, 0x75, 0xA9, 0x90, 0x00, 0x22, 0xD2, 0x85, 0x00, 0x6B, 0x5A, 0xE2, 0x20, 0xAD, 0x51])) + rom.write_bytes(0x007A60, bytearray([0x14, 0xC9, 0x00, 0xC2, 0x20, 0xF0, 0x0D, 0x22, 0xCF, 0xF7, 0x00, 0x7A, 0x9B, 0xAD, 0x30, 0x00])) + rom.write_bytes(0x007A70, bytearray([0x5C, 0x62, 0xB7, 0x03, 0x22, 0xC5, 0xF7, 0x00, 0x7A, 0x9B, 0xA9, 0x03, 0x00, 0x5C, 0x62, 0xB7])) + rom.write_bytes(0x007A80, bytearray([0x03, 0x22, 0x23, 0xAF, 0x03, 0xE2, 0x20, 0xAD, 0x53, 0x14, 0xF0, 0x07, 0xC2, 0x20, 0x22, 0xCF])) + rom.write_bytes(0x007A90, bytearray([0xF7, 0x00, 0x6B, 0xC2, 0x20, 0x22, 0xC5, 0xF7, 0x00, 0x6B, 0xE2, 0x20, 0xAD, 0x53, 0x14, 0xF0])) + rom.write_bytes(0x007AA0, bytearray([0x07, 0xC2, 0x20, 0x22, 0x78, 0xBA, 0x07, 0x6B, 0xC2, 0x20, 0x6B, 0xC9, 0x06, 0x00, 0xB0, 0x0F])) + rom.write_bytes(0x007AB0, bytearray([0xE2, 0x20, 0xAD, 0x54, 0x14, 0xC9, 0x00, 0xC2, 0x20, 0xF0, 0x04, 0x5C, 0x94, 0x81, 0x07, 0x5C])) + rom.write_bytes(0x007AC0, bytearray([0xFB, 0x81, 0x07, 0x22, 0x23, 0xAF, 0x03, 0xE2, 0x20, 0xAD, 0x54, 0x14, 0xC2, 0x20, 0xF0, 0x08])) + rom.write_bytes(0x007AD0, bytearray([0x22, 0xCF, 0xF7, 0x00, 0x5C, 0xF7, 0x80, 0x07, 0x22, 0xC5, 0xF7, 0x00, 0x5C, 0xF7, 0x80, 0x07])) + rom.write_bytes(0x007AE0, bytearray([0x5A, 0xE2, 0x20, 0xAD, 0x55, 0x14, 0xC2, 0x20, 0xF0, 0x06, 0x22, 0xCF, 0xF7, 0x00, 0x80, 0x06])) + rom.write_bytes(0x007AF0, bytearray([0x22, 0xC5, 0xF7, 0x00, 0x80, 0x04, 0x22, 0x65, 0xC3, 0x0E, 0x7A, 0x5C, 0xFA, 0xBE, 0x0E, 0xE2])) + rom.write_bytes(0x007B00, bytearray([0x20, 0xAD, 0x56, 0x14, 0xC2, 0x20, 0xF0, 0x0A, 0x22, 0xCF, 0xF7, 0x00, 0x22, 0xB7, 0xA5, 0x03])) + rom.write_bytes(0x007B10, bytearray([0x80, 0x04, 0x22, 0xC5, 0xF7, 0x00, 0x5C, 0x3D, 0x96, 0x07, 0xBD, 0x02, 0x79, 0x85, 0x0E, 0xE2])) + rom.write_bytes(0x007B20, bytearray([0x20, 0xAD, 0x57, 0x14, 0xC2, 0x20, 0xF0, 0x05, 0x22, 0xCF, 0xF7, 0x00, 0x6B, 0x22, 0xC5, 0xF7])) + rom.write_bytes(0x007B30, bytearray([0x00, 0x6B, 0xE2, 0x20, 0xAD, 0x57, 0x14, 0xC2, 0x20, 0xD0, 0x0C, 0xAD, 0x74, 0x79, 0x29, 0x01])) + rom.write_bytes(0x007B40, bytearray([0x00, 0xD0, 0x04, 0x5C, 0x4A, 0xF3, 0x06, 0xBD, 0xD6, 0x79, 0x38, 0xFD, 0xE2, 0x70, 0x5C, 0x45])) + rom.write_bytes(0x007B50, bytearray([0xF2, 0x06, 0xAD, 0xAA, 0x60, 0x48, 0x30, 0x0E, 0xE2, 0x20, 0xAD, 0x57, 0x14, 0xC2, 0x20, 0xF0])) + rom.write_bytes(0x007B60, bytearray([0x05, 0x68, 0x5C, 0xA0, 0xF3, 0x06, 0x68, 0x5C, 0xE6, 0xF3, 0x06, 0xBD, 0x02, 0x79, 0x85, 0x0E])) + rom.write_bytes(0x007B70, bytearray([0xE2, 0x20, 0xAD, 0x57, 0x14, 0xC2, 0x20, 0xD0, 0x08, 0x22, 0xC5, 0xF7, 0x00, 0x5C, 0x35, 0xE5])) + rom.write_bytes(0x007B80, bytearray([0x06, 0x22, 0xCF, 0xF7, 0x00, 0x5C, 0x35, 0xE5, 0x06, 0xE2, 0x20, 0xAD, 0x57, 0x14, 0xC2, 0x20])) + rom.write_bytes(0x007B90, bytearray([0xD0, 0x0C, 0xAD, 0x74, 0x79, 0x29, 0x01, 0x00, 0xD0, 0x04, 0x5C, 0x48, 0xF3, 0x06, 0xBD, 0x36])) + rom.write_bytes(0x007BA0, bytearray([0x7A, 0x38, 0xE9, 0x08, 0x00, 0x5C, 0x63, 0xE8, 0x06, 0xAD, 0xAA, 0x60, 0x30, 0x0D, 0xE2, 0x20])) + rom.write_bytes(0x007BB0, bytearray([0xAD, 0x57, 0x14, 0xC2, 0x20, 0xF0, 0x04, 0x5C, 0x99, 0xE8, 0x06, 0x5C, 0xF1, 0xE8, 0x06, 0x9C])) + rom.write_bytes(0x007BC0, bytearray([0xB0, 0x61, 0x9C, 0x8C, 0x0C, 0xE2, 0x20, 0xAD, 0x58, 0x14, 0xC2, 0x20, 0xF0, 0x07, 0x9C, 0x8E])) + rom.write_bytes(0x007BD0, bytearray([0x0C, 0x5C, 0x9D, 0xA4, 0x02, 0xA9, 0x00, 0x00, 0x8F, 0xAE, 0x00, 0x70, 0x8F, 0xAC, 0x00, 0x70])) + rom.write_bytes(0x007BE0, bytearray([0xE2, 0x20, 0xA9, 0xFE, 0x9D, 0x78, 0x79, 0x8F, 0x49, 0x00, 0x7E, 0xC2, 0x20, 0x5C, 0x9D, 0xA4])) + rom.write_bytes(0x007BF0, bytearray([0x02, 0xE2, 0x20, 0xAF, 0x49, 0x00, 0x7E, 0xC2, 0x20, 0xF0, 0x0D, 0xA9, 0x00, 0x00, 0x9D, 0xD8])) + rom.write_bytes(0x007C00, bytearray([0x79, 0x9D, 0x78, 0x79, 0x8F, 0x49, 0x00, 0x7E, 0xBD, 0x16, 0x7C, 0x18, 0x5C, 0x51, 0xA3, 0x02])) + rom.write_bytes(0x007C10, bytearray([0xE2, 0x20, 0xAD, 0x59, 0x14, 0xC2, 0x20, 0xD0, 0x0D, 0x22, 0xC5, 0xF7, 0x00, 0xBD, 0x38, 0x7D])) + rom.write_bytes(0x007C20, bytearray([0xF0, 0x0A, 0x5C, 0x4F, 0xA0, 0x02, 0x22, 0xCF, 0xF7, 0x00, 0x80, 0xF1, 0x5C, 0x59, 0xA0, 0x02])) + rom.write_bytes(0x007C30, bytearray([0xE2, 0x20, 0xAD, 0x59, 0x14, 0xC2, 0x20, 0xF0, 0x09, 0xBB, 0x22, 0x87, 0xBF, 0x03, 0x5C, 0x8D])) + rom.write_bytes(0x007C40, bytearray([0xA3, 0x02, 0x5C, 0x81, 0xA3, 0x02, 0xE2, 0x20, 0xAD, 0x5A, 0x14, 0xC2, 0x20, 0xF0, 0x09, 0xB5])) + rom.write_bytes(0x007C50, bytearray([0x76, 0x29, 0xFF, 0x00, 0x5C, 0x9D, 0x93, 0x02, 0x8D, 0x04, 0x30, 0xA9, 0x00, 0x00, 0x8D, 0x08])) + rom.write_bytes(0x007C60, bytearray([0x30, 0x5C, 0xA5, 0x93, 0x02, 0xE2, 0x20, 0xAD, 0x5A, 0x14, 0xC2, 0x20, 0xD0, 0x01, 0x6B, 0x22])) + rom.write_bytes(0x007C70, bytearray([0x23, 0xAF, 0x03, 0x5C, 0xDA, 0x93, 0x02, 0xE2, 0x20, 0xAD, 0x5B, 0x14, 0xC2, 0x20, 0xF0, 0x09])) + rom.write_bytes(0x007C80, bytearray([0x9B, 0xBD, 0xD6, 0x79, 0x0A, 0x5C, 0xCA, 0xC4, 0x05, 0x6B, 0xE2, 0x20, 0xAD, 0x5B, 0x14, 0xC2])) + rom.write_bytes(0x007C90, bytearray([0x20, 0xF0, 0x09, 0x9B, 0xBD, 0xD6, 0x79, 0x0A, 0x5C, 0xC1, 0xC8, 0x05, 0x6B, 0x22, 0x52, 0xAA])) + rom.write_bytes(0x007CA0, bytearray([0x03, 0xE2, 0x20, 0xAD, 0x5B, 0x14, 0xC2, 0x20, 0xF0, 0x0A, 0xA0, 0x00, 0x22, 0xD1, 0xF7, 0x00])) + rom.write_bytes(0x007CB0, bytearray([0x5C, 0xD9, 0xC4, 0x05, 0x22, 0xC5, 0xF7, 0x00, 0x5C, 0x70, 0xC5, 0x05, 0x22, 0x23, 0xAF, 0x03])) + rom.write_bytes(0x007CC0, bytearray([0xE2, 0x20, 0xAD, 0x5C, 0x14, 0xC2, 0x20, 0xF0, 0x0A, 0xA0, 0x00, 0x22, 0xD1, 0xF7, 0x00, 0x5C])) + rom.write_bytes(0x007CD0, bytearray([0x24, 0xC9, 0x0C, 0x22, 0xC5, 0xF7, 0x00, 0x80, 0xF6, 0xE2, 0x20, 0xAD, 0x5C, 0x14, 0xC2, 0x20])) + rom.write_bytes(0x007CE0, bytearray([0xF0, 0x08, 0x8A, 0x8D, 0x02, 0x30, 0x5C, 0x4D, 0xCD, 0x0C, 0xFA, 0x5C, 0x3A, 0xCD, 0x0C, 0x48])) + rom.write_bytes(0x007CF0, bytearray([0xDA, 0xE2, 0x20, 0xAD, 0x5D, 0x14, 0xF0, 0x33, 0xAA, 0x4C, 0x53, 0xFF, 0xFF, 0x18, 0x4C, 0x71])) + rom.write_bytes(0x007D00, bytearray([0xFF, 0x8D, 0x5E, 0x14, 0xC2, 0x20, 0xFA, 0x68, 0x1A, 0x1A, 0xC9, 0x0E, 0x00, 0x90, 0x06, 0x80])) + rom.write_bytes(0x007D10, bytearray([0x16, 0x5C, 0x15, 0xBF, 0x03, 0xE2, 0x20, 0x48, 0xBD, 0x60, 0x73, 0xC9, 0x27, 0xF0, 0x12, 0x68])) + rom.write_bytes(0x007D20, bytearray([0xCD, 0x5E, 0x14, 0xC2, 0x20, 0x90, 0xEA, 0x5C, 0xE5, 0xFA, 0x0B, 0x1A, 0x8D, 0x5D, 0x14, 0x80])) + rom.write_bytes(0x007D30, bytearray([0xC0, 0x68, 0xC2, 0x20, 0xEE, 0xCC, 0x00, 0xEE, 0xCC, 0x00, 0x80, 0xD5, 0xA8, 0x5C, 0x20, 0xBF])) + rom.write_bytes(0x007D40, bytearray([0x03, 0x8B, 0xA9, 0x03, 0x8D, 0x4B, 0x09, 0x8D, 0x01, 0x21, 0x22, 0x39, 0xB4, 0x00, 0x22, 0x79])) + rom.write_bytes(0x007D50, bytearray([0x82, 0x10, 0xDA, 0xAD, 0x0E, 0x03, 0x4A, 0xAA, 0xBF, 0xF3, 0xFE, 0x06, 0xAA, 0xAD, 0x1A, 0x02])) + rom.write_bytes(0x007D60, bytearray([0x9F, 0x00, 0x7C, 0x70, 0x9C, 0x22, 0x02, 0xAF, 0x83, 0xFC, 0x0D, 0xAA, 0xBF, 0xB2, 0xAF, 0x09])) + rom.write_bytes(0x007D70, bytearray([0x0C, 0xCE, 0x00, 0xAD, 0x60, 0x14, 0x0C, 0xCE, 0x00, 0x5A, 0xC2, 0x10, 0xA2, 0xAA, 0xAF, 0xAD])) + rom.write_bytes(0x007D80, bytearray([0xCE, 0x00, 0x89, 0x01, 0xF0, 0x06, 0xA0, 0x22, 0x02, 0x20, 0xCA, 0xFD, 0x89, 0x02, 0xF0, 0x06])) + rom.write_bytes(0x007D90, bytearray([0xA0, 0x2E, 0x02, 0x20, 0xCA, 0xFD, 0x89, 0x04, 0xF0, 0x06, 0xA0, 0x3A, 0x02, 0x20, 0xCA, 0xFD])) + rom.write_bytes(0x007DA0, bytearray([0x89, 0x08, 0xF0, 0x06, 0xA0, 0x46, 0x02, 0x20, 0xCA, 0xFD, 0x89, 0x10, 0xF0, 0x06, 0xA0, 0x52])) + rom.write_bytes(0x007DB0, bytearray([0x02, 0x20, 0xCA, 0xFD, 0x89, 0x20, 0xF0, 0x06, 0xA0, 0x5E, 0x02, 0x20, 0xCA, 0xFD, 0x9C, 0x65])) + rom.write_bytes(0x007DC0, bytearray([0x02, 0xE2, 0x10, 0x7A, 0xFA, 0xAB, 0x5C, 0xB6, 0xA5, 0x17, 0xC2, 0x20, 0x48, 0xA9, 0x07, 0x00])) + rom.write_bytes(0x007DD0, bytearray([0xDA, 0x54, 0x00, 0x09, 0xFA, 0x68, 0xE2, 0x20, 0x60, 0xDA, 0x5A, 0x8B, 0xAD, 0x0E, 0x03, 0xC2])) + rom.write_bytes(0x007DE0, bytearray([0x20, 0xC2, 0x10, 0xAA, 0xBF, 0x07, 0xFF, 0x06, 0xA8, 0xE2, 0x20, 0xA9, 0x00, 0xEB, 0xA9, 0x7F])) + rom.write_bytes(0x007DF0, bytearray([0xA2, 0xC0, 0x14, 0x54, 0x70, 0x7E, 0xA2, 0xC0, 0x14, 0xA0, 0x40, 0x14, 0xA9, 0x00, 0xEB, 0xA9])) + rom.write_bytes(0x007E00, bytearray([0x7F, 0x54, 0x7E, 0x7E, 0xE2, 0x10, 0xAB, 0x7A, 0xFA, 0xA9, 0x1E, 0x8D, 0x18, 0x01, 0xAF, 0x83])) + rom.write_bytes(0x007E10, bytearray([0xFC, 0x0D, 0xDA, 0xAA, 0xBF, 0xB8, 0xAF, 0x09, 0x8D, 0x18, 0x02, 0xAF, 0x88, 0xFC, 0x0D, 0x49])) + rom.write_bytes(0x007E20, bytearray([0x01, 0x8D, 0x5A, 0x14, 0xFA, 0x5C, 0x58, 0x99, 0x17, 0xAE, 0x15, 0x11, 0xAD, 0x60, 0x14, 0x89])) + rom.write_bytes(0x007E30, bytearray([0x01, 0xD0, 0x0D, 0xAF, 0x83, 0xFC, 0x0D, 0xF0, 0x07, 0x9E, 0x10, 0x00, 0x5C, 0xB1, 0xD8, 0x17])) + rom.write_bytes(0x007E40, bytearray([0xFE, 0x10, 0x00, 0x80, 0xF7, 0xA9, 0xF0, 0x85, 0x4D, 0x8D, 0x63, 0x14, 0xA9, 0x80, 0x8D, 0x20])) + rom.write_bytes(0x007E50, bytearray([0x02, 0x8D, 0x4A, 0x00, 0x5C, 0x59, 0xC1, 0x01, 0xE2, 0x20, 0xAD, 0x61, 0x14, 0x89, 0x01, 0xF0])) + rom.write_bytes(0x007E60, bytearray([0x08, 0x48, 0xA9, 0x09, 0x8F, 0x17, 0x03, 0x17, 0x68, 0x89, 0x02, 0xF0, 0x08, 0x48, 0xA9, 0x09])) + rom.write_bytes(0x007E70, bytearray([0x8F, 0x23, 0x03, 0x17, 0x68, 0x89, 0x04, 0xF0, 0x08, 0x48, 0xA9, 0x09, 0x8F, 0x2F, 0x03, 0x17])) + rom.write_bytes(0x007E80, bytearray([0x68, 0x89, 0x08, 0xF0, 0x08, 0x48, 0xA9, 0x09, 0x8F, 0x3B, 0x03, 0x17, 0x68, 0x89, 0x10, 0xF0])) + rom.write_bytes(0x007E90, bytearray([0x08, 0x48, 0xA9, 0x09, 0x8F, 0x47, 0x03, 0x17, 0x68, 0x89, 0x20, 0xF0, 0x08, 0x48, 0xA9, 0x09])) + rom.write_bytes(0x007EA0, bytearray([0x8F, 0x53, 0x03, 0x17, 0x68, 0xAD, 0x62, 0x14, 0x89, 0x01, 0xF0, 0x08, 0x48, 0xA9, 0x0A, 0x8F])) + rom.write_bytes(0x007EB0, bytearray([0x18, 0x03, 0x17, 0x68, 0x89, 0x02, 0xF0, 0x08, 0x48, 0xA9, 0x0A, 0x8F, 0x24, 0x03, 0x17, 0x68])) + rom.write_bytes(0x007EC0, bytearray([0x89, 0x04, 0xF0, 0x08, 0x48, 0xA9, 0x0A, 0x8F, 0x30, 0x03, 0x17, 0x68, 0x89, 0x08, 0xF0, 0x08])) + rom.write_bytes(0x007ED0, bytearray([0x48, 0xA9, 0x0A, 0x8F, 0x3C, 0x03, 0x17, 0x68, 0x89, 0x10, 0xF0, 0x08, 0x48, 0xA9, 0x0A, 0x8F])) + rom.write_bytes(0x007EE0, bytearray([0x48, 0x03, 0x17, 0x68, 0x89, 0x20, 0xF0, 0x08, 0x48, 0xA9, 0x0A, 0x8F, 0x54, 0x03, 0x17, 0x68])) + rom.write_bytes(0x007EF0, bytearray([0xC2, 0x20, 0x5C, 0x26, 0xDB, 0x17, 0xAD, 0x63, 0x14, 0xF0, 0x0E, 0xA9, 0x00, 0x8D, 0x63, 0x14])) + rom.write_bytes(0x007F00, bytearray([0xA9, 0x20, 0x8D, 0x18, 0x01, 0x5C, 0x04, 0xA9, 0x17, 0xA9, 0x25, 0x80, 0xF5, 0xAD, 0x06, 0x7E])) + rom.write_bytes(0x007F10, bytearray([0xD0, 0x15, 0xE2, 0x20, 0xAD, 0x64, 0x14, 0xD0, 0x0E, 0xAF, 0x84, 0xFC, 0x0D, 0x89, 0x01, 0xD0])) + rom.write_bytes(0x007F20, bytearray([0x06, 0xC2, 0x20, 0x5C, 0x49, 0xEA, 0x0C, 0xC2, 0x20, 0x5C, 0x47, 0xEA, 0x0C, 0xAD, 0x06, 0x7E])) + rom.write_bytes(0x007F30, bytearray([0xD0, 0x15, 0xE2, 0x20, 0xAD, 0x64, 0x14, 0xD0, 0x0E, 0xAF, 0x84, 0xFC, 0x0D, 0x89, 0x02, 0xD0])) + rom.write_bytes(0x007F40, bytearray([0x06, 0xC2, 0x20, 0x5C, 0x91, 0xC0, 0x03, 0xC2, 0x20, 0x5C, 0xCC, 0xC0, 0x03])) + rom.write_bytes(0x007F53, bytearray([0xBF, 0xA3, 0xAF, 0x09, 0xE0, 0x06, 0xF0, 0x03, 0x4C, 0xFD, 0xFC, 0x4C, 0x01, 0xFD])) + rom.write_bytes(0x007F61, bytearray([0xAF, 0xAE, 0x00, 0x70, 0xD0, 0x07, 0xFA, 0xA9, 0x0E, 0x00, 0x4C, 0x2C, 0xFA, 0x4C, 0x26, 0xFA])) + rom.write_bytes(0x007F71, bytearray([0x6D, 0xCC, 0x00, 0xC9, 0x0E, 0x90, 0x02, 0xA9, 0x0E, 0x4C, 0x01, 0xFD])) + + rom.write_bytes(0x077F82, bytearray([0xE2, 0x20, 0xAD, 0x40, 0x14, 0xD0, 0x08, 0xC2, 0x20, 0x22, 0xC5, 0xF7, 0x00, 0x80])) + rom.write_bytes(0x077F90, bytearray([0x06, 0xA0, 0x00, 0x22, 0xD1, 0xF7, 0x00, 0xC2, 0x20, 0x20, 0x1A, 0xB6, 0x60, 0xE2, 0x20, 0xAD])) + rom.write_bytes(0x077FA0, bytearray([0x55, 0x14, 0xC2, 0x20, 0xD0, 0x03, 0x4C, 0xFD, 0xBE, 0x20, 0xBB, 0xBF, 0x4C, 0xFD, 0xBE])) + + rom.write_bytes(0x01FEEE, bytearray([0xB9, 0x00])) + rom.write_bytes(0x01FEF0, bytearray([0x6F, 0x48, 0xDA, 0xBD, 0x60, 0x73, 0xA2, 0x00, 0xDF, 0x70, 0xAF, 0x09, 0xF0, 0x08, 0xE8, 0xE8])) + rom.write_bytes(0x01FF00, bytearray([0xE0, 0x08, 0xF0, 0x1A, 0x80, 0xF2, 0x8A, 0x4A, 0xE2, 0x20, 0xAA, 0xBF, 0x78, 0xAF, 0x09, 0xAA])) + rom.write_bytes(0x01FF10, bytearray([0xBD, 0x40, 0x14, 0xC2, 0x20, 0xD0, 0x07, 0xFA, 0x68, 0xC9, 0x00, 0x00, 0x80, 0x05, 0xFA, 0x68])) + rom.write_bytes(0x01FF20, bytearray([0xC9, 0x10, 0x00, 0x5C, 0x34, 0xC3, 0x03, 0xAE, 0x12, 0x98, 0xE2, 0x20, 0xAD, 0x5E, 0x14, 0xC9])) + rom.write_bytes(0x01FF30, bytearray([0x0E, 0xF0, 0x08, 0x3A, 0x3A, 0xA8, 0xC2, 0x20, 0x4C, 0x15, 0xBF, 0x98, 0x80, 0xF8])) + + rom.write_bytes(0x02FFC0, bytearray([0x0C, 0xA6, 0x12, 0x6B, 0xBD, 0x60, 0x73, 0xC9, 0x1E, 0x01, 0xE2, 0x20, 0xF0, 0x05, 0xAD, 0x4C])) + rom.write_bytes(0x02FFD0, bytearray([0x14, 0x80, 0x03, 0xAD, 0x4B, 0x14, 0xC9, 0x00, 0xC2, 0x20, 0xF0, 0x03, 0x20, 0xF6, 0xF1, 0x4C])) + rom.write_bytes(0x02FFE0, bytearray([0xB0, 0xF0])) + + rom.write_bytes(0x017FD7, bytearray([0xE2, 0x20, 0xAD, 0x4D, 0x14, 0xC2, 0x20, 0xF0, 0x10])) + rom.write_bytes(0x017FE0, bytearray([0xBD, 0x60, 0x73, 0xC9, 0xA9, 0x01, 0xF0, 0x04, 0xA9, 0x04, 0x00, 0x60, 0xA9, 0x0A, 0x00, 0x60])) + rom.write_bytes(0x017FF0, bytearray([0x68, 0x4C, 0x90, 0xAD])) + + rom.write_bytes(0x03FF48, bytearray([0xE2, 0x20, 0xAD, 0x56, 0x14, 0xC2, 0x20, 0xD0])) + rom.write_bytes(0x03FF50, bytearray([0x03, 0x4C, 0x5B, 0x96, 0x20, 0x3D, 0x9D, 0x4C, 0x4F, 0x96])) + + +def Item_Data(rom: LocalRom) -> None: + rom.write_bytes(0x04AF70, bytearray([0xBB, 0x00, 0xBA, 0x00, 0xC7, 0x00, 0xC8, 0x00, 0x01, 0x02, 0x03, 0x03, 0xB1, 0x00, 0xB0, 0x00])) + rom.write_bytes(0x04AF80, bytearray([0xB2, 0x00, 0xAF, 0x00, 0xB4, 0x00, 0x04, 0x05, 0x06, 0x07, 0x08, 0x07, 0x00, 0x05, 0x00, 0x09])) + rom.write_bytes(0x04AF90, bytearray([0x00, 0x0D, 0x0E, 0x0F, 0x22, 0x00, 0x26, 0x00, 0x29, 0x00, 0x2A, 0x00, 0x2B, 0x00, 0x11, 0x12])) + rom.write_bytes(0x04AFA0, bytearray([0x12, 0x12, 0x12, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01])) + rom.write_bytes(0x04AFB0, bytearray([0x01, 0x01, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A])) + + +def Server_Data(rom: LocalRom) -> None: + rom.write_bytes(0x037EAA, bytearray([0x00, 0x00, 0x01, 0x02, 0x03, 0x04])) + rom.write_bytes(0x037EB0, bytearray([0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14])) + rom.write_bytes(0x037EC0, bytearray([0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x24, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x01])) + rom.write_bytes(0x037ED0, bytearray([0x02, 0x04, 0x08, 0x10, 0x20, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30])) + rom.write_bytes(0x037EE0, bytearray([0x31, 0x32, 0x33, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0xFF, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39])) + rom.write_bytes(0x037EF0, bytearray([0x3A, 0x3B, 0x3C, 0x02, 0x6A, 0xD2, 0x04, 0x03, 0x06, 0x07, 0x08, 0x09, 0x05, 0x01, 0x02, 0x3D])) + rom.write_bytes(0x037F00, bytearray([0x3E, 0x3F, 0x40, 0x01, 0x02, 0x03, 0x0A, 0x80, 0x7E, 0x00, 0x7F, 0x80, 0x7F])) + + +def Menu_Data(rom: LocalRom) -> None: + rom.write_bytes(0x115348, bytearray([0x80, 0x80, 0x4E, 0x80, 0x80, 0x4E, 0x80, 0x80])) + rom.write_bytes(0x115350, bytearray([0x4E, 0x80, 0x80, 0x4E, 0x80, 0x80, 0x4E, 0x80, 0x80, 0x4E, 0x80, 0x80, 0x4E, 0x80, 0x80, 0x4E])) + rom.write_bytes(0x115360, bytearray([0x80, 0x80, 0x4E, 0x80, 0x80, 0x4E, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03])) + rom.write_bytes(0x115370, bytearray([0x03, 0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x08, 0x08])) + rom.write_bytes(0x115380, bytearray([0x08, 0x09, 0x09, 0x09, 0x0A, 0x0A, 0x0A, 0x24, 0x2C, 0x00, 0x06, 0x1E, 0x00, 0x06, 0x24, 0x00])) + rom.write_bytes(0x115390, bytearray([0x02, 0x24, 0x00, 0x0E, 0x04, 0x00, 0x18, 0x26, 0x00, 0x26, 0x1A, 0x00, 0x04, 0x22, 0x00, 0x24])) + rom.write_bytes(0x1153A0, bytearray([0x18, 0x00, 0x24, 0x02, 0x00, 0x16, 0x24, 0x00, 0x00, 0x2C, 0x00, 0x2A, 0x2C, 0x00, 0x2C, 0x18])) + rom.write_bytes(0x1153B0, bytearray([0x00, 0x10, 0x18, 0x00, 0x0A, 0x18, 0x00, 0x24, 0x24, 0x00, 0x0A, 0x08, 0x00, 0x0C, 0x08, 0x00])) + rom.write_bytes(0x1153C0, bytearray([0x08, 0x16, 0x00, 0x08, 0x1E, 0x00, 0x04, 0x14, 0x00, 0x1E, 0x0E, 0x00, 0x1E, 0x0C, 0x00, 0x24])) + rom.write_bytes(0x1153D0, bytearray([0x14, 0x00, 0x14, 0x30, 0x00, 0x18, 0x22, 0x00, 0x02, 0x04, 0x00, 0x26, 0x16, 0x00, 0x24, 0x16])) + rom.write_bytes(0x1153E0, bytearray([0x00, 0x5C, 0x38, 0x60, 0x4E, 0x28, 0x1A, 0x16, 0x1C, 0x04, 0x14, 0x36, 0x36, 0x36, 0x80, 0x80])) + rom.write_bytes(0x1153F0, bytearray([0x34, 0x81, 0x81, 0x4E, 0x4E, 0x4E, 0x5C, 0x38, 0x60, 0x4E, 0x04, 0x16, 0x08, 0x00, 0x22, 0x36])) + rom.write_bytes(0x115400, bytearray([0x36, 0x36, 0x36, 0x80, 0x80, 0x34, 0x81, 0x81, 0x4E, 0x4E, 0x4E, 0x50, 0x52, 0x54, 0x56, 0x58])) + rom.write_bytes(0x115410, bytearray([0x5A, 0x5C, 0x5E, 0x60, 0x62, 0x50, 0x52, 0x54, 0x09, 0x15, 0x21, 0x2D, 0x39, 0x45, 0x0C, 0x03])) + rom.write_bytes(0x115420, bytearray([0x07, 0x0F, 0x13, 0x1B, 0x1F, 0x27, 0x2B, 0x33, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x02, 0x04, 0x04])) + rom.write_bytes(0x115430, bytearray([0x06, 0x06, 0x08, 0x08, 0x0A, 0x41, 0x00, 0x3C, 0x00, 0x33, 0x00, 0x25, 0x00, 0x1B, 0x00, 0x14])) + rom.write_bytes(0x115440, bytearray([0x00, 0x0B, 0x00, 0x02, 0x00, 0xF6, 0x3F, 0xEC, 0x3F, 0xDC, 0x3F])) + + rom.write_bytes(0x082660, bytearray([0x07])) + rom.write_bytes(0x082667, bytearray([0x05])) + rom.write_bytes(0x082677, bytearray([0x0A, 0x03, 0x05])) + rom.write_bytes(0x082688, bytearray([0x00])) + + rom.write_bytes(0x11548E, bytearray([0x60, 0x3d, 0x66, 0x3b, 0x60, 0x3f, 0x60, 0x39, 0x66, 0x39, 0x66, 0x3d, 0x66, 0x3f, 0x60, 0x3b])) + rom.write_bytes(0x11549E, bytearray([0x02, 0x06, 0x04, 0x00, 0x01, 0x03, 0x05, 0x07])) + + +def CodeHandler(rom: LocalRom) -> None: + rom.write_bytes(0x073637, bytearray([0x5C, 0xB0, 0xF7, 0x00])) # Check ! Switch + rom.write_bytes(0x07360B, bytearray([0x20, 0x82, 0xFF])) # Flash ! Switch + + rom.write_bytes(0x01C2F3, bytearray([0x22, 0x11, 0xF8, 0x00])) # Check visibility of winged clouds + rom.write_bytes(0x01C32E, bytearray([0x5C, 0xEE, 0xFE, 0x03])) # Check items in winged clouds + + rom.write_bytes(0x01C9AD, bytearray([0x5C, 0x19, 0xF8, 0x00])) # Check transformations + rom.write_bytes(0x01C995, bytearray([0x5C, 0x59, 0xF8, 0x00])) # Flash transformations + rom.write_bytes(0x01C943, bytearray([0x5C, 0x77, 0xF8, 0x00])) # Fixes a bug where transformation bubbles flashing would displace the sprite + + rom.write_bytes(0x028329, bytearray([0x5C, 0x9A, 0xF8, 0x00])) # Flash Spring Ball + rom.write_bytes(0x02837E, bytearray([0x5C, 0xC4, 0xF8, 0x00])) # Check Spring Ball + + rom.write_bytes(0x02F0A2, bytearray([0x5C, 0xEA, 0xF8, 0x00])) # Flash Arrow Wheel + rom.write_bytes(0x02F0AD, bytearray([0x4C, 0xC4, 0xFF])) # Check Arrow Wheel + + rom.write_bytes(0x02001D, bytearray([0x5C, 0x15, 0xF9, 0x00])) # Check Melon + rom.write_bytes(0x020028, bytearray([0x5C, 0x41, 0xF9, 0x00])) # Secondary check for melon used to overwrite visibility on the ground + rom.write_bytes(0x020031, bytearray([0x5C, 0xAE, 0xF9, 0x00])) # Check for melons that are spawned by objects which skips the initial check + rom.write_bytes(0x012DF7, bytearray([0x20, 0xD7, 0xFF])) # Check for monkeys holding melons + rom.write_bytes(0x012E07, bytearray([0x20, 0xD7, 0xFF])) # Check for monkeys holding melons + rom.write_bytes(0x03F17D, bytearray([0x5C, 0xE1, 0xF9, 0x00])) # Fixes a bug where balloons with ice melons will write to yoshi's mouth before deactivating the melon. + + rom.write_bytes(0x011901, bytearray([0x5C, 0x7A, 0xF9, 0x00])) # Flash Super Star + rom.write_bytes(0x01192A, bytearray([0x5C, 0x95, 0xF9, 0x00])) # Check Super Star + + rom.write_bytes(0x01BEB9, bytearray([0x5C, 0xF6, 0xF9, 0x00])) # Check egg-type items + rom.write_bytes(0x01B75E, bytearray([0x5C, 0x5B, 0xFA, 0x00])) # Flash flashing eggs and force them to purple + + rom.write_bytes(0x03BA31, bytearray([0x22, 0x81, 0xFA, 0x00])) # Flash Arrow Cloud + rom.write_bytes(0x03BA35, bytearray([0x22, 0x9A, 0xFA, 0x00])) # Check Arrow Cloud + rom.write_bytes(0x03BA3D, bytearray([0x22, 0x81, 0xFA, 0x00])) # Flash Arrow Cloud, rotating + rom.write_bytes(0x03BA5A, bytearray([0x22, 0x9A, 0xFA, 0x00])) # Check Arrow Cloud, rotating + + rom.write_bytes(0x03818F, bytearray([0x5C, 0xAB, 0xFA, 0x00])) # Check Egg Plant + rom.write_bytes(0x0380F3, bytearray([0x5C, 0xC3, 0xFA, 0x00])) # Flash Egg Plant + + rom.write_bytes(0x073EF6, bytearray([0x5C, 0xE0, 0xFA, 0x00])) # Flash Chomp Rock + rom.write_bytes(0x073EFA, bytearray([0x4C, 0x9D, 0xFF])) # Check Chomp Rock + + rom.write_bytes(0x039639, bytearray([0x5C, 0xFF, 0xFA, 0x00])) # Flash Poochy + rom.write_bytes(0x03964C, bytearray([0x4C, 0x48, 0xFF])) # Check Poochy + + rom.write_bytes(0x0370C2, bytearray([0x22, 0x1A, 0xFB, 0x00, 0xEA])) # Flash Platform Ghosts + rom.write_bytes(0x03723F, bytearray([0x5C, 0x32, 0xFB, 0x00])) # Fixes a bug where the eyes would assign to a random sprite while flashing + rom.write_bytes(0x03739B, bytearray([0x5C, 0x52, 0xFB, 0x00])) # Check Vertical Platform Ghost + rom.write_bytes(0x036530, bytearray([0x5C, 0x6B, 0xFB, 0x00])) # Flash horizontal ghost + rom.write_bytes(0x03685C, bytearray([0x5C, 0x89, 0xFB, 0x00])) # Fix flashing horizontal ghost + rom.write_bytes(0x036894, bytearray([0x5C, 0xA9, 0xFB, 0x00])) # Check horizontal ghost + + rom.write_bytes(0x012497, bytearray([0x5C, 0xBF, 0xFB, 0x00])) # Check Skis + rom.write_bytes(0x01234D, bytearray([0x5C, 0xF1, 0xFB, 0x00])) # Allow ski doors to be re-entered + + rom.write_bytes(0x01204A, bytearray([0x5C, 0x10, 0xFC, 0x00])) # Flash Key + rom.write_bytes(0x012388, bytearray([0x5C, 0x30, 0xFC, 0x00])) # Check Key + + rom.write_bytes(0x011398, bytearray([0x5C, 0x46, 0xFC, 0x00])) # Flash MidRing + rom.write_bytes(0x0113D6, bytearray([0x5C, 0x65, 0xFC, 0x00])) # Check MidRing + + rom.write_bytes(0x02C4C6, bytearray([0x5C, 0x77, 0xFC, 0x00])) # Check Bucket w/ Item + rom.write_bytes(0x02C8BD, bytearray([0x5C, 0x8A, 0xFC, 0x00])) # Check Bucket, ridable + rom.write_bytes(0x02C4D5, bytearray([0x5C, 0x9D, 0xFC, 0x00])) # Flash Bucket + + rom.write_bytes(0x064920, bytearray([0x5C, 0xBC, 0xFC, 0x00])) # Flash Tulip + rom.write_bytes(0x064D49, bytearray([0x5C, 0xD9, 0xFC, 0x00])) # Check Tulip + + rom.write_bytes(0x01BEC7, bytearray([0x5C, 0xEF, 0xFC, 0x00])) # Check Egg Capacity + rom.write_bytes(0x01BF12, bytearray([0x4C, 0x27, 0xFF])) # Set current egg max + rom.write_bytes(0x01BF1A, bytearray([0x5C, 0x3C, 0xFD, 0x00])) # Cap eggs + + rom.write_bytes(0x0BA5AE, bytearray([0x5C, 0x41, 0xFD, 0x00])) # Unlock Levels + + rom.write_bytes(0x0B9953, bytearray([0x5C, 0xD9, 0xFD, 0x00])) # File initialization + + rom.write_bytes(0x0BD8AB, bytearray([0x5C, 0x29, 0xFE, 0x00])) # Prevent the world 1 tab from being drawn without it being unlocked + + rom.write_bytes(0x00C155, bytearray([0x5C, 0x45, 0xFE, 0x00])) # Save between levels + + rom.write_bytes(0x0BDB20, bytearray([0x5C, 0x58, 0xFE, 0x00])) # Unlock extra and bonus stages + + rom.write_bytes(0x0BA8FF, bytearray([0x5C, 0xF6, 0xFE, 0x00])) # Skip the score animation if coming from start-select, but still save + + rom.write_bytes(0x0BA8A9, bytearray([0x80, 0x46])) # Prevent unlocking new levels + + rom.write_bytes(0x066A42, bytearray([0x5C, 0x0D, 0xFF, 0x00])) # Coin visibility + rom.write_bytes(0x01C08C, bytearray([0x5C, 0x2D, 0xFF, 0x00])) # Cloud visibility + + rom.write_bytes(0x00C0D9, bytearray([0x5C, 0xB8, 0xF3, 0x0B])) # Receive item from server + + rom.write_bytes(0x00C153, bytearray([0xEA, 0xEA])) # Always enable Start/Select + + rom.write_bytes(0x00C18B, bytearray([0x5C, 0x1B, 0xF5, 0x0B])) # Enable traps + + rom.write_bytes(0x01B365, bytearray([0x5C, 0x86, 0xF5, 0x0B])) # Red Coin checks + rom.write_bytes(0x0734C6, bytearray([0x5C, 0xCE, 0xF5, 0x0B])) # Flower checks + rom.write_bytes(0x00C0DE, bytearray([0x5C, 0xF5, 0xF5, 0x0B])) # Star checks + rom.write_bytes(0x00B580, bytearray([0x5C, 0xB1, 0xF5, 0x0B])) # Level Clear checks + + rom.write_bytes(0x0B9937, bytearray([0x5C, 0x23, 0xF6, 0x0B])) # Load AP data + rom.write_bytes(0x0BE14A, bytearray([0x5C, 0x58, 0xF6, 0x0B])) # Save AP data + + rom.write_bytes(0x00D09F, bytearray([0x5C, 0x8C, 0xF6, 0x0B])) # Clear Menu + rom.write_bytes(0x00BCB5, bytearray([0x5C, 0xAD, 0xF6, 0x0B])) # Clear Score for menu + rom.write_bytes(0x00D072, bytearray([0x5C, 0xC3, 0xF6, 0x0B])) # Loads the data for the AP menu + rom.write_bytes(0x00D07A, bytearray([0x5C, 0x5A, 0xF7, 0x0B])) # Draw the AP menu over the pause menu + rom.write_bytes(0x00D17A, bytearray([0x5C, 0xDA, 0xF7, 0x0B])) # Skip the flower counter in the AP menu + rom.write_bytes(0x00D0DE, bytearray([0x5C, 0xF1, 0xF7, 0x0B])) # Skip the coin counter in the AP menu + rom.write_bytes(0x00CFB4, bytearray([0x5C, 0x06, 0xF8, 0x0B])) # Get the number of bosses required to unlock 6-8 + rom.write_bytes(0x00CFD0, bytearray([0x5C, 0x2B, 0xF8, 0x0B])) # Get bosses for 6-8 clear + rom.write_bytes(0x00D203, bytearray([0x5C, 0xF0, 0xF8, 0x0B])) # Wipe total score line + rom.write_bytes(0x00D277, bytearray([0x5C, 0x04, 0xF9, 0x0B])) # Wipe high score line + rom.write_bytes(0x00C104, bytearray([0x5C, 0x18, 0xF9, 0x0B])) # Replace the pause menu with AP menu when SELECT is pressed + rom.write_bytes(0x00C137, bytearray([0x5C, 0x31, 0xF9, 0x0B])) # Prevent accidentally quitting out of a stage while opening the AP menu + rom.write_bytes(0x00CE48, bytearray([0x5C, 0x42, 0xF9, 0x0B])) # When closing the AP menu, reset the AP menu flag so the normal menu can be opened. + + rom.write_bytes(0x0BA5B6, bytearray([0x5C, 0x4E, 0xF9, 0x0B])) # Unlock 6-8 if the current number of defeated bosses is higher than the number of bosses required. If 6-8 is marked 'cleared', skip boss checks + rom.write_bytes(0x01209E, bytearray([0x5C, 0x92, 0xF9, 0x0B])) # Write a flag to check bosses if setting up the final boss door + rom.write_bytes(0x0123AA, bytearray([0x5C, 0xA3, 0xF9, 0x0B])) # If the boss check flag is set, read the number of bosses before opening door + + rom.write_bytes(0x015F7A, bytearray([0x5C, 0xCA, 0xF9, 0x0B])) # Write Boss Clears + + rom.write_bytes(0x0BE16E, bytearray([0x80, 0x12])) # Disable overworld bandit code + + rom.write_bytes(0x083015, bytearray([0x5C, 0x26, 0xFA, 0x0B])) # Flip Cards + rom.write_bytes(0x0839B6, bytearray([0x5C, 0x18, 0xFA, 0x0B])) # Scratch Cards + rom.write_bytes(0x085094, bytearray([0x5C, 0x31, 0xFA, 0x0B])) # Draw Lots + rom.write_bytes(0x0852C5, bytearray([0x5C, 0x3D, 0xFA, 0x0B])) # Match Cards + rom.write_bytes(0x0845EA, bytearray([0x5C, 0x48, 0xFA, 0x0B])) # Roulette + rom.write_bytes(0x083E0A, bytearray([0x5C, 0x53, 0xFA, 0x0B])) # Slots + + rom.write_bytes(0x01D845, bytearray([0x5C, 0x76, 0xF9, 0x0B])) # Check setting for disabled autoscrolls + + rom.write_bytes(0x0BDAC2, bytearray([0x80, 0x0E])) # Prevent extra and bonus stages from auto-unlocking at 100 points + rom.write_bytes(0x0BA720, bytearray([0xA9, 0x00, 0x00])) # Always read level scores as 0. This stops extras and bonus from trying to unlock + + rom.write_bytes(0x0BA720, bytearray([0xA9, 0x00, 0x00])) # Always read level scores as 0. This stops extras and bonus from trying to unlock + + rom.write_bytes(0x03FE85, bytearray([0x5C, 0x09, 0xFB, 0x0B])) # Decrement the key counter when unlocking the 6-4 cork + + rom.write_bytes(0x06F1B4, bytearray([0x5C, 0x22, 0xFB, 0x0B])) # Mark the goal and bowser clear after defeating bowser + + rom.write_bytes(0x005FE2, bytearray([0x5C, 0x9C, 0xFB, 0x0B])) # Flag red coins as checked if the last one came from a pole + + rom.write_bytes(0x01C2E1, bytearray([0x80])) # Makes hidden clouds not flash + rom.write_bytes(0x0120C0, bytearray([0x80])) # Prevents bandit game doors from sealing + + rom.write_bytes(0x0382A7, bytearray([0x5C, 0xC2, 0xFB, 0x0B])) # Make cactus eggplants check the eggplant item correctly + + rom.write_bytes(0x025E71, bytearray([0x5C, 0xFA, 0xFB, 0x0B])) # Write the stored reverse value + + rom.write_bytes(0x00B587, bytearray([0x5C, 0x24, 0xFC, 0x0B])) # Store the reverse value and zero it + + rom.write_bytes(0x0B9932, bytearray([0x5C, 0x96, 0xFA, 0x0B])) # Get 16 bit life count + + rom.write_bytes(0x00C288, bytearray([0x00])) + rom.write_bytes(0x00C28B, bytearray([0x80])) # Disable baby mario tutorial text + + rom.write_bytes(0x01141F, bytearray([0x80])) # Disable Middle Ring tutorial + + rom.write_bytes(0x073534, bytearray([0x80])) # Disable Flower tutorial + + rom.write_bytes(0x065B24, bytearray([0x5C, 0x45, 0xFC, 0x0B])) # Fix boss cutscenes + + rom.write_bytes(0x011507, bytearray([0x5C, 0x70, 0xFC, 0x0B])) # Fix Hookbill middle ring during boss shuffle + + rom.write_bytes(0x019E98, bytearray([0x5C, 0xB4, 0xFC, 0x0B])) # Flag red coins as checked if the last one was eaten + + rom.write_bytes(0x011AB6, bytearray([0x5C, 0xD7, 0xFC, 0x0B])) # Check egg refills for how many eggs to spawn + + rom.write_bytes(0x00DCA6, bytearray([0x5C, 0x00, 0xFD, 0x0B])) # Check egg refill pause use + + rom.write_bytes(0x0BE06B, bytearray([0x5C, 0x56, 0xFD, 0x0B])) # Get level from shuffled order + + rom.write_bytes(0x00C14B, bytearray([0xAE, 0x7C, 0x02, 0x8E, 0x1A, 0x02])) # Return to the original list when exiting a level + + rom.write_bytes(0x00BEA8, bytearray([0x5C, 0x3F, 0xFE, 0x0B])) # Save the original level when beating a shuffled one. + + rom.write_bytes(0x00E702, bytearray([0xAD, 0x7C, 0x02, 0x8D, 0x1A, 0x02, 0x80, 0x05])) # Save the original level when leaving through death + + rom.write_bytes(0x0BE72A, bytearray([0x7C])) # Load yoshi colors by slot number not level number + + rom.write_bytes(0x003346, bytearray([0x22, 0x54, 0xFE, 0x0B, 0xEA, 0xEA])) # Fix World 6 levels using weird tilesets + + rom.write_bytes(0x003A37, bytearray([0x22, 0x54, 0xFE, 0x0B, 0xEA, 0xEA])) # Fix World 6 levels using weird tilesets + + rom.write_bytes(0x0B87D5, bytearray([0x5C, 0x67, 0xFE, 0x0B])) + + rom.write_bytes(0x07081F, bytearray([0x80])) # Fix for weird falling chomps. Why does this even read the world number????? + + rom.write_bytes(0x0BC0B2, bytearray([0x5C, 0xD0, 0xED, 0x01])) # Load randomized yoshi colors on the world map + + rom.write_bytes(0x0BC6F7, bytearray([0x5C, 0x04, 0xEE, 0x01])) # Load selected yoshi color on the world map + + rom.write_bytes(0x0BC0AB, bytearray([0x80])) # Skip special color check for world 6; Levels handle this anyway + + +def write_lives(rom: LocalRom) -> None: + rom.write_bytes(0x05FA96, bytearray([0xC2, 0x20, 0xAF, 0x89, 0xFC, 0x0D, 0x8D, 0x79, 0x03, 0xE2, 0x20, 0x5C, 0x37, 0x99, 0x17])) + rom.write_bytes(0x05FABF, bytearray([0x48, 0xE2, 0x20, 0xAD, 0xCC, 0x00, 0xF0, 0x06, 0xCE, 0xCC, 0x00, 0xCE, 0xCC, 0x00, 0xC2, 0x20, 0x68, 0x22, 0x87, 0xBF, 0x03, 0x5C, 0x89, 0xFE, 0x07])) + + +def bonus_checks(rom: LocalRom) -> None: + rom.write_bytes(0x082156, bytearray([0x5C, 0x5F, 0xFA, 0x0B])) # Write bonus check + + +def bandit_checks(rom: LocalRom) -> None: + rom.write_bytes(0x08C9E4, bytearray([0x5C, 0xF3, 0xF9, 0x0B])) # Write Bandit Checks + + +def Handle_Locations(rom: LocalRom) -> None: + rom.write_bytes(0x05F3B8, bytearray([0xAD, 0x67, 0x14, 0xF0, 0x59, 0xDA, 0xC9, 0x1F])) + rom.write_bytes(0x05F3C0, bytearray([0xF0, 0x16, 0xC9, 0x20, 0xB0, 0x27, 0xAA, 0xBF, 0xAA, 0xFE, 0x06, 0xAA, 0xA9, 0x01, 0x9D, 0x40])) + rom.write_bytes(0x05F3D0, bytearray([0x14, 0xA9, 0x43, 0x8D, 0x53, 0x00, 0x80, 0x67, 0xAD, 0x5D, 0x14, 0xD0, 0x01, 0x1A, 0xC9, 0x06])) + rom.write_bytes(0x05F3E0, bytearray([0xF0, 0x04, 0x1A, 0x8D, 0x5D, 0x14, 0xA9, 0x03, 0x8D, 0x53, 0x00, 0x80, 0x52, 0xC9, 0x26, 0xB0])) + rom.write_bytes(0x05F3F0, bytearray([0x27, 0xA2, 0x00, 0xDF, 0xC9, 0xFE, 0x06, 0xF0, 0x03, 0xE8, 0x80, 0xF7, 0xBF, 0xCF, 0xFE, 0x06])) + rom.write_bytes(0x05F400, bytearray([0x8D, 0x4C, 0x00, 0xAD, 0x60, 0x14, 0x0C, 0x4C, 0x00, 0xAD, 0x4C, 0x00, 0x8D, 0x60, 0x14, 0xA9])) + rom.write_bytes(0x05F410, bytearray([0x97, 0x8D, 0x53, 0x00, 0x80, 0x29, 0x80, 0x70, 0xC9, 0x2D, 0xB0, 0x25, 0xA2, 0x00, 0xDF, 0xD5])) + rom.write_bytes(0x05F420, bytearray([0xFE, 0x06, 0xF0, 0x03, 0xE8, 0x80, 0xF7, 0xBF, 0xE3, 0xFE, 0x06, 0x8D, 0xCF, 0x00, 0xAD, 0x61])) + rom.write_bytes(0x05F430, bytearray([0x14, 0x0C, 0xCF, 0x00, 0xAD, 0xCF, 0x00, 0x8D, 0x61, 0x14, 0xA9, 0x95, 0x8D, 0x53, 0x00, 0x80])) + rom.write_bytes(0x05F440, bytearray([0x78, 0xC9, 0x34, 0xB0, 0x25, 0xA2, 0x00, 0xDF, 0xDC, 0xFE, 0x06, 0xF0, 0x03, 0xE8, 0x80, 0xF7])) + rom.write_bytes(0x05F450, bytearray([0xBF, 0xE3, 0xFE, 0x06, 0x8D, 0xCF, 0x00, 0xAD, 0x62, 0x14, 0x0C, 0xCF, 0x00, 0xAD, 0xCF, 0x00])) + rom.write_bytes(0x05F460, bytearray([0x8D, 0x62, 0x14, 0xA9, 0x95, 0x8D, 0x53, 0x00, 0x80, 0x4F, 0xC9, 0x3D, 0xB0, 0x1C, 0xA2, 0x00])) + rom.write_bytes(0x05F470, bytearray([0xDF, 0xEA, 0xFE, 0x06, 0xF0, 0x03, 0xE8, 0x80, 0xF7, 0xBF, 0xF6, 0xFE, 0x06, 0x22, 0xA6, 0x9C])) + rom.write_bytes(0x05F480, bytearray([0x10, 0xA9, 0x36, 0x8D, 0x53, 0x00, 0x80, 0x31, 0x80, 0x64, 0xC9, 0x41, 0xB0, 0x2D, 0xA2, 0x00])) + rom.write_bytes(0x05F490, bytearray([0xDF, 0xFF, 0xFE, 0x06, 0xF0, 0x03, 0xE8, 0x80, 0xF7, 0xA9, 0x00, 0xEB, 0xBF, 0x03, 0xFF, 0x06])) + rom.write_bytes(0x05F4A0, bytearray([0xAA, 0x18, 0xC2, 0x20, 0x6D, 0x79, 0x03, 0x8D, 0x79, 0x03, 0xE2, 0x20, 0xA9, 0x08, 0x22, 0xD2])) + rom.write_bytes(0x05F4B0, bytearray([0x85, 0x00, 0xCA, 0xE0, 0x00, 0xF0, 0x02, 0x80, 0xF5, 0x80, 0x51, 0xC9, 0x41, 0xF0, 0x1E, 0xC9])) + rom.write_bytes(0x05F4C0, bytearray([0x42, 0xF0, 0x2D, 0xC9, 0x43, 0xF0, 0x3A, 0xC2, 0x20, 0x5C, 0xFB, 0xB3, 0x21, 0x77, 0x14, 0xE2])) + rom.write_bytes(0x05F4D0, bytearray([0x20, 0xA9, 0x01, 0x8D, 0x7D, 0x02, 0xA9, 0x2E, 0x8D, 0x53, 0x00, 0x80, 0x2F, 0xA9, 0x01, 0x8D])) + rom.write_bytes(0x05F4E0, bytearray([0x68, 0x14, 0xC2, 0x20, 0xA9, 0x00, 0x04, 0x8D, 0x69, 0x14, 0xE2, 0x20, 0x80, 0x1E, 0x80, 0x22])) + rom.write_bytes(0x05F4F0, bytearray([0xC2, 0x20, 0xA9, 0x2C, 0x01, 0x8D, 0xCC, 0x0C, 0xE2, 0x20, 0xA9, 0xA0, 0x8D, 0x53, 0x00, 0x80])) + rom.write_bytes(0x05F500, bytearray([0x0B, 0xA9, 0x15, 0x8D, 0x53, 0x00, 0xA9, 0x05, 0x8F, 0xED, 0x61, 0x04, 0xFA, 0xA9, 0x00, 0x8D])) + rom.write_bytes(0x05F510, bytearray([0x67, 0x14, 0xA9, 0x10, 0x8D, 0x83, 0x0B, 0x5C, 0xDE, 0xC0, 0x01, 0xE2, 0x20, 0xAD, 0x7D, 0x02])) + rom.write_bytes(0x05F520, bytearray([0xF0, 0x25, 0xC2, 0x20, 0xAD, 0x7E, 0x02, 0xE2, 0x20, 0xF0, 0x12, 0xA9, 0x02, 0x8D, 0x00, 0x02])) + rom.write_bytes(0x05F530, bytearray([0xC2, 0x20, 0xAD, 0x7E, 0x02, 0x3A, 0x8D, 0x7E, 0x02, 0xE2, 0x20, 0x80, 0x0A, 0xA9, 0x0F, 0x8D])) + rom.write_bytes(0x05F540, bytearray([0x00, 0x02, 0xA9, 0x00, 0x8D, 0x7D, 0x02, 0xAD, 0x68, 0x14, 0xF0, 0x32, 0xC2, 0x20, 0xAD, 0x69])) + rom.write_bytes(0x05F550, bytearray([0x14, 0xF0, 0x1B, 0x3A, 0x8D, 0x69, 0x14, 0xE2, 0x20, 0x4C, 0x40, 0xFD, 0xE8, 0x1F, 0x70, 0xAD])) + rom.write_bytes(0x05F560, bytearray([0xD0, 0x00, 0xD0, 0x08, 0xEE, 0xD0, 0x00, 0xA9, 0x21, 0x8D, 0x53, 0x00, 0x80, 0x10, 0xE2, 0x20])) + rom.write_bytes(0x05F570, bytearray([0xA9, 0x22, 0x8D, 0x53, 0x00, 0xA9, 0x00, 0x8D, 0x68, 0x14, 0x8F, 0xE8, 0x1F, 0x70, 0x22, 0x59])) + rom.write_bytes(0x05F580, bytearray([0x82, 0x00, 0x5C, 0x8F, 0xC1, 0x01, 0xAC, 0xB4, 0x03, 0xC0, 0x14, 0x30, 0x20, 0x48, 0xDA, 0xE2])) + rom.write_bytes(0x05F590, bytearray([0x20, 0xAE, 0x1A, 0x02, 0xBD, 0x6D, 0x14, 0x8D, 0xD1, 0x00, 0xA9, 0x01, 0x0C, 0xD1, 0x00, 0xAD])) + rom.write_bytes(0x05F5A0, bytearray([0xD1, 0x00, 0x9D, 0x6D, 0x14, 0xC2, 0x20, 0xFA, 0x68, 0x5C, 0x6C, 0xB3, 0x03, 0x5C, 0x6D, 0xB3])) + rom.write_bytes(0x05F5B0, bytearray([0x03, 0xAE, 0x1A, 0x02, 0xBD, 0x6D, 0x14, 0x8D, 0xD1, 0x00, 0xA9, 0x08, 0x0C, 0xD1, 0x00, 0xAD])) + rom.write_bytes(0x05F5C0, bytearray([0xD1, 0x00, 0x9D, 0x6D, 0x14, 0xAE, 0x57, 0x0B, 0xE0, 0x0D, 0x5C, 0x85, 0xB5, 0x01, 0xA0, 0x05])) + rom.write_bytes(0x05F5D0, bytearray([0x8C, 0xB8, 0x03, 0x08, 0xE2, 0x20, 0xDA, 0x48, 0xAE, 0x1A, 0x02, 0xBD, 0x6D, 0x14, 0x8D, 0xD1])) + rom.write_bytes(0x05F5E0, bytearray([0x00, 0xA9, 0x02, 0x0C, 0xD1, 0x00, 0xAD, 0xD1, 0x00, 0x9D, 0x6D, 0x14, 0x68, 0xFA, 0xC2, 0x20])) + rom.write_bytes(0x05F5F0, bytearray([0x28, 0x5C, 0xCB, 0xB4, 0x0E, 0xC2, 0x20, 0xAD, 0xB6, 0x03, 0xC9, 0x2C, 0x01, 0x90, 0x18, 0xE2])) + rom.write_bytes(0x05F600, bytearray([0x20, 0xDA, 0xAE, 0x1A, 0x02, 0xBD, 0x6D, 0x14, 0x8D, 0xD1, 0x00, 0xA9, 0x04, 0x0C, 0xD1, 0x00])) + rom.write_bytes(0x05F610, bytearray([0xAD, 0xD1, 0x00, 0x9D, 0x6D, 0x14, 0xFA, 0x9C, 0x84, 0x0B, 0xE2, 0x20, 0xAD, 0x0F, 0x0D, 0x5C])) + rom.write_bytes(0x05F620, bytearray([0xE4, 0xC0, 0x01, 0xC2, 0x20, 0x48, 0xE2, 0x20, 0xA9, 0x1F, 0x8D, 0x18, 0x01, 0xDA, 0x5A, 0x8B])) + rom.write_bytes(0x05F630, bytearray([0x4C, 0xB2, 0xFA, 0xC2, 0x20, 0xC2, 0x10, 0xAA, 0xBF, 0x07, 0xFF, 0x06, 0xAA, 0xE2, 0x20, 0xA9])) + rom.write_bytes(0x05F640, bytearray([0x00, 0xEB, 0xA9, 0x7F, 0xA0, 0x40, 0x14, 0x54, 0x7E, 0x70, 0xE2, 0x10, 0xAB, 0x7A, 0xFA, 0xC2])) + rom.write_bytes(0x05F650, bytearray([0x20, 0x68, 0xE2, 0x20, 0x5C, 0x3C, 0x99, 0x17, 0xC2, 0x20, 0x48, 0xC2, 0x10, 0xDA, 0x5A, 0x8B])) + rom.write_bytes(0x05F660, bytearray([0xAD, 0x0E, 0x03, 0x29, 0x0F, 0x00, 0xAA, 0xBF, 0x07, 0xFF, 0x06, 0xA8, 0xE2, 0x20, 0xA9, 0x00])) + rom.write_bytes(0x05F670, bytearray([0xEB, 0xA9, 0x7F, 0xA2, 0x40, 0x14, 0x54, 0x70, 0x7E, 0xAB, 0x7A, 0xFA, 0xE2, 0x10, 0xC2, 0x20])) + rom.write_bytes(0x05F680, bytearray([0x68, 0xE2, 0x20, 0xAD, 0x3D, 0x09, 0x29, 0x20, 0x5C, 0x4F, 0xE1, 0x17, 0xE2, 0x20, 0xAD, 0xD2])) + rom.write_bytes(0x05F690, bytearray([0x00, 0xC2, 0x20, 0xD0, 0x09, 0xA9, 0xC1, 0xB1, 0x85, 0x10, 0x5C, 0xA4, 0xD0, 0x01, 0xA9, 0x00])) + rom.write_bytes(0x05F6A0, bytearray([0x00, 0x85, 0x10, 0x85, 0x12, 0x85, 0x14, 0x85, 0x16, 0x5C, 0xB3, 0xD0, 0x01, 0xE2, 0x20, 0xAD])) + rom.write_bytes(0x05F6B0, bytearray([0xD2, 0x00, 0xC2, 0x20, 0xD0, 0x09, 0xA9, 0x6F, 0x01, 0x05, 0x02, 0x5C, 0xBA, 0xBC, 0x01, 0x5C])) + rom.write_bytes(0x05F6C0, bytearray([0xBC, 0xBC, 0x01, 0xE2, 0x20, 0xAD, 0xD2, 0x00, 0xC2, 0x20, 0xD0, 0x0B, 0xBF, 0xED, 0xB7, 0x01])) + rom.write_bytes(0x05F6D0, bytearray([0x29, 0xFF, 0x00, 0x5C, 0x79, 0xD0, 0x01, 0xBF, 0x48, 0xD3, 0x22, 0x29, 0xFF, 0x00, 0xC9, 0x80])) + rom.write_bytes(0x05F6E0, bytearray([0x00, 0xF0, 0x04, 0x5C, 0x79, 0xD0, 0x01, 0xBF, 0x66, 0xD3, 0x22, 0xDA, 0xAA, 0xAD, 0xD3, 0x00])) + rom.write_bytes(0x05F6F0, bytearray([0x29, 0xFF, 0x00, 0xC9, 0x01, 0x00, 0xF0, 0x21, 0xC9, 0x02, 0x00, 0xF0, 0x38, 0xBD, 0x40, 0x14])) + rom.write_bytes(0x05F700, bytearray([0x29, 0xFF, 0x00, 0xF0, 0x0C, 0xFA, 0xBF, 0x87, 0xD3, 0x22, 0x29, 0xFF, 0x00, 0x5C, 0x79, 0xD0])) + rom.write_bytes(0x05F710, bytearray([0x01, 0xFA, 0xA9, 0x4E, 0x00, 0x5C, 0x79, 0xD0, 0x01, 0xBD, 0x4A, 0x14, 0x29, 0xFF, 0x00, 0xF0])) + rom.write_bytes(0x05F720, bytearray([0x0C, 0xFA, 0xBF, 0xA5, 0xD3, 0x22, 0x29, 0xFF, 0x00, 0x5C, 0x79, 0xD0, 0x01, 0xFA, 0xA9, 0x4E])) + rom.write_bytes(0x05F730, bytearray([0x00, 0x5C, 0x79, 0xD0, 0x01, 0xE0, 0x09, 0xD0, 0x05, 0xAD, 0x64, 0x14, 0x80, 0x03, 0xBD, 0x54])) + rom.write_bytes(0x05F740, bytearray([0x14, 0x29, 0xFF, 0x00, 0xF0, 0x0C, 0xFA, 0xBF, 0xC3, 0xD3, 0x22, 0x29, 0xFF, 0x00, 0x5C, 0x79])) + rom.write_bytes(0x05F750, bytearray([0xD0, 0x01, 0xFA, 0xA9, 0x4E, 0x00, 0x5C, 0x79, 0xD0, 0x01, 0xE2, 0x20, 0xAD, 0xD2, 0x00, 0xC2])) + rom.write_bytes(0x05F760, bytearray([0x20, 0xD0, 0x08, 0xBF, 0x5F, 0xB8, 0x01, 0x5C, 0x7E, 0xD0, 0x01, 0xAD, 0xD3, 0x00, 0x29, 0xFF])) + rom.write_bytes(0x05F770, bytearray([0x00, 0xC9, 0x01, 0x00, 0xF0, 0x3C, 0xC9, 0x02, 0x00, 0xF0, 0x4B, 0xBF, 0x5F, 0xB8, 0x01, 0x05])) + rom.write_bytes(0x05F780, bytearray([0x18, 0x99, 0xA1, 0xB1, 0xBF, 0xDD, 0xB8, 0x01, 0x05, 0x18, 0x99, 0xE1, 0xB1, 0xFA, 0xC8, 0xC8])) + rom.write_bytes(0x05F790, bytearray([0xE8, 0xE0, 0x1D, 0x90, 0x19, 0xEE, 0xD3, 0x00, 0xA0, 0x00, 0xA2, 0x00, 0xAD, 0xD3, 0x00, 0x29])) + rom.write_bytes(0x05F7A0, bytearray([0xFF, 0x00, 0xC9, 0x03, 0x00, 0xD0, 0x07, 0x9C, 0xD3, 0x00, 0x5C, 0x94, 0xD0, 0x01, 0x5C, 0x71])) + rom.write_bytes(0x05F7B0, bytearray([0xD0, 0x01, 0xBF, 0x5F, 0xB8, 0x01, 0x05, 0x18, 0x99, 0x21, 0xB2, 0xBF, 0xDD, 0xB8, 0x01, 0x05])) + rom.write_bytes(0x05F7C0, bytearray([0x18, 0x99, 0x61, 0xB2, 0x80, 0xC7, 0xBF, 0x5F, 0xB8, 0x01, 0x05, 0x18, 0x99, 0xA1, 0xB2, 0xBF])) + rom.write_bytes(0x05F7D0, bytearray([0xDD, 0xB8, 0x01, 0x05, 0x18, 0x99, 0xE1, 0xB2, 0x80, 0xB3, 0xE2, 0x20, 0xAD, 0xD2, 0x00, 0xC2])) + rom.write_bytes(0x05F7E0, bytearray([0x20, 0xD0, 0x0A, 0x64, 0x18, 0xAF, 0xB8, 0x03, 0x00, 0x5C, 0x80, 0xD1, 0x01, 0x5C, 0x02, 0xD2])) + rom.write_bytes(0x05F7F0, bytearray([0x01, 0xE2, 0x20, 0xAD, 0xD2, 0x00, 0xC2, 0x20, 0xD0, 0x08, 0x64, 0x18, 0xA0, 0x00, 0x5C, 0xE2])) + rom.write_bytes(0x05F800, bytearray([0xD0, 0x01, 0x5C, 0x02, 0xD2, 0x01, 0xAD, 0xD2, 0x00, 0x29, 0xFF, 0x00, 0xD0, 0x08, 0xBF, 0x35])) + rom.write_bytes(0x05F810, bytearray([0xB8, 0x01, 0x5C, 0xB8, 0xCF, 0x01, 0xBF, 0xE1, 0xD3, 0x22, 0x29, 0xFF, 0x00, 0xC9, 0x80, 0x00])) + rom.write_bytes(0x05F820, bytearray([0xF0, 0x2E, 0xC9, 0x81, 0x00, 0xF0, 0x47, 0x5C, 0xB8, 0xCF, 0x01, 0xAD, 0xD2, 0x00, 0x29, 0xFF])) + rom.write_bytes(0x05F830, bytearray([0x00, 0xD0, 0x08, 0xBF, 0x4A, 0xB8, 0x01, 0x5C, 0xD4, 0xCF, 0x01, 0xBF, 0xF6, 0xD3, 0x22, 0x29])) + rom.write_bytes(0x05F840, bytearray([0xFF, 0x00, 0x4C, 0xB6, 0xFD, 0xF0, 0x18, 0xC9, 0x81, 0x00, 0xF0, 0x30, 0x5C, 0xD4, 0xCF, 0x01])) + rom.write_bytes(0x05F850, bytearray([0xDA, 0xE2, 0x20, 0xAD, 0xB3, 0x14, 0xAA, 0xC2, 0x20, 0x20, 0x8A, 0xF8, 0xFA, 0x80, 0xC8, 0xDA])) + rom.write_bytes(0x05F860, bytearray([0xE2, 0x20, 0xAD, 0xB3, 0x14, 0xAA, 0xC2, 0x20, 0x20, 0xBD, 0xF8, 0xFA, 0x80, 0xDE, 0xDA, 0xE2])) + rom.write_bytes(0x05F870, bytearray([0x20, 0xAF, 0x85, 0xFC, 0x0D, 0xAA, 0x20, 0x8A, 0xF8, 0xFA, 0x80, 0xAB, 0xDA, 0xE2, 0x20, 0xAF])) + rom.write_bytes(0x05F880, bytearray([0x86, 0xFC, 0x0D, 0xAA, 0x20, 0xBD, 0xF8, 0xFA, 0x80, 0xC2, 0xE2, 0x20, 0xC9, 0x0A, 0xB0, 0x1F])) + rom.write_bytes(0x05F890, bytearray([0xAD, 0xD5, 0x00, 0xD0, 0x0D, 0xBF, 0x0B, 0xD4, 0x22, 0xC2, 0x20, 0xA9, 0x50, 0x00, 0xEE, 0xD5])) + rom.write_bytes(0x05F8A0, bytearray([0x00, 0x60, 0xBF, 0x0B, 0xD4, 0x22, 0x9C, 0xD4, 0x00, 0x9C, 0xD5, 0x00, 0xC2, 0x20, 0x60, 0xAD])) + rom.write_bytes(0x05F8B0, bytearray([0xD4, 0x00, 0xD0, 0xEE, 0xEE, 0xD4, 0x00, 0xC2, 0x20, 0xA9, 0x52, 0x00, 0x60, 0xE2, 0x20, 0xC9])) + rom.write_bytes(0x05F8C0, bytearray([0x0A, 0xB0, 0x1F, 0xAD, 0xD6, 0x00, 0xD0, 0x0D, 0xBF, 0x0B, 0xD4, 0x22, 0xC2, 0x20, 0xA9, 0x50])) + rom.write_bytes(0x05F8D0, bytearray([0x00, 0xEE, 0xD6, 0x00, 0x60, 0xBF, 0x0B, 0xD4, 0x22, 0x9C, 0xD7, 0x00, 0x9C, 0xD6, 0x00, 0xC2])) + rom.write_bytes(0x05F8E0, bytearray([0x20, 0x60, 0xAD, 0xD7, 0x00, 0xD0, 0xEE, 0xEE, 0xD7, 0x00, 0xC2, 0x20, 0xA9, 0x52, 0x00, 0x60])) + rom.write_bytes(0x05F8F0, bytearray([0xAD, 0xD2, 0x00, 0x29, 0xFF, 0x00, 0xF0, 0x04, 0x5C, 0x74, 0xD2, 0x01, 0x64, 0x18, 0xA0, 0x00])) + rom.write_bytes(0x05F900, bytearray([0x5C, 0x07, 0xD2, 0x01, 0xAD, 0xD2, 0x00, 0x29, 0xFF, 0x00, 0xF0, 0x04, 0x5C, 0x74, 0xD2, 0x01])) + rom.write_bytes(0x05F910, bytearray([0xAF, 0x7C, 0x02, 0x00, 0x5C, 0x7B, 0xD2, 0x01, 0xA5, 0x38, 0x89, 0x20, 0xD0, 0x0A, 0x29, 0x10])) + rom.write_bytes(0x05F920, bytearray([0xF0, 0x02, 0xA9, 0x01, 0x5C, 0x08, 0xC1, 0x01, 0xEE, 0xD2, 0x00, 0x64, 0x38, 0x5C, 0x08, 0xC1])) + rom.write_bytes(0x05F930, bytearray([0x01, 0xAD, 0xD2, 0x00, 0xD0, 0x08, 0xA5, 0x38, 0x29, 0x20, 0x5C, 0x3B, 0xC1, 0x01, 0xA9, 0x00])) + rom.write_bytes(0x05F940, bytearray([0x80, 0xF8, 0xAD, 0x10, 0x0B, 0x49, 0x01, 0x9C, 0xD2, 0x00, 0x5C, 0x4D, 0xCE, 0x01, 0x9C, 0x01])) + rom.write_bytes(0x05F950, bytearray([0x02, 0xAD, 0x5E, 0x02, 0xF0, 0x16, 0xAD, 0xB0, 0x14, 0x89, 0x08, 0xD0, 0x15, 0xAD, 0xB3, 0x14])) + rom.write_bytes(0x05F960, bytearray([0xCF, 0x85, 0xFC, 0x0D, 0x90, 0x06, 0xA9, 0x80, 0x8F, 0x65, 0x02, 0x7E, 0xC2, 0x20, 0x5C, 0xBB])) + rom.write_bytes(0x05F970, bytearray([0xA5, 0x17, 0xA9, 0x01, 0x80, 0xF2, 0xE2, 0x20, 0xAF, 0x87, 0xFC, 0x0D, 0xC2, 0x20, 0xF0, 0x0D])) + rom.write_bytes(0x05F980, bytearray([0x4C, 0xBF, 0xFA, 0x8D, 0x1C, 0x0C, 0x8D, 0x1E, 0x0C, 0x5C, 0x4E, 0xD8, 0x03, 0xB9, 0x04, 0x0C])) + rom.write_bytes(0x05F990, bytearray([0x80, 0xF1, 0xE2, 0x20, 0xA9, 0x01, 0x8D, 0xD8, 0x00, 0xC2, 0x20, 0x22, 0xBE, 0xAE, 0x03, 0x5C])) + rom.write_bytes(0x05F9A0, bytearray([0xA2, 0xA0, 0x02, 0xE2, 0x20, 0xAD, 0xD8, 0x00, 0xD0, 0x0F, 0xC2, 0x20, 0xA9, 0x02, 0x00, 0x9D])) + rom.write_bytes(0x05F9B0, bytearray([0x96, 0x7A, 0xFE, 0x78, 0x79, 0x5C, 0xAF, 0xA3, 0x02, 0xAD, 0xB3, 0x14, 0xCF, 0x86, 0xFC, 0x0D])) + rom.write_bytes(0x05F9C0, bytearray([0xC2, 0x20, 0xB0, 0xE8, 0xC2, 0x20, 0x5C, 0x81, 0xA3, 0x02, 0xE2, 0x20, 0xDA, 0xAE, 0x1A, 0x02])) + rom.write_bytes(0x05F9D0, bytearray([0xBD, 0x6D, 0x14, 0x89, 0x20, 0xF0, 0x0D, 0xFA, 0xC2, 0x20, 0xAD, 0x02, 0x74, 0xC9, 0x32, 0x00])) + rom.write_bytes(0x05F9E0, bytearray([0x5C, 0x80, 0xDF, 0x02, 0x18, 0x69, 0x20, 0x9D, 0x6D, 0x14, 0xAD, 0xB3, 0x14, 0x1A, 0x8D, 0xB3])) + rom.write_bytes(0x05F9F0, bytearray([0x14, 0x80, 0xE4, 0xE2, 0x20, 0xDA, 0xAE, 0x1A, 0x02, 0xBD, 0x6D, 0x14, 0x8D, 0xD1, 0x00, 0xA9])) + rom.write_bytes(0x05FA00, bytearray([0x10, 0x0C, 0xD1, 0x00, 0xAD, 0xD1, 0x00, 0x9D, 0x6D, 0x14, 0xFA, 0xC2, 0x20, 0xA9, 0x36, 0x00])) + rom.write_bytes(0x05FA10, bytearray([0x22, 0xD2, 0x85, 0x00, 0x5C, 0xEB, 0xC9, 0x11, 0xB9, 0xE4, 0xB9, 0xC0, 0x00, 0xF0, 0x03, 0xEE])) + rom.write_bytes(0x05FA20, bytearray([0xD9, 0x00, 0x5C, 0xBB, 0xB9, 0x10, 0xA9, 0x06, 0x85, 0x4D, 0xEE, 0xD9, 0x00, 0x5C, 0x19, 0xB0])) + rom.write_bytes(0x05FA30, bytearray([0x10, 0xA9, 0x05, 0x00, 0x85, 0x4D, 0xEE, 0xD9, 0x00, 0x5C, 0x9A, 0xD0, 0x10, 0xA9, 0x06, 0x85])) + rom.write_bytes(0x05FA40, bytearray([0x4D, 0xEE, 0xD9, 0x00, 0x5C, 0xC9, 0xD2, 0x10, 0xA9, 0x05, 0x85, 0x4D, 0xEE, 0xD9, 0x00, 0x5C])) + rom.write_bytes(0x05FA50, bytearray([0xEE, 0xC5, 0x10, 0xA9, 0x05, 0x00, 0x85, 0x4D, 0xEE, 0xD9, 0x00, 0x5C, 0x0F, 0xBE, 0x10, 0xDA])) + rom.write_bytes(0x05FA60, bytearray([0xE2, 0x20, 0xAD, 0xD9, 0x00, 0xF0, 0x26, 0xA2, 0x00, 0xAD, 0x1A, 0x02, 0xDF, 0x18, 0xD4, 0x22])) + rom.write_bytes(0x05FA70, bytearray([0xF0, 0x07, 0xE8, 0xE0, 0x06, 0xF0, 0x16, 0x80, 0xF3, 0xAE, 0x1A, 0x02, 0xBD, 0x6D, 0x14, 0x8D])) + rom.write_bytes(0x05FA80, bytearray([0xD1, 0x00, 0xA9, 0x10, 0x0C, 0xD1, 0x00, 0xAD, 0xD1, 0x00, 0x9D, 0x6D, 0x14, 0xFA, 0x22, 0x67])) + rom.write_bytes(0x05FA90, bytearray([0xFA, 0x04, 0x5C, 0x5A, 0xA1, 0x10])) + + rom.write_bytes(0x05FAB2, bytearray([0xA9, 0x00, 0xEB, 0xAD, 0x0E, 0x03, 0xC2, 0x20, 0xC2, 0x10, 0x4C, 0x37, 0xF6])) + rom.write_bytes(0x05FABF, bytearray([0xE2])) + rom.write_bytes(0x05FAC0, bytearray([0x20, 0xAD, 0x1A, 0x02, 0xDA, 0xA2, 0x00, 0x00, 0xDF, 0x1E, 0xD4, 0x22, 0xF0, 0x11, 0xE8, 0xE0])) + rom.write_bytes(0x05FAD0, bytearray([0x01, 0x00, 0xF0, 0x02, 0x80, 0xF2, 0xFA, 0xC2, 0x20, 0xA9, 0x00, 0x00, 0x4C, 0x83, 0xF9, 0xFA])) + rom.write_bytes(0x05FAE0, bytearray([0xC2, 0x20, 0x4C, 0x8D, 0xF9])) + rom.write_bytes(0x05FAE5, bytearray([0x48, 0xE2, 0x20, 0xAD, 0x5D, 0x14, 0xC9, 0x01, 0xF0, 0x07])) + rom.write_bytes(0x05FAEF, bytearray([0xC2, 0x20, 0x68, 0x5C, 0xCE, 0xBE, 0x03, 0xAD, 0xCC, 0x00, 0xD0, 0xF4, 0xAF, 0xFA, 0x1D, 0x70])) + rom.write_bytes(0x05FAFF, bytearray([0xF0, 0xEE, 0xA9, 0x00, 0x8F, 0xFA, 0x1D, 0x70, 0x80, 0xE6])) + rom.write_bytes(0x05FB09, bytearray([0x48, 0xE2, 0x20, 0xAD, 0xCC, 0x00, 0xF0, 0x06, 0xCE, 0xCC, 0x00, 0xCE, 0xCC, 0x00, 0xC2, 0x20, 0x68, 0x22, 0x87, 0xBF, 0x03, 0x5C, 0x89, 0xFE, 0x07])) + rom.write_bytes(0x05FB22, bytearray([0xA0, 0x0A, 0x8C, 0x4D, 0x00, 0xE2, 0x20, 0xA9, 0x08, 0x0C, 0xB0, 0x14, 0x8D, 0xB6, 0x14, 0xC2, 0x20, 0x5C, 0xB9, 0xF1, 0x0D, 0x0D, 0xA8, 0xE2])) + rom.write_bytes(0x05FB3A, bytearray([0x20, 0xA9, 0x08, 0x0C, 0xB0, 0x14, 0xA9, 0x00, 0xEB, 0xA9, 0x7F, 0xA2, 0x40, 0x14, 0x54, 0x70, 0x7E, 0xAB, 0x7A, 0xFA, 0x1A, 0xEE, 0x14, 0xC2, 0x20, 0x68, 0x5C, 0xB9, 0xF1, 0x0D])) + rom.write_bytes(0x05FB58, bytearray([0x4C, 0xDD, 0xFB, 0x04, 0xAF, 0xAC, 0x00, 0x70])) + rom.write_bytes(0x05FB60, bytearray([0xD0, 0x2C, 0xAD, 0x35, 0x00, 0xC9, 0x50, 0xD0, 0x25, 0xAD, 0xDA, 0x00, 0xC9, 0x80, 0xF0, 0x11])) + rom.write_bytes(0x05FB70, bytearray([0xC9, 0x00, 0xF0, 0x21, 0xC9, 0x2A, 0xF0, 0x1D, 0xC9, 0x54, 0xF0, 0x19, 0xEE, 0xDA, 0x00, 0x80])) + rom.write_bytes(0x05FB80, bytearray([0x10, 0xA9, 0x2F, 0x8D, 0x53, 0x00, 0xA9, 0x11, 0x8D, 0x18, 0x01, 0xEE, 0xDB, 0x00, 0x9C, 0xDA])) + rom.write_bytes(0x05FB90, bytearray([0x00, 0x5C, 0x93, 0xC1, 0x01, 0xA9, 0x28, 0x8D, 0x53, 0x00, 0x80, 0xE0])) + rom.write_bytes(0x05FB9C, bytearray([0xA9, 0x93, 0x00, 0xEE])) + rom.write_bytes(0x05FBA0, bytearray([0xB4, 0x03, 0xAC, 0xB4, 0x03, 0xC0, 0x14, 0x00, 0x90, 0x14, 0xE2, 0x20, 0xDA, 0xAE, 0x1A, 0x02])) + rom.write_bytes(0x05FBB0, bytearray([0xBD, 0x6D, 0x14, 0x09, 0x01, 0x9D, 0x6D, 0x14, 0xFA, 0xC2, 0x20, 0xA9, 0x94, 0x00, 0x5C, 0xF1, 0xDF, 0x00])) + rom.write_bytes(0x05FBC2, bytearray([0x48, 0xC9, 0x06, 0x00, 0xB0, 0x10, 0xE2, 0x20, 0xAD, 0x54, 0x14, 0xC9, 0x00, 0xC2, 0x20, 0xF0])) + rom.write_bytes(0x05FBD2, bytearray([0x05, 0x68, 0x5C, 0xAC, 0x82, 0x07, 0x68, 0x5C, 0xFB, 0x81, 0x07, 0xAD, 0x6A, 0x02, 0xF0, 0x11])) + rom.write_bytes(0x05FBE2, bytearray([0xC2, 0x20, 0xA9, 0x0E, 0x00, 0x22, 0xE2, 0xF6, 0x04, 0xA9, 0x00, 0x00, 0x8D, 0x6A, 0x02, 0xE2])) + rom.write_bytes(0x05FBF2, bytearray([0x20, 0x22, 0x28, 0xFD, 0x04, 0x4C, 0x5C, 0xFB, 0xAF, 0xB0, 0x23, 0x7E, 0xF0, 0x18, 0xAF, 0xAC])) + rom.write_bytes(0x05FC02, bytearray([0x00, 0x70, 0x29, 0xFF, 0x00, 0xD0, 0x0F, 0xAF, 0xB0, 0x23, 0x7E, 0x8F, 0xEC, 0x61, 0x04, 0xA9])) + rom.write_bytes(0x05FC12, bytearray([0x00, 0x00, 0x8F, 0xB0, 0x23, 0x7E, 0xBD, 0xD0, 0x61, 0xF0, 0x03, 0xDE, 0xD0, 0x61, 0x5C, 0x79])) + rom.write_bytes(0x05FC22, bytearray([0xDE, 0x04, 0x48, 0xC2, 0x20, 0xAF, 0xEC, 0x61, 0x04, 0xD0, 0x0B, 0xE2, 0x20, 0x68, 0x22, 0xCE])) + rom.write_bytes(0x05FC32, bytearray([0xC0, 0x01, 0x5C, 0x8B, 0xB5, 0x01, 0x8F, 0xB0, 0x23, 0x7E, 0xA9, 0x00, 0x00, 0x8F, 0xEC, 0x61])) + rom.write_bytes(0x05FC42, bytearray([0x04, 0x80, 0xE8, 0x48, 0xDA, 0xE2, 0x20, 0x4C, 0xA5, 0xFC, 0xA2, 0x00, 0xDF, 0x1F, 0xD4, 0x22])) + rom.write_bytes(0x05FC52, bytearray([0xF0, 0x03, 0xE8, 0x80, 0xF7, 0xBF, 0x8D, 0xFC, 0x0D, 0xAA, 0xBF, 0x2A, 0xD4, 0x22, 0x8D, 0xDC])) + rom.write_bytes(0x05FC62, bytearray([0x00, 0xC2, 0x20, 0xFA, 0x68, 0x0D, 0xDC, 0x00, 0x95, 0x76, 0x5C, 0x29, 0xDB, 0x0C, 0xE2, 0x20])) + rom.write_bytes(0x05FC72, bytearray([0xAD, 0x48, 0x0B, 0xF0, 0x23, 0xAF, 0xBE, 0x03, 0x02, 0xC9, 0x02, 0xD0, 0x1B, 0xAD, 0x1A, 0x02])) + rom.write_bytes(0x05FC82, bytearray([0xA2, 0x00, 0xDF, 0x1F, 0xD4, 0x22, 0xF0, 0x03, 0xE8, 0x80, 0xF7, 0x8A, 0x0A, 0xAA, 0xC2, 0x20])) + rom.write_bytes(0x05FC92, bytearray([0xBF, 0x35, 0xD4, 0x22, 0x8F, 0xBE, 0x03, 0x02, 0xC2, 0x20, 0xEE, 0xAC, 0x03, 0xC2, 0x10, 0x5C])) + rom.write_bytes(0x05FCA2, bytearray([0x0C, 0x95, 0x02, 0xAD, 0x1A, 0x02, 0xC9, 0x43, 0xF0, 0x03, 0x4C, 0x4C, 0xFC, 0xA9, 0x0A, 0x4C])) + rom.write_bytes(0x05FCB2, bytearray([0x60, 0xFC, 0xAC, 0xB4, 0x03, 0xC0, 0x14, 0x30, 0x14, 0x1A, 0xE2, 0x20, 0xDA, 0x48, 0xAE, 0x1A])) + rom.write_bytes(0x05FCC2, bytearray([0x02, 0xBD, 0x6D, 0x14, 0x09, 0x01, 0x9D, 0x6D, 0x14, 0x68, 0xFA, 0xC2, 0x20, 0x22, 0xD2, 0x85])) + rom.write_bytes(0x05FCD2, bytearray([0x00, 0x5C, 0xA4, 0x9E, 0x03, 0xE2, 0x20, 0xAD, 0xF6, 0x7D, 0xC9, 0x0C, 0xB0, 0x1A, 0xAD, 0xCC])) + rom.write_bytes(0x05FCE2, bytearray([0x00, 0xAD, 0x5E, 0x14, 0x38, 0xED, 0xCC, 0x00, 0x3A, 0x3A, 0x8D, 0xDE, 0x00, 0xAD, 0xF6, 0x7D])) + rom.write_bytes(0x05FCF2, bytearray([0x38, 0xED, 0xCC, 0x00, 0x18, 0xCD, 0xDE, 0x00, 0xC2, 0x20, 0x5C, 0xBC, 0x9A, 0x02, 0xE2, 0x20])) + rom.write_bytes(0x05FD02, bytearray([0xAD, 0x5D, 0x14, 0xF0, 0x33, 0xAA, 0xBF, 0xA3, 0xAF, 0x09, 0x18, 0x6D, 0xCC, 0x00, 0x8D, 0x5E])) + rom.write_bytes(0x05FD12, bytearray([0x14, 0xAD, 0xF6, 0x7D, 0xC9, 0x0C, 0xB0, 0x1A, 0xAD, 0xCC, 0x00, 0xAD, 0x5E, 0x14, 0x38, 0xED])) + rom.write_bytes(0x05FD22, bytearray([0xCC, 0x00, 0x3A, 0x3A, 0x8D, 0xDE, 0x00, 0xAD, 0xF6, 0x7D, 0x38, 0xED, 0xCC, 0x00, 0x18, 0xCD])) + rom.write_bytes(0x05FD32, bytearray([0xDE, 0x00, 0xC2, 0x20, 0x5C, 0xAC, 0xDC, 0x01, 0x1A, 0x8D, 0x5D, 0x14, 0x80, 0xC0, 0xA9, 0x00])) + rom.write_bytes(0x05FD42, bytearray([0x8F, 0xE8, 0x1F, 0x70, 0xAD, 0xAC, 0x60, 0xC9, 0x00, 0xD0, 0x06, 0xA9, 0x01, 0x8F, 0xE8, 0x1F])) + rom.write_bytes(0x05FD52, bytearray([0x70, 0x4C, 0x5F, 0xF5, 0xDA, 0xAD, 0x1A, 0x02, 0x8D, 0x7C, 0x02, 0xAD, 0x12, 0x11, 0xC9, 0x08])) + rom.write_bytes(0x05FD62, bytearray([0xB0, 0x1D, 0xAD, 0x18, 0x02, 0x4A, 0xAA, 0xA9, 0x00, 0xE0, 0x00, 0xF0, 0x06, 0x18, 0x69, 0x08])) + rom.write_bytes(0x05FD72, bytearray([0xCA, 0x80, 0xF6, 0x18, 0x6D, 0x12, 0x11, 0xAA, 0xBF, 0x4B, 0xD4, 0x22, 0x8D, 0x1A, 0x02, 0xFA])) + rom.write_bytes(0x05FD82, bytearray([0xA9, 0x02, 0x8D, 0x13, 0x11, 0x5C, 0x70, 0xE0, 0x17, 0xAC, 0x7C, 0x02, 0x8C, 0x1A, 0x02, 0xB9])) + rom.write_bytes(0x05FD92, bytearray([0x22, 0x02, 0x5C, 0xA7, 0x82, 0x10, 0xAD, 0x7C, 0x02, 0x8D, 0x1A, 0x02, 0xC2, 0x20, 0xE2, 0x10])) + rom.write_bytes(0x05FDA2, bytearray([0x5C, 0xBC, 0xB2, 0x01, 0xC9, 0x45, 0xB0, 0x03, 0x8D, 0x7C, 0x02, 0x8D, 0x1A, 0x02, 0x29, 0x07])) + rom.write_bytes(0x05FDB2, bytearray([0x5C, 0x3A, 0x81, 0x10, 0xC9, 0x82, 0x00, 0xF0, 0x2E, 0xC9, 0x83, 0x00, 0xF0, 0x40, 0xC9, 0x84])) + rom.write_bytes(0x05FDC2, bytearray([0x00, 0xF0, 0x0B, 0xC9, 0x85, 0x00, 0xF0, 0x0E, 0xC9, 0x80, 0x00, 0x4C, 0x45, 0xF8, 0xE2, 0x20])) + rom.write_bytes(0x05FDD2, bytearray([0xAF, 0x99, 0xFC, 0x0D, 0x80, 0x16, 0xDA, 0xE2, 0x20, 0xAD, 0xE3, 0x00, 0xD0, 0x4E, 0x9C, 0xE1])) + rom.write_bytes(0x05FDE2, bytearray([0x00, 0xAF, 0x99, 0xFC, 0x0D, 0x80, 0x25, 0xE2, 0x20, 0xAD, 0xB5, 0x14, 0xC9, 0x64, 0xC2, 0x20])) + rom.write_bytes(0x05FDF2, bytearray([0xB0, 0x06, 0xA9, 0x4E, 0x00, 0x4C, 0x4C, 0xF8, 0xA9, 0x52, 0x00, 0x4C, 0x4C, 0xF8, 0xDA, 0xE2])) + rom.write_bytes(0x05FE02, bytearray([0x20, 0xAD, 0xE3, 0x00, 0xD0, 0x26, 0x9C, 0xE1, 0x00, 0xAD, 0xB5, 0x14, 0xC9, 0x0A, 0x90, 0x08])) + rom.write_bytes(0x05FE12, bytearray([0x38, 0xE9, 0x0A, 0xEE, 0xE1, 0x00, 0x80, 0xF4, 0x8D, 0xE2, 0x00, 0xEE, 0xE3, 0x00, 0xAD, 0xE1])) + rom.write_bytes(0x05FE22, bytearray([0x00, 0xAA, 0xBF, 0x0B, 0xD4, 0x22, 0xC2, 0x20, 0xFA, 0x4C, 0x46, 0xF8, 0x9C, 0xE3, 0x00, 0xAD])) + rom.write_bytes(0x05FE32, bytearray([0xE2, 0x00, 0xAA, 0xBF, 0x0B, 0xD4, 0x22, 0xC2, 0x20, 0xFA, 0x4C, 0x4C, 0xF8, 0x22, 0xB7, 0xB2])) + rom.write_bytes(0x05FE42, bytearray([0x01, 0xEA, 0xEA, 0xEA, 0xAE, 0x7C, 0x02, 0x8E, 0x1A, 0x02, 0xEA, 0xEA, 0xEA, 0xEA, 0x5C, 0xAC])) + rom.write_bytes(0x05FE52, bytearray([0xBE, 0x01, 0xE2, 0x20, 0xAD, 0x1A, 0x02, 0xc9, 0x3C, 0xC2, 0x20, 0xB0, 0x04, 0xA9, 0x02, 0x00])) + rom.write_bytes(0x05FE62, bytearray([0x6B, 0xA9, 0x00, 0x00, 0x6B, 0xAD, 0x18, 0x01, 0xC9, 0x19, 0xD0, 0x3A, 0xC2, 0x20, 0x48, 0xA9])) + rom.write_bytes(0x05FE72, bytearray([0x00, 0x00, 0xE2, 0x20, 0xAF, 0x9A, 0xFC, 0x0D, 0xD0, 0x05, 0xA9, 0x08, 0x0C, 0xB0, 0x14, 0xC2])) + rom.write_bytes(0x05FE82, bytearray([0x10, 0xDA, 0x5A, 0x8B, 0xAD, 0x0E, 0x03, 0xC2, 0x20, 0xAA, 0xBF, 0x07, 0xFF, 0x06, 0xA8, 0xE2])) + rom.write_bytes(0x05FE92, bytearray([0x20, 0xA9, 0x00, 0xEB, 0xA9, 0x7F, 0xA2, 0x40, 0x14, 0x54, 0x70, 0x7E, 0xAB, 0x7A, 0xFA, 0xC2])) + rom.write_bytes(0x05FEA2, bytearray([0x20, 0x68, 0xE2, 0x20, 0xE2, 0x10, 0x22, 0x4B, 0x82, 0x00, 0x5C, 0xD9, 0x87, 0x17])) + + rom.write_bytes(0x00EDD0, bytearray([0xda, 0xa2, 0x00, 0x00, 0xe2, 0x20, 0xc9, 0x00, 0xf0, 0x0b, 0x48, 0x8a, 0x18, 0x69, 0x0c, 0xaa])) + rom.write_bytes(0x00EDE0, bytearray([0x68, 0x3a, 0x3a, 0x80, 0xf1, 0x98, 0x4a, 0x8f, 0x80, 0x24, 0x7e, 0x18, 0x8a, 0x6f, 0x80, 0x24])) + rom.write_bytes(0x00EDF0, bytearray([0x7e, 0xaa, 0xbf, 0x00, 0x80, 0x02, 0x0a, 0xaa, 0xc2, 0x20, 0xbf, 0x8e, 0xd4, 0x22, 0xfa, 0x18])) + rom.write_bytes(0x00EE00, bytearray([0x5c, 0xb6, 0xc0, 0x17, 0xda, 0xe2, 0x20, 0xa2, 0x00, 0x00, 0xdf, 0x4b, 0xd4, 0x22, 0xf0, 0x0e])) + rom.write_bytes(0x00EE10, bytearray([0xe8, 0xe0, 0x30, 0x00, 0xb0, 0x02, 0x80, 0xf2, 0xa9, 0x00, 0xeb, 0xad, 0x1a, 0x02, 0xaa, 0xbf])) + rom.write_bytes(0x00EE20, bytearray([0x00, 0x80, 0x02, 0xaa, 0xbf, 0x9e, 0xd4, 0x22, 0xc2, 0x20, 0x29, 0xff, 0x00, 0xfa, 0x5c, 0xfd])) + rom.write_bytes(0x00EE30, bytearray([0xc6, 0x17])) + + +def ExtendedItemHandler(rom: LocalRom) -> None: + rom.write_bytes(0x10B3FB, bytearray([0xE2, 0x20, 0xC9, 0x45, 0xB0])) + rom.write_bytes(0x10B400, bytearray([0x0C, 0xC2, 0x20, 0xA9, 0x10, 0x03, 0x8D, 0x7E, 0x02, 0x5C, 0xCF, 0xF4, 0x0B, 0xAD, 0x0F, 0x0B])) + rom.write_bytes(0x10B410, bytearray([0xD0, 0x38, 0xEE, 0xB5, 0x14, 0xA9, 0x18, 0x8D, 0x53, 0x00, 0xAF, 0x9A, 0xFC, 0x0D, 0xF0, 0x09])) + rom.write_bytes(0x10B420, bytearray([0xAD, 0xB5, 0x14, 0xCF, 0x99, 0xFC, 0x0D, 0xB0, 0x04, 0x5C, 0x0A, 0xF5, 0x0B, 0xAD, 0xB6, 0x14])) + rom.write_bytes(0x10B430, bytearray([0xD0, 0xF7, 0xA9, 0x01, 0x8D, 0xB6, 0x14, 0xA9, 0x0A, 0x8D, 0x18, 0x02, 0xA9, 0x16, 0x8D, 0x18])) + rom.write_bytes(0x10B440, bytearray([0x01, 0xA9, 0x97, 0x8D, 0x53, 0x00, 0x5C, 0x0A, 0xF5, 0x0B, 0xFA, 0x5C, 0x10, 0xF5, 0x0B])) + + +def patch_rom(world: "YoshisIslandWorld", rom: LocalRom, player: int) -> None: + handle_items(rom) # Implement main item functionality + Item_Data(rom) # Pointers necessary for item functionality + write_lives(rom) # Writes the number of lives as set in AP + CodeHandler(rom) # Jumps to my code + Server_Data(rom) # Pointers mostly related to receiving items + Menu_Data(rom) # Data related to the AP menu + Handle_Locations(rom) + ExtendedItemHandler(rom) + rom.write_bytes(0x11544B, bytearray(world.global_level_list)) + rom.write_bytes(0x11547A, bytearray([0x43])) + + rom.write_bytes(0x06FC89, world.starting_lives) + rom.write_bytes(0x03464F, ([world.baby_mario_sfx])) + rom.write_bytes(0x06FC83, ([world.options.starting_world.value])) + rom.write_bytes(0x06FC84, ([world.options.hidden_object_visibility.value])) + rom.write_bytes(0x06FC88, ([world.options.shuffle_midrings.value])) + rom.write_bytes(0x06FC85, ([world.options.castle_open_condition.value])) + rom.write_bytes(0x06FC86, ([world.options.castle_clear_condition.value])) + rom.write_bytes(0x06FC87, ([world.options.disable_autoscroll.value])) + rom.write_bytes(0x06FC8B, ([world.options.minigame_checks.value])) + rom.write_byte(0x06FC8C, world.options.death_link.value) + rom.write_bytes(0x06FC8D, bytearray(world.boss_room_id)) + rom.write_bytes(0x06FC99, bytearray([world.options.luigi_pieces_required.value])) + rom.write_bytes(0x06FC9A, bytearray([world.options.goal.value])) + + if world.options.yoshi_colors != YoshiColors.option_normal: + rom.write_bytes(0x113A33, bytearray(world.bowser_text)) + + rom.write_bytes(0x0A060C, bytearray(world.boss_burt_data)) + rom.write_bytes(0x0A8666, bytearray(world.boss_slime_data)) + rom.write_bytes(0x0A9D90, bytearray(world.boss_boo_data)) + rom.write_bytes(0x0074EA, bytearray(world.boss_pot_data)) + rom.write_bytes(0x08DC0A, bytearray(world.boss_frog_data)) + rom.write_bytes(0x0A4440, bytearray(world.boss_plant_data)) + rom.write_bytes(0x0968A2, bytearray(world.boss_milde_data)) + rom.write_bytes(0x0B3E10, bytearray(world.boss_koop_data)) + rom.write_bytes(0x0B4BD0, bytearray(world.boss_slug_data)) + rom.write_bytes(0x0B6BBA, bytearray(world.boss_raph_data)) + rom.write_bytes(0x087BED, bytearray(world.boss_tap_data)) + + rom.write_bytes(0x07A00D, ([world.tap_tap_room])) + rom.write_bytes(0x079DF2, ([world.tap_tap_room])) + rom.write_bytes(0x079CCF, ([world.tap_tap_room])) + rom.write_bytes(0x079C4D, ([world.tap_tap_room])) + + rom.write_bytes(0x045A2E, bytearray(world.Stage11StageGFX)) + rom.write_bytes(0x045A31, bytearray(world.Stage12StageGFX)) + rom.write_bytes(0x045A34, bytearray(world.Stage13StageGFX)) + rom.write_bytes(0x045A37, bytearray(world.Stage14StageGFX)) + rom.write_bytes(0x045A3A, bytearray(world.Stage15StageGFX)) + rom.write_bytes(0x045A3D, bytearray(world.Stage16StageGFX)) + rom.write_bytes(0x045A40, bytearray(world.Stage17StageGFX)) + rom.write_bytes(0x045A43, bytearray(world.Stage18StageGFX)) + + rom.write_bytes(0x045A52, bytearray(world.Stage21StageGFX)) + rom.write_bytes(0x045A55, bytearray(world.Stage22StageGFX)) + rom.write_bytes(0x045A58, bytearray(world.Stage23StageGFX)) + rom.write_bytes(0x045A5B, bytearray(world.Stage24StageGFX)) + rom.write_bytes(0x045A5E, bytearray(world.Stage25StageGFX)) + rom.write_bytes(0x045A61, bytearray(world.Stage26StageGFX)) + rom.write_bytes(0x045A64, bytearray(world.Stage27StageGFX)) + rom.write_bytes(0x045A67, bytearray(world.Stage28StageGFX)) + + rom.write_bytes(0x045A76, bytearray(world.Stage31StageGFX)) + rom.write_bytes(0x045A79, bytearray(world.Stage32StageGFX)) + rom.write_bytes(0x045A7C, bytearray(world.Stage33StageGFX)) + rom.write_bytes(0x045A7F, bytearray(world.Stage34StageGFX)) + rom.write_bytes(0x045A82, bytearray(world.Stage35StageGFX)) + rom.write_bytes(0x045A85, bytearray(world.Stage36StageGFX)) + rom.write_bytes(0x045A88, bytearray(world.Stage37StageGFX)) + rom.write_bytes(0x045A8B, bytearray(world.Stage38StageGFX)) + + rom.write_bytes(0x045A9A, bytearray(world.Stage41StageGFX)) + rom.write_bytes(0x045A9D, bytearray(world.Stage42StageGFX)) + rom.write_bytes(0x045AA0, bytearray(world.Stage43StageGFX)) + rom.write_bytes(0x045AA3, bytearray(world.Stage44StageGFX)) + rom.write_bytes(0x045AA6, bytearray(world.Stage45StageGFX)) + rom.write_bytes(0x045AA9, bytearray(world.Stage46StageGFX)) + rom.write_bytes(0x045AAC, bytearray(world.Stage47StageGFX)) + rom.write_bytes(0x045AAF, bytearray(world.Stage48StageGFX)) + + rom.write_bytes(0x045ABE, bytearray(world.Stage51StageGFX)) + rom.write_bytes(0x045AC1, bytearray(world.Stage52StageGFX)) + rom.write_bytes(0x045AC4, bytearray(world.Stage53StageGFX)) + rom.write_bytes(0x045AC7, bytearray(world.Stage54StageGFX)) + rom.write_bytes(0x045ACA, bytearray(world.Stage55StageGFX)) + rom.write_bytes(0x045ACD, bytearray(world.Stage56StageGFX)) + rom.write_bytes(0x045AD0, bytearray(world.Stage57StageGFX)) + rom.write_bytes(0x045AD3, bytearray(world.Stage58StageGFX)) + + rom.write_bytes(0x045AE2, bytearray(world.Stage61StageGFX)) + rom.write_bytes(0x045AE5, bytearray(world.Stage62StageGFX)) + rom.write_bytes(0x045AE8, bytearray(world.Stage63StageGFX)) + rom.write_bytes(0x045AEB, bytearray(world.Stage64StageGFX)) + rom.write_bytes(0x045AEE, bytearray(world.Stage65StageGFX)) + rom.write_bytes(0x045AF1, bytearray(world.Stage66StageGFX)) + rom.write_bytes(0x045AF4, bytearray(world.Stage67StageGFX)) + + rom.write_bytes(0x0BDBAF, bytearray(world.level_gfx_table)) + rom.write_bytes(0x0BDC4F, bytearray(world.palette_panel_list)) + + if world.options.yoshi_colors == YoshiColors.option_random_order: + rom.write_bytes(0x010000, ([world.leader_color])) + rom.write_bytes(0x010008, ([world.leader_color])) + rom.write_bytes(0x010009, ([world.leader_color])) + rom.write_bytes(0x010001, bytearray(world.color_order)) + rom.write_bytes(0x01000C, ([world.leader_color])) + rom.write_bytes(0x010014, ([world.leader_color])) + rom.write_bytes(0x010015, ([world.leader_color])) + rom.write_bytes(0x01000D, bytearray(world.color_order)) + rom.write_bytes(0x010018, ([world.leader_color])) + rom.write_bytes(0x010020, ([world.leader_color])) + rom.write_bytes(0x010021, ([world.leader_color])) + rom.write_bytes(0x01001A, bytearray(world.color_order)) + rom.write_bytes(0x010024, ([world.leader_color])) + rom.write_bytes(0x01002C, ([world.leader_color])) + rom.write_bytes(0x01002D, ([world.leader_color])) + rom.write_bytes(0x010025, bytearray(world.color_order)) + rom.write_bytes(0x010030, ([world.leader_color])) + rom.write_bytes(0x010038, ([world.leader_color])) + rom.write_bytes(0x010039, ([world.leader_color])) + rom.write_bytes(0x010031, bytearray(world.color_order)) + rom.write_bytes(0x01003C, ([world.leader_color])) + rom.write_bytes(0x010044, ([world.leader_color])) + rom.write_bytes(0x010045, ([world.leader_color])) + rom.write_bytes(0x01003D, bytearray(world.color_order)) + rom.write_bytes(0x010043, ([world.leader_color])) + elif world.options.yoshi_colors in {YoshiColors.option_random_color, YoshiColors.option_singularity}: + rom.write_bytes(0x010000, bytearray(world.level_colors)) + + if world.options.minigame_checks in {MinigameChecks.option_bonus_games, MinigameChecks.option_both}: + bonus_checks(rom) + + if world.options.minigame_checks in {MinigameChecks.option_bandit_games, MinigameChecks.option_both}: + bandit_checks(rom) + + rom.write_bytes(0x00BF2C, bytearray(world.world_bonus)) + + if world.options.softlock_prevention: + rom.write_bytes(0x00C18F, bytearray([0x5C, 0x58, 0xFB, 0x0B])) # R + X Code + + if world.options.bowser_door_mode != BowserDoor.option_manual: + rom.write_bytes(0x07891F, bytearray(world.castle_door)) # 1 Entry + rom.write_bytes(0x078923, bytearray(world.castle_door)) # 2 Entry + rom.write_bytes(0x078927, bytearray(world.castle_door)) # 3 Entry + rom.write_bytes(0x07892B, bytearray(world.castle_door)) # 4 Entry + + if world.options.bowser_door_mode == BowserDoor.option_gauntlet: + rom.write_bytes(0x0AF517, bytearray([0xC6, 0x07, 0x7A, 0x00])) # Door 2 + rom.write_bytes(0x0AF6B7, bytearray([0xCD, 0x05, 0x5B, 0x00])) # Door 3 + rom.write_bytes(0x0AF8F2, bytearray([0xD3, 0x00, 0x77, 0x06])) # Door 4 + + if world.options.goal == PlayerGoal.option_luigi_hunt: + rom.write_bytes(0x1153F6, bytearray([0x16, 0x28, 0x10, 0x0C, 0x10, 0x4E, 0x1E, 0x10, 0x08, 0x04, 0x08, 0x24, 0x36, 0x82, 0x83, 0x83, 0x34, 0x84, 0x85, 0x85])) # Luigi piece clear text + rom.write_bytes(0x06FC86, bytearray([0xFF])) # Boss clear goal = 255, renders bowser inaccessible + + from Main import __version__ + rom.name = bytearray(f'YOSHIAP{__version__.replace(".", "")[0:3]}_{player}_{world.multiworld.seed:11}\0', "utf8")[:21] + rom.name.extend([0] * (21 - len(rom.name))) + rom.write_bytes(0x007FC0, rom.name) + + +class YoshisIslandDeltaPatch(APDeltaPatch): + hash = USHASH + game: str = "Yoshi's Island" + patch_file_ending = ".apyi" + + @classmethod + def get_source_data(cls) -> bytes: + return get_base_rom_bytes() + + +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(Utils.read_snes_rom(open(file_name, "rb"))) + + basemd5 = hashlib.md5() + basemd5.update(base_rom_bytes) + if USHASH != basemd5.hexdigest(): + raise Exception("Supplied Base Rom does not match known MD5 for US(1.0) 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: + if not file_name: + file_name = get_settings()["yoshisisland_options"]["rom_file"] + if not os.path.exists(file_name): + file_name = Utils.user_path(file_name) + return file_name diff --git a/worlds/yoshisisland/Rules.py b/worlds/yoshisisland/Rules.py new file mode 100644 index 000000000000..09f6eaced07c --- /dev/null +++ b/worlds/yoshisisland/Rules.py @@ -0,0 +1,612 @@ +from .level_logic import YoshiLogic +from worlds.generic.Rules import set_rule +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from . import YoshisIslandWorld + + +def set_easy_rules(world: "YoshisIslandWorld") -> None: + logic = YoshiLogic(world) + player = world.player + + set_rule(world.multiworld.get_location("Make Eggs, Throw Eggs: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Beanstalk"}, player)) + set_rule(world.multiworld.get_location("Make Eggs, Throw Eggs: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Beanstalk"}, player)) + set_rule(world.multiworld.get_location("Make Eggs, Throw Eggs: Stars", player), lambda state: state.has_all({"Tulip", "Beanstalk", "Dashed Stairs"}, player)) + set_rule(world.multiworld.get_location("Make Eggs, Throw Eggs: Level Clear", player), lambda state: state.has("Beanstalk", player)) + + set_rule(world.multiworld.get_location("Watch Out Below!: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Watch Out Below!: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Watch Out Below!: Stars", player), lambda state: state.has("Large Spring Ball", player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Watch Out Below!: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("The Cave Of Chomp Rock: Red Coins", player), lambda state: state.has("Chomp Rock", player)) + set_rule(world.multiworld.get_location("The Cave Of Chomp Rock: Flowers", player), lambda state: state.has("Chomp Rock", player)) + + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Red Coins", player), lambda state: state.has("Spring Ball", player)) + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Flowers", player), lambda state: state.has_all({"Spring Ball", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Stars", player), lambda state: state.has("Spring Ball", player) and (logic.has_midring(state) or state.has("Key", player))) + + set_rule(world.multiworld.get_location("Hop! Hop! Donut Lifts: Stars", player), lambda state: logic.has_midring(state) or logic.cansee_clouds(state)) + + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "Flashing Eggs", "Mole Tank Morph", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Stars", player), lambda state: (logic.has_midring(state) and state.has("Tulip", player) or logic.has_midring(state) and state.has("Beanstalk", player)) and state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "Beanstalk"}, player)) + + set_rule(world.multiworld.get_location("Touch Fuzzy Get Dizzy: Red Coins", player), lambda state: state.has_all({"Flashing Eggs", "Spring Ball", "Chomp Rock", "Beanstalk"}, player)) + set_rule(world.multiworld.get_location("Touch Fuzzy Get Dizzy: Stars", player), lambda state: logic.has_midring(state) or (logic.cansee_clouds and state.has_all({"Spring Ball", "Chomp Rock", "Beanstalk"}, player))) + + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Red Coins", player), lambda state: state.has("Platform Ghost", player)) + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Flowers", player), lambda state: state.has("Platform Ghost", player)) + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Stars", player), lambda state: logic.has_midring(state) and (state.has("Platform Ghost", player) or state.has_all({"Arrow Wheel", "Key"}, player))) + + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Red Coins", player), lambda state: state.has_all({"Poochy", "Large Spring Ball", "Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Flowers", player), lambda state: state.has_all({"Super Star", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Stars", player), lambda state: state.has("Large Spring Ball", player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("The Baseball Boys: Red Coins", player), lambda state: state.has_all({"Beanstalk", "Super Star", "Egg Launcher", "Large Spring Ball", "Mole Tank Morph"}, player)) + set_rule(world.multiworld.get_location("The Baseball Boys: Flowers", player), lambda state: state.has_all({"Beanstalk", "Super Star", "Egg Launcher", "Large Spring Ball", "Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Baseball Boys: Stars", player), lambda state: (logic.has_midring(state) and (state.has("Tulip", player))) and state.has_all({"Beanstalk", "Super Star", "Large Spring Ball", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Baseball Boys: Level Clear", player), lambda state: state.has_all({"Beanstalk", "Super Star", "Egg Launcher", "Large Spring Ball"}, player)) + + set_rule(world.multiworld.get_location("What's Gusty Taste Like?: Red Coins", player), lambda state: state.has("! Switch", player)) + set_rule(world.multiworld.get_location("What's Gusty Taste Like?: Flowers", player), lambda state: state.has_any({"Large Spring Ball", "Super Star"}, player)) + set_rule(world.multiworld.get_location("What's Gusty Taste Like?: Level Clear", player), lambda state: state.has_any({"Large Spring Ball", "Super Star"}, player)) + + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Red Coins", player), lambda state: state.has_all({"! Switch", "Key", "Dashed Stairs"}, player)) + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Flowers", player), lambda state: state.has_all({"! Switch", "Key", "Dashed Stairs"}, player)) + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Stars", player), lambda state: state.has_all({"! Switch", "Key", "Dashed Stairs"}, player) and logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Watch Out For Lakitu: Red Coins", player), lambda state: state.has("Chomp Rock", player)) + set_rule(world.multiworld.get_location("Watch Out For Lakitu: Flowers", player), lambda state: state.has_all({"Key", "Train Morph", "Chomp Rock"}, player)) + set_rule(world.multiworld.get_location("Watch Out For Lakitu: Level Clear", player), lambda state: state.has("Chomp Rock", player)) + + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Stars", player), lambda state: state.has("Large Spring Ball", player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Lakitu's Wall: Red Coins", player), lambda state: (state.has_any({"Dashed Platform", "Giant Eggs"}, player) or logic.combat_item(state)) and state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Lakitu's Wall: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player) and (logic.combat_item(state) or state.has("Giant Eggs", player))) + set_rule(world.multiworld.get_location("Lakitu's Wall: Stars", player), lambda state: state.has("Giant Eggs", player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Lakitu's Wall: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "Car Morph"}, player)) + + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Red Coins", player), lambda state: state.has_all({"Arrow Wheel", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 1))) + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Flowers", player), lambda state: state.has_all({"Arrow Wheel", "Train Morph", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 1))) + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Stars", player), lambda state: state.has_all({"Arrow Wheel", "Key"}, player) and logic.has_midring(state) and (state.has("Egg Capacity Upgrade", player, 1))) + + set_rule(world.multiworld.get_location("Welcome To Monkey World!: Stars", player), lambda state: logic.has_midring(state)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Stars", player), lambda state: logic.has_midring(state) and state.has("Tulip", player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Level Clear", player), lambda state: state.has_all({"Dashed Stairs", "Spring Ball"}, player)) + + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Red Coins", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Flowers", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Stars", player), lambda state: logic.has_midring(state) or state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Level Clear", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Red Coins", player), lambda state: state.has("Submarine Morph", player)) + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Flowers", player), lambda state: (state.has("Egg Capacity Upgrade", player, 5) or logic.combat_item(state)) and (state.has("Dashed Platform", player))) + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Jammin' Through The Trees: Flowers", player), lambda state: state.has("Watermelon", player) or logic.melon_item(state)) + set_rule(world.multiworld.get_location("Jammin' Through The Trees: Stars", player), lambda state: ((logic.has_midring(state) or state.has("Tulip", player)) and logic.cansee_clouds(state)) or logic.has_midring(state) and state.has("Tulip", player)) + + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Red Coins", player), lambda state: state.has_all({"Chomp Rock", "Beanstalk", "Mole Tank Morph", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Flowers", player), lambda state: state.has_all({"Chomp Rock", "Beanstalk", "Mole Tank Morph", "Large Spring Ball", "! Switch"}, player)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Stars", player), lambda state: state.has_all({"Tulip", "Large Spring Ball", "Dashed Stairs", "Chomp Rock", "Beanstalk", "Mole Tank Morph"}, player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Level Clear", player), lambda state: state.has_all({"Chomp Rock", "Large Spring Ball", "Key"}, player)) + + set_rule(world.multiworld.get_location("Monkeys' Favorite Lake: Red Coins", player), lambda state: state.has_all({"! Switch", "Submarine Morph", "Large Spring Ball", "Beanstalk"}, player)) + set_rule(world.multiworld.get_location("Monkeys' Favorite Lake: Flowers", player), lambda state: state.has_all({"Beanstalk", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Monkeys' Favorite Lake: Stars", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Monkeys' Favorite Lake: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Naval Piranha's Castle: Red Coins", player), lambda state: (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Naval Piranha's Castle: Flowers", player), lambda state: (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Naval Piranha's Castle: Stars", player), lambda state: logic.has_midring(state) and state.has("Tulip", player)) + + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Red Coins", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Flowers", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Stars", player), lambda state: logic.has_midring(state) or (state.has("Tulip", player) and logic.cansee_clouds(state))) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Level Clear", player), lambda state: state.has("Super Star", player)) + + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "! Switch", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Stars", player), lambda state: state.has_all({"Large Spring Ball", "Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Don't Look Back!: Red Coins", player), lambda state: state.has_all({"Helicopter Morph", "! Switch", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Flowers", player), lambda state: state.has_all({"! Switch", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Stars", player), lambda state: (logic.has_midring(state) and state.has("Tulip", player)) and state.has("! Switch", player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Level Clear", player), lambda state: state.has("! Switch", player)) + + set_rule(world.multiworld.get_location("Marching Milde's Fort: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Marching Milde's Fort: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket"}, player) and (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Marching Milde's Fort: Stars", player), lambda state: state.has("Dashed Stairs", player) and (logic.has_midring(state) or state.has("Vanishing Arrow Wheel", player) or logic.cansee_clouds(state))) + + set_rule(world.multiworld.get_location("Chomp Rock Zone: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "Chomp Rock"}, player)) + set_rule(world.multiworld.get_location("Chomp Rock Zone: Flowers", player), lambda state: state.has_all({"Chomp Rock", "! Switch", "Spring Ball", "Dashed Platform"}, player)) + set_rule(world.multiworld.get_location("Chomp Rock Zone: Stars", player), lambda state: state.has_all({"Chomp Rock", "! Switch", "Spring Ball", "Dashed Platform"}, player)) + + set_rule(world.multiworld.get_location("Lake Shore Paradise: Red Coins", player), lambda state: state.has_any({"Large Spring Ball", "Spring Ball"}, player) and (state.has("Egg Plant", player) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Lake Shore Paradise: Flowers", player), lambda state: state.has_any({"Large Spring Ball", "Spring Ball"}, player) and (state.has("Egg Plant", player) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Lake Shore Paradise: Stars", player), lambda state: (logic.has_midring(state) or (state.has("Tulip", player) and logic.cansee_clouds(state))) and (state.has("Egg Plant", player) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Lake Shore Paradise: Level Clear", player), lambda state: state.has("Egg Plant", player) or logic.combat_item(state)) + + set_rule(world.multiworld.get_location("Ride Like The Wind: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Stars", player), lambda state: (logic.has_midring(state) and state.has("Helicopter Morph", player)) and state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Key"}, player)) + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Key"}, player)) + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Stars", player), lambda state: logic.has_midring(state) and (state.has_any({"Dashed Stairs", "Vanishing Arrow Wheel"}, player))) + + set_rule(world.multiworld.get_location("BLIZZARD!!!: Red Coins", player), lambda state: state.has_all({"Helicopter Morph", "Dashed Stairs"}, player)) + set_rule(world.multiworld.get_location("BLIZZARD!!!: Stars", player), lambda state: logic.cansee_clouds(state) or ((logic.has_midring(state) and state.has("Dashed Stairs", player)) or state.has("Tulip", player))) + + set_rule(world.multiworld.get_location("Ride The Ski Lifts: Stars", player), lambda state: logic.has_midring(state) or state.has("Super Star", player)) + + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Red Coins", player), lambda state: (state.has("Fire Melon", player) or logic.melon_item(state)) and (state.has_all({"Bucket", "Spring Ball", "Super Star", "Skis", "Dashed Platform"}, player))) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Flowers", player), lambda state: (state.has("Fire Melon", player) or logic.melon_item(state)) and state.has_all({"Spring Ball", "Skis", "Dashed Platform"}, player)) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Stars", player), lambda state: (logic.has_midring(state) and (state.has("Fire Melon", player) or logic.melon_item(state))) and state.has("Spring Ball", player)) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Level Clear", player), lambda state: state.has_all({"Spring Ball", "Skis", "Dashed Platform"}, player)) + + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Dashed Platform", "Platform Ghost"}, player)) + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Platform Ghost", "Dashed Platform"}, player)) + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Stars", player), lambda state: ((state.has_all({"Dashed Stairs", "Platform Ghost"}, player)) and logic.has_midring(state)) or (logic.cansee_clouds(state) and state.has("Dashed Stairs", player) and state.has("Dashed Platform", player))) + + set_rule(world.multiworld.get_location("Goonie Rides!: Red Coins", player), lambda state: state.has_all({"Helicopter Morph", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Goonie Rides!: Flowers", player), lambda state: state.has_all({"Helicopter Morph", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Goonie Rides!: Stars", player), lambda state: logic.has_midring(state)) + set_rule(world.multiworld.get_location("Goonie Rides!: Level Clear", player), lambda state: state.has_all({"Helicopter Morph", "! Switch"}, player)) + + set_rule(world.multiworld.get_location("Welcome To Cloud World: Stars", player), lambda state: logic.has_midring(state) or state.has("Tulip", player)) + + set_rule(world.multiworld.get_location("Shifting Platforms Ahead: Red Coins", player), lambda state: state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state)) + set_rule(world.multiworld.get_location("Shifting Platforms Ahead: Flowers", player), lambda state: state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state)) + set_rule(world.multiworld.get_location("Shifting Platforms Ahead: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Red Coins", player), lambda state: state.has_all({"Arrow Wheel", "Train Morph"}, player)) + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Flowers", player), lambda state: state.has_all({"Arrow Wheel", "Train Morph"}, player)) + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Stars", player), lambda state: logic.has_midring(state) and state.has("Arrow Wheel", player)) + + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Red Coins", player), lambda state: state.has_all({"Dashed Platform", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Flowers", player), lambda state: state.has_all({"Dashed Platform", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Stars", player), lambda state: state.has("Dashed Platform", player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Level Clear", player), lambda state: state.has_all({"Dashed Platform", "Large Spring Ball"}, player)) + + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Red Coins", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Flowers", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Stars", player), lambda state: logic.cansee_clouds(state) or logic.has_midring(state)) + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Level Clear", player), lambda state: state.has("Super Star", player)) + + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Red Coins", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Egg Plant", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Flowers", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Egg Plant", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Stars", player), lambda state: logic.has_midring(state) and state.has_all({"Spring Ball", "Large Spring Ball", "Egg Plant", "Key"}, player)) + + set_rule(world.multiworld.get_location("The Very Loooooong Cave: Red Coins", player), lambda state: state.has("Chomp Rock", player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("The Very Loooooong Cave: Flowers", player), lambda state: state.has("Chomp Rock", player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("The Very Loooooong Cave: Stars", player), lambda state: state.has("Chomp Rock", player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state)) and logic.has_midring(state)) + + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Red Coins", player), lambda state: state.has_all({"Chomp Rock", "Key", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Flowers", player), lambda state: state.has_all({"Chomp Rock", "Key", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Stars", player), lambda state: state.has_all({"Chomp Rock", "Tulip", "Key"}, player) or (logic.has_midring(state) and state.has_all({"Key", "Chomp Rock", "Large Spring Ball"}, player))) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Level Clear", player), lambda state: state.has_all({"Chomp Rock", "Key", "Large Spring Ball", "Dashed Platform"}, player)) + + set_rule(world.multiworld.get_location("KEEP MOVING!!!!: Red Coins", player), lambda state: (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("KEEP MOVING!!!!: Flowers", player), lambda state: state.has("Egg Plant", player)) + set_rule(world.multiworld.get_location("KEEP MOVING!!!!: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("King Bowser's Castle: Red Coins", player), lambda state: state.has_all({"Helicopter Morph", "Egg Plant"}, player) and logic._68CollectibleRoute(state)) + set_rule(world.multiworld.get_location("King Bowser's Castle: Flowers", player), lambda state: state.has_all({"Helicopter Morph", "Egg Plant"}, player) and logic._68CollectibleRoute(state)) + set_rule(world.multiworld.get_location("King Bowser's Castle: Stars", player), lambda state: state.has_all({"Helicopter Morph", "Egg Plant"}, player) and logic._68Route(state)) + + set_easy_extra_rules(world) + + +def set_easy_extra_rules(world: "YoshisIslandWorld") -> None: + player = world.player + logic = YoshiLogic(world) + if not world.options.extras_enabled: + return + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Red Coins", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Flowers", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Stars", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Level Clear", player), lambda state: state.has("Poochy", player)) + + set_rule(world.multiworld.get_location("Hit That Switch!!: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Hit That Switch!!: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Hit That Switch!!: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + + set_rule(world.multiworld.get_location("The Impossible? Maze: Red Coins", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph", "Flashing Eggs"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Flowers", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Stars", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Level Clear", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Kamek's Revenge: Red Coins", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph", "! Switch"}, player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Flowers", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph", "! Switch"}, player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Stars", player), lambda state: state.has("! Switch", player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Level Clear", player), lambda state: state.has_all({"Key", "Skis", "! Switch", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Red Coins", player), lambda state: (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state)) and state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Flowers", player), lambda state: (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state)) and state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Stars", player), lambda state: logic.has_midring(state) and state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Level Clear", player), lambda state: (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state)) and state.has(("Large Spring Ball"), player)) + + +def set_normal_rules(world: "YoshisIslandWorld") -> None: + logic = YoshiLogic(world) + player = world.player + + set_rule(world.multiworld.get_location("Make Eggs, Throw Eggs: Red Coins", player), lambda state: state.has("Dashed Stairs", player)) + set_rule(world.multiworld.get_location("Make Eggs, Throw Eggs: Flowers", player), lambda state: state.has("Dashed Stairs", player)) + set_rule(world.multiworld.get_location("Make Eggs, Throw Eggs: Stars", player), lambda state: state.has_any({"Tulip", "Dashed Stairs"}, player)) + + set_rule(world.multiworld.get_location("Watch Out Below!: Red Coins", player), lambda state: state.has("Helicopter Morph", player)) + set_rule(world.multiworld.get_location("Watch Out Below!: Flowers", player), lambda state: state.has("Helicopter Morph", player)) + set_rule(world.multiworld.get_location("Watch Out Below!: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Red Coins", player), lambda state: state.has("Spring Ball", player)) + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Flowers", player), lambda state: state.has_all({"Spring Ball", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Stars", player), lambda state: state.has("Spring Ball", player)) + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Level Clear", player), lambda state: logic._14CanFightBoss(state)) + + + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "Flashing Eggs", "Mole Tank Morph", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Stars", player), lambda state: (logic.has_midring(state) and state.has_any(["Tulip", "Beanstalk"], player)) or (state.has_all(["Tulip", "Beanstalk", "Large Spring Ball"], player))) + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "Beanstalk"}, player)) + + set_rule(world.multiworld.get_location("Touch Fuzzy Get Dizzy: Red Coins", player), lambda state: state.has_all({"Flashing Eggs", "Spring Ball", "Chomp Rock", "Beanstalk"}, player)) + set_rule(world.multiworld.get_location("Touch Fuzzy Get Dizzy: Stars", player), lambda state: logic.has_midring(state) or (logic.cansee_clouds and state.has_all({"Spring Ball", "Chomp Rock", "Beanstalk"}, player))) + + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Red Coins", player), lambda state: state.has("Platform Ghost", player)) + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Flowers", player), lambda state: state.has("Platform Ghost", player)) + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Stars", player), lambda state: logic.has_midring(state) and (state.has("Platform Ghost", player) or state.has_all({"Arrow Wheel", "Key"}, player))) + + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Red Coins", player), lambda state: state.has_all({"Poochy", "Large Spring Ball", "Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Flowers", player), lambda state: state.has_all({"Super Star", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Stars", player), lambda state: state.has("Large Spring Ball", player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("The Baseball Boys: Red Coins", player), lambda state: state.has_all({"Beanstalk", "Super Star", "Large Spring Ball", "Mole Tank Morph"}, player)) + set_rule(world.multiworld.get_location("The Baseball Boys: Flowers", player), lambda state: state.has_all({"Super Star", "Large Spring Ball", "Beanstalk", "Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Baseball Boys: Stars", player), lambda state: (logic.has_midring(state) or (state.has("Tulip", player))) and state.has_all({"Beanstalk", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Baseball Boys: Level Clear", player), lambda state: state.has_all({"Beanstalk", "Super Star", "Large Spring Ball"}, player)) + + set_rule(world.multiworld.get_location("What's Gusty Taste Like?: Red Coins", player), lambda state: state.has("! Switch", player)) + + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Red Coins", player), lambda state: state.has_all({"! Switch", "Key", "Dashed Stairs"}, player)) + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Flowers", player), lambda state: state.has_all({"! Switch", "Key", "Dashed Stairs"}, player)) + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Stars", player), lambda state: state.has_all({"! Switch", "Dashed Stairs"}, player)) + + set_rule(world.multiworld.get_location("Watch Out For Lakitu: Flowers", player), lambda state: state.has_all({"Key", "Train Morph"}, player)) + + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Stars", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Lakitu's Wall: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player) and (logic.combat_item(state) or state.has("Giant Eggs", player))) + set_rule(world.multiworld.get_location("Lakitu's Wall: Stars", player), lambda state: state.has("Giant Eggs", player) or logic.has_midring(state)) + + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Red Coins", player), lambda state: state.has_all({"Arrow Wheel", "Key"}, player)) + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Flowers", player), lambda state: state.has_all({"Arrow Wheel", "Key", "Train Morph"}, player)) + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Stars", player), lambda state: state.has("Arrow Wheel", player) and (logic.has_midring(state) or state.has("Key", player))) + + set_rule(world.multiworld.get_location("Welcome To Monkey World!: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Jungle Rhythm...: Red Coins", player), lambda state: state.has("Dashed Stairs", player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Flowers", player), lambda state: state.has("Dashed Stairs", player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Stars", player), lambda state: logic.has_midring(state) and state.has("Tulip", player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Level Clear", player), lambda state: state.has("Dashed Stairs", player)) + + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Red Coins", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Flowers", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Level Clear", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Red Coins", player), lambda state: state.has("Submarine Morph", player)) + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Flowers", player), lambda state: (state.has("Egg Capacity Upgrade", player, 5) or logic.combat_item(state)) and (state.has("Dashed Platform", player) or logic.has_midring(state))) + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Jammin' Through The Trees: Flowers", player), lambda state: state.has("Watermelon", player) or logic.melon_item(state)) + + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Red Coins", player), lambda state: state.has_any({"Dashed Stairs", "Beanstalk"}, player) and state.has_all({"Mole Tank Morph", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Flowers", player), lambda state: state.has_any({"Dashed Stairs", "Beanstalk"}, player) and state.has_all({"! Switch", "Mole Tank Morph", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Stars", player), lambda state: (state.has_any({"Dashed Stairs", "Beanstalk"}, player) and state.has_all({"Mole Tank Morph", "Large Spring Ball"}, player)) and (logic.has_midring(state) or state.has("Tulip", player))) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "Key"}, player)) + + set_rule(world.multiworld.get_location("Monkeys' Favorite Lake: Red Coins", player), lambda state: state.has_all({"! Switch", "Submarine Morph", "Large Spring Ball", "Beanstalk"}, player)) + set_rule(world.multiworld.get_location("Monkeys' Favorite Lake: Flowers", player), lambda state: state.has("Beanstalk", player)) + + set_rule(world.multiworld.get_location("Naval Piranha's Castle: Red Coins", player), lambda state: (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Naval Piranha's Castle: Flowers", player), lambda state: (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Naval Piranha's Castle: Stars", player), lambda state: (logic.has_midring(state) and state.has("Tulip", player)) and state.has("Egg Capacity Upgrade", player, 1)) + + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Red Coins", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Flowers", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Stars", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Level Clear", player), lambda state: state.has("Super Star", player)) + + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "! Switch", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Stars", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Don't Look Back!: Red Coins", player), lambda state: state.has_all({"Helicopter Morph", "! Switch", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Stars", player), lambda state: (logic.has_midring(state) or state.has("Tulip", player)) and state.has("! Switch", player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Level Clear", player), lambda state: state.has("! Switch", player)) + + set_rule(world.multiworld.get_location("Marching Milde's Fort: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket", "Key"}, player)) + set_rule(world.multiworld.get_location("Marching Milde's Fort: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket"}, player)) + set_rule(world.multiworld.get_location("Marching Milde's Fort: Stars", player), lambda state: state.has("Dashed Stairs", player)) + + set_rule(world.multiworld.get_location("Chomp Rock Zone: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "Chomp Rock"}, player)) + set_rule(world.multiworld.get_location("Chomp Rock Zone: Flowers", player), lambda state: state.has_all({"Chomp Rock", "! Switch", "Dashed Platform"}, player)) + set_rule(world.multiworld.get_location("Chomp Rock Zone: Stars", player), lambda state: state.has_all({"Chomp Rock", "! Switch", "Dashed Platform"}, player)) + + set_rule(world.multiworld.get_location("Lake Shore Paradise: Red Coins", player), lambda state: state.has("Egg Plant", player) or logic.combat_item(state)) + set_rule(world.multiworld.get_location("Lake Shore Paradise: Flowers", player), lambda state: state.has("Egg Plant", player) or logic.combat_item(state)) + set_rule(world.multiworld.get_location("Lake Shore Paradise: Stars", player), lambda state: (logic.has_midring(state) or (state.has("Tulip", player) and logic.cansee_clouds(state))) and (state.has("Egg Plant", player) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Lake Shore Paradise: Level Clear", player), lambda state: state.has("Egg Plant", player) or logic.combat_item(state)) + + set_rule(world.multiworld.get_location("Ride Like The Wind: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Stars", player), lambda state: (logic.has_midring(state) or state.has("Helicopter Morph", player)) and state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Key"}, player)) + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Key"}, player)) + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Stars", player), lambda state: logic.has_midring(state) or (state.has_any({"Dashed Stairs", "Vanishing Arrow Wheel"}, player))) + + set_rule(world.multiworld.get_location("BLIZZARD!!!: Red Coins", player), lambda state: state.has_any({"Dashed Stairs", "Ice Melon"}, player) and (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state) or state.has("Helicopter Morph", player))) + + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Red Coins", player), lambda state: (state.has("Fire Melon", player) or logic.melon_item(state)) and (state.has_all({"Spring Ball", "Skis"}, player)) and (state.has("Super Star", player) or logic.melon_item(state))) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Flowers", player), lambda state: (state.has("Fire Melon", player) or logic.melon_item(state)) and state.has_all({"Spring Ball", "Skis"}, player)) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Stars", player), lambda state: (logic.has_midring(state) and (state.has("Fire Melon", player) or logic.melon_item(state))) or (logic.has_midring(state) and (state.has_all({"Tulip", "Dashed Platform"}, player)))) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Level Clear", player), lambda state: state.has_all({"Spring Ball", "Skis"}, player)) + + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Dashed Platform", "Platform Ghost"}, player)) + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Platform Ghost", "Dashed Platform"}, player)) + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Stars", player), lambda state: ((state.has_all({"Dashed Stairs", "Platform Ghost"}, player))) or (logic.cansee_clouds(state) and state.has("Dashed Stairs", player))) + + set_rule(world.multiworld.get_location("Goonie Rides!: Red Coins", player), lambda state: state.has("Helicopter Morph", player)) + set_rule(world.multiworld.get_location("Goonie Rides!: Flowers", player), lambda state: state.has_all({"Helicopter Morph", "! Switch"}, player)) + + set_rule(world.multiworld.get_location("Shifting Platforms Ahead: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Red Coins", player), lambda state: state.has_all({"Arrow Wheel", "Train Morph"}, player)) + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Flowers", player), lambda state: state.has_all({"Arrow Wheel", "Train Morph"}, player)) + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Stars", player), lambda state: logic.has_midring(state)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Red Coins", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Flowers", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Level Clear", player), lambda state: state.has("Super Star", player)) + + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "Egg Plant", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "Egg Plant", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Stars", player), lambda state: logic.has_midring(state) and state.has_all({"Spring Ball", "Egg Plant", "Key"}, player)) + + set_rule(world.multiworld.get_location("The Very Loooooong Cave: Red Coins", player), lambda state: state.has("Chomp Rock", player) and (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("The Very Loooooong Cave: Flowers", player), lambda state: state.has("Chomp Rock", player) and (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("The Very Loooooong Cave: Stars", player), lambda state: state.has("Chomp Rock", player) and (state.has("Egg Capacity Upgrade", player, 2) or logic.combat_item(state)) and logic.has_midring(state)) + + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Red Coins", player), lambda state: state.has_all({"Chomp Rock", "Key", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Flowers", player), lambda state: state.has_all({"Chomp Rock", "Key", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Stars", player), lambda state: state.has_all({"Chomp Rock", "Tulip", "Key"}, player) or (logic.has_midring(state) and state.has_all({"Key", "Chomp Rock", "Large Spring Ball"}, player))) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Level Clear", player), lambda state: state.has_all({"Key", "Large Spring Ball", "Dashed Platform"}, player) and (logic.combat_item(state) or state.has("Chomp Rock", player))) + + set_rule(world.multiworld.get_location("KEEP MOVING!!!!: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("King Bowser's Castle: Red Coins", player), lambda state: state.has_all({"Helicopter Morph", "Egg Plant"}, player) and logic._68CollectibleRoute(state)) + set_rule(world.multiworld.get_location("King Bowser's Castle: Flowers", player), lambda state: state.has_all({"Helicopter Morph", "Egg Plant"}, player) and logic._68CollectibleRoute(state)) + set_rule(world.multiworld.get_location("King Bowser's Castle: Stars", player), lambda state: state.has_all({"Helicopter Morph", "Egg Plant"}, player) and logic._68Route(state)) + + set_normal_extra_rules(world) + + +def set_normal_extra_rules(world: "YoshisIslandWorld") -> None: + player = world.player + logic = YoshiLogic(world) + if not world.options.extras_enabled: + return + + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Red Coins", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Flowers", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Stars", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Level Clear", player), lambda state: state.has("Poochy", player)) + + set_rule(world.multiworld.get_location("Hit That Switch!!: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Hit That Switch!!: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Hit That Switch!!: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + + set_rule(world.multiworld.get_location("The Impossible? Maze: Red Coins", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph", "Flashing Eggs"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Flowers", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Stars", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Level Clear", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Kamek's Revenge: Red Coins", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph", "! Switch"}, player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Flowers", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph", "! Switch"}, player) and logic.has_midring(state)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Stars", player), lambda state: state.has("! Switch", player) or logic.has_midring(state)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Level Clear", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Red Coins", player), lambda state: (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state)) and state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Flowers", player), lambda state: (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state)) and state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Stars", player), lambda state: logic.has_midring(state) or state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Level Clear", player), lambda state: (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state)) and state.has(("Large Spring Ball"), player)) + + +def set_hard_rules(world: "YoshisIslandWorld"): + logic = YoshiLogic(world) + player = world.player + + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Red Coins", player), lambda state: state.has("Spring Ball", player)) + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Flowers", player), lambda state: state.has_all({"Spring Ball", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 3) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Burt The Bashful's Fort: Stars", player), lambda state: state.has("Spring Ball", player)) + + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "Flashing Eggs", "Mole Tank Morph", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Shy-Guys On Stilts: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Touch Fuzzy Get Dizzy: Red Coins", player), lambda state: state.has_all({"Flashing Eggs", "Spring Ball", "Chomp Rock", "Beanstalk"}, player)) + + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Red Coins", player), lambda state: state.has("Platform Ghost", player)) + set_rule(world.multiworld.get_location("Salvo The Slime's Castle: Flowers", player), lambda state: state.has("Platform Ghost", player)) + + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Red Coins", player), lambda state: state.has("Large Spring Ball", player) and (state.has("Poochy", player) or logic.melon_item(state))) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Flowers", player), lambda state: state.has_all({"Super Star", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Stars", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Visit Koopa And Para-Koopa: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("The Baseball Boys: Red Coins", player), lambda state: state.has("Mole Tank Morph", player) and (state.has_any({"Ice Melon", "Large Spring Ball"}, player) or logic.melon_item(state))) + set_rule(world.multiworld.get_location("The Baseball Boys: Flowers", player), lambda state: (state.has_any({"Ice Melon", "Large Spring Ball"}, player) or logic.melon_item(state))) + set_rule(world.multiworld.get_location("The Baseball Boys: Level Clear", player), lambda state: (state.has_any({"Ice Melon", "Large Spring Ball"}, player) or logic.melon_item(state))) + + set_rule(world.multiworld.get_location("What's Gusty Taste Like?: Red Coins", player), lambda state: state.has("! Switch", player)) + + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Red Coins", player), lambda state: state.has_all({"! Switch", "Key"}, player)) + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Flowers", player), lambda state: state.has_all({"! Switch", "Key"}, player)) + set_rule(world.multiworld.get_location("Bigger Boo's Fort: Stars", player), lambda state: state.has("! Switch", player)) + + set_rule(world.multiworld.get_location("Watch Out For Lakitu: Flowers", player), lambda state: state.has_all({"Key", "Train Morph"}, player)) + + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("The Cave Of The Mystery Maze: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Lakitu's Wall: Flowers", player), lambda state: state.has("! Switch", player)) + set_rule(world.multiworld.get_location("Lakitu's Wall: Stars", player), lambda state: state.has("Giant Eggs", player) or logic.has_midring(state)) + + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Red Coins", player), lambda state: state.has_all({"Arrow Wheel", "Key"}, player)) + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Flowers", player), lambda state: state.has_all({"Arrow Wheel", "Key", "Train Morph"}, player)) + set_rule(world.multiworld.get_location("The Potted Ghost's Castle: Stars", player), lambda state: state.has("Arrow Wheel", player)) + + set_rule(world.multiworld.get_location("Welcome To Monkey World!: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Jungle Rhythm...: Red Coins", player), lambda state: state.has("Dashed Stairs", player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Flowers", player), lambda state: state.has("Dashed Stairs", player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Stars", player), lambda state: logic.has_midring(state) and state.has("Tulip", player)) + set_rule(world.multiworld.get_location("Jungle Rhythm...: Level Clear", player), lambda state: state.has("Dashed Stairs", player)) + + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Red Coins", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Flowers", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Nep-Enuts' Domain: Level Clear", player), lambda state: state.has_all({"Submarine Morph", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Red Coins", player), lambda state: state.has("Submarine Morph", player)) + set_rule(world.multiworld.get_location("Prince Froggy's Fort: Flowers", player), lambda state: (state.has("Egg Capacity Upgrade", player, 5) or logic.combat_item(state))) + + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Red Coins", player), lambda state: state.has("Mole Tank Morph", player)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Flowers", player), lambda state: state.has_all({"Mole Tank Morph", "! Switch"}, player)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Stars", player), lambda state: logic.has_midring(state) or state.has("Tulip", player)) + set_rule(world.multiworld.get_location("The Cave Of Harry Hedgehog: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "Key"}, player)) + + set_rule(world.multiworld.get_location("Monkeys' Favorite Lake: Red Coins", player), lambda state: state.has_all({"! Switch", "Submarine Morph"}, player)) + + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Red Coins", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Flowers", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("GO! GO! MARIO!!: Level Clear", player), lambda state: state.has("Super Star", player)) + + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Red Coins", player), lambda state: state.has_all({"! Switch", "Egg Launcher"}, player)) + set_rule(world.multiworld.get_location("The Cave Of The Lakitus: Flowers", player), lambda state: state.has("Egg Launcher", player)) + + set_rule(world.multiworld.get_location("Don't Look Back!: Red Coins", player), lambda state: state.has_all({"Helicopter Morph", "Large Spring Ball"}, player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Don't Look Back!: Stars", player), lambda state: logic.has_midring(state) or state.has("Tulip", player)) + + set_rule(world.multiworld.get_location("Marching Milde's Fort: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket", "Key"}, player)) + set_rule(world.multiworld.get_location("Marching Milde's Fort: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket"}, player)) + set_rule(world.multiworld.get_location("Marching Milde's Fort: Stars", player), lambda state: state.has("Dashed Stairs", player)) + + set_rule(world.multiworld.get_location("Chomp Rock Zone: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Chomp Rock Zone: Flowers", player), lambda state: state.has_all({"Chomp Rock", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Chomp Rock Zone: Stars", player), lambda state: state.has_all({"Chomp Rock", "! Switch"}, player)) + + set_rule(world.multiworld.get_location("Lake Shore Paradise: Stars", player), lambda state: (logic.has_midring(state) or (state.has("Tulip", player) and logic.cansee_clouds(state)))) + + set_rule(world.multiworld.get_location("Ride Like The Wind: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Stars", player), lambda state: (logic.has_midring(state) or state.has("Helicopter Morph", player)) and state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Ride Like The Wind: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Red Coins", player), lambda state: state.has_all({"Key", "Dashed Stairs"}, player)) + set_rule(world.multiworld.get_location("Hookbill The Koopa's Castle: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Key"}, player)) + + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Red Coins", player), lambda state: (state.has("Fire Melon", player) or logic.melon_item(state)) and (state.has_all({"Spring Ball", "Skis"}, player)) and (state.has("Super Star", player) or logic.melon_item(state))) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Flowers", player), lambda state: (state.has("Fire Melon", player) or logic.melon_item(state)) and state.has_all({"Spring Ball", "Skis"}, player)) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Stars", player), lambda state: (logic.has_midring(state) and (state.has("Fire Melon", player) or logic.melon_item(state))) or (logic.has_midring(state) and (state.has_all({"Tulip", "Dashed Platform"}, player)))) + set_rule(world.multiworld.get_location("Danger - Icy Conditions Ahead: Level Clear", player), lambda state: state.has_all({"Spring Ball", "Skis"}, player)) + + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Red Coins", player), lambda state: state.has_all({"Dashed Stairs", "Dashed Platform", "Platform Ghost"}, player)) + set_rule(world.multiworld.get_location("Sluggy The Unshaven's Fort: Flowers", player), lambda state: state.has_all({"Dashed Stairs", "Platform Ghost"}, player)) + + set_rule(world.multiworld.get_location("Shifting Platforms Ahead: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Red Coins", player), lambda state: state.has_all({"Arrow Wheel", "Train Morph"}, player)) + set_rule(world.multiworld.get_location("Raphael The Raven's Castle: Flowers", player), lambda state: state.has_all({"Arrow Wheel", "Train Morph"}, player)) + + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Red Coins", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Flowers", player), lambda state: state.has("Large Spring Ball", player)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Stars", player), lambda state: logic.has_midring(state)) + set_rule(world.multiworld.get_location("Scary Skeleton Goonies!: Level Clear", player), lambda state: state.has("Large Spring Ball", player)) + + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Red Coins", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Flowers", player), lambda state: state.has("Super Star", player)) + set_rule(world.multiworld.get_location("The Cave Of The Bandits: Level Clear", player), lambda state: state.has("Super Star", player)) + + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Red Coins", player), lambda state: state.has_all({"Egg Plant", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Flowers", player), lambda state: state.has_all({"Egg Plant", "Key"}, player) and (state.has("Egg Capacity Upgrade", player, 1) or logic.combat_item(state))) + set_rule(world.multiworld.get_location("Tap-Tap The Red Nose's Fort: Stars", player), lambda state: state.has("Egg Plant", player) and state.has("Key", player)) + + set_rule(world.multiworld.get_location("The Very Loooooong Cave: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Red Coins", player), lambda state: state.has_all({"Key", "Large Spring Ball"}, player) and (logic.combat_item(state) or state.has("Chomp Rock", player))) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Flowers", player), lambda state: state.has_all({"Key", "Large Spring Ball"}, player) and (logic.combat_item(state) or state.has("Chomp Rock", player))) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Stars", player), lambda state: state.has_all({"Chomp Rock", "Key"}, player)) + set_rule(world.multiworld.get_location("The Deep, Underground Maze: Level Clear", player), lambda state: state.has_all({"Key", "Large Spring Ball", "Dashed Platform"}, player) and (logic.combat_item(state) or state.has("Chomp Rock", player))) + + set_rule(world.multiworld.get_location("KEEP MOVING!!!!: Stars", player), lambda state: logic.has_midring(state)) + + set_rule(world.multiworld.get_location("King Bowser's Castle: Red Coins", player), lambda state: state.has("Helicopter Morph", player) and logic._68CollectibleRoute(state)) + set_rule(world.multiworld.get_location("King Bowser's Castle: Flowers", player), lambda state: state.has("Helicopter Morph", player) and logic._68CollectibleRoute(state)) + set_rule(world.multiworld.get_location("King Bowser's Castle: Stars", player), lambda state: state.has("Helicopter Morph", player) and logic._68Route(state)) + + set_hard_extra_rules(world) + + +def set_hard_extra_rules(world: "YoshisIslandWorld") -> None: + player = world.player + logic = YoshiLogic(world) + if not world.options.extras_enabled: + return + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Red Coins", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Flowers", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Stars", player), lambda state: state.has("Poochy", player)) + set_rule(world.multiworld.get_location("Poochy Ain't Stupid: Level Clear", player), lambda state: state.has("Poochy", player)) + + set_rule(world.multiworld.get_location("Hit That Switch!!: Red Coins", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Hit That Switch!!: Flowers", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Hit That Switch!!: Level Clear", player), lambda state: state.has_all({"Large Spring Ball", "! Switch"}, player)) + + set_rule(world.multiworld.get_location("The Impossible? Maze: Red Coins", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph", "Flashing Eggs"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Flowers", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Stars", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph"}, player)) + set_rule(world.multiworld.get_location("The Impossible? Maze: Level Clear", player), lambda state: state.has_all({"Spring Ball", "Large Spring Ball", "Mole Tank Morph", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Kamek's Revenge: Red Coins", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph"}, player)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Flowers", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph", "! Switch"}, player)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Stars", player), lambda state: state.has("! Switch", player) or logic.has_midring(state)) + set_rule(world.multiworld.get_location("Kamek's Revenge: Level Clear", player), lambda state: state.has_all({"Key", "Skis", "Helicopter Morph"}, player)) + + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Red Coins", player), lambda state: state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Flowers", player), lambda state: state.has(("Large Spring Ball"), player)) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Stars", player), lambda state: True) + set_rule(world.multiworld.get_location("Castles - Masterpiece Set: Level Clear", player), lambda state: state.has(("Large Spring Ball"), player)) diff --git a/worlds/yoshisisland/__init__.py b/worlds/yoshisisland/__init__.py new file mode 100644 index 000000000000..b5d7e137b5f3 --- /dev/null +++ b/worlds/yoshisisland/__init__.py @@ -0,0 +1,388 @@ +import base64 +import os +import typing +import threading + +from typing import List, Set, TextIO, Dict +from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification +from worlds.AutoWorld import World, WebWorld +import settings +from .Items import get_item_names_per_category, item_table, filler_items, trap_items +from .Locations import get_locations +from .Regions import init_areas +from .Options import YoshisIslandOptions, PlayerGoal, ObjectVis, StageLogic, MinigameChecks +from .setup_game import setup_gamevars +from .Client import YoshisIslandSNIClient +from .Rules import set_easy_rules, set_normal_rules, set_hard_rules +from .Rom import LocalRom, patch_rom, get_base_rom_path, YoshisIslandDeltaPatch, USHASH + + +class YoshisIslandSettings(settings.Group): + class RomFile(settings.SNESRomPath): + """File name of the Yoshi's Island 1.0 US rom""" + description = "Yoshi's Island ROM File" + copy_to = "Super Mario World 2 - Yoshi's Island (U).sfc" + md5s = [USHASH] + + rom_file: RomFile = RomFile(RomFile.copy_to) + + +class YoshisIslandWeb(WebWorld): + theme = "ocean" + + setup_en = Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Yoshi's Island randomizer" + "and connecting to an Archipelago server.", + "English", + "setup_en.md", + "setup/en", + ["Pink Switch"] + ) + + tutorials = [setup_en] + + +class YoshisIslandWorld(World): + """ + Yoshi's Island is a 2D platforming game. + During a delivery, Bowser's evil ward, Kamek, attacked the stork, kidnapping Luigi and dropping Mario onto Yoshi's Island. + As Yoshi, you must run, jump, and throw eggs to escort the baby Mario across the island to defeat Bowser and reunite the two brothers with their parents. + """ + game = "Yoshi's Island" + option_definitions = YoshisIslandOptions + required_client_version = (0, 4, 4) + + item_name_to_id = {item: item_table[item].code for item in item_table} + location_name_to_id = {location.name: location.code for location in get_locations(None)} + item_name_groups = get_item_names_per_category() + + web = YoshisIslandWeb() + settings: typing.ClassVar[YoshisIslandSettings] + # topology_present = True + + options_dataclass = YoshisIslandOptions + options: YoshisIslandOptions + + locked_locations: List[str] + set_req_bosses: str + lives_high: int + lives_low: int + castle_bosses: int + bowser_bosses: int + baby_mario_sfx: int + leader_color: int + boss_order: list + boss_burt: int + luigi_count: int + + rom_name: bytearray + + def __init__(self, multiworld: MultiWorld, player: int): + self.rom_name_available_event = threading.Event() + super().__init__(multiworld, player) + self.locked_locations = [] + + @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 fill_slot_data(self) -> Dict[str, List[int]]: + return { + "world_1": self.world_1_stages, + "world_2": self.world_2_stages, + "world_3": self.world_3_stages, + "world_4": self.world_4_stages, + "world_5": self.world_5_stages, + "world_6": self.world_6_stages + } + + def write_spoiler_header(self, spoiler_handle: TextIO) -> None: + spoiler_handle.write(f"Burt The Bashful's Boss Door: {self.boss_order[0]}\n") + spoiler_handle.write(f"Salvo The Slime's Boss Door: {self.boss_order[1]}\n") + spoiler_handle.write(f"Bigger Boo's Boss Door: {self.boss_order[2]}\n") + spoiler_handle.write(f"Roger The Ghost's Boss Door: {self.boss_order[3]}\n") + spoiler_handle.write(f"Prince Froggy's Boss Door: {self.boss_order[4]}\n") + spoiler_handle.write(f"Naval Piranha's Boss Door: {self.boss_order[5]}\n") + spoiler_handle.write(f"Marching Milde's Boss Door: {self.boss_order[6]}\n") + spoiler_handle.write(f"Hookbill The Koopa's Boss Door: {self.boss_order[7]}\n") + spoiler_handle.write(f"Sluggy The Unshaven's Boss Door: {self.boss_order[8]}\n") + spoiler_handle.write(f"Raphael The Raven's Boss Door: {self.boss_order[9]}\n") + spoiler_handle.write(f"Tap-Tap The Red Nose's Boss Door: {self.boss_order[10]}\n") + spoiler_handle.write(f"\nLevels:\n1-1: {self.level_name_list[0]}\n") + spoiler_handle.write(f"1-2: {self.level_name_list[1]}\n") + spoiler_handle.write(f"1-3: {self.level_name_list[2]}\n") + spoiler_handle.write(f"1-4: {self.level_name_list[3]}\n") + spoiler_handle.write(f"1-5: {self.level_name_list[4]}\n") + spoiler_handle.write(f"1-6: {self.level_name_list[5]}\n") + spoiler_handle.write(f"1-7: {self.level_name_list[6]}\n") + spoiler_handle.write(f"1-8: {self.level_name_list[7]}\n") + + spoiler_handle.write(f"\n2-1: {self.level_name_list[8]}\n") + spoiler_handle.write(f"2-2: {self.level_name_list[9]}\n") + spoiler_handle.write(f"2-3: {self.level_name_list[10]}\n") + spoiler_handle.write(f"2-4: {self.level_name_list[11]}\n") + spoiler_handle.write(f"2-5: {self.level_name_list[12]}\n") + spoiler_handle.write(f"2-6: {self.level_name_list[13]}\n") + spoiler_handle.write(f"2-7: {self.level_name_list[14]}\n") + spoiler_handle.write(f"2-8: {self.level_name_list[15]}\n") + + spoiler_handle.write(f"\n3-1: {self.level_name_list[16]}\n") + spoiler_handle.write(f"3-2: {self.level_name_list[17]}\n") + spoiler_handle.write(f"3-3: {self.level_name_list[18]}\n") + spoiler_handle.write(f"3-4: {self.level_name_list[19]}\n") + spoiler_handle.write(f"3-5: {self.level_name_list[20]}\n") + spoiler_handle.write(f"3-6: {self.level_name_list[21]}\n") + spoiler_handle.write(f"3-7: {self.level_name_list[22]}\n") + spoiler_handle.write(f"3-8: {self.level_name_list[23]}\n") + + spoiler_handle.write(f"\n4-1: {self.level_name_list[24]}\n") + spoiler_handle.write(f"4-2: {self.level_name_list[25]}\n") + spoiler_handle.write(f"4-3: {self.level_name_list[26]}\n") + spoiler_handle.write(f"4-4: {self.level_name_list[27]}\n") + spoiler_handle.write(f"4-5: {self.level_name_list[28]}\n") + spoiler_handle.write(f"4-6: {self.level_name_list[29]}\n") + spoiler_handle.write(f"4-7: {self.level_name_list[30]}\n") + spoiler_handle.write(f"4-8: {self.level_name_list[31]}\n") + + spoiler_handle.write(f"\n5-1: {self.level_name_list[32]}\n") + spoiler_handle.write(f"5-2: {self.level_name_list[33]}\n") + spoiler_handle.write(f"5-3: {self.level_name_list[34]}\n") + spoiler_handle.write(f"5-4: {self.level_name_list[35]}\n") + spoiler_handle.write(f"5-5: {self.level_name_list[36]}\n") + spoiler_handle.write(f"5-6: {self.level_name_list[37]}\n") + spoiler_handle.write(f"5-7: {self.level_name_list[38]}\n") + spoiler_handle.write(f"5-8: {self.level_name_list[39]}\n") + + spoiler_handle.write(f"\n6-1: {self.level_name_list[40]}\n") + spoiler_handle.write(f"6-2: {self.level_name_list[41]}\n") + spoiler_handle.write(f"6-3: {self.level_name_list[42]}\n") + spoiler_handle.write(f"6-4: {self.level_name_list[43]}\n") + spoiler_handle.write(f"6-5: {self.level_name_list[44]}\n") + spoiler_handle.write(f"6-6: {self.level_name_list[45]}\n") + spoiler_handle.write(f"6-7: {self.level_name_list[46]}\n") + spoiler_handle.write("6-8: King Bowser's Castle") + + def create_item(self, name: str) -> Item: + data = item_table[name] + return Item(name, data.classification, data.code, self.player) + + def create_regions(self) -> None: + init_areas(self, get_locations(self)) + + def get_filler_item_name(self) -> str: + trap_chance: int = self.options.trap_percent.value + + if self.random.random() < (trap_chance / 100) and self.options.traps_enabled: + return self.random.choice(trap_items) + else: + return self.random.choice(filler_items) + + def set_rules(self) -> None: + rules_per_difficulty = { + 0: set_easy_rules, + 1: set_normal_rules, + 2: set_hard_rules + } + + rules_per_difficulty[self.options.stage_logic.value](self) + self.multiworld.completion_condition[self.player] = lambda state: state.has("Saved Baby Luigi", self.player) + self.get_location("Burt The Bashful's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Salvo The Slime's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Bigger Boo's Boss Room", ).place_locked_item(self.create_item("Boss Clear")) + self.get_location("Roger The Ghost's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Prince Froggy's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Naval Piranha's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Marching Milde's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Hookbill The Koopa's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Sluggy The Unshaven's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Raphael The Raven's Boss Room").place_locked_item(self.create_item("Boss Clear")) + self.get_location("Tap-Tap The Red Nose's Boss Room").place_locked_item(self.create_item("Boss Clear")) + + if self.options.goal == PlayerGoal.option_luigi_hunt: + self.get_location("Reconstituted Luigi").place_locked_item(self.create_item("Saved Baby Luigi")) + else: + self.get_location("King Bowser's Castle: Level Clear").place_locked_item( + self.create_item("Saved Baby Luigi") + ) + + self.get_location("Touch Fuzzy Get Dizzy: Gather Coins").place_locked_item( + self.create_item("Bandit Consumables") + ) + self.get_location("The Cave Of the Mystery Maze: Seed Spitting Contest").place_locked_item( + self.create_item("Bandit Watermelons") + ) + self.get_location("Lakitu's Wall: Gather Coins").place_locked_item(self.create_item("Bandit Consumables")) + self.get_location("Ride Like The Wind: Gather Coins").place_locked_item(self.create_item("Bandit Consumables")) + + def generate_early(self) -> None: + setup_gamevars(self) + + def get_excluded_items(self) -> Set[str]: + excluded_items: Set[str] = set() + + starting_gate = ["World 1 Gate", "World 2 Gate", "World 3 Gate", + "World 4 Gate", "World 5 Gate", "World 6 Gate"] + + excluded_items.add(starting_gate[self.options.starting_world]) + + if not self.options.shuffle_midrings: + excluded_items.add("Middle Ring") + + if not self.options.add_secretlens: + excluded_items.add("Secret Lens") + + if not self.options.extras_enabled: + excluded_items.add("Extra Panels") + excluded_items.add("Extra 1") + excluded_items.add("Extra 2") + excluded_items.add("Extra 3") + excluded_items.add("Extra 4") + excluded_items.add("Extra 5") + excluded_items.add("Extra 6") + + if self.options.split_extras: + excluded_items.add("Extra Panels") + else: + excluded_items.add("Extra 1") + excluded_items.add("Extra 2") + excluded_items.add("Extra 3") + excluded_items.add("Extra 4") + excluded_items.add("Extra 5") + excluded_items.add("Extra 6") + + if self.options.split_bonus: + excluded_items.add("Bonus Panels") + else: + excluded_items.add("Bonus 1") + excluded_items.add("Bonus 2") + excluded_items.add("Bonus 3") + excluded_items.add("Bonus 4") + excluded_items.add("Bonus 5") + excluded_items.add("Bonus 6") + + return excluded_items + + def create_item_with_correct_settings(self, name: str) -> Item: + data = item_table[name] + item = Item(name, data.classification, data.code, self.player) + + if not item.advancement: + return item + + if name == "Car Morph" and self.options.stage_logic != StageLogic.option_strict: + item.classification = ItemClassification.useful + + secret_lens_visibility_check = ( + self.options.hidden_object_visibility >= ObjectVis.option_clouds_only + or self.options.stage_logic != StageLogic.option_strict + ) + if name == "Secret Lens" and secret_lens_visibility_check: + item.classification = ItemClassification.useful + + is_bonus_location = name in {"Bonus 1", "Bonus 2", "Bonus 3", "Bonus 4", "Bonus 5", "Bonus 6", "Bonus Panels"} + bonus_games_disabled = ( + self.options.minigame_checks not in {MinigameChecks.option_bonus_games, MinigameChecks.option_both} + ) + if is_bonus_location and bonus_games_disabled: + item.classification = ItemClassification.useful + + if name in {"Bonus 1", "Bonus 3", "Bonus 4", "Bonus Panels"} and self.options.item_logic: + item.classification = ItemClassification.progression + + if name == "Piece of Luigi" and self.options.goal == PlayerGoal.option_luigi_hunt: + if self.luigi_count >= self.options.luigi_pieces_required: + item.classification = ItemClassification.useful + else: + item.classification = ItemClassification.progression_skip_balancing + self.luigi_count += 1 + + return item + + def generate_filler(self, pool: List[Item]) -> None: + if self.options.goal == PlayerGoal.option_luigi_hunt: + for _ in range(self.options.luigi_pieces_in_pool.value): + item = self.create_item_with_correct_settings("Piece of Luigi") + pool.append(item) + + for _ in range(len(self.multiworld.get_unfilled_locations(self.player)) - len(pool) - 16): + item = self.create_item_with_correct_settings(self.get_filler_item_name()) + pool.append(item) + + def get_item_pool(self, excluded_items: Set[str]) -> List[Item]: + pool: List[Item] = [] + + for name, data in item_table.items(): + if name not in excluded_items: + for _ in range(data.amount): + item = self.create_item_with_correct_settings(name) + pool.append(item) + + return pool + + def create_items(self) -> None: + self.luigi_count = 0 + + if self.options.minigame_checks in {MinigameChecks.option_bonus_games, MinigameChecks.option_both}: + self.multiworld.get_location("Flip Cards", self.player).place_locked_item( + self.create_item("Bonus Consumables")) + self.multiworld.get_location("Drawing Lots", self.player).place_locked_item( + self.create_item("Bonus Consumables")) + self.multiworld.get_location("Match Cards", self.player).place_locked_item( + self.create_item("Bonus Consumables")) + + pool = self.get_item_pool(self.get_excluded_items()) + + self.generate_filler(pool) + + self.multiworld.itempool += pool + + def generate_output(self, output_directory: str) -> None: + rompath = "" # if variable is not declared finally clause may fail + try: + world = self.multiworld + player = self.player + rom = LocalRom(get_base_rom_path()) + patch_rom(self, rom, self.player) + + 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 = YoshisIslandDeltaPatch(os.path.splitext(rompath)[0] + YoshisIslandDeltaPatch.patch_file_ending, + player=player, player_name=world.player_name[player], patched_path=rompath) + patch.write() + finally: + self.rom_name_available_event.set() + if os.path.exists(rompath): + os.unlink(rompath) + + def modify_multidata(self, multidata: dict) -> None: + # wait for self.rom_name to be available. + self.rom_name_available_event.wait() + rom_name = getattr(self, "rom_name", None) + 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 extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]) -> None: + world_names = [f"World {i}" for i in range(1, 7)] + world_stages = [ + self.world_1_stages, self.world_2_stages, self.world_3_stages, + self.world_4_stages, self.world_5_stages, self.world_6_stages + ] + + stage_pos_data = {} + for loc in self.multiworld.get_locations(self.player): + if loc.address is None: + continue + + level_id = getattr(loc, "level_id") + for level, stages in zip(world_names, world_stages): + if level_id in stages: + stage_pos_data[loc.address] = level + break + + hint_data[self.player] = stage_pos_data diff --git a/worlds/yoshisisland/docs/en_Yoshi's Island.md b/worlds/yoshisisland/docs/en_Yoshi's Island.md new file mode 100644 index 000000000000..8cd825cc7f34 --- /dev/null +++ b/worlds/yoshisisland/docs/en_Yoshi's Island.md @@ -0,0 +1,71 @@ +# Yoshi's Island + +## Where is the settings page? + +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. + +## What does randomization do to this game? + +Certain interactable objects within levels will be unable to be used until the corresponding item is found. If the item is not in the player's posession, the object will flash and will not function. Objects include: +- Spring Ball +- Large Spring Ball +- ! Switch +- Dashed Platform +- Dashed Stairs +- Beanstalk +- Arrow Wheel +- Vanishing Arrow Wheel +- Ice, fire, and normal watermelons +- Super Star +- Flashing Eggs +- Giant Eggs +- Egg Launcher +- Egg Refill Plant +- Chomp Rock +- Poochy +- Transformation Morphs +- Skis +- Platform Ghost +- Middle Rings +- Buckets +- Tulips + +Yoshi will start out being able to carry only one egg, and 5 capacity upgrades can be found to bring the total up to 6. +The player will start with all levels unlocked in their starting world, and can collect 'World Gates' to unlock levels from other worlds. +Extra and Bonus stages will also start out locked, and require respective items to access them. 6-8 is locked, and will be unlocked +upon reaching the number of boss clears defined by the player. +Other checks will grant the player extra lives, consumables for use in the inventory, or traps. + +Additionally, the player is able to randomize the bosses found at the end of boss stages, the order of stages, +the world they start in, the starting amount of lives, route through 6-8, and the color of Yoshi for each stage. + +## What is the goal of Yoshi's Island when randomized? + +The player can choose one of two goals: +- Bowser: Defeat a pre-defined number of bosses, and defeat Bowser at the end of 6-8. +- Luigi Hunt: Collect a pre-defined number of 'Pieces of Luigi' within levels. + +## What items and locations get shuffled? + +Locations consist of 'level objectives', that being: +- Beating the stage +- Collecting 20 red coins. +- Collecting 5 flowers. +- Collecting 30 stars. + +Checks will be sent immediately upon achieving that objective, regardless of if the stage is cleared or not. +Additional checks can be placed on Bandit mini-battles, or overworld minigames. + + +## Which items can be in another player's world? + +Any shuffled item can be in other players' worlds. + +## What does another world's item look like in Yoshi's Island + +Items do not have an appearance in Yoshi's Island + +## When the player receives an item, what happens? + +When the player recieves an item, a fanfare or sound will be heard to reflect the item received. Most items, aside from Egg Capacity and level unlocks, can be checked on the menu by pressing SELECT. +If an item is in the queue and has not been received, checks will not be processed. diff --git a/worlds/yoshisisland/docs/setup_en.md b/worlds/yoshisisland/docs/setup_en.md new file mode 100644 index 000000000000..30aadbfa604d --- /dev/null +++ b/worlds/yoshisisland/docs/setup_en.md @@ -0,0 +1,123 @@ +# Yoshi's Island Archipelago 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 such as: + - snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), + - BizHawk from: [TASVideos](https://tasvideos.org/BizHawk) + - snes9x-nwa from: [snes9x nwa](https://github.com/Skarsnik/snes9x-emunwa/releases) + + NOTE: RetroArch and FXPakPro are not currently supported. +- Your legally obtained Yoshi's Island English 1.0 ROM file, probably named `Super Mario World 2 - Yoshi's Island (U).sfc` + + +## Installation Procedures + +### Windows Setup + +1. Download and install Archipelago from the link above, making sure to install the most recent version. +2. During generation/patching, you will be asked to locate your base ROM file. This is your Yoshi's Island ROM file. +3. If you are using an emulator, you should assign your Lua capable 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? + +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 Options 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 page. YAML +validator page: [YAML Validation page](/mysterycheck) + +## 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 whomever 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 `.apyi` 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/x64` or `/SNI/lua/x86` depending on if the + emulator is 64-bit or 32-bit. +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. This is done with the main menubar, under: + - (≤ 2.8) `Config` 〉 `Cores` 〉 `SNES` 〉 `BSNES` + - (≥ 2.9) `Config` 〉 `Preferred Cores` 〉 `SNES` 〉 `BSNESv115+` +2. Load your ROM file if it hasn't already been loaded. + If you changed your core preference after loading the ROM, don't forget to reload it (default hotkey: Ctrl+R). +3. Drag+drop the `Connector.lua` file included with your client onto the main EmuHawk window. + - Look in the Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if the + emulator is 64-bit or 32-bit. Please note the most recent versions of BizHawk are 64-bit only. + - You could instead open the Lua Console manually, click `Script` 〉 `Open Script`, and navigate to `Connector.lua` + with the file picker. + + + +### 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! + +## Hosting a MultiWorld game + +The recommended way to host a game is to use our hosting service. 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 Generate page above. + - Generate page: [WebHost Seed Generation Page](/generate) +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/yoshisisland/level_logic.py b/worlds/yoshisisland/level_logic.py new file mode 100644 index 000000000000..094e5efed12d --- /dev/null +++ b/worlds/yoshisisland/level_logic.py @@ -0,0 +1,482 @@ +from BaseClasses import CollectionState +from typing import TYPE_CHECKING + +from .Options import StageLogic, BowserDoor, ObjectVis + +if TYPE_CHECKING: + from . import YoshisIslandWorld + + +class YoshiLogic: + player: int + game_logic: str + midring_start: bool + clouds_always_visible: bool + consumable_logic: bool + luigi_pieces: int + + def __init__(self, world: "YoshisIslandWorld") -> None: + self.player = world.player + self.boss_order = world.boss_order + self.luigi_pieces = world.options.luigi_pieces_required.value + + if world.options.stage_logic == StageLogic.option_strict: + self.game_logic = "Easy" + elif world.options.stage_logic == StageLogic.option_loose: + self.game_logic = "Normal" + else: + self.game_logic = "Hard" + + self.midring_start = not world.options.shuffle_midrings + self.consumable_logic = not world.options.item_logic + + self.clouds_always_visible = world.options.hidden_object_visibility >= ObjectVis.option_clouds_only + + self.bowser_door = world.options.bowser_door_mode.value + if self.bowser_door == BowserDoor.option_door_4: + self.bowser_door = BowserDoor.option_door_3 + + def has_midring(self, state: CollectionState) -> bool: + return self.midring_start or state.has("Middle Ring", self.player) + + def reconstitute_luigi(self, state: CollectionState) -> bool: + return state.has("Piece of Luigi", self.player, self.luigi_pieces) + + def bandit_bonus(self, state: CollectionState) -> bool: + return state.has("Bandit Consumables", self.player) or state.has("Bandit Watermelons", self.player) + + def item_bonus(self, state: CollectionState) -> bool: + return state.has("Bonus Consumables", self.player) + + def combat_item(self, state: CollectionState) -> bool: + if not self.consumable_logic: + return False + else: + if self.game_logic == "Easy": + return self.item_bonus(state) + else: + return self.bandit_bonus(state) or self.item_bonus(state) + + def melon_item(self, state: CollectionState) -> bool: + if not self.consumable_logic: + return False + else: + if self.game_logic == "Easy": + return self.item_bonus(state) + else: + return state.has("Bandit Watermelons", self.player) or self.item_bonus(state) + + def default_vis(self, state: CollectionState) -> bool: + if self.clouds_always_visible: + return True + else: + return False + + def cansee_clouds(self, state: CollectionState) -> bool: + if self.game_logic != "Easy": + return True + else: + return self.default_vis(state) or state.has("Secret Lens", self.player) or self.combat_item(state) + + def bowserdoor_1(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Egg Plant", "! Switch"}, self.player) and state.has("Egg Capacity Upgrade", self.player, 2) + elif self.game_logic == "Normal": + return state.has("Egg Plant", self.player) and state.has("Egg Capacity Upgrade", self.player, 1) + else: + return state.has("Egg Plant", self.player) + + def bowserdoor_2(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return ((state.has("Egg Capacity Upgrade", self.player, 3) and state.has("Egg Plant", self.player)) or self.combat_item(state)) and state.has("Key", self.player) + elif self.game_logic == "Normal": + return ((state.has("Egg Capacity Upgrade", self.player, 2) and state.has("Egg Plant", self.player)) or self.combat_item(state)) and state.has("Key", self.player) + else: + return ((state.has("Egg Capacity Upgrade", self.player, 1) and state.has("Egg Plant", self.player)) or self.combat_item(state)) and state.has("Key", self.player) + + def bowserdoor_3(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def bowserdoor_4(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def _68Route(self, state: CollectionState) -> bool: + if self.bowser_door == 0: + return True + elif self.bowser_door == 1: + return self.bowserdoor_1(state) + elif self.bowser_door == 2: + return self.bowserdoor_2(state) + elif self.bowser_door == 3: + return True + elif self.bowser_door == 4: + return True + elif self.bowser_door == 5: + return self.bowserdoor_1(state) and self.bowserdoor_2(state) and self.bowserdoor_3(state) + + def _68CollectibleRoute(self, state: CollectionState) -> bool: + if self.bowser_door == 0: + return True + elif self.bowser_door == 1: + return self.bowserdoor_1(state) + elif self.bowser_door == 2: + return self.bowserdoor_2(state) + elif self.bowser_door == 3: + return True + elif self.bowser_door == 4: + return True + elif self.bowser_door == 5: + return self.bowserdoor_1(state) + + +############################################################################## + def _13Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Key", self.player) + elif self.game_logic == "Normal": + return state.has("Key", self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _14Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Spring Ball", "Key"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Spring Ball", "Key"}, self.player) + else: + return state.has_all({"Spring Ball", "Key"}, self.player) + + def _14Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Egg Plant", self.player) + elif self.game_logic == "Normal": + return state.has("Egg Plant", self.player) + else: + return (state.has("Egg Capacity Upgrade", self.player, 5) or state.has("Egg Plant", self.player)) + + def _14CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[0], "Location", self.player): + return True +############################################################################## + def _17Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Key", self.player) + elif self.game_logic == "Normal": + return state.has("Key", self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _18Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Key", "Arrow Wheel"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Key", "Arrow Wheel"}, self.player) + else: + return state.has_all({"Key", "Arrow Wheel"}, self.player) + + def _18Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def _18CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[1], "Location", self.player): + return True +############################################################################## + def _21Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Poochy", "Large Spring Ball", "Key"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Poochy", "Large Spring Ball", "Key"}, self.player) + else: + return state.has_all({"Poochy", "Large Spring Ball", "Key"}, self.player) +############################################################################## + def _23Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Mole Tank Morph", "Key"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Mole Tank Morph", "Key"}, self.player) + else: + return state.has_all({"Mole Tank Morph", "Key"}, self.player) +############################################################################## + def _24Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"! Switch", "Key", "Dashed Stairs"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"! Switch", "Dashed Stairs"}, self.player) + else: + return state.has("! Switch", self.player) + + def _24Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def _24CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[2], "Location", self.player): + return True +############################################################################## + def _26Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Large Spring Ball", "Key"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Large Spring Ball", "Key"}, self.player) + else: + return state.has_all({"Large Spring Ball", "Key"}, self.player) +############################################################################## + def _27Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Key", self.player) + elif self.game_logic == "Normal": + return state.has("Key", self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _28Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Arrow Wheel", "Key"}, self.player) and (state.has("Egg Capacity Upgrade", self.player, 1)) + elif self.game_logic == "Normal": + return state.has_all({"Arrow Wheel", "Key"}, self.player) + else: + return state.has_all({"Arrow Wheel", "Key"}, self.player) + + def _28Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def _28CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[3], "Location", self.player): + return True +############################################################################## + def _32Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Dashed Stairs", "Spring Ball", "Key"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Dashed Stairs", "Key"}, self.player) + else: + return state.has_all({"Dashed Stairs", "Key"}, self.player) +############################################################################## + def _34Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Dashed Platform", self.player) + elif self.game_logic == "Normal": + return (state.has("Dashed Platform", self.player) or self.has_midring(state)) + else: + return True + + def _34Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Giant Eggs", self.player) + elif self.game_logic == "Normal": + return True + else: + return True + + def _34CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[4], "Location", self.player): + return True +############################################################################## + def _37Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Key", "Large Spring Ball"}, self.player) + elif self.game_logic == "Normal": + return state.has("Key", self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _38Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return (state.has("Egg Capacity Upgrade", self.player, 3) or self.combat_item(state)) + elif self.game_logic == "Normal": + return (state.has("Egg Capacity Upgrade", self.player, 1) or self.combat_item(state)) + else: + return True + + def _38Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def _38CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[5], "Location", self.player): + return True +############################################################################## + def _42Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Large Spring Ball", "Key"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Large Spring Ball", "Key"}, self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _44Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket", "Key"}, self.player) and (state.has("Egg Capacity Upgrade", self.player, 1) or self.combat_item(state)) + elif self.game_logic == "Normal": + return state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket", "Key"}, self.player) + else: + return state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Arrow Wheel", "Bucket", "Key"}, self.player) + + def _44Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def _44CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[6], "Location", self.player): + return True +######################################################################################################## + + def _46Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Key", "Large Spring Ball"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Key", "Large Spring Ball"}, self.player) + else: + return state.has_all({"Key", "Large Spring Ball"}, self.player) + + def _47Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Key", "Large Spring Ball"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Key", "Large Spring Ball"}, self.player) + else: + return state.has_all({"Key", "Large Spring Ball"}, self.player) +############################################################################## + def _48Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return (state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Key", "Large Spring Ball"}, self.player)) + elif self.game_logic == "Normal": + return (state.has_all({"Dashed Stairs", "Vanishing Arrow Wheel", "Key", "Large Spring Ball"}, self.player)) + else: + return (state.has_all({"Key", "Large Spring Ball"}, self.player)) + + def _48Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return (state.has("Egg Capacity Upgrade", self.player, 3)) + elif self.game_logic == "Normal": + return (state.has("Egg Capacity Upgrade", self.player, 2)) + else: + return (state.has("Egg Capacity Upgrade", self.player, 1)) + + def _48CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[7], "Location", self.player): + return True +###################################################################################################################### + def _51Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Key", self.player) + elif self.game_logic == "Normal": + return state.has("Key", self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _54Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return (state.has_all({"Dashed Stairs", "Platform Ghost", "Dashed Platform"}, self.player)) + elif self.game_logic == "Normal": + return (state.has_all({"Dashed Stairs", "Platform Ghost", "Dashed Platform"}, self.player)) + else: + return (state.has_all({"Dashed Stairs", "Platform Ghost"}, self.player)) + + def _54Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return (state.has("Egg Capacity Upgrade", self.player, 2) and state.has("Egg Plant", self.player)) + elif self.game_logic == "Normal": + return ((state.has("Egg Capacity Upgrade", self.player, 1) and state.has("Egg Plant", self.player)) or (state.has("Egg Capacity Upgrade", self.player, 5) and self.has_midring(state))) + else: + return ((state.has("Egg Plant", self.player)) or (state.has("Egg Capacity Upgrade", self.player, 3) and self.has_midring(state))) + + def _54CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[8], "Location", self.player): + return True +################################################################################################### + def _58Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Arrow Wheel", "Large Spring Ball"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Arrow Wheel", "Large Spring Ball"}, self.player) + else: + return state.has_all({"Arrow Wheel", "Large Spring Ball"}, self.player) + + def _58Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return True + elif self.game_logic == "Normal": + return True + else: + return True + + def _58CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[9], "Location", self.player): + return True +############################################################################## + def _61Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Dashed Platform", "Key", "Beanstalk"}, self.player) + elif self.game_logic == "Normal": + return state.has_all({"Dashed Platform", "Key", "Beanstalk"}, self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _64Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Spring Ball", "Large Spring Ball", "Egg Plant", "Key"}, self.player) and (state.has("Egg Capacity Upgrade", self.player, 3) or self.combat_item(state)) + elif self.game_logic == "Normal": + return state.has_all({"Large Spring Ball", "Egg Plant", "Key"}, self.player) and (state.has("Egg Capacity Upgrade", self.player, 2) or self.combat_item(state)) + else: + return state.has_all({"Egg Plant", "Key"}, self.player) and (state.has("Egg Capacity Upgrade", self.player, 1) or self.combat_item(state)) + + def _64Boss(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Egg Plant", self.player) + elif self.game_logic == "Normal": + return state.has("Egg Plant", self.player) + else: + return True + + def _64CanFightBoss(self, state: CollectionState) -> bool: + if state.can_reach(self.boss_order[10], "Location", self.player): + return True +############################################################################## + def _67Game(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has("Key", self.player) + elif self.game_logic == "Normal": + return state.has("Key", self.player) + else: + return state.has("Key", self.player) +############################################################################## + def _68Clear(self, state: CollectionState) -> bool: + if self.game_logic == "Easy": + return state.has_all({"Helicopter Morph", "Egg Plant", "Giant Eggs"}, self.player) and self._68Route(state) + elif self.game_logic == "Normal": + return state.has_all({"Helicopter Morph", "Egg Plant", "Giant Eggs"}, self.player) and self._68Route(state) + else: + return state.has_all({"Helicopter Morph", "Giant Eggs"}, self.player) and self._68Route(state) diff --git a/worlds/yoshisisland/setup_bosses.py b/worlds/yoshisisland/setup_bosses.py new file mode 100644 index 000000000000..bbefdd31a05c --- /dev/null +++ b/worlds/yoshisisland/setup_bosses.py @@ -0,0 +1,19 @@ +from BaseClasses import CollectionState +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from . import YoshisIslandWorld + + +class BossReqs: + player: int + + def __init__(self, world: "YoshisIslandWorld") -> None: + self.player = world.player + self.castle_unlock = world.options.castle_open_condition.value + self.boss_unlock = world.options.castle_clear_condition.value + + def castle_access(self, state: CollectionState) -> bool: + return state.has("Boss Clear", self.player, self.castle_unlock) + + def castle_clear(self, state: CollectionState) -> bool: + return state.has("Boss Clear", self.player, self.boss_unlock) diff --git a/worlds/yoshisisland/setup_game.py b/worlds/yoshisisland/setup_game.py new file mode 100644 index 000000000000..000420a95b07 --- /dev/null +++ b/worlds/yoshisisland/setup_game.py @@ -0,0 +1,460 @@ +import struct +from typing import TYPE_CHECKING + +from .Options import YoshiColors, BabySound, LevelShuffle + +if TYPE_CHECKING: + from . import YoshisIslandWorld + + +def setup_gamevars(world: "YoshisIslandWorld") -> None: + if world.options.luigi_pieces_in_pool < world.options.luigi_pieces_required: + world.options.luigi_pieces_in_pool.value = world.random.randint(world.options.luigi_pieces_required.value, 100) + world.starting_lives = struct.pack("H", world.options.starting_lives) + + world.level_colors = [] + world.color_order = [] + for i in range(72): + world.level_colors.append(world.random.randint(0, 7)) + if world.options.yoshi_colors == YoshiColors.option_singularity: + singularity_color = world.options.yoshi_singularity_color.value + for i in range(len(world.level_colors)): + world.level_colors[i] = singularity_color + elif world.options.yoshi_colors == YoshiColors.option_random_order: + world.leader_color = world.random.randint(0, 7) + for i in range(7): + world.color_order.append(world.random.randint(0, 7)) + + bonus_valid = [0x00, 0x02, 0x04, 0x06, 0x08, 0x0A] + + world.world_bonus = [] + for i in range(12): + world.world_bonus.append(world.random.choice(bonus_valid)) + + safe_baby_sounds = [0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, + 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, + 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x52, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, + 0x73, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, + 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2] + + if world.options.baby_mario_sound == BabySound.option_random_sound_effect: + world.baby_mario_sfx = world.random.choice(safe_baby_sounds) + elif world.options.baby_mario_sound == BabySound.option_disabled: + world.baby_mario_sfx = 0x42 + else: + world.baby_mario_sfx = 0x44 + + boss_list = ["Burt The Bashful's Boss Room", "Salvo The Slime's Boss Room", + "Bigger Boo's Boss Room", "Roger The Ghost's Boss Room", + "Prince Froggy's Boss Room", "Naval Piranha's Boss Room", + "Marching Milde's Boss Room", "Hookbill The Koopa's Boss Room", + "Sluggy The Unshaven's Boss Room", "Raphael The Raven's Boss Room", + "Tap-Tap The Red Nose's Boss Room"] + + world.boss_order = [] + + if world.options.boss_shuffle: + world.random.shuffle(boss_list) + world.boss_order = boss_list + + burt_pointers = [0x3D, 0x05, 0x63, 0x00] + slime_pointers = [0x70, 0x04, 0x78, 0x00] + boo_pointers = [0x74, 0xBB, 0x7A, 0x00] + pot_pointers = [0xCF, 0x04, 0x4D, 0x00] + frog_pointers = [0xBF, 0x12, 0x62, 0x04] + plant_pointers = [0x7F, 0x0D, 0x42, 0x00] + milde_pointers = [0x82, 0x06, 0x64, 0x00] + koop_pointers = [0x86, 0x0D, 0x78, 0x00] + slug_pointers = [0x8A, 0x09, 0x7A, 0x00] + raph_pointers = [0xC4, 0x03, 0x4B, 0x05] + tap_pointers = [0xCC, 0x49, 0x64, 0x02] + + boss_data_list = [ + burt_pointers, + slime_pointers, + boo_pointers, + pot_pointers, + frog_pointers, + plant_pointers, + milde_pointers, + koop_pointers, + slug_pointers, + raph_pointers, + tap_pointers + ] + + boss_levels = [0x03, 0x07, 0x0F, 0x13, 0x1B, 0x1F, 0x27, 0x2B, 0x33, 0x37, 0x3F] + + boss_room_idlist = { + "Burt The Bashful's Boss Room": 0, + "Salvo The Slime's Boss Room": 1, + "Bigger Boo's Boss Room": 2, + "Roger The Ghost's Boss Room": 3, + "Prince Froggy's Boss Room": 4, + "Naval Piranha's Boss Room": 5, + "Marching Milde's Boss Room": 6, + "Hookbill The Koopa's Boss Room": 7, + "Sluggy The Unshaven's Boss Room": 8, + "Raphael The Raven's Boss Room": 9, + "Tap-Tap The Red Nose's Boss Room": 10, + } + + boss_check_list = { + "Burt The Bashful's Boss Room": "Burt The Bashful Defeated", + "Salvo The Slime's Boss Room": "Salvo The Slime Defeated", + "Bigger Boo's Boss Room": "Bigger Boo Defeated", + "Roger The Ghost's Boss Room": "Roger The Ghost Defeated", + "Prince Froggy's Boss Room": "Prince Froggy Defeated", + "Naval Piranha's Boss Room": "Naval Piranha Defeated", + "Marching Milde's Boss Room": "Marching Milde Defeated", + "Hookbill The Koopa's Boss Room": "Hookbill The Koopa Defeated", + "Sluggy The Unshaven's Boss Room": "Sluggy The Unshaven Defeated", + "Raphael The Raven's Boss Room": "Raphael The Raven Defeated", + "Tap-Tap The Red Nose's Boss Room": "Tap-Tap The Red Nose Defeated", + } + + world.boss_room_id = [boss_room_idlist[roomnum] for roomnum in world.boss_order] + world.tap_tap_room = boss_levels[world.boss_room_id.index(10)] + world.boss_ap_loc = [boss_check_list[roomnum] for roomnum in world.boss_order] + + world.boss_burt_data = boss_data_list[world.boss_room_id[0]] + + world.boss_slime_data = boss_data_list[world.boss_room_id[1]] + + world.boss_boo_data = boss_data_list[world.boss_room_id[2]] + + world.boss_pot_data = boss_data_list[world.boss_room_id[3]] + + world.boss_frog_data = boss_data_list[world.boss_room_id[4]] + + world.boss_plant_data = boss_data_list[world.boss_room_id[5]] + + world.boss_milde_data = boss_data_list[world.boss_room_id[6]] + + world.boss_koop_data = boss_data_list[world.boss_room_id[7]] + + world.boss_slug_data = boss_data_list[world.boss_room_id[8]] + + world.boss_raph_data = boss_data_list[world.boss_room_id[9]] + + world.boss_tap_data = boss_data_list[world.boss_room_id[10]] + + world.global_level_list = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42] + level_id_list = { + 0x00: "1-1", + 0x01: "1-2", + 0x02: "1-3", + 0x03: "1-4", + 0x04: "1-5", + 0x05: "1-6", + 0x06: "1-7", + 0x07: "1-8", + 0x0C: "2-1", + 0x0D: "2-2", + 0x0E: "2-3", + 0x0F: "2-4", + 0x10: "2-5", + 0x11: "2-6", + 0x12: "2-7", + 0x13: "2-8", + 0x18: "3-1", + 0x19: "3-2", + 0x1A: "3-3", + 0x1B: "3-4", + 0x1C: "3-5", + 0x1D: "3-6", + 0x1E: "3-7", + 0x1F: "3-8", + 0x24: "4-1", + 0x25: "4-2", + 0x26: "4-3", + 0x27: "4-4", + 0x28: "4-5", + 0x29: "4-6", + 0x2A: "4-7", + 0x2B: "4-8", + 0x30: "5-1", + 0x31: "5-2", + 0x32: "5-3", + 0x33: "5-4", + 0x34: "5-5", + 0x35: "5-6", + 0x36: "5-7", + 0x37: "5-8", + 0x3C: "6-1", + 0x3D: "6-2", + 0x3E: "6-3", + 0x3F: "6-4", + 0x40: "6-5", + 0x41: "6-6", + 0x42: "6-7" + } + + level_names = { + 0x00: "Make Eggs, Throw Eggs", + 0x01: "Watch Out Below!", + 0x02: "The Cave Of Chomp Rock", + 0x03: "Burt The Bashful's Fort", + 0x04: "Hop! Hop! Donut Lifts", + 0x05: "Shy-Guys On Stilts", + 0x06: "Touch Fuzzy Get Dizzy", + 0x07: "Salvo The Slime's Castle", + 0x0C: "Visit Koopa And Para-Koopa", + 0x0D: "The Baseball Boys", + 0x0E: "What's Gusty Taste Like?", + 0x0F: "Bigger Boo's Fort", + 0x10: "Watch Out For Lakitu", + 0x11: "The Cave Of The Mystery Maze", + 0x12: "Lakitu's Wall", + 0x13: "The Potted Ghost's Castle", + 0x18: "Welcome To Monkey World!", + 0x19: "Jungle Rhythm...", + 0x1A: "Nep-Enuts' Domain", + 0x1B: "Prince Froggy's Fort", + 0x1C: "Jammin' Through The Trees", + 0x1D: "The Cave Of Harry Hedgehog", + 0x1E: "Monkeys' Favorite Lake", + 0x1F: "Naval Piranha's Castle", + 0x24: "GO! GO! MARIO!!", + 0x25: "The Cave Of The Lakitus", + 0x26: "Don't Look Back!", + 0x27: "Marching Milde's Fort", + 0x28: "Chomp Rock Zone", + 0x29: "Lake Shore Paradise", + 0x2A: "Ride Like The Wind", + 0x2B: "Hookbill The Koopa's Castle", + 0x30: "BLIZZARD!!!", + 0x31: "Ride The Ski Lifts", + 0x32: "Danger - Icy Conditions Ahead", + 0x33: "Sluggy The Unshaven's Fort", + 0x34: "Goonie Rides!", + 0x35: "Welcome To Cloud World", + 0x36: "Shifting Platforms Ahead", + 0x37: "Raphael The Raven's Castle", + 0x3C: "Scary Skeleton Goonies!", + 0x3D: "The Cave Of The Bandits", + 0x3E: "Beware The Spinning Logs", + 0x3F: "Tap-Tap The Red Nose's Fort", + 0x40: "The Very Loooooong Cave", + 0x41: "The Deep, Underground Maze", + 0x42: "KEEP MOVING!!!!" + } + + world_1_offsets = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00] + world_2_offsets = [0x01, 0x01, 0x00, 0x00, 0x00, 0x00] + world_3_offsets = [0x01, 0x01, 0x01, 0x00, 0x00, 0x00] + world_4_offsets = [0x01, 0x01, 0x01, 0x01, 0x00, 0x00] + world_5_offsets = [0x01, 0x01, 0x01, 0x01, 0x01, 0x00] + easy_start_lv = [0x02, 0x04, 0x06, 0x0E, 0x10, 0x18, 0x1C, 0x28, + 0x30, 0x31, 0x35, 0x36, 0x3E, 0x40, 0x42] + norm_start_lv = [0x00, 0x01, 0x02, 0x04, 0x06, 0x0E, 0x10, 0x12, 0x18, 0x1A, + 0x1C, 0x1E, 0x28, 0x30, 0x31, 0x34, 0x35, 0x36, 0x3D, 0x3E, 0x40, 0x42] + hard_start_lv = [0x00, 0x01, 0x02, 0x04, 0x06, 0x0D, 0x0E, 0x10, 0x11, 0x12, 0x18, 0x1A, 0x1C, + 0x1E, 0x24, 0x25, 0x26, 0x28, 0x29, 0x30, 0x31, 0x34, 0x35, 0x36, 0x3D, 0x3E, + 0x40, 0x42] + diff_index = [easy_start_lv, norm_start_lv, hard_start_lv] + diff_level = diff_index[world.options.stage_logic.value] + boss_lv = [0x03, 0x07, 0x0F, 0x13, 0x1B, 0x1F, 0x27, 0x2B, 0x33, 0x37, 0x3F] + world.world_start_lv = [0, 8, 16, 24, 32, 40] + if not world.options.shuffle_midrings: + easy_start_lv.extend([0x1A, 0x24, 0x34]) + norm_start_lv.extend([0x24, 0x3C]) + hard_start_lv.extend([0x1D, 0x3C]) + + if world.options.level_shuffle != LevelShuffle.option_bosses_guranteed: + hard_start_lv.extend([0x07, 0x1B, 0x1F, 0x2B, 0x33, 0x37]) + if not world.options.shuffle_midrings: + easy_start_lv.extend([0x1B]) + norm_start_lv.extend([0x1B, 0x2B, 0x37]) + + starting_level = world.random.choice(diff_level) + + starting_level_entrance = world.world_start_lv[world.options.starting_world.value] + if world.options.level_shuffle: + world.global_level_list.remove(starting_level) + world.random.shuffle(world.global_level_list) + if world.options.level_shuffle == LevelShuffle.option_bosses_guranteed: + for i in range(11): + world.global_level_list = [item for item in world.global_level_list + if item not in boss_lv] + world.random.shuffle(boss_lv) + + world.global_level_list.insert(3 - world_1_offsets[world.options.starting_world.value], boss_lv[0]) # 1 if starting world is 1, 0 otherwise + world.global_level_list.insert(7 - world_1_offsets[world.options.starting_world.value], boss_lv[1]) + world.global_level_list.insert(11 - world_2_offsets[world.options.starting_world.value], boss_lv[2]) + world.global_level_list.insert(15 - world_2_offsets[world.options.starting_world.value], boss_lv[3]) + world.global_level_list.insert(19 - world_3_offsets[world.options.starting_world.value], boss_lv[4]) + world.global_level_list.insert(23 - world_3_offsets[world.options.starting_world.value], boss_lv[5]) + world.global_level_list.insert(27 - world_4_offsets[world.options.starting_world.value], boss_lv[6]) + world.global_level_list.insert(31 - world_4_offsets[world.options.starting_world.value], boss_lv[7]) + world.global_level_list.insert(35 - world_5_offsets[world.options.starting_world.value], boss_lv[8]) + world.global_level_list.insert(39 - world_5_offsets[world.options.starting_world.value], boss_lv[9]) + world.global_level_list.insert(43 - 1, boss_lv[10]) + world.global_level_list.insert(starting_level_entrance, starting_level) + world.level_location_list = [level_id_list[LevelID] for LevelID in world.global_level_list] + world.level_name_list = [level_names[LevelID] for LevelID in world.global_level_list] + + level_panel_dict = { + 0x00: [0x04, 0x04, 0x53], + 0x01: [0x20, 0x04, 0x53], + 0x02: [0x3C, 0x04, 0x53], + 0x03: [0x58, 0x04, 0x53], + 0x04: [0x74, 0x04, 0x53], + 0x05: [0x90, 0x04, 0x53], + 0x06: [0xAC, 0x04, 0x53], + 0x07: [0xC8, 0x04, 0x53], + 0x0C: [0x04, 0x24, 0x53], + 0x0D: [0x20, 0x24, 0x53], + 0x0E: [0x3C, 0x24, 0x53], + 0x0F: [0x58, 0x24, 0x53], + 0x10: [0x74, 0x24, 0x53], + 0x11: [0x90, 0x24, 0x53], + 0x12: [0xAC, 0x24, 0x53], + 0x13: [0xC8, 0x24, 0x53], + 0x18: [0x04, 0x44, 0x53], + 0x19: [0x20, 0x44, 0x53], + 0x1A: [0x3C, 0x44, 0x53], + 0x1B: [0x58, 0x44, 0x53], + 0x1C: [0x74, 0x44, 0x53], + 0x1D: [0x90, 0x44, 0x53], + 0x1E: [0xAC, 0x44, 0x53], + 0x1F: [0xC8, 0x44, 0x53], + 0x24: [0x04, 0x64, 0x53], + 0x25: [0x20, 0x64, 0x53], + 0x26: [0x3C, 0x64, 0x53], + 0x27: [0x58, 0x64, 0x53], + 0x28: [0x74, 0x64, 0x53], + 0x29: [0x90, 0x64, 0x53], + 0x2A: [0xAC, 0x64, 0x53], + 0x2B: [0xC8, 0x64, 0x53], + 0x30: [0x04, 0x04, 0x53], + 0x31: [0x20, 0x04, 0x53], + 0x32: [0x3C, 0x04, 0x53], + 0x33: [0x58, 0x04, 0x53], + 0x34: [0x74, 0x04, 0x53], + 0x35: [0x90, 0x04, 0x53], + 0x36: [0xAC, 0x04, 0x53], + 0x37: [0xC8, 0x04, 0x53], + 0x3C: [0x04, 0x24, 0x53], + 0x3D: [0x20, 0x24, 0x53], + 0x3E: [0x3C, 0x24, 0x53], + 0x3F: [0x58, 0x24, 0x53], + 0x40: [0x74, 0x24, 0x53], + 0x41: [0x90, 0x24, 0x53], + 0x42: [0xAC, 0x24, 0x53], + } + panel_palette_1 = [0x00, 0x03, 0x04, 0x05, 0x0C, 0x10, 0x12, 0x13, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, + 0x24, 0x26, 0x27, 0x29, 0x2A, 0x2B, 0x30, 0x32, 0x34, + 0x35, 0x37, 0x3C, 0x3D, 0x40, 0x41] # 000C + panel_palette_2 = [0x01, 0x02, 0x06, 0x07, 0x0D, 0x0E, 0x0F, 0x11, 0x18, 0x1E, 0x1F, 0x25, 0x28, + 0x31, 0x33, 0x36, 0x3E, 0x3F, 0x42] # 0010 + + stage_number = 0 + world_number = 1 + for i in range(47): + stage_number += 1 + if stage_number >= 9: + world_number += 1 + stage_number = 1 + for _ in range(3): + setattr(world, f"Stage{world_number}{stage_number}StageGFX", + level_panel_dict[world.global_level_list[i]]) + + world.level_gfx_table = [] + world.palette_panel_list = [] + + for i in range(47): + if world.global_level_list[i] >= 0x30: + world.level_gfx_table.append(0x15) + else: + world.level_gfx_table.append(0x11) + + if world.global_level_list[i] in panel_palette_1: + world.palette_panel_list.extend([0x00, 0x0C]) + elif world.global_level_list[i] in panel_palette_2: + world.palette_panel_list.extend([0x00, 0x10]) + + world.palette_panel_list[16:16] = [0x00, 0x0c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x18] + world.palette_panel_list[40:40] = [0x00, 0x0c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x18] + world.palette_panel_list[64:64] = [0x00, 0x0c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x18] + world.palette_panel_list[88:88] = [0x00, 0x0c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x18] + world.palette_panel_list[112:112] = [0x00, 0x0c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x18] + + world.level_gfx_table.insert(8, 0x15) + world.level_gfx_table.insert(8, 0x15) + world.level_gfx_table.insert(8, 0x15) + world.level_gfx_table.insert(8, 0x11) + + world.level_gfx_table.insert(20, 0x15) + world.level_gfx_table.insert(20, 0x15) + world.level_gfx_table.insert(20, 0x15) + world.level_gfx_table.insert(20, 0x11) + + world.level_gfx_table.insert(32, 0x15) + world.level_gfx_table.insert(32, 0x15) + world.level_gfx_table.insert(32, 0x15) + world.level_gfx_table.insert(32, 0x11) + + world.level_gfx_table.insert(44, 0x15) + world.level_gfx_table.insert(44, 0x15) + world.level_gfx_table.insert(44, 0x15) + world.level_gfx_table.insert(44, 0x11) + + world.level_gfx_table.insert(56, 0x15) + world.level_gfx_table.insert(56, 0x15) + world.level_gfx_table.insert(56, 0x15) + world.level_gfx_table.insert(56, 0x15) + + castle_door_dict = { + 0: [0xB8, 0x05, 0x77, 0x00], + 1: [0xB8, 0x05, 0x77, 0x00], + 2: [0xC6, 0x07, 0x7A, 0x00], + 3: [0xCD, 0x05, 0x5B, 0x00], + 4: [0xD3, 0x00, 0x77, 0x06], + 5: [0xB8, 0x05, 0x77, 0x00], + } + + world.castle_door = castle_door_dict[world.options.bowser_door_mode.value] + + world.world_1_stages = world.global_level_list[0:8] + world.world_2_stages = world.global_level_list[8:16] + world.world_3_stages = world.global_level_list[16:24] + world.world_4_stages = world.global_level_list[24:32] + world.world_5_stages = world.global_level_list[32:40] + world.world_6_stages = world.global_level_list[40:47] + + world.world_1_stages.extend([0x08, 0x09]) + world.world_2_stages.extend([0x14, 0x15]) + world.world_3_stages.extend([0x20, 0x21]) + world.world_4_stages.extend([0x2C, 0x2D]) + world.world_5_stages.extend([0x38, 0x39]) + world.world_6_stages.extend([0x43, 0x44, 0x45]) + + bowser_text_table = { + 0: [0xDE, 0xEE, 0xDC, 0xDC, 0xE5], # Gween + 1: [0xE7, 0xE0, 0xE5, 0xE2, 0xD0], # Pink + 3: [0xEB, 0xDF, 0xF0, 0xD8, 0xE5], # Thyan + 2: [0xF0, 0xDC, 0xEE, 0xEE, 0xE6], # Yewow + 4: [0xE7, 0xEC, 0xDF, 0xE7, 0xE3], # puhpl + 5: [0xD9, 0xEE, 0xE6, 0xEE, 0xE5], # Bwown + 6: [0xEE, 0xDC, 0xDB, 0xD0, 0xD0], # Wed + 7: [0xD9, 0xEE, 0xEC, 0xDC, 0xD0], # Bwue + } + + if world.options.yoshi_colors == YoshiColors.option_random_order: + world.bowser_text = bowser_text_table[world.leader_color] + else: + world.bowser_text = bowser_text_table[world.level_colors[67]] diff --git a/worlds/zillion/docs/en_Zillion.md b/worlds/zillion/docs/en_Zillion.md index 06a11b7d7993..697a9b7dadbe 100644 --- a/worlds/zillion/docs/en_Zillion.md +++ b/worlds/zillion/docs/en_Zillion.md @@ -4,9 +4,9 @@ Zillion is a metroidvania-style game released in 1987 for the 8-bit Sega Master It's based on the anime Zillion (赤い光弾ジリオン, Akai Koudan Zillion). -## Where is the settings page? +## Where is the options page? -The [player settings page for this game](../player-settings) contains all the options you need to configure and export a config file. +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. ## What changes are made to this game? diff --git a/worlds/zillion/docs/setup_en.md b/worlds/zillion/docs/setup_en.md index 79f7912dd4fd..c8e29fc36cde 100644 --- a/worlds/zillion/docs/setup_en.md +++ b/worlds/zillion/docs/setup_en.md @@ -47,7 +47,7 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) ### Where do I get a config file? -The [player settings page](/games/Zillion/player-settings) on the website allows you to configure your personal settings and export a config file from +The [player options page](/games/Zillion/player-options) on the website allows you to configure your personal options and export a config file from them. ### Verifying your config file @@ -56,7 +56,7 @@ If you would like to validate your config file to make sure it works, you may do ## Generating a Single-Player Game -1. Navigate to the [player settings page](/games/Zillion/player-settings), configure your options, and click the "Generate Game" button. +1. Navigate to the [player options page](/games/Zillion/player-options), configure your options, and click the "Generate Game" button. 2. A "Seed Info" page will appear. 3. Click the "Create New Room" link. 4. A server page will appear. Download your patch file from this page.