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 32 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
79 changes: 76 additions & 3 deletions worlds/tunic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from typing import Dict, List, Any
from typing import Dict, List, Any, Tuple, TypedDict
from logging import warning
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
from .er_rules import set_er_location_rules
from .regions import tunic_regions
from .er_scripts import create_er_regions
from .er_data import portal_mapping
from .options import TunicOptions
from .options import TunicOptions, EntranceRando
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,58 @@ 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 it's one of the options, then it isn't a custom seed group
if tunic.options.entrance_rando.value in EntranceRando.options:
continue
group = tunic.options.entrance_rando.value
# 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,
laurels_at_10_fairies=tunic.options.laurels_location == 3,
fixed_shop=bool(tunic.options.fixed_shop),
plando=multiworld.plando_connections[tunic.player])
continue

# 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_cxn = True
for group_cxn in cls.seed_groups[group]["plando"]:
# if neither entrance nor exit match anything in the group, add to group
if ((cxn.entrance == group_cxn.entrance and cxn.exit == group_cxn.exit)
or (cxn.exit == group_cxn.entrance and cxn.entrance == group_cxn.exit)):
new_cxn = False
break

# check if this pair is the same as a pair in the group already
is_mismatched = (
cxn.entrance == group_cxn.entrance and cxn.exit != group_cxn.exit
or cxn.entrance == group_cxn.exit and cxn.exit != group_cxn.entrance
or cxn.exit == group_cxn.entrance and cxn.entrance != group_cxn.exit
or cxn.exit == group_cxn.exit and cxn.entrance != group_cxn.entrance
)
if is_mismatched:
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_cxn:
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
62 changes: 42 additions & 20 deletions worlds/tunic/er_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .er_data import Portal, tunic_er_regions, portal_mapping, \
dependent_regions_restricted, dependent_regions_nmg, dependent_regions_ur
from .er_rules import set_er_region_rules
from .options import EntranceRando
from worlds.generic import PlandoConnection
from random import Random

Expand Down Expand Up @@ -69,7 +70,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 +123,21 @@ 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 it's not one of the EntranceRando options, it's a custom seed
if world.options.entrance_rando.value not in EntranceRando.options:
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 +167,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 world.options.entrance_rando.value in EntranceRando.options:
plando_connections = world.multiworld.plando_connections[world.player]
else:
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"):
Expand Down Expand Up @@ -192,10 +205,6 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
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

Expand All @@ -207,7 +216,18 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
portal2 = portal

# search dead_ends individually since we can't really remove items from two_plus during the loop
if not portal1:
if portal1:
two_plus.remove(portal1)
else:
# if not both, they're both dead ends
if not portal2:
if world.options.entrance_rando.value not in EntranceRando.options:
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.")

for portal in dead_ends:
if p_entrance == portal.name:
portal1 = portal
Expand All @@ -216,16 +236,18 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
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:
if portal2:
two_plus.remove(portal2)
else:
# check if portal2 is a dead end
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",
# if it's not a dead end, it might be a shop
if p_exit == "Shop Portal":
portal2 = Portal(name="Shop Portal", region="Shop",
destination="Previous Region", tag="_")
shop_count -= 1
if shop_count < 0:
Expand All @@ -234,13 +256,12 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
if p.name == p_entrance:
shop_scenes.add(p.scene())
break
# and if it's neither shop nor dead end, it just isn't correct
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

Expand All @@ -264,7 +285,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 +306,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 +322,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 option's value to a string, it will be used as a custom seed.
Every player who uses the same custom 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