Skip to content

Commit b0b3e36

Browse files
ScipioWrightsilent-destroyerExempt-Medicqwint
authored
TUNIC: The Big Refactor (ArchipelagoMW#5195)
* 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 a6ee9a9. * Revert "# Conflicts:" This reverts commit c74ccd7. * 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 * Pull out fuse and bell shuffle * Pull out fuse and bell shuffle * Update worlds/tunic/options.py Co-authored-by: qwint <qwint.42@gmail.com> * Update worlds/tunic/logic_helpers.py Co-authored-by: qwint <qwint.42@gmail.com> * {} -> () in state functions * {} -> () in state functions * Change {} -> () in state functions, use constant for gun * Remove floating constants in er_data * Finish hard deprecating FixedShop * Finish hard deprecating FixedShop * Fix zig skip showing up in decoupled fixed shop --------- 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> Co-authored-by: qwint <qwint.42@gmail.com>
1 parent 42ace29 commit b0b3e36

17 files changed

+749
-918
lines changed

worlds/tunic/__init__.py

Lines changed: 148 additions & 95 deletions
Large diffs are not rendered by default.

worlds/tunic/breakables.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1+
from enum import IntEnum
12
from typing import TYPE_CHECKING, NamedTuple
23

3-
from enum import IntEnum
44
from BaseClasses import CollectionState, Region
55
from worlds.generic.Rules import set_rule
6-
from .rules import has_sword, has_melee
6+
7+
from .constants import base_id
78
from .er_rules import can_shop
8-
if TYPE_CHECKING:
9-
from . import TunicWorld
9+
from .logic_helpers import has_sword, has_melee
1010

1111

12-
# just getting an id that is a decent chunk ahead of the grass ones
13-
breakable_base_id = 509342400 + 8000
12+
if TYPE_CHECKING:
13+
from . import TunicWorld
1414

1515

1616
class BreakableType(IntEnum):
@@ -341,6 +341,7 @@ class TunicLocationData(NamedTuple):
341341
}
342342

343343

344+
breakable_base_id = base_id + 8000
344345
breakable_location_name_to_id: dict[str, int] = {name: breakable_base_id + index
345346
for index, name in enumerate(breakable_location_table)}
346347

@@ -358,6 +359,7 @@ class TunicLocationData(NamedTuple):
358359
"Beneath the Well Main": "Beneath the Well",
359360
"Well Boss": "Dark Tomb Checkpoint",
360361
"Dark Tomb Main": "Dark Tomb",
362+
"Magic Dagger House": "West Garden House",
361363
"Fortress Courtyard Upper": "Fortress Courtyard",
362364
"Fortress Courtyard Upper pot": "Fortress Courtyard",
363365
"Fortress Courtyard west pots": "Fortress Courtyard",
@@ -370,13 +372,16 @@ class TunicLocationData(NamedTuple):
370372
"Fortress Grave Path westmost pot": "Fortress Grave Path",
371373
"Fortress Grave Path pots": "Fortress Grave Path",
372374
"Dusty": "Fortress Leaf Piles",
373-
"Frog Stairs Upper": "Frog Stairs",
375+
"Frog Stairs Upper": "Frog Stairway",
376+
"Frog's Domain Front": "Frog's Domain",
377+
"Frog's Domain Main": "Frog's Domain",
374378
"Quarry Monastery Entry": "Quarry",
375379
"Quarry Back": "Quarry",
376380
"Lower Quarry": "Quarry",
377381
"Lower Quarry upper pots": "Quarry",
378382
"Even Lower Quarry": "Quarry",
379383
"Monastery Back": "Monastery",
384+
"Cathedral Main": "Cathedral",
380385
}
381386

382387

worlds/tunic/combat_logic.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
from typing import Dict, List, NamedTuple, Tuple, Optional
2-
from enum import IntEnum
31
from collections import defaultdict
2+
from enum import IntEnum
3+
from typing import NamedTuple
4+
45
from BaseClasses import CollectionState
5-
from .rules import has_sword, has_melee
66
from worlds.AutoWorld import LogicMixin
77

8+
from .logic_helpers import has_sword, has_melee
9+
810

911
# the vanilla stats you are expected to have to get through an area, based on where they are in vanilla
1012
class AreaStats(NamedTuple):
@@ -16,12 +18,12 @@ class AreaStats(NamedTuple):
1618
sp_level: int
1719
mp_level: int
1820
potion_count: int
19-
equipment: List[str] = []
21+
equipment: list[str] = []
2022
is_boss: bool = False
2123

2224

2325
# the vanilla upgrades/equipment you would have
24-
area_data: Dict[str, AreaStats] = {
26+
area_data: dict[str, AreaStats] = {
2527
# The upgrade page is right by the Well entrance. Upper Overworld by the chest in the top right might need something
2628
"Overworld": AreaStats(1, 1, 1, 1, 1, 1, 0, ["Stick"]),
2729
"East Forest": AreaStats(1, 1, 1, 1, 1, 1, 0, ["Sword"]),
@@ -52,9 +54,9 @@ class AreaStats(NamedTuple):
5254

5355
# these are used for caching which areas can currently be reached in state
5456
# Gauntlet does not have exclusively higher stat requirements, so it will be checked separately
55-
boss_areas: List[str] = [name for name, data in area_data.items() if data.is_boss and name != "Gauntlet"]
57+
boss_areas: list[str] = [name for name, data in area_data.items() if data.is_boss and name != "Gauntlet"]
5658
# Swamp does not have exclusively higher stat requirements, so it will be checked separately
57-
non_boss_areas: List[str] = [name for name, data in area_data.items() if not data.is_boss and name != "Swamp"]
59+
non_boss_areas: list[str] = [name for name, data in area_data.items() if not data.is_boss and name != "Swamp"]
5860

5961

6062
class CombatState(IntEnum):
@@ -114,7 +116,7 @@ def has_combat_reqs(area_name: str, state: CollectionState, player: int) -> bool
114116
return met_combat_reqs
115117

116118

117-
def check_combat_reqs(area_name: str, state: CollectionState, player: int, alt_data: Optional[AreaStats] = None) -> bool:
119+
def check_combat_reqs(area_name: str, state: CollectionState, player: int, alt_data: AreaStats | None = None) -> bool:
118120
data = alt_data or area_data[area_name]
119121
extra_att_needed = 0
120122
extra_def_needed = 0
@@ -303,7 +305,7 @@ def has_required_stats(data: AreaStats, state: CollectionState, player: int) ->
303305

304306

305307
# returns a tuple of your max attack level, the number of attack offerings
306-
def get_att_level(state: CollectionState, player: int) -> Tuple[int, int]:
308+
def get_att_level(state: CollectionState, player: int) -> tuple[int, int]:
307309
att_offerings = state.count("ATT Offering", player)
308310
att_upgrades = state.count("Hero Relic - ATT", player)
309311
sword_level = state.count("Sword Upgrade", player)
@@ -315,44 +317,44 @@ def get_att_level(state: CollectionState, player: int) -> Tuple[int, int]:
315317

316318

317319
# returns a tuple of your max defense level, the number of defense offerings
318-
def get_def_level(state: CollectionState, player: int) -> Tuple[int, int]:
320+
def get_def_level(state: CollectionState, player: int) -> tuple[int, int]:
319321
def_offerings = state.count("DEF Offering", player)
320322
# defense falls off, can just cap it at 8 for simplicity
321323
return (min(8, 1 + def_offerings
322-
+ state.count_from_list({"Hero Relic - DEF", "Secret Legend", "Phonomath"}, player))
324+
+ state.count_from_list(("Hero Relic - DEF", "Secret Legend", "Phonomath"), player))
323325
+ (2 if state.has("Shield", player) else 0)
324326
+ (2 if state.has("Hero's Laurels", player) else 0),
325327
def_offerings)
326328

327329

328330
# returns a tuple of your max potion level, the number of potion offerings
329-
def get_potion_level(state: CollectionState, player: int) -> Tuple[int, int]:
331+
def get_potion_level(state: CollectionState, player: int) -> tuple[int, int]:
330332
potion_offerings = min(2, state.count("Potion Offering", player))
331333
# your third potion upgrade (from offerings) costs 1,000 money, reasonable to assume you won't do that
332334
return (1 + potion_offerings
333-
+ state.count_from_list({"Hero Relic - POTION", "Just Some Pals", "Spring Falls", "Back To Work"}, player),
335+
+ state.count_from_list(("Hero Relic - POTION", "Just Some Pals", "Spring Falls", "Back To Work"), player),
334336
potion_offerings)
335337

336338

337339
# returns a tuple of your max hp level, the number of hp offerings
338-
def get_hp_level(state: CollectionState, player: int) -> Tuple[int, int]:
340+
def get_hp_level(state: CollectionState, player: int) -> tuple[int, int]:
339341
hp_offerings = state.count("HP Offering", player)
340342
return 1 + hp_offerings + state.count("Hero Relic - HP", player), hp_offerings
341343

342344

343345
# returns a tuple of your max sp level, the number of sp offerings
344-
def get_sp_level(state: CollectionState, player: int) -> Tuple[int, int]:
346+
def get_sp_level(state: CollectionState, player: int) -> tuple[int, int]:
345347
sp_offerings = state.count("SP Offering", player)
346348
return (1 + sp_offerings
347-
+ state.count_from_list({"Hero Relic - SP", "Mr Mayor", "Power Up",
348-
"Regal Weasel", "Forever Friend"}, player),
349+
+ state.count_from_list(("Hero Relic - SP", "Mr Mayor", "Power Up",
350+
"Regal Weasel", "Forever Friend"), player),
349351
sp_offerings)
350352

351353

352-
def get_mp_level(state: CollectionState, player: int) -> Tuple[int, int]:
354+
def get_mp_level(state: CollectionState, player: int) -> tuple[int, int]:
353355
mp_offerings = state.count("MP Offering", player)
354356
return (1 + mp_offerings
355-
+ state.count_from_list({"Hero Relic - MP", "Sacred Geometry", "Vintage", "Dusty"}, player),
357+
+ state.count_from_list(("Hero Relic - MP", "Sacred Geometry", "Vintage", "Dusty"), player),
356358
mp_offerings)
357359

358360

@@ -426,9 +428,9 @@ def calc_def_sp_cost(def_upgrades: int, sp_upgrades: int) -> int:
426428

427429

428430
class TunicState(LogicMixin):
429-
tunic_need_to_reset_combat_from_collect: Dict[int, bool]
430-
tunic_need_to_reset_combat_from_remove: Dict[int, bool]
431-
tunic_area_combat_state: Dict[int, Dict[str, int]]
431+
tunic_need_to_reset_combat_from_collect: dict[int, bool]
432+
tunic_need_to_reset_combat_from_remove: dict[int, bool]
433+
tunic_area_combat_state: dict[int, dict[str, int]]
432434

433435
def init_mixin(self, _):
434436
# the per-player need to reset the combat state when collecting a combat item

worlds/tunic/constants.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
base_id = 509342400
2+
3+
laurels = "Hero's Laurels"
4+
grapple = "Magic Orb"
5+
ice_dagger = "Magic Dagger"
6+
fire_wand = "Magic Wand"
7+
gun = "Gun"
8+
lantern = "Lantern"
9+
fairies = "Fairy"
10+
coins = "Golden Coin"
11+
prayer = "Pages 24-25 (Prayer)"
12+
holy_cross = "Pages 42-43 (Holy Cross)"
13+
icebolt = "Pages 52-53 (Icebolt)"
14+
shield = "Shield"
15+
key = "Key"
16+
house_key = "Old House Key"
17+
vault_key = "Fortress Vault Key"
18+
mask = "Scavenger Mask"
19+
red_hexagon = "Red Questagon"
20+
green_hexagon = "Green Questagon"
21+
blue_hexagon = "Blue Questagon"
22+
gold_hexagon = "Gold Questagon"
23+
24+
swamp_fuse_1 = "Swamp Fuse 1"
25+
swamp_fuse_2 = "Swamp Fuse 2"
26+
swamp_fuse_3 = "Swamp Fuse 3"
27+
cathedral_elevator_fuse = "Cathedral Elevator Fuse"
28+
quarry_fuse_1 = "Quarry Fuse 1"
29+
quarry_fuse_2 = "Quarry Fuse 2"
30+
ziggurat_miniboss_fuse = "Ziggurat Miniboss Fuse"
31+
ziggurat_teleporter_fuse = "Ziggurat Teleporter Fuse"
32+
fortress_exterior_fuse_1 = "Fortress Exterior Fuse 1"
33+
fortress_exterior_fuse_2 = "Fortress Exterior Fuse 2"
34+
fortress_courtyard_upper_fuse = "Fortress Courtyard Upper Fuse"
35+
fortress_courtyard_lower_fuse = "Fortress Courtyard Fuse"
36+
beneath_the_vault_fuse = "Beneath the Vault Fuse" # event needs to be renamed probably
37+
fortress_candles_fuse = "Fortress Candles Fuse"
38+
fortress_door_left_fuse = "Fortress Door Left Fuse"
39+
fortress_door_right_fuse = "Fortress Door Right Fuse"
40+
west_furnace_fuse = "West Furnace Fuse"
41+
west_garden_fuse = "West Garden Fuse"
42+
atoll_northeast_fuse = "Atoll Northeast Fuse"
43+
atoll_northwest_fuse = "Atoll Northwest Fuse"
44+
atoll_southeast_fuse = "Atoll Southeast Fuse"
45+
atoll_southwest_fuse = "Atoll Southwest Fuse"
46+
library_lab_fuse = "Library Lab Fuse"
47+
48+
# "Quarry - [East] Bombable Wall" is excluded from this list since it has slightly different rules
49+
bomb_walls = ["East Forest - Bombable Wall", "Eastern Vault Fortress - [East Wing] Bombable Wall",
50+
"Overworld - [Central] Bombable Wall", "Overworld - [Southwest] Bombable Wall Near Fountain",
51+
"Quarry - [West] Upper Area Bombable Wall", "Ruined Atoll - [Northwest] Bombable Wall"]

0 commit comments

Comments
 (0)