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]]]] = {} item_link_locations: Dict[int, Dict[str, List[Tuple[int, str]]]] = {}
player_item_link_locations: Dict[str, List[Location]] 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: def generate_early(self) -> None:
if self.options.logic_rules >= LogicRules.option_no_major_glitches: if self.options.logic_rules >= LogicRules.option_no_major_glitches:
self.options.laurels_zips.value = LaurelsZips.option_true 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 # Universal tracker stuff, shouldn't do anything in standard gen
if hasattr(self.multiworld, "re_gen_passthrough"): if hasattr(self.multiworld, "re_gen_passthrough"):
if "TUNIC" in self.multiworld.re_gen_passthrough: if "TUNIC" in self.multiworld.re_gen_passthrough:
passthrough = self.multiworld.re_gen_passthrough["TUNIC"] self.using_ut = True
self.options.start_with_sword.value = passthrough["start_with_sword"] self.passthrough = self.multiworld.re_gen_passthrough["TUNIC"]
self.options.keys_behind_bosses.value = passthrough["keys_behind_bosses"] self.options.start_with_sword.value = self.passthrough["start_with_sword"]
self.options.sword_progression.value = passthrough["sword_progression"] self.options.keys_behind_bosses.value = self.passthrough["keys_behind_bosses"]
self.options.ability_shuffling.value = passthrough["ability_shuffling"] self.options.sword_progression.value = self.passthrough["sword_progression"]
self.options.laurels_zips.value = passthrough["laurels_zips"] self.options.ability_shuffling.value = self.passthrough["ability_shuffling"]
self.options.ice_grappling.value = passthrough["ice_grappling"] self.options.laurels_zips.value = self.passthrough["laurels_zips"]
self.options.ladder_storage.value = passthrough["ladder_storage"] self.options.ice_grappling.value = self.passthrough["ice_grappling"]
self.options.ladder_storage_without_items = passthrough["ladder_storage_without_items"] self.options.ladder_storage.value = self.passthrough["ladder_storage"]
self.options.lanternless.value = passthrough["lanternless"] self.options.ladder_storage_without_items = self.passthrough["ladder_storage_without_items"]
self.options.maskless.value = passthrough["maskless"] self.options.lanternless.value = self.passthrough["lanternless"]
self.options.hexagon_quest.value = passthrough["hexagon_quest"] self.options.maskless.value = self.passthrough["maskless"]
self.options.entrance_rando.value = passthrough["entrance_rando"] self.options.hexagon_quest.value = self.passthrough["hexagon_quest"]
self.options.shuffle_ladders.value = passthrough["shuffle_ladders"] 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.fixed_shop.value = self.options.fixed_shop.option_false
self.options.laurels_location.value = self.options.laurels_location.option_anywhere 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 @classmethod
def stage_generate_early(cls, multiworld: MultiWorld) -> None: 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) self.ability_unlocks = randomize_ability_unlocks(self.random, self.options)
# stuff for universal tracker support, can be ignored for standard gen # stuff for universal tracker support, can be ignored for standard gen
if hasattr(self.multiworld, "re_gen_passthrough"): if self.using_ut:
if "TUNIC" in self.multiworld.re_gen_passthrough: self.ability_unlocks["Pages 24-25 (Prayer)"] = self.passthrough["Hexagon Quest Prayer"]
passthrough = self.multiworld.re_gen_passthrough["TUNIC"] self.ability_unlocks["Pages 42-43 (Holy Cross)"] = self.passthrough["Hexagon Quest Holy Cross"]
self.ability_unlocks["Pages 24-25 (Prayer)"] = passthrough["Hexagon Quest Prayer"] self.ability_unlocks["Pages 52-53 (Icebolt)"] = self.passthrough["Hexagon Quest Icebolt"]
self.ability_unlocks["Pages 42-43 (Holy Cross)"] = passthrough["Hexagon Quest Holy Cross"]
self.ability_unlocks["Pages 52-53 (Icebolt)"] = passthrough["Hexagon Quest Icebolt"]
# Ladders and Combat Logic uses ER rules with vanilla connections for easier maintenance # 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: 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) logic_tricks: Tuple[bool, int, int] = (laurels_zips, ice_grappling, ladder_storage)
# marking that you don't immediately have laurels # 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 has_laurels = False
shop_count = 6 shop_count = 6
@ -191,9 +191,8 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal
break break
# If using Universal Tracker, restore portal_map. Could be cleaner, but it does not matter for UT even a little bit # 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 world.using_ut:
if "TUNIC" in world.multiworld.re_gen_passthrough: portal_map = portal_mapping.copy()
portal_map = portal_mapping.copy()
# create separate lists for dead ends and non-dead ends # create separate lists for dead ends and non-dead ends
for portal in portal_map: 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"] plando_connections = world.seed_groups[world.options.entrance_rando.value]["plando"]
# universal tracker support stuff, don't need to care about region dependency # universal tracker support stuff, don't need to care about region dependency
if hasattr(world.multiworld, "re_gen_passthrough"): if world.using_ut:
if "TUNIC" in world.multiworld.re_gen_passthrough: plando_connections.clear()
plando_connections.clear() # universal tracker stuff, won't do anything in normal gen
# universal tracker stuff, won't do anything in normal gen for portal1, portal2 in world.passthrough["Entrance Rando"].items():
for portal1, portal2 in world.multiworld.re_gen_passthrough["TUNIC"]["Entrance Rando"].items(): portal_name1 = ""
portal_name1 = "" portal_name2 = ""
portal_name2 = ""
for portal in portal_mapping: for portal in portal_mapping:
if portal.scene_destination() == portal1: if portal.scene_destination() == portal1:
portal_name1 = portal.name portal_name1 = portal.name
# connected_regions.update(add_dependent_regions(portal.region, logic_rules)) # connected_regions.update(add_dependent_regions(portal.region, logic_rules))
if portal.scene_destination() == portal2: if portal.scene_destination() == portal2:
portal_name2 = portal.name portal_name2 = portal.name
# connected_regions.update(add_dependent_regions(portal.region, logic_rules)) # connected_regions.update(add_dependent_regions(portal.region, logic_rules))
# shops have special handling # shops have special handling
if not portal_name2 and portal2 == "Shop, Previous Region_": if not portal_name2 and portal2 == "Shop, Previous Region_":
portal_name2 = "Shop Portal" portal_name2 = "Shop Portal"
plando_connections.append(PlandoConnection(portal_name1, portal_name2, "both")) plando_connections.append(PlandoConnection(portal_name1, portal_name2, "both"))
non_dead_end_regions = set() non_dead_end_regions = set()
for region_name, region_info in world.er_regions.items(): 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 # if we have plando connections, our connected regions may change somewhat
connected_regions = update_reachable_regions(connected_regions, traversal_reqs, has_laurels, logic_tricks) 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 portal1 = None
for portal in two_plus: for portal in two_plus:
if portal.scene_destination() == "Overworld Redux, Windmill_": 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 fail_count = 0
while len(connected_regions) < len(non_dead_end_regions): while len(connected_regions) < len(non_dead_end_regions):
# if this is universal tracker, just break immediately and move on # if this is universal tracker, just break immediately and move on
if hasattr(world.multiworld, "re_gen_passthrough"): if world.using_ut:
break break
# if the connected regions length stays unchanged for too long, it's stuck in a loop # 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 # 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) random_object.shuffle(two_plus)
# for universal tracker, we want to skip shop gen # for universal tracker, we want to skip shop gen
if hasattr(world.multiworld, "re_gen_passthrough"): if world.using_ut:
if "TUNIC" in world.multiworld.re_gen_passthrough: shop_count = 0
shop_count = 0
for i in range(shop_count): for i in range(shop_count):
portal1 = two_plus.pop() 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 # 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 # none of the key events are in dead ends, so we don't need to do gate_before_switch
while len(dead_ends) > 0: while len(dead_ends) > 0:
if hasattr(world.multiworld, "re_gen_passthrough"): if world.using_ut:
break break
portal1 = two_plus.pop() portal1 = two_plus.pop()
portal2 = dead_ends.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 # then randomly connect the remaining portals to each other
# every region is accessible, so gate_before_switch is not necessary # every region is accessible, so gate_before_switch is not necessary
while len(two_plus) > 1: while len(two_plus) > 1:
if hasattr(world.multiworld, "re_gen_passthrough"): if world.using_ut:
break break
portal1 = two_plus.pop() portal1 = two_plus.pop()
portal2 = two_plus.pop() portal2 = two_plus.pop()