diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index 250f6706..ff1dc414 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -13,10 +13,10 @@ from .er_data import portal_mapping, RegionInfo, tunic_er_regions from .options import (TunicOptions, EntranceRando, tunic_option_groups, tunic_option_presets, TunicPlandoConnections, LaurelsLocation, LogicRules, LaurelsZips, IceGrappling, LadderStorage, check_options, get_hexagons_in_pool, HexagonQuestAbilityUnlockType) +from .breakables import breakable_location_name_to_id, breakable_location_groups, breakable_location_table from .combat_logic import area_data, CombatState from worlds.AutoWorld import WebWorld, World from Options import PlandoConnection, OptionError -from decimal import Decimal, ROUND_HALF_UP from settings import Group, Bool @@ -81,10 +81,13 @@ class TunicWorld(World): location_name_groups = location_name_groups for group_name, members in grass_location_name_groups.items(): 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) 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) player_location_table: Dict[str, int] ability_unlocks: Dict[str, int] @@ -158,6 +161,7 @@ class TunicWorld(World): self.options.entrance_rando.value = self.passthrough["entrance_rando"] self.options.shuffle_ladders.value = self.passthrough["shuffle_ladders"] self.options.grass_randomizer.value = self.passthrough.get("grass_randomizer", 0) + self.options.breakable_shuffle.value = self.passthrough.get("breakable_shuffle", 0) self.options.fixed_shop.value = self.options.fixed_shop.option_false self.options.laurels_location.value = self.options.laurels_location.option_anywhere self.options.combat_logic.value = self.passthrough["combat_logic"] @@ -170,7 +174,12 @@ class TunicWorld(World): if self.options.local_fill == -1: if self.options.grass_randomizer: - self.options.local_fill.value = 95 + if self.options.breakable_shuffle: + self.options.local_fill.value = 96 + else: + self.options.local_fill.value = 95 + elif self.options.breakable_shuffle: + self.options.local_fill.value = 40 else: self.options.local_fill.value = 0 @@ -182,6 +191,13 @@ class TunicWorld(World): self.player_location_table.update(grass_location_name_to_id) + if self.options.breakable_shuffle: + if self.options.entrance_rando: + self.player_location_table.update(breakable_location_name_to_id) + else: + self.player_location_table.update({name: num for name, num in breakable_location_name_to_id.items() + if not name.startswith("Purgatory")}) + @classmethod def stage_generate_early(cls, multiworld: MultiWorld) -> None: tunic_worlds: Tuple[TunicWorld] = multiworld.get_game_worlds("TUNIC") @@ -258,7 +274,8 @@ class TunicWorld(World): itemclass: ItemClassification = (classification or (item_data.combat_ic if self.options.combat_logic else None) or (ItemClassification.progression | ItemClassification.useful - if name == "Glass Cannon" and self.options.grass_randomizer + if name == "Glass Cannon" + and (self.options.grass_randomizer or self.options.breakable_shuffle) and not self.options.start_with_sword else None) or (ItemClassification.progression | ItemClassification.useful if name == "Shield" and self.options.ladder_storage @@ -280,6 +297,13 @@ class TunicWorld(World): items_to_create["Fool Trap"] += items_to_create[money_fool] items_to_create[money_fool] = 0 + # creating these after the fool traps are made mostly so we don't have to mess with it + if self.options.breakable_shuffle: + for loc_data in breakable_location_table.values(): + if not self.options.entrance_rando and loc_data.er_region == "Purgatory": + continue + items_to_create[f"Money x{self.random.randint(1, 5)}"] += 1 + if self.options.start_with_sword: self.multiworld.push_precollected(self.create_item("Sword")) @@ -472,9 +496,9 @@ class TunicWorld(World): self.ability_unlocks["Pages 42-43 (Holy Cross)"] = self.passthrough["Hexagon Quest Holy Cross"] self.ability_unlocks["Pages 52-53 (Icebolt)"] = self.passthrough["Hexagon Quest Icebolt"] - # Ladders and Combat Logic uses ER rules with vanilla connections for easier maintenance + # Most non-standard options use ER regions if (self.options.entrance_rando or self.options.shuffle_ladders or self.options.combat_logic - or self.options.grass_randomizer): + or self.options.grass_randomizer or self.options.breakable_shuffle): portal_pairs = create_er_regions(self) if self.options.entrance_rando: # these get interpreted by the game to tell it which entrances to connect @@ -502,9 +526,9 @@ class TunicWorld(World): victory_region.locations.append(victory_location) def set_rules(self) -> None: - # same reason as in create_regions, could probably be put into create_regions + # same reason as in create_regions if (self.options.entrance_rando or self.options.shuffle_ladders or self.options.combat_logic - or self.options.grass_randomizer): + or self.options.grass_randomizer or self.options.breakable_shuffle): set_er_location_rules(self) else: set_region_rules(self) @@ -609,6 +633,7 @@ class TunicWorld(World): "Hexagon Quest Goal": self.options.hexagon_goal.value, "Entrance Rando": self.tunic_portal_pairs, "disable_local_spoiler": int(self.settings.disable_local_spoiler or self.multiworld.is_race), + "breakable_shuffle": self.options.breakable_shuffle.value, } # this would be in a stage if there was an appropriate stage for it diff --git a/worlds/tunic/breakables.py b/worlds/tunic/breakables.py new file mode 100644 index 00000000..156bece7 --- /dev/null +++ b/worlds/tunic/breakables.py @@ -0,0 +1,466 @@ +from typing import TYPE_CHECKING, NamedTuple + +from enum import IntEnum +from BaseClasses import CollectionState, Region +from worlds.generic.Rules import set_rule +from .rules import has_sword, has_melee +from .er_rules import can_shop +if TYPE_CHECKING: + from . import TunicWorld + + +# just getting an id that is a decent chunk ahead of the grass ones +breakable_base_id = 509342400 + 8000 + + +class BreakableType(IntEnum): + pot = 1 + fire_pot = 2 + explosive_pot = 3 + sign = 4 + barrel = 5 + crate = 6 + table = 7 + glass = 8 + leaves = 9 + wall = 10 + + +class TunicLocationData(NamedTuple): + er_region: str + breakable: BreakableType + + +breakable_location_table: dict[str, TunicLocationData] = { + "Overworld - [Northwest] Sign by Quarry Gate": TunicLocationData("Overworld", BreakableType.sign), + "Overworld - [Central] Sign South of Checkpoint": TunicLocationData("Overworld", BreakableType.sign), + "Overworld - [Central] Sign by Ruined Passage": TunicLocationData("Overworld", BreakableType.sign), + "Overworld - [East] Pot near Slimes 1": TunicLocationData("East Overworld", BreakableType.pot), + "Overworld - [East] Pot near Slimes 2": TunicLocationData("East Overworld", BreakableType.pot), + "Overworld - [East] Pot near Slimes 3": TunicLocationData("East Overworld", BreakableType.pot), + "Overworld - [East] Pot near Slimes 4": TunicLocationData("East Overworld", BreakableType.pot), + "Overworld - [East] Pot near Slimes 5": TunicLocationData("East Overworld", BreakableType.pot), + "Overworld - [East] Forest Sign": TunicLocationData("East Overworld", BreakableType.sign), + "Overworld - [East] Fortress Sign": TunicLocationData("East Overworld", BreakableType.sign), + "Overworld - [North] Pot 1": TunicLocationData("Upper Overworld", BreakableType.pot), + "Overworld - [North] Pot 2": TunicLocationData("Upper Overworld", BreakableType.pot), + "Overworld - [North] Pot 3": TunicLocationData("Upper Overworld", BreakableType.pot), + "Overworld - [North] Pot 4": TunicLocationData("Upper Overworld", BreakableType.pot), + "Overworld - [West] Sign Near West Garden Entrance": TunicLocationData("Overworld to West Garden from Furnace", BreakableType.sign), + "Stick House - Pot 1": TunicLocationData("Stick House", BreakableType.pot), + "Stick House - Pot 2": TunicLocationData("Stick House", BreakableType.pot), + "Stick House - Pot 3": TunicLocationData("Stick House", BreakableType.pot), + "Stick House - Pot 4": TunicLocationData("Stick House", BreakableType.pot), + "Stick House - Pot 5": TunicLocationData("Stick House", BreakableType.pot), + "Stick House - Pot 6": TunicLocationData("Stick House", BreakableType.pot), + "Stick House - Pot 7": TunicLocationData("Stick House", BreakableType.pot), + "Ruined Shop - Pot 1": TunicLocationData("Ruined Shop", BreakableType.pot), + "Ruined Shop - Pot 2": TunicLocationData("Ruined Shop", BreakableType.pot), + "Ruined Shop - Pot 3": TunicLocationData("Ruined Shop", BreakableType.pot), + "Ruined Shop - Pot 4": TunicLocationData("Ruined Shop", BreakableType.pot), + "Ruined Shop - Pot 5": TunicLocationData("Ruined Shop", BreakableType.pot), + "Hourglass Cave - Sign": TunicLocationData("Hourglass Cave", BreakableType.sign), + "Forest Belltower - Pot by Slimes 1": TunicLocationData("Forest Belltower Main", BreakableType.pot), + "Forest Belltower - Pot by Slimes 2": TunicLocationData("Forest Belltower Main", BreakableType.pot), + "Forest Belltower - Pot by Slimes 3": TunicLocationData("Forest Belltower Main", BreakableType.pot), + "Forest Belltower - Pot by Slimes 4": TunicLocationData("Forest Belltower Main", BreakableType.pot), + "Forest Belltower - Pot by Slimes 5": TunicLocationData("Forest Belltower Main", BreakableType.pot), + "Forest Belltower - Pot by Slimes 6": TunicLocationData("Forest Belltower Main", BreakableType.pot), + "Forest Belltower - [Upper] Barrel 1": TunicLocationData("Forest Belltower Upper", BreakableType.barrel), + "Forest Belltower - [Upper] Barrel 2": TunicLocationData("Forest Belltower Upper", BreakableType.barrel), + "Forest Belltower - [Upper] Barrel 3": TunicLocationData("Forest Belltower Upper", BreakableType.barrel), + "Forest Belltower - Pot after Guard Captain 1": TunicLocationData("Forest Belltower Upper", BreakableType.pot), + "Forest Belltower - Pot after Guard Captain 2": TunicLocationData("Forest Belltower Upper", BreakableType.pot), + "Forest Belltower - Pot after Guard Captain 3": TunicLocationData("Forest Belltower Upper", BreakableType.pot), + "Forest Belltower - Pot after Guard Captain 4": TunicLocationData("Forest Belltower Upper", BreakableType.pot), + "Forest Belltower - Pot after Guard Captain 5": TunicLocationData("Forest Belltower Upper", BreakableType.pot), + "Forest Belltower - Pot after Guard Captain 6": TunicLocationData("Forest Belltower Upper", BreakableType.pot), + "Forest Belltower - Pot after Guard Captain 7": TunicLocationData("Forest Belltower Upper", BreakableType.pot), + "Forest Belltower - Pot after Guard Captain 8": TunicLocationData("Forest Belltower Upper", BreakableType.pot), + "Forest Belltower - Pot after Guard Captain 9": TunicLocationData("Forest Belltower Upper", BreakableType.pot), + "Guardhouse 1 - Pot 1": TunicLocationData("Guard House 1 East", BreakableType.pot), + "Guardhouse 1 - Pot 2": TunicLocationData("Guard House 1 East", BreakableType.pot), + "Guardhouse 1 - Pot 3": TunicLocationData("Guard House 1 East", BreakableType.pot), + "Guardhouse 1 - Pot 4": TunicLocationData("Guard House 1 East", BreakableType.pot), + "Guardhouse 1 - Pot 5": TunicLocationData("Guard House 1 East", BreakableType.pot), + "East Forest - Sign by Grave Path": TunicLocationData("East Forest", BreakableType.sign), + "East Forest - Sign by Guardhouse 1": TunicLocationData("East Forest", BreakableType.sign), + "East Forest - Pot by Grave Path 1": TunicLocationData("East Forest", BreakableType.pot), + "East Forest - Pot by Grave Path 2": TunicLocationData("East Forest", BreakableType.pot), + "East Forest - Pot by Grave Path 3": TunicLocationData("East Forest", BreakableType.pot), + "East Forest - Pot by Envoy 1": TunicLocationData("East Forest", BreakableType.pot), + "East Forest - Pot by Envoy 2": TunicLocationData("East Forest", BreakableType.pot), + "East Forest - Pot by Envoy 3": TunicLocationData("East Forest", BreakableType.pot), + "Guardhouse 2 - Bottom Floor Pot 1": TunicLocationData("Guard House 2 Lower", BreakableType.pot), + "Guardhouse 2 - Bottom Floor Pot 2": TunicLocationData("Guard House 2 Lower", BreakableType.pot), + "Guardhouse 2 - Bottom Floor Pot 3": TunicLocationData("Guard House 2 Lower", BreakableType.pot), + "Guardhouse 2 - Bottom Floor Pot 4": TunicLocationData("Guard House 2 Lower", BreakableType.pot), + "Guardhouse 2 - Bottom Floor Pot 5": TunicLocationData("Guard House 2 Lower", BreakableType.pot), + "Beneath the Well - [Side Room] Pot by Chest 1": TunicLocationData("Beneath the Well Back", BreakableType.pot), + "Beneath the Well - [Side Room] Pot by Chest 2": TunicLocationData("Beneath the Well Back", BreakableType.pot), + "Beneath the Well - [Side Room] Pot by Chest 3": TunicLocationData("Beneath the Well Back", BreakableType.pot), + "Beneath the Well - [Third Room] Barrel by Bridge 1": TunicLocationData("Beneath the Well Main", BreakableType.barrel), + "Beneath the Well - [Third Room] Barrel by Bridge 2": TunicLocationData("Beneath the Well Main", BreakableType.barrel), + "Beneath the Well - [Third Room] Barrel by Bridge 3": TunicLocationData("Beneath the Well Main", BreakableType.barrel), + "Beneath the Well - [Third Room] Barrel after Back Corridor 1": TunicLocationData("Beneath the Well Main", BreakableType.barrel), + "Beneath the Well - [Third Room] Barrel after Back Corridor 2": TunicLocationData("Beneath the Well Main", BreakableType.barrel), + "Beneath the Well - [Third Room] Barrel after Back Corridor 3": TunicLocationData("Beneath the Well Main", BreakableType.barrel), + "Beneath the Well - [Third Room] Barrel after Back Corridor 4": TunicLocationData("Beneath the Well Main", BreakableType.barrel), + "Beneath the Well - [Third Room] Barrel after Back Corridor 5": TunicLocationData("Beneath the Well Main", BreakableType.barrel), + "Beneath the Well - [Third Room] Barrel by West Turret 1": TunicLocationData("Beneath the Well Main", BreakableType.barrel), + "Beneath the Well - [Third Room] Barrel by West Turret 2": TunicLocationData("Beneath the Well Main", BreakableType.barrel), + "Beneath the Well - [Third Room] Barrel by West Turret 3": TunicLocationData("Beneath the Well Main", BreakableType.barrel), + "Beneath the Well - [Third Room] Pot by East Turret 1": TunicLocationData("Beneath the Well Main", BreakableType.pot), + "Beneath the Well - [Third Room] Pot by East Turret 2": TunicLocationData("Beneath the Well Main", BreakableType.pot), + "Beneath the Well - [Third Room] Pot by East Turret 3": TunicLocationData("Beneath the Well Main", BreakableType.pot), + "Beneath the Well - [Third Room] Pot by East Turret 4": TunicLocationData("Beneath the Well Main", BreakableType.pot), + "Beneath the Well - [Third Room] Pot by East Turret 5": TunicLocationData("Beneath the Well Main", BreakableType.pot), + "Beneath the Well - [Third Room] Pot by East Turret 6": TunicLocationData("Beneath the Well Main", BreakableType.pot), + "Beneath the Well - [Third Room] Pot by East Turret 7": TunicLocationData("Beneath the Well Main", BreakableType.pot), + "Well Boss - Barrel 1": TunicLocationData("Well Boss", BreakableType.barrel), + "Well Boss - Barrel 2": TunicLocationData("Well Boss", BreakableType.barrel), + "Dark Tomb - Pot Hallway Pot 1": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - Pot Hallway Pot 2": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - Pot Hallway Pot 3": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - Pot Hallway Pot 4": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - Pot Hallway Pot 5": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - Pot Hallway Pot 6": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - Pot Hallway Pot 7": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - Pot Hallway Pot 8": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - Pot Hallway Pot 9": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - Pot Hallway Pot 10": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - Pot Hallway Pot 11": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - Pot Hallway Pot 12": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - Pot Hallway Pot 13": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - Pot Hallway Pot 14": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - 2nd Laser Room Pot 1": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - 2nd Laser Room Pot 2": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - 2nd Laser Room Pot 3": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - 2nd Laser Room Pot 4": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "Dark Tomb - 2nd Laser Room Pot 5": TunicLocationData("Dark Tomb Main", BreakableType.pot), + "West Garden House - Pot 1": TunicLocationData("Magic Dagger House", BreakableType.pot), + "West Garden House - Pot 2": TunicLocationData("Magic Dagger House", BreakableType.pot), + "West Garden House - Pot 3": TunicLocationData("Magic Dagger House", BreakableType.pot), + "Fortress Courtyard - Fire Pot 1": TunicLocationData("Fortress Courtyard westmost pots", BreakableType.fire_pot), + "Fortress Courtyard - Fire Pot 2": TunicLocationData("Fortress Courtyard westmost pots", BreakableType.fire_pot), + "Fortress Courtyard - Fire Pot 3": TunicLocationData("Fortress Courtyard west pots", BreakableType.fire_pot), + "Fortress Courtyard - Fire Pot 4": TunicLocationData("Fortress Courtyard west pots", BreakableType.fire_pot), + "Fortress Courtyard - Fire Pot 5": TunicLocationData("Fortress Courtyard", BreakableType.fire_pot), + "Fortress Courtyard - Fire Pot 6": TunicLocationData("Fortress Courtyard", BreakableType.fire_pot), + "Fortress Courtyard - Fire Pot 7": TunicLocationData("Fortress Courtyard", BreakableType.fire_pot), + "Fortress Courtyard - Fire Pot 8": TunicLocationData("Fortress Courtyard", BreakableType.fire_pot), + "Fortress Courtyard - Upper Fire Pot": TunicLocationData("Fortress Courtyard Upper pot", BreakableType.fire_pot), + "Fortress Grave Path - [Entry] Pot 1": TunicLocationData("Fortress Grave Path Entry", BreakableType.pot), + "Fortress Grave Path - [Entry] Pot 2": TunicLocationData("Fortress Grave Path Entry", BreakableType.pot), + "Fortress Grave Path - [By Grave] Pot 1": TunicLocationData("Fortress Grave Path pots", BreakableType.pot), + "Fortress Grave Path - [By Grave] Pot 2": TunicLocationData("Fortress Grave Path pots", BreakableType.pot), + "Fortress Grave Path - [By Grave] Pot 3": TunicLocationData("Fortress Grave Path pots", BreakableType.pot), + "Fortress Grave Path - [By Grave] Pot 4": TunicLocationData("Fortress Grave Path pots", BreakableType.pot), + "Fortress Grave Path - [By Grave] Pot 5": TunicLocationData("Fortress Grave Path pots", BreakableType.pot), + "Fortress Grave Path - [By Grave] Pot 6": TunicLocationData("Fortress Grave Path pots", BreakableType.pot), + "Fortress Grave Path - [Central] Fire Pot 1": TunicLocationData("Fortress Grave Path westmost pot", BreakableType.fire_pot), + "Fortress Grave Path - [Central] Fire Pot 2": TunicLocationData("Fortress Grave Path Combat", BreakableType.fire_pot), + "Eastern Vault Fortress - [Central] Pot by Door 1": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [Central] Pot by Door 2": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [Central] Pot by Door 3": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [Central] Pot by Door 4": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [Central] Pot by Door 5": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [Central] Pot by Door 6": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [Central] Pot by Door 7": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [Central] Pot by Door 8": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [Central] Pot by Door 9": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [Central] Pot by Door 10": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [Central] Pot by Door 11": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [East Wing] Pot by Broken Checkpoint 1": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [East Wing] Pot by Broken Checkpoint 2": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [East Wing] Pot by Broken Checkpoint 3": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [West Wing] Pot by Checkpoint 1": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [West Wing] Pot by Checkpoint 2": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [West Wing] Pot by Checkpoint 3": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [West Wing] Pot by Overlook 1": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [West Wing] Pot by Overlook 2": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [West Wing] Slorm Room Pot 1": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [West Wing] Slorm Room Pot 2": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [West Wing] Slorm Room Pot 3": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [West Wing] Chest Room Pot 1": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [West Wing] Chest Room Pot 2": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [West Wing] Pot by Stairs to Basement 1": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [West Wing] Pot by Stairs to Basement 2": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Eastern Vault Fortress - [West Wing] Pot by Stairs to Basement 3": TunicLocationData("Eastern Vault Fortress", BreakableType.pot), + "Beneath the Fortress - Entry Spot Pot 1": TunicLocationData("Beneath the Vault Entry Spot", BreakableType.pot), + "Beneath the Fortress - Entry Spot Pot 2": TunicLocationData("Beneath the Vault Entry Spot", BreakableType.pot), + "Beneath the Fortress - Entry Spot Crate 1": TunicLocationData("Beneath the Vault Entry Spot", BreakableType.crate), + "Beneath the Fortress - Entry Spot Crate 2": TunicLocationData("Beneath the Vault Entry Spot", BreakableType.crate), + "Beneath the Fortress - Entry Spot Crate 3": TunicLocationData("Beneath the Vault Entry Spot", BreakableType.crate), + "Beneath the Fortress - Entry Spot Crate 4": TunicLocationData("Beneath the Vault Entry Spot", BreakableType.crate), + "Beneath the Fortress - Entry Spot Crate 5": TunicLocationData("Beneath the Vault Entry Spot", BreakableType.crate), + "Beneath the Fortress - Entry Spot Crate 6": TunicLocationData("Beneath the Vault Entry Spot", BreakableType.crate), + "Beneath the Fortress - Entry Spot Crate 7": TunicLocationData("Beneath the Vault Entry Spot", BreakableType.crate), + "Beneath the Fortress - Slorm Room Crate 1": TunicLocationData("Beneath the Vault Main", BreakableType.crate), + "Beneath the Fortress - Slorm Room Crate 2": TunicLocationData("Beneath the Vault Main", BreakableType.crate), + "Beneath the Fortress - Crate under Rope 1": TunicLocationData("Beneath the Vault Main", BreakableType.crate), + "Beneath the Fortress - Crate under Rope 2": TunicLocationData("Beneath the Vault Main", BreakableType.crate), + "Beneath the Fortress - Crate under Rope 3": TunicLocationData("Beneath the Vault Main", BreakableType.crate), + "Beneath the Fortress - Crate under Rope 4": TunicLocationData("Beneath the Vault Main", BreakableType.crate), + "Beneath the Fortress - Crate under Rope 5": TunicLocationData("Beneath the Vault Main", BreakableType.crate), + "Beneath the Fortress - Crate under Rope 6": TunicLocationData("Beneath the Vault Main", BreakableType.crate), + "Beneath the Fortress - Fuse Room Fire Pot 1": TunicLocationData("Beneath the Vault Back", BreakableType.fire_pot), + "Beneath the Fortress - Fuse Room Fire Pot 2": TunicLocationData("Beneath the Vault Back", BreakableType.fire_pot), + "Beneath the Fortress - Fuse Room Fire Pot 3": TunicLocationData("Beneath the Vault Back", BreakableType.fire_pot), + "Beneath the Fortress - Barrel by Back Room 1": TunicLocationData("Beneath the Vault Back", BreakableType.barrel), + "Beneath the Fortress - Barrel by Back Room 2": TunicLocationData("Beneath the Vault Back", BreakableType.barrel), + "Beneath the Fortress - Barrel by Back Room 3": TunicLocationData("Beneath the Vault Back", BreakableType.barrel), + "Beneath the Fortress - Barrel by Back Room 4": TunicLocationData("Beneath the Vault Back", BreakableType.barrel), + "Beneath the Fortress - Barrel by Back Room 5": TunicLocationData("Beneath the Vault Back", BreakableType.barrel), + "Beneath the Fortress - Barrel by Back Room 6": TunicLocationData("Beneath the Vault Back", BreakableType.barrel), + "Beneath the Fortress - Back Room Barrel 1": TunicLocationData("Beneath the Vault Back", BreakableType.barrel), + "Beneath the Fortress - Back Room Barrel 2": TunicLocationData("Beneath the Vault Back", BreakableType.barrel), + "Beneath the Fortress - Back Room Barrel 3": TunicLocationData("Beneath the Vault Back", BreakableType.barrel), + "Beneath the Fortress - Back Room Barrel 4": TunicLocationData("Beneath the Vault Back", BreakableType.barrel), + "Beneath the Fortress - Back Room Barrel 5": TunicLocationData("Beneath the Vault Back", BreakableType.barrel), + "Beneath the Fortress - Back Room Barrel 6": TunicLocationData("Beneath the Vault Back", BreakableType.barrel), + "Beneath the Fortress - Back Room Barrel 7": TunicLocationData("Beneath the Vault Back", BreakableType.barrel), + "Fortress Leaf Piles - Leaf Pile 1": TunicLocationData("Fortress Leaf Piles", BreakableType.leaves), + "Fortress Leaf Piles - Leaf Pile 2": TunicLocationData("Fortress Leaf Piles", BreakableType.leaves), + "Fortress Leaf Piles - Leaf Pile 3": TunicLocationData("Fortress Leaf Piles", BreakableType.leaves), + "Fortress Leaf Piles - Leaf Pile 4": TunicLocationData("Fortress Leaf Piles", BreakableType.leaves), + "Fortress Arena - Pot 1": TunicLocationData("Fortress Arena", BreakableType.pot), + "Fortress Arena - Pot 2": TunicLocationData("Fortress Arena", BreakableType.pot), + "Ruined Atoll - [West] Pot in Broken House 1": TunicLocationData("Ruined Atoll", BreakableType.pot), + "Ruined Atoll - [West] Pot in Broken House 2": TunicLocationData("Ruined Atoll", BreakableType.pot), + "Ruined Atoll - [West] Table in Broken House": TunicLocationData("Ruined Atoll", BreakableType.table), + "Ruined Atoll - [South] Explosive Pot near Birds": TunicLocationData("Ruined Atoll", BreakableType.explosive_pot), + "Frog Stairs - [Upper] Pot 1": TunicLocationData("Frog Stairs Upper", BreakableType.pot), + "Frog Stairs - [Upper] Pot 2": TunicLocationData("Frog Stairs Upper", BreakableType.pot), + "Frog Stairs - [Upper] Pot 3": TunicLocationData("Frog Stairs Upper", BreakableType.pot), + "Frog Stairs - [Upper] Pot 4": TunicLocationData("Frog Stairs Upper", BreakableType.pot), + "Frog Stairs - [Upper] Pot 5": TunicLocationData("Frog Stairs Upper", BreakableType.pot), + "Frog Stairs - [Upper] Pot 6": TunicLocationData("Frog Stairs Upper", BreakableType.pot), + "Frog's Domain - Pot above Orb Altar 1": TunicLocationData("Frog's Domain Front", BreakableType.pot), + "Frog's Domain - Pot above Orb Altar 2": TunicLocationData("Frog's Domain Front", BreakableType.pot), + "Frog's Domain - Side Room Pot 1": TunicLocationData("Frog's Domain Main", BreakableType.pot), + "Frog's Domain - Side Room Pot 2": TunicLocationData("Frog's Domain Main", BreakableType.pot), + "Frog's Domain - Side Room Pot 3": TunicLocationData("Frog's Domain Main", BreakableType.pot), + "Frog's Domain - Main Room Pot 1": TunicLocationData("Frog's Domain Main", BreakableType.pot), + "Frog's Domain - Main Room Pot 2": TunicLocationData("Frog's Domain Main", BreakableType.pot), + "Frog's Domain - Side Room Pot 4": TunicLocationData("Frog's Domain Main", BreakableType.pot), + "Frog's Domain - Pot after Gate 1": TunicLocationData("Frog's Domain Main", BreakableType.pot), + "Frog's Domain - Pot after Gate 2": TunicLocationData("Frog's Domain Main", BreakableType.pot), + "Frog's Domain - Orb Room Explosive Pot 1": TunicLocationData("Frog's Domain Main", BreakableType.explosive_pot), + "Frog's Domain - Orb Room Explosive Pot 2": TunicLocationData("Frog's Domain Main", BreakableType.explosive_pot), + "Library Lab - Display Case 1": TunicLocationData("Library Lab", BreakableType.glass), + "Library Lab - Display Case 2": TunicLocationData("Library Lab", BreakableType.glass), + "Library Lab - Display Case 3": TunicLocationData("Library Lab", BreakableType.glass), + "Quarry - [East] Explosive Pot 1": TunicLocationData("Quarry", BreakableType.explosive_pot), + "Quarry - [East] Explosive Pot 2": TunicLocationData("Quarry", BreakableType.explosive_pot), + "Quarry - [East] Explosive Pot 3": TunicLocationData("Quarry", BreakableType.explosive_pot), + "Quarry - [East] Explosive Pot beneath Scaffolding": TunicLocationData("Quarry", BreakableType.explosive_pot), + "Quarry - [Central] Explosive Pot near Monastery 1": TunicLocationData("Quarry Monastery Entry", BreakableType.explosive_pot), + "Quarry - [Central] Explosive Pot near Monastery 2": TunicLocationData("Quarry Monastery Entry", BreakableType.explosive_pot), + "Quarry - [Back Entrance] Pot 1": TunicLocationData("Quarry Back", BreakableType.pot), + "Quarry - [Back Entrance] Pot 2": TunicLocationData("Quarry Back", BreakableType.pot), + "Quarry - [Back Entrance] Pot 3": TunicLocationData("Quarry Back", BreakableType.pot), + "Quarry - [Back Entrance] Pot 4": TunicLocationData("Quarry Back", BreakableType.pot), + "Quarry - [Back Entrance] Pot 5": TunicLocationData("Quarry Back", BreakableType.pot), + "Quarry - [Central] Explosive Pot near Shortcut Ladder 1": TunicLocationData("Quarry Back", BreakableType.explosive_pot), + "Quarry - [Central] Explosive Pot near Shortcut Ladder 2": TunicLocationData("Quarry Back", BreakableType.explosive_pot), + "Quarry - [Central] Crate near Shortcut Ladder 1": TunicLocationData("Quarry Back", BreakableType.crate), + "Quarry - [Central] Crate near Shortcut Ladder 2": TunicLocationData("Quarry Back", BreakableType.crate), + "Quarry - [Central] Crate near Shortcut Ladder 3": TunicLocationData("Quarry Back", BreakableType.crate), + "Quarry - [Central] Crate near Shortcut Ladder 4": TunicLocationData("Quarry Back", BreakableType.crate), + "Quarry - [Central] Crate near Shortcut Ladder 5": TunicLocationData("Quarry Back", BreakableType.crate), + "Quarry - [West] Explosive Pot near Bombable Wall 1": TunicLocationData("Lower Quarry upper pots", BreakableType.explosive_pot), + "Quarry - [West] Explosive Pot near Bombable Wall 2": TunicLocationData("Lower Quarry upper pots", BreakableType.explosive_pot), + "Quarry - [West] Explosive Pot above Shooting Range": TunicLocationData("Lower Quarry", BreakableType.explosive_pot), + "Quarry - [West] Explosive Pot near Isolated Chest 1": TunicLocationData("Lower Quarry", BreakableType.explosive_pot), + "Quarry - [West] Explosive Pot near Isolated Chest 2": TunicLocationData("Lower Quarry", BreakableType.explosive_pot), + "Quarry - [West] Crate by Shooting Range 1": TunicLocationData("Lower Quarry", BreakableType.crate), + "Quarry - [West] Crate by Shooting Range 2": TunicLocationData("Lower Quarry", BreakableType.crate), + "Quarry - [West] Crate by Shooting Range 3": TunicLocationData("Lower Quarry", BreakableType.crate), + "Quarry - [West] Crate by Shooting Range 4": TunicLocationData("Lower Quarry", BreakableType.crate), + "Quarry - [West] Crate by Shooting Range 5": TunicLocationData("Lower Quarry", BreakableType.crate), + "Quarry - [West] Crate near Isolated Chest 1": TunicLocationData("Lower Quarry", BreakableType.crate), + "Quarry - [West] Crate near Isolated Chest 2": TunicLocationData("Lower Quarry", BreakableType.crate), + "Quarry - [West] Crate near Isolated Chest 3": TunicLocationData("Lower Quarry", BreakableType.crate), + "Quarry - [West] Crate near Isolated Chest 4": TunicLocationData("Lower Quarry", BreakableType.crate), + "Quarry - [West] Crate near Isolated Chest 5": TunicLocationData("Lower Quarry", BreakableType.crate), + "Quarry - [Lowlands] Crate 1": TunicLocationData("Even Lower Quarry", BreakableType.crate), + "Quarry - [Lowlands] Crate 2": TunicLocationData("Even Lower Quarry", BreakableType.crate), + "Monastery - Crate 1": TunicLocationData("Monastery Back", BreakableType.crate), + "Monastery - Crate 2": TunicLocationData("Monastery Back", BreakableType.crate), + "Monastery - Crate 3": TunicLocationData("Monastery Back", BreakableType.crate), + "Monastery - Crate 4": TunicLocationData("Monastery Back", BreakableType.crate), + "Monastery - Crate 5": TunicLocationData("Monastery Back", BreakableType.crate), + "Monastery - Crate 6": TunicLocationData("Monastery Back", BreakableType.crate), + "Monastery - Crate 7": TunicLocationData("Monastery Back", BreakableType.crate), + "Monastery - Crate 8": TunicLocationData("Monastery Back", BreakableType.crate), + "Monastery - Crate 9": TunicLocationData("Monastery Back", BreakableType.crate), + "Cathedral - [1F] Pot by Stairs 1": TunicLocationData("Cathedral Main", BreakableType.pot), + "Cathedral - [1F] Pot by Stairs 2": TunicLocationData("Cathedral Main", BreakableType.pot), + "Purgatory - Pot 1": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 2": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 3": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 4": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 5": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 6": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 7": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 8": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 9": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 10": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 11": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 12": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 13": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 14": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 15": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 16": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 17": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 18": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 19": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 20": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 21": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 22": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 23": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 24": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 25": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 26": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 27": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 28": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 29": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 30": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 31": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 32": TunicLocationData("Purgatory", BreakableType.pot), + "Purgatory - Pot 33": TunicLocationData("Purgatory", BreakableType.pot), + "Overworld - [Central] Break Bombable Wall": TunicLocationData("Overworld", BreakableType.wall), + "Overworld - [Southwest] Break Cube Cave Bombable Wall": TunicLocationData("Overworld", BreakableType.wall), + "Overworld - [Southwest] Break Bombable Wall near Fountain": TunicLocationData("Overworld", BreakableType.wall), + "Ruined Atoll - [Northwest] Break Bombable Wall": TunicLocationData("Ruined Atoll", BreakableType.wall), + "East Forest - Break Bombable Wall": TunicLocationData("East Forest", BreakableType.wall), + "Eastern Vault Fortress - [East Wing] Break Bombable Wall": TunicLocationData("Eastern Vault Fortress", BreakableType.wall), + "Quarry - [West] Break Upper Area Bombable Wall": TunicLocationData("Quarry Back", BreakableType.wall), + "Quarry - [East] Break Bombable Wall": TunicLocationData("Quarry", BreakableType.wall), +} + + +breakable_location_name_to_id: dict[str, int] = {name: breakable_base_id + index + for index, name in enumerate(breakable_location_table)} + + +# key is the name in the table above, value is the loc group name for the area +loc_group_convert: dict[str, str] = { + "East Overworld": "Overworld", + "Upper Overworld": "Overworld", + "Overworld to West Garden from Furnace": "Overworld", + "Forest Belltower Upper": "Forest Belltower", + "Forest Belltower Main": "Forest Belltower", + "Guard House 1 East": "Guardhouse 1", + "Guard House 2 Lower": "Guardhouse 2", + "Beneath the Well Back": "Beneath the Well", + "Beneath the Well Main": "Beneath the Well", + "Well Boss": "Dark Tomb Checkpoint", + "Dark Tomb Main": "Dark Tomb", + "Fortress Courtyard Upper": "Fortress Courtyard", + "Fortress Courtyard Upper pot": "Fortress Courtyard", + "Fortress Courtyard west pots": "Fortress Courtyard", + "Fortress Courtyard westmost pots": "Fortress Courtyard", + "Beneath the Vault Entry Spot": "Beneath the Fortress", + "Beneath the Vault Main": "Beneath the Fortress", + "Beneath the Vault Back": "Beneath the Fortress", + "Fortress Grave Path Entry": "Fortress Grave Path", + "Fortress Grave Path Combat": "Fortress Grave Path", + "Fortress Grave Path westmost pot": "Fortress Grave Path", + "Fortress Grave Path pots": "Fortress Grave Path", + "Dusty": "Fortress Leaf Piles", + "Frog Stairs Upper": "Frog Stairs", + "Quarry Monastery Entry": "Quarry", + "Quarry Back": "Quarry", + "Lower Quarry": "Quarry", + "Lower Quarry upper pots": "Quarry", + "Even Lower Quarry": "Quarry", + "Monastery Back": "Monastery", +} + + +breakable_location_groups: dict[str, set[str]] = {} +for location_name, location_data in breakable_location_table.items(): + group_name = loc_group_convert.get(location_data.er_region, location_data.er_region) + breakable_location_groups.setdefault(group_name, set()).add(location_name) + + +def can_break_breakables(state: CollectionState, world: "TunicWorld") -> bool: + return has_melee(state, world.player) or state.has_any(("Magic Wand", "Gun"), world.player) + + +# and also the table +def can_break_signs(state: CollectionState, world: "TunicWorld") -> bool: + return (has_sword(state, world.player) or state.has_any(("Magic Wand", "Gun"), world.player) + or (has_melee(state, world.player) and state.has("Glass Cannon", world.player))) + + +def can_break_leaf_piles(state: CollectionState, world: "TunicWorld") -> bool: + return has_melee(state, world.player) or state.has_any(("Magic Dagger", "Gun"), world.player) + + +def can_break_bomb_walls(state: CollectionState, world: "TunicWorld") -> bool: + return state.has("Gun", world.player) or can_shop(state, world) + + +def create_breakable_exclusive_regions(world: "TunicWorld") -> list[Region]: + player = world.player + multiworld = world.multiworld + new_regions: list[Region] = [] + + region = Region("Fortress Courtyard westmost pots", player, multiworld) + new_regions.append(region) + world.get_region("Fortress Courtyard").connect(region) + world.get_region("Fortress Exterior near cave").connect( + region, rule=lambda state: state.has_any(("Magic Wand", "Gun"), player)) + + region = Region("Fortress Courtyard west pots", player, multiworld) + new_regions.append(region) + world.get_region("Fortress Courtyard").connect(region) + world.get_region("Fortress Exterior near cave").connect( + region, rule=lambda state: state.has("Magic Wand", player)) + + region = Region("Fortress Courtyard Upper pot", player, multiworld) + new_regions.append(region) + world.get_region("Fortress Courtyard Upper").connect(region) + world.get_region("Fortress Courtyard").connect( + region, rule=lambda state: state.has("Magic Wand", player)) + + region = Region("Fortress Grave Path westmost pot", player, multiworld) + new_regions.append(region) + world.get_region("Fortress Grave Path Entry").connect(region) + world.get_region("Fortress Grave Path Upper").connect( + region, rule=lambda state: state.has_any(("Magic Wand", "Gun"), player)) + + region = Region("Fortress Grave Path pots", player, multiworld) + new_regions.append(region) + world.get_region("Fortress Grave Path by Grave").connect(region) + world.get_region("Fortress Grave Path Dusty Entrance Region").connect( + region, rule=lambda state: state.has("Magic Wand", player)) + + region = Region("Lower Quarry upper pots", player, multiworld) + new_regions.append(region) + world.get_region("Lower Quarry").connect(region) + world.get_region("Quarry Back").connect( + region, rule=lambda state: state.has_any(("Magic Wand", "Gun"), player)) + + for region in new_regions: + multiworld.regions.append(region) + + return new_regions + + +def set_breakable_location_rules(world: "TunicWorld") -> None: + for loc_name, loc_data in breakable_location_table.items(): + if not world.options.entrance_rando and loc_data.er_region == "Purgatory": + continue + location = world.get_location(loc_name) + if loc_data.breakable == BreakableType.leaves: + set_rule(location, lambda state: can_break_leaf_piles(state, world)) + elif loc_data.breakable in (BreakableType.sign, BreakableType.table): + set_rule(location, lambda state: can_break_signs(state, world)) + elif loc_data.breakable == BreakableType.wall: + set_rule(location, lambda state: can_break_bomb_walls(state, world)) + else: + set_rule(location, lambda state: can_break_breakables(state, world)) diff --git a/worlds/tunic/er_data.py b/worlds/tunic/er_data.py index f1a428cc..0b3a1616 100644 --- a/worlds/tunic/er_data.py +++ b/worlds/tunic/er_data.py @@ -679,8 +679,9 @@ tunic_er_regions: Dict[str, RegionInfo] = { "Fortress Courtyard": RegionInfo("Fortress Courtyard"), "Fortress Courtyard Upper": RegionInfo("Fortress Courtyard"), "Beneath the Vault Ladder Exit": RegionInfo("Fortress Basement"), - "Beneath the Vault Main": RegionInfo("Fortress Basement"), # the vanilla entry point - "Beneath the Vault Back": RegionInfo("Fortress Basement"), # the vanilla exit point + "Beneath the Vault Entry Spot": RegionInfo("Fortress Basement"), # where the boxes are + "Beneath the Vault Main": RegionInfo("Fortress Basement"), + "Beneath the Vault Back": RegionInfo("Fortress Basement"), "Eastern Vault Fortress": RegionInfo("Fortress Main"), "Eastern Vault Fortress Gold Door": RegionInfo("Fortress Main"), "Fortress East Shortcut Upper": RegionInfo("Fortress East"), @@ -1421,11 +1422,17 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = { }, "Beneath the Vault Ladder Exit": { + "Beneath the Vault Entry Spot": + [], + }, + "Beneath the Vault Entry Spot": { "Beneath the Vault Main": [], + "Beneath the Vault Ladder Exit": + [], }, "Beneath the Vault Main": { - "Beneath the Vault Ladder Exit": + "Beneath the Vault Entry Spot": [], "Beneath the Vault Back": [], diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index fe01337c..7a3264b6 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -855,16 +855,21 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_ regions["Fortress Courtyard Upper"].connect( connecting_region=regions["Fortress Exterior from Overworld"]) - btv_front_to_main = regions["Beneath the Vault Ladder Exit"].connect( + regions["Beneath the Vault Ladder Exit"].connect( + connecting_region=regions["Beneath the Vault Entry Spot"], + rule=lambda state: has_ladder("Ladder to Beneath the Vault", state, world)) + regions["Beneath the Vault Entry Spot"].connect( + connecting_region=regions["Beneath the Vault Ladder Exit"], + rule=lambda state: has_ladder("Ladder to Beneath the Vault", state, world)) + + btv_front_to_main = regions["Beneath the Vault Entry Spot"].connect( connecting_region=regions["Beneath the Vault Main"], - rule=lambda state: has_ladder("Ladder to Beneath the Vault", state, world) - and has_lantern(state, world) + rule=lambda state: has_lantern(state, world) # there's some boxes in the way and (has_melee(state, player) or state.has_any((gun, grapple, fire_wand, laurels), player))) # on the reverse trip, you can lure an enemy over to break the boxes if needed regions["Beneath the Vault Main"].connect( - connecting_region=regions["Beneath the Vault Ladder Exit"], - rule=lambda state: has_ladder("Ladder to Beneath the Vault", state, world)) + connecting_region=regions["Beneath the Vault Entry Spot"]) regions["Beneath the Vault Main"].connect( connecting_region=regions["Beneath the Vault Back"]) diff --git a/worlds/tunic/er_scripts.py b/worlds/tunic/er_scripts.py index 4cd0f49d..ddb4ec6c 100644 --- a/worlds/tunic/er_scripts.py +++ b/worlds/tunic/er_scripts.py @@ -3,6 +3,7 @@ from BaseClasses import Region, ItemClassification, Item, Location from .locations import all_locations from .er_data import Portal, portal_mapping, traversal_requirements, DeadEnd, RegionInfo from .er_rules import set_er_region_rules +from .breakables import create_breakable_exclusive_regions, set_breakable_location_rules from Options import PlandoConnection from .options import EntranceRando from random import Random @@ -22,19 +23,26 @@ class TunicERLocation(Location): def create_er_regions(world: "TunicWorld") -> Dict[Portal, Portal]: regions: Dict[str, Region] = {} + for region_name, region_data in world.er_regions.items(): + if world.options.entrance_rando and region_name == "Zig Skip Exit": + # need to check if there's a seed group for this first + if world.options.entrance_rando.value not in EntranceRando.options.values(): + if not world.seed_groups[world.options.entrance_rando.value]["fixed_shop"]: + continue + elif not world.options.fixed_shop: + continue + if not world.options.entrance_rando and region_name in ("Zig Skip Exit", "Purgatory"): + continue + + region = Region(region_name, world.player, world.multiworld) + regions[region_name] = region + world.multiworld.regions.append(region) + + if world.options.breakable_shuffle: + breakable_regions = create_breakable_exclusive_regions(world) + regions.update({region.name: region for region in breakable_regions}) if world.options.entrance_rando: - for region_name, region_data in world.er_regions.items(): - # if fewer shops is off, zig skip is not made - if region_name == "Zig Skip Exit": - # need to check if there's a seed group for this first - if world.options.entrance_rando.value not in EntranceRando.options.values(): - if not world.seed_groups[world.options.entrance_rando.value]["fixed_shop"]: - continue - elif not world.options.fixed_shop: - continue - regions[region_name] = Region(region_name, world.player, world.multiworld) - portal_pairs = pair_portals(world, regions) # output the entrances to the spoiler log here for convenience @@ -42,11 +50,6 @@ def create_er_regions(world: "TunicWorld") -> Dict[Portal, Portal]: for portal1, portal2 in sorted_portal_pairs.items(): world.multiworld.spoiler.set_entrance(portal1, portal2, "both", world.player) else: - for region_name, region_data in world.er_regions.items(): - # filter out regions that are inaccessible in non-er - if region_name not in ["Zig Skip Exit", "Purgatory"]: - regions[region_name] = Region(region_name, world.player, world.multiworld) - portal_pairs = vanilla_portals(world, regions) create_randomized_entrances(portal_pairs, regions) @@ -58,8 +61,8 @@ def create_er_regions(world: "TunicWorld") -> Dict[Portal, Portal]: location = TunicERLocation(world.player, location_name, location_id, region) region.locations.append(location) - for region in regions.values(): - world.multiworld.regions.append(region) + if world.options.breakable_shuffle: + set_breakable_location_rules(world) place_event_items(world, regions) @@ -557,4 +560,3 @@ def sort_portals(portal_pairs: Dict[Portal, Portal]) -> Dict[str, str]: sorted_pairs[portal1.name] = portal2.name break return sorted_pairs - diff --git a/worlds/tunic/items.py b/worlds/tunic/items.py index 20696eb5..1898534c 100644 --- a/worlds/tunic/items.py +++ b/worlds/tunic/items.py @@ -103,6 +103,10 @@ item_table: Dict[str, TunicItemData] = { "Forever Friend": TunicItemData(IC.useful, 1, 84, "Golden Treasures", combat_ic=IC.progression), "Fool Trap": TunicItemData(IC.trap, 0, 85), "Money x1": TunicItemData(IC.filler, 3, 86, "Money"), + "Money x2": TunicItemData(IC.filler, 0, 152, "Money"), + "Money x3": TunicItemData(IC.filler, 0, 153, "Money"), + "Money x4": TunicItemData(IC.filler, 0, 154, "Money"), + "Money x5": TunicItemData(IC.filler, 0, 155, "Money"), "Money x10": TunicItemData(IC.filler, 1, 87, "Money"), "Money x15": TunicItemData(IC.filler, 10, 88, "Money"), "Money x16": TunicItemData(IC.filler, 1, 89, "Money"), diff --git a/worlds/tunic/locations.py b/worlds/tunic/locations.py index d3c23406..18c0fb3c 100644 --- a/worlds/tunic/locations.py +++ b/worlds/tunic/locations.py @@ -1,5 +1,6 @@ from typing import Dict, NamedTuple, Set, Optional, List from .grass import grass_location_table +from .breakables import breakable_location_table class TunicLocationData(NamedTuple): @@ -342,6 +343,7 @@ standard_location_name_to_id: Dict[str, int] = {name: location_base_id + index f all_locations = location_table.copy() all_locations.update(grass_location_table) +all_locations.update(breakable_location_table) location_name_groups: Dict[str, Set[str]] = {} for loc_name, loc_data in location_table.items(): diff --git a/worlds/tunic/options.py b/worlds/tunic/options.py index 3ace28cf..c17b085b 100644 --- a/worlds/tunic/options.py +++ b/worlds/tunic/options.py @@ -313,6 +313,14 @@ class LogicRules(Choice): default = 0 +class BreakableShuffle(Toggle): + """ + Turns approximately 250 breakable objects in the game into checks. + """ + internal_name = "breakable_shuffle" + display_name = "Breakable Shuffle" + + @dataclass class TunicOptions(PerGameCommonOptions): start_inventory_from_pool: StartInventoryPool @@ -331,6 +339,7 @@ class TunicOptions(PerGameCommonOptions): shuffle_ladders: ShuffleLadders grass_randomizer: GrassRandomizer + breakable_shuffle: BreakableShuffle local_fill: LocalFill entrance_rando: EntranceRando