From 248ac8ca687bf31858ad4b7e623f763199635ba6 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Sun, 25 Feb 2024 19:58:15 -0500 Subject: [PATCH 1/9] Make plando connections work --- worlds/tunic/er_data.py | 12 +-- worlds/tunic/er_rules.py | 13 --- worlds/tunic/er_scripts.py | 194 ++++++++++++++++++++----------------- 3 files changed, 113 insertions(+), 106 deletions(-) diff --git a/worlds/tunic/er_data.py b/worlds/tunic/er_data.py index d76af113390..bdf1a34f338 100644 --- a/worlds/tunic/er_data.py +++ b/worlds/tunic/er_data.py @@ -681,12 +681,6 @@ class Hint(IntEnum): "Hero Relic - Library": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats, hint=Hint.region), "Hero Relic - Swamp": RegionInfo("RelicVoid", dead_end=DeadEnd.all_cats, hint=Hint.region), "Purgatory": RegionInfo("Purgatory"), - "Shop Entrance 1": RegionInfo("Shop", dead_end=DeadEnd.all_cats), - "Shop Entrance 2": RegionInfo("Shop", dead_end=DeadEnd.all_cats), - "Shop Entrance 3": RegionInfo("Shop", dead_end=DeadEnd.all_cats), - "Shop Entrance 4": RegionInfo("Shop", dead_end=DeadEnd.all_cats), - "Shop Entrance 5": RegionInfo("Shop", dead_end=DeadEnd.all_cats), - "Shop Entrance 6": RegionInfo("Shop", dead_end=DeadEnd.all_cats), "Shop": RegionInfo("Shop", dead_end=DeadEnd.all_cats), "Spirit Arena": RegionInfo("Spirit Arena", dead_end=DeadEnd.all_cats, hint=Hint.region), "Spirit Arena Victory": RegionInfo("Spirit Arena", dead_end=DeadEnd.all_cats) @@ -749,6 +743,8 @@ class Hint(IntEnum): ["Forest Belltower Main", "Forest Belltower Lower"], ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"): ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"], + ("Guard House 1 East", "Guard House 1 West"): + ["Guard House 1 East", "Guard House 1 West"], ("Forest Grave Path Main", "Forest Grave Path Upper"): ["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"], ("Forest Grave Path by Grave", "Forest Hero's Grave"): @@ -842,6 +838,8 @@ class Hint(IntEnum): ["Forest Belltower Main", "Forest Belltower Lower"], ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"): ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"], + ("Guard House 1 East", "Guard House 1 West"): + ["Guard House 1 East", "Guard House 1 West"], ("Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"): ["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"], ("Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back"): @@ -934,6 +932,8 @@ class Hint(IntEnum): ["Forest Belltower Main", "Forest Belltower Lower"], ("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"): ["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"], + ("Guard House 1 East", "Guard House 1 West"): + ["Guard House 1 East", "Guard House 1 West"], # can use laurels, ice grapple, or ladder storage to traverse ("Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"): ["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"], diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index ebc563c3da5..5f26fd61096 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -609,19 +609,6 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re connecting_region=regions["Far Shore"]) # Misc - regions["Shop Entrance 1"].connect( - connecting_region=regions["Shop"]) - regions["Shop Entrance 2"].connect( - connecting_region=regions["Shop"]) - regions["Shop Entrance 3"].connect( - connecting_region=regions["Shop"]) - regions["Shop Entrance 4"].connect( - connecting_region=regions["Shop"]) - regions["Shop Entrance 5"].connect( - connecting_region=regions["Shop"]) - regions["Shop Entrance 6"].connect( - connecting_region=regions["Shop"]) - regions["Spirit Arena"].connect( connecting_region=regions["Spirit Arena Victory"], rule=lambda state: (state.has(gold_hexagon, player, world.options.hexagon_goal.value) if diff --git a/worlds/tunic/er_scripts.py b/worlds/tunic/er_scripts.py index d2b854f5df0..f27f6eaa97d 100644 --- a/worlds/tunic/er_scripts.py +++ b/worlds/tunic/er_scripts.py @@ -185,9 +185,14 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: portal_pairs: Dict[Portal, Portal] = {} dead_ends: List[Portal] = [] two_plus: List[Portal] = [] - plando_connections: List[PlandoConnection] = [] - fixed_shop = False logic_rules = world.options.logic_rules.value + player_name = world.multiworld.get_player_name(world.player) + + shop_scenes: Set[str] = set() + shop_count = 6 + if world.options.fixed_shop.value: + shop_count = 1 + shop_scenes.add("Overworld Redux") if not logic_rules: dependent_regions = dependent_regions_restricted @@ -215,9 +220,12 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: start_region = "Overworld" connected_regions.update(add_dependent_regions(start_region, logic_rules)) + plando_connections = world.multiworld.plando_connections[world.player] + # universal tracker support stuff, don't need to care about region dependency if hasattr(world.multiworld, "re_gen_passthrough"): if "TUNIC" in world.multiworld.re_gen_passthrough: + plando_connections.clear() # universal tracker stuff, won't do anything in normal gen for portal1, portal2 in world.multiworld.re_gen_passthrough["TUNIC"]["Entrance Rando"].items(): portal_name1 = "" @@ -240,9 +248,78 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: portal_name2 = "Shop Portal" plando_connections.append(PlandoConnection(portal_name1, portal_name2, "both")) + non_dead_end_regions = set() + for region_name, region_info in tunic_er_regions.items(): + if not region_info.dead_end: + non_dead_end_regions.add(region_name) + elif region_info.dead_end == 2 and logic_rules: + non_dead_end_regions.add(region_name) + if plando_connections: - portal_pairs, dependent_regions, dead_ends, two_plus = \ - create_plando_connections(plando_connections, dependent_regions, dead_ends, two_plus) + for connection in plando_connections: + p_entrance = connection.entrance + p_exit = connection.exit + + if p_entrance.startswith("Shop"): + p_entrance = p_exit + p_exit = "Shop Portal" + + portal1 = None + portal2 = None + + # search two_plus for both at once + for portal in two_plus: + if p_entrance == portal.name: + portal1 = portal + if p_exit == portal.name: + portal2 = portal + + # search dead_ends individually since we can't really remove items from two_plus during the loop + if not portal1: + for portal in dead_ends: + if p_entrance == portal.name: + portal1 = portal + break + if not portal1: + raise Exception(f"Could not find entrance named {p_entrance} for " + f"plando connections in {player_name}'s YAML") + dead_ends.remove(portal1) + else: + two_plus.remove(portal1) + + if not portal2: + for portal in dead_ends: + if p_exit == portal.name: + portal2 = portal + break + if p_exit in ["Shop Portal", "Shop"]: + portal2 = Portal(name="Shop Portal", region=f"Shop", + destination="Previous Region_") + shop_count -= 1 + if shop_count < 0: + shop_count += 2 + for p in portal_mapping: + if p.name == p_entrance: + shop_scenes.add(p.scene()) + break + else: + if not portal2: + raise Exception(f"Could not find entrance named {p_exit} for " + f"plando connections in {player_name}'s YAML") + dead_ends.remove(portal2) + else: + two_plus.remove(portal2) + + portal_pairs[portal1] = portal2 + + # update dependent regions based on the plando'd connections, to ensure the portals connect well, logically + for origins, destinations in dependent_regions.items(): + if portal1.region in origins: + if portal2.region in non_dead_end_regions: + destinations.append(portal2.region) + if portal2.region in origins: + if portal1.region in non_dead_end_regions: + destinations.append(portal1.region) # if we have plando connections, our connected regions may change somewhat while True: @@ -266,34 +343,47 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: if portal.scene_destination() == "Waterfall, Overworld Redux_": portal2 = portal break + if not portal1: + raise Exception(f"Failed to do Laurels Location at 10 Fairies option. " + f"Did {player_name} plando connection the Secret Gathering Place Entrance?") + if not portal2: + raise Exception(f"Failed to do Laurels Location at 10 Fairies option. " + f"Did {player_name} plando connection the Secret Gathering Place Exit?") portal_pairs[portal1] = portal2 two_plus.remove(portal1) dead_ends.remove(portal2) if world.options.fixed_shop and not hasattr(world.multiworld, "re_gen_passthrough"): - fixed_shop = True portal1 = None for portal in two_plus: if portal.scene_destination() == "Overworld Redux, Windmill_": portal1 = portal break - portal2 = Portal(name="Shop Portal", region=f"Shop Entrance 2", destination="Previous Region_") + portal2 = Portal(name="Shop Portal", region="Shop", destination="Previous Region_") + if not portal1: + raise Exception(f"Failed to do Fixed Shop option. " + f"Did {player_name} plando connection the Windmill Shop entrance?") portal_pairs[portal1] = portal2 two_plus.remove(portal1) # we want to start by making sure every region is accessible - non_dead_end_regions = set() - for region_name, region_info in tunic_er_regions.items(): - if not region_info.dead_end: - non_dead_end_regions.add(region_name) - elif region_info.dead_end == 2 and logic_rules: - non_dead_end_regions.add(region_name) - world.random.shuffle(two_plus) check_success = 0 portal1 = None portal2 = None + previous_conn_num = 0 + fail_count = 0 while len(connected_regions) < len(non_dead_end_regions): + # if the connected regions length stays unchanged for too long, it's stuck in a loop + # should, hopefully, only ever occur if someone plandos connections poorly + if previous_conn_num == len(connected_regions): + fail_count += 1 + if fail_count >= 100: + raise Exception(f"Failed to pair regions. Check plando connections for {player_name} for loops.") + else: + fail_count = 0 + previous_conn_num = len(connected_regions) + # find a portal in an inaccessible region if check_success == 0: for portal in two_plus: @@ -327,15 +417,6 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: check_success = 0 world.random.shuffle(two_plus) - # add 6 shops, connect them to unique scenes - # this is due to a limitation in Tunic -- you wrong warp if there's multiple shops - shop_scenes: Set[str] = set() - shop_count = 6 - - if fixed_shop: - shop_count = 1 - shop_scenes.add("Overworld Redux") - # for universal tracker, we want to skip shop gen if hasattr(world.multiworld, "re_gen_passthrough"): if "TUNIC" in world.multiworld.re_gen_passthrough: @@ -351,7 +432,7 @@ 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=f"Shop Entrance {i + 1}", destination="Previous Region_") + portal2 = Portal(name="Shop Portal", region="Shop", destination="Previous Region_") portal_pairs[portal1] = portal2 # connect dead ends to random non-dead ends @@ -379,9 +460,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}") + if not portal1.name.startswith("Shop"): + region1.connect(region2, f"{portal1.name} -> {portal2.name}") # prevent the logic from thinking you can get to any shop-connected region from the shop - if portal2.name != "Shop": + if not portal2.name.startswith("Shop"): region2.connect(region1, f"{portal2.name} -> {portal1.name}") @@ -507,65 +589,3 @@ def gate_before_switch(check_portal: Portal, two_plus: List[Portal]) -> bool: # false means you're good to place the portal return False - - -# this is for making the connections themselves -def create_plando_connections(plando_connections: List[PlandoConnection], - dependent_regions: Dict[Tuple[str, ...], List[str]], dead_ends: List[Portal], - two_plus: List[Portal]) \ - -> Tuple[Dict[Portal, Portal], Dict[Tuple[str, ...], List[str]], List[Portal], List[Portal]]: - - portal_pairs: Dict[Portal, Portal] = {} - shop_num = 1 - for connection in plando_connections: - p_entrance = connection.entrance - p_exit = connection.exit - - portal1 = None - portal2 = None - - # search two_plus for both at once - for portal in two_plus: - if p_entrance == portal.name: - portal1 = portal - if p_exit == portal.name: - portal2 = portal - - # search dead_ends individually since we can't really remove items from two_plus during the loop - if not portal1: - for portal in dead_ends: - if p_entrance == portal.name: - portal1 = portal - break - dead_ends.remove(portal1) - else: - two_plus.remove(portal1) - - if not portal2: - for portal in dead_ends: - if p_exit == portal.name: - portal2 = portal - break - if p_exit == "Shop Portal": - portal2 = Portal(name="Shop Portal", region=f"Shop Entrance {shop_num}", destination="Previous Region_") - shop_num += 1 - else: - dead_ends.remove(portal2) - else: - two_plus.remove(portal2) - - if not portal1: - raise Exception("could not find entrance named " + p_entrance + " for Tunic player's plando") - if not portal2: - raise Exception("could not find entrance named " + p_exit + " for Tunic player's plando") - - portal_pairs[portal1] = portal2 - - # update dependent regions based on the plando'd connections, to make sure the portals connect well, logically - for origins, destinations in dependent_regions.items(): - if portal1.region in origins: - destinations.append(portal2.region) - if portal2.region in origins: - destinations.append(portal1.region) - - return portal_pairs, dependent_regions, dead_ends, two_plus From 08f26a8af900002407c705bc8a848398dd4909ac Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Sun, 25 Feb 2024 20:06:15 -0500 Subject: [PATCH 2/9] Slightly modify exception text --- worlds/tunic/er_scripts.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/worlds/tunic/er_scripts.py b/worlds/tunic/er_scripts.py index f27f6eaa97d..8b2677cddca 100644 --- a/worlds/tunic/er_scripts.py +++ b/worlds/tunic/er_scripts.py @@ -282,7 +282,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: break if not portal1: raise Exception(f"Could not find entrance named {p_entrance} for " - f"plando connections in {player_name}'s YAML") + f"plando connections in {player_name}'s YAML.") dead_ends.remove(portal1) else: two_plus.remove(portal1) @@ -305,7 +305,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: else: if not portal2: raise Exception(f"Could not find entrance named {p_exit} for " - f"plando connections in {player_name}'s YAML") + f"plando connections in {player_name}'s YAML.") dead_ends.remove(portal2) else: two_plus.remove(portal2) @@ -431,7 +431,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: two_plus.remove(portal) break if portal1 is None: - raise Exception("Too many shops in the pool, or something else went wrong") + raise Exception("Too many shops in the pool, or something else went wrong.") portal2 = Portal(name="Shop Portal", region="Shop", destination="Previous Region_") portal_pairs[portal1] = portal2 @@ -460,8 +460,7 @@ 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] - if not portal1.name.startswith("Shop"): - region1.connect(region2, f"{portal1.name} -> {portal2.name}") + region1.connect(region2, f"{portal1.name} -> {portal2.name}") # prevent the logic from thinking you can get to any shop-connected region from the shop if not portal2.name.startswith("Shop"): region2.connect(region1, f"{portal2.name} -> {portal1.name}") From dcba5fbc2baa1327a4714b67bd08ce733eaae578 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Sun, 25 Feb 2024 20:49:08 -0500 Subject: [PATCH 3/9] Make it work with UT again --- worlds/tunic/er_scripts.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/worlds/tunic/er_scripts.py b/worlds/tunic/er_scripts.py index 8b2677cddca..ec624d9130b 100644 --- a/worlds/tunic/er_scripts.py +++ b/worlds/tunic/er_scripts.py @@ -231,11 +231,6 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: portal_name1 = "" portal_name2 = "" - # skip this if 10 fairies laurels location is on, it can be handled normally - if portal1 == "Overworld Redux, Waterfall_" and portal2 == "Waterfall, Overworld Redux_" \ - and world.options.laurels_location == "10_fairies": - continue - for portal in portal_mapping: if portal.scene_destination() == portal1: portal_name1 = portal.name @@ -332,7 +327,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: # need to plando fairy cave, or it could end up laurels locked # fix this later to be random after adding some item logic to dependent regions - if world.options.laurels_location == "10_fairies": + if world.options.laurels_location == "10_fairies" and not hasattr(world.multiworld, "re_gen_passthrough"): portal1 = None portal2 = None for portal in two_plus: @@ -376,9 +371,11 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: while len(connected_regions) < len(non_dead_end_regions): # if the connected regions length stays unchanged for too long, it's stuck in a loop # should, hopefully, only ever occur if someone plandos connections poorly + if hasattr(world.multiworld, "re_gen_passthrough"): + break if previous_conn_num == len(connected_regions): fail_count += 1 - if fail_count >= 100: + if fail_count >= 500: raise Exception(f"Failed to pair regions. Check plando connections for {player_name} for loops.") else: fail_count = 0 @@ -438,6 +435,8 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: # connect dead ends to random non-dead ends # none of the key events are in dead ends, so we don't need to do gate_before_switch while len(dead_ends) > 0: + if hasattr(world.multiworld, "re_gen_passthrough"): + break portal1 = two_plus.pop() portal2 = dead_ends.pop() portal_pairs[portal1] = portal2 @@ -445,6 +444,8 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: # then randomly connect the remaining portals to each other # every region is accessible, so gate_before_switch is not necessary while len(two_plus) > 1: + if hasattr(world.multiworld, "re_gen_passthrough"): + break portal1 = two_plus.pop() portal2 = two_plus.pop() portal_pairs[portal1] = portal2 From dcdb281789a821ef42c9dec07539d754b6e915fe Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Mon, 26 Feb 2024 10:22:32 -0500 Subject: [PATCH 4/9] Update docs to detail plando connections --- worlds/tunic/docs/en_TUNIC.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/worlds/tunic/docs/en_TUNIC.md b/worlds/tunic/docs/en_TUNIC.md index e957f9eafaf..02799ca1e2f 100644 --- a/worlds/tunic/docs/en_TUNIC.md +++ b/worlds/tunic/docs/en_TUNIC.md @@ -62,3 +62,20 @@ Bombs, consumables (non-bomb ones), weapons, melee weapons (stick and sword), ke ## 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. + +## Is Connection Plando supported? +Yes. The host needs to enable it in their `host.yaml`, and the player's yaml needs to contain a plando_connections block. +Example: +``` +plando_connections: + - entrance: Stick House Entrance + exit: Stick House Exit + - entrance: Special Shop Exit + exit: Stairs to Top of the Mountain +``` +Notes: +- The `direction` field is not supported. Connections are always coupled. +- The Entrance Randomizer option must be enabled for it to work. +- There is no limit to the number of Shops hard-coded into place. +- If you have more than one shop in a scene, you may be wrong warped when exiting a shop. +- If you have a shop in every scene, and you have an odd number of shops, it will error out. From d386813e1a63043e0fc42b19f982e9fbacc47a1b Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Mon, 26 Feb 2024 10:25:22 -0500 Subject: [PATCH 5/9] Note where to find entrance names --- worlds/tunic/docs/en_TUNIC.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worlds/tunic/docs/en_TUNIC.md b/worlds/tunic/docs/en_TUNIC.md index 02799ca1e2f..3d0674a9dc3 100644 --- a/worlds/tunic/docs/en_TUNIC.md +++ b/worlds/tunic/docs/en_TUNIC.md @@ -74,8 +74,9 @@ plando_connections: exit: Stairs to Top of the Mountain ``` Notes: -- The `direction` field is not supported. Connections are always coupled. - The Entrance Randomizer option must be enabled for it to work. +- The `direction` field is not supported. Connections are always coupled. +- For a list of entrance names, check `er_data.py` in the TUNIC world folder or generate a game with the Entrance Randomizer option enabled and check the spoiler log. - There is no limit to the number of Shops hard-coded into place. - If you have more than one shop in a scene, you may be wrong warped when exiting a shop. - If you have a shop in every scene, and you have an odd number of shops, it will error out. From 4ab5a9a4e52157376412c9cb3aa20bbb867ee787 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Tue, 12 Mar 2024 19:25:49 -0400 Subject: [PATCH 6/9] Make entrance rando a TextChoice, preserving its previous functionality and now allowing for you to choose the seed --- worlds/tunic/__init__.py | 2 +- worlds/tunic/er_scripts.py | 12 ++++++++---- worlds/tunic/options.py | 7 +++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index b10ccd43af5..c4b1bbec8ea 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -226,7 +226,7 @@ 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": self.options.entrance_rando.value, + "entrance_rando": bool(self.options.entrance_rando.value), "Hexagon Quest Prayer": self.ability_unlocks["Pages 24-25 (Prayer)"], "Hexagon Quest Holy Cross": self.ability_unlocks["Pages 42-43 (Holy Cross)"], "Hexagon Quest Icebolt": self.ability_unlocks["Pages 52-53 (Icebolt)"], diff --git a/worlds/tunic/er_scripts.py b/worlds/tunic/er_scripts.py index ec624d9130b..291cd7b3310 100644 --- a/worlds/tunic/er_scripts.py +++ b/worlds/tunic/er_scripts.py @@ -5,6 +5,7 @@ dependent_regions_restricted, dependent_regions_nmg, dependent_regions_ur from .er_rules import set_er_region_rules from worlds.generic import PlandoConnection +from random import Random if TYPE_CHECKING: from . import TunicWorld @@ -361,8 +362,11 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: portal_pairs[portal1] = portal2 two_plus.remove(portal1) + random_object: Random = world.random + if world.options.entrance_rando.value != 1: + random_object = Random(world.options.entrance_rando.value) # we want to start by making sure every region is accessible - world.random.shuffle(two_plus) + random_object.shuffle(two_plus) check_success = 0 portal1 = None portal2 = None @@ -387,7 +391,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: if portal.region in connected_regions: # if there's risk of self-locking, start over if gate_before_switch(portal, two_plus): - world.random.shuffle(two_plus) + random_object.shuffle(two_plus) break portal1 = portal two_plus.remove(portal) @@ -400,7 +404,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: if portal.region not in connected_regions: # if there's risk of self-locking, shuffle and try again if gate_before_switch(portal, two_plus): - world.random.shuffle(two_plus) + random_object.shuffle(two_plus) break portal2 = portal two_plus.remove(portal) @@ -412,7 +416,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: connected_regions.update(add_dependent_regions(portal2.region, logic_rules)) portal_pairs[portal1] = portal2 check_success = 0 - world.random.shuffle(two_plus) + random_object.shuffle(two_plus) # for universal tracker, we want to skip shop gen if hasattr(world.multiworld, "re_gen_passthrough"): diff --git a/worlds/tunic/options.py b/worlds/tunic/options.py index f4790da3672..9d53848ffef 100644 --- a/worlds/tunic/options.py +++ b/worlds/tunic/options.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from Options import DefaultOnToggle, Toggle, StartInventoryPool, Choice, Range, PerGameCommonOptions +from Options import DefaultOnToggle, Toggle, StartInventoryPool, Choice, Range, TextChoice, PerGameCommonOptions class SwordProgression(DefaultOnToggle): @@ -104,11 +104,14 @@ class ExtraHexagonPercentage(Range): default = 50 -class EntranceRando(Toggle): +class EntranceRando(TextChoice): """Randomize the connections between scenes. A small, very lost fox on a big adventure.""" internal_name = "entrance_rando" display_name = "Entrance Rando" + option_false = 0 + option_true = 1 + default = 0 class FixedShop(Toggle): From 0f9cbe86b7ee7b405165c29ee4901c7d1df9d527 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Tue, 12 Mar 2024 19:30:11 -0400 Subject: [PATCH 7/9] ...and also update the option description to match --- worlds/tunic/options.py | 1 + 1 file changed, 1 insertion(+) diff --git a/worlds/tunic/options.py b/worlds/tunic/options.py index 9d53848ffef..4a528d75139 100644 --- a/worlds/tunic/options.py +++ b/worlds/tunic/options.py @@ -106,6 +106,7 @@ 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.""" internal_name = "entrance_rando" display_name = "Entrance Rando" From 51b905718dc04d3bddb6c3cae4e137c6e0a80a9f Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Tue, 12 Mar 2024 22:56:04 -0400 Subject: [PATCH 8/9] Add a couple of aliases --- worlds/tunic/options.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/worlds/tunic/options.py b/worlds/tunic/options.py index 4a528d75139..af6cbc1a602 100644 --- a/worlds/tunic/options.py +++ b/worlds/tunic/options.py @@ -110,8 +110,10 @@ class EntranceRando(TextChoice): A small, very lost fox on a big adventure.""" internal_name = "entrance_rando" display_name = "Entrance Rando" - option_false = 0 - option_true = 1 + alias_false = 0 + option_no = 0 + alias_true = 1 + option_yes = 1 default = 0 From d19332ff57ed8fb110451b966cdce691c3350d20 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Tue, 12 Mar 2024 23:53:11 -0400 Subject: [PATCH 9/9] Adjust test to not fail it --- worlds/tunic/test/test_access.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/tunic/test/test_access.py b/worlds/tunic/test/test_access.py index d74858bd27e..1c4f06d5046 100644 --- a/worlds/tunic/test/test_access.py +++ b/worlds/tunic/test/test_access.py @@ -59,7 +59,7 @@ def test_normal_goal(self): class TestER(TunicTestBase): - options = {options.EntranceRando.internal_name: options.EntranceRando.option_true, + options = {options.EntranceRando.internal_name: options.EntranceRando.option_yes, options.AbilityShuffling.internal_name: options.AbilityShuffling.option_true, options.HexagonQuest.internal_name: options.HexagonQuest.option_false}