TUNIC: Modify UT support to make a better pattern (#3860)

* Modify UT support to make a better pattern

* Handle keyerror for logic_rules option

* Missed self.passthrough value setting

* Less laziness for passthrough

* Remove extra newline

* Fix missing using_ut = True, also remove now unnecessary try except since 0.5.1 is out

* New UT thing, it goes in this PR because it's been open for 5 months for a very very tiny change
This commit is contained in:
Scipio Wright 2025-01-10 15:49:13 -05:00 committed by GitHub
parent 043ba418ec
commit 258ea10c52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 54 additions and 50 deletions

View File

@ -90,6 +90,10 @@ class TunicWorld(World):
item_link_locations: Dict[int, Dict[str, List[Tuple[int, str]]]] = {}
player_item_link_locations: Dict[str, List[Location]]
using_ut: bool # so we can check if we're using UT only once
passthrough: Dict[str, Any]
ut_can_gen_without_yaml = True # class var that tells it to ignore the player yaml
def generate_early(self) -> None:
if self.options.logic_rules >= LogicRules.option_no_major_glitches:
self.options.laurels_zips.value = LaurelsZips.option_true
@ -113,23 +117,28 @@ class TunicWorld(World):
# Universal tracker stuff, shouldn't do anything in standard gen
if hasattr(self.multiworld, "re_gen_passthrough"):
if "TUNIC" in self.multiworld.re_gen_passthrough:
passthrough = self.multiworld.re_gen_passthrough["TUNIC"]
self.options.start_with_sword.value = passthrough["start_with_sword"]
self.options.keys_behind_bosses.value = passthrough["keys_behind_bosses"]
self.options.sword_progression.value = passthrough["sword_progression"]
self.options.ability_shuffling.value = passthrough["ability_shuffling"]
self.options.laurels_zips.value = passthrough["laurels_zips"]
self.options.ice_grappling.value = passthrough["ice_grappling"]
self.options.ladder_storage.value = passthrough["ladder_storage"]
self.options.ladder_storage_without_items = passthrough["ladder_storage_without_items"]
self.options.lanternless.value = passthrough["lanternless"]
self.options.maskless.value = passthrough["maskless"]
self.options.hexagon_quest.value = passthrough["hexagon_quest"]
self.options.entrance_rando.value = passthrough["entrance_rando"]
self.options.shuffle_ladders.value = passthrough["shuffle_ladders"]
self.using_ut = True
self.passthrough = self.multiworld.re_gen_passthrough["TUNIC"]
self.options.start_with_sword.value = self.passthrough["start_with_sword"]
self.options.keys_behind_bosses.value = self.passthrough["keys_behind_bosses"]
self.options.sword_progression.value = self.passthrough["sword_progression"]
self.options.ability_shuffling.value = self.passthrough["ability_shuffling"]
self.options.laurels_zips.value = self.passthrough["laurels_zips"]
self.options.ice_grappling.value = self.passthrough["ice_grappling"]
self.options.ladder_storage.value = self.passthrough["ladder_storage"]
self.options.ladder_storage_without_items = self.passthrough["ladder_storage_without_items"]
self.options.lanternless.value = self.passthrough["lanternless"]
self.options.maskless.value = self.passthrough["maskless"]
self.options.hexagon_quest.value = self.passthrough["hexagon_quest"]
self.options.entrance_rando.value = self.passthrough["entrance_rando"]
self.options.shuffle_ladders.value = self.passthrough["shuffle_ladders"]
self.options.fixed_shop.value = self.options.fixed_shop.option_false
self.options.laurels_location.value = self.options.laurels_location.option_anywhere
self.options.combat_logic.value = passthrough["combat_logic"]
self.options.combat_logic.value = self.passthrough["combat_logic"]
else:
self.using_ut = False
else:
self.using_ut = False
@classmethod
def stage_generate_early(cls, multiworld: MultiWorld) -> None:
@ -331,12 +340,10 @@ class TunicWorld(World):
self.ability_unlocks = randomize_ability_unlocks(self.random, self.options)
# stuff for universal tracker support, can be ignored for standard gen
if hasattr(self.multiworld, "re_gen_passthrough"):
if "TUNIC" in self.multiworld.re_gen_passthrough:
passthrough = self.multiworld.re_gen_passthrough["TUNIC"]
self.ability_unlocks["Pages 24-25 (Prayer)"] = passthrough["Hexagon Quest Prayer"]
self.ability_unlocks["Pages 42-43 (Holy Cross)"] = passthrough["Hexagon Quest Holy Cross"]
self.ability_unlocks["Pages 52-53 (Icebolt)"] = passthrough["Hexagon Quest Icebolt"]
if self.using_ut:
self.ability_unlocks["Pages 24-25 (Prayer)"] = self.passthrough["Hexagon Quest Prayer"]
self.ability_unlocks["Pages 42-43 (Holy Cross)"] = self.passthrough["Hexagon Quest Holy Cross"]
self.ability_unlocks["Pages 52-53 (Icebolt)"] = self.passthrough["Hexagon Quest Icebolt"]
# Ladders and Combat Logic uses ER rules with vanilla connections for easier maintenance
if self.options.entrance_rando or self.options.shuffle_ladders or self.options.combat_logic:

View File

@ -177,7 +177,7 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal
logic_tricks: Tuple[bool, int, int] = (laurels_zips, ice_grappling, ladder_storage)
# marking that you don't immediately have laurels
if laurels_location == "10_fairies" and not hasattr(world.multiworld, "re_gen_passthrough"):
if laurels_location == "10_fairies" and not world.using_ut:
has_laurels = False
shop_count = 6
@ -191,9 +191,8 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal
break
# If using Universal Tracker, restore portal_map. Could be cleaner, but it does not matter for UT even a little bit
if hasattr(world.multiworld, "re_gen_passthrough"):
if "TUNIC" in world.multiworld.re_gen_passthrough:
portal_map = portal_mapping.copy()
if world.using_ut:
portal_map = portal_mapping.copy()
# create separate lists for dead ends and non-dead ends
for portal in portal_map:
@ -232,25 +231,24 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal
plando_connections = world.seed_groups[world.options.entrance_rando.value]["plando"]
# universal tracker support stuff, don't need to care about region dependency
if hasattr(world.multiworld, "re_gen_passthrough"):
if "TUNIC" in world.multiworld.re_gen_passthrough:
plando_connections.clear()
# universal tracker stuff, won't do anything in normal gen
for portal1, portal2 in world.multiworld.re_gen_passthrough["TUNIC"]["Entrance Rando"].items():
portal_name1 = ""
portal_name2 = ""
if world.using_ut:
plando_connections.clear()
# universal tracker stuff, won't do anything in normal gen
for portal1, portal2 in world.passthrough["Entrance Rando"].items():
portal_name1 = ""
portal_name2 = ""
for portal in portal_mapping:
if portal.scene_destination() == portal1:
portal_name1 = portal.name
# connected_regions.update(add_dependent_regions(portal.region, logic_rules))
if portal.scene_destination() == portal2:
portal_name2 = portal.name
# connected_regions.update(add_dependent_regions(portal.region, logic_rules))
# shops have special handling
if not portal_name2 and portal2 == "Shop, Previous Region_":
portal_name2 = "Shop Portal"
plando_connections.append(PlandoConnection(portal_name1, portal_name2, "both"))
for portal in portal_mapping:
if portal.scene_destination() == portal1:
portal_name1 = portal.name
# connected_regions.update(add_dependent_regions(portal.region, logic_rules))
if portal.scene_destination() == portal2:
portal_name2 = portal.name
# connected_regions.update(add_dependent_regions(portal.region, logic_rules))
# shops have special handling
if not portal_name2 and portal2 == "Shop, Previous Region_":
portal_name2 = "Shop Portal"
plando_connections.append(PlandoConnection(portal_name1, portal_name2, "both"))
non_dead_end_regions = set()
for region_name, region_info in world.er_regions.items():
@ -362,7 +360,7 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal
# if we have plando connections, our connected regions may change somewhat
connected_regions = update_reachable_regions(connected_regions, traversal_reqs, has_laurels, logic_tricks)
if fixed_shop and not hasattr(world.multiworld, "re_gen_passthrough"):
if fixed_shop and not world.using_ut:
portal1 = None
for portal in two_plus:
if portal.scene_destination() == "Overworld Redux, Windmill_":
@ -392,7 +390,7 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal
fail_count = 0
while len(connected_regions) < len(non_dead_end_regions):
# if this is universal tracker, just break immediately and move on
if hasattr(world.multiworld, "re_gen_passthrough"):
if world.using_ut:
break
# 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
@ -445,9 +443,8 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal
random_object.shuffle(two_plus)
# for universal tracker, we want to skip shop gen
if hasattr(world.multiworld, "re_gen_passthrough"):
if "TUNIC" in world.multiworld.re_gen_passthrough:
shop_count = 0
if world.using_ut:
shop_count = 0
for i in range(shop_count):
portal1 = two_plus.pop()
@ -462,7 +459,7 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[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"):
if world.using_ut:
break
portal1 = two_plus.pop()
portal2 = dead_ends.pop()
@ -470,7 +467,7 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[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"):
if world.using_ut:
break
portal1 = two_plus.pop()
portal2 = two_plus.pop()