Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TUNIC: Better seed groups for Entrance Rando #2998

Merged
merged 34 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4ee6b9e
Update entrance rando description to discuss seed groups
ScipioWright Mar 19, 2024
f6bb6b2
Starting off, setting up some names
ScipioWright Mar 19, 2024
c626f00
It lives
ScipioWright Mar 20, 2024
f3090dd
Some preliminary plando connection handling, probably has errors
ScipioWright Mar 20, 2024
d8ed4a1
Add missed comma
ScipioWright Mar 20, 2024
ca4ecaa
if -> elif
ScipioWright Mar 20, 2024
58ea87e
I think this is working properly to handle plando connections
ScipioWright Mar 21, 2024
cd72e38
Update comments
ScipioWright Mar 21, 2024
661392b
Merge branch 'main' into tunc-seed-groups
ScipioWright Mar 21, 2024
2f0e494
Fix up shop -> shop portal stuff
ScipioWright Mar 21, 2024
10740ff
Add back comma that got removed for no reason in the ladder PR
ScipioWright Mar 21, 2024
6f915cf
Remove unnecessary if else
ScipioWright Mar 21, 2024
a4efc31
add back the actually necessary if but not the else
ScipioWright Mar 21, 2024
2b25017
okay they were both necessary
ScipioWright Mar 21, 2024
0e02674
Update entrance rando description
ScipioWright Mar 21, 2024
7b876c1
blasphemy
ScipioWright Mar 24, 2024
2892409
Rename other instances of tunc -> tunic
ScipioWright Mar 24, 2024
4bc2039
Merge branch 'main' into tunc-seed-groups
ScipioWright Mar 28, 2024
59cb87f
Update per Vi's review (thank you)
ScipioWright Mar 29, 2024
bb24e75
Fix a not that shouldn't have been
ScipioWright Mar 29, 2024
1b3c857
Rearrange, update per Vi's comments (thank you)
ScipioWright Mar 29, 2024
c69d121
Fix indent
ScipioWright Mar 29, 2024
5afdeda
Add a .value
ScipioWright Mar 29, 2024
35b3fbc
Add .values
ScipioWright Mar 29, 2024
11eac12
Fix bad comparison
ScipioWright Mar 29, 2024
7f9b6cd
Add a not that was supposed to be there
ScipioWright Mar 29, 2024
d108446
Replace another isinstance
ScipioWright Mar 29, 2024
729887f
Merge branch 'main' into tunc-seed-groups
ScipioWright Apr 2, 2024
c73407b
Revise option description
ScipioWright Apr 2, 2024
bac8f4e
Merge branch 'main' into tunc-seed-groups
ScipioWright Apr 14, 2024
e17dcc2
Merge branch 'main' into tunc-seed-groups
ScipioWright Apr 14, 2024
190d1bb
Fix per Kaito's comment
ScipioWright Apr 19, 2024
8d07305
Merge branch 'main' into tunc-seed-groups
ScipioWright Apr 21, 2024
9e53923
Merge branch 'main' into tunc-seed-groups
ScipioWright May 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 73 additions & 2 deletions worlds/tunic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Dict, List, Any
from typing import Dict, List, Any, Tuple, TypedDict

from BaseClasses import Region, Location, Item, Tutorial, ItemClassification
from BaseClasses import Region, Location, Item, Tutorial, ItemClassification, MultiWorld
from .items import item_name_to_id, item_table, item_name_groups, fool_tiers, filler_items, slot_data_item_names
from .locations import location_table, location_name_groups, location_name_to_id, hexagon_locations
from .rules import set_location_rules, set_region_rules, randomize_ability_unlocks, gold_hexagon
Expand All @@ -10,6 +10,7 @@
from .er_data import portal_mapping
from .options import TunicOptions
from worlds.AutoWorld import WebWorld, World
from worlds.generic import PlandoConnection
from decimal import Decimal, ROUND_HALF_UP


Expand All @@ -36,6 +37,13 @@ class TunicLocation(Location):
game: str = "TUNIC"


class SeedGroup(TypedDict):
logic_rules: int # logic rules value
laurels_at_10_fairies: bool # laurels location value
fixed_shop: bool # fixed shop value
plando: List[PlandoConnection] # consolidated list of plando connections for the seed group


class TunicWorld(World):
"""
Explore a land filled with lost legends, ancient powers, and ferocious monsters in TUNIC, an isometric action game
Expand All @@ -57,8 +65,21 @@ class TunicWorld(World):
slot_data_items: List[TunicItem]
tunic_portal_pairs: Dict[str, str]
er_portal_hints: Dict[int, str]
seed_groups: Dict[str, SeedGroup] = {}

def generate_early(self) -> None:
if self.multiworld.plando_connections[self.player]:
for index, cxn in enumerate(self.multiworld.plando_connections[self.player]):
# making shops second to simplify other things later
if cxn.entrance.startswith("Shop"):
replacement = PlandoConnection(cxn.exit, "Shop Portal", "both")
self.multiworld.plando_connections[self.player].remove(cxn)
self.multiworld.plando_connections[self.player].insert(index, replacement)
elif cxn.exit.startswith("Shop"):
replacement = PlandoConnection(cxn.entrance, "Shop Portal", "both")
self.multiworld.plando_connections[self.player].remove(cxn)
self.multiworld.plando_connections[self.player].insert(index, replacement)

# 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:
Expand All @@ -74,6 +95,56 @@ def generate_early(self) -> None:
self.options.entrance_rando.value = passthrough["entrance_rando"]
self.options.shuffle_ladders.value = passthrough["shuffle_ladders"]

@classmethod
def stage_generate_early(cls, multiworld: MultiWorld) -> None:
tunic_worlds: Tuple[TunicWorld] = multiworld.get_game_worlds("TUNIC")
for tunic in tunic_worlds:
if isinstance(tunic.options.entrance_rando.value, str):
group = tunic.options.entrance_rando.value
ScipioWright marked this conversation as resolved.
Show resolved Hide resolved
# if this is the first world in the group, set the rules equal to its rules
if group not in cls.seed_groups:
cls.seed_groups[group] = SeedGroup(logic_rules=tunic.options.logic_rules.value,
ScipioWright marked this conversation as resolved.
Show resolved Hide resolved
laurels_at_10_fairies=tunic.options.laurels_location == 3,
fixed_shop=bool(tunic.options.fixed_shop),
plando=multiworld.plando_connections[tunic.player])
else:
# lower value is more restrictive
if tunic.options.logic_rules.value < cls.seed_groups[group]["logic_rules"]:
cls.seed_groups[group]["logic_rules"] = tunic.options.logic_rules.value
# laurels at 10 fairies changes logic for secret gathering place placement
if tunic.options.laurels_location == 3:
cls.seed_groups[group]["laurels_at_10_fairies"] = True
# fewer shops, one at windmill
if tunic.options.fixed_shop:
cls.seed_groups[group]["fixed_shop"] = True

if multiworld.plando_connections[tunic.player]:
# loop through the connections in the player's yaml
for cxn in multiworld.plando_connections[tunic.player]:
new_conn = True
# check if either of the entrances in the pair match, then check for conflicts
for group_cxn in cls.seed_groups[group]["plando"]:
if cxn.entrance == group_cxn.entrance:
new_conn = False
ScipioWright marked this conversation as resolved.
Show resolved Hide resolved
if cxn.exit == group_cxn.exit:
continue
else:
raise Exception(f"TUNIC: Conflict between seed group {group}'s plando "
f"connection {group_cxn.entrance} <-> {group_cxn.exit} and "
ScipioWright marked this conversation as resolved.
Show resolved Hide resolved
f"{tunic.multiworld.get_player_name(tunic.player)}'s plando "
f"connection {cxn.entrance} <-> {cxn.exit}")
elif cxn.entrance == group_cxn.exit:
new_conn = False
ScipioWright marked this conversation as resolved.
Show resolved Hide resolved
if cxn.exit == group_cxn.entrance:
continue
else:
raise Exception(f"TUNIC: Conflict between seed group {group}'s plando "
f"connection {group_cxn.entrance} <-> {group_cxn.exit} and "
f"{tunic.multiworld.get_player_name(tunic.player)}'s plando "
f"connection {cxn.entrance} <-> {cxn.exit}")
if new_conn:
cls.seed_groups[group]["plando"].append(cxn)

def create_item(self, name: str) -> TunicItem:
item_data = item_table[name]
return TunicItem(name, item_data.classification, self.item_name_to_id[name], self.player)
Expand Down
52 changes: 39 additions & 13 deletions worlds/tunic/er_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def create_er_regions(world: "TunicWorld") -> Dict[Portal, Portal]:
"Quarry Fuse": "Quarry",
"Ziggurat Fuse": "Rooted Ziggurat Lower Back",
"West Garden Fuse": "West Garden",
"Library Fuse": "Library Lab"
"Library Fuse": "Library Lab",
}


Expand Down Expand Up @@ -122,12 +122,20 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
portal_pairs: Dict[Portal, Portal] = {}
dead_ends: List[Portal] = []
two_plus: List[Portal] = []
logic_rules = world.options.logic_rules.value
player_name = world.multiworld.get_player_name(world.player)

logic_rules = world.options.logic_rules.value
fixed_shop = world.options.fixed_shop
laurels_location = world.options.laurels_location

if isinstance(world.options.entrance_rando.value, str):
seed_group = world.seed_groups[world.options.entrance_rando.value]
logic_rules = seed_group["logic_rules"]
fixed_shop = seed_group["fixed_shop"]
laurels_location = "10_fairies" if seed_group["laurels_at_10_fairies"] is True else False

shop_scenes: Set[str] = set()
shop_count = 6
if world.options.fixed_shop.value:
if fixed_shop:
shop_count = 1
shop_scenes.add("Overworld Redux")

Expand Down Expand Up @@ -157,7 +165,10 @@ 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]
if isinstance(world.options.entrance_rando.value, str):
plando_connections = world.seed_groups[world.options.entrance_rando.value]["plando"]
else:
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"):
Expand Down Expand Up @@ -191,10 +202,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
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"
entrance_dead_end = False

portal1 = None
portal2 = None
Expand All @@ -215,6 +223,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
if not portal1:
raise Exception(f"Could not find entrance named {p_entrance} for "
f"plando connections in {player_name}'s YAML.")
entrance_dead_end = True
dead_ends.remove(portal1)
else:
two_plus.remove(portal1)
Expand All @@ -224,7 +233,15 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
if p_exit == portal.name:
portal2 = portal
break
if p_exit in ["Shop Portal", "Shop"]:
if p_exit == "Shop Portal":
# don't pair dead ends to dead ends, that's bad
if entrance_dead_end:
if isinstance(world.options.entrance_rando.value, str):
raise Exception(f"Tunic ER seed group {world.options.entrance_rando.value} paired a dead "
"end to a dead end in their plando connections.")
else:
raise Exception(f"{player_name} paired a dead end to a dead end in their "
"plando connections.")
portal2 = Portal(name="Shop Portal", region=f"Shop",
destination="Previous Region", tag="_")
shop_count -= 1
Expand All @@ -238,6 +255,14 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
if not portal2:
raise Exception(f"Could not find entrance named {p_exit} for "
f"plando connections in {player_name}'s YAML.")
# don't pair dead ends to dead ends, that's bad
if entrance_dead_end:
if isinstance(world.options.entrance_rando.value, str):
raise Exception(f"Tunic ER seed group {world.options.entrance_rando.value} paired a dead "
"end to a dead end in their plando connections.")
else:
raise Exception(f"{player_name} paired a dead end to a dead end in their "
"plando connections.")
ScipioWright marked this conversation as resolved.
Show resolved Hide resolved
dead_ends.remove(portal2)
else:
two_plus.remove(portal2)
Expand All @@ -264,7 +289,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" and not hasattr(world.multiworld, "re_gen_passthrough"):
if laurels_location == "10_fairies" and not hasattr(world.multiworld, "re_gen_passthrough"):
portal1 = None
portal2 = None
for portal in two_plus:
Expand All @@ -285,7 +310,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
two_plus.remove(portal1)
dead_ends.remove(portal2)

if world.options.fixed_shop and not hasattr(world.multiworld, "re_gen_passthrough"):
if fixed_shop and not hasattr(world.multiworld, "re_gen_passthrough"):
portal1 = None
for portal in two_plus:
if portal.scene_destination() == "Overworld Redux, Windmill_":
Expand All @@ -301,7 +326,8 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
two_plus.remove(portal1)

random_object: Random = world.random
if world.options.entrance_rando.value != 1:
# use the seed given in the options to shuffle the portals
if isinstance(world.options.entrance_rando.value, str):
random_object = Random(world.options.entrance_rando.value)
# we want to start by making sure every region is accessible
random_object.shuffle(two_plus)
Expand Down
4 changes: 3 additions & 1 deletion worlds/tunic/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ class ExtraHexagonPercentage(Range):
class EntranceRando(TextChoice):
"""
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.

If you set this to a value besides true or false, that value will be used as a custom seed.
Every player who uses the same seed will have the same entrances, choosing the most restrictive settings among these players for the purpose of pairing entrances.
"""
internal_name = "entrance_rando"
display_name = "Entrance Rando"
Expand Down
Loading