From 985c02de38e053d2e943c0caf540f47067da0d05 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Fri, 14 Jun 2024 19:41:23 -0400 Subject: [PATCH] Finishing out hooking the new rules into the code --- worlds/tunic/__init__.py | 34 ++++++++++++++++++--------- worlds/tunic/er_data.py | 48 +++++++++++++++++++------------------- worlds/tunic/er_rules.py | 1 - worlds/tunic/er_scripts.py | 39 ++++++++++++++++++++----------- worlds/tunic/options.py | 28 ++++++---------------- worlds/tunic/rules.py | 11 +++++---- 6 files changed, 85 insertions(+), 76 deletions(-) diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index 93c9510692..aa993cd5b6 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -8,7 +8,8 @@ from .er_rules import set_er_location_rules from .regions import tunic_regions from .er_scripts import create_er_regions from .er_data import portal_mapping -from .options import TunicOptions, EntranceRando, tunic_option_groups, tunic_option_presets, TunicPlandoConnections +from .options import (TunicOptions, EntranceRando, tunic_option_groups, tunic_option_presets, TunicPlandoConnections, + LaurelsLocation) from worlds.AutoWorld import WebWorld, World from Options import PlandoConnection from decimal import Decimal, ROUND_HALF_UP @@ -40,10 +41,12 @@ class TunicLocation(Location): class SeedGroup(TypedDict): - logic_rules: int # logic rules value + laurels_zips: bool # laurels_zips value + ice_grappling: int # ice_grappling value + ladder_storage: int # ls value laurels_at_10_fairies: bool # laurels location value fixed_shop: bool # fixed shop value - plando: TunicPlandoConnections # consolidated of plando connections for the seed group + plando: TunicPlandoConnections # consolidated plando connections for the seed group class TunicWorld(World): @@ -112,19 +115,28 @@ class TunicWorld(World): group = tunic.options.entrance_rando.value # if this is the first world in the group, set the rules equal to its rules if group not in cls.seed_groups: - cls.seed_groups[group] = SeedGroup(logic_rules=tunic.options.logic_rules.value, - laurels_at_10_fairies=tunic.options.laurels_location == 3, - fixed_shop=bool(tunic.options.fixed_shop), - plando=multiworld.plando_connections[tunic.player]) + cls.seed_groups[group] = \ + SeedGroup(laurels_zips=bool(tunic.options.laurels_zips), + ice_grappling=tunic.options.ice_grappling.value, + ladder_storage=tunic.options.ladder_storage.value, + laurels_at_10_fairies=tunic.options.laurels_location == LaurelsLocation.option_10_fairies, + fixed_shop=bool(tunic.options.fixed_shop), + plando=multiworld.plando_connections[tunic.player]) continue - + + # off is more restrictive + if not tunic.options.laurels_zips: + cls.seed_groups[group]["laurels_zips"] = False # lower value is more restrictive - if tunic.options.logic_rules.value < cls.seed_groups[group]["logic_rules"]: - cls.seed_groups[group]["logic_rules"] = tunic.options.logic_rules.value + if tunic.options.ice_grappling < cls.seed_groups[group]["ice_grappling"]: + cls.seed_groups[group]["ice_grappling"] = tunic.options.ice_grappling.value + # lower value is more restrictive + if tunic.options.ladder_storage.value < cls.seed_groups[group]["ladder_storage"]: + cls.seed_groups[group]["ladder_storage"] = tunic.options.ladder_storage.value # 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 - # fewer shops, one at windmill + # more restrictive, overrides the option for others in the same group, which is better than failing imo if tunic.options.fixed_shop: cls.seed_groups[group]["fixed_shop"] = True diff --git a/worlds/tunic/er_data.py b/worlds/tunic/er_data.py index 7c706ebb37..d267dc39c0 100644 --- a/worlds/tunic/er_data.py +++ b/worlds/tunic/er_data.py @@ -783,8 +783,8 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = { [], "After Ruined Passage": [["IG1"], ["LS1"]], - # "Overworld": - # [], + # "Overworld": + # [], "Overworld at Patrol Cave": [], "Overworld above Patrol Cave": @@ -799,8 +799,8 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = { "Overworld Belltower": { "Overworld Belltower at Bell": [], - # "Overworld": - # [], + # "Overworld": + # [], "Overworld to West Garden Upper": [], }, @@ -817,8 +817,8 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = { # [], # }, "Overworld Beach": { - # "Overworld": - # [], + # "Overworld": + # [], "Overworld West Garden Laurels Entry": [["Hyperdash"], ["LS1"]], "Overworld to Atoll Upper": @@ -831,20 +831,20 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = { [["Hyperdash"]], }, "Overworld to Atoll Upper": { - # "Overworld": - # [], + # "Overworld": + # [], "Overworld Beach": [], }, "Overworld Tunnel Turret": { - # "Overworld": - # [], + # "Overworld": + # [], "Overworld Beach": [], }, "Overworld Well Ladder": { - # "Overworld": - # [], + # "Overworld": + # [], }, "Overworld at Patrol Cave": { "East Overworld": @@ -853,8 +853,8 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = { [], }, "Overworld above Patrol Cave": { - # "Overworld": - # [], + # "Overworld": + # [], "East Overworld": [], "Upper Overworld": @@ -876,32 +876,32 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = { [], }, "Overworld above Quarry Entrance": { - # "Overworld": - # [], + # "Overworld": + # [], "Upper Overworld": [], }, "Overworld Quarry Entry": { "Overworld after Envoy": [], - # "Overworld": - # [["IG1"]], + # "Overworld": + # [["IG1"]], }, "Overworld after Envoy": { - # "Overworld": - # [], + # "Overworld": + # [], "Overworld Quarry Entry": [], }, "After Ruined Passage": { - # "Overworld": - # [], + # "Overworld": + # [], "Above Ruined Passage": [], }, "Above Ruined Passage": { - # "Overworld": - # [], + # "Overworld": + # [], "After Ruined Passage": [], "East Overworld": diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index 86e70f2c92..ddf9701f7c 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -1104,7 +1104,6 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_ def set_er_location_rules(world: "TunicWorld") -> None: player = world.player multiworld = world.multiworld - options = world.options forbid_item(multiworld.get_location("Secret Gathering Place - 20 Fairy Reward", player), fairies, player) diff --git a/worlds/tunic/er_scripts.py b/worlds/tunic/er_scripts.py index 20409a338f..5f7a739f8c 100644 --- a/worlds/tunic/er_scripts.py +++ b/worlds/tunic/er_scripts.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Set, TYPE_CHECKING +from typing import Dict, List, Set, Tuple, TYPE_CHECKING from BaseClasses import Region, ItemClassification, Item, Location from .locations import location_table from .er_data import Portal, tunic_er_regions, portal_mapping, traversal_requirements, DeadEnd @@ -132,7 +132,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: two_plus: List[Portal] = [] player_name = world.multiworld.get_player_name(world.player) portal_map = portal_mapping.copy() - logic_rules = world.options.logic_rules.value + laurels_zips = world.options.laurels_zips.value ice_grappling = world.options.ice_grappling.value ladder_storage = world.options.ladder_storage.value fixed_shop = world.options.fixed_shop @@ -144,10 +144,14 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: # if it's not one of the EntranceRando options, it's a custom seed if world.options.entrance_rando.value not in EntranceRando.options.values(): seed_group = world.seed_groups[world.options.entrance_rando.value] - logic_rules = seed_group["logic_rules"] + laurels_zips = seed_group["laurels_zips"] + ice_grappling = seed_group["ice_grappling"] + ladder_storage = seed_group["ladder_storage"] fixed_shop = seed_group["fixed_shop"] laurels_location = "10_fairies" if seed_group["laurels_at_10_fairies"] is True else False + logic_tricks: Tuple[bool, int, int] = (laurels_zips, ice_grappling, ladder_storage) + # marking that you don't immediately have laurels if laurels_location == "10_fairies" and not hasattr(world.multiworld, "re_gen_passthrough"): has_laurels = False @@ -198,7 +202,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: # make better start region stuff when/if implementing random start start_region = "Overworld" connected_regions.add(start_region) - connected_regions = update_reachable_regions(connected_regions, traversal_reqs, has_laurels, logic_rules) + connected_regions = update_reachable_regions(connected_regions, traversal_reqs, has_laurels, logic_tricks) if world.options.entrance_rando.value in EntranceRando.options.values(): plando_connections = world.options.plando_connections.value @@ -230,9 +234,11 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: for region_name, region_info in tunic_er_regions.items(): if not region_info.dead_end: non_dead_end_regions.add(region_name) - elif region_info.dead_end == 2 and logic_rules: + # if ice grappling to places is in logic, both places stop being dead ends + elif region_info.dead_end == DeadEnd.restricted and ice_grappling: non_dead_end_regions.add(region_name) - elif region_info.dead_end == 3: + # secret gathering place and zig skip get weird, special handling + elif region_info.dead_end == DeadEnd.special: if (region_name == "Secret Gathering Place" and laurels_location == "10_fairies") \ or (region_name == "Zig Skip Exit" and fixed_shop): non_dead_end_regions.add(region_name) @@ -333,7 +339,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: portal_pairs[portal1] = portal2 # if we have plando connections, our connected regions may change somewhat - connected_regions = update_reachable_regions(connected_regions, traversal_reqs, has_laurels, logic_rules) + connected_regions = update_reachable_regions(connected_regions, traversal_reqs, has_laurels, logic_tricks) if fixed_shop and not hasattr(world.multiworld, "re_gen_passthrough"): portal1 = None @@ -395,7 +401,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: if waterfall_plando: cr = connected_regions.copy() cr.add(portal.region) - if "Secret Gathering Place" not in update_reachable_regions(cr, traversal_reqs, has_laurels, logic_rules): + if "Secret Gathering Place" not in update_reachable_regions(cr, traversal_reqs, has_laurels, logic_tricks): continue elif portal.region != "Secret Gathering Place": continue @@ -407,7 +413,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]: # once we have both portals, connect them and add the new region(s) to connected_regions if check_success == 2: - connected_regions = update_reachable_regions(connected_regions, traversal_reqs, has_laurels, logic_rules) + connected_regions = update_reachable_regions(connected_regions, traversal_reqs, has_laurels, logic_tricks) if "Secret Gathering Place" in connected_regions: has_laurels = True portal_pairs[portal1] = portal2 @@ -468,7 +474,8 @@ def create_randomized_entrances(portal_pairs: Dict[Portal, Portal], regions: Dic def update_reachable_regions(connected_regions: Set[str], traversal_reqs: Dict[str, Dict[str, List[List[str]]]], - has_laurels: bool, logic: int) -> Set[str]: + has_laurels: bool, logic: Tuple[bool, int, int]) -> Set[str]: + zips, ice_grapples, ls = logic # starting count, so we can run it again if this changes region_count = len(connected_regions) for origin, destinations in traversal_reqs.items(): @@ -487,11 +494,15 @@ def update_reachable_regions(connected_regions: Set[str], traversal_reqs: Dict[s if req == "Hyperdash": if not has_laurels: break - elif req == "NMG": - if not logic: + elif req == "Zip": + if not zips: break - elif req == "UR": - if logic < 2: + # if req is higher than logic option, then it breaks since it's not a valid connection + elif req.startswith("IG"): + if int(req[-1]) > ice_grapples: + break + elif req.startswith("LS"): + if int(req[-1]) > ls: break elif req not in connected_regions: break diff --git a/worlds/tunic/options.py b/worlds/tunic/options.py index d64cf9cee8..c88997292a 100644 --- a/worlds/tunic/options.py +++ b/worlds/tunic/options.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from typing import Dict, Any from Options import (DefaultOnToggle, Toggle, StartInventoryPool, Choice, Range, TextChoice, PlandoConnections, - PerGameCommonOptions, OptionGroup) + PerGameCommonOptions, OptionGroup, Removed) from .er_data import portal_mapping @@ -39,22 +39,6 @@ class AbilityShuffling(Toggle): display_name = "Shuffle Abilities" -class LogicRules(Choice): - """ - Deprecated, and will be removed in a later version. - If you have this set to NMG, it will set Laurels Zips on and Ice Grappling to medium. - If you have this set to Unrestricted, it will set Laurels Zips on, Ice Grappling to hard, and Ladder Storage to medium. - """ - internal_name = "logic_rules" - display_name = "Logic Rules" - option_restricted = 0 - option_no_major_glitches = 1 - alias_nmg = 1 - option_unrestricted = 2 - alias_ur = 2 - default = 0 - - class Lanternless(Toggle): """ Choose whether you require the Lantern for dark areas. @@ -189,8 +173,9 @@ class IceGrappling(Choice): Choose whether grappling frozen enemies is in logic. Easy includes ice grappling enemies that are in range without luring them. May include clips through terrain. Medium includes using ice grapples to push enemies through doors or off ledges without luring them. Also includes bringing an enemy over to the Temple Door to grapple through it. - Hard includes luring or grappling enemies to get to where you want to go. Hard difficulty will give the player the Torch item to return to the Overworld checkpoint to avoid softlocks. - Note: You will still be expected to ice grapple to the slime in East Forest. + Hard includes luring or grappling enemies to get to where you want to go. + The Medium and Hard options will give the player the Torch to return to the Overworld checkpoint to avoid softlocks. + Note: You will still be expected to ice grapple to the slime in East Forest even with this option off. """ internal_name = "ice_grappling" display_name = "Ice Grapple Logic" @@ -241,7 +226,6 @@ class TunicOptions(PerGameCommonOptions): shuffle_ladders: ShuffleLadders entrance_rando: EntranceRando fixed_shop: FixedShop - logic_rules: LogicRules fool_traps: FoolTraps hexagon_quest: HexagonQuest hexagon_goal: HexagonGoal @@ -254,11 +238,12 @@ class TunicOptions(PerGameCommonOptions): ice_grappling: IceGrappling ladder_storage: LadderStorage ladder_storage_without_items: LadderStorageWithoutItems + + logic_rules: Removed tunic_option_groups = [ OptionGroup("Logic Options", [ - LogicRules, Lanternless, Maskless, LaurelsZips, @@ -283,6 +268,7 @@ tunic_option_presets: Dict[str, Dict[str, Any]] = { "ability_shuffling": True, "entrance_rando": True, "fool_traps": "onslaught", + "laurels_zips": True, "ice_grappling": "hard", "ladder_storage": "hard", "ladder_storage_without_items": True, diff --git a/worlds/tunic/rules.py b/worlds/tunic/rules.py index 827c14c097..c3f1e34f84 100644 --- a/worlds/tunic/rules.py +++ b/worlds/tunic/rules.py @@ -65,7 +65,7 @@ def laurels_zip(state: CollectionState, world: "TunicWorld") -> bool: # todo: find and put proper typing on ice_grapple -def has_ice_grapple_logic(long_range: bool, difficulty, state: CollectionState, world: "TunicWorld") -> bool: +def has_ice_grapple_logic(long_range: bool, difficulty: IceGrappling, state: CollectionState, world: "TunicWorld") -> bool: if world.options.ice_grappling < difficulty: return False if not long_range: @@ -149,7 +149,6 @@ def set_region_rules(world: "TunicWorld") -> None: def set_location_rules(world: "TunicWorld") -> None: multiworld = world.multiworld player = world.player - options = world.options forbid_item(multiworld.get_location("Secret Gathering Place - 20 Fairy Reward", player), fairies, player) @@ -160,11 +159,13 @@ def set_location_rules(world: "TunicWorld") -> None: lambda state: has_ability(prayer, state, world) or state.has(laurels, player) or can_ladder_storage(state, world) - or (has_ice_grapple_logic(True, IceGrappling.option_easy, state, world) and has_lantern(state, world))) + or (has_ice_grapple_logic(True, IceGrappling.option_easy, state, world) + and has_lantern(state, world))) set_rule(multiworld.get_location("Fortress Courtyard - Page Near Cave", player), lambda state: has_ability(prayer, state, world) or state.has(laurels, player) or can_ladder_storage(state, world) - or (has_ice_grapple_logic(True, IceGrappling.option_easy, state, world) and has_lantern(state, world))) + or (has_ice_grapple_logic(True, IceGrappling.option_easy, state, world) + and has_lantern(state, world))) set_rule(multiworld.get_location("East Forest - Dancing Fox Spirit Holy Cross", player), lambda state: has_ability(holy_cross, state, world)) set_rule(multiworld.get_location("Forest Grave Path - Holy Cross Code by Grave", player), @@ -317,7 +318,7 @@ def set_location_rules(world: "TunicWorld") -> None: # nmg - kill boss scav with orb + firecracker, or similar set_rule(multiworld.get_location("Rooted Ziggurat Lower - Hexagon Blue", player), - lambda state: has_sword(state, player) or (state.has(grapple, player) and options.logic_rules)) + lambda state: has_sword(state, player)) # Swamp set_rule(multiworld.get_location("Cathedral Gauntlet - Gauntlet Reward", player),