From 1afb5694b770ffe789a212928b7545931314a5c2 Mon Sep 17 00:00:00 2001 From: CookieCat Date: Tue, 3 Oct 2023 21:19:24 -0400 Subject: [PATCH] 1.2 --- worlds/ahit/DeathWishRules.py | 91 +++++---- worlds/ahit/Items.py | 15 +- worlds/ahit/Locations.py | 44 ++--- worlds/ahit/Options.py | 32 ++-- worlds/ahit/Regions.py | 19 +- worlds/ahit/Rules.py | 345 +++++++++++++++++++--------------- worlds/ahit/Types.py | 7 + worlds/ahit/__init__.py | 5 - worlds/ahit/test/TestActs.py | 1 + 9 files changed, 315 insertions(+), 244 deletions(-) diff --git a/worlds/ahit/DeathWishRules.py b/worlds/ahit/DeathWishRules.py index 8a3f6e5915..d547d19ba2 100644 --- a/worlds/ahit/DeathWishRules.py +++ b/worlds/ahit/DeathWishRules.py @@ -1,11 +1,11 @@ from worlds.AutoWorld import World, CollectionState from .Locations import LocData, death_wishes, HatInTimeLocation -from .Rules import can_use_hat, can_use_hookshot, can_hit, zipline_logic, has_paintings -from .Types import HatType +from .Rules import can_use_hat, can_use_hookshot, can_hit, zipline_logic, has_paintings, get_difficulty +from .Types import HatType, Difficulty from .DeathWishLocations import dw_prereqs, dw_candles from .Items import HatInTimeItem from BaseClasses import Entrance, Location, ItemClassification -from worlds.generic.Rules import add_rule +from worlds.generic.Rules import add_rule, set_rule from typing import List, Callable # Any speedruns expect the player to have Sprint Hat @@ -21,18 +21,18 @@ dw_requirements = { "Community Rift: Rhythm Jump Studio": LocData(required_hats=[HatType.ICE]), "Speedrun Well": LocData(hookshot=True, hit_requirement=1, required_hats=[HatType.SPRINT]), - "Boss Rush": LocData(hit_requirement=1, umbrella=True), + "Boss Rush": LocData(umbrella=True, hookshot=True), "Community Rift: Twilight Travels": LocData(hookshot=True, required_hats=[HatType.DWELLER]), "Bird Sanctuary": LocData(hookshot=True), "Wound-Up Windmill": LocData(hookshot=True), - "The Illness has Speedrun": LocData(hookshot=True, required_hats=[HatType.SPRINT]), + "The Illness has Speedrun": LocData(hookshot=True), "Community Rift: The Mountain Rift": LocData(hookshot=True, required_hats=[HatType.DWELLER]), "Camera Tourist": LocData(misc_required=["Camera Badge"]), "The Mustache Gauntlet": LocData(hookshot=True, required_hats=[HatType.DWELLER]), - "Rift Collapse - Deep Sea": LocData(hookshot=True, required_hats=[HatType.DWELLER]), + "Rift Collapse - Deep Sea": LocData(hookshot=True), } # Includes main objective requirements @@ -43,15 +43,16 @@ dw_bonus_requirements = { "10 Seconds until Self-Destruct": LocData(misc_required=["One-Hit Hero Badge", "Badge Pin"]), - "Boss Rush": LocData(misc_required=["One-Hit Hero Badge"]), + "Boss Rush": LocData(misc_required=["One-Hit Hero Badge", "Badge Pin"]), "Community Rift: Twilight Travels": LocData(required_hats=[HatType.BREWING]), "Bird Sanctuary": LocData(misc_required=["One-Hit Hero Badge", "Badge Pin"], required_hats=[HatType.DWELLER]), "Wound-Up Windmill": LocData(misc_required=["One-Hit Hero Badge", "Badge Pin"]), + "The Illness has Speedrun": LocData(required_hats=[HatType.SPRINT]), "The Mustache Gauntlet": LocData(required_hats=[HatType.ICE]), - "Rift Collapse - Deep Sea": LocData(required_hats=[HatType.SPRINT]), + "Rift Collapse - Deep Sea": LocData(required_hats=[HatType.DWELLER]), } dw_stamp_costs = { @@ -122,21 +123,9 @@ def set_dw_rules(world: World): continue # Specific Rules - if name == "The Illness has Speedrun": - # killing the flowers without the umbrella is way too slow - add_rule(main_objective, lambda state: state.has("Umbrella", world.player)) - elif name == "The Mustache Gauntlet": - # don't get burned bonus requires a way to kill fire crows without being burned - add_rule(full_clear, lambda state: state.has("Umbrella", world.player) - or can_use_hat(state, world, HatType.ICE)) - elif name == "Vault Codes in the Wind": - add_rule(main_objective, lambda state: can_use_hat(state, world, HatType.TIME_STOP), "or") - - if name in dw_candles: - set_candle_dw_rules(name, world) + modify_dw_rules(world, name) main_rule: Callable[[CollectionState], bool] - for i in range(len(temp_list)): loc = temp_list[i] data: LocData @@ -217,13 +206,55 @@ def set_dw_rules(world: World): world.player) +def modify_dw_rules(world: World, name: str): + difficulty: Difficulty = get_difficulty(world) + main_objective = world.multiworld.get_location(f"{name} - Main Objective", world.player) + full_clear = world.multiworld.get_location(f"{name} - All Clear", world.player) + + if name == "The Illness has Speedrun": + # All stamps with hookshot only in Expert + if difficulty >= Difficulty.EXPERT: + set_rule(full_clear, lambda state: True) + else: + add_rule(main_objective, lambda state: state.has("Umbrella", world.player)) + + elif name == "The Mustache Gauntlet": + # Need a way to kill fire crows without being burned. + add_rule(main_objective, lambda state: state.has("Umbrella", world.player) + or can_use_hat(state, world, HatType.ICE) or can_use_hat(state, world, HatType.BREWING)) + add_rule(full_clear, lambda state: state.has("Umbrella", world.player) + or can_use_hat(state, world, HatType.ICE)) + + elif name == "Vault Codes in the Wind": + # Sprint is normally expected here + if difficulty >= Difficulty.HARD: + set_rule(main_objective, lambda state: True) + + elif name == "Speedrun Well": + # All stamps with nothing :) + if difficulty >= Difficulty.EXPERT: + set_rule(main_objective, lambda state: True) + + elif name == "Mafia's Jumps": + # Main objective without Ice, still expected for bonuses + if difficulty >= Difficulty.HARD: + set_rule(main_objective, lambda state: True) + set_rule(full_clear, lambda state: can_use_hat(state, world, HatType.ICE)) + + elif name == "So You're Back from Outer Space": + # Without Hookshot + if difficulty >= Difficulty.HARD: + set_rule(main_objective, lambda state: True) + + if name in dw_candles: + set_candle_dw_rules(name, world) + + def get_total_dw_stamps(state: CollectionState, world: World) -> int: if world.multiworld.DWShuffle[world.player].value > 0: return 999 # no stamp costs in death wish shuffle count: int = 0 - peace_and_tranquility: bool = world.multiworld.DWEnableBonus[world.player].value == 0 \ - and world.multiworld.DWAutoCompleteBonuses[world.player].value == 0 for name in death_wishes: if name == "Snatcher Coins in Nyakuza Metro" and not world.is_dlc2(): @@ -234,12 +265,6 @@ def get_total_dw_stamps(state: CollectionState, world: World) -> int: else: continue - # If bonus rewards and auto bonus completion is off, obtaining stamps via P&T is in logic - # Candles don't have P&T - if peace_and_tranquility and name not in dw_candles: - count += 2 - continue - if state.has(f"2 Stamps - {name}", world.player): count += 2 elif name not in dw_candles: @@ -272,7 +297,7 @@ def set_candle_dw_rules(name: str, world: World): add_rule(full_clear, lambda state: state.has("CTR Access", world.player) or state.has("HUMT Access", world.player) - and (world.multiworld.UmbrellaLogic[world.player].value == 0 or state.has("Umbrella", world.player)) + and can_hit(state, world, True) or state.has("DWTM Access", world.player) or state.has("TGV Access", world.player)) @@ -424,8 +449,10 @@ def set_enemy_rules(world: World): elif enemy == "Snatcher" or enemy == "Mustache Girl": if area == "Boss Rush": - # need to be able to kill toilet - add_rule(event, lambda state: can_hit(state, world)) + # need to be able to kill toilet and snatcher + add_rule(event, lambda state: can_hit(state, world) and can_use_hookshot(state, world)) + if enemy == "Mustache Girl": + add_rule(event, lambda state: can_hit(state, world, True) and can_use_hookshot(state, world)) elif area == "The Finale" and enemy == "Mustache Girl": add_rule(event, lambda state: can_use_hookshot(state, world) diff --git a/worlds/ahit/Items.py b/worlds/ahit/Items.py index d987d4662b..bd9150d98c 100644 --- a/worlds/ahit/Items.py +++ b/worlds/ahit/Items.py @@ -1,8 +1,8 @@ from BaseClasses import Item, ItemClassification from worlds.AutoWorld import World -from .Types import HatDLC, HatType, hat_type_to_item +from .Types import HatDLC, HatType, hat_type_to_item, Difficulty from .Locations import get_total_locations -from .Rules import get_difficulty, is_player_knowledgeable +from .Rules import get_difficulty from typing import Optional, NamedTuple, List, Dict @@ -45,10 +45,13 @@ def create_itempool(world: World) -> List[Item]: if item_type is ItemClassification.progression \ or item_type is ItemClassification.progression_skip_balancing: continue - - if get_difficulty(world) >= 1 or is_player_knowledgeable(world) \ - and (name == "Scooter Badge" or name == "No Bonk Badge") and not world.is_dw_only(): - item_type = ItemClassification.progression + else: + if name == "Scooter Badge": + if world.multiworld.CTRLogic[world.player].value >= 1 or get_difficulty(world) >= Difficulty.MODERATE: + item_type = ItemClassification.progression + elif name == "No Bonk Badge": + if get_difficulty(world) >= Difficulty.MODERATE: + item_type = ItemClassification.progression # some death wish bonuses require one hit hero + hookshot if world.is_dw() and name == "Badge Pin" and not world.is_dw_only(): diff --git a/worlds/ahit/Locations.py b/worlds/ahit/Locations.py index a817047423..954c54818f 100644 --- a/worlds/ahit/Locations.py +++ b/worlds/ahit/Locations.py @@ -170,10 +170,10 @@ ahit_locations = { "Dead Bird Studio - Red Building Top": LocData(305024, "Dead Bird Studio - Elevator Area"), "Dead Bird Studio - Behind Water Tower": LocData(305248, "Dead Bird Studio - Elevator Area"), "Dead Bird Studio - Side of House": LocData(305247, "Dead Bird Studio - Elevator Area"), - "Dead Bird Studio - DJ Grooves Sign Chest": LocData(303901, "Dead Bird Studio", hit_requirement=1), - "Dead Bird Studio - Tightrope Chest": LocData(303898, "Dead Bird Studio", hit_requirement=1), - "Dead Bird Studio - Tepee Chest": LocData(303899, "Dead Bird Studio", hit_requirement=1), - "Dead Bird Studio - Conductor Chest": LocData(303900, "Dead Bird Studio", hit_requirement=1), + "Dead Bird Studio - DJ Grooves Sign Chest": LocData(303901, "Dead Bird Studio - Post Elevator Area", hit_requirement=1), + "Dead Bird Studio - Tightrope Chest": LocData(303898, "Dead Bird Studio - Post Elevator Area", hit_requirement=1), + "Dead Bird Studio - Tepee Chest": LocData(303899, "Dead Bird Studio - Post Elevator Area", hit_requirement=1), + "Dead Bird Studio - Conductor Chest": LocData(303900, "Dead Bird Studio - Post Elevator Area", hit_requirement=1), "Murder on the Owl Express - Cafeteria": LocData(305313, "Murder on the Owl Express"), "Murder on the Owl Express - Luggage Room Top": LocData(305090, "Murder on the Owl Express"), @@ -276,11 +276,11 @@ ahit_locations = { "Queen Vanessa's Manor - Chandelier": LocData(325546, "Queen Vanessa's Manor", hit_requirement=2, paintings=1), # Alpine Skyline - "Alpine Skyline - Goat Village: Below Hookpoint": LocData(334856, "Goat Village"), - "Alpine Skyline - Goat Village: Hidden Branch": LocData(334855, "Goat Village"), + "Alpine Skyline - Goat Village: Below Hookpoint": LocData(334856, "Alpine Skyline Area (TIHS)"), + "Alpine Skyline - Goat Village: Hidden Branch": LocData(334855, "Alpine Skyline Area (TIHS)"), "Alpine Skyline - Goat Refinery": LocData(333635, "Alpine Skyline Area"), - "Alpine Skyline - Bird Pass Fork": LocData(335911, "Alpine Skyline Area"), - "Alpine Skyline - Yellow Band Hills": LocData(335756, "Alpine Skyline Area", required_hats=[HatType.BREWING]), + "Alpine Skyline - Bird Pass Fork": LocData(335911, "Alpine Skyline Area (TIHS)"), + "Alpine Skyline - Yellow Band Hills": LocData(335756, "Alpine Skyline Area (TIHS)", required_hats=[HatType.BREWING]), "Alpine Skyline - The Purrloined Village: Horned Stone": LocData(335561, "Alpine Skyline Area"), "Alpine Skyline - The Purrloined Village: Chest Reward": LocData(334831, "Alpine Skyline Area"), "Alpine Skyline - The Birdhouse: Triple Crow Chest": LocData(334758, "The Birdhouse"), @@ -295,7 +295,7 @@ ahit_locations = { "Alpine Skyline - Mystifying Time Mesa: Zipline": LocData(337058, "Alpine Skyline Area"), "Alpine Skyline - Mystifying Time Mesa: Gate Puzzle": LocData(336052, "Alpine Skyline Area"), - "Alpine Skyline - Ember Summit": LocData(336311, "Alpine Skyline Area"), + "Alpine Skyline - Ember Summit": LocData(336311, "Alpine Skyline Area (TIHS)"), "Alpine Skyline - The Lava Cake: Center Fence Cage": LocData(335448, "The Lava Cake"), "Alpine Skyline - The Lava Cake: Outer Island Chest": LocData(334291, "The Lava Cake"), "Alpine Skyline - The Lava Cake: Dweller Pillars": LocData(335417, "The Lava Cake"), @@ -304,7 +304,7 @@ ahit_locations = { "Alpine Skyline - The Twilight Bell: Wide Purple Platform": LocData(336478, "The Twilight Bell"), "Alpine Skyline - The Twilight Bell: Ice Platform": LocData(335826, "The Twilight Bell"), "Alpine Skyline - Goat Outpost Horn": LocData(334760, "Alpine Skyline Area"), - "Alpine Skyline - Windy Passage": LocData(334776, "Alpine Skyline Area"), + "Alpine Skyline - Windy Passage": LocData(334776, "Alpine Skyline Area (TIHS)"), "Alpine Skyline - The Windmill: Inside Pon Cluster": LocData(336395, "The Windmill"), "Alpine Skyline - The Windmill: Entrance": LocData(335783, "The Windmill"), "Alpine Skyline - The Windmill: Dropdown": LocData(335815, "The Windmill"), @@ -327,7 +327,8 @@ ahit_locations = { "Rock the Boat - Reception Room - Under Desk": LocData(304047, "Rock the Boat", dlc_flags=HatDLC.dlc1), "Rock the Boat - Lamp Post": LocData(304048, "Rock the Boat", dlc_flags=HatDLC.dlc1), "Rock the Boat - Iceberg Top": LocData(304046, "Rock the Boat", dlc_flags=HatDLC.dlc1), - "Rock the Boat - Post Captain Rescue": LocData(304049, "Rock the Boat", dlc_flags=HatDLC.dlc1), + "Rock the Boat - Post Captain Rescue": LocData(304049, "Rock the Boat", dlc_flags=HatDLC.dlc1, + required_hats=[HatType.ICE]), "Nyakuza Metro - Main Station Dining Area": LocData(304105, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2), "Nyakuza Metro - Top of Ramen Shop": LocData(304104, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2), @@ -350,14 +351,14 @@ ahit_locations = { } act_completions = { - "Act Completion (Time Rift - Gallery)": LocData(312758, "Time Rift - Gallery"), + "Act Completion (Time Rift - Gallery)": LocData(312758, "Time Rift - Gallery", required_hats=[HatType.BREWING]), "Act Completion (Time Rift - The Lab)": LocData(312838, "Time Rift - The Lab"), "Act Completion (Welcome to Mafia Town)": LocData(311771, "Welcome to Mafia Town"), "Act Completion (Barrel Battle)": LocData(311958, "Barrel Battle"), "Act Completion (She Came from Outer Space)": LocData(312262, "She Came from Outer Space"), "Act Completion (Down with the Mafia!)": LocData(311326, "Down with the Mafia!"), - "Act Completion (Cheating the Race)": LocData(312318, "Cheating the Race"), + "Act Completion (Cheating the Race)": LocData(312318, "Cheating the Race", required_hats=[HatType.TIME_STOP]), "Act Completion (Heating Up Mafia Town)": LocData(311481, "Heating Up Mafia Town", umbrella=True), "Act Completion (The Golden Vault)": LocData(312250, "The Golden Vault"), "Act Completion (Time Rift - Bazaar)": LocData(312465, "Time Rift - Bazaar"), @@ -404,9 +405,10 @@ act_completions = { "Act Completion (Bon Voyage!)": LocData(311520, "Bon Voyage!", dlc_flags=HatDLC.dlc1, hookshot=True), "Act Completion (Ship Shape)": LocData(311451, "Ship Shape", dlc_flags=HatDLC.dlc1), - "Act Completion (Rock the Boat)": LocData(311437, "Rock the Boat", dlc_flags=HatDLC.dlc1), + "Act Completion (Rock the Boat)": LocData(311437, "Rock the Boat", dlc_flags=HatDLC.dlc1, required_hats=[HatType.ICE]), "Act Completion (Time Rift - Balcony)": LocData(312226, "Time Rift - Balcony", dlc_flags=HatDLC.dlc1, hookshot=True), - "Act Completion (Time Rift - Deep Sea)": LocData(312434, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, hookshot=True), + "Act Completion (Time Rift - Deep Sea)": LocData(312434, "Time Rift - Deep Sea", dlc_flags=HatDLC.dlc1, + hookshot=True, required_hats=[HatType.DWELLER, HatType.ICE]), "Act Completion (Nyakuza Metro Intro)": LocData(311138, "Nyakuza Free Roam", dlc_flags=HatDLC.dlc2), @@ -422,7 +424,7 @@ act_completions = { "Act Completion (Green Clean Manhole)": LocData(311388, "Green Clean Manhole", dlc_flags=HatDLC.dlc2, - required_hats=[HatType.ICE]), + required_hats=[HatType.ICE, HatType.DWELLER]), "Act Completion (Bluefin Tunnel)": LocData(311208, "Bluefin Tunnel", dlc_flags=HatDLC.dlc2), @@ -669,16 +671,6 @@ zipline_unlocks = { "Alpine Skyline - The Twilight Path": "Zipline Unlock - The Twilight Bell Path", } -# Locations in Alpine that are available in The Illness has Spread -# Goat Village locations don't need to be put here -tihs_locations = [ - "Alpine Skyline - Bird Pass Fork", - "Alpine Skyline - Yellow Band Hills", - "Alpine Skyline - Ember Summit", - "Alpine Skyline - Goat Outpost Horn", - "Alpine Skyline - Windy Passage", -] - event_locs = { "HUMT Access": LocData(0, "Heating Up Mafia Town", act_complete_event=False), "TOD Access": LocData(0, "Toilet of Doom", act_complete_event=False), diff --git a/worlds/ahit/Options.py b/worlds/ahit/Options.py index 39501f17b9..c8eced5836 100644 --- a/worlds/ahit/Options.py +++ b/worlds/ahit/Options.py @@ -60,7 +60,7 @@ def adjust_options(world: World): world.multiworld.ShuffleStorybookPages[world.player].value = 0 world.multiworld.ShuffleActContracts[world.player].value = 0 world.multiworld.EnableDLC1[world.player].value = 0 - world.multiworld.LogicDifficulty[world.player].value = 0 + world.multiworld.LogicDifficulty[world.player].value = -1 world.multiworld.KnowledgeChecks[world.player].value = 0 world.multiworld.DWTimePieceRequirement[world.player].value = 0 @@ -118,17 +118,20 @@ class FinaleShuffle(Toggle): class LogicDifficulty(Choice): """Choose the difficulty setting for logic.""" display_name = "Logic Difficulty" - option_normal = 0 + option_normal = -1 + option_moderate = 0 option_hard = 1 option_expert = 2 - default = 0 + default = -1 -class KnowledgeChecks(Toggle): - """Put tricks into logic that are not necessarily difficult, - but require knowledge that is not obvious or commonly known. Can include glitches such as No Bonk Surfing. - This option will be forced on if logic difficulty is at least hard.""" - display_name = "Knowledge Checks" +class CTRLogic(Choice): + """Choose how you want to logically clear Cheating the Race.""" + display_name = "Cheating the Race Logic" + option_time_stop_only = 0 + option_scooter = 1 + option_sprint = 2 + option_nothing = 3 default = 0 @@ -350,12 +353,6 @@ class BadgeSellerMaxItems(Range): default = 8 -class CTRWithSprint(Toggle): - """If enabled, clearing Cheating the Race with just Sprint Hat can be in logic.""" - display_name = "Cheating the Race with Sprint Hat" - default = 0 - - class EnableDLC1(Toggle): """Shuffle content from The Arctic Cruise (Chapter 6) into the game. This also includes the Tour time rift. DO NOT ENABLE THIS OPTION IF YOU DO NOT HAVE SEAL THE DEAL DLC INSTALLED!!!""" @@ -396,7 +393,7 @@ class ExcludeTour(Toggle): class ShipShapeCustomTaskGoal(Range): """Change the amount of tasks required to complete Ship Shape. This will not affect Cruisin' for a Bruisin'.""" display_name = "Ship Shape Custom Task Goal" - range_start = 5 + range_start = 1 range_end = 30 default = 18 @@ -602,7 +599,6 @@ ahit_options: typing.Dict[str, type(Option)] = { "ShuffleAlpineZiplines": ShuffleAlpineZiplines, "FinaleShuffle": FinaleShuffle, "LogicDifficulty": LogicDifficulty, - "KnowledgeChecks": KnowledgeChecks, "YarnBalancePercent": YarnBalancePercent, "TimePieceBalancePercent": TimePieceBalancePercent, "RandomizeHatOrder": RandomizeHatOrder, @@ -613,7 +609,7 @@ ahit_options: typing.Dict[str, type(Option)] = { "ShuffleActContracts": ShuffleActContracts, "ShuffleSubconPaintings": ShuffleSubconPaintings, "StartingChapter": StartingChapter, - "CTRWithSprint": CTRWithSprint, + "CTRLogic": CTRLogic, "EnableDLC1": EnableDLC1, "Tasksanity": Tasksanity, @@ -675,7 +671,7 @@ slot_data_options: typing.Dict[str, type(Option)] = { "ActRandomizer": ActRandomizer, "ShuffleAlpineZiplines": ShuffleAlpineZiplines, "LogicDifficulty": LogicDifficulty, - "KnowledgeChecks": KnowledgeChecks, + "CTRLogic": CTRLogic, "RandomizeHatOrder": RandomizeHatOrder, "UmbrellaLogic": UmbrellaLogic, "CompassBadgeMode": CompassBadgeMode, diff --git a/worlds/ahit/Regions.py b/worlds/ahit/Regions.py index 5c7beec2b8..c3fbfe8359 100644 --- a/worlds/ahit/Regions.py +++ b/worlds/ahit/Regions.py @@ -3,7 +3,7 @@ from BaseClasses import Region, Entrance, ItemClassification, Location from .Locations import HatInTimeLocation, location_table, storybook_pages, event_locs, is_location_valid, \ shop_locations, get_tasksanity_start_id from .Items import HatInTimeItem -from .Types import ChapterIndex +from .Types import ChapterIndex, Difficulty import typing from .Rules import set_rift_rules @@ -308,9 +308,13 @@ def create_regions(world: World): create_rift_connections(w, create_region(w, "Time Rift - The Owl Express")) create_rift_connections(w, create_region(w, "Time Rift - The Moon")) - # Items near the Dead Bird Studio elevator can be reached from the basement act + # Items near the Dead Bird Studio elevator can be reached from the basement act, and beyond in Expert ev_area = create_region_and_connect(w, "Dead Bird Studio - Elevator Area", "DBS -> Elevator Area", dbs) - connect_regions(mw.get_region("Dead Bird Studio Basement", p), ev_area, "DBS Basement -> Elevator Area", p) + post_ev_area = create_region_and_connect(w, "Dead Bird Studio - Post Elevator Area", "DBS -> Post Elevator Area", dbs) + basement = mw.get_region("Dead Bird Studio Basement", p) + connect_regions(basement, ev_area, "DBS Basement -> Elevator Area", p) + if world.multiworld.LogicDifficulty[world.player].value >= int(Difficulty.EXPERT): + connect_regions(basement, post_ev_area, "DBS Basement -> Post Elevator Area", p) # ------------------------------------------- SUBCON FOREST --------------------------------------- # subcon_forest = create_region_and_connect(w, "Subcon Forest", "Telescope -> Subcon Forest", spaceship) @@ -325,7 +329,9 @@ def create_regions(world: World): alpine_skyline = create_region_and_connect(w, "Alpine Skyline", "Telescope -> Alpine Skyline", spaceship) alpine_freeroam = create_region_and_connect(w, "Alpine Free Roam", "Alpine Skyline - Free Roam", alpine_skyline) alpine_area = create_region_and_connect(w, "Alpine Skyline Area", "AFR -> Alpine Skyline Area", alpine_freeroam) - goat_village = create_region_and_connect(w, "Goat Village", "ASA -> Goat Village", alpine_area) + + # Needs to be separate because there are a lot of locations in Alpine that can't be accessed from Illness + alpine_area_tihs = create_region_and_connect(w, "Alpine Skyline Area (TIHS)", "-> Alpine Skyline Area (TIHS)", alpine_area) create_region_and_connect(w, "The Birdhouse", "-> The Birdhouse", alpine_area) create_region_and_connect(w, "The Lava Cake", "-> The Lava Cake", alpine_area) @@ -333,8 +339,7 @@ def create_regions(world: World): create_region_and_connect(w, "The Twilight Bell", "-> The Twilight Bell", alpine_area) illness = create_region_and_connect(w, "The Illness has Spread", "Alpine Skyline - Finale", alpine_skyline) - connect_regions(illness, alpine_area, "TIHS -> Alpine Skyline Area", p) - connect_regions(illness, goat_village, "TIHS -> Goat Village", p) + connect_regions(illness, alpine_area_tihs, "TIHS -> Alpine Skyline Area (TIHS)", p) create_rift_connections(w, create_region(w, "Time Rift - Alpine Skyline")) create_rift_connections(w, create_region(w, "Time Rift - The Twilight Bell")) create_rift_connections(w, create_region(w, "Time Rift - Curly Tail Trail")) @@ -373,7 +378,7 @@ def create_regions(world: World): connect_regions(mw.get_region("Dead Bird Studio", p), badge_seller, "DBS -> Badge Seller", p) connect_regions(mw.get_region("Picture Perfect", p), badge_seller, "PP -> Badge Seller", p) connect_regions(mw.get_region("Train Rush", p), badge_seller, "TR -> Badge Seller", p) - connect_regions(mw.get_region("Goat Village", p), badge_seller, "GV -> Badge Seller", p) + connect_regions(mw.get_region("Alpine Skyline Area (TIHS)", p), badge_seller, "ASA -> Badge Seller", p) times_end = create_region_and_connect(w, "Time's End", "Telescope -> Time's End", spaceship) create_region_and_connect(w, "The Finale", "Time's End - Act 1", times_end) diff --git a/worlds/ahit/Rules.py b/worlds/ahit/Rules.py index b6e3ef50bf..5123250d85 100644 --- a/worlds/ahit/Rules.py +++ b/worlds/ahit/Rules.py @@ -1,8 +1,8 @@ from worlds.AutoWorld import World, CollectionState from worlds.generic.Rules import add_rule, set_rule -from .Locations import location_table, tihs_locations, zipline_unlocks, is_location_valid, contract_locations, \ +from .Locations import location_table, zipline_unlocks, is_location_valid, contract_locations, \ shop_locations, event_locs -from .Types import HatType, ChapterIndex, hat_type_to_item +from .Types import HatType, ChapterIndex, hat_type_to_item, Difficulty from BaseClasses import Location, Entrance, Region import typing @@ -57,13 +57,9 @@ def painting_logic(world: World) -> bool: return world.multiworld.ShuffleSubconPaintings[world.player].value > 0 -def is_player_knowledgeable(world: World) -> bool: - return world.multiworld.KnowledgeChecks[world.player].value > 0 - - -# 0 = Normal, 1 = Hard, 2 = Expert -def get_difficulty(world: World) -> int: - return world.multiworld.LogicDifficulty[world.player].value +# -1 = Normal, 0 = Moderate, 1 = Hard, 2 = Expert +def get_difficulty(world: World) -> Difficulty: + return Difficulty(world.multiworld.LogicDifficulty[world.player].value) def has_paintings(state: CollectionState, world: World, count: int) -> bool: @@ -71,18 +67,17 @@ def has_paintings(state: CollectionState, world: World, count: int) -> bool: return True # Cherry Hover - if get_difficulty(world) == 2: + if get_difficulty(world) >= Difficulty.EXPERT: return True # All paintings can be skipped with No Bonk, very easily, if the player knows - if is_player_knowledgeable(world) and can_surf(state, world): + if get_difficulty(world) >= Difficulty.MODERATE and can_surf(state, world): return True paintings: int = state.count("Progressive Painting Unlock", world.player) - - if is_player_knowledgeable(world): - # Green paintings can also be skipped very easily without No Bonk - if paintings >= 1 and count == 3: + if get_difficulty(world) >= Difficulty.MODERATE: + # Green+Yellow paintings can also be skipped easily + if count == 1 or paintings >= 1 and count == 3: return True return paintings >= count @@ -96,11 +91,11 @@ def can_use_hookshot(state: CollectionState, world: World): return state.has("Hookshot Badge", world.player) -def can_hit(state: CollectionState, world: World): +def can_hit(state: CollectionState, world: World, umbrella_only: bool = False): if world.multiworld.UmbrellaLogic[world.player].value == 0: return True - return state.has("Umbrella", world.player) or can_use_hat(state, world, HatType.BREWING) + return state.has("Umbrella", world.player) or not umbrella_only and can_use_hat(state, world, HatType.BREWING) def can_surf(state: CollectionState, world: World): @@ -271,18 +266,6 @@ def set_rules(world: World): location = world.multiworld.get_location(key, world.player) - # Not all locations in Alpine can be reached from The Illness has Spread - # as many of the ziplines are blocked off - if data.region == "Alpine Skyline Area": - if key not in tihs_locations: - add_rule(location, lambda state: state.can_reach("Alpine Free Roam", "Region", world.player), "and") - else: - add_rule(location, lambda state: can_use_hookshot(state, world)) - - if data.region == "The Birdhouse" or data.region == "The Lava Cake" \ - or data.region == "The Windmill" or data.region == "The Twilight Bell": - add_rule(location, lambda state: state.can_reach("Alpine Free Roam", "Region", world.player), "and") - for hat in data.required_hats: if hat is not HatType.NONE: add_rule(location, lambda state, h=hat: can_use_hat(state, world, h)) @@ -305,11 +288,44 @@ def set_rules(world: World): for misc in data.misc_required: add_rule(location, lambda state, item=misc: state.has(item, world.player)) - if get_difficulty(world) >= 1: - world.multiworld.KnowledgeChecks[world.player].value = 1 - set_specific_rules(world) + # Putting all of this here, so it doesn't get overridden by anything + # Illness starts the player past the intro + alpine_entrance = world.multiworld.get_entrance("AFR -> Alpine Skyline Area", world.player) + add_rule(alpine_entrance, lambda state: can_use_hookshot(state, world)) + if world.multiworld.UmbrellaLogic[world.player].value > 0: + add_rule(alpine_entrance, lambda state: state.has("Umbrella", world.player)) + + if zipline_logic(world): + add_rule(world.multiworld.get_entrance("-> The Birdhouse", world.player), + lambda state: state.has("Zipline Unlock - The Birdhouse Path", world.player)) + + add_rule(world.multiworld.get_entrance("-> The Lava Cake", world.player), + lambda state: state.has("Zipline Unlock - The Lava Cake Path", world.player)) + + add_rule(world.multiworld.get_entrance("-> The Windmill", world.player), + lambda state: state.has("Zipline Unlock - The Windmill Path", world.player)) + + add_rule(world.multiworld.get_entrance("-> The Twilight Bell", world.player), + lambda state: state.has("Zipline Unlock - The Twilight Bell Path", world.player)) + + add_rule(world.multiworld.get_location("Act Completion (The Illness has Spread)", world.player), + lambda state: state.has("Zipline Unlock - The Birdhouse Path", world.player) + and state.has("Zipline Unlock - The Lava Cake Path", world.player) + and state.has("Zipline Unlock - The Windmill Path", world.player)) + + if zipline_logic(world): + for (loc, zipline) in zipline_unlocks.items(): + add_rule(world.multiworld.get_location(loc, world.player), + lambda state, z=zipline: state.has(z, world.player)) + + for loc in world.multiworld.get_region("Alpine Skyline Area (TIHS)", world.player).locations: + if "Goat Village" in loc.name: + continue + + add_rule(loc, lambda state: can_use_hookshot(state, world)) + for (key, acts) in act_connections.items(): if "Arctic Cruise" in key and not world.is_dlc1(): continue @@ -346,11 +362,6 @@ def set_rules(world: World): set_event_rules(world) - for entrance in world.multiworld.get_region("Alpine Free Roam", world.player).entrances: - add_rule(entrance, lambda state: can_use_hookshot(state, world)) - if world.multiworld.UmbrellaLogic[world.player].value > 0: - add_rule(entrance, lambda state: state.has("Umbrella", world.player)) - if world.multiworld.EndGoal[world.player].value == 1: world.multiworld.completion_condition[world.player] = lambda state: state.has("Time Piece Cluster", world.player) elif world.multiworld.EndGoal[world.player].value == 2: @@ -375,48 +386,85 @@ def set_specific_rules(world: World): if world.is_dlc2(): set_dlc2_rules(world) - difficulty: int = get_difficulty(world) - if is_player_knowledgeable(world) or difficulty >= 1: - set_knowledge_rules(world) + difficulty: Difficulty = get_difficulty(world) - if difficulty == 0: - set_normal_rules(world) + if difficulty >= Difficulty.MODERATE: + set_moderate_rules(world) - if difficulty >= 1: + if difficulty >= Difficulty.HARD: set_hard_rules(world) if difficulty >= 2: set_expert_rules(world) -def set_normal_rules(world: World): - # Hard: get to Birdhouse without Brewing Hat - add_rule(world.multiworld.get_entrance("-> The Birdhouse", world.player), - lambda state: can_use_hat(state, world, HatType.BREWING)) +def set_moderate_rules(world: World): + # Moderate: Gallery without Brewing Hat + set_rule(world.multiworld.get_location("Act Completion (Time Rift - Gallery)", world.player), lambda state: True) - add_rule(world.multiworld.get_location("Alpine Skyline - Yellow Band Hills", world.player), - lambda state: can_use_hat(state, world, HatType.BREWING)) + # Moderate: Clock Tower Chest + Ruined Tower with nothing + add_rule(world.multiworld.get_location("Mafia Town - Clock Tower Chest", world.player), lambda state: True) + add_rule(world.multiworld.get_location("Mafia Town - Top of Ruined Tower", world.player), lambda state: True) - # Hard: gallery without Brewing Hat - add_rule(world.multiworld.get_location("Act Completion (Time Rift - Gallery)", world.player), - lambda state: can_use_hat(state, world, HatType.BREWING)) + # Moderate: hitting the bell is not required to enter Subcon Well, however hookshot is still expected to clear + set_rule(world.multiworld.get_location("Subcon Well - Hookshot Badge Chest", world.player), + lambda state: has_paintings(state, world, 1)) + set_rule(world.multiworld.get_location("Subcon Well - Above Chest", world.player), + lambda state: has_paintings(state, world, 1)) + set_rule(world.multiworld.get_location("Subcon Well - Mushroom", world.player), + lambda state: has_paintings(state, world, 1)) + + # Moderate: Vanessa Manor with nothing + for loc in world.multiworld.get_region("Queen Vanessa's Manor", world.player).locations: + set_rule(loc, lambda state: True) + + set_rule(world.multiworld.get_location("Subcon Forest - Manor Rooftop", world.player), lambda state: True) + + # Moderate: get to Birdhouse/Yellow Band Hills without Brewing Hat + set_rule(world.multiworld.get_entrance("-> The Birdhouse", world.player), + lambda state: can_use_hookshot(state, world)) + set_rule(world.multiworld.get_location("Alpine Skyline - Yellow Band Hills", world.player), + lambda state: can_use_hookshot(state, world)) + + # Moderate: The Birdhouse - Dweller Platforms Relic with only Birdhouse access + set_rule(world.multiworld.get_location("Alpine Skyline - The Birdhouse: Dweller Platforms Relic", world.player), + lambda state: True) if world.is_dlc1(): - # Hard: clear Deep Sea without Dweller Mask - add_rule(world.multiworld.get_location("Act Completion (Time Rift - Deep Sea)", world.player), - lambda state: can_use_hat(state, world, HatType.DWELLER)) + # Moderate: clear Rock the Boat without Ice Hat + add_rule(world.multiworld.get_location("Rock the Boat - Post Captain Rescue", world.player), lambda state: True) + add_rule(world.multiworld.get_location("Act Completion (Rock the Boat)", world.player), lambda state: True) - # Hard: clear Rock the Boat without Ice Hat - add_rule(world.multiworld.get_location("Rock the Boat - Post Captain Rescue", world.player), - lambda state: can_use_hat(state, world, HatType.ICE)) - - add_rule(world.multiworld.get_location("Act Completion (Rock the Boat)", world.player), - lambda state: can_use_hat(state, world, HatType.ICE)) + # Moderate: clear Deep Sea without Ice Hat + set_rule(world.multiworld.get_location("Act Completion (Time Rift - Deep Sea)", world.player), + lambda state: can_use_hookshot(state, world) and can_use_hat(state, world, HatType.DWELLER)) + # There is a glitched fall damage volume near the Yellow Overpass time piece that warps the player to Pink Paw. + # Yellow Overpass time piece can also be reached without Hookshot quite easily. if world.is_dlc2(): - # Hard: clear Green Clean Manhole without Dweller Mask - add_rule(world.multiworld.get_location("Act Completion (Green Clean Manhole)", world.player), - lambda state: can_use_hat(state, world, HatType.DWELLER)) + set_rule(world.multiworld.get_entrance("-> Pink Paw Station", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Act Completion (Yellow Overpass Station)", world.player), + lambda state: True) + + set_rule(world.multiworld.get_location("Pink Paw Station - Cat Vacuum", world.player), lambda state: True) + + # The player can quite literally walk past the fan from the side without Time Stop. + set_rule(world.multiworld.get_location("Pink Paw Station - Behind Fan", world.player), lambda state: True) + + # The player can't jump back down to the manhole due to a fall damage volume preventing them from doing so + set_rule(world.multiworld.get_location("Act Completion (Pink Paw Manhole)", world.player), + lambda state: (state.has("Metro Ticket - Pink", world.player) + or state.has("Metro Ticket - Yellow", world.player) + and state.has("Metro Ticket - Blue", world.player)) + and can_use_hat(state, world, HatType.ICE)) + + # Moderate: clear Rush Hour without Hookshot + set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), + lambda state: state.has("Metro Ticket - Pink", world.player) + and state.has("Metro Ticket - Yellow", world.player) + and state.has("Metro Ticket - Blue", world.player) + and can_use_hat(state, world, HatType.ICE) + and can_use_hat(state, world, HatType.BREWING)) def set_hard_rules(world: World): @@ -425,95 +473,105 @@ def set_hard_rules(world: World): lambda state: can_use_hat(state, world, HatType.SPRINT) and state.has("Scooter Badge", world.player), "or") - # Hard: Cross Subcon boss arena gap with No Bonk + SDJ, - # allowing access to the boss arena chest, and Toilet of Doom without Hookshot + # Hard: Cross Subcon boss arena gap with No Bonk + SDJ, allowing access to the boss arena chest # Doing this in reverse from YCHE is expert logic, which expects you to cherry hover - add_rule(world.multiworld.get_location("Act Completion (Toilet of Doom)", world.player), - lambda state: can_surf(state, world) and can_sdj(state, world) and can_hit(state, world), "or") - add_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player), lambda state: can_surf(state, world) and can_sdj(state, world), "or") + set_rule(world.multiworld.get_location("Subcon Forest - Dweller Floating Rocks", world.player), + lambda state: has_paintings(state, world, 3)) + + # SDJ add_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player), lambda state: can_sdj(state, world) and has_paintings(state, world, 2), "or") - add_rule(world.multiworld.get_location("Alpine Skyline - The Birdhouse: Dweller Platforms Relic", world.player), - lambda state: can_sdj(state, world), "or") + add_rule(world.multiworld.get_location("Subcon Forest - Dweller Platforming Tree B", world.player), + lambda state: has_paintings(state, world, 3) and can_sdj(state, world), "or") add_rule(world.multiworld.get_location("Act Completion (Time Rift - Curly Tail Trail)", world.player), lambda state: can_sdj(state, world), "or") + add_rule(world.multiworld.get_location("Act Completion (The Finale)", world.player), + lambda state: can_use_hat(state, world, HatType.DWELLER) and can_sdj(state, world), "or") + + # Hard: Mystifying Time Mesa time trial without hats + set_rule(world.multiworld.get_location("Alpine Skyline - Mystifying Time Mesa: Zipline", world.player), + lambda state: can_use_hookshot(state, world)) + + if world.is_dlc1(): + # Hard: clear Deep Sea without Dweller Mask + set_rule(world.multiworld.get_location("Act Completion (Time Rift - Deep Sea)", world.player), + lambda state: can_use_hookshot(state, world)) + + if world.is_dlc2(): + # Hard: clear Green Clean Manhole without Dweller Mask + set_rule(world.multiworld.get_location("Act Completion (Green Clean Manhole)", world.player), + lambda state: can_use_hat(state, world, HatType.ICE)) + + # Hard: clear Rush Hour with Brewing Hat only + set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), + lambda state: can_use_hat(state, world, HatType.BREWING)) + def set_expert_rules(world: World): - # Expert: get to and clear Twilight Bell without Dweller Mask using SDJ. Brewing Hat required to complete act. - add_rule(world.multiworld.get_location("Alpine Skyline - The Twilight Path", world.player), - lambda state: can_sdj(state, world) - and (not zipline_logic(world) or state.has("Zipline Unlock - The Twilight Bell Path", world.player)), "or") + # Expert: Mafia Town - Above Boats with nothing + set_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player), lambda state: True) + + # Expert: Clear Dead Bird Studio with nothing + for loc in world.multiworld.get_region("Dead Bird Studio - Post Elevator Area", world.player).locations: + set_rule(loc, lambda state: True) + + set_rule(world.multiworld.get_location("Act Completion (Dead Bird Studio)", world.player), lambda state: True) + + # Expert: get to and clear Twilight Bell without Dweller Mask. + # Dweller Mask OR Sprint Hat OR Brewing Hat OR Time Stop + Umbrella required to complete act. + set_rule(world.multiworld.get_location("Alpine Skyline - The Twilight Path", world.player), lambda state: True) add_rule(world.multiworld.get_entrance("-> The Twilight Bell", world.player), - lambda state: can_sdj(state, world) and can_use_hookshot(state, world) - and (not zipline_logic(world) or state.has("Zipline Unlock - The Twilight Bell Path", world.player)), "or") + lambda state: can_use_hookshot(state, world), "or") add_rule(world.multiworld.get_location("Act Completion (The Twilight Bell)", world.player), - lambda state: can_sdj(state, world) and can_use_hat(state, world, HatType.BREWING), "or") + lambda state: can_use_hat(state, world, HatType.BREWING) + or can_use_hat(state, world, HatType.DWELLER) + or can_use_hat(state, world, HatType.SPRINT) + or (can_use_hat(state, world, HatType.TIME_STOP) and state.has("Umbrella", world.player))) - # Expert: enter and clear The Subcon Well with No Bonk Badge only + # Expert: Time Rift - Curly Tail Trail with nothing + # Time Rift - Twilight Bell and Time Rift - Village with nothing + set_rule(world.multiworld.get_location("Act Completion (Time Rift - Curly Tail Trail)", world.player), + lambda state: True) + + set_rule(world.multiworld.get_location("Act Completion (Time Rift - Village)", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Act Completion (Time Rift - The Twilight Bell)", world.player), + lambda state: True) + + # Expert: enter and clear The Subcon Well with nothing for loc in world.multiworld.get_region("The Subcon Well", world.player).locations: - add_rule(loc, lambda state: can_surf(state, world), "or") + set_rule(loc, lambda state: True) # Expert: Cherry Hovering connect_regions(world.multiworld.get_region("Your Contract has Expired", world.player), world.multiworld.get_region("Subcon Forest Area", world.player), "Subcon Forest Entrance YCHE", world.player) - set_rule(world.multiworld.get_location("Act Completion (Toilet of Doom)", world.player), - lambda state: can_hit(state, world)) - set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Subcon Forest - Noose Treehouse", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Subcon Forest - Noose Treehouse", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Subcon Forest - Dweller Platforming Tree B", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Subcon Forest - Tall Tree Hookshot Swing", world.player), lambda state: True) - # Manor hover with 1 painting unlock - for loc in world.multiworld.get_region("Queen Vanessa's Manor", world.player).locations: - set_rule(loc, lambda state: not painting_logic(world) - or state.count("Progressive Painting Unlock", world.player) >= 1) + # You can cherry hover to Snatcher's post-fight cutscene, which completes the level without having to fight him + connect_regions(world.multiworld.get_region("Subcon Forest Area", world.player), + world.multiworld.get_region("Your Contract has Expired", world.player), + "Snatcher Hover", world.player) + set_rule(world.multiworld.get_location("Act Completion (Your Contract has Expired)", world.player), + lambda state: True) - set_rule(world.multiworld.get_location("Subcon Forest - Manor Rooftop", world.player), - lambda state: not painting_logic(world) - or state.count("Progressive Painting Unlock", world.player) >= 1) - - -def set_knowledge_rules(world: World): - # Can jump down from HQ to get these - add_rule(world.multiworld.get_location("Mafia Town - Clock Tower Chest", world.player), - lambda state: state.can_reach("Act Completion (Heating Up Mafia Town)", "Location", world.player) - or state.can_reach("Cheating the Race", "Region", world.player) - or state.can_reach("The Golden Vault", "Region", world.player), "or") - - add_rule(world.multiworld.get_location("Mafia Town - Top of Ruined Tower", world.player), - lambda state: state.can_reach("Act Completion (Heating Up Mafia Town)", "Location", world.player) - or state.can_reach("Cheating the Race", "Region", world.player) - or state.can_reach("The Golden Vault", "Region", world.player), "or") - - # Dweller Mask requirement in Pink Paw can also be skipped by jumping on lamp post. - # The item behind the Time Stop fan can be walked past without Time Stop hat as well. - # (just set hookshot rule, because dweller requirement is skipped, but hookshot is still necessary) if world.is_dlc2(): - # There is a glitched fall damage volume near the Yellow Overpass time piece that warps the player to Pink Paw - add_rule(world.multiworld.get_entrance("-> Pink Paw Station", world.player), - lambda state: can_use_hookshot(state, world), "or") - - for loc in world.multiworld.get_region("Pink Paw Station", world.player).locations: - - # Can't jump back down to the manhole due to a fall damage trigger. - if loc.name == "Act Completion (Pink Paw Manhole)": - set_rule(loc, lambda state: (state.has("Metro Ticket - Pink", world.player) - or state.has("Metro Ticket - Yellow", world.player) - and state.has("Metro Ticket - Blue", world.player)) - and can_use_hat(state, world, HatType.ICE)) - - continue - - set_rule(loc, lambda state: can_use_hookshot(state, world)) + # Expert: clear Rush Hour with nothing + set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), lambda state: True) def set_mafia_town_rules(world: World): @@ -564,9 +622,16 @@ def set_mafia_town_rules(world: World): add_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player), lambda state: state.has("HUMT Access", world.player), "or") - set_rule(world.multiworld.get_location("Act Completion (Cheating the Race)", world.player), - lambda state: can_use_hat(state, world, HatType.TIME_STOP) - or world.multiworld.CTRWithSprint[world.player].value > 0 and can_use_hat(state, world, HatType.SPRINT)) + ctr_logic: int = world.multiworld.CTRLogic[world.player].value + if ctr_logic == 3: + set_rule(world.multiworld.get_location("Act Completion (Cheating the Race)", world.player), lambda state: True) + elif ctr_logic == 2: + add_rule(world.multiworld.get_location("Act Completion (Cheating the Race)", world.player), + lambda state: can_use_hat(state, world, HatType.SPRINT), "or") + elif ctr_logic == 1: + add_rule(world.multiworld.get_location("Act Completion (Cheating the Race)", world.player), + lambda state: can_use_hat(state, world, HatType.SPRINT) + and state.has("Scooter Badge", world.player), "or") def set_subcon_rules(world: World): @@ -608,14 +673,16 @@ def set_subcon_rules(world: World): def set_alps_rules(world: World): add_rule(world.multiworld.get_entrance("-> The Birdhouse", world.player), - lambda state: can_use_hookshot(state, world)) + lambda state: can_use_hookshot(state, world) and can_use_hat(state, world, HatType.BREWING)) + add_rule(world.multiworld.get_entrance("-> The Lava Cake", world.player), lambda state: can_use_hookshot(state, world)) + add_rule(world.multiworld.get_entrance("-> The Windmill", world.player), lambda state: can_use_hookshot(state, world)) add_rule(world.multiworld.get_entrance("-> The Twilight Bell", world.player), - lambda state: can_use_hat(state, world, HatType.DWELLER) and can_use_hookshot(state, world)) + lambda state: can_use_hookshot(state, world) and can_use_hat(state, world, HatType.DWELLER)) add_rule(world.multiworld.get_location("Alpine Skyline - Mystifying Time Mesa: Zipline", world.player), lambda state: can_use_hat(state, world, HatType.SPRINT) or can_use_hat(state, world, HatType.TIME_STOP)) @@ -623,28 +690,6 @@ def set_alps_rules(world: World): add_rule(world.multiworld.get_entrance("Alpine Skyline - Finale", world.player), lambda state: can_clear_alpine(state, world)) - if zipline_logic(world): - add_rule(world.multiworld.get_entrance("-> The Birdhouse", world.player), - lambda state: state.has("Zipline Unlock - The Birdhouse Path", world.player)) - - add_rule(world.multiworld.get_entrance("-> The Lava Cake", world.player), - lambda state: state.has("Zipline Unlock - The Lava Cake Path", world.player)) - - add_rule(world.multiworld.get_entrance("-> The Windmill", world.player), - lambda state: state.has("Zipline Unlock - The Windmill Path", world.player)) - - add_rule(world.multiworld.get_entrance("-> The Twilight Bell", world.player), - lambda state: state.has("Zipline Unlock - The Twilight Bell Path", world.player)) - - add_rule(world.multiworld.get_location("Act Completion (The Illness has Spread)", world.player), - lambda state: state.has("Zipline Unlock - The Birdhouse Path", world.player) - and state.has("Zipline Unlock - The Lava Cake Path", world.player) - and state.has("Zipline Unlock - The Windmill Path", world.player)) - - for (loc, zipline) in zipline_unlocks.items(): - add_rule(world.multiworld.get_location(loc, world.player), - lambda state, z=zipline: state.has(z, world.player)) - def set_dlc1_rules(world: World): add_rule(world.multiworld.get_entrance("Cruise Ship Entrance BV", world.player), diff --git a/worlds/ahit/Types.py b/worlds/ahit/Types.py index 2915512f57..a62ad581cc 100644 --- a/worlds/ahit/Types.py +++ b/worlds/ahit/Types.py @@ -28,6 +28,13 @@ class ChapterIndex(IntEnum): METRO = 7 +class Difficulty(IntEnum): + NORMAL = -1 + MODERATE = 0 + HARD = 1 + EXPERT = 2 + + hat_type_to_item = { HatType.SPRINT: "Sprint Hat", HatType.BREWING: "Brewing Hat", diff --git a/worlds/ahit/__init__.py b/worlds/ahit/__init__.py index 5b2b902770..67a9fb0816 100644 --- a/worlds/ahit/__init__.py +++ b/worlds/ahit/__init__.py @@ -98,11 +98,6 @@ class HatInTimeWorld(World): if self.multiworld.ShuffleActContracts[self.player].value == 0: for name in contract_locations.keys(): self.multiworld.get_location(name, self.player).place_locked_item(create_item(self, name)) - else: - # The bag trap contract check needs to be excluded, because if the player has the Subcon Well contract, - # the trap will not activate, locking the player out of the check permanently - self.multiworld.get_location("Snatcher's Contract - The Subcon Well", - self.player).progress_type = LocationProgressType.EXCLUDED def create_items(self): hat_yarn_costs[self.player] = {HatType.SPRINT: -1, HatType.BREWING: -1, HatType.ICE: -1, diff --git a/worlds/ahit/test/TestActs.py b/worlds/ahit/test/TestActs.py index 0b9983ca15..3c5918c0db 100644 --- a/worlds/ahit/test/TestActs.py +++ b/worlds/ahit/test/TestActs.py @@ -7,6 +7,7 @@ class TestActs(HatInTimeTestBase): "ActRandomizer": 2, "EnableDLC1": 1, "EnableDLC2": 1, + "ShuffleActContracts": 0, } def test_act_shuffle(self):