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:
@@ -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)"],
|
||||
|
||||
39
worlds/tunic/bells.py
Normal file
39
worlds/tunic/bells.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from typing import NamedTuple, TYPE_CHECKING
|
||||
|
||||
from worlds.generic.Rules import set_rule
|
||||
|
||||
from .constants import base_id
|
||||
from .logic_helpers import has_melee
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import TunicWorld
|
||||
|
||||
|
||||
class TunicLocationData(NamedTuple):
|
||||
region: str
|
||||
er_region: str
|
||||
|
||||
|
||||
bell_location_table: dict[str, TunicLocationData] = {
|
||||
"Forest Belltower - Ring the East Bell": TunicLocationData("Forest Belltower", "Forest Belltower Upper"),
|
||||
"Overworld - [West] Ring the West Bell": TunicLocationData("Overworld", "Overworld Belltower at Bell"),
|
||||
}
|
||||
|
||||
bell_location_base_id = base_id + 11000
|
||||
bell_location_name_to_id: dict[str, int] = {name: bell_location_base_id + index
|
||||
for index, name in enumerate(bell_location_table)}
|
||||
|
||||
bell_location_groups: dict[str, set[str]] = {}
|
||||
for location_name, location_data in bell_location_table.items():
|
||||
bell_location_groups.setdefault(location_data.region, set()).add(location_name)
|
||||
bell_location_groups.setdefault("Bells", set()).add(location_name)
|
||||
|
||||
|
||||
def set_bell_location_rules(world: "TunicWorld") -> None:
|
||||
player = world.player
|
||||
|
||||
set_rule(world.get_location("Forest Belltower - Ring the East Bell"),
|
||||
lambda state: has_melee(state, player) or state.has("Magic Wand", player))
|
||||
set_rule(world.get_location("Overworld - [West] Ring the West Bell"),
|
||||
lambda state: has_melee(state, player) or state.has("Magic Wand", player))
|
||||
@@ -3,11 +3,11 @@ from typing import FrozenSet, TYPE_CHECKING
|
||||
from BaseClasses import Region
|
||||
from worlds.generic.Rules import set_rule, add_rule, forbid_item
|
||||
|
||||
# from .bells import set_bell_location_rules
|
||||
from .bells import set_bell_location_rules
|
||||
from .combat_logic import has_combat_reqs
|
||||
from .constants import *
|
||||
from .er_data import Portal, get_portal_outlet_region
|
||||
# from .fuses import set_fuse_location_rules, has_fuses
|
||||
from .fuses import set_fuse_location_rules
|
||||
from .grass import set_grass_location_rules
|
||||
from .ladder_storage_data import ow_ladder_groups, region_ladders, easy_ls, medium_ls, hard_ls
|
||||
from .logic_helpers import (has_ability, has_ladder, has_melee, has_sword, has_lantern, has_mask, has_fuses,
|
||||
@@ -17,9 +17,6 @@ from .options import IceGrappling, LadderStorage, CombatLogic
|
||||
if TYPE_CHECKING:
|
||||
from . import TunicWorld
|
||||
|
||||
fuses_option = False # replace with options.shuffle_fuses when fuse shuffle is in
|
||||
bells_option = False # replace with options.shuffle_bells when bell shuffle is in
|
||||
|
||||
|
||||
def set_er_region_rules(world: "TunicWorld", regions: dict[str, Region], portal_pairs: dict[Portal, Portal]) -> None:
|
||||
player = world.player
|
||||
@@ -334,8 +331,8 @@ def set_er_region_rules(world: "TunicWorld", regions: dict[str, Region], portal_
|
||||
# nmg: ice grapple through temple door
|
||||
regions["Overworld"].connect(
|
||||
connecting_region=regions["Overworld Temple Door"],
|
||||
rule=lambda state: (state.has_all(("Ring Eastern Bell", "Ring Western Bell"), player) and not bells_option)
|
||||
or (state.has_all(("East Bell", "West Bell"), player) and bells_option)
|
||||
rule=lambda state: (state.has_all(("Ring Eastern Bell", "Ring Western Bell"), player) and not options.shuffle_bells)
|
||||
or (state.has_all(("East Bell", "West Bell"), player) and options.shuffle_bells)
|
||||
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world))
|
||||
|
||||
regions["Overworld Temple Door"].connect(
|
||||
@@ -671,9 +668,9 @@ def set_er_region_rules(world: "TunicWorld", regions: dict[str, Region], portal_
|
||||
and (has_sword(state, player) or state.has_any((gun, fire_wand), player)))
|
||||
# shoot fuse and have the shot hit you mid-LS
|
||||
or (can_ladder_storage(state, world) and state.has(fire_wand, player)
|
||||
and options.ladder_storage >= LadderStorage.option_hard))) and not fuses_option)
|
||||
and options.ladder_storage >= LadderStorage.option_hard))) and not options.shuffle_fuses)
|
||||
or (state.has_all((atoll_northwest_fuse, atoll_northeast_fuse, atoll_southwest_fuse, atoll_southeast_fuse), player)
|
||||
and fuses_option))
|
||||
and options.shuffle_fuses))
|
||||
)
|
||||
|
||||
regions["Ruined Atoll Statue"].connect(
|
||||
@@ -804,12 +801,12 @@ def set_er_region_rules(world: "TunicWorld", regions: dict[str, Region], portal_
|
||||
connecting_region=regions["Fortress Exterior from Overworld"],
|
||||
rule=lambda state: state.has(laurels, player)
|
||||
or (has_ability(prayer, state, world) and state.has(fortress_exterior_fuse_1, player)
|
||||
and fuses_option))
|
||||
and options.shuffle_fuses))
|
||||
regions["Fortress Exterior from Overworld"].connect(
|
||||
connecting_region=regions["Fortress Exterior near cave"],
|
||||
rule=lambda state: state.has(laurels, player)
|
||||
or (has_ability(prayer, state, world) and state.has(fortress_exterior_fuse_1, player)
|
||||
if fuses_option else has_ability(prayer, state, world)))
|
||||
if options.shuffle_fuses else has_ability(prayer, state, world)))
|
||||
|
||||
# shoot far fire pot, enemy gets aggro'd
|
||||
regions["Fortress Exterior near cave"].connect(
|
||||
@@ -880,7 +877,7 @@ def set_er_region_rules(world: "TunicWorld", regions: dict[str, Region], portal_
|
||||
rule=lambda state: has_ice_grapple_logic(False, IceGrappling.option_easy, state, world)
|
||||
or (has_fuses("Activate Eastern Vault West Fuses", state, world)
|
||||
and has_fuses("Activate Eastern Vault East Fuse", state, world)
|
||||
and fuses_option))
|
||||
and options.shuffle_fuses))
|
||||
|
||||
fort_grave_entry_to_combat = regions["Fortress Grave Path Entry"].connect(
|
||||
connecting_region=regions["Fortress Grave Path Combat"])
|
||||
@@ -1027,18 +1024,18 @@ def set_er_region_rules(world: "TunicWorld", regions: dict[str, Region], portal_
|
||||
connecting_region=regions["Rooted Ziggurat Lower Miniboss Platform"])
|
||||
zig_low_miniboss_to_mid = regions["Rooted Ziggurat Lower Miniboss Platform"].connect(
|
||||
connecting_region=regions["Rooted Ziggurat Lower Mid Checkpoint"],
|
||||
rule=lambda state: state.has(ziggurat_miniboss_fuse, player) if fuses_option
|
||||
rule=lambda state: state.has(ziggurat_miniboss_fuse, player) if options.shuffle_fuses
|
||||
else (has_sword(state, player) and has_ability(prayer, state, world)))
|
||||
# can ice grapple to the voidlings to get to the double admin fight, still need to pray at the fuse
|
||||
zig_low_miniboss_to_back = regions["Rooted Ziggurat Lower Miniboss Platform"].connect(
|
||||
connecting_region=regions["Rooted Ziggurat Lower Back"],
|
||||
rule=lambda state: state.has(laurels, player) or (state.has(ziggurat_miniboss_fuse, player) and fuses_option)
|
||||
or (has_sword(state, player) and has_ability(prayer, state, world) and not fuses_option))
|
||||
rule=lambda state: state.has(laurels, player) or (state.has(ziggurat_miniboss_fuse, player) and options.shuffle_fuses)
|
||||
or (has_sword(state, player) and has_ability(prayer, state, world) and not options.shuffle_fuses))
|
||||
regions["Rooted Ziggurat Lower Back"].connect(
|
||||
connecting_region=regions["Rooted Ziggurat Lower Miniboss Platform"],
|
||||
rule=lambda state: state.has(laurels, player)
|
||||
or has_ice_grapple_logic(True, IceGrappling.option_easy, state, world)
|
||||
or (state.has(ziggurat_miniboss_fuse, player) and fuses_option))
|
||||
or (state.has(ziggurat_miniboss_fuse, player) and options.shuffle_fuses))
|
||||
|
||||
regions["Rooted Ziggurat Lower Back"].connect(
|
||||
connecting_region=regions["Rooted Ziggurat Portal Room Entrance"],
|
||||
@@ -1086,8 +1083,8 @@ def set_er_region_rules(world: "TunicWorld", regions: dict[str, Region], portal_
|
||||
and state.can_reach_region("Overworld Beach", player)))))
|
||||
and (not options.combat_logic
|
||||
or has_combat_reqs("Swamp", state, player))
|
||||
and not fuses_option)
|
||||
or (state.has_all((swamp_fuse_1, swamp_fuse_2, swamp_fuse_3), player) and fuses_option)
|
||||
and not options.shuffle_fuses)
|
||||
or (state.has_all((swamp_fuse_1, swamp_fuse_2, swamp_fuse_3), player) and options.shuffle_fuses)
|
||||
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world))
|
||||
|
||||
if options.ladder_storage >= LadderStorage.option_hard and options.shuffle_ladders:
|
||||
@@ -1096,7 +1093,7 @@ def set_er_region_rules(world: "TunicWorld", regions: dict[str, Region], portal_
|
||||
regions["Swamp to Cathedral Main Entrance Region"].connect(
|
||||
connecting_region=regions["Swamp Mid"],
|
||||
rule=lambda state: has_ice_grapple_logic(False, IceGrappling.option_easy, state, world)
|
||||
or (state.has_all((swamp_fuse_1, swamp_fuse_2, swamp_fuse_3), player) and fuses_option))
|
||||
or (state.has_all((swamp_fuse_1, swamp_fuse_2, swamp_fuse_3), player) and options.shuffle_fuses))
|
||||
|
||||
# grapple push the enemy by the door down, then grapple to it. Really jank
|
||||
regions["Swamp Mid"].connect(
|
||||
@@ -1142,7 +1139,7 @@ def set_er_region_rules(world: "TunicWorld", regions: dict[str, Region], portal_
|
||||
|
||||
cath_entry_to_elev = regions["Cathedral Entry"].connect(
|
||||
connecting_region=regions["Cathedral to Gauntlet"],
|
||||
rule=lambda state: ((state.has(cathedral_elevator_fuse, player) if fuses_option else has_ability(prayer, state, world))
|
||||
rule=lambda state: ((state.has(cathedral_elevator_fuse, player) if options.shuffle_fuses else has_ability(prayer, state, world))
|
||||
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world))
|
||||
or options.entrance_rando) # elevator is always there in ER
|
||||
regions["Cathedral to Gauntlet"].connect(
|
||||
@@ -1444,17 +1441,17 @@ def set_er_region_rules(world: "TunicWorld", regions: dict[str, Region], portal_
|
||||
lambda state: has_combat_reqs("Rooted Ziggurat", state, player))
|
||||
set_rule(zig_low_miniboss_to_back,
|
||||
lambda state: state.has(laurels, player)
|
||||
or (state.has(ziggurat_miniboss_fuse, player) if fuses_option
|
||||
or (state.has(ziggurat_miniboss_fuse, player) if options.shuffle_fuses
|
||||
else (has_ability(prayer, state, world) and has_combat_reqs("Rooted Ziggurat", state, player))))
|
||||
set_rule(zig_low_miniboss_to_mid,
|
||||
lambda state: state.has(ziggurat_miniboss_fuse, player) if fuses_option
|
||||
lambda state: state.has(ziggurat_miniboss_fuse, player) if options.shuffle_fuses
|
||||
else (has_ability(prayer, state, world) and has_combat_reqs("Rooted Ziggurat", state, player)))
|
||||
|
||||
# only activating the fuse requires combat logic
|
||||
set_rule(cath_entry_to_elev,
|
||||
lambda state: options.entrance_rando
|
||||
or has_ice_grapple_logic(False, IceGrappling.option_medium, state, world)
|
||||
or (state.has(cathedral_elevator_fuse, player) if fuses_option
|
||||
or (state.has(cathedral_elevator_fuse, player) if options.shuffle_fuses
|
||||
else (has_ability(prayer, state, world) and has_combat_reqs("Swamp", state, player))))
|
||||
|
||||
set_rule(cath_entry_to_main,
|
||||
@@ -1535,11 +1532,11 @@ def set_er_location_rules(world: "TunicWorld") -> None:
|
||||
if options.grass_randomizer:
|
||||
set_grass_location_rules(world)
|
||||
|
||||
# if options.shuffle_fuses:
|
||||
# set_fuse_location_rules(world)
|
||||
#
|
||||
# if options.shuffle_bells:
|
||||
# set_bell_location_rules(world)
|
||||
if options.shuffle_fuses:
|
||||
set_fuse_location_rules(world)
|
||||
|
||||
if options.shuffle_bells:
|
||||
set_bell_location_rules(world)
|
||||
|
||||
forbid_item(world.get_location("Secret Gathering Place - 20 Fairy Reward"), fairies, player)
|
||||
|
||||
@@ -1702,7 +1699,7 @@ def set_er_location_rules(world: "TunicWorld") -> None:
|
||||
and (state.has(laurels, player)
|
||||
or options.entrance_rando)))
|
||||
set_rule(world.get_location("Rooted Ziggurat Lower - After Guarded Fuse"),
|
||||
lambda state: state.has(ziggurat_miniboss_fuse, player) if fuses_option
|
||||
lambda state: state.has(ziggurat_miniboss_fuse, player) if options.shuffle_fuses
|
||||
else has_sword(state, player) and has_ability(prayer, state, world))
|
||||
|
||||
# Bosses
|
||||
@@ -1745,12 +1742,12 @@ def set_er_location_rules(world: "TunicWorld") -> None:
|
||||
lambda state: state.has(laurels, player))
|
||||
|
||||
# Events
|
||||
if not bells_option:
|
||||
if not options.shuffle_bells:
|
||||
set_rule(world.get_location("Eastern Bell"),
|
||||
lambda state: (has_melee(state, player) or state.has(fire_wand, player)))
|
||||
set_rule(world.get_location("Western Bell"),
|
||||
lambda state: (has_melee(state, player) or state.has(fire_wand, player)))
|
||||
if not fuses_option:
|
||||
if not options.shuffle_fuses:
|
||||
set_rule(world.get_location("Furnace Fuse"),
|
||||
lambda state: has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("South and West Fortress Exterior Fuses"),
|
||||
@@ -1877,7 +1874,7 @@ def set_er_location_rules(world: "TunicWorld") -> None:
|
||||
|
||||
# could just do the last two, but this outputs better in the spoiler log
|
||||
# dagger is maybe viable here, but it's sketchy -- activate ladder switch, save to reset enemies, climb up
|
||||
if not fuses_option:
|
||||
if not options.shuffle_fuses:
|
||||
combat_logic_to_loc("Upper and Central Fortress Exterior Fuses", "Eastern Vault Fortress")
|
||||
combat_logic_to_loc("Beneath the Vault Fuse", "Beneath the Vault")
|
||||
combat_logic_to_loc("Eastern Vault West Fuses", "Eastern Vault Fortress")
|
||||
@@ -1896,10 +1893,10 @@ def set_er_location_rules(world: "TunicWorld") -> None:
|
||||
and (state.has(laurels, player) or world.options.entrance_rando))
|
||||
or has_combat_reqs("Rooted Ziggurat", state, player))
|
||||
set_rule(world.get_location("Rooted Ziggurat Lower - After Guarded Fuse"),
|
||||
lambda state: state.has(ziggurat_miniboss_fuse, player) if fuses_option
|
||||
lambda state: state.has(ziggurat_miniboss_fuse, player) if options.shuffle_fuses
|
||||
else (has_ability(prayer, state, world) and has_combat_reqs("Rooted Ziggurat", state, player)))
|
||||
|
||||
if fuses_option:
|
||||
if options.shuffle_fuses:
|
||||
set_rule(world.get_location("Rooted Ziggurat Lower - [Miniboss] Activate Fuse"),
|
||||
lambda state: has_ability(prayer, state, world) and has_combat_reqs("Rooted Ziggurat", state, player))
|
||||
combat_logic_to_loc("Beneath the Fortress - Activate Fuse", "Beneath the Vault")
|
||||
|
||||
@@ -113,13 +113,13 @@ def place_event_items(world: "TunicWorld", regions: dict[str, Region]) -> None:
|
||||
location.place_locked_item(
|
||||
TunicERItem("Unseal the Heir", ItemClassification.progression, None, world.player))
|
||||
elif event_name.endswith("Bell"):
|
||||
# if world.options.shuffle_bells:
|
||||
# continue
|
||||
if world.options.shuffle_bells:
|
||||
continue
|
||||
location.place_locked_item(
|
||||
TunicERItem("Ring " + event_name, ItemClassification.progression, None, world.player))
|
||||
elif event_name.endswith("Fuse") or event_name.endswith("Fuses"):
|
||||
# if world.options.shuffle_fuses:
|
||||
# continue
|
||||
if world.options.shuffle_fuses:
|
||||
continue
|
||||
location.place_locked_item(
|
||||
TunicERItem("Activate " + event_name, ItemClassification.progression, None, world.player))
|
||||
region.locations.append(location)
|
||||
@@ -200,10 +200,8 @@ def pair_portals(world: "TunicWorld", regions: dict[str, Region]) -> dict[Portal
|
||||
entrance_layout = world.options.entrance_layout
|
||||
laurels_location = world.options.laurels_location
|
||||
decoupled = world.options.decoupled
|
||||
# shuffle_fuses = bool(world.options.shuffle_fuses.value)
|
||||
# shuffle_bells = bool(world.options.shuffle_bells.value)
|
||||
shuffle_fuses = False
|
||||
shuffle_bells = False
|
||||
shuffle_fuses = bool(world.options.shuffle_fuses.value)
|
||||
shuffle_bells = bool(world.options.shuffle_bells.value)
|
||||
traversal_reqs = deepcopy(traversal_requirements)
|
||||
has_laurels = True
|
||||
waterfall_plando = False
|
||||
@@ -216,6 +214,8 @@ def pair_portals(world: "TunicWorld", regions: dict[str, Region]) -> dict[Portal
|
||||
ladder_storage = seed_group["ladder_storage"]
|
||||
entrance_layout = seed_group["entrance_layout"]
|
||||
laurels_location = "10_fairies" if seed_group["laurels_at_10_fairies"] is True else False
|
||||
shuffle_bells = seed_group["bell_shuffle"]
|
||||
shuffle_fuses = seed_group["fuse_shuffle"]
|
||||
|
||||
logic_tricks: tuple[bool, int, int] = (laurels_zips, ice_grappling, ladder_storage)
|
||||
|
||||
|
||||
@@ -1,30 +1,123 @@
|
||||
from .constants import *
|
||||
from typing import NamedTuple, TYPE_CHECKING
|
||||
|
||||
# for fuse locations and reusing event names to simplify er_rules
|
||||
fuse_activation_reqs: dict[str, list[str]] = {
|
||||
swamp_fuse_2: [swamp_fuse_1],
|
||||
swamp_fuse_3: [swamp_fuse_1, swamp_fuse_2],
|
||||
fortress_exterior_fuse_2: [fortress_exterior_fuse_1],
|
||||
beneath_the_vault_fuse: [fortress_exterior_fuse_1, fortress_exterior_fuse_2],
|
||||
fortress_candles_fuse: [fortress_exterior_fuse_1, fortress_exterior_fuse_2, beneath_the_vault_fuse],
|
||||
fortress_door_left_fuse: [fortress_exterior_fuse_1, fortress_exterior_fuse_2, beneath_the_vault_fuse,
|
||||
fortress_candles_fuse],
|
||||
fortress_courtyard_upper_fuse: [fortress_exterior_fuse_1],
|
||||
fortress_courtyard_lower_fuse: [fortress_exterior_fuse_1, fortress_courtyard_upper_fuse],
|
||||
fortress_door_right_fuse: [fortress_exterior_fuse_1, fortress_courtyard_upper_fuse, fortress_courtyard_lower_fuse],
|
||||
quarry_fuse_2: [quarry_fuse_1],
|
||||
"Activate Furnace Fuse": [west_furnace_fuse],
|
||||
"Activate South and West Fortress Exterior Fuses": [fortress_exterior_fuse_1, fortress_exterior_fuse_2],
|
||||
"Activate Upper and Central Fortress Exterior Fuses": [fortress_exterior_fuse_1, fortress_courtyard_upper_fuse,
|
||||
fortress_courtyard_lower_fuse],
|
||||
"Activate Beneath the Vault Fuse": [fortress_exterior_fuse_1, fortress_exterior_fuse_2, beneath_the_vault_fuse],
|
||||
"Activate Eastern Vault West Fuses": [fortress_exterior_fuse_1, fortress_exterior_fuse_2, beneath_the_vault_fuse,
|
||||
fortress_candles_fuse, fortress_door_left_fuse],
|
||||
"Activate Eastern Vault East Fuse": [fortress_exterior_fuse_1, fortress_courtyard_upper_fuse,
|
||||
fortress_courtyard_lower_fuse, fortress_door_right_fuse],
|
||||
"Activate Quarry Connector Fuse": [quarry_fuse_1],
|
||||
"Activate Quarry Fuse": [quarry_fuse_1, quarry_fuse_2],
|
||||
"Activate Ziggurat Fuse": [ziggurat_teleporter_fuse],
|
||||
"Activate West Garden Fuse": [west_garden_fuse],
|
||||
"Activate Library Fuse": [library_lab_fuse],
|
||||
from BaseClasses import CollectionState
|
||||
from worlds.generic.Rules import set_rule
|
||||
|
||||
from .constants import *
|
||||
from .logic_helpers import has_ability, has_sword, fuse_activation_reqs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import TunicWorld
|
||||
|
||||
|
||||
class TunicLocationData(NamedTuple):
|
||||
loc_group: str
|
||||
er_region: str
|
||||
|
||||
|
||||
fuse_location_table: dict[str, TunicLocationData] = {
|
||||
"Overworld - [Southeast] Activate Fuse": TunicLocationData("Overworld", "Overworld"),
|
||||
"Swamp - [Central] Activate Fuse": TunicLocationData("Swamp", "Swamp Mid"),
|
||||
"Swamp - [Outside Cathedral] Activate Fuse": TunicLocationData("Swamp", "Swamp Mid"),
|
||||
"Cathedral - Activate Fuse": TunicLocationData("Cathedral", "Cathedral Main"),
|
||||
"West Furnace - Activate Fuse": TunicLocationData("West Furnace", "Furnace Fuse"),
|
||||
"West Garden - [South Highlands] Activate Fuse": TunicLocationData("West Garden", "West Garden South Checkpoint"),
|
||||
"Ruined Atoll - [Northwest] Activate Fuse": TunicLocationData("Ruined Atoll", "Ruined Atoll"),
|
||||
"Ruined Atoll - [Northeast] Activate Fuse": TunicLocationData("Ruined Atoll", "Ruined Atoll"),
|
||||
"Ruined Atoll - [Southeast] Activate Fuse": TunicLocationData("Ruined Atoll", "Ruined Atoll Ladder Tops"),
|
||||
"Ruined Atoll - [Southwest] Activate Fuse": TunicLocationData("Ruined Atoll", "Ruined Atoll"),
|
||||
"Library Lab - Activate Fuse": TunicLocationData("Library Lab", "Library Lab"),
|
||||
"Fortress Courtyard - [From Overworld] Activate Fuse": TunicLocationData("Fortress Courtyard", "Fortress Exterior from Overworld"),
|
||||
"Fortress Courtyard - [Near Cave] Activate Fuse": TunicLocationData("Fortress Courtyard", "Fortress Exterior from Overworld"),
|
||||
"Fortress Courtyard - [Upper] Activate Fuse": TunicLocationData("Fortress Courtyard", "Fortress Courtyard Upper"),
|
||||
"Fortress Courtyard - [Central] Activate Fuse": TunicLocationData("Fortress Courtyard", "Fortress Courtyard"),
|
||||
"Beneath the Fortress - Activate Fuse": TunicLocationData("Beneath the Fortress", "Beneath the Vault Back"),
|
||||
"Eastern Vault Fortress - [Candle Room] Activate Fuse": TunicLocationData("Eastern Vault Fortress", "Eastern Vault Fortress"),
|
||||
"Eastern Vault Fortress - [Left of Door] Activate Fuse": TunicLocationData("Eastern Vault Fortress", "Eastern Vault Fortress"),
|
||||
"Eastern Vault Fortress - [Right of Door] Activate Fuse": TunicLocationData("Eastern Vault Fortress", "Eastern Vault Fortress"),
|
||||
"Quarry Entryway - Activate Fuse": TunicLocationData("Quarry Connector", "Quarry Connector"),
|
||||
"Quarry - Activate Fuse": TunicLocationData("Quarry", "Quarry Entry"),
|
||||
"Rooted Ziggurat Lower - [Miniboss] Activate Fuse": TunicLocationData("Rooted Ziggurat Lower", "Rooted Ziggurat Lower Miniboss Platform"),
|
||||
"Rooted Ziggurat Lower - [Before Boss] Activate Fuse": TunicLocationData("Rooted Ziggurat Lower", "Rooted Ziggurat Lower Back"),
|
||||
}
|
||||
|
||||
fuse_location_base_id = base_id + 10000
|
||||
fuse_location_name_to_id: dict[str, int] = {name: fuse_location_base_id + index
|
||||
for index, name in enumerate(fuse_location_table)}
|
||||
|
||||
fuse_location_groups: dict[str, set[str]] = {}
|
||||
for location_name, location_data in fuse_location_table.items():
|
||||
fuse_location_groups.setdefault(location_data.loc_group, set()).add(location_name)
|
||||
fuse_location_groups.setdefault("Fuses", set()).add(location_name)
|
||||
|
||||
|
||||
# to be deduplicated in the big refactor
|
||||
def has_ladder(ladder: str, state: CollectionState, world: "TunicWorld") -> bool:
|
||||
return not world.options.shuffle_ladders or state.has(ladder, world.player)
|
||||
|
||||
|
||||
def set_fuse_location_rules(world: "TunicWorld") -> None:
|
||||
player = world.player
|
||||
|
||||
set_rule(world.get_location("Overworld - [Southeast] Activate Fuse"),
|
||||
lambda state: state.has(laurels, player)
|
||||
and has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Swamp - [Central] Activate Fuse"),
|
||||
lambda state: state.has_all(fuse_activation_reqs[swamp_fuse_2], player)
|
||||
and has_ability(prayer, state, world)
|
||||
and has_sword(state, player))
|
||||
set_rule(world.get_location("Swamp - [Outside Cathedral] Activate Fuse"),
|
||||
lambda state: state.has_all(fuse_activation_reqs[swamp_fuse_3], player)
|
||||
and has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Cathedral - Activate Fuse"),
|
||||
lambda state: has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("West Furnace - Activate Fuse"),
|
||||
lambda state: has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("West Garden - [South Highlands] Activate Fuse"),
|
||||
lambda state: has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Ruined Atoll - [Northwest] Activate Fuse"),
|
||||
lambda state: state.has_any([grapple, laurels], player)
|
||||
and has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Ruined Atoll - [Northeast] Activate Fuse"),
|
||||
lambda state: has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Ruined Atoll - [Southeast] Activate Fuse"),
|
||||
lambda state: has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Ruined Atoll - [Southwest] Activate Fuse"),
|
||||
lambda state: has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Library Lab - Activate Fuse"),
|
||||
lambda state: has_ability(prayer, state, world)
|
||||
and has_ladder("Ladders in Library", state, world))
|
||||
set_rule(world.get_location("Fortress Courtyard - [From Overworld] Activate Fuse"),
|
||||
lambda state: has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Fortress Courtyard - [Near Cave] Activate Fuse"),
|
||||
lambda state: state.has(fortress_exterior_fuse_1, player)
|
||||
and has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Fortress Courtyard - [Upper] Activate Fuse"),
|
||||
lambda state: state.has(fortress_exterior_fuse_1, player)
|
||||
and has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Fortress Courtyard - [Central] Activate Fuse"),
|
||||
lambda state: state.has_all(fuse_activation_reqs[fortress_courtyard_lower_fuse], player)
|
||||
and has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Beneath the Fortress - Activate Fuse"),
|
||||
lambda state: state.has_all(fuse_activation_reqs[beneath_the_vault_fuse], player)
|
||||
and has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Eastern Vault Fortress - [Candle Room] Activate Fuse"),
|
||||
lambda state: state.has_all(fuse_activation_reqs[fortress_candles_fuse], player)
|
||||
and has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Eastern Vault Fortress - [Left of Door] Activate Fuse"),
|
||||
lambda state: state.has_all(fuse_activation_reqs[fortress_door_left_fuse], player)
|
||||
and has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Eastern Vault Fortress - [Right of Door] Activate Fuse"),
|
||||
lambda state: state.has_all(fuse_activation_reqs[fortress_door_right_fuse], player)
|
||||
and has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Quarry Entryway - Activate Fuse"),
|
||||
lambda state: state.has(grapple, player)
|
||||
and has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Quarry - Activate Fuse"),
|
||||
lambda state: state.has_all(fuse_activation_reqs[quarry_fuse_2], player)
|
||||
and has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Rooted Ziggurat Lower - [Miniboss] Activate Fuse"),
|
||||
lambda state: has_sword(state, player)
|
||||
and has_ability(prayer, state, world))
|
||||
set_rule(world.get_location("Rooted Ziggurat Lower - [Before Boss] Activate Fuse"),
|
||||
lambda state: has_ability(prayer, state, world))
|
||||
|
||||
@@ -173,6 +173,31 @@ item_table: dict[str, TunicItemData] = {
|
||||
"Ladders in Lower Quarry": TunicItemData(IC.progression, 0, 149, "Ladders"),
|
||||
"Ladders in Swamp": TunicItemData(IC.progression, 0, 150, "Ladders"),
|
||||
"Grass": TunicItemData(IC.filler, 0, 151),
|
||||
"Swamp Fuse 1": TunicItemData(IC.progression, 0, 157, "Fuses"),
|
||||
"Swamp Fuse 2": TunicItemData(IC.progression, 0, 158, "Fuses"),
|
||||
"Swamp Fuse 3": TunicItemData(IC.progression, 0, 159, "Fuses"),
|
||||
"Cathedral Elevator Fuse": TunicItemData(IC.progression, 0, 160, "Fuses"),
|
||||
"Quarry Fuse 1": TunicItemData(IC.progression, 0, 161, "Fuses"),
|
||||
"Quarry Fuse 2": TunicItemData(IC.progression, 0, 162, "Fuses"),
|
||||
"Ziggurat Miniboss Fuse": TunicItemData(IC.progression, 0, 163, "Fuses"),
|
||||
"Ziggurat Teleporter Fuse": TunicItemData(IC.progression, 0, 164, "Fuses"),
|
||||
"Fortress Exterior Fuse 1": TunicItemData(IC.progression, 0, 165, "Fuses"),
|
||||
"Fortress Exterior Fuse 2": TunicItemData(IC.progression, 0, 166, "Fuses"),
|
||||
"Fortress Courtyard Upper Fuse": TunicItemData(IC.progression, 0, 167, "Fuses"),
|
||||
"Fortress Courtyard Fuse": TunicItemData(IC.progression, 0, 168, "Fuses"),
|
||||
"Beneath the Vault Fuse": TunicItemData(IC.progression, 0, 169, "Fuses"),
|
||||
"Fortress Candles Fuse": TunicItemData(IC.progression, 0, 170, "Fuses"),
|
||||
"Fortress Door Left Fuse": TunicItemData(IC.progression, 0, 171, "Fuses"),
|
||||
"Fortress Door Right Fuse": TunicItemData(IC.progression, 0, 172, "Fuses"),
|
||||
"West Furnace Fuse": TunicItemData(IC.progression, 0, 173, "Fuses"),
|
||||
"West Garden Fuse": TunicItemData(IC.progression, 0, 174, "Fuses"),
|
||||
"Atoll Northeast Fuse": TunicItemData(IC.progression, 0, 175, "Fuses"),
|
||||
"Atoll Northwest Fuse": TunicItemData(IC.progression, 0, 176, "Fuses"),
|
||||
"Atoll Southeast Fuse": TunicItemData(IC.progression, 0, 177, "Fuses"),
|
||||
"Atoll Southwest Fuse": TunicItemData(IC.progression, 0, 178, "Fuses"),
|
||||
"Library Lab Fuse": TunicItemData(IC.progression, 0, 179, "Fuses"),
|
||||
"East Bell": TunicItemData(IC.progression, 0, 180, "Bells"),
|
||||
"West Bell": TunicItemData(IC.progression, 0, 181, "Bells")
|
||||
}
|
||||
|
||||
# items to be replaced by fool traps
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from typing import NamedTuple
|
||||
|
||||
# from .bells import bell_location_table
|
||||
from .bells import bell_location_table
|
||||
from .breakables import breakable_location_table
|
||||
from .constants import base_id
|
||||
# from .fuses import fuse_location_table
|
||||
from .fuses import fuse_location_table
|
||||
from .grass import grass_location_table
|
||||
|
||||
|
||||
@@ -329,8 +329,8 @@ standard_location_name_to_id: dict[str, int] = {name: base_id + index for index,
|
||||
all_locations = location_table.copy()
|
||||
all_locations.update(grass_location_table)
|
||||
all_locations.update(breakable_location_table)
|
||||
# all_locations.update(fuse_location_table)
|
||||
# all_locations.update(bell_location_table)
|
||||
all_locations.update(fuse_location_table)
|
||||
all_locations.update(bell_location_table)
|
||||
|
||||
location_name_groups: dict[str, set[str]] = {}
|
||||
for loc_name, loc_data in location_table.items():
|
||||
|
||||
@@ -3,7 +3,6 @@ from typing import TYPE_CHECKING
|
||||
from BaseClasses import CollectionState
|
||||
|
||||
from .constants import *
|
||||
from .fuses import fuse_activation_reqs
|
||||
from .options import HexagonQuestAbilityUnlockType, IceGrappling
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -89,10 +88,38 @@ def can_get_past_bushes(state: CollectionState, world: "TunicWorld") -> bool:
|
||||
return has_sword(state, world.player) or state.has_any((fire_wand, laurels, gun), world.player)
|
||||
|
||||
|
||||
def has_fuses(fuse_event: str, state: CollectionState, world: "TunicWorld") -> bool:
|
||||
player = world.player
|
||||
fuses_option = False # replace fuses_option with world.options.shuffle_fuses when fuse shuffle is in
|
||||
if fuses_option:
|
||||
return state.has_all(fuse_activation_reqs[fuse_event], player)
|
||||
# for fuse locations and reusing event names to simplify er_rules
|
||||
fuse_activation_reqs: dict[str, list[str]] = {
|
||||
swamp_fuse_2: [swamp_fuse_1],
|
||||
swamp_fuse_3: [swamp_fuse_1, swamp_fuse_2],
|
||||
fortress_exterior_fuse_2: [fortress_exterior_fuse_1],
|
||||
beneath_the_vault_fuse: [fortress_exterior_fuse_1, fortress_exterior_fuse_2],
|
||||
fortress_candles_fuse: [fortress_exterior_fuse_1, fortress_exterior_fuse_2, beneath_the_vault_fuse],
|
||||
fortress_door_left_fuse: [fortress_exterior_fuse_1, fortress_exterior_fuse_2, beneath_the_vault_fuse,
|
||||
fortress_candles_fuse],
|
||||
fortress_courtyard_upper_fuse: [fortress_exterior_fuse_1],
|
||||
fortress_courtyard_lower_fuse: [fortress_exterior_fuse_1, fortress_courtyard_upper_fuse],
|
||||
fortress_door_right_fuse: [fortress_exterior_fuse_1, fortress_courtyard_upper_fuse, fortress_courtyard_lower_fuse],
|
||||
quarry_fuse_2: [quarry_fuse_1],
|
||||
"Activate Furnace Fuse": [west_furnace_fuse],
|
||||
"Activate South and West Fortress Exterior Fuses": [fortress_exterior_fuse_1, fortress_exterior_fuse_2],
|
||||
"Activate Upper and Central Fortress Exterior Fuses": [fortress_exterior_fuse_1, fortress_courtyard_upper_fuse,
|
||||
fortress_courtyard_lower_fuse],
|
||||
"Activate Beneath the Vault Fuse": [fortress_exterior_fuse_1, fortress_exterior_fuse_2, beneath_the_vault_fuse],
|
||||
"Activate Eastern Vault West Fuses": [fortress_exterior_fuse_1, fortress_exterior_fuse_2, beneath_the_vault_fuse,
|
||||
fortress_candles_fuse, fortress_door_left_fuse],
|
||||
"Activate Eastern Vault East Fuse": [fortress_exterior_fuse_1, fortress_courtyard_upper_fuse,
|
||||
fortress_courtyard_lower_fuse, fortress_door_right_fuse],
|
||||
"Activate Quarry Connector Fuse": [quarry_fuse_1],
|
||||
"Activate Quarry Fuse": [quarry_fuse_1, quarry_fuse_2],
|
||||
"Activate Ziggurat Fuse": [ziggurat_teleporter_fuse],
|
||||
"Activate West Garden Fuse": [west_garden_fuse],
|
||||
"Activate Library Fuse": [library_lab_fuse],
|
||||
}
|
||||
|
||||
return state.has(fuse_event, player)
|
||||
|
||||
def has_fuses(fuse_event: str, state: CollectionState, world: "TunicWorld") -> bool:
|
||||
if world.options.shuffle_fuses:
|
||||
return state.has_all(fuse_activation_reqs[fuse_event], world.player)
|
||||
|
||||
return state.has(fuse_event, world.player)
|
||||
|
||||
@@ -198,6 +198,24 @@ class ShuffleLadders(Toggle):
|
||||
display_name = "Shuffle Ladders"
|
||||
|
||||
|
||||
class ShuffleFuses(Toggle):
|
||||
"""
|
||||
Praying at a fuse will reward a check instead of turning on the power. The power from each fuse gets turned into an
|
||||
item that must be found in order to restore power for that part of the path.
|
||||
"""
|
||||
internal_name = "shuffle_fuses"
|
||||
display_name = "Shuffle Fuses"
|
||||
|
||||
|
||||
class ShuffleBells(Toggle):
|
||||
"""
|
||||
The East and West bells are shuffled into the item pool and must be found in order to unlock the Sealed Temple.
|
||||
Ringing the bells will instead now reward a check.
|
||||
"""
|
||||
internal_name = "shuffle_bells"
|
||||
display_name = "Shuffle Bells"
|
||||
|
||||
|
||||
class GrassRandomizer(Toggle):
|
||||
"""
|
||||
Turns over 6,000 blades of grass and bushes in the game into checks.
|
||||
@@ -357,8 +375,8 @@ class TunicOptions(PerGameCommonOptions):
|
||||
hexagon_quest_ability_type: HexagonQuestAbilityUnlockType
|
||||
|
||||
shuffle_ladders: ShuffleLadders
|
||||
# shuffle_fuses: ShuffleFuses
|
||||
# shuffle_bells: ShuffleBells
|
||||
shuffle_fuses: ShuffleFuses
|
||||
shuffle_bells: ShuffleBells
|
||||
grass_randomizer: GrassRandomizer
|
||||
breakable_shuffle: BreakableShuffle
|
||||
local_fill: LocalFill
|
||||
|
||||
@@ -2,7 +2,7 @@ from .. import options
|
||||
from .bases import TunicTestBase
|
||||
|
||||
|
||||
class TestAccess(TunicTestBase):
|
||||
class TestWells(TunicTestBase):
|
||||
options = {options.CombatLogic.internal_name: options.CombatLogic.option_off}
|
||||
|
||||
# test that the wells function properly. Since fairies is written the same way, that should succeed too
|
||||
|
||||
@@ -25,8 +25,8 @@ def setup_options_from_slot_data(world: "TunicWorld") -> None:
|
||||
world.options.hexagon_quest_ability_type.value = world.passthrough.get("hexagon_quest_ability_type", 0)
|
||||
world.options.entrance_rando.value = world.passthrough["entrance_rando"]
|
||||
world.options.shuffle_ladders.value = world.passthrough["shuffle_ladders"]
|
||||
# world.options.shuffle_fuses.value = world.passthrough.get("shuffle_fuses", 0)
|
||||
# world.options.shuffle_bells.value = world.passthrough.get("shuffle_bells", 0)
|
||||
world.options.shuffle_fuses.value = world.passthrough.get("shuffle_fuses", 0)
|
||||
world.options.shuffle_bells.value = world.passthrough.get("shuffle_bells", 0)
|
||||
world.options.grass_randomizer.value = world.passthrough.get("grass_randomizer", 0)
|
||||
world.options.breakable_shuffle.value = world.passthrough.get("breakable_shuffle", 0)
|
||||
world.options.entrance_layout.value = EntranceLayout.option_standard
|
||||
|
||||
Reference in New Issue
Block a user