From 74715c397cbcfac1314df26542cd94aa191fa96a Mon Sep 17 00:00:00 2001 From: CookieCat Date: Sun, 5 Nov 2023 14:09:29 -0500 Subject: [PATCH] 1.3.6 --- worlds/ahit/DeathWishLocations.py | 1 + worlds/ahit/Locations.py | 4 +- worlds/ahit/Options.py | 11 +++++ worlds/ahit/Regions.py | 10 ++--- worlds/ahit/Rules.py | 67 ++++++++++++++++++++++++------- worlds/ahit/__init__.py | 6 ++- worlds/ahit/test/TestActs.py | 7 ++-- 7 files changed, 80 insertions(+), 26 deletions(-) diff --git a/worlds/ahit/DeathWishLocations.py b/worlds/ahit/DeathWishLocations.py index f51d4948ee..bc529261c1 100644 --- a/worlds/ahit/DeathWishLocations.py +++ b/worlds/ahit/DeathWishLocations.py @@ -197,6 +197,7 @@ def create_dw_regions(world: World): if i == 0: connect_regions(dw_map, dw, f"-> {name}", world.player) else: + # noinspection PyUnboundLocalVariable connect_regions(prev_dw, dw, f"{prev_dw.name} -> {name}", world.player) loc_id = death_wishes[name] diff --git a/worlds/ahit/Locations.py b/worlds/ahit/Locations.py index bf31c8cba8..2f47c2ebc0 100644 --- a/worlds/ahit/Locations.py +++ b/worlds/ahit/Locations.py @@ -283,7 +283,7 @@ ahit_locations = { # Alpine Skyline "Alpine Skyline - Goat Village: Below Hookpoint": LocData(2000334856, "Alpine Skyline Area (TIHS)"), "Alpine Skyline - Goat Village: Hidden Branch": LocData(2000334855, "Alpine Skyline Area (TIHS)"), - "Alpine Skyline - Goat Refinery": LocData(2000333635, "Alpine Skyline Area"), + "Alpine Skyline - Goat Refinery": LocData(2000333635, "Alpine Skyline Area (TIHS)"), "Alpine Skyline - Bird Pass Fork": LocData(2000335911, "Alpine Skyline Area (TIHS)"), "Alpine Skyline - Yellow Band Hills": LocData(2000335756, "Alpine Skyline Area (TIHS)", @@ -900,6 +900,8 @@ event_locs = { "HUMT Access": LocData(0, "Heating Up Mafia Town"), "TOD Access": LocData(0, "Toilet of Doom"), "YCHE Access": LocData(0, "Your Contract has Expired"), + "AFR Access": LocData(0, "Alpine Free Roam"), + "TIHS Access": LocData(0, "The Illness has Spread"), "Birdhouse Cleared": LocData(0, "The Birdhouse", act_event=True), "Lava Cake Cleared": LocData(0, "The Lava Cake", act_event=True), diff --git a/worlds/ahit/Options.py b/worlds/ahit/Options.py index f3dd2a8c66..18d93802a1 100644 --- a/worlds/ahit/Options.py +++ b/worlds/ahit/Options.py @@ -458,6 +458,15 @@ class NyakuzaThugMaxShopItems(Range): default = 4 +class NoTicketSkips(Choice): + """Prevent metro gate skips from being in logic on higher difficulties. + Rush Hour option will only consider the ticket skips for Rush Hour in logic.""" + display_name = "No Ticket Skips" + option_false = 0 + option_true = 1 + option_rush_hour = 2 + + class BaseballBat(Toggle): """Replace the Umbrella with the baseball bat from Nyakuza Metro. DLC2 content does not have to be shuffled for this option but Nyakuza Metro still needs to be installed.""" @@ -656,6 +665,7 @@ ahit_options: typing.Dict[str, type(Option)] = { "MetroMaxPonCost": MetroMaxPonCost, "NyakuzaThugMinShopItems": NyakuzaThugMinShopItems, "NyakuzaThugMaxShopItems": NyakuzaThugMaxShopItems, + "NoTicketSkips": NoTicketSkips, "LowestChapterCost": LowestChapterCost, "HighestChapterCost": HighestChapterCost, @@ -720,6 +730,7 @@ slot_data_options: typing.Dict[str, type(Option)] = { "MetroMinPonCost": MetroMinPonCost, "MetroMaxPonCost": MetroMaxPonCost, "BaseballBat": BaseballBat, + "NoTicketSkips": NoTicketSkips, "MinPonCost": MinPonCost, "MaxPonCost": MaxPonCost, diff --git a/worlds/ahit/Regions.py b/worlds/ahit/Regions.py index 807f1ee77f..0737f880be 100644 --- a/worlds/ahit/Regions.py +++ b/worlds/ahit/Regions.py @@ -309,10 +309,10 @@ def create_regions(world: World): # 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) - post_ev_area = create_region_and_connect(w, "Dead Bird Studio - Post Elevator Area", "DBS -> Post Elevator Area", dbs) + post_ev = create_region_and_connect(w, "Dead Bird Studio - Post Elevator Area", "DBS -> Post Elevator Area", dbs) 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) + connect_regions(basement, post_ev, "DBS Basement -> Post Elevator Area", p) # ------------------------------------------- SUBCON FOREST --------------------------------------- # subcon_forest = create_region_and_connect(w, "Subcon Forest", "Telescope -> Subcon Forest", spaceship) @@ -501,12 +501,12 @@ def randomize_act_entrances(world: World): region_list.append(region) for region in region_list.copy(): - if "Time Rift" in region.name: + if region.name in chapter_finales: region_list.remove(region) region_list.append(region) for region in region_list.copy(): - if region.name in chapter_finales: + if "Time Rift" in region.name: region_list.remove(region) region_list.append(region) @@ -631,8 +631,8 @@ def randomize_act_entrances(world: World): candidate = c break + # noinspection PyUnboundLocalVariable shuffled_list.append(candidate) - # print(region, candidate) # Vanilla if candidate.name == region.name: diff --git a/worlds/ahit/Rules.py b/worlds/ahit/Rules.py index 7eb09bedfc..b50a7cdf35 100644 --- a/worlds/ahit/Rules.py +++ b/worlds/ahit/Rules.py @@ -317,9 +317,24 @@ def set_rules(world: World): for loc in world.multiworld.get_region("Alpine Skyline Area (TIHS)", world.player).locations: if "Goat Village" in loc.name: continue + # This needs some special handling + if loc.name == "Alpine Skyline - Goat Refinery": + add_rule(loc, lambda state: state.has("AFR Access", world.player) + and can_use_hookshot(state, world) + and can_hit(state, world, True)) + + difficulty: Difficulty = Difficulty(world.multiworld.LogicDifficulty[world.player].value) + if difficulty >= Difficulty.MODERATE: + add_rule(loc, lambda state: state.has("TIHS Access", world.player) + and can_use_hat(state, world, HatType.SPRINT), "or") + elif difficulty >= Difficulty.HARD: + add_rule(loc, lambda state: state.has("TIHS Access", world.player, "or")) + + continue add_rule(loc, lambda state: can_use_hookshot(state, world)) + dummy_entrances: typing.List[Entrance] = [] for (key, acts) in act_connections.items(): if "Arctic Cruise" in key and not world.is_dlc1(): continue @@ -328,7 +343,7 @@ def set_rules(world: World): entrance: Entrance = world.multiworld.get_entrance(key, world.player) region: Region = entrance.connected_region access_rules: typing.List[typing.Callable[[CollectionState], bool]] = [] - entrance.parent_region.exits.remove(entrance) + dummy_entrances.append(entrance) # Entrances to this act that we have to set access_rules on entrances: typing.List[Entrance] = [] @@ -354,6 +369,9 @@ def set_rules(world: World): for rules in access_rules: add_rule(e, rules) + for e in dummy_entrances: + set_rule(e, lambda state: False) + set_event_rules(world) if world.multiworld.EndGoal[world.player].value == 1: @@ -448,13 +466,12 @@ def set_moderate_rules(world: World): # 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(): - set_rule(world.multiworld.get_entrance("-> Pink Paw Station", world.player), lambda state: True) + # No Hookshot set_rule(world.multiworld.get_location("Act Completion (Yellow Overpass Station)", world.player), lambda state: True) + # No Dweller, Hookshot, or Time Stop for these 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) # Moderate: clear Rush Hour without Hookshot @@ -465,8 +482,10 @@ def set_moderate_rules(world: World): and can_use_hat(state, world, HatType.ICE) and can_use_hat(state, world, HatType.BREWING)) - # Moderate: Bluefin Tunnel without tickets - set_rule(world.multiworld.get_entrance("-> Bluefin Tunnel", world.player), lambda state: True) + # Moderate: Bluefin Tunnel + Pink Paw Station without tickets + if world.multiworld.NoTicketSkips[world.player].value == 0: + set_rule(world.multiworld.get_entrance("-> Pink Paw Station", world.player), lambda state: True) + set_rule(world.multiworld.get_entrance("-> Bluefin Tunnel", world.player), lambda state: True) def set_hard_rules(world: World): @@ -483,6 +502,13 @@ def set_hard_rules(world: World): set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player), lambda state: has_paintings(state, world, 1, False) or state.has("YCHE Access", world.player)) + set_rule(world.multiworld.get_location("Subcon Forest - Noose Treehouse", world.player), + lambda state: has_paintings(state, world, 2, True)) + set_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player), + lambda state: has_paintings(state, world, 2, True)) + set_rule(world.multiworld.get_location("Subcon Forest - Tall Tree Hookshot Swing", world.player), + lambda state: has_paintings(state, world, 3, True)) + # 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") @@ -508,8 +534,15 @@ def set_hard_rules(world: World): 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)) + if world.multiworld.NoTicketSkips[world.player].value != 1: + set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), + lambda state: can_use_hat(state, world, HatType.BREWING)) + else: + set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), + lambda state: can_use_hat(state, world, HatType.BREWING) + and state.has("Metro Ticket - Yellow", world.player) + and state.has("Metro Ticket - Blue", world.player) + and state.has("Metro Ticket - Pink", world.player)) def set_expert_rules(world: World): @@ -517,8 +550,10 @@ def set_expert_rules(world: World): set_rule(world.multiworld.get_entrance("Telescope -> Time's End", world.player), lambda state: state.has("Time Piece", world.player, world.get_chapter_cost(ChapterIndex.FINALE))) - # Expert: Mafia Town - Above Boats with nothing + # Expert: Mafia Town - Above Boats, Top of Lighthouse, and Hot Air Balloon with nothing set_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Mafia Town - Top of Lighthouse", world.player), lambda state: True) + set_rule(world.multiworld.get_location("Mafia Town - Hot Air Balloon", 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: @@ -561,13 +596,9 @@ def set_expert_rules(world: World): # Set painting rules only. Skipping paintings is determined in has_paintings set_rule(world.multiworld.get_location("Subcon Forest - Boss Arena Chest", world.player), lambda state: has_paintings(state, world, 1, True)) - set_rule(world.multiworld.get_location("Subcon Forest - Noose Treehouse", world.player), - lambda state: has_paintings(state, world, 2, True)) - set_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player), - lambda state: has_paintings(state, world, 2, True)) set_rule(world.multiworld.get_location("Subcon Forest - Dweller Platforming Tree B", world.player), lambda state: has_paintings(state, world, 3, True)) - set_rule(world.multiworld.get_location("Subcon Forest - Tall Tree Hookshot Swing", world.player), + set_rule(world.multiworld.get_location("Subcon Forest - Magnet Badge Bush", world.player), lambda state: has_paintings(state, world, 3, True)) # You can cherry hover to Snatcher's post-fight cutscene, which completes the level without having to fight him @@ -579,7 +610,13 @@ def set_expert_rules(world: World): if world.is_dlc2(): # Expert: clear Rush Hour with nothing - set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), lambda state: True) + if world.multiworld.NoTicketSkips[world.player].value == 0: + set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), lambda state: True) + else: + set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), + lambda state: state.has("Metro Ticket - Yellow", world.player) + and state.has("Metro Ticket - Blue", world.player) + and state.has("Metro Ticket - Pink", world.player)) def set_mafia_town_rules(world: World): diff --git a/worlds/ahit/__init__.py b/worlds/ahit/__init__.py index 0ed14c6376..805dc57898 100644 --- a/worlds/ahit/__init__.py +++ b/worlds/ahit/__init__.py @@ -1,7 +1,8 @@ from BaseClasses import Item, ItemClassification, Tutorial from .Items import item_table, create_item, relic_groups, act_contracts, create_itempool from .Regions import create_regions, randomize_act_entrances, chapter_act_info, create_events, get_shuffled_region -from .Locations import location_table, contract_locations, is_location_valid, get_location_names, TASKSANITY_START_ID +from .Locations import location_table, contract_locations, is_location_valid, get_location_names, TASKSANITY_START_ID, \ + get_total_locations from .Rules import set_rules from .Options import ahit_options, slot_data_options, adjust_options from .Types import HatType, ChapterIndex, HatInTimeItem @@ -173,7 +174,8 @@ class HatInTimeWorld(World): "Chapter7Cost": chapter_timepiece_costs[self.player][ChapterIndex.METRO], "BadgeSellerItemCount": badge_seller_count[self.player], "SeedNumber": str(self.multiworld.seed), # For shop prices - "SeedName": self.multiworld.seed_name} + "SeedName": self.multiworld.seed_name, + "TotalLocations": get_total_locations(self)} if self.multiworld.HatItems[self.player].value == 0: slot_data.setdefault("SprintYarnCost", hat_yarn_costs[self.player][HatType.SPRINT]) diff --git a/worlds/ahit/test/TestActs.py b/worlds/ahit/test/TestActs.py index 7c2b9783e6..da3d5f5c0c 100644 --- a/worlds/ahit/test/TestActs.py +++ b/worlds/ahit/test/TestActs.py @@ -1,4 +1,5 @@ from worlds.ahit.Regions import act_chapters +from worlds.ahit.Rules import act_connections from worlds.ahit.test.TestBase import HatInTimeTestBase @@ -6,9 +7,6 @@ class TestActs(HatInTimeTestBase): def run_default_tests(self) -> bool: return False - def testAllStateCanReachEverything(self): - pass - options = { "ActRandomizer": 2, "EnableDLC1": 1, @@ -24,6 +22,9 @@ class TestActs(HatInTimeTestBase): for name in act_chapters.keys(): region = self.multiworld.get_region(name, 1) for entrance in region.entrances: + if entrance.name in act_connections.keys(): + continue + self.assertTrue(self.can_reach_entrance(entrance.name), f"Can't reach {name} from {entrance}\n" f"{entrance.parent_region.entrances[0]} -> {entrance.parent_region} "