diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index 65ed345604..9946f4b1b2 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -4,6 +4,7 @@ from .options import IceGrappling, LadderStorage from .rules import (has_ability, has_sword, has_stick, has_ice_grapple_logic, has_lantern, has_mask, can_ladder_storage, laurels_zip) from .er_data import Portal +from .ladder_storage_data import ow_ladder_groups, region_ladders from BaseClasses import Region, CollectionState if TYPE_CHECKING: @@ -999,6 +1000,61 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_ world.options.hexagon_quest else state.has_all({red_hexagon, green_hexagon, blue_hexagon, "Unseal the Heir"}, player))) + if options.ladder_storage: + def get_portal_info(portal_sd: str) -> Tuple[str, str]: + for portal1, portal2 in portal_pairs.items(): + if portal1.scene_destination() == portal_sd: + return portal1.name, portal2.region + if portal2.scene_destination() == portal_sd: + return portal2.name, portal1.region + raise Exception("no matches found in get_paired_region") + + def ls_connect(ladder_elev: str, portal_sdt: str) -> None: + p_name, paired_region_name = get_portal_info(portal_sdt) + ladder_regions[ladder_elev].connect( + regions[paired_region_name], + name=p_name + " (LS) " + ladder_elev) + + # create the ls elevation regions + ladder_regions: Dict[str, Region] = {} + for name in ow_ladder_groups.keys(): + ladder_regions[name] = Region(name, player, world.multiworld) + + # connect the ls elevations to each other where applicable + if options.ladder_storage >= LadderStorage.option_medium: + for i in range(len(ow_ladder_groups) - 1): + ladder_regions[f"LS Elev {i}"].connect(ladder_regions[f"LS Elev {i + 1}"]) + + # connect the applicable overworld regions to the ls elevation regions + for origin_region, ladders in region_ladders.items(): + for ladder_region, region_info in ow_ladder_groups.items(): + common_ladders: Set[str] = ladders.intersection(region_info.ladders) + if common_ladders: + regions[origin_region].connect(ladder_regions[ladder_region], + rule=lambda state: state.has_any(common_ladders, player)) + + # connect ls elevation regions to the region on the other side of the portals + for ladder_region, region_info in ow_ladder_groups.items(): + for portal_dest in region_info.portals: + ls_connect(ladder_region, "Overworld Redux, " + portal_dest) + + # connect ls elevation regions to regions where you can get an enemy to knock you down + if options.ladder_storage >= LadderStorage.option_medium: + for ladder_region, region_info in ow_ladder_groups.items(): + for dest_region in region_info.regions: + ladder_regions[ladder_region].connect( + connecting_region=regions[dest_region], + name=ladder_region + " (LS) " + dest_region) + + # connect ls elevation regions to portals where you need to get behind the map to enter it + if options.ladder_storage >= LadderStorage.option_hard: + ls_connect("LS Elev 1", "Overworld Redux, EastFiligreeCache_") + ls_connect("LS Elev 2", "Overworld Redux, Town_FiligreeRoom_") + ls_connect("LS Elev 3", "Overworld Redux, Overworld Interiors_house") + ls_connect("LS Elev 5", "Overworld Redux, Temple_main") + # todo: add ls in other areas + # todo: add rules when er is off + # connecting the regions portals are in to other portals you can access via ladder storage # using has_stick instead of can_ladder_storage since it's already checking the logic rules if options.logic_rules == "unrestricted": diff --git a/worlds/tunic/ladder_data.py b/worlds/tunic/ladder_data.py deleted file mode 100644 index 1323dc922c..0000000000 --- a/worlds/tunic/ladder_data.py +++ /dev/null @@ -1,63 +0,0 @@ -# for making rules for ladder storage in overworld -from typing import Dict, Set, NamedTuple - - -class LadderInfo(NamedTuple): - ladders: Set[str] # ladders where the top or bottom is at the same elevation - portals: Set[str] # portals at the same elevation - regions: Set[str] # regions where a melee enemy can hit you out of ladder storage - - -# groups for ladders at the same elevation, for use in determing whether you can ls to entrances in diff rulesets -overworld_ladder_groups: Dict[str, LadderInfo] = { - # lowest elevation - "Group 0": LadderInfo({"Ladders in Overworld Town", "Ladder to Ruined Atoll", "Ladder to Swamp"}, - {"Swamp Redux 2_conduit", "Overworld Cave_", "Atoll Redux_lower", "Maze Room_", - "Town Basement_beach", "Archipelagos Redux_lowest"}, - {"Overworld Beach"}), - "Group 1": LadderInfo({"Ladders near Weathervane", "Ladders in Overworld Town", "Ladder to Swamp"}, - {"Furnace_gyro_lower", "Swamp Redux 2_wall"}, - {"Overworld Tunnel Turret"}), - "Group 2": LadderInfo({"Ladders near Weathervane", "Ladders to West Bell"}, - {"Archipelagos Redux_lower", "Ruins Passage_east", "Ruins Passage_west"}, - {"After Ruined Passage"}), - "Group 3": LadderInfo({"Ladders near Weathervane", "Ladder to Quarry", "Ladders to West Bell", - "Ladders in Overworld Town"}, - {"Overworld Interiors_house"}, - {"Overworld after Envoy", "East Overworld"}), - # skip top of top ladder next to weathervane level, does not provide logical access to anything - "Group 4": LadderInfo({"Ladders near Dark Tomb", "Ladder to Quarry", "Ladders to West Bell", - "Ladders in Overworld Town", "Ladders in Well"}, - {"Darkwoods Tunnel_"}, - set()), - "Group 5": LadderInfo({"Ladders near Overworld Checkpoint", "Ladders near Patrol Cave"}, - {"Temple_main", "PatrolCave_", "Forest Belltower_", "Fortress Courtyard_", "ShopSpecial_"}, - {"East Overworld"}), - # skip top of belltower, middle of dark tomb ladders, and top of checkpoint, does not grant access to anything - "Group 6": LadderInfo({"Ladders near Patrol Cave", "Ladder near Temple Rafters"}, - {"Temple_rafters"}, - {"Overworld above Patrol Cave"}), - # in-line with the chest above dark tomb, gets you up the mountain stairs - "Group 7": LadderInfo({"Ladders near Patrol Cave", "Ladder near Temple Rafters", "Ladders near Dark Tomb"}, - {"Mountain_"}, - {"Upper Overworld"}), -} - - -# ladders accessible within different regions of overworld, only those that are relevant -# other scenes will just have them hardcoded since this type of structure is not necessary there -region_ladders: Dict[str, Set[str]] = { - "Overworld": {"Ladders near Weathervane", "Ladders near Overworld Checkpoint", "Ladders near Dark Tomb", - "Ladders in Overworld Town", "Ladder to Swamp", "Ladders in Well"}, - "Overworld Beach": {"Ladder to Ruined Atoll"}, - "Overworld at Patrol Cave": {"Ladders near Patrol Cave"}, - "Overworld Quarry Entry": {"Ladder to Quarry"}, - "Overworld Belltower": {"Ladders to West Bell"}, -} - - -def ladder_and_lower(index: int) -> Set[str]: - ladder_set: Set[str] = set() - for i in range(index): - ladder_set.update(overworld_ladder_groups[f"Group {i}"].ladders) - return ladder_set diff --git a/worlds/tunic/ladder_storage_data.py b/worlds/tunic/ladder_storage_data.py new file mode 100644 index 0000000000..fca8fb6a4d --- /dev/null +++ b/worlds/tunic/ladder_storage_data.py @@ -0,0 +1,60 @@ +# for making rules for ladder storage in overworld +from typing import Dict, List, Set, NamedTuple + + +class LadderInfo(NamedTuple): + ladders: Set[str] # ladders where the top or bottom is at the same elevation + portals: List[str] # portals at the same elevation, only those without doors + regions: List[str] # regions where a melee enemy can hit you out of ladder storage + + +# groups for ladders at the same elevation, for use in determing whether you can ls to entrances in diff rulesets +ow_ladder_groups: Dict[str, LadderInfo] = { + # lowest elevation + "LS Elev 0": LadderInfo({"Ladders in Overworld Town", "Ladder to Ruined Atoll", "Ladder to Swamp"}, + ["Swamp Redux 2_conduit", "Overworld Cave_", "Atoll Redux_lower", "Maze Room_", + "Town Basement_beach", "Archipelagos Redux_lowest"], + ["Overworld Beach"]), + # also the east filigree room + "LS Elev 1": LadderInfo({"Ladders near Weathervane", "Ladders in Overworld Town", "Ladder to Swamp"}, + ["Furnace_gyro_lower", "Swamp Redux 2_wall"], + ["Overworld Tunnel Turret"]), + # also the fountain filigree room and ruined passage door + "LS Elev 2": LadderInfo({"Ladders near Weathervane", "Ladders to West Bell"}, + ["Archipelagos Redux_lower", "Ruins Passage_east"], + ["After Ruined Passage"]), + # also old house door + "LS Elev 3": LadderInfo({"Ladders near Weathervane", "Ladder to Quarry", "Ladders to West Bell", + "Ladders in Overworld Town"}, + [], + ["Overworld after Envoy", "East Overworld"]), + # skip top of top ladder next to weathervane level, does not provide logical access to anything + "LS Elev 4": LadderInfo({"Ladders near Dark Tomb", "Ladder to Quarry", "Ladders to West Bell", "Ladders in Well", + "Ladders in Overworld Town"}, + ["Darkwoods Tunnel_"], + []), + "LS Elev 5": LadderInfo({"Ladders near Overworld Checkpoint", "Ladders near Patrol Cave"}, + ["PatrolCave_", "Forest Belltower_", "Fortress Courtyard_", "ShopSpecial_"], + ["East Overworld"]), + # skip top of belltower, middle of dark tomb ladders, and top of checkpoint, does not grant access to anything + "LS Elev 6": LadderInfo({"Ladders near Patrol Cave", "Ladder near Temple Rafters"}, + ["Temple_rafters"], + ["Overworld above Patrol Cave"]), + # in-line with the chest above dark tomb, gets you up the mountain stairs + "LS Elev 7": LadderInfo({"Ladders near Patrol Cave", "Ladder near Temple Rafters", "Ladders near Dark Tomb"}, + ["Mountain_"], + ["Upper Overworld"]), +} + + +# ladders accessible within different regions of overworld, only those that are relevant +# other scenes will just have them hardcoded since this type of structure is not necessary there +region_ladders: Dict[str, Set[str]] = { + "Overworld": {"Ladders near Weathervane", "Ladders near Overworld Checkpoint", "Ladders near Dark Tomb", + "Ladders in Overworld Town", "Ladder to Swamp", "Ladders in Well"}, + "Overworld Beach": {"Ladder to Ruined Atoll"}, + "Overworld at Patrol Cave": {"Ladders near Patrol Cave"}, + "Overworld Quarry Entry": {"Ladder to Quarry"}, + "Overworld Belltower": {"Ladders to West Bell"}, + "Overworld after Temple Rafters": {"Ladders near Temple Rafters"}, +}