TUNIC: Universal Tracker Support Update (#2786)
Adds better support for the Universal Tracker (see its channel in the future game design section). This does absolutely nothing regarding standard gen, just adds some checks for an attribute that only exists when UT is being used.
This commit is contained in:
parent
4d9202537c
commit
539307cf0b
|
@ -59,6 +59,20 @@ class TunicWorld(World):
|
|||
er_portal_hints: Dict[int, str]
|
||||
|
||||
def generate_early(self) -> None:
|
||||
# Universal tracker stuff, shouldn't do anything in standard gen
|
||||
if hasattr(self.multiworld, "re_gen_passthrough"):
|
||||
if "TUNIC" in self.multiworld.re_gen_passthrough:
|
||||
passthrough = self.multiworld.re_gen_passthrough["TUNIC"]
|
||||
self.options.start_with_sword.value = passthrough["start_with_sword"]
|
||||
self.options.keys_behind_bosses.value = passthrough["keys_behind_bosses"]
|
||||
self.options.sword_progression.value = passthrough["sword_progression"]
|
||||
self.options.ability_shuffling.value = passthrough["ability_shuffling"]
|
||||
self.options.logic_rules.value = passthrough["logic_rules"]
|
||||
self.options.lanternless.value = passthrough["lanternless"]
|
||||
self.options.maskless.value = passthrough["maskless"]
|
||||
self.options.hexagon_quest.value = passthrough["hexagon_quest"]
|
||||
self.options.entrance_rando.value = passthrough["entrance_rando"]
|
||||
|
||||
if self.options.start_with_sword and "Sword" not in self.options.start_inventory:
|
||||
self.options.start_inventory.value["Sword"] = 1
|
||||
|
||||
|
@ -150,10 +164,20 @@ class TunicWorld(World):
|
|||
self.tunic_portal_pairs = {}
|
||||
self.er_portal_hints = {}
|
||||
self.ability_unlocks = randomize_ability_unlocks(self.random, self.options)
|
||||
|
||||
# stuff for universal tracker support, can be ignored for standard gen
|
||||
if hasattr(self.multiworld, "re_gen_passthrough"):
|
||||
if "TUNIC" in self.multiworld.re_gen_passthrough:
|
||||
passthrough = self.multiworld.re_gen_passthrough["TUNIC"]
|
||||
self.ability_unlocks["Pages 24-25 (Prayer)"] = passthrough["Hexagon Quest Prayer"]
|
||||
self.ability_unlocks["Pages 42-43 (Holy Cross)"] = passthrough["Hexagon Quest Holy Cross"]
|
||||
self.ability_unlocks["Pages 52-53 (Ice Rod)"] = passthrough["Hexagon Quest Ice Rod"]
|
||||
|
||||
if self.options.entrance_rando:
|
||||
portal_pairs, portal_hints = create_er_regions(self)
|
||||
for portal1, portal2 in portal_pairs.items():
|
||||
self.tunic_portal_pairs[portal1.scene_destination()] = portal2.scene_destination()
|
||||
|
||||
self.er_portal_hints = portal_hints
|
||||
|
||||
else:
|
||||
|
@ -199,6 +223,9 @@ class TunicWorld(World):
|
|||
"ability_shuffling": self.options.ability_shuffling.value,
|
||||
"hexagon_quest": self.options.hexagon_quest.value,
|
||||
"fool_traps": self.options.fool_traps.value,
|
||||
"logic_rules": self.options.logic_rules.value,
|
||||
"lanternless": self.options.lanternless.value,
|
||||
"maskless": self.options.maskless.value,
|
||||
"entrance_rando": self.options.entrance_rando.value,
|
||||
"Hexagon Quest Prayer": self.ability_unlocks["Pages 24-25 (Prayer)"],
|
||||
"Hexagon Quest Holy Cross": self.ability_unlocks["Pages 42-43 (Holy Cross)"],
|
||||
|
@ -236,44 +263,7 @@ class TunicWorld(World):
|
|||
return slot_data
|
||||
|
||||
# for the universal tracker, doesn't get called in standard gen
|
||||
def interpret_slot_data(self, slot_data: Dict[str, Any]) -> None:
|
||||
# bypassing random yaml settings
|
||||
self.options.start_with_sword.value = slot_data["start_with_sword"]
|
||||
self.options.keys_behind_bosses.value = slot_data["keys_behind_bosses"]
|
||||
self.options.sword_progression.value = slot_data["sword_progression"]
|
||||
self.options.ability_shuffling.value = slot_data["ability_shuffling"]
|
||||
self.options.hexagon_quest.value = slot_data["hexagon_quest"]
|
||||
self.ability_unlocks["Pages 24-25 (Prayer)"] = slot_data["Hexagon Quest Prayer"]
|
||||
self.ability_unlocks["Pages 42-43 (Holy Cross)"] = slot_data["Hexagon Quest Holy Cross"]
|
||||
self.ability_unlocks["Pages 52-53 (Ice Rod)"] = slot_data["Hexagon Quest Ice Rod"]
|
||||
|
||||
# swapping entrances around so the mapping matches what was generated
|
||||
if slot_data["entrance_rando"]:
|
||||
from BaseClasses import Entrance
|
||||
from .er_data import portal_mapping
|
||||
entrance_dict: Dict[str, Entrance] = {entrance.name: entrance
|
||||
for region in self.multiworld.get_regions(self.player)
|
||||
for entrance in region.entrances}
|
||||
slot_portals: Dict[str, str] = slot_data["Entrance Rando"]
|
||||
for portal1, portal2 in slot_portals.items():
|
||||
portal_name1: str = ""
|
||||
portal_name2: str = ""
|
||||
entrance1 = None
|
||||
entrance2 = None
|
||||
for portal in portal_mapping:
|
||||
if portal.scene_destination() == portal1:
|
||||
portal_name1 = portal.name
|
||||
if portal.scene_destination() == portal2:
|
||||
portal_name2 = portal.name
|
||||
|
||||
for entrance_name, entrance in entrance_dict.items():
|
||||
if entrance_name.startswith(portal_name1):
|
||||
entrance1 = entrance
|
||||
if entrance_name.startswith(portal_name2):
|
||||
entrance2 = entrance
|
||||
if entrance1 is None:
|
||||
raise Exception("entrance1 not found, portal1 is " + portal1)
|
||||
if entrance2 is None:
|
||||
raise Exception("entrance2 not found, portal2 is " + portal2)
|
||||
entrance1.connected_region = entrance2.parent_region
|
||||
entrance2.connected_region = entrance1.parent_region
|
||||
@staticmethod
|
||||
def interpret_slot_data(slot_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
# returning slot_data so it regens, giving it back in multiworld.re_gen_passthrough
|
||||
return slot_data
|
||||
|
|
|
@ -730,7 +730,7 @@ for p1, p2 in hallways_ur.items():
|
|||
|
||||
# the key is the region you have, the value is the regions you get for having that region
|
||||
# this is mostly so we don't have to do something overly complex to get this information
|
||||
dependent_regions: Dict[Tuple[str, ...], List[str]] = {
|
||||
dependent_regions_restricted: Dict[Tuple[str, ...], List[str]] = {
|
||||
("Overworld", "Overworld Belltower", "Overworld Swamp Upper Entry", "Overworld Special Shop Entry",
|
||||
"Overworld West Garden Laurels Entry", "Overworld Southeast Cross Door", "Overworld Temple Door",
|
||||
"Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal"):
|
||||
|
|
|
@ -2,8 +2,9 @@ from typing import Dict, List, Set, Tuple, TYPE_CHECKING
|
|||
from BaseClasses import Region, ItemClassification, Item, Location
|
||||
from .locations import location_table
|
||||
from .er_data import Portal, tunic_er_regions, portal_mapping, hallway_helper, hallway_helper_ur, \
|
||||
dependent_regions, dependent_regions_nmg, dependent_regions_ur
|
||||
dependent_regions_restricted, dependent_regions_nmg, dependent_regions_ur
|
||||
from .er_rules import set_er_region_rules
|
||||
from worlds.generic import PlandoConnection
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import TunicWorld
|
||||
|
@ -22,12 +23,17 @@ def create_er_regions(world: "TunicWorld") -> Tuple[Dict[Portal, Portal], Dict[i
|
|||
portal_pairs: Dict[Portal, Portal] = pair_portals(world)
|
||||
logic_rules = world.options.logic_rules
|
||||
|
||||
# output the entrances to the spoiler log here for convenience
|
||||
for portal1, portal2 in portal_pairs.items():
|
||||
world.multiworld.spoiler.set_entrance(portal1.name, portal2.name, "both", world.player)
|
||||
|
||||
# check if a portal leads to a hallway. if it does, update the hint text accordingly
|
||||
def hint_helper(portal: Portal, hint_string: str = "") -> str:
|
||||
# start by setting it as the name of the portal, for the case we're not using the hallway helper
|
||||
if hint_string == "":
|
||||
hint_string = portal.name
|
||||
|
||||
# unrestricted has fewer hallways, like the well rail
|
||||
if logic_rules == "unrestricted":
|
||||
hallways = hallway_helper_ur
|
||||
else:
|
||||
|
@ -69,6 +75,7 @@ def create_er_regions(world: "TunicWorld") -> Tuple[Dict[Portal, Portal], Dict[i
|
|||
return hint_string
|
||||
|
||||
# create our regions, give them hint text if they're in a spot where it makes sense to
|
||||
# we're limiting which ones get hints so that it still gets that ER feel with a little less BS
|
||||
for region_name, region_data in tunic_er_regions.items():
|
||||
hint_text = "error"
|
||||
if region_data.hint == 1:
|
||||
|
@ -90,7 +97,7 @@ def create_er_regions(world: "TunicWorld") -> Tuple[Dict[Portal, Portal], Dict[i
|
|||
break
|
||||
regions[region_name] = Region(region_name, world.player, world.multiworld, hint_text)
|
||||
elif region_data.hint == 3:
|
||||
# only the west garden portal item for now
|
||||
# west garden portal item is at a dead end in restricted, otherwise just in west garden
|
||||
if region_name == "West Garden Portal Item":
|
||||
if world.options.logic_rules:
|
||||
for portal1, portal2 in portal_pairs.items():
|
||||
|
@ -178,9 +185,17 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
|||
portal_pairs: Dict[Portal, Portal] = {}
|
||||
dead_ends: List[Portal] = []
|
||||
two_plus: List[Portal] = []
|
||||
plando_connections: List[PlandoConnection] = []
|
||||
fixed_shop = False
|
||||
logic_rules = world.options.logic_rules.value
|
||||
|
||||
if not logic_rules:
|
||||
dependent_regions = dependent_regions_restricted
|
||||
elif logic_rules == 1:
|
||||
dependent_regions = dependent_regions_nmg
|
||||
else:
|
||||
dependent_regions = dependent_regions_ur
|
||||
|
||||
# create separate lists for dead ends and non-dead ends
|
||||
if logic_rules:
|
||||
for portal in portal_mapping:
|
||||
|
@ -200,8 +215,46 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
|||
start_region = "Overworld"
|
||||
connected_regions.update(add_dependent_regions(start_region, logic_rules))
|
||||
|
||||
# universal tracker support stuff, don't need to care about region dependency
|
||||
if hasattr(world.multiworld, "re_gen_passthrough"):
|
||||
if "TUNIC" in world.multiworld.re_gen_passthrough:
|
||||
# universal tracker stuff, won't do anything in normal gen
|
||||
for portal1, portal2 in world.multiworld.re_gen_passthrough["TUNIC"]["Entrance Rando"].items():
|
||||
portal_name1 = ""
|
||||
portal_name2 = ""
|
||||
|
||||
# skip this if 10 fairies laurels location is on, it can be handled normally
|
||||
if portal1 == "Overworld Redux, Waterfall_" and portal2 == "Waterfall, Overworld Redux_" \
|
||||
and world.options.laurels_location == "10_fairies":
|
||||
continue
|
||||
|
||||
for portal in portal_mapping:
|
||||
if portal.scene_destination() == portal1:
|
||||
portal_name1 = portal.name
|
||||
# connected_regions.update(add_dependent_regions(portal.region, logic_rules))
|
||||
if portal.scene_destination() == portal2:
|
||||
portal_name2 = portal.name
|
||||
# connected_regions.update(add_dependent_regions(portal.region, logic_rules))
|
||||
# shops have special handling
|
||||
if not portal_name2 and portal2 == "Shop, Previous Region_":
|
||||
portal_name2 = "Shop Portal"
|
||||
plando_connections.append(PlandoConnection(portal_name1, portal_name2, "both"))
|
||||
|
||||
if plando_connections:
|
||||
portal_pairs, dependent_regions, dead_ends, two_plus = \
|
||||
create_plando_connections(plando_connections, dependent_regions, dead_ends, two_plus)
|
||||
|
||||
# if we have plando connections, our connected regions may change somewhat
|
||||
while True:
|
||||
test1 = len(connected_regions)
|
||||
for region in connected_regions.copy():
|
||||
connected_regions.update(add_dependent_regions(region, logic_rules))
|
||||
test2 = len(connected_regions)
|
||||
if test1 == test2:
|
||||
break
|
||||
|
||||
# need to plando fairy cave, or it could end up laurels locked
|
||||
# fix this later to be random? probably not?
|
||||
# fix this later to be random after adding some item logic to dependent regions
|
||||
if world.options.laurels_location == "10_fairies":
|
||||
portal1 = None
|
||||
portal2 = None
|
||||
|
@ -217,7 +270,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
|||
two_plus.remove(portal1)
|
||||
dead_ends.remove(portal2)
|
||||
|
||||
if world.options.fixed_shop:
|
||||
if world.options.fixed_shop and not hasattr(world.multiworld, "re_gen_passthrough"):
|
||||
fixed_shop = True
|
||||
portal1 = None
|
||||
for portal in two_plus:
|
||||
|
@ -283,6 +336,11 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
|||
shop_count = 1
|
||||
shop_scenes.add("Overworld Redux")
|
||||
|
||||
# for universal tracker, we want to skip shop gen
|
||||
if hasattr(world.multiworld, "re_gen_passthrough"):
|
||||
if "TUNIC" in world.multiworld.re_gen_passthrough:
|
||||
shop_count = 0
|
||||
|
||||
for i in range(shop_count):
|
||||
portal1 = None
|
||||
for portal in two_plus:
|
||||
|
@ -311,10 +369,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
|
|||
portal_pairs[portal1] = portal2
|
||||
|
||||
if len(two_plus) == 1:
|
||||
raise Exception("two plus had an odd number of portals, investigate this")
|
||||
|
||||
for portal1, portal2 in portal_pairs.items():
|
||||
world.multiworld.spoiler.set_entrance(portal1.name, portal2.name, "both", world.player)
|
||||
raise Exception("two plus had an odd number of portals, investigate this. last portal is " + two_plus[0].name)
|
||||
|
||||
return portal_pairs
|
||||
|
||||
|
@ -331,10 +386,11 @@ def create_randomized_entrances(portal_pairs: Dict[Portal, Portal], regions: Dic
|
|||
|
||||
|
||||
# loop through the static connections, return regions you can reach from this region
|
||||
# todo: refactor to take region_name and dependent_regions
|
||||
def add_dependent_regions(region_name: str, logic_rules: int) -> Set[str]:
|
||||
region_set = set()
|
||||
if not logic_rules:
|
||||
regions_to_add = dependent_regions
|
||||
regions_to_add = dependent_regions_restricted
|
||||
elif logic_rules == 1:
|
||||
regions_to_add = dependent_regions_nmg
|
||||
else:
|
||||
|
@ -451,3 +507,65 @@ def gate_before_switch(check_portal: Portal, two_plus: List[Portal]) -> bool:
|
|||
|
||||
# false means you're good to place the portal
|
||||
return False
|
||||
|
||||
|
||||
# this is for making the connections themselves
|
||||
def create_plando_connections(plando_connections: List[PlandoConnection],
|
||||
dependent_regions: Dict[Tuple[str, ...], List[str]], dead_ends: List[Portal],
|
||||
two_plus: List[Portal]) \
|
||||
-> Tuple[Dict[Portal, Portal], Dict[Tuple[str, ...], List[str]], List[Portal], List[Portal]]:
|
||||
|
||||
portal_pairs: Dict[Portal, Portal] = {}
|
||||
shop_num = 1
|
||||
for connection in plando_connections:
|
||||
p_entrance = connection.entrance
|
||||
p_exit = connection.exit
|
||||
|
||||
portal1 = None
|
||||
portal2 = None
|
||||
|
||||
# search two_plus for both at once
|
||||
for portal in two_plus:
|
||||
if p_entrance == portal.name:
|
||||
portal1 = portal
|
||||
if p_exit == portal.name:
|
||||
portal2 = portal
|
||||
|
||||
# search dead_ends individually since we can't really remove items from two_plus during the loop
|
||||
if not portal1:
|
||||
for portal in dead_ends:
|
||||
if p_entrance == portal.name:
|
||||
portal1 = portal
|
||||
break
|
||||
dead_ends.remove(portal1)
|
||||
else:
|
||||
two_plus.remove(portal1)
|
||||
|
||||
if not portal2:
|
||||
for portal in dead_ends:
|
||||
if p_exit == portal.name:
|
||||
portal2 = portal
|
||||
break
|
||||
if p_exit == "Shop Portal":
|
||||
portal2 = Portal(name="Shop Portal", region=f"Shop Entrance {shop_num}", destination="Previous Region_")
|
||||
shop_num += 1
|
||||
else:
|
||||
dead_ends.remove(portal2)
|
||||
else:
|
||||
two_plus.remove(portal2)
|
||||
|
||||
if not portal1:
|
||||
raise Exception("could not find entrance named " + p_entrance + " for Tunic player's plando")
|
||||
if not portal2:
|
||||
raise Exception("could not find entrance named " + p_exit + " for Tunic player's plando")
|
||||
|
||||
portal_pairs[portal1] = portal2
|
||||
|
||||
# update dependent regions based on the plando'd connections, to make sure the portals connect well, logically
|
||||
for origins, destinations in dependent_regions.items():
|
||||
if portal1.region in origins:
|
||||
destinations.append(portal2.region)
|
||||
if portal2.region in origins:
|
||||
destinations.append(portal1.region)
|
||||
|
||||
return portal_pairs, dependent_regions, dead_ends, two_plus
|
||||
|
|
Loading…
Reference in New Issue