TUNIC: Fuse and Bell Shuffle (#5420)

* Making the fix better (thanks medic)

* Make it actually return false if it gets to the backup lists and fails them

* Fix stuff after merge

* Add outlet regions, create new regions as needed for them

* Put together part of decoupled and direction pairs

* make direction pairs work

* Make decoupled work

* Make fixed shop work again

* Fix a few minor bugs

* Fix a few minor bugs

* Fix plando

* god i love programming

* Reorder portal list

* Update portal sorter for variable shops

* Add missing parameter

* Some cleanup of prints and functions

* Fix typo

* it's aliiiiiive

* Make seed groups not sync decoupled

* Add test with full-shop plando

* Fix bug with vanilla portals

* Handle plando connections and direction pair errors

* Update plando checking for decoupled

* Fix typo

* Fix exception text to be shorter

* Add some more comments

* Add todo note

* Remove unused safety thing

* Remove extra plando connections definition in options

* Make seed groups in decoupled with overlapping but not fully overlapped plando connections interact nicely without messing with what the entrances look like in the spoiler log

* Fix weird edge case that is technically user error

* Add note to fixed shop

* Fix parsing shop names in UT

* Remove debug print

* Actually make UT work

* multiworld. to world.

* Fix typo from merge

* Make it so the shops show up in the entrance hints

* Fix bug in ladder storage rules

* Remove blank line

* # Conflicts:
#	worlds/tunic/__init__.py
#	worlds/tunic/er_data.py
#	worlds/tunic/er_rules.py
#	worlds/tunic/er_scripts.py
#	worlds/tunic/rules.py
#	worlds/tunic/test/test_access.py

* Fix issues after merge

* Update plando connections stuff in docs

* Make early bushes only contain grass

* Fix library mistake

* Backport changes to grass rando (#20)

* Backport changes to grass rando

* add_rule instead of set_rule for the special cases, add special cases for back of swamp laurels area cause I should've made a new region for the swamp upper entrance

* Remove item name group for grass

* Update grass rando option descriptions

- Also ignore grass fill for single player games

* Ignore grass fill option for solo rando

* Update er_rules.py

* Fix pre fill issue

* Remove duplicate option

* Add excluded grass locations back

* Hide grass fill option from simple ui options page

* Check for start with sword before setting grass rules

* Update worlds/tunic/options.py

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

* has_stick -> has_melee

* has_stick -> has_melee

* Add a failsafe for direction pairing

* Fix playthrough crash bug

* Remove init from logicmixin

* Updates per code review (thanks hesto)

* has_stick to has_melee in newer update

* has_stick to has_melee in newer update

* Exclude grass from get_filler_item_name

- non-grass rando games were accidentally seeing grass items get shuffled in as filler, which is funny but probably shouldn't happen

* Update worlds/tunic/__init__.py

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

* Apply suggestions from code review

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
Co-authored-by: Scipio Wright <scipiowright@gmail.com>

* change the rest of grass_fill to local_fill

* Filter out grass from filler_items

* remove -> discard

* Update worlds/tunic/__init__.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

* Starting out

* Rules for breakable regions

* # Conflicts:
#	worlds/tunic/__init__.py
#	worlds/tunic/combat_logic.py
#	worlds/tunic/er_data.py
#	worlds/tunic/er_rules.py
#	worlds/tunic/er_scripts.py

* Cleanup more stuff after merge

* Revert "Cleanup more stuff after merge"

This reverts commit a6ee9a93da8f2fcc4413de6df6927b246017889d.

* Revert "# Conflicts:"

This reverts commit c74ccd74a45b6ad6b9abe6e339d115a0c98baf30.

* Cleanup more stuff after merge

* change has_stick to has_melee

* Update grass list with combat logic regions

* More fixes from combat logic merge

* Fix some dumb stuff (#21)

* Reorganize pre fill for grass

* make the rest of it work, it's pr ready, boom

* Make it work in not pot shuffle

* Merge grass rando

* multiworld -> world get_location, use has_any

* Swap out region for West Garden Before Terry grass

* Adjust west garden rules to add west combat region

* Adjust grass regions for south checkpoint grass

* Adjust grass regions for after terry grass

* Adjust grass regions for west combat grass

* Adjust grass regions for dagger house grass

* Adjust grass regions for south checkpoint grass, adjust regions and rules for some related locations

* Finish the remainder of the west garden grass, reformat ruined atoll a little

* More hex quest updates

- Implement page ability shuffle for hex quest
- Fix keys behind bosses if hex goal is less than 3
- Added check to fix conflicting hex quest options
- Add option to slot data

* Change option comparison

* Change option checking and fix some stuff

- also keep prayer first on low hex counts

* Update option defaulting

* Update option checking

* Fix option assignment again

* Merge in hex hunt

* Merge in changes

* Clean up imports

* Add ability type to UT stuff

* merge it all

* Make local fill work across pot and grass (to be adjusted later)

* Make separate pools for the grass and non-grass fills

* Fix id overlap

* Update option description

* Fix default

* Reorder localfill option desc

* Load the purgatory ones in

* Adjustments after merge

* Fully remove logicrules

* Fix UT support with fixed shop option

* Add breakable shuffle to the ut stuff

* Make it load in a specific number of locations

* Add Silent's spoiler log ability thing

* Fix for groups

* Fix for groups

* Fix typo

* Fix hex quest UT support

* Use .get

* UT fixes, classification fixes

* Rename some locations

* Adjust guard house names

* Adjust guard house names

* Rework create_item

* Fix for plando connections

* Rename, add new breakables

* Rename more stuff

* Time to rename them again

* Fix issue with fixed shop + decoupled

* Put in an exception to catch that error in the future

* Update create_item to match main

* Update spoiler log lines for hex abilities

* Burn the signs down

* Bring over the combat logic fix

* Merge in combat logic fix

* Silly static method thing

* Move a few areas to before well instead of east forest

* Add an all_random hidden option for dev stuff

* Port over changes from main

* Fix west courtyard pot regions

* Remove debug prints

* Fix fortress courtyard and beneath the fortress loc groups again

* Add exception handling to deal with duplicate apworlds

* Fix typo

* More missing loc group conversions

* Initial fuse shuffle stuff

* Fix gun missing from combat_items, add new for combat logic cache, very slight refactor of check_combat_reqs to let it do the changeover in a less complicated fashion, fix area being a boss area rather than non-boss area for a check

* Add fuse shuffle logic

* reorder atoll statue rule

* Update traversal reqs

* Remove fuse shuffle from temple door

* Combine rules and option checking

* Add bell shuffle; fix fuse location groups

* Fix portal rules not requiring prayer

* Merge the grass laurels exit grass PR

* Merge in fortress bridge PR

* Do a little clean up

* Fix a regression

* Update after merge

* Some more stuff

* More Silent changes

* Update more info section in game info page

* Fix rules for atoll and swamp fuses

* Precollect cathedral fuse in ER

* actually just make the fuse useful instead of progression

* Add it to the swamp and cath rules too

* Fix cath fuse name

* Minor fixes and edits

* Some UT stuff

* Fix a couple more groups

* Move a bunch of UT stuff to its own file

* Fix up a couple UT things

* Couple minor ER fixes

* Formatting change

* UT poptracker stuff enabled since it's optional in one of the releases

* Add author string to world class

* Adjust local fill option name

* Update ut_stuff to match the PR

* Add exception handling for UT with old apworld

* Fix missing tracker_world

* Remove extra entrance from cath main -> elevator

Entry <-> Elev exists,
Entry <-> Main exists
So no connection is needed between Main and Elev

* Fix so that decoupled doesn't incorrectly use get_portal_info and get_paired_portal

* Fix so that decoupled doesn't incorrectly use get_portal_info and get_paired_portal

* Update for breakables poptracker

* Backup and warnings instead

* Update typing

* Delete old regions and rules, move stuff to logic_helpers and constants

* Delete now much less useful tests

* Fix breakables map tracking

* Add more comments to init

* Add todo to grass.py

* Fix up tests

* Fully remove fixed_shop

* Finish hard deprecating FixedShop

* Fix zig skip showing up in decoupled fixed shop

* Make local_fill show up on the website

* Merge with main

* Fixes after merge

* More fixes after merge

* oh right that's why it was there, circular imports

* Swap {} to ()

* Add fuse and bell shuffle to seed groups since they're logically significant for entrance pairing

---------

Co-authored-by: silent-destroyer <osilentdestroyer@gmail.com>
Co-authored-by: Silent <110704408+silent-destroyer@users.noreply.github.com>
Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
This commit is contained in:
Scipio Wright
2025-09-30 15:39:41 -04:00
committed by GitHub
parent e6fb7d9c6a
commit 6a63de2f0f
12 changed files with 325 additions and 104 deletions

View File

@@ -7,13 +7,13 @@ from Options import PlandoConnection, OptionError, PerGameCommonOptions, Range,
from settings import Group, Bool, FilePath
from worlds.AutoWorld import WebWorld, World
# from .bells import bell_location_groups, bell_location_name_to_id
from .bells import bell_location_groups, bell_location_name_to_id
from .breakables import breakable_location_name_to_id, breakable_location_groups, breakable_location_table
from .combat_logic import area_data, CombatState
from .er_data import portal_mapping, RegionInfo, tunic_er_regions
from .er_rules import set_er_location_rules
from .er_scripts import create_er_regions, verify_plando_directions
# from .fuses import fuse_location_name_to_id, fuse_location_groups
from .fuses import fuse_location_name_to_id, fuse_location_groups
from .grass import grass_location_table, grass_location_name_to_id, grass_location_name_groups, excluded_grass_locations
from .items import (item_name_to_id, item_table, item_name_groups, fool_tiers, filler_items, slot_data_item_names,
combat_items)
@@ -75,6 +75,8 @@ class SeedGroup(TypedDict):
entrance_layout: int # entrance layout value
has_decoupled_enabled: bool # for checking that players don't have conflicting options
plando: list[PlandoConnection] # consolidated plando connections for the seed group
bell_shuffle: bool # off controls
fuse_shuffle: bool # off controls
class TunicWorld(World):
@@ -98,17 +100,17 @@ class TunicWorld(World):
location_name_groups.setdefault(group_name, set()).update(members)
for group_name, members in breakable_location_groups.items():
location_name_groups.setdefault(group_name, set()).update(members)
# for group_name, members in fuse_location_groups.items():
# location_name_groups.setdefault(group_name, set()).update(members)
# for group_name, members in bell_location_groups.items():
# location_name_groups.setdefault(group_name, set()).update(members)
for group_name, members in fuse_location_groups.items():
location_name_groups.setdefault(group_name, set()).update(members)
for group_name, members in bell_location_groups.items():
location_name_groups.setdefault(group_name, set()).update(members)
item_name_to_id = item_name_to_id
location_name_to_id = standard_location_name_to_id.copy()
location_name_to_id.update(grass_location_name_to_id)
location_name_to_id.update(breakable_location_name_to_id)
# location_name_to_id.update(fuse_location_name_to_id)
# location_name_to_id.update(bell_location_name_to_id)
location_name_to_id.update(fuse_location_name_to_id)
location_name_to_id.update(bell_location_name_to_id)
player_location_table: dict[str, int]
ability_unlocks: dict[str, int]
@@ -227,11 +229,11 @@ class TunicWorld(World):
self.player_location_table.update({name: num for name, num in breakable_location_name_to_id.items()
if not name.startswith("Purgatory")})
# if self.options.shuffle_fuses:
# self.player_location_table.update(fuse_location_name_to_id)
#
# if self.options.shuffle_bells:
# self.player_location_table.update(bell_location_name_to_id)
if self.options.shuffle_fuses:
self.player_location_table.update(fuse_location_name_to_id)
if self.options.shuffle_bells:
self.player_location_table.update(bell_location_name_to_id)
@classmethod
def stage_generate_early(cls, multiworld: MultiWorld) -> None:
@@ -259,7 +261,9 @@ class TunicWorld(World):
laurels_at_10_fairies=tunic.options.laurels_location == LaurelsLocation.option_10_fairies,
entrance_layout=tunic.options.entrance_layout.value,
has_decoupled_enabled=bool(tunic.options.decoupled),
plando=tunic.options.plando_connections.value.copy())
plando=tunic.options.plando_connections.value.copy(),
bell_shuffle=bool(tunic.options.shuffle_bells),
fuse_shuffle=bool(tunic.options.shuffle_fuses))
continue
# I feel that syncing this one is worse than erroring out
if bool(tunic.options.decoupled) != cls.seed_groups[group]["has_decoupled_enabled"]:
@@ -277,6 +281,12 @@ class TunicWorld(World):
# 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
# off is more restrictive
if not tunic.options.shuffle_bells:
cls.seed_groups[group]["bell_shuffle"] = False
# off is more restrictive
if not tunic.options.shuffle_fuses:
cls.seed_groups[group]["fuse_shuffle"] = False
# fixed shop and direction pairs override standard, but conflict with each other
if tunic.options.entrance_layout:
if cls.seed_groups[group]["entrance_layout"] == EntranceLayout.option_standard:
@@ -428,6 +438,19 @@ class TunicWorld(World):
ladder_count += 1
remove_filler(ladder_count)
if self.options.shuffle_fuses:
for item_name, item_data in item_table.items():
if item_data.item_group == "Fuses":
if item_name == "Cathedral Elevator Fuse" and self.options.entrance_rando:
tunic_items.append(self.create_item(item_name, ItemClassification.useful))
continue
items_to_create[item_name] = 1
if self.options.shuffle_bells:
for item_name, item_data in item_table.items():
if item_data.item_group == "Bells":
items_to_create[item_name] = 1
if self.options.hexagon_quest:
# Replace pages and normal hexagons with filler
for replaced_item in list(filter(lambda item: "Pages" in item or item in hexagon_locations, items_to_create)):
@@ -480,7 +503,6 @@ class TunicWorld(World):
# pull out the filler so that we can place it manually during pre_fill
self.fill_items = []
if self.options.local_fill > 0 and self.multiworld.players > 1:
# skip items marked local or non-local, let fill deal with them in its own way
all_filler: list[TunicItem] = []
non_filler: list[TunicItem] = []
for tunic_item in tunic_items:
@@ -709,8 +731,8 @@ class TunicWorld(World):
"entrance_rando": int(bool(self.options.entrance_rando.value)),
"decoupled": self.options.decoupled.value if self.options.entrance_rando else 0,
"shuffle_ladders": self.options.shuffle_ladders.value,
# "shuffle_fuses": self.options.shuffle_fuses.value,
# "shuffle_bells": self.options.shuffle_bells.value,
"shuffle_fuses": self.options.shuffle_fuses.value,
"shuffle_bells": self.options.shuffle_bells.value,
"grass_randomizer": self.options.grass_randomizer.value,
"combat_logic": self.options.combat_logic.value,
"Hexagon Quest Prayer": self.ability_unlocks["Pages 24-25 (Prayer)"],